You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
10 KiB
358 lines
10 KiB
//--------------------------------------------------------------------------------------
|
|
// 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__ */
|