From 545754635a2d512132847a4a077d134adf619f36 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] 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];