diff options
Diffstat (limited to 'sourcevr')
| -rw-r--r-- | sourcevr/cbase.h | 12 | ||||
| -rw-r--r-- | sourcevr/sourcevirtualreality.cpp | 857 | ||||
| -rw-r--r-- | sourcevr/sourcevirtualreality.h | 134 | ||||
| -rw-r--r-- | sourcevr/sourcevr.vpc | 72 | ||||
| -rw-r--r-- | sourcevr/stdafx.cpp | 2 |
5 files changed, 1077 insertions, 0 deletions
diff --git a/sourcevr/cbase.h b/sourcevr/cbase.h new file mode 100644 index 0000000..ecf83a4 --- /dev/null +++ b/sourcevr/cbase.h @@ -0,0 +1,12 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include "platform.h" + +#include <vector> +#include <math.h> +#include <assert.h> +#include <float.h> + +// Prevent OpenCV and other Vortex modules from using D3D (they will want D3D11, and we're a D3D9 game) +#define NO_D3D + diff --git a/sourcevr/sourcevirtualreality.cpp b/sourcevr/sourcevirtualreality.cpp new file mode 100644 index 0000000..de7180c --- /dev/null +++ b/sourcevr/sourcevirtualreality.cpp @@ -0,0 +1,857 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" + +#include "sourcevirtualreality.h" +#include "icommandline.h" +#include "filesystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterialvar.h" +#include "renderparm.h" +#include "openvr/openvr.h" + +using namespace vr; + +CSourceVirtualReality g_SourceVirtualReality; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSourceVirtualReality, ISourceVirtualReality, + SOURCE_VIRTUAL_REALITY_INTERFACE_VERSION, g_SourceVirtualReality ); + +static VMatrix VMatrixFrom44(const float v[4][4]); +static VMatrix VMatrixFrom34(const float v[3][4]); +static VMatrix OpenVRToSourceCoordinateSystem(const VMatrix& vortex); + +// -------------------------------------------------------------------- +// Purpose: Set the current HMD pose as the zero pose +// -------------------------------------------------------------------- +void CC_VR_Reset_Home_Pos( const CCommand& args ) +{ + g_SourceVirtualReality.AcquireNewZeroPose(); +} +static ConCommand vr_reset_home_pos("vr_reset_home_pos", CC_VR_Reset_Home_Pos, "Sets the current HMD position as the zero point" ); + + +// -------------------------------------------------------------------- +// Purpose: Reinitialize the IHeadtrack object +// -------------------------------------------------------------------- +void CC_VR_Track_Reinit( const CCommand& args ) +{ + if( g_SourceVirtualReality.ResetTracking() ) + { + // Tracker can't be restarted: show a message, but don't quit. + Warning("Can't reset HMD tracker"); + } +} +static ConCommand vr_track_reinit("vr_track_reinit", CC_VR_Track_Reinit, "Reinitializes HMD tracking" ); + + +// Disable distortion processing altogether. +ConVar vr_distortion_enable ( "vr_distortion_enable", "1" ); + +// Disable distortion by changing the distortion texture itself, so that the rendering path is otherwise identical. +// This won't take effect until the texture is refresed. +ConVar vr_debug_nodistortion ( "vr_debug_nodistortion", "0" ); + +// Disable just the chromatic aberration correction in the distortion texture, to make undistort quality/artifacts +// easier to see and debug. As above, won't take effect until the texture is refreshed. +ConVar vr_debug_nochromatic ( "vr_debug_nochromatic", "0" ); + +// Resolution of the undistort map. +static const int distortionTextureSize = 128; + + +void CC_vr_refresh_distortion_texture( const CCommand& args ) +{ + g_SourceVirtualReality.RefreshDistortionTexture(); +} +ConCommand vr_refresh_distortion_texture( "vr_refresh_distortion_texture", CC_vr_refresh_distortion_texture ); + +ConVar vr_use_offscreen_render_target( "vr_use_offscreen_render_target", "0", 0, "Experimental: Use larger offscreen render target for pre-distorted scene in VR" ); + +// -------------------------------------------------------------------- +// construction/destruction +// -------------------------------------------------------------------- +CSourceVirtualReality::CSourceVirtualReality() + : m_textureGeneratorLeft( vr::Eye_Left ), + m_textureGeneratorRight( vr::Eye_Right ) +{ + m_bActive = false; + m_bUsingOffscreenRenderTarget = false; + m_pHmd = NULL; +} + +CSourceVirtualReality::~CSourceVirtualReality() +{ +} + + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +bool CSourceVirtualReality::Connect( CreateInterfaceFn factory ) +{ + if ( !factory ) + return false; + + if ( !BaseClass::Connect( factory ) ) + return false; + + if ( !g_pFullFileSystem ) + { + Warning( "The head tracker requires the filesystem to run!\n" ); + return false; + } + + return true; +} + + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +void CSourceVirtualReality::Disconnect() +{ + BaseClass::Disconnect(); +} + + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +void * CSourceVirtualReality::QueryInterface( const char *pInterfaceName ) +{ + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary + return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. +} + + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +InitReturnVal_t CSourceVirtualReality::Init() +{ + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + + // if our tracker expects to use the texture base distortion shader, + // make the procedural textures for that shader now + m_pDistortionTextureLeft.Init( materials->CreateProceduralTexture( "vr_distort_map_left", TEXTURE_GROUP_PIXEL_SHADERS, + distortionTextureSize, distortionTextureSize, IMAGE_FORMAT_RGBA16161616, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT ) ); + m_pDistortionTextureRight.Init( materials->CreateProceduralTexture( "vr_distort_map_right", TEXTURE_GROUP_PIXEL_SHADERS, + distortionTextureSize, distortionTextureSize, IMAGE_FORMAT_RGBA16161616, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT ) ); + m_pDistortionTextureLeft->SetTextureRegenerator( &m_textureGeneratorLeft ); + m_pDistortionTextureRight->SetTextureRegenerator( &m_textureGeneratorRight ); + + return INIT_OK; +} + +void CSourceVirtualReality::RefreshDistortionTexture() +{ + m_pDistortionTextureLeft->Download(); + m_pDistortionTextureRight->Download(); +} + + +void CDistortionTextureRegen::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) +{ + // only do this if we have an HMD + if( !g_SourceVirtualReality.GetHmd() ) + return; + + unsigned short *imageData = (unsigned short*) pVTFTexture->ImageData( 0, 0, 0 ); + enum ImageFormat imageFormat = pVTFTexture->Format(); + if( imageFormat != IMAGE_FORMAT_RGBA16161616 ) + { + return; + } + + + // we use different UVs for the full FB source texture + float fUScale; + float fUOffset; + if( g_SourceVirtualReality.UsingOffscreenRenderTarget() ) + { + fUScale = 1.f; + fUOffset = 0.f; + } + else + { + fUScale = 0.5f; + fUOffset = m_eEye == Eye_Left ? 0.f : 0.5f; + } + + // optimize + int width = pVTFTexture->Width(); + int height = pVTFTexture->Height(); + float fHeight = height; + float fWidth = width; + int x, y; + for( y = 0; y < height; y++ ) + { + for( x = 0; x < width; x++ ) + { + int offset = 4 * ( x + y * width ); + assert( offset < width * height * 4 ); + + float u = ( (float)x + 0.5f) / fWidth; + float v = ( (float)y + 0.5f) / fHeight; + + DistortionCoordinates_t coords = g_SourceVirtualReality.GetHmd()->ComputeDistortion( m_eEye, u, v ); + + coords.rfRed[0] = Clamp( coords.rfRed[0], 0.f, 1.f ) * fUScale + fUOffset; + coords.rfGreen[0] = Clamp( coords.rfGreen[0], 0.f, 1.f ) * fUScale + fUOffset; + coords.rfBlue[0] = Clamp( coords.rfBlue[0], 0.f, 1.f ) * fUScale + fUOffset; + + if ( vr_debug_nodistortion.GetBool() ) + { + coords.rfRed[0] = coords.rfGreen[0] = coords.rfBlue[0] = u * fUScale + fUOffset; + coords.rfRed[1] = coords.rfGreen[1] = coords.rfBlue[1] = v; + } + + if ( vr_debug_nochromatic.GetBool() ) + { + coords.rfRed[0] = coords.rfBlue[0] = coords.rfGreen[0]; + coords.rfRed[1] = coords.rfBlue[1] = coords.rfGreen[1]; + } + + imageData[offset + 0] = (unsigned short)(Clamp( coords.rfRed[0], 0.f, 1.f ) * 65535.f ); + imageData[offset + 1] = (unsigned short)(Clamp( coords.rfRed[1], 0.f, 1.f ) * 65535.f ); + imageData[offset + 2] = (unsigned short)(Clamp( coords.rfBlue[0], 0.f, 1.f ) * 65535.f ); + imageData[offset + 3] = (unsigned short)(Clamp( coords.rfBlue[1], 0.f, 1.f ) * 65535.f ); + } + } +} + + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +void CSourceVirtualReality::Shutdown() +{ + BaseClass::Shutdown(); + + if( m_pHmd ) + VR_Shutdown(); + + m_pDistortionTextureLeft.Shutdown(); + m_pDistortionTextureRight.Shutdown(); +} + + +// -------------------------------------------------------------------- +// Purpose: Let the caller know if we're in VR mode +// -------------------------------------------------------------------- +bool CSourceVirtualReality::ShouldRunInVR() +{ + return m_bActive && m_pHmd; +} + + +// -------------------------------------------------------------------- +// Purpose: Returns true if there's an Hmd connected and everything +// started up. +// -------------------------------------------------------------------- +bool CSourceVirtualReality::IsHmdConnected() +{ + // we really just care if OpenVR init was successful + return EnsureOpenVRInited(); +} + + +// -------------------------------------------------------------------- +// Purpose: Let the caller know how big to make the window and where +// to put it. +// -------------------------------------------------------------------- +bool CSourceVirtualReality::GetDisplayBounds( VRRect_t *pRect ) +{ + if( m_pHmd ) + { + int32_t x, y; + uint32_t width, height; + m_pHmd->GetWindowBounds( &x, &y, &width, &height ); + pRect->nX = x; + pRect->nY = y; + pRect->nWidth = width; + pRect->nHeight = height; + return true; + } + else + { + return false; + } +} + +// -------------------------------------------------------------------- +// Purpose: Allocates the pre-distortion render targets. +// -------------------------------------------------------------------- +void CSourceVirtualReality::CreateRenderTargets( IMaterialSystem *pMaterialSystem ) +{ + if( !m_pHmd || !m_bActive ) + return; + + g_StereoGuiTexture.Init( materials->CreateNamedRenderTargetTextureEx2( + "_rt_gui", + 640, 480, RT_SIZE_OFFSCREEN, + materials->GetBackBufferFormat(), + MATERIAL_RT_DEPTH_SHARED, + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT, + CREATERENDERTARGETFLAGS_HDR ) + ); + + if( UsingOffscreenRenderTarget() ) + { + uint32_t nWidth, nHeight; + m_pHmd->GetRecommendedRenderTargetSize( &nWidth, &nHeight ); + + m_pPredistortRT.Init( pMaterialSystem->CreateNamedRenderTargetTextureEx2( + "_rt_vr_predistort", + nWidth, nHeight, RT_SIZE_LITERAL, + IMAGE_FORMAT_RGBA8888, + MATERIAL_RT_DEPTH_SEPARATE, + TEXTUREFLAGS_RENDERTARGET |TEXTUREFLAGS_NOMIP/*TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT */, + 0 ) ); + + //TODO: Figure out what I really want for the depth texture format + m_pPredistortRTDepth.Init( pMaterialSystem->CreateNamedRenderTargetTextureEx2( "_rt_vr_predistort_depth", nWidth, nHeight, + RT_SIZE_LITERAL, IMAGE_FORMAT_NV_DST24, MATERIAL_RT_DEPTH_NONE, + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT |TEXTUREFLAGS_NOMIP, + 0 ) ); + } + +} + +void CSourceVirtualReality::ShutdownRenderTargets() +{ + g_StereoGuiTexture.Shutdown(); + m_pPredistortRT.Shutdown(); + m_pPredistortRTDepth.Shutdown(); +} + + +// Returns the (possibly overridden) framebuffer size for render target sizing. +void CSourceVirtualReality::GetRenderTargetFrameBufferDimensions( int & nWidth, int & nHeight ) +{ + if( m_pHmd && UsingOffscreenRenderTarget() ) + { + + uint32_t w, h; + m_pHmd->GetRecommendedRenderTargetSize( &w, &h ); + nWidth = w; + nHeight = h; + } + else + { + // this will cause material system to fall back to the + // actual size of the frame buffer + nWidth = nHeight = 0; + } +} + + +// -------------------------------------------------------------------- +// Purpose: fetches the render target for the specified eye +// -------------------------------------------------------------------- +ITexture *CSourceVirtualReality::GetRenderTarget( ISourceVirtualReality::VREye eEye, ISourceVirtualReality::EWhichRenderTarget eWhich ) +{ + // we don't use any render targets if distortion is disabled + // Just let the game render to the frame buffer. + if( !vr_distortion_enable.GetBool() ) + return NULL; + + if( !m_bActive || !m_pHmd ) + return NULL; + + if( !UsingOffscreenRenderTarget() ) + return NULL; + + switch( eWhich ) + { + case ISourceVirtualReality::RT_Color: + return m_pPredistortRT; + + case ISourceVirtualReality::RT_Depth: + return m_pPredistortRTDepth; + } + return NULL; +} + +vr::Hmd_Eye SourceEyeToHmdEye( ISourceVirtualReality::VREye eEye ) +{ + if( eEye == ISourceVirtualReality::VREye_Left ) + return vr::Eye_Left; + else + return vr::Eye_Right; +} + + +// -------------------------------------------------------------------- +// Purpose: Let the caller know if we're in VR mode +// -------------------------------------------------------------------- +void CSourceVirtualReality::GetViewportBounds( VREye eEye, int *pnX, int *pnY, int *pnWidth, int *pnHeight ) +{ + if( !m_pHmd || !m_bActive ) + { + *pnWidth = 0; + *pnHeight = 0; + return; + } + + // if there are textures, use those + if( m_pPredistortRT && vr_distortion_enable.GetBool() ) + { + if( pnX && pnY ) + { + *pnX = 0; + *pnY = 0; + } + *pnWidth = m_pPredistortRT->GetActualWidth(); + *pnHeight = m_pPredistortRT->GetActualHeight(); + } + else + { + uint32_t x, y, w, h; + m_pHmd->GetEyeOutputViewport( SourceEyeToHmdEye( eEye ), &x, &y, &w, &h ); + if( pnX && pnY ) + { + *pnX = x; + *pnY = y; + } + *pnWidth = w; + *pnHeight = h; + } +} + + +// -------------------------------------------------------------------- +// Purpose: Returns the current pose +// -------------------------------------------------------------------- +VMatrix CSourceVirtualReality::GetMideyePose() +{ + return m_ZeroFromHeadPose; +} + +// ---------------------------------------------------------------------- +// Purpose: Create a 4x4 projection transform from eye projection and distortion parameters +// ---------------------------------------------------------------------- +inline static void ComposeProjectionTransform(float fLeft, float fRight, float fTop, float fBottom, float zNear, float zFar, float fovScale, VMatrix *pmProj ) +{ + if( fovScale != 1.0f && fovScale > 0.f ) + { + float fFovScaleAdjusted = tan( atan( fTop ) / fovScale ) / fTop; + fRight *= fFovScaleAdjusted; + fLeft *= fFovScaleAdjusted; + fTop *= fFovScaleAdjusted; + fBottom *= fFovScaleAdjusted; + } + + float idx = 1.0f / (fRight - fLeft); + float idy = 1.0f / (fBottom - fTop); + float idz = 1.0f / (zFar - zNear); + float sx = fRight + fLeft; + float sy = fBottom + fTop; + + float (*p)[4] = pmProj->m; + p[0][0] = 2*idx; p[0][1] = 0; p[0][2] = sx*idx; p[0][3] = 0; + p[1][0] = 0; p[1][1] = 2*idy; p[1][2] = sy*idy; p[1][3] = 0; + p[2][0] = 0; p[2][1] = 0; p[2][2] = -zFar*idz; p[2][3] = -zFar*zNear*idz; + p[3][0] = 0; p[3][1] = 0; p[3][2] = -1.0f; p[3][3] = 0; +} + + +// ---------------------------------------------------------------------- +// Purpose: Computes and returns the projection matrix for the eye +// ---------------------------------------------------------------------- +bool CSourceVirtualReality::GetEyeProjectionMatrix ( VMatrix *pResult, VREye eEye, float zNear, float zFar, float fovScale ) +{ + Assert ( pResult != NULL ); + if( !pResult || !m_pHmd || !m_bActive ) + return false; + + float fLeft, fRight, fTop, fBottom; + m_pHmd->GetProjectionRaw( SourceEyeToHmdEye( eEye ), &fLeft, &fRight, &fTop, &fBottom ); + + ComposeProjectionTransform( fLeft, fRight, fTop, fBottom, zNear, zFar, fovScale, pResult ); + return true; +} + + +// ---------------------------------------------------------------------- +// Purpose: Returns the mid eye from left/right eye part of the view +// matrix transform chain. +// ---------------------------------------------------------------------- +VMatrix CSourceVirtualReality::GetMidEyeFromEye( VREye eEye ) +{ + if( m_pHmd ) + { + vr::HmdMatrix34_t matMidEyeFromEye = m_pHmd->GetEyeToHeadTransform( SourceEyeToHmdEye( eEye ) ); + return OpenVRToSourceCoordinateSystem( VMatrixFrom34( matMidEyeFromEye.m ) ); + } + else + { + VMatrix mat; + mat.Identity(); + return mat; + } +} + +// returns the adapter index to use for VR mode +int CSourceVirtualReality::GetVRModeAdapter() +{ + if( EnsureOpenVRInited() ) + { + Assert( m_pHmd ); + return m_pHmd->GetD3D9AdapterIndex(); + } + else + { + return -1; + } +} + +bool CSourceVirtualReality::WillDriftInYaw() +{ + if( m_pHmd ) + return m_pHmd->GetBoolTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, Prop_WillDriftInYaw_Bool ); + else + return false; +} + +void CSourceVirtualReality::AcquireNewZeroPose() +{ + // just let the next tracker update re-zero us + if( m_pHmd ) + m_pHmd->ResetSeatedZeroPose(); +} + +bool CSourceVirtualReality::SampleTrackingState ( float PlayerGameFov, float fPredictionSeconds ) +{ + if( !m_pHmd || !m_bActive ) + return false; + + // If tracker can't return a pose (it's possibly recalibrating itself) + // then we will freeze tracking at its current state, rather than + // snapping it back to the zero position + vr::TrackedDevicePose_t pose; + + if ( m_pHmd->IsTrackedDeviceConnected( k_unTrackedDeviceIndex_Hmd ) ) + { + float fSecondsSinceLastVsync; + m_pHmd->GetTimeSinceLastVsync( &fSecondsSinceLastVsync, NULL ); + + float fFrameDuration = 1.f / m_pHmd->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, + vr::Prop_DisplayFrequency_Float ); + float fPredictedSecondsFromNow = fFrameDuration - fSecondsSinceLastVsync \ + + m_pHmd->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, + vr::Prop_SecondsFromVsyncToPhotons_Float ); + + // Use Seated here because everything using this interface or older is expecting a seated experience + m_pHmd->GetDeviceToAbsoluteTrackingPose( vr::TrackingUniverseSeated, fPredictedSecondsFromNow, &pose, 1 ); + + m_bHaveValidPose = pose.bPoseIsValid; + } + else + { + m_bHaveValidPose = false; + } + + if( !m_bHaveValidPose ) + return false; + + m_ZeroFromHeadPose = OpenVRToSourceCoordinateSystem( VMatrixFrom34( pose.mDeviceToAbsoluteTracking.m ) ); + + return true; +} + + +// ---------------------------------------------------------------------- +// Purpose: Performs the distortion required for the HMD display +// ---------------------------------------------------------------------- +bool CSourceVirtualReality::DoDistortionProcessing ( VREye eEye ) +{ + if( !ShouldRunInVR() ) + return false; + if ( !vr_distortion_enable.GetBool() ) + { + return false; + } + + CMatRenderContextPtr pRenderContext( materials ); + + IMaterial *pDistortMaterial; + if( eEye == VREye_Left ) + pDistortMaterial = m_DistortLeftMaterial; + else + pDistortMaterial = m_DistortRightMaterial; + + if( !UsingOffscreenRenderTarget() ) + { + // copy the frame buffer to the source texture + ITexture *pFullFrameFB1 = materials->FindTexture( "_rt_FullFrameFB1", TEXTURE_GROUP_RENDER_TARGET ); + if( !pFullFrameFB1 ) + return false; + + Rect_t r; + this->GetViewportBounds( eEye, &r.x, &r.y, &r.width, &r.height ); + pRenderContext->CopyRenderTargetToTextureEx( pFullFrameFB1, 0, &r, &r ); + } + + // This is where we are rendering to + uint32_t x, y, w, h; + m_pHmd->GetEyeOutputViewport( SourceEyeToHmdEye( eEye ), &x, &y, &w, &h ); + + pRenderContext->DrawScreenSpaceRectangle ( pDistortMaterial, + x, y, w, h, + 0, 0, distortionTextureSize-1,distortionTextureSize-1,distortionTextureSize,distortionTextureSize); + + return true; +} + + +// -------------------------------------------------------------------- +// Pastes the HUD directly onto the backbuffer / render target, including +// applying the undistort. +// -------------------------------------------------------------------- +bool CSourceVirtualReality::CompositeHud ( VREye eEye, float ndcHudBounds[4], bool bDoUndistort, bool bBlackout, bool bTranslucent ) +{ + // run away if we're not doing VR at all + if ( ! ShouldRunInVR() ) + return false; + + bDoUndistort = bDoUndistort && vr_distortion_enable.GetBool(); + + IMaterial *pDistortHUDMaterial = ( eEye == VREye_Left ) ? m_DistortHUDLeftMaterial : m_DistortHUDRightMaterial; + + // The translucency flag will enable/disable both blending and alpha test. The only case where we don't want them enabled + // is when we're blacking out the entire screen (we use blending to smooth the edges of the HUD, and we use alpha test to kill + // the pixels outside the HUD). Note that right now I'm not expecting to see a mode with bTranslucent and bBlackout + // both true (maybe happens in sniper mode?). + pDistortHUDMaterial->SetMaterialVarFlag( MATERIAL_VAR_TRANSLUCENT, ! bBlackout ); + + // The ndcHudBounds are the min x, min y, max x, max y of where we want to paste the HUD texture in NDC coordinates + // of the main 3D view. We conver to UV (0->1) space here for the shader. + float huduvs[4]; + huduvs[0] = ndcHudBounds[0] * 0.5 + 0.5; + huduvs[1] = ndcHudBounds[1] * 0.5 + 0.5; + huduvs[2] = ndcHudBounds[2] * 0.5 + 0.5; + huduvs[3] = ndcHudBounds[3] * 0.5 + 0.5; + + // Fix up coordinates depending on whether we're rendering to a buffer sized for one eye or two. + // (note that disabling distortion also disables use of the offscreen render target) + if ( vr_distortion_enable.GetBool() && ! UsingOffscreenRenderTarget() ) + { + huduvs[0] *= 0.5; + huduvs[2] *= 0.5; + if ( eEye == VREye_Right ) + { + huduvs[0] += 0.5; + huduvs[2] += 0.5; + } + } + + IMaterialVar *pVar; + + pVar = pDistortHUDMaterial->FindVar( "$distortbounds", NULL ); + if ( pVar ) + { + pVar->SetVecValue( huduvs, 4 ); + } + + pVar = pDistortHUDMaterial->FindVar( "$hudtranslucent", NULL ); + if ( pVar ) + { + pVar->SetIntValue( bTranslucent ); + } + + pVar = pDistortHUDMaterial->FindVar( "$hudundistort", NULL ); + if ( pVar ) + { + pVar->SetIntValue( bDoUndistort ); + } + + CMatRenderContextPtr pRenderContext( materials ); + + uint32_t x, y, w, h; + m_pHmd->GetEyeOutputViewport( SourceEyeToHmdEye( eEye ), &x, &y, &w, &h ); + + pRenderContext->DrawScreenSpaceRectangle ( pDistortHUDMaterial, + x, y, w, h, + 0, 0, distortionTextureSize-1,distortionTextureSize-1,distortionTextureSize,distortionTextureSize); + + return true; +} + + +bool CSourceVirtualReality::EnsureOpenVRInited() +{ + if( m_pHmd ) + return true; + + return StartTracker(); +} + +bool CSourceVirtualReality::StartTracker() +{ + Assert( m_pHmd == NULL ); + + // Initialize SteamVR + vr::HmdError err; + m_pHmd = vr::VR_Init( &err ); + if( err != HmdError_None ) + { + Msg( "Unable to initialize HMD tracker. Error code %d\n", err ); + return false; + } + + m_pHmd->ResetSeatedZeroPose(); + + m_bHaveValidPose = false; + m_ZeroFromHeadPose.Identity(); + + return true; +} + +void CSourceVirtualReality::StopTracker() +{ + if ( m_pHmd ) + { + VR_Shutdown(); + m_pHmd = NULL; + } +} + +bool CSourceVirtualReality::ResetTracking() +{ + StopTracker(); + return StartTracker(); +} + + +bool CSourceVirtualReality::Activate() +{ + // init the HMD itself + if( !ResetTracking() ) + return false; + + m_bActive = true; + m_bUsingOffscreenRenderTarget = vr_use_offscreen_render_target.GetBool(); + + m_warpMaterial.Init( "dev/warp", "Other" ); + if( UsingOffscreenRenderTarget() ) + { + m_DistortLeftMaterial.Init( "vr/vr_distort_texture_left", "Other" ); + m_DistortRightMaterial.Init( "vr/vr_distort_texture_right", "Other" ); + } + else + { + m_DistortLeftMaterial.Init( "vr/vr_distort_texture_left_nort", "Other" ); + m_DistortRightMaterial.Init( "vr/vr_distort_texture_right_nort", "Other" ); + } + m_InWorldUIMaterial.Init( "vgui/inworldui", "Other" ); + m_InWorldUIOpaqueMaterial.Init( "vgui/inworldui_opaque", "Other" ); + m_blackMaterial.Init( "vgui/black", "Other" ); + + m_DistortHUDLeftMaterial.Init( "vr/vr_distort_hud_left", "Other" ); + m_DistortHUDRightMaterial.Init( "vr/vr_distort_hud_right", "Other" ); + + RefreshDistortionTexture(); + return true; +} + + +void CSourceVirtualReality::Deactivate() +{ + m_bActive = false; + m_bShouldForceVRMode = false; + + m_warpMaterial.Shutdown(); + m_DistortLeftMaterial.Shutdown(); + m_DistortRightMaterial.Shutdown(); + m_DistortHUDLeftMaterial.Shutdown(); + m_DistortHUDRightMaterial.Shutdown(); + m_InWorldUIMaterial.Shutdown(); + m_InWorldUIOpaqueMaterial.Shutdown(); + m_blackMaterial.Shutdown(); +} + +bool CSourceVirtualReality::ShouldForceVRMode() +{ + return m_bShouldForceVRMode; +} + +void CSourceVirtualReality::SetShouldForceVRMode() +{ + m_bShouldForceVRMode = true; +} + +static VMatrix OpenVRToSourceCoordinateSystem(const VMatrix& vortex) +{ + const float inchesPerMeter = (float)(39.3700787); + + // From Vortex: X=right, Y=up, Z=backwards, scale is meters. + // To Source: X=forwards, Y=left, Z=up, scale is inches. + // + // s_from_v = [ 0 0 -1 0 + // -1 0 0 0 + // 0 1 0 0 + // 0 0 0 1]; + // + // We want to compute vmatrix = s_from_v * vortex * v_from_s; v_from_s = s_from_v' + // Given vortex = + // [00 01 02 03 + // 10 11 12 13 + // 20 21 22 23 + // 30 31 32 33] + // + // s_from_v * vortex * s_from_v' = + // 22 20 -21 -23 + // 02 00 -01 -03 + // -12 -10 11 13 + // -32 -30 31 33 + // + const vec_t (*v)[4] = vortex.m; + VMatrix result( + v[2][2], v[2][0], -v[2][1], -v[2][3] * inchesPerMeter, + v[0][2], v[0][0], -v[0][1], -v[0][3] * inchesPerMeter, + -v[1][2], -v[1][0], v[1][1], v[1][3] * inchesPerMeter, + -v[3][2], -v[3][0], v[3][1], v[3][3]); + + return result; +} + +static VMatrix VMatrixFrom44(const float v[4][4]) +{ + return VMatrix( + v[0][0], v[0][1], v[0][2], v[0][3], + v[1][0], v[1][1], v[1][2], v[1][3], + v[2][0], v[2][1], v[2][2], v[2][3], + v[3][0], v[3][1], v[3][2], v[3][3]); +} + +static VMatrix VMatrixFrom34(const float v[3][4]) +{ + return VMatrix( + v[0][0], v[0][1], v[0][2], v[0][3], + v[1][0], v[1][1], v[1][2], v[1][3], + v[2][0], v[2][1], v[2][2], v[2][3], + 0, 0, 0, 1 ); +} + +static VMatrix VMatrixFrom33(const float v[3][3]) +{ + return VMatrix( + v[0][0], v[0][1], v[0][2], 0, + v[1][0], v[1][1], v[1][2], 0, + v[2][0], v[2][1], v[2][2], 0, + 0, 0, 0, 1); +} diff --git a/sourcevr/sourcevirtualreality.h b/sourcevr/sourcevirtualreality.h new file mode 100644 index 0000000..0e78750 --- /dev/null +++ b/sourcevr/sourcevirtualreality.h @@ -0,0 +1,134 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The implementation of ISourceVirtualReality, which provides utility +// functions for VR including head tracking, window/viewport information, +// rendering information, and distortion +// +//============================================================================= + +#ifndef SOURCEVIRTUALREALITY_H +#define SOURCEVIRTUALREALITY_H +#if defined( _WIN32 ) +#pragma once +#endif + +#include "tier3/tier3.h" +#include "sourcevr/isourcevirtualreality.h" +#include "materialsystem/itexture.h" +#include "materialsystem/MaterialSystemUtil.h" +#include "openvr/openvr.h" + + +// this is a callback so we can regenerate the distortion texture whenever we need to +class CDistortionTextureRegen : public ITextureRegenerator +{ +public: + CDistortionTextureRegen( vr::Hmd_Eye eEye ) : m_eEye( eEye ) {} + virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) OVERRIDE; + virtual void Release() OVERRIDE {} + +private: + vr::Hmd_Eye m_eEye; +}; + +//----------------------------------------------------------------------------- +// The implementation +//----------------------------------------------------------------------------- + + +class CSourceVirtualReality: public CTier3AppSystem< ISourceVirtualReality > +{ + typedef CTier3AppSystem< ISourceVirtualReality > BaseClass; + +public: + + CSourceVirtualReality(); + ~CSourceVirtualReality(); + + //--------------------------------------------------------- + // Initialization and shutdown + //--------------------------------------------------------- + + // + // IAppSystem + // + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void * QueryInterface( const char *pInterfaceName ); + + // these will come from the engine + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + + //--------------------------------------------------------- + // ISourceVirtualReality implementation + //--------------------------------------------------------- + virtual bool ShouldRunInVR() OVERRIDE; + virtual bool IsHmdConnected() OVERRIDE; + virtual void GetViewportBounds( VREye eEye, int *pnX, int *pnY, int *pnWidth, int *pnHeight ) OVERRIDE; + virtual bool DoDistortionProcessing ( VREye eEye ) OVERRIDE; + virtual bool CompositeHud ( VREye eEye, float ndcHudBounds[4], bool bDoUndistort, bool bBlackout, bool bTranslucent ) OVERRIDE; + virtual VMatrix GetMideyePose() OVERRIDE; + virtual bool SampleTrackingState ( float PlayerGameFov, float fPredictionSeconds ) OVERRIDE; + virtual bool GetEyeProjectionMatrix ( VMatrix *pResult, VREye, float zNear, float zFar, float fovScale ) OVERRIDE; + virtual bool WillDriftInYaw() OVERRIDE; + virtual bool GetDisplayBounds( VRRect_t *pRect ) OVERRIDE; + virtual VMatrix GetMidEyeFromEye( VREye eEye ) OVERRIDE; + virtual int GetVRModeAdapter() OVERRIDE; + virtual void CreateRenderTargets( IMaterialSystem *pMaterialSystem ) OVERRIDE; + virtual void ShutdownRenderTargets() OVERRIDE; + virtual ITexture *GetRenderTarget( VREye eEye, EWhichRenderTarget eWhich ) OVERRIDE; + virtual void GetRenderTargetFrameBufferDimensions( int & nWidth, int & nHeight ) OVERRIDE; + virtual bool Activate() OVERRIDE; + virtual void Deactivate() OVERRIDE; + virtual bool ShouldForceVRMode( ) OVERRIDE; + virtual void SetShouldForceVRMode( ) OVERRIDE; + + void RefreshDistortionTexture(); + void AcquireNewZeroPose(); + + bool StartTracker(); + void StopTracker(); + bool ResetTracking(); // Called to reset tracking + + // makes sure we've initialized OpenVR so we can use + // m_pHmd + bool EnsureOpenVRInited(); + + // Prefer this to the convar so that convar will stick for the entire + // VR activation. We can't lazy-crate render targets and don't + // want to create the "just in case" somebody turns on this experimental + // mode + bool UsingOffscreenRenderTarget() const { return m_bUsingOffscreenRenderTarget; } + + vr::IVRSystem * GetHmd() { return m_pHmd; } +private: + bool m_bActive; + bool m_bShouldForceVRMode; + bool m_bUsingOffscreenRenderTarget; + CDistortionTextureRegen m_textureGeneratorLeft; + CDistortionTextureRegen m_textureGeneratorRight; + CTextureReference g_StereoGuiTexture; + CTextureReference m_pDistortionTextureLeft; + CTextureReference m_pDistortionTextureRight; + CTextureReference m_pPredistortRT; + CTextureReference m_pPredistortRTDepth; + CMaterialReference m_warpMaterial; + CMaterialReference m_DistortLeftMaterial; + CMaterialReference m_DistortRightMaterial; + CMaterialReference m_DistortHUDLeftMaterial; + CMaterialReference m_DistortHUDRightMaterial; + CMaterialReference m_InWorldUIMaterial; + CMaterialReference m_InWorldUIOpaqueMaterial; + CMaterialReference m_blackMaterial; + + vr::IVRSystem *m_pHmd; + + bool m_bHaveValidPose; + VMatrix m_ZeroFromHeadPose; + +}; + + +#endif // SOURCEVIRTUALREALITY_H diff --git a/sourcevr/sourcevr.vpc b/sourcevr/sourcevr.vpc new file mode 100644 index 0000000..e357f02 --- /dev/null +++ b/sourcevr/sourcevr.vpc @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// SOURCEVR.VPC +// +// Wraps OpenVR and provides VR-related services to other bits of Source +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Macro OUTBINDIR "..\..\game\bin" +$Macro BINNAME "sourcevr" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $General + { + $AdditionalProjectDependencies "$BASE;filesystem_stdio" + } + + $Compiler + { + $PreprocessorDefinitions "$BASE;SOURCEVR_DLL;VERSION_SAFE_STEAM_API_INTERFACES" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "cbase.h" + $PrecompiledHeaderFile "$(IntDir)/sourcevr.pch" + } + + $Linker + { + $IgnoreImportLibrary "TRUE" + } +} + +$Project "SourceVR" +{ + $Folder "Source Files" + { + $Folder "Precompiled Header" + { + $File "$SRCDIR\sourcevr\stdafx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + $File "$SRCDIR\sourcevr\cbase.h" + } + + $Folder "Public Headers" + { + $File "$SRCDIR\public\sourcevr\isourcevirtualreality.h" + $File "$SRCDIR\public\openvr\openvr.h" + } + + $File "$SRCDIR\sourcevr\sourcevirtualreality.h" + $File "$SRCDIR\sourcevr\sourcevirtualreality.cpp" + } + + $Folder "Link Libraries" + { + $ImpLibexternal openvr_api + $Lib mathlib + $Lib tier2 + $Lib tier3 + $Lib appframework + $ImpLibexternal steam_api + } +} diff --git a/sourcevr/stdafx.cpp b/sourcevr/stdafx.cpp new file mode 100644 index 0000000..ce81dd7 --- /dev/null +++ b/sourcevr/stdafx.cpp @@ -0,0 +1,2 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "cbase.h"
\ No newline at end of file |