/*==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 . 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 #include #include #ifdef DX_OLD_SDK #include #define DXGetErrorString9 DXGetErrorString #else #include #endif #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 //#define MF_TOSSER int mfCurrentTest = 100; PipelineParams plPipeline::fDefaultPipeParams; PipelineParams plPipeline::fInitialPipeParams; //#define MF_ENABLE_HACKOFF #ifdef MF_ENABLE_HACKOFF //WHITE static hsTArray 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 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éfaut de ses paramètres ont été ré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ó un error al inicializar tu tarjeta de ví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 *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(); ReleaseCapture(); 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(); plgDispatch::MsgSend(clean); SetCapture(fSettings.fHWnd); } 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 *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 &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 *res, int ColorDepth ) { int i, j; std::vector 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 *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(); 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& 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 &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 &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 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 { 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& 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 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& 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 hsTArrayvisList; 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& 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& 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& 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& 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 tmpList; static hsTArray moveList; static hsTArray 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 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& 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& 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& 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& 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& visList, plVisMgr* visMgr ) { static hsTArray 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 &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& visList ) { plProfile_BeginTiming(RenderSpan); hsMatrix44 lastL2W; UInt32 i, j; bool drewPatch = false; hsGMaterial *material; const hsTArray& 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 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 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(); // 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 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& 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 + /// Vtx preshaded: (particle systems) /// MATa * VTXd + 0 + /// Vtx non-preshaded: /// white * VTXd + MATa + /// 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 + 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 + // 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 + 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 + // 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 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 ) { fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA ); fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCALPHA ); } 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& 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 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(); if (wBnd.GetType() == kBoundsNormal) return fView.fCullTree.BoundsVisible(wBnd); else return false; } 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& pos = fView.fCullTree.GetCaptureVerts(); hsTArray& norm = fView.fCullTree.GetCaptureNorms(); hsTArray& color = fView.fCullTree.GetCaptureColors(); hsTArray& 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& 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 . 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& 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& 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& 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& 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 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* 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* 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& 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 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(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* 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; }