|
|
//--------------------------------------------------------------------------------------
// File: hl2stereo.h
// Authors: John McDonald
// Email: [email protected]
//
// Utility classes for stereo
//
// Copyright (c) 2009 NVIDIA Corporation. All rights reserved.
//
// NOTE: This file is provided as-is, with no warranty either expressed or implied.
//--------------------------------------------------------------------------------------
#pragma once
#ifndef __HL2STEREO__
#define __HL2STEREO__ 1
#include "nvapi.h"
namespace nv { namespace stereo { typedef struct _Nv_Stereo_Image_Header { unsigned int dwSignature; unsigned int dwWidth; unsigned int dwHeight; unsigned int dwBPP; unsigned int dwFlags; } NVSTEREOIMAGEHEADER, *LPNVSTEREOIMAGEHEADER;
#define NVSTEREO_IMAGE_SIGNATURE 0x4433564e //NV3D
#define NVSTEREO_SWAP_EYES 0x00000001
inline void PopulateTextureData( float *leftEye, float *rightEye, LPNVSTEREOIMAGEHEADER header, unsigned int width, unsigned int height, unsigned int pixelBytes, float eyeSep, float sep, float conv ) { // Normally sep is in [0, 100], and we want the fractional part of 1.
float finalSeparation = eyeSep * sep * 0.005f; leftEye[0] = -finalSeparation; leftEye[1] = conv; leftEye[2] = -1.0f;
rightEye[0] = -leftEye[0]; rightEye[1] = leftEye[1]; rightEye[2] = -leftEye[2];
// Fill the header
header->dwSignature = NVSTEREO_IMAGE_SIGNATURE; header->dwWidth = width; header->dwHeight = height; header->dwBPP = pixelBytes * 8; header->dwFlags = 0; }
// This is expensive...may take more than 1ms to return. Only call this once at startup.
inline bool IsStereoEnabled() { NvU8 stereoEnabled = 0; if ( NVAPI_OK != NvAPI_Stereo_IsEnabled( &stereoEnabled ) ) { // Only try to call initialize once here...doing this just in case their library always returns
// one of the other error codes continually.
static bool s_bFirstTime = true; if ( s_bFirstTime ) { s_bFirstTime = false; NvAPI_Initialize(); NvAPI_Stereo_CreateConfigurationProfileRegistryKey( NVAPI_STEREO_DX9_REGISTRY_PROFILE ); if ( NVAPI_OK != NvAPI_Stereo_IsEnabled( &stereoEnabled ) ) { return false; } } else { return false; } }
return stereoEnabled != 0; }
#ifndef NO_STEREO_D3D9
// The D3D9 "Driver" for stereo updates, encapsulates the logic that is Direct3D9 specific.
struct D3D9Type { typedef IDirect3DDevice9 Device; typedef IDirect3DTexture9 Texture; typedef IDirect3DSurface9 StagingResource;
static const NV_STEREO_REGISTRY_PROFILE_TYPE RegistryProfileType = NVAPI_STEREO_DX9_REGISTRY_PROFILE;
static const int StereoTexWidth = 8; static const int StereoTexHeight = 1; static const D3DFORMAT StereoTexFormat = D3DFMT_R32F; static const int StereoBytesPerPixel = 4;
static StagingResource *CreateStagingResource( Device *pDevice, float eyeSep, float sep, float conv ) { StagingResource *staging = 0; unsigned int stagingWidth = StereoTexWidth * 2; unsigned int stagingHeight = StereoTexHeight + 1;
pDevice->CreateOffscreenPlainSurface( stagingWidth, stagingHeight, StereoTexFormat, D3DPOOL_SYSTEMMEM, &staging, NULL );
if ( !staging ) { return 0; }
D3DLOCKED_RECT lr; staging->LockRect( &lr, NULL, 0 ); unsigned char *sysData = ( unsigned char * ) lr.pBits; unsigned int sysMemPitch = stagingWidth * StereoBytesPerPixel;
float *leftEyePtr = ( float * )sysData; float *rightEyePtr = leftEyePtr + StereoTexWidth; LPNVSTEREOIMAGEHEADER header = ( LPNVSTEREOIMAGEHEADER )( sysData + sysMemPitch ); PopulateTextureData( leftEyePtr, rightEyePtr, header, stagingWidth, stagingHeight, StereoBytesPerPixel, eyeSep, sep, conv ); staging->UnlockRect();
return staging; }
static void UpdateTextureFromStaging( Device *pDevice, Texture *tex, StagingResource *staging ) { RECT stereoSrcRect; stereoSrcRect.top = 0; stereoSrcRect.bottom = StereoTexHeight; stereoSrcRect.left = 0; stereoSrcRect.right = StereoTexWidth;
POINT stereoDstPoint; stereoDstPoint.x = 0; stereoDstPoint.y = 0;
IDirect3DSurface9 *texSurface; tex->GetSurfaceLevel( 0, &texSurface );
pDevice->UpdateSurface( staging, &stereoSrcRect, texSurface, &stereoDstPoint ); texSurface->Release(); } }; #endif // NO_STEREO_D3D9
#ifndef NO_STEREO_D3D10
// The D3D10 "Driver" for stereo updates, encapsulates the logic that is Direct3D10 specific.
struct D3D10Type { typedef ID3D10Device Device; typedef ID3D10Texture2D Texture; typedef ID3D10Texture2D StagingResource;
static const NV_STEREO_REGISTRY_PROFILE_TYPE RegistryProfileType = NVAPI_STEREO_DX10_REGISTRY_PROFILE;
static const int StereoTexWidth = 8; static const int StereoTexHeight = 1; static const DXGI_FORMAT StereoTexFormat = DXGI_FORMAT_R32_FLOAT; static const int StereoBytesPerPixel = 4;
static StagingResource *CreateStagingResource( Device *pDevice, float eyeSep, float sep, float conv ) { StagingResource *staging = 0; unsigned int stagingWidth = StereoTexWidth * 2; unsigned int stagingHeight = StereoTexHeight + 1;
// Allocate the buffer sys mem data to write the stereo tag and stereo params
D3D10_SUBRESOURCE_DATA sysData; sysData.SysMemPitch = StereoBytesPerPixel * stagingWidth; sysData.pSysMem = new unsigned char[sysData.SysMemPitch * stagingHeight];
float *leftEyePtr = ( float * )sysData.pSysMem; float *rightEyePtr = leftEyePtr + StereoTexWidth; LPNVSTEREOIMAGEHEADER header = ( LPNVSTEREOIMAGEHEADER )( ( unsigned char * )sysData.pSysMem + sysData.SysMemPitch ); PopulateTextureData( leftEyePtr, rightEyePtr, header, stagingWidth, stagingHeight, StereoBytesPerPixel, eyeSep, sep, conv );
D3D10_TEXTURE2D_DESC desc; memset( &desc, 0, sizeof( D3D10_TEXTURE2D_DESC ) ); desc.Width = stagingWidth; desc.Height = stagingHeight; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = StereoTexFormat; desc.SampleDesc.Count = 1; desc.Usage = D3D10_USAGE_DEFAULT; desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; desc.MiscFlags = 0;
pDevice->CreateTexture2D( &desc, &sysData, &staging ); delete [] sysData.pSysMem; return staging; }
static void UpdateTextureFromStaging( Device *pDevice, Texture *tex, StagingResource *staging ) { D3D10_BOX stereoSrcBox; stereoSrcBox.front = 0; stereoSrcBox.back = 1; stereoSrcBox.top = 0; stereoSrcBox.bottom = StereoTexHeight; stereoSrcBox.left = 0; stereoSrcBox.right = StereoTexWidth;
pDevice->CopySubresourceRegion( tex, 0, 0, 0, 0, staging, 0, &stereoSrcBox ); } }; #endif // NO_STEREO_D3D10
// The HL2 Stereo class, which can work for either D3D9 or D3D10, depending on which type it's specialized for
// Note that both types can live side-by-side in two seperate instances as well.
// Also note that there are convenient typedefs below the class definition.
template < class D3DType > class HL2Stereo { public: typedef typename D3DType Parms; typedef typename D3DType::Device Device; typedef typename D3DType::Texture Texture; typedef typename D3DType::StagingResource StagingResource;
HL2Stereo() : mEyeSeparation( 0 ), mSeparation( 0 ), mConvergence( 0 ), mStereoHandle( 0 ), mInitialized( false ), mActive( false ), mDeviceLost( true ) // mDeviceLost is set to true to initialize the texture with good data at app startup.
{ NvAPI_Initialize(); NvAPI_Stereo_CreateConfigurationProfileRegistryKey( D3DType::RegistryProfileType ); }
~HL2Stereo() { if ( mStereoHandle ) { NvAPI_Stereo_DestroyHandle( mStereoHandle ); mStereoHandle = 0; } }
void Init( Device *dev ) { NvAPI_Stereo_CreateHandleFromIUnknown( dev, &mStereoHandle );
// Set that we've initialized regardless --we'll only try to init once.
mInitialized = true; }
// Not const because we will update the various values if an update is needed.
bool RequiresUpdate( bool deviceLost ) { bool active = IsStereoActive(); bool updateRequired; float eyeSep, sep, conv; if ( active ) { if ( NVAPI_OK != NvAPI_Stereo_GetEyeSeparation( mStereoHandle, &eyeSep ) ) return false; if ( NVAPI_OK != NvAPI_Stereo_GetSeparation( mStereoHandle, &sep ) ) return false; if ( NVAPI_OK != NvAPI_Stereo_GetConvergence( mStereoHandle, &conv ) ) return false;
// clamp the convergence to prevent wallhack exploit
if ( conv > 31.0f ) { conv = 31.0f; NvAPI_Stereo_SetConvergence( mStereoHandle, conv ); DevMsg( "[NVIDIA Stereo 3D] Clamping convergence: %.2f\n", conv); }
updateRequired = ( eyeSep != mEyeSeparation ) || ( sep != mSeparation ) || ( conv != mConvergence ) || ( active != mActive ); } else { eyeSep = sep = conv = 0; updateRequired = active != mActive; }
// If the device was lost and is now restored, need to update the texture contents again.
updateRequired = updateRequired || ( !deviceLost && mDeviceLost ); mDeviceLost = deviceLost;
if ( updateRequired ) { //Msg( "*** NV_STEREO - UpdateRequired == true\n" );
mEyeSeparation = eyeSep; mSeparation = sep; mConvergence = conv; mActive = active; return true; }
return false; }
bool IsStereoActive() const { NvU8 stereoActive = 0; if ( NVAPI_OK != NvAPI_Stereo_IsActivated( mStereoHandle, &stereoActive ) ) { return false; }
return stereoActive != 0; }
void UpdateStereoTexture( Device *dev, Texture *tex, bool deviceLost ) { if ( !mInitialized ) { Init( dev ); }
if ( !RequiresUpdate( deviceLost ) ) { return; }
DevMsg( "[NVIDIA Stereo 3D] UpdateStereoTexture: EyeSep: %.2f, Sep: %.2f, Conv: %.2f\n", mEyeSeparation, mSeparation, mConvergence);
StagingResource *staging = D3DType::CreateStagingResource( dev, mEyeSeparation, mSeparation, mConvergence ); if ( staging ) { D3DType::UpdateTextureFromStaging( dev, tex, staging ); staging->Release(); } }
private: float mEyeSeparation; float mSeparation; float mConvergence;
StereoHandle mStereoHandle; bool mInitialized; bool mActive; bool mDeviceLost; };
#ifndef NO_STEREO_D3D9
typedef HL2Stereo< D3D9Type > HL2StereoD3D9; #endif
#ifndef NO_STEREO_D3D10
typedef HL2Stereo< D3D10Type > HL2StereoD3D10; #endif
}; };
#endif /* __HL2STEREO__ */
|