From 038781a05e29d454321d94551132519cc264a021 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:50:02 +0200 Subject: [PATCH 01/14] Latte: Widen conditions for fast draw mode --- .../HW/Latte/Core/LatteCommandProcessor.cpp | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index ceec8d24..afcaaaa3 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -330,19 +330,18 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) return cmd; } +// similar to LatteCP_itSetRegistersGeneric, but calls a callback for every register range checked and returns true ONLY if any register value has actually changed (e.g. not updated to the same value as before) template -LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange) +bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange) { - uint32 registerOffset = LatteReadCMD(); - uint32 registerIndex = TRegisterBase + registerOffset; - uint32 registerStartIndex = registerIndex; - uint32 registerEndIndex = registerStartIndex + nWords; -#ifdef CEMU_DEBUG_ASSERT + const uint32 registerOffset = LatteReadCMD(); + const uint32 registerIndex = TRegisterBase + registerOffset; + const uint32 registerStartIndex = registerIndex; + const uint32 registerEndIndex = registerStartIndex + nWords; cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); -#endif - cbRegRange(registerStartIndex, registerEndIndex); uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); + bool hasRegChange = false; if (LatteGPUState.contextControl0 == 0x80000077) { // state shadowing enabled @@ -354,6 +353,7 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRa MPTR regShadowAddr = shadowAddrs[indexCounter]; if (regShadowAddr) *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); + hasRegChange |= (outputReg[indexCounter] != dataWord); outputReg[indexCounter] = dataWord; indexCounter++; } @@ -364,13 +364,17 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRa sint32 indexCounter = 0; while (--nWords) { - *outputReg = LatteReadCMD(); + uint32 dataWord = LatteReadCMD(); + hasRegChange |= (*outputReg != dataWord); + *outputReg = dataWord; outputReg++; } } // some register writes trigger special behavior LatteCP_itSetRegistersGeneric_handleSpecialRanges(registerStartIndex, registerEndIndex); - return cmd; + // callback + cbRegRange(registerStartIndex, registerEndIndex, hasRegChange); + return hasRegChange; } LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords) @@ -973,6 +977,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt LatteCMDPtr cmd, cmdStart, cmdEnd; if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) { + // cemuLog_log(LogType::Force, "[FastDrawMode] End due running out of commands"); drawPassCtx.endDrawPass(); return; } @@ -992,16 +997,28 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt { case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units { - LatteCP_itSetRegistersGeneric(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd) + LatteCP_itSetRegistersGeneric2(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd, bool regValuesChanged) { 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))) - drawPassCtx.endDrawPass(); // texture updates end the current draw sequence + { + if (regValuesChanged) + { + // 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) - drawPassCtx.notifyModifiedVertexBuffer(); + { + if (regValuesChanged) + drawPassCtx.notifyModifiedVertexBuffer(); + } else - drawPassCtx.notifyModifiedUniformBuffer(); + { + if (regValuesChanged) + drawPassCtx.notifyModifiedUniformBuffer(); + } }); if (!drawPassCtx.isWithinDrawPass()) { @@ -1042,9 +1059,18 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } case IT_SET_CONTEXT_REG: { - drawPassCtx.endDrawPass(); - drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); - return; + // LatteCP_itSetRegistersGeneric(cmd, nWords); + bool hasChanged = LatteCP_itSetRegistersGeneric2(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){}); + //cemuLog_log(LogType::Force, "[FastDrawMode] Potential end due to SetContextReg (itHeader {:08x} data {:08x}) -> HasChanged {}", itHeader, (uint32)cmdData[0], hasChanged); + // drawPassCtx.endDrawPass(); + // drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); + if (hasChanged) + { + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + return; + } + break; } case IT_INDIRECT_BUFFER_PRIV: { @@ -1052,13 +1078,25 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx); if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer cemu_assert_debug(false); - - //if (!drawPassCtx.isWithinDrawPass()) - // return cmdData; + break; + } + case IT_SET_SAMPLER: + { + bool hasChanged = LatteCP_itSetRegistersGeneric2(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){}); + //cemuLog_log(LogType::Force, "[FastDrawMode] Set sampler (reg change = {} - filtered)", hasChanged); + if (hasChanged) + { + //cemuLog_log(LogType::Force, "[FastDrawMode] End due to sampler change"); + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + return; + } break; } default: // unsupported command for fast draw + //cemuLog_log(LogType::Force, "[FastDrawMode] Unallowed command. Data: {:08x} {:08x} {:08x} {:08x} {:08x} {:08x}", (uint32)itHeader, (uint32)cmdData[0], (uint32)cmdData[1], (uint32)cmdData[2], (uint32)cmdData[3], (uint32)cmdData[4], (uint32)cmdData[5]); + //cemuLog_log(LogType::Force, "[FastDrawMode] End due to unallowed packet 0x{:08x}", itHeader); drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; @@ -1071,6 +1109,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt else { // unsupported command for fast draw + //cemuLog_log(LogType::Force, "[FastDrawMode] End due to uknown packet 0x{:08x}", itHeader); drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; @@ -1167,6 +1206,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) case IT_DRAW_INDEX_2: { drawPassCtx.beginDrawPass(); + //cemuLog_log(LogType::Force, "[CmdBuf] DrawIndex2"); LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); // enter fast draw mode drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); @@ -1179,6 +1219,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) case IT_DRAW_INDEX_AUTO: { drawPassCtx.beginDrawPass(); + //cemuLog_log(LogType::Force, "[CmdBuf] DrawIndexAuto"); LatteCP_itDrawIndexAuto(cmdData, nWords, drawPassCtx); // enter fast draw mode drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); @@ -1192,6 +1233,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); + //cemuLog_log(LogType::Force, "[CmdBuf] DrawIndexImm"); LatteCP_itDrawImmediate(cmdData, nWords, drawPassCtx); drawPassCtx.endDrawPass(); break; @@ -1448,6 +1490,7 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); + cemuLog_log(LogType::Force, "[TopLevel] DrawIndex2"); LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; @@ -1457,6 +1500,7 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); + cemuLog_log(LogType::Force, "[TopLevel] DrawIndexAuto"); LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 512; @@ -1466,6 +1510,7 @@ void LatteCP_ProcessRingbuffer() { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); + cemuLog_log(LogType::Force, "[TopLevel] DrawIndexImm"); LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; From 321fa4d74d2084a416c56c7beffd26880e614cfb Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:51:16 +0200 Subject: [PATCH 02/14] Latte+Vulkan: Small code cleanup and optimization --- .../LegacyShaderDecompiler/LatteDecompiler.h | 59 +++++++------------ .../LatteDecompilerAnalyzer.cpp | 33 +++++++---- .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 2 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 4 +- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h index b370123e..6a7018e7 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -45,64 +45,49 @@ typedef struct struct LatteDecompilerShaderResourceMapping { + static constexpr sint8 UNUSED_BINDING = -1; + LatteDecompilerShaderResourceMapping() { - std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, -1); - std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, -1); - std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, -1); + std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, UNUSED_BINDING); + std::fill(relBindingPointToRelTextureUnit, relBindingPointToRelTextureUnit + LATTE_NUM_MAX_TEX_UNITS, UNUSED_BINDING); + std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, UNUSED_BINDING); + std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, UNUSED_BINDING); } - static const sint8 UNUSED_BINDING = -1; - // most of this is for Vulkan - sint8 setIndex{}; // texture - sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS]; + sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS]; // mostly for OpenGL backwards compatibility where texture units are not remapped and binding points are sparse + sint8 relBindingPointToRelTextureUnit[LATTE_NUM_MAX_TEX_UNITS]; // (only used on VK and Metal) index is relative binding point (absoluteBindingPoint - textureUnitBaseBindingPoint) + sint8 textureUnitBaseBindingPoint{UNUSED_BINDING}; + sint8 textureUnitCount{0}; // number of entries set in textureUnitToBindingPoint // uniform buffer - sint8 uniformVarsBufferBindingPoint{-1}; // special block for uniform registers/remapped array/custom variables + sint8 uniformVarsBufferBindingPoint{UNUSED_BINDING}; // special block for uniform registers/remapped array/custom variables sint8 uniformBuffersBindingPoint[LATTE_NUM_MAX_UNIFORM_BUFFERS]; // shader storage buffer for transform feedback (if alternative mode is used) - sint8 tfStorageBindingPoint{-1}; + sint8 tfStorageBindingPoint{UNUSED_BINDING}; // attributes (vertex shader only) sint8 attributeMapping[LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS]; + // Vulkan exclusive + sint8 setIndex{}; // Metal exclusive - sint8 verticesPerInstanceBinding{-1}; - sint8 indexBufferBinding{-1}; - sint8 indexTypeBinding{-1}; + sint8 verticesPerInstanceBinding{UNUSED_BINDING}; + sint8 indexBufferBinding{UNUSED_BINDING}; + sint8 indexTypeBinding{UNUSED_BINDING}; sint32 getTextureCount() { - sint32 count = 0; - for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) - { - if (textureUnitToBindingPoint[i] >= 0) - count++; - } - return count; + return textureUnitCount; } - sint32 getTextureUnitFromBindingPoint(sint8 bindingPoint) + sint32 getRelativeTextureUnitFromRelativeBindingPoint(sint8 relativeBindingPoint) { - for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) - { - if (textureUnitToBindingPoint[i] == bindingPoint) - return i; - } - - cemu_assert_debug(false); - return -1; + cemu_assert_debug(relativeBindingPoint >= 0 && relativeBindingPoint < LATTE_NUM_MAX_TEX_UNITS); + return relBindingPointToRelTextureUnit[relativeBindingPoint]; } // returns -1 if no there is no texture binding point sint32 getTextureBaseBindingPoint() { - sint32 bindingPoint = 9999; - for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) - { - if (textureUnitToBindingPoint[i] >= 0) - bindingPoint = std::min(bindingPoint, (sint32)textureUnitToBindingPoint[i]); - } - if (bindingPoint == 9999) - return -1; - return bindingPoint; + return textureUnitBaseBindingPoint; } bool getUniformBufferBindingRange(sint32& minBinding, sint32& maxBinding) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index e8d5d490..0e468f02 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -482,45 +482,58 @@ namespace LatteDecompiler void _initTextureBindingPointsGL(LatteDecompilerShaderContext* decompilerContext) { // for OpenGL we use the relative texture unit index + sint8 bindingBase = 0; + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + bindingBase = LATTE_CEMU_VS_TEX_UNIT_BASE; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + bindingBase = LATTE_CEMU_GS_TEX_UNIT_BASE; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + bindingBase = LATTE_CEMU_PS_TEX_UNIT_BASE; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i]) continue; - sint32 textureBindingPoint; - if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) - textureBindingPoint = i + LATTE_CEMU_VS_TEX_UNIT_BASE; - else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) - textureBindingPoint = i + LATTE_CEMU_GS_TEX_UNIT_BASE; - else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) - textureBindingPoint = i + LATTE_CEMU_PS_TEX_UNIT_BASE; - - decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = textureBindingPoint; + decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = bindingBase + i; } } void _initTextureBindingPointsVK(LatteDecompilerShaderContext* decompilerContext) { // for Vulkan we use consecutive indices + decompilerContext->output->resourceMappingVK.textureUnitBaseBindingPoint = decompilerContext->currentBindingPointVK; + sint32 relBindingPointIndex = 0; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i]) continue; decompilerContext->output->resourceMappingVK.textureUnitToBindingPoint[i] = decompilerContext->currentBindingPointVK; + decompilerContext->output->resourceMappingVK.relBindingPointToRelTextureUnit[relBindingPointIndex] = i; + relBindingPointIndex++; + decompilerContext->output->resourceMappingVK.textureUnitCount++; decompilerContext->currentBindingPointVK++; } + if (relBindingPointIndex==0) + decompilerContext->output->resourceMappingVK.textureUnitBaseBindingPoint = -1; } #ifdef ENABLE_METAL void _initTextureBindingPointsMTL(LatteDecompilerShaderContext* decompilerContext) { - // for Vulkan we use consecutive indices + decompilerContext->output->resourceMappingMTL.textureUnitBaseBindingPoint = decompilerContext->currentTextureBindingPointMTL; + sint32 relBindingPointIndex = 0; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i] || decompilerContext->shader->textureRenderTargetIndex[i] != 255) continue; decompilerContext->output->resourceMappingMTL.textureUnitToBindingPoint[i] = decompilerContext->currentTextureBindingPointMTL; + decompilerContext->output->resourceMappingMTL.relBindingPointToRelTextureUnit[relBindingPointIndex] = i; + relBindingPointIndex++; + decompilerContext->output->resourceMappingMTL.textureUnitCount++; decompilerContext->currentTextureBindingPointMTL++; } + if (relBindingPointIndex==0) + decompilerContext->output->resourceMappingMTL.textureUnitBaseBindingPoint = -1; } #endif diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index 8c2da361..b27e3aeb 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -1996,7 +1996,7 @@ void MetalRenderer::BindStageResources(MTL::RenderCommandEncoder* renderCommandE sint32 textureCount = shader->resourceMapping.getTextureCount(); for (int i = 0; i < textureCount; ++i) { - const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); + const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i); auto hostTextureUnit = relative_textureUnit; // Don't bind textures that are accessed with a framebuffer fetch diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index d0933e35..e5a2fc60 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -483,7 +483,7 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) const sint32 textureCount = shader->resourceMapping.getTextureCount(); for (int i = 0; i < textureCount; ++i) { - const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); + const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i); auto hostTextureUnit = relative_textureUnit; auto textureDim = shader->textureUnitDim[relative_textureUnit]; auto texUnitRegIndex = hostTextureUnit * 7; @@ -601,7 +601,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* for (int i = 0; i < textureCount; ++i) { VkDescriptorImageInfo info{}; - const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); + const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i); auto hostTextureUnit = relative_textureUnit; auto textureDim = shader->textureUnitDim[relative_textureUnit]; auto texUnitRegIndex = hostTextureUnit * 7; From 377307515ce005ab2b37d24b9f56168a16b9212d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 15 Jun 2026 04:07:57 +0200 Subject: [PATCH 03/14] Latte+Vulkan: Several smaller optimizations --- src/Cafe/HW/Latte/Core/FetchShader.cpp | 10 ++-- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 11 ++-- .../HW/Latte/Core/LatteCommandProcessor.cpp | 7 +-- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 22 +++---- src/Cafe/HW/Latte/Core/LatteShader.cpp | 9 ++- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 6 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 + .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 2 + .../Renderer/Vulkan/VulkanRendererCore.cpp | 60 ++++++++++--------- 9 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp index 4ea633ba..a264633d 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.cpp +++ b/src/Cafe/HW/Latte/Core/FetchShader.cpp @@ -1,9 +1,6 @@ #include "Cafe/HW/Latte/Core/LatteConst.h" -#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" -#include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" @@ -503,7 +500,7 @@ LatteFetchShader::CacheHash LatteFetchShader::CalculateCacheHash(void* programCo LatteFetchShader* LatteFetchShader::FindInCacheByHash(LatteFetchShader::CacheHash fsHash) { - // does not hold s_fetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache() + // does not hold s_spinlockFetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache() auto itr = s_fetchShaderByHash.find(fsHash); if (itr == s_fetchShaderByHash.end()) return nullptr; @@ -539,6 +536,11 @@ LatteFetchShader* LatteFetchShader::FindByGPUState() } // update lookup info CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize()); + if (lookupInfo->fetchShader->m_cacheHash == fsHash && lookupInfo->programSize == fsSize) // check if its still the same hash + { + lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; + return lookupInfo->fetchShader; + } LatteFetchShader* fetchShader = FindInCacheByHash(fsHash); if (!fetchShader) { diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index 33d148bb..7d9e7043 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -171,15 +171,16 @@ bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); if (!parsedFetchShader) return false; + uint32* __restrict bufferRegStartPtr = LatteGPUState.contextRegister + mmSQ_VTX_ATTRIBUTE_BLOCK_START; for (auto& bufferGroup : parsedFetchShader->bufferGroups) { uint32 bufferIndex = bufferGroup.attributeBufferIndex; - uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; - MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; - uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; - uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + uint32* __restrict bufferRegs = bufferRegStartPtr + bufferIndex * 7; + MPTR bufferAddress = bufferRegs[0]; + /* uint32 bufferSize = bufferRegs[1] + 1; */ + uint32 bufferStride = (bufferRegs[2] >> 11) & 0xFFFF; - if (bufferAddress == MPTR_NULL) + if (bufferAddress == MPTR_NULL) [[unlikely]] { g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0); continue; diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index afcaaaa3..4a5fd480 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -124,7 +124,7 @@ private: bool m_isFirstDraw{false}; bool m_vertexBufferChanged{ false }; bool m_uniformBufferChanged{ false }; - boost::container::small_vector m_queuePosStack; + boost::container::static_vector m_queuePosStack; }; void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx); @@ -299,11 +299,11 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); #endif - uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); + uint32* __restrict outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); if (LatteGPUState.contextControl0 == 0x80000077) { // state shadowing enabled - uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; + uint32* __restrict shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; while (--nWords) { @@ -318,7 +318,6 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) else { // state shadowing disabled - sint32 indexCounter = 0; while (--nWords) { *outputReg = LatteReadCMD(); diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 2edcf4ea..148a6dce 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -325,22 +325,17 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN // get mask of all used color buffers uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr) { + if (!pixelShader) [[unlikely]] + return 0; const uint32* regView = lcr.GetRawView(); - - uint8 colorBufferMask = 0; - for (uint32 i = 0; i < 8; i++) - { - if (regView[mmCB_COLOR0_BASE + i] != MPTR_NULL) - colorBufferMask |= (1 << i); - } // check if color buffer output is active const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = lcr.CB_COLOR_CONTROL; uint32 colorBufferDisable = colorControlReg.get_SPECIAL_OP() == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE; if (colorBufferDisable) return 0; cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported - // combine color buffer mask with pixel output mask from pixel shader - colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0); + // start with color buffer mask from pixel shader output + uint8 colorBufferMask = pixelShader->pixelColorOutputMask; // combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked) uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK(); for (uint32 i = 0; i < 8; i++) @@ -348,9 +343,9 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade if (((channelTargetMask >> (i * 4)) & 0xF) == 0) colorBufferMask &= ~(1 << i); } - // render targets smaller than the scissor size are not allowed // this fixes a few render issues in Cemu but we dont know if this matches HW behavior + // also check for color buffers without a valid pointer cemu_assert_debug(lcr.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API) uint32 scissorAccessWidth = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); uint32 scissorAccessHeight = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); @@ -358,6 +353,8 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade { if( (colorBufferMask&(1<> 8) & 1; uint32 spi0_positionCentroid = (psControl0 >> 9) & 1; cemu_assert_debug(spi0_positionCentroid == 0); // controls gl_FragCoord - uint32 spi0_positionAddr = (psControl0 >> 10) & 0x1F; // controls gl_FragCoord + uint32 spi0_positionAddr = spi0_positionEnable ? ((psControl0 >> 10) & 0x1F) : 0xFFFFFFFF; // controls gl_FragCoord uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; // used for gl_PointCoords uint32 spi0_paramGenAddr = (psControl0 >> 19) & 0x7F; sint32 importIndex = 0; @@ -278,7 +278,7 @@ void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint3 key += (uint64)psInputControl; key = std::rotl(key, 7); - if (spi0_positionEnable && f == spi0_positionAddr) + if (f == spi0_positionAddr) { psInputTable->import[f].semanticId = LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION; psInputTable->import[f].isFlat = false; @@ -1053,7 +1053,7 @@ void LatteSHRC_UpdateActiveShaders() vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; copyProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); - if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) + if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]] { copyProgramCode = NULL; debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?"); @@ -1062,7 +1062,7 @@ void LatteSHRC_UpdateActiveShaders() } else { - if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) + if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]] { debug_printf("No vertex shader program set\n"); LatteGPUState.activeShaderHasError = true; @@ -1089,7 +1089,6 @@ void LatteSHRC_UpdateActiveShaders() // returns the sampler base index for the given shader type sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType) { - uint32 samplerId = LATTE_DECOMPILER_SAMPLER_NONE; if (shaderType == LatteConst::ShaderType::Vertex) return Latte::SAMPLER_BASE_INDEX_VERTEX; else if (shaderType == LatteConst::ShaderType::Pixel) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 43f64b2c..74b089a8 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1365,10 +1365,8 @@ void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture { for (auto& it : texture->list_compatibleRelations) { - if (texture == it->baseTexture) - it->subTexture->reloadFromDynamicTextures = true; - else - it->baseTexture->reloadFromDynamicTextures = true; + LatteTexture* connectedTexture = (texture == it->baseTexture) ? it->subTexture : it->baseTexture; + connectedTexture->reloadFromDynamicTextures = true; } } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index b4d99e35..686ead43 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -334,6 +334,8 @@ void VulkanRenderer::GetDeviceFeatures() m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); cemuLog_log(LogType::Force, fmt::format("VulkanLimits: UBAlignment {0} nonCoherentAtomSize {1}", prop2.properties.limits.minUniformBufferOffsetAlignment, prop2.properties.limits.nonCoherentAtomSize)); + // calculate used limits + m_featureControl.limits.calcUniformBufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; } #if BOOST_OS_LINUX diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 62ecd797..1a337951 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -470,6 +470,8 @@ private: { uint32 minUniformBufferOffsetAlignment = 256; uint32 nonCoherentAtomSize = 256; + // calculated + uint32 calcUniformBufferAlignmentM1{}; }limits; bool usingDebugMarkerTool{ false }; // validation layer or other tool capable of handling debug markers is used diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index e5a2fc60..9800db74 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -315,7 +315,7 @@ float s_vkUniformData[512 * 4]; uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span data) { - const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; + const uint32 bufferAlignmentM1 = m_featureControl.limits.calcUniformBufferAlignmentM1; const uint32 uniformSize = ((uint32)data.size() + bufferAlignmentM1) & ~bufferAlignmentM1; auto waitWhileCondition = [&](std::function condition) { @@ -481,40 +481,44 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) uint64 hash = 0; const sint32 textureCount = shader->resourceMapping.getTextureCount(); + + LatteTextureViewVk** texViewBase = m_state.boundTexture; + uint32* __restrict texRegBase = LatteGPUState.contextRegister; + uint32 samplerBaseIndex = 0; + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + texViewBase += LATTE_CEMU_VS_TEX_UNIT_BASE; + texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; + samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_VERTEX; + break; + case LatteConst::ShaderType::Pixel: + texViewBase += LATTE_CEMU_PS_TEX_UNIT_BASE; + texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; + samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_PIXEL; + break; + case LatteConst::ShaderType::Geometry: + texViewBase += LATTE_CEMU_GS_TEX_UNIT_BASE; + texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; + samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_GEOMETRY; + break; + default: + UNREACHABLE; + } + + for (int i = 0; i < textureCount; ++i) { const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i); - auto hostTextureUnit = relative_textureUnit; - auto textureDim = shader->textureUnitDim[relative_textureUnit]; - auto texUnitRegIndex = hostTextureUnit * 7; - switch (shader->shaderType) - { - case LatteConst::ShaderType::Vertex: - hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; - texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; - break; - case LatteConst::ShaderType::Pixel: - hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; - texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; - break; - case LatteConst::ShaderType::Geometry: - hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; - texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; - break; - default: - UNREACHABLE; - } - - auto texture = m_state.boundTexture[hostTextureUnit]; + uint32* __restrict texRegs = texRegBase + relative_textureUnit * 7; + auto texture = texViewBase[relative_textureUnit]; if (!texture) continue; - - const uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; - + const uint32 word4 = texRegs[4]; uint32 samplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; - if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) + if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) [[likely]] { - samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); + samplerIndex += samplerBaseIndex; hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 0]; hash = std::rotl(hash, 7); hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 1]; From 309bca61b21b683651b8eba82752b6b83358d114 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:57:36 +0200 Subject: [PATCH 04/14] Latte+Vulkan: More code tweaks for performance and cleanup --- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 10 ++--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 + .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 2 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 38 +++++-------------- 4 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index 7d9e7043..5a44a4a7 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -70,8 +70,6 @@ void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1, output[f] = _swapEndianU32(output[f]); } -#define ATTRIBUTE_CACHE_RING_SIZE (128) // up to 128 entries can be cached - void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData) { uint32 shaderAluConst; @@ -92,7 +90,7 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: - cemu_assert_debug(false); + UNREACHABLE; } // sourced from uniform registers @@ -110,11 +108,11 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4]; if (physicalAddr) { - uint8* uniformBase = memory_base + physicalAddr; + uint8* __restrict uniformBase = memory_base + physicalAddr; for (auto& it : bufferGroup.entries) { - uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - uint64* uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset); + uint64* __restrict regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + uint64* __restrict uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset); memcpy(regDest, uniformEntrySrc, 16); } } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 686ead43..ddff392f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -3791,6 +3791,7 @@ void VulkanRenderer::bufferCache_init(const sint32 bufferSize) m_importedMemBaseAddress = 0x10000000; size_t hostAllocationSize = 0x40000000ull; // todo - get size of allocation + /* bool configUseHostMemory = false; // todo - replace this with a config option m_useHostMemoryForCache = false; if (m_featureControl.deviceExtensions.external_memory_host && configUseHostMemory) @@ -3801,6 +3802,7 @@ void VulkanRenderer::bufferCache_init(const sint32 bufferSize) cemuLog_log(LogType::Force, "Unable to import host memory to Vulkan buffer. Use default cache system instead"); } } + */ if(!m_useHostMemoryForCache) memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_bufferCache, m_bufferCacheMemory); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 1a337951..b44e7232 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -635,7 +635,7 @@ private: // if VK_EXT_external_memory_host is supported we can (optionally) import all of the Wii U memory into a Vulkan memory object // this allows us to skip any vertex/uniform caching logic and let the GPU directly read the memory from main RAM // Wii U memory imported into a buffer - bool m_useHostMemoryForCache{ false }; + static constexpr bool m_useHostMemoryForCache{ false }; // currently disabled and made constexpr so the compiler eliminates the branches that will never be taken VkBuffer m_importedMem = VK_NULL_HANDLE; VkDeviceMemory m_importedMemMemory = VK_NULL_HANDLE; MPTR m_importedMemBaseAddress = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 9800db74..e32fdb78 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -456,9 +456,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } } -void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, - sint32& numDynOffsets, - const PipelineInfo* pipeline_info) +void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, sint32& numDynOffsets, const PipelineInfo* pipeline_info) { numDynOffsets = 0; if (pipeline_info->dynamicOffsetInfo.hasUniformVar[shaderStageIndex]) @@ -1355,7 +1353,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; // update index binding - bool isPrevIndexData = false; if (hostIndexType != INDEX_TYPE::NONE) { uint32 indexBufferIndex = indexReservation->bufferIndex; @@ -1374,8 +1371,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 cemu_assert(false); vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType); } - else - isPrevIndexData = true; } if (m_useHostMemoryForCache) @@ -1480,45 +1475,32 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 // 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); + 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); + 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); + 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); + 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; - draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, - pipeline_info); - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, - dynamicOffsets); + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } // draw From 45eedb4cd3e03719e0b3754f6642a9a4547ea7c6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:12:45 +0200 Subject: [PATCH 05/14] Vulkan: Separate function for continued drawcalls --- src/Cafe/HW/Latte/Core/Latte.h | 9 + .../HW/Latte/Core/LatteCommandProcessor.cpp | 14 +- .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 2 +- .../HW/Latte/Renderer/Metal/MetalRenderer.h | 2 +- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 +- .../Renderer/OpenGL/OpenGLRendererCore.cpp | 4 +- src/Cafe/HW/Latte/Renderer/Renderer.h | 2 +- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 6 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 216 +++++++++++++++--- 9 files changed, 215 insertions(+), 42 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index b6ff6ba0..9a243eae 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -70,6 +70,15 @@ struct LatteGPUState_t extern LatteGPUState_t LatteGPUState; +// drawcall context + +struct LatteDrawcallContext +{ + bool isFirst{}; // first _execute() in current sequence + // these are only valid if isFirst is false: + // todo +}; + // texture #include "Cafe/HW/Latte/Core/LatteTexture.h" diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 4a5fd480..32c7f387 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -1,10 +1,8 @@ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" // for write gatherer and special state. Get rid of dependency -#include "Cafe/OS/libs/gx2/GX2_Misc.h" // for GX2::sGX2MainCoreIndex. Legacy dependency #include "Cafe/OS/libs/gx2/GX2_Event.h" // for notification callbacks #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" @@ -53,7 +51,7 @@ public: void beginDrawPass() { m_drawPassActive = true; - m_isFirstDraw = true; + m_drawcallContext.isFirst = true; m_vertexBufferChanged = true; m_uniformBufferChanged = true; g_renderer->draw_beginSequence(); @@ -71,16 +69,16 @@ public: if (physIndices == MPTR_NULL) return; auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE(); - g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_isFirstDraw); + g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_drawcallContext); } else { - g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_isFirstDraw); + g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_drawcallContext); } performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; - if (!m_isFirstDraw) + if (!m_drawcallContext.isFirst) performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++; - m_isFirstDraw = false; + m_drawcallContext.isFirst = false; m_vertexBufferChanged = false; m_uniformBufferChanged = false; } @@ -121,7 +119,7 @@ public: private: bool m_drawPassActive{ false }; - bool m_isFirstDraw{false}; + LatteDrawcallContext m_drawcallContext{}; bool m_vertexBufferChanged{ false }; bool m_uniformBufferChanged{ false }; boost::container::static_vector m_queuePosStack; diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index b27e3aeb..a8f0a8f1 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -1067,7 +1067,7 @@ void MetalRenderer::draw_beginSequence() m_state.m_skipDrawSequence = true; } -void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) +void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) { if (m_state.m_skipDrawSequence) { diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h index e905b834..eadad94f 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h @@ -250,7 +250,7 @@ public: // core drawing logic void draw_beginSequence() override; - void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override; void draw_endSequence() override; void draw_updateVertexBuffersDirectAccess(); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 5b9ccd41..f691d8da 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -159,7 +159,7 @@ public: void draw_init(); void draw_beginSequence() override; - void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override; void draw_endSequence() override; template diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index c9437353..74d93190 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -1148,9 +1148,9 @@ void OpenGLRenderer::draw_beginSequence() // no-op } -void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) +void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) { - bool isMinimal = !isFirst; + bool isMinimal = !drawcallContext.isFirst; if (isMinimal) draw_genericDrawHandler(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); else diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 01a6d3b2..6a792fa2 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -140,7 +140,7 @@ public: // core drawing logic virtual void draw_beginSequence() = 0; - virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) = 0; + virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) = 0; virtual void draw_endSequence() = 0; // index diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index b44e7232..d6b1f889 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -530,7 +530,7 @@ private: void draw_endRenderPass(); void draw_beginSequence() override; - void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override; void draw_endSequence() override; void draw_updateVertexBuffersDirectAccess(); @@ -663,6 +663,10 @@ private: uint32 m_submitThreshold{}; // submit current buffer if recordedDrawcalls exceeds this number bool m_submitOnIdle{}; // submit current buffer if Latte command processor goes into idle state (no more commands or waiting for externally signaled condition) + // drawcall handling + void draw_execute_first(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext); + void draw_execute_continued(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext); + // tracking for dynamic offsets struct { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index e32fdb78..25a1a70e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1302,11 +1302,177 @@ void VulkanRenderer::draw_beginSequence() m_state.drawSequenceSkip = true; } -void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) +void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) +{ + if (m_state.drawSequenceSkip) + { + return; + } + + // fast clear color as depth + if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) + { + LatteDraw_handleSpecialState8_clearAsDepth(); + return; + } + else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) + { + draw_handleSpecialState5(); + return; + } + + // prepare streamout + m_streamoutState.verticesPerInstance = count; + LatteStreamout_PrepareDrawcall(count, instanceCount); + + // update uniform vars + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + + if (vertexShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); + if (pixelShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); + if (geometryShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); + // store where the read pointer should go after command buffer execution + m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; + + // process index data + const LattePrimitiveMode primitiveMode = static_cast(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); + + Renderer::INDEX_TYPE hostIndexType; + uint32 hostIndexCount; + uint32 indexMin = 0; + uint32 indexMax = 0; + Renderer::IndexAllocation indexAllocation; + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); + VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; + // update index binding + if (hostIndexType != INDEX_TYPE::NONE) + { + uint32 indexBufferIndex = indexReservation->bufferIndex; + uint32 indexBufferOffset = indexReservation->bufferOffset; + if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType) + { + m_state.activeIndexType = hostIndexType; + m_state.activeIndexBufferOffset = indexBufferOffset; + m_state.activeIndexBufferIndex = indexBufferIndex; + VkIndexType vkType; + if (hostIndexType == INDEX_TYPE::U16) + vkType = VK_INDEX_TYPE_UINT16; + else if (hostIndexType == INDEX_TYPE::U32) + vkType = VK_INDEX_TYPE_UINT32; + else + cemu_assert(false); + vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType); + } + } + + if (m_useHostMemoryForCache) + { + // direct memory access (Wii U memory space imported as a Vulkan buffer), update buffer bindings + draw_updateVertexBuffersDirectAccess(); + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + if (vertexShader) + draw_updateUniformBuffersDirectAccess(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + if (geometryShader) + draw_updateUniformBuffersDirectAccess(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + if (pixelShader) + draw_updateUniformBuffersDirectAccess(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); + } + else + { + // synchronize vertex and uniform cache and update buffer bindings + LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + } + + PipelineInfo* pipeline_info = draw_getOrCreateGraphicsPipeline(count); + m_state.activePipelineInfo = pipeline_info; + + auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; + if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) + { + // invalid/uninitialized pipeline + m_state.activeVertexDS = nullptr; + return; + } + + VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr; + draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS); + m_state.activeVertexDS = vertexDS; + m_state.activePixelDS = pixelDS; + m_state.activeGeometryDS = geometryDS; + m_state.descriptorSetsChanged = true; + + draw_setRenderPass(); + + if (m_state.currentPipeline != vkObjPipeline->GetPipeline()) + { + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->GetPipeline()); + vkObjPipeline->flagForCurrentCommandBuffer(); + m_state.currentPipeline = vkObjPipeline->GetPipeline(); + // depth bias + if (pipeline_info->usesDepthBias) + draw_updateDepthBias(true); + } + else + { + if (pipeline_info->usesDepthBias) + draw_updateDepthBias(false); + } + // update blend constants + if (pipeline_info->usesBlendConstants) + draw_updateVkBlendConstants(); + // update descriptor sets + uint32_t dynamicOffsets[17 * 2]; + 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; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); + } + // draw + if (hostIndexType != INDEX_TYPE::NONE) + vkCmdDrawIndexed(m_state.currentCommandBuffer, hostIndexCount, instanceCount, 0, baseVertex, baseInstance); + else + vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance); + LatteStreamout_FinishDrawcall(m_useHostMemoryForCache); +} + +void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) { if (m_state.drawSequenceSkip) { - LatteGPUState.drawCallCounter++; return; } @@ -1314,13 +1480,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) { LatteDraw_handleSpecialState8_clearAsDepth(); - LatteGPUState.drawCallCounter++; return; } else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) { draw_handleSpecialState5(); - LatteGPUState.drawCallCounter++; return; } @@ -1394,31 +1558,23 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 } PipelineInfo* pipeline_info; - - if (!isFirst) + // 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)) - { - // pipeline changed - pipeline_info = draw_getOrCreateGraphicsPipeline(count); - m_state.activePipelineInfo = pipeline_info; - } - else - { - pipeline_info = m_state.activePipelineInfo; -#ifdef CEMU_DEBUG_ASSERT - auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count); - if (pipeline_info != pipeline_info2) - { - cemu_assert_debug(false); - } -#endif - } + // pipeline changed + pipeline_info = draw_getOrCreateGraphicsPipeline(count); + m_state.activePipelineInfo = pipeline_info; } else { - pipeline_info = draw_getOrCreateGraphicsPipeline(count); - m_state.activePipelineInfo = pipeline_info; + pipeline_info = m_state.activePipelineInfo; +#ifdef CEMU_DEBUG_ASSERT + auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count); + if (pipeline_info != pipeline_info2) + { + cemu_assert_debug(false); + } +#endif } auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; @@ -1429,9 +1585,8 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 return; } - VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr; - if (!isFirst && m_state.activeVertexDS) + if (m_state.activeVertexDS) { vertexDS = m_state.activeVertexDS; pixelDS = m_state.activePixelDS; @@ -1510,7 +1665,14 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance); LatteStreamout_FinishDrawcall(m_useHostMemoryForCache); +} +void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) +{ + if (drawcallContext.isFirst) + draw_execute_first(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType, drawcallContext); + else + draw_execute_continued(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType, drawcallContext); LatteGPUState.drawCallCounter++; } From c7ee701edd949d60de3e4a8f8ecd413275b76ece Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 16 Jun 2026 00:13:21 +0200 Subject: [PATCH 06/14] Latte: Omit FragCoordScale uniform in shader if unused We insert padding when needed to keep alignment intact for backwards compatibility with gfx pack shaders. --- .../LatteDecompilerAnalyzer.cpp | 24 ++++++++++++++++- .../LatteDecompilerEmitGLSLHeader.hpp | 27 ++++++++++++++++--- .../LatteDecompilerEmitMSLHeader.hpp | 27 +++++++++++++++---- .../LatteDecompilerInternal.h | 1 + src/Cafe/HW/Latte/Renderer/RendererShader.cpp | 2 ++ 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index 0e468f02..6982b408 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -562,7 +562,7 @@ namespace LatteDecompiler bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable != 0) decompilerContext->hasUniformVarBlock = true; // uf_alphaTestRef - if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess) decompilerContext->hasUniformVarBlock = true; // uf_fragCoordScale if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) decompilerContext->hasUniformVarBlock = true; // uf_pointSize @@ -607,6 +607,11 @@ namespace LatteDecompiler decompilerContext->output->resourceMappingMTL.uniformVarsBufferBindingPoint = decompilerContext->currentBufferBindingPointMTL; decompilerContext->currentBufferBindingPointMTL++; } + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) // compatibility workaround to keep binding indices the same with existing gfx pack shader replacements, we now only emit uf_fragCoord when needed and it can lead to hasUniformVarBlock being false + { + decompilerContext->currentBindingPointVK++; + decompilerContext->currentBufferBindingPointMTL++; + } // assign binding points to uniform buffers if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { @@ -1006,6 +1011,23 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD shaderContext->analyzer.gprUseMask[i / 8] |= (1 << (i % 8)); } } + // check if shader accesses frag coord + if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + for (sint32 i = 0; i < psInputTable->count; i++) + { + sint32 gprIndex = i; + if ((shaderContext->analyzer.gprUseMask[gprIndex / 8] & (1 << (gprIndex % 8))) == 0 && shaderContext->analyzer.usesRelativeGPRRead == false) + continue; + uint32 psInputSemanticId = psInputTable->import[i].semanticId; + if (psInputSemanticId == LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION) + { + shaderContext->analyzer.hasFragCoordAccess = true; + break; + } + } + } // analyze CF stack sint32 cfCurrentStackDepth = 0; sint32 cfCurrentMaxStackDepth = 0; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp index 0c98418a..9d6a4385 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -83,9 +83,15 @@ namespace LatteDecompiler } } // define uf_fragCoordScale which holds the xy scale for render target resolution vs effective resolution + bool compatNeedFragCoordScalePadding = false; // 2026-06-15 - uf_fragCoordScale is only emitted when accessed now. To keep compatible with old shader replacements we insert padding if its not the last element if (shader->shaderType == LatteConst::ShaderType::Pixel) { - if (rendererType == RendererAPI::OpenGL) + if (!decompilerContext->analyzer.hasFragCoordAccess) + { + // omit uf_fragCoordScale + compatNeedFragCoordScalePadding = true; + } + else if (rendererType == RendererAPI::OpenGL) { uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; shaderSrc->add("uniform vec2 uf_fragCoordScale;" _CRLF); @@ -106,6 +112,20 @@ namespace LatteDecompiler { if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) continue; + if (compatNeedFragCoordScalePadding) + { + if (rendererType == RendererAPI::OpenGL) + { + uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; + shaderSrc->add("uniform vec2 uf_fragCoordScaleCompatPadding;" _CRLF); uniformCurrentOffset += 8; + } + else + { + uniformCurrentOffset = (uniformCurrentOffset + 15)&~15; + shaderSrc->add("uniform vec4 uf_fragCoordScaleCompatPadding;" _CRLF); uniformCurrentOffset += 16; + } + compatNeedFragCoordScalePadding = false; + } uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7; shaderSrc->addFmt("uniform vec2 uf_tex{}Scale;" _CRLF, t); uniformOffsets.offset_texScale[t] = uniformCurrentOffset; @@ -116,6 +136,7 @@ namespace LatteDecompiler (shader->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->options->usesGeometryShader == false) || (shader->shaderType == LatteConst::ShaderType::Geometry) ) { + // note - we dont need to handle compatNeedFragCoordScalePadding here because it's pixel shader only shaderSrc->add("uniform int uf_verticesPerInstance;" _CRLF); uniformOffsets.offset_verticesPerInstance = uniformCurrentOffset; uniformCurrentOffset += 4; @@ -280,7 +301,7 @@ namespace LatteDecompiler src->add("#define V2G_LAYOUT layout(location = 0)" _CRLF); } } - else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess) { src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy,gl_FragCoord.z, 1.0/gl_FragCoord.w)" _CRLF); } @@ -305,7 +326,7 @@ namespace LatteDecompiler if (decompilerContext->options->usesGeometryShader) src->add("#define V2G_LAYOUT" _CRLF); } - else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess) { src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale,gl_FragCoord.zw)" _CRLF); } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp index ac48f1f7..8ce05180 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp @@ -68,18 +68,34 @@ namespace LatteDecompiler } } // define fragCoordScale which holds the xy scale for render target resolution vs effective resolution + bool compatNeedFragCoordScalePadding = false; // 2026-06-15 - fragCoordScale is only emitted when accessed now. To keep compatible with old shader replacements we insert padding if its not the last element if (shader->shaderType == LatteConst::ShaderType::Pixel) { - uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; - src->add("float2 fragCoordScale;" _CRLF); - uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; - uniformCurrentOffset += 8; + if (!decompilerContext->analyzer.hasFragCoordAccess) + { + // omit fragCoordScale + compatNeedFragCoordScalePadding = true; + } + else + { + uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; + src->add("float2 fragCoordScale;" _CRLF); + uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; + uniformCurrentOffset += 8; + } } // provide scale factor for every texture that is accessed via texel coordinates (texelFetch) for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) continue; + if (compatNeedFragCoordScalePadding) + { + uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; + src->add("float2 fragCoordScaleCompatPadding;" _CRLF); + uniformCurrentOffset += 8; + compatNeedFragCoordScalePadding = false; + } uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7; src->addFmt("float2 tex{}Scale;" _CRLF, t); uniformOffsets.offset_texScale[t] = uniformCurrentOffset; @@ -248,7 +264,8 @@ namespace LatteDecompiler { auto* src = shaderContext->shaderSource; - src->add("#define GET_FRAGCOORD() float4(in.position.xy * supportBuffer.fragCoordScale.xy, in.position.z, 1.0 / in.position.w)" _CRLF); + if (shaderContext->analyzer.hasFragCoordAccess) + src->add("#define GET_FRAGCOORD() float4(in.position.xy * supportBuffer.fragCoordScale.xy, in.position.z, 1.0 / in.position.w)" _CRLF); src->add("struct FragmentIn {" _CRLF); src->add("float4 position [[position]];" _CRLF); diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h index 0b19bcae..c9632044 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -237,6 +237,7 @@ struct LatteDecompilerShaderContext uint8 gprUseMask[(LATTE_NUM_GPR + 7) / 8]; // 1 bit per GPR, set if GPR is read/written anywhere in the program (ignores GPR accesses with relative index) bool hasStreamoutWrite; // stream-out CF instructions are used bool hasRedcCUBE; // has cube reduction instruction + bool hasFragCoordAccess{false}; // accesses gl_FragCoord bool modifiesPixelActiveState; // set if the active mask is changed anywhere in the shader (If false, we can skip active mask checks) bool usesIntegerValues; // set if the shader uses any kind of integer instruction or integer-based GPR/AR access sint32 activeStackMaxDepth; // maximum depth of pixel state stack diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp index 23c8d0ea..9203b3be 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp @@ -19,6 +19,8 @@ uint32 RendererShader::GeneratePrecompiledCacheId() // settings that can influence shaders v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; + v += 0x820a5277; // change this value for manual invalidation + return v; } 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 07/14] 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; From a5d8ceb0dd00bbcaeb0a22b1844d26ccc1faf52c Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 17 Jun 2026 05:11:15 +0200 Subject: [PATCH 08/14] Vulkan: Additional smaller optimizations --- .../HW/Latte/Core/LatteCommandProcessor.cpp | 41 ++++++++++++++----- src/Cafe/HW/Latte/Core/LatteConst.h | 13 +++--- .../Latte/Renderer/Vulkan/VKRPipelineInfo.cpp | 19 +++------ .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 25 +---------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 26 ++++++++---- .../Renderer/Vulkan/VulkanRendererCore.cpp | 35 +++------------- 6 files changed, 69 insertions(+), 90 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 3a1c1cf6..77f9c734 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -311,6 +311,7 @@ void LatteCP_itSetRegistersGeneric_handleSpecialRanges(uint32 registerStartIndex template LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) { + nWords--; // subtract the register offset field uint32 registerOffset = LatteReadCMD(); uint32 registerIndex = TRegisterBase + registerOffset; uint32 registerStartIndex = registerIndex; @@ -324,7 +325,7 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) // state shadowing enabled uint32* __restrict shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; - while (--nWords) + while (nWords--) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; @@ -337,10 +338,19 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) else { // state shadowing disabled - while (--nWords) + if (nWords == 1) // common case { *outputReg = LatteReadCMD(); - outputReg++; + } + else + { + sint32 i = 0; + while (i < nWords) + { + outputReg[i] = cmd[i]; + i++; + } + cmd += nWords; } } // some register writes trigger special behavior @@ -352,6 +362,7 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) template bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange) { + nWords--; const uint32 registerOffset = LatteReadCMD(); const uint32 registerIndex = TRegisterBase + registerOffset; const uint32 registerStartIndex = registerIndex; @@ -365,7 +376,7 @@ bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCal // state shadowing enabled uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; - while (--nWords) + while (nWords--) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; @@ -379,13 +390,23 @@ bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCal else { // state shadowing disabled - sint32 indexCounter = 0; - while (--nWords) + if (nWords == 1) // common case { - uint32 dataWord = LatteReadCMD(); - hasRegChange |= (*outputReg != dataWord); - *outputReg = dataWord; - outputReg++; + uint32 v = LatteReadCMD(); + hasRegChange |= (*outputReg != v); + *outputReg = v; + } + else + { + sint32 i = 0; + while (i < nWords) + { + uint32 v = cmd[i]; + hasRegChange |= (outputReg[i] != v); + outputReg[i] = v; + i++; + } + cmd += nWords; } } // some register writes trigger special behavior diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h index ebe741e9..07711464 100644 --- a/src/Cafe/HW/Latte/Core/LatteConst.h +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -109,16 +109,13 @@ namespace LatteConst { enum class ShaderType : uint32 { - Reserved = 0, // shaders for drawing - FirstRender = 1, - Vertex = 1, - Pixel = 2, - Geometry = 3, - LastRender = 3, + Vertex = 0, + Pixel = 1, + Geometry = 2, // compute shader - Compute = 4, - TotalCount = 5 + Compute = 3, + TotalCount = 4 }; enum class VertexFetchNFA diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index c2c3234e..fbd4292b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -58,20 +58,13 @@ PipelineInfo::~PipelineInfo() } // delete descriptor sets - while (!pixel_ds_cache.empty()) + for (auto& it : ds_cache) { - VkDescriptorSetInfo* dsInfo = pixel_ds_cache.begin()->second; - delete dsInfo; - } - while (!geometry_ds_cache.empty()) - { - VkDescriptorSetInfo* dsInfo = geometry_ds_cache.begin()->second; - delete dsInfo; - } - while (!vertex_ds_cache.empty()) - { - VkDescriptorSetInfo* dsInfo = vertex_ds_cache.begin()->second; - delete dsInfo; + while (!it.empty()) + { + VkDescriptorSetInfo* dsInfo = it.begin()->second; + delete dsInfo; + } } // disassociate from shaders diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index ddff392f..136cc76b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -3374,29 +3374,8 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo() for (auto& it : list_referencedViews) it->RemoveDescriptorSetReference(this); // unregister - switch (shaderType) - { - case LatteConst::ShaderType::Vertex: - { - auto r = pipeline_info->vertex_ds_cache.erase(stateHash); - cemu_assert_debug(r == 1); - break; - } - case LatteConst::ShaderType::Pixel: - { - auto r = pipeline_info->pixel_ds_cache.erase(stateHash); - cemu_assert_debug(r == 1); - break; - } - case LatteConst::ShaderType::Geometry: - { - auto r = pipeline_info->geometry_ds_cache.erase(stateHash); - cemu_assert_debug(r == 1); - break; - } - default: - UNREACHABLE; - } + auto r = pipeline_info->GetDescriptorSetCache(shaderType).erase(stateHash); + cemu_assert_debug(r == 1); // update global stats performanceMonitor.vk.numDescriptorSamplerTextures.decrement(statsNumSamplerTextures); performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 47041972..94c94f67 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -55,12 +55,17 @@ private: namespace VulkanRendererConst { - static const inline int SHADER_STAGE_INDEX_VERTEX = 0; - static const inline int SHADER_STAGE_INDEX_FRAGMENT = 1; - static const inline int SHADER_STAGE_INDEX_GEOMETRY = 2; - static const inline int SHADER_STAGE_INDEX_COUNT = 3; + static const inline int SHADER_STAGE_INDEX_VERTEX = static_cast(LatteConst::ShaderType::Vertex); + static const inline int SHADER_STAGE_INDEX_FRAGMENT = static_cast(LatteConst::ShaderType::Pixel); + static const inline int SHADER_STAGE_INDEX_GEOMETRY = static_cast(LatteConst::ShaderType::Geometry); + static const inline int SHADER_STAGE_INDEX_COUNT = 4; }; +// the order doesnt really matter but the types should cover range 0-2 since we use them as an array index +static_assert(static_cast(LatteConst::ShaderType::Vertex) == 0); +static_assert(static_cast(LatteConst::ShaderType::Pixel) == 1); +static_assert(static_cast(LatteConst::ShaderType::Geometry) == 2); + class PipelineInfo { public: @@ -82,11 +87,18 @@ public: return k; } }; + using DescriptorSetCache = ska::flat_hash_map>; + + FORCEINLINE DescriptorSetCache& GetDescriptorSetCache(LatteConst::ShaderType shaderType) + { + cemu_assert_debug(shaderType == LatteConst::ShaderType::Vertex || shaderType == LatteConst::ShaderType::Pixel || shaderType == LatteConst::ShaderType::Geometry); + return ds_cache[static_cast(shaderType)]; + } // std::unordered_map 3.16% (total CPU time) - // robin_hood::unordered_flat_map vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; ~1.80% - // ska::bytell_hash_map> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; -> 1.91% - ska::flat_hash_map> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; // 1.71% + // robin_hood::unordered_flat_map descriptor set cache; ~1.80% + // ska::bytell_hash_map> descriptor set cache; -> 1.91% + DescriptorSetCache ds_cache[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; // 1.71% VKRObjectPipeline* m_vkrObjPipeline; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index a29caeb4..f9b1f714 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -590,31 +590,27 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader) { const uint64 stateHash = GetDescriptorSetStateHash(shader); + cemu_assert_debug(shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Pixel || shader->shaderType == LatteConst::ShaderType::Geometry); + auto& ds_cache = pipeline_info->GetDescriptorSetCache(shader->shaderType); + const auto it = ds_cache.find(stateHash); + if (it != ds_cache.cend()) + return it->second; VkDescriptorSetLayout descriptor_set_layout; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: { - const auto it = pipeline_info->vertex_ds_cache.find(stateHash); - if (it != pipeline_info->vertex_ds_cache.cend()) - return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_vertexDSL; break; } case LatteConst::ShaderType::Pixel: { - const auto it = pipeline_info->pixel_ds_cache.find(stateHash); - if (it != pipeline_info->pixel_ds_cache.cend()) - return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_pixelDSL; break; } case LatteConst::ShaderType::Geometry: { - const auto it = pipeline_info->geometry_ds_cache.find(stateHash); - if (it != pipeline_info->geometry_ds_cache.cend()) - return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_geometryDSL; break; } @@ -1006,26 +1002,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* if (!descriptorWrites.empty()) vkUpdateDescriptorSets(m_logicalDevice, (uint32)descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); - switch (shader->shaderType) - { - case LatteConst::ShaderType::Vertex: - { - pipeline_info->vertex_ds_cache[stateHash] = dsInfo; - break; - } - case LatteConst::ShaderType::Pixel: - { - pipeline_info->pixel_ds_cache[stateHash] = dsInfo; - break; - } - case LatteConst::ShaderType::Geometry: - { - pipeline_info->geometry_ds_cache[stateHash] = dsInfo; - break; - } - default: - UNREACHABLE; - } + ds_cache[stateHash] = dsInfo; return dsInfo; } From a39e99ecd41039e712bb96ca8d7ae50c4a454f4f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 17 Jun 2026 05:11:44 +0200 Subject: [PATCH 09/14] h264: Improve H264DECMemoryRequirement --- src/Cafe/OS/libs/h264_avc/H264Dec.cpp | 34 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp index 108b2f3c..d03ca553 100644 --- a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp +++ b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp @@ -39,7 +39,7 @@ namespace H264 struct { MEMPTR outputFunc{ nullptr }; - uint8be outputPerFrame{ 0 }; // whats the default? + uint8be outputPerFrame{ 0 }; // default is 0 MEMPTR userMemoryParam{ nullptr }; }Param; // misc @@ -52,18 +52,18 @@ namespace H264 }decoderState; }; - uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) + H264DEC_STATUS H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) { if (H264_IsBotW()) { static_assert(sizeof(H264Context) < 256); *sizeRequirementOut = 256; - return 0; + return H264DEC_STATUS::SUCCESS; } // note: On console this seems to check if maxWidth or maxHeight < 64 but Pikmin 3 passes 32x32 and crashes if this function fails ? - if (width < 0x20 || height < 0x20 || width > 2800 || height > 1408 || sizeRequirementOut == MPTR_NULL || codecLevel >= 52 || (codecProfile != 0x42 && codecProfile != 0x4D && codecProfile != 0x64)) - return 0x1010000; + if (width < 32 || height < 32 || width > 2800 || height > 1408 || !sizeRequirementOut || codecLevel >= 52 || (codecProfile != 66 && codecProfile != 77 && codecProfile != 100)) + return H264DEC_STATUS::INVALID_PARAM; uint32 workbufferSize = 0; if (codecLevel < 0xB) @@ -112,7 +112,7 @@ namespace H264 } workbufferSize += 0x447; *sizeRequirementOut = workbufferSize; - return 0; + return H264DEC_STATUS::SUCCESS; } uint32 H264DECCheckMemSegmentation(MPTR memory, uint32 size) @@ -189,16 +189,15 @@ namespace H264 return H264DEC_STATUS::BAD_STREAM; } - H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight) + H264DEC_STATUS H264DECGetImageSize(uint8* stream, sint32 streamSize, sint32 offset, uint32be* outputWidth, uint32be* outputHeight) { - if(!stream || length < 4 || !outputWidth || !outputHeight) + if (!stream || streamSize < 4 || offset < 0 || !outputWidth || !outputHeight || offset < streamSize) return H264DEC_STATUS::INVALID_PARAM; - if( (offset+4) > length ) + if ( (offset+4) >= streamSize ) return H264DEC_STATUS::INVALID_PARAM; uint8* cur = stream + offset; - uint8* end = stream + length; - cur += 2; // we access cur[-2] and cur[-1] so we need to start at offset 2 - while(cur < end-2) + uint8* end = stream + streamSize; + while (cur < end-2) { // check for start code if(*cur != 1) @@ -207,7 +206,7 @@ namespace H264 continue; } // check if this is a valid NAL header - if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) + if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) // if offset is < 2, this will read out of bounds. The console implementation has this behavior too so we have to replicate this bug { cur++; continue; @@ -226,7 +225,14 @@ namespace H264 return H264DEC_STATUS::BAD_STREAM; } *outputWidth = (psp.pic_width_in_mbs_minus1 + 1) * 16; - *outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; // affected by frame_mbs_only_flag? + *outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; + if (!psp.frame_mbs_only_flag) + *outputHeight *= 2; + if (!*outputHeight || !*outputWidth) + return H264DEC_STATUS::BAD_STREAM; + // BotW 1080p video support + if (H264_IsBotW() && *outputWidth == 1920 && *outputHeight == 1088) + *outputHeight = 1080; return H264DEC_STATUS::SUCCESS; } return H264DEC_STATUS::BAD_STREAM; From 3923bfcc0127fecf1aed946bc1baf005f768f99b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:04:30 +0200 Subject: [PATCH 10/14] Latte: Optimize shader lookup by caching whole sets Skips the overhead of having to lookup each shader stage individually. Additionally, having whole sets as a lookup key gives us a more trustworthy heuristic than if we only have independent information for each shader stage. We can use this to skip shader hashing most of the time when they are part of a known set. --- src/Cafe/HW/Latte/Core/FetchShader.cpp | 5 + src/Cafe/HW/Latte/Core/FetchShader.h | 7 +- .../HW/Latte/Core/LatteCommandProcessor.cpp | 1 - src/Cafe/HW/Latte/Core/LatteOverlay.cpp | 3 + src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 1 + src/Cafe/HW/Latte/Core/LatteShader.cpp | 389 ++++++++++++++---- src/Cafe/HW/Latte/Core/LatteShader.h | 3 +- .../LegacyShaderDecompiler/LatteDecompiler.h | 2 + src/util/containers/LookupTableL3.h | 15 +- src/util/helpers/StateHasher.h | 42 ++ 10 files changed, 377 insertions(+), 91 deletions(-) create mode 100644 src/util/helpers/StateHasher.h diff --git a/src/Cafe/HW/Latte/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp index e705d3d1..0779fac9 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.cpp +++ b/src/Cafe/HW/Latte/Core/FetchShader.cpp @@ -14,6 +14,8 @@ #include /* SHA1_DIGEST_LENGTH */ #include /* EVP_Digest */ +void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key); + uint32 LatteShaderRecompiler_getAttributeSize(LatteParsedFetchShaderAttribute_t* attrib) { if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT) @@ -468,6 +470,9 @@ LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::Cach LatteFetchShader::~LatteFetchShader() { UnregisterInCache(); + // remove from shader state cache + while (!m_shaderStateCacheKeys.empty()) + LatteSHRC_RemoveShaderStateCacheEntryByKey(m_shaderStateCacheKeys.back()); } struct FetchShaderLookupInfo diff --git a/src/Cafe/HW/Latte/Core/FetchShader.h b/src/Cafe/HW/Latte/Core/FetchShader.h index aac084f2..257cb2f1 100644 --- a/src/Cafe/HW/Latte/Core/FetchShader.h +++ b/src/Cafe/HW/Latte/Core/FetchShader.h @@ -64,11 +64,12 @@ struct LatteFetchShader static bool isValidBufferIndex(const uint32 index) { return index < 0x10; }; - // cache - LatteFetchShader* RegisterInCache(CacheHash fsHash); // Fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null - void UnregisterInCache(); + // keys in shader state cache + std::vector m_shaderStateCacheKeys; // fetch shader cache (move these to separate Cache class?) + LatteFetchShader* RegisterInCache(CacheHash fsHash); // fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null + void UnregisterInCache(); static CacheHash CalculateCacheHash(void* programCode, uint32 programSize); static LatteFetchShader* FindInCacheByHash(CacheHash fsHash); static LatteFetchShader* FindByGPUState(); diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 77f9c734..6e377575 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -1262,7 +1262,6 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) case IT_DRAW_INDEX_2: { drawPassCtx.beginDrawPass(); - //cemuLog_log(LogType::Force, "[CmdBuf] DrawIndex2"); LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); // enter fast draw mode drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp index 1a1a560c..8fdc8f3e 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -35,6 +35,8 @@ struct OverlayStats extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; +extern std::atomic_int g_shaderStateCacheSetCount; +extern std::atomic_int g_shaderStateCacheSetAuxCount; std::atomic_int g_compiling_pipelines; std::atomic_int g_compiling_pipelines_async; @@ -110,6 +112,7 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio // general debug info ImGui::Text("--- Debug info ---"); ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024); + ImGui::Text("SHCSets: %d / %d", g_shaderStateCacheSetCount.load(), g_shaderStateCacheSetAuxCount.load()); // backend specific info g_renderer->AppendOverlayDebugInfo(); } diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 148a6dce..79b2d694 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -689,6 +689,7 @@ void LatteRenderTarget_itHLESwapScanBuffer() performanceMonitor.gpuTime_frameTime.beginMeasuring(); LatteTC_CleanupUnusedTextures(); + LatteSHRC_CleanupShaderStateCache(); #ifdef ENABLE_OPENGL LatteDraw_cleanupAfterFrame(); #endif diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index dbbf9fbf..38fc5504 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -15,10 +15,10 @@ #include "config/ActiveSettings.h" #include "Cafe/GameProfile/GameProfile.h" #include "util/containers/flat_hash_map.hpp" +#include "util/helpers/StateHasher.h" #ifdef ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #endif -#include // experimental new decompiler (WIP) #include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h" @@ -56,6 +56,8 @@ uint64 _shaderBaseHash_ps; std::atomic_int g_compiled_shaders_total = 0; std::atomic_int g_compiled_shaders_async = 0; +std::atomic_int g_shaderStateCacheSetCount = 0; +std::atomic_int g_shaderStateCacheSetAuxCount = 0; LatteFetchShader* LatteSHRC_GetActiveFetchShader() { @@ -87,6 +89,8 @@ inline ska::flat_hash_map& LatteSHRC_GetCacheByT return sPixelShaders; } +void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key); + // calculate hash from shader binary // this algorithm could be more efficient since we could leverage the fact that the size is always aligned to 8 byte // but since this is baked into the shader names used for gfx packs and shader caches we can't really change this @@ -149,7 +153,7 @@ LatteShaderPSInputTable* LatteSHRC_GetPSInputTable() return &_activePSImportTable; } -void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) +void LatteSHRC_RemoveFromCaches(LatteDecompilerShader* shader) { bool removed = false; auto& cache = LatteSHRC_GetCacheByType(shader->shaderType); @@ -186,6 +190,10 @@ void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) } } cemu_assert(removed); + // remove from shader state cache + // deleting by key means we delete all the other aux variants associated with it too, but it keeps the code simple and cache entries are cheap to recreate anyway + while (!shader->m_shaderStateCacheKeys.empty()) + LatteSHRC_RemoveShaderStateCacheEntryByKey(shader->m_shaderStateCacheKeys.back()); } void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type) @@ -198,12 +206,12 @@ void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_ else if (type == LatteConst::ShaderType::Pixel) shader = LatteSHRC_FindPixelShader(shader_base_hash, shader_aux_hash); if (shader) - LatteSHRC_RemoveFromCache(shader); + LatteSHRC_RemoveFromCaches(shader); } -void LatteShader_free(LatteDecompilerShader* shader) +void LatteShader_free(LatteDecompilerShader* shader) // todo - make this ~LatteDecompilerShader() { - LatteSHRC_RemoveFromCache(shader); + LatteSHRC_RemoveFromCaches(shader); if (shader->shader) delete shader->shader; shader->shader = nullptr; @@ -436,10 +444,9 @@ LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash return LatteSHRC_Get(sPixelShaders, baseHash, auxHash); } -// update the currently active fetch shader -void LatteShaderSHRC_UpdateFetchShader() +LatteFetchShader* LatteSHRC_GetOrCreateFetchShader() { - _activeFetchShader = LatteFetchShader::FindByGPUState(); + return LatteFetchShader::FindByGPUState(); } void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader) @@ -499,14 +506,14 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin } } -void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) +void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) { uint32* vsProgramCode = (uint32*)vertexShaderPtr; // update hash from vertex shader data uint64 vsHash1 = 0; uint64 vsHash2 = 0; _calculateShaderProgramHash(vsProgramCode, vertexShaderSize, &hashCacheVS, &vsHash1, &vsHash2); - uint64 vsHash = vsHash1 + vsHash2 + _activeFetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL); + uint64 vsHash = vsHash1 + vsHash2 + fetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL); uint32 tmp = LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() ^ 0x43F; vsHash += tmp; @@ -532,11 +539,11 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, { bool isRectVertexShader = (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); - if ((usesGeometryShader || isRectVertexShader) || _activeFetchShader->mtlFetchVertexManually) + if ((usesGeometryShader || isRectVertexShader) || fetchShader->mtlFetchVertexManually) { - for (sint32 g = 0; g < _activeFetchShader->bufferGroups.size(); g++) + for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++) { - LatteParsedFetchShaderBufferGroup_t& group = _activeFetchShader->bufferGroups[g]; + LatteParsedFetchShaderBufferGroup_t& group = fetchShader->bufferGroups[g]; uint32 bufferIndex = group.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; @@ -552,7 +559,7 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, vsHash += 51ULL; // Vertex fetch - if (_activeFetchShader->mtlFetchVertexManually) + if (fetchShader->mtlFetchVertexManually) vsHash += 349ULL; } } @@ -881,17 +888,17 @@ LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash, return vertexShader; } -LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) +LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize, LatteDecompilerShader* vertexShader) { LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Geometry, true); LatteDecompilerOutput_t decompilerOutput{}; - LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, _activeVertexShader->ringParameterCount, options, &decompilerOutput); + LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, vertexShader->ringParameterCount, options, &decompilerOutput); LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); if (geometryShader->hasError == false) { - LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), _activeVertexShader->ringParameterCount); + LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), vertexShader->ringParameterCount); } LatteShader_DumpShader(geometryShader->baseHash, geometryShader->auxHash, geometryShader); LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderPtr, geometryShaderSize); @@ -943,10 +950,10 @@ LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash, return pixelShader; } -void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) +LatteDecompilerShader* LatteSHRC_GetOrCreateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) { // todo - should include VTX_SEMANTIC table in state - LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader); + LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader); uint64 vsAuxHash = 0; auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs); LatteDecompilerShader* vertexShader = nullptr; @@ -956,22 +963,18 @@ void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSiz vertexShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_vs, vsAuxHash); } if (!vertexShader) - vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, _activeFetchShader); + vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader); if (vertexShader->hasError) - { LatteGPUState.activeShaderHasError = true; - return; - } - _activeVertexShader = vertexShader; + return vertexShader; } -void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) +LatteDecompilerShader* LatteSHRC_GetOrCreateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize, LatteDecompilerShader* vertexShader) { if (!usesGeometryShader || !_activeVertexShader) { _shaderBaseHash_gs = 0; - _activeGeometryShader = nullptr; - return; + return nullptr; } LatteSHRC_UpdateGSBaseHash(geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); auto itBaseShader = sGeometryShaders.find(_shaderBaseHash_gs); @@ -985,17 +988,14 @@ void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShad else { // decompile geometry shader - geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); + geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, vertexShader); } if (geometryShader->hasError) - { LatteGPUState.activeShaderHasError = true; - return; - } - _activeGeometryShader = geometryShader; + return geometryShader; } -void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) +LatteDecompilerShader* LatteSHRC_GetOrCreatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) { LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader); uint64 psAuxHash = 0; @@ -1009,11 +1009,143 @@ void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, if (!pixelShader) pixelShader = LatteShader_CompileSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, usesGeometryShader); if (pixelShader->hasError) - { LatteGPUState.activeShaderHasError = true; + return pixelShader; +} + +static inline uint64_t mix64(uint64_t v) +{ + v ^= v >> 30; + v *= 0xbf58476d1ce4e5b9ULL; + v ^= v >> 27; + v *= 0x94d049bb133111ebULL; + v ^= v >> 31; + return v; +} + +struct ShaderStateInfoAuxVariant +{ + uint64 combinedAuxHash{}; + LatteDecompilerShader* vertexShader{nullptr}; + LatteDecompilerShader* pixelShader{nullptr}; + LatteDecompilerShader* geometryShader{nullptr}; + bool hasError{false}; // in case of error just set all the shaders to nullptr? +}; + +struct ShaderStateInfo +{ + // we need any set of shaders from an aux chain to calculate the actual aux variant hash + // so these just match the first one encountered + LatteFetchShader* fetchShader{nullptr}; + LatteDecompilerShader* vertexShader{nullptr}; + LatteDecompilerShader* pixelShader{nullptr}; + LatteDecompilerShader* geometryShader{nullptr}; + uint64 combinedAuxHash{}; + uint32 lastAccessFrameCount{}; + bool shaderError{false}; + std::vector auxVariants; +}; + +struct ShaderStateDirectHash +{ + size_t operator()(uint64 x) const noexcept + { + return x; + } +}; + +std::vector s_shaderStateCacheKeys; +size_t s_shaderStateCacheCleanupIndex = 0; +robin_hood::unordered_flat_map s_shaderStateCache; +// also benchmarked here but didn't perform better or only marginally better and not worth pulling in an extra library: +// jg::dense_hash_map +// folly::F14FastMap +// robin_hood::unordered_flat_map ended up performing close to best and was choosen because we already have it included in the project anyway + +FORCEINLINE uint64 CalcCombinedAuxHash(LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader) +{ + uint64 vsAuxHash = vertexShader ? LatteSHRC_CalcVSAuxHash(vertexShader, LatteGPUState.contextRegister) : 0; + uint64 psAuxHash = pixelShader ? LatteSHRC_CalcPSAuxHash(pixelShader, LatteGPUState.contextRegister) : 0; + uint64 combinedAuxHash = vsAuxHash + mix64(psAuxHash); +#ifdef ENABLE_METAL + if (g_renderer->GetType() == RendererAPI::Metal && fetchShader) + { + for (auto& bufferGroup : fetchShader->bufferGroups) + { + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferGroup.attributeBufferIndex * 7; + uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + combinedAuxHash = std::rotl(combinedAuxHash, 7); + combinedAuxHash += bufferStride; + } + } +#endif + return combinedAuxHash; +} + +void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key) +{ + auto it = s_shaderStateCache.find(key); + if (it == s_shaderStateCache.end()) + { + cemu_assert_suspicious(); // shader shouldn't have a key which was already removed return; } - _activePixelShader = pixelShader; + ShaderStateInfo& shaderStateInfo = it->second; + if (shaderStateInfo.fetchShader) + std::erase(shaderStateInfo.fetchShader->m_shaderStateCacheKeys, key); + g_shaderStateCacheSetAuxCount -= shaderStateInfo.auxVariants.size(); + for (auto& auxVariant : shaderStateInfo.auxVariants) + { + if (auxVariant.vertexShader) + std::erase(auxVariant.vertexShader->m_shaderStateCacheKeys, key); + if (auxVariant.pixelShader) + std::erase(auxVariant.pixelShader->m_shaderStateCacheKeys, key); + if (auxVariant.geometryShader) + std::erase(auxVariant.geometryShader->m_shaderStateCacheKeys, key); + } + s_shaderStateCache.erase(it); + --g_shaderStateCacheSetCount; +} + +void LatteSHRC_CleanupShaderStateCache() +{ + constexpr uint32 NUM_FRAMES_UNTIL_EXPIRE = 5 * 60; // entries expire after not being used for 5 seconds at 60 FPS + constexpr sint32 MAX_CHECKS_PER_FRAME = 30; + constexpr sint32 MAX_DELETES_PER_FRAME = 8; + + if (s_shaderStateCache.empty()) + { + s_shaderStateCacheKeys.clear(); + s_shaderStateCacheCleanupIndex = 0; + return; + } + + sint32 deleteCount = 0; + for (sint32 i = 0; i < MAX_CHECKS_PER_FRAME && !s_shaderStateCacheKeys.empty(); i++) + { + if (s_shaderStateCacheCleanupIndex >= s_shaderStateCacheKeys.size()) + s_shaderStateCacheCleanupIndex = 0; + uint64 key = s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex]; + auto it = s_shaderStateCache.find(key); + if (it == s_shaderStateCache.end()) + { + s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex] = s_shaderStateCacheKeys.back(); + s_shaderStateCacheKeys.pop_back(); + continue; + } + uint32 framesSinceLastAccess = LatteGPUState.frameCounter - it->second.lastAccessFrameCount; + if (framesSinceLastAccess >= NUM_FRAMES_UNTIL_EXPIRE) + { + if (deleteCount >= MAX_DELETES_PER_FRAME) + break; + LatteSHRC_RemoveShaderStateCacheEntryByKey(key); + s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex] = s_shaderStateCacheKeys.back(); + s_shaderStateCacheKeys.pop_back(); + deleteCount++; + continue; + } + s_shaderStateCacheCleanupIndex++; + } } void LatteSHRC_UpdateActiveShaders() @@ -1024,66 +1156,158 @@ void LatteSHRC_UpdateActiveShaders() cemu_assert_debug(LatteGPUState.contextNew.VGT_GS_MODE.get_ES_PASSTHRU() == false); // todo: Support for ES passthrough and cut mode in mmVGT_GS_MODE - bool geometryShaderUsed = false; - if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF) - { - geometryShaderUsed = false; - } - else if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G) - { - // could also be compute shader? - geometryShaderUsed = true; - } - else - { - cemu_assert_debug(false); - } - // get shader programs - uint8* psProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_PS] & 0xFFFFFF) << 8); - uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3; - uint8* gsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_GS] & 0xFFFFFF) << 8); - uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3; + cemu_assert_debug(gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF || gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G); // other modes are not supported + bool geometryShaderUsed = gsMode != Latte::LATTE_VGT_GS_MODE::E_MODE::OFF; - uint8* vsProgramCode; - uint32 vsProgramSize; - uint8* copyProgramCode = NULL; + // get program pointers and sizes + uint32 fsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_FS] << 8; + uint32 fsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3; + uint32 vsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_VS] << 8; + uint32 vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; + uint32 psProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_PS] << 8; + uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3; + uint32 gsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_GS] << 8; + uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3; + uint32 copyProgramAddr = 0; uint32 copyProgramSize = 0; + if (geometryShaderUsed) { - vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); + // VS parameters come from ES instead + copyProgramAddr = vsProgramAddr; + copyProgramSize = vsProgramSize; + vsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_ES] << 8; vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; - copyProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); - if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]] - { - copyProgramCode = NULL; - debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?"); - } - copyProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; } else { - if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]] + gsProgramAddr = 0; + gsProgramSize = 0; + if (vsProgramAddr == 0 || vsProgramSize == 0) { + // todo - we dont really need to handle this since invalid shader states get baked into the lookup cache anyway debug_printf("No vertex shader program set\n"); LatteGPUState.activeShaderHasError = true; return; } - vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); - vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; } - // set new shaders + // build a hash from the combined shader state + // we use this as a lookup into a "shader state" cache rather than looking up all the shader stages individually + DualStateHasher hasher; + + uint64_t f = ((uint64_t)fsProgramAddr << 32) | (uint64_t)fsProgramSize; + uint64_t a = ((uint64_t)vsProgramAddr << 32) | (uint64_t)vsProgramSize; + uint64_t b = ((uint64_t)psProgramAddr << 32) | (uint64_t)psProgramSize; + uint64_t c = ((uint64_t)gsProgramAddr << 32) | (uint64_t)gsProgramSize; + uint64_t d = ((uint64_t)copyProgramAddr << 32) | (uint64_t)copyProgramSize; + + hasher.MixIn(f, a); + hasher.MixIn(b, c); + + constexpr uint32 PA_CL_VTE_CNTL_MASK = 0x3F; // viewport scale and offset enable bits + constexpr uint32 PA_CL_CLIP_CNTL_MASK = 1 << 19; // DX_CLIP_SPACE_DEF (halfZ) + constexpr uint32 VGT_PRIMITIVE_TYPE_MASK = 0x3F; + constexpr uint32 SPI_PS_IN_CONTROL_0_MASK = 0x3F | (1 << 8) | (0x1F << 10) | (0xF << 15) | (0x7F << 19); + constexpr uint32 SPI_PS_IN_CONTROL_1_MASK = 0x1FF << 8; // front-face settings (gl_FrontFacing) + constexpr uint32 SPI_INTERP_CONTROL_0_MASK = 1 << 1; // point sprite coord enable + + uint64 baseState0 = ((uint64)(LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() & PA_CL_VTE_CNTL_MASK) << 32) | (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.getRawValue() & PA_CL_CLIP_CNTL_MASK); + uint64 baseState1 = ((uint64)(LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.getRawValue() & VGT_PRIMITIVE_TYPE_MASK) << 32) | LatteGPUState.contextRegister[mmVGT_STRMOUT_EN]; + hasher.MixIn(baseState0, baseState1); + + LatteShader_UpdatePSInputs(LatteGPUState.contextRegister); // updates _activePSImportTable + + hasher.MixIn(d, _activePSImportTable.key); + hasher.MixIn(LatteGPUState.contextRegister[mmSPI_PS_IN_CONTROL_0] & SPI_PS_IN_CONTROL_0_MASK, LatteGPUState.contextRegister[mmSPI_PS_IN_CONTROL_1] & SPI_PS_IN_CONTROL_1_MASK); + hasher.MixIn(LatteGPUState.contextRegister[mmSPI_INTERP_CONTROL_0] & SPI_INTERP_CONTROL_0_MASK, 0); +#ifdef ENABLE_METAL + if (g_renderer->GetType() == RendererAPI::Metal) + { + uint64 mtlState = LatteGPUState.contextNew.IsRasterizationEnabled() ? 1 : 0; + hasher.MixInSingle(mtlState); + } +#endif + uint64 h = hasher.Finish(); + LatteGPUState.activeShaderHasError = false; - LatteShader_UpdatePSInputs(LatteGPUState.contextRegister); - LatteShaderSHRC_UpdateFetchShader(); - LatteSHRC_UpdateVertexShader(vsProgramCode, vsProgramSize, geometryShaderUsed); - if (LatteGPUState.activeShaderHasError) - return; - LatteSHRC_UpdateGeometryShader(geometryShaderUsed, gsProgramCode, gsProgramSize, copyProgramCode, copyProgramSize); - if (LatteGPUState.activeShaderHasError) - return; - LatteSHRC_UpdatePixelShader(psProgramCode, psProgramSize, geometryShaderUsed); - if (LatteGPUState.activeShaderHasError) - return; + + ShaderStateInfo* shaderStateInfo = nullptr; + + auto it = s_shaderStateCache.find(h); + if (it != s_shaderStateCache.end()) + { + shaderStateInfo = &it->second; + shaderStateInfo->lastAccessFrameCount = LatteGPUState.frameCounter; + uint64 combinedAuxHash = CalcCombinedAuxHash(shaderStateInfo->fetchShader, shaderStateInfo->vertexShader, shaderStateInfo->pixelShader); + if (shaderStateInfo->combinedAuxHash == combinedAuxHash) [[likely]] + { + _activeFetchShader = shaderStateInfo->fetchShader; + _activeVertexShader = shaderStateInfo->vertexShader; + _activePixelShader = shaderStateInfo->pixelShader; + _activeGeometryShader = shaderStateInfo->geometryShader; + return; + } + for (auto& auxVariant : shaderStateInfo->auxVariants) + { + if (auxVariant.combinedAuxHash == combinedAuxHash) + { + _activeFetchShader = shaderStateInfo->fetchShader; + _activeVertexShader = auxVariant.vertexShader; + _activePixelShader = auxVariant.pixelShader; + _activeGeometryShader = auxVariant.geometryShader; + return; + } + } + } + // no cache entry found, get/create shaders individually and add to cache + LatteFetchShader* fetchShader = LatteSHRC_GetOrCreateFetchShader(); + _activeFetchShader = fetchShader; + bool shaderError = LatteGPUState.activeShaderHasError; + LatteDecompilerShader* vertexShader = LatteSHRC_GetOrCreateVertexShader((uint8*)memory_getPointerFromPhysicalOffset(vsProgramAddr), vsProgramSize, geometryShaderUsed, fetchShader); + shaderError |= LatteGPUState.activeShaderHasError; + LatteDecompilerShader* pixelShader = LatteSHRC_GetOrCreatePixelShader((uint8*)memory_getPointerFromPhysicalOffset(psProgramAddr), psProgramSize, geometryShaderUsed); + shaderError |= LatteGPUState.activeShaderHasError; + LatteDecompilerShader* geometryShader = LatteSHRC_GetOrCreateGeometryShader(geometryShaderUsed, (uint8*)memory_getPointerFromPhysicalOffset(gsProgramAddr), gsProgramSize, (uint8*)memory_getPointerFromPhysicalOffset(copyProgramAddr), copyProgramSize, vertexShader); + shaderError |= LatteGPUState.activeShaderHasError; + uint64 combinedAuxHash = CalcCombinedAuxHash(fetchShader, vertexShader, pixelShader); + + if (!shaderStateInfo) + { + // create base entry + shaderStateInfo = &s_shaderStateCache[h]; + s_shaderStateCacheKeys.emplace_back(h); + shaderStateInfo->shaderError = shaderError; + shaderStateInfo->fetchShader = fetchShader; + shaderStateInfo->vertexShader = vertexShader; + shaderStateInfo->pixelShader = pixelShader; + shaderStateInfo->geometryShader = geometryShader; + shaderStateInfo->lastAccessFrameCount = LatteGPUState.frameCounter; + shaderStateInfo->combinedAuxHash = combinedAuxHash; + if (shaderStateInfo->fetchShader) + shaderStateInfo->fetchShader->m_shaderStateCacheKeys.emplace_back(h); + ++g_shaderStateCacheSetCount; + } + + ShaderStateInfoAuxVariant auxVariant; + auxVariant.combinedAuxHash = combinedAuxHash; + auxVariant.vertexShader = vertexShader; + auxVariant.pixelShader = pixelShader; + auxVariant.geometryShader = geometryShader; + auxVariant.hasError = shaderError; + if (auxVariant.vertexShader) + vectorAppendUnique(auxVariant.vertexShader->m_shaderStateCacheKeys, h); + if (auxVariant.pixelShader) + vectorAppendUnique(auxVariant.pixelShader->m_shaderStateCacheKeys, h); + if (auxVariant.geometryShader) + vectorAppendUnique(auxVariant.geometryShader->m_shaderStateCacheKeys, h); + shaderStateInfo->auxVariants.emplace_back(auxVariant); + ++g_shaderStateCacheSetAuxCount; + + // set shaders as active + _activeFetchShader = shaderStateInfo->fetchShader; + _activeVertexShader = auxVariant.vertexShader; + _activePixelShader = auxVariant.pixelShader; + _activeGeometryShader = auxVariant.geometryShader; } // returns the sampler base index for the given shader type @@ -1105,6 +1329,8 @@ void LatteSHRC_Init() cemu_assert_debug(sVertexShaders.empty()); cemu_assert_debug(sGeometryShaders.empty()); cemu_assert_debug(sPixelShaders.empty()); + cemu_assert_debug(s_shaderStateCache.empty()); + cemu_assert_debug(s_shaderStateCacheKeys.empty()); } void LatteSHRC_UnloadAll() @@ -1118,4 +1344,7 @@ void LatteSHRC_UnloadAll() while(!sPixelShaders.empty()) LatteShader_free(sPixelShaders.begin()->second); cemu_assert_debug(sPixelShaders.empty()); + cemu_assert_debug(s_shaderStateCache.empty()); + s_shaderStateCacheKeys.clear(); + s_shaderStateCacheCleanupIndex = 0; } diff --git a/src/Cafe/HW/Latte/Core/LatteShader.h b/src/Cafe/HW/Latte/Core/LatteShader.h index 85d53b01..3844e9f7 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.h +++ b/src/Cafe/HW/Latte/Core/LatteShader.h @@ -6,9 +6,10 @@ void LatteSHRC_Init(); void LatteSHRC_UnloadAll(); void LatteSHRC_ResetCachedShaderHash(); -void LatteShaderSHRC_UpdateFetchShader(); +LatteFetchShader* LatteSHRC_GetOrCreateFetchShader(); void LatteSHRC_UpdateActiveShaders(); +void LatteSHRC_CleanupShaderStateCache(); struct LatteFetchShader* LatteSHRC_GetActiveFetchShader(); LatteDecompilerShader* LatteSHRC_GetActiveVertexShader(); diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h index c95f8098..5e1421ef 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -216,6 +216,8 @@ struct LatteDecompilerShader }; std::vector list_remappedUniformEntries_register; std::vector<_RemappedUniformBufferGroup> list_remappedUniformEntries_bufferGroups; + // keys in shader state cache + std::vector m_shaderStateCacheKeys; }; struct LatteDecompilerOutputUniformOffsets diff --git a/src/util/containers/LookupTableL3.h b/src/util/containers/LookupTableL3.h index 2d93dc06..836c9095 100644 --- a/src/util/containers/LookupTableL3.h +++ b/src/util/containers/LookupTableL3.h @@ -45,13 +45,12 @@ public: // if no match is found a default-constructed object is returned T lookup(uint32 offset) { + uint32 indexX = (offset >> (TBitsZ + TBitsY)) & ((1u << TBitsX) - 1); + auto& a = m_tableXArr[indexX]; + uint32 indexY = (offset >> TBitsZ) & ((1u << TBitsY) - 1); + auto& b = a->arr[indexY]; uint32 indexZ = offset & ((1u << TBitsZ) - 1); - offset >>= TBitsZ; - uint32 indexY = offset & ((1u << TBitsY) - 1); - offset >>= TBitsY; - uint32 indexX = offset & ((1u << TBitsX) - 1); - //offset >>= TBitsX; - return m_tableXArr[indexX]->arr[indexY]->arr[indexZ]; + return b->arr[indexZ]; } void store(uint32 offset, T& t) @@ -77,6 +76,7 @@ private: TableY* tableY = new TableY(); for (auto& itr : tableY->arr) itr = m_placeholderTableZ; + tYCount++; return tableY; } @@ -84,8 +84,11 @@ private: TableZ* GenerateNewTableZ() { TableZ* tableZ = new TableZ(); + tZCount++; return tableZ; } TableY* m_tableXArr[1 << TBitsX]; // x lookup + int tYCount = 0; + int tZCount = 0; }; diff --git a/src/util/helpers/StateHasher.h b/src/util/helpers/StateHasher.h new file mode 100644 index 00000000..e46a49ea --- /dev/null +++ b/src/util/helpers/StateHasher.h @@ -0,0 +1,42 @@ +#pragma once + +// hashes two separate streams at once for better parallelism, combined into one final uint64 hash at the end +// inspired by xxHash and MurmurHash3 +class DualStateHasher +{ +public: + FORCEINLINE DualStateHasher() + { + m_h0 = 0x9E3779B97F4A7C15; + m_h1 = 0xC2B2AE3D27D4EB4F; + }; + + FORCEINLINE void MixIn(uint64 a, uint64 b) + { + uint64 tmp = m_h1; + m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; + m_h0 = (tmp ^ b) * 0x165667B19E3779F9ULL; + } + + FORCEINLINE void MixInSingle(uint64 a) + { + uint64 tmp = m_h1; + m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; + m_h0 = tmp; + } + + FORCEINLINE uint64 Finish() + { + uint64 combined = m_h0 ^ std::rotl(m_h1, 31); + combined ^= combined >> 33; + combined *= 0xff51afd7ed558ccdULL; + combined ^= combined >> 33; + combined *= 0xc4ceb9fe1a85ec53ULL; + combined ^= combined >> 33; + return combined; + } + +private: + uint64 m_h0; + uint64 m_h1; +}; From 57bc919e7cbd17c04bae2e70f404eb11746c48ef Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:34:25 +0200 Subject: [PATCH 11/14] Latte: Simplify code --- src/Cafe/HW/Latte/Core/Latte.h | 18 +- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 184 +++--------------- .../HW/Latte/Core/LatteCommandProcessor.cpp | 5 + src/Cafe/HW/Latte/Core/LatteConst.h | 2 +- .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 5 +- .../Renderer/OpenGL/OpenGLRendererCore.cpp | 3 +- .../OpenGL/OpenGLRendererUniformData.cpp | 2 +- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 2 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 33 ++-- 9 files changed, 61 insertions(+), 193 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index 2a11c412..fae9e51f 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -76,12 +76,12 @@ struct LatteDrawcallContext { bool isFirst{}; // first _execute() in current sequence // these are only valid if isFirst is false: - 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{}; + mutable uint32 vertexBufferDirtyMask{}; // mask of vertex buffer indices which have been modified since last draw execute + uint32 vsUniformBufferDirtyMask{}; // mask of uniform buffer indices which have been modified (changed address or size) since last draw execute + uint32 psUniformBufferDirtyMask{}; + uint32 gsUniformBufferDirtyMask{}; + bool aluConstVSDirty{}; + bool aluConstPSDirty{}; }; // texture @@ -168,10 +168,8 @@ 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 LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental = false); +bool LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask); void LatteRenderTarget_updateViewport(); diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index 09210267..17a781d5 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -70,101 +70,32 @@ void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1, output[f] = _swapEndianU32(output[f]); } -void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData) -{ - uint32 shaderAluConst; - uint32 shaderUniformRegisterOffset; - - switch (shader->shaderType) - { - case LatteConst::ShaderType::Vertex: - shaderAluConst = 0x400; - shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; - break; - case LatteConst::ShaderType::Pixel: - shaderAluConst = 0; - shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; - break; - case LatteConst::ShaderType::Geometry: - shaderAluConst = 0; // geometry shader has no ALU const - shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; - break; - default: - UNREACHABLE; - } - - // sourced from uniform registers - uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; - for (auto it : shader->list_remappedUniformEntries_register) - { - uint64* uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4); - uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - regDest[0] = uniformRegData[0]; - regDest[1] = uniformRegData[1]; - } - // sourced from uniform buffers - for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) - { - MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4]; - if (physicalAddr) - { - uint8* __restrict uniformBase = memory_base + physicalAddr; - for (auto& it : bufferGroup.entries) - { - uint64* __restrict regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - uint64* __restrict uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset); - memcpy(regDest, uniformEntrySrc, 16); - } - } - else - { - for (auto& it : bufferGroup.entries) - { - uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - regDest[0] = 0; - regDest[1] = 0; - } - } - } -} - -// 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) +bool LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask) { bool hasChange = false; uint32 shaderAluConst; uint32 shaderUniformRegisterOffset; - bool hasDirtyAluConst = false; - uint32 uniformBufferDirtyMask = 0; switch (shader->shaderType) { 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) + if (aluConstDirty) { uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; for (auto it : shader->list_remappedUniformEntries_register) @@ -238,112 +169,52 @@ bool LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const static sint32 s_vtxStateMaxIndex{}; static sint32 s_vtxStateMaxInstance{}; -// upload vertex and uniform buffers -bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount) +void LatteBufferCache_ProcessQueues() { static uint32 s_syncBufferCounter = 0; - s_vtxStateMaxIndex = maxIndex; - s_vtxStateMaxInstance = baseInstance + instanceCount - 1; s_syncBufferCounter++; - if (s_syncBufferCounter >= 20) + if (s_syncBufferCounter >= 25) { LatteBufferCache_incrementalCleanup(); s_syncBufferCounter = 0; } - LatteBufferCache_processDCFlushQueue(); // process queued deallocations from previous drawcall LatteBufferCache_processDeallocations(); - - // sync and bind vertex buffers - LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); - if (!parsedFetchShader) - return false; - uint32* __restrict bufferRegStartPtr = LatteGPUState.contextRegister + mmSQ_VTX_ATTRIBUTE_BLOCK_START; - for (auto& bufferGroup : parsedFetchShader->bufferGroups) - { - uint32 bufferIndex = bufferGroup.attributeBufferIndex; - uint32* __restrict bufferRegs = bufferRegStartPtr + bufferIndex * 7; - MPTR bufferAddress = bufferRegs[0]; - /* uint32 bufferSize = bufferRegs[1] + 1; */ - uint32 bufferStride = (bufferRegs[2] >> 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(); - 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) +// upload vertex and uniform buffers and update bindings +void LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental) { - // uint32 attribBufferDirtyMask, uint32 uniformBufferVSDirtyMask, uint32 uniformBufferPSDirtyMask, uint32 uniformBufferGSDirtyMask LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); cemu_assert_debug(parsedFetchShader); - uint32 attribBufferDirtyMask = drawcallContext.vertexBufferDirtyMask; - if ( maxIndex > s_vtxStateMaxIndex ) + // todo - vertex attribute offsets may eventually be allowed to change between incremental draws, we should set the attrib dirty bits in that case + if (isIncremental) { - 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; + // 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 + if ( maxIndex > s_vtxStateMaxIndex ) + { + attribBufferDirtyMask = 0xFFFFFFFF; + s_vtxStateMaxIndex = maxIndex; + } + uint32 maxInstance = baseInstance + instanceCount - 1; + if ( maxInstance > s_vtxStateMaxInstance ) + { + attribBufferDirtyMask = 0xFFFFFFFF; + s_vtxStateMaxInstance = maxInstance; + } } - uint32 maxInstance = baseInstance + instanceCount - 1; - if ( maxInstance > s_vtxStateMaxInstance ) + else { - attribBufferDirtyMask = 0xFFFFFFFF; + LatteBufferCache_ProcessQueues(); + s_vtxStateMaxIndex = maxIndex; + uint32 maxInstance = baseInstance + instanceCount - 1; 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) { @@ -353,7 +224,6 @@ void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 b uint32 bufferIndex = bufferGroup.attributeBufferIndex; if ((attribBufferDirtyMask&(1<> 11) & 0xFFFF; @@ -403,17 +273,17 @@ void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 b // 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)) + if (LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex, vsUniformBufferDirtyMask)) stageUniformModifiedMask |= (1<uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { - if (LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, drawcallContext.psUniformBufferDirtyMask)) + if (LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, psUniformBufferDirtyMask)) stageUniformModifiedMask |= (1<uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { - if ( LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, drawcallContext.gsUniformBufferDirtyMask) ) + if ( LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, gsUniformBufferDirtyMask) ) stageUniformModifiedMask |= (1<uniform.loc_remapped >= 0) { - LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); + LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), true, (1<uniform.loc_uniformRegister >= 0) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index 74d93190..56d7a8aa 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -975,7 +975,8 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta endPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr); // synchronize vertex and uniform buffers - LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount); + uint8 stageUniformModifiedMask = 0; + LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); _setupVertexAttributes(); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp index 656536df..e3d85215 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp @@ -36,7 +36,7 @@ void OpenGLRenderer::uniformData_update() auto& list_uniformMapping = shader->list_remappedUniformEntries; cemu_assert_debug(list_uniformMapping.size() <= 256); sint32 remappedArraySize = (sint32)list_uniformMapping.size(); - LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData)); + LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData), true, (1< 0) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 94c94f67..dc90eb09 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -563,7 +563,7 @@ private: // uniform uint32 uniformData_uploadUniformDataBufferGetOffset(std::span data); 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); + void uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, uint8& stageUniformModifiedMask, float* __restrict uniformBuf, bool aluConstDirty, uint32 uniformBufferDirtyMask); // misc void CreatePipelineCache(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index f9b1f714..11314581 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -405,7 +405,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } if (shader->uniform.loc_remapped >= 0) { - LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); + LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), true, (1<uniform.loc_uniformRegister >= 0) { @@ -454,7 +454,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt 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) +void VulkanRenderer::uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, uint8& stageUniformModifiedMask, float* __restrict uniformBuf, bool aluConstDirty, uint32 uniformBufferDirtyMask) { // 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); }; @@ -470,22 +470,19 @@ void VulkanRenderer::uniformData_updateUniformVarsIncremental(uint32 shaderStage 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); + hasChange = LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), aluConstDirty, uniformBufferDirtyMask); } - if (shader->uniform.loc_uniformRegister >= 0) + if (shader->uniform.loc_uniformRegister >= 0 && aluConstDirty) { cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE); - bool isDirty = false; sint32 shaderAluConst; switch (shader->shaderType) { 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(); @@ -495,12 +492,9 @@ void VulkanRenderer::uniformData_updateUniformVarsIncremental(uint32 shaderStage default: UNREACHABLE; } - 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); - } + 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 (hasChange) { @@ -1417,7 +1411,8 @@ void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, else { // synchronize vertex and uniform cache and update buffer bindings - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + uint8 stageUniformModifiedMask = 0; + LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); } PipelineInfo* pipeline_info = draw_getOrCreateGraphicsPipeline(count); @@ -1529,13 +1524,11 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan 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_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataVS); + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, stageUniformModifiedMask, s_vkUniformDataVS, drawcallContext.aluConstVSDirty, drawcallContext.vsUniformBufferDirtyMask); if (pixelShader) - uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataPS); + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, stageUniformModifiedMask, s_vkUniformDataPS, drawcallContext.aluConstPSDirty, drawcallContext.psUniformBufferDirtyMask); if (geometryShader) - uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataGS); - drawcallContext.aluConstVSDirty = false; - drawcallContext.aluConstPSDirty = false; + uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, stageUniformModifiedMask, s_vkUniformDataGS, false, drawcallContext.gsUniformBufferDirtyMask); // store where the read pointer should go after command buffer execution m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; @@ -1587,7 +1580,7 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan else { // synchronize vertex and uniform cache and update buffer bindings - LatteBufferCache_SyncIncremental(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, drawcallContext, stageUniformModifiedMask); + LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, drawcallContext.vertexBufferDirtyMask, drawcallContext.vsUniformBufferDirtyMask, drawcallContext.psUniformBufferDirtyMask, drawcallContext.gsUniformBufferDirtyMask, stageUniformModifiedMask, true); } m_state.descriptorSetsChanged = false; From e142fadfcdebb25cab7acf0d30e9e3cb9cc42c0f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 21 Jun 2026 12:38:36 +0200 Subject: [PATCH 12/14] Latte: Remove calculation of unused minIndex --- src/Cafe/HW/Latte/Core/Latte.h | 2 +- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteIndices.cpp | 181 +++++------------- src/Cafe/HW/Latte/Core/LatteIndices.h | 2 +- .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 5 +- .../Renderer/OpenGL/OpenGLRendererCore.cpp | 2 +- .../HW/Latte/Renderer/Vulkan/VulkanAPI.cpp | 8 + .../Renderer/Vulkan/VulkanRendererCore.cpp | 10 +- 8 files changed, 70 insertions(+), 142 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index fae9e51f..711bb087 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -168,7 +168,7 @@ void LatteCP_ProcessRingbuffer(); // buffer cache -void LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental = false); +void LatteBufferCache_Sync(uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental = false); bool LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask); void LatteRenderTarget_updateViewport(); diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index 17a781d5..e7fb4893 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -185,7 +185,7 @@ void LatteBufferCache_ProcessQueues() } // upload vertex and uniform buffers and update bindings -void LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental) +void LatteBufferCache_Sync(uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental) { LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); cemu_assert_debug(parsedFetchShader); diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.cpp b/src/Cafe/HW/Latte/Core/LatteIndices.cpp index 260c6cd6..a2376443 100644 --- a/src/Cafe/HW/Latte/Core/LatteIndices.cpp +++ b/src/Cafe/HW/Latte/Core/LatteIndices.cpp @@ -21,7 +21,6 @@ struct LatteIndexType lastIndexType; uint64 lastUsed; // output - uint32 indexMin; uint32 indexMax; Renderer::INDEX_TYPE renderIndexType; uint32 outputCount; @@ -140,7 +139,7 @@ uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, L } template -void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const betype* src = (betype*)indexDataInput; T* dst = (T*)indexDataOutput; @@ -148,7 +147,6 @@ void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, u { T v = *src; *dst = v; - indexMin = std::min(indexMin, (uint32)v); indexMax = std::max(indexMax, (uint32)v); dst++; src++; @@ -156,7 +154,7 @@ void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, u } template -void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const T* src = (T*)indexDataInput; T* dst = (T*)indexDataOutput; @@ -164,7 +162,6 @@ void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, u { T v = *src; *dst = v; - indexMin = std::min(indexMin, (uint32)v); indexMax = std::max(indexMax, (uint32)v); dst++; src++; @@ -172,7 +169,7 @@ void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, u } template -void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { sint32 numQuads = count / 4; const betype* src = (betype*)indexDataInput; @@ -183,13 +180,9 @@ void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexD T idx1 = src[1]; T idx2 = src[2]; T idx3 = src[3]; - indexMin = std::min(indexMin, (uint32)idx0); indexMax = std::max(indexMax, (uint32)idx0); - indexMin = std::min(indexMin, (uint32)idx1); indexMax = std::max(indexMax, (uint32)idx1); - indexMin = std::min(indexMin, (uint32)idx2); indexMax = std::max(indexMax, (uint32)idx2); - indexMin = std::min(indexMin, (uint32)idx3); indexMax = std::max(indexMax, (uint32)idx3); dst[0] = idx0; dst[1] = idx1; @@ -203,7 +196,7 @@ void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexD } template -void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { sint32 numQuads = count / 4; const betype* src = (betype*)indexDataInput; @@ -223,12 +216,11 @@ void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* inde src += 4; dst += 6; } - indexMin = 0; indexMax = std::max(count, 1u) - 1; } template -void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { if (count <= 3) return; @@ -241,13 +233,9 @@ void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* in T idx1 = src[1]; T idx2 = src[2]; T idx3 = src[3]; - indexMin = std::min(indexMin, (uint32)idx0); indexMax = std::max(indexMax, (uint32)idx0); - indexMin = std::min(indexMin, (uint32)idx1); indexMax = std::max(indexMax, (uint32)idx1); - indexMin = std::min(indexMin, (uint32)idx2); indexMax = std::max(indexMax, (uint32)idx2); - indexMin = std::min(indexMin, (uint32)idx3); indexMax = std::max(indexMax, (uint32)idx3); dst[0] = idx0; dst[1] = idx1; @@ -261,7 +249,7 @@ void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* in } template -void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { if (count <= 0) return; @@ -271,7 +259,6 @@ void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* ind for (sint32 i = 0; i < (sint32)count; i++) { T idx = *src; - indexMin = std::min(indexMin, (uint32)idx); indexMax = std::max(indexMax, (uint32)idx); *dst = idx; src++; @@ -281,7 +268,7 @@ void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* ind } template -void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMax) { if (count <= 3) return; @@ -301,13 +288,12 @@ void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 cou dst[5] = idx3; dst += 6; } - indexMin = 0; indexMax = std::max(count, 1u) - 1; } template -void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMax) { if (count == 0) return; @@ -319,12 +305,11 @@ void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 coun } *dst = 0; dst++; - indexMin = 0; indexMax = std::max(count, 1u) - 1; } template -void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const betype* src = (betype*)indexDataInput; T* dst = (T*)indexDataOutput; @@ -337,14 +322,13 @@ void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* else i0 = count - 1 - i / 2; T idx = src[i0]; - indexMin = std::min(indexMin, (uint32)idx); indexMax = std::max(indexMax, (uint32)idx); dst[i] = idx; } } template -void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const betype* src = (betype*)indexDataInput; T* dst = (T*)indexDataOutput; @@ -357,13 +341,12 @@ void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, voi idx = count - 1 - idx / 2; dst[i] = idx; } - indexMin = 0; indexMax = std::max(count, 1u) - 1; } #if defined(ARCH_X86_64) ATTRIBUTE_AVX2 -void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { // using AVX + AVX2 we can process 16 indices at a time const uint16* indicesU16BE = (const uint16*)indexDataInput; @@ -385,33 +368,23 @@ void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDat // endian swap mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle16Swap); _mm256_store_si256((__m256i*)indexOutput, mIndexData); - mMin = _mm256_min_epu16(mIndexData, mMin); mMax = _mm256_max_epu16(mIndexData, mMax); indexOutput += 16; } while (--count16); // fold 32 to 16 byte - mMin = _mm256_min_epu16(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); mMax = _mm256_max_epu16(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); // fold 16 to 8 byte - mMin = _mm256_min_epu16(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); mMax = _mm256_max_epu16(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); - uint16* mMinU16 = (uint16*)&mMin; uint16* mMaxU16 = (uint16*)&mMax; - indexMin = std::min(indexMin, (uint32)mMinU16[0]); - indexMin = std::min(indexMin, (uint32)mMinU16[1]); - indexMin = std::min(indexMin, (uint32)mMinU16[2]); - indexMin = std::min(indexMin, (uint32)mMinU16[3]); - indexMax = std::max(indexMax, (uint32)mMaxU16[0]); indexMax = std::max(indexMax, (uint32)mMaxU16[1]); indexMax = std::max(indexMax, (uint32)mMaxU16[2]); indexMax = std::max(indexMax, (uint32)mMaxU16[3]); } // process remaining indices - uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { @@ -420,15 +393,13 @@ void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDat indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); - _minIndex = std::min(_minIndex, (uint32)idx); } - // update min/max + // update max indexMax = std::max(indexMax, _maxIndex); - indexMin = std::min(indexMin, _minIndex); } ATTRIBUTE_SSE41 -void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { // SSSE3 & SSE4.1 optimized decoding const uint16* indicesU16BE = (const uint16*)indexDataInput; @@ -437,7 +408,6 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa sint32 countRemaining = count & 7; if (count8) { - __m128i mMin = _mm_set_epi16((short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF); __m128i mMax = _mm_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000); __m128i mTemp; __m128i* mRawIndices = (__m128i*)indicesU16BE; @@ -450,14 +420,12 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa mTemp = _mm_loadu_si128(mRawIndices); mRawIndices++; mTemp = _mm_shuffle_epi8(mTemp, shufmask); - mMin = _mm_min_epu16(mMin, mTemp); mMax = _mm_max_epu16(mMax, mTemp); _mm_store_si128(mOutputIndices, mTemp); mOutputIndices++; } uint16* mMaxU16 = (uint16*)&mMax; - uint16* mMinU16 = (uint16*)&mMin; indexMax = std::max(indexMax, (uint32)mMaxU16[0]); indexMax = std::max(indexMax, (uint32)mMaxU16[1]); @@ -467,16 +435,7 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa indexMax = std::max(indexMax, (uint32)mMaxU16[5]); indexMax = std::max(indexMax, (uint32)mMaxU16[6]); indexMax = std::max(indexMax, (uint32)mMaxU16[7]); - indexMin = std::min(indexMin, (uint32)mMinU16[0]); - indexMin = std::min(indexMin, (uint32)mMinU16[1]); - indexMin = std::min(indexMin, (uint32)mMinU16[2]); - indexMin = std::min(indexMin, (uint32)mMinU16[3]); - indexMin = std::min(indexMin, (uint32)mMinU16[4]); - indexMin = std::min(indexMin, (uint32)mMinU16[5]); - indexMin = std::min(indexMin, (uint32)mMinU16[6]); - indexMin = std::min(indexMin, (uint32)mMinU16[7]); } - uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { @@ -485,14 +444,12 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); - _minIndex = std::min(_minIndex, (uint32)idx); } indexMax = std::max(indexMax, _maxIndex); - indexMin = std::min(indexMin, _minIndex); } ATTRIBUTE_AVX2 -void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { // using AVX + AVX2 we can process 8 indices at a time const uint32* indicesU32BE = (const uint32*)indexDataInput; @@ -501,7 +458,6 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat sint32 countRemaining = count & 7; if (count8) { - __m256i mMin = _mm256_set_epi32((sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF); __m256i mMax = _mm256_set_epi32(0, 0, 0, 0, 0, 0, 0, 0); __m256i mShuffle32Swap = _mm256_set_epi8(28,29,30,31, 24,25,26,27, @@ -520,29 +476,20 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat // endian swap mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle32Swap); _mm256_store_si256((__m256i*)indexOutput, mIndexData); - mMin = _mm256_min_epu32(mIndexData, mMin); mMax = _mm256_max_epu32(mIndexData, mMax); indexOutput += 8; } while (--count8); // fold 32 to 16 byte - mMin = _mm256_min_epu32(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); mMax = _mm256_max_epu32(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); // fold 16 to 8 byte - mMin = _mm256_min_epu32(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); mMax = _mm256_max_epu32(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); - uint32* mMinU32 = (uint32*)&mMin; uint32* mMaxU32 = (uint32*)&mMax; - - indexMin = std::min(indexMin, (uint32)mMinU32[0]); - indexMin = std::min(indexMin, (uint32)mMinU32[1]); - indexMax = std::max(indexMax, (uint32)mMaxU32[0]); indexMax = std::max(indexMax, (uint32)mMaxU32[1]); } // process remaining indices - uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { @@ -551,15 +498,13 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat indexOutput++; indicesU32BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); - _minIndex = std::min(_minIndex, (uint32)idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); - indexMin = std::min(indexMin, _minIndex); } #elif defined(__aarch64__) -void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const uint16* indicesU16BE = (const uint16*)indexDataInput; uint16* indexOutput = (uint16*)indexDataOutput; @@ -568,7 +513,6 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat if (count8) { - uint16x8_t mMin = vdupq_n_u16(0xFFFF); uint16x8_t mMax = vdupq_n_u16(0x0000); uint16x8_t mTemp; uint16x8_t* mRawIndices = (uint16x8_t*) indicesU16BE; @@ -581,22 +525,18 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat mTemp = vld1q_u16((uint16*)mRawIndices); mRawIndices++; mTemp = vrev16q_u8(mTemp); - mMin = vminq_u16(mMin, mTemp); mMax = vmaxq_u16(mMax, mTemp); vst1q_u16((uint16*)mOutputIndices, mTemp); mOutputIndices++; } uint16* mMaxU16 = (uint16*)&mMax; - uint16* mMinU16 = (uint16*)&mMin; for (int i = 0; i < 8; ++i) { indexMax = std::max(indexMax, (uint32)mMaxU16[i]); - indexMin = std::min(indexMin, (uint32)mMinU16[i]); } } // process remaining indices - uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { @@ -605,14 +545,12 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); - _minIndex = std::min(_minIndex, (uint32)idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); - indexMin = std::min(indexMin, _minIndex); } -void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax) { const uint32* indicesU32BE = (const uint32*)indexDataInput; uint32* indexOutput = (uint32*)indexDataOutput; @@ -621,7 +559,6 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat if (count8) { - uint32x4_t mMin = vdupq_n_u32(0xFFFFFFFF); uint32x4_t mMax = vdupq_n_u32(0x00000000); uint32x4_t mTemp; uint32x4_t* mRawIndices = (uint32x4_t*) indicesU32BE; @@ -634,22 +571,18 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat mTemp = vld1q_u32((uint32*)mRawIndices); mRawIndices++; mTemp = vrev32q_u8(mTemp); - mMin = vminq_u32(mMin, mTemp); mMax = vmaxq_u32(mMax, mTemp); vst1q_u32((uint32*)mOutputIndices, mTemp); mOutputIndices++; } uint32* mMaxU32 = (uint32*)&mMax; - uint32* mMinU32 = (uint32*)&mMin; for (int i = 0; i < 4; ++i) { indexMax = std::max(indexMax, mMaxU32[i]); - indexMin = std::min(indexMin, mMinU32[i]); } } // process remaining indices - uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { @@ -658,21 +591,18 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat indexOutput++; indicesU32BE++; _maxIndex = std::max(_maxIndex, idx); - _minIndex = std::min(_minIndex, idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); - indexMin = std::min(indexMin, _minIndex); } #endif template -void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMin, uint32& indexMax) +void _LatteIndices_alternativeCalculateIndexMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMax) { cemu_assert_debug(count != 0); const betype* idxPtrT = (betype*)indexData; - T _indexMin = *idxPtrT; T _indexMax = *idxPtrT; cemu_assert_debug(primitiveRestartIndex <= std::numeric_limits::max()); T restartIndexT = (T)primitiveRestartIndex; @@ -681,23 +611,20 @@ void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32 T idx = *idxPtrT; if (idx != restartIndexT) { - _indexMin = std::min(_indexMin, idx); _indexMax = std::max(_indexMax, idx); } idxPtrT++; count--; } - indexMin = _indexMin; indexMax = _indexMax; } // calculate min and max index while taking primitive restart into account // fallback implementation in case the fast path gives us invalid results -void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMin, uint32& indexMax) +void LatteIndices_alternativeCalculateIndexMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMax) { if (count == 0) { - indexMin = 0; indexMax = 0; return; } @@ -705,11 +632,11 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn if (indexType == LatteIndexType::U16_BE) { - _LatteIndices_alternativeCalculateIndexMinMax(indexData, count, primitiveRestartIndex, indexMin, indexMax); + _LatteIndices_alternativeCalculateIndexMax(indexData, count, primitiveRestartIndex, indexMax); } else if (indexType == LatteIndexType::U32_BE) { - _LatteIndices_alternativeCalculateIndexMinMax(indexData, count, primitiveRestartIndex, indexMin, indexMax); + _LatteIndices_alternativeCalculateIndexMax(indexData, count, primitiveRestartIndex, indexMax); } else { @@ -717,7 +644,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn } } -void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation) +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation) { // what this should do: // [x] use fast SIMD-based index decoding @@ -733,7 +660,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 }); if (cacheEntry != LatteIndexCache.entry.end()) { - indexMin = cacheEntry->indexMin; indexMax = cacheEntry->indexMax; renderIndexType = cacheEntry->renderIndexType; outputCount = cacheEntry->outputCount; @@ -759,7 +685,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 if (indexOutputSize == 0) { outputCount = count; - indexMin = 0; indexMax = std::max(count, 1u)-1; renderIndexType = Renderer::INDEX_TYPE::NONE; indexAllocation = {}; @@ -770,7 +695,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 void* indexOutputPtr = indexAllocation.mem; // decode indices - indexMin = std::numeric_limits::max(); indexMax = std::numeric_limits::min(); if (primitiveMode == LattePrimitiveMode::QUADS) { @@ -779,19 +703,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { if (count <= 0xFFFF) { - LatteIndices_generateAutoQuadIndices(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoQuadIndices(indexData, indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { - LatteIndices_generateAutoQuadIndices(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoQuadIndices(indexData, indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) - LatteIndices_unpackQuadsAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackQuadsAndConvert(indexData, indexOutputPtr, count, indexMax); else if (indexType == LatteIndexType::U32_BE) - LatteIndices_unpackQuadsAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackQuadsAndConvert(indexData, indexOutputPtr, count, indexMax); else cemu_assert_debug(false); outputCount = count / 4 * 6; @@ -803,19 +727,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { if (count <= 0xFFFF) { - LatteIndices_generateAutoQuadStripIndices(indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoQuadStripIndices(indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { - LatteIndices_generateAutoQuadStripIndices(indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoQuadStripIndices(indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) - LatteIndices_unpackQuadStripAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackQuadStripAndConvert(indexData, indexOutputPtr, count, indexMax); else if (indexType == LatteIndexType::U32_BE) - LatteIndices_unpackQuadStripAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackQuadStripAndConvert(indexData, indexOutputPtr, count, indexMax); else cemu_assert_debug(false); if (count >= 2) @@ -830,19 +754,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { if (count <= 0xFFFF) { - LatteIndices_generateAutoLineLoopIndices(indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoLineLoopIndices(indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { - LatteIndices_generateAutoLineLoopIndices(indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoLineLoopIndices(indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) - LatteIndices_unpackLineLoopAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackLineLoopAndConvert(indexData, indexOutputPtr, count, indexMax); else if (indexType == LatteIndexType::U32_BE) - LatteIndices_unpackLineLoopAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackLineLoopAndConvert(indexData, indexOutputPtr, count, indexMax); else cemu_assert_debug(false); outputCount = count + 1; @@ -853,19 +777,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { if (count <= 0xFFFF) { - LatteIndices_generateAutoTriangleFanIndices(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoTriangleFanIndices(indexData, indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { - LatteIndices_generateAutoTriangleFanIndices(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_generateAutoTriangleFanIndices(indexData, indexOutputPtr, count, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) - LatteIndices_unpackTriangleFanAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackTriangleFanAndConvert(indexData, indexOutputPtr, count, indexMax); else if (indexType == LatteIndexType::U32_BE) - LatteIndices_unpackTriangleFanAndConvert(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_unpackTriangleFanAndConvert(indexData, indexOutputPtr, count, indexMax); else cemu_assert_debug(false); outputCount = count; @@ -876,48 +800,48 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { #if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) - LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMax); else if (g_CPUFeatures.x86.sse4_1 && g_CPUFeatures.x86.ssse3) - LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMax); else - LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMax); #elif defined(__aarch64__) - LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMax); #else - LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMax); #endif } else if (indexType == LatteIndexType::U32_BE) { #if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) - LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMax); else - LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMax); #elif defined(__aarch64__) - LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMax); #else - LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertBE(indexData, indexOutputPtr, count, indexMax); #endif } else if (indexType == LatteIndexType::U16_LE) { - LatteIndices_convertLE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertLE(indexData, indexOutputPtr, count, indexMax); } else if (indexType == LatteIndexType::U32_LE) { - LatteIndices_convertLE(indexData, indexOutputPtr, count, indexMin, indexMax); + LatteIndices_convertLE(indexData, indexOutputPtr, count, indexMax); } else cemu_assert_debug(false); outputCount = count; } - // the above algorithms use a simplistic approach to get indexMin/indexMax - // here we make sure primitive restart indices dont influence the index range - if (primitiveRestartIndex == indexMin || primitiveRestartIndex == indexMax) + // the above algorithms use a fast approach to get indexMax which does not filter out indices matching primitiveRestartIndex + // here we use a fallback in case the determined index equals the primitive restart index + if (primitiveRestartIndex == indexMax) { // recalculate index range but filter out primitive restart index - LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax); + LatteIndices_alternativeCalculateIndexMax(indexData, indexType, count, indexMax); } g_renderer->indexData_uploadIndexMemory(indexAllocation); performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize; @@ -934,7 +858,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 lruEntry->lastCount = count; lruEntry->lastPrimitiveMode = primitiveMode; lruEntry->lastIndexType = indexType; - lruEntry->indexMin = indexMin; lruEntry->indexMax = indexMax; lruEntry->renderIndexType = renderIndexType; lruEntry->outputCount = outputCount; diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.h b/src/Cafe/HW/Latte/Core/LatteIndices.h index 8aace24e..26ddb062 100644 --- a/src/Cafe/HW/Latte/Core/LatteIndices.h +++ b/src/Cafe/HW/Latte/Core/LatteIndices.h @@ -4,4 +4,4 @@ void LatteIndices_invalidate(const void* memPtr, uint32 size); void LatteIndices_invalidateAll(); -void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation); \ No newline at end of file +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index 50e60cc2..a694411b 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -1142,10 +1142,9 @@ void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 // Index buffer Renderer::INDEX_TYPE hostIndexType; uint32 hostIndexCount; - uint32 indexMin = 0; uint32 indexMax = 0; Renderer::IndexAllocation indexAllocation; - LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation); auto indexAllocationMtl = static_cast(indexAllocation.rendererInternal); // Buffer cache @@ -1165,7 +1164,7 @@ void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 // synchronize vertex and uniform cache and update buffer bindings // We need to call this before getting the render command encoder, since it can cause buffer copies uint8 stageUniformModifiedMask = 0; - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); + LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); } // Render pass diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index 56d7a8aa..a67306b3 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -976,7 +976,7 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta // synchronize vertex and uniform buffers uint8 stageUniformModifiedMask = 0; - LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); + LatteBufferCache_Sync(indexState.maxIndex + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); _setupVertexAttributes(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp index e09ebcd1..b7dc5001 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp @@ -58,6 +58,14 @@ void VulkanBenchmarkPrintResults() cemuLog_log(LogType::Force, "--- Vulkan API CPU benchmark ---"); cemuLog_log(LogType::Force, "Elapsed cycles this frame: {:} | Current cycle {:} | NumFunc {:}", elapsedCycles, currentCycle, s_vulkanBenchmarkFuncs.size()); + // sum up total time of Vulkan calls + uint64 totalVkCycles = 0; + for (auto& it : s_vulkanBenchmarkFuncs) + { + totalVkCycles += it->cycles; + } + cemuLog_log(LogType::Force, "Total Vulkan time: {:.4}%", ((double)totalVkCycles / elapsedCyclesDbl) * 100.0); + std::vector sortedIndices(s_vulkanBenchmarkFuncs.size()); std::iota(sortedIndices.begin(), sortedIndices.end(), 0); std::sort(sortedIndices.begin(), sortedIndices.end(), diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 11314581..01295a4d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1368,10 +1368,9 @@ void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, Renderer::INDEX_TYPE hostIndexType; uint32 hostIndexCount; - uint32 indexMin = 0; uint32 indexMax = 0; Renderer::IndexAllocation indexAllocation; - LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation); VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; // update index binding if (hostIndexType != INDEX_TYPE::NONE) @@ -1412,7 +1411,7 @@ void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, { // synchronize vertex and uniform cache and update buffer bindings uint8 stageUniformModifiedMask = 0; - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); + LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); } PipelineInfo* pipeline_info = draw_getOrCreateGraphicsPipeline(count); @@ -1537,10 +1536,9 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan Renderer::INDEX_TYPE hostIndexType; uint32 hostIndexCount; - uint32 indexMin = 0; uint32 indexMax = 0; Renderer::IndexAllocation indexAllocation; - LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation); VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; // update index binding if (hostIndexType != INDEX_TYPE::NONE) @@ -1580,7 +1578,7 @@ 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, drawcallContext.vertexBufferDirtyMask, drawcallContext.vsUniformBufferDirtyMask, drawcallContext.psUniformBufferDirtyMask, drawcallContext.gsUniformBufferDirtyMask, stageUniformModifiedMask, true); + LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, drawcallContext.vertexBufferDirtyMask, drawcallContext.vsUniformBufferDirtyMask, drawcallContext.psUniformBufferDirtyMask, drawcallContext.gsUniformBufferDirtyMask, stageUniformModifiedMask, true); } m_state.descriptorSetsChanged = false; From 62ac48f0e9d52fa8a39bca998c647723e9157632 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:02:18 +0200 Subject: [PATCH 13/14] Latte: Add support for VK_EXT_attachment_feedback_loop Requires both VK_EXT_attachment_feedback_loop and VK_EXT_attachment_feedback_loop_dynamic_state to be provided by the driver. Otherwise it will fall back to the old code. Main benefit is for improving performance as it makes barriers cheaper and doesn't force us to unnecessarily interrupt renderpasses --- dependencies/Vulkan-Headers | 2 +- .../HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp | 65 ++++++----- .../HW/Latte/Renderer/Vulkan/CachedFBOVk.h | 23 +++- .../Latte/Renderer/Vulkan/LatteTextureVk.cpp | 6 + .../HW/Latte/Renderer/Vulkan/LatteTextureVk.h | 5 +- .../Renderer/Vulkan/TextureReadbackVk.cpp | 6 +- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 3 + .../Vulkan/VulkanPipelineCompiler.cpp | 6 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 108 +++++++++++++++--- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 8 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 59 +++++++--- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 10 +- 12 files changed, 225 insertions(+), 76 deletions(-) diff --git a/dependencies/Vulkan-Headers b/dependencies/Vulkan-Headers index 9b9fd871..01393c3d 160000 --- a/dependencies/Vulkan-Headers +++ b/dependencies/Vulkan-Headers @@ -1 +1 @@ -Subproject commit 9b9fd871b08110cd8f0b74e721b03213d9cc3081 +Subproject commit 01393c3df0e5285b54ee6527466513f9e614be94 diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp index 8a999000..20c88b28 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp @@ -126,7 +126,10 @@ void CachedFBOVk::InitDynamicRenderingData() continue; } else + { + m_vkColorAttachments[i].imageLayout = static_cast(buffer.texture->baseTexture)->GetDefaultLayout(); m_vkColorAttachments[i].imageView = cbView->m_textureImageView; + } } m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments; @@ -167,6 +170,9 @@ void CachedFBOVk::InitDynamicRenderingData() // setup depth and stencil attachment if (depthStencilView) { + auto depthTexVk = static_cast(depthBuffer.texture->baseTexture); + m_vkDepthAttachment.imageLayout = depthTexVk->GetDefaultLayout(); + m_vkStencilAttachment.imageLayout = depthTexVk->GetDefaultLayout(); m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView; m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -189,41 +195,46 @@ void CachedFBOVk::InitDynamicRenderingData() m_vkRenderingInfo.layerCount = 1; } +static uint32 s_selfDependencyCheckIndex = 1; -uint32 s_currentCollisionCheckIndex = 1; - -bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const +CachedFBOVk::RendertargetSelfDependencyMask CachedFBOVk::CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const { - s_currentCollisionCheckIndex++; - const uint32 curColIndex = s_currentCollisionCheckIndex; - for (auto& itr : m_referencedTextures) + s_selfDependencyCheckIndex++; + const uint32 curColIndex = s_selfDependencyCheckIndex; + for (auto& colorAttachment : colorBuffer) { - LatteTextureVk* vkTex = (LatteTextureVk*)itr; - vkTex->m_collisionCheckIndex = curColIndex; - } - if (vsDS) - { - for (auto& itr : vsDS->list_fboCandidates) + if (colorAttachment.texture) { - if (itr->m_collisionCheckIndex == curColIndex) - return true; + LatteTextureVk* vkTex = static_cast(colorAttachment.texture->baseTexture); + vkTex->m_selfDependencyCheckIndex = curColIndex; + vkTex->m_selfDependencyCheckAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } } - if (gsDS) + if (depthBuffer.texture) { - for (auto& itr : gsDS->list_fboCandidates) - { - if (itr->m_collisionCheckIndex == curColIndex) - return true; - } + LatteTextureVk* vkTex = static_cast(depthBuffer.texture->baseTexture); + vkTex->m_selfDependencyCheckIndex = curColIndex; + vkTex->m_selfDependencyCheckAspectMask = depthBuffer.hasStencil ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT): VK_IMAGE_ASPECT_DEPTH_BIT; } - if (psDS) + + auto getSelfDependencyMask = [curColIndex](VkDescriptorSetInfo* ds) -> VkImageAspectFlags { - for (auto& itr : psDS->list_fboCandidates) + VkImageAspectFlags aspectMask = 0; + if (!ds) + return aspectMask; + for (auto& itr : ds->list_fboCandidates) { - if (itr->m_collisionCheckIndex == curColIndex) - return true; + if (itr->m_selfDependencyCheckIndex == curColIndex) + aspectMask |= itr->m_selfDependencyCheckAspectMask; } - } - return false; -} \ No newline at end of file + return aspectMask; + }; + + RendertargetSelfDependencyMask selfDepInfo{}; + VkImageAspectFlags vertexAspectFlags = getSelfDependencyMask(vsDS); + VkImageAspectFlags geometryAspectFlags = getSelfDependencyMask(gsDS); + VkImageAspectFlags pixelAspectFlags = getSelfDependencyMask(psDS); + selfDepInfo.aspectMaskFlags = vertexAspectFlags | geometryAspectFlags | pixelAspectFlags; + selfDepInfo.hasNonPixelSelfDependency = (vertexAspectFlags | geometryAspectFlags) != 0; + return selfDepInfo; +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h index bf72996e..5a603579 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h @@ -51,8 +51,29 @@ public: [[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;} + struct RendertargetSelfDependencyMask + { + VkImageAspectFlags aspectMaskFlags{}; // aspect flags which are simultaneously sampled and written + bool hasNonPixelSelfDependency{false}; + + VkImageAspectFlags GetAspectMask() const + { + return aspectMaskFlags; + } + + bool HasSelfDependency() const + { + return GetAspectMask() != 0; + } + + bool HasVertexOrGeometrySelfDependency() const + { + return hasNonPixelSelfDependency; // vertex or geometry shader samples texture which is written to + } + }; + // checks if any of the sampled textures are output by the FBO - bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; + RendertargetSelfDependencyMask CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index 88d3291d..c6a9b274 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -67,6 +67,12 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } + if (m_vkr->UseAttachmentFeedbackLoop() && (imageInfo.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) + { + imageInfo.usage |= VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT; + m_defaultLayout = VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + } + if (dim == Latte::E_DIM::DIM_2D) imageInfo.imageType = VK_IMAGE_TYPE_2D; else if (dim == Latte::E_DIM::DIM_1D) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h index 612e2e70..b0d74879 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -20,6 +20,7 @@ public: VkFormat GetFormat() const { return vkObjTex->m_format; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } + VkImageLayout GetDefaultLayout() const { return m_defaultLayout; } VkImageLayout GetImageLayout(VkImageSubresource& subresource) { @@ -83,12 +84,14 @@ public: uint64 m_vkFlushIndex_read{}; uint64 m_vkFlushIndex_write{}; - uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall + uint32 m_selfDependencyCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall + VkImageAspectFlags m_selfDependencyCheckAspectMask{}; private: class VulkanRenderer* m_vkr; VKRObjectTexture* vkObjTex{}; + VkImageLayout m_defaultLayout{ VK_IMAGE_LAYOUT_GENERAL }; // the targetted long term layout of the texture. Can be either VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT for potential rendertargets if supported std::vector m_layouts; uint32 m_layoutsMips; uint32 m_layoutsDepth; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp index bce23b59..81281ebc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp @@ -122,15 +122,15 @@ void LatteTextureReadbackInfoVk::StartTransfer() const auto renderer = VulkanRenderer::GetInstance(); renderer->draw_endRenderPass(); - renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); + renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); renderer->barrier_sequentializeTransfer(); - vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, ®ion); + vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buffer, 1, ®ion); renderer->barrier_sequentializeTransfer(); - renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified + renderer->barrier_image(baseTexture, region.imageSubresource, baseTexture->GetDefaultLayout()); // make sure transfer is finished before image is modified renderer->barrier_bufferRange(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index c44a7b1c..f503c108 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -189,6 +189,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR); +// ext_attachment_feedback_loop_dynamic_state +VKFUNC_DEVICE(vkCmdSetAttachmentFeedbackLoopEnableEXT); + // khr_present_wait VKFUNC_DEVICE(vkWaitForPresentKHR); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ca166e69..2800b81e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -815,6 +815,10 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS); pipelineInfo->usesDepthBias = true; } + if (VulkanRenderer::GetInstance()->UseAttachmentFeedbackLoop()) + { + dynamicStates.emplace_back(VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT); + } dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = dynamicStates.size(); @@ -1135,4 +1139,4 @@ void PipelineCompiler::CompileThreadPool_Stop() void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v) { s_pipelineCompileRequests.push(v); -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 136cc76b..bf9ae10c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -50,7 +50,9 @@ const std::vector kOptionalDeviceExtensions = VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, - VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME + VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME }; const std::vector kRequiredDeviceExtensions = @@ -271,6 +273,22 @@ void VulkanRenderer::GetDeviceFeatures() prevStruct = &pprf; } + VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{}; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state) + { + attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT; + attachmentFeedbackLoopDynamicStateFeature.pNext = prevStruct; + prevStruct = &attachmentFeedbackLoopDynamicStateFeature; + } + + VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{}; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + { + attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT; + attachmentFeedbackLoopLayoutFeature.pNext = prevStruct; + prevStruct = &attachmentFeedbackLoopLayoutFeature; + } + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; @@ -330,6 +348,12 @@ void VulkanRenderer::GetDeviceFeatures() if ( pprf.pipelineRobustness != VK_TRUE ) m_featureControl.deviceExtensions.pipeline_robustness = false; } + if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + m_featureControl.deviceExtensions.attachment_feedback_loop_layout = attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout == VK_TRUE; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state && m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state = attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState == VK_TRUE; + if (!UseAttachmentFeedbackLoop()) + cemuLog_log(LogType::Force, "VK_EXT_attachment_feedback_loop_layout(_dynamic_state) not supported"); // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); @@ -698,6 +722,21 @@ VulkanRenderer::VulkanRenderer() deviceExtensionFeatures = &pipelineRobustnessFeature; pipelineRobustnessFeature.pipelineRobustness = VK_TRUE; } + // enable attachment feedback loop layout + dynamic state if both are supported + VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{}; + VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{}; + if (UseAttachmentFeedbackLoop()) + { + attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT; + attachmentFeedbackLoopLayoutFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &attachmentFeedbackLoopLayoutFeature; + attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout = VK_TRUE; + + attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT; + attachmentFeedbackLoopDynamicStateFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &attachmentFeedbackLoopDynamicStateFeature; + attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState = VK_TRUE; + } std::vector used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); @@ -1122,7 +1161,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad range.mipLevel = 0; range.baseArrayLayer = texViewVk->firstSlice; range.layerCount = 1; - barrier_image(baseImageTex, range, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(baseImageTex, range, baseImageTex->GetDefaultLayout()); } format = VK_FORMAT_R8G8B8A8_UNORM; @@ -1178,6 +1217,10 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad } vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ®ion); + if (dumpImage == baseImage) + { + barrier_image(baseImageTex, region.imageSubresource, baseImageTex->GetDefaultLayout()); + } SubmitCommandBuffer(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); @@ -1282,6 +1325,11 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector(texViewVk->baseTexture)->GetDefaultLayout(); imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); @@ -3395,7 +3445,7 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI else { cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); + ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, vkTexture->GetDefaultLayout()); } } @@ -3406,7 +3456,7 @@ void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s { cemu_assert_unimplemented(); } - ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); + ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, vkTexture->GetDefaultLayout()); } void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) @@ -3447,7 +3497,7 @@ void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range); - barrier_image(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(vkTexture, subresourceRange, vkTexture->GetDefaultLayout()); } void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) @@ -3550,7 +3600,7 @@ void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); - barrier_image(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(vkTexture, barrierSubresourceRange, vkTexture->GetDefaultLayout()); } LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, @@ -3628,13 +3678,11 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 mipWidth = std::max(dst->width >> dstMip, 1); sint32 mipHeight = std::max(dst->height >> dstMip, 1); - if (mipWidth < 4 || mipHeight < 4) { cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4"); return; } - } // make sure all write operations to the src image have finished @@ -3645,7 +3693,8 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); // make sure the transfer is finished before the image is read or written - barrier_image(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(srcVk, region.srcSubresource, srcVk->GetDefaultLayout()); + barrier_image(dstVk, region.dstSubresource, dstVk->GetDefaultLayout()); } LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView) @@ -4064,6 +4113,10 @@ void VKRObjectSampler::DestroyCache() VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) { + VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); + bool useAttachmentFeedbackLoop = vkRenderer->UseAttachmentFeedbackLoop(); + VkImageLayout attachmentLayout = useAttachmentFeedbackLoop ? VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT : VK_IMAGE_LAYOUT_GENERAL; + // generate helper hash for pipeline state uint64 stateHash = 0; for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i) @@ -4097,7 +4150,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format; color_attachments_references[i].attachment = (uint32)attachments_descriptions.size(); - color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL; + color_attachments_references[i].layout = attachmentLayout; VkAttachmentDescription entry{}; entry.format = attachmentInfo.colorAttachment[i].format; @@ -4106,8 +4159,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; - entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.initialLayout = attachmentLayout; + entry.finalLayout = attachmentLayout; attachments_descriptions.emplace_back(entry); numColorAttachments = i + 1; @@ -4124,7 +4177,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 { hasDepthStencilAttachment = true; depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size(); - depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL; + depth_stencil_attachments_references.layout = attachmentLayout; m_depthAttachmentFormat = attachmentInfo.depthAttachment.format; VkAttachmentDescription entry{}; @@ -4142,8 +4195,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; } - entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; - entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.initialLayout = attachmentLayout; + entry.finalLayout = attachmentLayout; attachments_descriptions.emplace_back(entry); } @@ -4164,12 +4217,29 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - renderPassInfo.pDependencies = nullptr; - renderPassInfo.dependencyCount = 0; + VkSubpassDependency feedbackLoopDependency{}; + if (useAttachmentFeedbackLoop) + { + feedbackLoopDependency.srcSubpass = 0; + feedbackLoopDependency.dstSubpass = 0; + feedbackLoopDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + feedbackLoopDependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + feedbackLoopDependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + feedbackLoopDependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + feedbackLoopDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT | VK_DEPENDENCY_FEEDBACK_LOOP_BIT_EXT; + renderPassInfo.pDependencies = &feedbackLoopDependency; + renderPassInfo.dependencyCount = 1; + } + else + { + renderPassInfo.pDependencies = nullptr; + renderPassInfo.dependencyCount = 0; + } // before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above) // Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred + // as of Cemu 2.7 we are now using VK_EXT_attachment_feedback_loop_layout with a matching renderpass dependency if supported. Otherwise we are falling back to the above - if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) + if (vkCreateRenderPass(vkRenderer->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass"); throw std::runtime_error("failed to create render pass!"); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index dc90eb09..4581e9e7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -380,7 +380,7 @@ private: VkDescriptorSetInfo* activePixelDS{ nullptr }; VkDescriptorSetInfo* activeGeometryDS{ nullptr }; bool descriptorSetsChanged{ false }; - bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget + VkImageAspectFlags feedbackLoopImageAspect{0xFFFFFFFF}; // viewport and scissor box VkViewport currentViewport{}; VkRect2D currentScissorRect{}; @@ -414,6 +414,7 @@ private: activeIndexType = Renderer::INDEX_TYPE::NONE; activeIndexBufferIndex = std::numeric_limits::max(); activeIndexBufferOffset = std::numeric_limits::max(); + feedbackLoopImageAspect = 0xFFFFFFFF; } // invalidation / flushing @@ -465,6 +466,8 @@ private: bool present_wait = false; // VK_KHR_present_wait bool depth_clip_enable = false; // VK_EXT_depth_clip_enable bool pipeline_robustness = false; // VK_EXT_pipeline_robustness + bool attachment_feedback_loop_layout = false; // VK_EXT_attachment_feedback_loop_layout + bool attachment_feedback_loop_dynamic_state = false; // VK_EXT_attachment_feedback_loop_dynamic_state (this is forced to false if VK_EXT_attachment_feedback_loop_layout is not supported) }deviceExtensions; struct @@ -553,7 +556,7 @@ private: void draw_handleSpecialState5(); // draw synchronization helper - void sync_inputTexturesChanged(); + void sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass = false); void sync_RenderPassLoadTextures(CachedFBOVk* fboVk); void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); @@ -952,6 +955,7 @@ public: bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; } bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; } bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; } + bool UseAttachmentFeedbackLoop() const { return m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state; } private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 01295a4d..717d14fe 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -711,14 +711,13 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* continue; } - info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - LatteTexture* baseTexture = textureView->baseTexture; + LatteTextureVk* baseTexture = static_cast(textureView->baseTexture); + info.imageLayout = baseTexture->GetDefaultLayout(); // get texture register word 0 uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; @@ -731,7 +730,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* textureView->AddDescriptorSetReference(dsInfo); if (!baseTexture->IsCompressedFormat()) - vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture); + vectorAppendUnique(dsInfo->list_fboCandidates, baseTexture); uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) @@ -1001,7 +1000,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* return dsInfo; } -void VulkanRenderer::sync_inputTexturesChanged() +void VulkanRenderer::sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass) { bool writeFlushRequired = false; @@ -1050,14 +1049,27 @@ void VulkanRenderer::sync_inputTexturesChanged() srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - // dst - dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + if (withinFeedbackLoopRenderPass) + { + // this renderpass has a pixel self dependency. Feedback loop extension allows barriers during renderpass, but they must be BY_REGION + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + } + else + { + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + } + + VkDependencyFlags dependencyFlags = withinFeedbackLoopRenderPass ? VK_DEPENDENCY_BY_REGION_BIT : 0; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, dependencyFlags, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); @@ -1180,22 +1192,32 @@ bool s_syncOnNextDraw = false; void VulkanRenderer::draw_setRenderPass() { CachedFBOVk* fboVk = m_state.activeFBO; + // note - pixel self dependency can be handled via feedback_loop extension + // vertex/geometry self dependency needs renderpass split + CachedFBOVk::RendertargetSelfDependencyMask renderSelfDependencyInfo{}; - // update self-dependency flag + // update self-dependency state if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk) { - m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); } auto vkObjRenderPass = fboVk->GetRenderPassObj(); auto vkObjFramebuffer = fboVk->GetFramebufferObj(); - bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); + bool feedbackLoopHandlesSelfDependency = UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.HasSelfDependency() && !renderSelfDependencyInfo.HasVertexOrGeometrySelfDependency(); + bool selfDependencyNeedsPassSplit = renderSelfDependencyInfo.HasSelfDependency() && !feedbackLoopHandlesSelfDependency; + bool overridePassReuse = selfDependencyNeedsPassSplit && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk) { if (m_state.descriptorSetsChanged) - sync_inputTexturesChanged(); + sync_inputTexturesChanged(feedbackLoopHandlesSelfDependency); + if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect) + { + m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask(); + vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask()); + } return; } draw_endRenderPass(); @@ -1203,7 +1225,7 @@ void VulkanRenderer::draw_setRenderPass() sync_inputTexturesChanged(); // assume that FBO changed, update self-dependency state - m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); sync_RenderPassLoadTextures(fboVk); @@ -1228,6 +1250,11 @@ void VulkanRenderer::draw_setRenderPass() } m_state.activeRenderpassFBO = fboVk; + if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect) + { + m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask(); + vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask()); + } vkObjRenderPass->flagForCurrentCommandBuffer(); vkObjFramebuffer->flagForCurrentCommandBuffer(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index e3e42012..d102a89e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -557,7 +557,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0]; descriptorImageInfo.imageView = vkObjImageView->m_textureImageView; - descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + descriptorImageInfo.imageLayout = state.sourceTexture->GetDefaultLayout(); VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -673,8 +673,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image); - barrier_image(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete - barrier_image(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete + barrier_image(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for any modifying operations on source image to complete + barrier_image(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for any operations on destination image to complete vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -692,8 +692,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 vkCmdEndRenderPass(m_state.currentCommandBuffer); - barrier_image(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image - barrier_image(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image + barrier_image(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the source image + barrier_image(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the destination image // restore viewport and scissor box vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); From 4c4ef50f493e6618958b99a7c47375bac60b4533 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 27 Jun 2026 06:43:42 +0200 Subject: [PATCH 14/14] Vulkan: Smaller fixes + code cleanup --- src/Cafe/HW/Latte/Core/LatteBufferData.cpp | 8 +++++- .../HW/Latte/Core/LatteCommandProcessor.cpp | 17 +++---------- src/Cafe/HW/Latte/Core/LatteShader.cpp | 2 +- .../LatteDecompilerAnalyzer.cpp | 15 +++++++++++ .../HW/Latte/Renderer/Metal/MetalRenderer.cpp | 2 +- .../HW/Latte/Renderer/Metal/MetalRenderer.h | 5 ---- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 2 +- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 -- src/Cafe/HW/Latte/Renderer/Renderer.h | 4 ++- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 25 ++++++++----------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 8 +++--- .../Renderer/Vulkan/VulkanRendererCore.cpp | 23 +++++++---------- src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp | 5 ++-- src/util/CMakeLists.txt | 1 + src/util/helpers/StateHasher.h | 8 +++--- 15 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp index e7fb4893..71b4a145 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -194,17 +194,23 @@ void LatteBufferCache_Sync(uint32 maxIndex, uint32 baseInstance, uint32 instance if (isIncremental) { // 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 + uint32 maxInstance = baseInstance + instanceCount - 1; + bool hasBufferChange = attribBufferDirtyMask != 0; if ( maxIndex > s_vtxStateMaxIndex ) { attribBufferDirtyMask = 0xFFFFFFFF; s_vtxStateMaxIndex = maxIndex; } - uint32 maxInstance = baseInstance + instanceCount - 1; if ( maxInstance > s_vtxStateMaxInstance ) { attribBufferDirtyMask = 0xFFFFFFFF; s_vtxStateMaxInstance = maxInstance; } + if (hasBufferChange) + { + s_vtxStateMaxIndex = maxIndex; + s_vtxStateMaxInstance = maxInstance; + } } else { diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 42ade0a6..82cf041c 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -1021,7 +1021,6 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt LatteCMDPtr cmd, cmdStart, cmdEnd; if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) { - // cemuLog_log(LogType::Force, "[FastDrawMode] End due running out of commands"); drawPassCtx.endDrawPass(); return; } @@ -1049,7 +1048,6 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt (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))) { - // 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) @@ -1089,7 +1087,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt drawPassCtx.MarkVSAluConstantsDirty(); else drawPassCtx.MarkPSAluConstantsDirty(); - // todo - we could further optimize by tracking the min/max range of modified ALU constants + // todo - we could further optimize by tracking the min/max range of modified ALU constants and only uploading the affected range. Possibly not worth it }); break; } @@ -1120,11 +1118,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } case IT_SET_CONTEXT_REG: { - // LatteCP_itSetRegistersGeneric(cmd, nWords); bool hasChanged = LatteCP_itSetRegistersGeneric2(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){}); - //cemuLog_log(LogType::Force, "[FastDrawMode] Potential end due to SetContextReg (itHeader {:08x} data {:08x}) -> HasChanged {}", itHeader, (uint32)cmdData[0], hasChanged); - // drawPassCtx.endDrawPass(); - // drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); if (hasChanged) { drawPassCtx.endDrawPass(); @@ -1144,10 +1138,8 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt case IT_SET_SAMPLER: { bool hasChanged = LatteCP_itSetRegistersGeneric2(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){}); - //cemuLog_log(LogType::Force, "[FastDrawMode] Set sampler (reg change = {} - filtered)", hasChanged); if (hasChanged) { - //cemuLog_log(LogType::Force, "[FastDrawMode] End due to sampler change"); drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); return; @@ -1155,9 +1147,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt break; } default: - // unsupported command for fast draw - //cemuLog_log(LogType::Force, "[FastDrawMode] Unallowed command. Data: {:08x} {:08x} {:08x} {:08x} {:08x} {:08x}", (uint32)itHeader, (uint32)cmdData[0], (uint32)cmdData[1], (uint32)cmdData[2], (uint32)cmdData[3], (uint32)cmdData[4], (uint32)cmdData[5]); - //cemuLog_log(LogType::Force, "[FastDrawMode] End due to unallowed packet 0x{:08x}", itHeader); + // unallowed command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; @@ -1169,8 +1159,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } else { - // unsupported command for fast draw - //cemuLog_log(LogType::Force, "[FastDrawMode] End due to uknown packet 0x{:08x}", itHeader); + // unallowed command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index 38fc5504..4e89b02f 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -1062,7 +1062,7 @@ robin_hood::unordered_flat_map s // folly::F14FastMap // robin_hood::unordered_flat_map ended up performing close to best and was choosen because we already have it included in the project anyway -FORCEINLINE uint64 CalcCombinedAuxHash(LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader) +FORCE_INLINE uint64 CalcCombinedAuxHash(LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader) { uint64 vsAuxHash = vertexShader ? LatteSHRC_CalcVSAuxHash(vertexShader, LatteGPUState.contextRegister) : 0; uint64 psAuxHash = pixelShader ? LatteSHRC_CalcPSAuxHash(pixelShader, LatteGPUState.contextRegister) : 0; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index 6982b408..6fc279ba 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -1027,6 +1027,21 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD break; } } + // some existing graphic pack replacement shaders rely on uf_fragCoordScale despite the original shader not needing it. We handle these exceptions here + switch (shaderContext->shaderBaseHash) + { + case 0x21e6bc9b0cdbe8d7: case 0x37040a485a29d54e: case 0x37a4ec1a7dbc7391: + case 0x50e29e8929cea348: case 0x572a6cfa3943923d: case 0x59df1c7e1806366c: + case 0x6ea8b1aa69c0b6f7: case 0x88133ee405eaae28: case 0x95a5a89d62998e0d: + case 0x998a9f67e353657b: case 0x9f6adb9a651f84b9: case 0xa5e9d150276a805c: + case 0xa7f4801a8d29e333: case 0xbe99d80628d31127: case 0xc14019840473ff86: + case 0xc612390d4c70f430: case 0xcb0e6e8cbec4502a: case 0xe334517825fdd599: + case 0xe39a2a718bc419fe: case 0xfdf33c607cd1d737: case 0xff71dcd2ad4defdc: + shaderContext->analyzer.hasFragCoordAccess = true; + break; + default: + break; + } } // analyze CF stack sint32 cfCurrentStackDepth = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index a694411b..878bcb9e 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -50,7 +50,7 @@ std::vector MetalRenderer::GetDevices() return result; } -MetalRenderer::MetalRenderer() +MetalRenderer::MetalRenderer() : Renderer(RendererAPI::Metal) { // Options diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h index eadad94f..c75f12bf 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h @@ -153,11 +153,6 @@ public: MetalRenderer(); ~MetalRenderer() override; - RendererAPI GetType() override - { - return RendererAPI::Metal; - } - static MetalRenderer* GetInstance() { return static_cast(g_renderer.get()); } diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index a4d1966d..9d30da31 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -111,7 +111,7 @@ static const GLenum glAlphaTestFunc[] = GL_ALWAYS }; -OpenGLRenderer::OpenGLRenderer() +OpenGLRenderer::OpenGLRenderer() : Renderer(RendererAPI::OpenGL) { glRendererState.useTextureUploadBuffer = false; if (glRendererState.useTextureUploadBuffer) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index f691d8da..435ea427 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -35,8 +35,6 @@ public: OpenGLRenderer(); ~OpenGLRenderer(); - RendererAPI GetType() override { return RendererAPI::OpenGL; } - static OpenGLRenderer* GetInstance(); // imgui diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 6a792fa2..6482f37a 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -50,9 +50,10 @@ public: U32 }; + Renderer(RendererAPI api) : m_rendererAPI(api) {}; virtual ~Renderer() = default; - virtual RendererAPI GetType() = 0; + RendererAPI GetType() const { return m_rendererAPI; } virtual void Initialize(); virtual void Shutdown(); @@ -162,6 +163,7 @@ public: protected: virtual void GetVendorInformation() { } + RendererAPI m_rendererAPI; GfxVendor m_vendor = GfxVendor::Generic; static uint8 SRGBComponentToRGB(uint8 ci); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index bf9ae10c..eeba119e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -492,7 +492,7 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc #endif -VulkanRenderer::VulkanRenderer() +VulkanRenderer::VulkanRenderer() : Renderer(RendererAPI::Vulkan) { glslang::InitializeProcess(); @@ -908,7 +908,7 @@ VulkanRenderer::~VulkanRenderer() defaultShaders.copySurface_psDepth2Color = nullptr; // destroy misc - for (auto& it : m_cmd_buffer_fences) + for (auto& it : m_cmdBufferFences) { vkDestroyFence(m_logicalDevice, it, nullptr); it = VK_NULL_HANDLE; @@ -958,11 +958,8 @@ VulkanRenderer::~VulkanRenderer() VulkanRenderer* VulkanRenderer::GetInstance() { -#ifdef CEMU_DEBUG_ASSERT - cemu_assert_debug(g_renderer && dynamic_cast(g_renderer.get())); - // Use #if here because dynamic_casts dont get optimized away even if the result is not stored as with cemu_assert_debug -#endif - return (VulkanRenderer*)g_renderer.get(); + cemu_assert_debug(g_renderer->GetType() == RendererAPI::Vulkan); + return static_cast(g_renderer.get()); } void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow) @@ -1664,14 +1661,14 @@ void VulkanRenderer::CreateCommandPool() void VulkanRenderer::CreateCommandBuffers() { - auto it = m_cmd_buffer_fences.begin(); + auto it = m_cmdBufferFences.begin(); VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); ++it; fenceInfo.flags = 0; - for (; it != m_cmd_buffer_fences.end(); ++it) + for (; it != m_cmdBufferFences.end(); ++it) { vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); } @@ -2131,7 +2128,7 @@ void VulkanRenderer::InitFirstCommandBuffer() m_commandBufferSyncIndex = 0; m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; - vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); + vkResetFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferIndex]); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -2148,7 +2145,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers() bool finishedCmdBuffers = false; while (m_commandBufferSyncIndex != m_commandBufferIndex) { - VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]); + VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmdBufferFences[m_commandBufferSyncIndex]); if (fenceStatus == VK_SUCCESS) { ProcessDestructionQueue(); @@ -2177,7 +2174,7 @@ void VulkanRenderer::WaitForNextFinishedCommandBuffer() { cemu_assert_debug(m_commandBufferSyncIndex != m_commandBufferIndex); // wait on least recently submitted command buffer - VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferSyncIndex], true, UINT64_MAX); + VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferSyncIndex], true, UINT64_MAX); if (result == VK_TIMEOUT) { cemuLog_log(LogType::Force, "vkWaitForFences: Returned VK_TIMEOUT on infinite fence"); @@ -2230,7 +2227,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor submitInfo.pWaitDstStageMask = semWaitStageMask; submitInfo.pWaitSemaphores = waitSemArray; - const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmd_buffer_fences[m_commandBufferIndex]); + const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmdBufferFences[m_commandBufferIndex]); if (result != VK_SUCCESS) UnrecoverableError(fmt::format("failed to submit command buffer. Error {}", result).c_str()); m_numSubmittedCmdBuffers++; @@ -2251,7 +2248,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; - vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); + vkResetFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferIndex]); vkResetCommandBuffer(m_state.currentCommandBuffer, 0); VkCommandBufferBeginInfo beginInfo{}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 4581e9e7..2909440f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -89,7 +89,7 @@ public: }; using DescriptorSetCache = ska::flat_hash_map>; - FORCEINLINE DescriptorSetCache& GetDescriptorSetCache(LatteConst::ShaderType shaderType) + FORCE_INLINE DescriptorSetCache& GetDescriptorSetCache(LatteConst::ShaderType shaderType) { cemu_assert_debug(shaderType == LatteConst::ShaderType::Vertex || shaderType == LatteConst::ShaderType::Pixel || shaderType == LatteConst::ShaderType::Geometry); return ds_cache[static_cast(shaderType)]; @@ -189,8 +189,6 @@ public: VulkanRenderer(); virtual ~VulkanRenderer(); - RendererAPI GetType() override { return RendererAPI::Vulkan; } - static VulkanRenderer* GetInstance(); void UnrecoverableError(const char* errMsg) const; @@ -661,8 +659,8 @@ private: size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) size_t m_commandBufferIDOfPrevFrame = 0; - std::array m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer - std::array m_cmd_buffer_fences; + std::array m_cmdBufferUniformRingbufIndices {}; // read index in the uniform ringbuffer after the command buffer finishes + std::array m_cmdBufferFences; std::array m_commandBuffers; std::array m_commandBufferSemaphores; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 717d14fe..2b50d88f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -320,23 +320,18 @@ uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span condition) { + auto waitWhileCondition = [&](TCondition&& condition) { while (condition()) { if (m_commandBufferSyncIndex == m_commandBufferIndex) { - if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex) - { - draw_endRenderPass(); - SubmitCommandBuffer(); - } - else - { - // submitting work would not change readIndex, so there's no way for conditions based on it to change - cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption"); - cemu_assert_suspicious(); - break; - } + // we caught up to the current command buffer and there is still not enough space in the uniform ringbuffer + // in this case we just accept the corruption. In practice this is very unlikely to happen with recent reductions to uniform size + // If this does continue to be an issue then the likely easiest solution is to start a new command buffer at the end of a draw sequence if the ringbuffer is filled beyond a certain threshold + // submitting the command buffere here is complicated because we'd have to restore all of the dynamic state + cemuLog_log(LogType::Force, "Vulkan: Uniform ring buffer overflowed. Expect visual corruption"); + cemu_assert_suspicious(); + break; } WaitForNextFinishedCommandBuffer(); } @@ -1650,7 +1645,7 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan } else { - __debugbreak(); // should never happen unless draw_prepareDescriptorSets can fail? + cemu_assert(false); // should never happen unless draw_prepareDescriptorSets can fail? } draw_setRenderPass(); diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp index e4ac4443..09964f99 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -199,10 +199,9 @@ void GX2CopySurfaceInternal(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSli return; } // make sure formats are compatible - if( srcHwFormat != dstHwFormat ) + if( surfOutSrc.bpp != surfOutDst.bpp ) { - // mismatching format - cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format mismatch (src=0x{:04x} dst=0x{:04x})", (sint32)srcFormat, (sint32)dstFormat); + cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format bpp mismatch (src=0x{:04x} dst=0x{:04x})", (sint32)srcFormat, (sint32)dstFormat); return; } // get input pointer diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index c170adc5..a23754ef 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(CemuUtil helpers/MapAdaptor.h helpers/MemoryPool.h helpers/ringbuffer.h + helpers/StateHasher.h helpers/Semaphore.h helpers/Serializer.cpp helpers/Serializer.h diff --git a/src/util/helpers/StateHasher.h b/src/util/helpers/StateHasher.h index e46a49ea..c061e9e5 100644 --- a/src/util/helpers/StateHasher.h +++ b/src/util/helpers/StateHasher.h @@ -5,27 +5,27 @@ class DualStateHasher { public: - FORCEINLINE DualStateHasher() + FORCE_INLINE DualStateHasher() { m_h0 = 0x9E3779B97F4A7C15; m_h1 = 0xC2B2AE3D27D4EB4F; }; - FORCEINLINE void MixIn(uint64 a, uint64 b) + FORCE_INLINE void MixIn(uint64 a, uint64 b) { uint64 tmp = m_h1; m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; m_h0 = (tmp ^ b) * 0x165667B19E3779F9ULL; } - FORCEINLINE void MixInSingle(uint64 a) + FORCE_INLINE void MixInSingle(uint64 a) { uint64 tmp = m_h1; m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; m_h0 = tmp; } - FORCEINLINE uint64 Finish() + FORCE_INLINE uint64 Finish() { uint64 combined = m_h0 ^ std::rotl(m_h1, 31); combined ^= combined >> 33;