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/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp index 4ea633ba..0779fac9 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" @@ -17,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) @@ -243,6 +242,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; @@ -470,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 @@ -503,7 +506,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 +542,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/FetchShader.h b/src/Cafe/HW/Latte/Core/FetchShader.h index db72987b..257cb2f1 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 @@ -63,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/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index b6ff6ba0..711bb087 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -70,6 +70,20 @@ 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: + 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 #include "Cafe/HW/Latte/Core/LatteTexture.h" @@ -154,8 +168,8 @@ void LatteCP_ProcessRingbuffer(); // buffer cache -bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount); -void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData); +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 33d148bb..71b4a145 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -70,10 +70,9 @@ 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) +bool LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask) { + bool hasChange = false; uint32 shaderAluConst; uint32 shaderUniformRegisterOffset; @@ -92,139 +91,205 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: - cemu_assert_debug(false); + UNREACHABLE; } // sourced from uniform registers - uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; - for (auto it : shader->list_remappedUniformEntries_register) + if (aluConstDirty) { - uint64* uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4); - uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - regDest[0] = uniformRegData[0]; - regDest[1] = uniformRegData[1]; + uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; + for (auto it : shader->list_remappedUniformEntries_register) + { + 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 - for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) + if (uniformBufferDirtyMask) { - MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4]; - if (physicalAddr) + for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) { - uint8* uniformBase = memory_base + physicalAddr; - for (auto& it : bufferGroup.entries) - { - uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); - uint64* 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; - } - } - } -} - -void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) -{ - if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) - { - for(const auto& buf : shader->list_quickBufferList) - { - 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); + 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; } -// upload vertex and uniform buffers -bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount) +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{}; + +void LatteBufferCache_ProcessQueues() { static uint32 s_syncBufferCounter = 0; s_syncBufferCounter++; - if (s_syncBufferCounter >= 30) + if (s_syncBufferCounter >= 25) { LatteBufferCache_incrementalCleanup(); s_syncBufferCounter = 0; } - LatteBufferCache_processDCFlushQueue(); // process queued deallocations from previous drawcall LatteBufferCache_processDeallocations(); +} - // sync and bind vertex buffers +// upload vertex and uniform buffers and update bindings +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(); - if (!parsedFetchShader) - return false; - for (auto& bufferGroup : parsedFetchShader->bufferGroups) + cemu_assert_debug(parsedFetchShader); + + // 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) { - 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; - - if (bufferAddress == MPTR_NULL) + // 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 ) { - g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0); - continue; + attribBufferDirtyMask = 0xFFFFFFFF; + s_vtxStateMaxIndex = maxIndex; } - - // dont rely on buffer size given by game - uint32 fixedBufferSize = 0; - if (bufferGroup.hasVtxIndexAccess) - fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset; - if (bufferGroup.hasInstanceIndexAccess) + if ( maxInstance > s_vtxStateMaxInstance ) { - uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset; - fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance); + attribBufferDirtyMask = 0xFFFFFFFF; + s_vtxStateMaxInstance = maxInstance; } - if (fixedBufferSize == 0 || bufferStride == 0) - fixedBufferSize += 128; + if (hasBufferChange) + { + s_vtxStateMaxIndex = maxIndex; + s_vtxStateMaxInstance = maxInstance; + } + } + else + { + LatteBufferCache_ProcessQueues(); + s_vtxStateMaxIndex = maxIndex; + uint32 maxInstance = baseInstance + instanceCount - 1; + s_vtxStateMaxInstance = maxInstance; + } + attribBufferDirtyMask &= parsedFetchShader->attributeBufferMask; + + // 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(bufferStride % 4 != 0) { - if (VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance()) - { - auto fixedBuffer = vkRenderer->buffer_genStrideWorkaroundVertexBuffer(bufferAddress, fixedBufferSize, bufferStride); - vkRenderer->buffer_bindVertexStrideWorkaroundBuffer(fixedBuffer.first, fixedBuffer.second, bufferIndex, fixedBufferSize); - continue; - } + 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); + uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize); + g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize); + } } // 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); - return true; + // 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, vsUniformBufferDirtyMask)) + stageUniformModifiedMask |= (1<uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + 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, gsUniformBufferDirtyMask) ) + stageUniformModifiedMask |= (1<draw_beginSequence(); } @@ -71,18 +73,22 @@ 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_vertexBufferChanged = false; - m_uniformBufferChanged = false; + m_drawcallContext.isFirst = false; + m_drawcallContext.vertexBufferDirtyMask = 0; + m_drawcallContext.vsUniformBufferDirtyMask = 0; + m_drawcallContext.psUniformBufferDirtyMask = 0; + m_drawcallContext.gsUniformBufferDirtyMask = 0; + m_drawcallContext.aluConstVSDirty = false; + m_drawcallContext.aluConstPSDirty = false; } void endDrawPass() @@ -91,14 +97,34 @@ public: m_drawPassActive = false; } - void notifyModifiedVertexBuffer() + void MarkVertexBufferDirty(uint32 index) { - m_vertexBufferChanged = true; + m_drawcallContext.vertexBufferDirtyMask |= (1< m_queuePosStack; + LatteDrawcallContext m_drawcallContext{}; + boost::container::static_vector m_queuePosStack; }; void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx); @@ -292,6 +316,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; @@ -299,13 +324,13 @@ 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) + while (nWords--) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; @@ -318,11 +343,19 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) else { // state shadowing disabled - sint32 indexCounter = 0; - 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 @@ -330,30 +363,31 @@ 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 + nWords--; + const uint32 registerOffset = LatteReadCMD(); + const uint32 registerIndex = TRegisterBase + registerOffset; + const uint32 registerStartIndex = registerIndex; + const uint32 registerEndIndex = registerStartIndex + nWords - 1; 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 uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; - while (--nWords) + while (nWords--) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; if (regShadowAddr) *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); + hasRegChange |= (outputReg[indexCounter] != dataWord); outputReg[indexCounter] = dataWord; indexCounter++; } @@ -361,16 +395,30 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRa else { // state shadowing disabled - sint32 indexCounter = 0; - while (--nWords) + if (nWords == 1) // common case { - *outputReg = LatteReadCMD(); - 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 LatteCP_itSetRegistersGeneric_handleSpecialRanges(registerStartIndex, registerEndIndex); - return cmd; + // callback + cbRegRange(registerStartIndex, registerEndIndex, hasRegChange); + return hasRegChange; } LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords) @@ -992,16 +1040,36 @@ 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 (!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))) + { 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(); - else - drawPassCtx.notifyModifiedUniformBuffer(); + { + uint32 bufferIndex = (registerStart - mmSQ_VTX_ATTRIBUTE_BLOCK_START) / 7; + drawPassCtx.MarkVertexBufferDirty(bufferIndex); + } + else if (registerStart >= mmSQ_VTX_UNIFORM_BLOCK_START && registerEnd <= mmSQ_VTX_UNIFORM_BLOCK_END) + { + 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()) { @@ -1012,7 +1080,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 and only uploading the affected range. Possibly not worth it + }); break; } case IT_SET_CTL_CONST: @@ -1042,9 +1118,14 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } case IT_SET_CONTEXT_REG: { - drawPassCtx.endDrawPass(); - drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); - return; + bool hasChanged = LatteCP_itSetRegistersGeneric2(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){}); + if (hasChanged) + { + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + return; + } + break; } case IT_INDIRECT_BUFFER_PRIV: { @@ -1052,13 +1133,21 @@ 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){}); + if (hasChanged) + { + drawPassCtx.endDrawPass(); + drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); + return; + } break; } default: - // unsupported command for fast draw + // unallowed command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; @@ -1070,7 +1159,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt } else { - // unsupported command for fast draw + // unallowed command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; @@ -1179,6 +1268,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 +1282,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 +1539,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 +1549,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 +1559,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; diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h index ebe741e9..cfc3c288 100644 --- a/src/Cafe/HW/Latte/Core/LatteConst.h +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -10,7 +10,7 @@ #define LATTE_NUM_COLOR_TARGET 8 #define LATTE_NUM_MAX_TEX_UNITS 18 // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?) -#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations +#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations per shader stage #define LATTE_VS_ATTRIBUTE_LIMIT 32 // todo: verify #define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS 256 // should this be 128 since there are only 128 GPRs? @@ -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/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/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 2edcf4ea..79b2d694 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< // 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; @@ -217,7 +225,7 @@ void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint3 uint32 spi0_positionEnable = (psControl0 >> 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 +286,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; @@ -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 + +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; + 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,72 +1156,163 @@ 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) - { - 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) + 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 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) @@ -1106,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() @@ -1119,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/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/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 b370123e..5e1421ef 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) @@ -224,13 +209,15 @@ 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; std::vector<_RemappedUniformBufferGroup> list_remappedUniformEntries_bufferGroups; + // keys in shader state cache + std::vector m_shaderStateCacheKeys; }; struct LatteDecompilerOutputUniformOffsets diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index e8d5d490..6fc279ba 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 @@ -549,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 @@ -594,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) { @@ -993,6 +1011,38 @@ 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; + } + } + // 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; 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/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index 8c2da361..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 @@ -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) { @@ -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 @@ -1164,7 +1163,8 @@ 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 - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + uint8 stageUniformModifiedMask = 0; + LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); } // Render pass @@ -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 @@ -2138,7 +2138,7 @@ void MetalRenderer::BindStageResources(MTL::RenderCommandEncoder* renderCommandE } 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) { diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h index e905b834..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()); } @@ -250,7 +245,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.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 5b9ccd41..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 @@ -159,7 +157,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..a67306b3 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.maxIndex + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); _setupVertexAttributes(); @@ -1148,9 +1149,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/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/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 01a6d3b2..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(); @@ -140,7 +141,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 @@ -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/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; } 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/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/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/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 b4d99e35..eeba119e 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,10 +348,18 @@ 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); 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 @@ -466,7 +492,7 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc #endif -VulkanRenderer::VulkanRenderer() +VulkanRenderer::VulkanRenderer() : Renderer(RendererAPI::Vulkan) { glslang::InitializeProcess(); @@ -696,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); @@ -867,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; @@ -917,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) @@ -1120,7 +1158,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; @@ -1176,6 +1214,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()); @@ -1280,6 +1322,11 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector(texViewVk->baseTexture)->GetDefaultLayout(); imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); @@ -3372,29 +3421,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); @@ -3414,7 +3442,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()); } } @@ -3425,7 +3453,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) @@ -3466,7 +3494,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) @@ -3569,7 +3597,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, @@ -3647,13 +3675,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 @@ -3664,7 +3690,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) @@ -3789,6 +3816,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) @@ -3799,6 +3827,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); } @@ -4081,6 +4110,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) @@ -4114,7 +4147,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; @@ -4123,8 +4156,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; @@ -4141,7 +4174,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{}; @@ -4159,8 +4192,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); } @@ -4181,12 +4214,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 62ecd797..2909440f 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>; + + 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)]; + } // 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; @@ -177,8 +189,6 @@ public: VulkanRenderer(); virtual ~VulkanRenderer(); - RendererAPI GetType() override { return RendererAPI::Vulkan; } - static VulkanRenderer* GetInstance(); void UnrecoverableError(const char* errMsg) const; @@ -368,8 +378,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{}; @@ -403,6 +412,7 @@ private: activeIndexType = Renderer::INDEX_TYPE::NONE; activeIndexBufferIndex = std::numeric_limits::max(); activeIndexBufferOffset = std::numeric_limits::max(); + feedbackLoopImageAspect = 0xFFFFFFFF; } // invalidation / flushing @@ -454,6 +464,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 @@ -470,6 +482,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 @@ -528,7 +542,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(); @@ -540,7 +554,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); @@ -549,7 +563,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, uint8& stageUniformModifiedMask, float* __restrict uniformBuf, bool aluConstDirty, uint32 uniformBufferDirtyMask); // misc void CreatePipelineCache(); @@ -633,7 +648,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; @@ -644,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; @@ -661,6 +676,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 { @@ -934,6 +953,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 d0933e35..2b50d88f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -311,30 +311,27 @@ 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) { - 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) { + 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(); } @@ -375,90 +372,133 @@ 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; + 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->resourceMapping.uniformVarsBufferBindingPoint >= 0) + if (shader->uniform.loc_alphaTestRef >= 0) { - if (shader->uniform.list_ufTexRescale.empty() == false) + *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), true, (1<uniform.loc_uniformRegister >= 0) + { + sint32 shaderAluConst; + switch (shader->shaderType) { - for (auto& entry : shader->uniform.list_ufTexRescale) + 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, 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); }; + 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_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), aluConstDirty, uniformBufferDirtyMask); + } + if (shader->uniform.loc_uniformRegister >= 0 && aluConstDirty) + { + cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE); + 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; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + 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) - { - 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}); + 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) + { + dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize}); + stageUniformModifiedMask |= (1 << shaderStageIndex); } } -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]) @@ -481,40 +521,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.getTextureUnitFromBindingPoint(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]; + const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i); + 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]; @@ -535,31 +579,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; } @@ -601,7 +641,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; @@ -666,14 +706,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]; @@ -686,7 +725,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) @@ -951,31 +990,12 @@ 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; } -void VulkanRenderer::sync_inputTexturesChanged() +void VulkanRenderer::sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass) { bool writeFlushRequired = false; @@ -1024,14 +1044,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(); @@ -1154,22 +1187,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(); @@ -1177,7 +1220,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); @@ -1202,6 +1245,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(); @@ -1300,11 +1348,10 @@ 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) { - LatteGPUState.drawCallCounter++; return; } @@ -1312,13 +1359,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; } @@ -1332,11 +1377,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 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; @@ -1345,13 +1390,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 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 - bool isPrevIndexData = false; if (hostIndexType != INDEX_TYPE::NONE) { uint32 indexBufferIndex = indexReservation->bufferIndex; @@ -1370,8 +1413,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) @@ -1391,36 +1432,12 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 else { // synchronize vertex and uniform cache and update buffer bindings - LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + uint8 stageUniformModifiedMask = 0; + LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask); } - PipelineInfo* pipeline_info; - - if (!isFirst) - { - 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 - } - } - else - { - pipeline_info = draw_getOrCreateGraphicsPipeline(count); - m_state.activePipelineInfo = pipeline_info; - } + PipelineInfo* pipeline_info = draw_getOrCreateGraphicsPipeline(count); + m_state.activePipelineInfo = pipeline_info; auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) @@ -1430,22 +1447,205 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 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) + { + 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(); + + 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, stageUniformModifiedMask, s_vkUniformDataVS, drawcallContext.aluConstVSDirty, drawcallContext.vsUniformBufferDirtyMask); + if (pixelShader) + 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, stageUniformModifiedMask, s_vkUniformDataGS, false, drawcallContext.gsUniformBufferDirtyMask); + // 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 indexMax = 0; + Renderer::IndexAllocation 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) + { + 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(indexMax + baseVertex, baseInstance, instanceCount, drawcallContext.vertexBufferDirtyMask, drawcallContext.vsUniformBufferDirtyMask, drawcallContext.psUniformBufferDirtyMask, drawcallContext.gsUniformBufferDirtyMask, stageUniformModifiedMask, true); + } + + 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)) [[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 + { + // sanity check that we are using the right pipeline +#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; + if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) + { + // invalid/uninitialized pipeline + //m_state.activeVertexDS = nullptr; + 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; 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; + cemu_assert(false); // should never happen unless draw_prepareDescriptorSets can fail? } draw_setRenderPass(); @@ -1471,50 +1671,63 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 // 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; - 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 @@ -1524,7 +1737,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++; } 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); 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/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; 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/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..c061e9e5 --- /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: + FORCE_INLINE DualStateHasher() + { + m_h0 = 0x9E3779B97F4A7C15; + m_h1 = 0xC2B2AE3D27D4EB4F; + }; + + FORCE_INLINE void MixIn(uint64 a, uint64 b) + { + uint64 tmp = m_h1; + m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; + m_h0 = (tmp ^ b) * 0x165667B19E3779F9ULL; + } + + FORCE_INLINE void MixInSingle(uint64 a) + { + uint64 tmp = m_h1; + m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL; + m_h0 = tmp; + } + + FORCE_INLINE 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; +};