From 6d050e68ea8372958a9b7d725a6d3b530e22fe68 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:59:11 +0200 Subject: [PATCH] Latte+Vulkan: Optimize by using incremental state update checking --- src/Cafe/HW/Latte/Core/FetchShader.cpp | 1 + src/Cafe/HW/Latte/Core/FetchShader.h | 1 + src/Cafe/HW/Latte/Core/Latte.h | 9 +- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 228 ++++++++++++-- .../HW/Latte/Core/LatteCommandProcessor.cpp | 82 +++-- .../LatteDecompiler.cpp | 4 +- .../LegacyShaderDecompiler/LatteDecompiler.h | 6 +- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 4 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 296 +++++++++++------- 9 files changed, 475 insertions(+), 156 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp index a264633d..e705d3d1 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.cpp +++ b/src/Cafe/HW/Latte/Core/FetchShader.cpp @@ -240,6 +240,7 @@ void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* pars else attribGroup = &parsedFetchShader->bufferGroupsInvalid.emplace_back(); + parsedFetchShader->attributeBufferMask |= (1 << bufferIndex); attribGroup->attributeBufferIndex = bufferIndex; attribGroup->minOffset = offset; attribGroup->maxOffset = offset; diff --git a/src/Cafe/HW/Latte/Core/FetchShader.h b/src/Cafe/HW/Latte/Core/FetchShader.h index db72987b..aac084f2 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.h +++ b/src/Cafe/HW/Latte/Core/FetchShader.h @@ -42,6 +42,7 @@ struct LatteFetchShader std::vector bufferGroupsInvalid; // groups with buffer index not being a valid buffer (dst components of these can affect shader code, but no actual vertex imports are done) uint64 key{}; + uint32 attributeBufferMask{}; // mask of buffers sourced by this fetch shader // Vulkan uint64 vkPipelineHashFragment{}; // hash of all fetch shader state that influences the Vulkan graphics pipeline diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 9a243eae..2a11c412 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -76,7 +76,12 @@ struct LatteDrawcallContext { bool isFirst{}; // first _execute() in current sequence // these are only valid if isFirst is false: - // todo + mutable uint32 vertexBufferDirtyMask{}; // mask of vertex buffer indices which have been modified since last draw execute. It's the responsibility of the backend to unset bits + mutable uint32 vsUniformBufferDirtyMask{}; // mask of uniform buffer indices which have been modified (changed address or size) since last draw execute. It's the responsibility of the backend to unset bits + mutable uint32 psUniformBufferDirtyMask{}; + mutable uint32 gsUniformBufferDirtyMask{}; + mutable bool aluConstVSDirty{}; + mutable bool aluConstPSDirty{}; }; // texture @@ -164,7 +169,9 @@ void LatteCP_ProcessRingbuffer(); // buffer cache bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount); +void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask); void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData); +bool LatteBufferCache_LoadRemappedUniformsIncremental(LatteDecompilerShader* shader, float* uniformData, const LatteDrawcallContext& drawcallContext); void LatteRenderTarget_updateViewport(); diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index 5a44a4a7..09210267 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -128,34 +128,125 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* } } -void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) +// like LatteBufferCache_LoadRemappedUniforms but only updates data that is flagged as dirty in drawcallContext, returns true if any uniform value was updated. +bool LatteBufferCache_LoadRemappedUniformsIncremental(LatteDecompilerShader* shader, float* uniformData, const LatteDrawcallContext& drawcallContext) { - if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + bool hasChange = false; + uint32 shaderAluConst; + uint32 shaderUniformRegisterOffset; + + bool hasDirtyAluConst = false; + uint32 uniformBufferDirtyMask = 0; + switch (shader->shaderType) { - for(const auto& buf : shader->list_quickBufferList) + case LatteConst::ShaderType::Vertex: + shaderAluConst = 0x400; + shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; + hasDirtyAluConst = drawcallContext.aluConstVSDirty; + uniformBufferDirtyMask = drawcallContext.vsUniformBufferDirtyMask; + drawcallContext.vsUniformBufferDirtyMask = 0; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; + hasDirtyAluConst = drawcallContext.aluConstPSDirty; + uniformBufferDirtyMask = drawcallContext.psUniformBufferDirtyMask; + drawcallContext.psUniformBufferDirtyMask = 0; + break; + case LatteConst::ShaderType::Geometry: + shaderAluConst = 0; // geometry shader has no ALU const + shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; + uniformBufferDirtyMask = drawcallContext.gsUniformBufferDirtyMask; + drawcallContext.gsUniformBufferDirtyMask = 0; + break; + default: + UNREACHABLE; + } + + // sourced from uniform registers + if (hasDirtyAluConst) + { + uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; + for (auto it : shader->list_remappedUniformEntries_register) { - sint32 i = buf.index; - MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; - uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; - if (physicalAddr == MPTR_NULL) [[unlikely]] - { - g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0); + uint64* __restrict uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4); + uint64* __restrict regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + regDest[0] = uniformRegData[0]; + regDest[1] = uniformRegData[1]; + } + if (!shader->list_remappedUniformEntries_register.empty()) + hasChange = true; + } + // sourced from uniform buffers + if (uniformBufferDirtyMask) + { + for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) + { + if ((uniformBufferDirtyMask&(1<(uniformSize, buf.size); - uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize); - g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize); + else + { + for (auto& it : bufferGroup.entries) + { + uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + regDest[0] = 0; + regDest[1] = 0; + } + } + hasChange = true; } } + return hasChange; } +bool LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType, uint32 bufferDirtyMask) +{ + cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK); + bool hasChange = false; + for(const auto& buf : shader->list_quickBufferList) + { + sint32 i = buf.index; + if ((bufferDirtyMask&(1<buffer_bindUniformBuffer(shaderType, i, 0, 0); + continue; + } + uniformSize = std::min(uniformSize, buf.size); + uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize); + g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize); + } + return hasChange; +} + +// for detecting when vertex buffer size needs to be extended during incremental rendering +static sint32 s_vtxStateMaxIndex{}; +static sint32 s_vtxStateMaxInstance{}; + // upload vertex and uniform buffers bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount) { static uint32 s_syncBufferCounter = 0; + s_vtxStateMaxIndex = maxIndex; + s_vtxStateMaxInstance = baseInstance + instanceCount - 1; s_syncBufferCounter++; - if (s_syncBufferCounter >= 30) + if (s_syncBufferCounter >= 20) { LatteBufferCache_incrementalCleanup(); s_syncBufferCounter = 0; @@ -217,13 +308,112 @@ bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance } // sync uniform buffers LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); - if (vertexShader) - LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); - if (geometryShader) - LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); - if (pixelShader) - LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); + if (vertexShader && vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex, 0xFFFFFFFF); + if (pixelShader && pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, 0xFFFFFFFF); + if (geometryShader && geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, 0xFFFFFFFF); return true; } + +// similar to LatteBufferCache_SyncIncremental but updates only the bindings that are marked in the dirty masks +void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask) +{ + // uint32 attribBufferDirtyMask, uint32 uniformBufferVSDirtyMask, uint32 uniformBufferPSDirtyMask, uint32 uniformBufferGSDirtyMask + LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); + cemu_assert_debug(parsedFetchShader); + uint32 attribBufferDirtyMask = drawcallContext.vertexBufferDirtyMask; + + if ( maxIndex > s_vtxStateMaxIndex ) + { + attribBufferDirtyMask = 0xFFFFFFFF; + // todo - if the fetch shader can change between incremental calls, then we should also mark all of drawcallContext.vertexBufferDirtyMask as dirty. Or we could track the count per-buffer instead of as max index and max instance + // but for simplicity sake we should disallow fetch shader changes that use a differing set of buffers (this is already the case) + s_vtxStateMaxIndex = maxIndex; + } + uint32 maxInstance = baseInstance + instanceCount - 1; + if ( maxInstance > s_vtxStateMaxInstance ) + { + attribBufferDirtyMask = 0xFFFFFFFF; + s_vtxStateMaxInstance = maxInstance; + } + attribBufferDirtyMask &= parsedFetchShader->attributeBufferMask; + + // dont process flush queue and dont process deallocations yet, we are in the middle of a sequence of drawcalls that (most likely) reuse previous bindings + + // sync and bind dirty vertex buffers + if (attribBufferDirtyMask != 0) + { + uint32* __restrict bufferRegStartPtr = LatteGPUState.contextRegister + mmSQ_VTX_ATTRIBUTE_BLOCK_START; + for (auto& bufferGroup : parsedFetchShader->bufferGroups) + { + uint32 bufferIndex = bufferGroup.attributeBufferIndex; + if ((attribBufferDirtyMask&(1<> 11) & 0xFFFF; + + if (bufferAddress == MPTR_NULL) [[unlikely]] + { + g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0); + continue; + } + + // dont rely on buffer size given by game + uint32 fixedBufferSize = 0; + if (bufferGroup.hasVtxIndexAccess) + fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset; + if (bufferGroup.hasInstanceIndexAccess) + { + uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset; + fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance); + } + if (fixedBufferSize == 0 || bufferStride == 0) + fixedBufferSize += 128; + + +#if BOOST_OS_MACOS && defined(ENABLE_VULKAN) + if(bufferStride % 4 != 0) + { + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + if (VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance()) + { + auto fixedBuffer = vkRenderer->buffer_genStrideWorkaroundVertexBuffer(bufferAddress, fixedBufferSize, bufferStride); + vkRenderer->buffer_bindVertexStrideWorkaroundBuffer(fixedBuffer.first, fixedBuffer.second, bufferIndex, fixedBufferSize); + continue; + } + } + } +#endif + + uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize); + g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize); + } + } + // sync uniform buffers + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + // todo - if we AND the shader uniform buffer mask and the dirty mask we can completely skip calling syncGPUUniformBuffers if no relevant buffer was updated + if (vertexShader && vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + if (LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex, drawcallContext.vsUniformBufferDirtyMask)) + stageUniformModifiedMask |= (1<uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + if (LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, drawcallContext.psUniformBufferDirtyMask)) + stageUniformModifiedMask |= (1<uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + if ( LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, drawcallContext.gsUniformBufferDirtyMask) ) + stageUniformModifiedMask |= (1<draw_beginSequence(); } @@ -79,8 +83,7 @@ public: if (!m_drawcallContext.isFirst) performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++; m_drawcallContext.isFirst = false; - m_vertexBufferChanged = false; - m_uniformBufferChanged = false; + m_drawcallContext.vertexBufferDirtyMask = 0; } void endDrawPass() @@ -89,14 +92,34 @@ public: m_drawPassActive = false; } - void notifyModifiedVertexBuffer() + void MarkVertexBufferDirty(uint32 index) { - m_vertexBufferChanged = true; + m_drawcallContext.vertexBufferDirtyMask |= (1< m_queuePosStack; }; @@ -334,7 +355,7 @@ bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCal const uint32 registerOffset = LatteReadCMD(); const uint32 registerIndex = TRegisterBase + registerOffset; const uint32 registerStartIndex = registerIndex; - const uint32 registerEndIndex = registerStartIndex + nWords; + const uint32 registerEndIndex = registerStartIndex + nWords - 1; cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); @@ -996,25 +1017,34 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt { LatteCP_itSetRegistersGeneric2(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd, bool regValuesChanged) { + if (!regValuesChanged) + return; if ((registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7))) { - if (regValuesChanged) - { - // cemuLog_log(LogType::Force, "[FastDrawMode] End due to texture change"); - drawPassCtx.endDrawPass(); // texture updates end the current draw sequence - } + // cemuLog_log(LogType::Force, "[FastDrawMode] End due to texture change"); + drawPassCtx.endDrawPass(); // texture updates end the current draw sequence } else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END) { - if (regValuesChanged) - drawPassCtx.notifyModifiedVertexBuffer(); + uint32 bufferIndex = (registerStart - mmSQ_VTX_ATTRIBUTE_BLOCK_START) / 7; + drawPassCtx.MarkVertexBufferDirty(bufferIndex); } - else + else if (registerStart >= mmSQ_VTX_UNIFORM_BLOCK_START && registerEnd <= mmSQ_VTX_UNIFORM_BLOCK_END) { - if (regValuesChanged) - drawPassCtx.notifyModifiedUniformBuffer(); + uint32 bufferIndex = (registerStart - mmSQ_VTX_UNIFORM_BLOCK_START) / 7; + drawPassCtx.MarkVSUniformBufferDirty(bufferIndex); + } + else if (registerStart >= mmSQ_PS_UNIFORM_BLOCK_START && registerEnd <= mmSQ_PS_UNIFORM_BLOCK_END) + { + uint32 bufferIndex = (registerStart - mmSQ_PS_UNIFORM_BLOCK_START) / 7; + drawPassCtx.MarkPSUniformBufferDirty(bufferIndex); + } + else if (registerStart >= mmSQ_GS_UNIFORM_BLOCK_START && registerEnd <= mmSQ_GS_UNIFORM_BLOCK_END) + { + uint32 bufferIndex = (registerStart - mmSQ_GS_UNIFORM_BLOCK_START) / 7; + drawPassCtx.MarkGSUniformBufferDirty(bufferIndex); } }); if (!drawPassCtx.isWithinDrawPass()) @@ -1026,7 +1056,15 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } case IT_SET_ALU_CONST: // uniform register { - LatteCP_itSetRegistersGeneric(cmdData, nWords); + LatteCP_itSetRegistersGeneric2(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd, bool regValuesChanged) { + if (!regValuesChanged) + return; + if ( registerStart >= (mmSQ_ALU_CONSTANT0_0 + 0x400) ) + drawPassCtx.MarkVSAluConstantsDirty(); + else + drawPassCtx.MarkPSAluConstantsDirty(); + // todo - we could further optimize by tracking the min/max range of modified ALU constants + }); break; } case IT_SET_CTL_CONST: diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp index 6a975a4e..cbb36a79 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -1038,7 +1038,7 @@ void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader) else { LatteFastAccessRemappedUniformEntry_buffer_t entryBuf; - uint32 kcacheBankIdOffset = entry->kcacheBankId* (7 * 4); + uint32 kcacheBankIdOffset = entry->kcacheBankId * (7 * 4); entryBuf.indexOffset = entry->index * 16; entryBuf.mappedIndexOffset = entry->mappedIndex * 16; // find or create buffer group @@ -1049,7 +1049,7 @@ void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader) } else { - shader->list_remappedUniformEntries_bufferGroups.emplace_back(kcacheBankIdOffset).entries.emplace_back(entryBuf); + shader->list_remappedUniformEntries_bufferGroups.emplace_back(entry->kcacheBankId, kcacheBankIdOffset).entries.emplace_back(entryBuf); } } } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h index 6a7018e7..c95f8098 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -209,9 +209,9 @@ struct LatteDecompilerShader // fast access struct _RemappedUniformBufferGroup { - _RemappedUniformBufferGroup(uint32 _kcacheBankIdOffset) : kcacheBankIdOffset(_kcacheBankIdOffset) {}; - - uint32 kcacheBankIdOffset; + _RemappedUniformBufferGroup(uint16 bufferId, uint16 _kcacheBankIdOffset) : bufferId(bufferId), kcacheBankIdOffset(_kcacheBankIdOffset) {}; + uint16 bufferId; + uint16 kcacheBankIdOffset; std::vector entries; }; std::vector list_remappedUniformEntries_register; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index d6b1f889..47041972 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -369,7 +369,6 @@ private: VkDescriptorSetInfo* activeGeometryDS{ nullptr }; bool descriptorSetsChanged{ false }; bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget - // viewport and scissor box VkViewport currentViewport{}; VkRect2D currentScissorRect{}; @@ -551,7 +550,8 @@ private: // uniform uint32 uniformData_uploadUniformDataBufferGetOffset(std::span data); - void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); + void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader, float* __restrict uniformBuf); + void uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask, float* __restrict uniformBuf); // misc void CreatePipelineCache(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 25a1a70e..a29caeb4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -311,7 +311,9 @@ void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation) memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal); } -float s_vkUniformData[512 * 4]; +float s_vkUniformDataVS[512 * 4]; +float s_vkUniformDataPS[512 * 4]; +float s_vkUniformDataGS[512 * 4]; uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span data) { @@ -375,84 +377,135 @@ uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::spanshaderType) + auto GET_UNIFORM_DATA_PTR = [&uniformBuf](size_t index) { return uniformBuf + (index / 4); }; + if (shader->resourceMapping.uniformVarsBufferBindingPoint < 0) + return; + if (shader->uniform.list_ufTexRescale.empty() == false) { - case LatteConst::ShaderType::Vertex: - shaderAluConst = 0x400; - break; - case LatteConst::ShaderType::Pixel: - shaderAluConst = 0; - break; - case LatteConst::ShaderType::Geometry: - shaderAluConst = 0; // geometry shader has no ALU const - break; - default: - UNREACHABLE; - } - - if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) - { - if (shader->uniform.list_ufTexRescale.empty() == false) + for (auto& entry : shader->uniform.list_ufTexRescale) { - for (auto& entry : shader->uniform.list_ufTexRescale) + float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); + memcpy(entry.currentValue, xyScale, sizeof(float) * 2); + memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2); + } + } + if (shader->uniform.loc_alphaTestRef >= 0) + { + *GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); + } + if (shader->uniform.loc_pointSize >= 0) + { + const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; + float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; + if (pointWidth == 0.0f) + pointWidth = 1.0f / 8.0f; // minimum size + *GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth; + } + if (shader->uniform.loc_remapped >= 0) + { + LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); + } + if (shader->uniform.loc_uniformRegister >= 0) + { + sint32 shaderAluConst; + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + shaderAluConst = 0x400; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + break; + case LatteConst::ShaderType::Geometry: + shaderAluConst = 0; // geometry shader has no ALU const + break; + default: + UNREACHABLE; + } + uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); + memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16); + } + if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) + { + sint32 viewportWidth; + sint32 viewportHeight; + LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() + float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform); + v[0] = 2.0f / (float)viewportWidth; + v[1] = 2.0f / (float)viewportHeight; + } + if (shader->uniform.loc_fragCoordScale >= 0) + { + LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale)); + } + if (shader->uniform.loc_verticesPerInstance >= 0) + { + *(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance; + for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) + { + if (shader->uniform.loc_streamoutBufferBase[b] >= 0) { - float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); - memcpy(entry.currentValue, xyScale, sizeof(float) * 2); - memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2); + *(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset; } } - if (shader->uniform.loc_alphaTestRef >= 0) + } + dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize}); +} + +void VulkanRenderer::uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask, float* __restrict uniformBuf) +{ + // similar to uniformData_updateUniformVars, but checks only the fields that can change during a fast draw sequence + auto GET_UNIFORM_DATA_PTR = [&uniformBuf](size_t index) { return uniformBuf + (index / 4); }; + if (shader->resourceMapping.uniformVarsBufferBindingPoint < 0) + return; + // list_ufTexRescale -> Skipped because textures do not change + // loc_alphaTestRef -> Skipped because modifying SX_ALPHA_REF ends sequence + // loc_pointSize -> Skipped because modifying PA_SU_POINT_SIZE ends sequence + // loc_windowSpaceToClipSpaceTransform/loc_fragCoordScale/ -> Skipped because viewport doesn't change + // loc_verticesPerInstance -> Skipped because sequences with streamout enabled are not allowed + + bool hasChange = false; // todo - For loc_remapped and loc_uniformRegister we want to pass a mask of dirty uniform var / buffers. If nothing is dirty we can also skip it + if (shader->uniform.loc_remapped >= 0) + { + cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED); + hasChange = LatteBufferCache_LoadRemappedUniformsIncremental(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), drawcallContext); + } + if (shader->uniform.loc_uniformRegister >= 0) + { + cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE); + bool isDirty = false; + sint32 shaderAluConst; + switch (shader->shaderType) { - *GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); + case LatteConst::ShaderType::Vertex: + shaderAluConst = 0x400; + isDirty = drawcallContext.aluConstVSDirty; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + isDirty = drawcallContext.aluConstPSDirty; + break; + case LatteConst::ShaderType::Geometry: + cemu_assert_suspicious(); + shaderAluConst = 0; + UNREACHABLE; // geometry shader has no ALU const + break; + default: + UNREACHABLE; } - if (shader->uniform.loc_pointSize >= 0) - { - const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; - float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; - if (pointWidth == 0.0f) - pointWidth = 1.0f / 8.0f; // minimum size - *GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth; - } - if (shader->uniform.loc_remapped >= 0) - { - LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); - } - if (shader->uniform.loc_uniformRegister >= 0) + if (isDirty) { + hasChange = true; uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16); } - if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) - { - sint32 viewportWidth; - sint32 viewportHeight; - LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() - float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform); - v[0] = 2.0f / (float)viewportWidth; - v[1] = 2.0f / (float)viewportHeight; - } - if (shader->uniform.loc_fragCoordScale >= 0) - { - LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale)); - } - if (shader->uniform.loc_verticesPerInstance >= 0) - { - *(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance; - for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) - { - if (shader->uniform.loc_streamoutBufferBase[b] >= 0) - { - *(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset; - } - } - } - dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)s_vkUniformData, shader->uniform.uniformRangeSize}); + } + if (hasChange) + { + dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize}); + stageUniformModifiedMask |= (1 << shaderStageIndex); } } @@ -1331,11 +1384,11 @@ void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (vertexShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, s_vkUniformDataVS); if (pixelShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, s_vkUniformDataPS); if (geometryShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, s_vkUniformDataGS); // store where the read pointer should go after command buffer execution m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; @@ -1497,12 +1550,15 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + uint8 stageUniformModifiedMask = 0; // one bit for each stage (using SHADER_STAGE_INDEX_* as bit index). Set if any uniform data has been modified and needs to be reuploaded if (vertexShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataVS); if (pixelShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataPS); if (geometryShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataGS); + drawcallContext.aluConstVSDirty = false; + drawcallContext.aluConstPSDirty = false; // store where the read pointer should go after command buffer execution m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; @@ -1554,20 +1610,25 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan else { // synchronize vertex and uniform cache and update buffer bindings - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + LatteBufferCache_SyncIncremental(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, drawcallContext, stageUniformModifiedMask); } - PipelineInfo* pipeline_info; + m_state.descriptorSetsChanged = false; + PipelineInfo* pipeline_info = m_state.activePipelineInfo; // recalculate the part of the pipeline hash that can change during fast draws - if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew)) + if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew)) [[unlikely]] { // pipeline changed pipeline_info = draw_getOrCreateGraphicsPipeline(count); m_state.activePipelineInfo = pipeline_info; + // if the pipeline is changed then we also need to get the descriptor sets + draw_prepareDescriptorSets(pipeline_info, m_state.activeVertexDS, m_state.activePixelDS, m_state.activeGeometryDS); + m_state.descriptorSetsChanged = true; + stageUniformModifiedMask = 0x7; } else { - pipeline_info = m_state.activePipelineInfo; + // sanity check that we are using the right pipeline #ifdef CEMU_DEBUG_ASSERT auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count); if (pipeline_info != pipeline_info2) @@ -1581,7 +1642,7 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) { // invalid/uninitialized pipeline - m_state.activeVertexDS = nullptr; + //m_state.activeVertexDS = nullptr; return; } @@ -1591,15 +1652,10 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan vertexDS = m_state.activeVertexDS; pixelDS = m_state.activePixelDS; geometryDS = m_state.activeGeometryDS; - m_state.descriptorSetsChanged = false; } else { - draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS); - m_state.activeVertexDS = vertexDS; - m_state.activePixelDS = pixelDS; - m_state.activeGeometryDS = geometryDS; - m_state.descriptorSetsChanged = true; + __debugbreak(); // should never happen unless draw_prepareDescriptorSets can fail? } draw_setRenderPass(); @@ -1625,32 +1681,58 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan // update descriptor sets uint32_t dynamicOffsets[17 * 2]; - if (vertexDS && pixelDS) + VkDescriptorSet dsArray[2]; + sint32 dsArraySize = 0; + sint32 dsArrayBase = 0; + sint32 numDynOffsets = 0; + if (vertexDS && (stageUniformModifiedMask&(1<m_vkObjDescriptorSet->descriptorSet; - dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, tmpNumDynOffsets, pipeline_info); + dsArraySize = 1; + numDynOffsets += tmpNumDynOffsets; + } + if (pixelDS && (stageUniformModifiedMask&(1<m_vkObjDescriptorSet->descriptorSet; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsets, tmpNumDynOffsets, pipeline_info); + dsArraySize++; + numDynOffsets += tmpNumDynOffsets; + } + if (dsArraySize > 0) + { + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, dsArrayBase, dsArraySize, dsArray, numDynOffsets, dynamicOffsets); + } - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets); - } - else if (vertexDS) - { - sint32 numDynOffsets; - draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info); - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); - } - else if (pixelDS) - { - sint32 numDynOffsets; - draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info); - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); - } + // if (vertexDS && pixelDS) + // { + // // update vertex and pixel descriptor set in a single call to vkCmdBindDescriptorSets + // sint32 numDynOffsetsVS; + // sint32 numDynOffsetsPS; + // draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsetsVS, pipeline_info); + // draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsetsVS, numDynOffsetsPS, pipeline_info); + // + // VkDescriptorSet dsArray[2]; + // dsArray[0] = vertexDS->m_vkObjDescriptorSet->descriptorSet; + // dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; + // + // vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets); + // } + // else if (vertexDS) + // { + // sint32 numDynOffsets; + // draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info); + // vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); + // } + // else if (pixelDS) + // { + // sint32 numDynOffsets; + // draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info); + // vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); + // } if (geometryDS) { sint32 numDynOffsets;