//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "gfx/gfxDevice.h" #include "gfx/gfxInit.h" #include "gfx/gfxCubemap.h" #include "gfx/primBuilder.h" #include "gfx/gfxDrawUtil.h" #include "gfx/gfxFence.h" #include "gfx/gfxFontRenderBatcher.h" #include "gfx/gfxPrimitiveBuffer.h" #include "gfx/gfxShader.h" #include "gfx/gfxStateBlock.h" #include "gfx/screenshot.h" #include "gfx/gfxStringEnumTranslate.h" #include "gfx/gfxTextureManager.h" #include "core/frameAllocator.h" #include "core/stream/fileStream.h" #include "core/strings/unicode.h" #include "core/util/journal/process.h" #include "core/util/safeDelete.h" #include "math/util/frustum.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" GFXDevice * GFXDevice::smGFXDevice = NULL; bool GFXDevice::smWireframe = false; bool GFXDevice::smDisableVSync = true; F32 GFXDevice::smForcedPixVersion = -1.0f; bool GFXDevice::smDisableOcclusionQuery = false; bool gDisassembleAllShaders = false; void GFXDevice::initConsole() { GFXStringEnumTranslate::init(); Con::addVariable( "$gfx::wireframe", TypeBool, &smWireframe, "Used to toggle wireframe rendering at runtime.\n" "@ingroup GFX\n" ); Con::addVariable( "$gfx::disassembleAllShaders", TypeBool, &gDisassembleAllShaders, "On supported devices this will dump shader disassembly to the " "procedural shader folder.\n" "@ingroup GFX\n" ); Con::addVariable( "$gfx::disableOcclusionQuery", TypeBool, &smDisableOcclusionQuery, "Debug helper that disables all hardware occlusion queries causing " "them to return only the visibile state.\n" "@ingroup GFX\n" ); Con::addVariable( "$pref::Video::disableVerticalSync", TypeBool, &smDisableVSync, "Disables vertical sync on the active device.\n" "@note The video mode must be reset for the change to take affect.\n" "@ingroup GFX\n" ); Con::addVariable( "$pref::Video::forcedPixVersion", TypeF32, &smForcedPixVersion, "Will force the shader model if the value is positive and less than the " "shader model supported by the active device. Use 0 for fixed function.\n" "@note The graphics device must be reset for the change to take affect.\n" "@ingroup GFX\n" ); } GFXDevice::DeviceEventSignal& GFXDevice::getDeviceEventSignal() { static DeviceEventSignal theSignal; return theSignal; } GFXDevice::GFXDevice() { VECTOR_SET_ASSOCIATION( mVideoModes ); VECTOR_SET_ASSOCIATION( mRTStack ); mWorldMatrixDirty = false; mWorldStackSize = 0; mProjectionMatrixDirty = false; mViewMatrixDirty = false; mTextureMatrixCheckDirty = false; mViewMatrix.identity(); mProjectionMatrix.identity(); for( int i = 0; i < WORLD_STACK_MAX; i++ ) mWorldMatrix[i].identity(); AssertFatal(smGFXDevice == NULL, "Already a GFXDevice created! Bad!"); smGFXDevice = this; // Vertex buffer cache mCurrVertexDecl = NULL; mVertexDeclDirty = false; for ( U32 i=0; i < VERTEX_STREAM_COUNT; i++ ) { mVertexBufferDirty[i] = false; mVertexBufferFrequency[i] = 0; mVertexBufferFrequencyDirty[i] = false; } // Primitive buffer cache mPrimitiveBufferDirty = false; mTexturesDirty = false; // Use of TEXTURE_STAGE_COUNT in initialization is okay [7/2/2007 Pat] for(U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) { mTextureDirty[i] = false; mCurrentTexture[i] = NULL; mNewTexture[i] = NULL; mCurrentCubemap[i] = NULL; mNewCubemap[i] = NULL; mTexType[i] = GFXTDT_Normal; mTextureMatrix[i].identity(); mTextureMatrixDirty[i] = false; } mLightsDirty = false; for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) { mLightDirty[i] = false; mCurrentLightEnable[i] = false; } mGlobalAmbientColorDirty = false; mGlobalAmbientColor = ColorF(0.0f, 0.0f, 0.0f, 1.0f); mLightMaterialDirty = false; dMemset(&mCurrentLightMaterial, NULL, sizeof(GFXLightMaterial)); // State block mStateBlockDirty = false; mCurrentStateBlock = NULL; mNewStateBlock = NULL; mCurrentShaderConstBuffer = NULL; // misc mAllowRender = true; mCurrentRenderStyle = RS_Standard; mCurrentProjectionOffset = Point2F::Zero; mStereoEyeOffset = Point3F::Zero; mCanCurrentlyRender = false; mInitialized = false; mRTDirty = false; mViewport = RectI::Zero; mViewportDirty = false; mCurrentFrontBufferIdx = 0; mDeviceSwizzle32 = NULL; mDeviceSwizzle24 = NULL; mResourceListHead = NULL; mCardProfiler = NULL; // Initialize our drawing utility. mDrawer = NULL; // Add a few system wide shader macros. GFXShader::addGlobalMacro( "TORQUE", "1" ); GFXShader::addGlobalMacro( "TORQUE_VERSION", String::ToString(getVersionNumber()) ); #if defined TORQUE_OS_WIN32 GFXShader::addGlobalMacro( "TORQUE_OS_WIN32" ); #elif defined TORQUE_OS_MAC GFXShader::addGlobalMacro( "TORQUE_OS_MAC" ); #elif defined TORQUE_OS_LINUX GFXShader::addGlobalMacro( "TORQUE_OS_LINUX" ); #elif defined TORQUE_OS_XENON GFXShader::addGlobalMacro( "TORQUE_OS_XENON" ); #elif defined TORQUE_OS_XBOX GFXShader::addGlobalMacro( "TORQUE_OS_XBOX" ); #elif defined TORQUE_OS_PS3 GFXShader::addGlobalMacro( "TORQUE_OS_PS3" ); #endif } GFXDrawUtil* GFXDevice::getDrawUtil() { if (!mDrawer) { mDrawer = new GFXDrawUtil(this); } return mDrawer; } void GFXDevice::deviceInited() { getDeviceEventSignal().trigger(deInit); mDeviceStatistics.setPrefix("$GFXDeviceStatistics::"); // Initialize the static helper textures. GBitmap temp( 2, 2, false, GFXFormatR8G8B8A8 ); temp.fill( ColorI::ONE ); GFXTexHandle::ONE.set( &temp, &GFXDefaultStaticDiffuseProfile, false, "GFXTexHandle::ONE" ); temp.fill( ColorI::ZERO ); GFXTexHandle::ZERO.set( &temp, &GFXDefaultStaticDiffuseProfile, false, "GFXTexHandle::ZERO" ); temp.fill( ColorI( 128, 128, 255 ) ); GFXTexHandle::ZUP.set( &temp, &GFXDefaultStaticNormalMapProfile, false, "GFXTexHandle::ZUP" ); } bool GFXDevice::destroy() { // Cleanup the static helper textures. GFXTexHandle::ONE.free(); GFXTexHandle::ZERO.free(); GFXTexHandle::ZUP.free(); // Make this release its buffer. PrimBuild::shutdown(); // Let people know we are shutting down getDeviceEventSignal().trigger(deDestroy); if(smGFXDevice) smGFXDevice->preDestroy(); SAFE_DELETE(smGFXDevice); return true; } void GFXDevice::preDestroy() { // Delete draw util SAFE_DELETE( mDrawer ); } GFXDevice::~GFXDevice() { smGFXDevice = NULL; // Clean up our current buffers. mCurrentPrimitiveBuffer = NULL; for ( U32 i=0; i < VERTEX_STREAM_COUNT; i++ ) mCurrentVertexBuffer[i] = NULL; // Clear out our current texture references for (U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) { mCurrentTexture[i] = NULL; mNewTexture[i] = NULL; mCurrentCubemap[i] = NULL; mNewCubemap[i] = NULL; } // Release all the unreferenced textures in the cache. mTextureManager->cleanupCache(); // Check for resource leaks #ifdef TORQUE_DEBUG AssertFatal( GFXTextureObject::dumpActiveTOs() == 0, "There is a texture object leak, check the log for more details." ); GFXPrimitiveBuffer::dumpActivePBs(); #endif SAFE_DELETE( mTextureManager ); // Clear out our state block references mCurrentStateBlocks.clear(); mNewStateBlock = NULL; mCurrentStateBlock = NULL; mCurrentShaderConstBuffer = NULL; /// End Block above BTR // -- Clear out resource list // Note: our derived class destructor will have already released resources. // Clearing this list saves us from having our resources (which are not deleted // just released) turn around and try to remove themselves from this list. while (mResourceListHead) { GFXResource * head = mResourceListHead; mResourceListHead = head->mNextResource; head->mPrevResource = NULL; head->mNextResource = NULL; head->mOwningDevice = NULL; } } GFXStateBlockRef GFXDevice::createStateBlock(const GFXStateBlockDesc& desc) { PROFILE_SCOPE( GFXDevice_CreateStateBlock ); U32 hashValue = desc.getHashValue(); if (mCurrentStateBlocks[hashValue]) return mCurrentStateBlocks[hashValue]; GFXStateBlockRef result = createStateBlockInternal(desc); result->registerResourceWithDevice(this); mCurrentStateBlocks[hashValue] = result; return result; } void GFXDevice::setStateBlock(GFXStateBlock* block) { AssertFatal(block, "NULL state block!"); AssertFatal(block->getOwningDevice() == this, "This state doesn't apply to this device!"); if (block != mCurrentStateBlock) { mStateDirty = true; mStateBlockDirty = true; mNewStateBlock = block; } else { mStateBlockDirty = false; mNewStateBlock = mCurrentStateBlock; } } void GFXDevice::setStateBlockByDesc( const GFXStateBlockDesc &desc ) { PROFILE_SCOPE( GFXDevice_SetStateBlockByDesc ); GFXStateBlock *block = createStateBlock( desc ); setStateBlock( block ); } void GFXDevice::setShaderConstBuffer(GFXShaderConstBuffer* buffer) { mCurrentShaderConstBuffer = buffer; } void GFXDevice::updateStates(bool forceSetAll /*=false*/) { PROFILE_SCOPE(GFXDevice_updateStates); if(forceSetAll) { bool rememberToEndScene = false; if(!canCurrentlyRender()) { if (!beginScene()) { AssertFatal(false, "GFXDevice::updateStates: Unable to beginScene!"); } rememberToEndScene = true; } setMatrix( GFXMatrixProjection, mProjectionMatrix ); setMatrix( GFXMatrixWorld, mWorldMatrix[mWorldStackSize] ); setMatrix( GFXMatrixView, mViewMatrix ); setVertexDecl( mCurrVertexDecl ); for ( U32 i=0; i < VERTEX_STREAM_COUNT; i++ ) { setVertexStream( i, mCurrentVertexBuffer[i] ); setVertexStreamFrequency( i, mVertexBufferFrequency[i] ); } if( mCurrentPrimitiveBuffer.isValid() ) // This could be NULL when the device is initalizing mCurrentPrimitiveBuffer->prepare(); /// Stateblocks if ( mNewStateBlock ) setStateBlockInternal(mNewStateBlock, true); mCurrentStateBlock = mNewStateBlock; for(U32 i = 0; i < getNumSamplers(); i++) { switch (mTexType[i]) { case GFXTDT_Normal : { mCurrentTexture[i] = mNewTexture[i]; setTextureInternal(i, mCurrentTexture[i]); } break; case GFXTDT_Cube : { mCurrentCubemap[i] = mNewCubemap[i]; if (mCurrentCubemap[i]) mCurrentCubemap[i]->setToTexUnit(i); else setTextureInternal(i, NULL); } break; default: AssertFatal(false, "Unknown texture type!"); break; } } // Set our material setLightMaterialInternal(mCurrentLightMaterial); // Set our lights for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) { setLightInternal(i, mCurrentLight[i], mCurrentLightEnable[i]); } _updateRenderTargets(); if(rememberToEndScene) endScene(); return; } if (!mStateDirty) return; // Normal update logic begins here. mStateDirty = false; // Update Projection Matrix if( mProjectionMatrixDirty ) { setMatrix( GFXMatrixProjection, mProjectionMatrix ); mProjectionMatrixDirty = false; } // Update World Matrix if( mWorldMatrixDirty ) { setMatrix( GFXMatrixWorld, mWorldMatrix[mWorldStackSize] ); mWorldMatrixDirty = false; } // Update View Matrix if( mViewMatrixDirty ) { setMatrix( GFXMatrixView, mViewMatrix ); mViewMatrixDirty = false; } if( mTextureMatrixCheckDirty ) { for( int i = 0; i < getNumSamplers(); i++ ) { if( mTextureMatrixDirty[i] ) { mTextureMatrixDirty[i] = false; setMatrix( (GFXMatrixType)(GFXMatrixTexture + i), mTextureMatrix[i] ); } } mTextureMatrixCheckDirty = false; } // Update the vertex declaration. if ( mVertexDeclDirty ) { setVertexDecl( mCurrVertexDecl ); mVertexDeclDirty = false; } // Update the vertex buffers. for ( U32 i=0; i < VERTEX_STREAM_COUNT; i++ ) { if ( mVertexBufferDirty[i] ) { setVertexStream( i, mCurrentVertexBuffer[i] ); mVertexBufferDirty[i] = false; } if ( mVertexBufferFrequencyDirty[i] ) { setVertexStreamFrequency( i, mVertexBufferFrequency[i] ); mVertexBufferFrequencyDirty[i] = false; } } // Update primitive buffer // // NOTE: It is very important to set the primitive buffer AFTER the vertex buffer // because in order to draw indexed primitives in DX8, the call to SetIndicies // needs to include the base vertex offset, and the DX8 GFXDevice relies on // having mCurrentVB properly assigned before the call to setIndices -patw if( mPrimitiveBufferDirty ) { if( mCurrentPrimitiveBuffer.isValid() ) // This could be NULL when the device is initalizing mCurrentPrimitiveBuffer->prepare(); mPrimitiveBufferDirty = false; } // NOTE: With state blocks, it's now important to update state before setting textures // some devices (e.g. OpenGL) set states on the texture and we need that information before // the texture is activated. if (mStateBlockDirty) { setStateBlockInternal(mNewStateBlock, false); mCurrentStateBlock = mNewStateBlock; mStateBlockDirty = false; } if( mTexturesDirty ) { mTexturesDirty = false; for(U32 i = 0; i < getNumSamplers(); i++) { if(!mTextureDirty[i]) continue; mTextureDirty[i] = false; switch (mTexType[i]) { case GFXTDT_Normal : { mCurrentTexture[i] = mNewTexture[i]; setTextureInternal(i, mCurrentTexture[i]); } break; case GFXTDT_Cube : { mCurrentCubemap[i] = mNewCubemap[i]; if (mCurrentCubemap[i]) mCurrentCubemap[i]->setToTexUnit(i); else setTextureInternal(i, NULL); } break; default: AssertFatal(false, "Unknown texture type!"); break; } } } // Set light material if(mLightMaterialDirty) { setLightMaterialInternal(mCurrentLightMaterial); mLightMaterialDirty = false; } // Set our lights if(mLightsDirty) { mLightsDirty = false; for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) { if(!mLightDirty[i]) continue; mLightDirty[i] = false; setLightInternal(i, mCurrentLight[i], mCurrentLightEnable[i]); } } _updateRenderTargets(); #ifdef TORQUE_DEBUG_RENDER doParanoidStateCheck(); #endif } void GFXDevice::setPrimitiveBuffer( GFXPrimitiveBuffer *buffer ) { if( buffer == mCurrentPrimitiveBuffer ) return; mCurrentPrimitiveBuffer = buffer; mPrimitiveBufferDirty = true; mStateDirty = true; } void GFXDevice::drawPrimitive( U32 primitiveIndex ) { AssertFatal( mCurrentPrimitiveBuffer.isValid(), "Trying to call drawPrimitive with no current primitive buffer, call setPrimitiveBuffer()" ); AssertFatal( primitiveIndex < mCurrentPrimitiveBuffer->mPrimitiveCount, "Out of range primitive index."); drawPrimitive( mCurrentPrimitiveBuffer->mPrimitiveArray[primitiveIndex] ); } void GFXDevice::drawPrimitive( const GFXPrimitive &prim ) { // Do NOT add index buffer offset to this call, it will be added by drawIndexedPrimitive drawIndexedPrimitive( prim.type, prim.startVertex, prim.minIndex, prim.numVertices, prim.startIndex, prim.numPrimitives ); } void GFXDevice::drawPrimitives() { AssertFatal( mCurrentPrimitiveBuffer.isValid(), "Trying to call drawPrimitive with no current primitive buffer, call setPrimitiveBuffer()" ); GFXPrimitive *info = NULL; for( U32 i = 0; i < mCurrentPrimitiveBuffer->mPrimitiveCount; i++ ) { info = &mCurrentPrimitiveBuffer->mPrimitiveArray[i]; // Do NOT add index buffer offset to this call, it will be added by drawIndexedPrimitive drawIndexedPrimitive( info->type, info->startVertex, info->minIndex, info->numVertices, info->startIndex, info->numPrimitives ); } } DefineEngineFunction( getDisplayDeviceList, String, (),, "Returns a tab-seperated string of the detected devices across all adapters.\n" "@ingroup GFX\n" ) { Vector adapters; GFXInit::getAdapters(&adapters); StringBuilder str; for (S32 i=0; imName); } return str.end(); } void GFXDevice::setFrustum( F32 left, F32 right, F32 bottom, F32 top, F32 nearPlane, F32 farPlane, bool bRotate ) { // store values mFrustum.set(false, left, right, top, bottom, nearPlane, farPlane); // compute matrix MatrixF projection; mFrustum.getProjectionMatrix(&projection, bRotate); setProjectionMatrix( projection ); } void GFXDevice::setFrustum( const Frustum& frust, bool bRotate ) { // store values mFrustum = frust; // compute matrix MatrixF projection; mFrustum.getProjectionMatrix(&projection, bRotate); setProjectionMatrix( projection ); } void GFXDevice::getFrustum( F32 *left, F32 *right, F32 *bottom, F32 *top, F32 *nearPlane, F32 *farPlane, bool *isOrtho ) const { if ( left ) *left = mFrustum.getNearLeft(); if ( right ) *right = mFrustum.getNearRight(); if ( bottom ) *bottom = mFrustum.getNearBottom(); if ( top ) *top = mFrustum.getNearTop(); if ( nearPlane ) *nearPlane = mFrustum.getNearDist(); if ( farPlane ) *farPlane = mFrustum.getFarDist(); if ( isOrtho ) *isOrtho = mFrustum.isOrtho(); } void GFXDevice::setOrtho( F32 left, F32 right, F32 bottom, F32 top, F32 nearPlane, F32 farPlane, bool doRotate ) { // store values mFrustum.set(true, left, right, top, bottom, nearPlane, farPlane); // compute matrix MatrixF projection; mFrustum.getProjectionMatrix(&projection, doRotate); setProjectionMatrix( projection ); } Point2F GFXDevice::getWorldToScreenScale() const { Point2F scale; const RectI &viewport = getViewport(); if ( mFrustum.isOrtho() ) scale.set( viewport.extent.x / mFrustum.getWidth(), viewport.extent.y / mFrustum.getHeight() ); else scale.set( ( mFrustum.getNearDist() * viewport.extent.x ) / mFrustum.getWidth(), ( mFrustum.getNearDist() * viewport.extent.y ) / mFrustum.getHeight() ); return scale; } //----------------------------------------------------------------------------- // Set Light //----------------------------------------------------------------------------- void GFXDevice::setLight(U32 stage, GFXLightInfo* light) { AssertFatal(stage < LIGHT_STAGE_COUNT, "GFXDevice::setLight - out of range stage!"); if(!mLightDirty[stage]) { mStateDirty = true; mLightsDirty = true; mLightDirty[stage] = true; } mCurrentLightEnable[stage] = (light != NULL); if(mCurrentLightEnable[stage]) mCurrentLight[stage] = *light; } //----------------------------------------------------------------------------- // Set Light Material //----------------------------------------------------------------------------- void GFXDevice::setLightMaterial(GFXLightMaterial mat) { mCurrentLightMaterial = mat; mLightMaterialDirty = true; mStateDirty = true; } void GFXDevice::setGlobalAmbientColor(ColorF color) { if(mGlobalAmbientColor != color) { mGlobalAmbientColor = color; mGlobalAmbientColorDirty = true; } } //----------------------------------------------------------------------------- // Set texture //----------------------------------------------------------------------------- void GFXDevice::setTexture( U32 stage, GFXTextureObject *texture ) { AssertFatal(stage < getNumSamplers(), "GFXDevice::setTexture - out of range stage!"); if ( mTexType[stage] == GFXTDT_Normal && ( ( mTextureDirty[stage] && mNewTexture[stage].getPointer() == texture ) || ( !mTextureDirty[stage] && mCurrentTexture[stage].getPointer() == texture ) ) ) return; mStateDirty = true; mTexturesDirty = true; mTextureDirty[stage] = true; mNewTexture[stage] = texture; mTexType[stage] = GFXTDT_Normal; // Clear out the cubemaps mNewCubemap[stage] = NULL; mCurrentCubemap[stage] = NULL; } //----------------------------------------------------------------------------- // Set cube texture //----------------------------------------------------------------------------- void GFXDevice::setCubeTexture( U32 stage, GFXCubemap *texture ) { AssertFatal(stage < getNumSamplers(), "GFXDevice::setTexture - out of range stage!"); if ( mTexType[stage] == GFXTDT_Cube && ( ( mTextureDirty[stage] && mNewCubemap[stage].getPointer() == texture ) || ( !mTextureDirty[stage] && mCurrentCubemap[stage].getPointer() == texture ) ) ) return; mStateDirty = true; mTexturesDirty = true; mTextureDirty[stage] = true; mNewCubemap[stage] = texture; mTexType[stage] = GFXTDT_Cube; // Clear out the normal textures mNewTexture[stage] = NULL; mCurrentTexture[stage] = NULL; } inline bool GFXDevice::beginScene() { AssertFatal( mCanCurrentlyRender == false, "GFXDevice::beginScene() - The scene has already begun!" ); mDeviceStatistics.clear(); // Send the start of frame signal. getDeviceEventSignal().trigger( GFXDevice::deStartOfFrame ); return beginSceneInternal(); } //------------------------------------------------------------------------------ inline void GFXDevice::endScene() { AssertFatal( mCanCurrentlyRender == true, "GFXDevice::endScene() - The scene has already ended!" ); // End frame signal getDeviceEventSignal().trigger( GFXDevice::deEndOfFrame ); endSceneInternal(); mDeviceStatistics.exportToConsole(); } void GFXDevice::setViewport( const RectI &inRect ) { // Clip the rect against the renderable size. Point2I size = mCurrentRT->getSize(); RectI maxRect(Point2I(0,0), size); RectI rect = inRect; rect.intersect(maxRect); if ( mViewport != rect ) { mViewport = rect; mViewportDirty = true; } } void GFXDevice::pushActiveRenderTarget() { // Push the current target on to the stack. mRTStack.push_back( mCurrentRT ); } void GFXDevice::popActiveRenderTarget() { AssertFatal( mRTStack.size() > 0, "GFXDevice::popActiveRenderTarget() - stack is empty!" ); // Restore the last item on the stack and pop. setActiveRenderTarget( mRTStack.last() ); mRTStack.pop_back(); } void GFXDevice::setActiveRenderTarget( GFXTarget *target, bool updateViewport ) { AssertFatal( target, "GFXDevice::setActiveRenderTarget - must specify a render target!" ); if ( target == mCurrentRT ) return; // If we're not dirty then store the // current RT for deactivation later. if ( !mRTDirty ) { // Deactivate the target queued for deactivation if(mRTDeactivate) mRTDeactivate->deactivate(); mRTDeactivate = mCurrentRT; } mRTDirty = true; mCurrentRT = target; // When a target changes we also change the viewport // to match it. This causes problems when the viewport // has been modified for clipping to a GUI bounds. // // We should consider removing this and making it the // responsibility of the caller to set a proper viewport // when the target is changed. if ( updateViewport ) { setViewport( RectI( Point2I::Zero, mCurrentRT->getSize() ) ); } } /// Helper class for GFXDevice::describeResources. class DescriptionOutputter { /// Are we writing to a file? bool mWriteToFile; /// File if we are writing to a file FileStream mFile; public: DescriptionOutputter(const char* file) { mWriteToFile = false; // If we've been given what could be a valid file path, open it. if(file && file[0] != '\0') { mWriteToFile = mFile.open(file, Torque::FS::File::Write); // Note that it is safe to retry. If this is hit, we'll just write to the console instead of to the file. AssertFatal(mWriteToFile, avar("DescriptionOutputter::DescriptionOutputter - could not open file %s", file)); } } ~DescriptionOutputter() { // Close the file if(mWriteToFile) mFile.close(); } /// Writes line to the file or to the console, depending on what we want. void write(const char* line) { if(mWriteToFile) mFile.writeLine((const U8*)line); else Con::printf(line); } }; #ifndef TORQUE_SHIPPING void GFXDevice::dumpStates( const char *fileName ) const { DescriptionOutputter output(fileName); output.write("Current state"); if (!mCurrentStateBlock.isNull()) output.write(mCurrentStateBlock->getDesc().describeSelf().c_str()); else output.write("No state!"); output.write("\nAll states:\n"); GFXResource* walk = mResourceListHead; while(walk) { const GFXStateBlock* sb = dynamic_cast(walk); if (sb) { output.write(sb->getDesc().describeSelf().c_str()); } walk = walk->getNextResource(); } } #endif void GFXDevice::listResources(bool unflaggedOnly) { U32 numTextures = 0, numShaders = 0, numRenderToTextureTargs = 0, numWindowTargs = 0; U32 numCubemaps = 0, numVertexBuffers = 0, numPrimitiveBuffers = 0, numFences = 0; U32 numStateBlocks = 0; GFXResource* walk = mResourceListHead; while(walk) { if(unflaggedOnly && walk->isFlagged()) { walk = walk->getNextResource(); continue; } if(dynamic_cast(walk)) numTextures++; else if(dynamic_cast(walk)) numShaders++; else if(dynamic_cast(walk)) numRenderToTextureTargs++; else if(dynamic_cast(walk)) numWindowTargs++; else if(dynamic_cast(walk)) numCubemaps++; else if(dynamic_cast(walk)) numVertexBuffers++; else if(dynamic_cast(walk)) numPrimitiveBuffers++; else if(dynamic_cast(walk)) numFences++; else if (dynamic_cast(walk)) numStateBlocks++; else Con::warnf("Unknown resource: %x", walk); walk = walk->getNextResource(); } const char* flag = unflaggedOnly ? "unflagged" : "allocated"; Con::printf("GFX currently has:"); Con::printf(" %i %s textures", numTextures, flag); Con::printf(" %i %s shaders", numShaders, flag); Con::printf(" %i %s texture targets", numRenderToTextureTargs, flag); Con::printf(" %i %s window targets", numWindowTargs, flag); Con::printf(" %i %s cubemaps", numCubemaps, flag); Con::printf(" %i %s vertex buffers", numVertexBuffers, flag); Con::printf(" %i %s primitive buffers", numPrimitiveBuffers, flag); Con::printf(" %i %s fences", numFences, flag); Con::printf(" %i %s state blocks", numStateBlocks, flag); } void GFXDevice::fillResourceVectors(const char* resNames, bool unflaggedOnly, Vector &textureObjects, Vector &textureTargets, Vector &windowTargets, Vector &vertexBuffers, Vector &primitiveBuffers, Vector &fences, Vector &cubemaps, Vector &shaders, Vector &stateblocks) { bool describeTexture = true, describeTextureTarget = true, describeWindowTarget = true, describeVertexBuffer = true, describePrimitiveBuffer = true, describeFence = true, describeCubemap = true, describeShader = true, describeStateBlock = true; // If we didn't specify a string of names, we'll print all of them if(resNames && resNames[0] != '\0') { // If we did specify a string of names, determine which names describeTexture = (dStrstr(resNames, "GFXTextureObject") != NULL); describeTextureTarget = (dStrstr(resNames, "GFXTextureTarget") != NULL); describeWindowTarget = (dStrstr(resNames, "GFXWindowTarget") != NULL); describeVertexBuffer = (dStrstr(resNames, "GFXVertexBuffer") != NULL); describePrimitiveBuffer = (dStrstr(resNames, "GFXPrimitiveBuffer") != NULL); describeFence = (dStrstr(resNames, "GFXFence") != NULL); describeCubemap = (dStrstr(resNames, "GFXCubemap") != NULL); describeShader = (dStrstr(resNames, "GFXShader") != NULL); describeStateBlock = (dStrstr(resNames, "GFXStateBlock") != NULL); } // Start going through the list GFXResource* walk = mResourceListHead; while(walk) { // If we only want unflagged resources, skip all flagged resources if(unflaggedOnly && walk->isFlagged()) { walk = walk->getNextResource(); continue; } // All of the following checks go through the same logic. // if(describingThisResource) // { // ResourceType* type = dynamic_cast(walk) // if(type) // { // typeVector.push_back(type); // walk = walk->getNextResource(); // continue; // } // } if(describeTexture) { GFXTextureObject* tex = dynamic_cast(walk); { if(tex) { textureObjects.push_back(tex); walk = walk->getNextResource(); continue; } } } if(describeShader) { GFXShader* shd = dynamic_cast(walk); if(shd) { shaders.push_back(shd); walk = walk->getNextResource(); continue; } } if(describeVertexBuffer) { GFXVertexBuffer* buf = dynamic_cast(walk); if(buf) { vertexBuffers.push_back(buf); walk = walk->getNextResource(); continue; } } if(describePrimitiveBuffer) { GFXPrimitiveBuffer* buf = dynamic_cast(walk); if(buf) { primitiveBuffers.push_back(buf); walk = walk->getNextResource(); continue; } } if(describeTextureTarget) { GFXTextureTarget* targ = dynamic_cast(walk); if(targ) { textureTargets.push_back(targ); walk = walk->getNextResource(); continue; } } if(describeWindowTarget) { GFXWindowTarget* targ = dynamic_cast(walk); if(targ) { windowTargets.push_back(targ); walk = walk->getNextResource(); continue; } } if(describeCubemap) { GFXCubemap* cube = dynamic_cast(walk); if(cube) { cubemaps.push_back(cube); walk = walk->getNextResource(); continue; } } if(describeFence) { GFXFence* fence = dynamic_cast(walk); if(fence) { fences.push_back(fence); walk = walk->getNextResource(); continue; } } if (describeStateBlock) { GFXStateBlock* sb = dynamic_cast(walk); if (sb) { stateblocks.push_back(sb); walk = walk->getNextResource(); continue; } } // Wasn't something we were looking for walk = walk->getNextResource(); } } void GFXDevice::describeResources(const char* resNames, const char* filePath, bool unflaggedOnly) { const U32 numResourceTypes = 9; Vector resVectors[numResourceTypes]; const char* reslabels[numResourceTypes] = { "texture", "texture target", "window target", "vertex buffers", "primitive buffers", "fences", "cubemaps", "shaders", "stateblocks" }; // Fill the vectors with the right resources fillResourceVectors(resNames, unflaggedOnly, resVectors[0], resVectors[1], resVectors[2], resVectors[3], resVectors[4], resVectors[5], resVectors[6], resVectors[7], resVectors[8]); // Helper object DescriptionOutputter output(filePath); // Print the info to the file // Note that we check if we have any objects of that type. for (U32 i = 0; i < numResourceTypes; i++) { if (resVectors[i].size()) { // Header String header = String::ToString("--------Dumping GFX %s descriptions...----------", reslabels[i]); output.write(header); // Data for (U32 j = 0; j < resVectors[i].size(); j++) { GFXResource* resource = resVectors[i][j]; String dataline = String::ToString("Addr: %x %s", resource, resource->describeSelf().c_str()); output.write(dataline.c_str()); } // Footer output.write("--------------------Done---------------------"); output.write(""); } } } void GFXDevice::flagCurrentResources() { GFXResource* walk = mResourceListHead; while(walk) { walk->setFlag(); walk = walk->getNextResource(); } } void GFXDevice::clearResourceFlags() { GFXResource* walk = mResourceListHead; while(walk) { walk->clearFlag(); walk = walk->getNextResource(); } } DefineEngineFunction( listGFXResources, void, ( bool unflaggedOnly ), ( false ), "Returns a list of the unflagged GFX resources. See flagCurrentGFXResources for usage details.\n" "@ingroup GFX\n" "@see flagCurrentGFXResources, clearGFXResourceFlags, describeGFXResources" ) { GFX->listResources(unflaggedOnly); } DefineEngineFunction( flagCurrentGFXResources, void, (),, "@brief Flags all currently allocated GFX resources.\n" "Used for resource allocation and leak tracking by flagging " "current resources then dumping a list of unflagged resources " "at some later point in execution.\n" "@ingroup GFX\n" "@see listGFXResources, clearGFXResourceFlags, describeGFXResources" ) { GFX->flagCurrentResources(); } DefineEngineFunction( clearGFXResourceFlags, void, (),, "Clears the flagged state on all allocated GFX resources. " "See flagCurrentGFXResources for usage details.\n" "@ingroup GFX\n" "@see flagCurrentGFXResources, listGFXResources, describeGFXResources" ) { GFX->clearResourceFlags(); } DefineEngineFunction( describeGFXResources, void, ( const char *resourceTypes, const char *filePath, bool unflaggedOnly ), ( false ), "@brief Dumps a description of GFX resources to a file or the console.\n" "@param resourceTypes A space seperated list of resource types or an empty string for all resources.\n" "@param filePath A file to dump the list to or an empty string to write to the console.\n" "@param unflaggedOnly If true only unflagged resources are dumped. See flagCurrentGFXResources.\n" "@note The resource types can be one or more of the following:\n\n" " - texture\n" " - texture target\n" " - window target\n" " - vertex buffers\n" " - primitive buffers\n" " - fences\n" " - cubemaps\n" " - shaders\n" " - stateblocks\n\n" "@ingroup GFX\n" ) { GFX->describeResources( resourceTypes, filePath, unflaggedOnly ); } DefineEngineFunction( describeGFXStateBlocks, void, ( const char *filePath ),, "Dumps a description of all state blocks.\n" "@param filePath A file to dump the state blocks to or an empty string to write to the console.\n" "@ingroup GFX\n" ) { GFX->dumpStates( filePath ); } DefineEngineFunction( getPixelShaderVersion, F32, (),, "Returns the pixel shader version for the active device.\n" "@ingroup GFX\n" ) { return GFX->getPixelShaderVersion(); } DefineEngineFunction( setPixelShaderVersion, void, ( float version ),, "@brief Sets the pixel shader version for the active device.\n" "This can be used to force a lower pixel shader version than is supported by " "the device for testing or performance optimization.\n" "@param version The floating point shader version number.\n" "@note This will only affect shaders/materials created after the call " "and should be used before the game begins.\n" "@see $pref::Video::forcedPixVersion\n" "@ingroup GFX\n" ) { GFX->setPixelShaderVersion( version ); } DefineEngineFunction( getDisplayDeviceInformation, const char*, (),, "Get the string describing the active GFX device.\n" "@ingroup GFX\n" ) { if (!GFXDevice::devicePresent()) return "(no device)"; const GFXAdapter& adapter = GFX->getAdapter(); return adapter.getName(); } DefineEngineFunction( getBestHDRFormat, GFXFormat, (),, "Returns the best texture format for storage of HDR data for the active device.\n" "@ingroup GFX\n" ) { // TODO: Maybe expose GFX::selectSupportedFormat() so that this // specialized method can be moved to script. // Figure out the best HDR format. This is the smallest // format which supports blending and filtering. Vector formats; formats.push_back( GFXFormatR10G10B10A2 ); formats.push_back( GFXFormatR16G16B16A16F ); formats.push_back( GFXFormatR16G16B16A16 ); GFXFormat format = GFX->selectSupportedFormat( &GFXDefaultRenderTargetProfile, formats, true, true, true ); return format; }