//========= 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);
}