From 73629ada72c31bd9ce426fc09240e60d4ea08bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Fri, 19 Jun 2026 22:22:12 -0700 Subject: [PATCH] Dedup bindings per encoder. (#3772) --- src/bgfx.cpp | 8 ++-- src/bgfx_p.h | 88 +++++++++++++++++++++++++++++++++++++++-- src/renderer_d3d11.cpp | 5 ++- src/renderer_d3d12.cpp | 69 +++++++++++++++++++++----------- src/renderer_gl.cpp | 5 ++- src/renderer_mtl.cpp | 5 ++- src/renderer_vk.cpp | 10 +++-- src/renderer_webgpu.cpp | 5 ++- 8 files changed, 153 insertions(+), 42 deletions(-) diff --git a/src/bgfx.cpp b/src/bgfx.cpp index 93e9606aa..78c078a3b 100644 --- a/src/bgfx.cpp +++ b/src/bgfx.cpp @@ -1555,11 +1555,11 @@ namespace bgfx m_draw.m_occlusionQuery = _occlusionQuery; } + m_draw.m_bindIdx = bindStateIndexCached(); m_frame->m_renderItem[renderItemIdx].draw = m_draw; - m_frame->m_renderItemBind[renderItemIdx] = m_bind; m_draw.clear(_flags); - m_bind.clear(_flags); + clearBind(_flags); if (_flags & BGFX_DISCARD_STATE) { m_uniformBegin = m_uniformEnd; @@ -1610,11 +1610,11 @@ namespace bgfx m_compute.m_uniformIdx = m_uniformIdx; m_compute.m_uniformBegin = m_uniformBegin; m_compute.m_uniformEnd = m_uniformEnd; + m_compute.m_bindIdx = bindStateIndexCached(); m_frame->m_renderItem[renderItemIdx].compute = m_compute; - m_frame->m_renderItemBind[renderItemIdx] = m_bind; m_compute.clear(_flags); - m_bind.clear(_flags); + clearBind(_flags); m_uniformBegin = m_uniformEnd; } diff --git a/src/bgfx_p.h b/src/bgfx_p.h index 4fe7413ce..67e1d796e 100644 --- a/src/bgfx_p.h +++ b/src/bgfx_p.h @@ -1998,6 +1998,7 @@ namespace bgfx m_indirectBuffer = BGFX_INVALID_HANDLE; m_numIndirectBuffer = BGFX_INVALID_HANDLE; m_occlusionQuery = BGFX_INVALID_HANDLE; + m_bindIdx = 0; } bool setStreamBit(uint8_t _stream, VertexBufferHandle _handle) @@ -2030,6 +2031,7 @@ namespace bgfx uint32_t m_numIndirect; uint32_t m_numIndirectIndex; uint32_t m_streamMask; + uint32_t m_bindIdx; uint16_t m_instanceDataStride; uint16_t m_numMatrices; uint16_t m_scissor; @@ -2043,6 +2045,8 @@ namespace bgfx OcclusionQueryHandle m_occlusionQuery; }; + static_assert(sizeof(RenderDraw) == 128, "RenderDraw size changed."); + BX_ALIGN_DECL_CACHE_LINE(struct) RenderCompute { void clear(uint8_t _flags) @@ -2067,6 +2071,7 @@ namespace bgfx m_indirectBuffer = BGFX_INVALID_HANDLE; m_startIndirect = 0; m_numIndirect = UINT32_MAX; + m_bindIdx = 0; } uint32_t m_uniformBegin; @@ -2079,6 +2084,7 @@ namespace bgfx uint32_t m_numZ; uint32_t m_startIndirect; uint32_t m_numIndirect; + uint32_t m_bindIdx; uint16_t m_numMatrices; uint8_t m_submitFlags; uint8_t m_uniformIdx; @@ -2573,7 +2579,7 @@ namespace bgfx m_perfStats.viewStats = m_viewStats; - bx::memSet(&m_renderItemBind[0], 0, sizeof(m_renderItemBind) ); + bx::memSet(&m_renderBind[0], 0, sizeof(m_renderBind) ); } ~Frame() @@ -2626,6 +2632,7 @@ namespace bgfx m_frameCache.reset(); m_numRenderItems = 0; + m_numRenderBinds = 0; m_numBlitItems = 0; m_iboffset = 0; m_vboffset = 0; @@ -2745,7 +2752,7 @@ namespace bgfx uint64_t m_sortKeys[BGFX_CONFIG_MAX_DRAW_CALLS+1]; RenderItemCount m_sortValues[BGFX_CONFIG_MAX_DRAW_CALLS+1]; RenderItem m_renderItem[BGFX_CONFIG_MAX_DRAW_CALLS+1]; - RenderBind m_renderItemBind[BGFX_CONFIG_MAX_DRAW_CALLS + 1]; + RenderBind m_renderBind[BGFX_CONFIG_MAX_DRAW_CALLS + 1]; uint32_t m_blitKeys[BGFX_CONFIG_MAX_BLIT_ITEMS+1]; BlitItem m_blitItem[BGFX_CONFIG_MAX_BLIT_ITEMS+1]; @@ -2756,6 +2763,7 @@ namespace bgfx UniformBuffer** m_uniformBuffer; uint32_t m_numRenderItems; + uint32_t m_numRenderBinds; uint32_t m_numBlitItems; uint32_t m_iboffset; @@ -2884,6 +2892,11 @@ namespace bgfx m_numSubmitted = 0; m_numDropped = 0; + + m_bindHashMap.clear(); + m_bindLlastIdx = 0; + m_bindEmptyIdx = UINT32_MAX; + m_bindDirty = true; } void end(bool _finalize) @@ -2907,6 +2920,64 @@ namespace bgfx } } + uint32_t bindStateIndex() + { + const uint32_t hash = bx::hash(m_bind.m_bind, sizeof(m_bind.m_bind) ); + + BindHashMap::const_iterator it = m_bindHashMap.find(hash); + if (it != m_bindHashMap.end() ) + { + const uint32_t idx = it->second; + + BX_ASSERT(0 == bx::memCmp(&m_frame->m_renderBind[idx], &m_bind, sizeof(m_bind) ) + , "RenderBind hash collision (hash 0x%08x)." + , hash + ); + + return idx; + } + + const uint32_t idx = bx::atomicFetchAndAddsat(&m_frame->m_numRenderBinds, 1, BGFX_CONFIG_MAX_DRAW_CALLS); + m_frame->m_renderBind[idx] = m_bind; + m_bindHashMap.insert(stl::make_pair(hash, idx) ); + + return idx; + } + + uint32_t bindStateIndexCached() + { + if (m_bindDirty) + { + m_bindLlastIdx = bindStateIndex(); + m_bindDirty = false; + } + else + { + BX_ASSERT(0 == bx::memCmp(&m_frame->m_renderBind[m_bindLlastIdx], &m_bind, sizeof(m_bind) ) + , "Stale bind dirty flag: cached index %d does not match current bind." + , m_bindLlastIdx + ); + } + + return m_bindLlastIdx; + } + + void clearBind(uint8_t _flags) + { + m_bind.clear(_flags); + + if (0 != (_flags & BGFX_DISCARD_BINDINGS) ) + { + if (UINT32_MAX == m_bindEmptyIdx) + { + m_bindEmptyIdx = bindStateIndex(); + } + + m_bindLlastIdx = m_bindEmptyIdx; + m_bindDirty = false; + } + } + void setMarker(const bx::StringView& _name) { UniformBuffer::update(&m_frame->m_uniformBuffer[m_uniformIdx]); @@ -3144,6 +3215,7 @@ namespace bgfx void setTexture(uint8_t _stage, UniformHandle _sampler, TextureHandle _handle, uint32_t _flags) { + m_bindDirty = true; Binding& bind = m_bind.m_bind[_stage]; bind.setTexture( _handle @@ -3161,6 +3233,7 @@ namespace bgfx void setTexture(uint8_t _stage, UniformHandle _sampler, TextureHandle _handle, uint16_t _firstLayer, uint16_t _numLayers, uint8_t _firstMip, uint8_t _numMips, uint32_t _flags) { + m_bindDirty = true; Binding& bind = m_bind.m_bind[_stage]; bind.setTexture( _handle @@ -3182,18 +3255,21 @@ namespace bgfx void setBuffer(uint8_t _stage, IndexBufferHandle _handle, Access::Enum _access) { + m_bindDirty = true; Binding& bind = m_bind.m_bind[_stage]; bind.setIndexBuffer(_handle, _access); } void setBuffer(uint8_t _stage, VertexBufferHandle _handle, Access::Enum _access) { + m_bindDirty = true; Binding& bind = m_bind.m_bind[_stage]; bind.setBuffer(_handle, _access); } void setImage(uint8_t _stage, TextureHandle _handle, uint8_t _mip, Access::Enum _access, TextureFormat::Enum _format) { + m_bindDirty = true; Binding& bind = m_bind.m_bind[_stage]; bind.setImage(_handle, _mip, _access, _format); } @@ -3208,7 +3284,7 @@ namespace bgfx m_discard = false; m_draw.clear(_flags); m_compute.clear(_flags); - m_bind.clear(_flags); + clearBind(_flags); if (_flags & BGFX_DISCARD_STATE) { @@ -3269,6 +3345,12 @@ namespace bgfx HandleSet m_uniformSet; HandleSet m_occlusionQuerySet; + typedef stl::unordered_map BindHashMap; + BindHashMap m_bindHashMap; + uint32_t m_bindLlastIdx; + uint32_t m_bindEmptyIdx; + bool m_bindDirty; + int64_t m_cpuTimeBegin; int64_t m_cpuTimeEnd; }; diff --git a/src/renderer_d3d11.cpp b/src/renderer_d3d11.cpp index c8edc3194..dcb2695b5 100644 --- a/src/renderer_d3d11.cpp +++ b/src/renderer_d3d11.cpp @@ -5848,7 +5848,7 @@ namespace bgfx { namespace d3d11 const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const RenderBind& renderBind = _render->m_renderBind[isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx]; ++item; if (viewChanged) @@ -6759,10 +6759,11 @@ namespace bgfx { namespace d3d11 ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds: %4d / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > maxGpuElapsed ? '>' : '<' , maxGpuElapsed diff --git a/src/renderer_d3d12.cpp b/src/renderer_d3d12.cpp index 4de3c50cb..1665514c3 100644 --- a/src/renderer_d3d12.cpp +++ b/src/renderer_d3d12.cpp @@ -7011,6 +7011,10 @@ namespace bgfx { namespace d3d12 uint16_t m_samplerStateIdx; }; + static constexpr uint8_t kBindStateNotBuilt = 0; + static constexpr uint8_t kBindStateValid = 1; + static constexpr uint8_t kBindStateEmpty = 2; + void RendererContextD3D12::submitBlit(BlitState& _bs, uint16_t _view) { TextureHandle currentSrc = { kInvalidHandle }; @@ -7191,7 +7195,7 @@ namespace bgfx { namespace d3d12 uint16_t currentSamplerStateIdx = kInvalidHandle; ProgramHandle currentProgram = BGFX_INVALID_HANDLE; - uint32_t currentBindHash = 0; + uint32_t currentBindIdx = UINT32_MAX; bool hasPredefined = false; bool commandListChanged = false; ID3D12PipelineState* currentPso = NULL; @@ -7255,7 +7259,11 @@ namespace bgfx { namespace d3d12 D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = UINT64_C(0); - StateCacheLru bindLru; + const uint32_t numRenderBinds = _render->m_numRenderBinds; + Bind* bindCache = (Bind* )BX_STACK_ALLOC(bx::max(numRenderBinds, 1)*sizeof(Bind) ); + uint8_t* bindState = (uint8_t*)BX_STACK_ALLOC(bx::max(numRenderBinds, 1) ); + bx::memSet(bindState, kBindStateNotBuilt, numRenderBinds); + uint32_t bindCacheCount = 0; if (NULL != m_msaaRt) { @@ -7302,7 +7310,8 @@ namespace bgfx { namespace d3d12 const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const uint32_t bindIdx = isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx; + const RenderBind& renderBind = _render->m_renderBind[bindIdx]; ++item; if (viewChanged) @@ -7408,16 +7417,19 @@ namespace bgfx { namespace d3d12 { currentPso = pso; m_commandList->SetPipelineState(pso); - currentBindHash = 0; + currentBindIdx = UINT32_MAX; } - uint32_t bindHash = bx::hash(renderBind.m_bind, sizeof(renderBind.m_bind) ); - if (currentBindHash != bindHash) + if (currentBindIdx != bindIdx) { - currentBindHash = bindHash; + currentBindIdx = bindIdx; - Bind* bindCached = bindLru.find(bindHash); - if (NULL == bindCached) + Bind* bindCached = kBindStateValid == bindState[bindIdx] + ? &bindCache[bindIdx] + : NULL + ; + + if (kBindStateNotBuilt == bindState[bindIdx]) { uint32_t numSet = 0; D3D12_GPU_DESCRIPTOR_HANDLE srvHandle[BGFX_MAX_COMPUTE_BINDINGS] = {}; @@ -7498,10 +7510,16 @@ namespace bgfx { namespace d3d12 if (0 != numSet) { - Bind bind; + Bind& bind = bindCache[bindIdx]; bind.m_srvHandle = srvHandle[0]; bind.m_samplerStateIdx = getSamplerState(samplerFlags, maxComputeBindings, _render->m_colorPalette); - bindCached = bindLru.add(bindHash, bind, 0); + bindState[bindIdx] = kBindStateValid; + ++bindCacheCount; + bindCached = &bind; + } + else + { + bindState[bindIdx] = kBindStateEmpty; } } } @@ -7640,7 +7658,7 @@ namespace bgfx { namespace d3d12 m_commandList->SetDescriptorHeaps(BX_COUNTOF(heaps), heaps); currentPso = NULL; - currentBindHash = 0; + currentBindIdx = UINT32_MAX; currentSamplerStateIdx = kInvalidHandle; currentProgram = BGFX_INVALID_HANDLE; currentState.clear(); @@ -7709,9 +7727,7 @@ namespace bgfx { namespace d3d12 , uint8_t(draw.m_instanceDataStride/16) ); - const uint32_t bindHash = bx::hash(renderBind.m_bind, sizeof(renderBind.m_bind) ); - - if (currentBindHash != bindHash + if (currentBindIdx != bindIdx || 0 != changedStencil || (hasFactor && blendFactor != draw.m_rgba) || (0 != (BGFX_STATE_PT_MASK & changedFlags) @@ -7723,12 +7739,12 @@ namespace bgfx { namespace d3d12 m_batch.flush(m_commandList); } - if (currentBindHash != bindHash) + if (currentBindIdx != bindIdx) { - currentBindHash = bindHash; + currentBindIdx = bindIdx; - Bind* bindCached = bindLru.find(bindHash); - if (NULL == bindCached) + Bind* bindCached = kBindStateValid == bindState[bindIdx] ? &bindCache[bindIdx] : NULL; + if (kBindStateNotBuilt == bindState[bindIdx]) { uint32_t numSet = 0; D3D12_GPU_DESCRIPTOR_HANDLE srvHandle[BGFX_CONFIG_MAX_TEXTURE_SAMPLERS] = {}; @@ -7812,10 +7828,16 @@ namespace bgfx { namespace d3d12 if (0 != numSet) { - Bind bind; + Bind& bind = bindCache[bindIdx]; bind.m_srvHandle = srvHandle[0]; bind.m_samplerStateIdx = getSamplerState(samplerFlags, BGFX_CONFIG_MAX_TEXTURE_SAMPLERS, _render->m_colorPalette); - bindCached = bindLru.add(bindHash, bind, 0); + bindState[bindIdx] = kBindStateValid; + ++bindCacheCount; + bindCached = &bind; + } + else + { + bindState[bindIdx] = kBindStateEmpty; } } @@ -8156,10 +8178,11 @@ namespace bgfx { namespace d3d12 ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds: %4d / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > maxGpuElapsed ? '>' : '<' , maxGpuElapsed @@ -8205,7 +8228,7 @@ namespace bgfx { namespace d3d12 tvm.printf(10, pos++, 0x8b, " %6d | %6d | %6d | %6d " , m_pipelineStateCache.getCount() , m_samplerAllocator.getCount() - , bindLru.getCount() + , bindCacheCount , m_cmd.m_control.getNumUsed() ); pos++; diff --git a/src/renderer_gl.cpp b/src/renderer_gl.cpp index 87ed0a0c1..f1ff70a0c 100644 --- a/src/renderer_gl.cpp +++ b/src/renderer_gl.cpp @@ -7809,7 +7809,7 @@ namespace bgfx { namespace gl const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const RenderBind& renderBind = _render->m_renderBind[isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx]; ++item; if (viewChanged) @@ -8888,10 +8888,11 @@ namespace bgfx { namespace gl ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds: %4d / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > elapsedGpuMs ? '>' : '<' , maxGpuElapsed diff --git a/src/renderer_mtl.cpp b/src/renderer_mtl.cpp index 1bb634c6f..2bb32d48b 100644 --- a/src/renderer_mtl.cpp +++ b/src/renderer_mtl.cpp @@ -4994,7 +4994,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const RenderBind& renderBind = _render->m_renderBind[isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx]; ++item; if (viewChanged @@ -6013,10 +6013,11 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %4d (draw %4d, compute %4d) / CPU %3.4f [ms] %c GPU %3.4f [ms] (latency %d)" + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds: %4d / CPU %3.4f [ms] %c GPU %3.4f [ms] (latency %d)" , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > maxGpuElapsed ? '>' : '<' , maxGpuElapsed diff --git a/src/renderer_vk.cpp b/src/renderer_vk.cpp index 3b9129873..d3ed35f06 100644 --- a/src/renderer_vk.cpp +++ b/src/renderer_vk.cpp @@ -9358,7 +9358,8 @@ VK_DESTROY const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const uint32_t bindIdx = isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx; + const RenderBind& renderBind = _render->m_renderBind[bindIdx]; ++item; if (viewChanged) @@ -9682,7 +9683,7 @@ VK_DESTROY bx::HashMurmur2A hash; hash.begin(); hash.add(program.m_descriptorSetLayout); - hash.add(renderBind.m_bind, sizeof(renderBind.m_bind) ); + hash.add(bindIdx); hash.add(sbo.buffer); hash.add(vsSize); hash.add(0); @@ -9977,7 +9978,7 @@ VK_DESTROY bx::HashMurmur2A hash; hash.begin(); hash.add(program.m_descriptorSetLayout); - hash.add(renderBind.m_bind, sizeof(renderBind.m_bind) ); + hash.add(bindIdx); hash.add(sbo.buffer); hash.add(vsSize); hash.add(fsSize); @@ -10351,10 +10352,11 @@ VK_DESTROY ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds %d / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > maxGpuElapsed ? '>' : '<' , maxGpuElapsed diff --git a/src/renderer_webgpu.cpp b/src/renderer_webgpu.cpp index 178b9a522..4e0f319f6 100644 --- a/src/renderer_webgpu.cpp +++ b/src/renderer_webgpu.cpp @@ -5609,7 +5609,7 @@ m_resolution.formatColor = TextureFormat::BGRA8; const uint32_t itemIdx = _render->m_sortValues[item]; const RenderItem& renderItem = _render->m_renderItem[itemIdx]; - const RenderBind& renderBind = _render->m_renderItemBind[itemIdx]; + const RenderBind& renderBind = _render->m_renderBind[isCompute ? renderItem.compute.m_bindIdx : renderItem.draw.m_bindIdx]; ++item; if (viewChanged) @@ -6469,10 +6469,11 @@ m_resolution.formatColor = TextureFormat::BGRA8; ); double elapsedCpuMs = double(frameTime)*toMs; - tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " + tvm.printf(10, pos++, 0x8b, " Submitted: %5d (draw %5d, compute %4d) / Binds: %4d / CPU %7.4f [ms] %c GPU %7.4f [ms] (latency %d) " , _render->m_numRenderItems , statsKeyType[0] , statsKeyType[1] + , _render->m_numRenderBinds , elapsedCpuMs , elapsedCpuMs > maxGpuElapsed ? '>' : '<' , maxGpuElapsed