diff --git a/D3D11Engine/BaseLineRenderer.cpp b/D3D11Engine/BaseLineRenderer.cpp index c0011dbec..ed243dc18 100644 --- a/D3D11Engine/BaseLineRenderer.cpp +++ b/D3D11Engine/BaseLineRenderer.cpp @@ -1,10 +1,51 @@ #include "pch.h" #include "BaseLineRenderer.h" +#include "Logger.h" BaseLineRenderer::BaseLineRenderer() {} BaseLineRenderer::~BaseLineRenderer() {} +/** Adds a line to the list */ +XRESULT BaseLineRenderer::AddLine( const LineVertex& v1, const LineVertex& v2 ) { + if ( LineCache.size() + 2 > kMaxCachedVertices ) { + if ( !CacheOverflowLogged ) { + LogWarn() << "Debug-line cache full (" << kMaxCachedVertices + << " vertices). Further lines are dropped until it is flushed."; + CacheOverflowLogged = true; + } + return XR_FAILED; + } + + LineCache.push_back( v1 ); + LineCache.push_back( v2 ); + return XR_SUCCESS; +} + +/** Adds a screen-space line to the list */ +XRESULT BaseLineRenderer::AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) { + if ( ScreenSpaceLineCache.size() + 2 > kMaxCachedVertices ) { + if ( !CacheOverflowLogged ) { + LogWarn() << "Screen-space debug-line cache full (" << kMaxCachedVertices + << " vertices). Further lines are dropped until it is flushed."; + CacheOverflowLogged = true; + } + return XR_FAILED; + } + + ScreenSpaceLineCache.push_back( v1 ); + ScreenSpaceLineCache.push_back( v2 ); + return XR_SUCCESS; +} + +/** Clears the line cache */ +XRESULT BaseLineRenderer::ClearCache() { + LineCache.clear(); + ScreenSpaceLineCache.clear(); + CacheOverflowLogged = false; + return XR_SUCCESS; +} + /** Plots a vector of floats */ void BaseLineRenderer::PlotNumbers( const std::vector& values, const XMFLOAT3& location, const XMFLOAT3& direction, float distance, float heightScale, const XMFLOAT4& color ) { for ( unsigned int i = 1; i < values.size(); i++ ) { diff --git a/D3D11Engine/BaseLineRenderer.h b/D3D11Engine/BaseLineRenderer.h index 67771039b..ec2c0b667 100644 --- a/D3D11Engine/BaseLineRenderer.h +++ b/D3D11Engine/BaseLineRenderer.h @@ -32,15 +32,15 @@ class BaseLineRenderer { virtual ~BaseLineRenderer(); /** Adds a line to the list */ - virtual XRESULT AddLine( const LineVertex& v1, const LineVertex& v2 ) = 0; - virtual XRESULT AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) = 0; + virtual XRESULT AddLine( const LineVertex& v1, const LineVertex& v2 ); + virtual XRESULT AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ); /** Flushes the cached lines */ virtual XRESULT Flush() = 0; virtual XRESULT FlushScreenSpace() = 0; /** Clears the line cache */ - virtual XRESULT ClearCache() = 0; + virtual XRESULT ClearCache(); /** Adds a point locator to the renderlist */ void AddPointLocator( const XMFLOAT3& location, float size = 1, const XMFLOAT4& color = XMFLOAT4( 1, 1, 1, 1 ) ); @@ -64,5 +64,16 @@ class BaseLineRenderer { /** Draws a wireframe mesh */ void AddWireframeMesh( const std::vector& vertices, const std::vector& indices, const XMFLOAT4& color = XMFLOAT4( 1, 1, 1, 1 ), const XMFLOAT4X4* world = nullptr ); + +protected: + // Hard cap per list, in VERTICES (two per line). Nothing drains these lists unless a frame actually + // renders the world, so an unbounded push (a stuck editor overlay, a world that never renders) would + // otherwise grow forever. 1M vertices = 32 MB of cache — far past anything the debug overlays emit. + static constexpr size_t kMaxCachedVertices = 1u << 20; + + /** Line cache, shared storage for backends to Flush()/FlushScreenSpace() from */ + std::vector LineCache; + std::vector ScreenSpaceLineCache; + bool CacheOverflowLogged = false; }; diff --git a/D3D11Engine/D3D11Effect.cpp b/D3D11Engine/D3D11Effect.cpp index 7d6ef37cb..e751dd24f 100644 --- a/D3D11Engine/D3D11Effect.cpp +++ b/D3D11Engine/D3D11Effect.cpp @@ -8,6 +8,7 @@ #include "D3D11VShader.h" #include "D3D11GShader.h" #include "D3D11PShader.h" +#include "D3D11PipelineStateCache.h" #include "GSky.h" #include #include "RenderToTextureBuffer.h" @@ -201,12 +202,12 @@ XRESULT D3D11Effect::DrawRain() { e->GetContext()->SOSetTargets( 1, D3D11VertexBuffer::From( RainBufferStreamTo.get() )->GetVertexBuffer().GetAddressOf(), &offset ); // Apply shaders - e->GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( e->GetContext().Get(), nullptr ); particleAdvanceVS->Apply(); streamOutGS->Apply(); // Rendering points only - e->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_POINTLIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( e->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_POINTLIST ); e->SetDefaultStates(); e->UpdateRenderStates(); @@ -236,7 +237,7 @@ XRESULT D3D11Effect::DrawRain() { state.RasterizerState.SetDirty(); // Rendering instances only - e->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); + D3D11PipelineStateCache::SetPrimitiveTopology( e->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); e->UpdateRenderStates(); // Apply particle shaders @@ -283,7 +284,7 @@ XRESULT D3D11Effect::DrawRain() { } // Reset this - e->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( e->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); e->GetContext()->GSSetShader( nullptr, 0, 0 ); return XR_SUCCESS; } @@ -395,7 +396,7 @@ XRESULT D3D11Effect::DrawRain_CS() { state.RasterizerState.SetDirty(); // Rendering instances only - e->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); + D3D11PipelineStateCache::SetPrimitiveTopology( e->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); e->UpdateRenderStates(); // Apply particle shaders @@ -441,7 +442,7 @@ XRESULT D3D11Effect::DrawRain_CS() { } // Reset primitive topology - e->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( e->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); return XR_SUCCESS; } diff --git a/D3D11Engine/D3D11ForwardPlusRenderer.cpp b/D3D11Engine/D3D11ForwardPlusRenderer.cpp index 9e1789382..f3f65e375 100644 --- a/D3D11Engine/D3D11ForwardPlusRenderer.cpp +++ b/D3D11Engine/D3D11ForwardPlusRenderer.cpp @@ -12,6 +12,7 @@ #include "GothicAPI.h" #include "GothicGraphicsState.h" #include "ConstantBufferStructs.h" +#include "D3D11PipelineStateCache.h" #include "GSky.h" #include "zCTexture.h" #include "zCMaterial.h" @@ -64,7 +65,7 @@ void D3D11ForwardPlusRenderer::AddGeometryPasses( context->OMSetRenderTargets( 1, &nullRTV, dsv ); // Disable pixel shader for depth-only rendering - context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( context.Get(), nullptr ); engine.SetRenderingStage( D3D11ENGINE_RENDER_STAGE::DES_Z_PRE_PASS ); diff --git a/D3D11Engine/D3D11GraphicsEngine.cpp b/D3D11Engine/D3D11GraphicsEngine.cpp index 2be0c57a2..0f0e0cadd 100644 --- a/D3D11Engine/D3D11GraphicsEngine.cpp +++ b/D3D11Engine/D3D11GraphicsEngine.cpp @@ -10,6 +10,7 @@ #include "D3D11OcclusionQuerry.h" #include "D3D11PShader.h" #include "D3D11PfxRenderer.h" +#include "D3D11PipelineStateCache.h" #include "D3D11PipelineStates.h" #include "D3D11PointLight.h" #include "D3D11ShaderManager.h" @@ -709,6 +710,9 @@ XRESULT D3D11GraphicsEngine::Init() { if ( SUCCEEDED( hr ) ) { FeatureRTArrayIndexFromAnyShader = options3.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer; Engine::GAPI->GetRendererState().RendererSettings.DebugSettings.FeatureSet.UseLayeredRendering = FeatureRTArrayIndexFromAnyShader; + LogInfo() << "D3D11_FEATURE_D3D11_OPTIONS3: VPAndRTArrayIndexFromAnyShaderFeedingRasterizer = " << (FeatureRTArrayIndexFromAnyShader ? "Supported" : "Unsupported"); + } else { + LogInfo() << "D3D11_FEATURE_D3D11_OPTIONS3: CheckFeatureSupport failed, assuming Unsupported"; } LogInfo() << "Creating ShaderManager"; @@ -753,13 +757,6 @@ XRESULT D3D11GraphicsEngine::Init() { SetDebugName( TempMorphedMeshBigVertexBuffer->GetShaderResourceView().Get(), "TempVertexBuffer->ShaderResourceView" ); SetDebugName( TempMorphedMeshBigVertexBuffer->GetVertexBuffer().Get(), "TempVertexBuffer->VertexBuffer" ); - TempHUDVertexBuffer = std::make_unique(); - TempHUDVertexBuffer->Init( - nullptr, HUD_BUFFER_SIZE, D3D11VertexBuffer::B_VERTEXBUFFER, - D3D11VertexBuffer::U_DYNAMIC, D3D11VertexBuffer::CA_WRITE ); - SetDebugName( TempHUDVertexBuffer->GetShaderResourceView().Get(), "TempVertexBuffer->ShaderResourceView" ); - SetDebugName( TempHUDVertexBuffer->GetVertexBuffer().Get(), "TempVertexBuffer->VertexBuffer" ); - DynamicInstancingBuffer = std::make_unique(); DynamicInstancingBuffer->Init( nullptr, INSTANCING_BUFFER_SIZE, D3D11VertexBuffer::B_VERTEXBUFFER, @@ -1384,7 +1381,7 @@ void D3D11GraphicsEngine::BeginFrameTransientBufferPools() { } for ( FrameInstancingBufferPool* pool : { &m_MainVobInstancingPool, &m_ShadowVobInstancingPool, - &m_MainNodeAttachmentInstancingPool, &m_ShadowNodeAttachmentInstancingPool } ) { + &m_MainNodeAttachmentInstancingPool, &m_ShadowNodeAttachmentInstancingPool, &m_UIVertexPool } ) { pool->FrameIndex = (pool->FrameIndex + 1) % kTransientPoolFrameCount; auto& slot = pool->Slots[pool->FrameIndex]; WaitForTransientPoolFence( slot.Fence, slot.FencePending ); @@ -1412,7 +1409,7 @@ void D3D11GraphicsEngine::EndFrameTransientBufferPools() { } for ( FrameInstancingBufferPool* pool : { &m_MainVobInstancingPool, &m_ShadowVobInstancingPool, - &m_MainNodeAttachmentInstancingPool, &m_ShadowNodeAttachmentInstancingPool } ) { + &m_MainNodeAttachmentInstancingPool, &m_ShadowNodeAttachmentInstancingPool, &m_UIVertexPool } ) { auto& slot = pool->Slots[pool->FrameIndex]; if ( !slot.Buffer ) { continue; @@ -2201,7 +2198,7 @@ XRESULT D3D11GraphicsEngine::DrawScreenFade( void* c ) { ActivePS->UpdateBuffer("AlphaBlendInfo", &colorBuffer, sizeof(colorBuffer)); UpdateRenderStates(); - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); GetContext()->Draw( 12, 0 ); } @@ -2297,12 +2294,29 @@ XRESULT D3D11GraphicsEngine::DrawVertexArray( ExVertexStruct* vertices, SetupVS_ExMeshDrawCall(); - EnsureTempVertexBufferSize( TempHUDVertexBuffer, stride * numVertices ); - TempHUDVertexBuffer->UpdateBuffer( vertices, stride * numVertices ); + // Fixed-function 2D/UI quads (zCView::Blit via MyDirect3DDevice7::DrawPrimitive, glyph runs, + // the Bink YUV quad, ...) land here once per draw, often many times a frame at small vertex + // counts. A per-call WRITE_DISCARD forces the driver to rename the buffer's backing allocation + // every time; sub-allocating from the fenced instancing-style ring instead lets every map after + // the first (already fence-waited in BeginFrameTransientBufferPools) use WRITE_NO_OVERWRITE, + // same as the VOB/node-attachment instancing pools. + const unsigned int neededBytes = stride * numVertices; + FrameInstancingAllocation uiAlloc = AcquireFrameInstancingAllocation( m_UIVertexPool, neededBytes, "UIVertexRing" ); + if ( !uiAlloc.Buffer ) { + return XR_FAILED; + } - UINT offset = 0; + void* mappedData; + UINT mappedSize; + if ( XR_SUCCESS != uiAlloc.Buffer->Map( D3D11VertexBuffer::M_WRITE_NO_OVERWRITE, &mappedData, &mappedSize ) ) { + return XR_FAILED; + } + memcpy( static_cast(mappedData) + uiAlloc.OffsetInBytes, vertices, neededBytes ); + uiAlloc.Buffer->Unmap(); + + UINT offset = uiAlloc.OffsetInBytes; UINT uStride = stride; - GetContext()->IASetVertexBuffers( 0, 1, TempHUDVertexBuffer->GetVertexBuffer().GetAddressOf(), &uStride, &offset ); + GetContext()->IASetVertexBuffers( 0, 1, uiAlloc.Buffer->GetVertexBuffer().GetAddressOf(), &uStride, &offset ); // Draw the mesh GetContext()->Draw( numVertices, startVertex ); @@ -2460,7 +2474,7 @@ XRESULT D3D11GraphicsEngine::DrawSkeletalVertexNormals( SkeletalVobInfo* vi, SetupVS_ExMeshDrawCall(); SetupVS_ExConstantBuffer(); - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); VS_ExConstantBuffer_PerInstanceSkeletal cb2; cb2.World = world; @@ -2536,7 +2550,7 @@ XRESULT D3D11GraphicsEngine::DrawSkeletalMesh( SkeletalVobInfo* vi, SetupVS_ExMeshDrawCall(); SetupVS_ExConstantBuffer(); - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); VS_ExConstantBuffer_PerInstanceSkeletal cb2; cb2.World = world; @@ -2618,7 +2632,7 @@ XRESULT D3D11GraphicsEngine::DrawSkeletalMesh( SkeletalVobInfo* vi, ActivePS->Apply(); } else if ( RenderingStage == DES_SHADOWMAP ) { // Unbind PixelShader in this case - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; } else { // It is only to indicate that we want pixel shader(to populate gbuffer) @@ -2673,7 +2687,7 @@ XRESULT D3D11GraphicsEngine::DrawSkeletalMesh_Layered( SkeletalVobInfo* vi, SetupVS_ExMeshDrawCall(); SetupVS_ExConstantBuffer(); - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); VS_ExConstantBuffer_PerInstanceSkeletal cb2; cb2.World = world; @@ -2719,7 +2733,7 @@ XRESULT D3D11GraphicsEngine::DrawSkeletalMesh_Layered( SkeletalVobInfo* vi, ActivePS->Apply(); } else if ( RenderingStage == DES_SHADOWMAP ) { // Unbind PixelShader in this case - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; } else { // It is only to indicate that we want pixel shader(to populate gbuffer) @@ -2997,7 +3011,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( SetupVS_ExMeshDrawCall(); SetupVS_ExConstantBuffer(); - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); auto perInstanceCb = ActiveVS->GetInputIndex("Matrices_PerInstances"); auto boneRangeCb = ActiveVS->GetInputIndex( "BoneTransformRange" ); @@ -3037,7 +3051,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( } else if ( RenderingStage == DES_SHADOWMAP) { // Unbind PixelShader in this case if (ActivePS) { - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; } wantShader = false; @@ -3047,7 +3061,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( if ( isZPrepass ) { // Unbind PS for z-prepass, we need to try to ignore any textures that require alpha(testing) // as this otherwise slows down prepass too much. - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; wantShader = true; } @@ -3081,7 +3095,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( } else if (lastTex != nullptr) { Context->PSSetShaderResources( 0, 1, s_nullSRVs ); lastTex = nullptr; - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; } return true; @@ -3700,7 +3714,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( BindActivePixelShader(); } - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); // Bind instance buffer to slot 1 (persists across batches) UINT instOffset = nodeAttachmentBufferOffset; @@ -3717,7 +3731,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( ActivePS = ShaderManager->GetPShader( PShaderID::PS_LinDepth ); ActivePS->Apply(); } else { - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); ActivePS = nullptr; } wantShader = false; @@ -3763,7 +3777,7 @@ void D3D11GraphicsEngine::DrawSkeletalMeshVobs( } else if ( lastPs != nullptr ) { ActivePS = nullptr; lastPs = nullptr; - GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( GetContext().Get(), nullptr ); } } else if ( wantShader && batch.Texture && batch.Texture != lastBatchTex ) { if ( !BindTextureNRFX( batch.Material, batch.Texture, isMainOrGhost, true ) ) { @@ -4724,7 +4738,7 @@ void D3D11GraphicsEngine::SetupVS_ExMeshDrawCall() { ActivePS->Apply(); } - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); } void D3D11GraphicsEngine::PreparePerFrameConstantBuffer(VS_ExConstantBuffer_PerFrame& cb) @@ -5298,7 +5312,7 @@ XRESULT D3D11GraphicsEngine::DrawWorldMesh( bool noTextures ) { // Sorted later, against every other blended drawable, by the transparency queue. TransparencyQueue& transparencyQueue = Engine::GAPI->GetTransparencyQueue(); - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); GetContext()->DSSetShader( nullptr, nullptr, 0 ); GetContext()->HSSetShader( nullptr, nullptr, 0 ); @@ -5398,7 +5412,7 @@ XRESULT D3D11GraphicsEngine::DrawWorldMesh( bool noTextures ) { || isZPrepass) { ZoneScopedN( "DrawWorldMesh::DepthPrepass" ); auto _scopeDepthPrepass = RecordGraphicsEvent( GE_NAME( "DrawWorldMesh::DepthPrepass" ) ); - GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( GetContext().Get(), nullptr ); auto isAlphaMesh = []( const auto& mesh ) { zCTexture* texture = mesh.first.Texture; @@ -5649,7 +5663,7 @@ void D3D11GraphicsEngine::DrawWaterSurfaces() { Engine::GAPI->GetRendererState().BlendState.SetDirty(); UpdateRenderStates(); - GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( GetContext().Get(), nullptr ); if ( !FeatureLevel10Compatibility ) { // MDI path: upload all draw args and dispatch in one call @@ -5801,7 +5815,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; - ActiveVS->UpdateBuffer("Matrices_PerInstances", &identityMatrix, 16); + ActiveVS->UpdateBuffer("Matrices_PerInstances", &identityMatrix, sizeof(identityMatrix)); // Update and bind buffer of PS PerObjectState ocb; @@ -5829,6 +5843,37 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( const bool drawAnimatedCasters = (casterMask & SHADOW_CASTER_ANIMATED) != 0; if ( drawWorldCasters && Engine::GAPI->GetRendererState().RendererSettings.DrawWorldMesh ) { + // World-mesh sub-meshes don't own standalone GPU buffers - section->WorldMeshes / + // worldMeshCache only carry an index range (MeshInfo::BaseIndexLocation) into the single + // wrapped world mesh (Engine::GAPI->GetWrappedWorldMesh()), packed as ExVertexStructGPU - + // the same buffer ShadowPass_DrawWorldMesh/CSM draw from. VS_ExCube's plain VS_INPUT can't + // decode that stream, so switch to the packed-decoding cube variant and bind the wrapped + // mesh once for this block; VOBs afterward are unpacked ExVertexStruct and need VS_ExCube + // back. FullStaticMesh (the FastShadows branch below) is the one exception - it's built as + // plain ExVertexStruct with its own buffer, so it keeps using whatever VS is already active. + // + // First pass: always draw the full (non-welded) index range - GetShadowAwareIndexCount(mesh, + // true) / mesh->BaseIndexLocation, as if every material were alpha-tested - rather than + // picking the shadow-welded range per material. Simpler, and correctness comes first; the + // welded/reduced range can come back once this is confirmed working. + bool usedPackedWorldMeshVS = false; + auto ensurePackedWorldMeshVS = [&]() { + if ( usedPackedWorldMeshVS ) return; + usedPackedWorldMeshVS = true; + SetActiveVertexShader( VShaderID::VS_ExPackedCube ); + SetupVS_ExMeshDrawCall(); + SetupVS_ExConstantBuffer(); + ActiveVS->UpdateBuffer( "Matrices_PerInstances", &identityMatrix, sizeof( identityMatrix ) ); + BindWrappedWorldMeshPacked( Engine::GAPI->GetWrappedWorldMesh() ); + }; + auto drawFromWrappedMesh = [&]( MeshInfo* mesh ) { + ensurePackedWorldMeshVS(); + const unsigned int count = GetShadowAwareIndexCount( mesh, true ); + if ( !count ) return; + Context->DrawIndexed( count, mesh->BaseIndexLocation, 0 ); + Engine::GAPI->GetRendererState().RendererInfo.FrameDrawnTriangles += count / 3; + }; + // Only use cache if we haven't already collected the vobs // TODO: Collect vobs in a different way than using the drawn sections! // The current solution won't use the cache at all when there are @@ -5860,7 +5905,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( if ( !linearDepth ) // Only unbind when not rendering linear depth { // Unbind PS - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } else { if ( lastTex != WhiteTexture.get() ) { WhiteTexture->BindToPixelShader( 0 ); @@ -5875,13 +5920,10 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( } } - // Draw from wrapped mesh - MeshInfo* mesh = meshInfoByKey->second; - DrawVertexBufferIndexed( mesh->GetMeshVertexBuffer(), - GetShadowAwareIndexBuffer( mesh, isAlpha ), - GetShadowAwareIndexCount( mesh, isAlpha ) ); + drawFromWrappedMesh( meshInfoByKey->second ); } } else { + auto _ = RecordGraphicsEvent( GE_NAME( "DrawWorldAround::WorldMesh" ) ); Frustum f; f.BuildCubemapFace( position, range, 0 ); std::vector sections = {}; @@ -5901,6 +5943,10 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( continue; } + if ( !Engine::GAPI->IsWorldMeshVisibleInFrustum( meshInfoByKey->second, f ) ) { + continue; + } + bool isAlpha = false; // Bind texture if ( meshInfoByKey->first.Material && meshInfoByKey->first.Material->GetTexture() ) { @@ -5922,7 +5968,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( // depth { // Unbind PS - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } else { if ( lastTex != WhiteTexture.get() ) { WhiteTexture->BindToPixelShader( 0 ); @@ -5937,18 +5983,22 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( } } - // Draw from wrapped mesh - MeshInfo* mesh = meshInfoByKey->second; - DrawVertexBufferIndexed( mesh->GetMeshVertexBuffer(), - GetShadowAwareIndexBuffer( mesh, isAlpha ), - GetShadowAwareIndexCount( mesh, isAlpha ) ); + drawFromWrappedMesh( meshInfoByKey->second ); } } } } + + if ( usedPackedWorldMeshVS ) { + // Restore VS_ExCube (plain unpacked ExVertexStruct) for the VOB/mob draws below. + SetActiveVertexShader( VShaderID::VS_ExCube ); + SetupVS_ExMeshDrawCall(); + SetupVS_ExConstantBuffer(); + } } if ( drawVobCasters && Engine::GAPI->GetRendererState().RendererSettings.DrawVOBs ) { + auto _ = RecordGraphicsEvent( GE_NAME( "DrawWorldAround::Vobs" ) ); // Draw visible vobs here std::list rndVob; // construct new renderedvob list or fake one @@ -5973,6 +6023,10 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( if ( isOutdoor && it->IsIndoorVob != indoor ) { continue; } + + if ( ignoreVob != nullptr && ignoreVob( it->Vob ) ) { + continue; + } rndVob.emplace_back( it ); } } @@ -5984,7 +6038,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( GfxTexture* lastBoundTexture = nullptr; std::list& rl = renderedVobs != nullptr ? *renderedVobs : rndVob; VS_ExConstantBuffer_PerInstance cb; - + for ( auto const& vobInfo : rl ) { // Bind per-instance buffer vobInfo->UpdateVobConstantBuffer( cb ); @@ -6027,6 +6081,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( bool renderNPCs = !noNPCs && drawAnimatedCasters; if ( drawMobCasters && Engine::GAPI->GetRendererState().RendererSettings.DrawMobs ) { + auto _ = RecordGraphicsEvent( GE_NAME( "DrawWorldAround::MOBs" ) ); // Draw visible vobs here std::list rndVob; @@ -6082,6 +6137,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround( if ( drawAnimatedCasters && Engine::GAPI->GetRendererState().RendererSettings.DrawSkeletalMeshes ) { // Draw animated skeletal meshes if wanted if ( renderNPCs ) { + auto _ = RecordGraphicsEvent( GE_NAME( "DrawWorldAround::NPCs" ) ); for ( auto const& skeletalMeshVob : Engine::GAPI->GetAnimatedSkeletalMeshVobs() ) { if ( !skeletalMeshVob->VisualInfo ) { // Seems to happen in Gothic 1 @@ -6214,7 +6270,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround_Layered( if ( !linearDepth ) // Only unbind when not rendering linear depth { // Unbind PS - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } else { if ( lastTex != WhiteTexture.get() ) { WhiteTexture->BindToPixelShader( 0 ); @@ -6247,7 +6303,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround_Layered( if ( Engine::GAPI->GetRendererState().RendererSettings.FastShadows ) { // Draw world mesh if ( section->FullStaticMesh ) - Engine::GAPI->DrawMeshInfo( nullptr, section->FullStaticMesh ); + Engine::GAPI->DrawMeshInfo_Layered( nullptr, section->FullStaticMesh ); } else { for ( auto&& meshInfoByKey = section->WorldMeshes.begin(); meshInfoByKey != section->WorldMeshes.end(); ++meshInfoByKey ) { @@ -6256,6 +6312,10 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround_Layered( continue; } + if ( !Engine::GAPI->IsWorldMeshVisibleInFrustum( meshInfoByKey->second, f ) ) { + continue; + } + bool isAlpha = false; // Bind texture if ( meshInfoByKey->first.Material && meshInfoByKey->first.Material->GetTexture() ) { @@ -6275,7 +6335,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAround_Layered( // depth { // Unbind PS - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } else { if ( lastTex != WhiteTexture.get() ) { WhiteTexture->BindToPixelShader( 0 ); @@ -6486,7 +6546,7 @@ void D3D11GraphicsEngine::ShadowPass_DrawWorldMesh_Indirect( const std::vectorGetRendererState().RendererSettings.FastShadows && !cullingFrustum ) { if ( !linearDepth ) { - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } for ( const WorldMeshSectionInfo* section : visibleSections ) { @@ -6565,7 +6625,7 @@ void D3D11GraphicsEngine::ShadowPass_DrawWorldMesh_Indirect( const std::vectorPSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } // Depth-only opaque geometry needs only Position: feed the slim 12-byte stream + VS_ExDepth. @@ -6707,7 +6767,7 @@ void D3D11GraphicsEngine::ShadowPass_DrawWorldMesh( const std::vectorPSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } // For pure depth output (null PS) the transform only needs Position, so feed the slim @@ -7011,7 +7071,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAroundForWorldShadow( FXMVECTOR p if ( !linearDepth ) // Only unbind when not rendering linear depth { // Unbind PS - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } ConstantBufferSlot windBuffer = INVALID_SHADER_CB_SLOT; @@ -7131,7 +7191,7 @@ void XM_CALLCONV D3D11GraphicsEngine::DrawWorldAroundForWorldShadow( FXMVECTOR p { // Unbind PS if ( currPs != nullptr ) { - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); currPs = nullptr; } } @@ -7477,7 +7537,7 @@ XRESULT D3D11GraphicsEngine::DrawVOBsInstanced() { } if ( isZPrepass ) { - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } if ( renderSettings.DrawVOBs || @@ -7701,7 +7761,7 @@ XRESULT D3D11GraphicsEngine::DrawVOBsInstanced() { if ( isZPrepass ) { // force alpha testing for vobs in prepass. ActivePS = nullptr; - Context->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( Context.Get(), nullptr ); } zCTexture* lastTex = nullptr; @@ -7937,7 +7997,7 @@ XRESULT D3D11GraphicsEngine::DrawVOBsInstanced() { } } - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); GetContext()->DSSetShader( nullptr, nullptr, 0 ); GetContext()->HSSetShader( nullptr, nullptr, 0 ); ActiveHDS = nullptr; @@ -9680,7 +9740,7 @@ void D3D11GraphicsEngine::DrawFrameParticles( ActiveVS->UpdateBuffer("ParticleGSInfo", &gcb, sizeof(gcb)); // Rendering points only - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); UpdateRenderStates(); for ( auto const& textureParticleRenderInfo : pvecAdd ) { @@ -9744,7 +9804,7 @@ void D3D11GraphicsEngine::DrawFrameParticles( DrawVertexBufferInstanced( TempParticlesVertexBuffer.get(), 4, instances.size(), sizeof( ParticleInstanceInfo ) ); } - Context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( Context.Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); state.BlendState.SetDefault(); state.BlendState.SetDirty(); @@ -10032,11 +10092,6 @@ void D3D11GraphicsEngine::DrawString( std::string_view str, float x, float y, co reinterpret_cast(GothicMemoryLocations::zCRndD3D::XD3D_SetRenderState)(zrenderer, 19, 5); // D3DRENDERSTATE_SRCBLEND reinterpret_cast(GothicMemoryLocations::zCRndD3D::XD3D_SetRenderState)(zrenderer, 20, 6); // D3DRENDERSTATE_DESTBLEND - // - // Backup old renderstates, BlendState can be ignored here. - // - auto oldDepthState = Engine::GAPI->GetRendererState().DepthState.Clone(); - Engine::GAPI->GetRendererState().DepthState.DepthWriteEnabled = false; Engine::GAPI->GetRendererState().DepthState.DepthBufferCompareFunc = GothicDepthBufferStateInfo::CF_COMPARISON_ALWAYS; Engine::GAPI->GetRendererState().DepthState.SetDirty(); @@ -10067,7 +10122,7 @@ void D3D11GraphicsEngine::DrawString( std::string_view str, float x, float y, co BindActivePixelShader(); // Set vertex type - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); BindViewportInformation( VShaderID::VS_TransformedEx, 0 ); @@ -10094,11 +10149,6 @@ void D3D11GraphicsEngine::DrawString( std::string_view str, float x, float y, co // DrawVertexBuffer( TempVertexBuffer.get(), vertices.size(), sizeof( ExVertexStruct ) ); - oldDepthState.ApplyTo( Engine::GAPI->GetRendererState().DepthState ); - Engine::GAPI->GetRendererState().DepthState.SetDirty(); - - UpdateRenderStates(); - graphicState.FF_Stages[0].ColorOp = copyColorOp; graphicState.FF_Stages[1].ColorOp = copyColorOp2; graphicState.FF_Stages[0].ColorArg1 = copyColorArg1; diff --git a/D3D11Engine/D3D11GraphicsEngine.h b/D3D11Engine/D3D11GraphicsEngine.h index e991c9e20..a1e8becef 100644 --- a/D3D11Engine/D3D11GraphicsEngine.h +++ b/D3D11Engine/D3D11GraphicsEngine.h @@ -35,7 +35,6 @@ const unsigned int POLYS_BUFFER_SIZE = 1024 * sizeof( ExVertexStruct ); const unsigned int PARTICLES_BUFFER_SIZE = 3072 * sizeof( ParticleInstanceInfo ); const unsigned int MORPHEDMESH_SMALL_BUFFER_SIZE = 3072 * sizeof( ExVertexStruct ); const unsigned int MORPHEDMESH_HIGH_BUFFER_SIZE = 20480 * sizeof( ExVertexStruct ); -const unsigned int HUD_BUFFER_SIZE = 6 * sizeof( ExVertexStruct ); const int NUM_MAX_BONES = 96; const int unsigned INSTANCING_BUFFER_SIZE = sizeof( VobInstanceInfo ) * 2048; @@ -716,6 +715,11 @@ class D3D11GraphicsEngine : public D3D11GraphicsEngineBase { FrameInstancingBufferPool m_ShadowVobInstancingPool; FrameInstancingBufferPool m_MainNodeAttachmentInstancingPool; FrameInstancingBufferPool m_ShadowNodeAttachmentInstancingPool; + // DrawVertexArray's dynamic vertex data (2D UI/FF-pipe quads, glyph runs, Bink YUV quad, ...). + // Was a single D3D11VertexBuffer remapped WRITE_DISCARD on every call (TempHUDVertexBuffer, now + // removed); every DrawPrimitive-driven UI draw forced a fresh driver-side rename. This pool gets + // the same fenced-ring/NO_OVERWRITE treatment as the instancing pools above instead. + FrameInstancingBufferPool m_UIVertexPool; /** Water surface indirect buffer */ std::unique_ptr WaterIndirectBuffer; @@ -761,7 +765,6 @@ class D3D11GraphicsEngine : public D3D11GraphicsEngineBase { std::unique_ptr TempParticlesVertexBuffer; std::unique_ptr TempMorphedMeshSmallVertexBuffer; std::unique_ptr TempMorphedMeshBigVertexBuffer; - std::unique_ptr TempHUDVertexBuffer; /** Cached refresh rate for the current exclusive-fullscreen mode (D3D11-only concept). */ DXGI_RATIONAL CachedRefreshRate; diff --git a/D3D11Engine/D3D11GraphicsEngineBase.cpp b/D3D11Engine/D3D11GraphicsEngineBase.cpp index b0b8b8ae7..f3bd6308d 100644 --- a/D3D11Engine/D3D11GraphicsEngineBase.cpp +++ b/D3D11Engine/D3D11GraphicsEngineBase.cpp @@ -1,6 +1,7 @@ #include "D3D11GraphicsEngineBase.h" #include "D3D11LineRenderer.h" +#include "D3D11PipelineStateCache.h" #include "D3D11PipelineStates.h" #include "D3D11PointLight.h" #include "D3D11PShader.h" @@ -171,7 +172,7 @@ XRESULT D3D11GraphicsEngineBase::DrawVertexArray( ExVertexStruct* vertices, unsi pShader->Apply(); // Set vertex type - GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); // Bind the viewport information to the shader D3D11_VIEWPORT vp; diff --git a/D3D11Engine/D3D11LineRenderer.cpp b/D3D11Engine/D3D11LineRenderer.cpp index a2e786027..61245a4f4 100644 --- a/D3D11Engine/D3D11LineRenderer.cpp +++ b/D3D11Engine/D3D11LineRenderer.cpp @@ -4,7 +4,7 @@ #include "Engine.h" #include "D3D11VertexBuffer.h" #include "GothicAPI.h" -#include "D3D11VertexBuffer.h" +#include "D3D11PipelineStateCache.h" D3D11LineRenderer::D3D11LineRenderer() { LineBuffer = nullptr; @@ -15,28 +15,6 @@ D3D11LineRenderer::~D3D11LineRenderer() { LineBuffer.reset(); } -/** Adds a line to the list */ -XRESULT D3D11LineRenderer::AddLine( const LineVertex& v1, const LineVertex& v2 ) { - if ( LineCache.size() >= 0xFFFFFFFF ) { - return XR_FAILED; - } - - LineCache.push_back( v1 ); - LineCache.push_back( v2 ); - return XR_SUCCESS; -} - -/** Adds a line to the list */ -XRESULT D3D11LineRenderer::AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) { - if ( ScreenSpaceLineCache.size() >= 0xFFFFFFFF ) { - return XR_FAILED; - } - - ScreenSpaceLineCache.push_back( v1 ); - ScreenSpaceLineCache.push_back( v2 ); - return XR_SUCCESS; -} - /** Flushes the cached lines */ XRESULT D3D11LineRenderer::Flush() { D3D11GraphicsEngineBase* engine = reinterpret_cast(Engine::GraphicsEngine); @@ -70,7 +48,7 @@ XRESULT D3D11LineRenderer::Flush() { engine->SetupVS_ExMeshDrawCall(); engine->SetupVS_ExConstantBuffer(); engine->SetupVS_ExPerInstanceConstantBuffer(); - engine->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_LINELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( engine->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_LINELIST ); // Draw the lines UINT offset = 0; @@ -115,7 +93,7 @@ XRESULT D3D11LineRenderer::FlushScreenSpace() { Engine::GAPI->GetRendererState().BlendState.SetDirty(); engine->SetupVS_ExMeshDrawCall(); - engine->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_LINELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( engine->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_LINELIST ); // Draw the lines UINT offset = 0; @@ -129,9 +107,3 @@ XRESULT D3D11LineRenderer::FlushScreenSpace() { ScreenSpaceLineCache.clear(); return XR_SUCCESS; } - -/** Clears the line cache */ -XRESULT D3D11LineRenderer::ClearCache() { - LineCache.clear(); - return XR_SUCCESS; -} diff --git a/D3D11Engine/D3D11LineRenderer.h b/D3D11Engine/D3D11LineRenderer.h index a7fe03c56..df0ee962e 100644 --- a/D3D11Engine/D3D11LineRenderer.h +++ b/D3D11Engine/D3D11LineRenderer.h @@ -9,24 +9,12 @@ class D3D11LineRenderer : D3D11LineRenderer(); ~D3D11LineRenderer() override; - /** Adds a line to the list */ - XRESULT AddLine( const LineVertex& v1, const LineVertex& v2 ) override; - - XRESULT AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) override; - /** Flushes the cached lines */ XRESULT Flush() override; XRESULT FlushScreenSpace() override; - /** Clears the line cache */ - XRESULT ClearCache() override; - private: - /** Line cache */ - std::vector LineCache; - std::vector ScreenSpaceLineCache; - /** Buffer to hold the lines on the GPU */ std::unique_ptr LineBuffer; unsigned int LineBufferSize; // Size in elements the line buffer can hold diff --git a/D3D11Engine/D3D11NVHBAO.cpp b/D3D11Engine/D3D11NVHBAO.cpp index f9871ccb7..02bc6fe33 100644 --- a/D3D11Engine/D3D11NVHBAO.cpp +++ b/D3D11Engine/D3D11NVHBAO.cpp @@ -3,6 +3,7 @@ #include "Engine.h" #include "D3D11GraphicsEngine.h" #include "GFSDK_SSAO.h" +#include "D3D11PipelineStateCache.h" #include "RenderToTextureBuffer.h" #include "GothicAPI.h" @@ -81,6 +82,10 @@ XRESULT D3D11NVHBAO::Render( GFSDK_SSAO_Status status; status = AOContext->RenderAO( engine->GetContext().Get(), Input, Params, Output ); + // The HBAO+ SDK is a precompiled black box that rebinds IA/VS/PS state on the context directly; + // our cache has no visibility into that, so forget what it believed was bound. + D3D11PipelineStateCache::InvalidateAll(); + if ( status != GFSDK_SSAO_OK ) { LogError() << "Failed to render Nvidia HBAO+! Result: " << status; return XR_FAILED; diff --git a/D3D11Engine/D3D11OcclusionQuerry.cpp b/D3D11Engine/D3D11OcclusionQuerry.cpp index e04294232..2f99ea6e1 100644 --- a/D3D11Engine/D3D11OcclusionQuerry.cpp +++ b/D3D11Engine/D3D11OcclusionQuerry.cpp @@ -2,6 +2,7 @@ #include "D3D11OcclusionQuerry.h" #include "Engine.h" #include "D3D11GraphicsEngine.h" +#include "D3D11PipelineStateCache.h" #include "GothicAPI.h" #include "zCBspTree.h" #include "Toolbox.h" @@ -144,7 +145,7 @@ void D3D11OcclusionQuerry::BeginOcclusionPass() { g->SetupVS_ExConstantBuffer(); // Unbind not needed shaders - g->GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( g->GetContext().Get(), nullptr ); g->GetContext()->HSSetShader( nullptr, nullptr, 0 ); g->GetContext()->DSSetSamplers( 0, 0, nullptr ); } diff --git a/D3D11Engine/D3D11PFX_ASSAO.cpp b/D3D11Engine/D3D11PFX_ASSAO.cpp index 8aa165bcc..4bfbd559c 100644 --- a/D3D11Engine/D3D11PFX_ASSAO.cpp +++ b/D3D11Engine/D3D11PFX_ASSAO.cpp @@ -2,6 +2,7 @@ #include "Engine.h" #include "GothicAPI.h" #include "BaseGraphicsEngine.h" +#include "D3D11PipelineStateCache.h" #include #include @@ -55,6 +56,10 @@ void D3D11PFX_ASSAO::Render( m_assaoEffect->Draw( settingsCopy, &inputs ); + + // ASSAODX11 (vendored) rebinds IA/VS/PS directly on the context; our cache has no visibility + // into that, so forget what it believed was bound. + D3D11PipelineStateCache::InvalidateAll(); } void DestroyAssaoEffect( ASSAO_Effect* effect ) diff --git a/D3D11Engine/D3D11PShader.cpp b/D3D11Engine/D3D11PShader.cpp index e77503256..94f17daf9 100644 --- a/D3D11Engine/D3D11PShader.cpp +++ b/D3D11Engine/D3D11PShader.cpp @@ -9,6 +9,7 @@ #include "D3D11ShaderManager.h" #include "D3D11_Helpers.h" #include "StringID.h" +#include "D3D11PipelineStateCache.h" extern bool FeatureLevel10Compatibility; @@ -48,7 +49,8 @@ XRESULT D3D11PShader::LoadShader( const ShaderInfo& si, const std::vector(Engine::GraphicsEngine)->GetContext()->PSSetShader( PixelShader.Get(), nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( + reinterpret_cast(Engine::GraphicsEngine)->GetContext().Get(), PixelShader.Get() ); return XR_SUCCESS; } diff --git a/D3D11Engine/D3D11PfxRenderer.cpp b/D3D11Engine/D3D11PfxRenderer.cpp index ef8d4673d..c6b10cebf 100644 --- a/D3D11Engine/D3D11PfxRenderer.cpp +++ b/D3D11Engine/D3D11PfxRenderer.cpp @@ -22,6 +22,7 @@ #include "D3D11PFX_FSR3.h" #include "D3D11PFX_SAO.h" #include "D3D11PFX_ASSAO.h" +#include "D3D11PipelineStateCache.h" #include "ConstantBufferStructs.h" #include "GothicAPI.h" #include "GSky.h" @@ -159,7 +160,7 @@ XRESULT D3D11PfxRenderer::DrawFullScreenQuad() { D3D11GraphicsEngine* engine = reinterpret_cast(Engine::GraphicsEngine); engine->UpdateRenderStates(); - engine->GetContext()->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + D3D11PipelineStateCache::SetPrimitiveTopology( engine->GetContext().Get(), D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); //Draw the mesh engine->GetContext()->Draw( 3, 0 ); diff --git a/D3D11Engine/D3D11PipelineStateCache.h b/D3D11Engine/D3D11PipelineStateCache.h new file mode 100644 index 000000000..8f784e000 --- /dev/null +++ b/D3D11Engine/D3D11PipelineStateCache.h @@ -0,0 +1,59 @@ +#pragma once +#include + +// Engine-wide redundant-bind filter for the four IA/VS/PS states Gothic's per-item/per-draw loops +// re-issue identically, over and over, in a single frame: input layout, vertex shader, pixel shader, +// and primitive topology. D3D11VShader::Apply / D3D11PShader::Apply and every direct +// IASetPrimitiveTopology / PSSetShader(nullptr,...) call site route through here instead of the raw +// context, and skip the driver call entirely when the value already matches what's bound. +// +// NOT a context wrapper (compare D3D12CmdList in D3D12Engine/D3D12StateCache.h, which legitimately can +// be one because a D3D12 command list has exactly one recorder). D3D11's immediate context is also +// driven directly by code this engine doesn't own the source of -- ASSAODX11.cpp, D3D11SMAA.cpp, +// imgui_impl_dx11.cpp -- which rebind these same four states behind this cache's back. Anything that +// does MUST call InvalidateAll() once it returns, or a later "already bound, skip" here would leave +// the wrong layout/shader/topology bound. See the call sites in D3D11PfxRenderer.cpp (RenderASSAO, +// RenderSMAA) and ImGuiShim.cpp (Draw). +// +// THREADING: none needed. There is exactly one D3D11 immediate context for the whole engine. +class D3D11PipelineStateCache { +public: + /** Forget everything this cache believes is bound. Call after any code path sets IA input layout, + VS, PS, or IA primitive topology directly on the context without going through this class. */ + static void InvalidateAll() { + s_InputLayout = nullptr; + s_VertexShader = nullptr; + s_PixelShader = nullptr; + s_Topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; + } + + static void SetInputLayout( ID3D11DeviceContext1* context, ID3D11InputLayout* layout ) { + if ( s_InputLayout == layout ) return; + s_InputLayout = layout; + context->IASetInputLayout( layout ); + } + + static void SetVertexShader( ID3D11DeviceContext1* context, ID3D11VertexShader* shader ) { + if ( s_VertexShader == shader ) return; + s_VertexShader = shader; + context->VSSetShader( shader, nullptr, 0 ); + } + + static void SetPixelShader( ID3D11DeviceContext1* context, ID3D11PixelShader* shader ) { + if ( s_PixelShader == shader ) return; + s_PixelShader = shader; + context->PSSetShader( shader, nullptr, 0 ); + } + + static void SetPrimitiveTopology( ID3D11DeviceContext1* context, D3D11_PRIMITIVE_TOPOLOGY topology ) { + if ( s_Topology == topology ) return; + s_Topology = topology; + context->IASetPrimitiveTopology( topology ); + } + +private: + static inline ID3D11InputLayout* s_InputLayout = nullptr; + static inline ID3D11VertexShader* s_VertexShader = nullptr; + static inline ID3D11PixelShader* s_PixelShader = nullptr; + static inline D3D11_PRIMITIVE_TOPOLOGY s_Topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; +}; diff --git a/D3D11Engine/D3D11TiledDeferredShading.cpp b/D3D11Engine/D3D11TiledDeferredShading.cpp index a3b6e82f1..e17b1d429 100644 --- a/D3D11Engine/D3D11TiledDeferredShading.cpp +++ b/D3D11Engine/D3D11TiledDeferredShading.cpp @@ -65,7 +65,8 @@ void D3D11TiledDeferredShading::EnsureShadowArray() { desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; desc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; - m_device->CreateTexture2D( &desc, nullptr, m_ShadowCubeArray.ReleaseAndGetAddressOf() ); + HRESULT hr; + LE( m_device->CreateTexture2D( &desc, nullptr, m_ShadowCubeArray.ReleaseAndGetAddressOf() )); SetDebugName( m_ShadowCubeArray.Get(), "TiledDeferred_ShadowCubeArray" ); // SRV for sampling in the tiled shading CS @@ -77,7 +78,7 @@ void D3D11TiledDeferredShading::EnsureShadowArray() { srvDesc.TextureCubeArray.First2DArrayFace = 0; srvDesc.TextureCubeArray.NumCubes = MAX_SHADOW_CUBEMAPS; - m_device->CreateShaderResourceView( m_ShadowCubeArray.Get(), &srvDesc, m_ShadowCubeArraySRV.ReleaseAndGetAddressOf() ); + LE(m_device->CreateShaderResourceView( m_ShadowCubeArray.Get(), &srvDesc, m_ShadowCubeArraySRV.ReleaseAndGetAddressOf() )); SetDebugName( m_ShadowCubeArraySRV.Get(), "TiledDeferred_ShadowCubeArray_SRV" ); // Per-slot DSVs (6 faces each) and RenderToDepthStencilBuffer view wrappers @@ -89,7 +90,7 @@ void D3D11TiledDeferredShading::EnsureShadowArray() { dsvDesc.Texture2DArray.ArraySize = 6; dsvDesc.Texture2DArray.MipSlice = 0; - m_device->CreateDepthStencilView( m_ShadowCubeArray.Get(), &dsvDesc, m_SlotDSVs[slot].ReleaseAndGetAddressOf() ); + LE(m_device->CreateDepthStencilView( m_ShadowCubeArray.Get(), &dsvDesc, m_SlotDSVs[slot].ReleaseAndGetAddressOf() )); // View wrapper for RenderShadowCube() interface (uses GetSizeX() and GetDepthStencilView()) m_SlotViews[slot] = std::make_unique( @@ -139,9 +140,10 @@ void D3D11TiledDeferredShading::EnsureDynShadowArray() { srvDesc.TextureCubeArray.First2DArrayFace = 0; srvDesc.TextureCubeArray.NumCubes = MAX_SHADOW_CUBEMAPS; - m_device->CreateShaderResourceView( m_ShadowDynCubeArray.Get(), &srvDesc, m_ShadowDynCubeArraySRV.ReleaseAndGetAddressOf() ); + HRESULT hr; + LE(m_device->CreateShaderResourceView( m_ShadowDynCubeArray.Get(), &srvDesc, m_ShadowDynCubeArraySRV.ReleaseAndGetAddressOf() )); SetDebugName( m_ShadowDynCubeArraySRV.Get(), "TiledDeferred_ShadowDynCubeArray_SRV" ); - + for ( uint32_t slot = 0; slot < MAX_SHADOW_CUBEMAPS; slot++ ) { D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {}; dsvDesc.Format = DXGI_FORMAT_D16_UNORM; @@ -150,7 +152,7 @@ void D3D11TiledDeferredShading::EnsureDynShadowArray() { dsvDesc.Texture2DArray.ArraySize = 6; dsvDesc.Texture2DArray.MipSlice = 0; - m_device->CreateDepthStencilView( m_ShadowDynCubeArray.Get(), &dsvDesc, m_SlotDynDSVs[slot].ReleaseAndGetAddressOf() ); + LE(m_device->CreateDepthStencilView( m_ShadowDynCubeArray.Get(), &dsvDesc, m_SlotDynDSVs[slot].ReleaseAndGetAddressOf() )); m_SlotDynViews[slot] = std::make_unique( m_ShadowDynCubeArray, m_SlotDynDSVs[slot], nullptr, @@ -190,7 +192,8 @@ void D3D11TiledDeferredShading::EnsureStaticShadowArray() { srvDesc.TextureCubeArray.First2DArrayFace = 0; srvDesc.TextureCubeArray.NumCubes = MAX_STATIC_SHADOW_CUBEMAPS; - m_device->CreateShaderResourceView( m_ShadowStaticCubeArray.Get(), &srvDesc, m_ShadowStaticCubeArraySRV.ReleaseAndGetAddressOf() ); + HRESULT hr; + LE(m_device->CreateShaderResourceView( m_ShadowStaticCubeArray.Get(), &srvDesc, m_ShadowStaticCubeArraySRV.ReleaseAndGetAddressOf() )); SetDebugName( m_ShadowStaticCubeArraySRV.Get(), "TiledDeferred_ShadowStaticCubeArray_SRV" ); for ( uint32_t slot = 0; slot < MAX_STATIC_SHADOW_CUBEMAPS; slot++ ) { @@ -201,7 +204,7 @@ void D3D11TiledDeferredShading::EnsureStaticShadowArray() { dsvDesc.Texture2DArray.ArraySize = 6; dsvDesc.Texture2DArray.MipSlice = 0; - m_device->CreateDepthStencilView( m_ShadowStaticCubeArray.Get(), &dsvDesc, m_StaticSlotDSVs[slot].ReleaseAndGetAddressOf() ); + LE(m_device->CreateDepthStencilView( m_ShadowStaticCubeArray.Get(), &dsvDesc, m_StaticSlotDSVs[slot].ReleaseAndGetAddressOf() )); m_StaticSlotViews[slot] = std::make_unique( m_ShadowStaticCubeArray, m_StaticSlotDSVs[slot], nullptr, @@ -282,7 +285,9 @@ void D3D11TiledDeferredShading::EnsureBuffers( uint32_t numTilesX, uint32_t numT desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; desc.StructureByteStride = sizeof( LightGrid ); - m_device->CreateBuffer( &desc, nullptr, m_LightGrid.ReleaseAndGetAddressOf() ); + HRESULT hr; + + LE(m_device->CreateBuffer( &desc, nullptr, m_LightGrid.ReleaseAndGetAddressOf() )); SetDebugName( m_LightGrid.Get(), "TiledDeferred_LightGrid" ); D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; @@ -290,7 +295,7 @@ void D3D11TiledDeferredShading::EnsureBuffers( uint32_t numTilesX, uint32_t numT srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; srvDesc.Buffer.ElementWidth = totalClusters; - m_device->CreateShaderResourceView( m_LightGrid.Get(), &srvDesc, m_LightGridSRV.ReleaseAndGetAddressOf() ); + LE(m_device->CreateShaderResourceView( m_LightGrid.Get(), &srvDesc, m_LightGridSRV.ReleaseAndGetAddressOf() )); SetDebugName( m_LightGridSRV.Get(), "TiledDeferred_LightGrid_SRV" ); D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; @@ -298,7 +303,7 @@ void D3D11TiledDeferredShading::EnsureBuffers( uint32_t numTilesX, uint32_t numT uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavDesc.Buffer.NumElements = totalClusters; - m_device->CreateUnorderedAccessView( m_LightGrid.Get(), &uavDesc, m_LightGridUAV.ReleaseAndGetAddressOf() ); + LE(m_device->CreateUnorderedAccessView( m_LightGrid.Get(), &uavDesc, m_LightGridUAV.ReleaseAndGetAddressOf() )); SetDebugName( m_LightGridUAV.Get(), "TiledDeferred_LightGrid_UAV" ); } diff --git a/D3D11Engine/D3D11VShader.cpp b/D3D11Engine/D3D11VShader.cpp index 968a8cf14..29a3af0f6 100644 --- a/D3D11Engine/D3D11VShader.cpp +++ b/D3D11Engine/D3D11VShader.cpp @@ -8,6 +8,7 @@ #include "GothicAPI.h" #include "D3D11ShaderManager.h" #include "D3D11_Helpers.h" +#include "D3D11PipelineStateCache.h" extern bool FeatureLevel10Compatibility; @@ -245,8 +246,8 @@ XRESULT D3D11VShader::LoadShader( const ShaderInfo& si, const std::vector(Engine::GraphicsEngine)->GetContext().Get(); - context->IASetInputLayout( InputLayout.Get() ); - context->VSSetShader( VertexShader.Get(), nullptr, 0 ); + D3D11PipelineStateCache::SetInputLayout( context, InputLayout.Get() ); + D3D11PipelineStateCache::SetVertexShader( context, VertexShader.Get() ); return XR_SUCCESS; } diff --git a/D3D11Engine/D3D12Engine/D3D12LineRenderer.cpp b/D3D11Engine/D3D12Engine/D3D12LineRenderer.cpp index 5e4b38157..fd81ad928 100644 --- a/D3D11Engine/D3D12Engine/D3D12LineRenderer.cpp +++ b/D3D11Engine/D3D12Engine/D3D12LineRenderer.cpp @@ -26,38 +26,6 @@ namespace { } -XRESULT D3D12LineRenderer::AddLine( const LineVertex& v1, const LineVertex& v2 ) { - if ( LineCache.size() + 2 > kMaxCachedVertices ) { - if ( !CacheOverflowLogged ) { - LogWarn() << "D3D12: debug-line cache full (" << kMaxCachedVertices - << " vertices). Further lines are dropped until it is flushed."; - CacheOverflowLogged = true; - } - return XR_FAILED; - } - - LineCache.push_back( v1 ); - LineCache.push_back( v2 ); - return XR_SUCCESS; -} - - -XRESULT D3D12LineRenderer::AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) { - if ( ScreenSpaceLineCache.size() + 2 > kMaxCachedVertices ) { - if ( !CacheOverflowLogged ) { - LogWarn() << "D3D12: screen-space debug-line cache full (" << kMaxCachedVertices - << " vertices). Further lines are dropped until it is flushed."; - CacheOverflowLogged = true; - } - return XR_FAILED; - } - - ScreenSpaceLineCache.push_back( v1 ); - ScreenSpaceLineCache.push_back( v2 ); - return XR_SUCCESS; -} - - XRESULT D3D12LineRenderer::Flush() { if ( !LineCache.empty() ) { if ( D3D12GraphicsEngine* engine = ActiveD3D12Engine() ) @@ -80,16 +48,6 @@ XRESULT D3D12LineRenderer::FlushScreenSpace() { } -XRESULT D3D12LineRenderer::ClearCache() { - // D3D11LineRenderer::ClearCache only clears the world-space list; clearing both here is strictly safer - // (this is the only path that can drop screen-space lines without a frame having drawn them). - LineCache.clear(); - ScreenSpaceLineCache.clear(); - CacheOverflowLogged = false; - return XR_SUCCESS; -} - - bool D3D12GraphicsEngine::CreateLineVertexBuffers() { D3D12MA::ALLOCATION_DESC allocDesc = {}; allocDesc.HeapType = DefaultUploadHeapType; diff --git a/D3D11Engine/D3D12Engine/D3D12LineRenderer.h b/D3D11Engine/D3D12Engine/D3D12LineRenderer.h index cc9eeb871..af1676c84 100644 --- a/D3D11Engine/D3D12Engine/D3D12LineRenderer.h +++ b/D3D11Engine/D3D12Engine/D3D12LineRenderer.h @@ -13,25 +13,7 @@ engine's per-frame, persistently-mapped line ring: no per-frame allocation, drop-and-log on overflow. */ class D3D12LineRenderer : public BaseLineRenderer { public: - /** Adds a world-space line to the list. */ - XRESULT AddLine( const LineVertex& v1, const LineVertex& v2 ) override; - /** Adds a pre-transformed (xyzrhw) screen-space line to the list. */ - XRESULT AddLineScreenSpace( const LineVertex& v1, const LineVertex& v2 ) override; - /** Draws + clears the cached lines. */ XRESULT Flush() override; XRESULT FlushScreenSpace() override; - - /** Clears the line caches without drawing. */ - XRESULT ClearCache() override; - -private: - // Hard cap per list, in VERTICES (two per line). Nothing drains these lists unless a frame actually - // renders the world, so an unbounded push (a stuck editor overlay, a world that never renders) would - // otherwise grow forever. 1M vertices = 32 MB of cache — far past anything the debug overlays emit. - static constexpr size_t kMaxCachedVertices = 1u << 20; - - std::vector LineCache; - std::vector ScreenSpaceLineCache; - bool CacheOverflowLogged = false; }; diff --git a/D3D11Engine/D3D7/MyDirect3DDevice7.h b/D3D11Engine/D3D7/MyDirect3DDevice7.h index d7dd718df..2805ba0bd 100644 --- a/D3D11Engine/D3D7/MyDirect3DDevice7.h +++ b/D3D11Engine/D3D7/MyDirect3DDevice7.h @@ -207,6 +207,12 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE SetRenderState( D3DRENDERSTATETYPE State, DWORD Value ) override { DebugWrite( "MyDirect3DDevice7::SetRenderState" ); + // Any renderstate Gothic's fixed-function UI touches (blend/depth/alpha/fog/...) is read lazily by + // FlushFF2DBatch()'s eventual DrawVertexArray call, not by this setter. A batch already accumulated + // under the OLD state must go out before the new state takes effect, or it would silently be redrawn + // with the wrong state. See FlushFF2DBatch's comment for the full rationale. + FlushFF2DBatch(); + GothicRendererState& state = Engine::GAPI->GetRendererState(); // Extract the needed renderstates @@ -296,9 +302,26 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE SetTexture( DWORD dwStage, LPDIRECTDRAWSURFACE7 lplpTexture ) override { DebugWrite( "MyDirect3DDevice7::SetTexture" ); - // Bind the texture - MyDirectDrawSurface7* surface = static_cast(lplpTexture); - if ( surface ) { + // Gothic's 2D UI (zCView::Blit) rebinds its slot-0 texture before every single quad, but long runs of + // quads reuse the exact same surface pointer (e.g. every empty inventory slot shares one border + // texture). Skip the rebind AND the flush when the pointer hasn't changed, so those runs stay in one + // FF2DBatch instead of being cut into one draw call per quad. A real change still flushes first (see + // FlushFF2DBatch) so no batch is submitted under the wrong texture. + if ( dwStage == 0 ) { + if ( lplpTexture && lplpTexture != m_LastFF2DTextureStage0 ) { + FlushFF2DBatch(); + static_cast(lplpTexture)->BindToSlot( dwStage ); + m_LastFF2DTextureStage0 = lplpTexture; + } + // lplpTexture == nullptr: mirrors the original behavior of doing nothing (Gothic never actually + // unbinds slot 0 this way); lplpTexture == m_LastFF2DTextureStage0: already bound, nothing to do. + return S_OK; + } + + // Stages other than 0 aren't part of the UI batching (Gothic's fixed-function UI never uses them); + // keep the original unconditional flush+bind behavior. + FlushFF2DBatch(); + if ( MyDirectDrawSurface7* surface = static_cast(lplpTexture) ) { surface->BindToSlot( dwStage ); } @@ -313,6 +336,10 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE SetTextureStageState( DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value ) override { DebugWrite( "MyDirect3DDevice7::SetTextureStageState" ); + // Same reasoning as SetRenderState: FF_Stages[] feeds FFPipelineConstantBuffer, read only when + // FlushFF2DBatch() finally issues the draw. + FlushFF2DBatch(); + GothicRendererState& state = Engine::GAPI->GetRendererState(); switch ( Type ) { case D3DTSS_COLOROP: @@ -436,6 +463,10 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE SetViewport( LPD3DVIEWPORT7 lpViewport ) override { DebugWrite( "MyDirect3DDevice7::SetViewport" ); + // A pending batch was built against the OLD viewport's BindViewportInformation state; flush before + // switching (inventory slots each set their own item-preview viewport between UI quad draws). + FlushFF2DBatch(); + float scale = std::max( 0.1f, Engine::GAPI->GetRendererState().RendererSettings.GothicUIScale ); ViewportInfo vp; @@ -459,6 +490,11 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE BeginScene() override { DebugWrite( "MyDirect3DDevice7::BeginScene" ); + // Nothing should carry a pending FF2DBatch across a frame boundary, but drop it rather than let a + // stale batch (built against last frame's now-invalid dynamic VB contents) get flushed into this one. + m_FF2DBatch.clear(); + m_LastFF2DTextureStage0 = nullptr; + Engine::GraphicsEngine->OnBeginFrame(); return S_OK; } @@ -475,6 +511,7 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE Clear( DWORD dwCount, LPD3DRECT lpRects, DWORD dwFlags, D3DCOLOR dwColor, D3DVALUE dvZ, DWORD dwStencil ) override { DebugWrite( "MyDirect3DDevice7::Clear" ); + FlushFF2DBatch(); return S_OK; } @@ -495,16 +532,19 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE DrawIndexedPrimitive( D3DPRIMITIVETYPE dptPrimitiveType, DWORD dwVertexTypeDesc, LPVOID lpvVertices, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags ) override { DebugWrite( "MyDirect3DDevice7::DrawIndexedPrimitive" ); + FlushFF2DBatch(); return S_OK; } HRESULT __declspec(nothrow) STDMETHODCALLTYPE DrawIndexedPrimitiveStrided( D3DPRIMITIVETYPE dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags ) override { DebugWrite( "MyDirect3DDevice7::DrawIndexedPrimitiveStrided" ); + FlushFF2DBatch(); return S_OK; } HRESULT __declspec(nothrow) STDMETHODCALLTYPE DrawIndexedPrimitiveVB( D3DPRIMITIVETYPE d3dptPrimitiveType, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer, DWORD dwStartVertex, DWORD dwNumVertices, LPWORD lpwIndices, DWORD dwIndexCount, DWORD dwFlags ) override { DebugWrite( "MyDirect3DDevice7::DrawIndexedPrimitiveVB" ); + FlushFF2DBatch(); return S_OK; } @@ -564,15 +604,20 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { } Engine::GraphicsEngine->SetActivePixelShader( PShaderID::PS_FixedFunctionPipe ); + + // This call's geometry chooses between VS_TransformedEx and its MAX_Z (sky) variant based on the + // CURRENT render stage, above. A batch already accumulated for the other variant must go out first, + // or it would end up drawn with whichever VS this call selects instead of the one it was built for. + const bool isSky = Engine::GAPI->GetRendererState().RendererInfo.RenderStage == STAGE_DRAW_SKY; + if ( !m_FF2DBatch.empty() && isSky != m_FF2DBatchIsSky ) { + FlushFF2DBatch(); + } + m_FF2DBatchIsSky = isSky; + if ( dptPrimitiveType == D3DPT_TRIANGLEFAN ) { - static std::vector vertexList; - vertexList.clear(); - WorldConverter::TriangleFanToList( &exv[0], dwVertexCount, &vertexList ); - - Engine::GraphicsEngine->DrawVertexArray( &vertexList[0], vertexList.size() ); - } else { - if ( dptPrimitiveType == D3DPT_TRIANGLELIST ) - Engine::GraphicsEngine->DrawVertexArray( &exv[0], dwVertexCount ); + WorldConverter::TriangleFanToList( &exv[0], dwVertexCount, &m_FF2DBatch ); + } else if ( dptPrimitiveType == D3DPT_TRIANGLELIST ) { + m_FF2DBatch.insert( m_FF2DBatch.end(), exv.begin(), exv.end() ); } exv.clear(); // static, keep the memory allocated @@ -580,13 +625,29 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { return S_OK; } + /** Submits every 2D screen quad accumulated by DrawPrimitive since the last flush as a single draw call. + Gothic's fixed-function UI (zCView::Blit, oCItemContainer::Draw, ...) issues one DrawPrimitive per + quad and typically re-binds the same texture/blend state for long runs of them (e.g. every empty + inventory slot border) — accumulating those into one CPU-side vertex list and submitting once here + turns N draw calls + N dynamic-VB uploads into 1, without changing what actually gets drawn. Must be + called (see call sites) before anything that would make the accumulated batch render differently than + intended: a texture/state/viewport change, or any non-UI draw path. */ + void FlushFF2DBatch() { + if ( m_FF2DBatch.empty() ) return; + + Engine::GraphicsEngine->DrawVertexArray( m_FF2DBatch.data(), static_cast(m_FF2DBatch.size()) ); + m_FF2DBatch.clear(); + } + HRESULT __declspec(nothrow) STDMETHODCALLTYPE DrawPrimitiveStrided( D3DPRIMITIVETYPE dptPrimitiveType, DWORD dwVertexTypeDesc, LPD3DDRAWPRIMITIVESTRIDEDDATA lpVertexArray, DWORD dwVertexCount, DWORD dwFlags ) override { DebugWrite( "MyDirect3DDevice7::DrawPrimitiveStrided" ); + FlushFF2DBatch(); return S_OK; } HRESULT __declspec(nothrow) STDMETHODCALLTYPE DrawPrimitiveVB( D3DPRIMITIVETYPE d3dptPrimitiveType, LPDIRECT3DVERTEXBUFFER7 lpd3dVertexBuffer, DWORD dwStartVertex, DWORD dwNumVertices, DWORD dwFlags ) override { DebugWrite( "MyDirect3DDevice7::DrawPrimitiveVB" ); + FlushFF2DBatch(); if ( d3dptPrimitiveType < 4 ) { return S_OK; @@ -625,6 +686,8 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { HRESULT __declspec(nothrow) STDMETHODCALLTYPE EndScene() override { DebugWrite( "MyDirect3DDevice7::EndScene" ); + FlushFF2DBatch(); + hook_infunc Engine::GraphicsEngine->OnEndFrame(); @@ -703,4 +766,10 @@ class MyDirect3DDevice7 final : public IDirect3DDevice7 { private: D3DDEVICEDESC7 FakeDeviceDesc; int RefCount; + + // See FlushFF2DBatch(). Kept as members (not the DrawPrimitive-local `static std::vector + // exv` above) since they must survive across separate DrawPrimitive calls, unlike that per-call scratch buffer. + std::vector m_FF2DBatch; + bool m_FF2DBatchIsSky = false; + LPDIRECTDRAWSURFACE7 m_LastFF2DTextureStage0 = nullptr; }; diff --git a/D3D11Engine/EditorLinePrimitive.cpp b/D3D11Engine/EditorLinePrimitive.cpp index 8d91e8332..e3eb14f77 100644 --- a/D3D11Engine/EditorLinePrimitive.cpp +++ b/D3D11Engine/EditorLinePrimitive.cpp @@ -3,6 +3,7 @@ #include "D3D11GraphicsEngineBase.h" #include "Engine.h" #include "D3D11PShader.h" +#include "D3D11PipelineStateCache.h" #include "BaseLineRenderer.h" #include "GothicAPI.h" #include "GfxVertexBuffer.h" @@ -851,7 +852,7 @@ void EditorLinePrimitive::RenderVertexBuffer( const std::unique_ptrGetContext()->IASetVertexBuffers( 0, 1, &nativeVB, &stride, &offset ); engine->GetContext()->IASetIndexBuffer( nullptr, DXGI_FORMAT_UNKNOWN, 0 ); - engine->GetContext()->IASetPrimitiveTopology( Topology ); + D3D11PipelineStateCache::SetPrimitiveTopology( engine->GetContext().Get(), Topology ); engine->GetContext()->Draw( NumVertices, 0 ); } diff --git a/D3D11Engine/GothicAPI.cpp b/D3D11Engine/GothicAPI.cpp index 4bb3ffbd4..d23a88da8 100644 --- a/D3D11Engine/GothicAPI.cpp +++ b/D3D11Engine/GothicAPI.cpp @@ -49,6 +49,7 @@ // TODO: REMOVE THIS! #include "D3D11GraphicsEngine.h" +#include "D3D11PipelineStateCache.h" #include "MeshManager.h" #include "SharedVisualRegistry.h" #include "AsyncVisualExtractor.h" @@ -3037,9 +3038,31 @@ void GothicAPI::DrawSkeletalMeshVob( SkeletalVobInfo* vi, float distance, bool u if ( g->GetRenderingStage() == DES_SHADOWMAP || g->GetRenderingStage() == DES_SHADOWMAP_CUBE ) { + + const bool isCube = g->GetRenderingStage() == DES_SHADOWMAP_CUBE; + g->GetWhiteTexture()->BindToPixelShader( 0 ); + void* lastTex = g->GetWhiteTexture()->GetShaderResourceView().Get(); + for ( auto const& itm : mvi->Meshes ) { // no texture binding for shadowmap + if ( lastTex != itm.first->GetAniTexture() ) { + if ( itm.first->GetAniTexture()->GetCacheState() != zRES_CACHED_IN ) { + continue; + } + if ( itm.first->HasAlphaTest() || itm.first->GetAniTexture()->HasAlphaChannel() ) { + itm.first->GetAniTexture()->GetSurface()->GetEngineTexture()->BindToPixelShader( 0 ); + lastTex = itm.first->GetAniTexture(); + } else if ( isCube ) { + g->GetWhiteTexture()->BindToPixelShader( 0 ); + lastTex = g->GetWhiteTexture()->GetShaderResourceView().Get(); + } else { + lastTex = nullptr; + static ID3D11ShaderResourceView* nullSrv = nullptr; + g->GetContext()->PSSetShaderResources( 0, 1, &nullSrv ); + } + } + // Go through all meshes using that material for ( unsigned int m = 0; m < itm.second.size(); m++ ) { DrawMeshInfo( itm.first, itm.second[m].get() ); @@ -3331,7 +3354,7 @@ void GothicAPI::DrawTransparencyVob( const TransparencyVobInfo& TransVobInfo ) { if ( TransVobInfo.skeletalVob ) { // We need to do Z-prepass first g->UnbindActivePS(); - g->GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( g->GetContext().Get(), nullptr ); DrawSkeletalMeshVob( TransVobInfo.skeletalVob, TransVobInfo.distance ); RendererState.RendererInfo.FrameDrawnVobs--; // Don't calculate prepass as drawn vob @@ -3354,7 +3377,7 @@ void GothicAPI::DrawTransparencyVob( const TransparencyVobInfo& TransVobInfo ) { // We need to do Z-prepass first g->UnbindActivePS(); - g->GetContext()->PSSetShader( nullptr, nullptr, 0 ); + D3D11PipelineStateCache::SetPixelShader( g->GetContext().Get(), nullptr ); for ( auto const& materialMesh : TransVobInfo.normalVob->VisualInfo->Meshes ) { if ( materialMesh.first ) { diff --git a/D3D11Engine/ImGuiShim.cpp b/D3D11Engine/ImGuiShim.cpp index 0d426c39e..672bf5752 100644 --- a/D3D11Engine/ImGuiShim.cpp +++ b/D3D11Engine/ImGuiShim.cpp @@ -1,5 +1,6 @@ #include "ImGuiShim.h" #include "GSky.h" +#include "D3D11PipelineStateCache.h" #include "D3D12Engine/D3D12GraphicsEngine.h" #include #include @@ -300,6 +301,11 @@ void ImGuiShim::RenderLoop() ImGui::Render(); ImGui_ImplDX11_RenderDrawData( ImGui::GetDrawData() ); + // imgui_impl_dx11 binds its own IA/VS/PS state directly on the context (and, though it restores + // the pre-render state on exit today, that's an implementation detail of the vendored backend, not + // a contract) -- don't trust our cache's belief about what's bound across it. + D3D11PipelineStateCache::InvalidateAll(); + CallEndFrameScript(); } diff --git a/D3D11Engine/SMAA/D3D11SMAA.cpp b/D3D11Engine/SMAA/D3D11SMAA.cpp index 0747a2be3..0aa409d02 100644 --- a/D3D11Engine/SMAA/D3D11SMAA.cpp +++ b/D3D11Engine/SMAA/D3D11SMAA.cpp @@ -1,5 +1,6 @@ #include "D3D11SMAA.h" #include "../D3D11ShaderManager.h" +#include "../D3D11PipelineStateCache.h" // Include DirectXTK or your preferred texture loader #include "DDSTextureLoader.h" // Assuming DirectXTK availability @@ -200,6 +201,11 @@ void D3D11SMAA::Render(ID3D11ShaderResourceView* inputSRV, // Cleanup m_context->PSSetShaderResources(0, 3, nullSRVs); + + // This class binds IA/VS/PS directly on the shared context, not through D3D11PipelineStateCache + // (it owns its own m_vsEdge/m_psLumaEdge/... rather than the engine's ShaderManager shaders), so + // the cache has no idea any of this happened. Forget what it believed was bound. + D3D11PipelineStateCache::InvalidateAll(); } void D3D11SMAA::ReleaseResources() { diff --git a/D3D11Engine/ShaderIDs.h b/D3D11Engine/ShaderIDs.h index b873a63b4..f885b824d 100644 --- a/D3D11Engine/ShaderIDs.h +++ b/D3D11Engine/ShaderIDs.h @@ -35,6 +35,7 @@ enum class VShaderID : size_t { VS_DecalInstanced, VS_ExDepth, VS_ExPacked, + VS_ExPackedCube, COUNT }; diff --git a/D3D11Engine/ShaderRegistry.cpp b/D3D11Engine/ShaderRegistry.cpp index 4398572d2..788d0f6e6 100644 --- a/D3D11Engine/ShaderRegistry.cpp +++ b/D3D11Engine/ShaderRegistry.cpp @@ -344,7 +344,7 @@ void ShaderRegistry::Build() { .with_layout( VERTEX_INPUT_LAYOUT_1 ) ); Shaders.push_back( ShaderInfo::make( "VS_ExNodeLayered.hlsl" ) - .with_layout( VERTEX_INPUT_LAYOUT_PACKED_EX ) ); + .with_layout( VERTEX_INPUT_LAYOUT_1 ) ); Shaders.push_back( ShaderInfo::make( "VS_ExSkeletalLayered.hlsl" ) .with_layout( VERTEX_INPUT_LAYOUT_3_VS_ExSkeletal ) @@ -359,8 +359,13 @@ void ShaderRegistry::Build() { Shaders.push_back( ShaderInfo::make( "VS_ExCube.hlsl" ) .with_layout( VERTEX_INPUT_LAYOUT_1 ) ); + // Decodes the wrapped world mesh (Engine::GAPI->GetWrappedWorldMesh(), packed + // ExVertexStructGPU, 36 B) - VS_ExCube's plain VS_INPUT can't read that stream. + Shaders.push_back( ShaderInfo::make( "VS_ExPackedCube.hlsl" ) + .with_layout( VERTEX_INPUT_LAYOUT_PACKED_EX ) ); + Shaders.push_back( ShaderInfo::make( "VS_ExNodeCube.hlsl" ) - .with_layout( VERTEX_INPUT_LAYOUT_PACKED_EX ) ); + .with_layout( VERTEX_INPUT_LAYOUT_1 ) ); Shaders.push_back( ShaderInfo::make( "VS_ExSkeletalCube.hlsl" ) .with_layout( VERTEX_INPUT_LAYOUT_3_VS_ExSkeletal ) diff --git a/D3D11Engine/Shaders/VS_ExPackedCube.hlsl b/D3D11Engine/Shaders/VS_ExPackedCube.hlsl new file mode 100644 index 000000000..6ce224d73 --- /dev/null +++ b/D3D11Engine/Shaders/VS_ExPackedCube.hlsl @@ -0,0 +1,62 @@ +//-------------------------------------------------------------------------------------- +// World-mesh vertex shader for the packed 36-byte vertex (ExVertexStructGPU), feeding +// GS_Cubemap.hlsl for point-light cube shadow rendering. Mirrors VS_ExPacked.hlsl's decode +// of the packed stream, but - like VS_ExCube.hlsl - outputs world-space position/normal +// instead of a final SV_POSITION, since the geometry shader computes the per-face clip +// position from PCR_ViewProj[f]. See D3D11ShadowMap::RenderShadowCube's GS branch. +//-------------------------------------------------------------------------------------- + +#include "Globals_VS_ExConstants.h" +#include "VertexPacking.h" + +cbuffer Matrices_PerFrame : register( b0 ) +{ + VS_ExConstantBuffer_PerFrame frame; +}; + +cbuffer Matrices_PerInstances : register( b1 ) +{ + matrix M_World; +}; + +//-------------------------------------------------------------------------------------- +// Input / Output structures +//-------------------------------------------------------------------------------------- +struct VS_INPUT +{ + float3 vPosition : POSITION; + float2 vNormalOct : NORMAL; // octahedral-encoded (R16G16_SNORM) + float4 vTangent : TANGENT; // R10G10B10A2 - not needed for depth-only shadow rendering + float2 vTex1 : TEXCOORD0; + float2 vTex2 : TEXCOORD1; + float4 vDiffuse : DIFFUSE; +}; + +struct VS_OUTPUT +{ + float2 vTexcoord : TEXCOORD0; + float2 vTexcoord2 : TEXCOORD1; + float4 vDiffuse : TEXCOORD2; + float3 vNormalWS : TEXCOORD3; + float3 vWorldPosition : TEXCOORD4; +}; + +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +VS_OUTPUT VSMain( VS_INPUT Input ) +{ + VS_OUTPUT Output; + + float3 vNormal = DecodeOctNormal( Input.vNormalOct ); + + float3 positionWorld = mul(float4(Input.vPosition, 1), M_World).xyz; + + Output.vTexcoord2 = Input.vTex2; + Output.vTexcoord = Input.vTex1; + Output.vDiffuse = Input.vDiffuse; + Output.vNormalWS = mul(vNormal, (float3x3)M_World); + Output.vWorldPosition = positionWorld; + + return Output; +}