|
|
/*==LICENSE==* |
|
|
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
|
|
This program is free software: you can redistribute it and/or modify |
|
|
it under the terms of the GNU General Public License as published by |
|
|
the Free Software Foundation, either version 3 of the License, or |
|
|
(at your option) any later version. |
|
|
|
|
|
This program is distributed in the hope that it will be useful, |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
GNU General Public License for more details. |
|
|
|
|
|
You should have received a copy of the GNU General Public License |
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
|
|
If you modify this Program, or any covered work, by linking or |
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
|
(or a modified version of those libraries), |
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
|
licensors of this Program grant you additional |
|
|
permission to convey the resulting work. Corresponding Source for a |
|
|
non-source form of such a combination shall include the source code for |
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
|
work. |
|
|
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
|
or by snail mail at: |
|
|
Cyan Worlds, Inc. |
|
|
14617 N Newport Hwy |
|
|
Mead, WA 99021 |
|
|
|
|
|
*==LICENSE==*/ |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// // |
|
|
// plDXPipeline Class Functions // |
|
|
// plPipeline derivative for DirectX // |
|
|
// Cyan, Inc. // |
|
|
// // |
|
|
//// Version History ////////////////////////////////////////////////////////// |
|
|
// // |
|
|
// 2.23.2001 mcn - Created. // |
|
|
// // |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
#include "hsConfig.h" |
|
|
#include "hsWindows.h" |
|
|
|
|
|
#include <d3d9.h> |
|
|
#include <ddraw.h> |
|
|
#include <d3dx9mesh.h> |
|
|
#include <dxerr.h> |
|
|
|
|
|
#include "hsWinRef.h" |
|
|
|
|
|
#include "hsTypes.h" |
|
|
#include "plDXPipeline.h" |
|
|
#include "plPipelineCreate.h" |
|
|
#include "plDebugText.h" |
|
|
#include "plDXEnumerate.h" |
|
|
#include "hsG3DDeviceSelector.h" |
|
|
#include "hsGDDrawDllLoad.h" |
|
|
#include "hsResMgr.h" |
|
|
#include "plStatusLogDrawer.h" |
|
|
#include "plQuality.h" |
|
|
|
|
|
#include "plPipeDebugFlags.h" |
|
|
|
|
|
#include "hsTemplates.h" |
|
|
//#include "hsGEnviron.h" |
|
|
#include "plProfile.h" |
|
|
#include "plMessage/plDeviceRecreateMsg.h" |
|
|
#include "pnMessage/plSelfDestructMsg.h" |
|
|
#include "pnMessage/plClientMsg.h" |
|
|
#include "plSurface/hsGMaterial.h" |
|
|
#include "plSurface/plLayerInterface.h" |
|
|
#include "plSurface/plLayerShadowBase.h" |
|
|
#include "plGImage/plMipmap.h" |
|
|
#include "plGImage/plCubicEnvironmap.h" |
|
|
#include "plDrawable/plDrawableSpans.h" |
|
|
#include "plDrawable/plGeometrySpan.h" |
|
|
#include "plDrawable/plSpaceTree.h" |
|
|
#include "plDrawable/plDrawableGenerator.h" |
|
|
#include "plDrawable/plSpanTypes.h" |
|
|
#include "plDrawable/plAccessSpan.h" |
|
|
#include "plDrawable/plAuxSpan.h" |
|
|
#include "pnSceneObject/plSceneObject.h" |
|
|
#include "pnSceneObject/plDrawInterface.h" |
|
|
#include "hsFastMath.h" |
|
|
#include "plGLight/plLightInfo.h" |
|
|
#include "plParticleSystem/plParticleEmitter.h" |
|
|
#include "plParticleSystem/plParticle.h" |
|
|
#include "plAvatar/plAvatarClothing.h" |
|
|
#include "plDebugText.h" |
|
|
#include "plFogEnvironment.h" |
|
|
#include "plDXTextFont.h" |
|
|
#include "plGBufferGroup.h" |
|
|
#include "hsTimer.h" |
|
|
#include "plgDispatch.h" |
|
|
#include "plScene/plRenderRequest.h" |
|
|
#include "plScene/plVisMgr.h" |
|
|
#include "plRenderTarget.h" |
|
|
#include "plCubicRenderTarget.h" |
|
|
#include "plDynamicEnvMap.h" |
|
|
#include "pfCamera/plVirtualCamNeu.h" |
|
|
|
|
|
#include "plDXBufferRefs.h" |
|
|
#include "plDXTextureRef.h" |
|
|
#include "plDXLightRef.h" |
|
|
#include "plDXRenderTargetRef.h" |
|
|
#include "plDXVertexShader.h" |
|
|
#include "plDXPixelShader.h" |
|
|
|
|
|
#include "plGLight/plShadowSlave.h" |
|
|
#include "plGLight/plShadowCaster.h" |
|
|
|
|
|
#include "hsGMatState.inl" |
|
|
|
|
|
#include "plSurface/plShader.h" |
|
|
#include "plDXVertexShader.h" |
|
|
#include "plDXPixelShader.h" |
|
|
|
|
|
#include "pnMessage/plPipeResMakeMsg.h" |
|
|
#include "plPipeResReq.h" |
|
|
#include "pnNetCommon/plNetApp.h" // for dbg logging |
|
|
#include "pfCamera/plVirtualCamNeu.h" |
|
|
#include "pfCamera/plCameraModifier.h" |
|
|
#include "plResMgr/plLocalization.h" |
|
|
|
|
|
|
|
|
// mf horse - test hack, nuke this later |
|
|
#include "plSurface/plLayerDepth.h" |
|
|
|
|
|
#include "plGImage/hsCodecManager.h" |
|
|
//#include "plGImage/hsDXTDirectXCodec.h" |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
// This is so VC++ will let us view the contents of plIcicle::fOwnerKey |
|
|
#include "pnKeyedObject/plKey.h" |
|
|
#endif |
|
|
|
|
|
#include "plCullTree.h" |
|
|
|
|
|
#include "plTweak.h" |
|
|
|
|
|
#include <algorithm> |
|
|
|
|
|
//#define MF_TOSSER |
|
|
|
|
|
int mfCurrentTest = 100; |
|
|
PipelineParams plPipeline::fDefaultPipeParams; |
|
|
PipelineParams plPipeline::fInitialPipeParams; |
|
|
//#define MF_ENABLE_HACKOFF |
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
//WHITE |
|
|
static hsTArray<plRenderTarget*> hackOffscreens; |
|
|
UInt32 doHackPlate = UInt32(-1); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
|
|
|
UInt32 fDbgSetupInitFlags; // HACK temp only |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
void plReleaseObject(IUnknown* x) |
|
|
{ |
|
|
if( x ) |
|
|
{ |
|
|
int refs = x->Release(); |
|
|
if( refs ) |
|
|
refs = 0; |
|
|
} |
|
|
} |
|
|
#else // HS_DEBUGGING |
|
|
void plReleaseObject(IUnknown* x) |
|
|
{ |
|
|
if( x ) |
|
|
x->Release(); |
|
|
} |
|
|
#endif // HS_DEBUGGING |
|
|
|
|
|
//// Local Static Stuff /////////////////////////////////////////////////////// |
|
|
|
|
|
/// Macros for getting/setting data in a D3D vertex buffer |
|
|
inline UInt8* inlStuffPoint( UInt8* ptr, const hsScalarTriple& point ) |
|
|
{ |
|
|
register float* dst = (float*)ptr; |
|
|
register const float* src = (float*)&point.fX; |
|
|
*dst++ = *src++; |
|
|
*dst++ = *src++; |
|
|
*dst++ = *src++; |
|
|
return (UInt8*)dst; |
|
|
} |
|
|
inline UInt8* inlStuffUInt32( UInt8* ptr, const UInt32 uint ) |
|
|
{ |
|
|
*(UInt32*)ptr = uint; |
|
|
return ptr + sizeof(uint); |
|
|
} |
|
|
inline UInt8* inlExtractPoint( const UInt8* ptr, const hsScalarTriple& pt ) |
|
|
{ |
|
|
register const float* src = (float*)ptr; |
|
|
register float* dst = (float*)&pt.fX; |
|
|
*dst++ = *src++; |
|
|
*dst++ = *src++; |
|
|
*dst++ = *src++; |
|
|
return (UInt8*)src; |
|
|
} |
|
|
inline UInt8* inlExtractFloat( const UInt8*& ptr, float& f ) |
|
|
{ |
|
|
register const float* src = (float*)ptr; |
|
|
f = *src++; |
|
|
return (UInt8*)src; |
|
|
} |
|
|
inline UInt8* inlExtractUInt32( const UInt8*& ptr, UInt32& uint ) |
|
|
{ |
|
|
const UInt32* src = (UInt32*)ptr; |
|
|
uint = *src++; |
|
|
return (UInt8*)src; |
|
|
} |
|
|
|
|
|
inline DWORD F2DW( FLOAT f ) |
|
|
{ |
|
|
return *((DWORD*)&f); |
|
|
} |
|
|
|
|
|
//// Macros for D3D error handling |
|
|
#define INIT_ERROR_CHECK( cond, errMsg ) if( FAILED( fSettings.fDXError = cond ) ) { return ICreateFail( errMsg ); } |
|
|
|
|
|
#if 1 // DEBUG |
|
|
#define STRONG_ERROR_CHECK( cond ) if( FAILED( fSettings.fDXError = cond ) ) { IGetD3DError(); IShowErrorMessage(); } |
|
|
#define WEAK_ERROR_CHECK( cond ) STRONG_ERROR_CHECK( cond ) |
|
|
#else |
|
|
#define STRONG_ERROR_CHECK( cond ) if( FAILED( fSettings.fDXError = cond ) ) { IGetD3DError(); } |
|
|
#define WEAK_ERROR_CHECK( cond ) cond |
|
|
#endif |
|
|
|
|
|
static D3DXMATRIX d3dIdentityMatrix( 1.0f, 0.0f, 0.0f, 0.0f, |
|
|
0.0f, 1.0f, 0.0f, 0.0f, |
|
|
0.0f, 0.0f, 1.0f, 0.0f, |
|
|
0.0f, 0.0f, 0.0f, 1.0f ); |
|
|
|
|
|
static const enum _D3DTRANSFORMSTATETYPE sTextureStages[ 8 ] = |
|
|
{ |
|
|
D3DTS_TEXTURE0, D3DTS_TEXTURE1, D3DTS_TEXTURE2, D3DTS_TEXTURE3, |
|
|
D3DTS_TEXTURE4, D3DTS_TEXTURE5, D3DTS_TEXTURE6, D3DTS_TEXTURE7 |
|
|
}; |
|
|
|
|
|
static const float kPerspLayerScale = 0.00001f; |
|
|
static const float kPerspLayerScaleW = 0.001f; |
|
|
static const float kPerspLayerTrans = 0.00002f; |
|
|
static const hsScalar kAvTexPoolShrinkThresh = 30.f; // seconds |
|
|
|
|
|
// This caps the number of D3D lights we use. We'll use up to the max allowed |
|
|
// or this number, whichever is smaller. (This is to prevent us going haywire |
|
|
// on trying to allocate an array for ALL of the lights in the Ref device.) |
|
|
//#define kD3DMaxTotalLights 32 |
|
|
///HAAAAACK Let's be mean and limit the artists to only 4 run-time lights.... hehehehhehe (not my idea!!!) |
|
|
const int kD3DMaxTotalLights = 8; |
|
|
// The framerate is the limit on the number of projected lights an object can have. |
|
|
const int kMaxProjectors = 100; |
|
|
|
|
|
/// This controls whether we can draw bounds boxes around all the ice spans. |
|
|
//#ifdef HS_DEBUGGING |
|
|
#define MCN_BOUNDS_SPANS 1 |
|
|
//#endif |
|
|
|
|
|
#define MF_BOUNDS_LEVEL_ICE 1 |
|
|
//#define HS_D3D_USE_SPECULAR |
|
|
|
|
|
/// Define this to write out z-buffer debug info to plasmalog.txt |
|
|
#ifdef HS_DEBUGGING |
|
|
//#define DBG_WRITE_FORMATS |
|
|
#endif |
|
|
|
|
|
plProfile_CreateMemCounter("Pipeline Surfaces", "Memory", MemPipelineSurfaces); |
|
|
plProfile_Extern(MemVertex); |
|
|
plProfile_Extern(MemIndex); |
|
|
plProfile_CreateCounter("Feed Triangles", "Draw", DrawFeedTriangles); |
|
|
plProfile_CreateCounter("Polys", "General", DrawTriangles); |
|
|
plProfile_CreateCounter("Draw Prim Static", "Draw", DrawPrimStatic); |
|
|
plProfile_CreateMemCounter("Total Texture Size", "Draw", TotalTexSize); |
|
|
plProfile_CreateTimer("Harvest", "Draw", Harvest); |
|
|
plProfile_CreateCounter("Material Change", "Draw", MatChange); |
|
|
plProfile_CreateCounter("Layer Change", "Draw", LayChange); |
|
|
|
|
|
plProfile_Extern(DrawOccBuild); |
|
|
|
|
|
plProfile_CreateCounterNoReset("Reload", "PipeC", PipeReload); |
|
|
|
|
|
plProfile_CreateTimer("RenderScene", "PipeT", RenderScene); |
|
|
plProfile_CreateTimer("VisEval", "PipeT", VisEval); |
|
|
plProfile_CreateTimer("VisSelect", "PipeT", VisSelect); |
|
|
plProfile_CreateTimer("FindSceneLights", "PipeT", FindSceneLights); |
|
|
plProfile_CreateTimer("PrepShadows", "PipeT", PrepShadows); |
|
|
plProfile_CreateTimer("PrepDrawable", "PipeT", PrepDrawable); |
|
|
plProfile_CreateTimer(" Skin", "PipeT", Skin); |
|
|
plProfile_CreateTimer(" AvSort", "PipeT", AvatarSort); |
|
|
plProfile_CreateTimer(" Find Lights", "PipeT", FindLights); |
|
|
plProfile_CreateTimer(" Find Perms", "PipeT", FindPerm); |
|
|
plProfile_CreateTimer(" FindSpan", "PipeT", FindSpan); |
|
|
plProfile_CreateTimer(" FindActiveLights", "PipeT", FindActiveLights); |
|
|
plProfile_CreateTimer(" ApplyActiveLights", "PipeT", ApplyActiveLights); |
|
|
plProfile_CreateTimer(" ApplyMoving", "PipeT", ApplyMoving); |
|
|
plProfile_CreateTimer(" ApplyToSpec", "PipeT", ApplyToSpec); |
|
|
plProfile_CreateTimer(" ApplyToMoving", "PipeT", ApplyToMoving); |
|
|
plProfile_CreateTimer(" ClearLights", "PipeT", ClearLights); |
|
|
plProfile_CreateTimer("RenderSpan", "PipeT", RenderSpan); |
|
|
plProfile_CreateTimer(" MergeCheck", "PipeT", MergeCheck); |
|
|
plProfile_CreateTimer(" MergeSpan", "PipeT", MergeSpan); |
|
|
plProfile_CreateTimer(" SpanTransforms", "PipeT", SpanTransforms); |
|
|
plProfile_CreateTimer(" SpanFog", "PipeT", SpanFog); |
|
|
plProfile_CreateTimer(" SelectLights", "PipeT", SelectLights); |
|
|
plProfile_CreateTimer(" SelectProj", "PipeT", SelectProj); |
|
|
plProfile_CreateTimer(" CheckDyn", "PipeT", CheckDyn); |
|
|
plProfile_CreateTimer(" CheckStat", "PipeT", CheckStat); |
|
|
plProfile_CreateTimer(" RenderBuff", "PipeT", RenderBuff); |
|
|
plProfile_CreateTimer(" RenderPrim", "PipeT", RenderPrim); |
|
|
plProfile_CreateTimer("PlateMgr", "PipeT", PlateMgr); |
|
|
plProfile_CreateTimer("DebugText", "PipeT", DebugText); |
|
|
plProfile_CreateTimer("Reset", "PipeT", Reset); |
|
|
|
|
|
plProfile_CreateMemCounter("DefMem", "PipeC", DefaultMem); |
|
|
plProfile_CreateMemCounter("ManMem", "PipeC", ManagedMem); |
|
|
plProfile_CreateMemCounterReset("CurrTex", "PipeC", CurrTex); |
|
|
plProfile_CreateMemCounterReset("CurrVB", "PipeC", CurrVB); |
|
|
plProfile_CreateMemCounter("TexTot", "PipeC", TexTot); |
|
|
plProfile_CreateMemCounterReset("fTexUsed", "PipeC", fTexUsed); |
|
|
plProfile_CreateMemCounterReset("fTexManaged", "PipeC", fTexManaged); |
|
|
plProfile_CreateMemCounterReset("fVtxUsed", "PipeC", fVtxUsed); |
|
|
plProfile_CreateMemCounterReset("fVtxManaged", "PipeC", fVtxManaged); |
|
|
plProfile_CreateMemCounter("ManSeen", "PipeC", ManSeen); |
|
|
plProfile_CreateCounterNoReset("ManEvict", "PipeC", ManEvict); |
|
|
plProfile_CreateCounter("LightOn", "PipeC", LightOn); |
|
|
plProfile_CreateCounter("LightVis", "PipeC", LightVis); |
|
|
plProfile_CreateCounter("LightChar", "PipeC", LightChar); |
|
|
plProfile_CreateCounter("LightActive", "PipeC", LightActive); |
|
|
plProfile_CreateCounter("Lights Found", "PipeC", FindLightsFound); |
|
|
plProfile_CreateCounter("Perms Found", "PipeC", FindLightsPerm); |
|
|
plProfile_CreateCounter("Merge", "PipeC", SpanMerge); |
|
|
plProfile_CreateCounter("TexNum", "PipeC", NumTex); |
|
|
plProfile_CreateCounter("LiState", "PipeC", MatLightState); |
|
|
plProfile_CreateCounter("OccPoly", "PipeC", OccPolyUsed); |
|
|
plProfile_CreateCounter("OccNode", "PipeC", OccNodeUsed); |
|
|
plProfile_CreateCounter("NumSkin", "PipeC", NumSkin); |
|
|
plProfile_CreateCounter("AvatarFaces", "PipeC", AvatarFaces); |
|
|
plProfile_CreateCounter("VertexChange", "PipeC", VertexChange); |
|
|
plProfile_CreateCounter("IndexChange", "PipeC", IndexChange); |
|
|
plProfile_CreateCounter("DynVBuffs", "PipeC", DynVBuffs); |
|
|
plProfile_CreateCounter("EmptyList", "PipeC", EmptyList); |
|
|
plProfile_CreateCounter("AvRTPoolUsed", "PipeC", AvRTPoolUsed); |
|
|
plProfile_CreateCounter("AvRTPoolCount", "PipeC", AvRTPoolCount); |
|
|
plProfile_CreateCounter("AvRTPoolRes", "PipeC", AvRTPoolRes); |
|
|
plProfile_CreateCounter("AvRTShrinkTime", "PipeC", AvRTShrinkTime); |
|
|
|
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
|
/// Fun inlines for keeping track of surface creation/deletion memory |
|
|
void D3DSURF_MEMNEW(IDirect3DSurface9* surf) |
|
|
{ |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURFACE_DESC info; |
|
|
surf->GetDesc( &info ); |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, info.Width * info.Height * plDXPipeline::GetDXBitDepth(info.Format) / 8 + sizeof(IDirect3DSurface9), true, "D3DSurface"); |
|
|
plProfile_NewMem(MemPipelineSurfaces, info.Width * info.Height * plDXPipeline::GetDXBitDepth(info.Format) / 8 + sizeof(IDirect3DSurface9)); |
|
|
} |
|
|
} |
|
|
|
|
|
void D3DSURF_MEMNEW(IDirect3DTexture9* tex) |
|
|
{ |
|
|
if( tex ) |
|
|
{ |
|
|
IDirect3DSurface9* surf; |
|
|
tex->GetSurfaceLevel(0, &surf); |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(surf); |
|
|
surf->Release(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void D3DSURF_MEMNEW(IDirect3DCubeTexture9* cTex) |
|
|
{ |
|
|
if( cTex ) |
|
|
{ |
|
|
IDirect3DSurface9* surf; |
|
|
cTex->GetCubeMapSurface(D3DCUBEMAP_FACE_POSITIVE_X, 0, &surf); |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(surf); |
|
|
D3DSURF_MEMNEW(surf); |
|
|
D3DSURF_MEMNEW(surf); |
|
|
D3DSURF_MEMNEW(surf); |
|
|
D3DSURF_MEMNEW(surf); |
|
|
D3DSURF_MEMNEW(surf); |
|
|
surf->Release(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void D3DSURF_MEMDEL(IDirect3DSurface9* surf) |
|
|
{ |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURFACE_DESC info; |
|
|
surf->GetDesc( &info ); |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, info.Width * info.Height * plDXPipeline::GetDXBitDepth(info.Format) / 8 + sizeof(IDirect3DSurface9), false, "D3DSurface"); |
|
|
plProfile_DelMem(MemPipelineSurfaces, info.Width * info.Height * plDXPipeline::GetDXBitDepth(info.Format) / 8 + sizeof(IDirect3DSurface9)); |
|
|
} |
|
|
} |
|
|
|
|
|
void D3DSURF_MEMDEL(IDirect3DTexture9* tex) |
|
|
{ |
|
|
if( tex ) |
|
|
{ |
|
|
IDirect3DSurface9* surf; |
|
|
tex->GetSurfaceLevel(0, &surf); |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURF_MEMDEL(surf); |
|
|
surf->Release(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void D3DSURF_MEMDEL(IDirect3DCubeTexture9* cTex) |
|
|
{ |
|
|
if( cTex ) |
|
|
{ |
|
|
IDirect3DSurface9* surf; |
|
|
cTex->GetCubeMapSurface(D3DCUBEMAP_FACE_POSITIVE_X, 0, &surf); |
|
|
if( surf ) |
|
|
{ |
|
|
D3DSURF_MEMDEL(surf); |
|
|
D3DSURF_MEMDEL(surf); |
|
|
D3DSURF_MEMDEL(surf); |
|
|
D3DSURF_MEMDEL(surf); |
|
|
D3DSURF_MEMDEL(surf); |
|
|
D3DSURF_MEMDEL(surf); |
|
|
surf->Release(); |
|
|
} |
|
|
} |
|
|
} |
|
|
#else |
|
|
void D3DSURF_MEMNEW(IDirect3DSurface9* surf) {} |
|
|
void D3DSURF_MEMNEW(IDirect3DTexture9* tex) {} |
|
|
void D3DSURF_MEMNEW(IDirect3DCubeTexture9* cTex) {} |
|
|
void D3DSURF_MEMDEL(IDirect3DSurface9* surf) {} |
|
|
void D3DSURF_MEMDEL(IDirect3DTexture9* tex) {} |
|
|
void D3DSURF_MEMDEL(IDirect3DCubeTexture9* cTex) {} |
|
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
|
void plDXPipeline::ProfilePoolMem(D3DPOOL poolType, UInt32 size, hsBool add, char *id) |
|
|
{ |
|
|
switch( poolType ) |
|
|
{ |
|
|
case D3DPOOL_MANAGED: |
|
|
if (add) |
|
|
{ |
|
|
plProfile_NewMem(ManagedMem, size); |
|
|
//plStatusLog::AddLineS("pipeline.log", 0xffff0000, "Adding MANAGED mem. Size: %10d, Total: %10d ID: %s", |
|
|
// size, gProfileVarManagedMem.GetValue(), id); |
|
|
} |
|
|
else |
|
|
{ |
|
|
plProfile_DelMem(ManagedMem, size); |
|
|
//plStatusLog::AddLineS("pipeline.log", 0xffff0000, "Deleting MANAGED mem. Size: %10d, Total: %10d ID: %s", |
|
|
// size, gProfileVarManagedMem.GetValue(), id); |
|
|
} |
|
|
break; |
|
|
default: |
|
|
if (add) |
|
|
{ |
|
|
plProfile_NewMem(DefaultMem, size); |
|
|
//plStatusLog::AddLineS("pipeline.log", 0xffff0000, "Adding DEFAULT mem. Size: %10d, Total: %10d ID: %s", |
|
|
// size, gProfileVarDefaultMem.GetValue(), id); |
|
|
} |
|
|
else |
|
|
{ |
|
|
plProfile_DelMem(DefaultMem, size); |
|
|
//plStatusLog::AddLineS("pipeline.log", 0xffff0000, "Deleting DEFAULT mem. Size: %10d, Total: %10d ID: %s", |
|
|
// size, gProfileVarDefaultMem.GetValue(), id); |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////// |
|
|
// Implementations of RenderPrims types. |
|
|
// Currently support render tri list |
|
|
// These allow the same setup code path to be followed, no matter what the primitive type |
|
|
// (i.e. data-type/draw-call is going to happen once the render state is set. |
|
|
// Originally useful to make one code path for trilists, tri-patches, and rect-patches, but |
|
|
// we've since dropped support for patches. We still use the RenderNil function to allow the |
|
|
// code to go through all the state setup without knowing whether a render call is going to |
|
|
// come out the other end. |
|
|
// Would allow easy extension for supporting tristrips or pointsprites, but we've never had |
|
|
// a strong reason to use either. |
|
|
// First, Declarations. |
|
|
|
|
|
// Adding a nil RenderPrim for turning off drawing |
|
|
class plRenderNilFunc : public plRenderPrimFunc |
|
|
{ |
|
|
public: |
|
|
plRenderNilFunc() {} |
|
|
|
|
|
virtual hsBool RenderPrims() const { return false; } |
|
|
}; |
|
|
static plRenderNilFunc sRenderNil; |
|
|
|
|
|
class plRenderTriListFunc : public plRenderPrimFunc |
|
|
{ |
|
|
protected: |
|
|
LPDIRECT3DDEVICE9 fD3DDevice; |
|
|
int fBaseVertexIndex; |
|
|
int fVStart; |
|
|
int fVLength; |
|
|
int fIStart; |
|
|
int fNumTris; |
|
|
public: |
|
|
plRenderTriListFunc(LPDIRECT3DDEVICE9 d3dDevice, int baseVertexIndex, |
|
|
int vStart, int vLength, int iStart, int iNumTris) |
|
|
: fD3DDevice(d3dDevice), fBaseVertexIndex(baseVertexIndex), fVStart(vStart), fVLength(vLength), fIStart(iStart), fNumTris(iNumTris) {} |
|
|
|
|
|
virtual hsBool RenderPrims() const; |
|
|
}; |
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
// Implementations |
|
|
|
|
|
hsBool plRenderTriListFunc::RenderPrims() const |
|
|
{ |
|
|
plProfile_IncCount(DrawFeedTriangles, fNumTris); |
|
|
plProfile_IncCount(DrawTriangles, fNumTris); |
|
|
plProfile_Inc(DrawPrimStatic); |
|
|
|
|
|
return FAILED( fD3DDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, fBaseVertexIndex, fVStart, fVLength, fIStart, fNumTris ) ); |
|
|
} |
|
|
|
|
|
//// Constructor & Destructor ///////////////////////////////////////////////// |
|
|
|
|
|
UInt32 plDXPipeline::fTexUsed(0); |
|
|
UInt32 plDXPipeline::fTexManaged(0); |
|
|
UInt32 plDXPipeline::fVtxUsed(0); |
|
|
UInt32 plDXPipeline::fVtxManaged(0); |
|
|
|
|
|
plDXPipeline::plDXPipeline( hsWinRef hWnd, const hsG3DDeviceModeRecord *devModeRec ) |
|
|
: fManagedAlloced(false), |
|
|
fAllocUnManaged(false) |
|
|
{ |
|
|
hsAssert(D3DTSS_TCI_PASSTHRU == plLayerInterface::kUVWPassThru, "D3D Enum has changed. Notify graphics department."); |
|
|
hsAssert(D3DTSS_TCI_CAMERASPACENORMAL == plLayerInterface::kUVWNormal, "D3D Enum has changed. Notify graphics department."); |
|
|
hsAssert(D3DTSS_TCI_CAMERASPACEPOSITION == plLayerInterface::kUVWPosition, "D3D Enum has changed. Notify graphics department."); |
|
|
hsAssert(D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR == plLayerInterface::kUVWReflect, "D3D Enum has changed. Notify graphics department."); |
|
|
|
|
|
// Initialize everything to NULL. |
|
|
IClearMembers(); |
|
|
|
|
|
// Get the requested mode and setup |
|
|
const hsG3DDeviceRecord *devRec = devModeRec->GetDevice(); |
|
|
const hsG3DDeviceMode *devMode = devModeRec->GetMode(); |
|
|
|
|
|
/// Init our screen mode |
|
|
fSettings.fHWnd = hWnd; |
|
|
if(!fInitialPipeParams.Windowed) |
|
|
{ |
|
|
fSettings.fOrigWidth = devMode->GetWidth(); |
|
|
fSettings.fOrigHeight = devMode->GetHeight(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
// windowed can run in any mode |
|
|
fSettings.fOrigHeight = fInitialPipeParams.Height; |
|
|
fSettings.fOrigWidth = fInitialPipeParams.Width; |
|
|
} |
|
|
IGetViewTransform().SetScreenSize((UInt16)(fSettings.fOrigWidth), (UInt16)(fSettings.fOrigHeight)); |
|
|
fSettings.fColorDepth = devMode->GetColorDepth(); |
|
|
fVSync = fInitialPipeParams.VSync; |
|
|
|
|
|
if( devRec->GetAASetting() == 0 ) |
|
|
fSettings.fNumAASamples = 0; |
|
|
else |
|
|
fSettings.fNumAASamples = devMode->GetFSAAType( devRec->GetAASetting() - 1 ); |
|
|
|
|
|
hsGDirect3DTnLEnumerate d3dEnum; |
|
|
if( d3dEnum.GetEnumeErrorStr()[ 0 ] ) |
|
|
{ |
|
|
IShowErrorMessage( (char *)d3dEnum.GetEnumeErrorStr() ); |
|
|
return; |
|
|
} |
|
|
|
|
|
if( d3dEnum.SelectFromDevMode(devRec, devMode) ) |
|
|
{ |
|
|
IShowErrorMessage( (char *)d3dEnum.GetEnumeErrorStr() ); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Gotta create this very first, so that the device/driver init works |
|
|
if( !fD3DObject ) |
|
|
{ |
|
|
if( ICreateMaster() ) |
|
|
{ |
|
|
IShowErrorMessage( "Cannot create D3D master object" ); |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
// Record the requested mode/setup. |
|
|
ISetCurrentDriver( d3dEnum.GetCurrentDriver() ); |
|
|
ISetCurrentDevice( d3dEnum.GetCurrentDevice() ); |
|
|
D3DEnum_ModeInfo *pModeInfo = d3dEnum.GetCurrentMode(); |
|
|
pModeInfo->fWindowed = fInitialPipeParams.Windowed; // set windowed mode from ini file |
|
|
ISetCurrentMode( d3dEnum.GetCurrentMode() ); |
|
|
|
|
|
fSettings.fFullscreen = !fCurrentMode->fWindowed; |
|
|
|
|
|
fSettings.fNumAASamples = fInitialPipeParams.AntiAliasingAmount; |
|
|
|
|
|
// ISetCaps just records the card capabilities that were passed in. |
|
|
ISetCaps(); |
|
|
// IRestrictCaps looks over those explicit caps and makes some decisions on |
|
|
// what the card can really do. |
|
|
IRestrictCaps( *devRec ); |
|
|
|
|
|
fSettings.fMaxAnisotropicSamples = fInitialPipeParams.AnisotropicLevel; |
|
|
if(fSettings.fMaxAnisotropicSamples > fCurrentDevice->fDDCaps.MaxAnisotropy) |
|
|
fSettings.fMaxAnisotropicSamples = (UInt8)fCurrentDevice->fDDCaps.MaxAnisotropy; |
|
|
|
|
|
|
|
|
plConst(UInt32) kDefaultDynVtxSize(32000 * 44); |
|
|
plConst(UInt32) kDefaultDynIdxSize(0 * plGBufferGroup::kMaxNumIndicesPerBuffer * 2); |
|
|
fDynVtxSize = kDefaultDynVtxSize; |
|
|
fVtxRefTime = 0; |
|
|
|
|
|
// Go create surfaces and DX-dependent objects |
|
|
if( ICreateDeviceObjects() ) |
|
|
{ |
|
|
IShowErrorMessage( "Cannot create Direct3D device" ); |
|
|
return; |
|
|
} |
|
|
/*plStatusLog::AddLineS("pipeline.log", "Supported Resolutions:"); |
|
|
std::vector<plDisplayMode> temp; |
|
|
GetSupportedDisplayModes( &temp, 16 ); |
|
|
for(int i = 0; i < temp.size(); i++) |
|
|
{ |
|
|
plStatusLog::AddLineS("pipeline.log", "%d, %d, %d", temp[i].Width, temp[i].Height, 16); |
|
|
} |
|
|
temp.clear(); |
|
|
GetSupportedDisplayModes( &temp, 32 ); |
|
|
for(int i = 0; i < temp.size(); i++) |
|
|
{ |
|
|
plStatusLog::AddLineS("pipeline.log", "%d, %d, %d", temp[i].Width, temp[i].Height, 32); |
|
|
}*/ |
|
|
|
|
|
} |
|
|
|
|
|
// Cleanup - Most happens in IReleaseDeviceObject(). |
|
|
plDXPipeline::~plDXPipeline() |
|
|
{ |
|
|
fCurrLay = nil; |
|
|
hsAssert( fCurrMaterial == nil, "Current material not unrefed properly" ); |
|
|
|
|
|
// fCullProxy is a debugging representation of our CullTree. See plCullTree.cpp, |
|
|
// plScene/plOccluder.cpp and plScene/plOccluderProxy.cpp for more info |
|
|
if( fCullProxy ) |
|
|
fCullProxy->GetKey()->UnRefObject(); |
|
|
delete fCurrentDriver; |
|
|
delete fCurrentDevice; |
|
|
delete fCurrentMode; |
|
|
|
|
|
IReleaseDeviceObjects(); |
|
|
IClearClothingOutfits(&fClothingOutfits); |
|
|
IClearClothingOutfits(&fPrevClothingOutfits); |
|
|
} |
|
|
|
|
|
//// IClearMembers //////////////////////////////////////////////////////////// |
|
|
// Initialize everything to a nil state. |
|
|
// This does not initialize to a working state, but to a state that can be |
|
|
// built from. For example, the fD3DObject pointer is set to nil so that it's safe |
|
|
// to delete or set to a valid pointer. It must be set to a valid pointer |
|
|
// before the pipeline can be used for much. |
|
|
// After the core initialization is done (in ICreateMaster and ICreateDeviceObjects) |
|
|
// render state will be initialized in IInitDeviceState. |
|
|
|
|
|
void plDXPipeline::IClearMembers() |
|
|
{ |
|
|
/// Clear some stuff |
|
|
fVtxBuffRefList = nil; |
|
|
fIdxBuffRefList = nil; |
|
|
fTextureRefList = nil; |
|
|
fTextFontRefList = nil; |
|
|
fRenderTargetRefList = nil; |
|
|
fVShaderRefList = nil; |
|
|
fPShaderRefList = nil; |
|
|
fCurrMaterial = nil; |
|
|
fCurrLay = nil; |
|
|
fCurrRenderLayer = 0; |
|
|
#if MCN_BOUNDS_SPANS |
|
|
fBoundsMat = nil; |
|
|
fBoundsSpans = nil; |
|
|
#endif |
|
|
fPlateMgr = nil; |
|
|
fLogDrawer = nil; |
|
|
fDebugTextMgr = nil; |
|
|
fCurrLightingMethod = plSpan::kLiteMaterial; |
|
|
|
|
|
fCurrCullMode = D3DCULL_CW; |
|
|
fTexturing = false; |
|
|
fCurrNumLayers = 0; |
|
|
fLastEndingStage = -1; |
|
|
|
|
|
fSettings.Reset(); |
|
|
fStencil.Reset(); |
|
|
fTweaks.Reset(); |
|
|
fLights.Reset(this); |
|
|
fCurrFog.Reset(); |
|
|
fDeviceLost = false; |
|
|
fDevWasLost = false; |
|
|
|
|
|
fSettings.fCurrFVFFormat = 0; |
|
|
fDynVtxBuff = nil; |
|
|
fNextDynVtx = 0; |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
fLayerRef[i] = nil; |
|
|
|
|
|
IResetRenderTargetPools(); |
|
|
fULutTextureRef = nil; |
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
fBlurVBuffers[i] = nil; |
|
|
fBlurVSHandle = nil; |
|
|
|
|
|
fD3DObject = nil; |
|
|
fD3DDevice = nil; |
|
|
fD3DBackBuff = nil; |
|
|
fD3DDepthSurface = nil; |
|
|
fD3DMainSurface = nil; |
|
|
|
|
|
fSharedDepthSurface[0] = nil; |
|
|
fSharedDepthFormat[0] = D3DFMT_UNKNOWN; |
|
|
fSharedDepthSurface[1] = nil; |
|
|
fSharedDepthFormat[1] = D3DFMT_UNKNOWN; |
|
|
|
|
|
fCurrentMode = nil; |
|
|
fCurrentDriver = nil; |
|
|
fCurrentDevice = nil; |
|
|
|
|
|
fOverLayerStack.Reset(); |
|
|
fOverBaseLayer = nil; |
|
|
fOverAllLayer = nil; |
|
|
fPiggyBackStack.Reset(); |
|
|
fMatPiggyBacks = 0; |
|
|
fActivePiggyBacks = 0; |
|
|
|
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
fLayerState[i].Reset(); |
|
|
fOldLayerState[i].Reset(); |
|
|
} |
|
|
fMatOverOn.Reset(); |
|
|
fMatOverOff.Reset(); |
|
|
// SetMaterialOverride( hsGMatState::kShade, hsGMatState::kShadeSpecularHighlight, false ); |
|
|
|
|
|
fView.Reset(); |
|
|
|
|
|
fCullProxy = nil; |
|
|
|
|
|
fTime = 0; |
|
|
fFrame = 0; |
|
|
|
|
|
fInSceneDepth = 0; |
|
|
fTextUseTime = 0; |
|
|
fEvictTime = 0; |
|
|
fManagedSeen = 0; |
|
|
fManagedCutoff = 0; |
|
|
fRenderCnt = 0; |
|
|
|
|
|
fDebugFlags.Clear(); |
|
|
|
|
|
fForceMatHandle = true; |
|
|
fAvRTShrinkValidSince = 0; |
|
|
fAvRTWidth = 1024; |
|
|
fAvNextFreeRT = 0; |
|
|
} |
|
|
|
|
|
// plDXViewSettings are just a convenience member struct to segregate the current view settings. |
|
|
// |
|
|
// Reset - Initialize the ViewSettings to default (normal/neutral) values. |
|
|
void plDXViewSettings::Reset() |
|
|
{ |
|
|
// Normal render, on clear, clear the color buffer and depth buffer. |
|
|
fRenderState = plPipeline::kRenderNormal | plPipeline::kRenderClearColor | plPipeline::kRenderClearDepth; |
|
|
|
|
|
fRenderRequest = nil; |
|
|
|
|
|
fDrawableTypeMask = plDrawable::kNormal; |
|
|
fSubDrawableTypeMask = plDrawable::kSubNormal; |
|
|
|
|
|
// Clear color to black, depth to yon. |
|
|
fClearColor = 0; |
|
|
fClearDepth = 1.f; |
|
|
fDefaultFog.Clear(); |
|
|
|
|
|
// Want to limit the number of nodes in the cull tree. After adding so many nodes, |
|
|
// the benefits (#objects culled) falls off, but the cost (evaluating objects against |
|
|
// node planes) keeps rising. |
|
|
const UInt16 kCullMaxNodes = 250; |
|
|
fCullTree.Reset(); |
|
|
fCullTreeDirty = true; |
|
|
fCullMaxNodes = kCullMaxNodes; |
|
|
|
|
|
// Object Local to world transform and its inverse. |
|
|
fLocalToWorld.Reset(); |
|
|
fWorldToLocal.Reset(); |
|
|
|
|
|
// see Core/plViewTransform.h |
|
|
fTransform.Reset(); |
|
|
|
|
|
fTransform.SetScreenSize(800, 600); |
|
|
|
|
|
// Keep track of handedness of local to world and camera transform for winding. |
|
|
fLocalToWorldLeftHanded = false; |
|
|
fWorldToCamLeftHanded = false; |
|
|
} |
|
|
|
|
|
//// plDXGeneralSettings::Reset ////////////////////////////////////////////// |
|
|
// Catch all struct of general settings plus pointers to current d3d objects. |
|
|
|
|
|
void plDXGeneralSettings::Reset() |
|
|
{ |
|
|
fCurrVertexBuffRef = nil; |
|
|
fCurrIndexBuffRef = nil; |
|
|
fFullscreen = false; |
|
|
fHWnd = nil; |
|
|
fColorDepth = 32; |
|
|
fD3DCaps = 0; |
|
|
fBoardKluge = 0; |
|
|
fStageEnd = 0; |
|
|
fMaxNumLights = kD3DMaxTotalLights; |
|
|
fMaxNumProjectors = kMaxProjectors; |
|
|
fMaxLayersAtOnce = 1; |
|
|
fMaxPiggyBacks = 0; |
|
|
fBoundsDrawLevel = -1; |
|
|
|
|
|
fProperties = 0; |
|
|
fClearColor = 0; |
|
|
|
|
|
fNoGammaCorrect = false; |
|
|
fMaxUVWSrc = 8; |
|
|
fCantProj = false; |
|
|
fLimitedProj = false; |
|
|
fBadManaged = false; |
|
|
fShareDepth = false; |
|
|
fCurrAnisotropy = false; |
|
|
fIsIntel = false; |
|
|
|
|
|
fDXError = D3D_OK; |
|
|
memset( fErrorStr, 0, sizeof( fErrorStr ) ); |
|
|
|
|
|
fCurrRenderTarget = nil; |
|
|
fCurrBaseRenderTarget = nil; |
|
|
fCurrD3DMainSurface = nil; |
|
|
fCurrD3DDepthSurface = nil; |
|
|
fCurrRenderTargetRef = nil; |
|
|
|
|
|
fCurrFVFFormat = 0; |
|
|
fCurrVertexShader = nil; |
|
|
fCurrPixelShader = nil; |
|
|
|
|
|
fVeryAnnoyingTextureInvalidFlag = false; |
|
|
} |
|
|
|
|
|
//// IInitDeviceState ///////////////////////////////////////////////////////// |
|
|
// Initialize the device to a known state. This also syncs it up with our internal state |
|
|
// as recorded in the fLayerStates. |
|
|
// Some of these states reflect the caps of the device, but for the most part, the |
|
|
// important thing here is NOT what state we're in coming out of this function, but |
|
|
// that we are in a known state, and that the known state is recorded in fLayerStates. |
|
|
void plDXPipeline::IInitDeviceState() |
|
|
{ |
|
|
fLayerState[0].Reset(); |
|
|
fCurrCullMode = D3DCULL_CW; |
|
|
|
|
|
/// Set D3D states |
|
|
fCurrFog.Reset(); |
|
|
ISetFogParameters( nil, nil ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZENABLE, ( fSettings.fD3DCaps & kCapsWBuffer ) ? D3DZB_USEW : D3DZB_TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_CLIPPING, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_CULLMODE, fCurrCullMode ); |
|
|
ISetCullMode(); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHAREF, 0x00000001 ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_MULTISAMPLEANTIALIAS, ( fSettings.fD3DCaps & kCapsFSAntiAlias ) ? TRUE : FALSE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ANTIALIASEDLINEENABLE, FALSE ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_DITHERENABLE, ( fSettings.fD3DCaps & kCapsDither ) ? TRUE : FALSE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); |
|
|
fCurrD3DLiteState = false; |
|
|
fD3DDevice->SetRenderState( D3DRS_TEXTUREFACTOR, 0x0 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ); |
|
|
fD3DDevice->SetTransform( D3DTS_TEXTURE0, &d3dIdentityMatrix ); |
|
|
fD3DDevice->SetTransform( D3DTS_WORLD, &d3dIdentityMatrix ); |
|
|
|
|
|
/// NEW: to compensate for scaling transformations that might screw up our nicely |
|
|
/// normalized normals. Note: nVidia says this is as fast or faster than with |
|
|
/// this disabled, but who knows what it'll do on other cards... |
|
|
fD3DDevice->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_LOCALVIEWER, TRUE ); |
|
|
|
|
|
UInt32 totalMem = fD3DDevice->GetAvailableTextureMem(); |
|
|
plProfile_Set(TotalTexSize, totalMem); |
|
|
|
|
|
// Initialization for all 8 stages (even though we only use a few of them). |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
fLayerLODBias[ i ] = fTweaks.fDefaultLODBias; |
|
|
fLayerTransform[ i ] = false; |
|
|
fLayerRef[ i ] = nil; |
|
|
fLayerUVWSrcs[ i ] = i; |
|
|
fLayerState[ i ].Reset(); |
|
|
|
|
|
fD3DDevice->SetTexture( i, nil ); |
|
|
fD3DDevice->SetTextureStageState( i, D3DTSS_TEXCOORDINDEX, i ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MIPMAPLODBIAS, *(DWORD *)( &fLayerLODBias[ i ] ) ); |
|
|
|
|
|
if( fSettings.fMaxAnisotropicSamples > 0 && !IsDebugFlagSet(plPipeDbg::kFlagNoAnisotropy)) |
|
|
{ |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAXANISOTROPY, (DWORD)fSettings.fMaxAnisotropicSamples ); |
|
|
fSettings.fCurrAnisotropy = true; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); |
|
|
fSettings.fCurrAnisotropy = false; |
|
|
} |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR ); |
|
|
|
|
|
fD3DDevice->SetTransform( sTextureStages[ i ], &d3dIdentityMatrix ); |
|
|
fLayerXformFlags[ i ] = D3DTTFF_COUNT2; |
|
|
fD3DDevice->SetTextureStageState( i, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 ); |
|
|
} |
|
|
|
|
|
// Initialize our bump mapping matrices. |
|
|
for( i = 0; i < 4; i++ ) |
|
|
{ |
|
|
int j; |
|
|
for( j = 0; j < 4; j++ ) |
|
|
{ |
|
|
fBumpDuMatrix.fMap[i][j] = 0; |
|
|
fBumpDvMatrix.fMap[i][j] = 0; |
|
|
fBumpDwMatrix.fMap[i][j] = 0; |
|
|
|
|
|
} |
|
|
} |
|
|
fBumpDuMatrix.NotIdentity(); |
|
|
fBumpDvMatrix.NotIdentity(); |
|
|
fBumpDwMatrix.NotIdentity(); |
|
|
|
|
|
PushMaterialOverride( hsGMatState::kShade, hsGMatState::kShadeSpecularHighlight, false ); |
|
|
|
|
|
fLights.Reset(this); |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
//// ISetCaps ///////////////////////////////////////////////////////////////// |
|
|
// We've recorded the capabilities of the current device in fCurrentDevice (traditionally in the setup program), |
|
|
// now translate that into our own caps flags. |
|
|
void plDXPipeline::ISetCaps() |
|
|
{ |
|
|
fSettings.fD3DCaps = kCapsNone; |
|
|
|
|
|
// Set relevant caps (ones we can do something about). |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DEPTHBIAS ) |
|
|
fSettings.fD3DCaps |= kCapsZBias; |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGRANGE ) |
|
|
fSettings.fD3DCaps |= kCapsRangeFog; |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGTABLE ) |
|
|
fSettings.fD3DCaps |= kCapsLinearFog | kCapsExpFog | kCapsExp2Fog | kCapsPixelFog; |
|
|
else |
|
|
fSettings.fD3DCaps |= kCapsLinearFog; |
|
|
if( fCurrentDevice->fDDCaps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR ) |
|
|
fSettings.fD3DCaps |= kCapsMipmap; |
|
|
if( fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_MIPCUBEMAP ) |
|
|
fSettings.fD3DCaps |= kCapsCubicMipmap; |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WBUFFER ) |
|
|
fSettings.fD3DCaps |= kCapsWBuffer; |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DITHER ) |
|
|
fSettings.fD3DCaps |= kCapsDither; |
|
|
if( fSettings.fNumAASamples > 0 ) |
|
|
fSettings.fD3DCaps |= kCapsFSAntiAlias; |
|
|
if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WFOG ) |
|
|
fSettings.fD3DCaps |= kCapsDoesWFog; |
|
|
if( fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP ) |
|
|
fSettings.fD3DCaps |= kCapsCubicTextures; |
|
|
|
|
|
/// New 1.5.2000 - cull out mixed vertex processing |
|
|
if( fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT |
|
|
&& fCurrentMode->fDDBehavior == D3DCREATE_HARDWARE_VERTEXPROCESSING |
|
|
) |
|
|
fSettings.fD3DCaps |= kCapsHWTransform; |
|
|
|
|
|
|
|
|
// Currently always want d3d to transform |
|
|
fSettings.fD3DCaps |= kCapsHWTransform; |
|
|
|
|
|
/// Always assume we can do small textures (IRestrictCaps will turn this off |
|
|
/// if necessary) |
|
|
fSettings.fD3DCaps |= kCapsDoesSmallTextures; |
|
|
|
|
|
/// Look for supported texture formats |
|
|
if( IFindCompressedFormats() ) |
|
|
fSettings.fD3DCaps |= kCapsCompressTextures; |
|
|
if( IFindLuminanceFormats() ) |
|
|
fSettings.fD3DCaps |= kCapsLuminanceTextures; |
|
|
|
|
|
/// Max # of hardware lights |
|
|
fSettings.fMaxNumLights = fCurrentDevice->fDDCaps.MaxActiveLights; |
|
|
if( fSettings.fMaxNumLights > kD3DMaxTotalLights ) |
|
|
fSettings.fMaxNumLights = kD3DMaxTotalLights; |
|
|
|
|
|
// Intel Extreme chips report 0 lights, meaning T&L is done |
|
|
// in software, so you can have as many lights as you want. |
|
|
// We only need 8, so set that here. Also turn off shadows, |
|
|
// since the extreme can't really afford them, and record |
|
|
// the fact this is the extreme for other driver problem |
|
|
// workarounds. |
|
|
if( !fSettings.fMaxNumLights ) |
|
|
{ |
|
|
fSettings.fMaxNumLights = kD3DMaxTotalLights; |
|
|
fSettings.fIsIntel = true; |
|
|
plShadowCaster::SetCanShadowCast(false); |
|
|
} |
|
|
|
|
|
/// Max # of textures at once |
|
|
fSettings.fMaxLayersAtOnce = fCurrentDevice->fDDCaps.MaxSimultaneousTextures; |
|
|
if( fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_SEPARATETEXTUREMEMORIES ) |
|
|
fSettings.fMaxLayersAtOnce = 1; |
|
|
// Alloc half our simultaneous textures to piggybacks. |
|
|
// Won't hurt us unless we try to many things at once. |
|
|
fSettings.fMaxPiggyBacks = fSettings.fMaxLayersAtOnce >> 1; |
|
|
|
|
|
// Less than 4 layers at once means we have to fallback on uv bumpmapping |
|
|
if (fSettings.fMaxLayersAtOnce < 4) |
|
|
SetDebugFlag(plPipeDbg::kFlagBumpUV, true); |
|
|
|
|
|
fSettings.fMaxAnisotropicSamples = (UInt8)(fCurrentDevice->fDDCaps.MaxAnisotropy); |
|
|
|
|
|
fSettings.fNoGammaCorrect = !(fCurrentDevice->fDDCaps.Caps2 & D3DCAPS2_FULLSCREENGAMMA); |
|
|
|
|
|
if (!(fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_PROJECTED)) |
|
|
plDynamicCamMap::SetCapable(false); |
|
|
|
|
|
ISetGraphicsCapability(fCurrentDevice->fDDCaps.PixelShaderVersion); |
|
|
} |
|
|
|
|
|
// ISetGraphicsCapability /////////////////////////////////////////////////////// |
|
|
// Tell our global quality settings what we can do. We'll use this to only load |
|
|
// versions we can render. So if we can render it, we load it and skip its low quality substitute, |
|
|
// if we can't render it, we skip it and load its low quality substitute. |
|
|
// Naturally, this must happen before we do any loading. |
|
|
void plDXPipeline::ISetGraphicsCapability(UInt32 v) |
|
|
{ |
|
|
int pixelMajor = D3DSHADER_VERSION_MAJOR(v); |
|
|
int pixelMinor = D3DSHADER_VERSION_MINOR(v); |
|
|
if( pixelMajor > 1 ) |
|
|
{ |
|
|
plQuality::SetCapability(plQuality::kPS_2_Plus); |
|
|
} |
|
|
else if( pixelMajor > 0 ) |
|
|
{ |
|
|
if( pixelMinor >= 4 ) |
|
|
plQuality::SetCapability(plQuality::kPS_1_4); |
|
|
else if( pixelMinor > 0 ) |
|
|
plQuality::SetCapability(plQuality::kPS_1_1); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IRestrictCaps //////////////////////////////////////////////////////////// |
|
|
// ISetCaps() sets our native caps based on the D3D caps bits D3D returns. |
|
|
// IRestrictCaps looks at our hsG3DDeviceSelector flags and translates those |
|
|
// into our runtime native caps. |
|
|
// The DeviceSelector flags aren't set by what the board claims, but rather |
|
|
// we try to identify the board and set them according to previous knowledge. |
|
|
// For example, the ATI7500 will only use uvw coordinates 0 or 1. There's |
|
|
// no d3d cap to reflect this, and it really should support [0..7], but |
|
|
// there's no way to force it to be d3d compliant. So when we see we have |
|
|
// an ATI7500, we set the cap kCapsMaxUVWSrc2. |
|
|
// See hsG3DDeviceSelector.cpp for details and implementation. |
|
|
void plDXPipeline::IRestrictCaps( const hsG3DDeviceRecord& devRec ) |
|
|
{ |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsMipmap ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsMipmap; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsCubicMipmap ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsCubicMipmap; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsWBuffer ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsWBuffer; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsZBias ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsZBias; |
|
|
// if( !devRec.GetCap( hsG3DDeviceSelector::kCapsHWTransform ) ) |
|
|
// fSettings.fD3DCaps &= ~kCapsHWTransform; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsDither ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsDither; |
|
|
// if( devRec.GetAASetting() == 0 ) |
|
|
// fSettings.fD3DCaps &= ~kCapsFSAntiAlias; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsFogExp ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsExpFog; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsCubicTextures ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsCubicTextures; |
|
|
|
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsCantShadow) ) |
|
|
plShadowCaster::SetCanShadowCast(false); |
|
|
|
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsCantProj) ) |
|
|
fSettings.fCantProj = true; |
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsLimitedProj) ) |
|
|
fSettings.fLimitedProj = true; |
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsBadManaged) ) |
|
|
fSettings.fBadManaged = true; |
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsShareDepth) ) |
|
|
fSettings.fShareDepth = true; |
|
|
|
|
|
/// Added 9.6.2000 mcn - shouldn't they be here anyway? |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsFogExp2 ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsExp2Fog; |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsDoesSmallTextures ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsDoesSmallTextures; |
|
|
|
|
|
/// 9.22.2000 mcn - dFlag for bad (savage4) yon fix |
|
|
if( devRec.GetCap( hsG3DDeviceSelector::kCapsBadYonStuff ) ) |
|
|
fSettings.fD3DCaps |= kCapsHasBadYonStuff; |
|
|
|
|
|
/// 10.31.2000 mcn - Flag for can't-handle-under-8-pixel-dimensions-on-textures |
|
|
/// (see, isn't the name flag actually better in retrospect? :) |
|
|
if( devRec.GetCap( hsG3DDeviceSelector::kCapsNoKindaSmallTexs ) ) |
|
|
fSettings.fD3DCaps |= kCapsNoKindaSmallTexs; |
|
|
|
|
|
/// Note: the following SHOULD be here, but we later detect for texture |
|
|
/// formats and reset this flag. It should only be set if it is set already, |
|
|
/// but that means ensuring it's set beforehand, which it might not be. |
|
|
if( !devRec.GetCap( hsG3DDeviceSelector::kCapsCompressTextures ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsCompressTextures; |
|
|
|
|
|
/// Set up tweaks |
|
|
SetZBiasScale( (float)devRec.GetZBiasRating() ); |
|
|
fTweaks.fDefaultLODBias = (float)-( 0.25 + (float)devRec.GetLODBiasRating() ); |
|
|
devRec.GetFogApproxStarts( fTweaks.fFogExpApproxStart, fTweaks.fFogExp2ApproxStart ); |
|
|
fTweaks.fFogEndBias = (float)devRec.GetFogEndBias(); |
|
|
|
|
|
// Fog knee stuff |
|
|
devRec.GetFogKneeParams( hsG3DDeviceRecord::kFogExp, fTweaks.fExpFogKnee, fTweaks.fExpFogKneeVal ); |
|
|
devRec.GetFogKneeParams( hsG3DDeviceRecord::kFogExp2, fTweaks.fExp2FogKnee, fTweaks.fExp2FogKneeVal ); |
|
|
|
|
|
// Max # of layers |
|
|
UInt32 max = devRec.GetLayersAtOnce(); |
|
|
if( max > 0 && max < fSettings.fMaxLayersAtOnce ) |
|
|
fSettings.fMaxLayersAtOnce = max; |
|
|
|
|
|
/// Debug flag to force high-level cards down to GeForce 2 caps |
|
|
if( fDbgSetupInitFlags & 0x00000004 ) |
|
|
{ |
|
|
fSettings.fD3DCaps &= ~kCapsFSAntiAlias; |
|
|
if( fSettings.fMaxLayersAtOnce > 2 ) |
|
|
fSettings.fMaxLayersAtOnce = 2; |
|
|
fSettings.fMaxAnisotropicSamples = 0; |
|
|
|
|
|
plQuality::SetCapability(plQuality::kMinimum); |
|
|
} |
|
|
|
|
|
// There's a bug in NVidia drivers on Windows 2000 for GeForce1-4 (all flavors, including MX). |
|
|
// When the amount allocated into managed memory approaches the on board memory size, the performance |
|
|
// severely degrades, no matter how little is actually in use in the current rendering. So say all |
|
|
// our d3d textures are created into managed memory at age load. Also say you are |
|
|
// consistently viewing only 5Mb of managed materials (texture + vertex buffer). So as |
|
|
// you walk through the age, the new textures you see get loaded on demand into video memory. |
|
|
// Once you've seen enough to fill the on board memory, your frame rate starts falling and |
|
|
// continues to fall as more textures get loaded. So either the memory manager is not letting |
|
|
// go of LRU textures, or fragmentation is so horrible as to make the manager useless. |
|
|
// So on these boards and with this OS, we keep track of how much managed memory we've seen, |
|
|
// and when it reaches a threshhold, we flush managed memory with an EvictManagedResources() call. |
|
|
// There's an unfortunate glitch, and then the frame rate is fine again. |
|
|
// So if we need this workaround, we set fManagedCutoff to 1 here, and then once we have our |
|
|
// D3D device, we query for the amount of memory and set the threshhold for flushing memory |
|
|
// based on that. |
|
|
OSVERSIONINFO osinfo; |
|
|
memset(&osinfo, 0, sizeof(osinfo)); |
|
|
osinfo.dwOSVersionInfoSize = sizeof(osinfo); |
|
|
GetVersionEx(&osinfo); |
|
|
if( (osinfo.dwMajorVersion == 5) |
|
|
&&(osinfo.dwMinorVersion == 0) ) |
|
|
{ |
|
|
// It's the dreaded win2k |
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsDoubleFlush) ) |
|
|
fManagedCutoff = 1; |
|
|
else if( devRec.GetCap(hsG3DDeviceSelector::kCapsSingleFlush) ) |
|
|
fManagedCutoff = 1; |
|
|
} |
|
|
|
|
|
//// Our temp debug flag to force z-buffering... |
|
|
if( !( fDbgSetupInitFlags & 0x00000001 ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsWBuffer; |
|
|
|
|
|
/// Set up the z-bias scale values, based on z- or w-buffering |
|
|
if( fSettings.fD3DCaps & kCapsWBuffer ) |
|
|
fTweaks.fDefaultPerspLayerScale = kPerspLayerScaleW; |
|
|
else |
|
|
fTweaks.fDefaultPerspLayerScale = kPerspLayerScale; |
|
|
|
|
|
|
|
|
// Less than 4 layers at once means we have to fallback on uv bumpmapping |
|
|
if( fSettings.fMaxLayersAtOnce < 4 ) |
|
|
SetDebugFlag(plPipeDbg::kFlagBumpUV, true); |
|
|
|
|
|
if( ( fSettings.fD3DCaps & kCapsHWTransform ) && ( fCurrentMode->fDDBehavior == D3DCREATE_SOFTWARE_VERTEXPROCESSING ) ) |
|
|
fSettings.fD3DCaps &= ~kCapsHWTransform; |
|
|
|
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsMaxUVWSrc2) ) |
|
|
fSettings.fMaxUVWSrc = 2; |
|
|
|
|
|
/// Anisotropy stuff |
|
|
//if( devRec.GetMaxAnisotropicSamples() < fSettings.fMaxAnisotropicSamples ) |
|
|
// fSettings.fMaxAnisotropicSamples = devRec.GetMaxAnisotropicSamples(); |
|
|
if( devRec.GetCap(hsG3DDeviceSelector::kCapsNoAniso) || (fSettings.fMaxAnisotropicSamples <= 1) ) |
|
|
fSettings.fMaxAnisotropicSamples = 0; |
|
|
} |
|
|
|
|
|
//// Get/SetZBiasScale //////////////////////////////////////////////////////// |
|
|
// If the board really doesn't support Z-biasing, we adjust the perspective matrix in IGetCameraToNDC |
|
|
// The layer scale and translation are tailored to the current hardware. |
|
|
hsScalar plDXPipeline::GetZBiasScale() const |
|
|
{ |
|
|
return ( fTweaks.fPerspLayerScale / fTweaks.fDefaultPerspLayerScale ) - 1.0f; |
|
|
} |
|
|
|
|
|
void plDXPipeline::SetZBiasScale( hsScalar scale ) |
|
|
{ |
|
|
scale += 1.0f; |
|
|
fTweaks.fPerspLayerScale = fTweaks.fDefaultPerspLayerScale * scale; |
|
|
fTweaks.fPerspLayerTrans = kPerspLayerTrans * scale; |
|
|
} |
|
|
|
|
|
// Create all our video memory consuming D3D objects. |
|
|
hsBool plDXPipeline::ICreateDynDeviceObjects() |
|
|
{ |
|
|
// Front/Back/Depth buffers |
|
|
if( ICreateNormalSurfaces() ) |
|
|
return true; |
|
|
|
|
|
// RenderTarget pools are shared for our shadow generation algorithm. |
|
|
// Different sizes for different resolutions. |
|
|
IMakeRenderTargetPools(); |
|
|
|
|
|
// Create device-specific stuff |
|
|
fDebugTextMgr = TRACKED_NEW plDebugTextManager(); |
|
|
if( fDebugTextMgr == nil ) |
|
|
return true; |
|
|
|
|
|
// Vertex buffers, index buffers, textures, etc. |
|
|
LoadResources(); |
|
|
|
|
|
return false; |
|
|
} |
|
|
//// ICreateDeviceObjects ///////////////////////////////////////////////////// |
|
|
// Create all of our steady state D3D objects. More D3D objects will be created |
|
|
// and destroyed as ages are loaded and unloaded, but these are the things that |
|
|
// only go away when we lose the device. |
|
|
|
|
|
hsBool plDXPipeline::ICreateDeviceObjects() |
|
|
{ |
|
|
// The D3D device |
|
|
if( ICreateDevice(!fSettings.fFullscreen) ) |
|
|
return true; |
|
|
|
|
|
// Most everything else D3D |
|
|
if( ICreateDynDeviceObjects() ) |
|
|
return true; |
|
|
|
|
|
// PlateMgr is largely for debugging and performance stats, |
|
|
// but also gets used for some things like the cursor and |
|
|
// linking fade to/from black. |
|
|
fPlateMgr = TRACKED_NEW plDXPlateManager( this, fD3DDevice ); |
|
|
if( fPlateMgr == nil || !fPlateMgr->IsValid() ) |
|
|
return true; |
|
|
|
|
|
// We've got everything created now, initialize to a known state. |
|
|
IInitDeviceState(); |
|
|
if( FAILED( fD3DDevice->Clear( 0, nil, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, fSettings.fClearColor, 1.0f, 0L ) ) ) |
|
|
return true; |
|
|
|
|
|
// You may be wondering what this is. It's a workaround for a GeForce2 driver bug, where |
|
|
// clears to the Zbuffer (but not color) are getting partially ignored. Don't even ask. |
|
|
// So this is just to try and get the board used to the kind of foolishness it can expect |
|
|
// from here out. |
|
|
if( FAILED( fD3DDevice->Clear( 0, nil, D3DCLEAR_ZBUFFER, fSettings.fClearColor, 1.0f, 0L ) ) ) |
|
|
return true; |
|
|
if( FAILED( fD3DDevice->Clear( 0, nil, D3DCLEAR_ZBUFFER, fSettings.fClearColor, 1.0f, 0L ) ) ) |
|
|
return true; |
|
|
if( FAILED( fD3DDevice->Clear( 0, nil, D3DCLEAR_ZBUFFER, fSettings.fClearColor, 1.0f, 0L ) ) ) |
|
|
return true; |
|
|
|
|
|
/// Log renderer |
|
|
fLogDrawer = TRACKED_NEW plStatusLogDrawer( this ); |
|
|
plStatusLogMgr::GetInstance().SetDrawer( fLogDrawer ); |
|
|
|
|
|
/// Ok, we're done now |
|
|
#if MCN_BOUNDS_SPANS |
|
|
fBoundsSpans = TRACKED_NEW plDrawableSpans(); |
|
|
hsgResMgr::ResMgr()->NewKey( "BoundsSpans", fBoundsSpans, plLocation::kGlobalFixedLoc ); |
|
|
fBoundsSpans->SetNativeProperty( plDrawable::kPropVolatile, true ); |
|
|
fBoundsMat = TRACKED_NEW hsGMaterial(); |
|
|
hsgResMgr::ResMgr()->NewKey( "BoundsMaterial", fBoundsMat, plLocation::kGlobalFixedLoc ); |
|
|
plLayer *lay = fBoundsMat->MakeBaseLayer(); |
|
|
lay->SetMiscFlags( hsGMatState::kMiscWireFrame | hsGMatState::kMiscTwoSided ); |
|
|
lay->SetShadeFlags( lay->GetShadeFlags() | hsGMatState::kShadeWhite ); |
|
|
|
|
|
// Set up a ref to these. Since we don't have a key, we use the |
|
|
// generic RefObject() (and matching UnRefObject() when we're done). |
|
|
// If we had a key, we would use myKey->AddViaNotify(otherKey) and myKey->Release(otherKey). |
|
|
fBoundsMat->GetKey()->RefObject(); |
|
|
fBoundsSpans->GetKey()->RefObject(); |
|
|
#endif |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
//// ISetCurrentDriver //////////////////////////////////////////////////////// |
|
|
// Copy over the driver info. |
|
|
void plDXPipeline::ISetCurrentDriver( D3DEnum_DriverInfo *driv ) |
|
|
{ |
|
|
if( fCurrentDriver != nil ) |
|
|
delete fCurrentDriver; |
|
|
|
|
|
fCurrentDriver = TRACKED_NEW D3DEnum_DriverInfo; |
|
|
|
|
|
fCurrentDriver->fGuid = driv->fGuid; |
|
|
hsStrncpy( fCurrentDriver->fStrDesc, driv->fStrDesc, 40 ); |
|
|
hsStrncpy( fCurrentDriver->fStrName, driv->fStrName, 40 ); |
|
|
|
|
|
fCurrentDriver->fDesktopMode = driv->fDesktopMode; |
|
|
fCurrentDriver->fAdapterInfo = driv->fAdapterInfo; |
|
|
|
|
|
fCurrentDriver->fCurrentMode = nil; |
|
|
fCurrentDriver->fCurrentDevice = nil; |
|
|
|
|
|
/// Go looking for an adapter to match this one |
|
|
UINT iAdapter; |
|
|
for( fCurrentAdapter = 0, iAdapter = 0; iAdapter < fD3DObject->GetAdapterCount(); iAdapter++ ) |
|
|
{ |
|
|
D3DADAPTER_IDENTIFIER9 adapterInfo; |
|
|
fD3DObject->GetAdapterIdentifier( iAdapter, 0, &adapterInfo ); |
|
|
|
|
|
if( adapterInfo.DeviceIdentifier == fCurrentDriver->fAdapterInfo.DeviceIdentifier ) |
|
|
{ |
|
|
fCurrentAdapter = iAdapter; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
//// ISetCurrentDevice //////////////////////////////////////////////////////// |
|
|
// Copy over the device info. |
|
|
void plDXPipeline::ISetCurrentDevice( D3DEnum_DeviceInfo *dev ) |
|
|
{ |
|
|
if( fCurrentDevice != nil ) |
|
|
delete fCurrentDevice; |
|
|
fCurrentDevice = TRACKED_NEW D3DEnum_DeviceInfo; |
|
|
|
|
|
hsStrncpy( fCurrentDevice->fStrName, dev->fStrName, 40 ); |
|
|
|
|
|
fCurrentDevice->fDDCaps = dev->fDDCaps; |
|
|
fCurrentDevice->fDDType = dev->fDDType; |
|
|
fCurrentDevice->fIsHardware = dev->fIsHardware; |
|
|
fCurrentDevice->fCanWindow = dev->fCanWindow; |
|
|
// fCurrentDevice->fCanAntialias = dev->fCanAntialias; |
|
|
fCurrentDevice->fCompatibleWithDesktop = dev->fCompatibleWithDesktop; |
|
|
|
|
|
// copy over supported device modes |
|
|
D3DEnum_ModeInfo currMode; |
|
|
|
|
|
for(int i = 0; i < dev->fModes.Count(); i++) |
|
|
{ |
|
|
// filter unusable modes |
|
|
if(dev->fModes[i].fDDmode.Width < MIN_WIDTH || dev->fModes[i].fDDmode.Height < MIN_HEIGHT) |
|
|
continue; |
|
|
|
|
|
currMode.fBitDepth = dev->fModes[i].fBitDepth; |
|
|
currMode.fCanRenderToCubic = dev->fModes[i].fCanRenderToCubic; |
|
|
currMode.fDDBehavior = dev->fModes[i].fDDBehavior; |
|
|
currMode.fDepthFormats = dev->fModes[i].fDepthFormats; |
|
|
currMode.fFSAATypes = dev->fModes[i].fFSAATypes; |
|
|
memcpy(&currMode.fDDmode, &dev->fModes[i].fDDmode, sizeof(D3DDISPLAYMODE)); |
|
|
strcpy(currMode.fStrDesc, dev->fModes[i].fStrDesc); |
|
|
currMode.fWindowed = dev->fModes[i].fWindowed; |
|
|
|
|
|
fCurrentDevice->fModes.Push(currMode); |
|
|
} |
|
|
} |
|
|
|
|
|
//// ISetCurrentMode ////////////////////////////////////////////////////////// |
|
|
// Copy over the mode info. |
|
|
void plDXPipeline::ISetCurrentMode( D3DEnum_ModeInfo *mode ) |
|
|
{ |
|
|
if( fCurrentMode != nil ) |
|
|
delete fCurrentMode; |
|
|
fCurrentMode = TRACKED_NEW D3DEnum_ModeInfo; |
|
|
|
|
|
*fCurrentMode = *mode; |
|
|
} |
|
|
|
|
|
//// IFindCompressedFormats /////////////////////////////////////////////////// |
|
|
// |
|
|
// New DX Way: Check to see if each format is valid. |
|
|
|
|
|
hsBool plDXPipeline::IFindCompressedFormats() |
|
|
{ |
|
|
D3DFORMAT toCheckFor[] = {D3DFMT_DXT1, |
|
|
//D3DFMT_DXT2, |
|
|
//D3DFMT_DXT3, |
|
|
//D3DFMT_DXT4, |
|
|
D3DFMT_DXT5, |
|
|
D3DFMT_UNKNOWN }; |
|
|
short i = 0; |
|
|
|
|
|
|
|
|
for( i = 0; toCheckFor[ i ] != D3DFMT_UNKNOWN; i++ ) |
|
|
{ |
|
|
if( FAILED( fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, |
|
|
fCurrentMode->fDDmode.Format, |
|
|
0, D3DRTYPE_TEXTURE, toCheckFor[ i ] ) ) ) |
|
|
return false; |
|
|
} |
|
|
|
|
|
/// Got here, must have found them all |
|
|
return true; |
|
|
} |
|
|
|
|
|
//// IFindLuminanceFormats //////////////////////////////////////////////////// |
|
|
// |
|
|
// New DX Way: Check to see if each format we want is valid |
|
|
|
|
|
hsBool plDXPipeline::IFindLuminanceFormats() |
|
|
{ |
|
|
D3DFORMAT toCheckFor[] = { D3DFMT_L8, D3DFMT_A8L8, D3DFMT_UNKNOWN }; |
|
|
short i = 0; |
|
|
|
|
|
|
|
|
for( i = 0; toCheckFor[ i ] != D3DFMT_UNKNOWN; i++ ) |
|
|
{ |
|
|
if( FAILED( fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, |
|
|
fCurrentMode->fDDmode.Format, |
|
|
0, D3DRTYPE_TEXTURE, toCheckFor[ i ] ) ) ) |
|
|
return false; |
|
|
} |
|
|
|
|
|
/// Got here, must have found them all |
|
|
return true; |
|
|
} |
|
|
|
|
|
//// ITextureFormatAllowed //////////////////////////////////////////////////// |
|
|
// |
|
|
// Returns true if the given format is supported on the current device and |
|
|
// mode, false if it isn't. |
|
|
|
|
|
hsBool plDXPipeline::ITextureFormatAllowed( D3DFORMAT format ) |
|
|
{ |
|
|
if( FAILED( fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, |
|
|
fCurrentMode->fDDmode.Format, |
|
|
0, D3DRTYPE_TEXTURE, format ) ) ) |
|
|
return false; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
//// SetDebugFlag ///////////////////////////////////////////////////////////// |
|
|
// Debug flags should never be employed to do a game effect, although they can |
|
|
// be useful for developing effects. Mostly they help in diagnosing problems |
|
|
// in rendering or performance. |
|
|
void plDXPipeline::SetDebugFlag( UInt32 flag, hsBool on ) |
|
|
{ |
|
|
fDebugFlags.SetBit(flag, on); |
|
|
|
|
|
if (flag == plPipeDbg::kFlagColorizeMipmaps) |
|
|
{ |
|
|
// Force textures to reload |
|
|
plDXTextureRef *ref = fTextureRefList; |
|
|
while( ref != nil ) |
|
|
{ |
|
|
ref->SetDirty( true ); |
|
|
ref = ref->GetNext(); |
|
|
} |
|
|
|
|
|
// Reset mipmap filtering state (usually is LINEAR, but we set it to POINT for coloring) |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MIPFILTER, on ? D3DTEXF_POINT : D3DTEXF_LINEAR ); |
|
|
} |
|
|
|
|
|
if (flag == plPipeDbg::kFlagNoAnisotropy) |
|
|
{ |
|
|
ISetAnisotropy(!on); |
|
|
} |
|
|
} |
|
|
|
|
|
hsBool plDXPipeline::IsDebugFlagSet( UInt32 flag ) const |
|
|
{ |
|
|
return fDebugFlags.IsBitSet(flag); |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Device Creation ////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// ICreateMaster //////////////////////////////////////////////////////////// |
|
|
// Creates the master Direct3D objects. I guess just in case you want |
|
|
// multiple Direct3D devices.... :~ |
|
|
|
|
|
hsBool plDXPipeline::ICreateMaster() |
|
|
{ |
|
|
hsAssert( !fD3DObject, "ICreateMaster() should only be called for Master Direct3DDevice" ); |
|
|
|
|
|
/// The new DirectX Way: Create a Direct3D object, out of which everything else springs |
|
|
if( hsGDDrawDllLoad::GetD3DDll() == nil ) |
|
|
return ICreateFail( "Cannot load Direct3D driver!" ); |
|
|
|
|
|
Direct3DCreateProc procPtr; |
|
|
procPtr = (Direct3DCreateProc)GetProcAddress( hsGDDrawDllLoad::GetD3DDll(), "Direct3DCreate9" ); |
|
|
if( procPtr == nil ) |
|
|
return ICreateFail( "Cannot load D3D Create Proc!" ); |
|
|
|
|
|
// Create a D3D object to use |
|
|
fD3DObject = procPtr( D3D_SDK_VERSION ); |
|
|
|
|
|
if( fD3DObject == nil ) |
|
|
return ICreateFail( "Cannot create Direct3D object" ); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
//// ICreateDevice //////////////////////////////////////////////////// |
|
|
// |
|
|
// Creates the device. Surfaces, buffers, etc. created separately (in case of lost device). |
|
|
// See ICreateDeviceObjects. |
|
|
|
|
|
hsBool plDXPipeline::ICreateDevice(hsBool windowed) |
|
|
{ |
|
|
/// First, create the D3D Device object |
|
|
D3DPRESENT_PARAMETERS params; |
|
|
D3DDISPLAYMODE dispMode; |
|
|
int i; |
|
|
#ifdef DBG_WRITE_FORMATS |
|
|
char msg[ 256 ]; |
|
|
#endif // DBG_WRITE_FORMATS |
|
|
|
|
|
INIT_ERROR_CHECK( fD3DObject->GetAdapterDisplayMode( fCurrentAdapter, &dispMode ), |
|
|
"Cannot get desktop display mode" ); |
|
|
|
|
|
// save desktop properties |
|
|
fDesktopParams.Width = dispMode.Width; |
|
|
fDesktopParams.Height = dispMode.Height; |
|
|
fDesktopParams.ColorDepth = GetDXBitDepth( dispMode.Format ); |
|
|
|
|
|
|
|
|
if( windowed ) |
|
|
{ |
|
|
// Reset fColor, since we're getting the desktop bitdepth |
|
|
fSettings.fColorDepth = GetDXBitDepth( dispMode.Format ); |
|
|
if(fSettings.fOrigWidth > fDesktopParams.Width || fSettings.fOrigHeight > fDesktopParams.Height) |
|
|
{ |
|
|
fSettings.fOrigWidth = fDesktopParams.Width; |
|
|
fSettings.fOrigHeight = fDesktopParams.Height; |
|
|
IGetViewTransform().SetScreenSize(fDesktopParams.Width, fDesktopParams.Height); |
|
|
} |
|
|
} |
|
|
|
|
|
memset( ¶ms, 0, sizeof( params ) ); |
|
|
params.Windowed = ( windowed ? TRUE : FALSE ); |
|
|
params.Flags = 0;//D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; |
|
|
params.BackBufferCount = 1; |
|
|
params.BackBufferWidth = GetViewTransform().GetScreenWidth(); |
|
|
params.BackBufferHeight = GetViewTransform().GetScreenHeight(); |
|
|
params.EnableAutoDepthStencil = TRUE; |
|
|
|
|
|
// NOTE: This was changed 5.29.2001 mcn to avoid the nasty flashing bug on nVidia's 12.60 beta drivers |
|
|
// SWAPEFFECT must be _DISCARD when using antialiasing, so we'll just go with _DISCARD for the time being. mf |
|
|
params.SwapEffect = D3DSWAPEFFECT_DISCARD; |
|
|
params.FullScreen_RefreshRateInHz = ( windowed ? 0 : D3DPRESENT_RATE_DEFAULT ); |
|
|
if(windowed) |
|
|
{ |
|
|
params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; |
|
|
} |
|
|
else |
|
|
{ |
|
|
params.PresentationInterval = ( fVSync ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE ); |
|
|
} |
|
|
|
|
|
#ifdef DBG_WRITE_FORMATS |
|
|
for( i = 0; i < fCurrentMode->fDepthFormats.GetCount(); i++ ) |
|
|
{ |
|
|
sprintf( msg, "-- Valid depth buffer format: %s", IGetDXFormatName( fCurrentMode->fDepthFormats[ i ] ) ); |
|
|
hsDebugMessage( msg, 0 ); |
|
|
} |
|
|
#endif |
|
|
|
|
|
// Attempt to find the closest AA setting we can |
|
|
params.MultiSampleType = D3DMULTISAMPLE_NONE; |
|
|
for( i = fSettings.fNumAASamples; i >= 2; i-- ) |
|
|
{ |
|
|
if( fCurrentMode->fFSAATypes.Find( (D3DMULTISAMPLE_TYPE)i ) != fCurrentMode->fFSAATypes.kMissingIndex ) |
|
|
{ |
|
|
params.MultiSampleType = (D3DMULTISAMPLE_TYPE)i; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
if( !IFindDepthFormat(params) ) |
|
|
{ |
|
|
// If we haven't found a depth format, turn off multisampling and try it again. |
|
|
params.MultiSampleType = D3DMULTISAMPLE_NONE; |
|
|
if( !IFindDepthFormat(params) ) |
|
|
// Okay, we're screwed here, we might as well bail. |
|
|
return ICreateFail( "Can't find a Depth Buffer format" ); |
|
|
} |
|
|
|
|
|
/// TEMP HACK--if we're running 16-bit z-buffer or below, use our z-bias (go figure, it works better |
|
|
/// in 16-bit, worse in 24 and 32) |
|
|
if( params.AutoDepthStencilFormat == D3DFMT_D15S1 || |
|
|
params.AutoDepthStencilFormat == D3DFMT_D16 || |
|
|
params.AutoDepthStencilFormat == D3DFMT_D16_LOCKABLE ) |
|
|
fSettings.fD3DCaps &= ~kCapsZBias; |
|
|
|
|
|
#ifdef DBG_WRITE_FORMATS |
|
|
sprintf( msg, "-- Requesting depth buffer format: %s", IGetDXFormatName( params.AutoDepthStencilFormat ) ); |
|
|
hsDebugMessage( msg, 0 ); |
|
|
#endif |
|
|
|
|
|
|
|
|
params.BackBufferFormat = ( windowed ? dispMode.Format : fCurrentMode->fDDmode.Format ); |
|
|
#ifdef DBG_WRITE_FORMATS |
|
|
sprintf( msg, "-- Requesting back buffer format: %s", IGetDXFormatName( params.BackBufferFormat ) ); |
|
|
hsDebugMessage( msg, 0 ); |
|
|
#endif |
|
|
|
|
|
params.hDeviceWindow = fSettings.fHWnd; |
|
|
|
|
|
// Enable this to switch to a pure device. |
|
|
// fCurrentMode->fDDBehavior |= D3DCREATE_PUREDEVICE; |
|
|
// fCurrentMode->fDDBehavior |= D3DCREATE_DISABLE_DRIVER_MANAGEMENT; |
|
|
|
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
|
UINT adapter; |
|
|
for (adapter = 0; adapter < fD3DObject->GetAdapterCount(); adapter++) |
|
|
{ |
|
|
D3DADAPTER_IDENTIFIER9 id; |
|
|
fD3DObject->GetAdapterIdentifier(adapter, 0, &id); |
|
|
|
|
|
// We should be matching against "NVIDIA NVPerfHUD", but the space |
|
|
// in the description seems to be bogus. This seems to be a fair |
|
|
// alternative |
|
|
if (strstr(id.Description, "NVPerfHUD")) |
|
|
{ |
|
|
// This won't actually use the REF device, but we ask for |
|
|
// it as part of the handshake to let NVPerfHUD know we give |
|
|
// it permission to analyze us. |
|
|
fCurrentAdapter = adapter; |
|
|
fCurrentDevice->fDDType= D3DDEVTYPE_REF; |
|
|
SetDebugFlag(plPipeDbg::kFlagNVPerfHUD, true); |
|
|
break; |
|
|
} |
|
|
} |
|
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
INIT_ERROR_CHECK( fD3DObject->CreateDevice( fCurrentAdapter, fCurrentDevice->fDDType, |
|
|
fSettings.fHWnd, fCurrentMode->fDDBehavior, |
|
|
¶ms, &fD3DDevice ), |
|
|
"Cannot create primary display surface via CreateDevice()" ); |
|
|
|
|
|
fSettings.fPresentParams = params; |
|
|
|
|
|
// This bit matches up with the fManagedCutoff workaround for a problem |
|
|
// with the NVidia drivers on win2k. Search for "GetVersionEx" in IRestrictCaps |
|
|
// for more info. |
|
|
UInt32 mem = fD3DDevice->GetAvailableTextureMem(); |
|
|
plProfile_IncCount(TexTot, mem); |
|
|
|
|
|
const UInt32 kSingleFlush(40000000); |
|
|
const UInt32 kDoubleFlush(24000000); |
|
|
if( fManagedCutoff ) |
|
|
{ |
|
|
if( mem < 64000000 ) |
|
|
fManagedCutoff = kDoubleFlush; |
|
|
else |
|
|
fManagedCutoff = kSingleFlush; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
// IFindDepthFormat ////////////////////////////////////////////////////////////// |
|
|
// Look through available depth formats for the closest to what we want that |
|
|
// will work. |
|
|
hsBool plDXPipeline::IFindDepthFormat(D3DPRESENT_PARAMETERS& params) |
|
|
{ |
|
|
// Okay, we're not using the stencil buffer right now, and it's bringing out |
|
|
// some painful driver bugs on the GeForce2. So rather than go out of our way |
|
|
// looking for trouble, we're going to look for a depth buffer with NO STENCIL. |
|
|
int i; |
|
|
for( i = fCurrentMode->fDepthFormats.GetCount() - 1; i >= 0; i-- ) |
|
|
{ |
|
|
D3DFORMAT fmt = fCurrentMode->fDepthFormats[ i ]; |
|
|
if( (fmt == D3DFMT_D32) |
|
|
||(fmt == D3DFMT_D24X8) |
|
|
||(fmt == D3DFMT_D16) ) |
|
|
{ |
|
|
HRESULT hr = fD3DObject->CheckDeviceMultiSampleType(fCurrentAdapter, |
|
|
fCurrentDevice->fDDType, |
|
|
fmt, |
|
|
fCurrentMode->fWindowed ? TRUE : FALSE, |
|
|
params.MultiSampleType, NULL); |
|
|
if( !FAILED(hr) ) |
|
|
{ |
|
|
params.AutoDepthStencilFormat = fmt; |
|
|
fStencil.fDepth = 0; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
if( i < 0 ) |
|
|
{ |
|
|
for( i = fCurrentMode->fDepthFormats.GetCount() - 1; i >= 0; i-- ) |
|
|
{ |
|
|
D3DFORMAT fmt = fCurrentMode->fDepthFormats[ i ]; |
|
|
if( fmt == D3DFMT_D15S1 || fmt == D3DFMT_D24X4S4 || fmt == D3DFMT_D24S8 ) |
|
|
{ |
|
|
HRESULT hr = fD3DObject->CheckDeviceMultiSampleType(fCurrentAdapter, |
|
|
fCurrentDevice->fDDType, |
|
|
fmt, |
|
|
fCurrentMode->fWindowed ? TRUE : FALSE, |
|
|
params.MultiSampleType, NULL); |
|
|
if( !FAILED(hr) ) |
|
|
{ |
|
|
params.AutoDepthStencilFormat = fmt; |
|
|
if( fmt == D3DFMT_D15S1 ) |
|
|
fStencil.fDepth = 1; |
|
|
else if( fmt == D3DFMT_D24X4S4 ) |
|
|
fStencil.fDepth = 4; |
|
|
else |
|
|
fStencil.fDepth = 8; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return i >= 0; |
|
|
} |
|
|
|
|
|
// ICreateNormalSurfaces ////////////////////////////////////////////////////// |
|
|
// Create the primary color and depth buffers. |
|
|
// |
|
|
hsBool plDXPipeline::ICreateNormalSurfaces() |
|
|
{ |
|
|
/// Now get the backbuffer surface pointer |
|
|
INIT_ERROR_CHECK( fD3DDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &fD3DBackBuff ), |
|
|
"Cannot get primary surface's back buffer" ); |
|
|
|
|
|
/// And finally, get the main D3D surfaces (for restoring after rendertargets ) |
|
|
INIT_ERROR_CHECK( fD3DDevice->GetRenderTarget( 0, &fD3DMainSurface ), "Cannot capture primary surface" ); |
|
|
INIT_ERROR_CHECK( fD3DDevice->GetDepthStencilSurface( &fD3DDepthSurface ), "Cannot capture primary depth surface" ); |
|
|
|
|
|
fSettings.fCurrD3DMainSurface = fD3DMainSurface; |
|
|
fSettings.fCurrD3DDepthSurface = fD3DDepthSurface; |
|
|
|
|
|
D3DSURF_MEMNEW( fD3DMainSurface ); |
|
|
D3DSURF_MEMNEW( fD3DDepthSurface ); |
|
|
D3DSURF_MEMNEW( fD3DBackBuff ); |
|
|
|
|
|
D3DSURFACE_DESC info; |
|
|
fD3DMainSurface->GetDesc( &info ); |
|
|
fD3DDepthSurface->GetDesc( &info ); |
|
|
fD3DBackBuff->GetDesc( &info ); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
// IReleaseRenderTargetPools ////////////////////////////////////////////////// |
|
|
// Free up all resources assosiated with our pools of rendertargets of varying |
|
|
// sizes. Primary user of these pools is the shadow generation. |
|
|
void plDXPipeline::IReleaseRenderTargetPools() |
|
|
{ |
|
|
int i; |
|
|
|
|
|
for( i = 0; i < fRenderTargetPool512.GetCount(); i++ ) |
|
|
{ |
|
|
delete fRenderTargetPool512[i]; |
|
|
fRenderTargetPool512[i] = nil; |
|
|
} |
|
|
fRenderTargetPool512.SetCount(0); |
|
|
|
|
|
for( i = 0; i < fRenderTargetPool256.GetCount(); i++ ) |
|
|
{ |
|
|
delete fRenderTargetPool256[i]; |
|
|
fRenderTargetPool256[i] = nil; |
|
|
} |
|
|
fRenderTargetPool256.SetCount(0); |
|
|
|
|
|
for( i = 0; i < fRenderTargetPool128.GetCount(); i++ ) |
|
|
{ |
|
|
delete fRenderTargetPool128[i]; |
|
|
fRenderTargetPool128[i] = nil; |
|
|
} |
|
|
fRenderTargetPool128.SetCount(0); |
|
|
|
|
|
for( i = 0; i < fRenderTargetPool64.GetCount(); i++ ) |
|
|
{ |
|
|
delete fRenderTargetPool64[i]; |
|
|
fRenderTargetPool64[i] = nil; |
|
|
} |
|
|
fRenderTargetPool64.SetCount(0); |
|
|
|
|
|
for( i = 0; i < fRenderTargetPool32.GetCount(); i++ ) |
|
|
{ |
|
|
delete fRenderTargetPool32[i]; |
|
|
fRenderTargetPool32[i] = nil; |
|
|
} |
|
|
fRenderTargetPool32.SetCount(0); |
|
|
|
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
{ |
|
|
fRenderTargetNext[i] = 0; |
|
|
fBlurScratchRTs[i] = nil; |
|
|
fBlurDestRTs[i] = nil; |
|
|
} |
|
|
|
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
hackOffscreens.Reset(); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
} |
|
|
|
|
|
// IReleaseDynDeviceObjects ////////////////////////////////////////////// |
|
|
// Make sure we aren't holding on to anything, and release all of |
|
|
// the D3D resources that we normally hang on to forever. Meaning things |
|
|
// that persist through unloading one age and loading the next. |
|
|
void plDXPipeline::IReleaseDynDeviceObjects() |
|
|
{ |
|
|
// We should do this earlier, but the textFont objects don't remove |
|
|
// themselves from their parent objects yet |
|
|
delete fDebugTextMgr; |
|
|
fDebugTextMgr = nil; |
|
|
|
|
|
if( fD3DDevice ) |
|
|
{ |
|
|
fD3DDevice->SetStreamSource(0, nil, 0, 0); |
|
|
fD3DDevice->SetIndices(nil); |
|
|
} |
|
|
|
|
|
/// Delete actual d3d objects |
|
|
hsRefCnt_SafeUnRef( fSettings.fCurrVertexBuffRef ); |
|
|
fSettings.fCurrVertexBuffRef = nil; |
|
|
hsRefCnt_SafeUnRef( fSettings.fCurrIndexBuffRef ); |
|
|
fSettings.fCurrIndexBuffRef = nil; |
|
|
|
|
|
while( fTextFontRefList ) |
|
|
delete fTextFontRefList; |
|
|
|
|
|
while( fRenderTargetRefList ) |
|
|
{ |
|
|
plDXRenderTargetRef* rtRef = fRenderTargetRefList; |
|
|
rtRef->Release(); |
|
|
rtRef->Unlink(); |
|
|
} |
|
|
|
|
|
// The shared dynamic vertex buffers used by things like objects skinned on CPU, or |
|
|
// particle systems. |
|
|
IReleaseDynamicBuffers(); |
|
|
IReleaseAvRTPool(); |
|
|
IReleaseRenderTargetPools(); |
|
|
|
|
|
if( fSharedDepthSurface[0] ) |
|
|
{ |
|
|
D3DSURF_MEMDEL(fSharedDepthSurface[0]); |
|
|
ReleaseObject(fSharedDepthSurface[0]); |
|
|
fSharedDepthFormat[0] = D3DFMT_UNKNOWN; |
|
|
} |
|
|
if( fSharedDepthSurface[1] ) |
|
|
{ |
|
|
D3DSURF_MEMDEL(fSharedDepthSurface[1]); |
|
|
ReleaseObject(fSharedDepthSurface[1]); |
|
|
fSharedDepthFormat[1] = D3DFMT_UNKNOWN; |
|
|
} |
|
|
|
|
|
D3DSURF_MEMDEL( fD3DMainSurface ); |
|
|
D3DSURF_MEMDEL( fD3DDepthSurface ); |
|
|
D3DSURF_MEMDEL( fD3DBackBuff ); |
|
|
|
|
|
ReleaseObject( fD3DBackBuff ); |
|
|
ReleaseObject( fD3DDepthSurface ); |
|
|
ReleaseObject( fD3DMainSurface ); |
|
|
|
|
|
} |
|
|
|
|
|
// IReleaseShaders /////////////////////////////////////////////////////////////// |
|
|
// Delete our vertex and pixel shaders. Releasing the plasma ref will release the |
|
|
// D3D handle. |
|
|
void plDXPipeline::IReleaseShaders() |
|
|
{ |
|
|
while( fVShaderRefList ) |
|
|
{ |
|
|
plDXVertexShader* ref = fVShaderRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
|
|
|
while( fPShaderRefList ) |
|
|
{ |
|
|
plDXPixelShader* ref = fPShaderRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IReleaseDeviceObjects /////////////////////////////////////////////////////// |
|
|
// Release everything we've created. This is the main cleanup function. |
|
|
void plDXPipeline::IReleaseDeviceObjects() |
|
|
{ |
|
|
plDXDeviceRef *ref; |
|
|
|
|
|
/// Delete d3d-dependent objects |
|
|
#if MCN_BOUNDS_SPANS |
|
|
if( fBoundsSpans ) |
|
|
fBoundsSpans->GetKey()->UnRefObject(); |
|
|
fBoundsSpans = nil; |
|
|
if( fBoundsMat ) |
|
|
fBoundsMat->GetKey()->UnRefObject(); |
|
|
fBoundsMat = nil; |
|
|
#endif |
|
|
|
|
|
plStatusLogMgr::GetInstance().SetDrawer( nil ); |
|
|
delete fLogDrawer; |
|
|
fLogDrawer = nil; |
|
|
|
|
|
IGetPixelScratch( 0 ); |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
if( fLayerRef[i] ) |
|
|
{ |
|
|
hsRefCnt_SafeUnRef(fLayerRef[i]); |
|
|
fLayerRef[i] = nil; |
|
|
} |
|
|
} |
|
|
|
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
//WHITE |
|
|
hackOffscreens.SetCount(0); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
|
|
|
if( fULutTextureRef ) |
|
|
delete [] fULutTextureRef->fData; |
|
|
hsRefCnt_SafeUnRef(fULutTextureRef); |
|
|
fULutTextureRef = nil; |
|
|
|
|
|
while( fVtxBuffRefList ) |
|
|
{ |
|
|
ref = fVtxBuffRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
while( fIdxBuffRefList ) |
|
|
{ |
|
|
ref = fIdxBuffRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
while( fTextureRefList ) |
|
|
{ |
|
|
ref = fTextureRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
|
|
|
IReleaseShaders(); |
|
|
|
|
|
fLights.Release(); |
|
|
|
|
|
IReleaseDynDeviceObjects(); |
|
|
|
|
|
delete fPlateMgr; |
|
|
fPlateMgr = nil; |
|
|
|
|
|
if( fD3DDevice != nil ) |
|
|
{ |
|
|
LONG ret; |
|
|
while( ret = fD3DDevice->Release() ) |
|
|
{ |
|
|
hsStatusMessageF("%d - Error releasing device", ret); |
|
|
} |
|
|
fD3DDevice = nil; |
|
|
} |
|
|
|
|
|
if( fD3DObject != nil ) |
|
|
{ |
|
|
LONG ret; |
|
|
while( ret = fD3DObject->Release() ) |
|
|
{ |
|
|
hsStatusMessageF("%d - Error releasing Direct3D Object", ret); |
|
|
} |
|
|
fD3DObject = nil; |
|
|
} |
|
|
|
|
|
fManagedAlloced = false; |
|
|
fAllocUnManaged = false; |
|
|
} |
|
|
|
|
|
// IReleaseDynamicBuffers ///////////////////////////////////////////////// |
|
|
// Release everything we've created in POOL_DEFAULT. |
|
|
// This is called on shutdown or when we lose the device. Search for D3DERR_DEVICELOST. |
|
|
void plDXPipeline::IReleaseDynamicBuffers() |
|
|
{ |
|
|
// Workaround for ATI driver bug. |
|
|
if( fSettings.fBadManaged ) |
|
|
{ |
|
|
plDXTextureRef* tRef = fTextureRefList; |
|
|
while( tRef ) |
|
|
{ |
|
|
tRef->Release(); |
|
|
tRef = tRef->GetNext(); |
|
|
} |
|
|
} |
|
|
plDXVertexBufferRef* vbRef = fVtxBuffRefList; |
|
|
while( vbRef ) |
|
|
{ |
|
|
if( vbRef->Volatile() && vbRef->fD3DBuffer ) |
|
|
{ |
|
|
vbRef->fD3DBuffer->Release(); |
|
|
vbRef->fD3DBuffer = nil; |
|
|
|
|
|
// Actually, if it's volatile, it's sharing the global dynamic vertex buff, so we're already |
|
|
// accounting for the memory when we clear the global buffer. |
|
|
//PROFILE_POOL_MEM(D3DPOOL_DEFAULT, vbRef->fCount * vbRef->fVertexSize, false, "VtxBuff"); |
|
|
} |
|
|
// 9600 THRASH |
|
|
else if( fSettings.fBadManaged ) |
|
|
{ |
|
|
vbRef->Release(); |
|
|
} |
|
|
vbRef = vbRef->GetNext(); |
|
|
} |
|
|
plDXIndexBufferRef* iRef = fIdxBuffRefList; |
|
|
while( iRef ) |
|
|
{ |
|
|
// If it's volatile, we have to release it. |
|
|
// If it's not, we want to release it so |
|
|
// we can make it volatile (D3DPOOL_DEFAULT) |
|
|
if (iRef->fD3DBuffer) |
|
|
{ |
|
|
iRef->fD3DBuffer->Release(); |
|
|
iRef->fD3DBuffer = nil; |
|
|
PROFILE_POOL_MEM(iRef->fPoolType, iRef->fCount * sizeof(UInt16), false, "IndexBuff"); |
|
|
} |
|
|
iRef = iRef->GetNext(); |
|
|
} |
|
|
if (fDynVtxBuff) |
|
|
{ |
|
|
ReleaseObject(fDynVtxBuff); |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, fDynVtxSize, false, "DynVtxBuff"); |
|
|
fDynVtxBuff = nil; |
|
|
} |
|
|
|
|
|
fNextDynVtx = 0; |
|
|
|
|
|
fVtxRefTime++; |
|
|
|
|
|
// PlateMgr has a POOL_DEFAULT vertex buffer for drawing quads. |
|
|
if( fPlateMgr ) |
|
|
fPlateMgr->IReleaseGeometry(); |
|
|
|
|
|
// Also has POOL_DEFAULT vertex buffer. |
|
|
plDXTextFont::ReleaseShared(fD3DDevice); |
|
|
|
|
|
IReleaseBlurVBuffers(); |
|
|
} |
|
|
|
|
|
// ICreateDynamicBuffers ///////////////////////////////////////////////////// |
|
|
// Create the things we need in POOL_DEFAULT. We clump them into this function, |
|
|
// because they must be created before anything in POOL_MANAGED. |
|
|
// So we create these global POOL_DEFAULT objects here, then send out a message |
|
|
// to the objects in the scene to create anything they need in POOL_DEFAULT, |
|
|
// then go on to create things on POOL_MANAGED. |
|
|
// Set LoadResources(). |
|
|
void plDXPipeline::ICreateDynamicBuffers() |
|
|
{ |
|
|
ICreateBlurVBuffers(); |
|
|
|
|
|
plDXTextFont::CreateShared(fD3DDevice); |
|
|
|
|
|
if( fPlateMgr ) |
|
|
fPlateMgr->ICreateGeometry(this); |
|
|
|
|
|
fNextDynVtx = 0; |
|
|
|
|
|
fVtxRefTime++; |
|
|
|
|
|
DWORD usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; |
|
|
hsAssert(!fManagedAlloced, "Alloc default with managed alloc'd"); |
|
|
D3DPOOL poolType = D3DPOOL_DEFAULT; |
|
|
if( fDynVtxSize ) |
|
|
{ |
|
|
PROFILE_POOL_MEM(poolType, fDynVtxSize, true, "DynVtxBuff"); |
|
|
if( FAILED( fD3DDevice->CreateVertexBuffer( fDynVtxSize, |
|
|
usage, |
|
|
0, |
|
|
poolType, |
|
|
&fDynVtxBuff, NULL) ) ) |
|
|
{ |
|
|
hsAssert(false, "Don't know what to do here."); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void plDXPipeline::IPrintDeviceInitError() |
|
|
{ |
|
|
char str[256]; |
|
|
char err[16]; |
|
|
switch(plLocalization::GetLanguage()) |
|
|
{ |
|
|
case plLocalization::kFrench: strcpy(err, "Erreur"); strcpy(str, "Erreur d'initialisation de votre carte graphique. Les valeurs par d<EFBFBD>faut de ses param<EFBFBD>tres ont <EFBFBD>t<EFBFBD> r<EFBFBD>tablis. "); break; |
|
|
case plLocalization::kGerman: strcpy(err, "Fehler"); strcpy(str, "Bei der Initialisierung Ihrer Grafikkarte ist ein Fehler aufgetreten. Standardeinstellungen werden wiederhergestellt."); break; |
|
|
case plLocalization::kSpanish: strcpy(err, "Error"); strcpy(str, "Ocurri<EFBFBD> un error al inicializar tu tarjeta de v<EFBFBD>deo. Hemos restaurado los ajustes por defecto. "); break; |
|
|
case plLocalization::kItalian: strcpy(err, "Errore"); strcpy(str, "Errore di inizializzazione della scheda video. Sono state ripristinate le impostazioni predefinite."); break; |
|
|
default: strcpy(err, "Error"); strcpy(str, "There was an error initializing your video card. We have reset it to its Default settings."); break; |
|
|
} |
|
|
hsMessageBox(str, err, hsMessageBoxNormal, hsMessageBoxIconError); |
|
|
} |
|
|
|
|
|
// Reset device creation parameters to default and write to ini file |
|
|
void plDXPipeline::IResetToDefaults(D3DPRESENT_PARAMETERS *params) |
|
|
{ |
|
|
// this will reset device parameters to default and make sure all other necessary parameters are updated |
|
|
params->BackBufferWidth = fDefaultPipeParams.Width; |
|
|
params->BackBufferHeight = fDefaultPipeParams.Height; |
|
|
fSettings.fOrigWidth = fDefaultPipeParams.Width; |
|
|
fSettings.fOrigHeight = fDefaultPipeParams.Height; |
|
|
IGetViewTransform().SetScreenSize(fDefaultPipeParams.Width, fDefaultPipeParams.Height); |
|
|
params->BackBufferFormat = D3DFMT_X8R8G8B8; |
|
|
fSettings.fColorDepth = fDefaultPipeParams.ColorDepth; |
|
|
|
|
|
int i; |
|
|
hsTArray<D3DEnum_ModeInfo> *modes = &fCurrentDevice->fModes; |
|
|
for( i = 0; i < modes->Count(); i++ ) |
|
|
{ |
|
|
D3DEnum_ModeInfo *mode = &(*modes)[i]; |
|
|
if(mode->fDDmode.Width == params->BackBufferWidth && |
|
|
mode->fDDmode.Height == params->BackBufferHeight && |
|
|
mode->fBitDepth == 32 ) |
|
|
{ |
|
|
ISetCurrentMode(&(*modes)[i]); |
|
|
break; |
|
|
} |
|
|
} |
|
|
params->Windowed = fDefaultPipeParams.Windowed; |
|
|
fSettings.fFullscreen = !fDefaultPipeParams.Windowed; |
|
|
fCurrentMode->fWindowed = fDefaultPipeParams.Windowed; |
|
|
|
|
|
// Attempt to find the closest AA setting we can |
|
|
params->MultiSampleType = D3DMULTISAMPLE_NONE; |
|
|
fSettings.fNumAASamples = 0; |
|
|
for( int i = fDefaultPipeParams.AntiAliasingAmount; i >= 2; i-- ) |
|
|
{ |
|
|
if( fCurrentMode->fFSAATypes.Find( (D3DMULTISAMPLE_TYPE)i ) != fCurrentMode->fFSAATypes.kMissingIndex ) |
|
|
{ |
|
|
fSettings.fNumAASamples = i; |
|
|
params->MultiSampleType = (D3DMULTISAMPLE_TYPE)i; |
|
|
break; |
|
|
} |
|
|
} |
|
|
fSettings.fMaxAnisotropicSamples = fDefaultPipeParams.AnisotropicLevel; |
|
|
|
|
|
fVSync = false; |
|
|
|
|
|
params->PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; |
|
|
|
|
|
plShadowCaster::EnableShadowCast(fDefaultPipeParams.Shadows ? true : false); |
|
|
plQuality::SetQuality(fDefaultPipeParams.VideoQuality); |
|
|
plQuality::SetCapability(fDefaultPipeParams.VideoQuality); |
|
|
plDynamicCamMap::SetEnabled(fDefaultPipeParams.PlanarReflections ? true : false); |
|
|
plBitmap::SetGlobalLevelChopCount(2 - fDefaultPipeParams.TextureQuality); |
|
|
|
|
|
// adjust camera properties |
|
|
plVirtualCam1::SetAspectRatio((float)fSettings.fOrigWidth / (float)fSettings.fOrigHeight); |
|
|
plVirtualCam1::SetFOV(plVirtualCam1::GetFOVw(), plVirtualCam1::GetFOVh()); |
|
|
|
|
|
// fire off a message to the client so we can write defaults to the ini file, and adjust the window size |
|
|
plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY ); |
|
|
plClientMsg* clientMsg = TRACKED_NEW plClientMsg(plClientMsg::kSetGraphicsDefaults); |
|
|
clientMsg->Send(clientKey); |
|
|
|
|
|
} |
|
|
|
|
|
// IResetDevice |
|
|
// reset the device to its operational state. |
|
|
// returns true if not ready yet, false if the reset was successful. |
|
|
// All this is generally in response to a fullscreen alt-tab. |
|
|
hsBool plDXPipeline::IResetDevice() |
|
|
{ |
|
|
hsBool fakeDevLost(false); |
|
|
if( fakeDevLost ) |
|
|
fDeviceLost = true; |
|
|
|
|
|
if( fDeviceLost ) |
|
|
{ |
|
|
IClearShadowSlaves(); |
|
|
|
|
|
Sleep(100); |
|
|
HRESULT coopLev = fD3DDevice->TestCooperativeLevel(); |
|
|
if( coopLev == D3DERR_DEVICELOST ) |
|
|
{ |
|
|
// Nothing to do yet. |
|
|
return true; |
|
|
} |
|
|
if( fakeDevLost ) |
|
|
coopLev = D3DERR_DEVICENOTRESET; |
|
|
if( coopLev == D3DERR_DEVICENOTRESET || fForceDeviceReset) |
|
|
{ |
|
|
plStatusLog::AddLineS("pipeline.log", 0xffff0000, "Resetting Device"); |
|
|
IReleaseDynDeviceObjects(); |
|
|
if( !IFindDepthFormat(fSettings.fPresentParams) ) |
|
|
{ |
|
|
// If we haven't found a depth format, turn off multisampling and try it again. |
|
|
fSettings.fPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE; |
|
|
IFindDepthFormat(fSettings.fPresentParams); |
|
|
} |
|
|
HRESULT hr = fD3DDevice->Reset(&fSettings.fPresentParams); |
|
|
int count = 0; |
|
|
while( FAILED(hr) ) |
|
|
{ |
|
|
if(count++ == 25) |
|
|
{ |
|
|
IPrintDeviceInitError(); |
|
|
IResetToDefaults(&fSettings.fPresentParams); |
|
|
} |
|
|
// Still not ready? This is bad. |
|
|
// Until we called Reset(), we could make any D3D call we wanted, |
|
|
// and it would turn into a no-op. But once we call Reset(), until |
|
|
// the device really is reset, anything but TestCoop/Reset/Release |
|
|
// has just become illegal. We've already released everything, Reset |
|
|
// just failed, not much to do but wait and try again. |
|
|
::Sleep(250); |
|
|
hr = fD3DDevice->Reset(&fSettings.fPresentParams); |
|
|
} |
|
|
fSettings.fCurrFVFFormat = 0; |
|
|
fSettings.fCurrVertexShader = NULL; |
|
|
fManagedAlloced = false; |
|
|
ICreateDynDeviceObjects(); |
|
|
IInitDeviceState(); |
|
|
|
|
|
/// Broadcast a message letting everyone know that we were recreated and that |
|
|
/// all device-specific stuff needs to be recreated |
|
|
plDeviceRecreateMsg* clean = TRACKED_NEW plDeviceRecreateMsg(this); |
|
|
plgDispatch::MsgSend(clean); |
|
|
} |
|
|
fDevWasLost = true; |
|
|
fDeviceLost = false; |
|
|
|
|
|
// We return true here, even though we've successfully recreated, to take |
|
|
// another spin around the update loop and give everyone a chance to |
|
|
// get back in sync. |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
void plDXPipeline::ResetDisplayDevice(int Width, int Height, int ColorDepth, hsBool Windowed, int NumAASamples, int MaxAnisotropicSamples, hsBool VSync /* = false */) |
|
|
{ |
|
|
if( fSettings.fPresentParams.BackBufferWidth == Width && |
|
|
fSettings.fPresentParams.BackBufferHeight == Height && |
|
|
(fSettings.fPresentParams.Windowed ? 1 : fSettings.fColorDepth == ColorDepth) && // if we're windowed dont check color depth we just use the desktop colordepth |
|
|
((fSettings.fPresentParams.Windowed && Windowed) || (!fSettings.fPresentParams.Windowed && !Windowed)) && |
|
|
fSettings.fNumAASamples == NumAASamples && |
|
|
fSettings.fMaxAnisotropicSamples == MaxAnisotropicSamples && |
|
|
fVSync == VSync |
|
|
) |
|
|
{ |
|
|
return; // nothing has changed |
|
|
} |
|
|
|
|
|
fVSync = VSync; |
|
|
int i = 0; |
|
|
hsTArray<D3DEnum_ModeInfo> *modes = &fCurrentDevice->fModes; |
|
|
// check for supported resolution if we're not going to windowed mode |
|
|
if(!Windowed) |
|
|
{ |
|
|
for( i = 0; i < modes->Count(); i++ ) |
|
|
{ |
|
|
D3DEnum_ModeInfo *mode = &(*modes)[i]; |
|
|
if(mode->fDDmode.Width == Width && |
|
|
mode->fDDmode.Height == Height && |
|
|
mode->fBitDepth == ColorDepth ) |
|
|
{ |
|
|
ISetCurrentMode(&(*modes)[i]); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
if(i != modes->Count()) |
|
|
{ |
|
|
// Set Resolution |
|
|
fSettings.fOrigWidth = Width; |
|
|
fSettings.fOrigHeight = Height; |
|
|
IGetViewTransform().SetScreenSize(Width, Height); |
|
|
fSettings.fPresentParams.BackBufferWidth = Width; |
|
|
fSettings.fPresentParams.BackBufferHeight = Height; |
|
|
fSettings.fColorDepth = ColorDepth; |
|
|
fSettings.fPresentParams.BackBufferFormat = D3DFMT_X8R8G8B8; |
|
|
} |
|
|
|
|
|
// set windowed/fullscreen mode |
|
|
fCurrentMode->fWindowed = Windowed; |
|
|
fSettings.fPresentParams.Windowed = Windowed; |
|
|
fSettings.fFullscreen = !Windowed; |
|
|
|
|
|
// set Antialiasing |
|
|
fSettings.fNumAASamples = 0; |
|
|
// Attempt to find the closest AA setting we can |
|
|
fSettings.fPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE; |
|
|
for( i = NumAASamples; i >= 2; i-- ) |
|
|
{ |
|
|
if( fCurrentMode->fFSAATypes.Find( (D3DMULTISAMPLE_TYPE)i ) != fCurrentMode->fFSAATypes.kMissingIndex ) |
|
|
{ |
|
|
fSettings.fNumAASamples = i; |
|
|
fSettings.fPresentParams.MultiSampleType = (D3DMULTISAMPLE_TYPE)i; |
|
|
break; |
|
|
} |
|
|
} |
|
|
if( fSettings.fNumAASamples > 0 ) |
|
|
fSettings.fD3DCaps |= kCapsFSAntiAlias; |
|
|
else |
|
|
fSettings.fD3DCaps &= ~kCapsFSAntiAlias; |
|
|
|
|
|
// Set Anisotropic filtering |
|
|
fSettings.fMaxAnisotropicSamples = MaxAnisotropicSamples; |
|
|
ISetAnisotropy(MaxAnisotropicSamples > 0); |
|
|
if(Windowed) |
|
|
{ |
|
|
fSettings.fPresentParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fSettings.fPresentParams.PresentationInterval = ( fVSync ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE ); |
|
|
} |
|
|
|
|
|
// Force a device reset |
|
|
fDeviceLost = true; |
|
|
fForceDeviceReset = true; |
|
|
|
|
|
plVirtualCam1::SetAspectRatio((float)Width / (float)Height); |
|
|
plVirtualCam1::SetFOV(plVirtualCam1::GetFOVw(), plVirtualCam1::GetFOVh()); |
|
|
|
|
|
IResetDevice(); |
|
|
|
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
void plDXPipeline::GetSupportedColorDepths(hsTArray<int> &ColorDepths) |
|
|
{ |
|
|
int i, j; |
|
|
// iterate through display modes |
|
|
for( i = 0; i < fCurrentDevice->fModes.Count(); i++ ) |
|
|
{ |
|
|
// Check to see if color depth has been added already |
|
|
for( j = 0; j < ColorDepths.Count(); j++ ) |
|
|
{ |
|
|
if( fCurrentDevice->fModes[i].fBitDepth == ColorDepths[i] ) |
|
|
break; |
|
|
} |
|
|
if(j == ColorDepths.Count()) |
|
|
{ |
|
|
//add it |
|
|
ColorDepths.Push( fCurrentDevice->fModes[i].fBitDepth ); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void plDXPipeline::GetSupportedDisplayModes(std::vector<plDisplayMode> *res, int ColorDepth ) |
|
|
{ |
|
|
int i, j; |
|
|
std::vector<plDisplayMode> supported; |
|
|
// loop through display modes |
|
|
for( i = 0; i < fCurrentDevice->fModes.Count(); i++ ) |
|
|
{ |
|
|
if( fCurrentDevice->fModes[i].fBitDepth == ColorDepth ) |
|
|
{ |
|
|
// check for duplicate mode |
|
|
for( j = 0; j < supported.size(); j++ ) |
|
|
{ |
|
|
if(supported[j].Width == fCurrentDevice->fModes[i].fDDmode.Width && supported[j].Height == fCurrentDevice->fModes[i].fDDmode.Height) |
|
|
break; |
|
|
} |
|
|
if(j == supported.size()) |
|
|
{ |
|
|
// new mode, add it |
|
|
plDisplayMode mode; |
|
|
mode.Width = fCurrentDevice->fModes[i].fDDmode.Width; |
|
|
mode.Height = fCurrentDevice->fModes[i].fDDmode.Height; |
|
|
mode.ColorDepth = ColorDepth; |
|
|
supported.push_back(mode); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
*res = supported; |
|
|
} |
|
|
|
|
|
// Get max anitialias for the specified displaymode |
|
|
int plDXPipeline::GetMaxAntiAlias(int Width, int Height, int ColorDepth) |
|
|
{ |
|
|
int max = 0; |
|
|
D3DEnum_ModeInfo *pCurrMode = nil; |
|
|
hsTArray<D3DEnum_ModeInfo> *modes = &fCurrentDevice->fModes; |
|
|
for(int i = 0; i < modes->Count(); i++ ) |
|
|
{ |
|
|
D3DEnum_ModeInfo *mode = &(*modes)[i]; |
|
|
if( mode->fDDmode.Width == Width && |
|
|
mode->fDDmode.Height == Height && |
|
|
mode->fBitDepth == ColorDepth ) |
|
|
{ |
|
|
pCurrMode = mode; |
|
|
} |
|
|
} |
|
|
if(pCurrMode) |
|
|
{ |
|
|
for(int i = 0; i < pCurrMode->fFSAATypes.Count(); i++) |
|
|
{ |
|
|
if(pCurrMode->fFSAATypes[i] > max) |
|
|
max = pCurrMode->fFSAATypes[i]; |
|
|
} |
|
|
} |
|
|
return max; |
|
|
} |
|
|
|
|
|
int plDXPipeline::GetMaxAnisotropicSamples() |
|
|
{ |
|
|
return fCurrentDevice ? fCurrentDevice->fDDCaps.MaxAnisotropy : 0; |
|
|
} |
|
|
|
|
|
//// Resize /////////////////////////////////////////////////////////////////// |
|
|
// Resize is fairly obsolete, having been replaced by IResetDevice, which is |
|
|
// automatically called if needed on BeginRender. |
|
|
// This Resize function used to serve as both to Resize the primary buffers and |
|
|
// to restore after losing the device (alt-tab). It didn't actually do either |
|
|
// very well, so I'm not sure why I haven't deleted it. |
|
|
void plDXPipeline::Resize( UInt32 width, UInt32 height ) |
|
|
{ |
|
|
hsMatrix44 w2c, c2w, proj; |
|
|
|
|
|
|
|
|
HRESULT coopLev = fD3DDevice->TestCooperativeLevel(); |
|
|
if( coopLev == D3DERR_DEVICELOST ) |
|
|
{ |
|
|
/// Direct3D is reporting that we lost the device but are unable to reset |
|
|
/// it yet, so ignore. |
|
|
hsStatusMessage( "Received Resize() request at an invalid time. Ignoring...\n" ); |
|
|
return; |
|
|
} |
|
|
if( !width && !height ) |
|
|
{ |
|
|
if( D3D_OK == coopLev ) |
|
|
return; |
|
|
|
|
|
IReleaseDynDeviceObjects(); |
|
|
HRESULT hr = fD3DDevice->Reset(&fSettings.fPresentParams); |
|
|
fManagedAlloced = false; |
|
|
if( !FAILED(hr) ) |
|
|
{ |
|
|
ICreateDynDeviceObjects(); |
|
|
IInitDeviceState(); |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
// Store some states that we *want* to restore back... |
|
|
plViewTransform resetTransform = GetViewTransform(); |
|
|
|
|
|
/// HACK: Don't recreate if we're windowed, bad things happen |
|
|
/// Comment out this if if you want to test the crashing thing in windowed alt-tabbing |
|
|
#if 0 |
|
|
if( ( width == 0 || height == 0 ) && !fSettings.fFullscreen ) |
|
|
return; |
|
|
#endif |
|
|
|
|
|
// Destroy old |
|
|
IReleaseDeviceObjects(); |
|
|
|
|
|
// Reset width and height |
|
|
if( width != 0 && height != 0 ) |
|
|
{ |
|
|
// Width and height of zero mean just recreate |
|
|
fSettings.fOrigWidth = width; |
|
|
fSettings.fOrigHeight = height; |
|
|
IGetViewTransform().SetScreenSize((UInt16)(fSettings.fOrigWidth), (UInt16)(fSettings.fOrigHeight)); |
|
|
resetTransform.SetScreenSize((UInt16)(fSettings.fOrigWidth), (UInt16)(fSettings.fOrigHeight)); |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Just for debug |
|
|
hsStatusMessage( "Recreating the pipeline...\n" ); |
|
|
} |
|
|
|
|
|
// Recreate |
|
|
if( !fD3DObject ) |
|
|
{ |
|
|
if( ICreateMaster() ) |
|
|
{ |
|
|
IShowErrorMessage( "Cannot create D3D master object" ); |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
// Go recreate surfaces and DX-dependent objects |
|
|
if( ICreateDeviceObjects() ) |
|
|
{ |
|
|
IShowErrorMessage( "Cannot create Direct3D device" ); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Restore states |
|
|
SetViewTransform(resetTransform); |
|
|
IProjectionMatrixToD3D(); |
|
|
|
|
|
/// Broadcast a message letting everyone know that we were recreated and that |
|
|
/// all device-specific stuff needs to be recreated |
|
|
plDeviceRecreateMsg* clean = TRACKED_NEW plDeviceRecreateMsg(this); |
|
|
plgDispatch::MsgSend(clean); |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Debug Text /////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// MakeTextFont ///////////////////////////////////////////////////////////// |
|
|
|
|
|
plTextFont *plDXPipeline::MakeTextFont( char *face, UInt16 size ) |
|
|
{ |
|
|
plTextFont *font; |
|
|
|
|
|
|
|
|
font = TRACKED_NEW plDXTextFont( this, fD3DDevice ); |
|
|
if( font == nil ) |
|
|
return nil; |
|
|
font->Create( face, size ); |
|
|
font->Link( &fTextFontRefList ); |
|
|
|
|
|
return font; |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Drawable Stuff /////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// Draw ///////////////////////////////////////////////////////////////////// |
|
|
|
|
|
// PreRender ////////////////////////////////////////////////////////////////// |
|
|
// Most of this is debugging stuff, drawing the bounds, drawing the normals, etc. |
|
|
// The functional part is in IGetVisibleSpans, which creates a list of the visible (non-culled) |
|
|
// span indices within this drawable. |
|
|
// This is called once per render, and generally well before rendering begins (as part of the |
|
|
// cull phase). |
|
|
hsBool plDXPipeline::PreRender( plDrawable* drawable, hsTArray<Int16>& visList, plVisMgr* visMgr ) |
|
|
{ |
|
|
plDrawableSpans *ds = plDrawableSpans::ConvertNoRef(drawable); |
|
|
if( !ds ) |
|
|
return false; |
|
|
if( ( ds->GetType() & fView.fDrawableTypeMask ) == 0 ) |
|
|
return false; |
|
|
|
|
|
IGetVisibleSpans( ds, visList, visMgr ); |
|
|
|
|
|
#if MCN_BOUNDS_SPANS |
|
|
if( ( drawable != fBoundsSpans ) && IsDebugFlagSet(plPipeDbg::kFlagShowAllBounds) ) |
|
|
{ |
|
|
const hsTArray<plSpan *> &spans = ds->GetSpanArray(); |
|
|
int i; |
|
|
for( i = 0; i < visList.GetCount(); i++ ) |
|
|
{ |
|
|
/// Add a span to our boundsIce to show this |
|
|
IAddBoundsSpan( fBoundsSpans, &spans[ visList[i] ]->fWorldBounds ); |
|
|
} |
|
|
} |
|
|
else if( ( drawable != fBoundsSpans ) && IsDebugFlagSet(plPipeDbg::kFlagShowNormals) ) |
|
|
{ |
|
|
const hsTArray<plSpan *> &spans = ds->GetSpanArray(); |
|
|
int i; |
|
|
for( i = 0; i < visList.GetCount(); i++ ) |
|
|
{ |
|
|
/// Add a span to our boundsIce to show this |
|
|
plIcicle *span = (plIcicle *)spans[ visList[ i ] ]; |
|
|
if( span->fTypeMask & plSpan::kIcicleSpan ) |
|
|
{ |
|
|
IAddNormalsSpan( fBoundsSpans, span, (plDXVertexBufferRef *)ds->GetVertexRef( span->fGroupIdx, span->fVBufferIdx ), 0xff0000ff ); |
|
|
} |
|
|
} |
|
|
} |
|
|
#endif |
|
|
#if MF_BOUNDS_LEVEL_ICE |
|
|
if( (fSettings.fBoundsDrawLevel >= 0) && ( drawable != fBoundsSpans ) ) |
|
|
{ |
|
|
hsTArray<Int16> bndList; |
|
|
drawable->GetSpaceTree()->HarvestLevel(fSettings.fBoundsDrawLevel, bndList); |
|
|
int i; |
|
|
for( i = 0; i < bndList.GetCount(); i++ ) |
|
|
{ |
|
|
IAddBoundsSpan( fBoundsSpans, &hsBounds3Ext(drawable->GetSpaceTree()->GetNode(bndList[i]).GetWorldBounds()), 0xff000000 | (0xf << ((fSettings.fBoundsDrawLevel % 6) << 2)) ); |
|
|
} |
|
|
} |
|
|
#endif // MF_BOUNDS_LEVEL_ICE |
|
|
|
|
|
|
|
|
return visList.GetCount() > 0; |
|
|
} |
|
|
|
|
|
struct plSortFace |
|
|
{ |
|
|
UInt16 fIdx[3]; |
|
|
hsScalar fDist; |
|
|
}; |
|
|
|
|
|
struct plCompSortFace : public std::binary_function<plSortFace, plSortFace, bool> |
|
|
{ |
|
|
bool operator()( const plSortFace& lhs, const plSortFace& rhs) const |
|
|
{ |
|
|
return lhs.fDist > rhs.fDist; |
|
|
} |
|
|
}; |
|
|
|
|
|
// IAvatarSort ///////////////////////////////////////////////////////////////////////// |
|
|
// We handle avatar sort differently from the rest of the face sort. The reason is that |
|
|
// within the single avatar index buffer, we want to only sort the faces of spans requesting |
|
|
// a sort, and sort them in place. |
|
|
// Contrast that with the normal scene translucency sort. There, we sort all the spans in a drawble, |
|
|
// then we sort all the faces in that drawable, then for each span in the sorted span list, we extract |
|
|
// the faces for that span appending onto the index buffer. This gives great efficiency because |
|
|
// only the visible faces are sorted and they wind up packed into the front of the index buffer, which |
|
|
// permits more batching. See plDrawableSpans::SortVisibleSpans. |
|
|
// For the avatar, it's generally the case that all the avatar is visible or not, and there is only |
|
|
// one material, so neither of those efficiencies is helpful. Moreover, for the avatar the faces we |
|
|
// want sorted are a tiny subset of the avatar's faces. Moreover, and most importantly, for the avatar, we |
|
|
// want to preserve the order that spans are drawn, so, for example, the opaque base head will always be |
|
|
// drawn before the translucent hair fringe, which will always be drawn before the pink clear plastic baseball cap. |
|
|
hsBool plDXPipeline::IAvatarSort(plDrawableSpans* d, const hsTArray<Int16>& visList) |
|
|
{ |
|
|
plProfile_BeginTiming(AvatarSort); |
|
|
int i; |
|
|
for( i = 0; i < visList.GetCount(); i++ ) |
|
|
{ |
|
|
hsAssert(d->GetSpan(visList[i])->fTypeMask & plSpan::kIcicleSpan, "Unknown type for sorting faces"); |
|
|
|
|
|
plIcicle* span = (plIcicle*)d->GetSpan(visList[i]); |
|
|
|
|
|
if( span->fProps & plSpan::kPartialSort ) |
|
|
{ |
|
|
hsAssert(d->GetBufferGroup(span->fGroupIdx)->AreIdxVolatile(), "Badly setup buffer group - set PartialSort too late?"); |
|
|
|
|
|
const hsPoint3 viewPos = GetViewPositionWorld(); |
|
|
|
|
|
plGBufferGroup* group = d->GetBufferGroup(span->fGroupIdx); |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)group->GetVertexBufferRef(span->fVBufferIdx); |
|
|
|
|
|
const UInt8* vdata = vRef->fData; |
|
|
const UInt32 stride = vRef->fVertexSize; |
|
|
|
|
|
const int numTris = span->fILength/3; |
|
|
|
|
|
static hsTArray<plSortFace> sortScratch; |
|
|
sortScratch.SetCount(numTris); |
|
|
|
|
|
plProfile_IncCount(AvatarFaces, numTris); |
|
|
|
|
|
plSortFace* begin = sortScratch.AcquireArray(); |
|
|
plSortFace* end = begin + numTris; |
|
|
|
|
|
// |
|
|
// Have three very similar sorts here, differing only on where the "position" of |
|
|
// each triangle is defined, either as the center of the triangle, the nearest |
|
|
// point on the triangle, or the farthest point on the triangle. |
|
|
// Having tried all three on the avatar (the only thing this sort is used on), |
|
|
// the best results surprisingly came from using the center of the triangle. |
|
|
UInt16* indices = group->GetIndexBufferData(span->fIBufferIdx) + span->fIStartIdx; |
|
|
int j; |
|
|
for( j = 0; j < numTris; j++ ) |
|
|
{ |
|
|
#if 1 // TRICENTER |
|
|
UInt16 idx = *indices++; |
|
|
sortScratch[j].fIdx[0] = idx; |
|
|
hsPoint3 pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[1] = idx; |
|
|
pos += *(hsPoint3*)(vdata + idx * stride); |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[2] = idx; |
|
|
pos += *(hsPoint3*)(vdata + idx * stride); |
|
|
|
|
|
pos *= 0.3333f; |
|
|
|
|
|
sortScratch[j].fDist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
#elif 0 // NEAREST |
|
|
UInt16 idx = *indices++; |
|
|
sortScratch[j].fIdx[0] = idx; |
|
|
hsPoint3 pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
hsScalar dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
hsScalar minDist = dist; |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[1] = idx; |
|
|
pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
if( dist < minDist ) |
|
|
minDist = dist; |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[2] = idx; |
|
|
pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
if( dist < minDist ) |
|
|
minDist = dist; |
|
|
|
|
|
sortScratch[j].fDist = minDist; |
|
|
#elif 1 // FURTHEST |
|
|
UInt16 idx = *indices++; |
|
|
sortScratch[j].fIdx[0] = idx; |
|
|
hsPoint3 pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
hsScalar dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
hsScalar maxDist = dist; |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[1] = idx; |
|
|
pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
if( dist > maxDist ) |
|
|
maxDist = dist; |
|
|
|
|
|
idx = *indices++; |
|
|
sortScratch[j].fIdx[2] = idx; |
|
|
pos = *(hsPoint3*)(vdata + idx * stride); |
|
|
dist = hsVector3(&pos, &viewPos).MagnitudeSquared(); |
|
|
if( dist > maxDist ) |
|
|
maxDist = dist; |
|
|
|
|
|
sortScratch[j].fDist = maxDist; |
|
|
#endif // SORTTYPES |
|
|
} |
|
|
|
|
|
std::sort(begin, end, plCompSortFace()); |
|
|
|
|
|
indices = group->GetIndexBufferData(span->fIBufferIdx) + span->fIStartIdx; |
|
|
plSortFace* iter = sortScratch.AcquireArray();; |
|
|
for( j = 0; j < numTris; j++ ) |
|
|
{ |
|
|
*indices++ = iter->fIdx[0]; |
|
|
*indices++ = iter->fIdx[1]; |
|
|
*indices++ = iter->fIdx[2]; |
|
|
iter++; |
|
|
} |
|
|
|
|
|
group->DirtyIndexBuffer(span->fIBufferIdx); |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(AvatarSort); |
|
|
return true; |
|
|
} |
|
|
|
|
|
// PrepForRender ////////////////////////////////////////////////////////////////// |
|
|
// Make sure the given drawable and each of the spans to be drawn (as noted in the |
|
|
// indices in visList) is ready to be rendered. |
|
|
// This means: |
|
|
// a) select which lights will be used for each span |
|
|
// b) do any necessary sorting (if required, spans are already in sorted order in visList, |
|
|
// so this only means face sorting). |
|
|
// c) do any necessary software skinning. |
|
|
// This is called once per render, and before any rendering actually starts. See plPageTreeMgr.cpp. |
|
|
// So any preperation needs to last until rendering actually begins. So cached information, like |
|
|
// which lights a span will use, needs to be stored on the span. |
|
|
hsBool plDXPipeline::PrepForRender(plDrawable* d, hsTArray<Int16>& visList, plVisMgr* visMgr) |
|
|
{ |
|
|
plProfile_BeginTiming(PrepDrawable); |
|
|
|
|
|
plDrawableSpans *drawable = plDrawableSpans::ConvertNoRef(d); |
|
|
if( !drawable ) |
|
|
{ |
|
|
plProfile_EndTiming(PrepDrawable); |
|
|
return false; |
|
|
} |
|
|
|
|
|
// Find our lights |
|
|
ICheckLighting(drawable, visList, visMgr); |
|
|
|
|
|
// Sort our faces |
|
|
if( drawable->GetNativeProperty(plDrawable::kPropSortFaces) ) |
|
|
{ |
|
|
drawable->SortVisibleSpans(visList, this); |
|
|
} |
|
|
|
|
|
// Prep for render. This is gives the drawable a chance to |
|
|
// do any last minute updates for its buffers, including |
|
|
// generating particle tri lists. |
|
|
drawable->PrepForRender( this ); |
|
|
|
|
|
// Any skinning necessary |
|
|
if( !ISoftwareVertexBlend(drawable, visList) ) |
|
|
{ |
|
|
plProfile_EndTiming(PrepDrawable); |
|
|
return false; |
|
|
} |
|
|
// Avatar face sorting happens after the software skin. |
|
|
if( drawable->GetNativeProperty(plDrawable::kPropPartialSort) ) |
|
|
{ |
|
|
IAvatarSort(drawable, visList); |
|
|
} |
|
|
|
|
|
plProfile_EndTiming(PrepDrawable); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// Draw /////////////////////////////////////////////////////////// |
|
|
// Convenience function for a drawable that needs to get drawn outside of |
|
|
// the normal scene graph render (i.e. something not managed by the plPageTreeMgr). |
|
|
// Not nearly as efficient, so only useful as a special case. |
|
|
void plDXPipeline::Draw( plDrawable *d ) |
|
|
{ |
|
|
plDrawableSpans *ds = plDrawableSpans::ConvertNoRef( d ); |
|
|
|
|
|
if( ds ) |
|
|
{ |
|
|
if( ( ds->GetType() & fView.fDrawableTypeMask ) == 0 ) |
|
|
return; |
|
|
|
|
|
static hsTArray<Int16>visList; |
|
|
|
|
|
PreRender( ds, visList ); |
|
|
PrepForRender(ds, visList); |
|
|
Render( ds, visList ); |
|
|
} |
|
|
} |
|
|
|
|
|
// Render //////////////////////////////////////////////////////////////////////////////// |
|
|
// The normal way to render a subset of a drawable. |
|
|
// This assumes that PreRender and PrepForRender have already been called. |
|
|
// Note that PreRender and PrepForRender are called once per drawable per render |
|
|
// with a visList containing all of the spans which will be rendered, but |
|
|
// Render itself may be called with multiple visList subsets which union to |
|
|
// the visList passed into PreRender/PrepForRender. This happens when drawing |
|
|
// sorted spans, because some spans from drawable B may be in the middle of |
|
|
// the spans of drawable A, so the sequence would be: |
|
|
// |
|
|
// PreRender(A, ATotalVisList); |
|
|
// PreRender(B, BTotalVisList); |
|
|
// PrepForRender(A, ATotalVisList); |
|
|
// PrepForRender(B, BTotalVisList); |
|
|
// Render(A, AFarHalfVisList); |
|
|
// Render(B, BTotalVisList); |
|
|
// Render(A, ANearHalfVisList); |
|
|
// See plPageTreeMgr, which handles all this. |
|
|
void plDXPipeline::Render( plDrawable *d, const hsTArray<Int16>& visList ) |
|
|
{ |
|
|
// Reset here, since we can push/pop renderTargets after BeginRender() but before |
|
|
// this function, which necessitates this being called |
|
|
if( fView.fXformResetFlags != 0 ) |
|
|
ITransformsToD3D(); |
|
|
|
|
|
plDrawableSpans *ds = plDrawableSpans::ConvertNoRef( d ); |
|
|
|
|
|
if( ds ) |
|
|
{ |
|
|
IRenderSpans( ds, visList ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// BeginDrawable //////////////////////////////////////////////////////////// |
|
|
// Obsolete, should be removed |
|
|
hsBool plDXPipeline::BeginDrawable( plDrawable *d ) |
|
|
{ |
|
|
return true; |
|
|
} |
|
|
|
|
|
//// EndDrawable ////////////////////////////////////////////////////////////// |
|
|
// Obsolete, should be removed |
|
|
|
|
|
hsBool plDXPipeline::EndDrawable( plDrawable *d ) |
|
|
{ |
|
|
return true; |
|
|
} |
|
|
|
|
|
// IMakeLightLists /////////////////////////////////////////////////////////// |
|
|
// Look through all the current lights, and fill out two lists. |
|
|
// Only active lights (not disabled, not exactly black, and not |
|
|
// ignored because of visibility regions by plVisMgr) will |
|
|
// be considered. |
|
|
// The first list is lights that will affect the avatar and similar |
|
|
// indeterminately mobile (physical) objects - fLights.fCharLights. |
|
|
// The second list is lights that aren't restricted by light include |
|
|
// lists. |
|
|
// These two abbreviated lists will be further refined for each object |
|
|
// and avatar to find the strongest 8 lights which affect that object. |
|
|
// A light with an include list, or LightGroup Component) has |
|
|
// been explicitly told which objects it affects, so they don't |
|
|
// need to be in the search lists. |
|
|
// These lists are only constructed once per render, but searched |
|
|
// multiple times |
|
|
void plDXPipeline::IMakeLightLists(plVisMgr* visMgr) |
|
|
{ |
|
|
plProfile_BeginTiming(FindSceneLights); |
|
|
fLights.fCharLights.SetCount(0); |
|
|
fLights.fVisLights.SetCount(0); |
|
|
if( visMgr ) |
|
|
{ |
|
|
const hsBitVector& visSet = visMgr->GetVisSet(); |
|
|
const hsBitVector& visNot = visMgr->GetVisNot(); |
|
|
plLightInfo* light; |
|
|
for( light = fLights.fActiveList; light != nil; light = light->GetNext() ) |
|
|
{ |
|
|
plProfile_IncCount(LightActive, 1); |
|
|
if( !light->IsIdle() && !light->InVisNot(visNot) && light->InVisSet(visSet) ) |
|
|
{ |
|
|
plProfile_IncCount(LightOn, 1); |
|
|
if( light->GetProperty(plLightInfo::kLPHasIncludes) ) |
|
|
{ |
|
|
if( light->GetProperty(plLightInfo::kLPIncludesChars) ) |
|
|
fLights.fCharLights.Append(light); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fLights.fVisLights.Append(light); |
|
|
fLights.fCharLights.Append(light); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
plLightInfo* light; |
|
|
for( light = fLights.fActiveList; light != nil; light = light->GetNext() ) |
|
|
{ |
|
|
plProfile_IncCount(LightActive, 1); |
|
|
if( !light->IsIdle() ) |
|
|
{ |
|
|
plProfile_IncCount(LightOn, 1); |
|
|
if( light->GetProperty(plLightInfo::kLPHasIncludes) ) |
|
|
{ |
|
|
if( light->GetProperty(plLightInfo::kLPIncludesChars) ) |
|
|
fLights.fCharLights.Append(light); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fLights.fVisLights.Append(light); |
|
|
fLights.fCharLights.Append(light); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_IncCount(LightVis, fLights.fVisLights.GetCount()); |
|
|
plProfile_IncCount(LightChar, fLights.fCharLights.GetCount()); |
|
|
|
|
|
plProfile_EndTiming(FindSceneLights); |
|
|
} |
|
|
|
|
|
// BeginVisMgr ///////////////////////////////////////////////////////// |
|
|
// Marks the beginning of a render with the given visibility manager. |
|
|
// In particular, we cache which lights the visMgr believes to be |
|
|
// currently active |
|
|
void plDXPipeline::BeginVisMgr(plVisMgr* visMgr) |
|
|
{ |
|
|
IMakeLightLists(visMgr); |
|
|
} |
|
|
|
|
|
// EndVisMgr /////////////////////////////////////////////////////////// |
|
|
// Marks the end of a render with the given visibility manager. |
|
|
void plDXPipeline::EndVisMgr(plVisMgr* visMgr) |
|
|
{ |
|
|
fLights.fCharLights.SetCount(0); |
|
|
fLights.fVisLights.SetCount(0); |
|
|
} |
|
|
|
|
|
// ICheckLighting /////////////////////////////////////////////////////// |
|
|
// For every span in the list of visible span indices, find the list of |
|
|
// lights that currently affect the span with an estimate of the strength |
|
|
// of how much the light affects it. The strongest 8 lights will be used |
|
|
// to illuminate that span. |
|
|
// For projective lights, there is no limit on how many are supported, other |
|
|
// than performance (usually fill rate limited). |
|
|
// The permaLights and permaProjs are lights explicitly selected for a span |
|
|
// via the LightGroup component. |
|
|
// For static objects and static lights, the lighting was done offline and stored |
|
|
// in the vertex diffuse color. |
|
|
// So here we're only looking for: |
|
|
// A) moving objects, which can't be staticly lit, so are affected by all runtime lights. |
|
|
// B) moving lights, which can't staticly light, so affect all objects |
|
|
// C) specular objects + specular lights, since specular can't be precomputed. |
|
|
void plDXPipeline::ICheckLighting(plDrawableSpans* drawable, hsTArray<Int16>& visList, plVisMgr* visMgr) |
|
|
{ |
|
|
if( fView.fRenderState & kRenderNoLights ) |
|
|
return; |
|
|
|
|
|
if( !visList.GetCount() ) |
|
|
return; |
|
|
|
|
|
plLightInfo *light; |
|
|
int j; |
|
|
|
|
|
// First add in the explicit lights (from LightGroups). |
|
|
// Refresh the lights as they are added (actually a lazy eval). |
|
|
plProfile_BeginTiming(FindLights); |
|
|
plProfile_BeginTiming(FindPerm); |
|
|
for( j = 0; j < visList.GetCount(); j++ ) |
|
|
{ |
|
|
drawable->GetSpan( visList[ j ] )->ClearLights(); |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoRuntimeLights)) |
|
|
continue; |
|
|
|
|
|
// Set the bits for the lights added from the permanent lists (during ClearLights()). |
|
|
int k; |
|
|
const hsTArray<plLightInfo*>& permaLights = drawable->GetSpan(visList[j])->fPermaLights; |
|
|
for( k = 0; k < permaLights.GetCount(); k++ ) |
|
|
{ |
|
|
permaLights[k]->Refresh(); |
|
|
if( permaLights[k]->GetProperty(plLightInfo::kLPShadowLightGroup) && !permaLights[k]->IsIdle() ) |
|
|
{ |
|
|
// If it casts a shadow, attach the shadow now. |
|
|
ISetShadowFromGroup(drawable, drawable->GetSpan(visList[j]), permaLights[k]); |
|
|
} |
|
|
} |
|
|
const hsTArray<plLightInfo*>& permaProjs = drawable->GetSpan(visList[j])->fPermaProjs; |
|
|
for( k = 0; k < permaProjs.GetCount(); k++ ) |
|
|
{ |
|
|
permaProjs[k]->Refresh(); |
|
|
if( permaProjs[k]->GetProperty(plLightInfo::kLPShadowLightGroup) && !permaProjs[k]->IsIdle() ) |
|
|
{ |
|
|
// If it casts a shadow, attach the shadow now. |
|
|
ISetShadowFromGroup(drawable, drawable->GetSpan(visList[j]), permaProjs[k]); |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(FindPerm); |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoRuntimeLights)) |
|
|
{ |
|
|
plProfile_EndTiming( FindLights ); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Sort the incoming spans as either |
|
|
// A) moving - affected by all lights - moveList |
|
|
// B) specular - affected by specular lights - specList |
|
|
// C) visible - affected by moving lights - visList |
|
|
static hsTArray<Int16> tmpList; |
|
|
static hsTArray<Int16> moveList; |
|
|
static hsTArray<Int16> specList; |
|
|
|
|
|
moveList.SetCount(0); |
|
|
specList.SetCount(0); |
|
|
|
|
|
plProfile_BeginTiming(FindSpan); |
|
|
int k; |
|
|
for( k = 0; k < visList.GetCount(); k++ ) |
|
|
{ |
|
|
const plSpan* span = drawable->GetSpan(visList[k]); |
|
|
|
|
|
if( span->fProps & plSpan::kPropRunTimeLight ) |
|
|
{ |
|
|
moveList.Append(visList[k]); |
|
|
specList.Append(visList[k]); |
|
|
} |
|
|
else if( span->fProps & plSpan::kPropMatHasSpecular ) |
|
|
specList.Append(visList[k]); |
|
|
} |
|
|
plProfile_EndTiming(FindSpan); |
|
|
|
|
|
// Make a list of lights that can potentially affect spans in this drawable |
|
|
// based on the drawables bounds and properties. |
|
|
// If the drawable has the PropCharacter property, it is affected by lights |
|
|
// in fLights.fCharLights, else only by the smaller list of fLights.fVisLights. |
|
|
plProfile_BeginTiming(FindActiveLights); |
|
|
static hsTArray<plLightInfo*> lightList; |
|
|
lightList.SetCount(0); |
|
|
const hsBool isChar = 0 != drawable->GetNativeProperty(plDrawable::kPropCharacter); |
|
|
if( isChar ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fLights.fCharLights.GetCount(); i++ ) |
|
|
{ |
|
|
if( fLights.fCharLights[i]->AffectsBound(drawable->GetSpaceTree()->GetWorldBounds()) ) |
|
|
lightList.Append(fLights.fCharLights[i]); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fLights.fVisLights.GetCount(); i++ ) |
|
|
{ |
|
|
if( fLights.fVisLights[i]->AffectsBound(drawable->GetSpaceTree()->GetWorldBounds()) ) |
|
|
lightList.Append(fLights.fVisLights[i]); |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(FindActiveLights); |
|
|
|
|
|
// Loop over the lights and for each light, extract a list of the spans that light |
|
|
// affects. Append the light to each spans list with a scalar strength of how strongly |
|
|
// the light affects it. Since the strength is based on the object's center position, |
|
|
// it's not very accurate, but good enough for selecting which lights to use. |
|
|
plProfile_BeginTiming(ApplyActiveLights); |
|
|
for( k = 0; k < lightList.GetCount(); k++ ) |
|
|
{ |
|
|
light = lightList[k]; |
|
|
|
|
|
tmpList.SetCount(0); |
|
|
if( light->GetProperty(plLightInfo::kLPMovable) ) |
|
|
{ |
|
|
plProfile_BeginTiming(ApplyMoving); |
|
|
|
|
|
const hsTArray<Int16>& litList = light->GetAffected(drawable->GetSpaceTree(), |
|
|
visList, |
|
|
tmpList, |
|
|
drawable->GetNativeProperty(plDrawable::kPropCharacter) ); |
|
|
|
|
|
// PUT OVERRIDE FOR KILLING PROJECTORS HERE!!!! |
|
|
hsBool proj = nil != light->GetProjection(); |
|
|
if( fView.fRenderState & kRenderNoProjection ) |
|
|
proj = false; |
|
|
|
|
|
for( j = 0; j < litList.GetCount(); j++ ) |
|
|
{ |
|
|
// Use the light IF light is enabled and |
|
|
// 1) light is movable |
|
|
// 2) span is movable, or |
|
|
// 3) Both the light and the span have specular |
|
|
const plSpan* span = drawable->GetSpan(litList[j]); |
|
|
hsBool currProj = proj; |
|
|
if( span->fProps & plSpan::kPropProjAsVtx ) |
|
|
currProj = false; |
|
|
|
|
|
if( !(currProj && (span->fProps & plSpan::kPropSkipProjection)) ) |
|
|
{ |
|
|
plDXLightRef *ref = (plDXLightRef *)light->GetDeviceRef(); |
|
|
hsScalar strength, scale; |
|
|
|
|
|
light->GetStrengthAndScale(span->fWorldBounds, strength, scale); |
|
|
|
|
|
// We can't pitch a light because it's "strength" is zero, because the strength is based |
|
|
// on the center of the span and isn't conservative enough. We can pitch based on the |
|
|
// scale though, since a light scaled down to zero will have no effect no where. |
|
|
if( scale > 0 ) |
|
|
{ |
|
|
plProfile_Inc(FindLightsFound); |
|
|
span->AddLight(light, strength, scale, currProj); |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(ApplyMoving); |
|
|
|
|
|
} |
|
|
else if( light->GetProperty(plLightInfo::kLPHasSpecular) ) |
|
|
{ |
|
|
if( !specList.GetCount() ) |
|
|
continue; |
|
|
|
|
|
plProfile_BeginTiming(ApplyToSpec); |
|
|
|
|
|
const hsTArray<Int16>& litList = light->GetAffected(drawable->GetSpaceTree(), |
|
|
specList, |
|
|
tmpList, |
|
|
drawable->GetNativeProperty(plDrawable::kPropCharacter) ); |
|
|
|
|
|
// PUT OVERRIDE FOR KILLING PROJECTORS HERE!!!! |
|
|
hsBool proj = nil != light->GetProjection(); |
|
|
if( fView.fRenderState & kRenderNoProjection ) |
|
|
proj = false; |
|
|
|
|
|
for( j = 0; j < litList.GetCount(); j++ ) |
|
|
{ |
|
|
// Use the light IF light is enabled and |
|
|
// 1) light is movable |
|
|
// 2) span is movable, or |
|
|
// 3) Both the light and the span have specular |
|
|
const plSpan* span = drawable->GetSpan(litList[j]); |
|
|
hsBool currProj = proj; |
|
|
if( span->fProps & plSpan::kPropProjAsVtx ) |
|
|
currProj = false; |
|
|
|
|
|
if( !(currProj && (span->fProps & plSpan::kPropSkipProjection)) ) |
|
|
{ |
|
|
plDXLightRef *ref = (plDXLightRef *)light->GetDeviceRef(); |
|
|
hsScalar strength, scale; |
|
|
|
|
|
light->GetStrengthAndScale(span->fWorldBounds, strength, scale); |
|
|
|
|
|
// We can't pitch a light because it's "strength" is zero, because the strength is based |
|
|
// on the center of the span and isn't conservative enough. We can pitch based on the |
|
|
// scale though, since a light scaled down to zero will have no effect no where. |
|
|
if( scale > 0 ) |
|
|
{ |
|
|
plProfile_Inc(FindLightsFound); |
|
|
span->AddLight(light, strength, scale, currProj); |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(ApplyToSpec); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( !moveList.GetCount() ) |
|
|
continue; |
|
|
|
|
|
plProfile_BeginTiming(ApplyToMoving); |
|
|
|
|
|
const hsTArray<Int16>& litList = light->GetAffected(drawable->GetSpaceTree(), |
|
|
moveList, |
|
|
tmpList, |
|
|
drawable->GetNativeProperty(plDrawable::kPropCharacter) ); |
|
|
|
|
|
// PUT OVERRIDE FOR KILLING PROJECTORS HERE!!!! |
|
|
hsBool proj = nil != light->GetProjection(); |
|
|
if( fView.fRenderState & kRenderNoProjection ) |
|
|
proj = false; |
|
|
|
|
|
for( j = 0; j < litList.GetCount(); j++ ) |
|
|
{ |
|
|
// Use the light IF light is enabled and |
|
|
// 1) light is movable |
|
|
// 2) span is movable, or |
|
|
// 3) Both the light and the span have specular |
|
|
const plSpan* span = drawable->GetSpan(litList[j]); |
|
|
hsBool currProj = proj; |
|
|
if( span->fProps & plSpan::kPropProjAsVtx ) |
|
|
currProj = false; |
|
|
|
|
|
if( !(currProj && (span->fProps & plSpan::kPropSkipProjection)) ) |
|
|
{ |
|
|
plDXLightRef *ref = (plDXLightRef *)light->GetDeviceRef(); |
|
|
hsScalar strength, scale; |
|
|
|
|
|
light->GetStrengthAndScale(span->fWorldBounds, strength, scale); |
|
|
|
|
|
// We can't pitch a light because it's "strength" is zero, because the strength is based |
|
|
// on the center of the span and isn't conservative enough. We can pitch based on the |
|
|
// scale though, since a light scaled down to zero will have no effect no where. |
|
|
if( scale > 0 ) |
|
|
{ |
|
|
plProfile_Inc(FindLightsFound); |
|
|
span->AddLight(light, strength, scale, currProj); |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(ApplyToMoving); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
plProfile_EndTiming(ApplyActiveLights); |
|
|
|
|
|
IAttachShadowsToReceivers(drawable, visList); |
|
|
|
|
|
plProfile_EndTiming(FindLights); |
|
|
} |
|
|
|
|
|
// HarvestVisible //////////////////////////////////////////////////////////////////////// |
|
|
// Contruct a list of the indices of leaf nodes in the given spacetree which are currently |
|
|
// visible according to the current cull tree. The cull tree factors in camera frustum and |
|
|
// occluder polys, but _not_ the current visibility regions, plVisMgr. |
|
|
// This is the normal path for visibility culling at a gross level (e.g. which SceneNodes |
|
|
// to bother with, which drawables within the SceneNode). For finer objects, like the spans |
|
|
// themselves, the culling is done via IGetVisibleSpans, which also takes the plVisMgr into |
|
|
// account. |
|
|
hsBool plDXPipeline::HarvestVisible(plSpaceTree* space, hsTArray<Int16>& visList) |
|
|
{ |
|
|
if( !space ) |
|
|
return false; |
|
|
|
|
|
space->SetViewPos(GetViewPositionWorld()); |
|
|
|
|
|
space->Refresh(); |
|
|
|
|
|
if( fView.fCullTreeDirty ) |
|
|
IRefreshCullTree(); |
|
|
|
|
|
plProfile_BeginTiming(Harvest); |
|
|
fView.fCullTree.Harvest(space, visList); |
|
|
plProfile_EndTiming(Harvest); |
|
|
|
|
|
return visList.GetCount() != 0; |
|
|
} |
|
|
|
|
|
//// IGetVisibleSpans ///////////////////////////////////////////////////// |
|
|
// Given a drawable, returns a list of visible span indices. Disabled spans will not |
|
|
// show up in the list, behaving as if they were culled. |
|
|
// See plCullTree (in plPipeline) and plSpaceTree (in plDrawable) and plVisMgr (in plScene). |
|
|
void plDXPipeline::IGetVisibleSpans( plDrawableSpans* drawable, hsTArray<Int16>& visList, plVisMgr* visMgr ) |
|
|
{ |
|
|
static hsTArray<Int16> tmpVis; |
|
|
tmpVis.SetCount(0); |
|
|
visList.SetCount(0); |
|
|
|
|
|
drawable->GetSpaceTree()->SetViewPos(GetViewPositionWorld()); |
|
|
|
|
|
drawable->GetSpaceTree()->Refresh(); |
|
|
|
|
|
if( fView.fCullTreeDirty ) |
|
|
IRefreshCullTree(); |
|
|
|
|
|
const hsScalar viewDist = GetViewDirWorld().InnerProduct(GetViewPositionWorld()); |
|
|
|
|
|
const hsTArray<plSpan *> &spans = drawable->GetSpanArray(); |
|
|
|
|
|
plProfile_BeginTiming(Harvest); |
|
|
if( visMgr ) |
|
|
{ |
|
|
drawable->SetVisSet(visMgr); |
|
|
fView.fCullTree.Harvest(drawable->GetSpaceTree(), tmpVis); |
|
|
drawable->SetVisSet(nil); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fView.fCullTree.Harvest(drawable->GetSpaceTree(), tmpVis); |
|
|
} |
|
|
|
|
|
// This is a big waste of time, As a desparate "optimization" pass, the artists |
|
|
// insist on going through and marking objects to fade or pop out of rendering |
|
|
// past a certain distance. This breaks the batching and requires more CPU to |
|
|
// check the objects by distance. Since there is no pattern to the distance at |
|
|
// which objects will be told not to draw, there's no way to make this hierarchical, |
|
|
// which is what it would take to make it a performance win. So they succeed in |
|
|
// reducing the poly count, but generally the frame rate goes _down_ as well. |
|
|
// Unfortunately, this technique actually does work in a few key areas, so |
|
|
// I haven't been able to purge it. |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagSkipVisDist)) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < tmpVis.GetCount(); i++ ) |
|
|
{ |
|
|
if( spans[tmpVis[i]]->fSubType & GetSubDrawableTypeMask() ) |
|
|
{ |
|
|
visList.Append(tmpVis[i]); |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < tmpVis.GetCount(); i++ ) |
|
|
{ |
|
|
if( spans[tmpVis[i]]->fSubType & GetSubDrawableTypeMask() ) |
|
|
{ |
|
|
// We'll check here for spans we can discard because they've completely distance faded out. |
|
|
// Note this is based on view direction distance (because the fade is), rather than the |
|
|
// preferrable distance to camera we sort by. |
|
|
hsScalar minDist, maxDist; |
|
|
if( drawable->GetSubVisDists(tmpVis[i], minDist, maxDist) ) |
|
|
{ |
|
|
const hsBounds3Ext& bnd = drawable->GetSpaceTree()->GetNode(tmpVis[i]).fWorldBounds; |
|
|
hsPoint2 depth; |
|
|
bnd.TestPlane(GetViewDirWorld(), depth); |
|
|
if( (0 < minDist + viewDist - depth.fY) |
|
|
||(0 > maxDist + viewDist - depth.fX) ) |
|
|
continue; |
|
|
} |
|
|
|
|
|
visList.Append(tmpVis[i]); |
|
|
} |
|
|
} |
|
|
} |
|
|
plProfile_EndTiming(Harvest); |
|
|
} |
|
|
|
|
|
// ISetupTransforms ////////////////////////////////////////////////////////////////////////////////// |
|
|
// Set the D3D world transform according to the input span. |
|
|
// Engine currently supports HW vertex blending with 2 matrices, |
|
|
// else a single Local To World. |
|
|
// If software skinning is being used, the WORLD matrix will be identity, |
|
|
// because the full local to world is folded into the skinned vertices. |
|
|
void plDXPipeline::ISetupTransforms(plDrawableSpans* drawable, const plSpan& span, hsMatrix44& lastL2W) |
|
|
{ |
|
|
if( span.fNumMatrices ) |
|
|
{ |
|
|
if( span.fNumMatrices <= 2 ) |
|
|
{ |
|
|
ISetLocalToWorld( span.fLocalToWorld, span.fWorldToLocal ); |
|
|
lastL2W = span.fLocalToWorld; |
|
|
} |
|
|
else |
|
|
{ |
|
|
lastL2W.Reset(); |
|
|
ISetLocalToWorld( lastL2W, lastL2W ); |
|
|
fView.fLocalToWorldLeftHanded = span.fLocalToWorld.GetParity(); |
|
|
} |
|
|
} |
|
|
else |
|
|
if( lastL2W != span.fLocalToWorld ) |
|
|
{ |
|
|
ISetLocalToWorld( span.fLocalToWorld, span.fWorldToLocal ); |
|
|
lastL2W = span.fLocalToWorld; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fView.fLocalToWorldLeftHanded = lastL2W.GetParity(); |
|
|
} |
|
|
|
|
|
if( span.fNumMatrices == 2 ) |
|
|
{ |
|
|
D3DXMATRIX mat; |
|
|
IMatrix44ToD3DMatrix(mat, drawable->GetPaletteMatrix(span.fBaseMatrix+1)); |
|
|
fD3DDevice->SetTransform(D3DTS_WORLDMATRIX(1), &mat); |
|
|
fD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_1WEIGHTS); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE); |
|
|
} |
|
|
} |
|
|
|
|
|
// IRefreshDynVertices //////////////////////////////////////////////////////////////////////// |
|
|
// All dynamic vertices share a single dynamic vertex buffer. They are cycled through |
|
|
// that buffer using the NOOVERWRITE/DISCARD paradigm. Since the vertices sharing that |
|
|
// buffer may be of different formats, care is taken to always start a group of vertices |
|
|
// a the next available position in the buffer aligned with that vertex size. |
|
|
// Only software skinned objects, dynamic decals, and particle systems currently use the |
|
|
// dynamic vertex buffer. |
|
|
hsBool plDXPipeline::IRefreshDynVertices(plGBufferGroup* group, plDXVertexBufferRef* vRef) |
|
|
{ |
|
|
// First, pad out our next slot to be on a vertex boundary (for this vertex size). |
|
|
fNextDynVtx = ((fNextDynVtx + vRef->fVertexSize-1) / vRef->fVertexSize) * vRef->fVertexSize; |
|
|
|
|
|
Int32 size = (group->GetVertBufferEnd(vRef->fIndex) - group->GetVertBufferStart(vRef->fIndex)) * vRef->fVertexSize; |
|
|
if( !size ) |
|
|
return false; // No error, just nothing to do. |
|
|
|
|
|
hsAssert(size > 0, "Bad start and end counts in a group"); |
|
|
|
|
|
// If we DON'T have room in our dynamic buffer |
|
|
if( fNextDynVtx + size > fDynVtxSize ) |
|
|
{ |
|
|
plProfile_IncCount(DynVBuffs, 1); |
|
|
|
|
|
// Advance the timestamp, because we're about to reuse the buffer |
|
|
fVtxRefTime++; |
|
|
|
|
|
// Reset next available spot index to zero |
|
|
fNextDynVtx = 0; |
|
|
|
|
|
} |
|
|
// Point our ref at the next available spot |
|
|
Int32 newStart = fNextDynVtx / vRef->fVertexSize; |
|
|
|
|
|
vRef->fOffset = newStart - group->GetVertBufferStart(vRef->fIndex); |
|
|
|
|
|
// Lock the buffer |
|
|
// If index is zero, lock with discard, else with overwrite. |
|
|
DWORD lockFlag = fNextDynVtx ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD; |
|
|
UInt8* destPtr = nil; |
|
|
if( FAILED( fDynVtxBuff->Lock( fNextDynVtx, |
|
|
size, |
|
|
(void **)&destPtr, |
|
|
lockFlag) ) ) |
|
|
{ |
|
|
hsAssert( false, "Cannot lock vertex buffer for writing" ); |
|
|
return true; |
|
|
} |
|
|
|
|
|
UInt8* vData; |
|
|
if( vRef->fData ) |
|
|
{ |
|
|
vData = vRef->fData; |
|
|
} |
|
|
else |
|
|
{ |
|
|
vData = group->GetVertBufferData(vRef->fIndex) + group->GetVertBufferStart(vRef->fIndex) * vRef->fVertexSize; |
|
|
} |
|
|
memcpy(destPtr, vData, size); |
|
|
|
|
|
// Unlock the buffer |
|
|
fDynVtxBuff->Unlock(); |
|
|
|
|
|
// Advance next available spot index |
|
|
fNextDynVtx += size; |
|
|
|
|
|
// Set the timestamp |
|
|
vRef->fRefTime = fVtxRefTime; |
|
|
vRef->SetDirty(false); |
|
|
|
|
|
if( !vRef->fD3DBuffer ) |
|
|
{ |
|
|
vRef->fD3DBuffer = fDynVtxBuff; |
|
|
fDynVtxBuff->AddRef(); |
|
|
} |
|
|
hsAssert(vRef->fD3DBuffer == fDynVtxBuff, "Holding on to an old dynamic buffer?"); |
|
|
|
|
|
// vRef->SetRebuiltSinceUsed(true); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
// ICheckAuxBuffers /////////////////////////////////////////////////////////////////////// |
|
|
// The AuxBuffers are associated with drawables for things to be drawn right after that |
|
|
// drawable's contents. In particular, see the plDynaDecal, which includes things like |
|
|
// water ripples, bullet hits, and footprints. |
|
|
// This function just makes sure they are ready to be rendered, called right before |
|
|
// the rendering. |
|
|
hsBool plDXPipeline::ICheckAuxBuffers(const plAuxSpan* span) |
|
|
{ |
|
|
plGBufferGroup* group = span->fGroup; |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)group->GetVertexBufferRef(span->fVBufferIdx); |
|
|
if( !vRef ) |
|
|
return true; |
|
|
|
|
|
plDXIndexBufferRef* iRef = (plDXIndexBufferRef*)group->GetIndexBufferRef(span->fIBufferIdx); |
|
|
if( !iRef ) |
|
|
return true; |
|
|
|
|
|
// If our vertex buffer ref is volatile and the timestamp is off |
|
|
// then it needs to be refilled |
|
|
if( vRef->Expired(fVtxRefTime) ) |
|
|
{ |
|
|
IRefreshDynVertices(group, vRef); |
|
|
} |
|
|
if( vRef->fOffset != iRef->fOffset ) |
|
|
{ |
|
|
iRef->fOffset = vRef->fOffset; |
|
|
|
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
return false; // No error |
|
|
} |
|
|
|
|
|
// ICheckDynBuffers //////////////////////////////////////////////////////////////////////////////////////// |
|
|
// Make sure the buffers underlying this span are ready to be rendered. Meaning that the underlying |
|
|
// D3D buffers are in sync with the plasma buffers. |
|
|
hsBool plDXPipeline::ICheckDynBuffers(plDrawableSpans* drawable, plGBufferGroup* group, const plSpan* spanBase) |
|
|
{ |
|
|
if( !(spanBase->fTypeMask & plSpan::kVertexSpan) ) |
|
|
return false; |
|
|
// If we arent' an trilist, we're toast. |
|
|
if( !(spanBase->fTypeMask & plSpan::kIcicleSpan) ) |
|
|
return false; |
|
|
|
|
|
plIcicle* span = (plIcicle*)spanBase; |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)group->GetVertexBufferRef(span->fVBufferIdx); |
|
|
if( !vRef ) |
|
|
return true; |
|
|
|
|
|
plDXIndexBufferRef* iRef = (plDXIndexBufferRef*)group->GetIndexBufferRef(span->fIBufferIdx); |
|
|
if( !iRef ) |
|
|
return true; |
|
|
|
|
|
// If our vertex buffer ref is volatile and the timestamp is off |
|
|
// then it needs to be refilled |
|
|
if( vRef->Expired(fVtxRefTime) ) |
|
|
{ |
|
|
IRefreshDynVertices(group, vRef); |
|
|
} |
|
|
if( vRef->fOffset != iRef->fOffset ) |
|
|
{ |
|
|
iRef->fOffset = vRef->fOffset; |
|
|
|
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
if( iRef->IsDirty() ) |
|
|
{ |
|
|
IFillIndexBufferRef(iRef, group, span->fIBufferIdx); |
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
return false; // No error |
|
|
} |
|
|
|
|
|
//// IRenderSpans ///////////////////////////////////////////////////////////// |
|
|
// Renders an array of spans obtained from a plDrawableSpans object |
|
|
// The incoming visList gives the indices of the spans which are visible and should |
|
|
// be drawn now, and gives them in sorted order. |
|
|
void plDXPipeline::IRenderSpans( plDrawableSpans *drawable, const hsTArray<Int16>& visList ) |
|
|
{ |
|
|
plProfile_BeginTiming(RenderSpan); |
|
|
|
|
|
hsMatrix44 lastL2W; |
|
|
UInt32 i, j; |
|
|
bool drewPatch = false; |
|
|
hsGMaterial *material; |
|
|
|
|
|
const hsTArray<plSpan *>& spans = drawable->GetSpanArray(); |
|
|
|
|
|
plProfile_IncCount(EmptyList, !visList.GetCount()); |
|
|
|
|
|
/// Set this (*before* we do our TestVisibleWorld stuff...) |
|
|
lastL2W.Reset(); |
|
|
ISetLocalToWorld( lastL2W, lastL2W ); // This is necessary; otherwise, we have to test for |
|
|
// the first transform set, since this'll be identity |
|
|
// but the actual device transform won't be (unless |
|
|
// we do this) |
|
|
|
|
|
|
|
|
/// Loop through our spans, combining them when possible |
|
|
for( i = 0; i < visList.GetCount(); ) |
|
|
{ |
|
|
material = GetOverrideMaterial() ? GetOverrideMaterial() : drawable->GetMaterial( spans[ visList[ i ] ]->fMaterialIdx ); |
|
|
|
|
|
/// It's an icicle--do our icicle merge loop |
|
|
plIcicle tempIce(*( (plIcicle *)spans[ visList[ i ] ] )); |
|
|
|
|
|
// Start at i + 1, look for as many spans as we can add to tempIce |
|
|
for( j = i + 1; j < visList.GetCount(); j++ ) |
|
|
{ |
|
|
if( GetOverrideMaterial() ) |
|
|
tempIce.fMaterialIdx = spans[visList[j]]->fMaterialIdx; |
|
|
|
|
|
plProfile_BeginTiming(MergeCheck); |
|
|
if( !spans[ visList[ j ] ]->CanMergeInto( &tempIce ) ) |
|
|
{ |
|
|
plProfile_EndTiming(MergeCheck); |
|
|
break; |
|
|
} |
|
|
plProfile_EndTiming(MergeCheck); |
|
|
plProfile_Inc(SpanMerge); |
|
|
|
|
|
plProfile_BeginTiming(MergeSpan); |
|
|
spans[ visList[ j ] ]->MergeInto( &tempIce ); |
|
|
plProfile_EndTiming(MergeSpan); |
|
|
} |
|
|
|
|
|
if( material != nil ) |
|
|
{ |
|
|
// What do we change? |
|
|
|
|
|
plProfile_BeginTiming(SpanTransforms); |
|
|
ISetupTransforms(drawable, tempIce, lastL2W); |
|
|
plProfile_EndTiming(SpanTransforms); |
|
|
|
|
|
// Turn on this spans lights and turn off the rest. |
|
|
IEnableLights( &tempIce ); |
|
|
|
|
|
// Check that the underlying buffers are ready to go. |
|
|
plProfile_BeginTiming(CheckDyn); |
|
|
ICheckDynBuffers(drawable, drawable->GetBufferGroup(tempIce.fGroupIdx), &tempIce); |
|
|
plProfile_EndTiming(CheckDyn); |
|
|
|
|
|
plProfile_BeginTiming(CheckStat); |
|
|
CheckVertexBufferRef(drawable->GetBufferGroup(tempIce.fGroupIdx), tempIce.fVBufferIdx); |
|
|
CheckIndexBufferRef(drawable->GetBufferGroup(tempIce.fGroupIdx), tempIce.fIBufferIdx); |
|
|
plProfile_EndTiming(CheckStat); |
|
|
|
|
|
// Draw this span now |
|
|
IRenderBufferSpan( tempIce, |
|
|
drawable->GetVertexRef( tempIce.fGroupIdx, tempIce.fVBufferIdx ), |
|
|
drawable->GetIndexRef( tempIce.fGroupIdx, tempIce.fIBufferIdx ), |
|
|
material, |
|
|
tempIce.fVStartIdx, tempIce.fVLength, // These are used as our accumulated range |
|
|
tempIce.fIPackedIdx, tempIce.fILength ); |
|
|
} |
|
|
|
|
|
// Restart our search... |
|
|
i = j; |
|
|
} |
|
|
|
|
|
plProfile_EndTiming(RenderSpan); |
|
|
/// All done! |
|
|
} |
|
|
|
|
|
//// IAddBoundsSpan /////////////////////////////////////////////////////////// |
|
|
// Creates a new span for the given drawable to represent the specified |
|
|
// world bounds. |
|
|
// Debugging only. |
|
|
|
|
|
void plDXPipeline::IAddBoundsSpan( plDrawableSpans *ice, const hsBounds3Ext *bounds, UInt32 bndColor ) |
|
|
{ |
|
|
#if MCN_BOUNDS_SPANS |
|
|
static hsTArray<plGeometrySpan *> spanArray; |
|
|
static hsMatrix44 identMatrix; |
|
|
static hsPoint3 c[ 8 ], n[ 8 ]; |
|
|
static int nPts[ 8 ][ 3 ] = { { -1, -1, -1 }, { 1, -1, -1 }, { -1, 1, -1 }, { 1, 1, -1 }, |
|
|
{ -1, -1, 1 }, { 1, -1, 1 }, { -1, 1, 1 }, { 1, 1, 1 } }; |
|
|
int i; |
|
|
plGeometrySpan *newSpan; |
|
|
|
|
|
|
|
|
if( spanArray.GetCount() == 0 ) |
|
|
{ |
|
|
spanArray.Reset(); |
|
|
spanArray.Append( TRACKED_NEW plGeometrySpan() ); |
|
|
identMatrix.Reset(); |
|
|
|
|
|
// Make normals |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
n[ i ].fX = (float)nPts[ i ][ 0 ]; |
|
|
n[ i ].fY = (float)nPts[ i ][ 1 ]; |
|
|
n[ i ].fZ = (float)nPts[ i ][ 2 ]; |
|
|
} |
|
|
} |
|
|
else |
|
|
spanArray[ 0 ] = TRACKED_NEW plGeometrySpan(); |
|
|
|
|
|
newSpan = spanArray[ 0 ]; |
|
|
|
|
|
newSpan->BeginCreate( fBoundsMat, identMatrix, 0 ); |
|
|
|
|
|
// Make corners |
|
|
c[1] = c[2] = c[4] = *bounds->GetCorner(&c[0]); |
|
|
hsVector3 axes[3]; |
|
|
bounds->GetAxes(axes+0, axes+1, axes+2); |
|
|
c[1] += axes[0]; |
|
|
c[2] += axes[1]; |
|
|
c[4] += axes[2]; |
|
|
|
|
|
c[3] = c[1]; |
|
|
c[3] += axes[1]; |
|
|
|
|
|
c[5] = c[1]; |
|
|
c[5] += axes[2]; |
|
|
|
|
|
c[6] = c[2]; |
|
|
c[6] += axes[2]; |
|
|
|
|
|
c[7] = c[6]; |
|
|
c[7] += axes[0]; |
|
|
|
|
|
for( i = 0; i < 8; i++ ) |
|
|
newSpan->AddVertex( &c[ i ], &n[ i ], bndColor ); |
|
|
|
|
|
newSpan->AddTriIndices( 0, 1, 2 ); |
|
|
newSpan->AddTriIndices( 2, 1, 3 ); |
|
|
|
|
|
newSpan->AddTriIndices( 6, 3, 7 ); |
|
|
newSpan->AddTriIndices( 7, 1, 5 ); |
|
|
newSpan->AddTriIndices( 5, 0, 4 ); |
|
|
newSpan->AddTriIndices( 4, 2, 6 ); |
|
|
|
|
|
newSpan->EndCreate(); |
|
|
|
|
|
fBSpansToDelete.Append( ice->AppendDISpans( spanArray ) ); |
|
|
|
|
|
#endif |
|
|
} |
|
|
|
|
|
//// IAddNormalsSpan ////////////////////////////////////////////////////////// |
|
|
// Creates a new span for the given drawable to represent the specified |
|
|
// world bounds. |
|
|
// Debugging only. |
|
|
|
|
|
void plDXPipeline::IAddNormalsSpan( plDrawableSpans *ice, plIcicle *span, plDXVertexBufferRef *vRef, UInt32 bndColor ) |
|
|
{ |
|
|
#if MCN_BOUNDS_SPANS |
|
|
static hsTArray<plGeometrySpan *> spanArray; |
|
|
static hsMatrix44 identMatrix; |
|
|
static hsPoint3 point, off, blank; |
|
|
hsVector3 b2; |
|
|
UInt16 v1, v2, v3; |
|
|
int i; |
|
|
plGeometrySpan *newSpan; |
|
|
|
|
|
|
|
|
if( spanArray.GetCount() == 0 ) |
|
|
{ |
|
|
spanArray.Reset(); |
|
|
spanArray.Append( TRACKED_NEW plGeometrySpan() ); |
|
|
identMatrix.Reset(); |
|
|
} |
|
|
else |
|
|
spanArray[ 0 ] = TRACKED_NEW plGeometrySpan(); |
|
|
|
|
|
newSpan = spanArray[ 0 ]; |
|
|
|
|
|
newSpan->BeginCreate( fBoundsMat, span->fLocalToWorld, 0 ); |
|
|
|
|
|
for( i = 0; i < span->fVLength; i++ ) |
|
|
{ |
|
|
point = vRef->fOwner->Position( span->fVBufferIdx, span->fCellIdx, span->fCellOffset + i ); |
|
|
b2 = vRef->fOwner->Normal( span->fVBufferIdx, span->fCellIdx, span->fCellOffset + i ); |
|
|
off.Set( point.fX + b2.fX, point.fY + b2.fY, point.fZ + b2.fZ ); |
|
|
v1 = newSpan->AddVertex( &point, &blank, bndColor ); |
|
|
v2 = newSpan->AddVertex( &off, &blank, bndColor ); |
|
|
v3 = newSpan->AddVertex( &point, &blank, bndColor ); |
|
|
newSpan->AddTriIndices( v1, v2, v3 ); |
|
|
} |
|
|
|
|
|
newSpan->EndCreate(); |
|
|
|
|
|
fBSpansToDelete.Append( ice->AppendDISpans( spanArray ) ); |
|
|
|
|
|
#endif |
|
|
} |
|
|
|
|
|
//// BeginRender ////////////////////////////////////////////////////////////// |
|
|
// Specifies the beginning of the render frame. |
|
|
// If this succeeds (returns false) it must be matched with a call to EndRender. |
|
|
// Normally, the main client loop will wrap the entire scene render (including |
|
|
// any offscreen rendering) in a BeginRender/EndRender pair. There is no need |
|
|
// for further calls for sub-renders. |
|
|
hsBool plDXPipeline::BeginRender() |
|
|
{ |
|
|
// Do we have some restoration work ahead of us? |
|
|
// Checks for Device Lost condition |
|
|
if( IResetDevice() ) |
|
|
return true; |
|
|
|
|
|
// We were lost, but now we're found! Spread the good word brother! |
|
|
if( fDevWasLost ) |
|
|
{ |
|
|
/// Broadcast a message letting everyone know that we were recreated and that |
|
|
/// all device-specific stuff needs to be recreated |
|
|
// plDeviceRecreateMsg* clean = TRACKED_NEW plDeviceRecreateMsg(this); |
|
|
// plgDispatch::MsgSend(clean); |
|
|
|
|
|
fDevWasLost = false; |
|
|
} |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagReload)) |
|
|
{ |
|
|
IReleaseShaders(); |
|
|
fD3DDevice->EvictManagedResources(); |
|
|
fEvictTime = fTextUseTime; |
|
|
fManagedSeen = 0; |
|
|
SetDebugFlag(plPipeDbg::kFlagReload, false); |
|
|
} |
|
|
|
|
|
// offset transform |
|
|
RefreshScreenMatrices(); |
|
|
|
|
|
// If this is the primary BeginRender, make sure we're really ready. |
|
|
if( !fInSceneDepth++ ) |
|
|
{ |
|
|
// Workaround for NVidia memory manager bug. Search for "OSVERSIONINFO" to |
|
|
// find notes on the bug. This is where we purge managed memory periodically. |
|
|
plProfile_Set(ManSeen, fManagedSeen); |
|
|
if( fManagedCutoff ) |
|
|
{ |
|
|
plConst(UInt32) kMinEvictTime(1800); // ~2 minutes @ 15FPS |
|
|
if( (fManagedSeen > fManagedCutoff) && (fTexUsed + fVtxUsed < fManagedCutoff) && (fTextUseTime - fEvictTime > kMinEvictTime) ) |
|
|
{ |
|
|
fD3DDevice->EvictManagedResources(); |
|
|
fManagedSeen = 0; |
|
|
fEvictTime = fTextUseTime; |
|
|
plProfile_IncCount(ManEvict, 1); |
|
|
} |
|
|
} |
|
|
|
|
|
// Superfluous setting of Z state. |
|
|
fD3DDevice->SetRenderState( D3DRS_ZENABLE, |
|
|
( fView.IsPerspective() && ( fSettings.fD3DCaps & kCapsWBuffer ) ) |
|
|
? D3DZB_USEW : D3DZB_TRUE ); |
|
|
|
|
|
/// If we have a renderTarget active, use its viewport |
|
|
ISetViewport(); |
|
|
|
|
|
// Tell D3D we're ready to start rendering. |
|
|
if( FAILED(fD3DDevice->BeginScene()) ) |
|
|
{ |
|
|
fDeviceLost = true; |
|
|
} |
|
|
|
|
|
// Reset all our buffer/image usage counters |
|
|
fNextDynVtx = 0; |
|
|
fVtxRefTime++; |
|
|
|
|
|
fTexUsed = 0; |
|
|
fVtxUsed = 0; |
|
|
fTextUseTime++; |
|
|
|
|
|
// Render any shadow maps that have been submitted for this frame. |
|
|
IPreprocessShadows(); |
|
|
IPreprocessAvatarTextures(); |
|
|
} |
|
|
fRenderCnt++; |
|
|
|
|
|
// Would probably rather this be an input. |
|
|
fTime = hsTimer::GetSysSeconds(); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
//// ISetViewport ///////////////////////////////////////////////////////////// |
|
|
// Translate our viewport into a D3D viewport |
|
|
void plDXPipeline::ISetViewport() |
|
|
{ |
|
|
D3DVIEWPORT9 vp = { GetViewTransform().GetViewPortLeft(), |
|
|
GetViewTransform().GetViewPortTop(), |
|
|
GetViewTransform().GetViewPortWidth(), |
|
|
GetViewTransform().GetViewPortHeight(), |
|
|
0.f, 1.f }; |
|
|
|
|
|
|
|
|
WEAK_ERROR_CHECK( fD3DDevice->SetViewport( &vp ) ); |
|
|
} |
|
|
|
|
|
//// RenderScreenElements ///////////////////////////////////////////////////// |
|
|
// Renders all the screen elements, such as debug text and plates. Also puts |
|
|
// up all the info about vertex buffers and such. Should be called right |
|
|
// before EndRender(), but only on the main surface (not on renderTargets, |
|
|
// for example). |
|
|
|
|
|
void plDXPipeline::RenderScreenElements() |
|
|
{ |
|
|
bool reset = false; |
|
|
|
|
|
|
|
|
#if MCN_BOUNDS_SPANS |
|
|
if( fBoundsSpans && fBSpansToDelete.GetCount() > 0 ) |
|
|
{ |
|
|
Draw( fBoundsSpans ); |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < fBSpansToDelete.GetCount(); i++ ) |
|
|
fBoundsSpans->RemoveDISpans( fBSpansToDelete[ i ] ); |
|
|
|
|
|
fBSpansToDelete.Reset(); |
|
|
} |
|
|
#endif |
|
|
if( fCullProxy ) |
|
|
Draw( fCullProxy ); |
|
|
|
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
//WHITE |
|
|
static plPlate* hackPlate = nil; |
|
|
if( doHackPlate < hackOffscreens.GetCount() ) |
|
|
{ |
|
|
if( !hackPlate ) |
|
|
{ |
|
|
fPlateMgr->CreatePlate(&hackPlate, 0.5f, 0.5f, 1.0f, 1.0f); |
|
|
hackPlate->CreateBlankMaterial(32, 32, false); |
|
|
} |
|
|
} |
|
|
if( hackPlate ) |
|
|
{ |
|
|
if( doHackPlate < hackOffscreens.GetCount() ) |
|
|
{ |
|
|
hsGMaterial* hackMat = hackPlate->GetMaterial(); |
|
|
plLayer* lay = plLayer::ConvertNoRef(hackMat->GetLayer(0)); |
|
|
if( lay ) |
|
|
lay->SetTexture(hackOffscreens[doHackPlate]); |
|
|
hackPlate->SetVisible( true ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
hackPlate->SetVisible( false ); |
|
|
} |
|
|
} |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
|
|
|
hsGMatState tHack = PushMaterialOverride(hsGMatState::kMisc, hsGMatState::kMiscWireFrame, false); |
|
|
hsGMatState ambHack = PushMaterialOverride(hsGMatState::kShade, hsGMatState::kShadeWhite, true); |
|
|
|
|
|
plProfile_BeginTiming(PlateMgr); |
|
|
/// Plates |
|
|
if( fPlateMgr ) |
|
|
{ |
|
|
fPlateMgr->DrawToDevice( this ); |
|
|
reset = true; |
|
|
} |
|
|
plProfile_EndTiming(PlateMgr); |
|
|
|
|
|
PopMaterialOverride(ambHack, true); |
|
|
PopMaterialOverride(tHack, false); |
|
|
|
|
|
plProfile_BeginTiming(DebugText); |
|
|
/// Debug text |
|
|
if( fDebugTextMgr && plDebugText::Instance().IsEnabled() ) |
|
|
{ |
|
|
fDebugTextMgr->DrawToDevice( this ); |
|
|
|
|
|
reset = true; |
|
|
} |
|
|
plProfile_EndTiming(DebugText); |
|
|
|
|
|
plProfile_BeginTiming(Reset); |
|
|
if( reset ) |
|
|
{ |
|
|
// Reset these since the drawing might have trashed them |
|
|
hsRefCnt_SafeUnRef( fSettings.fCurrVertexBuffRef ); |
|
|
hsRefCnt_SafeUnRef( fSettings.fCurrIndexBuffRef ); |
|
|
fSettings.fCurrVertexBuffRef = nil; |
|
|
fSettings.fCurrIndexBuffRef = nil; |
|
|
|
|
|
fView.fXformResetFlags = fView.kResetAll; // Text destroys view transforms |
|
|
hsRefCnt_SafeUnRef( fLayerRef[ 0 ] ); |
|
|
fLayerRef[ 0 ] = nil; // Text destroys stage 0 texture |
|
|
} |
|
|
plProfile_EndTiming(Reset); |
|
|
} |
|
|
|
|
|
//// EndRender //////////////////////////////////////////////////////////////// |
|
|
// Tell D3D we're through rendering for this frame, and flip the back buffer to front. |
|
|
// Also includes a bit of making sure we're not holding onto anything that might |
|
|
// get deleted before the next render. |
|
|
hsBool plDXPipeline::EndRender() |
|
|
{ |
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
hackOffscreens.SetCount(0); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
|
|
|
IBottomLayer(); |
|
|
|
|
|
hsBool retVal = false; |
|
|
/// Actually end the scene |
|
|
if( !--fInSceneDepth ) |
|
|
{ |
|
|
WEAK_ERROR_CHECK( fD3DDevice->EndScene() ); |
|
|
retVal = IFlipSurface(); |
|
|
|
|
|
IClearShadowSlaves(); |
|
|
} |
|
|
|
|
|
// Do this last, after we've drawn everything |
|
|
// Just letting go of things we're done with for the frame. |
|
|
fForceMatHandle = true; |
|
|
hsRefCnt_SafeUnRef( fCurrMaterial ); |
|
|
fCurrMaterial = nil; |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
if( fLayerRef[i] ) |
|
|
{ |
|
|
hsRefCnt_SafeUnRef(fLayerRef[i]); |
|
|
fLayerRef[i] = nil; |
|
|
} |
|
|
} |
|
|
|
|
|
return retVal; |
|
|
} |
|
|
|
|
|
// SetGamma //////////////////////////////////////////////////////////// |
|
|
// Create and set a gamma table based on the input exponent values for |
|
|
// R, G, and B. Can also set explicit table using the other SetGamma(). |
|
|
hsBool plDXPipeline::SetGamma(hsScalar eR, hsScalar eG, hsScalar eB) |
|
|
{ |
|
|
if( fSettings.fNoGammaCorrect ) |
|
|
return false; |
|
|
|
|
|
D3DGAMMARAMP ramp; |
|
|
|
|
|
ramp.red[0] = ramp.green[0] = ramp.blue[0] = 0L; |
|
|
|
|
|
plConst(hsScalar) kMinE(0.1f); |
|
|
if( eR > kMinE ) |
|
|
eR = 1.f / eR; |
|
|
else |
|
|
eR = 1.f / kMinE; |
|
|
if( eG > kMinE ) |
|
|
eG = 1.f / eG; |
|
|
else |
|
|
eG = 1.f / kMinE; |
|
|
if( eB > kMinE ) |
|
|
eB = 1.f / eB; |
|
|
else |
|
|
eB = 1.f / kMinE; |
|
|
|
|
|
int i; |
|
|
for( i = 1; i < 256; i++ ) |
|
|
{ |
|
|
hsScalar orig = hsScalar(i) / 255.f; |
|
|
|
|
|
hsScalar gamm; |
|
|
gamm = pow(orig, eR); |
|
|
gamm *= hsScalar(UInt16(-1)); |
|
|
ramp.red[i] = UInt16(gamm); |
|
|
|
|
|
gamm = pow(orig, eG); |
|
|
gamm *= hsScalar(UInt16(-1)); |
|
|
ramp.green[i] = UInt16(gamm); |
|
|
|
|
|
gamm = pow(orig, eB); |
|
|
gamm *= hsScalar(UInt16(-1)); |
|
|
ramp.blue[i] = UInt16(gamm); |
|
|
} |
|
|
|
|
|
fD3DDevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, &ramp); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// SetGamma |
|
|
// Copy the input gamma tables and pass them to the hardware. |
|
|
hsBool plDXPipeline::SetGamma(const UInt16* const tabR, const UInt16* const tabG, const UInt16* const tabB) |
|
|
{ |
|
|
if( fSettings.fNoGammaCorrect ) |
|
|
return false; |
|
|
|
|
|
D3DGAMMARAMP ramp; |
|
|
memcpy(ramp.red, tabR, 256 * sizeof(WORD)); |
|
|
memcpy(ramp.green, tabG, 256 * sizeof(WORD)); |
|
|
memcpy(ramp.blue, tabB, 256 * sizeof(WORD)); |
|
|
|
|
|
fD3DDevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, &ramp); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
//// IFlipSurface ///////////////////////////////////////////////////////////// |
|
|
// Initiate moving the back buffer contents to the front buffer. Will detect |
|
|
// and set the device lost condition when it occurs. |
|
|
hsBool plDXPipeline::IFlipSurface() |
|
|
{ |
|
|
/// Works now for both fullscreen and windowed modes |
|
|
HRESULT hr = D3D_OK; |
|
|
if( fSettings.fCurrRenderTarget == nil ) |
|
|
{ |
|
|
hr = fD3DDevice->Present( nil, nil, fSettings.fHWnd, nil ); |
|
|
} |
|
|
|
|
|
if( FAILED(hr) ) |
|
|
{ |
|
|
fDeviceLost = true; |
|
|
} |
|
|
return fDeviceLost; |
|
|
} |
|
|
|
|
|
// ExtractMipMap |
|
|
// This code works and is fairly fast for creating a new mipmap |
|
|
// as a copy of the data in an offscreen render target. It's not |
|
|
// currently used, because of driver bugs found in rendering to |
|
|
// offscreen render targets. |
|
|
plMipmap* plDXPipeline::ExtractMipMap(plRenderTarget* targ) |
|
|
{ |
|
|
if( plCubicRenderTarget::ConvertNoRef(targ) ) |
|
|
return nil; |
|
|
|
|
|
if( targ->GetPixelSize() != 32 ) |
|
|
{ |
|
|
hsAssert(false, "Only RGBA8888 currently implemented"); |
|
|
return nil; |
|
|
} |
|
|
|
|
|
plDXRenderTargetRef* ref = (plDXRenderTargetRef*)targ->GetDeviceRef(); |
|
|
if( !ref ) |
|
|
return nil; |
|
|
|
|
|
IDirect3DSurface9* surf = ref->GetColorSurface(); |
|
|
if( !surf ) |
|
|
return nil; |
|
|
|
|
|
D3DLOCKED_RECT rect; |
|
|
if( FAILED( surf->LockRect(&rect, nil, D3DLOCK_READONLY) ) ) |
|
|
{ |
|
|
return nil; |
|
|
} |
|
|
|
|
|
const int width = targ->GetWidth(); |
|
|
const int height = targ->GetHeight(); |
|
|
|
|
|
plMipmap* mipMap = TRACKED_NEW plMipmap(width, height, plMipmap::kARGB32Config, 1); |
|
|
|
|
|
UInt8* ptr = (UInt8*)(rect.pBits); |
|
|
const int pitch = rect.Pitch; |
|
|
|
|
|
const UInt32 blackOpaque = 0xff000000; |
|
|
int y; |
|
|
for( y = 0; y < height; y++ ) |
|
|
{ |
|
|
UInt32* destPtr = mipMap->GetAddr32(0, y); |
|
|
UInt32* srcPtr = (UInt32*)ptr; |
|
|
int x; |
|
|
for( x = 0; x < width; x++ ) |
|
|
{ |
|
|
destPtr[x] = srcPtr[x] | blackOpaque; |
|
|
} |
|
|
ptr += pitch; |
|
|
} |
|
|
|
|
|
surf->UnlockRect(); |
|
|
|
|
|
return mipMap; |
|
|
} |
|
|
|
|
|
//// CaptureScreen //////////////////////////////////////////////////////////// |
|
|
// Copy the current contents of the front buffer to the destination mipmap, with optional |
|
|
// rescaling. Note that the mipmap function which does this rescaling is of low quality |
|
|
// (pyramid filter even though it claims a box filter) and low performance (slow). |
|
|
// If it mattered, it would take about an hour to have a higher performance, higher quality, |
|
|
// more robust rescale function. |
|
|
// This function is fairly straightforward, the complexity only comes from making sure |
|
|
// all pixels in dest get written to, even though the client window may be partially |
|
|
// offscreen. If the client window is partially offscreen, there will be no values |
|
|
// for the "offscreen pixels" to copy to dest, so opaque black is used. |
|
|
hsBool plDXPipeline::CaptureScreen( plMipmap *dest, bool flipVertical, UInt16 desiredWidth, UInt16 desiredHeight ) |
|
|
{ |
|
|
UInt32 y, *destPtr, *srcPtr, width, height, bigWidth, bigHeight; |
|
|
IDirect3DSurface9 *surface; |
|
|
D3DLOCKED_RECT rect; |
|
|
RECT rToLock; |
|
|
|
|
|
|
|
|
width = GetViewTransform().GetViewPortWidth(); |
|
|
height = GetViewTransform().GetViewPortHeight(); |
|
|
|
|
|
int left = 0; |
|
|
int right = width; |
|
|
int top = 0; |
|
|
int bottom = height; |
|
|
|
|
|
if( fSettings.fFullscreen ) |
|
|
{ |
|
|
if (FAILED(fD3DDevice->CreateOffscreenPlainSurface(width, height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &surface, NULL))) |
|
|
return false; |
|
|
|
|
|
rToLock.left = GetViewTransform().GetViewPortLeft(); |
|
|
rToLock.top = GetViewTransform().GetViewPortTop(); |
|
|
rToLock.right = GetViewTransform().GetViewPortRight(); |
|
|
rToLock.bottom = GetViewTransform().GetViewPortBottom(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
bigWidth = GetSystemMetrics( SM_CXSCREEN ); |
|
|
bigHeight = GetSystemMetrics( SM_CYSCREEN ); |
|
|
|
|
|
if (FAILED(fD3DDevice->CreateOffscreenPlainSurface(bigWidth, bigHeight, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &surface, NULL))) |
|
|
return false; |
|
|
|
|
|
GetClientRect( fSettings.fHWnd, &rToLock ); |
|
|
MapWindowPoints( fSettings.fHWnd, nil, (POINT *)&rToLock, 2 ); |
|
|
|
|
|
if( rToLock.right > bigWidth ) |
|
|
{ |
|
|
right -= (rToLock.right - bigWidth); |
|
|
rToLock.right = bigWidth; |
|
|
} |
|
|
if( rToLock.bottom > bigHeight ) |
|
|
{ |
|
|
bottom -= (rToLock.bottom - bigHeight); |
|
|
rToLock.bottom = bigHeight; |
|
|
} |
|
|
if( rToLock.top < 0 ) |
|
|
{ |
|
|
top -= rToLock.top; |
|
|
rToLock.top = 0; |
|
|
} |
|
|
if( rToLock.left < 0 ) |
|
|
{ |
|
|
left -= rToLock.left; |
|
|
rToLock.left = 0; |
|
|
} |
|
|
} |
|
|
|
|
|
UINT swapChain = 0; |
|
|
if( FAILED( fD3DDevice->GetFrontBufferData(swapChain, surface) ) ) |
|
|
{ |
|
|
ReleaseObject( surface ); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if( FAILED( surface->LockRect( &rect, &rToLock, D3DLOCK_READONLY ) ) ) |
|
|
{ |
|
|
ReleaseObject( surface ); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if( dest->GetWidth() != width || dest->GetHeight() != height || |
|
|
dest->GetPixelSize() != 32 ) |
|
|
{ |
|
|
dest->Reset(); |
|
|
dest->Create( width, height, plMipmap::kARGB32Config, 1 ); |
|
|
} |
|
|
|
|
|
const UInt32 blackOpaque = 0xff000000; |
|
|
/// Copy over |
|
|
for( y = 0; y < top; y++ ) |
|
|
{ |
|
|
if (flipVertical) |
|
|
destPtr = dest->GetAddr32( 0, height - 1 - y ); |
|
|
else |
|
|
destPtr = dest->GetAddr32( 0, y ); |
|
|
|
|
|
int x; |
|
|
for( x = 0; x < width; x++ ) |
|
|
{ |
|
|
*destPtr++ = blackOpaque; |
|
|
} |
|
|
} |
|
|
for( y = top; y < bottom; y++ ) |
|
|
{ |
|
|
srcPtr = (UInt32 *)( (UInt8 *)rect.pBits + rect.Pitch * y ); |
|
|
if (flipVertical) |
|
|
destPtr = dest->GetAddr32( 0, height - 1 - y ); |
|
|
else |
|
|
destPtr = dest->GetAddr32( 0, y ); |
|
|
|
|
|
int x; |
|
|
for( x = 0; x < left; x++ ) |
|
|
*destPtr++ = blackOpaque; |
|
|
|
|
|
memcpy( destPtr, srcPtr, (right - left) * sizeof( UInt32 ) ); |
|
|
destPtr += (right - left); |
|
|
|
|
|
for( x = right; x < width; x++ ) |
|
|
*destPtr++ = blackOpaque; |
|
|
} |
|
|
for( y = bottom; y < height; y++ ) |
|
|
{ |
|
|
if (flipVertical) |
|
|
destPtr = dest->GetAddr32( 0, height - 1 - y ); |
|
|
else |
|
|
destPtr = dest->GetAddr32( 0, y ); |
|
|
|
|
|
int x; |
|
|
for( x = 0; x < width; x++ ) |
|
|
{ |
|
|
*destPtr++ = blackOpaque; |
|
|
} |
|
|
} |
|
|
|
|
|
surface->UnlockRect(); |
|
|
ReleaseObject( surface ); |
|
|
|
|
|
if( desiredWidth != 0 && desiredHeight != nil ) |
|
|
{ |
|
|
// Rescale to the right size |
|
|
dest->ResizeNicely( desiredWidth, desiredHeight, plMipmap::kDefaultFilter ); |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Render Targets /////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// MakeRenderTargetRef ////////////////////////////////////////////////////// |
|
|
// Create the a Plasma render target ref, filling in the underlying D3D resources |
|
|
// (e.g. color/depth buffers). |
|
|
// Note that for ATI boards, we create a single depth surface for them to share. |
|
|
// That can actually be 2 depth surfaces, if some color surfaces are 16 bit and |
|
|
// others are 24/32 bit, since the ATI's want to match color depth with depth depth. |
|
|
hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) |
|
|
{ |
|
|
plDXRenderTargetRef *ref = nil; |
|
|
IDirect3DSurface9 *surface = nil, *depthSurface = nil; |
|
|
IDirect3DTexture9 *texture = nil; |
|
|
IDirect3DCubeTexture9 *cTexture = nil; |
|
|
D3DFORMAT surfFormat = D3DFMT_UNKNOWN, depthFormat = D3DFMT_UNKNOWN; |
|
|
D3DRESOURCETYPE resType; |
|
|
int i; |
|
|
plCubicRenderTarget *cubicRT; |
|
|
UInt16 width, height; |
|
|
|
|
|
hsAssert(!fManagedAlloced, "Allocating non-managed resource with managed resources alloc'd"); |
|
|
|
|
|
/// Check--is this renderTarget really a child of a cubicRenderTarget? |
|
|
if( owner->GetParent() != nil ) |
|
|
{ |
|
|
/// This'll create the deviceRefs for all of its children as well |
|
|
MakeRenderTargetRef( owner->GetParent() ); |
|
|
return owner->GetDeviceRef(); |
|
|
} |
|
|
|
|
|
// If we already have a rendertargetref, we just need it filled out with D3D resources. |
|
|
if( owner->GetDeviceRef() != nil ) |
|
|
ref = (plDXRenderTargetRef *)owner->GetDeviceRef(); |
|
|
|
|
|
// Look for supported format. Note that the surfFormat and depthFormat are |
|
|
// passed in by ref, so they may be different after this function call (if |
|
|
// an exact match isn't supported, but something similar is). |
|
|
if( !IPrepRenderTargetInfo( owner, surfFormat, depthFormat, resType ) ) |
|
|
{ |
|
|
hsAssert( false, "Error getting renderTarget info" ); |
|
|
return nil; |
|
|
} |
|
|
|
|
|
|
|
|
/// Create the render target now |
|
|
// Start with the depth surface. |
|
|
// Note that we only ever give a cubic rendertarget a single shared depth buffer, |
|
|
// since we only render one face at a time. If we were rendering part of face X, then part |
|
|
// of face Y, then more of face X, then they would all need their own depth buffers. |
|
|
if( owner->GetZDepth() && (owner->GetFlags() & ( plRenderTarget::kIsTexture | plRenderTarget::kIsOffscreen )) ) |
|
|
{ |
|
|
// 9600 THRASH |
|
|
if( !fSettings.fShareDepth ) |
|
|
{ |
|
|
/// Create the depthbuffer |
|
|
if( FAILED( fD3DDevice->CreateDepthStencilSurface( |
|
|
owner->GetWidth(), owner->GetHeight(), depthFormat, |
|
|
D3DMULTISAMPLE_NONE, 0, FALSE, |
|
|
&depthSurface, NULL ) ) ) |
|
|
{ |
|
|
return nil; |
|
|
} |
|
|
|
|
|
// See plDXRenderTargetRef::Release() |
|
|
//D3DSURF_MEMNEW(depthSurface); |
|
|
} |
|
|
else |
|
|
{ |
|
|
const int iZ = owner->GetZDepth() / 24; |
|
|
if( !fSharedDepthSurface[iZ] ) |
|
|
{ |
|
|
plConst(DWORD) kSharedWidth(800); |
|
|
plConst(DWORD) kSharedHeight(600); |
|
|
if( FAILED( fD3DDevice->CreateDepthStencilSurface( |
|
|
kSharedWidth, kSharedHeight, depthFormat, |
|
|
D3DMULTISAMPLE_NONE, 0, FALSE, |
|
|
&fSharedDepthSurface[iZ], NULL ) ) ) |
|
|
{ |
|
|
return nil; |
|
|
} |
|
|
// See plDXRenderTargetRef::Release() |
|
|
//D3DSURF_MEMNEW(fSharedDepthSurface[iZ]); |
|
|
fSharedDepthFormat[iZ] = depthFormat; |
|
|
} |
|
|
hsAssert(depthFormat == fSharedDepthFormat[iZ], "Mismatch on render target types"); |
|
|
fSharedDepthSurface[iZ]->AddRef(); |
|
|
depthSurface = fSharedDepthSurface[iZ]; |
|
|
} |
|
|
} |
|
|
|
|
|
// See if it's a cubic render target. |
|
|
// Primary consumer here is the vertex/pixel shader water. |
|
|
cubicRT = plCubicRenderTarget::ConvertNoRef( owner ); |
|
|
if( cubicRT != nil ) |
|
|
{ |
|
|
/// And create the ref (it'll know how to set all the flags) |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, |
|
|
D3DPOOL_DEFAULT, (IDirect3DCubeTexture9 **)&cTexture, NULL ) ) ) |
|
|
{ |
|
|
/// Create a CUBIC texture |
|
|
for( i = 0; i < 6; i++ ) |
|
|
{ |
|
|
plRenderTarget *face = cubicRT->GetFace( i ); |
|
|
plDXRenderTargetRef *fRef; |
|
|
|
|
|
if( face->GetDeviceRef() != nil ) |
|
|
{ |
|
|
fRef = (plDXRenderTargetRef *)face->GetDeviceRef(); |
|
|
fRef->Set( surfFormat, 0, face ); |
|
|
if( !fRef->IsLinked() ) |
|
|
fRef->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
face->SetDeviceRef( TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, face, false ) ); |
|
|
( (plDXRenderTargetRef *)face->GetDeviceRef())->Link( &fRenderTargetRefList ); |
|
|
// Unref now, since for now ONLY the RT owns the ref, not us (not until we use it, at least) |
|
|
hsRefCnt_SafeUnRef( face->GetDeviceRef() ); |
|
|
} |
|
|
} |
|
|
|
|
|
D3DSURF_MEMNEW(cTexture); |
|
|
|
|
|
ref->SetTexture( cTexture, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
} |
|
|
// Not a cubic, is it a texture render target? These are currently used |
|
|
// primarily for shadow map generation. |
|
|
else if( owner->GetFlags() & plRenderTarget::kIsTexture ) |
|
|
{ |
|
|
/// Create a normal texture |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
if( !FAILED( fD3DDevice->CreateTexture( owner->GetWidth(), owner->GetHeight(), 1, D3DUSAGE_RENDERTARGET, surfFormat, |
|
|
D3DPOOL_DEFAULT, (IDirect3DTexture9 **)&texture, NULL ) ) ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(texture); |
|
|
|
|
|
ref->SetTexture( texture, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
} |
|
|
// Not a texture either, must be a plain offscreen. |
|
|
// Note that the plain offscreen code path works and was used until recently, |
|
|
// until it turned up that some hardware had bugs on rendering to |
|
|
// an offscreen. |
|
|
// Some GeForce1's had lighting anomolies, although my GeForce1 DDR didn't. |
|
|
// Some ATI's showed a momemtary glitch of corrupted rendering on the frame |
|
|
// when rendering both to the primary and an offscreen (again, not mine). |
|
|
// So the Offscreen isn't currently used for anything. |
|
|
else if( owner->GetFlags() & plRenderTarget::kIsOffscreen ) |
|
|
{ |
|
|
/// Create a blank surface |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
width = owner->GetWidth(); |
|
|
height = owner->GetHeight(); |
|
|
|
|
|
// Specify true for lockable, otherwise I'm not sure what we'd do with it. I guess we |
|
|
// could copyrect to another surface, presumably a texture. But right now the only |
|
|
// thing we use this for is to render a snapshot and copy it to sysmem, which implies |
|
|
// lockable. |
|
|
if( !FAILED( fD3DDevice->CreateRenderTarget( width, height, surfFormat, |
|
|
D3DMULTISAMPLE_NONE, 0, |
|
|
TRUE, &surface, NULL ) ) ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(surface); |
|
|
|
|
|
ref->SetTexture( surface, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// Keep it in a linked list for ready destruction. |
|
|
if( owner->GetDeviceRef() != ref ) |
|
|
{ |
|
|
owner->SetDeviceRef( ref ); |
|
|
// Unref now, since for now ONLY the RT owns the ref, not us (not until we use it, at least) |
|
|
hsRefCnt_SafeUnRef( ref ); |
|
|
if( ref != nil && !ref->IsLinked() ) |
|
|
ref->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( ref != nil && !ref->IsLinked() ) |
|
|
ref->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
|
|
|
// Mark as dirty. |
|
|
if( ref != nil ) |
|
|
{ |
|
|
ref->SetDirty( false ); |
|
|
} |
|
|
|
|
|
return ref; |
|
|
} |
|
|
|
|
|
//// SharedRenderTargetRef ////////////////////////////////////////////////////// |
|
|
// Same as MakeRenderTargetRef, except specialized for the shadow map generation. |
|
|
// The shadow map pools of a given dimension (called RenderTargetPool) all share |
|
|
// a single depth buffer of that size. This allows sharing on NVidia hardware |
|
|
// that wants the depth buffer dimensions to match the color buffer size. |
|
|
// It may be that NVidia hardware doesn't care any more. Contact Matthias |
|
|
// about that. |
|
|
hsGDeviceRef* plDXPipeline::SharedRenderTargetRef(plRenderTarget* share, plRenderTarget *owner) |
|
|
{ |
|
|
plDXRenderTargetRef* ref = nil; |
|
|
IDirect3DSurface9* surface = nil; |
|
|
IDirect3DSurface9* depthSurface = nil; |
|
|
IDirect3DTexture9* texture = nil; |
|
|
IDirect3DCubeTexture9* cTexture = nil; |
|
|
D3DFORMAT surfFormat = D3DFMT_UNKNOWN, depthFormat = D3DFMT_UNKNOWN; |
|
|
D3DRESOURCETYPE resType; |
|
|
int i; |
|
|
plCubicRenderTarget* cubicRT; |
|
|
UInt16 width, height; |
|
|
|
|
|
// If we don't already have one to share from, start from scratch. |
|
|
if( !share ) |
|
|
return MakeRenderTargetRef(owner); |
|
|
|
|
|
hsAssert(!fManagedAlloced, "Allocating non-managed resource with managed resources alloc'd"); |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
// Check out the validity of the match. Debug only. |
|
|
hsAssert(!owner->GetParent() == !share->GetParent(), "Mismatch on shared render target"); |
|
|
hsAssert(owner->GetWidth() == share->GetWidth(), "Mismatch on shared render target"); |
|
|
hsAssert(owner->GetHeight() == share->GetHeight(), "Mismatch on shared render target"); |
|
|
hsAssert(owner->GetZDepth() == share->GetZDepth(), "Mismatch on shared render target"); |
|
|
hsAssert(owner->GetStencilDepth() == share->GetStencilDepth(), "Mismatch on shared render target"); |
|
|
#endif // HS_DEBUGGING |
|
|
|
|
|
/// Check--is this renderTarget really a child of a cubicRenderTarget? |
|
|
if( owner->GetParent() != nil ) |
|
|
{ |
|
|
/// This'll create the deviceRefs for all of its children as well |
|
|
SharedRenderTargetRef(share->GetParent(), owner->GetParent()); |
|
|
return owner->GetDeviceRef(); |
|
|
} |
|
|
|
|
|
if( owner->GetDeviceRef() != nil ) |
|
|
ref = (plDXRenderTargetRef *)owner->GetDeviceRef(); |
|
|
|
|
|
// Look for a good format of matching color and depth size. |
|
|
if( !IFindRenderTargetInfo(owner, surfFormat, resType) ) |
|
|
{ |
|
|
hsAssert( false, "Error getting renderTarget info" ); |
|
|
return nil; |
|
|
} |
|
|
|
|
|
|
|
|
/// Create the render target now |
|
|
// Start with the depth. We're just going to share the depth surface on the |
|
|
// input shareRef. |
|
|
plDXRenderTargetRef* shareRef = (plDXRenderTargetRef*)share->GetDeviceRef(); |
|
|
hsAssert(shareRef, "Trying to share from a render target with no ref"); |
|
|
if( shareRef->fD3DDepthSurface ) |
|
|
shareRef->fD3DDepthSurface->AddRef(); |
|
|
depthSurface = shareRef->fD3DDepthSurface; |
|
|
|
|
|
// Check for Cubic. This is unlikely, since this function is currently only |
|
|
// used for the shadow map pools. |
|
|
cubicRT = plCubicRenderTarget::ConvertNoRef( owner ); |
|
|
if( cubicRT != nil ) |
|
|
{ |
|
|
/// And create the ref (it'll know how to set all the flags) |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
hsAssert(!fManagedAlloced, "Alloc default with managed alloc'd"); |
|
|
if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, |
|
|
D3DPOOL_DEFAULT, (IDirect3DCubeTexture9 **)&cTexture, NULL ) ) ) |
|
|
{ |
|
|
|
|
|
/// Create a CUBIC texture |
|
|
for( i = 0; i < 6; i++ ) |
|
|
{ |
|
|
plRenderTarget *face = cubicRT->GetFace( i ); |
|
|
plDXRenderTargetRef *fRef; |
|
|
|
|
|
if( face->GetDeviceRef() != nil ) |
|
|
{ |
|
|
fRef = (plDXRenderTargetRef *)face->GetDeviceRef(); |
|
|
fRef->Set( surfFormat, 0, face ); |
|
|
if( !fRef->IsLinked() ) |
|
|
fRef->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
face->SetDeviceRef( TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, face, false ) ); |
|
|
( (plDXRenderTargetRef *)face->GetDeviceRef())->Link( &fRenderTargetRefList ); |
|
|
// Unref now, since for now ONLY the RT owns the ref, not us (not until we use it, at least) |
|
|
hsRefCnt_SafeUnRef( face->GetDeviceRef() ); |
|
|
} |
|
|
} |
|
|
|
|
|
D3DSURF_MEMNEW(cTexture); |
|
|
|
|
|
ref->SetTexture( cTexture, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
} |
|
|
// Is it a texture render target? Probably, since shadow maps are all we use this for. |
|
|
else if( owner->GetFlags() & plRenderTarget::kIsTexture ) |
|
|
{ |
|
|
/// Create a normal texture |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
hsAssert(!fManagedAlloced, "Alloc default with managed alloc'd"); |
|
|
if( !FAILED( fD3DDevice->CreateTexture( owner->GetWidth(), owner->GetHeight(), 1, D3DUSAGE_RENDERTARGET, surfFormat, |
|
|
D3DPOOL_DEFAULT, (IDirect3DTexture9 **)&texture, NULL ) ) ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(texture); |
|
|
|
|
|
ref->SetTexture( texture, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
} |
|
|
// Pretty sure this code path has never been followed. |
|
|
else if( owner->GetFlags() & plRenderTarget::kIsOffscreen ) |
|
|
{ |
|
|
/// Create a blank surface |
|
|
if( ref != nil ) |
|
|
ref->Set( surfFormat, 0, owner ); |
|
|
else |
|
|
ref = TRACKED_NEW plDXRenderTargetRef( surfFormat, 0, owner ); |
|
|
|
|
|
width = owner->GetWidth(); |
|
|
height = owner->GetHeight(); |
|
|
|
|
|
if( !FAILED( fD3DDevice->CreateRenderTarget( width, height, surfFormat, |
|
|
D3DMULTISAMPLE_NONE, 0, |
|
|
FALSE, &surface, NULL ) ) ) |
|
|
{ |
|
|
D3DSURF_MEMNEW(surface); |
|
|
|
|
|
ref->SetTexture( surface, depthSurface ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ReleaseObject(depthSurface); |
|
|
hsRefCnt_SafeUnRef(ref); |
|
|
ref = nil; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if( owner->GetDeviceRef() != ref ) |
|
|
{ |
|
|
owner->SetDeviceRef( ref ); |
|
|
// Unref now, since for now ONLY the RT owns the ref, not us (not until we use it, at least) |
|
|
hsRefCnt_SafeUnRef( ref ); |
|
|
if( ref != nil && !ref->IsLinked() ) |
|
|
ref->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( ref != nil && !ref->IsLinked() ) |
|
|
ref->Link( &fRenderTargetRefList ); |
|
|
} |
|
|
|
|
|
if( ref != nil ) |
|
|
{ |
|
|
ref->SetDirty( false ); |
|
|
} |
|
|
|
|
|
return ref; |
|
|
} |
|
|
|
|
|
//// IPrepRenderTargetInfo //////////////////////////////////////////////////// |
|
|
// Shared processing of render target creation parameters. Also does the |
|
|
// dirty work of finding a good surface format to use. |
|
|
hsBool plDXPipeline::IPrepRenderTargetInfo( plRenderTarget *owner, D3DFORMAT &surfFormat, |
|
|
D3DFORMAT &depthFormat, D3DRESOURCETYPE &resType ) |
|
|
{ |
|
|
int i, j; |
|
|
UInt16 flags, width, height; |
|
|
Int8 bitDepth, zDepth, stencilDepth, stencilIndex; |
|
|
D3DFORMAT depthFormats[] = { D3DFMT_D24X8, D3DFMT_D24X4S4, D3DFMT_D24S8 }; |
|
|
|
|
|
|
|
|
flags = owner->GetFlags(); |
|
|
width = owner->GetWidth(); |
|
|
height = owner->GetHeight(); |
|
|
bitDepth = owner->GetPixelSize(); |
|
|
zDepth = owner->GetZDepth(); |
|
|
stencilDepth = owner->GetStencilDepth(); |
|
|
|
|
|
if( flags != 0 ) |
|
|
{ |
|
|
if( flags & plRenderTarget::kIsTexture ) |
|
|
{ |
|
|
/// Do an extra check for width and height here |
|
|
for( i = width >> 1, j = 0; i != 0; i >>= 1, j++ ); |
|
|
if( width != ( 1 << j ) ) |
|
|
return false; |
|
|
|
|
|
for( i = height >> 1, j = 0; i != 0; i >>= 1, j++ ); |
|
|
if( height!= ( 1 << j ) ) |
|
|
return false; |
|
|
|
|
|
resType = D3DRTYPE_TEXTURE; |
|
|
} |
|
|
else |
|
|
resType = D3DRTYPE_SURFACE; |
|
|
|
|
|
if( bitDepth == 16 ) |
|
|
surfFormat = D3DFMT_A4R4G4B4; |
|
|
else if( bitDepth == 32 ) |
|
|
surfFormat = D3DFMT_A8R8G8B8; |
|
|
|
|
|
/// Get the backbuffer format (if one is requested) |
|
|
if( zDepth ) |
|
|
{ |
|
|
if( zDepth == 16 && stencilDepth == 0 ) |
|
|
depthFormat = D3DFMT_D16; |
|
|
else if( zDepth == 24 ) |
|
|
{ |
|
|
if( stencilDepth == 0 ) stencilIndex = 0; |
|
|
else if( stencilDepth <= 4 ) stencilIndex = 1; |
|
|
else if( stencilDepth <= 8 ) stencilIndex = 2; |
|
|
else |
|
|
stencilIndex = 2; |
|
|
|
|
|
depthFormat = depthFormats[ stencilIndex ]; |
|
|
} |
|
|
else if( zDepth == 32 && stencilDepth == 0 ) |
|
|
depthFormat = D3DFMT_D32; |
|
|
else if( zDepth == 15 && stencilDepth == 1 ) |
|
|
depthFormat = D3DFMT_D15S1; |
|
|
|
|
|
if( surfFormat == D3DFMT_UNKNOWN || depthFormat == D3DFMT_UNKNOWN ) |
|
|
{ |
|
|
return false; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
depthFormat = D3DFMT_UNKNOWN; |
|
|
} |
|
|
|
|
|
/// Check the device format |
|
|
if( FAILED( fSettings.fDXError = fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
D3DUSAGE_RENDERTARGET, resType, surfFormat ) ) ) |
|
|
{ |
|
|
if( bitDepth == 16 ) |
|
|
{ |
|
|
bitDepth = 32; |
|
|
surfFormat = D3DFMT_A8R8G8B8; |
|
|
} |
|
|
else if( bitDepth == 32 ) |
|
|
{ |
|
|
bitDepth = 16; |
|
|
surfFormat = D3DFMT_A4R4G4B4; |
|
|
} |
|
|
if( FAILED( fSettings.fDXError = fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
D3DUSAGE_RENDERTARGET, resType, surfFormat ) ) ) |
|
|
{ |
|
|
IGetD3DError(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
if( zDepth ) |
|
|
{ |
|
|
while( FAILED( fSettings.fDXError = fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, depthFormat ) ) ) |
|
|
{ |
|
|
if( stencilIndex < sizeof( depthFormats ) / sizeof( depthFormats[ 0 ] ) - 1 ) |
|
|
{ |
|
|
stencilIndex++; |
|
|
depthFormat = depthFormats[ stencilIndex ]; |
|
|
} |
|
|
else |
|
|
{ |
|
|
IGetD3DError(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
if( FAILED( fSettings.fDXError = fD3DObject->CheckDepthStencilMatch( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
surfFormat, depthFormat ) ) ) |
|
|
{ |
|
|
IGetD3DError(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
//// IFindRenderTargetInfo //////////////////////////////////////////////////// |
|
|
// Shared processing of render target creation parameters. Also does the |
|
|
// dirty work of finding a good surface format to use. |
|
|
// Doesn't bother checking depth buffer, since this is only used for a render target |
|
|
// that's going to share a depth buffer that's already been created. |
|
|
hsBool plDXPipeline::IFindRenderTargetInfo( plRenderTarget *owner, D3DFORMAT &surfFormat, D3DRESOURCETYPE &resType ) |
|
|
{ |
|
|
UInt16 flags, width, height; |
|
|
Int8 bitDepth; |
|
|
|
|
|
|
|
|
flags = owner->GetFlags(); |
|
|
width = owner->GetWidth(); |
|
|
height = owner->GetHeight(); |
|
|
bitDepth = owner->GetPixelSize(); |
|
|
|
|
|
if( flags != 0 ) |
|
|
{ |
|
|
if( flags & plRenderTarget::kIsTexture ) |
|
|
{ |
|
|
resType = D3DRTYPE_TEXTURE; |
|
|
} |
|
|
else |
|
|
resType = D3DRTYPE_SURFACE; |
|
|
|
|
|
if( bitDepth == 16 ) |
|
|
surfFormat = D3DFMT_A4R4G4B4; |
|
|
else if( bitDepth == 32 ) |
|
|
surfFormat = D3DFMT_A8R8G8B8; |
|
|
|
|
|
if( surfFormat == D3DFMT_UNKNOWN ) |
|
|
{ |
|
|
return false; |
|
|
} |
|
|
|
|
|
/// Check the device format |
|
|
if( FAILED( fSettings.fDXError = fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
D3DUSAGE_RENDERTARGET, resType, surfFormat ) ) ) |
|
|
{ |
|
|
if( bitDepth == 16 ) |
|
|
{ |
|
|
bitDepth = 32; |
|
|
surfFormat = D3DFMT_A8R8G8B8; |
|
|
} |
|
|
else if( bitDepth == 32 ) |
|
|
{ |
|
|
bitDepth = 16; |
|
|
surfFormat = D3DFMT_A4R4G4B4; |
|
|
} |
|
|
if( FAILED( fSettings.fDXError = fD3DObject->CheckDeviceFormat( fCurrentAdapter, fCurrentDevice->fDDType, fCurrentMode->fDDmode.Format, |
|
|
D3DUSAGE_RENDERTARGET, resType, surfFormat ) ) ) |
|
|
{ |
|
|
IGetD3DError(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// PushRenderRequest /////////////////////////////////////////////// |
|
|
// We're moving from our current render (probably to primary) onto |
|
|
// another specialized render request. This may be to the primary (if req->GetRenderTarget() is nil) |
|
|
// or to a texture. This function saves enough state to resume rendering on PopRenderRequest. |
|
|
// The render request may just be a new camera position. |
|
|
void plDXPipeline::PushRenderRequest(plRenderRequest* req) |
|
|
{ |
|
|
// Save these, since we want to copy them to our current view |
|
|
hsMatrix44 l2w = fView.fLocalToWorld; |
|
|
hsMatrix44 w2l = fView.fWorldToLocal; |
|
|
|
|
|
plFogEnvironment defFog = fView.fDefaultFog; |
|
|
|
|
|
fSettings.fViewStack.Push(fView); |
|
|
|
|
|
SetViewTransform(req->GetViewTransform()); |
|
|
|
|
|
PushRenderTarget(req->GetRenderTarget()); |
|
|
fView.fRenderState = req->GetRenderState(); |
|
|
|
|
|
fView.fRenderRequest = req; |
|
|
hsRefCnt_SafeRef(fView.fRenderRequest); |
|
|
|
|
|
SetDrawableTypeMask(req->GetDrawableMask()); |
|
|
SetSubDrawableTypeMask(req->GetSubDrawableMask()); |
|
|
|
|
|
fView.fClearColor = inlGetD3DColor( req->GetClearColor() ); |
|
|
fView.fClearDepth = req->GetClearDepth(); |
|
|
|
|
|
if( req->GetFogStart() < 0 ) |
|
|
{ |
|
|
fView.fDefaultFog = defFog; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fView.fDefaultFog.Set( req->GetYon() * (1.f - req->GetFogStart()), req->GetYon(), 1.f, &req->GetClearColor()); |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
} |
|
|
|
|
|
if( req->GetOverrideMat() ) |
|
|
PushOverrideMaterial(req->GetOverrideMat()); |
|
|
|
|
|
// Set from our saved ones... |
|
|
fView.fWorldToLocal = w2l; |
|
|
fView.fLocalToWorld = l2w; |
|
|
|
|
|
RefreshMatrices(); |
|
|
|
|
|
if (req->GetIgnoreOccluders()) |
|
|
fView.fCullMaxNodes = 0; |
|
|
|
|
|
fView.fCullTreeDirty = true; |
|
|
} |
|
|
|
|
|
// PopRenderRequest ////////////////////////////////////////////////// |
|
|
// Restore state to resume rendering as before the preceding PushRenderRequest. |
|
|
void plDXPipeline::PopRenderRequest(plRenderRequest* req) |
|
|
{ |
|
|
if( req->GetOverrideMat() ) |
|
|
PopOverrideMaterial(nil); |
|
|
|
|
|
hsRefCnt_SafeUnRef(fView.fRenderRequest); |
|
|
fView = fSettings.fViewStack.Pop(); |
|
|
|
|
|
// Force the next thing drawn to update the fog settings. |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
|
|
|
PopRenderTarget(); |
|
|
fView.fXformResetFlags = fView.kResetProjection | fView.kResetCamera; |
|
|
} |
|
|
|
|
|
//// PushRenderTarget ///////////////////////////////////////////////////////// |
|
|
// Begin rendering to the specified target. If target is nil, that's the primary surface. |
|
|
void plDXPipeline::PushRenderTarget( plRenderTarget *target ) |
|
|
{ |
|
|
//WHITE |
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
if( target && (hackOffscreens.kMissingIndex == hackOffscreens.Find(target)) ) |
|
|
hackOffscreens.Append(target); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
|
|
|
|
|
|
fSettings.fCurrRenderTarget = target; |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrRenderTargetRef, ( target != nil ) ? (plDXDeviceRef *)target->GetDeviceRef() : nil ); |
|
|
|
|
|
while( target != nil ) |
|
|
{ |
|
|
fSettings.fCurrBaseRenderTarget = target; |
|
|
target = target->GetParent(); |
|
|
} |
|
|
|
|
|
fSettings.fRenderTargets.Push( fSettings.fCurrRenderTarget ); |
|
|
ISetRenderTarget( fSettings.fCurrRenderTarget ); |
|
|
} |
|
|
|
|
|
//// PopRenderTarget ////////////////////////////////////////////////////////// |
|
|
// Resume rendering to the render target before the last PushRenderTarget, |
|
|
// making sure we aren't holding on to anything from the render target getting |
|
|
// popped. |
|
|
plRenderTarget *plDXPipeline::PopRenderTarget() |
|
|
{ |
|
|
plRenderTarget *old = fSettings.fRenderTargets.Pop(), *temp; |
|
|
int i = fSettings.fRenderTargets.GetCount(); |
|
|
|
|
|
if( i == 0 ) |
|
|
{ |
|
|
fSettings.fCurrRenderTarget = nil; |
|
|
fSettings.fCurrBaseRenderTarget = nil; |
|
|
hsRefCnt_SafeUnRef( fSettings.fCurrRenderTargetRef ); |
|
|
fSettings.fCurrRenderTargetRef = nil; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fSettings.fCurrRenderTarget = fSettings.fRenderTargets[ i - 1 ]; |
|
|
temp = fSettings.fCurrRenderTarget; |
|
|
while( temp != nil ) |
|
|
{ |
|
|
fSettings.fCurrBaseRenderTarget = temp; |
|
|
temp = temp->GetParent(); |
|
|
} |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrRenderTargetRef, |
|
|
( fSettings.fCurrRenderTarget != nil ) ? |
|
|
(plDXDeviceRef *)fSettings.fCurrRenderTarget->GetDeviceRef() |
|
|
: nil ); |
|
|
} |
|
|
|
|
|
ISetRenderTarget( fSettings.fCurrRenderTarget ); |
|
|
|
|
|
return old; |
|
|
} |
|
|
|
|
|
// ISetAnisotropy /////////////////////////////////////////////////////////// |
|
|
// Set the current anisotropic filtering settings to D3D |
|
|
void plDXPipeline::ISetAnisotropy(hsBool on) |
|
|
{ |
|
|
if( (fSettings.fMaxAnisotropicSamples <= 0) || IsDebugFlagSet(plPipeDbg::kFlagNoAnisotropy) ) |
|
|
on = false; |
|
|
|
|
|
if( on == fSettings.fCurrAnisotropy ) |
|
|
return; |
|
|
|
|
|
if( on ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
// GeForce cards have decided that they no longer handle anisotropic as a mag filter. |
|
|
// We could detect caps... but I don't think we'd notice if we just made the mag |
|
|
// filter always be linear. |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAXANISOTROPY, (DWORD)fSettings.fMaxAnisotropicSamples ); |
|
|
} |
|
|
fSettings.fCurrAnisotropy = true; |
|
|
} |
|
|
else |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); |
|
|
fD3DDevice->SetSamplerState( i, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); |
|
|
} |
|
|
fSettings.fCurrAnisotropy = false; |
|
|
} |
|
|
} |
|
|
|
|
|
//// ISetRenderTarget ///////////////////////////////////////////////////////// |
|
|
// Set rendering to the specified render target. Nil rendertarget is the primary. |
|
|
// Invalidates the state as required by experience, not documentation. |
|
|
void plDXPipeline::ISetRenderTarget( plRenderTarget *target ) |
|
|
{ |
|
|
IDirect3DSurface9 *main, *depth; |
|
|
plDXRenderTargetRef *ref = nil; |
|
|
|
|
|
|
|
|
if( target != nil ) |
|
|
{ |
|
|
ref = (plDXRenderTargetRef *)target->GetDeviceRef(); |
|
|
if( ref == nil || ref->IsDirty() ) |
|
|
ref = (plDXRenderTargetRef *)MakeRenderTargetRef( target ); |
|
|
} |
|
|
|
|
|
if( ref == nil || ref->GetColorSurface() == nil ) |
|
|
{ |
|
|
/// Set to main screen |
|
|
main = fD3DMainSurface; |
|
|
depth = fD3DDepthSurface; |
|
|
ISetAnisotropy(true); |
|
|
} |
|
|
else |
|
|
{ |
|
|
/// Set to this target |
|
|
main = ref->GetColorSurface(); |
|
|
depth = ref->fD3DDepthSurface; |
|
|
ISetAnisotropy(false); |
|
|
} |
|
|
|
|
|
if( main != fSettings.fCurrD3DMainSurface || depth != fSettings.fCurrD3DDepthSurface ) |
|
|
{ |
|
|
fSettings.fCurrD3DMainSurface = main; |
|
|
fSettings.fCurrD3DDepthSurface = depth; |
|
|
fD3DDevice->SetRenderTarget(0, main); |
|
|
fD3DDevice->SetDepthStencilSurface(depth); |
|
|
} |
|
|
|
|
|
IInvalidateState(); |
|
|
|
|
|
ISetViewport(); |
|
|
} |
|
|
|
|
|
// SetClear ///////////////////////////////////////////////////////////////////// |
|
|
// Set the color and depth clear values. |
|
|
void plDXPipeline::SetClear(const hsColorRGBA* col, const hsScalar* depth) |
|
|
{ |
|
|
if( col ) |
|
|
fView.fClearColor = inlGetD3DColor(*col); |
|
|
if( depth ) |
|
|
fView.fClearDepth = *depth; |
|
|
} |
|
|
|
|
|
// GetClearColor //////////////////////////////////////////////////////////////// |
|
|
// Return the current clear color. |
|
|
hsColorRGBA plDXPipeline::GetClearColor() const |
|
|
{ |
|
|
return hsColorRGBA().FromARGB32(fView.fClearColor); |
|
|
} |
|
|
|
|
|
// GetClearDepth //////////////////////////////////////////////////////////////// |
|
|
// Return the current clear depth. |
|
|
hsScalar plDXPipeline::GetClearDepth() const |
|
|
{ |
|
|
return fView.fClearDepth; |
|
|
} |
|
|
|
|
|
//// ClearRenderTarget //////////////////////////////////////////////////////// |
|
|
// Clear the current color and depth buffers. If a drawable is passed in, then |
|
|
// the color buffer will be cleared by rendering that drawable. |
|
|
// The depth buffer is always cleared with a clear call. |
|
|
// Clearing of depth and/or color may be turned off by setting the kRenderClearDepth |
|
|
// and kRenderClearColor bits in fView.fRenderState to false. |
|
|
void plDXPipeline::ClearRenderTarget( plDrawable* d ) |
|
|
{ |
|
|
plDrawableSpans* src = plDrawableSpans::ConvertNoRef(d); |
|
|
|
|
|
if( !src ) |
|
|
{ |
|
|
ClearRenderTarget(); |
|
|
return; |
|
|
} |
|
|
// First clear the depth buffer as normal. |
|
|
if( fView.fRenderState & kRenderClearDepth ) |
|
|
{ |
|
|
D3DRECT r; |
|
|
hsBool useRect = IGetClearViewPort(r); |
|
|
|
|
|
if( useRect ) |
|
|
{ |
|
|
WEAK_ERROR_CHECK( fD3DDevice->Clear( 1, &r, D3DCLEAR_ZBUFFER, 0, fView.fClearDepth, 0L ) ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
WEAK_ERROR_CHECK( fD3DDevice->Clear( 0, nil, D3DCLEAR_ZBUFFER, 0, fView.fClearDepth, 0L ) ); |
|
|
// debug, clears to red WEAK_ERROR_CHECK( fD3DDevice->Clear( 0, nil, D3DCLEAR_ZBUFFER | D3DCLEAR_TARGET, 0xffff0000, fView.fClearDepth, 0L ) ); |
|
|
} |
|
|
} |
|
|
|
|
|
UInt32 s = fView.fRenderState; |
|
|
UInt32 dtm = fView.fDrawableTypeMask; |
|
|
UInt32 sdtm = fView.fSubDrawableTypeMask; |
|
|
|
|
|
fView.fDrawableTypeMask = plDrawable::kNormal; |
|
|
fView.fSubDrawableTypeMask = UInt32(-1); |
|
|
|
|
|
BeginDrawable(d); |
|
|
Draw(d); |
|
|
EndDrawable(d); |
|
|
|
|
|
fView.fSubDrawableTypeMask = sdtm; |
|
|
fView.fDrawableTypeMask = dtm; |
|
|
fView.fRenderState = s; |
|
|
|
|
|
} |
|
|
|
|
|
// IGetClearViewPort ////////////////////////////////////////////// |
|
|
// Sets the input rect to the current viewport. Returns true if |
|
|
// that is a subset of the current render target, else false. |
|
|
hsBool plDXPipeline::IGetClearViewPort(D3DRECT& r) |
|
|
{ |
|
|
r.x1 = GetViewTransform().GetViewPortLeft(); |
|
|
r.y1 = GetViewTransform().GetViewPortTop(); |
|
|
r.x2 = GetViewTransform().GetViewPortRight(); |
|
|
r.y2 = GetViewTransform().GetViewPortBottom(); |
|
|
|
|
|
hsBool useRect = false; |
|
|
if( fSettings.fCurrRenderTarget != nil ) |
|
|
{ |
|
|
useRect = ( (r.x1 != 0) || (r.y1 != 0) || (r.x2 != fSettings.fCurrRenderTarget->GetWidth()) || (r.y2 != fSettings.fCurrRenderTarget->GetHeight()) ); |
|
|
|
|
|
} |
|
|
else |
|
|
{ |
|
|
useRect = ( (r.x1 != 0) || (r.y1 != 0) || (r.x2 != fSettings.fOrigWidth) || (r.y2 != fSettings.fOrigHeight) ); |
|
|
} |
|
|
|
|
|
return useRect; |
|
|
} |
|
|
|
|
|
// ClearRenderTarget ////////////////////////////////////////////////////////////////////////////// |
|
|
// Flat fill the current render target with the specified color and depth values. |
|
|
void plDXPipeline::ClearRenderTarget( const hsColorRGBA *col, const hsScalar* depth ) |
|
|
{ |
|
|
if( fView.fRenderState & (kRenderClearColor | kRenderClearDepth) ) |
|
|
{ |
|
|
DWORD clearColor = inlGetD3DColor(col ? *col : GetClearColor()); |
|
|
hsScalar clearDepth = depth ? *depth : fView.fClearDepth; |
|
|
|
|
|
DWORD dwFlags = 0;//fStencil.fDepth > 0 ? D3DCLEAR_STENCIL : 0; |
|
|
if( fView.fRenderState & kRenderClearColor ) |
|
|
dwFlags |= D3DCLEAR_TARGET; |
|
|
if( fView.fRenderState & kRenderClearDepth ) |
|
|
dwFlags |= D3DCLEAR_ZBUFFER; |
|
|
|
|
|
D3DRECT r; |
|
|
hsBool useRect = IGetClearViewPort(r); |
|
|
if( useRect ) |
|
|
{ |
|
|
WEAK_ERROR_CHECK( fD3DDevice->Clear( 1, &r, dwFlags, clearColor, clearDepth, 0L ) ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
WEAK_ERROR_CHECK( fD3DDevice->Clear( 0, nil, dwFlags, clearColor, clearDepth, 0L ) ); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Fog ////////////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// The current fog system sucks. It was never meant to get used this way, but |
|
|
// the production artists started using it with debug commands that were around, |
|
|
// and before they could be stopped it was too late. |
|
|
// The good news is that there's a lot that could be done with fog here that |
|
|
// would be greatly appreciated. |
|
|
|
|
|
// IGetVSFogSet /////////////////////////////////////////////////////////////// |
|
|
// Translate the current fog settings into a linear fog that the current |
|
|
// vertex shaders can use. |
|
|
void plDXPipeline::IGetVSFogSet(float* const set) const |
|
|
{ |
|
|
set[2] = 0.f; |
|
|
set[3] = 1.f; |
|
|
if( fCurrFog.fEnvPtr ) |
|
|
{ |
|
|
hsColorRGBA colorTrash; |
|
|
hsScalar start; |
|
|
hsScalar end; |
|
|
fCurrFog.fEnvPtr->GetPipelineParams(&start, &end, &colorTrash); |
|
|
if( end > start ) |
|
|
{ |
|
|
set[0] = -end; |
|
|
set[1] = 1.f / (start - end); |
|
|
} |
|
|
else |
|
|
{ |
|
|
set[0] = 1.f; |
|
|
set[1] = 0.f; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
set[0] = 1.f; |
|
|
set[1] = 0.f; |
|
|
} |
|
|
} |
|
|
|
|
|
//// ISetFogParameters //////////////////////////////////////////////////////// |
|
|
// So looking at this function, one might guess that fog parameters were settable |
|
|
// individually for different objects, and that was the original intent, with transitions |
|
|
// as something like the avatar moved from one fog region to another. |
|
|
// Never happened. |
|
|
// So the current state is that there is one set of fog parameters per age, and things |
|
|
// are either fogged, or not fogged. |
|
|
// This is complicated by the DX vertex/pixel shaders only supporting per-vertex fog, |
|
|
// so the same plasma fog settings may turn into differing D3D fog state. |
|
|
void plDXPipeline::ISetFogParameters(const plSpan* span, const plLayerInterface* baseLay) |
|
|
{ |
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoFog)) |
|
|
{ |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); |
|
|
return; |
|
|
} |
|
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
plFogEnvironment* fog = (span ? (span->fFogEnvironment ? span->fFogEnvironment : &fView.fDefaultFog) : nil); |
|
|
|
|
|
UInt8 isVertex = 0; |
|
|
UInt8 isShader = false; |
|
|
if (baseLay) |
|
|
{ |
|
|
if ((baseLay->GetShadeFlags() & hsGMatState::kShadeReallyNoFog) && !(fMatOverOff.fShadeFlags & hsGMatState::kShadeReallyNoFog)) |
|
|
fog = nil; |
|
|
if (baseLay->GetVertexShader()) |
|
|
isShader = true; |
|
|
} |
|
|
if (fMatOverOn.fShadeFlags & hsGMatState::kShadeReallyNoFog) |
|
|
fog = nil; |
|
|
|
|
|
bool forceLoad = false; |
|
|
D3DRENDERSTATETYPE d3dFogType = D3DRS_FOGTABLEMODE; // Use VERTEXMODE for vertex fog |
|
|
|
|
|
#if !HS_BUILD_FOR_XBOX |
|
|
if (!(fSettings.fD3DCaps & kCapsPixelFog) || isShader) |
|
|
{ |
|
|
d3dFogType = D3DRS_FOGVERTEXMODE; |
|
|
isVertex = true; |
|
|
} |
|
|
#endif |
|
|
|
|
|
// Quick check |
|
|
if ((fCurrFog.fEnvPtr == fog) && (fCurrFog.fIsVertex == isVertex) && (fCurrFog.fIsShader == isShader)) |
|
|
return; |
|
|
|
|
|
UInt8 type = ( fog == nil ) ? plFogEnvironment::kNoFog : fog->GetType(); |
|
|
|
|
|
if (type == plFogEnvironment::kNoFog) |
|
|
{ |
|
|
/// No fog, just disable |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGENABLE, FALSE ); |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
return; |
|
|
} |
|
|
else if( fCurrFog.fEnvPtr != fog ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGENABLE, TRUE ); |
|
|
forceLoad = true; |
|
|
fCurrFog.fEnvPtr = fog; |
|
|
} |
|
|
|
|
|
if( isShader ) |
|
|
type = plFogEnvironment::kLinearFog; |
|
|
|
|
|
if( fCurrFog.fIsShader != isShader ) |
|
|
forceLoad = true; |
|
|
|
|
|
if( fCurrFog.fIsVertex != isVertex ) |
|
|
forceLoad = true; |
|
|
|
|
|
fCurrFog.fIsShader = isShader; |
|
|
fCurrFog.fIsVertex = isVertex; |
|
|
|
|
|
hsScalar startOrDensity, end; |
|
|
hsColorRGBA color; |
|
|
|
|
|
/// Get params |
|
|
if( type == plFogEnvironment::kLinearFog ) |
|
|
{ |
|
|
fog->GetPipelineParams( &startOrDensity, &end, &color ); |
|
|
|
|
|
if (startOrDensity == end) |
|
|
{ |
|
|
// This should be legal, but some cards don't like it. Just disable. Same thing. |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); |
|
|
return; |
|
|
} |
|
|
} |
|
|
else |
|
|
fog->GetPipelineParams( &startOrDensity, &color ); |
|
|
|
|
|
if( isShader ) |
|
|
{ |
|
|
// None of this is technically necessary, but it's to work around |
|
|
// a known goofiness in the NVidia drivers. Actually, I don't think |
|
|
// having to set the tablemode fog to linear in addition to setting |
|
|
// the vertexmode is even a "known" issue. But turns out to be |
|
|
// necessary on GeForceFX latest drivers. |
|
|
startOrDensity = 1.f; |
|
|
end = 0.f; |
|
|
|
|
|
// Setting FOGTABLEMODE to none seems to work on both ATI and NVidia, |
|
|
// but I haven't tried it on the GeForceFX yet. |
|
|
// if( fCurrFog.fMode != D3DFOG_LINEAR ) |
|
|
// fD3DDevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR); |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_NONE); |
|
|
} |
|
|
|
|
|
/// Set color |
|
|
if( !( fCurrFog.fColor == color ) || forceLoad ) |
|
|
{ |
|
|
fCurrFog.fColor = color; |
|
|
fCurrFog.fHexColor = inlGetD3DColor( color ); |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGCOLOR, fCurrFog.fHexColor ); |
|
|
} |
|
|
|
|
|
D3DFOGMODE modes[ 4 ] = { D3DFOG_LINEAR, D3DFOG_EXP, D3DFOG_EXP2, D3DFOG_NONE }; |
|
|
|
|
|
/// Set type |
|
|
if( fCurrFog.fMode != modes[type] || forceLoad ) |
|
|
{ |
|
|
fCurrFog.fMode = modes[type]; |
|
|
|
|
|
if( fCurrFog.fMode == D3DFOG_LINEAR ) |
|
|
{ |
|
|
fCurrFog.fStart = startOrDensity; |
|
|
fCurrFog.fEnd = end; |
|
|
|
|
|
fD3DDevice->SetRenderState( d3dFogType, fCurrFog.fMode ); |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGSTART, *(DWORD *)( &fCurrFog.fStart ) ); |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGEND, *(DWORD *)( &fCurrFog.fEnd ) ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fCurrFog.fDensity = startOrDensity; |
|
|
|
|
|
fD3DDevice->SetRenderState( d3dFogType, fCurrFog.fMode ); |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGDENSITY, *(DWORD *)( &fCurrFog.fDensity ) ); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Type is the same, but are the params? |
|
|
if( fCurrFog.fMode == D3DFOG_LINEAR ) |
|
|
{ |
|
|
if( fCurrFog.fStart != startOrDensity ) |
|
|
{ |
|
|
fCurrFog.fStart = startOrDensity; |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGSTART, *(DWORD *)( &fCurrFog.fStart ) ); |
|
|
} |
|
|
|
|
|
if( fCurrFog.fEnd != end ) |
|
|
{ |
|
|
fCurrFog.fEnd = end; |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGEND, *(DWORD *)( &fCurrFog.fEnd ) ); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( fCurrFog.fDensity != startOrDensity ) |
|
|
{ |
|
|
fCurrFog.fDensity = startOrDensity; |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGDENSITY, *(DWORD *)( &fCurrFog.fDensity ) ); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Stenciling /////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// I know that none of this stencil code has ever been used in production. |
|
|
// To my knowledge, none of this stencil code was ever even tested. |
|
|
// It may save you some time as a starting point, but don't trust it. |
|
|
|
|
|
//// StencilEnable //////////////////////////////////////////////////////////// |
|
|
|
|
|
hsBool plDXPipeline::StencilEnable( hsBool enable ) |
|
|
{ |
|
|
if( fStencil.fEnabled == enable ) |
|
|
return true; |
|
|
|
|
|
if( enable && fStencil.fDepth == 0 ) |
|
|
return false; // Can't enable stenciling when we don't support it! |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILENABLE, enable ? TRUE : FALSE ); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
//// StencilSetCompareFunc //////////////////////////////////////////////////// |
|
|
|
|
|
void plDXPipeline::StencilSetCompareFunc( UInt8 func, UInt32 refValue ) |
|
|
{ |
|
|
D3DCMPFUNC newFunc; |
|
|
|
|
|
|
|
|
switch( func ) |
|
|
{ |
|
|
case plStencilCaps::kCmpNever: newFunc = D3DCMP_NEVER; break; |
|
|
case plStencilCaps::kCmpLessThan: newFunc = D3DCMP_LESS; break; |
|
|
case plStencilCaps::kCmpEqual: newFunc = D3DCMP_EQUAL; break; |
|
|
case plStencilCaps::kCmpLessThanOrEqual: newFunc = D3DCMP_LESSEQUAL; break; |
|
|
case plStencilCaps::kCmpGreaterThan: newFunc = D3DCMP_GREATER; break; |
|
|
case plStencilCaps::kCmpNotEqual: newFunc = D3DCMP_NOTEQUAL; break; |
|
|
case plStencilCaps::kCmpGreaterThanOrEqual: newFunc = D3DCMP_GREATEREQUAL; break; |
|
|
case plStencilCaps::kCmpAlways: newFunc = D3DCMP_ALWAYS; break; |
|
|
default: hsAssert( false, "Invalid compare function to StencilSetCompareFunc()" ); return; |
|
|
} |
|
|
|
|
|
if( fStencil.fCmpFunc != newFunc ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILFUNC, newFunc ); |
|
|
fStencil.fCmpFunc = newFunc; |
|
|
} |
|
|
|
|
|
if( fStencil.fRefValue != refValue ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILREF, refValue ); |
|
|
fStencil.fRefValue = refValue; |
|
|
} |
|
|
} |
|
|
|
|
|
//// StencilSetMask /////////////////////////////////////////////////////////// |
|
|
|
|
|
void plDXPipeline::StencilSetMask( UInt32 mask, UInt32 writeMask ) |
|
|
{ |
|
|
if( fStencil.fMask != mask ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILMASK, mask ); |
|
|
fStencil.fMask = mask; |
|
|
} |
|
|
|
|
|
if( fStencil.fWriteMask != writeMask ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILWRITEMASK, writeMask ); |
|
|
fStencil.fWriteMask = writeMask; |
|
|
} |
|
|
} |
|
|
|
|
|
//// StencilSetOps //////////////////////////////////////////////////////////// |
|
|
|
|
|
void plDXPipeline::StencilSetOps( UInt8 passOp, UInt8 failOp, UInt8 passButZFailOp ) |
|
|
{ |
|
|
D3DSTENCILOP op; |
|
|
|
|
|
|
|
|
/// Pass op |
|
|
switch( passOp ) |
|
|
{ |
|
|
case plStencilCaps::kOpKeep: op = D3DSTENCILOP_KEEP; break; |
|
|
case plStencilCaps::kOpSetToZero: op = D3DSTENCILOP_ZERO; break; |
|
|
case plStencilCaps::kOpReplace: op = D3DSTENCILOP_REPLACE; break; |
|
|
case plStencilCaps::kOpIncClamp: op = D3DSTENCILOP_INCRSAT; break; |
|
|
case plStencilCaps::kOpDecClamp: op = D3DSTENCILOP_DECRSAT; break; |
|
|
case plStencilCaps::kOpInvert: op = D3DSTENCILOP_INVERT; break; |
|
|
case plStencilCaps::kOpIncWrap: op = D3DSTENCILOP_INCR; break; |
|
|
case plStencilCaps::kOpDecWrap: op = D3DSTENCILOP_DECR; break; |
|
|
default: hsAssert( false, "Invalid op to StencilSetOps()" ); return; |
|
|
} |
|
|
|
|
|
if( fStencil.fPassOp != op ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILPASS, op ); |
|
|
fStencil.fPassOp = op; |
|
|
} |
|
|
|
|
|
/// Fail op |
|
|
switch( failOp ) |
|
|
{ |
|
|
case plStencilCaps::kOpKeep: op = D3DSTENCILOP_KEEP; break; |
|
|
case plStencilCaps::kOpSetToZero: op = D3DSTENCILOP_ZERO; break; |
|
|
case plStencilCaps::kOpReplace: op = D3DSTENCILOP_REPLACE; break; |
|
|
case plStencilCaps::kOpIncClamp: op = D3DSTENCILOP_INCRSAT; break; |
|
|
case plStencilCaps::kOpDecClamp: op = D3DSTENCILOP_DECRSAT; break; |
|
|
case plStencilCaps::kOpInvert: op = D3DSTENCILOP_INVERT; break; |
|
|
case plStencilCaps::kOpIncWrap: op = D3DSTENCILOP_INCR; break; |
|
|
case plStencilCaps::kOpDecWrap: op = D3DSTENCILOP_DECR; break; |
|
|
default: hsAssert( false, "Invalid op to StencilSetOps()" ); return; |
|
|
} |
|
|
|
|
|
if( fStencil.fFailOp != op ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILFAIL, op ); |
|
|
fStencil.fFailOp = op; |
|
|
} |
|
|
|
|
|
/// Pass-but-z-fail op |
|
|
switch( passButZFailOp ) |
|
|
{ |
|
|
case plStencilCaps::kOpKeep: op = D3DSTENCILOP_KEEP; break; |
|
|
case plStencilCaps::kOpSetToZero: op = D3DSTENCILOP_ZERO; break; |
|
|
case plStencilCaps::kOpReplace: op = D3DSTENCILOP_REPLACE; break; |
|
|
case plStencilCaps::kOpIncClamp: op = D3DSTENCILOP_INCRSAT; break; |
|
|
case plStencilCaps::kOpDecClamp: op = D3DSTENCILOP_DECRSAT; break; |
|
|
case plStencilCaps::kOpInvert: op = D3DSTENCILOP_INVERT; break; |
|
|
case plStencilCaps::kOpIncWrap: op = D3DSTENCILOP_INCR; break; |
|
|
case plStencilCaps::kOpDecWrap: op = D3DSTENCILOP_DECR; break; |
|
|
default: hsAssert( false, "Invalid op to StencilSetOps()" ); return; |
|
|
} |
|
|
|
|
|
if( fStencil.fPassButZFailOp != op ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_STENCILZFAIL, op ); |
|
|
fStencil.fPassButZFailOp = op; |
|
|
} |
|
|
} |
|
|
|
|
|
//// StencilGetCaps /////////////////////////////////////////////////////////// |
|
|
|
|
|
hsBool plDXPipeline::StencilGetCaps( plStencilCaps *caps ) |
|
|
{ |
|
|
hsAssert( caps != nil, "Invalid pointer to StencilGetCaps()" ); |
|
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
/// Find supported depths |
|
|
caps->fSupportedDepths = 0; |
|
|
for( i = 0; i < fCurrentMode->fDepthFormats.GetCount(); i++ ) |
|
|
{ |
|
|
switch( fCurrentMode->fDepthFormats[ i ] ) |
|
|
{ |
|
|
case D3DFMT_D15S1: caps->fSupportedDepths |= plStencilCaps::kDepth1Bit; break; |
|
|
case D3DFMT_D24X4S4: caps->fSupportedDepths |= plStencilCaps::kDepth4Bits; break; |
|
|
case D3DFMT_D24S8: caps->fSupportedDepths |= plStencilCaps::kDepth8Bits; break; |
|
|
} |
|
|
} |
|
|
|
|
|
if( caps->fSupportedDepths == 0 ) |
|
|
{ |
|
|
caps->fIsSupported = false; |
|
|
return false; |
|
|
} |
|
|
|
|
|
/// Get supported ops |
|
|
caps->fSupportedOps = 0; |
|
|
|
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_DECR ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpDecWrap; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_DECRSAT ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpDecClamp; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_INCR ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpIncWrap; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_INCRSAT ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpIncClamp; |
|
|
|
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_INVERT ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpInvert; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_KEEP ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpKeep; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_REPLACE ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpReplace; |
|
|
if( fCurrentDevice->fDDCaps.StencilCaps & D3DSTENCILCAPS_ZERO ) |
|
|
caps->fSupportedOps |= plStencilCaps::kOpSetToZero; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Lighting ///////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// IMakeLightRef //////////////////////////////////////////////////////////// |
|
|
// Create a plasma device ref for a light. Includes reserving a D3D light |
|
|
// index for the light. Ref is kept in a linked list for ready disposal |
|
|
// as well as attached to the light. |
|
|
hsGDeviceRef *plDXPipeline::IMakeLightRef( plLightInfo *owner ) |
|
|
{ |
|
|
plDXLightRef *lRef = TRACKED_NEW plDXLightRef(); |
|
|
|
|
|
|
|
|
/// Assign stuff and update |
|
|
lRef->fD3DIndex = fLights.ReserveD3DIndex(); |
|
|
lRef->fOwner = owner; |
|
|
owner->SetDeviceRef( lRef ); |
|
|
// Unref now, since for now ONLY the BG owns the ref, not us (not until we use it, at least) |
|
|
hsRefCnt_SafeUnRef( lRef ); |
|
|
|
|
|
lRef->Link( &fLights.fRefList ); |
|
|
|
|
|
lRef->UpdateD3DInfo( fD3DDevice, &fLights ); |
|
|
|
|
|
// Neutralize it until we need it. |
|
|
fD3DDevice->LightEnable(lRef->fD3DIndex, false); |
|
|
|
|
|
return lRef; |
|
|
} |
|
|
|
|
|
//// RegisterLight //////////////////////////////////////////////////////////// |
|
|
// Register a light with the pipeline. Light become immediately |
|
|
// ready to illuminate the scene. |
|
|
void plDXPipeline::RegisterLight(plLightInfo* liInfo) |
|
|
{ |
|
|
if( liInfo->IsLinked() ) |
|
|
return; |
|
|
|
|
|
liInfo->Link( &fLights.fActiveList ); |
|
|
liInfo->SetDeviceRef( IMakeLightRef( liInfo ) ); |
|
|
fLights.fTime++; |
|
|
} |
|
|
|
|
|
//// UnRegisterLight ////////////////////////////////////////////////////////// |
|
|
// Remove a light from the pipeline's active light list. Light will |
|
|
// no longer illuminate the scene. |
|
|
void plDXPipeline::UnRegisterLight(plLightInfo* liInfo) |
|
|
{ |
|
|
liInfo->SetDeviceRef( nil ); |
|
|
liInfo->Unlink(); |
|
|
|
|
|
fLights.fTime++; |
|
|
} |
|
|
|
|
|
//// IEnableLights //////////////////////////////////////////////////////////// |
|
|
// Does the lighting enable pass. Given a span with lights to use, builds |
|
|
// a bit vector representing the lights to use, then uses that to mask off |
|
|
// which lights actually need to be enabled/disabled. |
|
|
// Constructs 2 lists on the span, one for normal lights, and one for projective lights. |
|
|
|
|
|
void plDXPipeline::IEnableLights( plSpan *span ) |
|
|
{ |
|
|
plProfile_BeginTiming(SelectLights); |
|
|
ISelectLights( span, fSettings.fMaxNumLights, false ); |
|
|
plProfile_EndTiming(SelectLights); |
|
|
if( !(fView.fRenderState & kRenderNoProjection) ) |
|
|
{ |
|
|
plProfile_BeginTiming(SelectProj); |
|
|
ISelectLights( span, fSettings.fMaxNumProjectors, true ); |
|
|
plProfile_EndTiming(SelectProj); |
|
|
} |
|
|
} |
|
|
|
|
|
// ISelectLights /////////////////////////////////////////////////////////////// |
|
|
// Find the strongest numLights lights to illuminate the span with. |
|
|
// Weaker lights are faded out in effect so they won't pop when the |
|
|
// strongest N changes membership. |
|
|
void plDXPipeline::ISelectLights( plSpan *span, int numLights, hsBool proj ) |
|
|
{ |
|
|
int i, startScale; |
|
|
static hsBitVector newFlags; |
|
|
static hsTArray<plLightInfo*> onLights; |
|
|
plDXLightRef *ref; |
|
|
float threshhold, overHold = 0.3, scale; |
|
|
|
|
|
/// Build new flags |
|
|
|
|
|
/// Step 1: Find the n strongest lights |
|
|
newFlags.Clear(); |
|
|
onLights.SetCount(0); |
|
|
|
|
|
if (!IsDebugFlagSet(plPipeDbg::kFlagNoRuntimeLights) && |
|
|
!(IsDebugFlagSet(plPipeDbg::kFlagNoApplyProjLights) && proj) && |
|
|
!(IsDebugFlagSet(plPipeDbg::kFlagOnlyApplyProjLights) && !proj)) |
|
|
{ |
|
|
hsTArray<plLightInfo*>& spanLights = span->GetLightList(proj); |
|
|
|
|
|
for( i = 0; i < spanLights.GetCount() && i < numLights; i++ ) |
|
|
{ |
|
|
ref = (plDXLightRef *)spanLights[i]->GetDeviceRef(); |
|
|
|
|
|
if( ref->IsDirty() ) |
|
|
{ |
|
|
if( ref->fD3DIndex == 0 ) |
|
|
ref->fD3DIndex = fLights.ReserveD3DIndex(); |
|
|
ref->UpdateD3DInfo( fD3DDevice, &fLights ); |
|
|
ref->SetDirty( false ); |
|
|
} |
|
|
|
|
|
newFlags.SetBit( ref->fD3DIndex ); |
|
|
onLights.Append(spanLights[i]); |
|
|
} |
|
|
startScale = i; |
|
|
|
|
|
/// Attempt #2: Take some of the n strongest lights (below a given threshhold) and |
|
|
/// fade them out to nothing as they get closer to the bottom. This way, they fade |
|
|
/// out of existence instead of pop out. |
|
|
|
|
|
if( i < spanLights.GetCount() - 1 && i > 0 ) |
|
|
{ |
|
|
threshhold = span->GetLightStrength( i, proj ); |
|
|
i--; |
|
|
overHold = threshhold * 1.5f; |
|
|
if( overHold > span->GetLightStrength( 0, proj ) ) |
|
|
overHold = span->GetLightStrength( 0, proj ); |
|
|
|
|
|
for( ; i > 0 && span->GetLightStrength( i, proj ) < overHold; i-- ) |
|
|
{ |
|
|
scale = ( overHold - span->GetLightStrength( i, proj ) ) / ( overHold - threshhold ); |
|
|
|
|
|
ref = (plDXLightRef *)spanLights[i]->GetDeviceRef(); |
|
|
|
|
|
IScaleD3DLight( ref, (1 - scale) * span->GetLightScale(i, proj) ); |
|
|
} |
|
|
startScale = i + 1; |
|
|
} |
|
|
|
|
|
/// Make sure those lights that aren't scaled....aren't |
|
|
for( i = 0; i < startScale; i++ ) |
|
|
{ |
|
|
ref = (plDXLightRef *)spanLights[i]->GetDeviceRef(); |
|
|
IScaleD3DLight(ref, span->GetLightScale(i, proj) ); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// If these are non-projected lights, go ahead and enable them. |
|
|
// For the projected lights, don't enable, just remember who they are. |
|
|
if( !proj ) |
|
|
{ |
|
|
// A little change here. Some boards get sticky about exactly |
|
|
// how many lights you have enabled, whether you are currently |
|
|
// rendering or not. So if we go through enabling the lights |
|
|
// we want and disabling the ones we don't, then even though |
|
|
// at the end of the loop, less than MaxNumLights are enabled, |
|
|
// we can still wind up screwed. |
|
|
// Think about if we have 8 lights enabled, and they all happen |
|
|
// to be at the end of fLights. Now we want to enable a different |
|
|
// 8 lights, which happen to be at the beginning of the list. |
|
|
// So we loop through and enable the lights we want, and then later |
|
|
// in the loop disable the lights we don't want. Problem is that |
|
|
// when we were enabling the ones we want we went over our 8 light |
|
|
// limit, and some boards (ATI) react by ignoring the enable request. |
|
|
// So then we disable the other lights at the end of the loop, but |
|
|
// it's too late because our enable requests at the beginning of the |
|
|
// loop were ignored. |
|
|
// Solution is to go through the list twice, first disabling, then |
|
|
// enabling. mf |
|
|
hsBitVector newOff = fLights.fEnabledFlags - newFlags; |
|
|
hsBitIterator iterOff(newOff); |
|
|
for( iterOff.Begin(); !iterOff.End(); iterOff.Advance() ) |
|
|
fD3DDevice->LightEnable(iterOff.Current(), false); |
|
|
|
|
|
hsBitVector newOn = newFlags - fLights.fEnabledFlags; |
|
|
hsBitIterator iterOn(newOn); |
|
|
for( iterOn.Begin(); !iterOn.End(); iterOn.Advance() ) |
|
|
fD3DDevice->LightEnable(iterOn.Current(), true); |
|
|
fLights.fEnabledFlags = newFlags; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fLights.fProjAll.SetCount(0); |
|
|
fLights.fProjEach.SetCount(0); |
|
|
for( i = 0; i < onLights.GetCount(); i++ ) |
|
|
{ |
|
|
if( onLights[i]->OverAll() ) |
|
|
fLights.fProjAll.Append(onLights[i]); |
|
|
else |
|
|
fLights.fProjEach.Append(onLights[i]); |
|
|
} |
|
|
onLights.SetCount(0); |
|
|
} |
|
|
} |
|
|
|
|
|
// IDisableSpanLights ///////////////////////////////////////////////////// |
|
|
// Disable all the enabled lights, remembering which they are for |
|
|
// quick reenabling. |
|
|
void plDXPipeline::IDisableSpanLights() |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fLights.fLastIndex + 1; i++ ) |
|
|
{ |
|
|
if( fLights.fEnabledFlags.IsBitSet(i) ) |
|
|
{ |
|
|
fD3DDevice->LightEnable(i, false); |
|
|
fLights.fHoldFlags.SetBit(i); |
|
|
} |
|
|
} |
|
|
fLights.fEnabledFlags.Clear(); |
|
|
} |
|
|
|
|
|
// IRestoreSpanLights ////////////////////////////////////////////////////// |
|
|
// Re-enable all the lights disabled by the matching IDisableSpanLights. |
|
|
void plDXPipeline::IRestoreSpanLights() |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fLights.fLastIndex + 1; i++ ) |
|
|
{ |
|
|
if( fLights.fHoldFlags.IsBitSet(i) ) |
|
|
{ |
|
|
fD3DDevice->LightEnable(i, true); |
|
|
fLights.fEnabledFlags.SetBit(i); |
|
|
} |
|
|
} |
|
|
fLights.fHoldFlags.Clear(); |
|
|
} |
|
|
|
|
|
//// IScaleD3DLight /////////////////////////////////////////////////////////// |
|
|
// Scale the D3D light by the given scale factor, used for fading lights |
|
|
// in and out by importance. |
|
|
void plDXPipeline::IScaleD3DLight( plDXLightRef *ref, hsScalar scale ) |
|
|
{ |
|
|
scale = int(scale * 1.e1f) * 1.e-1f; |
|
|
if( ref->fScale != scale ) |
|
|
{ |
|
|
D3DLIGHT9 light = ref->fD3DInfo; |
|
|
|
|
|
|
|
|
light.Diffuse.r *= scale; |
|
|
light.Diffuse.g *= scale; |
|
|
light.Diffuse.b *= scale; |
|
|
|
|
|
light.Ambient.r *= scale; |
|
|
light.Ambient.g *= scale; |
|
|
light.Ambient.b *= scale; |
|
|
|
|
|
light.Specular.r *= scale; |
|
|
light.Specular.g *= scale; |
|
|
light.Specular.b *= scale; |
|
|
|
|
|
fD3DDevice->SetLight( ref->fD3DIndex, &light ); |
|
|
ref->fScale = scale; |
|
|
} |
|
|
} |
|
|
|
|
|
// inlPlToDWORDColor ///////////////////////////////////////////////// |
|
|
// Convert a plasma floating point color to a D3D DWORD color |
|
|
static inline DWORD inlPlToDWORDColor(const hsColorRGBA& c) |
|
|
{ |
|
|
return (DWORD(c.a * 255.99f) << 24) |
|
|
| (DWORD(c.r * 255.99f) << 16) |
|
|
| (DWORD(c.g * 255.99f) << 8) |
|
|
| (DWORD(c.b * 255.99f) << 0); |
|
|
} |
|
|
|
|
|
// inlPlToD3DColor //////////////////////////////////////////////////// |
|
|
// Convert a plasma floating point color to a D3D floating point color. |
|
|
inline D3DCOLORVALUE plDXPipeline::inlPlToD3DColor(const hsColorRGBA& c, float a) const |
|
|
{ |
|
|
D3DCOLORVALUE ret; |
|
|
ret.r = c.r; |
|
|
ret.g = c.g; |
|
|
ret.b = c.b; |
|
|
ret.a = a; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
// inlEnsureLightingOn //////////////////////////////////////////////// |
|
|
// Turn D3D lighting on if it isn't already. |
|
|
inline void plDXPipeline::inlEnsureLightingOn() |
|
|
{ |
|
|
if( !fCurrD3DLiteState ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); |
|
|
fCurrD3DLiteState = true; |
|
|
} |
|
|
} |
|
|
|
|
|
// inlEnsureLightingOff /////////////////////////////////////////////// |
|
|
// Turn D3D lighting off if it isn't already. |
|
|
inline void plDXPipeline::inlEnsureLightingOff() |
|
|
{ |
|
|
if( fCurrD3DLiteState ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); |
|
|
fCurrD3DLiteState = false; |
|
|
} |
|
|
} |
|
|
|
|
|
// ColorMul /////////////////////////////////////////////////////////// |
|
|
// Multiply a D3D floating point color by a plasma floating point color, |
|
|
// returning the result as a D3D floating point color. |
|
|
static inline D3DCOLORVALUE ColorMul(const D3DCOLORVALUE& c0, const hsColorRGBA& c1) |
|
|
{ |
|
|
D3DCOLORVALUE ret; |
|
|
ret.r = c0.r * c1.r; |
|
|
ret.g = c0.g * c1.g; |
|
|
ret.b = c0.b * c1.b; |
|
|
ret.a = c0.a * c1.a; |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
//// ICalcLighting //////////////////////////////////////////////////////////// |
|
|
// Kind of misnamed. Sets the D3D material lighting model based on what we're |
|
|
// currently doing. |
|
|
void plDXPipeline::ICalcLighting( const plLayerInterface *currLayer, const plSpan *currSpan ) |
|
|
{ |
|
|
D3DMATERIAL9 mat; |
|
|
static hsScalar diffScale = 1.f; |
|
|
static hsScalar ambScale = 1.f; |
|
|
UInt32 props; |
|
|
|
|
|
|
|
|
plProfile_Inc(MatLightState); |
|
|
|
|
|
/// New (temporary) lighting method: |
|
|
/// The vertices now include the following: |
|
|
/// diffuse = maxVertexColor * matDiffuse + matAmbient |
|
|
/// specular = ( maxLighting + maxIllum ) * matDiffuse + matAmbient |
|
|
/// And we want the lighting set up like: |
|
|
/// L = I*v1 + v2 + (sigma)(light stuff * v3 + 0) |
|
|
/// Where I = 0 for now (will be the environmental light constant eventually), |
|
|
/// v1 is the diffuse vertex color and v2 is the specular vertex color. |
|
|
/// So it basically translates into: |
|
|
/// D3D ambient color = diffuse vertex color |
|
|
/// D3D ambient constant = environmental light constant (0 for now) |
|
|
/// D3D emissive color = specular vertex color |
|
|
/// D3D diffuse color = diffuse vertex color |
|
|
|
|
|
/// We now provide three lighting equations at the pipeline's disposal: |
|
|
/// Material: (the one we all know and love) |
|
|
/// MATd * VTXd + MATa + <sigma of lighting w/ MATd> |
|
|
/// Vtx preshaded: (particle systems) |
|
|
/// MATa * VTXd + 0 + <sigma of lighting w/ VTXd> |
|
|
/// Vtx non-preshaded: |
|
|
/// white * VTXd + MATa + <sigma of lighting w/ VTXd> |
|
|
/// We also have a few more for shadows and such, which are handled individually |
|
|
|
|
|
memset( &mat, 0, sizeof( mat ) ); |
|
|
|
|
|
/// Normal rendering--select the right lighting equation |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagAllBright)) |
|
|
{ |
|
|
inlEnsureLightingOn(); |
|
|
mat.Diffuse.r = mat.Diffuse.g = mat.Diffuse.b = mat.Diffuse.a = 1.f; |
|
|
mat.Ambient.r = mat.Ambient.g = mat.Ambient.b = mat.Ambient.a = 1.f; |
|
|
mat.Emissive.r = mat.Emissive.g = mat.Emissive.b = mat.Emissive.a = 1.f; |
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); |
|
|
return; |
|
|
} |
|
|
|
|
|
props = ( currSpan != nil ) ? ( currSpan->fProps & plSpan::kLiteMask ) : plSpan::kLiteMaterial; |
|
|
|
|
|
if( fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpChans ) |
|
|
{ |
|
|
props = plSpan::kLiteMaterial; |
|
|
fLayerState[0].fShadeFlags |= hsGMatState::kShadeNoShade | hsGMatState::kShadeWhite; |
|
|
} |
|
|
/// Select one of our three lighting methods |
|
|
switch( props ) |
|
|
{ |
|
|
case plSpan::kLiteMaterial: // Material shading |
|
|
|
|
|
/// Material: (the one we all know and love) |
|
|
/// MATd * VTXd + MATa + <sigma of lighting w/ MATd> |
|
|
|
|
|
inlEnsureLightingOn(); |
|
|
|
|
|
// D3D ambient - give it our material static diffuse, since it will be multiplied by the vertex color |
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeWhite ) |
|
|
{ |
|
|
mat.Ambient.r = mat.Ambient.g = mat.Ambient.b = diffScale; |
|
|
mat.Ambient.a = 1.f; |
|
|
|
|
|
} |
|
|
else if (IsDebugFlagSet(plPipeDbg::kFlagNoPreShade)) |
|
|
{ |
|
|
mat.Ambient.r = mat.Ambient.g = mat.Ambient.b = 0; |
|
|
mat.Ambient.a = 1.f; |
|
|
} |
|
|
else |
|
|
mat.Ambient = inlPlToD3DColor(currLayer->GetPreshadeColor() * diffScale, 1.f); |
|
|
|
|
|
// D3D diffuse - give it our runtime material diffuse |
|
|
mat.Diffuse = inlPlToD3DColor(currLayer->GetRuntimeColor() * diffScale, currLayer->GetOpacity()); |
|
|
|
|
|
// D3D emissive - give it our material ambient |
|
|
mat.Emissive = inlPlToD3DColor(currLayer->GetAmbientColor() * ambScale, 1.f); |
|
|
|
|
|
// Set specular properties |
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeSpecular ) |
|
|
{ |
|
|
mat.Specular = inlPlToD3DColor( currLayer->GetSpecularColor(), 1.f); |
|
|
mat.Power = currLayer->GetSpecularPower(); |
|
|
} |
|
|
|
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeWhite ) |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, inlGetD3DColor( *(hsColorRGBA*)&mat.Ambient ) ); |
|
|
|
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeNoShade ) |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
|
|
|
fCurrLightingMethod = plSpan::kLiteMaterial; |
|
|
break; |
|
|
|
|
|
case plSpan::kLiteVtxPreshaded: // Vtx preshaded |
|
|
// MATa * VTXd + 0 + <sigma of lighting w/ VTXd> |
|
|
// Mapping to: GLa * AMSrc + EMSrc + <.....................DMSrc> |
|
|
|
|
|
#if 0 // PARTICLESHADE |
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeEmissive ) |
|
|
{ |
|
|
inlEnsureLightingOff(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
inlEnsureLightingOn(); |
|
|
|
|
|
// Set a black material (we ONLY care about vertex color when doing particles, |
|
|
// er I mean, vtxPreshaded) |
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0 ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
} |
|
|
#else // PARTICLESHADE |
|
|
inlEnsureLightingOn(); |
|
|
|
|
|
// MATa * white + 0 + <sigma of lighting with VTXd> |
|
|
|
|
|
|
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0 ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeEmissive ) |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
#endif // PARTICLESHADE |
|
|
|
|
|
fCurrLightingMethod = plSpan::kLiteVtxPreshaded; |
|
|
break; |
|
|
|
|
|
|
|
|
case plSpan::kLiteVtxNonPreshaded: // Vtx non-preshaded |
|
|
// white * VTXd + MATa + <sigma of lighting w/ VTXd> |
|
|
// Mapping to: GLa * AMSrc + EMSrc + <.....................DMSrc> |
|
|
|
|
|
inlEnsureLightingOn(); |
|
|
|
|
|
// D3D emissive - give it our material ambient |
|
|
mat.Emissive = inlPlToD3DColor(currLayer->GetAmbientColor() * ambScale, 1.f); |
|
|
|
|
|
// Set specular properties |
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeSpecular ) |
|
|
{ |
|
|
mat.Specular = inlPlToD3DColor( currLayer->GetSpecularColor(), 1.f); |
|
|
mat.Power = currLayer->GetSpecularPower(); |
|
|
} |
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
|
|
|
// Lightmaps want WHITE here, otherwise we want BLACK |
|
|
DWORD preShadeStrength; |
|
|
preShadeStrength = inlPlToDWORDColor(currLayer->GetPreshadeColor()); |
|
|
fD3DDevice->SetRenderState(D3DRS_AMBIENT, preShadeStrength); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
fCurrLightingMethod = plSpan::kLiteVtxNonPreshaded; |
|
|
break; |
|
|
|
|
|
default: |
|
|
hsAssert( false, "Bad lighting type" ); |
|
|
break; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// plDXLightSettings Functions ///////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
plDXLightSettings::plDXLightSettings() |
|
|
: fActiveList(nil), |
|
|
fRefList(nil), |
|
|
fPipeline(nil) |
|
|
{ |
|
|
} |
|
|
|
|
|
//// Reset //////////////////////////////////////////////////////////////////// |
|
|
// Sets member variables to initial states. |
|
|
|
|
|
void plDXLightSettings::Reset( plDXPipeline *pipe ) |
|
|
{ |
|
|
Release(); |
|
|
|
|
|
fNextShadowLight = 0; |
|
|
|
|
|
fUsedFlags.Clear(); |
|
|
fEnabledFlags.Clear(); |
|
|
fHoldFlags.Clear(); |
|
|
fProjEach.Reset(); |
|
|
fProjAll.Reset(); |
|
|
fNextIndex = 1; /// Light 0 is reserved |
|
|
fLastIndex = 1; |
|
|
fTime = 0; |
|
|
fRefList = nil; |
|
|
fPipeline = pipe; |
|
|
} |
|
|
|
|
|
//// Release ////////////////////////////////////////////////////////////////// |
|
|
// Releases/deletes anything associated with these settings. |
|
|
// This includes unregistering all lights. |
|
|
void plDXLightSettings::Release() |
|
|
{ |
|
|
plDXLightRef *ref; |
|
|
|
|
|
fProjEach.Reset(); |
|
|
fProjAll.Reset(); |
|
|
|
|
|
while( fRefList ) |
|
|
{ |
|
|
ref = fRefList; |
|
|
ref->Release(); |
|
|
ref->Unlink(); |
|
|
} |
|
|
|
|
|
// Tell the light infos to unlink themselves |
|
|
while( fActiveList ) |
|
|
fPipeline->UnRegisterLight( fActiveList ); |
|
|
|
|
|
fShadowLights.SetCount(fShadowLights.GetNumAlloc()); |
|
|
int i; |
|
|
for( i = 0; i < fShadowLights.GetCount(); i++ ) |
|
|
{ |
|
|
hsRefCnt_SafeUnRef(fShadowLights[i]); |
|
|
fShadowLights[i] = nil; |
|
|
} |
|
|
fShadowLights.SetCount(0); |
|
|
|
|
|
} |
|
|
|
|
|
//// ReserveD3DIndex ////////////////////////////////////////////////////////// |
|
|
// Reserve a D3D light index. |
|
|
|
|
|
UInt32 plDXLightSettings::ReserveD3DIndex() |
|
|
{ |
|
|
for( ; fNextIndex < (UInt32)-1; fNextIndex++ ) |
|
|
{ |
|
|
if( !fUsedFlags.IsBitSet( fNextIndex ) ) |
|
|
break; |
|
|
} |
|
|
|
|
|
fUsedFlags.SetBit( fNextIndex ); |
|
|
fEnabledFlags.ClearBit( fNextIndex ); // Ensure it's cleared |
|
|
fHoldFlags.ClearBit( fNextIndex ); |
|
|
if( fNextIndex > fLastIndex ) |
|
|
fLastIndex = fNextIndex; |
|
|
|
|
|
return fNextIndex; |
|
|
} |
|
|
|
|
|
//// ReleaseD3DIndex ////////////////////////////////////////////////////////// |
|
|
// Release a reserved D3D light index to be reused. |
|
|
|
|
|
void plDXLightSettings::ReleaseD3DIndex( UInt32 idx ) |
|
|
{ |
|
|
fUsedFlags.SetBit( idx, false ); |
|
|
if( fNextIndex > idx ) |
|
|
fNextIndex = idx; // Forces search to start here next time |
|
|
|
|
|
// Dec down fLastIndex |
|
|
while( fLastIndex > 0 && !fUsedFlags.IsBitSet( fLastIndex ) ) |
|
|
fLastIndex--; |
|
|
|
|
|
if( fNextIndex > fLastIndex ) |
|
|
fNextIndex = fLastIndex; |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Materials //////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// ISetLayer //////////////////////////////////////////////////////////////// |
|
|
// Sets whether we're rendering a base layer or upper layer. Upper layer has |
|
|
// a Z bias to avoid Z fighting. |
|
|
void plDXPipeline::ISetLayer( UInt32 lay ) |
|
|
{ |
|
|
if( lay ) |
|
|
{ |
|
|
if( fCurrRenderLayer != lay ) |
|
|
{ |
|
|
fCurrRenderLayer = lay; |
|
|
|
|
|
plCONST(int) kBiasMult = 8; |
|
|
if( !( fSettings.fD3DCaps & kCapsZBias ) ) |
|
|
IProjectionMatrixToD3D(); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_DEPTHBIAS, kBiasMult * fCurrRenderLayer ); |
|
|
} |
|
|
} |
|
|
else |
|
|
IBottomLayer(); |
|
|
} |
|
|
|
|
|
//// IBottomLayer ///////////////////////////////////////////////////////////// |
|
|
// Turn off any Z bias. |
|
|
void plDXPipeline::IBottomLayer() |
|
|
{ |
|
|
if( fCurrRenderLayer != 0 ) |
|
|
{ |
|
|
fCurrRenderLayer = 0; |
|
|
if( !( fSettings.fD3DCaps & kCapsZBias ) ) |
|
|
IProjectionMatrixToD3D(); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_DEPTHBIAS, 0 ); |
|
|
} |
|
|
} |
|
|
|
|
|
// Special effects ///////////////////////////////////////////////////////////// |
|
|
|
|
|
// IPushOverBaseLayer ///////////////////////////////////////////////////////// |
|
|
// Sets fOverBaseLayer (if any) as a wrapper on top of input layer. |
|
|
// This allows the OverBaseLayer to intercept and modify queries of |
|
|
// the real current layer's properties (e.g. color or state). |
|
|
// fOverBaseLayer is set to only get applied to the base layer during |
|
|
// multitexturing. |
|
|
// Must be matched with call to IPopOverBaseLayer. |
|
|
plLayerInterface* plDXPipeline::IPushOverBaseLayer(plLayerInterface* li) |
|
|
{ |
|
|
if( !li ) |
|
|
return nil; |
|
|
|
|
|
fOverLayerStack.Push(li); |
|
|
|
|
|
if( !fOverBaseLayer ) |
|
|
return fOverBaseLayer = li; |
|
|
|
|
|
fForceMatHandle = true; |
|
|
fOverBaseLayer = fOverBaseLayer->Attach(li); |
|
|
fOverBaseLayer->Eval(fTime, fFrame, 0); |
|
|
return fOverBaseLayer; |
|
|
} |
|
|
|
|
|
// IPopOverBaseLayer ///////////////////////////////////////////////////////// |
|
|
// Removes fOverBaseLayer as wrapper on top of input layer. |
|
|
// Should match calls to IPushOverBaseLayer. |
|
|
plLayerInterface* plDXPipeline::IPopOverBaseLayer(plLayerInterface* li) |
|
|
{ |
|
|
if( !li ) |
|
|
return nil; |
|
|
|
|
|
fForceMatHandle = true; |
|
|
|
|
|
plLayerInterface* pop = fOverLayerStack.Pop(); |
|
|
fOverBaseLayer = fOverBaseLayer->Detach(pop); |
|
|
|
|
|
return pop; |
|
|
} |
|
|
|
|
|
// IPushOverAllLayer /////////////////////////////////////////////////// |
|
|
// Push fOverAllLayer (if any) as wrapper around the input layer. |
|
|
// fOverAllLayer is set to be applied to each layer during multitexturing. |
|
|
// Must be matched by call to IPopOverAllLayer |
|
|
plLayerInterface* plDXPipeline::IPushOverAllLayer(plLayerInterface* li) |
|
|
{ |
|
|
if( !li ) |
|
|
return nil; |
|
|
|
|
|
fOverLayerStack.Push(li); |
|
|
|
|
|
if( !fOverAllLayer ) |
|
|
{ |
|
|
fOverAllLayer = li; |
|
|
fOverAllLayer->Eval(fTime, fFrame, 0); |
|
|
return fOverAllLayer; |
|
|
} |
|
|
|
|
|
fForceMatHandle = true; |
|
|
fOverAllLayer = fOverAllLayer->Attach(li); |
|
|
fOverAllLayer->Eval(fTime, fFrame, 0); |
|
|
|
|
|
return fOverAllLayer; |
|
|
} |
|
|
|
|
|
// IPopOverAllLayer ////////////////////////////////////////////////// |
|
|
// Remove fOverAllLayer as wrapper on top of input layer. |
|
|
// Should match calls to IPushOverAllLayer. |
|
|
plLayerInterface* plDXPipeline::IPopOverAllLayer(plLayerInterface* li) |
|
|
{ |
|
|
if( !li ) |
|
|
return nil; |
|
|
|
|
|
fForceMatHandle = true; |
|
|
|
|
|
plLayerInterface* pop = fOverLayerStack.Pop(); |
|
|
fOverAllLayer = fOverAllLayer->Detach(pop); |
|
|
|
|
|
return pop; |
|
|
} |
|
|
|
|
|
// PiggyBacks - used in techniques like projective lighting. |
|
|
// PiggyBacks are layers appended to each drawprimitive pass. |
|
|
// For example, if a material has 3 layers which will be drawn |
|
|
// in 2 passes, |
|
|
// pass0: layer0+layer1 |
|
|
// pass1: layer2 |
|
|
// Then if a piggyback layer layerPB is active, the actual rendering would be |
|
|
// pass0: layer0+layer1+layerPB |
|
|
// pass1: layer2 + layerPB |
|
|
|
|
|
// ISetNumActivePiggyBacks ///////////////////////////////////////////// |
|
|
// Calculate the number of active piggy backs. |
|
|
int plDXPipeline::ISetNumActivePiggyBacks() |
|
|
{ |
|
|
return fActivePiggyBacks = hsMinimum(fSettings.fMaxPiggyBacks, fPiggyBackStack.GetCount()); |
|
|
} |
|
|
|
|
|
// IPushProjPiggyBack ////////////////////////////////////////////////// |
|
|
// Push a projected texture on as a piggy back. |
|
|
void plDXPipeline::IPushProjPiggyBack(plLayerInterface* li) |
|
|
{ |
|
|
if( fView.fRenderState & plPipeline::kRenderNoPiggyBacks ) |
|
|
return; |
|
|
|
|
|
fPiggyBackStack.Push(li); |
|
|
fActivePiggyBacks = fPiggyBackStack.GetCount() - fMatPiggyBacks; |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
|
|
|
// IPopProjPiggyBacks ///////////////////////////////////////////////// |
|
|
// Remove a projected texture from use as a piggy back. |
|
|
void plDXPipeline::IPopProjPiggyBacks() |
|
|
{ |
|
|
if( fView.fRenderState & plPipeline::kRenderNoPiggyBacks ) |
|
|
return; |
|
|
|
|
|
fPiggyBackStack.SetCount(fMatPiggyBacks); |
|
|
ISetNumActivePiggyBacks(); |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
|
|
|
// IPushPiggyBacks //////////////////////////////////////////////////// |
|
|
// Push any piggy backs associated with a material, presumed to |
|
|
// be a light map because that's all they are used for. |
|
|
// Matched with IPopPiggyBacks |
|
|
void plDXPipeline::IPushPiggyBacks(hsGMaterial* mat) |
|
|
{ |
|
|
hsAssert(!fMatPiggyBacks, "Push/Pop Piggy mismatch"); |
|
|
|
|
|
if( fView.fRenderState & plPipeline::kRenderNoPiggyBacks ) |
|
|
return; |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < mat->GetNumPiggyBacks(); i++ ) |
|
|
{ |
|
|
if( !mat->GetPiggyBack(i) ) |
|
|
continue; |
|
|
|
|
|
if ((mat->GetPiggyBack(i)->GetMiscFlags() & hsGMatState::kMiscLightMap) |
|
|
&& IsDebugFlagSet(plPipeDbg::kFlagNoLightmaps)) |
|
|
continue; |
|
|
|
|
|
fPiggyBackStack.Push(mat->GetPiggyBack(i)); |
|
|
fMatPiggyBacks++; |
|
|
} |
|
|
ISetNumActivePiggyBacks(); |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
|
|
|
// IPopPiggyBacks /////////////////////////////////////////////////////// |
|
|
// Pop any current piggy backs set from IPushPiggyBacks. |
|
|
// Matches IPushPiggyBacks. |
|
|
void plDXPipeline::IPopPiggyBacks() |
|
|
{ |
|
|
if( fView.fRenderState & plPipeline::kRenderNoPiggyBacks ) |
|
|
return; |
|
|
|
|
|
fPiggyBackStack.SetCount(fPiggyBackStack.GetCount() - fMatPiggyBacks); |
|
|
fMatPiggyBacks = 0; |
|
|
|
|
|
ISetNumActivePiggyBacks(); |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
|
|
|
//// IHandleMaterial ////////////////////////////////////////////////////////// |
|
|
// Takes the starting "layer" and uses as many layers as possible in the given |
|
|
// material and sets up the device to draw with it. Returns the first layer |
|
|
// index not yet used. (I.e. if we ate layers 0 and 1, it'll return 2). |
|
|
// A return value of -1 means don't bother rendering. |
|
|
|
|
|
Int32 plDXPipeline::IHandleMaterial( hsGMaterial *newMat, UInt32 layer, const plSpan *currSpan ) |
|
|
{ |
|
|
// No material means no draw. |
|
|
if( !newMat && newMat->GetLayer(layer) ) |
|
|
return -1; |
|
|
|
|
|
// If this is a bump mapping pass but the object isn't currently runtime lit, just skip. |
|
|
// Note that <layer> may change here, if we're skipping past the bump layers but there |
|
|
// are more layers (passes) to do after that. |
|
|
if( ISkipBumpMap(newMat, layer, currSpan) ) |
|
|
{ |
|
|
return -1; |
|
|
} |
|
|
|
|
|
// Workaround for the ATI Radeon 7500's inability to use uvw coordinates above 1. |
|
|
// If we have a layer trying to use uvw 2 or higher, skip it and any layers bound to |
|
|
// it. |
|
|
while( (layer < newMat->GetNumLayers()) |
|
|
&& newMat->GetLayer(layer) |
|
|
&& ((newMat->GetLayer(layer)->GetUVWSrc() & 0xf) > fSettings.fMaxUVWSrc) ) |
|
|
{ |
|
|
if( newMat->GetLayer(layer)->GetMiscFlags() & hsGMatState::kMiscBindNext ) |
|
|
layer++; |
|
|
layer++; |
|
|
} |
|
|
if( layer >= newMat->GetNumLayers() ) |
|
|
return -1; |
|
|
|
|
|
// If nothing has changed, we don't need to recompute and set state. |
|
|
if( !fForceMatHandle && (newMat == fCurrMaterial && layer == fCurrLayerIdx) ) |
|
|
{ |
|
|
// Before returning, check if we have to redo our lighting |
|
|
UInt32 lightType = ( currSpan != nil ) ? ( currSpan->fProps & plSpan::kLiteMask ) : plSpan::kLiteMaterial; |
|
|
if( lightType != fCurrLightingMethod ) |
|
|
ICalcLighting( fCurrLay, currSpan ); |
|
|
|
|
|
if( fLayerState[0].fMiscFlags & (hsGMatState::kMiscBumpDu|hsGMatState::kMiscBumpDw) ) |
|
|
ISetBumpMatrices(fCurrLay, currSpan); |
|
|
|
|
|
return layer + fCurrNumLayers; |
|
|
} |
|
|
|
|
|
fForceMatHandle = false; |
|
|
|
|
|
fCurrLayerIdx = layer; |
|
|
// fCurrNumLayers = newMat->GetNumLayers(); |
|
|
|
|
|
if (newMat != fCurrMaterial) |
|
|
plProfile_Inc(MatChange); |
|
|
plProfile_Inc(LayChange); |
|
|
|
|
|
/// Test for fail states |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoDecals) && (newMat->GetCompositeFlags() & hsGMaterial::kCompDecal)) |
|
|
{ |
|
|
return -1; |
|
|
} |
|
|
|
|
|
/// Workaround for a D3D limitation--you're not allowed to render with a texture that you're |
|
|
/// rendering INTO. Hence we can't have self-reflecting cubicRenderTargets (damn) |
|
|
if( fSettings.fCurrBaseRenderTarget != nil && |
|
|
newMat->GetLayer( layer )->GetTexture() == plBitmap::ConvertNoRef( fSettings.fCurrBaseRenderTarget ) ) |
|
|
{ |
|
|
return -1; |
|
|
} |
|
|
|
|
|
/// Figure out our current states |
|
|
// Start with the base layer. |
|
|
plLayerInterface *currLay = IPushOverBaseLayer(newMat->GetLayer(layer)); |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagBumpW) && (currLay->GetMiscFlags() & hsGMatState::kMiscBumpDu) ) |
|
|
currLay = newMat->GetLayer(fCurrLayerIdx = ++layer); |
|
|
|
|
|
currLay = IPushOverAllLayer(currLay); |
|
|
|
|
|
/// Save stuff for next time around |
|
|
ICompositeLayerState(0, currLay); |
|
|
hsRefCnt_SafeAssign( fCurrMaterial, newMat ); |
|
|
fCurrLayerIdx = layer; |
|
|
fCurrLay = currLay; |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagDisableSpecular)) |
|
|
fLayerState[0].fShadeFlags &= ~hsGMatState::kShadeSpecular; |
|
|
|
|
|
// ZIncLayer requests Z bias for upper layers. |
|
|
if( fLayerState[0].fZFlags & hsGMatState::kZIncLayer ) |
|
|
ISetLayer( 1 ); |
|
|
else |
|
|
IBottomLayer(); |
|
|
|
|
|
/// A few debugging things |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoAlphaBlending)) |
|
|
fLayerState[0].fBlendFlags &= ~hsGMatState::kBlendMask; |
|
|
|
|
|
if ((IsDebugFlagSet(plPipeDbg::kFlagBumpUV) || IsDebugFlagSet(plPipeDbg::kFlagBumpW)) && (fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpChans) ) |
|
|
{ |
|
|
switch( fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpChans ) |
|
|
{ |
|
|
case hsGMatState::kMiscBumpDu: |
|
|
break; |
|
|
case hsGMatState::kMiscBumpDv: |
|
|
if( !(fCurrMaterial->GetLayer(layer-2)->GetBlendFlags() & hsGMatState::kBlendAdd) ) |
|
|
{ |
|
|
fLayerState[0].fBlendFlags &= ~hsGMatState::kBlendMask; |
|
|
fLayerState[0].fBlendFlags |= hsGMatState::kBlendMADD; |
|
|
} |
|
|
break; |
|
|
case hsGMatState::kMiscBumpDw: |
|
|
if( !(fCurrMaterial->GetLayer(layer-1)->GetBlendFlags() & hsGMatState::kBlendAdd) ) |
|
|
{ |
|
|
fLayerState[0].fBlendFlags &= ~hsGMatState::kBlendMask; |
|
|
fLayerState[0].fBlendFlags |= hsGMatState::kBlendMADD; |
|
|
} |
|
|
break; |
|
|
default: |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
/// Get the # of layers we can draw in this pass into fCurrNumLayers |
|
|
int oldNumLayers = fCurrNumLayers; |
|
|
ILayersAtOnce( newMat, layer ); |
|
|
if( oldNumLayers != fCurrNumLayers ) |
|
|
{ |
|
|
// This hack is necessary to cover a hack necessary to cover a "limitation" in the GeForce2 drivers. |
|
|
// Basically, we have to handle NoTexAlpha/Color differently if it's stage 1 than other stages, |
|
|
// so even though the BlendFlags haven't changed, the calls to D3D are different. Another |
|
|
// way to handle this would be to have a different handler based on whether we are 2 TMU limited |
|
|
// or not, but whatever. |
|
|
if( fLayerState[1].fBlendFlags & (hsGMatState::kBlendNoTexAlpha | hsGMatState::kBlendNoTexColor) ) |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
} |
|
|
|
|
|
// Placed here, since it's material-dependent (or more accurately, current-layer-dependent) |
|
|
ICalcLighting( currLay, currSpan ); |
|
|
|
|
|
// If we're bump mapping, compute the texture transforms. |
|
|
if( fLayerState[0].fMiscFlags & (hsGMatState::kMiscBumpDu|hsGMatState::kMiscBumpDw) ) |
|
|
ISetBumpMatrices(currLay, currSpan); |
|
|
|
|
|
/// Transfer states to D3D now |
|
|
IHandleFirstTextureStage( currLay ); |
|
|
|
|
|
currLay = IPopOverAllLayer(currLay); |
|
|
currLay = IPopOverBaseLayer(currLay); |
|
|
fCurrLay = currLay; |
|
|
|
|
|
int nextLayer = fCurrLayerIdx + fCurrNumLayers; |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagBumpW) && (fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpDw) ) |
|
|
{ |
|
|
// Bump mapping approximation using only the W (normal direction) component of lighting. |
|
|
plLayerInterface* layPtr = IPushOverAllLayer(newMat->GetLayer(fCurrLayerIdx + 2)); |
|
|
if( !layPtr ) |
|
|
return -1; |
|
|
ICompositeLayerState(1, layPtr); |
|
|
IHandleTextureStage( 1, layPtr ); |
|
|
layPtr = IPopOverAllLayer(layPtr); |
|
|
nextLayer = fCurrLayerIdx + 3; |
|
|
} |
|
|
else if (IsDebugFlagSet(plPipeDbg::kFlagBumpUV) && (fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpDu) ) |
|
|
{ |
|
|
// Bump mapping approximation using only the UV (surface tangent directions) component of lighting. |
|
|
plLayerInterface* layPtr = IPushOverAllLayer(newMat->GetLayer(fCurrLayerIdx + 3)); |
|
|
if( !layPtr ) |
|
|
return -1; |
|
|
ICompositeLayerState(1, layPtr); |
|
|
IHandleTextureStage( 1, layPtr ); |
|
|
layPtr = IPopOverAllLayer(layPtr); |
|
|
nextLayer = fCurrLayerIdx + 2; |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Normal multi texturing. |
|
|
/// Loop through all multitexturing layers |
|
|
int i; |
|
|
if( fView.fRenderState & plPipeline::kRenderBaseLayerOnly ) |
|
|
nextLayer = newMat->GetNumLayers(); |
|
|
|
|
|
for( i = 1; i < fCurrNumLayers; i++ ) |
|
|
{ |
|
|
plLayerInterface* layPtr = newMat->GetLayer( fCurrLayerIdx + i ); |
|
|
if( !layPtr ) |
|
|
return -1; |
|
|
|
|
|
// Can't render into a render target using same rendertarget as a texture. |
|
|
if( fSettings.fCurrBaseRenderTarget |
|
|
&& |
|
|
layPtr->GetTexture() == (plBitmap*)(fSettings.fCurrBaseRenderTarget) ) |
|
|
{ |
|
|
// Oops, just bail |
|
|
return -1; |
|
|
} |
|
|
|
|
|
layPtr = IPushOverAllLayer(layPtr); |
|
|
ICompositeLayerState(i, layPtr); |
|
|
IHandleTextureStage( i, layPtr ); |
|
|
layPtr = IPopOverAllLayer(layPtr); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// More cleanup for the DX9.0c 2 texture limitation. See ILayersAtOnce() |
|
|
if (fSettings.fMaxLayersAtOnce == 2) |
|
|
{ |
|
|
if ((fLayerState[0].fBlendFlags & hsGMatState::kBlendAdd) |
|
|
&& (newMat->GetNumLayers() > fCurrLayerIdx + 1) |
|
|
&& (newMat->GetLayer(fCurrLayerIdx + 1)->GetUVWSrc() & plLayerInterface::kUVWPosition)) |
|
|
{ |
|
|
// If we're doing additive blending and the next layer is based on position, |
|
|
// it's probably a distance fade. We'd rather have our diffuse color. |
|
|
// ILayersAtOnce will already have told us we can't use it this pass. |
|
|
// Skip it so it won't draw on its own next pass. |
|
|
nextLayer++; |
|
|
} |
|
|
} |
|
|
|
|
|
int numActivePiggyBacks = 0; |
|
|
if( !(fLayerState[0].fMiscFlags & hsGMatState::kMiscBumpChans) && !(fLayerState[0].fShadeFlags & hsGMatState::kShadeEmissive) ) |
|
|
{ |
|
|
/// Tack lightmap onto last stage if we have one |
|
|
numActivePiggyBacks = fActivePiggyBacks; |
|
|
if( numActivePiggyBacks > fSettings.fMaxLayersAtOnce - fCurrNumLayers ) |
|
|
numActivePiggyBacks = fSettings.fMaxLayersAtOnce - fCurrNumLayers; |
|
|
if( numActivePiggyBacks ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < numActivePiggyBacks; i++ ) |
|
|
{ |
|
|
// Note that we take piggybacks off the end of fPiggyBackStack. |
|
|
plLayerInterface* layPtr = IPushOverAllLayer( fPiggyBackStack[fPiggyBackStack.GetCount()-1-i] ); |
|
|
if( !layPtr ) |
|
|
return -1; |
|
|
ICompositeLayerState(fCurrNumLayers+i, layPtr); |
|
|
IHandleTextureStage( fCurrNumLayers+i, layPtr ); |
|
|
layPtr = IPopOverAllLayer(layPtr); |
|
|
} |
|
|
|
|
|
// If we've got a piggyback, plus two layers that must be drawn together, but |
|
|
// only two TMU's to work with, we're screwed. Someone has got to get skipped and |
|
|
// hope no one notices. Typically, the first (base) layer has the color info, |
|
|
// and the second the opacity. So we'll try using the projection to brighten |
|
|
// the color, ignoring the opacity. |
|
|
// if( ((fCurrNumLayers + numActivePiggyBacks) == fSettings.fMaxLayersAtOnce) |
|
|
// && (fLayerState[0].fMiscFlags & hsGMatState::kMiscBindNext) ) |
|
|
if( (fLayerState[0].fMiscFlags & hsGMatState::kMiscBindNext) |
|
|
&& (fCurrNumLayers < 2) ) |
|
|
nextLayer++; |
|
|
} |
|
|
} |
|
|
|
|
|
// Declare we won't be using any more texture stages. |
|
|
IStageStop( fCurrNumLayers + numActivePiggyBacks ); |
|
|
|
|
|
return nextLayer; |
|
|
} |
|
|
|
|
|
// ICompositeLayerState ///////////////////////////////////////////////////////////////// |
|
|
// Set the current Plasma state based on the input layer state and the material overrides. |
|
|
// fMatOverOn overrides to set a state bit whether it is set in the layer or not. |
|
|
// fMatOverOff overrides to clear a state bit whether it is set in the layer or not. |
|
|
const hsGMatState& plDXPipeline::ICompositeLayerState(int which, plLayerInterface* layer) |
|
|
{ |
|
|
fOldLayerState[which] = fLayerState[which]; |
|
|
fLayerState[which].Composite(layer->GetState(), fMatOverOn, fMatOverOff); |
|
|
if( fOldLayerState[which].fBlendFlags == UInt32(-1) ) |
|
|
fOldLayerState[which].fBlendFlags = ~fLayerState[which].fBlendFlags; |
|
|
|
|
|
return fLayerState[which]; |
|
|
} |
|
|
|
|
|
//// IHandleFirstTextureStage ///////////////////////////////////////////////// |
|
|
// Convert internal material state to D3D state for the base layer. |
|
|
void plDXPipeline::IHandleFirstTextureStage( plLayerInterface *layer ) |
|
|
{ |
|
|
IHandleTextureMode(layer); |
|
|
IHandleShadeMode(); |
|
|
if( fLayerState[0].Differs( fLayerState[0].fZFlags, fOldLayerState[0].fZFlags, hsGMatState::kZMask ) ) |
|
|
IHandleZMode(); |
|
|
IHandleMiscMode(); |
|
|
|
|
|
IHandleTextureStage( 0, layer ); |
|
|
} |
|
|
|
|
|
//// IHandleShadeMode ///////////////////////////////////////////////////////// |
|
|
// Convert shade state into D3D settings. |
|
|
void plDXPipeline::IHandleShadeMode() |
|
|
{ |
|
|
if( fLayerState[0].Differs( fLayerState[0].fShadeFlags, fOldLayerState[0].fShadeFlags, hsGMatState::kShadeSpecular ) ) |
|
|
{ |
|
|
if( fLayerState[0].fShadeFlags & hsGMatState::kShadeSpecular ) |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARENABLE, TRUE ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IHandleZMode ///////////////////////////////////////////////////////////// |
|
|
// Convert Z state into D3D settings. |
|
|
void plDXPipeline::IHandleZMode() |
|
|
{ |
|
|
switch( fLayerState[0].fZFlags & hsGMatState::kZMask ) |
|
|
{ |
|
|
case hsGMatState::kZClearZ: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
break; |
|
|
case hsGMatState::kZNoZRead: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
break; |
|
|
case hsGMatState::kZNoZWrite: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); |
|
|
break; |
|
|
case hsGMatState::kZNoZRead | hsGMatState::kZClearZ: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
break; |
|
|
case hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS ); |
|
|
break; |
|
|
case 0: |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
break; |
|
|
|
|
|
// illegal combinations |
|
|
case hsGMatState::kZClearZ | hsGMatState::kZNoZWrite: |
|
|
case hsGMatState::kZClearZ | hsGMatState::kZNoZWrite | hsGMatState::kZNoZRead: |
|
|
hsAssert(false, "Illegal combination of Z Buffer modes (Clear but don't write)"); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
//// IHandleMiscMode ////////////////////////////////////////////////////////// |
|
|
// Convert Misc state into D3D settings. |
|
|
void plDXPipeline::IHandleMiscMode() |
|
|
{ |
|
|
if( fLayerState[0].Differs(fLayerState[0].fMiscFlags, fOldLayerState[0].fMiscFlags, hsGMatState::kMiscWireFrame) ) |
|
|
{ |
|
|
if( fLayerState[0].fMiscFlags & hsGMatState::kMiscWireFrame ) |
|
|
fD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IHandleTextureStage ////////////////////////////////////////////////////// |
|
|
// Issue D3D calls to enable rendering the given layer at the given texture stage. |
|
|
void plDXPipeline::IHandleTextureStage( UInt32 stage, plLayerInterface *layer ) |
|
|
{ |
|
|
hsGDeviceRef *ref = nil; |
|
|
plBitmap *texture; |
|
|
|
|
|
// Blend mode |
|
|
const hsGMatState& layState = fLayerState[stage]; |
|
|
if( fLayerState[ stage ].fBlendFlags ^ fOldLayerState[stage].fBlendFlags ) |
|
|
IHandleStageBlend(stage); |
|
|
|
|
|
// Texture wrap/clamp mode |
|
|
if( fLayerState[ stage ].fClampFlags ^ fOldLayerState[stage].fClampFlags ) |
|
|
IHandleStageClamp(stage); |
|
|
|
|
|
// UVW transform |
|
|
IHandleStageTransform( stage, layer ); |
|
|
|
|
|
// Create the D3D texture (if necessary) and set it to the device. |
|
|
if( ( texture = layer->GetTexture() ) != nil ) |
|
|
{ |
|
|
ref = texture->GetDeviceRef(); |
|
|
if( ref == nil || ref->IsDirty() ) |
|
|
{ |
|
|
// Normal textures |
|
|
plMipmap *mip; |
|
|
plCubicEnvironmap *cubic; |
|
|
|
|
|
if( ( mip = plMipmap::ConvertNoRef( texture ) ) != nil ) |
|
|
ref = MakeTextureRef( layer, mip ); |
|
|
|
|
|
// Cubic environment maps |
|
|
else if( ( cubic = plCubicEnvironmap::ConvertNoRef( texture ) ) != nil ) |
|
|
ref = IMakeCubicTextureRef( layer, cubic ); |
|
|
} |
|
|
} |
|
|
|
|
|
if( ref != nil ) |
|
|
IUseTextureRef(stage, ref, layer); |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetTexture( stage, NULL ); |
|
|
hsRefCnt_SafeUnRef( fLayerRef[ stage ] ); |
|
|
fLayerRef[ stage ] = nil; |
|
|
} |
|
|
} |
|
|
|
|
|
// CheckTextureRef ////////////////////////////////////////////////////// |
|
|
// Make sure the given layer's texture has background D3D resources allocated. |
|
|
void plDXPipeline::CheckTextureRef(plLayerInterface* layer) |
|
|
{ |
|
|
plBitmap* bitmap = layer->GetTexture(); |
|
|
if( bitmap ) |
|
|
{ |
|
|
hsGDeviceRef* ref = bitmap->GetDeviceRef(); |
|
|
|
|
|
if( !ref ) |
|
|
{ |
|
|
plMipmap* mip = plMipmap::ConvertNoRef(bitmap); |
|
|
if( mip ) |
|
|
{ |
|
|
MakeTextureRef(layer, mip); |
|
|
return; |
|
|
} |
|
|
|
|
|
plCubicEnvironmap* cubic = plCubicEnvironmap::ConvertNoRef(bitmap); |
|
|
if( cubic ) |
|
|
{ |
|
|
IMakeCubicTextureRef(layer, cubic); |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// IHandleBumpEnv ////////////////////////////////////////////////////////////// |
|
|
// D3D settings for BUMPENVMAPLUMINANCE. |
|
|
// This has never been used in production assets, because I never got |
|
|
// a good effect out of it, and BUMPENVMAPLUMINANCE isn't universally |
|
|
// supported in hardware. |
|
|
void plDXPipeline::IHandleBumpEnv(int stage, UInt32 blendFlags) |
|
|
{ |
|
|
DWORD current = stage ? D3DTA_CURRENT : D3DTA_DIFFUSE; |
|
|
UInt32 colorSrc = blendFlags & hsGMatState::kBlendInvertColor ? D3DTA_TEXTURE | D3DTA_COMPLEMENT : D3DTA_TEXTURE; |
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLOROP, D3DTOP_BUMPENVMAPLUMINANCE); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLORARG1, colorSrc); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLORARG2, current); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT); |
|
|
|
|
|
const hsMatrix44& envXfm = fCurrLay->GetBumpEnvMatrix(); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVMAT00, F2DW(envXfm.fMap[0][0])); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVMAT01, F2DW(envXfm.fMap[1][0])); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVMAT10, F2DW(envXfm.fMap[0][1])); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVMAT11, F2DW(envXfm.fMap[1][1])); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVLSCALE, F2DW(envXfm.fMap[2][2])); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_BUMPENVLOFFSET, F2DW(envXfm.fMap[2][3])); |
|
|
} |
|
|
|
|
|
//// IHandleStageBlend //////////////////////////////////////////////////////// |
|
|
// Translate current blend state for this stage into D3D settings. |
|
|
void plDXPipeline::IHandleStageBlend(int stage) |
|
|
{ |
|
|
const UInt32 blendFlags = fLayerState[stage].fBlendFlags; |
|
|
// If it's the base layer, handle that differently, because it's not really |
|
|
// texture stage settings, but frame buffer blend settings. |
|
|
if( stage == 0 ) |
|
|
{ |
|
|
IHandleFirstStageBlend(); |
|
|
return; |
|
|
} |
|
|
|
|
|
UInt32 colorSrc = D3DTA_TEXTURE; |
|
|
if( blendFlags & hsGMatState::kBlendInvertColor ) |
|
|
colorSrc |= D3DTA_COMPLEMENT ; |
|
|
// kBlendEnvBumpNext not really used. |
|
|
if( blendFlags & hsGMatState::kBlendEnvBumpNext ) |
|
|
{ |
|
|
IHandleBumpEnv(stage, blendFlags); |
|
|
} |
|
|
else switch( blendFlags & hsGMatState::kBlendMask ) |
|
|
{ |
|
|
// Alpha blending. Complicated by the ability to ignore either |
|
|
// color or alpha for any given texture. The lower end GeForces |
|
|
// don't orthogonally support settings, especially when the final |
|
|
// (3rd) stage is the diffuse color/alpha modulate and the board |
|
|
// really only wants to support 2 stages. |
|
|
// So we couldn't just translate our internal plasma stage states |
|
|
// into D3D states, we had to do some rearranging. |
|
|
// Note that by the time we get here, we _know_ that this isn't the |
|
|
// base layer (stage 0), because that's handled elsewhere. |
|
|
case hsGMatState::kBlendAlpha: |
|
|
// If the current number of layers is 2, then we've already handled the |
|
|
// base layer, so this must be layer 1 and the final layer. |
|
|
// If the base layer has NoTexColor or this layer has NoTexColor, we need |
|
|
// to do some rearranging. |
|
|
if( (fCurrNumLayers == 2) |
|
|
&&((blendFlags | fLayerState[0].fBlendFlags) & hsGMatState::kBlendNoTexColor) ) |
|
|
{ |
|
|
// If this layer AND base layer are NoTexColor, then we just want the diffuse color. |
|
|
if( (blendFlags & hsGMatState::kBlendNoTexColor) |
|
|
&&(fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexColor) ) |
|
|
{ |
|
|
// select diffuse color |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_SELECTARG2 ); |
|
|
} |
|
|
// If the base layer has NoTexColor but this layer doesn't, then we |
|
|
// want the output to be this texture color times diffuse (ignoring base texture color). |
|
|
else if( fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexColor ) |
|
|
{ |
|
|
// diffuse is arg2, modulate |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); |
|
|
} |
|
|
// If base layer doesn't have NoTexColor, but this layer does, then |
|
|
// we want the output to be diffuse times base texture, which is in current. |
|
|
else if( blendFlags & hsGMatState::kBlendNoTexColor ) |
|
|
{ |
|
|
// diffuse is arg1, modulate |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); |
|
|
} |
|
|
|
|
|
} |
|
|
// If we get here and this layer has NoTexColor, then we MUST be on a layer |
|
|
// above 1, which means we're on an advanced enough board to handle this orthogonally, |
|
|
// i.e. one with more than 2 texture stages. |
|
|
else if( blendFlags & hsGMatState::kBlendNoTexColor ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_SELECTARG2 ); |
|
|
} |
|
|
// Finally, no NoTexColor in sight, just set it. |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, |
|
|
blendFlags & hsGMatState::kBlendInvertAlpha |
|
|
? D3DTOP_MODULATEINVALPHA_ADDCOLOR |
|
|
: D3DTOP_BLENDTEXTUREALPHA ); |
|
|
} |
|
|
// The same ordeal for alpha, and the ability to ignore the alpha on any texture. |
|
|
// Note the additional logic for how to combine the alphas of multiple textures |
|
|
// into a final FB alpha. |
|
|
// This is orthogonal to using the alpha to combine colors of two different textures. |
|
|
// The default behavior is to use the upper texture alpha to blend the upper layer color |
|
|
// with the lower texture color, but retain the lower texture alpha (modulated by diffuse) |
|
|
// for the frame buffer alpha. |
|
|
switch( blendFlags & ( hsGMatState::kBlendAlphaAdd | hsGMatState::kBlendAlphaMult ) ) |
|
|
{ |
|
|
default: |
|
|
case 0: |
|
|
// Using alpha to blend textures, but this layer's alpha doesn't affect final FB |
|
|
// alpha. |
|
|
// Two layer setup with one or the other (or both) ignoring alpha. |
|
|
if( (fCurrNumLayers == 2) |
|
|
&&((blendFlags | fLayerState[0].fBlendFlags) & hsGMatState::kBlendNoTexAlpha) ) |
|
|
{ |
|
|
// Both ignoring alpha, use diffuse. |
|
|
if( (blendFlags & hsGMatState::kBlendNoTexAlpha) |
|
|
&&(fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexAlpha) ) |
|
|
{ |
|
|
// select diffuse alpha |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
} |
|
|
// Base ignoring alpha, use diffuse times this texure alpha. |
|
|
else if( fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexAlpha ) |
|
|
{ |
|
|
// diffuse is arg2, modulate |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG1, |
|
|
blendFlags & hsGMatState::kBlendInvertAlpha |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); |
|
|
} |
|
|
// This ignoring alpha, use diffuse times base alpha (in current). |
|
|
else if( blendFlags & hsGMatState::kBlendNoTexAlpha ) |
|
|
{ |
|
|
// diffuse is arg1, modulate |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
} |
|
|
} |
|
|
// Ignoring alpha or not, with more than 2 texture stages, |
|
|
// Either way, we'll ignore this texture's alpha, because it's an upper layer |
|
|
// and has already been used (if it's going to get used) to blend this texture's |
|
|
// color with the lower layers. |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
} |
|
|
break; |
|
|
// Alpha coming out of this stage is lower stage alpha plus this texture alpha. |
|
|
case hsGMatState::kBlendAlphaAdd: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_ADD ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG1, |
|
|
blendFlags & hsGMatState::kBlendInvertAlpha |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
// Alpha coming out of this stage is lower stage alpha times this texture alpha. |
|
|
case hsGMatState::kBlendAlphaMult: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG1, |
|
|
blendFlags & hsGMatState::kBlendInvertAlpha |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
} |
|
|
break; |
|
|
|
|
|
// Add texture colors, pass through current alpha. |
|
|
case hsGMatState::kBlendAdd: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_ADD ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
|
|
|
// Multiply texture colors, pass through current alpha |
|
|
case hsGMatState::kBlendMult: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
|
|
|
if (fSettings.fMaxLayersAtOnce == 2 && stage == 1) |
|
|
{ |
|
|
// On these boards, the only way we can do 2 textures plus diffuse is to |
|
|
// multiply it in during stage 0, but that only gives the same result |
|
|
// when doing a mult blend, which we won't know when setting up stage 0. |
|
|
// Now that we know, adjust stage 0 settings. |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
} |
|
|
break; |
|
|
|
|
|
// Dot3 texture colors, pass through current alpha. |
|
|
case hsGMatState::kBlendDot3: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3 ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
|
|
|
// Add signed texture colors, pass through current alpha. |
|
|
case hsGMatState::kBlendAddSigned: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_ADDSIGNED ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
|
|
|
// Add signed * 2 texture colors, pass through current alpha. |
|
|
case hsGMatState::kBlendAddSigned2X: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_ADDSIGNED2X ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
|
|
|
// kBlendAddColorTimesAlpha is only supported for the base layer. |
|
|
case hsGMatState::kBlendAddColorTimesAlpha: |
|
|
hsAssert(false, "Blend mode unsupported on upper layers"); |
|
|
break; |
|
|
|
|
|
// No blend, select this texture color and pass through current alpha |
|
|
case 0: |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG1, colorSrc ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); |
|
|
|
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
//// IHandleFirstStageBlend /////////////////////////////////////////////////// |
|
|
// Set frame buffer blend mode for blending the base layer |
|
|
// For the case of rendering to a texture with alpha, the alpha written to |
|
|
// the render target will be computed exactly as the color (limitation of D3D). |
|
|
void plDXPipeline::IHandleFirstStageBlend() |
|
|
{ |
|
|
// No color, just writing out Z values. |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendNoColor ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); |
|
|
fLayerState[0].fBlendFlags |= 0x80000000; |
|
|
} |
|
|
else |
|
|
{ |
|
|
switch( fLayerState[0].fBlendFlags & hsGMatState::kBlendMask ) |
|
|
{ |
|
|
// Detail is just a special case of alpha, handled in construction of the texture |
|
|
// mip chain by making higher levels of the chain more transparent. |
|
|
case hsGMatState::kBlendDetail: |
|
|
case hsGMatState::kBlendAlpha: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertFinalAlpha ) |
|
|
{ |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaPremultiplied ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA ); |
|
|
} |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCALPHA ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaPremultiplied ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); |
|
|
} |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); |
|
|
} |
|
|
break; |
|
|
// Multiply the final color onto the frame buffer. |
|
|
case hsGMatState::kBlendMult: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertFinalColor ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR ); |
|
|
} |
|
|
break; |
|
|
|
|
|
// Add final color to FB. |
|
|
case hsGMatState::kBlendAdd: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); |
|
|
|
|
|
break; |
|
|
|
|
|
// Multiply final color by FB color and add it into the FB. |
|
|
case hsGMatState::kBlendMADD: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); |
|
|
|
|
|
break; |
|
|
|
|
|
// Final color times final alpha, added into the FB. |
|
|
case hsGMatState::kBlendAddColorTimesAlpha: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertFinalAlpha ) |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); |
|
|
|
|
|
break; |
|
|
|
|
|
// Overwrite final color onto FB |
|
|
case 0: |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
{ |
|
|
hsAssert(false, "Too many blend modes specified in material"); |
|
|
plLayer* lay = plLayer::ConvertNoRef(fCurrMaterial->GetLayer(fCurrLayerIdx)->BottomOfStack()); |
|
|
if( lay ) |
|
|
{ |
|
|
if( lay->GetBlendFlags() & hsGMatState::kBlendAlpha ) |
|
|
{ |
|
|
lay->SetBlendFlags((lay->GetBlendFlags() & ~hsGMatState::kBlendMask) | hsGMatState::kBlendAlpha); |
|
|
} |
|
|
else |
|
|
{ |
|
|
lay->SetBlendFlags((lay->GetBlendFlags() & ~hsGMatState::kBlendMask) | hsGMatState::kBlendAdd); |
|
|
} |
|
|
} |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
// Blend ops, not currently used in production. |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, (hsGMatState::kBlendSubtract | hsGMatState::kBlendRevSubtract) ) ) |
|
|
{ |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendSubtract ) |
|
|
fD3DDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT ); |
|
|
else if( fLayerState[0].fBlendFlags & hsGMatState::kBlendRevSubtract ) |
|
|
fD3DDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); |
|
|
|
|
|
} |
|
|
|
|
|
// AlphaTestHigh is used for reducing sort artifacts on textures that are mostly opaque or transparent, but |
|
|
// have regions of translucency in transition. Like a texture for a bush billboard. It lets there be some |
|
|
// transparency falloff, but quit drawing before it gets so transparent that draw order problems (halos) |
|
|
// become apparent. |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, hsGMatState::kBlendAlphaTestHigh) ) |
|
|
{ |
|
|
plConst(UInt32) kHighAlphaTest(0x40); |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaTestHigh ) |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAREF, kHighAlphaTest); |
|
|
else |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAREF, 0x00000001); |
|
|
} |
|
|
// Set the alpha test function, turn on for alpha blending, else off. |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, hsGMatState::kBlendAlpha | hsGMatState::kBlendTest | hsGMatState::kBlendAlphaAlways | hsGMatState::kBlendAddColorTimesAlpha) ) |
|
|
{ |
|
|
if( (fLayerState[0].fBlendFlags & (hsGMatState::kBlendAlpha | hsGMatState::kBlendTest | hsGMatState::kBlendAddColorTimesAlpha)) |
|
|
&& !(fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaAlways) ) |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATER ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_ALWAYS ); |
|
|
} |
|
|
// Adjust the fog color based on the blend mode. Setting fog color to black for additive modes is |
|
|
// an exact solution, setting it to white for multipication is as close of an approximation to correct |
|
|
// as you're going to get with DX. |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, hsGMatState::kBlendAdd | hsGMatState::kBlendMult | hsGMatState::kBlendMADD | hsGMatState::kBlendAddColorTimesAlpha ) ) |
|
|
{ |
|
|
if( fLayerState[0].fBlendFlags & (hsGMatState::kBlendAdd | hsGMatState::kBlendMADD | hsGMatState::kBlendAddColorTimesAlpha) ) |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGCOLOR, 0 ); |
|
|
else if( fLayerState[0].fBlendFlags & hsGMatState::kBlendMult ) |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGCOLOR, 0xffffffff ); |
|
|
else |
|
|
fD3DDevice->SetRenderState( D3DRS_FOGCOLOR, fCurrFog.fHexColor ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IHandleTextureMode /////////////////////////////////////////////////////// |
|
|
// Handle the texture stage state for the base layer. |
|
|
void plDXPipeline::IHandleTextureMode(plLayerInterface* layer) |
|
|
{ |
|
|
plBitmap *bitmap = layer->GetTexture(); |
|
|
if( bitmap ) |
|
|
{ |
|
|
// EnvBumpNext not used in production. |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendEnvBumpNext ) |
|
|
{ |
|
|
IHandleBumpEnv(0, fLayerState[0].fBlendFlags); |
|
|
} |
|
|
// If the texture stage settings have changed. Note that this |
|
|
// is a bad test, we should just be doing something like keeping |
|
|
// an array of D3D TextureStageStates as we set them and checking against |
|
|
// that directly rather than trying to infer from higher level state |
|
|
// whether we need to make the D3D call. |
|
|
else if( fSettings.fVeryAnnoyingTextureInvalidFlag |
|
|
|| !fTexturing |
|
|
|| ( fLayerState[ 0 ].fBlendFlags ^ fOldLayerState[0].fBlendFlags ) |
|
|
|| ( fCurrNumLayers + fActivePiggyBacks != fLastEndingStage ) |
|
|
) |
|
|
{ |
|
|
// If we're only doing one layer, just modulate texture color by diffuse and we're done. |
|
|
if( ( fCurrNumLayers + fActivePiggyBacks ) <= 1 ) |
|
|
{ |
|
|
// See IHandleStageBlend for notes on NoTexColor. |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexColor ) |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 ); |
|
|
else |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, |
|
|
fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertColor |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); |
|
|
|
|
|
} |
|
|
else |
|
|
{ |
|
|
// See the check in IHandleStageBlend for fSettings.fMaxLayersAtOnce == 2. |
|
|
// It depends on these settings and adjusts what it needs. |
|
|
|
|
|
// Multitexturing, select texture color to make its way upstream on stages. |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, |
|
|
fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertColor |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
|
|
|
// If our NoTexColor setting has changed, for a refresh of blend state on the next stage |
|
|
// since it's affected by our NoTexColor state. |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, hsGMatState::kBlendNoTexColor) ) |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
} |
|
|
|
|
|
// Alpha Arg1 is texture alpha (possibly complemented), and Arg2 is diffuse (possibly complemented). |
|
|
// If we want to ignore vertex alpha, select arg1 |
|
|
// If we want to ignore texture alpha, select arg2 |
|
|
// Otherwise (and normally) multiply the two. |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, |
|
|
fLayerState[0].fBlendFlags & hsGMatState::kBlendNoVtxAlpha |
|
|
? D3DTOP_SELECTARG1 |
|
|
: fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexAlpha |
|
|
? D3DTOP_SELECTARG2 |
|
|
: D3DTOP_MODULATE ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, |
|
|
fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertAlpha |
|
|
? D3DTA_TEXTURE | D3DTA_COMPLEMENT |
|
|
: D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE | |
|
|
( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertVtxAlpha |
|
|
? D3DTA_COMPLEMENT |
|
|
: 0 ) ); |
|
|
|
|
|
fTexturing = true; |
|
|
} |
|
|
} |
|
|
// Here we've no texture for the base layer, but we have more than layer. |
|
|
// Select diffuse color and alpha, and pretend we have a texture but we're ignoring its |
|
|
// color and alpha. |
|
|
else if( fCurrNumLayers + fActivePiggyBacks > 1 ) |
|
|
{ |
|
|
fLayerState[0].fBlendFlags |= hsGMatState::kBlendNoTexColor | hsGMatState::kBlendNoTexAlpha; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE); |
|
|
if( fLayerState[0].Differs( fLayerState[0].fBlendFlags, fOldLayerState[0].fBlendFlags, (hsGMatState::kBlendNoTexColor|hsGMatState::kBlendNoTexAlpha)) ) |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
fTexturing = false; |
|
|
} |
|
|
// Finally, a color only (non-textured) pass. Just select diffuse. |
|
|
else |
|
|
{ |
|
|
if( fTexturing || fSettings.fVeryAnnoyingTextureInvalidFlag ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE | |
|
|
( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertVtxAlpha ? D3DTA_COMPLEMENT : 0 ) ); |
|
|
|
|
|
fTexturing = false; |
|
|
} |
|
|
} |
|
|
|
|
|
fSettings.fVeryAnnoyingTextureInvalidFlag = false; |
|
|
} |
|
|
|
|
|
//// IHandleStageClamp //////////////////////////////////////////////////////// |
|
|
// Translate our current wrap/clamp mode to D3D calls. |
|
|
void plDXPipeline::IHandleStageClamp(int stage) |
|
|
{ |
|
|
const UInt32 flags = fLayerState[stage].fClampFlags; |
|
|
switch( flags ) |
|
|
{ |
|
|
case 0: |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP ); |
|
|
break; |
|
|
case hsGMatState::kClampTextureU: |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP ); |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP ); |
|
|
break; |
|
|
case hsGMatState::kClampTextureV: |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP ); |
|
|
break; |
|
|
case hsGMatState::kClampTexture: |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP ); |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP ); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
void plDXPipeline::ISetBumpMatrices(const plLayerInterface* layer, const plSpan* span) |
|
|
{ |
|
|
//#define BUMP_COMPARE_MATH |
|
|
#ifdef BUMP_COMPARE_MATH |
|
|
// This section is just debugging, to compute the matrices that will be set. |
|
|
static hsMatrix44 preMDu; |
|
|
static hsMatrix44 preMDv; |
|
|
static hsMatrix44 preMDw; |
|
|
static int preMInit = false; |
|
|
if( !preMInit ) |
|
|
{ |
|
|
hsMatrix44 rotAndCollapseToX; |
|
|
int i, j; |
|
|
for( i = 0; i < 4; i++ ) |
|
|
{ |
|
|
for( j = 0; j < 4; j++ ) |
|
|
{ |
|
|
rotAndCollapseToX.fMap[i][j] = 0; |
|
|
} |
|
|
} |
|
|
rotAndCollapseToX.fMap[0][2] = 1.f; |
|
|
rotAndCollapseToX.fMap[3][3] = 1.f; |
|
|
rotAndCollapseToX.NotIdentity(); |
|
|
|
|
|
hsMatrix44 offset; |
|
|
offset.Reset(); |
|
|
offset.fMap[0][0] = 0.5f; |
|
|
offset.fMap[0][3] = 0.5f; |
|
|
offset.NotIdentity(); |
|
|
|
|
|
preMDu = offset * rotAndCollapseToX; |
|
|
|
|
|
offset.fMap[1][3] = 0.5f; |
|
|
|
|
|
preMDv = offset * rotAndCollapseToX; |
|
|
|
|
|
offset.fMap[1][3] = 1.f; |
|
|
|
|
|
preMDw = offset * rotAndCollapseToX; |
|
|
|
|
|
preMInit = true; |
|
|
} |
|
|
|
|
|
hsMatrix44 localToLight = span->GetLight(0, false)->GetWorldToLight() * span->fLocalToWorld; |
|
|
localToLight.fMap[0][3] = localToLight.fMap[1][3] = localToLight.fMap[2][3] = 0; |
|
|
|
|
|
fBumpDuMatrix = preMDu * localToLight; |
|
|
fBumpDvMatrix = preMDv * localToLight; |
|
|
|
|
|
hsMatrix44 c2w = fView.fCameraToWorld; |
|
|
hsMatrix44 cameraToLight = span->GetLight(0, false)->GetWorldToLight() * c2w; |
|
|
cameraToLight.fMap[0][3] = cameraToLight.fMap[1][3] = cameraToLight.fMap[2][3] = 0; |
|
|
fBumpDwMatrix = preMDw * cameraToLight; |
|
|
|
|
|
// HACK PART - FOR COMPARISON |
|
|
hsMatrix44 bDu = fBumpDuMatrix; |
|
|
hsMatrix44 bDv = fBumpDvMatrix; |
|
|
hsMatrix44 bDw = fBumpDwMatrix; |
|
|
static hsMatrix44 zeroMatrix; |
|
|
fBumpDuMatrix = zeroMatrix; |
|
|
fBumpDvMatrix = zeroMatrix; |
|
|
fBumpDwMatrix = zeroMatrix; |
|
|
// HACK PART - FOR COMPARISON |
|
|
|
|
|
#endif // BUMP_COMPARE_MATH |
|
|
|
|
|
// Here's the math |
|
|
// The incoming uv coordinate is either: |
|
|
// kMiscBumpDu - dPos/dU (in other words, the direction in space from this vertex where U increases and V remains constant) in local space. |
|
|
// kMiscBumpDv - dPos/dV (in other words, the direction in space from this vertex where V increases and U remains constant) in local space. |
|
|
// kMiscBumpDw - the normal in camera space. |
|
|
// |
|
|
// In each case, we need to transform the vector (uvw coord) into light space, and dot it with the light direction. |
|
|
// Well, in light space, the light direction is always (0,0,1). |
|
|
// So really, we just transform the vector into light space, and the z component is what we want. |
|
|
// Then, for each of these, we take that z value (the dot product) and put it into a color channel. |
|
|
// R = dPos/dU dot liDir |
|
|
// G = dPos/dV dot liDir |
|
|
// B = dPos/dW dot liDir |
|
|
// |
|
|
// That's what we want, here's how we get it. |
|
|
// Here, Li(vec) means the vector in light space, Loc(vec) is local space, Tan(vec) is tangent space |
|
|
// |
|
|
// Li(uvw) = local2Light * Loc(uvw) (uvw comes in in local space, ie input uvw == Loc(uvw) |
|
|
// Then we want to: |
|
|
// a) Rotate the Z component to be along X (U) axis |
|
|
// b) Zero out the new Y and Z |
|
|
// c) Scale and offset our new X (the old Z) so -1 => 0, 1 => 1 (scale by 0.5, add 0.5). |
|
|
// The following matrix does all this (it's just a concatenation of the above 3 simple matrices). |
|
|
// M = |0 0 0.5 0.5| |
|
|
// |0 0 0 0 | |
|
|
// |0 0 0 0 | |
|
|
// |0 0 0 1 | |
|
|
// |
|
|
// Our lookup texture that these transformed coords will read into has three horizontal bands, |
|
|
// the bottom 3rd is a ramp along U of 0->red |
|
|
// middle 3rd is a ramp along U of 0->green |
|
|
// last third (highest V) is a ramp along U of 0->blue. |
|
|
// So we can do the conversion from our dot to a color with an appropriate V offset in the above M. |
|
|
// |
|
|
// dPos/dU and dPos/dV are both input in local space, so the transform to get them into light space is |
|
|
// the same for each, and that's obviously WorldToLight * LocalToWorld. |
|
|
// That's a little inconvenient and inefficient. It's inconvenient, because for an omni light, we |
|
|
// can easily fake a light direction (span position - light position), but the full matrix is kind |
|
|
// of arbitrary. We could fake it, but instead we move on. It's inefficient because, looking at the |
|
|
// form of matrix M, we know we'll be throwing away a lot of it anyway. So we work through the matrix |
|
|
// math and find that we're going to wind up with: |
|
|
// |
|
|
// M1 = | M[0][2] * loc2li[2][0] M[0][2] * loc2li[2][1] M[0][2] * loc2li[2][2] 0.5 | |
|
|
// | 0 0 0 0 | |
|
|
// | 0 0 0 0 | |
|
|
// | 0 0 0 1 | |
|
|
// |
|
|
// So all we really need is loc2li[2] (row 2). A little more matrix math gives us: |
|
|
// |
|
|
// loc2li[2] = (w2li[2] dot loc2wT[0], w2li[2] dot loc2wT[1], w2li[2] dot loc2wT[2]) (where loc2wT is Transpose(loc2w) |
|
|
// |
|
|
// And hey, that's just dependent on the light's direction w2li[2]. The same thing works out for dPos/dW, except |
|
|
// substitue cam2w for loc2w (since input is in camera space instead of world space). |
|
|
// |
|
|
// And that's about it. We don't actually have to multiply all those matrices at run-time, because |
|
|
// we know what the answer will be anyway. We just construct the matrices, making sure we set the |
|
|
// appropriate translate for V to get each into the right color channel. The hardware does the three |
|
|
// uv transforms and lookups, sums the results, and the output is: |
|
|
// (dPos/dU dot liDir, dPos/dV dot liDir, dPos/dW dot liDir), which also happens to be the light direction |
|
|
// transformed into tangent space. We dot that with our bump map (which has the normals in tangent space), |
|
|
// and we've got per-pixel shading for this light direction. |
|
|
|
|
|
|
|
|
hsPoint3 spanPos = span->fWorldBounds.GetCenter(); |
|
|
hsVector3 liDir(0,0,0); |
|
|
int i; |
|
|
const hsTArray<plLightInfo*>& spanLights = span->GetLightList(false); |
|
|
hsScalar maxStrength = 0; |
|
|
for( i = 0; i < spanLights.GetCount(); i++ ) |
|
|
{ |
|
|
hsScalar liWgt = span->GetLightStrength(i, false); |
|
|
// A light strength of 2.f means it's from a light group, and we haven't actually calculated |
|
|
// the strength. So calculate it now. |
|
|
if( liWgt == 2.f ) |
|
|
{ |
|
|
hsScalar scale; |
|
|
spanLights[i]->GetStrengthAndScale(span->fWorldBounds, liWgt, scale); |
|
|
} |
|
|
if( liWgt > maxStrength ) |
|
|
maxStrength = liWgt; |
|
|
liDir += spanLights[i]->GetNegativeWorldDirection(spanPos) * liWgt; |
|
|
} |
|
|
hsFastMath::NormalizeAppr(liDir); |
|
|
|
|
|
static hsScalar kUVWScale = 1.f; |
|
|
hsScalar uvwScale = kUVWScale; |
|
|
if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAdd ) |
|
|
{ |
|
|
hsVector3 cam2span(&GetViewPositionWorld(), &spanPos); |
|
|
hsFastMath::NormalizeAppr(cam2span); |
|
|
liDir += cam2span; |
|
|
hsFastMath::NormalizeAppr(liDir); |
|
|
static hsScalar kSpecularMax = 0.1f; |
|
|
static hsScalar kSpecularMaxUV = 0.5f; |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagBumpUV)) |
|
|
uvwScale *= kSpecularMaxUV; |
|
|
else |
|
|
uvwScale *= kSpecularMax; |
|
|
} |
|
|
|
|
|
switch( fCurrMaterial->GetLayer(fCurrLayerIdx)->GetMiscFlags() & hsGMatState::kMiscBumpChans ) |
|
|
{ |
|
|
case hsGMatState::kMiscBumpDu: |
|
|
uvwScale *= fCurrMaterial->GetLayer(fCurrLayerIdx+3)->GetRuntimeColor().r; |
|
|
break; |
|
|
case hsGMatState::kMiscBumpDv: // This currently should never happen |
|
|
uvwScale *= fCurrMaterial->GetLayer(fCurrLayerIdx+1)->GetRuntimeColor().r; |
|
|
break; |
|
|
case hsGMatState::kMiscBumpDw: |
|
|
uvwScale *= fCurrMaterial->GetLayer(fCurrLayerIdx+2)->GetRuntimeColor().r; |
|
|
break; |
|
|
} |
|
|
maxStrength *= 20.f; |
|
|
if( maxStrength > 1.f ) |
|
|
maxStrength = 1.f; |
|
|
liDir *= uvwScale * maxStrength; |
|
|
|
|
|
const hsScalar kUVWOffset = 0.5f; |
|
|
|
|
|
hsScalar kOffsetToRed; |
|
|
hsScalar kOffsetToGreen; |
|
|
hsScalar kOffsetToBlue; |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagBumpUV) || IsDebugFlagSet(plPipeDbg::kFlagBumpW)) |
|
|
{ |
|
|
kOffsetToRed = 0.2f; |
|
|
kOffsetToGreen = 0.6f; |
|
|
kOffsetToBlue = 1.f; |
|
|
} |
|
|
else |
|
|
{ |
|
|
kOffsetToRed = 0.f; |
|
|
kOffsetToGreen = 0.4f; |
|
|
kOffsetToBlue = 0.8f; |
|
|
} |
|
|
|
|
|
const hsMatrix44& l2w = span->fLocalToWorld; |
|
|
|
|
|
fBumpDvMatrix.fMap[0][0] = fBumpDuMatrix.fMap[0][0] = (liDir.fX * l2w.fMap[0][0] + liDir.fY * l2w.fMap[1][0] + liDir.fZ * l2w.fMap[2][0]); |
|
|
fBumpDvMatrix.fMap[0][1] = fBumpDuMatrix.fMap[0][1] = (liDir.fX * l2w.fMap[0][1] + liDir.fY * l2w.fMap[1][1] + liDir.fZ * l2w.fMap[2][1]); |
|
|
fBumpDvMatrix.fMap[0][2] = fBumpDuMatrix.fMap[0][2] = (liDir.fX * l2w.fMap[0][2] + liDir.fY * l2w.fMap[1][2] + liDir.fZ * l2w.fMap[2][2]); |
|
|
|
|
|
fBumpDvMatrix.fMap[0][3] = fBumpDuMatrix.fMap[0][3] = kUVWOffset; |
|
|
|
|
|
fBumpDuMatrix.fMap[1][3] = kOffsetToRed; |
|
|
fBumpDvMatrix.fMap[1][3] = kOffsetToGreen; |
|
|
|
|
|
#ifndef BUMP_COMPARE_MATH |
|
|
hsMatrix44 c2w = fView.GetCameraToWorld(); |
|
|
#endif // BUMP_COMPARE_MATH |
|
|
|
|
|
// The bump textures created so far have very strong blue components, which make anything |
|
|
// bump mapped glow. The ideal fix would be to have the artists adjust the blue component |
|
|
// to a better (lower) value, so there would be a little extra illumination where the bump |
|
|
// is straight out into the normal direction, to complement the lateral illumination. |
|
|
// Attempts so far have been unsuccessful in getting them to get a better understanding |
|
|
// of bump maps, so I've just zeroed out the contribution in the normal direction. |
|
|
plConst(int) kBumpUVOnly(true); |
|
|
if( !kBumpUVOnly ) |
|
|
{ |
|
|
fBumpDwMatrix.fMap[0][0] = (liDir.fX * c2w.fMap[0][0] + liDir.fY * c2w.fMap[1][0] + liDir.fZ * c2w.fMap[2][0]); |
|
|
fBumpDwMatrix.fMap[0][1] = (liDir.fX * c2w.fMap[0][1] + liDir.fY * c2w.fMap[1][1] + liDir.fZ * c2w.fMap[2][1]); |
|
|
fBumpDwMatrix.fMap[0][2] = (liDir.fX * c2w.fMap[0][2] + liDir.fY * c2w.fMap[1][2] + liDir.fZ * c2w.fMap[2][2]); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fBumpDwMatrix.fMap[0][0] = 0; |
|
|
fBumpDwMatrix.fMap[0][1] = 0; |
|
|
fBumpDwMatrix.fMap[0][2] = 0; |
|
|
} |
|
|
|
|
|
fBumpDwMatrix.fMap[0][3] = kUVWOffset; |
|
|
fBumpDwMatrix.fMap[1][3] = kOffsetToBlue; |
|
|
} |
|
|
|
|
|
// IGetBumpMatrix /////////////////////////////////////////////////////// |
|
|
// Return the correct uvw transform for the bump map channel implied |
|
|
// in the miscFlags. The matrices have been previously set in ISetBumpMatrices. |
|
|
const hsMatrix44& plDXPipeline::IGetBumpMatrix(UInt32 miscFlags) const |
|
|
{ |
|
|
switch( miscFlags & hsGMatState::kMiscBumpChans ) |
|
|
{ |
|
|
case hsGMatState::kMiscBumpDu: |
|
|
return fBumpDuMatrix; |
|
|
case hsGMatState::kMiscBumpDv: |
|
|
return fBumpDvMatrix; |
|
|
case hsGMatState::kMiscBumpDw: |
|
|
default: |
|
|
return fBumpDwMatrix; |
|
|
} |
|
|
} |
|
|
|
|
|
// ISkipBumpMap ///////////////////////////////////////////////////////////////////////// |
|
|
// Determine whether to skip bumpmapping on this object/material/layer combination. |
|
|
// We skip if the span isn't illuminated by any lights, or bump mapping is disabled. |
|
|
// If skipping, we advance <layer> past the bump layers. |
|
|
// If there are no more layers after that, we return true (to abort further rendering of currSpan), |
|
|
// else false to continue rendering. |
|
|
hsBool plDXPipeline::ISkipBumpMap(hsGMaterial* newMat, UInt32& layer, const plSpan* currSpan) const |
|
|
{ |
|
|
if( newMat && currSpan ) |
|
|
{ |
|
|
if (newMat->GetLayer(layer) |
|
|
&&(newMat->GetLayer(layer)->GetMiscFlags() & hsGMatState::kMiscBumpChans) |
|
|
&&(!currSpan->GetNumLights(false) || IsDebugFlagSet(plPipeDbg::kFlagNoBump)) ) |
|
|
{ |
|
|
layer += 4; |
|
|
if( layer >= newMat->GetNumLayers() ) |
|
|
return true; |
|
|
} |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
//// IHandleStageTransform //////////////////////////////////////////////////// |
|
|
// Compute and set the UVW transform to D3D. |
|
|
// This only gets interesting if the transform is dependent on on the current camera transform, |
|
|
// as is the case with Reflection, Projection, or bump mapping. |
|
|
void plDXPipeline::IHandleStageTransform( int stage, plLayerInterface *layer ) |
|
|
{ |
|
|
if( 1 |
|
|
|| !(layer->GetTransform().fFlags & hsMatrix44::kIsIdent) |
|
|
|| (fLayerState[stage].fMiscFlags & (hsGMatState::kMiscUseReflectionXform|hsGMatState::kMiscUseRefractionXform|hsGMatState::kMiscProjection|hsGMatState::kMiscBumpChans)) ) |
|
|
{ |
|
|
D3DXMATRIX tXfm; |
|
|
|
|
|
if( fLayerState[stage].fMiscFlags & (hsGMatState::kMiscUseReflectionXform | hsGMatState::kMiscUseRefractionXform) ) |
|
|
{ |
|
|
// Reflection - this is just the camera to world, with translation removed, |
|
|
// and rotated to match cube map conventions. |
|
|
hsMatrix44 c2env = fView.GetCameraToWorld(); |
|
|
c2env = fView.GetCameraToWorld(); |
|
|
|
|
|
c2env.fMap[0][3] |
|
|
= c2env.fMap[1][3] |
|
|
= c2env.fMap[2][3] |
|
|
= 0.f; |
|
|
|
|
|
|
|
|
if( fLayerState[stage].fMiscFlags & hsGMatState::kMiscUseReflectionXform ) |
|
|
{ |
|
|
|
|
|
// This is just a rotation about X of Pi/2 (y = z, z = -y), |
|
|
// followed by flipping Z to reflect back towards us (z = -z). |
|
|
hsScalar t = c2env.fMap[1][0]; |
|
|
c2env.fMap[1][0] = c2env.fMap[2][0]; |
|
|
c2env.fMap[2][0] = t; |
|
|
|
|
|
t = c2env.fMap[1][1]; |
|
|
c2env.fMap[1][1] = c2env.fMap[2][1]; |
|
|
c2env.fMap[2][1] = t; |
|
|
|
|
|
t = c2env.fMap[1][2]; |
|
|
c2env.fMap[1][2] = c2env.fMap[2][2]; |
|
|
c2env.fMap[2][2] = t; |
|
|
} |
|
|
else // must be kMiscUseRefractionXform |
|
|
{ |
|
|
|
|
|
// Okay, I know this refraction isn't any where near |
|
|
// right, so don't sit down and try to figure out the |
|
|
// math and hook it to the refractive index. |
|
|
// It's just a hack that will fool anyone that isn't |
|
|
// really paying attention. |
|
|
|
|
|
// This is just a rotation about X of Pi/2 (y = z, z = -y), |
|
|
// followed by NOT flipping Z to reflect back towards us (z = -z). |
|
|
// In other words, same as reflection, but then c2env = c2env * scaleMatNegateZ. |
|
|
hsScalar t = c2env.fMap[1][0]; |
|
|
c2env.fMap[1][0] = c2env.fMap[2][0]; |
|
|
c2env.fMap[2][0] = t; |
|
|
|
|
|
t = c2env.fMap[1][1]; |
|
|
c2env.fMap[1][1] = c2env.fMap[2][1]; |
|
|
c2env.fMap[2][1] = t; |
|
|
|
|
|
t = c2env.fMap[1][2]; |
|
|
c2env.fMap[1][2] = c2env.fMap[2][2]; |
|
|
c2env.fMap[2][2] = t; |
|
|
|
|
|
c2env.fMap[0][2] = -c2env.fMap[0][2]; |
|
|
c2env.fMap[1][2] = -c2env.fMap[1][2]; |
|
|
c2env.fMap[2][2] = -c2env.fMap[2][2]; |
|
|
|
|
|
#if 0 |
|
|
const hsScalar kFishEyeScale = 0.5f; |
|
|
// You can adjust the fish-eye-ness of this by scaling |
|
|
// X and Y as well. Eventually, you wind up with the same |
|
|
// as c2env * scaleMatXYAndNegateZ, but this is shorter. |
|
|
// kFishEyeScale gets pretty fish-eye at about 0.5, and |
|
|
// like you're looking through the wrong end of a telescope |
|
|
// at about 1.5. |
|
|
// Ideally kFishEyeScale would be a parameter of the layer. |
|
|
c2env.fMap[0][0] *= kFishEyeScale; |
|
|
c2env.fMap[1][0] *= kFishEyeScale; |
|
|
c2env.fMap[2][0] *= kFishEyeScale; |
|
|
|
|
|
c2env.fMap[0][1] *= kFishEyeScale; |
|
|
c2env.fMap[1][1] *= kFishEyeScale; |
|
|
c2env.fMap[2][1] *= kFishEyeScale; |
|
|
#endif |
|
|
} |
|
|
|
|
|
IMatrix44ToD3DMatrix( tXfm, c2env ); |
|
|
} |
|
|
// cam2Screen will also have the kMiscPerspProjection flag set, so this needs |
|
|
// to go before the regular kMiscProjection check. |
|
|
else if (fLayerState[stage].fMiscFlags & hsGMatState::kMiscCam2Screen ) |
|
|
{ |
|
|
// Still needs a bit of cleaning... |
|
|
static hsVector3 camScale(0.5f, -0.5f, 1.f); |
|
|
static hsVector3 camTrans(0.5f, 0.5f, 0.f); |
|
|
hsMatrix44 p2s; |
|
|
p2s.MakeScaleMat(&camScale); |
|
|
p2s.fMap[0][3] += camTrans.fX; |
|
|
p2s.fMap[1][3] += camTrans.fY; |
|
|
|
|
|
// The scale and trans move us from NDC to Screen space. We need to swap |
|
|
// the Z and W coordinates so that the texture projection will divide by W |
|
|
// and give us projected 2D coordinates. |
|
|
hsScalar temp = p2s.fMap[2][2]; |
|
|
p2s.fMap[2][2] = p2s.fMap[3][2]; |
|
|
p2s.fMap[3][2] = temp; |
|
|
|
|
|
temp = p2s.fMap[2][3]; |
|
|
p2s.fMap[2][3] = p2s.fMap[3][3]; |
|
|
p2s.fMap[3][3] = temp; |
|
|
|
|
|
IMatrix44ToD3DMatrix(tXfm, p2s * IGetCameraToNDC()); |
|
|
} |
|
|
else if( fLayerState[stage].fMiscFlags & hsGMatState::kMiscProjection ) |
|
|
{ |
|
|
// For projection, the worldToLight transform is in the layer transform, |
|
|
// so we append the cameraToWorld, getting cameraToLight |
|
|
hsMatrix44 c2w = fView.GetCameraToWorld(); |
|
|
if( !(layer->GetUVWSrc() & plLayerInterface::kUVWPosition) ) |
|
|
{ |
|
|
c2w.fMap[0][3] = 0; |
|
|
c2w.fMap[1][3] = 0; |
|
|
c2w.fMap[2][3] = 0; |
|
|
} |
|
|
|
|
|
// We've already stuffed the worldToLight transform into the layer. |
|
|
hsMatrix44 c2l = layer->GetTransform() * c2w; |
|
|
|
|
|
IMatrix44ToD3DMatrix(tXfm, c2l); |
|
|
} |
|
|
else if( fLayerState[stage].fMiscFlags & hsGMatState::kMiscBumpChans ) |
|
|
{ |
|
|
// Bump matrices are already set, just get the right one and stuff it in. |
|
|
hsMatrix44 m = IGetBumpMatrix(fLayerState[stage].fMiscFlags); |
|
|
|
|
|
IMatrix44ToD3DMatrix(tXfm, m); |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Just put take the layer transform and stuff it in. |
|
|
IMatrix44ToD3DMatrix( tXfm, layer->GetTransform() ); |
|
|
} |
|
|
|
|
|
fD3DDevice->SetTransform( sTextureStages[ stage ], &tXfm ); |
|
|
fLayerTransform[ stage ] = true; |
|
|
} |
|
|
else if( fLayerTransform[ stage ] ) |
|
|
{ |
|
|
// We'd like to just turn it off, but the Voodoo board freaks if the |
|
|
// texture coordinates are 3-tuple for no apparent reason. |
|
|
fD3DDevice->SetTransform( sTextureStages[ stage ], &d3dIdentityMatrix ); |
|
|
fLayerTransform[ stage ] = false; |
|
|
} |
|
|
|
|
|
// If there's an lod bias associated with the layer, set it here. |
|
|
// There usually isn't. |
|
|
float newBias = fLayerState[stage].fZFlags & hsGMatState::kZLODBias ? layer->GetLODBias() : fTweaks.fDefaultLODBias; |
|
|
if( newBias != fLayerLODBias[ stage ] ) |
|
|
{ |
|
|
fLayerLODBias[ stage ] = newBias; |
|
|
fD3DDevice->SetSamplerState( stage, D3DSAMP_MIPMAPLODBIAS, *(DWORD*)(&fLayerLODBias[ stage ]) ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IUseTextureRef /////////////////////////////////////////////////////////// |
|
|
// Set the texturing flags and texture. |
|
|
void plDXPipeline::IUseTextureRef( int stage, hsGDeviceRef *dRef, plLayerInterface* layer ) |
|
|
{ |
|
|
plDXTextureRef *ref = (plDXTextureRef *)dRef; |
|
|
UInt32 xformFlags; |
|
|
|
|
|
UInt32 uvwSrc = layer->GetUVWSrc(); |
|
|
|
|
|
// Keep track of how much managed memory has been "seen" since the last |
|
|
// evict, for that NVidia bug. Look for OSVERSIONINFO for more notes. |
|
|
if( ref->fUseTime <= fEvictTime ) |
|
|
fManagedSeen += ref->fDataSize; |
|
|
|
|
|
// Also used for the same thing. |
|
|
if( ref->fUseTime ^ fTextUseTime ) |
|
|
{ |
|
|
plProfile_NewMem(CurrTex, ref->fDataSize); |
|
|
plProfile_Inc(NumTex); |
|
|
ref->fUseTime = fTextUseTime; |
|
|
|
|
|
fTexUsed += ref->fDataSize; |
|
|
} |
|
|
|
|
|
// DX pixel shaders require the TEXCOORDINDEX to be equal to the stage, |
|
|
// even though its ignored. |
|
|
if( layer->GetPixelShader() && (stage != uvwSrc) ) |
|
|
uvwSrc = stage; |
|
|
|
|
|
// Update our UVW source |
|
|
if( fLayerUVWSrcs[ stage ] != uvwSrc ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_TEXCOORDINDEX, uvwSrc ); |
|
|
fLayerUVWSrcs[ stage ] = uvwSrc; |
|
|
} |
|
|
|
|
|
if (!layer->GetVertexShader() && !layer->GetPixelShader()) |
|
|
{ |
|
|
/// Set the transform flags |
|
|
/// Note: the perspective projection flag must be taken from the layer, since it's layer-specific. |
|
|
/// Storing it on the texture ref is bad, because the texture ref can be shared among layers whose |
|
|
/// projection flags might not match. This should probably be cleaned up, but for now this fixes the |
|
|
/// problem. |
|
|
if( ref->GetFlags() & plDXTextureRef::kCubicMap ) |
|
|
xformFlags = D3DTTFF_COUNT3; |
|
|
else if( layer->GetMiscFlags() & hsGMatState::kMiscPerspProjection ) |
|
|
xformFlags = D3DTTFF_COUNT3 | D3DTTFF_PROJECTED; |
|
|
else |
|
|
xformFlags = D3DTTFF_COUNT2; |
|
|
|
|
|
if( xformFlags != fLayerXformFlags[ stage ] ) |
|
|
{ |
|
|
fLayerXformFlags[ stage ] = xformFlags; |
|
|
fD3DDevice->SetTextureStageState( stage, D3DTSS_TEXTURETRANSFORMFLAGS, xformFlags ); |
|
|
} |
|
|
} |
|
|
|
|
|
// Update our current ref |
|
|
if( !ref->fD3DTexture ) |
|
|
{ |
|
|
if( ref->fData ) |
|
|
IReloadTexture( ref ); |
|
|
} |
|
|
else if( dRef == fLayerRef[ stage ] ) |
|
|
{ |
|
|
return; |
|
|
} |
|
|
hsRefCnt_SafeAssign( fLayerRef[ stage ], dRef ); |
|
|
|
|
|
/// Actually make it active! |
|
|
fD3DDevice->SetTexture( stage, ref->fD3DTexture ); |
|
|
} |
|
|
|
|
|
//// IStageStop /////////////////////////////////////////////////////////////// |
|
|
// Tell the hardware we won't be using any more stages. |
|
|
// This is more complicated than it sounds. Cases: |
|
|
// a) single texture stage, we're done (because we've already set |
|
|
// texture times diffuse), so just disable stage 1. |
|
|
// b) we have 2 stages active. |
|
|
// b.0) we're skipping texture color on one of those 2 stages. In that |
|
|
// case, we've already modulated in our diffuse, so just |
|
|
// disable stage 2. |
|
|
// b.1) we're using texture color from both stages 0 and 1, and still need |
|
|
// to modulate in diffuse. So set stage 2 to modulate in diffuse, |
|
|
// and disable stage 3. |
|
|
// c) we have 3 or more stages active. Append a modulation by diffuse |
|
|
// Note that this only applies to color, because diffuse alpha is always modulated |
|
|
// in from the start. |
|
|
void plDXPipeline::IStageStop( UInt32 stage ) |
|
|
{ |
|
|
int disableStage = stage; |
|
|
|
|
|
// Note: even if we don't have a texture, we handle it similar to if we had one, |
|
|
// so the only special case we need here is if we only had one stage to set up -mcn |
|
|
if( ( stage <= 1 ) ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[ stage ].fBlendFlags = UInt32(-1); |
|
|
disableStage = stage; |
|
|
} |
|
|
else if( stage == 2 ) |
|
|
{ |
|
|
// The fMaxLayersAtOnce == 2 check is for the DX9.0c 2 texture limitation. |
|
|
// See ILayersAtOnce() |
|
|
if ((fLayerState[0].fBlendFlags & hsGMatState::kBlendNoTexColor) |
|
|
|| (fLayerState[1].fBlendFlags & hsGMatState::kBlendNoTexColor) |
|
|
|| fSettings.fMaxLayersAtOnce == 2) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
disableStage = 2; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_DIFFUSE); |
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(3, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
disableStage = 3; |
|
|
} |
|
|
|
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
|
|
|
fLayerState[2].fBlendFlags = UInt32(-1); |
|
|
fLayerState[3].fBlendFlags = UInt32(-1); |
|
|
} |
|
|
else |
|
|
{ |
|
|
// This is directly contrary to the DX documentation, but in line with |
|
|
// the code generated by MFCTex (which works). The docs say: |
|
|
// "Alpha operations cannot be disabled when color operations are enabled. |
|
|
// Setting the alpha operation to D3DTOP_DISABLE when color blending |
|
|
// is enabled causes undefined behavior." |
|
|
// But not disabling the earliest possible alpha stage causes the driver |
|
|
// to choke. |
|
|
|
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLORARG1, D3DTA_DIFFUSE); |
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[stage].fBlendFlags = UInt32(-1); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(stage+1, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fLayerState[stage+1].fBlendFlags = UInt32(-1); |
|
|
|
|
|
disableStage = stage+1; |
|
|
} |
|
|
|
|
|
fLastEndingStage = stage; |
|
|
|
|
|
if( fSettings.fIsIntel ) |
|
|
{ |
|
|
int maxUVW = 0; |
|
|
int k; |
|
|
for( k = 0; k < fCurrNumLayers; k++ ) |
|
|
{ |
|
|
if( (fCurrMaterial->GetLayer(k + fCurrLayerIdx)->GetUVWSrc() & 0xf) > maxUVW ) |
|
|
maxUVW = fCurrMaterial->GetLayer(k + fCurrLayerIdx)->GetUVWSrc() & 0xf; |
|
|
} |
|
|
for( k = disableStage; k <= maxUVW; k++ ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(k, D3DTSS_COLOROP, D3DTOP_SELECTARG2); |
|
|
fD3DDevice->SetTextureStageState(k, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
} |
|
|
fD3DDevice->SetTextureStageState(k, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
} |
|
|
} |
|
|
|
|
|
// IInvalidateState ///////////////////////////////////////////////////////////// |
|
|
// Documentation is unclear on what state persists or becomes invalid on switching |
|
|
// a render target or finishing a frame. I put into this function things that show |
|
|
// up as suspect, whether they "ought" to be here or not. |
|
|
void plDXPipeline::IInvalidateState() |
|
|
{ |
|
|
fLastEndingStage = 0; |
|
|
fTexturing = false; |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
hsRefCnt_SafeUnRef( fLayerRef[ i ] ); |
|
|
fLayerRef[ i ] = nil; |
|
|
fD3DDevice->SetTexture( i, nil ); |
|
|
} |
|
|
|
|
|
fLayerState[ 0 ].fZFlags = 0; |
|
|
fD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); |
|
|
|
|
|
// This is a workaround for the latest ATI drivers (6.14.10.6422). |
|
|
// They seem to be caching something on lights (possibly only specular |
|
|
// lights, but I haven't been able to prove it) the first time they |
|
|
// are used in a render, and then not letting go when the camera |
|
|
// is moved for another render in the same frame (same BeginScene/EndScene pair). |
|
|
// The effect is very incorrect lighting. Moreover, if the multiple renders |
|
|
// per frame are infrequent (e.g. refreshing an environment map every few |
|
|
// seconds), you'll get flashes after the double render frames. |
|
|
// Workaround is to Disable all lights at render target switch, although |
|
|
// a more correct workaround might be to disable all lights at camera move. |
|
|
// All of this is strictly conjecture, so I'm going with what works. |
|
|
// Note also that I'm only disabling lights that are currently enabled |
|
|
// at the time of the render target switch. Since this is dealing with |
|
|
// a driver bug, it might be safer to disable them all, but timings |
|
|
// show that looping through all the lights in a scene like Teledahn exterior, |
|
|
// with hundreds of active lights, incurs a measurable expense (some milliseconds), |
|
|
// whereas disabling only the active lights fixes the known problem but costs |
|
|
// zero. |
|
|
plProfile_BeginTiming(ClearLights); |
|
|
|
|
|
hsBitIterator iterOff(fLights.fEnabledFlags); |
|
|
for( iterOff.Begin(); !iterOff.End(); iterOff.Advance() ) |
|
|
fD3DDevice->LightEnable(iterOff.Current(), false); |
|
|
fLights.fEnabledFlags.Clear(); |
|
|
fLights.fHoldFlags.Clear(); |
|
|
|
|
|
plProfile_EndTiming(ClearLights); |
|
|
|
|
|
// This is very annoying. Set fTexturing to false doesn't work if the next layer |
|
|
// we draw doesn't have a texture. So we have to set this flag instead to force |
|
|
// a state update. I have an idea about how to do all of this a lot better, but |
|
|
// it's not time to do it...not yet at least.... --mcn |
|
|
fSettings.fVeryAnnoyingTextureInvalidFlag = true; |
|
|
} |
|
|
|
|
|
//// ILayersAtOnce //////////////////////////////////////////////////////////// |
|
|
// Compute how many of the upcoming layers we can render in a single pass on the |
|
|
// current hardware. |
|
|
UInt32 plDXPipeline::ILayersAtOnce( hsGMaterial *mat, UInt32 which ) |
|
|
{ |
|
|
fCurrNumLayers = 1; |
|
|
|
|
|
if( fView.fRenderState & plPipeline::kRenderBaseLayerOnly ) |
|
|
return fCurrNumLayers; |
|
|
|
|
|
plLayerInterface *lay = mat->GetLayer( which ); |
|
|
|
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoMultitexture)) |
|
|
return fCurrNumLayers; |
|
|
|
|
|
if ((IsDebugFlagSet(plPipeDbg::kFlagBumpUV) || IsDebugFlagSet(plPipeDbg::kFlagBumpW)) && (lay->GetMiscFlags() & hsGMatState::kMiscBumpChans) ) |
|
|
return fCurrNumLayers = 2; |
|
|
|
|
|
if( (lay->GetBlendFlags() & hsGMatState::kBlendNoColor) |
|
|
||(lay->GetMiscFlags() & hsGMatState::kMiscTroubledLoner) |
|
|
) |
|
|
return fCurrNumLayers; |
|
|
|
|
|
// New DX9.0c limitation for cards that can only do 2 textures per pass. |
|
|
// We used to be able to set stage 0 and 1 to textures, and set stage 2 to the |
|
|
// diffuse color. With DX9.0c we just get two texture stages. Period. |
|
|
// Either we give up a texture or the diffuse color. |
|
|
if (fSettings.fMaxLayersAtOnce == 2) |
|
|
{ |
|
|
if ((mat->GetNumLayers() > which + 1) |
|
|
&& !(mat->GetLayer(which + 1)->GetBlendFlags() & hsGMatState::kBlendNoTexColor)) |
|
|
{ |
|
|
// If we're just using the texture for alpha, we can multiply |
|
|
// the diffuse color in stage 1. Otherwise, save it for the next pass. |
|
|
return fCurrNumLayers; |
|
|
} |
|
|
} |
|
|
|
|
|
int i; |
|
|
int maxLayersAtOnce = fSettings.fMaxLayersAtOnce; |
|
|
|
|
|
// Now Reserve space for piggy backs, and see if there are |
|
|
// are any more layers we can pick up. |
|
|
// |
|
|
maxLayersAtOnce = fSettings.fMaxLayersAtOnce - fActivePiggyBacks; |
|
|
if( which + maxLayersAtOnce > mat->GetNumLayers() ) |
|
|
maxLayersAtOnce = mat->GetNumLayers() - which; |
|
|
|
|
|
for( i = fCurrNumLayers; i < maxLayersAtOnce; i++ ) |
|
|
{ |
|
|
plLayerInterface *lay = mat->GetLayer(which + i); |
|
|
if( (lay->GetUVWSrc() & 0xf) > fSettings.fMaxUVWSrc ) |
|
|
break; |
|
|
if( (lay->GetMiscFlags() & hsGMatState::kMiscBindNext) |
|
|
&&(i+1 >= maxLayersAtOnce) ) |
|
|
break; |
|
|
if( lay->GetMiscFlags() & hsGMatState::kMiscRestartPassHere ) |
|
|
break; |
|
|
if( !(mat->GetLayer(which+i-1)->GetMiscFlags() & hsGMatState::kMiscBindNext) |
|
|
&& !ICanEatLayer(lay) ) |
|
|
break; |
|
|
fCurrNumLayers++; |
|
|
} |
|
|
return fCurrNumLayers; |
|
|
} |
|
|
|
|
|
//// ICanEatLayer ///////////////////////////////////////////////////////////// |
|
|
// Determine if this layer can be an upper layer, or if it needs |
|
|
// to be the base on another pass. |
|
|
hsBool plDXPipeline::ICanEatLayer( plLayerInterface* lay ) |
|
|
{ |
|
|
if( !lay->GetTexture() ) |
|
|
return false; |
|
|
|
|
|
|
|
|
if( (lay->GetBlendFlags() & hsGMatState::kBlendNoColor) |
|
|
||(lay->GetBlendFlags() & hsGMatState::kBlendAddColorTimesAlpha) // has to be base layer |
|
|
||(lay->GetMiscFlags() & hsGMatState::kMiscTroubledLoner) ) |
|
|
return false; |
|
|
|
|
|
if( (lay->GetBlendFlags() & hsGMatState::kBlendAlpha ) |
|
|
&&(lay->GetAmbientColor().a < hsScalar1) ) |
|
|
return false; |
|
|
|
|
|
if( !(lay->GetZFlags() & hsGMatState::kZNoZWrite) ) |
|
|
return false; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Textures ///////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// IReloadTexture /////////////////////////////////////////////////////////// |
|
|
// Fills in D3D texture resource, creating it if necessary. |
|
|
void plDXPipeline::IReloadTexture( plDXTextureRef *ref ) |
|
|
{ |
|
|
if( ref->GetFlags() & plDXTextureRef::kCubicMap ) |
|
|
{ |
|
|
if( ref->fD3DTexture == nil ) |
|
|
ref->fD3DTexture = IMakeD3DCubeTexture( ref, ref->fFormatType ); |
|
|
|
|
|
if( ref->fD3DTexture != nil ) |
|
|
IFillD3DCubeTexture( (plDXCubeTextureRef *)ref ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( ref->fD3DTexture == nil ) |
|
|
ref->fD3DTexture = IMakeD3DTexture( ref, ref->fFormatType ); |
|
|
|
|
|
if( ref->fD3DTexture != nil ) |
|
|
IFillD3DTexture( ref ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IMakeD3DTexture ////////////////////////////////////////////////////////// |
|
|
// Makes a DX Texture object based on the ref given. |
|
|
|
|
|
IDirect3DTexture9 *plDXPipeline::IMakeD3DTexture( plDXTextureRef *ref, D3DFORMAT formatType ) |
|
|
{ |
|
|
D3DPOOL poolType = D3DPOOL_MANAGED; |
|
|
IDirect3DTexture9 *texPtr; |
|
|
fManagedAlloced = true; |
|
|
if( FAILED( fSettings.fDXError = fD3DDevice->CreateTexture( ref->fMaxWidth, ref->fMaxHeight, |
|
|
ref->fMMLvs, |
|
|
0, |
|
|
formatType, |
|
|
poolType, |
|
|
&texPtr, NULL ) ) ) |
|
|
{ |
|
|
IGetD3DError(); |
|
|
plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "Unable to create texture (%s) Owner: %s " |
|
|
"Size: %d x %d NumLvls: %d Flags: %x", |
|
|
fSettings.fErrorStr, ref->fOwner ? ref->fOwner->GetKey() ? ref->fOwner->GetKey()->GetUoid().GetObjectName() : "" : "", |
|
|
ref->fMaxWidth, ref->fMaxHeight, ref->fMMLvs, ref->GetFlags() ); |
|
|
return nil; |
|
|
} |
|
|
PROFILE_POOL_MEM(poolType, ref->fDataSize, true, (ref->fOwner ? ref->fOwner->GetKey() ? ref->fOwner->GetKey()->GetUoid().GetObjectName() : "(UnknownTexture)" : "(UnknownTexture)")); |
|
|
fTexManaged += ref->fDataSize; |
|
|
|
|
|
return texPtr; |
|
|
} |
|
|
|
|
|
//// IFillD3DTexture ////////////////////////////////////////////////////////// |
|
|
// Copies the data from the ref into the D3D texture, filling in all |
|
|
// mip levels. |
|
|
void plDXPipeline::IFillD3DTexture( plDXTextureRef *ref ) |
|
|
{ |
|
|
int i; |
|
|
UInt8 *pTexDat = (UInt8 *)ref->fData; |
|
|
|
|
|
|
|
|
if( pTexDat == nil ) |
|
|
{ |
|
|
plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "Unable to fill texture ref (data is nil) Owner: %s", |
|
|
ref->fOwner ? ref->fOwner->GetKey() ? ref->fOwner->GetKey()->GetUoid().GetObjectName() : "" : "" ); |
|
|
return; |
|
|
} |
|
|
|
|
|
IDirect3DTexture9 *lpDst = (IDirect3DTexture9 *)ref->fD3DTexture; |
|
|
|
|
|
for( i = 0; i < ref->fMMLvs; i++ ) |
|
|
{ |
|
|
D3DLOCKED_RECT lockInfo; |
|
|
|
|
|
if( FAILED( fSettings.fDXError = lpDst->LockRect( i, &lockInfo, nil, 0 ) ) ) |
|
|
{ |
|
|
IGetD3DError(); |
|
|
plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "Unable to lock texture level %d for filling (%s) Owner: %s " |
|
|
"Size: %d x %d NumLvls: %d Flags: %x", |
|
|
i, fSettings.fErrorStr, ref->fOwner ? ref->fOwner->GetKey() ? ref->fOwner->GetKey()->GetUoid().GetObjectName() : "" : "", |
|
|
ref->fMaxWidth, ref->fMaxHeight, ref->fMMLvs, ref->GetFlags() ); |
|
|
return; |
|
|
} |
|
|
|
|
|
memcpy( (char *)lockInfo.pBits, pTexDat, ref->fLevelSizes[ i ] ); |
|
|
pTexDat += ref->fLevelSizes[ i ]; |
|
|
lpDst->UnlockRect( i ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// IMakeD3DCubeTexture ////////////////////////////////////////////////////// |
|
|
// Makes a DX Cubic Texture object based on the ref given. |
|
|
|
|
|
IDirect3DCubeTexture9 *plDXPipeline::IMakeD3DCubeTexture( plDXTextureRef *ref, D3DFORMAT formatType ) |
|
|
{ |
|
|
D3DPOOL poolType = D3DPOOL_MANAGED; |
|
|
IDirect3DCubeTexture9 *texPtr = nil; |
|
|
fManagedAlloced = true; |
|
|
WEAK_ERROR_CHECK(fD3DDevice->CreateCubeTexture( ref->fMaxWidth, ref->fMMLvs, 0, formatType, poolType, &texPtr, NULL)); |
|
|
PROFILE_POOL_MEM(poolType, ref->fDataSize, true, (ref->fOwner ? ref->fOwner->GetKey() ? ref->fOwner->GetKey()->GetUoid().GetObjectName() : "(UnknownTexture)" : "(UnknownTexture)")); |
|
|
fTexManaged += ref->fDataSize; |
|
|
return texPtr; |
|
|
} |
|
|
|
|
|
//// IFillD3DCubeTexture ////////////////////////////////////////////////////// |
|
|
// Fill in all faces of the D3D cube map from the input reference. |
|
|
void plDXPipeline::IFillD3DCubeTexture( plDXCubeTextureRef *ref ) |
|
|
{ |
|
|
int i, f; |
|
|
D3DCUBEMAP_FACES faces[ 6 ] = { D3DCUBEMAP_FACE_NEGATIVE_X, // Left |
|
|
D3DCUBEMAP_FACE_POSITIVE_X, // Right |
|
|
D3DCUBEMAP_FACE_POSITIVE_Z, // Front |
|
|
D3DCUBEMAP_FACE_NEGATIVE_Z, // Back |
|
|
D3DCUBEMAP_FACE_POSITIVE_Y, // Top |
|
|
D3DCUBEMAP_FACE_NEGATIVE_Y }; // Bottom |
|
|
|
|
|
for( f = 0; f < 6; f++ ) |
|
|
{ |
|
|
UInt8 *pTexDat = ( f == 0 ) ? (UInt8 *)ref->fData : (UInt8 *)ref->fFaceData[ f - 1 ]; |
|
|
IDirect3DCubeTexture9 *lpDst = (IDirect3DCubeTexture9 *)ref->fD3DTexture; |
|
|
|
|
|
for( i = 0; i < ref->fMMLvs; i++ ) |
|
|
{ |
|
|
D3DLOCKED_RECT lockInfo; |
|
|
|
|
|
lpDst->LockRect( faces[ f ], i, &lockInfo, nil, 0 ); |
|
|
memcpy( (char *)lockInfo.pBits, pTexDat, ref->fLevelSizes[ i ] ); |
|
|
pTexDat += ref->fLevelSizes[ i ]; |
|
|
lpDst->UnlockRect( faces[ f ], i ); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
//// MakeTextureRef /////////////////////////////////////////////////////////// |
|
|
// Creates a hsGDeviceRef for a texture. |
|
|
// May have to decompress the texture if the hardware doesn't support compressed textures (unlikely). |
|
|
hsGDeviceRef *plDXPipeline::MakeTextureRef( plLayerInterface* layer, plMipmap *b ) |
|
|
{ |
|
|
plMipmap *original = b, *colorized = nil; |
|
|
|
|
|
// If the hardware doesn't support Luminance maps, we'll just treat as ARGB. |
|
|
if( !( fSettings.fD3DCaps & kCapsLuminanceTextures ) ) |
|
|
b->SetFlags( b->GetFlags() & ~plMipmap::kIntensityMap ); |
|
|
|
|
|
/// Colorize if we're supposed to (8.21.2000 mcn) |
|
|
// Debugging only. |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagColorizeMipmaps)) |
|
|
{ |
|
|
b = original->Clone(); |
|
|
if( b != nil ) |
|
|
b->Colorize(); |
|
|
else |
|
|
b = original; |
|
|
} |
|
|
|
|
|
if( !( fSettings.fD3DCaps & kCapsCompressTextures ) && b->IsCompressed() ) |
|
|
b = hsCodecManager::Instance().CreateUncompressedMipmap( b, hsCodecManager::k16BitDepth ); |
|
|
|
|
|
/// Set up some stuff |
|
|
UInt32 mmlvs = 1; |
|
|
D3DFORMAT formatType = D3DFMT_UNKNOWN; // D3D Format |
|
|
UInt32 formatSize = 0; |
|
|
UInt32 totalSize = 0; |
|
|
UInt32* levelSizes = nil; |
|
|
UInt32 numPix = 0; |
|
|
UInt32 externData = false; |
|
|
void *tData; |
|
|
hsBool noMip = !(fSettings.fD3DCaps & kCapsMipmap); |
|
|
|
|
|
|
|
|
/// Convert the bitmap over |
|
|
// Select a target format |
|
|
IGetD3DTextureFormat( b, formatType, formatSize ); |
|
|
|
|
|
// Process the texture data into a format that can be directly copied to the D3D texture. |
|
|
// externData returned as true means that tData just points directly into the mipmap's fImage, |
|
|
// so don't delete it when deleting the texture device ref. externData false means this is |
|
|
// a reformatted copy, so the ref owns it. |
|
|
externData = IProcessMipmapLevels( b, mmlvs, levelSizes, totalSize, numPix, tData, noMip ); |
|
|
|
|
|
// If the texture has a device ref, just re-purpose it, else make one and initialize it. |
|
|
plDXTextureRef *ref = (plDXTextureRef *)b->GetDeviceRef(); |
|
|
if( !ref ) |
|
|
{ |
|
|
ref = TRACKED_NEW plDXTextureRef( formatType, |
|
|
mmlvs, b->GetWidth(), b->GetHeight(), |
|
|
numPix, totalSize, totalSize, levelSizes, |
|
|
tData, externData ); |
|
|
ref->fOwner = original; |
|
|
ref->Link( &fTextureRefList ); |
|
|
original->SetDeviceRef( ref ); |
|
|
// Note: this is because SetDeviceRef() will ref it, and at this point, |
|
|
// only the bitmap should own the ref, not us. We ref/unref it on Use() |
|
|
hsRefCnt_SafeUnRef( ref ); |
|
|
} |
|
|
else |
|
|
ref->Set( formatType, mmlvs, b->GetWidth(), b->GetHeight(), |
|
|
numPix, totalSize, totalSize, levelSizes, tData, externData ); |
|
|
|
|
|
// Keep the refs in a linked list for easy disposal. |
|
|
if( !ref->IsLinked() ) |
|
|
{ |
|
|
// Re-linking |
|
|
ref->Link( &fTextureRefList ); |
|
|
} |
|
|
|
|
|
/// Copy the data into the ref |
|
|
IReloadTexture( ref ); |
|
|
|
|
|
ref->fData = nil; |
|
|
ref->SetDirty( false ); |
|
|
|
|
|
// Set any implied flags. |
|
|
if (layer) |
|
|
{ |
|
|
if( layer->GetMiscFlags() & hsGMatState::kMiscPerspProjection ) |
|
|
ref->SetFlags(ref->GetFlags() | plDXTextureRef::kPerspProjection); |
|
|
else if( layer->GetMiscFlags() & hsGMatState::kMiscOrthoProjection ) |
|
|
ref->SetFlags(ref->GetFlags() | plDXTextureRef::kOrthoProjection); |
|
|
|
|
|
if( layer->GetMiscFlags() & hsGMatState::kMiscBumpDw ) |
|
|
ref->SetFlags(ref->GetFlags() | plDXTextureRef::kUVWNormal); |
|
|
} |
|
|
|
|
|
if( b != original ) |
|
|
delete b; // Delete if we created a new (temporary) one |
|
|
|
|
|
// Turn this on to delete the plasma system memory copy once we have a D3D managed version. |
|
|
// Currently disabled, because there are still mipmaps that are read from after their managed |
|
|
// versions are created, but aren't flagged DontThrowAwayImage or kUserOwnesBitmap. |
|
|
if( !( original->GetFlags() & ( plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage ) ) |
|
|
&& !GetProperty( kPropDontDeleteTextures ) ) |
|
|
{ |
|
|
#ifdef MF_TOSSER |
|
|
original->Reset(); |
|
|
#endif // MF_TOSSER |
|
|
} |
|
|
|
|
|
return ref; |
|
|
} |
|
|
|
|
|
//// IMakeCubicTextureRef ///////////////////////////////////////////////////// |
|
|
// Same as MakeTextureRef, except done for the six faces of a cube map. |
|
|
hsGDeviceRef *plDXPipeline::IMakeCubicTextureRef( plLayerInterface* layer, plCubicEnvironmap *cubic ) |
|
|
{ |
|
|
plDXCubeTextureRef *ref; |
|
|
plMipmap *faces[ 6 ]; |
|
|
int i; |
|
|
D3DFORMAT formatType = D3DFMT_UNKNOWN; |
|
|
UInt32 formatSize = 0; |
|
|
UInt32 numLevels = 1; |
|
|
UInt32 totalSize = 0; |
|
|
UInt32 *levelSizes = nil; |
|
|
UInt32 numPixels = 0; |
|
|
UInt32 externData; |
|
|
void *textureData[ 6 ]; |
|
|
|
|
|
if( cubic == nil || !( fSettings.fD3DCaps & kCapsCubicTextures ) ) |
|
|
return nil; |
|
|
|
|
|
|
|
|
hsBool noMip = !(fSettings.fD3DCaps & kCapsMipmap) || !(fSettings.fD3DCaps & kCapsCubicMipmap); |
|
|
|
|
|
/// Get the mips |
|
|
if( !( fSettings.fD3DCaps & kCapsCompressTextures ) ) |
|
|
{ |
|
|
for( i = 0; i < 6; i++ ) |
|
|
{ |
|
|
faces[ i ] = cubic->GetFace( i ); |
|
|
if( faces[ i ]->IsCompressed() ) |
|
|
faces[ i ] = hsCodecManager::Instance().CreateUncompressedMipmap( faces[ i ], hsCodecManager::k16BitDepth ); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
for( i = 0; i < 6; i++ ) |
|
|
faces[ i ] = cubic->GetFace( i ); |
|
|
} |
|
|
|
|
|
/// Create the ref |
|
|
// Get format |
|
|
IGetD3DTextureFormat( faces[0], formatType, formatSize ); |
|
|
|
|
|
// Process the data. |
|
|
if( faces[0]->IsCompressed() || ( faces[0]->GetPixelSize() < 32 ) ) |
|
|
{ |
|
|
/// For this, we just take the image data pointers directly, so only call IProcess once |
|
|
externData = IProcessMipmapLevels( faces[ 0 ], numLevels, levelSizes, totalSize, numPixels, textureData[ 0 ], noMip ); |
|
|
for( i = 1; i < 6; i++ ) |
|
|
textureData[ i ] = faces[ i ]->GetImage(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
for( i = 0; i < 6; i++ ) |
|
|
{ |
|
|
/// Some of this will be redundant, but oh well |
|
|
externData = IProcessMipmapLevels( faces[ i ], numLevels, levelSizes, totalSize, numPixels, textureData[ i ], noMip ); |
|
|
} |
|
|
} |
|
|
|
|
|
ref = (plDXCubeTextureRef *)cubic->GetDeviceRef(); |
|
|
if( !ref ) |
|
|
{ |
|
|
ref = TRACKED_NEW plDXCubeTextureRef( formatType, |
|
|
numLevels, faces[ 0 ]->GetWidth(), faces[ 0 ]->GetHeight(), |
|
|
numPixels, totalSize, totalSize * 6, levelSizes, |
|
|
textureData[ 0 ], externData ); |
|
|
ref->fOwner = cubic; |
|
|
ref->Link( &fTextureRefList ); // So we don't ref later on down |
|
|
for( i = 0; i < 5; i++ ) |
|
|
ref->fFaceData[ i ] = textureData[ i + 1 ]; |
|
|
|
|
|
cubic->SetDeviceRef( ref ); |
|
|
// Note: this is because SetDeviceRef() will ref it, and at this point, |
|
|
// only the bitmap should own the ref, not us. We ref/unref it on Use() |
|
|
hsRefCnt_SafeUnRef( ref ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ref->Set( formatType, numLevels, faces[ 0 ]->GetWidth(), faces[ 0 ]->GetHeight(), |
|
|
numPixels, totalSize, totalSize * 6, levelSizes, textureData[ 0 ], externData ); |
|
|
|
|
|
for( i = 0; i < 5; i++ ) |
|
|
ref->fFaceData[ i ] = textureData[ i + 1 ]; |
|
|
} |
|
|
ref->SetFlags( ref->GetFlags() | plDXTextureRef::kCubicMap ); |
|
|
|
|
|
// Put in linked list for easy disposal. |
|
|
if( !ref->IsLinked() ) |
|
|
{ |
|
|
// Re-linking |
|
|
ref->Link( &fTextureRefList ); |
|
|
} |
|
|
|
|
|
/// Copy the data into the ref |
|
|
IReloadTexture( ref ); |
|
|
ref->SetDirty( false ); |
|
|
|
|
|
/// Cleanup |
|
|
for( i = 0; i < 6; i++ ) |
|
|
{ |
|
|
if( faces[ i ] != cubic->GetFace( i ) ) |
|
|
delete faces[ i ]; |
|
|
if( !( cubic->GetFace(i)->GetFlags() & (plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage) ) && !GetProperty( kPropDontDeleteTextures ) ) |
|
|
{ |
|
|
// Turn this on to delete the plasma system memory copy once we have a D3D managed version. |
|
|
// Currently disabled, because there are still mipmaps that are read from after their managed |
|
|
// versions are created, but aren't flagged DontThrowAwayImage or kUserOwnesBitmap. |
|
|
// cubic->GetFace(i)->Reset(); |
|
|
} |
|
|
} |
|
|
|
|
|
return ref; |
|
|
} |
|
|
|
|
|
//// IProcessMipmapLevels ///////////////////////////////////////////////////// |
|
|
// Compute proper values for the arguments passed in. |
|
|
// Return true if the data returned points directly into the mipmap data, |
|
|
// return false if textureData is a reformatted copy of the mipmap's data. |
|
|
hsBool plDXPipeline::IProcessMipmapLevels( plMipmap *mipmap, UInt32 &numLevels, |
|
|
UInt32 *&levelSizes, UInt32 &totalSize, |
|
|
UInt32 &numPixels, void *&textureData, hsBool noMip ) |
|
|
{ |
|
|
hsBool externData = false; |
|
|
D3DFORMAT formatType = D3DFMT_UNKNOWN; // D3D Format |
|
|
UInt32 formatSize; |
|
|
|
|
|
|
|
|
IGetD3DTextureFormat( mipmap, formatType, formatSize ); |
|
|
|
|
|
// Compressed or 16 bit, we can use directly. |
|
|
if( mipmap->IsCompressed() || ( mipmap->GetPixelSize() < 32 ) ) |
|
|
{ |
|
|
numPixels = 0; |
|
|
if( noMip ) |
|
|
{ |
|
|
numLevels = 1; |
|
|
levelSizes = nil; |
|
|
totalSize = mipmap->GetLevelSize(0); |
|
|
} |
|
|
else |
|
|
{ |
|
|
UInt32 sizeMask = 0x03; |
|
|
|
|
|
/// 10.31.2000 - If we have this flag set, we really have to cut out |
|
|
/// sizes under 8x8. So far only true on the KYRO... |
|
|
if( fSettings.fD3DCaps & kCapsNoKindaSmallTexs ) |
|
|
sizeMask = 0x07; |
|
|
|
|
|
int maxLevel = mipmap->GetNumLevels() - 1; |
|
|
|
|
|
/// 9.7.2000 - Also do this test if the card doesn't support |
|
|
/// itty bitty textures |
|
|
if( mipmap->IsCompressed() || !( fSettings.fD3DCaps & kCapsDoesSmallTextures ) ) |
|
|
{ |
|
|
mipmap->SetCurrLevel( maxLevel ); |
|
|
while( ( mipmap->GetCurrWidth() | mipmap->GetCurrHeight() ) & sizeMask ) |
|
|
{ |
|
|
maxLevel--; |
|
|
hsAssert( maxLevel >= 0, "How was this ever compressed?" ); |
|
|
mipmap->SetCurrLevel( maxLevel ); |
|
|
} |
|
|
} |
|
|
|
|
|
mipmap->SetCurrLevel( 0 ); |
|
|
totalSize = 0; |
|
|
numLevels = maxLevel + 1; |
|
|
levelSizes = TRACKED_NEW UInt32[ numLevels ]; |
|
|
int i; |
|
|
for( i = 0; i < numLevels; i++ ) |
|
|
{ |
|
|
levelSizes[ i ] = mipmap->GetLevelSize( i ); |
|
|
totalSize += mipmap->GetLevelSize( i ); |
|
|
} |
|
|
} |
|
|
|
|
|
textureData = mipmap->GetImage(); |
|
|
externData = true; |
|
|
} |
|
|
else |
|
|
{ |
|
|
// 32 bit uncompressed data. In general, we reformat to 16 bit if we're running |
|
|
// 16 bit, or if 32 bit leave it at 32. All subject to what the hardware can do |
|
|
// and what the texture is for. See IGetD3DTextureFormat. |
|
|
formatSize >>= 3; |
|
|
|
|
|
if( !noMip ) |
|
|
{ |
|
|
numPixels = mipmap->GetTotalSize() * 8 / mipmap->GetPixelSize(); |
|
|
numLevels = mipmap->GetNumLevels(); |
|
|
|
|
|
levelSizes = TRACKED_NEW UInt32[ numLevels ]; |
|
|
|
|
|
int i; |
|
|
UInt32 w, h; |
|
|
for( i = 0; i < numLevels; i++ ) |
|
|
{ |
|
|
mipmap->GetLevelPtr( i, &w, &h ); |
|
|
levelSizes[ i ] = w * h * formatSize; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
numPixels = mipmap->GetWidth() * mipmap->GetHeight(); |
|
|
numLevels = 1; |
|
|
levelSizes = nil; |
|
|
} |
|
|
totalSize = numPixels * formatSize; |
|
|
|
|
|
// Shared scratch space to reformat a texture before it's copied into |
|
|
// the D3D surface. |
|
|
textureData = IGetPixelScratch( totalSize ); |
|
|
|
|
|
// Convert it to the requested format. |
|
|
IFormatTextureData( formatType, numPixels, (hsRGBAColor32 *)mipmap->GetImage(), textureData ); |
|
|
} |
|
|
|
|
|
return externData; |
|
|
} |
|
|
|
|
|
//// IGetPixelScratch ///////////////////////////////////////////////////////// |
|
|
// Return scratch space at least of at least size bytes, to reformat a mipmap into. |
|
|
void *plDXPipeline::IGetPixelScratch( UInt32 size ) |
|
|
{ |
|
|
static char *sPtr = nil; |
|
|
static UInt32 sSize = 0; |
|
|
|
|
|
if( size > sSize ) |
|
|
{ |
|
|
if( sPtr != nil ) |
|
|
delete [] sPtr; |
|
|
|
|
|
if( size > 0 ) |
|
|
sPtr = TRACKED_NEW char[ sSize = size ]; |
|
|
else |
|
|
sPtr = nil; |
|
|
} |
|
|
else if( size == 0 ) |
|
|
{ |
|
|
if( sPtr != nil ) |
|
|
delete [] sPtr; |
|
|
|
|
|
sPtr = nil; |
|
|
sSize = 0; |
|
|
} |
|
|
|
|
|
return sPtr; |
|
|
} |
|
|
|
|
|
//// IGetD3DTextureFormat ///////////////////////////////////////////////////// |
|
|
// Given a bitmap, finds the matching D3D format. |
|
|
|
|
|
void plDXPipeline::IGetD3DTextureFormat( plBitmap *b, D3DFORMAT &formatType, UInt32& texSize ) |
|
|
{ |
|
|
hsAssert( b, "Nil input to GetTextureFormat()" ); |
|
|
|
|
|
hsBool prefer32bit = 0 != (b->GetFlags() & plBitmap::kForce32Bit); |
|
|
|
|
|
if( b->IsCompressed() ) |
|
|
{ |
|
|
hsAssert( plMipmap::kDirectXCompression == b->fCompressionType, "Unsupported compression format" ); |
|
|
texSize = 0; |
|
|
switch( b->fDirectXInfo.fCompressionType ) |
|
|
{ |
|
|
case plMipmap::DirectXInfo::kDXT1: |
|
|
formatType = D3DFMT_DXT1; |
|
|
break; |
|
|
// case plMipmap::DirectXInfo::kDXT2: |
|
|
// formatType = D3DFMT_DXT2; |
|
|
// break; |
|
|
// case plMipmap::DirectXInfo::kDXT3: |
|
|
// formatType = D3DFMT_DXT3; |
|
|
// break; |
|
|
// case plMipmap::DirectXInfo::kDXT4: |
|
|
// formatType = D3DFMT_DXT4; |
|
|
// break; |
|
|
case plMipmap::DirectXInfo::kDXT5: |
|
|
formatType = D3DFMT_DXT5; |
|
|
break; |
|
|
default: |
|
|
hsAssert(false, "Unknown DirectX compression format"); |
|
|
} |
|
|
} |
|
|
else if( b->GetFlags() & plMipmap::kBumpEnvMap ) |
|
|
{ |
|
|
texSize = 16; |
|
|
if( b->GetFlags() & plMipmap::kAlphaChannelFlag ) |
|
|
formatType = D3DFMT_L6V5U5; |
|
|
else |
|
|
formatType = D3DFMT_V8U8; |
|
|
} |
|
|
else if( b->GetPixelSize() == 16 ) |
|
|
{ |
|
|
texSize = 16; |
|
|
if( b->GetFlags() & plMipmap::kIntensityMap ) |
|
|
{ |
|
|
if( b->GetFlags() & plMipmap::kAlphaChannelFlag ) |
|
|
formatType = D3DFMT_A8L8; |
|
|
else |
|
|
formatType = D3DFMT_L8; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( b->GetFlags() & plMipmap::kAlphaChannelFlag ) |
|
|
formatType = D3DFMT_A4R4G4B4; |
|
|
else |
|
|
formatType = D3DFMT_A1R5G5B5; |
|
|
} |
|
|
} |
|
|
else if( b->GetFlags() & plMipmap::kIntensityMap ) |
|
|
{ |
|
|
if( b->GetFlags() & plMipmap::kAlphaChannelFlag ) |
|
|
{ |
|
|
if( ITextureFormatAllowed( D3DFMT_A8L8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A8L8; |
|
|
texSize = 16; |
|
|
} |
|
|
else if( !prefer32bit && ( fSettings.fColorDepth == 16 ) && ITextureFormatAllowed( D3DFMT_A4R4G4B4 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A4R4G4B4; |
|
|
texSize = 16; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A8R8G8B8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A8R8G8B8; |
|
|
texSize = 32; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A4R4G4B4 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A4R4G4B4; |
|
|
texSize = 16; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( ITextureFormatAllowed( D3DFMT_L8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_L8; |
|
|
texSize = 8; |
|
|
} |
|
|
else if( !prefer32bit && ( fSettings.fColorDepth == 16 ) && ITextureFormatAllowed( D3DFMT_A1R5G5B5 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A1R5G5B5; |
|
|
texSize = 16; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A8R8G8B8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A8R8G8B8; |
|
|
texSize = 32; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A1R5G5B5 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A1R5G5B5; |
|
|
texSize = 16; |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( b->GetFlags() & plMipmap::kAlphaChannelFlag ) |
|
|
{ |
|
|
if( !prefer32bit && ( fSettings.fColorDepth == 16 ) && ITextureFormatAllowed( D3DFMT_A4R4G4B4 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A4R4G4B4; |
|
|
texSize = 16; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A8R8G8B8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A8R8G8B8; |
|
|
texSize = 32; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A4R4G4B4 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A4R4G4B4; |
|
|
texSize = 16; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if( !prefer32bit && ( fSettings.fColorDepth == 16 ) && ITextureFormatAllowed( D3DFMT_A1R5G5B5 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A1R5G5B5; |
|
|
texSize = 16; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A8R8G8B8 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A8R8G8B8; |
|
|
texSize = 32; |
|
|
} |
|
|
else if( ITextureFormatAllowed( D3DFMT_A1R5G5B5 ) ) |
|
|
{ |
|
|
formatType = D3DFMT_A1R5G5B5; |
|
|
texSize = 16; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
hsAssert( formatType, "failing to find format type" ); |
|
|
} |
|
|
|
|
|
//// IFormatTextureData /////////////////////////////////////////////////////// |
|
|
// Convert the input 32 bit uncompressed RGBA data into the requested format. |
|
|
void plDXPipeline::IFormatTextureData( UInt32 formatType, UInt32 numPix, hsRGBAColor32* const src, void *dst ) |
|
|
{ |
|
|
switch( formatType ) |
|
|
{ |
|
|
case D3DFMT_L6V5U5: |
|
|
{ |
|
|
UInt16 *pixels = (UInt16 *)dst; |
|
|
hsRGBAColor32* p = src; |
|
|
hsRGBAColor32* end = src + numPix; |
|
|
|
|
|
while( p < end ) |
|
|
{ |
|
|
*pixels = ((p->a << 8) & 0xfc00) |
|
|
| ((p->g << 2) & 0x03e0) |
|
|
| ((p->r >> 3) & 0x001f); |
|
|
#ifdef HS_DEBUGGING |
|
|
if( *pixels & 0xfc00 ) |
|
|
pixels++; |
|
|
else if( *pixels & 0x03e0 ) |
|
|
pixels++; |
|
|
else if( *pixels & 0x001f ) |
|
|
pixels++; |
|
|
else |
|
|
#endif // HS_DEBUGGING |
|
|
pixels++; |
|
|
p++; |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_V8U8: |
|
|
{ |
|
|
UInt16 *pixels = (UInt16 *)dst; |
|
|
hsRGBAColor32* p = src; |
|
|
hsRGBAColor32* end = src + numPix; |
|
|
|
|
|
while( p < end ) |
|
|
{ |
|
|
*pixels = (p->g << 8) |
|
|
| (p->r << 0); |
|
|
pixels++; |
|
|
p++; |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_A8L8: |
|
|
{ |
|
|
UInt16 *pixels = (UInt16 *)dst; |
|
|
int i; |
|
|
hsRGBAColor32* const p = src; |
|
|
|
|
|
for(i =0; i < numPix; i++) |
|
|
pixels[i]= ((p[i].a & 0xff) << 8) | (p[i].r & 0xff); |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_A4R4G4B4: |
|
|
{ |
|
|
UInt16 *pixels = (UInt16 *)dst; |
|
|
int i; |
|
|
hsRGBAColor32* const p = src; |
|
|
|
|
|
for(i =0; i < numPix; i++) |
|
|
{ |
|
|
pixels[i]= (((p[i].r>>4) & 0xf) << 8) |
|
|
| (((p[i].g >> 4) & 0xf) << 4) |
|
|
| (((p[i].b >> 4) & 0xf) ) |
|
|
| (((p[i].a >> 4) & 0xf) << 12); |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_A1R5G5B5: |
|
|
{ |
|
|
UInt16 *pixels = (UInt16 *)dst; |
|
|
int i; |
|
|
hsRGBAColor32* const p = src; |
|
|
|
|
|
for(i =0; i < numPix; i++) |
|
|
{ |
|
|
pixels[i]= (((p[i].r>>3) & 0x1f) << 10) | |
|
|
(((p[i].g >> 3) & 0x1f) << 5) | |
|
|
((p[i].b >> 3) & 0x1f) | ((p[i].a == 0) ? 0 : 0x8000); |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_L8: |
|
|
{ |
|
|
UInt8 *pixels = (UInt8 *)dst; |
|
|
int i; |
|
|
hsRGBAColor32* const p = src; |
|
|
|
|
|
for(i =0; i < numPix; i++) |
|
|
pixels[i]= p[i].r; |
|
|
} |
|
|
break; |
|
|
|
|
|
case D3DFMT_A8R8G8B8: |
|
|
{ |
|
|
UInt32 *pixels = (UInt32 *)dst; |
|
|
int i; |
|
|
hsRGBAColor32* const p = src; |
|
|
|
|
|
for(i =0; i < numPix; i++) |
|
|
pixels[i]= ( ( p[i].a << 24 ) | ( p[i].r << 16 ) | ( p[i].g << 8 ) | p[i].b ); |
|
|
} |
|
|
break; |
|
|
|
|
|
default: |
|
|
hsAssert(false, "Unknown texture format selected"); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// View Stuff /////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
//// TestVisibleWorld ///////////////////////////////////////////////////////// |
|
|
// Check if the world space bounds are visible within the current view frustum. |
|
|
hsBool plDXPipeline::TestVisibleWorld( const hsBounds3Ext& wBnd ) |
|
|
{ |
|
|
if( fView.fCullTreeDirty ) |
|
|
IRefreshCullTree(); |
|
|
return fView.fCullTree.BoundsVisible(wBnd); |
|
|
} |
|
|
|
|
|
hsBool plDXPipeline::TestVisibleWorld( const plSceneObject* sObj ) |
|
|
{ |
|
|
const plDrawInterface* di = sObj->GetDrawInterface(); |
|
|
if( !di ) |
|
|
return false; |
|
|
|
|
|
const int numDraw = di->GetNumDrawables(); |
|
|
int i; |
|
|
for( i = 0; i < numDraw; i++ ) |
|
|
{ |
|
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(i)); |
|
|
if( !dr ) |
|
|
continue; |
|
|
|
|
|
plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(i)); |
|
|
if( diIndex.IsMatrixOnly() ) |
|
|
continue; |
|
|
|
|
|
const int numSpan = diIndex.GetCount(); |
|
|
int j; |
|
|
for( j = 0; j < numSpan; j++ ) |
|
|
{ |
|
|
const plSpan* span = dr->GetSpan(diIndex[j]); |
|
|
|
|
|
if( span->fProps & plSpan::kPropNoDraw ) |
|
|
continue; |
|
|
|
|
|
if( !span->GetVisSet().Overlap(plGlobalVisMgr::Instance()->GetVisSet()) |
|
|
|| span->GetVisSet().Overlap(plGlobalVisMgr::Instance()->GetVisNot()) ) |
|
|
|
|
|
continue; |
|
|
|
|
|
if( !TestVisibleWorld(span->fWorldBounds) ) |
|
|
continue; |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
//// GetViewAxesWorld ///////////////////////////////////////////////////////// |
|
|
// Get the current view direction, up and direction X up. |
|
|
void plDXPipeline::GetViewAxesWorld(hsVector3 axes[3] /* ac,up,at */ ) const |
|
|
{ |
|
|
axes[ 0 ] = GetViewAcrossWorld(); |
|
|
axes[ 1 ] = GetViewUpWorld(); |
|
|
axes[ 2 ] = GetViewDirWorld(); |
|
|
} |
|
|
|
|
|
//// GetFOV /////////////////////////////////////////////////////////////////// |
|
|
// Get the current FOV in degrees. |
|
|
void plDXPipeline::GetFOV(hsScalar& fovX, hsScalar& fovY) const |
|
|
{ |
|
|
fovX = GetViewTransform().GetFovXDeg(); |
|
|
fovY = GetViewTransform().GetFovYDeg(); |
|
|
} |
|
|
|
|
|
//// SetFOV /////////////////////////////////////////////////////////////////// |
|
|
// Set the current FOV in degrees. Forces perspective rendering to be true. |
|
|
void plDXPipeline::SetFOV( hsScalar fovX, hsScalar fovY ) |
|
|
{ |
|
|
IGetViewTransform().SetFovDeg(fovX, fovY); |
|
|
IGetViewTransform().SetPerspective(true); |
|
|
} |
|
|
|
|
|
// Get the orthogonal projection view size in world units (e.g. feet). |
|
|
void plDXPipeline::GetSize( hsScalar& width, hsScalar& height ) const |
|
|
{ |
|
|
width = GetViewTransform().GetScreenWidth(); |
|
|
height = GetViewTransform().GetScreenHeight(); |
|
|
} |
|
|
|
|
|
// Set the orthogonal projection view size in world units (e.g. feet). |
|
|
// Forces projection to orthogonal if it wasn't. |
|
|
void plDXPipeline::SetSize( hsScalar width, hsScalar height ) |
|
|
{ |
|
|
IGetViewTransform().SetWidth(width); |
|
|
IGetViewTransform().SetHeight(height); |
|
|
IGetViewTransform().SetOrthogonal(true); |
|
|
} |
|
|
|
|
|
//// GetDepth ///////////////////////////////////////////////////////////////// |
|
|
// Get the current hither and yon. |
|
|
void plDXPipeline::GetDepth(hsScalar& hither, hsScalar& yon) const |
|
|
{ |
|
|
GetViewTransform().GetDepth(hither, yon); |
|
|
} |
|
|
|
|
|
//// SetDepth ///////////////////////////////////////////////////////////////// |
|
|
// Set the current hither and yon. |
|
|
void plDXPipeline::SetDepth(hsScalar hither, hsScalar yon) |
|
|
{ |
|
|
IGetViewTransform().SetDepth(hither, yon); |
|
|
} |
|
|
|
|
|
//// ISavageYonHack /////////////////////////////////////////////////////////// |
|
|
// Corrects the yon for the *#(&$*#&$(*& Savage4 chipset (ex. Diamond Stealth |
|
|
// III S540). Let's just say this card SUCKS. |
|
|
// Obsolete since we don't support the Savage4 chipset any more. |
|
|
void plDXPipeline::ISavageYonHack() |
|
|
{ |
|
|
hsScalar yon = GetViewTransform().GetYon(); |
|
|
|
|
|
|
|
|
if( ( yon > 128.f - 5.0f ) && ( yon < 128.f + 1.01f ) ) |
|
|
yon = 128.f + 1.01f; |
|
|
else if( ( yon > 256.f - 10.0f ) && ( yon < 256.f + 1.02f ) ) |
|
|
yon = 256.f + 1.02f; |
|
|
else if( ( yon > 512.f - 35.0f ) && ( yon < 512.f + 1.02f ) ) |
|
|
yon = 512.f + 1.02f; |
|
|
else if( ( yon > 1024.f - 120.0f ) && ( yon < 1024.f + 1.f ) ) |
|
|
yon = 1024.f + 1.f; |
|
|
} |
|
|
|
|
|
//// GetWorldToCamera ///////////////////////////////////////////////////////// |
|
|
// Return current world to camera transform. |
|
|
const hsMatrix44& plDXPipeline::GetWorldToCamera() const |
|
|
{ |
|
|
return fView.GetWorldToCamera(); |
|
|
} |
|
|
|
|
|
//// GetCameraToWorld ///////////////////////////////////////////////////////// |
|
|
// Return current camera to world transform. |
|
|
const hsMatrix44& plDXPipeline::GetCameraToWorld() const |
|
|
{ |
|
|
return fView.GetCameraToWorld(); |
|
|
} |
|
|
|
|
|
// IUpdateViewFlags ///////////////////////////////////////////////////////// |
|
|
// Dirty anything cached dependent on the current camera matrix. |
|
|
void plDXPipeline::IUpdateViewFlags() |
|
|
{ |
|
|
fView.fCullTreeDirty = true; |
|
|
|
|
|
fView.fWorldToCamLeftHanded = fView.GetWorldToCamera().GetParity(); |
|
|
} |
|
|
//// SetWorldToCamera ///////////////////////////////////////////////////////// |
|
|
// Immediate set of camera transform. |
|
|
void plDXPipeline::SetWorldToCamera(const hsMatrix44& w2c, const hsMatrix44& c2w) |
|
|
{ |
|
|
IGetViewTransform().SetCameraTransform(w2c, c2w); |
|
|
|
|
|
IUpdateViewFlags(); |
|
|
|
|
|
IWorldToCameraToD3D(); |
|
|
} |
|
|
|
|
|
// IWorldToCameraToD3D /////////////////////////////////////////////////////// |
|
|
// Pass the current camera transform through to D3D. |
|
|
void plDXPipeline::IWorldToCameraToD3D() |
|
|
{ |
|
|
D3DXMATRIX mat; |
|
|
|
|
|
IMatrix44ToD3DMatrix( mat, fView.GetWorldToCamera() ); |
|
|
fD3DDevice->SetTransform( D3DTS_VIEW, &mat ); |
|
|
|
|
|
fView.fXformResetFlags &= ~fView.kResetCamera; |
|
|
|
|
|
fFrame++; |
|
|
} |
|
|
|
|
|
// SetViewTransform /////////////////////////////////////////////////////////// |
|
|
// ViewTransform encapsulates everything about the current camera, viewport and |
|
|
// window necessary to render or convert from world space to pixel space. Doesn't |
|
|
// include the object dependent local to world transform. |
|
|
// Set plViewTransform.h |
|
|
void plDXPipeline::SetViewTransform(const plViewTransform& v) |
|
|
{ |
|
|
fView.fTransform = v; |
|
|
|
|
|
if( !v.GetScreenWidth() || !v.GetScreenHeight() ) |
|
|
{ |
|
|
fView.fTransform.SetScreenSize((UInt16)(fSettings.fOrigWidth), (UInt16)(fSettings.fOrigHeight)); |
|
|
} |
|
|
|
|
|
IUpdateViewFlags(); |
|
|
|
|
|
IWorldToCameraToD3D(); |
|
|
} |
|
|
|
|
|
//// GetWorldToLocal ////////////////////////////////////////////////////////// |
|
|
// Return current World to Local transform. Note that this is only meaningful while an |
|
|
// object is being rendered, so this function is pretty worthless. |
|
|
const hsMatrix44& plDXPipeline::GetWorldToLocal() const |
|
|
{ |
|
|
return fView.fWorldToLocal; |
|
|
} |
|
|
|
|
|
//// GetLocalToWorld ////////////////////////////////////////////////////////// |
|
|
// Return current Local to World transform. Note that this is only meaningful while an |
|
|
// object is being rendered, so this function is pretty worthless. |
|
|
|
|
|
const hsMatrix44& plDXPipeline::GetLocalToWorld() const |
|
|
{ |
|
|
return fView.fLocalToWorld; |
|
|
} |
|
|
|
|
|
//// ISetLocalToWorld ///////////////////////////////////////////////////////// |
|
|
// Record and pass on to D3D the current local to world transform for the object |
|
|
// about to be rendered. |
|
|
void plDXPipeline::ISetLocalToWorld( const hsMatrix44& l2w, const hsMatrix44& w2l ) |
|
|
{ |
|
|
|
|
|
fView.fLocalToWorld = l2w; |
|
|
fView.fWorldToLocal = w2l; |
|
|
|
|
|
fView.fViewVectorsDirty = true; |
|
|
|
|
|
// We keep track of parity for winding order culling. |
|
|
fView.fLocalToWorldLeftHanded = fView.fLocalToWorld.GetParity(); |
|
|
|
|
|
ILocalToWorldToD3D(); |
|
|
} |
|
|
|
|
|
// ILocalToWorldToD3D /////////////////////////////////////////////////////////// |
|
|
// pass the current local to world tranform on to D3D. |
|
|
void plDXPipeline::ILocalToWorldToD3D() |
|
|
{ |
|
|
D3DXMATRIX mat; |
|
|
|
|
|
if( fView.fLocalToWorld.fFlags & hsMatrix44::kIsIdent ) |
|
|
fD3DDevice->SetTransform( D3DTS_WORLD, &d3dIdentityMatrix ); |
|
|
else |
|
|
{ |
|
|
IMatrix44ToD3DMatrix( mat, fView.fLocalToWorld ); |
|
|
fD3DDevice->SetTransform( D3DTS_WORLD, &mat ); |
|
|
} |
|
|
|
|
|
fView.fXformResetFlags &= ~fView.kResetL2W; |
|
|
} |
|
|
|
|
|
//// IIsViewLeftHanded //////////////////////////////////////////////////////// |
|
|
// Returns true if the combination of the local2world and world2camera |
|
|
// matrices is left-handed. |
|
|
|
|
|
hsBool plDXPipeline::IIsViewLeftHanded() |
|
|
{ |
|
|
return fView.fTransform.GetOrthogonal() ^ ( fView.fLocalToWorldLeftHanded ^ fView.fWorldToCamLeftHanded ) ? true : false; |
|
|
} |
|
|
|
|
|
//// ScreenToWorldPoint /////////////////////////////////////////////////////// |
|
|
// Given a screen space pixel position, and a world space distance from the camera, return a |
|
|
// full world space position. I.e. cast a ray through a screen pixel dist feet, and where |
|
|
// is it. |
|
|
void plDXPipeline::ScreenToWorldPoint( int n, UInt32 stride, Int32 *scrX, Int32 *scrY, hsScalar dist, UInt32 strideOut, hsPoint3 *worldOut ) |
|
|
{ |
|
|
while( n-- ) |
|
|
{ |
|
|
hsPoint3 scrP; |
|
|
scrP.Set(float(*scrX++), float(*scrY++), float(dist)); |
|
|
*worldOut++ = GetViewTransform().ScreenToWorld(scrP); |
|
|
} |
|
|
} |
|
|
|
|
|
// IRefreshCullTree //////////////////////////////////////////////////////////////////// |
|
|
// The cull tree captures the view frustum and any occluders in the scene into a single |
|
|
// BSP tree. See plCullTree.h. It must be recomputed any time the camera moves. |
|
|
void plDXPipeline::IRefreshCullTree() |
|
|
{ |
|
|
if( fView.fCullTreeDirty ) |
|
|
{ |
|
|
plProfile_BeginTiming(DrawOccBuild); |
|
|
|
|
|
fView.fCullTree.Reset(); |
|
|
|
|
|
fView.fCullTree.SetViewPos(GetViewPositionWorld()); |
|
|
|
|
|
if (fCullProxy && !IsDebugFlagSet(plPipeDbg::kFlagOcclusionSnap)) |
|
|
{ |
|
|
fCullProxy->GetKey()->UnRefObject(); |
|
|
fCullProxy = nil; |
|
|
SetDrawableTypeMask(GetDrawableTypeMask() & ~plDrawable::kOccSnapProxy); |
|
|
} |
|
|
hsBool doCullSnap = IsDebugFlagSet(plPipeDbg::kFlagOcclusionSnap)&& !fCullProxy && !fSettings.fViewStack.GetCount(); |
|
|
if( doCullSnap ) |
|
|
{ |
|
|
fView.fCullTree.BeginCapturePolys(); |
|
|
fView.fCullTree.SetVisualizationYon(GetViewTransform().GetYon()); |
|
|
} |
|
|
fView.fCullTree.InitFrustum(GetViewTransform().GetWorldToNDC()); |
|
|
fView.fCullTreeDirty = false; |
|
|
|
|
|
if( fView.fCullMaxNodes ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fCullPolys.GetCount(); i++ ) |
|
|
{ |
|
|
fView.fCullTree.AddPoly(*fCullPolys[i]); |
|
|
if( fView.fCullTree.GetNumNodes() >= fView.fCullMaxNodes ) |
|
|
break; |
|
|
} |
|
|
fCullPolys.SetCount(0); |
|
|
plProfile_Set(OccPolyUsed, i); |
|
|
|
|
|
for( i = 0; i < fCullHoles.GetCount(); i++ ) |
|
|
{ |
|
|
fView.fCullTree.AddPoly(*fCullHoles[i]); |
|
|
} |
|
|
fCullHoles.SetCount(0); |
|
|
plProfile_Set(OccNodeUsed, fView.fCullTree.GetNumNodes()); |
|
|
} |
|
|
if( doCullSnap ) |
|
|
{ |
|
|
fView.fCullTree.EndCapturePolys(); |
|
|
IMakeOcclusionSnap(); |
|
|
} |
|
|
|
|
|
plProfile_EndTiming(DrawOccBuild); |
|
|
} |
|
|
} |
|
|
|
|
|
// IMakeOcclusionSnap ///////////////////////////////////////////////////////////////////// |
|
|
// Debugging visualization tool only. Takes a snapshot of the current occlusion |
|
|
// BSP tree and renders it until told to stop. |
|
|
void plDXPipeline::IMakeOcclusionSnap() |
|
|
{ |
|
|
hsTArray<hsPoint3>& pos = fView.fCullTree.GetCaptureVerts(); |
|
|
hsTArray<hsVector3>& norm = fView.fCullTree.GetCaptureNorms(); |
|
|
hsTArray<hsColorRGBA>& color = fView.fCullTree.GetCaptureColors(); |
|
|
hsTArray<UInt16>& tris = fView.fCullTree.GetCaptureTris(); |
|
|
|
|
|
if( tris.GetCount() ) |
|
|
{ |
|
|
hsMatrix44 ident; |
|
|
ident.Reset(); |
|
|
|
|
|
hsGMaterial* mat = TRACKED_NEW hsGMaterial; |
|
|
hsgResMgr::ResMgr()->NewKey( "OcclusionSnapMat", mat, plLocation::kGlobalFixedLoc ); |
|
|
plLayer *lay = mat->MakeBaseLayer(); |
|
|
lay->SetZFlags(hsGMatState::kZNoZWrite); |
|
|
lay->SetPreshadeColor(hsColorRGBA().Set(1.f, 0.5f, 0.5f, 1.f)); |
|
|
lay->SetRuntimeColor(hsColorRGBA().Set(1.f, 0.5f, 0.5f, 1.f)); |
|
|
lay->SetAmbientColor(hsColorRGBA().Set(0,0,0,1.f)); |
|
|
lay->SetOpacity(0.5f); |
|
|
lay->SetBlendFlags(lay->GetBlendFlags() | hsGMatState::kBlendAlpha); |
|
|
|
|
|
fCullProxy = plDrawableGenerator::GenerateDrawable(pos.GetCount(), |
|
|
pos.AcquireArray(), |
|
|
norm.AcquireArray(), |
|
|
nil, |
|
|
0, |
|
|
color.AcquireArray(), |
|
|
true, |
|
|
nil, |
|
|
tris.GetCount(), |
|
|
tris.AcquireArray(), |
|
|
mat, |
|
|
ident, |
|
|
true, |
|
|
nil, |
|
|
nil); |
|
|
|
|
|
if( fCullProxy ) |
|
|
{ |
|
|
fCullProxy->GetKey()->RefObject(); |
|
|
fCullProxy->SetType(plDrawable::kOccSnapProxy); |
|
|
|
|
|
SetDrawableTypeMask(GetDrawableTypeMask() | plDrawable::kOccSnapProxy); |
|
|
|
|
|
fCullProxy->PrepForRender(this); |
|
|
} |
|
|
} |
|
|
fView.fCullTree.ReleaseCapture(); |
|
|
} |
|
|
|
|
|
// SubmitOccluders ///////////////////////////////////////////////////////////// |
|
|
// Add the input polys into the list of polys from which to generate the cull tree. |
|
|
hsBool plDXPipeline::SubmitOccluders(const hsTArray<const plCullPoly*>& polyList) |
|
|
{ |
|
|
fCullPolys.SetCount(0); |
|
|
fCullHoles.SetCount(0); |
|
|
int i; |
|
|
for( i = 0; i < polyList.GetCount(); i++ ) |
|
|
{ |
|
|
if( polyList[i]->IsHole() ) |
|
|
fCullHoles.Append(polyList[i]); |
|
|
else |
|
|
fCullPolys.Append(polyList[i]); |
|
|
} |
|
|
fView.fCullTreeDirty = true; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
//// RefreshScreenMatrices //////////////////////////////////////////////////// |
|
|
// Force a refresh of cached state when the projection matrix changes. |
|
|
void plDXPipeline::RefreshScreenMatrices() |
|
|
{ |
|
|
fView.fCullTreeDirty = true; |
|
|
IProjectionMatrixToD3D(); |
|
|
} |
|
|
|
|
|
//// RefreshMatrices ////////////////////////////////////////////////////////// |
|
|
// Just a wrapper |
|
|
|
|
|
void plDXPipeline::RefreshMatrices() |
|
|
{ |
|
|
RefreshScreenMatrices(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Overrides //////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// PushOverrideMaterial ///////////////////////////////////////////////////// |
|
|
// Push a material to be used instead of the material associated with objects |
|
|
// for rendering. |
|
|
// Must be matched with a PopOverrideMaterial. |
|
|
hsGMaterial *plDXPipeline::PushOverrideMaterial( hsGMaterial *mat ) |
|
|
{ |
|
|
hsGMaterial *ret = GetOverrideMaterial(); |
|
|
hsRefCnt_SafeRef( mat ); |
|
|
fOverrideMat.Push( mat ); |
|
|
fForceMatHandle = true; |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
//// PopOverrideMaterial ////////////////////////////////////////////////////// |
|
|
// Stop overriding with the current override material. |
|
|
// Must match a preceding PushOverrideMaterial. |
|
|
void plDXPipeline::PopOverrideMaterial( hsGMaterial *restore ) |
|
|
{ |
|
|
hsGMaterial *pop = fOverrideMat.Pop(); |
|
|
hsRefCnt_SafeUnRef( pop ); |
|
|
|
|
|
if( fCurrMaterial == pop ) |
|
|
{ |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
} |
|
|
|
|
|
//// GetOverrideMaterial ////////////////////////////////////////////////////// |
|
|
// Return the current override material, or nil if there isn't any. |
|
|
hsGMaterial *plDXPipeline::GetOverrideMaterial() const |
|
|
{ |
|
|
return fOverrideMat.GetCount() ? fOverrideMat.Peek() : nil; |
|
|
} |
|
|
|
|
|
//// GetMaterialOverrideOn //////////////////////////////////////////////////// |
|
|
// Return the current bits set to be always on for the given category (e.g. ZFlags). |
|
|
UInt32 plDXPipeline::GetMaterialOverrideOn( hsGMatState::StateIdx category ) const |
|
|
{ |
|
|
return fMatOverOn.Value(category); |
|
|
} |
|
|
|
|
|
//// GetMaterialOverrideOff /////////////////////////////////////////////////// |
|
|
// Return the current bits set to be always off for the given category (e.g. ZFlags). |
|
|
UInt32 plDXPipeline::GetMaterialOverrideOff( hsGMatState::StateIdx category ) const |
|
|
{ |
|
|
return fMatOverOff.Value(category); |
|
|
} |
|
|
|
|
|
//// PushMaterialOverride ///////////////////////////////////////////////////// |
|
|
// Force material state bits on or off. If you use this, save the return value |
|
|
// as input to PopMaterialOverride, to restore previous values. |
|
|
hsGMatState plDXPipeline::PushMaterialOverride( const hsGMatState& state, hsBool on ) |
|
|
{ |
|
|
hsGMatState ret = GetMaterialOverride( on ); |
|
|
if( on ) |
|
|
{ |
|
|
fMatOverOn |= state; |
|
|
fMatOverOff -= state; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fMatOverOff |= state; |
|
|
fMatOverOn -= state; |
|
|
} |
|
|
fForceMatHandle = true; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
// PushMaterialOverride /////////////////////////////////////////////////////// |
|
|
// Force material state bits on or off. If you use this, save the return value |
|
|
// as input to PopMaterialOverride, to restore previous values. |
|
|
// This version just sets for one category (e.g. Z flags). |
|
|
hsGMatState plDXPipeline::PushMaterialOverride(hsGMatState::StateIdx cat, UInt32 which, hsBool on) |
|
|
{ |
|
|
hsGMatState ret = GetMaterialOverride( on ); |
|
|
if( on ) |
|
|
{ |
|
|
fMatOverOn[ cat ] |= which; |
|
|
fMatOverOff[ cat ] &= ~which; |
|
|
} |
|
|
else |
|
|
{ |
|
|
fMatOverOn[ cat ] &= ~which; |
|
|
fMatOverOff[ cat ] |= which; |
|
|
} |
|
|
fForceMatHandle = true; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
//// PopMaterialOverride ////////////////////////////////////////////////////// |
|
|
// Restore the previous settings returned from the matching PushMaterialOverride. |
|
|
void plDXPipeline::PopMaterialOverride(const hsGMatState& restore, hsBool on) |
|
|
{ |
|
|
if( on ) |
|
|
{ |
|
|
fMatOverOn = restore; |
|
|
fMatOverOff.Clear( restore ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fMatOverOff = restore; |
|
|
fMatOverOn.Clear( restore ); |
|
|
} |
|
|
fForceMatHandle = true; |
|
|
} |
|
|
|
|
|
//// GetMaterialOverride ////////////////////////////////////////////////////// |
|
|
// Return the current material state bits force to on or off, depending on input <on>. |
|
|
const hsGMatState& plDXPipeline::GetMaterialOverride(hsBool on) const |
|
|
{ |
|
|
return on ? fMatOverOn : fMatOverOff; |
|
|
} |
|
|
|
|
|
//// PushColorOverride ////////////////////////////////////////////////// |
|
|
// Obsolete and unused. |
|
|
hsColorOverride plDXPipeline::PushColorOverride(const hsColorOverride& over) |
|
|
{ |
|
|
hsColorOverride ret = GetColorOverride(); |
|
|
PopColorOverride( over ); |
|
|
return ret; |
|
|
} |
|
|
|
|
|
// PopColorOverride //////////////////////////////////////////////////////// |
|
|
// Obsolete and unused. |
|
|
void plDXPipeline::PopColorOverride(const hsColorOverride& restore) |
|
|
{ |
|
|
return; |
|
|
/* |
|
|
hsColorOverride cpy = restore; |
|
|
if( !(cpy.fFlags & hsColorOverride::kModAlpha) ) |
|
|
cpy.fColor.a = 1.f; |
|
|
if( !(cpy.fFlags & (hsColorOverride::kModAlpha | hsColorOverride::kModColor)) ) |
|
|
fDev->SetColorNormal(); |
|
|
else |
|
|
fDev->SetColorOverride(cpy.fColor, !(cpy.fFlags & hsColorOverride::kModColor)); |
|
|
*/ |
|
|
} |
|
|
|
|
|
//// GetColorOverride ///////////////////////////////////////////////////////// |
|
|
// Obsolete and unused. |
|
|
const hsColorOverride& plDXPipeline::GetColorOverride() const |
|
|
{ |
|
|
static hsColorOverride ret; |
|
|
return ret; |
|
|
|
|
|
/* ret.fFlags = hsColorOverride::kNone; |
|
|
if( fDev->GetDebugFlags() & hsG3DDevice::kDeviceColor ) |
|
|
ret.fFlags |= hsColorOverride::kModColor; |
|
|
if( fDev->GetDebugFlags() & hsG3DDevice::kDeviceAlpha ) |
|
|
ret.fFlags |= hsColorOverride::kModAlpha; |
|
|
|
|
|
ret.fColor = fDev->GetColorOverride(); |
|
|
*/ |
|
|
return ret; |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Transforms /////////////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// IMatrix44ToD3DMatrix ///////////////////////////////////////////////////// |
|
|
// Make a D3DXMATRIX matching the input plasma matrix. Mostly a transpose. |
|
|
D3DXMATRIX& plDXPipeline::IMatrix44ToD3DMatrix( D3DXMATRIX& dst, const hsMatrix44& src ) |
|
|
{ |
|
|
if( src.fFlags & hsMatrix44::kIsIdent ) |
|
|
{ |
|
|
dst = d3dIdentityMatrix; |
|
|
} |
|
|
else |
|
|
{ |
|
|
dst(0,0) = src.fMap[0][0]; |
|
|
dst(1,0) = src.fMap[0][1]; |
|
|
dst(2,0) = src.fMap[0][2]; |
|
|
dst(3,0) = src.fMap[0][3]; |
|
|
|
|
|
dst(0,1) = src.fMap[1][0]; |
|
|
dst(1,1) = src.fMap[1][1]; |
|
|
dst(2,1) = src.fMap[1][2]; |
|
|
dst(3,1) = src.fMap[1][3]; |
|
|
|
|
|
dst(0,2) = src.fMap[2][0]; |
|
|
dst(1,2) = src.fMap[2][1]; |
|
|
dst(2,2) = src.fMap[2][2]; |
|
|
dst(3,2) = src.fMap[2][3]; |
|
|
|
|
|
dst(0,3) = src.fMap[3][0]; |
|
|
dst(1,3) = src.fMap[3][1]; |
|
|
dst(2,3) = src.fMap[3][2]; |
|
|
dst(3,3) = src.fMap[3][3]; |
|
|
} |
|
|
|
|
|
return dst; |
|
|
} |
|
|
|
|
|
///////////////////////////////////////////////////////// |
|
|
// IGetCameraToNDC ///////////////////////////////////////////// |
|
|
// Get the camera to NDC transform. This may be adjusted to create |
|
|
// a Z bias towards the camera for cases where the D3D Z bias fails us. |
|
|
hsMatrix44 plDXPipeline::IGetCameraToNDC() |
|
|
{ |
|
|
hsMatrix44 cam2ndc = GetViewTransform().GetCameraToNDC(); |
|
|
|
|
|
if( fView.IsPerspective() ) |
|
|
{ |
|
|
// Want to scale down W and offset in Z without |
|
|
// changing values of x/w, y/w. This is just |
|
|
// minimal math for |
|
|
// Mproj' * p = Mscaletrans * Mproj * p |
|
|
// where Mscaletrans = |
|
|
// [ s 0 0 0 ] |
|
|
// [ 0 s 0 0 ] |
|
|
// [ 0 0 s 0 ] |
|
|
// [ 0 0 t s ] |
|
|
// Resulting matrix Mproj' is not exactly "Fog Friendly", |
|
|
// but is close enough. |
|
|
// Resulting point is [sx, sy, sz + tw, sw] and after divide |
|
|
// is [x/w, y/w, z/w + t/s, 1/sw] |
|
|
|
|
|
|
|
|
if( fSettings.fD3DCaps & kCapsWBuffer ) |
|
|
{ |
|
|
// W-buffering is only true w-buffering on 3dfx cards. On everything else, |
|
|
// they REALLY base it off the Z value. So we want to scale (but NOT translate) |
|
|
// the Z... |
|
|
// Note: the base value for perspLayerScale should be 0.001 for w-buffering, |
|
|
// not the normal 0.00001 |
|
|
float scale = 1.f - float(fCurrRenderLayer) * fTweaks.fPerspLayerScale; |
|
|
|
|
|
cam2ndc.fMap[0][0] *= scale; |
|
|
cam2ndc.fMap[1][1] *= scale; |
|
|
cam2ndc.fMap[2][2] *= scale; |
|
|
cam2ndc.fMap[3][2] *= scale; |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Z-buffering, so do it the traditional way |
|
|
float scale = 1.f - float(fCurrRenderLayer) * fTweaks.fPerspLayerScale; |
|
|
// scale = -1.f; |
|
|
float zTrans = -scale * float(fCurrRenderLayer) * fTweaks.fPerspLayerTrans; |
|
|
|
|
|
cam2ndc.fMap[0][0] *= scale; |
|
|
cam2ndc.fMap[1][1] *= scale; |
|
|
|
|
|
cam2ndc.fMap[2][2] *= scale; |
|
|
cam2ndc.fMap[2][2] += zTrans * cam2ndc.fMap[3][2]; |
|
|
cam2ndc.fMap[3][2] *= scale; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
plConst(float) kZTrans = -1.e-4f; |
|
|
cam2ndc.fMap[2][3] += kZTrans * fCurrRenderLayer; |
|
|
} |
|
|
|
|
|
return cam2ndc; |
|
|
} |
|
|
|
|
|
// IProjectionMatrixToD3D ////////////////////////////////////////////////////////// |
|
|
// Send the current camera to NDC transform to D3D. |
|
|
void plDXPipeline::IProjectionMatrixToD3D() |
|
|
{ |
|
|
D3DXMATRIX matProjection; |
|
|
|
|
|
IMatrix44ToD3DMatrix( matProjection, IGetCameraToNDC() ); |
|
|
|
|
|
fD3DDevice->SetTransform( D3DTS_PROJECTION, &matProjection ); |
|
|
fView.fXformResetFlags &= ~fView.kResetProjection; |
|
|
} |
|
|
|
|
|
//// ISetCullMode ///////////////////////////////////////////////////////////// |
|
|
// Tests and sets the current winding order cull mode (CW, CCW, or none). |
|
|
// Will reverse the cull mode as necessary for left handed camera or local to world |
|
|
// transforms. |
|
|
void plDXPipeline::ISetCullMode(hsBool flip) |
|
|
{ |
|
|
D3DCULL newCull = D3DCULL_NONE; |
|
|
|
|
|
if( !(fLayerState[0].fMiscFlags & hsGMatState::kMiscTwoSided) ) |
|
|
newCull = !IIsViewLeftHanded() ^ !flip ? D3DCULL_CW : D3DCULL_CCW; |
|
|
|
|
|
if( newCull != fCurrCullMode ) |
|
|
{ |
|
|
fCurrCullMode = newCull; |
|
|
fD3DDevice->SetRenderState( D3DRS_CULLMODE, fCurrCullMode ); |
|
|
} |
|
|
} |
|
|
|
|
|
//// ITransformsToD3D ////////////////////////////////////////////////////////// |
|
|
// Refreshes all transforms. Useful after popping renderTargets :) |
|
|
|
|
|
void plDXPipeline::ITransformsToD3D() |
|
|
{ |
|
|
hsBool resetCullMode = fView.fXformResetFlags & (fView.kResetCamera | fView.kResetL2W); |
|
|
|
|
|
if( fView.fXformResetFlags & fView.kResetCamera ) |
|
|
IWorldToCameraToD3D(); |
|
|
|
|
|
if( fView.fXformResetFlags & fView.kResetL2W ) |
|
|
ILocalToWorldToD3D(); |
|
|
|
|
|
if( fView.fXformResetFlags & fView.kResetProjection ) |
|
|
IProjectionMatrixToD3D(); |
|
|
} |
|
|
|
|
|
// ISetupVertexBufferRef ///////////////////////////////////////////////////////// |
|
|
// Initialize input vertex buffer ref according to source. |
|
|
void plDXPipeline::ISetupVertexBufferRef(plGBufferGroup* owner, UInt32 idx, plDXVertexBufferRef* vRef) |
|
|
{ |
|
|
// Initialize to nil, in case something goes wrong. |
|
|
vRef->fD3DBuffer = nil; |
|
|
|
|
|
UInt8 format = owner->GetVertexFormat(); |
|
|
|
|
|
// All indexed skinning is currently done on CPU, so the source data |
|
|
// will have indices, but we strip them out for the D3D buffer. |
|
|
if( format & plGBufferGroup::kSkinIndices ) |
|
|
{ |
|
|
format &= ~(plGBufferGroup::kSkinWeightMask | plGBufferGroup::kSkinIndices); |
|
|
format |= plGBufferGroup::kSkinNoWeights; // Should do nothing, but just in case... |
|
|
vRef->SetSkinned(true); |
|
|
vRef->SetVolatile(true); |
|
|
} |
|
|
|
|
|
UInt32 vertSize = IGetBufferFormatSize(format); // vertex stride |
|
|
UInt32 numVerts = owner->GetVertBufferCount(idx); |
|
|
|
|
|
vRef->fDevice = fD3DDevice; |
|
|
|
|
|
vRef->fOwner = owner; |
|
|
vRef->fCount = numVerts; |
|
|
vRef->fVertexSize = vertSize; |
|
|
vRef->fFormat = format; |
|
|
vRef->fRefTime = 0; |
|
|
|
|
|
vRef->SetDirty(true); |
|
|
vRef->SetRebuiltSinceUsed(true); |
|
|
vRef->fData = nil; |
|
|
|
|
|
vRef->SetVolatile(vRef->Volatile() || owner->AreVertsVolatile()); |
|
|
|
|
|
vRef->fIndex = idx; |
|
|
|
|
|
owner->SetVertexBufferRef(idx, vRef); |
|
|
hsRefCnt_SafeUnRef(vRef); |
|
|
} |
|
|
|
|
|
// ICheckStaticVertexBuffer /////////////////////////////////////////////////////////////////////// |
|
|
// Ensure a static vertex buffer has any D3D resources necessary for rendering created and filled |
|
|
// with proper vertex data. |
|
|
void plDXPipeline::ICheckStaticVertexBuffer(plDXVertexBufferRef* vRef, plGBufferGroup* owner, UInt32 idx) |
|
|
{ |
|
|
hsAssert(!vRef->Volatile(), "Creating a managed vertex buffer for a volatile buffer ref"); |
|
|
|
|
|
if( !vRef->fD3DBuffer ) |
|
|
{ |
|
|
// Okay, haven't done this one. |
|
|
|
|
|
DWORD fvfFormat = IGetBufferD3DFormat(vRef->fFormat); |
|
|
|
|
|
|
|
|
D3DPOOL poolType = D3DPOOL_MANAGED; |
|
|
// DWORD usage = D3DUSAGE_WRITEONLY; |
|
|
DWORD usage = 0; |
|
|
const int numVerts = vRef->fCount; |
|
|
const int vertSize = vRef->fVertexSize; |
|
|
fManagedAlloced = true; |
|
|
if( FAILED( fD3DDevice->CreateVertexBuffer( numVerts * vertSize, |
|
|
usage, |
|
|
fvfFormat, |
|
|
poolType, |
|
|
&vRef->fD3DBuffer, NULL) ) ) |
|
|
{ |
|
|
hsAssert( false, "CreateVertexBuffer() call failed!" ); |
|
|
vRef->fD3DBuffer = nil; |
|
|
return; |
|
|
} |
|
|
PROFILE_POOL_MEM(poolType, numVerts * vertSize, true, "VtxBuff"); |
|
|
|
|
|
// Record that we've allocated this into managed memory, in case we're |
|
|
// fighting that NVidia driver bug. Search for OSVERSION for mor info. |
|
|
AllocManagedVertex(numVerts * vertSize); |
|
|
|
|
|
// Fill in the vertex data. |
|
|
IFillStaticVertexBufferRef(vRef, owner, idx); |
|
|
|
|
|
// This is currently a no op, but this would let the buffer know it can |
|
|
// unload the system memory copy, since we have a managed version now. |
|
|
owner->PurgeVertBuffer(idx); |
|
|
} |
|
|
} |
|
|
|
|
|
// IFillStaticVertexBufferRef ////////////////////////////////////////////////// |
|
|
// BufferRef is set up, just copy the data in. |
|
|
// This is uglied up hugely by the insane non-interleaved data case with cells |
|
|
// and whatever else. |
|
|
void plDXPipeline::IFillStaticVertexBufferRef(plDXVertexBufferRef *ref, plGBufferGroup *group, UInt32 idx) |
|
|
{ |
|
|
IDirect3DVertexBuffer9* vertexBuff = ref->fD3DBuffer; |
|
|
|
|
|
if( !vertexBuff ) |
|
|
{ |
|
|
// We most likely already warned about this earlier, best to just quietly return now |
|
|
return; |
|
|
} |
|
|
|
|
|
const UInt32 vertSize = ref->fVertexSize; |
|
|
const UInt32 vertStart = group->GetVertBufferStart(idx) * vertSize; |
|
|
const UInt32 size = group->GetVertBufferEnd(idx) * vertSize - vertStart; |
|
|
if( !size ) |
|
|
return; |
|
|
|
|
|
/// Lock the buffer |
|
|
UInt8* ptr; |
|
|
if( FAILED( vertexBuff->Lock( vertStart, size, (void **)&ptr, group->AreVertsVolatile() ? D3DLOCK_DISCARD : 0 ) ) ) |
|
|
{ |
|
|
hsAssert( false, "Failed to lock vertex buffer for writing" ); |
|
|
} |
|
|
|
|
|
if( ref->fData ) |
|
|
{ |
|
|
memcpy(ptr, ref->fData + vertStart, size); |
|
|
} |
|
|
else |
|
|
{ |
|
|
hsAssert(0 == vertStart, "Offsets on non-interleaved data not supported"); |
|
|
hsAssert(group->GetVertBufferCount(idx) * vertSize == size, "Trailing dead space on non-interleaved data not supported"); |
|
|
|
|
|
const UInt32 vertSmallSize = group->GetVertexLiteStride() - sizeof( hsPoint3 ) * 2; |
|
|
UInt8* srcVPtr = group->GetVertBufferData(idx); |
|
|
plGBufferColor* const srcCPtr = group->GetColorBufferData( idx ); |
|
|
|
|
|
const int numCells = group->GetNumCells(idx); |
|
|
int i; |
|
|
for( i = 0; i < numCells; i++ ) |
|
|
{ |
|
|
plGBufferCell *cell = group->GetCell( idx, i ); |
|
|
|
|
|
if( cell->fColorStart == (UInt32)-1 ) |
|
|
{ |
|
|
/// Interleaved, do straight copy |
|
|
memcpy( ptr, srcVPtr + cell->fVtxStart, cell->fLength * vertSize ); |
|
|
ptr += cell->fLength * vertSize; |
|
|
} |
|
|
else |
|
|
{ |
|
|
/// Separated, gotta interleave |
|
|
UInt8* tempVPtr = srcVPtr + cell->fVtxStart; |
|
|
plGBufferColor* tempCPtr = srcCPtr + cell->fColorStart; |
|
|
int j; |
|
|
for( j = 0; j < cell->fLength; j++ ) |
|
|
{ |
|
|
memcpy( ptr, tempVPtr, sizeof( hsPoint3 ) * 2 ); |
|
|
ptr += sizeof( hsPoint3 ) * 2; |
|
|
tempVPtr += sizeof( hsPoint3 ) * 2; |
|
|
|
|
|
memcpy( ptr, &tempCPtr->fDiffuse, sizeof( UInt32 ) ); |
|
|
ptr += sizeof( UInt32 ); |
|
|
memcpy( ptr, &tempCPtr->fSpecular, sizeof( UInt32 ) ); |
|
|
ptr += sizeof( UInt32 ); |
|
|
|
|
|
memcpy( ptr, tempVPtr, vertSmallSize ); |
|
|
ptr += vertSmallSize; |
|
|
tempVPtr += vertSmallSize; |
|
|
tempCPtr++; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/// Unlock and clean up |
|
|
vertexBuff->Unlock(); |
|
|
ref->SetRebuiltSinceUsed(true); |
|
|
ref->SetDirty(false); |
|
|
} |
|
|
|
|
|
// OpenAccess //////////////////////////////////////////////////////////////////////////////////////// |
|
|
// Lock the managed buffer and setup the accessSpan to point into the buffers data. |
|
|
hsBool plDXPipeline::OpenAccess(plAccessSpan& dst, plDrawableSpans* drawable, const plVertexSpan* span, hsBool readOnly) |
|
|
{ |
|
|
plGBufferGroup* grp = drawable->GetBufferGroup(span->fGroupIdx); |
|
|
hsAssert(!grp->AreVertsVolatile(), "Don't ask for D3DBuffer data on a volatile buffer"); |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)grp->GetVertexBufferRef(span->fVBufferIdx); |
|
|
if( !vRef ) |
|
|
{ |
|
|
dst.SetType(plAccessSpan::kUndefined); |
|
|
return false; |
|
|
} |
|
|
|
|
|
IDirect3DVertexBuffer9* vertexBuff = vRef->fD3DBuffer; |
|
|
if( !vertexBuff ) |
|
|
{ |
|
|
dst.SetType(plAccessSpan::kUndefined); |
|
|
return false; |
|
|
} |
|
|
|
|
|
const UInt32 stride = vRef->fVertexSize; |
|
|
const UInt32 vertStart = span->fVStartIdx * stride; |
|
|
const UInt32 size = span->fVLength * stride; |
|
|
|
|
|
if( !size ) |
|
|
{ |
|
|
dst.SetType(plAccessSpan::kUndefined); |
|
|
return false; |
|
|
} |
|
|
|
|
|
DWORD lockFlags = readOnly ? D3DLOCK_READONLY : 0; |
|
|
|
|
|
UInt8* ptr; |
|
|
if( FAILED( vertexBuff->Lock(vertStart, size, (void **)&ptr, lockFlags) ) ) |
|
|
{ |
|
|
hsAssert( false, "Failed to lock vertex buffer for writing" ); |
|
|
dst.SetType(plAccessSpan::kUndefined); |
|
|
return false; |
|
|
} |
|
|
|
|
|
plAccessVtxSpan& acc = dst.AccessVtx(); |
|
|
|
|
|
acc.SetVertCount((UInt16)(span->fVLength)); |
|
|
|
|
|
Int32 offset = (-(Int32)(span->fVStartIdx)) * ((Int32)stride); |
|
|
|
|
|
acc.PositionStream(ptr, (UInt16)stride, offset); |
|
|
ptr += sizeof(hsPoint3); |
|
|
|
|
|
int numWgts = grp->GetNumWeights(); |
|
|
if( numWgts ) |
|
|
{ |
|
|
acc.SetNumWeights(numWgts); |
|
|
acc.WeightStream(ptr, (UInt16)stride, offset); |
|
|
ptr += numWgts * sizeof(hsScalar); |
|
|
if( grp->GetVertexFormat() & plGBufferGroup::kSkinIndices ) |
|
|
{ |
|
|
acc.WgtIndexStream(ptr, (UInt16)stride, offset); |
|
|
ptr += sizeof(UInt32); |
|
|
} |
|
|
else |
|
|
{ |
|
|
acc.WgtIndexStream(nil, 0, offset); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
acc.SetNumWeights(0); |
|
|
} |
|
|
|
|
|
acc.NormalStream(ptr, (UInt16)stride, offset); |
|
|
ptr += sizeof(hsVector3); |
|
|
|
|
|
acc.DiffuseStream(ptr, (UInt16)stride, offset); |
|
|
ptr += sizeof(UInt32); |
|
|
|
|
|
acc.SpecularStream(ptr, (UInt16)stride, offset); |
|
|
ptr += sizeof(UInt32); |
|
|
|
|
|
acc.UVWStream(ptr, (UInt16)stride, offset); |
|
|
|
|
|
acc.SetNumUVWs(grp->GetNumUVs()); |
|
|
|
|
|
acc.SetVtxDeviceRef(vRef); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// CloseAccess ///////////////////////////////////////////////////////////////////// |
|
|
// Unlock the buffer, invalidating the accessSpan. |
|
|
hsBool plDXPipeline::CloseAccess(plAccessSpan& dst) |
|
|
{ |
|
|
if( !dst.HasAccessVtx() ) |
|
|
return false; |
|
|
|
|
|
plAccessVtxSpan& acc = dst.AccessVtx(); |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)acc.GetVtxDeviceRef(); |
|
|
if( !vRef ) |
|
|
return false; |
|
|
|
|
|
IDirect3DVertexBuffer9* vertexBuff = vRef->fD3DBuffer; |
|
|
if( !vertexBuff ) |
|
|
return false; |
|
|
|
|
|
vertexBuff->Unlock(); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// CheckVertexBufferRef ///////////////////////////////////////////////////// |
|
|
// Make sure the buffer group has a valid buffer ref and that it is up to date. |
|
|
void plDXPipeline::CheckVertexBufferRef(plGBufferGroup* owner, UInt32 idx) |
|
|
{ |
|
|
// First, do we have a device ref at this index? |
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)owner->GetVertexBufferRef(idx); |
|
|
// If not |
|
|
if( !vRef ) |
|
|
{ |
|
|
// Make the blank ref |
|
|
vRef = TRACKED_NEW plDXVertexBufferRef; |
|
|
|
|
|
ISetupVertexBufferRef(owner, idx, vRef); |
|
|
|
|
|
} |
|
|
if( !vRef->IsLinked() ) |
|
|
vRef->Link( &fVtxBuffRefList ); |
|
|
|
|
|
// One way or another, we now have a vbufferref[idx] in owner. |
|
|
// Now, does it need to be (re)filled? |
|
|
// If the owner is volatile, then we hold off. It might not |
|
|
// be visible, and we might need to refill it again if we |
|
|
// have an overrun of our dynamic D3D buffer. |
|
|
if( !vRef->Volatile() ) |
|
|
{ |
|
|
if( fAllocUnManaged ) |
|
|
return; |
|
|
|
|
|
// If it's a static buffer, allocate a D3D vertex buffer for it. Otherwise, it'll |
|
|
// be sharing the global D3D dynamic buffer, and marked as volatile. |
|
|
ICheckStaticVertexBuffer(vRef, owner, idx); |
|
|
|
|
|
// Might want to remove this assert, and replace it with a dirty check if |
|
|
// we have static buffers that change very seldom rather than never. |
|
|
hsAssert(!vRef->IsDirty(), "Non-volatile vertex buffers should never get dirty"); |
|
|
} |
|
|
else |
|
|
{ |
|
|
// Make sure we're going to be ready to fill it. |
|
|
|
|
|
if( !vRef->fData && (vRef->fFormat != owner->GetVertexFormat()) ) |
|
|
{ |
|
|
vRef->fData = TRACKED_NEW UInt8[vRef->fCount * vRef->fVertexSize]; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// CheckIndexBufferRef ///////////////////////////////////////////////////// |
|
|
// Make sure the buffer group has an index buffer ref and that its data is current. |
|
|
void plDXPipeline::CheckIndexBufferRef(plGBufferGroup* owner, UInt32 idx) |
|
|
{ |
|
|
plDXIndexBufferRef* iRef = (plDXIndexBufferRef*)owner->GetIndexBufferRef(idx); |
|
|
if( !iRef ) |
|
|
{ |
|
|
// Create one from scratch. |
|
|
|
|
|
iRef = TRACKED_NEW plDXIndexBufferRef; |
|
|
|
|
|
ISetupIndexBufferRef(owner, idx, iRef); |
|
|
|
|
|
} |
|
|
if( !iRef->IsLinked() ) |
|
|
iRef->Link(&fIdxBuffRefList); |
|
|
|
|
|
// Make sure it has all D3D resources created. |
|
|
ICheckIndexBuffer(iRef); |
|
|
|
|
|
// If it's dirty, refill it. |
|
|
if( iRef->IsDirty() ) |
|
|
IFillIndexBufferRef(iRef, owner, idx); |
|
|
} |
|
|
|
|
|
// IFillIndexBufferRef //////////////////////////////////////////////////////////// |
|
|
// Refresh the D3D index buffer from the plasma index buffer. |
|
|
void plDXPipeline::IFillIndexBufferRef(plDXIndexBufferRef* iRef, plGBufferGroup* owner, UInt32 idx) |
|
|
{ |
|
|
UInt32 startIdx = owner->GetIndexBufferStart(idx); |
|
|
UInt32 size = (owner->GetIndexBufferEnd(idx) - startIdx) * sizeof(UInt16); |
|
|
if( !size ) |
|
|
return; |
|
|
|
|
|
DWORD lockFlags = iRef->Volatile() ? D3DLOCK_DISCARD : 0; |
|
|
UInt16* destPtr = nil; |
|
|
if( FAILED( iRef->fD3DBuffer->Lock(startIdx * sizeof(UInt16), size, (void **)&destPtr, lockFlags) ) ) |
|
|
{ |
|
|
hsAssert( false, "Cannot lock index buffer for writing" ); |
|
|
return; |
|
|
} |
|
|
|
|
|
memcpy( destPtr, owner->GetIndexBufferData(idx) + startIdx, size ); |
|
|
|
|
|
iRef->fD3DBuffer->Unlock(); |
|
|
|
|
|
iRef->SetDirty( false ); |
|
|
|
|
|
} |
|
|
|
|
|
// ICheckIndexBuffer //////////////////////////////////////////////////////// |
|
|
// Make sure index buffer ref has any D3D resources it needs. |
|
|
void plDXPipeline::ICheckIndexBuffer(plDXIndexBufferRef* iRef) |
|
|
{ |
|
|
if( !iRef->fD3DBuffer && iRef->fCount ) |
|
|
{ |
|
|
D3DPOOL poolType = fAllocUnManaged ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; |
|
|
DWORD usage = D3DUSAGE_WRITEONLY; |
|
|
iRef->SetVolatile(false); |
|
|
if( FAILED( fD3DDevice->CreateIndexBuffer( sizeof( UInt16 ) * iRef->fCount, |
|
|
usage, |
|
|
D3DFMT_INDEX16, |
|
|
poolType, |
|
|
&iRef->fD3DBuffer, NULL) ) ) |
|
|
{ |
|
|
hsAssert( false, "CreateIndexBuffer() call failed!" ); |
|
|
iRef->fD3DBuffer = nil; |
|
|
return; |
|
|
} |
|
|
PROFILE_POOL_MEM(poolType, sizeof(UInt16) * iRef->fCount, true, "IndexBuff"); |
|
|
|
|
|
iRef->fPoolType = poolType; |
|
|
iRef->SetDirty(true); |
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
} |
|
|
|
|
|
// ISetupIndexBufferRef //////////////////////////////////////////////////////////////// |
|
|
// Initialize the index buffer ref, but don't create anything for it. |
|
|
void plDXPipeline::ISetupIndexBufferRef(plGBufferGroup* owner, UInt32 idx, plDXIndexBufferRef* iRef) |
|
|
{ |
|
|
UInt32 numIndices = owner->GetIndexBufferCount(idx); |
|
|
iRef->fCount = numIndices; |
|
|
iRef->fOwner = owner; |
|
|
iRef->fIndex = idx; |
|
|
iRef->fRefTime = 0; |
|
|
|
|
|
iRef->SetDirty(true); |
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
|
|
|
owner->SetIndexBufferRef(idx, iRef); |
|
|
hsRefCnt_SafeUnRef(iRef); |
|
|
|
|
|
iRef->SetVolatile(owner->AreIdxVolatile()); |
|
|
} |
|
|
|
|
|
//// ISoftwareVertexBlend /////////////////////////////////////////////////////// |
|
|
// Emulate matrix palette operations in software. The big difference between the hardware |
|
|
// and software versions is we only want to lock the vertex buffer once and blend all the |
|
|
// verts we're going to in software, so the vertex blend happens once for an entire drawable. |
|
|
// In hardware, we want the opposite, to break it into managable chunks, manageable meaning |
|
|
// few enough matrices to fit into hardware registers. So for hardware version, we set up |
|
|
// our palette, draw a span or few, setup our matrix palette with new matrices, draw, repeat. |
|
|
hsBool plDXPipeline::ISoftwareVertexBlend( plDrawableSpans* drawable, const hsTArray<Int16>& visList ) |
|
|
{ |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoSkinning)) |
|
|
return true; |
|
|
|
|
|
if( drawable->GetSkinTime() == fRenderCnt ) |
|
|
return true; |
|
|
|
|
|
const hsBitVector &blendBits = drawable->GetBlendingSpanVector(); |
|
|
|
|
|
if( drawable->GetBlendingSpanVector().Empty() ) |
|
|
{ |
|
|
// This sucker doesn't have any skinning spans anyway. Just return |
|
|
drawable->SetSkinTime( fRenderCnt ); |
|
|
return true; |
|
|
} |
|
|
|
|
|
plProfile_BeginTiming(Skin); |
|
|
|
|
|
// lock the data buffer |
|
|
|
|
|
// First, figure out which buffers we need to blend. |
|
|
const int kMaxBufferGroups = 20; |
|
|
const int kMaxVertexBuffers = 20; |
|
|
static char blendBuffers[kMaxBufferGroups][kMaxVertexBuffers]; |
|
|
memset(blendBuffers, 0, kMaxBufferGroups * kMaxVertexBuffers * sizeof(**blendBuffers)); |
|
|
|
|
|
hsAssert(kMaxBufferGroups >= drawable->GetNumBufferGroups(), "Bigger than we counted on num groups skin."); |
|
|
|
|
|
const hsTArray<plSpan *>& spans = drawable->GetSpanArray(); |
|
|
int i; |
|
|
for( i = 0; i < visList.GetCount(); i++ ) |
|
|
{ |
|
|
if( blendBits.IsBitSet( visList[ i ] ) ) |
|
|
{ |
|
|
const plVertexSpan &vSpan = *(plVertexSpan *)spans[visList[i]]; |
|
|
hsAssert(kMaxVertexBuffers > vSpan.fVBufferIdx, "Bigger than we counted on num buffers skin."); |
|
|
|
|
|
blendBuffers[vSpan.fGroupIdx][vSpan.fVBufferIdx] = 1; |
|
|
drawable->SetBlendingSpanVectorBit( visList[ i ], false ); |
|
|
} |
|
|
} |
|
|
|
|
|
// Now go through each of the group/buffer (= a real vertex buffer) pairs we found, |
|
|
// and blend into it. We'll lock the buffer once, and then for each span that |
|
|
// uses it, set the matrix palette and and then do the blend for that span. |
|
|
// When we've done all the spans for a group/buffer, we unlock it and move on. |
|
|
int j; |
|
|
for( i = 0; i < kMaxBufferGroups; i++ ) |
|
|
{ |
|
|
for( j = 0; j < kMaxVertexBuffers; j++ ) |
|
|
{ |
|
|
if( blendBuffers[i][j] ) |
|
|
{ |
|
|
// Found one. Do the lock. |
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)drawable->GetVertexRef(i, j); |
|
|
|
|
|
hsAssert(vRef->fData, "Going into skinning with no place to put results!"); |
|
|
|
|
|
UInt8* destPtr = vRef->fData; |
|
|
|
|
|
int k; |
|
|
for( k = 0; k < visList.GetCount(); k++ ) |
|
|
{ |
|
|
const plIcicle& span = *(plIcicle*)spans[visList[k]]; |
|
|
if( (span.fGroupIdx == i)&&(span.fVBufferIdx == j) ) |
|
|
{ |
|
|
plProfile_Inc(NumSkin); |
|
|
|
|
|
hsMatrix44* matrixPalette = drawable->GetMatrixPalette(span.fBaseMatrix); |
|
|
matrixPalette[0] = span.fLocalToWorld; |
|
|
|
|
|
UInt8* ptr = vRef->fOwner->GetVertBufferData(vRef->fIndex); |
|
|
ptr += span.fVStartIdx * vRef->fOwner->GetVertexSize(); |
|
|
IBlendVertsIntoBuffer( (plSpan*)&span, |
|
|
matrixPalette, span.fNumMatrices, |
|
|
ptr, |
|
|
vRef->fOwner->GetVertexFormat(), |
|
|
vRef->fOwner->GetVertexSize(), |
|
|
destPtr + span.fVStartIdx * vRef->fVertexSize, |
|
|
vRef->fVertexSize, |
|
|
span.fVLength, |
|
|
span.fLocalUVWChans ); |
|
|
vRef->SetDirty(true); |
|
|
} |
|
|
} |
|
|
// Unlock and move on. |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
plProfile_EndTiming(Skin); |
|
|
|
|
|
if( drawable->GetBlendingSpanVector().Empty() ) |
|
|
{ |
|
|
// Only do this if we've blended ALL of the spans. Thus, this becomes a trivial |
|
|
// rejection for all the skinning flags being cleared |
|
|
drawable->SetSkinTime(fRenderCnt); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// IBeginAllocUnmanaged /////////////////////////////////////////////////////////////////// |
|
|
// Before allocating anything into POOL_DEFAULT, we must evict managed memory. |
|
|
// See LoadResources. |
|
|
void plDXPipeline::IBeginAllocUnManaged() |
|
|
{ |
|
|
// Flush out all managed resources to make room for unmanaged resources. |
|
|
fD3DDevice->EvictManagedResources(); |
|
|
fEvictTime = fTextUseTime; |
|
|
fManagedSeen = 0; |
|
|
|
|
|
fManagedAlloced = false; |
|
|
fAllocUnManaged = true; // we're currently only allocating POOL_DEFAULT |
|
|
} |
|
|
|
|
|
// IEndAllocUnManged. |
|
|
// Before allocating anything into POOL_DEFAULT, we must evict managed memory. |
|
|
// See LoadResources. |
|
|
void plDXPipeline::IEndAllocUnManaged() |
|
|
{ |
|
|
fAllocUnManaged = false; |
|
|
|
|
|
// Flush the (should be empty) resource manager to reset its internal allocation pool. |
|
|
fD3DDevice->EvictManagedResources(); |
|
|
fEvictTime = fTextUseTime; |
|
|
fManagedSeen = 0; |
|
|
} |
|
|
|
|
|
// ICheckTextureUsage //////////////////////////////////////////////////////////////////// |
|
|
// Obsolete, unused. |
|
|
// Deletes textures LRU to try to get around NVidia memory manager bug. Found a |
|
|
// more robust/efficent way. Besides, it didn't help. See OSVERSION. |
|
|
void plDXPipeline::ICheckTextureUsage() |
|
|
{ |
|
|
plProfile_IncCount(fTexUsed, fTexUsed); |
|
|
plProfile_IncCount(fTexManaged, fTexManaged); |
|
|
|
|
|
plConst(UInt32) kMinTexManaged(5000000); |
|
|
if( fTexManaged < kMinTexManaged ) |
|
|
return; |
|
|
|
|
|
plConst(UInt32) kScale(2); |
|
|
if( fTexUsed * kScale < fTexManaged ) |
|
|
{ |
|
|
// Find the stalest |
|
|
UInt32 stalest = fTextUseTime; |
|
|
plDXTextureRef* ref = fTextureRefList; |
|
|
while( ref ) |
|
|
{ |
|
|
// I don't know if render targets even get put in this list. |
|
|
if( !(ref->GetFlags() & plDXTextureRef::kRenderTarget) && (ref->fUseTime < stalest) ) |
|
|
stalest = ref->fUseTime; |
|
|
ref = ref->GetNext(); |
|
|
} |
|
|
stalest = fTextUseTime - stalest; |
|
|
|
|
|
// If the stalest is fresh, live with thrashing |
|
|
plConst(UInt32) kMinAge(60); |
|
|
if( stalest < kMinAge ) |
|
|
return; |
|
|
|
|
|
// Kill the stalest, and everything more than half as stale |
|
|
stalest /= 2; |
|
|
if( stalest < kMinAge ) |
|
|
stalest = kMinAge; |
|
|
|
|
|
stalest = fTextUseTime - stalest; |
|
|
|
|
|
// Go through again slaughtering left and right |
|
|
ref = fTextureRefList; |
|
|
while( ref ) |
|
|
{ |
|
|
if( !(ref->GetFlags() & plDXTextureRef::kRenderTarget) && (ref->fUseTime < stalest) ) |
|
|
{ |
|
|
plDXTextureRef* nuke = ref; |
|
|
ref = ref->GetNext(); |
|
|
nuke->Release(); |
|
|
nuke->Unlink(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ref = ref->GetNext(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// ICheckVtxUsage //////////////////////////////////////////////////////////////////// |
|
|
// Obsolete, unused. |
|
|
// Deletes textures LRU to try to get around NVidia memory manager bug. Found a |
|
|
// more robust/efficent way. Besides, it didn't help. See OSVERSION. |
|
|
void plDXPipeline::ICheckVtxUsage() |
|
|
{ |
|
|
plProfile_IncCount(fVtxUsed, fVtxUsed); |
|
|
plProfile_IncCount(fVtxManaged, fVtxManaged); |
|
|
|
|
|
plConst(UInt32) kMinVtxManaged(5000000); |
|
|
if( fVtxManaged < kMinVtxManaged ) |
|
|
return; |
|
|
|
|
|
plConst(UInt32) kScale(2); |
|
|
if( fVtxUsed * kScale < fVtxManaged ) |
|
|
{ |
|
|
// Find the stalest |
|
|
UInt32 stalest = fTextUseTime; |
|
|
plDXVertexBufferRef* ref = fVtxBuffRefList; |
|
|
while( ref ) |
|
|
{ |
|
|
if( !ref->Volatile() && (ref->fUseTime < stalest) ) |
|
|
stalest = ref->fUseTime; |
|
|
ref = ref->GetNext(); |
|
|
} |
|
|
stalest = fTextUseTime - stalest; |
|
|
|
|
|
// If the stalest is fresh, live with thrashing |
|
|
plConst(UInt32) kMinAge(60); |
|
|
if( stalest < kMinAge ) |
|
|
return; |
|
|
|
|
|
// Kill the stalest, and everything more than half as stale |
|
|
stalest /= 2; |
|
|
if( stalest < kMinAge ) |
|
|
stalest = kMinAge; |
|
|
|
|
|
stalest = fTextUseTime - stalest; |
|
|
|
|
|
// Go through again slaughtering left and right |
|
|
ref = fVtxBuffRefList; |
|
|
while( ref ) |
|
|
{ |
|
|
if( !ref->Volatile() && (ref->fUseTime < stalest) ) |
|
|
{ |
|
|
plDXVertexBufferRef* nuke = ref; |
|
|
ref = ref->GetNext(); |
|
|
nuke->Release(); |
|
|
nuke->Unlink(); |
|
|
} |
|
|
else |
|
|
{ |
|
|
ref = ref->GetNext(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
hsBool plDXPipeline::CheckResources() |
|
|
{ |
|
|
if ((fClothingOutfits.GetCount() <= 1 && fAvRTPool.GetCount() > 1) || |
|
|
(fAvRTPool.GetCount() >= 16 && (fAvRTPool.GetCount() / 2 >= fClothingOutfits.GetCount()))) |
|
|
{ |
|
|
return (hsTimer::GetSysSeconds() - fAvRTShrinkValidSince > kAvTexPoolShrinkThresh); |
|
|
} |
|
|
|
|
|
fAvRTShrinkValidSince = hsTimer::GetSysSeconds(); |
|
|
return (fAvRTPool.GetCount() < fClothingOutfits.GetCount()); |
|
|
} |
|
|
|
|
|
// LoadResources /////////////////////////////////////////////////////////////////////// |
|
|
// Basically, we have to load anything that goes into POOL_DEFAULT before |
|
|
// anything into POOL_MANAGED, or the memory manager gets confused. |
|
|
// More precisely, we have to evict everything from POOL_MANAGED before we |
|
|
// can allocate anything into POOL_DEFAULT. |
|
|
// So, this function frees up everything in POOL_DEFAULT, evicts managed memory, |
|
|
// calls out for anything needing to be created POOL_DEFAULT to do so, |
|
|
// Then we're free to load into POOL_MANAGED on demand. |
|
|
// This is typically called at the beginning of the first render after loading |
|
|
// a new age. |
|
|
void plDXPipeline::LoadResources() |
|
|
{ |
|
|
hsStatusMessageF("Begin Device Reload t=%f",hsTimer::GetSeconds()); |
|
|
plNetClientApp::StaticDebugMsg("Begin Device Reload"); |
|
|
|
|
|
// Just to be safe. |
|
|
IInitDeviceState(); // 9700 THRASH |
|
|
|
|
|
// Evict mananged memory. |
|
|
IBeginAllocUnManaged(); |
|
|
|
|
|
// Release everything we have in POOL_DEFAULT. |
|
|
IReleaseDynamicBuffers(); |
|
|
IReleaseAvRTPool(); |
|
|
|
|
|
// Create all RenderTargets |
|
|
plPipeRTMakeMsg* rtMake = TRACKED_NEW plPipeRTMakeMsg(this); |
|
|
rtMake->Send(); |
|
|
|
|
|
// Create all our shadow render targets and pipeline specific POOL_DEFAULT vertex buffers. |
|
|
// This includes our single dynamic vertex buffer that we cycle through for software |
|
|
// skinned, particle systems, etc. |
|
|
ICreateDynamicBuffers(); |
|
|
|
|
|
// Create all POOL_DEFAULT (sorted) index buffers in the scene. |
|
|
plPipeGeoMakeMsg* defMake = TRACKED_NEW plPipeGeoMakeMsg(this, true); |
|
|
defMake->Send(); |
|
|
|
|
|
// This can be a bit of a mem hog and will use more mem if available, so keep it last in the |
|
|
// POOL_DEFAULT allocs. |
|
|
IFillAvRTPool(); |
|
|
|
|
|
// We should have everything POOL_DEFAULT we need now. |
|
|
IEndAllocUnManaged(); |
|
|
|
|
|
// Force a create of all our static D3D vertex buffers. |
|
|
#define MF_PRELOAD_MANAGEDBUFFERS |
|
|
#ifdef MF_PRELOAD_MANAGEDBUFFERS |
|
|
plPipeGeoMakeMsg* manMake = TRACKED_NEW plPipeGeoMakeMsg(this, false); |
|
|
manMake->Send(); |
|
|
#endif // MF_PRELOAD_MANAGEDBUFFERS |
|
|
|
|
|
// Forcing a preload of textures turned out to not be so great, |
|
|
// since there are typically so many in an age, it swamped out |
|
|
// VM. |
|
|
#ifdef MF_TOSSER |
|
|
#define MF_PRELOAD_TEXTURES |
|
|
#endif // MF_TOSSER |
|
|
#ifdef MF_PRELOAD_TEXTURES |
|
|
plPipeTexMakeMsg* texMake = TRACKED_NEW plPipeTexMakeMsg(this); |
|
|
texMake->Send(); |
|
|
#endif // MF_PRELOAD_TEXTURES |
|
|
|
|
|
fD3DDevice->EvictManagedResources(); |
|
|
|
|
|
// Okay, we've done it, clear the request. |
|
|
plPipeResReq::Clear(); |
|
|
|
|
|
plProfile_IncCount(PipeReload, 1); |
|
|
|
|
|
hsStatusMessageF("End Device Reload t=%f",hsTimer::GetSeconds()); |
|
|
plNetClientApp::StaticDebugMsg("End Device Reload"); |
|
|
} |
|
|
|
|
|
// Sorry about this, but it really did speed up the skinning. |
|
|
// Just some macros for the inner loop of IBlendVertsIntoBuffer. |
|
|
#define MATRIXMULTBEGIN(xfm, wgt) \ |
|
|
register float m00 = xfm.fMap[0][0]; \ |
|
|
register float m01 = xfm.fMap[0][1]; \ |
|
|
register float m02 = xfm.fMap[0][2]; \ |
|
|
register float m03 = xfm.fMap[0][3]; \ |
|
|
register float m10 = xfm.fMap[1][0]; \ |
|
|
register float m11 = xfm.fMap[1][1]; \ |
|
|
register float m12 = xfm.fMap[1][2]; \ |
|
|
register float m13 = xfm.fMap[1][3]; \ |
|
|
register float m20 = xfm.fMap[2][0]; \ |
|
|
register float m21 = xfm.fMap[2][1]; \ |
|
|
register float m22 = xfm.fMap[2][2]; \ |
|
|
register float m23 = xfm.fMap[2][3]; \ |
|
|
register float m_wgt = wgt; \ |
|
|
register float srcX, srcY, srcZ; |
|
|
|
|
|
#define MATRIXMULTPOINTADD(dst, src) \ |
|
|
srcX = src.fX; \ |
|
|
srcY = src.fY; \ |
|
|
srcZ = src.fZ; \ |
|
|
\ |
|
|
dst.fX += (srcX * m00 + srcY * m01 + srcZ * m02 + m03) * m_wgt; \ |
|
|
dst.fY += (srcX * m10 + srcY * m11 + srcZ * m12 + m13) * m_wgt; \ |
|
|
dst.fZ += (srcX * m20 + srcY * m21 + srcZ * m22 + m23) * m_wgt; |
|
|
|
|
|
#define MATRIXMULTVECTORADD(dst, src) \ |
|
|
srcX = src.fX; \ |
|
|
srcY = src.fY; \ |
|
|
srcZ = src.fZ; \ |
|
|
\ |
|
|
dst.fX += (srcX * m00 + srcY * m01 + srcZ * m02) * m_wgt; \ |
|
|
dst.fY += (srcX * m10 + srcY * m11 + srcZ * m12) * m_wgt; \ |
|
|
dst.fZ += (srcX * m20 + srcY * m21 + srcZ * m22) * m_wgt; |
|
|
|
|
|
// inlTESTPOINT ///////////////////////////////////////// |
|
|
// Update mins and maxs if destP is outside. |
|
|
inline void inlTESTPOINT(const hsPoint3& destP, |
|
|
hsScalar& minX, hsScalar& minY, hsScalar& minZ, |
|
|
hsScalar& maxX, hsScalar& maxY, hsScalar& maxZ) |
|
|
{ |
|
|
if( destP.fX < minX ) |
|
|
minX = destP.fX; |
|
|
else if( destP.fX > maxX ) |
|
|
maxX = destP.fX; |
|
|
|
|
|
if( destP.fY < minY ) |
|
|
minY = destP.fY; |
|
|
else if( destP.fY > maxY ) |
|
|
maxY = destP.fY; |
|
|
|
|
|
if( destP.fZ < minZ ) |
|
|
minZ = destP.fZ; |
|
|
else if( destP.fZ > maxZ ) |
|
|
maxZ = destP.fZ; |
|
|
} |
|
|
|
|
|
//// IBlendVertsIntoBuffer //////////////////////////////////////////////////// |
|
|
// Given a pointer into a buffer of verts that have blending data in the D3D |
|
|
// format, blends them into the destination buffer given without the blending |
|
|
// info. |
|
|
|
|
|
void plDXPipeline::IBlendVertsIntoBuffer( plSpan* span, |
|
|
hsMatrix44* matrixPalette, int numMatrices, |
|
|
const UInt8 *src, UInt8 format, UInt32 srcStride, |
|
|
UInt8 *dest, UInt32 destStride, UInt32 count, |
|
|
UInt16 localUVWChans ) |
|
|
{ |
|
|
UInt8 numUVs, numWeights; |
|
|
UInt32 i, j, indices, color, specColor, uvChanSize; |
|
|
float weights[ 4 ], weightSum; |
|
|
hsPoint3 pt, tempPt, destPt; |
|
|
hsVector3 vec, tempNorm, destNorm; |
|
|
|
|
|
|
|
|
/// Get some counts |
|
|
switch( format & plGBufferGroup::kSkinWeightMask ) |
|
|
{ |
|
|
case plGBufferGroup::kSkin1Weight: numWeights = 1; break; |
|
|
case plGBufferGroup::kSkin2Weights: numWeights = 2; break; |
|
|
case plGBufferGroup::kSkin3Weights: numWeights = 3; break; |
|
|
default: hsAssert( false, "Invalid weight count in IBlendVertsIntoBuffer()" ); |
|
|
} |
|
|
|
|
|
numUVs = plGBufferGroup::CalcNumUVs( format ); |
|
|
uvChanSize = numUVs * sizeof( float ) * 3; |
|
|
|
|
|
//#define MF_RECALC_BOUNDS |
|
|
#ifdef MF_RECALC_BOUNDS |
|
|
hsScalar minX = 1.e33f; |
|
|
hsScalar minY = 1.e33f; |
|
|
hsScalar minZ = 1.e33f; |
|
|
|
|
|
hsScalar maxX = -1.e33f; |
|
|
hsScalar maxY = -1.e33f; |
|
|
hsScalar maxZ = -1.e33f; |
|
|
#endif // MF_RECALC_BOUNDS |
|
|
|
|
|
// localUVWChans is bump mapping tangent space vectors, which need to |
|
|
// be skinned like the normal, as opposed to passed through like |
|
|
// garden variety UVW coordinates. |
|
|
// There are no localUVWChans that I know of in production assets (i.e. |
|
|
// the avatar is not skinned). |
|
|
if( !localUVWChans ) |
|
|
{ |
|
|
/// Copy whilst blending |
|
|
for( i = 0; i < count; i++ ) |
|
|
{ |
|
|
// Extract data |
|
|
src = inlExtractPoint( src, pt ); |
|
|
for( j = 0, weightSum = 0; j < numWeights; j++ ) |
|
|
{ |
|
|
src = inlExtractFloat( src, weights[ j ] ); |
|
|
weightSum += weights[ j ]; |
|
|
} |
|
|
weights[ j ] = 1 - weightSum; |
|
|
|
|
|
if( format & plGBufferGroup::kSkinIndices ) |
|
|
{ |
|
|
src = inlExtractUInt32( src, indices ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
indices = 1 << 8; |
|
|
} |
|
|
src = inlExtractPoint( src, vec ); |
|
|
src = inlExtractUInt32( src, color ); |
|
|
src = inlExtractUInt32( src, specColor ); |
|
|
|
|
|
// Blend |
|
|
destPt.Set( 0, 0, 0 ); |
|
|
destNorm.Set( 0, 0, 0 ); |
|
|
for( j = 0; j < numWeights + 1; j++ ) |
|
|
{ |
|
|
if( weights[ j ] ) |
|
|
{ |
|
|
MATRIXMULTBEGIN(matrixPalette[indices & 0xff], weights[j]); |
|
|
|
|
|
MATRIXMULTPOINTADD(destPt, pt); |
|
|
MATRIXMULTVECTORADD(destNorm, vec); |
|
|
} |
|
|
|
|
|
indices >>= 8; |
|
|
} |
|
|
// Probably don't really need to renormalize this. There errors are |
|
|
// going to be subtle and "smooth". |
|
|
// hsFastMath::NormalizeAppr(destNorm); |
|
|
|
|
|
#ifdef MF_RECALC_BOUNDS |
|
|
inlTESTPOINT(destPt, minX, minY, minZ, maxX, maxY, maxZ); |
|
|
#endif // MF_RECALC_BOUNDS |
|
|
|
|
|
// Slam data into position now |
|
|
dest = inlStuffPoint( dest, destPt ); |
|
|
dest = inlStuffPoint( dest, destNorm ); |
|
|
dest = inlStuffUInt32( dest, color ); |
|
|
dest = inlStuffUInt32( dest, specColor ); |
|
|
memcpy( dest, src, uvChanSize ); |
|
|
src += uvChanSize; |
|
|
dest += uvChanSize; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
UInt8 hiChan = localUVWChans >> 8; |
|
|
UInt8 loChan = localUVWChans & 0xff; |
|
|
/// Copy whilst blending |
|
|
for( i = 0; i < count; i++ ) |
|
|
{ |
|
|
hsVector3 srcUVWs[plGeometrySpan::kMaxNumUVChannels]; |
|
|
hsVector3 dstUVWs[plGeometrySpan::kMaxNumUVChannels]; |
|
|
|
|
|
// Extract data |
|
|
src = inlExtractPoint( src, pt ); |
|
|
for( j = 0, weightSum = 0; j < numWeights; j++ ) |
|
|
{ |
|
|
src = inlExtractFloat( src, weights[ j ] ); |
|
|
weightSum += weights[ j ]; |
|
|
} |
|
|
weights[ j ] = 1 - weightSum; |
|
|
|
|
|
if( format & plGBufferGroup::kSkinIndices ) |
|
|
{ |
|
|
src = inlExtractUInt32( src, indices ); |
|
|
} |
|
|
else |
|
|
{ |
|
|
indices = 1 << 8; |
|
|
} |
|
|
|
|
|
src = inlExtractPoint( src, vec ); |
|
|
src = inlExtractUInt32( src, color ); |
|
|
src = inlExtractUInt32( src, specColor ); |
|
|
|
|
|
UInt8 k; |
|
|
for( k = 0; k < numUVs; k++ ) |
|
|
{ |
|
|
src = inlExtractPoint( src, srcUVWs[k] ); |
|
|
} |
|
|
memcpy( dstUVWs, srcUVWs, uvChanSize); |
|
|
dstUVWs[loChan].Set(0,0,0); |
|
|
dstUVWs[hiChan].Set(0,0,0); |
|
|
|
|
|
// Blend |
|
|
destPt.Set( 0, 0, 0 ); |
|
|
destNorm.Set( 0, 0, 0 ); |
|
|
for( j = 0; j < numWeights + 1; j++ ) |
|
|
{ |
|
|
if( weights[ j ] ) |
|
|
{ |
|
|
MATRIXMULTBEGIN(matrixPalette[indices & 0xff], weights[j]); |
|
|
|
|
|
MATRIXMULTPOINTADD(destPt, pt); |
|
|
MATRIXMULTVECTORADD(destNorm, vec); |
|
|
MATRIXMULTVECTORADD(dstUVWs[loChan], srcUVWs[loChan]); |
|
|
MATRIXMULTVECTORADD(dstUVWs[hiChan], srcUVWs[hiChan]); |
|
|
} |
|
|
|
|
|
indices >>= 8; |
|
|
} |
|
|
// Probably don't really need to renormalize this. There errors are |
|
|
// going to be subtle and "smooth". |
|
|
// hsFastMath::NormalizeAppr(destNorm); |
|
|
// hsFastMath::NormalizeAppr(dstUVWs[loChan]); |
|
|
// hsFastMath::NormalizeAppr(dstUVWs[hiChan]); |
|
|
|
|
|
#ifdef MF_RECALC_BOUNDS |
|
|
inlTESTPOINT(destPt, minX, minY, minZ, maxX, maxY, maxZ); |
|
|
#endif // MF_RECALC_BOUNDS |
|
|
|
|
|
// Slam data into position now |
|
|
dest = inlStuffPoint( dest, destPt ); |
|
|
dest = inlStuffPoint( dest, destNorm ); |
|
|
dest = inlStuffUInt32( dest, color ); |
|
|
dest = inlStuffUInt32( dest, specColor ); |
|
|
memcpy( dest, dstUVWs, uvChanSize ); |
|
|
dest += uvChanSize; |
|
|
} |
|
|
} |
|
|
#ifdef MF_RECALC_BOUNDS |
|
|
hsBounds3Ext wBnd; |
|
|
wBnd.Reset(&hsPoint3(minX, minY, minZ)); |
|
|
wBnd.Union(&hsPoint3(maxX, maxY, maxZ)); |
|
|
span->fWorldBounds = wBnd; |
|
|
#endif // MF_RECALC_BOUNDS |
|
|
} |
|
|
|
|
|
// ISetPipeConsts ////////////////////////////////////////////////////////////////// |
|
|
// A shader can request that the pipeline fill in certain constants that are indeterminate |
|
|
// until the pipeline is about to render the object the shader is applied to. For example, |
|
|
// the object's local to world. A single shader may be used on multiple objects with |
|
|
// multiple local to world transforms. This ensures the pipeline will shove the proper |
|
|
// local to world into the shader immediately before the render. |
|
|
// See plShader.h for the list of available pipe constants. |
|
|
// Note that the lighting pipe constants are NOT implemented. |
|
|
void plDXPipeline::ISetPipeConsts(plShader* shader) |
|
|
{ |
|
|
int n = shader->GetNumPipeConsts(); |
|
|
int i; |
|
|
for( i = 0; i < n; i++ ) |
|
|
{ |
|
|
const plPipeConst& pc = shader->GetPipeConst(i); |
|
|
switch( pc.fType ) |
|
|
{ |
|
|
case plPipeConst::kFogSet: |
|
|
{ |
|
|
float set[4]; |
|
|
IGetVSFogSet(set); |
|
|
shader->SetFloat4(pc.fReg, set); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kLayAmbient: |
|
|
{ |
|
|
hsColorRGBA col = fCurrLay->GetAmbientColor(); |
|
|
shader->SetColor(pc.fReg, col); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kLayRuntime: |
|
|
{ |
|
|
hsColorRGBA col = fCurrLay->GetRuntimeColor(); |
|
|
col.a = fCurrLay->GetOpacity(); |
|
|
shader->SetColor(pc.fReg, col); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kLaySpecular: |
|
|
{ |
|
|
hsColorRGBA col = fCurrLay->GetSpecularColor(); |
|
|
shader->SetColor(pc.fReg, col); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kTex3x4_0: |
|
|
case plPipeConst::kTex3x4_1: |
|
|
case plPipeConst::kTex3x4_2: |
|
|
case plPipeConst::kTex3x4_3: |
|
|
case plPipeConst::kTex3x4_4: |
|
|
case plPipeConst::kTex3x4_5: |
|
|
case plPipeConst::kTex3x4_6: |
|
|
case plPipeConst::kTex3x4_7: |
|
|
{ |
|
|
int stage = pc.fType - plPipeConst::kTex3x4_0; |
|
|
|
|
|
if( stage > fCurrNumLayers ) |
|
|
{ |
|
|
// Ooops. This is bad, means the shader is expecting more layers than |
|
|
// we actually have (or is just bogus). Assert and quietly continue. |
|
|
hsAssert(false, "Shader asking for higher stage transform than we have"); |
|
|
continue; |
|
|
} |
|
|
const hsMatrix44& xfm = fCurrMaterial->GetLayer(fCurrLayerIdx + stage)->GetTransform(); |
|
|
|
|
|
shader->SetMatrix34(pc.fReg, xfm); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kTex2x4_0: |
|
|
case plPipeConst::kTex2x4_1: |
|
|
case plPipeConst::kTex2x4_2: |
|
|
case plPipeConst::kTex2x4_3: |
|
|
case plPipeConst::kTex2x4_4: |
|
|
case plPipeConst::kTex2x4_5: |
|
|
case plPipeConst::kTex2x4_6: |
|
|
case plPipeConst::kTex2x4_7: |
|
|
{ |
|
|
int stage = pc.fType - plPipeConst::kTex2x4_0; |
|
|
|
|
|
if( stage > fCurrNumLayers ) |
|
|
{ |
|
|
// Ooops. This is bad, means the shader is expecting more layers than |
|
|
// we actually have (or is just bogus). Assert and quietly continue. |
|
|
hsAssert(false, "Shader asking for higher stage transform than we have"); |
|
|
continue; |
|
|
} |
|
|
const hsMatrix44& xfm = fCurrMaterial->GetLayer(fCurrLayerIdx + stage)->GetTransform(); |
|
|
|
|
|
shader->SetMatrix24(pc.fReg, xfm); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kTex1x4_0: |
|
|
case plPipeConst::kTex1x4_1: |
|
|
case plPipeConst::kTex1x4_2: |
|
|
case plPipeConst::kTex1x4_3: |
|
|
case plPipeConst::kTex1x4_4: |
|
|
case plPipeConst::kTex1x4_5: |
|
|
case plPipeConst::kTex1x4_6: |
|
|
case plPipeConst::kTex1x4_7: |
|
|
{ |
|
|
int stage = pc.fType - plPipeConst::kTex1x4_0; |
|
|
|
|
|
if( stage > fCurrNumLayers ) |
|
|
{ |
|
|
// Ooops. This is bad, means the shader is expecting more layers than |
|
|
// we actually have (or is just bogus). Assert and quietly continue. |
|
|
hsAssert(false, "Shader asking for higher stage transform than we have"); |
|
|
continue; |
|
|
} |
|
|
const hsMatrix44& xfm = fCurrMaterial->GetLayer(fCurrLayerIdx + stage)->GetTransform(); |
|
|
|
|
|
shader->SetFloat4(pc.fReg, xfm.fMap[0]); |
|
|
} |
|
|
break; |
|
|
case plPipeConst::kLocalToNDC: |
|
|
{ |
|
|
hsMatrix44 cam2ndc = IGetCameraToNDC(); |
|
|
hsMatrix44 world2cam = GetViewTransform().GetWorldToCamera(); |
|
|
|
|
|
hsMatrix44 local2ndc = cam2ndc * world2cam * GetLocalToWorld(); |
|
|
|
|
|
shader->SetMatrix44(pc.fReg, local2ndc); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kCameraToNDC: |
|
|
{ |
|
|
hsMatrix44 cam2ndc = IGetCameraToNDC(); |
|
|
|
|
|
shader->SetMatrix44(pc.fReg, cam2ndc); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kWorldToNDC: |
|
|
{ |
|
|
hsMatrix44 cam2ndc = IGetCameraToNDC(); |
|
|
hsMatrix44 world2cam = GetViewTransform().GetWorldToCamera(); |
|
|
|
|
|
hsMatrix44 world2ndc = cam2ndc * world2cam; |
|
|
|
|
|
shader->SetMatrix44(pc.fReg, world2ndc); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kLocalToWorld: |
|
|
shader->SetMatrix34(pc.fReg, GetLocalToWorld()); |
|
|
break; |
|
|
|
|
|
case plPipeConst::kWorldToLocal: |
|
|
shader->SetMatrix34(pc.fReg, GetWorldToLocal()); |
|
|
break; |
|
|
|
|
|
case plPipeConst::kWorldToCamera: |
|
|
{ |
|
|
hsMatrix44 world2cam = GetViewTransform().GetWorldToCamera(); |
|
|
|
|
|
shader->SetMatrix34(pc.fReg, world2cam); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kCameraToWorld: |
|
|
{ |
|
|
hsMatrix44 cam2world = GetViewTransform().GetCameraToWorld(); |
|
|
|
|
|
shader->SetMatrix34(pc.fReg, cam2world); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kLocalToCamera: |
|
|
{ |
|
|
hsMatrix44 world2cam = GetViewTransform().GetWorldToCamera(); |
|
|
|
|
|
hsMatrix44 local2cam = world2cam * GetLocalToWorld(); |
|
|
|
|
|
shader->SetMatrix34(pc.fReg, local2cam); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kCameraToLocal: |
|
|
{ |
|
|
hsMatrix44 cam2world = GetViewTransform().GetCameraToWorld(); |
|
|
|
|
|
hsMatrix44 cam2local = GetWorldToLocal() * cam2world; |
|
|
|
|
|
shader->SetMatrix34(pc.fReg, cam2local); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kCamPosWorld: |
|
|
{ |
|
|
shader->SetVectorW(pc.fReg, GetViewTransform().GetCameraToWorld().GetTranslate(), 1.f); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kCamPosLocal: |
|
|
{ |
|
|
hsPoint3 localCam = GetWorldToLocal() * GetViewTransform().GetCameraToWorld().GetTranslate(); |
|
|
|
|
|
shader->SetVectorW(pc.fReg, localCam, 1.f); |
|
|
} |
|
|
break; |
|
|
|
|
|
case plPipeConst::kObjPosWorld: |
|
|
{ |
|
|
shader->SetVectorW(pc.fReg, GetLocalToWorld().GetTranslate(), 1.f); |
|
|
} |
|
|
break; |
|
|
|
|
|
// UNIMPLEMENTED |
|
|
case plPipeConst::kDirLight1: |
|
|
case plPipeConst::kDirLight2: |
|
|
case plPipeConst::kDirLight3: |
|
|
case plPipeConst::kDirLight4: |
|
|
case plPipeConst::kPointLight1: |
|
|
case plPipeConst::kPointLight2: |
|
|
case plPipeConst::kPointLight3: |
|
|
case plPipeConst::kPointLight4: |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// ISetShaders ///////////////////////////////////////////////////////////////////////////////////// |
|
|
// Setup to render using the input vertex and pixel shader. Either or both may |
|
|
// be nil, in which case the fixed function pipeline is indicated. |
|
|
// Any Pipe Constants the non-FFP shader wants will be set here. |
|
|
// Lastly, all constants will be set (as a block) for any non-FFP vertex or pixel shader. |
|
|
HRESULT plDXPipeline::ISetShaders(plShader* vShader, plShader* pShader) |
|
|
{ |
|
|
IDirect3DVertexShader9 *vsHandle = NULL; |
|
|
if( vShader ) |
|
|
{ |
|
|
hsAssert(vShader->IsVertexShader(), "Wrong type shader as vertex shader"); |
|
|
ISetPipeConsts(vShader); |
|
|
|
|
|
plDXVertexShader* vRef = (plDXVertexShader*)vShader->GetDeviceRef(); |
|
|
if( !vRef ) |
|
|
{ |
|
|
vRef = TRACKED_NEW plDXVertexShader(vShader); |
|
|
hsRefCnt_SafeUnRef(vRef); |
|
|
} |
|
|
if( !vRef->IsLinked() ) |
|
|
vRef->Link(&fVShaderRefList); |
|
|
vsHandle = vRef->GetShader(this); |
|
|
|
|
|
// This is truly obnoxious, but D3D insists that, while using the progammable pipeline, |
|
|
// all stages be set up like this, not just the ones we're going to use. We have to |
|
|
// do this if we have either a vertex or a pixel shader. See below. Whatever. mf |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXCOORDINDEX, fLayerUVWSrcs[i] = i); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXTURETRANSFORMFLAGS, fLayerXformFlags[i] = 0); |
|
|
} |
|
|
} |
|
|
|
|
|
IDirect3DPixelShader9 *psHandle = NULL; |
|
|
if( pShader ) |
|
|
{ |
|
|
hsAssert(pShader->IsPixelShader(), "Wrong type shader as pixel shader"); |
|
|
|
|
|
ISetPipeConsts(pShader); |
|
|
|
|
|
plDXPixelShader* pRef = (plDXPixelShader*)pShader->GetDeviceRef(); |
|
|
if( !pRef ) |
|
|
{ |
|
|
pRef = TRACKED_NEW plDXPixelShader(pShader); |
|
|
hsRefCnt_SafeUnRef(pRef); |
|
|
} |
|
|
if( !pRef->IsLinked() ) |
|
|
pRef->Link(&fPShaderRefList); |
|
|
psHandle = pRef->GetShader(this); |
|
|
|
|
|
if( !vShader ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < 8; i++ ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXCOORDINDEX, fLayerUVWSrcs[i] = i); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXTURETRANSFORMFLAGS, fLayerXformFlags[i] = 0); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if( vsHandle != fSettings.fCurrVertexShader ) |
|
|
{ |
|
|
HRESULT hr = fD3DDevice->SetVertexShader(fSettings.fCurrVertexShader = vsHandle); |
|
|
hsAssert(!FAILED(hr), "Error setting vertex shader"); |
|
|
} |
|
|
|
|
|
if( psHandle != fSettings.fCurrPixelShader ) |
|
|
{ |
|
|
HRESULT hr = fD3DDevice->SetPixelShader(fSettings.fCurrPixelShader = psHandle); |
|
|
hsAssert(!FAILED(hr), "Error setting pixel shader"); |
|
|
} |
|
|
|
|
|
// Handle cull mode here, because current cullmode is dependent on |
|
|
// the handedness of the LocalToCamera AND whether we are twosided. |
|
|
ISetCullMode(); |
|
|
|
|
|
return S_OK; |
|
|
} |
|
|
|
|
|
// IRenderAuxSpan ////////////////////////////////////////////////////////// |
|
|
// Aux spans (auxilliary) are geometry rendered immediately after, and therefore dependent, on |
|
|
// other normal geometry. They don't have SceneObjects, Drawables, DrawInterfaces or |
|
|
// any of that, and therefore don't correspond to any object in the scene. |
|
|
// They are dynamic procedural decals. See plDynaDecal.cpp and plDynaDecalMgr.cpp. |
|
|
// This is wrapped by IRenderAuxSpans, which makes sure state is restored to resume |
|
|
// normal rendering after the AuxSpan is rendered. |
|
|
void plDXPipeline::IRenderAuxSpan(const plSpan& span, const plAuxSpan* aux) |
|
|
{ |
|
|
// Make sure the underlying resources are created and filled in with current data. |
|
|
CheckVertexBufferRef(aux->fGroup, aux->fVBufferIdx); |
|
|
CheckIndexBufferRef(aux->fGroup, aux->fIBufferIdx); |
|
|
ICheckAuxBuffers(aux); |
|
|
|
|
|
// Set to render from the aux spans buffers. |
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef*)aux->fGroup->GetVertexBufferRef(aux->fVBufferIdx); |
|
|
|
|
|
if( !vRef ) |
|
|
return; |
|
|
|
|
|
plDXIndexBufferRef* iRef = (plDXIndexBufferRef*)aux->fGroup->GetIndexBufferRef(aux->fIBufferIdx); |
|
|
|
|
|
if( !iRef ) |
|
|
return; |
|
|
|
|
|
HRESULT r; |
|
|
|
|
|
r = fD3DDevice->SetStreamSource( 0, vRef->fD3DBuffer, 0, vRef->fVertexSize ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the stream source!" ); |
|
|
plProfile_Inc(VertexChange); |
|
|
|
|
|
fD3DDevice->SetFVF(fSettings.fCurrFVFFormat = IGetBufferD3DFormat(vRef->fFormat)); |
|
|
|
|
|
r = fD3DDevice->SetIndices( iRef->fD3DBuffer ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the indices!" ); |
|
|
|
|
|
plRenderTriListFunc render(fD3DDevice, iRef->fOffset, aux->fVStartIdx, aux->fVLength, aux->fIStartIdx, aux->fILength/3); |
|
|
|
|
|
// Now just loop through the aux material, rendering in as many passes as it takes. |
|
|
hsGMaterial* material = aux->fMaterial; |
|
|
int j; |
|
|
for( j = 0; j < material->GetNumLayers(); ) |
|
|
{ |
|
|
int iCurrMat = j; |
|
|
j = IHandleMaterial( material, iCurrMat, &span ); |
|
|
if (j == -1) |
|
|
break; |
|
|
|
|
|
ISetShaders(material->GetLayer(iCurrMat)->GetVertexShader(), material->GetLayer(iCurrMat)->GetPixelShader()); |
|
|
|
|
|
if( aux->fFlags & plAuxSpan::kOverrideLiteModel ) |
|
|
{ |
|
|
static D3DMATERIAL9 mat; |
|
|
fD3DDevice->SetRenderState(D3DRS_AMBIENT, 0xffffffff); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1 ); |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
} |
|
|
|
|
|
render.RenderPrims(); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// IRenderAuxSpans //////////////////////////////////////////////////////////////////////////// |
|
|
// Save and restore render state around calls to IRenderAuxSpan. This lets |
|
|
// a list of aux spans get rendered with only one save/restore state. |
|
|
void plDXPipeline::IRenderAuxSpans(const plSpan& span) |
|
|
{ |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoAuxSpans)) |
|
|
return; |
|
|
|
|
|
plDXVertexBufferRef* oldVRef = fSettings.fCurrVertexBuffRef; |
|
|
plDXIndexBufferRef* oldIRef = fSettings.fCurrIndexBuffRef; |
|
|
|
|
|
ISetLocalToWorld(hsMatrix44::IdentityMatrix(), hsMatrix44::IdentityMatrix()); |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < span.GetNumAuxSpans(); i++ ) |
|
|
IRenderAuxSpan(span, span.GetAuxSpan(i)); |
|
|
|
|
|
ISetLocalToWorld(span.fLocalToWorld, span.fWorldToLocal); |
|
|
|
|
|
HRESULT r; |
|
|
|
|
|
r = fD3DDevice->SetStreamSource( 0, oldVRef->fD3DBuffer, 0, oldVRef->fVertexSize ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the stream source!" ); |
|
|
plProfile_Inc(VertexChange); |
|
|
|
|
|
r = fD3DDevice->SetFVF(fSettings.fCurrFVFFormat = IGetBufferD3DFormat(oldVRef->fFormat)); |
|
|
|
|
|
r = fD3DDevice->SetIndices( oldIRef->fD3DBuffer ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the indices!" ); |
|
|
|
|
|
} |
|
|
|
|
|
// ICheckVBUsage ////////////////////////////////////////////////////////////// |
|
|
// Keep track of how much managed vertex buffer memory is being used and |
|
|
// has been used since the last evict. |
|
|
inline void plDXPipeline::ICheckVBUsage(plDXVertexBufferRef* vRef) |
|
|
{ |
|
|
if( !vRef->fOwner->AreVertsVolatile() ) |
|
|
{ |
|
|
if( vRef->fUseTime <= fEvictTime ) |
|
|
fManagedSeen += vRef->fVertexSize * vRef->fCount; |
|
|
|
|
|
if( vRef->fUseTime != fTextUseTime ) |
|
|
{ |
|
|
plProfile_NewMem(CurrVB, vRef->fVertexSize * vRef->fCount); |
|
|
fVtxUsed += vRef->fVertexSize * vRef->fCount; |
|
|
vRef->fUseTime = fTextUseTime; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
//// IRenderBufferSpan //////////////////////////////////////////////////////// |
|
|
// Sets up the vertex and index buffers for a span, and then |
|
|
// renders it in as many passes as it takes in ILoopOverLayers. |
|
|
void plDXPipeline::IRenderBufferSpan( const plIcicle& span, |
|
|
hsGDeviceRef *vb, hsGDeviceRef *ib, |
|
|
hsGMaterial *material, UInt32 vStart, UInt32 vLength, |
|
|
UInt32 iStart, UInt32 iLength ) |
|
|
{ |
|
|
plProfile_BeginTiming(RenderBuff); |
|
|
|
|
|
plDXVertexBufferRef *vRef = (plDXVertexBufferRef *)vb; |
|
|
plDXIndexBufferRef *iRef = (plDXIndexBufferRef *)ib; |
|
|
|
|
|
HRESULT r; |
|
|
|
|
|
if( vRef->fD3DBuffer == nil || iRef->fD3DBuffer == nil ) |
|
|
{ |
|
|
plProfile_EndTiming(RenderBuff); |
|
|
hsAssert( false, "Trying to render a nil buffer pair!" ); |
|
|
return; |
|
|
} |
|
|
|
|
|
/// Switch to the vertex buffer we want |
|
|
if( fSettings.fCurrVertexBuffRef != vRef ) |
|
|
{ |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrVertexBuffRef, vRef ); |
|
|
hsAssert( vRef->fD3DBuffer != nil, "Trying to render a buffer pair without a vertex buffer!" ); |
|
|
vRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
if( vRef->RebuiltSinceUsed() ) |
|
|
{ |
|
|
r = fD3DDevice->SetStreamSource( 0, vRef->fD3DBuffer, 0, vRef->fVertexSize ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the stream source!" ); |
|
|
plProfile_Inc(VertexChange); |
|
|
|
|
|
DWORD fvf = IGetBufferD3DFormat(vRef->fFormat); |
|
|
if (fSettings.fCurrFVFFormat != fvf) |
|
|
fD3DDevice->SetFVF(fSettings.fCurrFVFFormat = fvf); |
|
|
|
|
|
vRef->SetRebuiltSinceUsed(false); |
|
|
|
|
|
ICheckVBUsage(vRef); |
|
|
} |
|
|
|
|
|
// Note: both these stats are the same, since we don't do any culling or clipping on the tris |
|
|
if( fSettings.fCurrIndexBuffRef != iRef ) |
|
|
{ |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrIndexBuffRef, iRef ); |
|
|
hsAssert( iRef->fD3DBuffer != nil, "Trying to render with a nil index buffer" ); |
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
if( iRef->RebuiltSinceUsed() ) |
|
|
{ |
|
|
r = fD3DDevice->SetIndices( iRef->fD3DBuffer ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the indices!" ); |
|
|
plProfile_Inc(IndexChange); |
|
|
iRef->SetRebuiltSinceUsed(false); |
|
|
} |
|
|
|
|
|
plRenderTriListFunc render(fD3DDevice, iRef->fOffset, vStart, vLength, iStart, iLength/3); |
|
|
|
|
|
plProfile_EndTiming(RenderBuff); |
|
|
ILoopOverLayers(render, material, span); |
|
|
} |
|
|
|
|
|
// ILoopOverLayers ///////////////////////////////////////////////////////////////////////////////// |
|
|
// Render the input span with the input material in as many passes as it takes. |
|
|
// Also handles rendering projected lights, either onto each pass or |
|
|
// once onto the FB after all the passes, as appropriate. |
|
|
hsBool plDXPipeline::ILoopOverLayers(const plRenderPrimFunc& inRender, hsGMaterial* material, const plSpan& span) |
|
|
{ |
|
|
plProfile_BeginTiming(RenderPrim); |
|
|
|
|
|
const plRenderPrimFunc& render = IsDebugFlagSet(plPipeDbg::kFlagNoRender) ? (const plRenderPrimFunc&)sRenderNil : inRender; |
|
|
|
|
|
if( GetOverrideMaterial() ) |
|
|
material = GetOverrideMaterial(); |
|
|
|
|
|
IPushPiggyBacks(material); |
|
|
|
|
|
hsBool normalLightsDisabled = false; |
|
|
|
|
|
// Loop across all the layers we need to draw |
|
|
int j; |
|
|
for( j = 0; j < material->GetNumLayers(); ) |
|
|
{ |
|
|
int iCurrMat = j; |
|
|
j = IHandleMaterial( material, iCurrMat, &span ); |
|
|
if (j == -1) |
|
|
break; |
|
|
|
|
|
if( (fLayerState[0].fBlendFlags & hsGMatState::kBlendAlpha) |
|
|
&&(material->GetLayer(iCurrMat)->GetOpacity() <= 0) |
|
|
&&(fCurrLightingMethod != plSpan::kLiteVtxPreshaded) ) // This opt isn't good for particles, since their |
|
|
// material opacity is undefined/unused... -mcn |
|
|
continue; |
|
|
|
|
|
plProfile_BeginTiming(SpanFog); |
|
|
ISetFogParameters(&span, material->GetLayer(iCurrMat)); |
|
|
plProfile_EndTiming(SpanFog); |
|
|
|
|
|
ISetShaders(material->GetLayer(iCurrMat)->GetVertexShader(), material->GetLayer(iCurrMat)->GetPixelShader()); |
|
|
|
|
|
if( normalLightsDisabled ) |
|
|
IRestoreSpanLights(); |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
DWORD nPass; |
|
|
fSettings.fDXError = fD3DDevice->ValidateDevice(&nPass); |
|
|
if( fSettings.fDXError != D3D_OK ) |
|
|
IGetD3DError(); |
|
|
#endif // HS_DEBUGGING |
|
|
|
|
|
// Do the regular draw. |
|
|
render.RenderPrims(); |
|
|
|
|
|
// Take care of projections that get applied to each pass. |
|
|
if( fLights.fProjEach.GetCount() && !(fView.fRenderState & kRenderNoProjection) ) |
|
|
{ |
|
|
// Disable all the normal lights. |
|
|
IDisableSpanLights(); |
|
|
normalLightsDisabled = true; |
|
|
|
|
|
IRenderProjectionEach(render, material, iCurrMat, span); |
|
|
|
|
|
} |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagNoUpperLayers)) |
|
|
j = material->GetNumLayers(); |
|
|
} |
|
|
IPopPiggyBacks(); |
|
|
|
|
|
// If we disabled lighting, re-enable it. |
|
|
if( normalLightsDisabled ) |
|
|
IRestoreSpanLights(); |
|
|
|
|
|
// Render any aux spans associated. |
|
|
if( span.GetNumAuxSpans() ) |
|
|
IRenderAuxSpans(span); |
|
|
|
|
|
// Only render projections and shadows if we successfully rendered the span. |
|
|
// j == -1 means we aborted render. |
|
|
if( j >= 0 ) |
|
|
{ |
|
|
// Projections that get applied to the frame buffer (after all passes). |
|
|
if( fLights.fProjAll.GetCount() && !(fView.fRenderState & kRenderNoProjection) ) |
|
|
IRenderProjections(render); |
|
|
|
|
|
// Handle render of shadows onto geometry. |
|
|
if( fShadows.GetCount() ) |
|
|
IRenderShadowsOntoSpan(render, &span, material); |
|
|
} |
|
|
|
|
|
// Debug only |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagOverlayWire)) |
|
|
{ |
|
|
IRenderOverWire(render, material, span); |
|
|
} |
|
|
plProfile_EndTiming(RenderPrim); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
// IRenderOverWire /////////////////////////////////////////////////////////////////////////////// |
|
|
// Debug only, renders wireframe on top of normal render. |
|
|
void plDXPipeline::IRenderOverWire(const plRenderPrimFunc& render, hsGMaterial* material, const plSpan& span) |
|
|
{ |
|
|
UInt32 state = fView.fRenderState; |
|
|
fView.fRenderState |= plPipeline::kRenderBaseLayerOnly; |
|
|
static plLayerDepth depth; |
|
|
depth.SetMiscFlags(depth.GetMiscFlags() | hsGMatState::kMiscWireFrame | hsGMatState::kMiscTwoSided); |
|
|
depth.SetZFlags((depth.GetZFlags() & ~hsGMatState::kZNoZRead) | hsGMatState::kZIncLayer); |
|
|
|
|
|
AppendLayerInterface(&depth, false); |
|
|
|
|
|
if( IHandleMaterial( material, 0, &span ) >= 0 ) |
|
|
{ |
|
|
ISetShaders(nil, nil); |
|
|
render.RenderPrims(); |
|
|
} |
|
|
|
|
|
RemoveLayerInterface(&depth, false) ; |
|
|
|
|
|
fView.fRenderState = state; |
|
|
} |
|
|
|
|
|
// IRenderProjectionEach /////////////////////////////////////////////////////////////////////////////////////// |
|
|
// Render any lights that are to be projected onto each pass of the object. |
|
|
void plDXPipeline::IRenderProjectionEach(const plRenderPrimFunc& render, hsGMaterial* material, int iPass, const plSpan& span) |
|
|
{ |
|
|
// If this is a bump map pass, forget it, we've already "done" per-pixel lighting. |
|
|
if( fLayerState[iPass].fMiscFlags & (hsGMatState::kMiscBumpLayer | hsGMatState::kMiscBumpChans) ) |
|
|
return; |
|
|
|
|
|
// Push the LayerShadowBase override. This sets the blend |
|
|
// to framebuffer as Add/ZNoWrite and AmbientColor = 0. |
|
|
static plLayerLightBase layLightBase; |
|
|
|
|
|
int iNextPass = iPass + fCurrNumLayers; |
|
|
|
|
|
if( fSettings.fLimitedProj && (material->GetLayer(iPass)->GetUVWSrc() & ~plLayerInterface::kUVWIdxMask) ) |
|
|
return; |
|
|
|
|
|
// For each projector: |
|
|
int k; |
|
|
for( k = 0; k < fLights.fProjEach.GetCount(); k++ ) |
|
|
{ |
|
|
// Push it's projected texture as a piggyback. |
|
|
plLightInfo* li = fLights.fProjEach[k]; |
|
|
|
|
|
// Lower end boards are iffy on when they'll project correctly. |
|
|
if( fSettings.fCantProj && !li->GetProperty(plLightInfo::kLPForceProj) ) |
|
|
continue; |
|
|
|
|
|
plLayerInterface* proj = li->GetProjection(); |
|
|
hsAssert(proj, "A projector with no texture to project?"); |
|
|
IPushProjPiggyBack(proj); |
|
|
|
|
|
// Enable the projecting light only. |
|
|
plDXLightRef* ref = (plDXLightRef *)li->GetDeviceRef(); |
|
|
fD3DDevice->LightEnable( ref->fD3DIndex, true ); |
|
|
|
|
|
AppendLayerInterface(&layLightBase, false); |
|
|
|
|
|
// Render until it's done. |
|
|
int iRePass = iPass; |
|
|
while( iRePass < iNextPass ) |
|
|
{ |
|
|
iRePass = IHandleMaterial( material, iRePass, &span ); |
|
|
ISetShaders(nil, nil); |
|
|
|
|
|
// Do the render with projection. |
|
|
render.RenderPrims(); |
|
|
} |
|
|
|
|
|
RemoveLayerInterface(&layLightBase, false); |
|
|
|
|
|
// Disable the projecting light |
|
|
fD3DDevice->LightEnable(ref->fD3DIndex, false); |
|
|
|
|
|
// Pop it's projected texture off piggyback |
|
|
IPopProjPiggyBacks(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// IRenderProjections /////////////////////////////////////////////////////////// |
|
|
// Render any projected lights that want to be rendered a single time after |
|
|
// all passes on the object are complete. |
|
|
void plDXPipeline::IRenderProjections(const plRenderPrimFunc& render) |
|
|
{ |
|
|
IDisableSpanLights(); |
|
|
int i; |
|
|
for( i = 0; i < fLights.fProjAll.GetCount(); i++ ) |
|
|
{ |
|
|
plLightInfo* li = fLights.fProjAll[i]; |
|
|
|
|
|
if( fSettings.fCantProj && !li->GetProperty(plLightInfo::kLPForceProj) ) |
|
|
continue; |
|
|
|
|
|
IRenderProjection(render, li); |
|
|
} |
|
|
IRestoreSpanLights(); |
|
|
} |
|
|
|
|
|
// IRenderProjection ////////////////////////////////////////////////////////////// |
|
|
// Render this light's projection onto the frame buffer. |
|
|
void plDXPipeline::IRenderProjection(const plRenderPrimFunc& render, plLightInfo* li) |
|
|
{ |
|
|
plDXLightRef* ref = (plDXLightRef *)li->GetDeviceRef(); |
|
|
fD3DDevice->LightEnable(ref->fD3DIndex, true); |
|
|
|
|
|
plLayerInterface* proj = li->GetProjection(); |
|
|
|
|
|
static D3DMATERIAL9 mat; |
|
|
mat.Diffuse.r = mat.Diffuse.g = mat.Diffuse.b = mat.Diffuse.a = 1.f; |
|
|
|
|
|
fD3DDevice->SetMaterial( &mat ); |
|
|
fD3DDevice->SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
fD3DDevice->SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL ); |
|
|
|
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); //@@@ |
|
|
|
|
|
// Set the FB blend mode, texture, all that. |
|
|
ICompositeLayerState(0, proj); |
|
|
// We should have put ZNoZWrite on during export, but we didn't. |
|
|
fLayerState[0].fZFlags = hsGMatState::kZNoZWrite; |
|
|
fCurrNumLayers = 1; |
|
|
IHandleFirstTextureStage(proj); |
|
|
|
|
|
if( proj->GetBlendFlags() & hsGMatState::kBlendInvertFinalColor ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE | D3DTA_COMPLEMENT); |
|
|
} |
|
|
|
|
|
// Seal it up |
|
|
fLastEndingStage = 1; |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
DWORD nPass; |
|
|
fSettings.fDXError = fD3DDevice->ValidateDevice(&nPass); |
|
|
if( fSettings.fDXError != D3D_OK ) |
|
|
IGetD3DError(); |
|
|
#endif // HS_DEBUGGING |
|
|
|
|
|
// Okay, render it already. |
|
|
|
|
|
render.RenderPrims(); |
|
|
|
|
|
fD3DDevice->LightEnable(ref->fD3DIndex, false); |
|
|
} |
|
|
|
|
|
//// IGetBufferD3DFormat ////////////////////////////////////////////////////// |
|
|
// Convert the dumbest vertex format on the planet (ours) into an FVF code. |
|
|
// Note the assumption of position, normal, diffuse, and specular. |
|
|
// We no longer use FVF codes, just shader handles. |
|
|
long plDXPipeline::IGetBufferD3DFormat( UInt8 format ) const |
|
|
{ |
|
|
long fmt, i; |
|
|
|
|
|
|
|
|
switch( format & plGBufferGroup::kSkinWeightMask ) |
|
|
{ |
|
|
case plGBufferGroup::kSkinNoWeights: |
|
|
fmt = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_NORMAL; |
|
|
break; |
|
|
case plGBufferGroup::kSkin1Weight: |
|
|
fmt = D3DFVF_XYZB1 | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_NORMAL; |
|
|
break; |
|
|
case plGBufferGroup::kSkin2Weights: |
|
|
fmt = D3DFVF_XYZB2 | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_NORMAL; |
|
|
break; |
|
|
case plGBufferGroup::kSkin3Weights: |
|
|
fmt = D3DFVF_XYZB3 | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_NORMAL; |
|
|
break; |
|
|
default: |
|
|
hsAssert( false, "Bad skin weight value in IGetBufferD3DFormat()" ); |
|
|
} |
|
|
if( format & plGBufferGroup::kSkinIndices ) |
|
|
{ |
|
|
hsAssert(false, "Indexed skinning not supported"); |
|
|
fmt |= D3DFVF_LASTBETA_UBYTE4; |
|
|
} |
|
|
|
|
|
switch( plGBufferGroup::CalcNumUVs( format ) ) |
|
|
{ |
|
|
case 0: fmt |= D3DFVF_TEX0; break; |
|
|
case 1: fmt |= D3DFVF_TEX1; break; |
|
|
case 2: fmt |= D3DFVF_TEX2; break; |
|
|
case 3: fmt |= D3DFVF_TEX3; break; |
|
|
case 4: fmt |= D3DFVF_TEX4; break; |
|
|
case 5: fmt |= D3DFVF_TEX5; break; |
|
|
case 6: fmt |= D3DFVF_TEX6; break; |
|
|
case 7: fmt |= D3DFVF_TEX7; break; |
|
|
case 8: fmt |= D3DFVF_TEX8; break; |
|
|
} |
|
|
|
|
|
for( i = 0; i < plGBufferGroup::CalcNumUVs( format ); i++ ) |
|
|
fmt |= D3DFVF_TEXCOORDSIZE3( i ); |
|
|
|
|
|
return fmt; |
|
|
} |
|
|
|
|
|
//// IGetBufferFormatSize ///////////////////////////////////////////////////// |
|
|
// Calculate the vertex stride from the given format. |
|
|
UInt32 plDXPipeline::IGetBufferFormatSize( UInt8 format ) const |
|
|
{ |
|
|
UInt32 size = sizeof( float ) * 6 + sizeof( UInt32 ) * 2; // Position and normal, and two packed colors |
|
|
|
|
|
|
|
|
switch( format & plGBufferGroup::kSkinWeightMask ) |
|
|
{ |
|
|
case plGBufferGroup::kSkinNoWeights: |
|
|
break; |
|
|
case plGBufferGroup::kSkin1Weight: |
|
|
size += sizeof(float); |
|
|
break; |
|
|
default: |
|
|
hsAssert( false, "Invalid skin weight value in IGetBufferFormatSize()" ); |
|
|
} |
|
|
|
|
|
size += sizeof( float ) * 3 * plGBufferGroup::CalcNumUVs( format ); |
|
|
|
|
|
return size; |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Plate and PlateManager Functions ///////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
// None of this plate code is mine, so your guess is as good as mine. |
|
|
// I'll throw in comments where I happen to know what it's doing, but a lot |
|
|
// of this is just ugly. |
|
|
// The plates are mostly used for debugging/performance tools, but they do |
|
|
// unfortunately get used for some production things like the cursor. |
|
|
// By the way, a Plate is just a screen aligned textured quad that is rendered |
|
|
// on top of the normal scene. mf |
|
|
|
|
|
// ICreateGeometry ///////////////////////////////////////////////////////// |
|
|
// Make a quad suitable for rendering as a tristrip. |
|
|
void plDXPlateManager::ICreateGeometry(plDXPipeline* pipe) |
|
|
{ |
|
|
UInt32 fvfFormat = PLD3D_PLATEFVF; |
|
|
D3DPOOL poolType = D3DPOOL_DEFAULT; |
|
|
hsAssert(!pipe->ManagedAlloced(), "Alloc default with managed alloc'd"); |
|
|
if( FAILED( fD3DDevice->CreateVertexBuffer( 4 * sizeof( plPlateVertex ), |
|
|
D3DUSAGE_WRITEONLY, |
|
|
fvfFormat, |
|
|
poolType, &fVertBuffer, NULL ) ) ) |
|
|
{ |
|
|
hsAssert( false, "CreateVertexBuffer() call failed!" ); |
|
|
fCreatedSucessfully = false; |
|
|
return; |
|
|
} |
|
|
PROFILE_POOL_MEM(poolType, 4 * sizeof(plPlateVertex), true, "PlateMgrVtxBuff"); |
|
|
|
|
|
/// Lock the buffer |
|
|
plPlateVertex *ptr; |
|
|
if( FAILED( fVertBuffer->Lock( 0, 0, (void **)&ptr, D3DLOCK_NOSYSLOCK ) ) ) |
|
|
{ |
|
|
hsAssert( false, "Failed to lock vertex buffer for writing" ); |
|
|
fCreatedSucessfully = false; |
|
|
return; |
|
|
} |
|
|
|
|
|
/// Set 'em up |
|
|
ptr[ 0 ].fPoint.Set( -0.5f, -0.5f, 0.0f ); |
|
|
ptr[ 0 ].fColor = 0xffffffff; |
|
|
ptr[ 0 ].fUV.Set( 0.0f, 0.0f, 0.0f ); |
|
|
|
|
|
ptr[ 1 ].fPoint.Set( -0.5f, 0.5f, 0.0f ); |
|
|
ptr[ 1 ].fColor = 0xffffffff; |
|
|
ptr[ 1 ].fUV.Set( 0.0f, 1.0f, 0.0f ); |
|
|
|
|
|
ptr[ 2 ].fPoint.Set( 0.5f, -0.5f, 0.0f ); |
|
|
ptr[ 2 ].fColor = 0xffffffff; |
|
|
ptr[ 2 ].fUV.Set( 1.0f, 0.0f, 0.0f ); |
|
|
|
|
|
ptr[ 3 ].fPoint.Set( 0.5f, 0.5f, 0.0f ); |
|
|
ptr[ 3 ].fColor = 0xffffffff; |
|
|
ptr[ 3 ].fUV.Set( 1.0f, 1.0f, 0.0f ); |
|
|
|
|
|
/// Unlock and we're done! |
|
|
fVertBuffer->Unlock(); |
|
|
fCreatedSucessfully = true; |
|
|
|
|
|
} |
|
|
|
|
|
// IReleaseGeometry //////////////////////////////////////////////////////////// |
|
|
// Let go of any D3D resources created for this. |
|
|
void plDXPlateManager::IReleaseGeometry() |
|
|
{ |
|
|
if (fVertBuffer) |
|
|
{ |
|
|
ReleaseObject(fVertBuffer); |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, 4 * sizeof(plPlateVertex), false, "PlateMgrVtxBuff"); |
|
|
fVertBuffer = nil; |
|
|
} |
|
|
} |
|
|
|
|
|
//// Constructor & Destructor ///////////////////////////////////////////////// |
|
|
|
|
|
plDXPlateManager::plDXPlateManager( plDXPipeline *pipe, IDirect3DDevice9 *device ) : plPlateManager( pipe ), |
|
|
PLD3D_PLATEFVF( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE3(0) ), |
|
|
fD3DDevice(device), |
|
|
fVertBuffer(nil) |
|
|
{ |
|
|
} |
|
|
|
|
|
plDXPlateManager::~plDXPlateManager() |
|
|
{ |
|
|
IReleaseGeometry(); |
|
|
} |
|
|
|
|
|
//// IDrawPlate /////////////////////////////////////////////////////////////// |
|
|
// Render all currently enabled plates to the screen. |
|
|
void plDXPlateManager::IDrawToDevice( plPipeline *pipe ) |
|
|
{ |
|
|
plDXPipeline *dxPipe = (plDXPipeline *)pipe; |
|
|
plPlate *plate; |
|
|
UInt32 scrnWidthDiv2 = fOwner->Width() >> 1; |
|
|
UInt32 scrnHeightDiv2 = fOwner->Height() >> 1; |
|
|
D3DXMATRIX mat; |
|
|
D3DCULL oldCullMode; |
|
|
|
|
|
if( !fVertBuffer ) |
|
|
return; |
|
|
|
|
|
// Make sure skinning is disabled. |
|
|
fD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE); |
|
|
fD3DDevice->SetVertexShader( dxPipe->fSettings.fCurrVertexShader = NULL); |
|
|
fD3DDevice->SetFVF(dxPipe->fSettings.fCurrFVFFormat = PLD3D_PLATEFVF); |
|
|
fD3DDevice->SetStreamSource( 0, fVertBuffer, 0, sizeof( plPlateVertex ) ); |
|
|
plProfile_Inc(VertexChange); |
|
|
// To get plates properly pixel-aligned, we need to compensate for D3D9's weird half-pixel |
|
|
// offset (see http://drilian.com/2008/11/25/understanding-half-pixel-and-half-texel-offsets/ |
|
|
// or http://msdn.microsoft.com/en-us/library/bb219690(VS.85).aspx). |
|
|
D3DXMatrixTranslation(&mat, -0.5f/scrnWidthDiv2, -0.5f/scrnHeightDiv2, 0.0f); |
|
|
fD3DDevice->SetTransform( D3DTS_VIEW, &mat ); |
|
|
oldCullMode = dxPipe->fCurrCullMode; |
|
|
|
|
|
for( plate = fPlates; plate != nil; plate = plate->GetNext() ) |
|
|
{ |
|
|
if( plate->IsVisible() ) |
|
|
{ |
|
|
dxPipe->IDrawPlate( plate ); |
|
|
|
|
|
const char *title = plate->GetTitle(); |
|
|
if( plDebugText::Instance().IsEnabled() && title[ 0 ] != 0 ) |
|
|
{ |
|
|
hsPoint3 pt; |
|
|
pt.Set( 0, -0.5, 0 ); |
|
|
pt = plate->GetTransform() * pt; |
|
|
pt.fX = pt.fX * scrnWidthDiv2 + scrnWidthDiv2; |
|
|
pt.fY = pt.fY * scrnHeightDiv2 + scrnHeightDiv2; |
|
|
pt.fX -= plDebugText::Instance().CalcStringWidth( title ) >> 1; |
|
|
plDebugText::Instance().DrawString( (UInt16)pt.fX, (UInt16)pt.fY + 1, title, 255, 255, 255, 255, plDebugText::kStyleBold ); |
|
|
} |
|
|
|
|
|
if( plate->GetFlags() & plPlate::kFlagIsAGraph ) |
|
|
{ |
|
|
plGraphPlate *graph = (plGraphPlate *)plate; |
|
|
hsPoint3 pt, pt2; |
|
|
int i; |
|
|
|
|
|
if( graph->GetLabelText( 0 )[ 0 ] != 0 ) |
|
|
{ |
|
|
/// Draw key |
|
|
const char *str; |
|
|
|
|
|
pt.Set( -0.5, -0.5, 0 ); |
|
|
pt = plate->GetTransform() * pt; |
|
|
pt.fX = pt.fX * scrnWidthDiv2 + scrnWidthDiv2; |
|
|
pt.fY = pt.fY * scrnHeightDiv2 + scrnHeightDiv2; |
|
|
pt.fY += plDebugText::Instance().GetFontHeight(); |
|
|
|
|
|
UInt32 numLabels = graph->GetNumLabels(); |
|
|
if (numLabels > graph->GetNumColors()) |
|
|
numLabels = graph->GetNumColors(); |
|
|
|
|
|
for( i = 0; i < numLabels; i++ ) |
|
|
{ |
|
|
str = graph->GetLabelText( i ); |
|
|
if( str[ 0 ] == 0 ) |
|
|
break; |
|
|
|
|
|
pt2 = pt; |
|
|
pt2.fX -= plDebugText::Instance().CalcStringWidth( str ); |
|
|
plDebugText::Instance().DrawString( (UInt16)pt2.fX, (UInt16)pt2.fY, str, |
|
|
graph->GetDataColor( i ), plDebugText::kStyleBold ); |
|
|
pt.fY += plDebugText::Instance().GetFontHeight(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
dxPipe->fCurrCullMode = ( dxPipe->fLayerState[0].fMiscFlags & hsGMatState::kMiscTwoSided ) ? D3DCULL_NONE : oldCullMode; |
|
|
fD3DDevice->SetRenderState( D3DRS_CULLMODE, dxPipe->fCurrCullMode ); |
|
|
} |
|
|
|
|
|
// IDrawPlate /////////////////////////////////////////////////////////////////////// |
|
|
// Render this plate, in as many passes as it takes. |
|
|
void plDXPipeline::IDrawPlate( plPlate *plate ) |
|
|
{ |
|
|
int i; |
|
|
hsGMaterial *material = plate->GetMaterial(); |
|
|
D3DXMATRIX mat; |
|
|
|
|
|
|
|
|
/// Set up the D3D transform directly |
|
|
IMatrix44ToD3DMatrix( mat, plate->GetTransform() ); |
|
|
fD3DDevice->SetTransform( D3DTS_WORLD, &mat ); |
|
|
mat = d3dIdentityMatrix; |
|
|
mat(1,1) = -1.0f; |
|
|
mat(2,2) = 2.0f; |
|
|
mat(2,3) = 1.0f; |
|
|
mat(3,2) = -2.0f; |
|
|
mat(3,3) = 0.0f; |
|
|
|
|
|
IPushPiggyBacks(material); |
|
|
|
|
|
/// Draw the vertex buffer once for each material pass |
|
|
for( i = 0; i < material->GetNumLayers(); ) |
|
|
{ |
|
|
// Stat gather adjust: since IHandleMaterial will count this in the stat gather, |
|
|
// artificially decrement here so that the plates don't skew the stat gathering |
|
|
// Taking this out. If the plates are causing more material changes, they should |
|
|
// show up in the stats. mf |
|
|
|
|
|
|
|
|
i = IHandleMaterial( material, i, nil ); |
|
|
ISetShaders(nil, nil); |
|
|
|
|
|
// To override the transform done by the z-bias |
|
|
fD3DDevice->SetTransform( D3DTS_PROJECTION, &mat ); |
|
|
// And this to override cullmode set based on material 2-sidedness. |
|
|
fD3DDevice->SetRenderState( D3DRS_CULLMODE, fCurrCullMode = D3DCULL_CW ); |
|
|
|
|
|
WEAK_ERROR_CHECK( fD3DDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 ) ); |
|
|
} |
|
|
|
|
|
IPopPiggyBacks(); |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Error Message Stuff ////////////////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
// IAddErrorMessage //////////////////////////////////////////////////// |
|
|
// Append the error string to the current error string. |
|
|
void plDXPipeline::IAddErrorMessage( char *errStr ) |
|
|
{ |
|
|
static char str[ 512 ]; |
|
|
if( errStr && strlen( errStr ) + strlen( fSettings.fErrorStr ) < sizeof( fSettings.fErrorStr ) - 4 ) |
|
|
{ |
|
|
strcpy( str, fSettings.fErrorStr ); |
|
|
sprintf( fSettings.fErrorStr, "%s\n(%s)", errStr, str ); |
|
|
plStatusLog::AddLineS("pipeline.log", fSettings.fErrorStr); |
|
|
} |
|
|
} |
|
|
|
|
|
// ISetErrorMessage ////////////////////////////////////////////////////////// |
|
|
// Clear the current error string to the input string. |
|
|
void plDXPipeline::ISetErrorMessage( char *errStr ) |
|
|
{ |
|
|
if( errStr ) |
|
|
{ |
|
|
strcpy( fSettings.fErrorStr, errStr ); |
|
|
plStatusLog::AddLineS("pipeline.log", fSettings.fErrorStr); |
|
|
} |
|
|
else |
|
|
fSettings.fErrorStr[ 0 ] = nil; |
|
|
} |
|
|
|
|
|
// IGetD3DError ///////////////////////////////////////////////////////////////// |
|
|
// Convert the last D3D error code to a string (probably "Conflicting Render State"). |
|
|
void plDXPipeline::IGetD3DError() |
|
|
{ |
|
|
sprintf( fSettings.fErrorStr, "D3DError : %s", (char *)DXGetErrorString( fSettings.fDXError ) ); |
|
|
} |
|
|
|
|
|
// IShowErrorMessage ///////////////////////////////////////////////////////////// |
|
|
// Append the string to the running error string. |
|
|
void plDXPipeline::IShowErrorMessage( char *errStr ) |
|
|
{ |
|
|
if( errStr != nil ) |
|
|
IAddErrorMessage( errStr ); |
|
|
|
|
|
// hsAssert( false, fSettings.fErrorStr ); |
|
|
} |
|
|
|
|
|
// ICreateFail //////////////////////////////////////////////////////////////////// |
|
|
// Called on unrecoverable error during device creation. Frees up anything |
|
|
// allocated so far, sets the error string, and returns true. |
|
|
hsBool plDXPipeline::ICreateFail( char *errStr ) |
|
|
{ |
|
|
// Don't overwrite any error string we already had |
|
|
if( fSettings.fErrorStr[ 0 ] == 0 ) |
|
|
IGetD3DError(); |
|
|
|
|
|
if( errStr && *errStr ) |
|
|
{ |
|
|
IAddErrorMessage( errStr ); |
|
|
} |
|
|
else if( !*fSettings.fErrorStr ) |
|
|
IAddErrorMessage( "unknown" ); |
|
|
|
|
|
IReleaseDeviceObjects(); |
|
|
return true; |
|
|
} |
|
|
|
|
|
// GetErrorString /////////////////////////////////////////////////////////////////////////// |
|
|
// Return the current error string. |
|
|
const char *plDXPipeline::GetErrorString() |
|
|
{ |
|
|
if( fSettings.fErrorStr[ 0 ] == 0 ) |
|
|
return nil; |
|
|
|
|
|
return fSettings.fErrorStr; |
|
|
} |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Miscellaneous Utility Functions ////////////////////////////////////////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
//// GetDXBitDepth ////////////////////////////////////////////////////////// |
|
|
// |
|
|
// From a D3DFORMAT enumeration, return the bit depth associated with it. |
|
|
|
|
|
short plDXPipeline::GetDXBitDepth( D3DFORMAT format ) |
|
|
{ |
|
|
if( format == D3DFMT_UNKNOWN ) |
|
|
return 0; |
|
|
else if( format == D3DFMT_R8G8B8 ) |
|
|
return 24; |
|
|
else if( format == D3DFMT_A8R8G8B8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_X8R8G8B8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_R5G6B5 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_X1R5G5B5 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_A1R5G5B5 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_A4R4G4B4 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_R3G3B2 ) |
|
|
return 8; |
|
|
else if( format == D3DFMT_A8 ) |
|
|
return 8; |
|
|
else if( format == D3DFMT_A8R3G3B2 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_X4R4G4B4 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_A8P8 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_P8 ) |
|
|
return 8; |
|
|
else if( format == D3DFMT_L8 ) |
|
|
return 8; |
|
|
else if( format == D3DFMT_A8L8 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_A4L4 ) |
|
|
return 8; |
|
|
else if( format == D3DFMT_V8U8 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_L6V5U5 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_X8L8V8U8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_Q8W8V8U8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_V16U16 ) |
|
|
return 32; |
|
|
// else if( format == D3DFMT_W11V11U10 ) |
|
|
// return 32; |
|
|
/* /// These formats really don't have bit depths associated with them |
|
|
D3DFMT_UYVY |
|
|
D3DFMT_YUY2 |
|
|
D3DFMT_DXT1 |
|
|
D3DFMT_DXT2 |
|
|
D3DFMT_DXT3 |
|
|
D3DFMT_DXT4 |
|
|
D3DFMT_DXT5 |
|
|
D3DFMT_VERTEXDATA |
|
|
*/ |
|
|
else if( format == D3DFMT_D16_LOCKABLE ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_D32 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_D15S1 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_D24S8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_D16 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_D24X8 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_D24X4S4 ) |
|
|
return 32; |
|
|
else if( format == D3DFMT_INDEX16 ) |
|
|
return 16; |
|
|
else if( format == D3DFMT_INDEX32 ) |
|
|
return 32; |
|
|
|
|
|
// Unsupported translation format--return 0 |
|
|
return 0; |
|
|
} |
|
|
|
|
|
//// IGetDXFormatName //////////////////////////////////////////////////////// |
|
|
// |
|
|
// From a D3DFORMAT enumeration, return the string for it. |
|
|
|
|
|
const char *plDXPipeline::IGetDXFormatName( D3DFORMAT format ) |
|
|
{ |
|
|
switch( format ) |
|
|
{ |
|
|
case D3DFMT_UNKNOWN: return "D3DFMT_UNKNOWN"; |
|
|
case D3DFMT_R8G8B8: return "D3DFMT_R8G8B8"; |
|
|
case D3DFMT_A8R8G8B8: return "D3DFMT_A8R8G8B8"; |
|
|
case D3DFMT_X8R8G8B8: return "D3DFMT_X8R8G8B8"; |
|
|
case D3DFMT_R5G6B5: return "D3DFMT_R5G6B5"; |
|
|
case D3DFMT_X1R5G5B5: return "D3DFMT_X1R5G5B5"; |
|
|
case D3DFMT_A1R5G5B5: return "D3DFMT_A1R5G5B5"; |
|
|
case D3DFMT_A4R4G4B4: return "D3DFMT_A4R4G4B4"; |
|
|
case D3DFMT_R3G3B2: return "D3DFMT_R3G3B2"; |
|
|
case D3DFMT_A8: return "D3DFMT_A8"; |
|
|
case D3DFMT_A8R3G3B2: return "D3DFMT_A8R3G3B2"; |
|
|
case D3DFMT_X4R4G4B4: return "D3DFMT_X4R4G4B4"; |
|
|
case D3DFMT_A8P8: return "D3DFMT_A8P8"; |
|
|
case D3DFMT_P8: return "D3DFMT_P8"; |
|
|
case D3DFMT_L8: return "D3DFMT_L8"; |
|
|
case D3DFMT_A8L8: return "D3DFMT_A8L8"; |
|
|
case D3DFMT_A4L4: return "D3DFMT_A4L4"; |
|
|
case D3DFMT_V8U8: return "D3DFMT_V8U8"; |
|
|
case D3DFMT_L6V5U5: return "D3DFMT_L6V5U5"; |
|
|
case D3DFMT_X8L8V8U8: return "D3DFMT_X8L8V8U8"; |
|
|
case D3DFMT_Q8W8V8U8: return "D3DFMT_Q8W8V8U8"; |
|
|
case D3DFMT_V16U16: return "D3DFMT_V16U16"; |
|
|
//case D3DFMT_W11V11U10: return "D3DFMT_W11V11U10"; |
|
|
case D3DFMT_UYVY: return "D3DFMT_UYVY"; |
|
|
case D3DFMT_YUY2: return "D3DFMT_YUY2"; |
|
|
case D3DFMT_DXT1: return "D3DFMT_DXT1"; |
|
|
// case D3DFMT_DXT2: return "D3DFMT_DXT2"; |
|
|
// case D3DFMT_DXT3: return "D3DFMT_DXT3"; |
|
|
// case D3DFMT_DXT4: return "D3DFMT_DXT4"; |
|
|
case D3DFMT_DXT5: return "D3DFMT_DXT5"; |
|
|
case D3DFMT_VERTEXDATA: return "D3DFMT_VERTEXDATA"; |
|
|
case D3DFMT_D16_LOCKABLE: return "D3DFMT_D16_LOCKABLE"; |
|
|
case D3DFMT_D32: return "D3DFMT_D32"; |
|
|
case D3DFMT_D15S1: return "D3DFMT_D15S1"; |
|
|
case D3DFMT_D24S8: return "D3DFMT_D24S8"; |
|
|
case D3DFMT_D16: return "D3DFMT_D16"; |
|
|
case D3DFMT_D24X8: return "D3DFMT_D24X8"; |
|
|
case D3DFMT_D24X4S4: return "D3DFMT_D24X4S4"; |
|
|
case D3DFMT_INDEX16: return "D3DFMT_INDEX16"; |
|
|
case D3DFMT_INDEX32: return "D3DFMT_INDEX32"; |
|
|
default: return "Bad format"; |
|
|
} |
|
|
} |
|
|
|
|
|
//// IFPUCheck //////////////////////////////////////////////////////////////// |
|
|
// Checks the FPU to make sure it's in the right mode |
|
|
// This should return wSave to allow it to be restored after rendering. |
|
|
// This is obsolete as of DX8 |
|
|
void plDXPipeline::IFPUCheck() |
|
|
{ |
|
|
WORD wSave, wTemp; |
|
|
__asm fstcw wSave |
|
|
if (wSave & 0x300 || // Not single mode |
|
|
0x3f != (wSave & 0x3f) || // Exceptions enabled |
|
|
wSave & 0xC00) // Not round to nearest mode |
|
|
{ |
|
|
__asm |
|
|
{ |
|
|
mov ax, wSave |
|
|
and ax, not 0x300 ;; single mode |
|
|
or ax, 0x3f ;; disable all exceptions |
|
|
and ax, not 0xC00 ;; round to nearest mode |
|
|
mov wTemp, ax |
|
|
fldcw wTemp |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// PushPiggyBackLayer ///////////////////////////////////////////////////// |
|
|
// Push a piggy back onto the stack. |
|
|
plLayerInterface* plDXPipeline::PushPiggyBackLayer(plLayerInterface* li) |
|
|
{ |
|
|
fPiggyBackStack.Push(li); |
|
|
|
|
|
ISetNumActivePiggyBacks(); |
|
|
|
|
|
fForceMatHandle = true; |
|
|
|
|
|
return li; |
|
|
} |
|
|
|
|
|
// PopPiggyBackLayer /////////////////////////////////////////////////////////////////// |
|
|
// Pull the piggy back out of the stack (if it's there). |
|
|
plLayerInterface* plDXPipeline::PopPiggyBackLayer(plLayerInterface* li) |
|
|
{ |
|
|
int idx = fPiggyBackStack.Find(li); |
|
|
if( fPiggyBackStack.kMissingIndex == idx ) |
|
|
return nil; |
|
|
fPiggyBackStack.Remove(idx); |
|
|
|
|
|
ISetNumActivePiggyBacks(); |
|
|
|
|
|
fForceMatHandle = true; |
|
|
|
|
|
return li; |
|
|
} |
|
|
|
|
|
// AppendLayerInterface /////////////////////////////////////////////////////////////////// |
|
|
// Setup a layer wrapper to wrap around either all layers rendered with or just the base layers. |
|
|
// Note that a single material has multiple base layers if it takes mutliple passes to render. |
|
|
// Stays in effect until removed by RemoveLayerInterface. |
|
|
plLayerInterface* plDXPipeline::AppendLayerInterface(plLayerInterface* li, hsBool onAllLayers) |
|
|
{ |
|
|
fForceMatHandle = true; |
|
|
if( onAllLayers ) |
|
|
return fOverAllLayer = li->Attach(fOverAllLayer); |
|
|
else |
|
|
return fOverBaseLayer = li->Attach(fOverBaseLayer); |
|
|
} |
|
|
|
|
|
// RemoveLayerInterface ////////////////////////////////////////////////////////////////// |
|
|
// Removes a layer wrapper installed by AppendLayerInterface. |
|
|
plLayerInterface* plDXPipeline::RemoveLayerInterface(plLayerInterface* li, hsBool onAllLayers) |
|
|
{ |
|
|
fForceMatHandle = true; |
|
|
|
|
|
if( onAllLayers ) |
|
|
{ |
|
|
if( !fOverAllLayer ) |
|
|
return nil; |
|
|
return fOverAllLayer = fOverAllLayer->Remove(li); |
|
|
} |
|
|
|
|
|
if( !fOverBaseLayer ) |
|
|
return nil; |
|
|
|
|
|
return fOverBaseLayer = fOverBaseLayer->Remove(li); |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// ShadowSection |
|
|
//// Shadow specific internal functions |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// See plGLight/plShadowMaster.cpp for more notes. |
|
|
|
|
|
// IAttachShadowsToReceivers /////////////////////////////////////////////////////////// |
|
|
// For each active shadow map (in fShadows), attach it to all of the visible spans in drawable |
|
|
// that it affects. Shadows explicitly attached via light groups are handled separately in ISetShadowFromGroup. |
|
|
void plDXPipeline::IAttachShadowsToReceivers(plDrawableSpans* drawable, const hsTArray<Int16>& visList) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fShadows.GetCount(); i++ ) |
|
|
IAttachSlaveToReceivers(i, drawable, visList); |
|
|
} |
|
|
|
|
|
// IAttachSlaveToReceivers ///////////////////////////////////////////////////// |
|
|
// Find all the visible spans in this drawable affected by this shadow map, |
|
|
// and attach it to them. |
|
|
void plDXPipeline::IAttachSlaveToReceivers(int which, plDrawableSpans* drawable, const hsTArray<Int16>& visList) |
|
|
{ |
|
|
plShadowSlave* slave = fShadows[which]; |
|
|
|
|
|
// Whether the drawable is a character affects which lights/shadows affect it. |
|
|
hsBool isChar = drawable->GetNativeProperty(plDrawable::kPropCharacter); |
|
|
|
|
|
// If the shadow is part of a light group, it gets handled in ISetShadowFromGroup. |
|
|
// Unless the drawable is a character (something that moves around indeterminately, |
|
|
// like the avatar or a physical object), and the shadow affects all characters. |
|
|
if( slave->ObeysLightGroups() && !(slave->IncludesChars() && isChar) ) |
|
|
return; |
|
|
|
|
|
// Do a space tree harvest looking for spans that are visible and whose bounds |
|
|
// intercect the shadow volume. |
|
|
plSpaceTree* space = drawable->GetSpaceTree(); |
|
|
|
|
|
static hsBitVector cache; |
|
|
cache.Clear(); |
|
|
space->EnableLeaves(visList, cache); |
|
|
|
|
|
static hsTArray<Int16> hitList; |
|
|
hitList.SetCount(0); |
|
|
space->HarvestEnabledLeaves(slave->fIsect, cache, hitList); |
|
|
|
|
|
// For the visible spans that intercect the shadow volume, attach the shadow |
|
|
// to all appropriate for receiving this shadow map. |
|
|
int i; |
|
|
for( i = 0; i < hitList.GetCount(); i++ ) |
|
|
{ |
|
|
const plSpan* span = drawable->GetSpan(hitList[i]); |
|
|
hsGMaterial* mat = drawable->GetMaterial(span->fMaterialIdx); |
|
|
|
|
|
// Check that the span isn't flagged as unshadowable, or has |
|
|
// a material that we can't shadow onto. |
|
|
if( !IReceivesShadows(span, mat) ) |
|
|
continue; |
|
|
|
|
|
// Check for self shadowing. If the shadow doesn't want self shadowing, |
|
|
// and the span is part of the shadow caster, then skip. |
|
|
if( !IAcceptsShadow(span, slave) ) |
|
|
continue; |
|
|
|
|
|
// Add it to this span's shadow list for this frame. |
|
|
span->AddShadowSlave(fShadows[which]->fIndex); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// ISetShadowFromGroup //////////////////////////////////////////////////////////////////////// |
|
|
// The light casting this shadow has been explicitly attached to this span, so no need |
|
|
// for checking bounds, but we do anyway because the artists aren't very conservative |
|
|
// along those lines. The light has a bitvector indicating which of the current shadows |
|
|
// are from it (there will be a shadow map for each shadow-light/shadow-caster pair), |
|
|
// so we look through those shadow maps and if they are acceptable, attach them to |
|
|
// the span. |
|
|
// Note that a shadow slave corresponds to a shadow map. |
|
|
void plDXPipeline::ISetShadowFromGroup(plDrawableSpans* drawable, const plSpan* span, plLightInfo* liInfo) |
|
|
{ |
|
|
hsGMaterial* mat = drawable->GetMaterial(span->fMaterialIdx); |
|
|
|
|
|
// Check that this span/material combo can receive shadows at all. |
|
|
if( !IReceivesShadows(span, mat) ) |
|
|
return; |
|
|
|
|
|
const hsBitVector& slaveBits = liInfo->GetSlaveBits(); |
|
|
int i; |
|
|
for( i = 0; i < fShadows.GetCount(); i++ ) |
|
|
{ |
|
|
if( slaveBits.IsBitSet(fShadows[i]->fIndex) ) |
|
|
{ |
|
|
// Check self shadowing. |
|
|
if( IAcceptsShadow(span, fShadows[i]) ) |
|
|
{ |
|
|
// Check for overlapping bounds. |
|
|
if( fShadows[i]->fIsect->Test(span->fWorldBounds) != kVolumeCulled ) |
|
|
span->AddShadowSlave(fShadows[i]->fIndex); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// SubmitShadowSlave //////////////////////////////////////////////////////// |
|
|
// Puts the slave in a list valid for this frame only. The list will |
|
|
// be preprocessed at BeginRender. See IPreprocessShadows. |
|
|
|
|
|
void plDXPipeline::SubmitShadowSlave(plShadowSlave* slave) |
|
|
{ |
|
|
// Check that it's a valid slave. |
|
|
if( !(slave && slave->fCaster && slave->fCaster->GetKey()) ) |
|
|
return; |
|
|
|
|
|
// A board with limited projection capability (i.e. GeForce1) can't |
|
|
// do perspective shadows (from point source lights) because it |
|
|
// requires a count3 uvw on 2 texture units (0,1) simultaneously. Just skip. |
|
|
if( (fSettings.fLimitedProj || fSettings.fCantProj) && slave->fView.GetPerspective() ) |
|
|
return; |
|
|
|
|
|
// Ref the shadow caster so we're sure it will still be around when we go to |
|
|
// render it. |
|
|
slave->fCaster->GetKey()->RefObject(); |
|
|
|
|
|
// Keep the shadow slaves in a priority sorted list. For performance reasons, |
|
|
// we may want only the strongest N or those of a minimum priority. |
|
|
int i; |
|
|
for( i = 0; i < fShadows.GetCount(); i++ ) |
|
|
{ |
|
|
if( slave->fPriority <= fShadows[i]->fPriority ) |
|
|
break; |
|
|
} |
|
|
|
|
|
// Note that fIndex is no longer the index in the fShadows list, but |
|
|
// is still used as a unique identifier for this slave. |
|
|
slave->fIndex = fShadows.GetCount(); |
|
|
fShadows.Insert(i, slave); |
|
|
} |
|
|
|
|
|
hsScalar blurScale = -1.f; |
|
|
static const int kL2NumSamples = 3; // Log2(4) |
|
|
|
|
|
// IBlurShadowMap ////////////////////////////////////////////////////////////////// |
|
|
// For a shadow map, we've got a specific (non-general) blurring in mind. |
|
|
// This could be used as a loose model for more general blurring, but you |
|
|
// wouldn't want to run a generic texture or render target through this. |
|
|
// Specifically, we assume: |
|
|
// Input: |
|
|
// An RGBA rendertarget with an alpha we want to preserve, and color |
|
|
// going from black (unused) to white (written). |
|
|
// A blur factor |
|
|
// Output: |
|
|
// The rendertarget with alpha preserved, and the color channel blurred |
|
|
// appropriately. |
|
|
// We'll want to minimize our render target changes, so |
|
|
// we clear our scratch render target to black/white (color/alpha), then |
|
|
// render additively the color of our input with a zero alpha. The scratch |
|
|
// accumulates the color sum, but the alpha starts and stays saturated to 100%. |
|
|
// Then we modulate that back into the input, so the alpha is unchanged, the |
|
|
// color (within the white region) falls off at the edges. The color outside the |
|
|
// white region is black and stays black, but we don't care because we'll be ignoring |
|
|
// that anyway. |
|
|
// Notice that this depends on the input, each pixel having been all black or all "white". |
|
|
// Also depends on "white" having 1/N premodulated in, where N is the number of samples. |
|
|
// That's why we can just sum up the colors, without needing to do a divide. Otherwise |
|
|
// we'd saturate at 255 during the sum, and the divide would be pointless. |
|
|
// One other thing we're counting on here, is that we've just been rendering to an |
|
|
// offscreen, we're done, and we're about to pop our rendertarget, which is going |
|
|
// to reset a lot of render state that we would otherwise be responsible for here. |
|
|
// We're hoping that this blur function (if efficient enough) can get called enough times |
|
|
// per frame to warrant the sins described above. |
|
|
void plDXPipeline::IBlurShadowMap(plShadowSlave* slave) |
|
|
{ |
|
|
plRenderTarget* smap = (plRenderTarget*)slave->fPipeData; |
|
|
hsScalar scale = slave->fBlurScale; |
|
|
|
|
|
// Find a scratch rendertarget which matches the input. |
|
|
int which = IGetScratchRenderTarget(smap); |
|
|
plRenderTarget* scratchRT = fBlurScratchRTs[which]; |
|
|
if( !scratchRT ) |
|
|
return; |
|
|
plRenderTarget* destRT = fBlurDestRTs[which]; |
|
|
if( !destRT ) |
|
|
return; |
|
|
|
|
|
// Set up to render into it. |
|
|
IBlurSetRenderTarget(scratchRT); |
|
|
|
|
|
// Clear it appropriately |
|
|
fD3DDevice->Clear(0, nil, D3DCLEAR_TARGET, 0xff000000L, 1.0f, 0L); |
|
|
|
|
|
// Setup our quad for rendering |
|
|
ISetBlurQuadToRender(smap); |
|
|
|
|
|
// Render the input image into the scratch image, creating the blur. |
|
|
IRenderBlurFromShadowMap(scratchRT, smap, scale); |
|
|
|
|
|
// Set the rendertarget back to src |
|
|
// Setup renderstate to render it back modulating. |
|
|
// Render the scratch back into the input. |
|
|
IRenderBlurBackToShadowMap(smap, scratchRT, destRT); |
|
|
|
|
|
// dst is now now slave's rendertarget and smap is the new scratch dst |
|
|
// for this size. |
|
|
slave->fPipeData = (void*)destRT; |
|
|
fBlurDestRTs[which] = smap; |
|
|
} |
|
|
|
|
|
// IGetScratchRenderTarget //////////////////////////////////////////// |
|
|
// Look for a render target for as scratch space for blurring the input render target. |
|
|
// Note that the whole blur process requires 3 render targets, the source, |
|
|
// an intermediate, and the destination (which gets swapped with the source). |
|
|
// But that's only an extra 2 render targets for all shadow maps of a given |
|
|
// size. |
|
|
// Note also that the intermediate is one size smaller than the source, |
|
|
// to get better blurring through bilerp magnification. |
|
|
int plDXPipeline::IGetScratchRenderTarget(plRenderTarget* smap) |
|
|
{ |
|
|
int which = -1; |
|
|
switch(smap->GetHeight()) |
|
|
{ |
|
|
case 512: |
|
|
which = 9; |
|
|
break; |
|
|
case 256: |
|
|
which = 8; |
|
|
break; |
|
|
case 128: |
|
|
which = 7; |
|
|
break; |
|
|
case 64: |
|
|
which = 6; |
|
|
break; |
|
|
case 32: |
|
|
which = 5; |
|
|
break; |
|
|
default: |
|
|
return false; |
|
|
} |
|
|
if( !fBlurScratchRTs[which] ) |
|
|
{ |
|
|
// We may or may not get back the size we requested here, but if we didn't, |
|
|
// we aren't going to later, so we might as well stuff the smaller render target |
|
|
// into the bigger slot. Bad thing is that we might want a smaller render target |
|
|
// later, and we won't know to look in the bigger slot for it, so we could wind |
|
|
// up using say two 128x128's (one in the 256 slot, one in the 128 slot). |
|
|
// This intermediate is one power of 2 smaller than the source. |
|
|
UInt32 width = smap->GetWidth(); |
|
|
UInt32 height = smap->GetHeight(); |
|
|
if( width > 32 ) |
|
|
{ |
|
|
width >>= 1; |
|
|
height >>= 1; |
|
|
} |
|
|
fBlurScratchRTs[which] = IFindRenderTarget(width, height, smap->GetFlags() & plRenderTarget::kIsOrtho); |
|
|
} |
|
|
if( !fBlurDestRTs[which] ) |
|
|
{ |
|
|
// Destination is same size as source. |
|
|
UInt32 width = smap->GetWidth(); |
|
|
UInt32 height = smap->GetHeight(); |
|
|
fBlurDestRTs[which] = IFindRenderTarget(width, height, smap->GetFlags() & plRenderTarget::kIsOrtho); |
|
|
} |
|
|
#ifdef MF_ENABLE_HACKOFF |
|
|
if( hackOffscreens.kMissingIndex == hackOffscreens.Find(fBlurScratchRTs[which]) ) |
|
|
hackOffscreens.Append(fBlurScratchRTs[which]); |
|
|
if( hackOffscreens.kMissingIndex == hackOffscreens.Find(fBlurDestRTs[which]) ) |
|
|
hackOffscreens.Append(fBlurDestRTs[which]); |
|
|
#endif // MF_ENABLE_HACKOFF |
|
|
return which; |
|
|
} |
|
|
|
|
|
// IBlurSetRenderTarget ///////////////////////////////////////////////////////////////////// |
|
|
// Set the input render target up to be rendered into. This abbreviated version |
|
|
// of PushRenderTarget is possible because of the special case of the state coming |
|
|
// in, and that we know we're going to immediately pop the previous render target |
|
|
// when we're done. |
|
|
void plDXPipeline::IBlurSetRenderTarget(plRenderTarget* rt) |
|
|
{ |
|
|
plDXRenderTargetRef* ref = (plDXRenderTargetRef *)rt->GetDeviceRef(); |
|
|
// Set the rendertarget |
|
|
IDirect3DSurface9* main = ref->GetColorSurface(); |
|
|
IDirect3DSurface9* depth = ref->fD3DDepthSurface; |
|
|
|
|
|
fSettings.fCurrD3DMainSurface = main; |
|
|
fSettings.fCurrD3DDepthSurface = depth; |
|
|
fD3DDevice->SetRenderTarget(0, main); |
|
|
fD3DDevice->SetDepthStencilSurface(depth); |
|
|
|
|
|
// Now set the correct viewport |
|
|
D3DVIEWPORT9 vp = { 0, |
|
|
0, |
|
|
rt->GetWidth(), |
|
|
rt->GetHeight(), |
|
|
0.f, 1.f }; |
|
|
|
|
|
|
|
|
WEAK_ERROR_CHECK( fD3DDevice->SetViewport( &vp ) ); |
|
|
} |
|
|
|
|
|
|
|
|
// IRenderBlurFromShadowMap //////////////////////////////////////////////////////////////////////////////// |
|
|
// Render a shadow map into a scratch render target multiple times offset slightly to create a blur |
|
|
// in the color, preserving alpha exactly. It's just rendering a single quad with slight offsets |
|
|
// in the UVW transform. |
|
|
void plDXPipeline::IRenderBlurFromShadowMap(plRenderTarget* scratchRT, plRenderTarget* smap, hsScalar scale) |
|
|
{ |
|
|
// Quad is set up in camera space. |
|
|
fD3DDevice->SetTransform(D3DTS_VIEW, &d3dIdentityMatrix); |
|
|
fD3DDevice->SetTransform(D3DTS_WORLD, &d3dIdentityMatrix); |
|
|
fD3DDevice->SetTransform(D3DTS_PROJECTION, &d3dIdentityMatrix); |
|
|
|
|
|
// Figure out how many passes we'll need. |
|
|
// const int kNumSamples = 1 << kL2NumSamples; // HACKSAMPLE |
|
|
const int kNumSamples = mfCurrentTest > 101 ? 8 : 4; |
|
|
int nPasses = (int)hsCeil(float(kNumSamples) / fSettings.fMaxLayersAtOnce); |
|
|
int nSamplesPerPass = kNumSamples / nPasses; |
|
|
|
|
|
// Attenuate by number of passes, to average as we sum. |
|
|
DWORD atten = 255 / nPasses; |
|
|
plConst(float) kAtten(1.f); |
|
|
atten = DWORD(atten * kAtten); |
|
|
atten = (atten << 24) |
|
|
| (atten << 16) |
|
|
| (atten << 8) |
|
|
| (atten << 0); |
|
|
|
|
|
// Disable skinning |
|
|
fD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE); |
|
|
// |
|
|
// AlphaEnable = true |
|
|
// AlphaTest OFF |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); |
|
|
fD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); |
|
|
fD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); |
|
|
|
|
|
// ZBUFFER disabled |
|
|
fD3DDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); |
|
|
fD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); |
|
|
fLayerState[0].fZFlags &= ~hsGMatState::kZMask; |
|
|
fLayerState[0].fZFlags |= hsGMatState::kZNoZWrite | hsGMatState::kZNoZRead; |
|
|
// |
|
|
// Cullmode is NONE |
|
|
fCurrCullMode = D3DCULL_NONE; |
|
|
fD3DDevice->SetRenderState( D3DRS_CULLMODE, fCurrCullMode ); |
|
|
|
|
|
plDXTextureRef* ref = (plDXTextureRef*)smap->GetDeviceRef(); |
|
|
hsAssert(ref, "Shadow map ref should have been made when it was rendered"); |
|
|
if( !ref ) |
|
|
return; |
|
|
|
|
|
// TFactor contains the attenuation |
|
|
fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, atten); |
|
|
|
|
|
// Set the N texture stages all to use the same |
|
|
// src rendertarget texture. |
|
|
// Blend modes are: |
|
|
// Stage0: |
|
|
// Color |
|
|
// Arg1 = texture |
|
|
// Op = selectArg1 |
|
|
// Alpha |
|
|
// Arg1 = TFACTOR = white |
|
|
// Op = selectArg1 |
|
|
// Stage[1..n-1] |
|
|
// Color |
|
|
// Arg1 = texture |
|
|
// Arg2 = current |
|
|
// Op = AddSigned |
|
|
// Alpha |
|
|
// Arg1 = texture |
|
|
// Arg2 = current |
|
|
// Op = SelectArg2 |
|
|
// StageN |
|
|
// Color/Alpha |
|
|
// Op = disable |
|
|
// |
|
|
// Frame buffer blend is |
|
|
// SRCBLEND = ONE |
|
|
// DSTBLEND = ONE |
|
|
// All texture stages are clamped |
|
|
// |
|
|
// Set stage0, then loop over the rest |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[0].fClampFlags = hsGMatState::kClampTexture; |
|
|
|
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); |
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
|
|
|
hsRefCnt_SafeAssign( fLayerRef[0], ref ); |
|
|
fD3DDevice->SetTexture( 0, ref->fD3DTexture ); |
|
|
|
|
|
if( D3DTTFF_COUNT2 != fLayerXformFlags[0] ) |
|
|
{ |
|
|
fLayerXformFlags[0] = D3DTTFF_COUNT2; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); |
|
|
} |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); |
|
|
fLayerUVWSrcs[0] = 0; |
|
|
|
|
|
int i; |
|
|
for( i = 1; i < nSamplesPerPass; i++ ) |
|
|
{ |
|
|
fD3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(i, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[i].fClampFlags = hsGMatState::kClampTexture; |
|
|
|
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_COLORARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_COLOROP, D3DTOP_ADDSIGNED); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_ALPHAARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); |
|
|
fLayerState[i].fBlendFlags = UInt32(-1); |
|
|
|
|
|
hsRefCnt_SafeAssign( fLayerRef[i], ref ); |
|
|
fD3DDevice->SetTexture( i, ref->fD3DTexture ); |
|
|
|
|
|
if( D3DTTFF_COUNT2 != fLayerXformFlags[i] ) |
|
|
{ |
|
|
fLayerXformFlags[i] = D3DTTFF_COUNT2; |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); |
|
|
} |
|
|
fD3DDevice->SetTextureStageState(i, D3DTSS_TEXCOORDINDEX, 0); |
|
|
fLayerUVWSrcs[i] = 0; |
|
|
} |
|
|
fD3DDevice->SetTextureStageState(nSamplesPerPass, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(nSamplesPerPass, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
|
|
|
// N offsets are { (-1,-1), (1, -1), (1, 1), (-1, 1) } * offsetScale / size, with |
|
|
// useful offsetScales probably going from 0.5 to 1.5, but we'll just have |
|
|
// to experiment and see. Larger values likely to require more than the current |
|
|
// 4 samples |
|
|
struct offsetStruct |
|
|
{ |
|
|
float fU; |
|
|
float fV; |
|
|
}; |
|
|
offsetStruct offsetScale = { scale / scratchRT->GetWidth(), scale / scratchRT->GetHeight() }; |
|
|
static offsetStruct offsets[8] = { |
|
|
{-1.f, -1.f}, |
|
|
{1.f, -1.f}, |
|
|
{1.f, 1.f}, |
|
|
{-1.f, 1.f}, |
|
|
{0.f, -0.5f}, |
|
|
{0.f, 0.5f}, |
|
|
{-0.5f, 0.f}, |
|
|
{0.5f, 0.f} |
|
|
}; |
|
|
|
|
|
int iSample = 0; |
|
|
// For each pass, |
|
|
for( i = 0; i < nPasses; i++ ) |
|
|
{ |
|
|
// Set the N texture stage uv transforms to the |
|
|
// next N offsets. |
|
|
int j; |
|
|
for( j = 0; j < nSamplesPerPass; j++ ) |
|
|
{ |
|
|
D3DXMATRIX offXfm = d3dIdentityMatrix; |
|
|
offXfm(2,0) = offsets[iSample].fU * offsetScale.fU; |
|
|
offXfm(2,1) = offsets[iSample].fV * offsetScale.fV; |
|
|
fD3DDevice->SetTransform(sTextureStages[j], &offXfm); |
|
|
fLayerTransform[j] = true; |
|
|
|
|
|
iSample++; |
|
|
} |
|
|
|
|
|
// Render our quad |
|
|
fD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); |
|
|
|
|
|
// fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, 0L); |
|
|
// fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); |
|
|
// fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); |
|
|
// fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_ADDSIGNED); |
|
|
// fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
// IRenderBlurBackToShadowMap ///////////////////////////////////////////////////////////////////// |
|
|
// Render our intermediate blurred map back into a useable shadow map. |
|
|
void plDXPipeline::IRenderBlurBackToShadowMap(plRenderTarget* smap, plRenderTarget* scratch, plRenderTarget* dst) |
|
|
{ |
|
|
// Set the rendertarget |
|
|
IBlurSetRenderTarget(dst); |
|
|
|
|
|
// Clear it appropriately. This might not be necessary, since we're just going to overwrite. |
|
|
fD3DDevice->Clear(0, nil, D3DCLEAR_TARGET, 0xff000000L, 1.0f, 0L); |
|
|
|
|
|
// Scratch has an all white alpha, and the blurred color from smap. But the color |
|
|
// is a signed biased color. We need to remap [128..255] from scratch into [0..255] |
|
|
// on dst. Plus, we need to copy the alpha as is from smap into dst. |
|
|
// So, scratch is texture0, smap is texture1. TFACTOR is 0. |
|
|
// Color is ADDSIGNED2X(TFACTOR, texture0). |
|
|
// Alpha is SELECTARG1(texture1, current). |
|
|
// Then FB blend is just opaque copy. |
|
|
|
|
|
// Set Stage0 texture transform |
|
|
// Clamp still on (from RBFSM) |
|
|
D3DXMATRIX offXfm = d3dIdentityMatrix; |
|
|
fD3DDevice->SetTransform(sTextureStages[0], &offXfm); |
|
|
fD3DDevice->SetTransform(sTextureStages[1], &offXfm); |
|
|
fLayerTransform[0] = false; |
|
|
fLayerTransform[1] = false; |
|
|
|
|
|
plDXTextureRef* ref = (plDXTextureRef*)scratch->GetDeviceRef(); |
|
|
hsAssert(ref, "Blur scratch map ref should have been made when it was rendered"); |
|
|
if( !ref ) |
|
|
return; |
|
|
hsRefCnt_SafeAssign( fLayerRef[0], ref ); |
|
|
fD3DDevice->SetTexture( 0, ref->fD3DTexture ); |
|
|
|
|
|
ref = (plDXTextureRef*)smap->GetDeviceRef(); |
|
|
hsAssert(ref, "Blur src map ref should have been made when it was rendered"); |
|
|
if( !ref ) |
|
|
return; |
|
|
hsRefCnt_SafeAssign( fLayerRef[1], ref ); |
|
|
fD3DDevice->SetTexture( 1, ref->fD3DTexture ); |
|
|
|
|
|
// Stage0: |
|
|
// Color |
|
|
// Arg1 = TFACTOR = black |
|
|
// Arg2 = texture |
|
|
// Op = ADDSIGNED2X |
|
|
// Alpha |
|
|
// Arg1 = texture |
|
|
// Op = selectArg1 |
|
|
// Texture = scratch |
|
|
// Stage1: |
|
|
// Color |
|
|
// Arg1 = texture |
|
|
// Arg2 = current |
|
|
// Op = selectArg2 |
|
|
// Alpha |
|
|
// Arg1 = texture |
|
|
// Op = selectArg1 |
|
|
// Texture = smap |
|
|
// FB blend |
|
|
// SRCBLEND = ONE |
|
|
// DSTBLEND = ZERO |
|
|
|
|
|
fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, 0L); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_ADDSIGNED2X); |
|
|
|
|
|
// This alpha will be ignored, because in the next stage we select texture alpha. |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); |
|
|
|
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG2); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); |
|
|
|
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(2, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
|
|
|
fLastEndingStage = 2; |
|
|
|
|
|
fD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); |
|
|
fD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); |
|
|
|
|
|
// Our quad should still be setup to go. |
|
|
fD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); |
|
|
|
|
|
} |
|
|
|
|
|
struct plShadowVertStruct |
|
|
{ |
|
|
float fPos[3]; |
|
|
float fUv[2]; |
|
|
}; |
|
|
|
|
|
// IReleaseBlurVBuffers ////////////////////////////////////////////////////////// |
|
|
// Free up our blur quad vertex buffers. Note these are in POOL_DEFAULT |
|
|
void plDXPipeline::IReleaseBlurVBuffers() |
|
|
{ |
|
|
const UInt32 kVSize = sizeof(plShadowVertStruct); |
|
|
int i; |
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
{ |
|
|
if (fBlurVBuffers[i]) |
|
|
{ |
|
|
ReleaseObject(fBlurVBuffers[i]); |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, 4 * kVSize, false, "BlurVtxBuff"); |
|
|
fBlurVBuffers[i] = nil; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// ICreateBlurVBuffers ////////////////////////////////////////////////////////////////// |
|
|
// We need a quad for each size of shadow map, because there's a slight dependency |
|
|
// of UVW coordinates on size of render target. Sucks but it's true. |
|
|
hsBool plDXPipeline::ICreateBlurVBuffers() |
|
|
{ |
|
|
// vertex size is 4 verts, with 4 floats each for position, and 2 floats each for uv. |
|
|
const UInt32 kVSize = sizeof(plShadowVertStruct); |
|
|
const UInt32 kVFormat = D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0) ; |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
{ |
|
|
int width = 0; |
|
|
int height = 0; |
|
|
int which = -1; |
|
|
switch( i ) |
|
|
{ |
|
|
default: |
|
|
case 0: |
|
|
case 1: |
|
|
case 2: |
|
|
case 3: |
|
|
case 4: |
|
|
break; |
|
|
case 5: |
|
|
width = height = 1 << i; |
|
|
which = i; |
|
|
break; |
|
|
case 6: |
|
|
width = height = 1 << i; |
|
|
which = i; |
|
|
break; |
|
|
case 7: |
|
|
width = height = 1 << i; |
|
|
which = i; |
|
|
break; |
|
|
case 8: |
|
|
width = height = 1 << i; |
|
|
which = i; |
|
|
break; |
|
|
case 9: |
|
|
width = height = 1 << i; |
|
|
which = i; |
|
|
break; |
|
|
} |
|
|
if( which < 0 ) |
|
|
continue; |
|
|
|
|
|
// positions are { (-0.5,-0.5,0,1), (w-0.5,-0.5,0,1), (w-0.5,h-0.5,0,1), (-0.5,h-0.5,0,1) } |
|
|
// UVs are { (0,0), (1,0), (1,1), (0,1) } |
|
|
// So we won't have to bother with indices, we'll put them in as |
|
|
// p1, p2, p0, p3 and render tristrip |
|
|
|
|
|
|
|
|
// Create the buffer. |
|
|
IDirect3DVertexBuffer9* vBuffer = nil; |
|
|
|
|
|
UInt32 fvfFormat = kVFormat; |
|
|
hsAssert(!ManagedAlloced(), "Alloc default with managed alloc'd"); |
|
|
if( FAILED( fD3DDevice->CreateVertexBuffer( 4 * kVSize, |
|
|
D3DUSAGE_WRITEONLY, |
|
|
fvfFormat, |
|
|
D3DPOOL_DEFAULT, |
|
|
&vBuffer, NULL) ) ) |
|
|
{ |
|
|
hsAssert( false, "CreateVertexBuffer() call failed!" ); |
|
|
return false; |
|
|
} |
|
|
plShadowVertStruct* ptr = nil; |
|
|
|
|
|
/// Lock the buffer and fill it in. |
|
|
if( FAILED( vBuffer->Lock( 0, 0, (void **)&ptr, 0 ) ) ) |
|
|
{ |
|
|
hsAssert( false, "Failed to lock vertex buffer for writing" ); |
|
|
vBuffer->Release(); |
|
|
return false; |
|
|
} |
|
|
PROFILE_POOL_MEM(D3DPOOL_DEFAULT, 4 * kVSize, true, "BlurVtxBuff"); |
|
|
|
|
|
plShadowVertStruct vert; |
|
|
vert.fPos[0] = -1.f; |
|
|
vert.fPos[1] = -1.f; |
|
|
vert.fPos[2] = 0.5f; |
|
|
|
|
|
vert.fUv[0] = 0.5f / width; |
|
|
vert.fUv[1] = 1.f + 0.5f / height; |
|
|
|
|
|
// P0 |
|
|
ptr[2] = vert; |
|
|
|
|
|
// P1 |
|
|
ptr[0] = vert; |
|
|
ptr[0].fPos[0] += 2.f; |
|
|
ptr[0].fUv[0] += 1.f; |
|
|
|
|
|
// P2 |
|
|
ptr[1] = vert; |
|
|
ptr[1].fPos[0] += 2.f; |
|
|
ptr[1].fUv[0] += 1.f; |
|
|
ptr[1].fPos[1] += 2.f; |
|
|
ptr[1].fUv[1] -= 1.f; |
|
|
|
|
|
// P3 |
|
|
ptr[3] = vert; |
|
|
ptr[3].fPos[1] += 2.f; |
|
|
ptr[3].fUv[1] -= 1.f; |
|
|
|
|
|
vBuffer->Unlock(); |
|
|
|
|
|
fBlurVBuffers[which] = vBuffer; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
// ISetBlurQuadToRender //////////////////////////////////////////////////// |
|
|
// Select the appropriate blur quad (based on size of shadow map) and set it up to render. |
|
|
hsBool plDXPipeline::ISetBlurQuadToRender(plRenderTarget* smap) |
|
|
{ |
|
|
const UInt32 kVSize = sizeof(plShadowVertStruct); |
|
|
const UInt32 kVFormat = D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0) ; |
|
|
|
|
|
// Each vb will be rendertarget size specific, so select one based on input rendertarget |
|
|
int which = -1; |
|
|
switch(smap->GetHeight()) |
|
|
{ |
|
|
case 512: |
|
|
which = 9; |
|
|
break; |
|
|
case 256: |
|
|
which = 8; |
|
|
break; |
|
|
case 128: |
|
|
which = 7; |
|
|
break; |
|
|
case 64: |
|
|
which = 6; |
|
|
break; |
|
|
case 32: |
|
|
which = 5; |
|
|
break; |
|
|
default: |
|
|
return false; |
|
|
} |
|
|
|
|
|
// If we haven't created (or have lost) our d3d resources, make them |
|
|
IDirect3DVertexBuffer9* vBuffer = fBlurVBuffers[which]; |
|
|
if( !vBuffer ) |
|
|
{ |
|
|
ICreateBlurVBuffers(); |
|
|
vBuffer = fBlurVBuffers[which]; |
|
|
hsAssert(vBuffer, "AllocBlurVBuffers failed"); |
|
|
} |
|
|
|
|
|
HRESULT r = fD3DDevice->SetVertexShader(fSettings.fCurrVertexShader = NULL); |
|
|
fD3DDevice->SetFVF(fSettings.fCurrFVFFormat = kVFormat); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the vertex shader!" ); |
|
|
|
|
|
hsRefCnt_SafeUnRef(fSettings.fCurrVertexBuffRef); |
|
|
fSettings.fCurrVertexBuffRef = nil; |
|
|
|
|
|
r = fD3DDevice->SetStreamSource(0, vBuffer, 0, kVSize); |
|
|
plProfile_Inc(VertexChange); |
|
|
|
|
|
// No SetIndices, we'll do a direct DrawPrimitive (not DrawIndexedPrimitive) |
|
|
|
|
|
// No transforms, we're supplying screen ready verts. |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// IRenderShadowCasterSpan ////////////////////////////////////////////////////////////////////// |
|
|
// Render the span into a rendertarget of the correct size, generating |
|
|
// a depth map from this light to that span. |
|
|
void plDXPipeline::IRenderShadowCasterSpan(plShadowSlave* slave, plDrawableSpans* drawable, const plIcicle& span) |
|
|
{ |
|
|
// Check that it's ready to render. |
|
|
plProfile_BeginTiming(CheckDyn); |
|
|
ICheckDynBuffers(drawable, drawable->GetBufferGroup(span.fGroupIdx), &span); |
|
|
plProfile_EndTiming(CheckDyn); |
|
|
|
|
|
plDXVertexBufferRef* vRef = (plDXVertexBufferRef *)drawable->GetVertexRef(span.fGroupIdx, span.fVBufferIdx); |
|
|
plDXIndexBufferRef* iRef = (plDXIndexBufferRef *)drawable->GetIndexRef(span.fGroupIdx, span.fIBufferIdx); |
|
|
|
|
|
HRESULT r; |
|
|
|
|
|
if( vRef->fD3DBuffer == nil || iRef->fD3DBuffer == nil ) |
|
|
{ |
|
|
hsAssert( false, "Trying to render a nil buffer pair!" ); |
|
|
return; |
|
|
} |
|
|
|
|
|
/// Switch to the vertex buffer we want |
|
|
if( fSettings.fCurrVertexBuffRef != vRef ) |
|
|
{ |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrVertexBuffRef, vRef ); |
|
|
hsAssert( vRef->fD3DBuffer != nil, "Trying to render a buffer pair without a vertex buffer!" ); |
|
|
vRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
if( vRef->RebuiltSinceUsed() ) |
|
|
{ |
|
|
r = fD3DDevice->SetStreamSource( 0, vRef->fD3DBuffer, 0, vRef->fVertexSize ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the stream source!" ); |
|
|
plProfile_Inc(VertexChange); |
|
|
|
|
|
fSettings.fCurrFVFFormat = IGetBufferD3DFormat(vRef->fFormat); |
|
|
r = fD3DDevice->SetVertexShader(fSettings.fCurrVertexShader = NULL); |
|
|
fD3DDevice->SetFVF(fSettings.fCurrFVFFormat); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the vertex shader!" ); |
|
|
|
|
|
vRef->SetRebuiltSinceUsed(false); |
|
|
|
|
|
} |
|
|
|
|
|
if( fSettings.fCurrIndexBuffRef != iRef ) |
|
|
{ |
|
|
hsRefCnt_SafeAssign( fSettings.fCurrIndexBuffRef, iRef ); |
|
|
hsAssert( iRef->fD3DBuffer != nil, "Trying to render with a nil index buffer" ); |
|
|
iRef->SetRebuiltSinceUsed(true); |
|
|
} |
|
|
|
|
|
if( iRef->RebuiltSinceUsed() ) |
|
|
{ |
|
|
r = fD3DDevice->SetIndices( iRef->fD3DBuffer ); |
|
|
hsAssert( r == D3D_OK, "Error trying to set the indices!" ); |
|
|
plProfile_Inc(IndexChange); |
|
|
iRef->SetRebuiltSinceUsed(false); |
|
|
} |
|
|
|
|
|
UInt32 vStart = span.fVStartIdx; |
|
|
UInt32 vLength = span.fVLength; |
|
|
UInt32 iStart = span.fIPackedIdx; |
|
|
UInt32 iLength= span.fILength; |
|
|
|
|
|
plRenderTriListFunc render(fD3DDevice, iRef->fOffset, vStart, vLength, iStart, iLength/3); |
|
|
|
|
|
static hsMatrix44 emptyMatrix; |
|
|
hsMatrix44 m = emptyMatrix; |
|
|
|
|
|
ISetupTransforms(drawable, span, m); |
|
|
|
|
|
hsBool flip = slave->ReverseCull(); |
|
|
ISetCullMode(flip); |
|
|
|
|
|
render.RenderPrims(); |
|
|
} |
|
|
|
|
|
// IGetULutTextureRef /////////////////////////////////////////////////////////// |
|
|
// The ULut just translates a U coordinate in range [0..1] into |
|
|
// color and alpha of U * 255.9f. We just have the one we keep |
|
|
// lying around. |
|
|
plDXTextureRef* plDXPipeline::IGetULutTextureRef() |
|
|
{ |
|
|
const int width = 256; |
|
|
const int height = 1; |
|
|
if( !fULutTextureRef ) |
|
|
{ |
|
|
UInt32* tData = TRACKED_NEW UInt32[width * height]; |
|
|
|
|
|
UInt32* pData = tData; |
|
|
int j; |
|
|
for( j = 0; j < height; j++ ) |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < width; i++ ) |
|
|
{ |
|
|
*pData = (i << 24) |
|
|
| (i << 16) |
|
|
| (i << 8) |
|
|
| (i << 0); |
|
|
pData++; |
|
|
} |
|
|
} |
|
|
|
|
|
plDXTextureRef* ref = TRACKED_NEW plDXTextureRef( D3DFMT_A8R8G8B8, |
|
|
1, // Num mip levels |
|
|
width, height, // width by height |
|
|
width * height, // numpix |
|
|
width*height*sizeof(UInt32), // totalsize |
|
|
width*height*sizeof(UInt32), |
|
|
nil, // levels data |
|
|
tData, |
|
|
false // externData |
|
|
); |
|
|
ref->Link(&fTextureRefList); |
|
|
|
|
|
fULutTextureRef = ref; |
|
|
} |
|
|
return fULutTextureRef; |
|
|
} |
|
|
|
|
|
// IFindRenderTarget ////////////////////////////////////////////////////////////////// |
|
|
// Find a matching render target from the pools. We prefer the requested size, but |
|
|
// will look for a smaller size if there isn't one available. |
|
|
// Param ortho indicates whether it will be used for orthogonal projection as opposed |
|
|
// to perspective (directional light vs. point light), but is no longer used. |
|
|
plRenderTarget* plDXPipeline::IFindRenderTarget(UInt32& width, UInt32& height, hsBool ortho) |
|
|
{ |
|
|
hsTArray<plRenderTarget*>* pool = nil; |
|
|
UInt32* iNext = nil; |
|
|
// NOT CURRENTLY SUPPORTING NON-SQUARE SHADOWS. IF WE DO, CHANGE THIS. |
|
|
switch(height) |
|
|
{ |
|
|
case 512: |
|
|
pool = &fRenderTargetPool512; |
|
|
iNext = &fRenderTargetNext[9]; |
|
|
break; |
|
|
case 256: |
|
|
pool = &fRenderTargetPool256; |
|
|
iNext = &fRenderTargetNext[8]; |
|
|
break; |
|
|
case 128: |
|
|
pool = &fRenderTargetPool128; |
|
|
iNext = &fRenderTargetNext[7]; |
|
|
break; |
|
|
case 64: |
|
|
pool = &fRenderTargetPool64; |
|
|
iNext = &fRenderTargetNext[6]; |
|
|
break; |
|
|
case 32: |
|
|
pool = &fRenderTargetPool32; |
|
|
iNext = &fRenderTargetNext[5]; |
|
|
break; |
|
|
default: |
|
|
return nil; |
|
|
} |
|
|
plRenderTarget* rt = (*pool)[*iNext]; |
|
|
if( !rt ) |
|
|
{ |
|
|
// We didn't find one, try again the next size down. |
|
|
if( height > 32 ) |
|
|
return IFindRenderTarget(width >>= 1, height >>= 1, ortho); |
|
|
|
|
|
// We must be totally out. Oh well. |
|
|
return nil; |
|
|
} |
|
|
(*iNext)++; |
|
|
|
|
|
return rt; |
|
|
} |
|
|
|
|
|
// IPushShadowCastState //////////////////////////////////////////////////////////////////////////////// |
|
|
// Push all the state necessary to start rendering this shadow map, but independent of the |
|
|
// actual shadow caster to be rendered into the map. |
|
|
hsBool plDXPipeline::IPushShadowCastState(plShadowSlave* slave) |
|
|
{ |
|
|
plRenderTarget* renderTarg = IFindRenderTarget(slave->fWidth, slave->fHeight, slave->fView.GetOrthogonal()); |
|
|
if( !renderTarg ) |
|
|
return false; |
|
|
|
|
|
// Let the slave setup the transforms, viewport, etc. necessary to render it's shadow |
|
|
// map. This just goes into a plViewTransform, we translate that into D3D state ourselves below. |
|
|
if (!slave->SetupViewTransform(this)) |
|
|
return false; |
|
|
|
|
|
// Turn off fogging and specular. |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
fD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); |
|
|
fLayerState[0].fShadeFlags &= ~hsGMatState::kShadeSpecular; |
|
|
|
|
|
// Push the shadow slave's view transform as our current render state. |
|
|
fSettings.fViewStack.Push(fView); |
|
|
fView.fCullMaxNodes = 0; |
|
|
SetViewTransform(slave->fView); |
|
|
IProjectionMatrixToD3D(); |
|
|
|
|
|
// Push the shadow map as the current render target |
|
|
PushRenderTarget(renderTarg); |
|
|
|
|
|
// We'll be rendering the light space distance to the span fragment into |
|
|
// alpha (color is white), so our camera space position, transformed into light space |
|
|
// and then converted to [0..255] via our ULut. |
|
|
|
|
|
// For stage 0: |
|
|
// Set uvw src |
|
|
if( fLayerUVWSrcs[0] != D3DTSS_TCI_CAMERASPACEPOSITION ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); |
|
|
fLayerUVWSrcs[0] = D3DTSS_TCI_CAMERASPACEPOSITION; |
|
|
} |
|
|
UInt32 xformFlags = D3DTTFF_COUNT3; |
|
|
|
|
|
if( xformFlags != fLayerXformFlags[0] ) |
|
|
{ |
|
|
fLayerXformFlags[0] = xformFlags; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, xformFlags); |
|
|
} |
|
|
|
|
|
// Set texture transform to slave's lut transform. See plShadowMaster::IComputeLUT(). |
|
|
hsMatrix44 castLUT = slave->fCastLUT; |
|
|
if( slave->fFlags & plShadowSlave::kCastInCameraSpace ) |
|
|
{ |
|
|
hsMatrix44 c2w = GetCameraToWorld(); |
|
|
|
|
|
castLUT = castLUT * c2w; |
|
|
} |
|
|
|
|
|
D3DXMATRIX tXfm; |
|
|
IMatrix44ToD3DMatrix(tXfm, castLUT); |
|
|
|
|
|
fD3DDevice->SetTransform( sTextureStages[0], &tXfm ); |
|
|
fLayerTransform[0] = true; |
|
|
|
|
|
// Set texture to clamp |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[0].fClampFlags = hsGMatState::kClampTexture; |
|
|
|
|
|
DWORD clearColor = 0xff000000L; |
|
|
// const int l2NumSamples = kL2NumSamples; // HACKSAMPLE |
|
|
const int l2NumSamples = mfCurrentTest > 101 ? 3 : 2; |
|
|
DWORD intens; |
|
|
if( slave->fBlurScale > 0 ) |
|
|
{ |
|
|
const int kNumSamples = mfCurrentTest > 101 ? 8 : 4; |
|
|
int nPasses = (int)hsCeil(float(kNumSamples) / fSettings.fMaxLayersAtOnce); |
|
|
int nSamplesPerPass = kNumSamples / nPasses; |
|
|
DWORD k = int(128.f / float(nSamplesPerPass)); |
|
|
intens = (0xff << 24) |
|
|
| ((128 + k) << 16) |
|
|
| ((128 + k) << 8) |
|
|
| ((128 + k) << 0); |
|
|
clearColor = (0xff << 24) |
|
|
| ((128 - k) << 16) |
|
|
| ((128 - k) << 8) |
|
|
| ((128 - k) << 0); |
|
|
} |
|
|
else |
|
|
intens = 0xffffffff; |
|
|
|
|
|
// Note that we discard the shadow caster's alpha here, although we don't |
|
|
// need to. Even on a 2 texture stage system, we could include the diffuse |
|
|
// alpha and the texture alpha from the base texture. But we don't. |
|
|
|
|
|
// Set color to white. We could accomplish this easier by making the color |
|
|
// in our ULut white. |
|
|
fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, intens); |
|
|
|
|
|
fSettings.fVeryAnnoyingTextureInvalidFlag = true; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); |
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
|
|
|
// For stage 1 - disable |
|
|
fLastEndingStage = 1; |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
|
|
|
// Set texture to U_LUT |
|
|
plDXTextureRef* ref = IGetULutTextureRef(); |
|
|
|
|
|
if( !ref->fD3DTexture ) |
|
|
{ |
|
|
if( ref->fData ) |
|
|
IReloadTexture( ref ); |
|
|
} |
|
|
|
|
|
hsRefCnt_SafeAssign( fLayerRef[0], ref ); |
|
|
fD3DDevice->SetTexture( 0, ref->fD3DTexture ); |
|
|
|
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); |
|
|
fD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); |
|
|
fD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); |
|
|
|
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); |
|
|
|
|
|
slave->fPipeData = renderTarg; |
|
|
|
|
|
// Enable ZBuffering w/ write |
|
|
fD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE); |
|
|
fLayerState[0].fZFlags &= ~hsGMatState::kZMask; |
|
|
|
|
|
// Clear the render target: |
|
|
// alpha to white ensures no shadow where there's no caster |
|
|
// color to black in case we ever get blurring going |
|
|
// Z to 1 |
|
|
// Stencil ignored |
|
|
if( slave->ReverseZ() ) |
|
|
{ |
|
|
fD3DDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_GREATEREQUAL); |
|
|
fD3DDevice->Clear(0, nil, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clearColor, 0.0f, 0L); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); |
|
|
fD3DDevice->Clear(0, nil, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clearColor, 1.0f, 0L); |
|
|
} |
|
|
|
|
|
// Bring the viewport in (AFTER THE CLEAR) to protect the alpha boundary. |
|
|
fView.fTransform.SetViewPort(1, 1, (float)(slave->fWidth-2), (float)(slave->fHeight-2), false); |
|
|
ISetViewport(); |
|
|
|
|
|
inlEnsureLightingOff(); |
|
|
|
|
|
// See ISetupShadowLight below for how the shadow light is used. |
|
|
// The shadow light isn't used in generating the shadow map, it's used |
|
|
// in projecting the shadow map onto the scene. |
|
|
ISetupShadowLight(slave); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// ISetupShadowLight ////////////////////////////////////////////////////////////////// |
|
|
// We use the shadow light to modulate the shadow effect in two ways while |
|
|
// projecting the shadow map onto the scene. |
|
|
// First, the intensity of the shadow follows the N dot L of the light on |
|
|
// the surface being projected onto. So on a sphere, the darkening effect |
|
|
// of the shadow will fall off as the normals go from pointing to the light to |
|
|
// pointing 90 degrees off. |
|
|
// Second, we attenuate the whole shadow effect through the lights diffuse color. |
|
|
// We attenuate for different reasons, like the intensity of the light, or |
|
|
// to fade out a shadow as it gets too far in the distance to matter. |
|
|
void plDXPipeline::ISetupShadowLight(plShadowSlave* slave) |
|
|
{ |
|
|
plDXLightRef* lRef = INextShadowLight(slave); |
|
|
|
|
|
lRef->fD3DInfo.Diffuse.r |
|
|
= lRef->fD3DInfo.Diffuse.g |
|
|
= lRef->fD3DInfo.Diffuse.b |
|
|
= slave->fPower; |
|
|
|
|
|
slave->fSelfShadowOn = false; |
|
|
|
|
|
if( slave->Positional() ) |
|
|
{ |
|
|
hsPoint3 position = slave->fLightPos; |
|
|
lRef->fD3DInfo.Position.x = position.fX; |
|
|
lRef->fD3DInfo.Position.y = position.fY; |
|
|
lRef->fD3DInfo.Position.z = position.fZ; |
|
|
|
|
|
const float maxRange = 32767.f; |
|
|
lRef->fD3DInfo.Range = maxRange; |
|
|
lRef->fD3DInfo.Attenuation0 = 1.f; |
|
|
lRef->fD3DInfo.Attenuation1 = 0; |
|
|
lRef->fD3DInfo.Attenuation2 = 0; |
|
|
|
|
|
lRef->fD3DInfo.Type = D3DLIGHT_POINT; |
|
|
} |
|
|
else |
|
|
{ |
|
|
hsVector3 dir = slave->fLightDir; |
|
|
lRef->fD3DInfo.Direction.x = dir.fX; |
|
|
lRef->fD3DInfo.Direction.y = dir.fY; |
|
|
lRef->fD3DInfo.Direction.z = dir.fZ; |
|
|
|
|
|
lRef->fD3DInfo.Type = D3DLIGHT_DIRECTIONAL; |
|
|
} |
|
|
|
|
|
fD3DDevice->SetLight( lRef->fD3DIndex, &lRef->fD3DInfo ); |
|
|
|
|
|
slave->fLightIndex = lRef->fD3DIndex; |
|
|
} |
|
|
|
|
|
// INextShadowLight ///////////////////////////////////////////////////// |
|
|
// Get a scratch light for this shadow slave and assign it. The slave |
|
|
// only keeps it for this render frame. |
|
|
plDXLightRef* plDXPipeline::INextShadowLight(plShadowSlave* slave) |
|
|
{ |
|
|
fLights.fShadowLights.ExpandAndZero(fLights.fNextShadowLight+1); |
|
|
|
|
|
if( !fLights.fShadowLights[fLights.fNextShadowLight] ) |
|
|
{ |
|
|
plDXLightRef *lRef = TRACKED_NEW plDXLightRef(); |
|
|
|
|
|
/// Assign stuff and update |
|
|
lRef->fD3DIndex = fLights.ReserveD3DIndex(); |
|
|
lRef->fOwner = nil; |
|
|
lRef->fD3DDevice = fD3DDevice; |
|
|
|
|
|
lRef->Link( &fLights.fRefList ); |
|
|
|
|
|
fLights.fShadowLights[fLights.fNextShadowLight] = lRef; |
|
|
|
|
|
// Neutralize it until we need it. |
|
|
fD3DDevice->LightEnable(lRef->fD3DIndex, false); |
|
|
|
|
|
// Some things never change. |
|
|
memset(&lRef->fD3DInfo, 0, sizeof(lRef->fD3DInfo)); |
|
|
lRef->fD3DInfo.Ambient.r = lRef->fD3DInfo.Ambient.g = lRef->fD3DInfo.Ambient.b = 0; |
|
|
lRef->fD3DInfo.Specular.r = lRef->fD3DInfo.Specular.g = lRef->fD3DInfo.Specular.b = 0; |
|
|
|
|
|
} |
|
|
slave->fLightRefIdx = fLights.fNextShadowLight; |
|
|
|
|
|
return fLights.fShadowLights[fLights.fNextShadowLight++]; |
|
|
} |
|
|
|
|
|
// IPopShadowCastState /////////////////////////////////////////////////// |
|
|
// Pop the state set to render this shadow caster, so we're ready to render |
|
|
// a different shadow caster, or go on to our main render. |
|
|
hsBool plDXPipeline::IPopShadowCastState(plShadowSlave* slave) |
|
|
{ |
|
|
fView = fSettings.fViewStack.Pop(); |
|
|
|
|
|
PopRenderTarget(); |
|
|
fView.fXformResetFlags = fView.kResetProjection | fView.kResetCamera; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// IMakeRenderTargetPools ///////////////////////////////////////////////////////////// |
|
|
// These are actually only used as shadow map pools, but they could be used for other |
|
|
// render targets. |
|
|
// All these are created here in a single call because they go in POOL_DEFAULT, so they |
|
|
// must be created before we start creating things in POOL_MANAGED. |
|
|
void plDXPipeline::IMakeRenderTargetPools() |
|
|
{ |
|
|
hsAssert(!fManagedAlloced, "Allocating rendertargets with managed resources alloced"); |
|
|
IReleaseRenderTargetPools(); // Just to be sure. |
|
|
|
|
|
// Numbers of render targets to be created for each size. |
|
|
// These numbers were set with multi-player in mind, so should be reconsidered. |
|
|
// But do keep in mind that there are many things in production assets that cast |
|
|
// shadows besides the avatar. |
|
|
plConst(hsScalar) kCount[kMaxRenderTargetNext] = { |
|
|
0, // 1x1 |
|
|
0, // 2x2 |
|
|
0, // 4x4 |
|
|
0, // 8x8 |
|
|
0, // 16x16 |
|
|
32, // 32x32 |
|
|
16, // 64x64 |
|
|
8, // 128x128 |
|
|
4, // 256x256 |
|
|
0 // 512x512 |
|
|
}; |
|
|
int i; |
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
{ |
|
|
hsTArray<plRenderTarget*>* pool = nil; |
|
|
switch( i ) |
|
|
{ |
|
|
default: |
|
|
case 0: |
|
|
case 1: |
|
|
case 2: |
|
|
case 3: |
|
|
case 4: |
|
|
break; |
|
|
|
|
|
case 5: |
|
|
pool = &fRenderTargetPool32; |
|
|
break; |
|
|
case 6: |
|
|
pool = &fRenderTargetPool64; |
|
|
break; |
|
|
case 7: |
|
|
pool = &fRenderTargetPool128; |
|
|
break; |
|
|
case 8: |
|
|
pool = &fRenderTargetPool256; |
|
|
break; |
|
|
case 9: |
|
|
pool = &fRenderTargetPool512; |
|
|
break; |
|
|
} |
|
|
if( pool ) |
|
|
{ |
|
|
pool->SetCount((int)(kCount[i]+1)); |
|
|
(*pool)[0] = nil; |
|
|
(*pool)[(int)(kCount[i])] = nil; |
|
|
int j; |
|
|
for( j = 0; j < kCount[i]; j++ ) |
|
|
{ |
|
|
UInt16 flags = plRenderTarget::kIsTexture | plRenderTarget::kIsProjected; |
|
|
UInt8 bitDepth = 32; |
|
|
UInt8 zDepth = 24; |
|
|
UInt8 stencilDepth = 0; |
|
|
|
|
|
// If we ever allow non-square shadows, change this. |
|
|
int width = 1 << i; |
|
|
int height = width; |
|
|
|
|
|
plRenderTarget* rt = TRACKED_NEW plRenderTarget(flags, width, height, bitDepth, zDepth, stencilDepth); |
|
|
|
|
|
// If we've failed to create our render target ref, we're probably out of |
|
|
// video memory. We'll return nil, and this guy just doesn't get a shadow |
|
|
// until more video memory turns up (not likely). |
|
|
if( !SharedRenderTargetRef((*pool)[0], rt) ) |
|
|
{ |
|
|
delete rt; |
|
|
pool->SetCount(j+1); |
|
|
(*pool)[j] = nil; |
|
|
break; |
|
|
} |
|
|
(*pool)[j] = rt; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// IResetRenderTargetPools ///////////////////////////////////////////////////////////////// |
|
|
// No release of resources, this just resets for the start of a frame. So if a shadow |
|
|
// slave gets a render target from a pool, once this is called (conceptually at the |
|
|
// end of the frame), the slave no longer owns that render target. |
|
|
void plDXPipeline::IResetRenderTargetPools() |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < kMaxRenderTargetNext; i++ ) |
|
|
{ |
|
|
fRenderTargetNext[i] = 0; |
|
|
fBlurScratchRTs[i] = nil; |
|
|
fBlurDestRTs[i] = nil; |
|
|
} |
|
|
|
|
|
fLights.fNextShadowLight = 0; |
|
|
} |
|
|
|
|
|
// IPrepShadowCaster //////////////////////////////////////////////////////////////////////// |
|
|
// Make sure all the geometry in this shadow caster is ready to be rendered. |
|
|
// Keep in mind the single shadow caster may be multiple spans possibly in |
|
|
// multiple drawables. |
|
|
// The tricky part here is that we need to prep each drawable involved, |
|
|
// but only prep it once. Say the caster is composed of: |
|
|
// drawableA, span0 |
|
|
// drawableA, span1 |
|
|
// drawableB, span0 |
|
|
// Then we need to call plDrawable::PrepForRender() ONCE on drawableA, |
|
|
// and once on drawableB. Further, we need to do any necessary CPU |
|
|
// skinning with ISofwareVertexBlend(drawableA, visList={0,1}) and |
|
|
// ISofwareVertexBlend(drawableB, visList={1}). |
|
|
hsBool plDXPipeline::IPrepShadowCaster(const plShadowCaster* caster) |
|
|
{ |
|
|
static hsBitVector done; |
|
|
done.Clear(); |
|
|
const hsTArray<plShadowCastSpan>& castSpans = caster->Spans(); |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < castSpans.GetCount(); i++ ) |
|
|
{ |
|
|
if( !done.IsBitSet(i) ) |
|
|
{ |
|
|
// We haven't already done this castSpan |
|
|
|
|
|
plDrawableSpans* drawable = castSpans[i].fDraw; |
|
|
|
|
|
// Start a visList with this index. |
|
|
static hsTArray<Int16> visList; |
|
|
visList.SetCount(0); |
|
|
visList.Append((Int16)(castSpans[i].fIndex)); |
|
|
|
|
|
// We're about to have done this castSpan. |
|
|
done.SetBit(i); |
|
|
|
|
|
// Look forward through castSpans for any other spans |
|
|
// with the same drawable, and add them to visList. |
|
|
// We'll handle all the spans from this drawable at once. |
|
|
int j; |
|
|
for( j = i+1; j < castSpans.GetCount(); j++ ) |
|
|
{ |
|
|
if( !done.IsBitSet(j) && (castSpans[j].fDraw == drawable) ) |
|
|
{ |
|
|
// Add to list |
|
|
visList.Append((Int16)(castSpans[j].fIndex)); |
|
|
|
|
|
// We're about to have done this castSpan. |
|
|
done.SetBit(j); |
|
|
} |
|
|
} |
|
|
// That's all, prep the drawable. |
|
|
drawable->PrepForRender( this ); |
|
|
|
|
|
// Do any software skinning. |
|
|
if( !ISoftwareVertexBlend(drawable, visList) ) |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// IRenderShadowCaster //////////////////////////////////////////////// |
|
|
// Render the shadow caster into the slave's render target, creating a shadow map. |
|
|
hsBool plDXPipeline::IRenderShadowCaster(plShadowSlave* slave) |
|
|
{ |
|
|
const plShadowCaster* caster = slave->fCaster; |
|
|
|
|
|
// Setup to render into the slave's render target. |
|
|
if( !IPushShadowCastState(slave) ) |
|
|
return false; |
|
|
|
|
|
// Get the shadow caster ready to render. |
|
|
if( !IPrepShadowCaster(slave->fCaster) ) |
|
|
return false; |
|
|
|
|
|
// for each shadowCaster.fSpans |
|
|
int iSpan; |
|
|
for( iSpan = 0; iSpan < caster->Spans().GetCount(); iSpan++ ) |
|
|
{ |
|
|
plDrawableSpans* dr = caster->Spans()[iSpan].fDraw; |
|
|
const plSpan* sp = caster->Spans()[iSpan].fSpan; |
|
|
UInt32 spIdx = caster->Spans()[iSpan].fIndex; |
|
|
|
|
|
hsAssert(sp->fTypeMask & plSpan::kIcicleSpan, "Shadow casting from non-trimeshes not currently supported"); |
|
|
|
|
|
// render shadowcaster.fSpans[i] to rendertarget |
|
|
if( !(sp->fProps & plSpan::kPropNoShadowCast) ) |
|
|
IRenderShadowCasterSpan(slave, dr, *(const plIcicle*)sp); |
|
|
|
|
|
// Keep track of which shadow slaves this span was rendered into. |
|
|
// If self-shadowing is off, we use that to determine not to |
|
|
// project the shadow map onto its source geometry. |
|
|
sp->SetShadowBit(slave->fIndex); //index set in SubmitShadowSlave |
|
|
} |
|
|
|
|
|
// Debug only. |
|
|
if( blurScale >= 0.f ) |
|
|
slave->fBlurScale = blurScale; |
|
|
|
|
|
// If this shadow requests being blurred, do it. |
|
|
if( slave->fBlurScale > 0.f ) |
|
|
IBlurShadowMap(slave); |
|
|
|
|
|
// Finished up, restore previous state. |
|
|
IPopShadowCastState(slave); |
|
|
|
|
|
#if MCN_BOUNDS_SPANS |
|
|
if (IsDebugFlagSet(plPipeDbg::kFlagShowShadowBounds)) |
|
|
{ |
|
|
/// Add a span to our boundsIce to show this |
|
|
IAddBoundsSpan(fBoundsSpans, &slave->fWorldBounds); |
|
|
} |
|
|
#endif // MCN_BOUNDS_SPANS |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// We have a (possibly empty) list of shadows submitted for this frame. |
|
|
// At BeginRender, we need to accomplish: |
|
|
// Find render targets for each shadow request of the requested size. |
|
|
// Render the associated spans into the render targets. Something like the following: |
|
|
void plDXPipeline::IPreprocessShadows() |
|
|
{ |
|
|
plProfile_BeginTiming(PrepShadows); |
|
|
|
|
|
// Mark our shared resources as free to be used. |
|
|
IResetRenderTargetPools(); |
|
|
|
|
|
// Some board (possibly the Parhelia) freaked if anistropic filtering |
|
|
// was enabled when rendering to a render target. We never need it for |
|
|
// shadow maps, and it is slower, so we just kill it here. |
|
|
ISetAnisotropy(false); |
|
|
|
|
|
// Generate a shadow map for each submitted shadow slave. |
|
|
// Shadow slave corresponds to one shadow caster paired |
|
|
// with one shadow light that affects it. So a single caster |
|
|
// may be in multiple slaves (from different lights), or a |
|
|
// single light may be in different slaves (affecting different |
|
|
// casters). The overall number is low in spite of the possible |
|
|
// permutation explosion, because a slave is only generated |
|
|
// for a caster being affected (in range etc.) by a light. |
|
|
int iSlave; |
|
|
for( iSlave = 0; iSlave < fShadows.GetCount(); iSlave++ ) |
|
|
{ |
|
|
plShadowSlave* slave = fShadows[iSlave]; |
|
|
|
|
|
// Any trouble, remove it from the list for this frame. |
|
|
if( !IRenderShadowCaster(slave) ) |
|
|
{ |
|
|
fShadows.Remove(iSlave); |
|
|
iSlave--; |
|
|
continue; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// Restore |
|
|
ISetAnisotropy(true); |
|
|
|
|
|
plProfile_EndTiming(PrepShadows); |
|
|
} |
|
|
|
|
|
// IClearShadowSlaves /////////////////////////////////////////////////////////////////////////// |
|
|
// At EndRender(), we need to clear our list of shadow slaves. They are only valid for one frame. |
|
|
void plDXPipeline::IClearShadowSlaves() |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fShadows.GetCount(); i++ ) |
|
|
{ |
|
|
const plShadowCaster* caster = fShadows[i]->fCaster; |
|
|
caster->GetKey()->UnRefObject(); |
|
|
} |
|
|
fShadows.SetCount(0); |
|
|
} |
|
|
|
|
|
|
|
|
// IRenderShadowsOntoSpan ///////////////////////////////////////////////////////////////////// |
|
|
// After doing the usual render for a span (all passes), we call the following. |
|
|
// If the span accepts shadows, this will loop over all the shadows active this |
|
|
// frame, and apply the ones that intersect this spans bounds. See below for details. |
|
|
void plDXPipeline::IRenderShadowsOntoSpan(const plRenderPrimFunc& render, const plSpan* span, hsGMaterial* mat) |
|
|
{ |
|
|
// We've already computed which shadows affect this span. That's recorded in slaveBits. |
|
|
const hsBitVector& slaveBits = span->GetShadowSlaves(); |
|
|
|
|
|
hsBool first = true; |
|
|
|
|
|
int i; |
|
|
for( i = 0; i < fShadows.GetCount(); i++ ) |
|
|
{ |
|
|
if( slaveBits.IsBitSet(fShadows[i]->fIndex) ) |
|
|
{ |
|
|
// This slave affects this span. |
|
|
if( first ) |
|
|
{ |
|
|
// On the first, we do all the setup that is independent of |
|
|
// the shadow slave, so state that needs to get set once before |
|
|
// projecting any number of shadow maps. |
|
|
ISetupShadowRcvTextureStages(mat); |
|
|
|
|
|
first = false; |
|
|
|
|
|
} |
|
|
|
|
|
// Now setup any state specific to this shadow slave. |
|
|
ISetupShadowSlaveTextures(fShadows[i]); |
|
|
|
|
|
int selfShadowNow = span->IsShadowBitSet(fShadows[i]->fIndex); |
|
|
|
|
|
// We vary the shadow intensity when self shadowing (see below), |
|
|
// so we cache whether the shadow light is set for regular or |
|
|
// self shadowing intensity. If what we're doing now is different |
|
|
// than what we're currently set for, set it again. |
|
|
if( selfShadowNow != fShadows[i]->fSelfShadowOn ) |
|
|
{ |
|
|
plDXLightRef* lRef = fLights.fShadowLights[fShadows[i]->fLightRefIdx]; |
|
|
|
|
|
// We lower the power on self shadowing, because the artists like to |
|
|
// crank up the shadow strength to huge values to get a darker shadow |
|
|
// on the environment, which causes the shadow on the avatar to get |
|
|
// way too dark. Another way to look at it is when self shadowing, |
|
|
// the surface being projected onto is going to be very close to |
|
|
// the surface casting the shadow (because they are the same object). |
|
|
if( selfShadowNow ) |
|
|
{ |
|
|
plConst(hsScalar) kMaxSelfPower = 0.3f; |
|
|
hsScalar power = fShadows[i]->fPower > kMaxSelfPower ? kMaxSelfPower : fShadows[i]->fPower; |
|
|
lRef->fD3DInfo.Diffuse.r |
|
|
= lRef->fD3DInfo.Diffuse.g |
|
|
= lRef->fD3DInfo.Diffuse.b |
|
|
= power; |
|
|
} |
|
|
else |
|
|
{ |
|
|
lRef->fD3DInfo.Diffuse.r |
|
|
= lRef->fD3DInfo.Diffuse.g |
|
|
= lRef->fD3DInfo.Diffuse.b |
|
|
= fShadows[i]->fPower; |
|
|
} |
|
|
fD3DDevice->SetLight(lRef->fD3DIndex, &lRef->fD3DInfo); |
|
|
|
|
|
// record which our intensity is now set for. |
|
|
fShadows[i]->fSelfShadowOn = selfShadowNow; |
|
|
} |
|
|
|
|
|
// Enable the light. |
|
|
fD3DDevice->LightEnable(fShadows[i]->fLightIndex, true); |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
DWORD nPass; |
|
|
fSettings.fDXError = fD3DDevice->ValidateDevice(&nPass); |
|
|
if( fSettings.fDXError != D3D_OK ) |
|
|
IGetD3DError(); |
|
|
#endif // HS_DEBUGGING |
|
|
|
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
|
if (!IsDebugFlagSet(plPipeDbg::kFlagNoShadowApply)) |
|
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
render.RenderPrims(); |
|
|
|
|
|
// Disable it again. |
|
|
fD3DDevice->LightEnable(fShadows[i]->fLightIndex, false); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// ISetupShadowRcvTextureStages //////////////////////////////////////////// |
|
|
// Set the generic stage states. We'll fill in the specific textures |
|
|
// for each slave later. |
|
|
void plDXPipeline::ISetupShadowRcvTextureStages(hsGMaterial* mat) |
|
|
{ |
|
|
// Setup for nil shaders to get us back to fixed function pipeline. |
|
|
ISetShaders(nil, nil); |
|
|
|
|
|
// We're whacking about with renderstate independent of current material, |
|
|
// so make sure the next span processes it's material, even if it's the |
|
|
// same one. |
|
|
fForceMatHandle = true; |
|
|
|
|
|
// Set the D3D lighting/material model |
|
|
ISetShadowLightState(mat); |
|
|
|
|
|
// Zbuffering on read-only |
|
|
fD3DDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); |
|
|
fD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); |
|
|
fLayerState[0].fZFlags &= ~hsGMatState::kZMask; |
|
|
fLayerState[0].fZFlags |= hsGMatState::kZNoZWrite; |
|
|
|
|
|
// Stage 0: |
|
|
// Texture is slave specific |
|
|
// Texture transform is slave specific |
|
|
// ColorArg1 = texture |
|
|
// ColorArg2 = diffuse |
|
|
// ColorOp = modulate |
|
|
// AlphaArg1 = texture |
|
|
// AlphaOp = SelectArg1 |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); |
|
|
|
|
|
if( fLayerUVWSrcs[0] != D3DTSS_TCI_CAMERASPACEPOSITION ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); |
|
|
fLayerUVWSrcs[0] = D3DTSS_TCI_CAMERASPACEPOSITION; |
|
|
} |
|
|
|
|
|
// Set texture to clamp |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[0].fClampFlags = hsGMatState::kClampTexture; |
|
|
|
|
|
// Stage 1: |
|
|
// Set texture to ULut |
|
|
// Texture transform is slave specific |
|
|
// *** With the optional texture blurring, the state here becomes |
|
|
// *** partially slave dependent. Specifically, if we've done a blur, |
|
|
// *** then we want to modulate the lut color value by current (which is |
|
|
// *** the blurred color), else just select the lut. So we'll just move |
|
|
// *** the ColorOp down to the slave specific section. |
|
|
// %%% Okay, get this. The GeForce2 won't take a SelectArg1 on Stage1 if |
|
|
// %%% we're also trying to use Stage2 to modulate in the diffuse. But |
|
|
// %%% it WILL let us do a modulate on Stage1. So we're going to make sure |
|
|
// %%% that our shadowmap texture is white, then we can just modulate them |
|
|
// %%% with no effect. If we're blurring, we already wanted to modulate, so |
|
|
// %%% no change there. This means we can set the ColorOp now, rather than |
|
|
// %%% having to wait for the Slave specific section later. |
|
|
// ColorArg1 = 1 - ULut |
|
|
// ColorArg2 = Current |
|
|
// ColorOp = Modulate |
|
|
// AlphaArg1 = ULut |
|
|
// AlphaArg2 = Current |
|
|
// AlphaOp = Subtract |
|
|
plDXTextureRef* ref = IGetULutTextureRef(); |
|
|
if( !ref->fD3DTexture ) |
|
|
{ |
|
|
if( ref->fData ) |
|
|
IReloadTexture(ref); |
|
|
} |
|
|
hsRefCnt_SafeAssign(fLayerRef[1], ref); |
|
|
fD3DDevice->SetTexture(1, ref->fD3DTexture); |
|
|
|
|
|
// The following commented out block is kind of cool, because it |
|
|
// bases the darkness of the shadow on the distance between the |
|
|
// shadow caster and the point receiving the shadow. So, for example, |
|
|
// the hand's shadow would get darker as it reaches for the lever. |
|
|
// Unfortunately, it doesn't guarantee that the shadow will completely |
|
|
// attenuate out at the fAttenDist (in fact, it pretty much guarantees |
|
|
// that it won't), so shadows will pop in and out. So instead, we'll |
|
|
// base the color on the distance from the start of the slave. The |
|
|
// difference is subtle, and usually unnoticable, and we get no popping. |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SUBTRACT); |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
|
|
|
if( fLayerUVWSrcs[1] != D3DTSS_TCI_CAMERASPACEPOSITION ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); |
|
|
fLayerUVWSrcs[1] = D3DTSS_TCI_CAMERASPACEPOSITION; |
|
|
} |
|
|
if( D3DTTFF_COUNT3 != fLayerXformFlags[1] ) |
|
|
{ |
|
|
fLayerXformFlags[1] = D3DTTFF_COUNT3; |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT3); |
|
|
} |
|
|
|
|
|
// Set texture to clamp |
|
|
fD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[1].fClampFlags = hsGMatState::kClampTexture; |
|
|
|
|
|
int iNextStage = 2; |
|
|
|
|
|
// If mat's base layer is alpha'd, and we have > 3 TMU's factor |
|
|
// in the base layer's alpha. |
|
|
if( (fSettings.fMaxLayersAtOnce > 3) && mat->GetLayer(0)->GetTexture() && (mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendAlpha) ) |
|
|
{ |
|
|
plLayerInterface* layer = mat->GetLayer(0); |
|
|
|
|
|
// If the following conditions are met, it means that layer 1 is a better choice to |
|
|
// get the transparency from. The specific case we're looking for is vertex alpha |
|
|
// simulated by an invisible second layer alpha LUT (known as the alpha hack). |
|
|
if( (layer->GetMiscFlags() & hsGMatState::kMiscBindNext) |
|
|
&& mat->GetLayer(1) |
|
|
&& !(mat->GetLayer(1)->GetMiscFlags() & hsGMatState::kMiscNoShadowAlpha) |
|
|
&& !(mat->GetLayer(1)->GetBlendFlags() & hsGMatState::kBlendNoTexAlpha) |
|
|
&& mat->GetLayer(1)->GetTexture() ) |
|
|
layer = mat->GetLayer(1); |
|
|
|
|
|
// Take the texture alpha and modulate the color so far with it. In |
|
|
// the final shadow map, black will have no effect, white will be maximal |
|
|
// darkening. |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_ALPHAARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); |
|
|
|
|
|
// Blend flags to layer blend (alpha +- complement) |
|
|
fLayerState[iNextStage].fBlendFlags = UInt32(-1); |
|
|
|
|
|
// Clamp to whatever the texture wants. |
|
|
if( fLayerState[iNextStage].fClampFlags ^ layer->GetClampFlags() ) |
|
|
{ |
|
|
fLayerState[iNextStage].fClampFlags = layer->GetClampFlags(); |
|
|
IHandleStageClamp(iNextStage); |
|
|
} |
|
|
|
|
|
// Shade to 0 |
|
|
fLayerState[iNextStage].fShadeFlags = 0; |
|
|
|
|
|
// ZFlags to ZNoZWrite |
|
|
fLayerState[iNextStage].fZFlags = hsGMatState::kZNoZWrite; |
|
|
|
|
|
// MiscFlags to layer's misc flags |
|
|
fLayerState[iNextStage].fMiscFlags = layer->GetMiscFlags(); |
|
|
|
|
|
// Set up whatever UVW transform the layer normally uses. |
|
|
IHandleStageTransform(iNextStage, layer); |
|
|
|
|
|
// Normal UVW source. |
|
|
UInt32 uvwSrc = layer->GetUVWSrc(); |
|
|
|
|
|
if( fLayerUVWSrcs[ iNextStage ] != uvwSrc ) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState( iNextStage, D3DTSS_TEXCOORDINDEX, uvwSrc ); |
|
|
fLayerUVWSrcs[ iNextStage ] = uvwSrc; |
|
|
} |
|
|
|
|
|
UInt32 xformFlags; |
|
|
if( layer->GetMiscFlags() & hsGMatState::kMiscPerspProjection ) |
|
|
xformFlags = D3DTTFF_COUNT3 | D3DTTFF_PROJECTED; |
|
|
else if( uvwSrc & (plLayerInterface::kUVWNormal | plLayerInterface::kUVWPosition | plLayerInterface::kUVWReflect) ) |
|
|
xformFlags = D3DTTFF_COUNT3; |
|
|
else |
|
|
xformFlags = D3DTTFF_COUNT2; |
|
|
|
|
|
if( xformFlags != fLayerXformFlags[iNextStage] ) |
|
|
{ |
|
|
fLayerXformFlags[iNextStage] = xformFlags; |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_TEXTURETRANSFORMFLAGS, xformFlags); |
|
|
} |
|
|
|
|
|
// This ref should be pretty safe to use, because we just rendered it. |
|
|
ref = (plDXTextureRef*)layer->GetTexture()->GetDeviceRef(); |
|
|
|
|
|
hsRefCnt_SafeAssign( fLayerRef[iNextStage], ref ); |
|
|
fD3DDevice->SetTexture( iNextStage, ref->fD3DTexture ); |
|
|
|
|
|
iNextStage++; |
|
|
|
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLORARG1, D3DTA_DIFFUSE | D3DTA_ALPHAREPLICATE); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLORARG2, D3DTA_CURRENT); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
|
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
|
|
|
fLayerState[iNextStage].fBlendFlags = UInt32(-1); |
|
|
|
|
|
iNextStage++; |
|
|
} |
|
|
|
|
|
fLayerState[iNextStage].fBlendFlags = UInt32(-1); |
|
|
|
|
|
// And seal it up |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(iNextStage, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[iNextStage].fBlendFlags = UInt32(-1); |
|
|
|
|
|
fLastEndingStage = 0; |
|
|
|
|
|
// Now set the frame buffer blend |
|
|
// Remember that white darkens and black is no effect. |
|
|
// Form is Src * SrcBlend + Dst * DstBlend |
|
|
// We want inverse Src * Dst, so |
|
|
// Src * ZERO + Dst * InvSrc |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); |
|
|
fD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); |
|
|
fD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR); |
|
|
|
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
|
|
|
// Turn on alpha test. Alpha of zero means the shadow map depth |
|
|
// is greater or equal to the surface depth, i.e. the surface |
|
|
// is between the shadow caster and the light and doesn't receive |
|
|
// shadow. |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAREF, 0x00000001); |
|
|
fLayerState[0].fBlendFlags |= hsGMatState::kBlendTest; |
|
|
|
|
|
fD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); |
|
|
fLayerState[0].fShadeFlags &= ~hsGMatState::kShadeSpecular; |
|
|
|
|
|
// Set fog color to black |
|
|
// We should automatically reset it, because our blend mode is -1'd. |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGCOLOR, 0); |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
DWORD nPass; |
|
|
fSettings.fDXError = fD3DDevice->ValidateDevice(&nPass); |
|
|
if( fSettings.fDXError != D3D_OK ) |
|
|
IGetD3DError(); |
|
|
#endif // HS_DEBUGGING |
|
|
} |
|
|
|
|
|
// ISetupShadowSlaveTextures ////////////////////////////////////////////// |
|
|
// Set any state specific to this shadow slave for projecting the slave's |
|
|
// shadow map onto the surface. |
|
|
void plDXPipeline::ISetupShadowSlaveTextures(plShadowSlave* slave) |
|
|
{ |
|
|
D3DXMATRIX tXfm; |
|
|
|
|
|
hsMatrix44 c2w = GetCameraToWorld(); |
|
|
|
|
|
// Stage 0: |
|
|
// Set Stage 0's texture to the slave's rendertarget. |
|
|
// Set texture transform to slave's camera to texture transform |
|
|
plRenderTarget* renderTarg = (plRenderTarget*)slave->fPipeData; |
|
|
hsAssert(renderTarg, "Processing a slave that hasn't been rendered"); |
|
|
if( !renderTarg ) |
|
|
return; |
|
|
plDXTextureRef* ref = (plDXTextureRef*)renderTarg->GetDeviceRef(); |
|
|
hsAssert(ref, "Shadow map ref should have been made when it was rendered"); |
|
|
if( !ref ) |
|
|
return; |
|
|
|
|
|
hsRefCnt_SafeAssign( fLayerRef[0], ref ); |
|
|
fD3DDevice->SetTexture( 0, ref->fD3DTexture ); |
|
|
|
|
|
hsMatrix44 cameraToTexture = slave->fWorldToTexture * c2w; |
|
|
IMatrix44ToD3DMatrix(tXfm, cameraToTexture); |
|
|
|
|
|
fD3DDevice->SetTransform( sTextureStages[0], &tXfm ); |
|
|
fLayerTransform[0] = true; |
|
|
|
|
|
// Directional lights (ortho projection) just use COUNT2, point lights use COUNT3|PROJECTED. |
|
|
UInt32 xformFlags = slave->fView.GetOrthogonal() ? D3DTTFF_COUNT2 : D3DTTFF_COUNT3 | D3DTTFF_PROJECTED; |
|
|
|
|
|
if( xformFlags != fLayerXformFlags[0] ) |
|
|
{ |
|
|
fLayerXformFlags[0] = xformFlags; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, xformFlags); |
|
|
} |
|
|
|
|
|
// Stage 1: the lut |
|
|
// Set the texture transform to slave's fRcvLUT |
|
|
hsMatrix44 cameraToLut = slave->fRcvLUT * c2w; |
|
|
IMatrix44ToD3DMatrix(tXfm, cameraToLut); |
|
|
|
|
|
fD3DDevice->SetTransform( sTextureStages[1], &tXfm ); |
|
|
fLayerTransform[1] = true; |
|
|
|
|
|
} |
|
|
|
|
|
// ISetShadowLightState ////////////////////////////////////////////////////////////////// |
|
|
// Set the D3D lighting/material model for projecting the shadow map onto this material. |
|
|
void plDXPipeline::ISetShadowLightState(hsGMaterial* mat) |
|
|
{ |
|
|
IDisableLightsForShadow(); |
|
|
inlEnsureLightingOn(); |
|
|
|
|
|
fCurrLightingMethod = plSpan::kLiteShadow; |
|
|
|
|
|
static D3DMATERIAL9 d3dMat; |
|
|
if( mat && mat->GetNumLayers() && mat->GetLayer(0) ) |
|
|
d3dMat.Diffuse.r = d3dMat.Diffuse.g = d3dMat.Diffuse.b = mat->GetLayer(0)->GetOpacity(); |
|
|
else |
|
|
d3dMat.Diffuse.r = d3dMat.Diffuse.g = d3dMat.Diffuse.b = 1.f; |
|
|
d3dMat.Diffuse.a = 1.f; |
|
|
|
|
|
fD3DDevice->SetMaterial(&d3dMat); |
|
|
fD3DDevice->SetRenderState( D3DRS_AMBIENT, 0 ); |
|
|
} |
|
|
|
|
|
// IDisableLightsForShadow /////////////////////////////////////////////////////////// |
|
|
// Disable any lights that are enabled. We'll only want the shadow light illuminating |
|
|
// the surface. |
|
|
void plDXPipeline::IDisableLightsForShadow() |
|
|
{ |
|
|
int i; |
|
|
for( i = 0; i < fLights.fLastIndex + 1; i++ ) |
|
|
{ |
|
|
if( fLights.fEnabledFlags.IsBitSet(i) ) |
|
|
{ |
|
|
fD3DDevice->LightEnable(i, false); |
|
|
} |
|
|
} |
|
|
fLights.fEnabledFlags.Clear(); |
|
|
} |
|
|
|
|
|
// IEnableShadowLight /////////////////////////////////////////////// |
|
|
// Enable this shadow slave's light. |
|
|
// NOT USED. |
|
|
void plDXPipeline::IEnableShadowLight(plShadowSlave* slave) |
|
|
{ |
|
|
fD3DDevice->LightEnable(slave->fLightIndex, true); |
|
|
} |
|
|
|
|
|
// IAcceptsShadow //////////////////////////////////////////////////////////////// |
|
|
// Only allow self shadowing if requested. |
|
|
hsBool plDXPipeline::IAcceptsShadow(const plSpan* span, plShadowSlave* slave) |
|
|
{ |
|
|
// The span's shadow bits records which shadow maps that span was rendered |
|
|
// into. |
|
|
return slave->SelfShadow() || !span->IsShadowBitSet(slave->fIndex); |
|
|
} |
|
|
|
|
|
// IReceivesShadows //////////////////////////////////////////////////////////////////// |
|
|
// Want artists to be able to just disable shadows for spans where they'll either |
|
|
// look goofy, or won't contribute. |
|
|
// Also, if we have less than 3 simultaneous textures, we want to skip anything with |
|
|
// an alpha'd base layer, unless it's been overriden. |
|
|
hsBool plDXPipeline::IReceivesShadows(const plSpan* span, hsGMaterial* mat) |
|
|
{ |
|
|
if( span->fProps & plSpan::kPropNoShadow ) |
|
|
return false; |
|
|
|
|
|
if( span->fProps & plSpan::kPropForceShadow ) |
|
|
return true; |
|
|
|
|
|
if( span->fProps & (plSpan::kPropSkipProjection | plSpan::kPropProjAsVtx) ) |
|
|
return false; |
|
|
|
|
|
if( (fSettings.fMaxLayersAtOnce < 3) |
|
|
&& mat->GetLayer(0)->GetTexture() |
|
|
&& (mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendAlpha) ) |
|
|
return false; |
|
|
|
|
|
#ifdef ENABLE_INTEL_SHADOWS |
|
|
// Shouldn't hit this, since we're disabling shadows on the Intel chips, |
|
|
// but just in case. |
|
|
// To enable this, you'll need to start passing in the drawable as well. |
|
|
if( fSettings.fIsIntel ) |
|
|
{ |
|
|
const plVertexSpan* vertSpan = static_cast<const plVertexSpan*>(span); |
|
|
plGBufferGroup* group = drawable->GetBufferGroup(vertSpan->fGroupIdx); |
|
|
if( !group->GetNumUVs() ) |
|
|
return false; |
|
|
} |
|
|
#endif // ENABLE_INTEL_SHADOWS |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
void plDXPipeline::SubmitClothingOutfit(plClothingOutfit* co) |
|
|
{ |
|
|
if (fClothingOutfits.Find(co) == fClothingOutfits.kMissingIndex) |
|
|
{ |
|
|
fClothingOutfits.Append(co); |
|
|
if (!fPrevClothingOutfits.RemoveItem(co)) |
|
|
co->GetKey()->RefObject(); |
|
|
} |
|
|
} |
|
|
|
|
|
void plDXPipeline::IClearClothingOutfits(hsTArray<plClothingOutfit*>* outfits) |
|
|
{ |
|
|
int i; |
|
|
for (i = outfits->GetCount() - 1; i >= 0; i--) |
|
|
{ |
|
|
plClothingOutfit *co = outfits->Get(i); |
|
|
outfits->Remove(i); |
|
|
IFreeAvRT((plRenderTarget*)co->fTargetLayer->GetTexture()); |
|
|
co->fTargetLayer->SetTexture(nil); |
|
|
co->GetKey()->UnRefObject(); |
|
|
} |
|
|
} |
|
|
|
|
|
void plDXPipeline::IFillAvRTPool() |
|
|
{ |
|
|
fAvNextFreeRT = 0; |
|
|
fAvRTShrinkValidSince = hsTimer::GetSysSeconds(); |
|
|
int numRTs = 1; |
|
|
if (fClothingOutfits.GetCount() > 1) |
|
|
{ |
|
|
// Just jump to 8 for starters so we don't have to refresh for the 2nd, 4th, AND 8th player |
|
|
numRTs = 8; |
|
|
while (numRTs < fClothingOutfits.GetCount()) |
|
|
numRTs *= 2; |
|
|
} |
|
|
|
|
|
// I could see a 32MB video card going down to 64x64 RTs in extreme cases |
|
|
// (over 100 players onscreen at once), but really, if such hardware is ever trying to push |
|
|
// that, the low texture resolution is not going to be your major concern. |
|
|
for (fAvRTWidth = 1024 >> plMipmap::GetGlobalLevelChopCount(); fAvRTWidth >= 32; fAvRTWidth /= 2) |
|
|
{ |
|
|
if (IFillAvRTPool(numRTs, fAvRTWidth)) |
|
|
return; |
|
|
|
|
|
// Nope? Ok, lower the resolution and try again. |
|
|
} |
|
|
} |
|
|
|
|
|
hsBool plDXPipeline::IFillAvRTPool(UInt16 numRTs, UInt16 width) |
|
|
{ |
|
|
fAvRTPool.SetCount(numRTs); |
|
|
int i; |
|
|
for (i = 0; i < numRTs; i++) |
|
|
{ |
|
|
UInt16 flags = plRenderTarget::kIsTexture | plRenderTarget::kIsProjected; |
|
|
UInt8 bitDepth = 32; |
|
|
UInt8 zDepth = 0; |
|
|
UInt8 stencilDepth = 0; |
|
|
fAvRTPool[i] = TRACKED_NEW plRenderTarget(flags, width, width, bitDepth, zDepth, stencilDepth); |
|
|
|
|
|
// If anyone fails, release everyone we've created. |
|
|
if (!MakeRenderTargetRef(fAvRTPool[i])) |
|
|
{ |
|
|
int j; |
|
|
for (j = 0; j <= i; j++) |
|
|
{ |
|
|
delete fAvRTPool[j]; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
void plDXPipeline::IReleaseAvRTPool() |
|
|
{ |
|
|
int i; |
|
|
for (i = 0; i < fClothingOutfits.GetCount(); i++) |
|
|
{ |
|
|
fClothingOutfits[i]->fTargetLayer->SetTexture(nil); |
|
|
} |
|
|
for (i = 0; i < fPrevClothingOutfits.GetCount(); i++) |
|
|
{ |
|
|
fPrevClothingOutfits[i]->fTargetLayer->SetTexture(nil); |
|
|
} |
|
|
for (i = 0; i < fAvRTPool.GetCount(); i++) |
|
|
{ |
|
|
delete(fAvRTPool[i]); |
|
|
} |
|
|
fAvRTPool.Reset(); |
|
|
} |
|
|
|
|
|
plRenderTarget *plDXPipeline::IGetNextAvRT() |
|
|
{ |
|
|
return fAvRTPool[fAvNextFreeRT++]; |
|
|
} |
|
|
|
|
|
void plDXPipeline::IFreeAvRT(plRenderTarget* tex) |
|
|
{ |
|
|
UInt32 index = fAvRTPool.Find(tex); |
|
|
if (index != fAvRTPool.kMissingIndex) |
|
|
{ |
|
|
hsAssert(index < fAvNextFreeRT, "Freeing an avatar RT that's already free?"); |
|
|
fAvRTPool[index] = fAvRTPool[fAvNextFreeRT - 1]; |
|
|
fAvRTPool[fAvNextFreeRT - 1] = tex; |
|
|
fAvNextFreeRT--; |
|
|
} |
|
|
} |
|
|
|
|
|
struct plAVTexVert |
|
|
{ |
|
|
float fPos[3]; |
|
|
float fUv[2]; |
|
|
}; |
|
|
|
|
|
void plDXPipeline::IPreprocessAvatarTextures() |
|
|
{ |
|
|
plProfile_Set(AvRTPoolUsed, fClothingOutfits.GetCount()); |
|
|
plProfile_Set(AvRTPoolCount, fAvRTPool.GetCount()); |
|
|
plProfile_Set(AvRTPoolRes, fAvRTWidth); |
|
|
plProfile_Set(AvRTShrinkTime, UInt32(hsTimer::GetSysSeconds() - fAvRTShrinkValidSince)); |
|
|
|
|
|
IClearClothingOutfits(&fPrevClothingOutfits); // Frees anyone used last frame that we don't need this frame |
|
|
|
|
|
const UInt32 kVFormat = D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0); |
|
|
|
|
|
if (fClothingOutfits.GetCount() == 0) |
|
|
return; |
|
|
|
|
|
plMipmap *itemBufferTex = nil; |
|
|
|
|
|
fForceMatHandle = true; |
|
|
ISetShaders(nil, nil); // Has a side effect of futzing with our cull settings... |
|
|
|
|
|
// Even though we're going to use DrawPrimitiveUP, we explicitly set the current VB ref to nil, |
|
|
// otherwise we might try and use the same VB ref later, think it hasn't changed, and |
|
|
// not update our FVF. |
|
|
hsRefCnt_SafeUnRef(fSettings.fCurrVertexBuffRef); |
|
|
fSettings.fCurrVertexBuffRef = nil; |
|
|
fD3DDevice->SetStreamSource(0, NULL, 0, 0); |
|
|
fD3DDevice->SetFVF(fSettings.fCurrFVFFormat = kVFormat); |
|
|
fD3DDevice->SetTransform(D3DTS_VIEW, &d3dIdentityMatrix); |
|
|
fD3DDevice->SetTransform(D3DTS_WORLD, &d3dIdentityMatrix); |
|
|
fD3DDevice->SetTransform(D3DTS_PROJECTION, &d3dIdentityMatrix); |
|
|
fD3DDevice->SetRenderState(D3DRS_CULLMODE, fCurrCullMode = D3DCULL_NONE); |
|
|
fD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE); |
|
|
fD3DDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); |
|
|
fD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); |
|
|
fLayerState[0].fZFlags &= ~hsGMatState::kZMask; |
|
|
fLayerState[0].fZFlags |= hsGMatState::kZNoZWrite | hsGMatState::kZNoZRead; |
|
|
if (fLayerUVWSrcs[0] != 0) |
|
|
{ |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); |
|
|
fLayerUVWSrcs[0] = 0; |
|
|
} |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); |
|
|
fD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); |
|
|
fLayerState[0].fClampFlags = hsGMatState::kClampTexture; |
|
|
if (D3DTTFF_DISABLE != fLayerXformFlags[0]) |
|
|
{ |
|
|
fLayerXformFlags[0] = D3DTTFF_DISABLE; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); |
|
|
} |
|
|
fD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); |
|
|
fLayerState[0].fShadeFlags &= ~hsGMatState::kShadeSpecular; |
|
|
fD3DDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); |
|
|
fCurrFog.fEnvPtr = nil; |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); |
|
|
fD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); |
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); |
|
|
fD3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); |
|
|
fLayerState[1].fBlendFlags = UInt32(-1); |
|
|
inlEnsureLightingOff(); |
|
|
|
|
|
int oIdx; |
|
|
for (oIdx = 0; oIdx < fClothingOutfits.GetCount(); oIdx++) |
|
|
{ |
|
|
plClothingOutfit *co = fClothingOutfits[oIdx]; |
|
|
if (co->fBase == nil || co->fBase->fBaseTexture == nil) |
|
|
continue; |
|
|
|
|
|
plRenderTarget *rt = plRenderTarget::ConvertNoRef(co->fTargetLayer->GetTexture()); |
|
|
if (rt != nil && co->fDirtyItems.Empty()) |
|
|
{ |
|
|
// we've still got our valid RT from last frame and we have nothing to do. |
|
|
continue; |
|
|
} |
|
|
|
|
|
if (rt == nil) |
|
|
{ |
|
|
rt = IGetNextAvRT(); |
|
|
co->fTargetLayer->SetTexture(rt); |
|
|
} |
|
|
|
|
|
PushRenderTarget(rt); |
|
|
D3DVIEWPORT9 vp = {0, 0, rt->GetWidth(), rt->GetHeight(), 0.f, 1.f}; |
|
|
WEAK_ERROR_CHECK(fD3DDevice->SetViewport(&vp)); |
|
|
|
|
|
hsScalar uOff = 0.5f / rt->GetWidth(); |
|
|
hsScalar vOff = 0.5f / rt->GetHeight(); |
|
|
|
|
|
// Copy over the base |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); |
|
|
fD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA); |
|
|
fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, 0xffffffff); |
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
IDrawClothingQuad(-1.f, -1.f, 2.f, 2.f, uOff, vOff, co->fBase->fBaseTexture); |
|
|
plClothingLayout *layout = plClothingMgr::GetClothingMgr()->GetLayout(co->fBase->fLayoutName); |
|
|
|
|
|
int i, j, k; |
|
|
for (i = 0; i < co->fItems.GetCount(); i++) |
|
|
{ |
|
|
plClothingItem *item = co->fItems[i]; |
|
|
//if (!co->fDirtyItems.IsBitSet(item->fTileset)) |
|
|
// continue; // Not dirty, don't update |
|
|
|
|
|
for (j = 0; j < item->fElements.GetCount(); j++) |
|
|
{ |
|
|
for (k = 0; k < plClothingElement::kLayerMax; k++) |
|
|
{ |
|
|
if (item->fTextures[j][k] == nil) |
|
|
continue; |
|
|
|
|
|
itemBufferTex = item->fTextures[j][k]; |
|
|
hsColorRGBA tint = co->GetItemTint(item, k); |
|
|
if (k >= plClothingElement::kLayerSkinBlend1 && k <= plClothingElement::kLayerSkinLast) |
|
|
tint.a = co->fSkinBlends[k - plClothingElement::kLayerSkinBlend1]; |
|
|
|
|
|
if (k == plClothingElement::kLayerBase) |
|
|
{ |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); |
|
|
fD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA); |
|
|
} |
|
|
else |
|
|
{ |
|
|
fD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); |
|
|
fD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); |
|
|
fD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); |
|
|
fD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE); |
|
|
} |
|
|
fD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, tint.ToARGB32()); |
|
|
fLayerState[0].fBlendFlags = UInt32(-1); |
|
|
hsScalar screenW = (hsScalar)item->fElements[j]->fWidth / layout->fOrigWidth * 2.f; |
|
|
hsScalar screenH = (hsScalar)item->fElements[j]->fHeight / layout->fOrigWidth * 2.f; |
|
|
hsScalar screenX = (hsScalar)item->fElements[j]->fXPos / layout->fOrigWidth * 2.f - 1.f; |
|
|
hsScalar screenY = (1.f - (hsScalar)item->fElements[j]->fYPos / layout->fOrigWidth) * 2.f - 1.f - screenH; |
|
|
IDrawClothingQuad(screenX, screenY, screenW, screenH, uOff, vOff, itemBufferTex); |
|
|
} |
|
|
} |
|
|
} |
|
|
PopRenderTarget(); |
|
|
co->fDirtyItems.Clear(); |
|
|
} |
|
|
// Nothing else sets this render state, so let's just set it back to the default to be safe |
|
|
fD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA); |
|
|
fView.fXformResetFlags = fView.kResetAll; |
|
|
|
|
|
fClothingOutfits.Swap(fPrevClothingOutfits); |
|
|
} |
|
|
|
|
|
void plDXPipeline::IDrawClothingQuad(hsScalar x, hsScalar y, hsScalar w, hsScalar h, |
|
|
hsScalar uOff, hsScalar vOff, plMipmap *tex) |
|
|
{ |
|
|
const UInt32 kVSize = sizeof(plAVTexVert); |
|
|
plDXTextureRef* ref = (plDXTextureRef*)tex->GetDeviceRef(); |
|
|
if (!ref || ref->IsDirty()) |
|
|
{ |
|
|
MakeTextureRef(nil, tex); |
|
|
ref = (plDXTextureRef*)tex->GetDeviceRef(); |
|
|
} |
|
|
if (!ref->fD3DTexture) |
|
|
{ |
|
|
if (ref->fData) |
|
|
IReloadTexture(ref); |
|
|
} |
|
|
hsRefCnt_SafeAssign( fLayerRef[0], ref ); |
|
|
fD3DDevice->SetTexture(0, ref->fD3DTexture); |
|
|
|
|
|
plAVTexVert ptr[4]; |
|
|
plAVTexVert vert; |
|
|
vert.fPos[0] = x; |
|
|
vert.fPos[1] = y; |
|
|
vert.fPos[2] = 0.5f; |
|
|
vert.fUv[0] = uOff; |
|
|
vert.fUv[1] = 1.f + vOff; |
|
|
|
|
|
// P0 |
|
|
ptr[2] = vert; |
|
|
|
|
|
// P1 |
|
|
ptr[0] = vert; |
|
|
ptr[0].fPos[0] += w; |
|
|
ptr[0].fUv[0] += 1.f; |
|
|
|
|
|
// P2 |
|
|
ptr[1] = vert; |
|
|
ptr[1].fPos[0] += w; |
|
|
ptr[1].fUv[0] += 1.f; |
|
|
ptr[1].fPos[1] += h; |
|
|
ptr[1].fUv[1] -= 1.f; |
|
|
|
|
|
// P3 |
|
|
ptr[3] = vert; |
|
|
ptr[3].fPos[1] += h; |
|
|
ptr[3].fUv[1] -= 1.f; |
|
|
|
|
|
#ifdef HS_DEBUGGING |
|
|
DWORD nPass; |
|
|
fSettings.fDXError = fD3DDevice->ValidateDevice(&nPass); |
|
|
if( fSettings.fDXError != D3D_OK ) |
|
|
IGetD3DError(); |
|
|
#endif // HS_DEBUGGING |
|
|
fD3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, ptr, kVSize); |
|
|
} |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// Test hackery as R&D for water |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
// End Test hackery as R&D for water |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
//// Functions from Other Classes That Need to Be Here to Compile Right /////// |
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
plPipeline *plPipelineCreate::ICreateDXPipeline( hsWinRef hWnd, const hsG3DDeviceModeRecord *devMode ) |
|
|
{ |
|
|
plDXPipeline *pipe = TRACKED_NEW plDXPipeline( hWnd, devMode ); |
|
|
|
|
|
// Taken out 8.1.2001 mcn - If we have an error, still return so the client can grab the string |
|
|
// if( pipe->GetErrorString() != nil ) |
|
|
// { |
|
|
// delete pipe; |
|
|
// pipe = nil; |
|
|
// } |
|
|
|
|
|
return pipe; |
|
|
}
|
|
|
|