From c85c9439a540276ea7e0193ca99e053956559765 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 9 Oct 2026 07:42:03 -0400 Subject: [PATCH] Magma: the pipeline, program and sampler hashes read their thread_local state once per hash (GetOrCreatePipeline 0.026 -> 0.013 ms/frame). --- .../DirectVulkan/Renderer/PipelineFactory.cpp | 84 ++++++++++--------- .../DirectVulkan/Renderer/ProgramFactory.cpp | 24 +++--- .../Renderer/VkSamplerManager.cpp | 38 +++++---- 3 files changed, 76 insertions(+), 70 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index ffeb571a..0e9aaae1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -194,67 +194,69 @@ namespace MobileGL::MG_Backend::DirectVulkan { } PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const { - XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout))); + // P15: one read of the thread_local per hash (emulated TLS: every read is a call). + XXH64_state_t* const hashState = m_hashState; + XXHASH_VERIFY(XXH64_reset(hashState, m_config.CacheVersion)); + XXHASH_VERIFY(XXH64_update(hashState, &payload.programHash, sizeof(payload.programHash))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout))); if (payload.wireRenderPassCompatibilityId != 0) { constexpr Uint64 wireDomain = 0x5749524552504b59ull; - XXHASH_VERIFY(XXH64_update(m_hashState, &wireDomain, sizeof(wireDomain))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.wireRenderPassCompatibilityId, + XXHASH_VERIFY(XXH64_update(hashState, &wireDomain, sizeof(wireDomain))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.wireRenderPassCompatibilityId, sizeof(payload.wireRenderPassCompatibilityId))); } else { - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.renderPass, sizeof(payload.renderPass))); } - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleShadingEnable, sizeof(payload.sampleShadingEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.minSampleShading, sizeof(payload.minSampleShading))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleMask, sizeof(payload.sampleMask))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.sampleShadingEnable, sizeof(payload.sampleShadingEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.minSampleShading, sizeof(payload.minSampleShading))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.sampleMask, sizeof(payload.sampleMask))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.subpass, sizeof(payload.subpass))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.topology, sizeof(payload.topology))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.passthroughTessControlKey, + XXH64_update(hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.passthroughTessControlKey, sizeof(payload.passthroughTessControlKey))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.viewportCount, sizeof(payload.viewportCount))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.viewportCount, sizeof(payload.viewportCount))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.polygonMode, sizeof(payload.polygonMode))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.cullMode, sizeof(payload.cullMode))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.frontFace, sizeof(payload.frontFace))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable))); + XXH64_update(hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp))); + XXH64_update(hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.logicOp, sizeof(payload.logicOp))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp))); + XXH64_update(hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp))); - XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp))); + XXH64_update(hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp))); + XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp))); + XXH64_update(hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp))); + XXH64_update(hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp))); XXHASH_VERIFY( - XXH64_update(m_hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth))); + XXH64_update(hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth))); if (payload.colorAttachmentCount > 0) { XXHASH_VERIFY(XXH64_update( - m_hashState, + hashState, payload.colorBlendAttachments.data(), sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount)); } - return XXH64_digest(m_hashState); + return XXH64_digest(hashState); } VkPipeline PipelineFactory::GetOrCreatePipeline(const PipelineCreatePayload& payload) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 5b50bc15..e0727381 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -2575,26 +2575,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { ProgramFactory::HashType ProgramFactory::ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const { - XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); + // P15: one read of the thread_local per hash (emulated TLS: every read is a call). + XXH64_state_t* const hashState = m_hashState; + XXHASH_VERIFY(XXH64_reset(hashState, m_config.CacheVersion)); // We expect shader stages in program object are sorted const auto& spirvs = program.GetGeneratedSpirv(); for (const auto& spv : spirvs) { - XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint))); + XXHASH_VERIFY(XXH64_update(hashState, spv.data(), spv.size() * sizeof(Uint))); } - XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags))); + XXHASH_VERIFY(XXH64_update(hashState, &flags, sizeof(CompileOptionFlags))); // Only FragCoordYFlip variants bake the height in, so mixing it unconditionally would // re-key every program in the cache on a resize for no reason. if (flags & CompileOptionBit::FragCoordYFlip) { - XXHASH_VERIFY(XXH64_update(m_hashState, &m_defaultFramebufferHeight, + XXHASH_VERIFY(XXH64_update(hashState, &m_defaultFramebufferHeight, sizeof(m_defaultFramebufferHeight))); } // Include UBO block bindings in hash so different binding configurations produce different entries const Uint32 blockCount = static_cast(program.GetActiveUniformBlocksCount()); - XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount))); + XXHASH_VERIFY(XXH64_update(hashState, &blockCount, sizeof(blockCount))); for (Uint32 i = 0; i < blockCount; ++i) { const Uint32 binding = program.GetUniformBlockBinding(i); - XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding))); + XXHASH_VERIFY(XXH64_update(hashState, &binding, sizeof(binding))); } // The transform feedback capture layout is baked into the modules by @@ -2605,18 +2607,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { // hashed for a capturing compile, so nothing else changes key. if (flags & CompileOptionBit::XfbCapture) { for (const auto& varying : program.GetTransformFeedbackVaryings()) { - XXHASH_VERIFY(XXH64_update(m_hashState, varying.name.data(), varying.name.size())); - XXHASH_VERIFY(XXH64_update(m_hashState, &varying.bufferIndex, sizeof(varying.bufferIndex))); - XXHASH_VERIFY(XXH64_update(m_hashState, &varying.offsetBytes, sizeof(varying.offsetBytes))); + XXHASH_VERIFY(XXH64_update(hashState, varying.name.data(), varying.name.size())); + XXHASH_VERIFY(XXH64_update(hashState, &varying.bufferIndex, sizeof(varying.bufferIndex))); + XXHASH_VERIFY(XXH64_update(hashState, &varying.offsetBytes, sizeof(varying.offsetBytes))); } const SizeT bufferCount = program.GetTransformFeedbackBufferCount(); for (SizeT i = 0; i < bufferCount; ++i) { const Uint32 stride = program.GetTransformFeedbackStride(static_cast(i)); - XXHASH_VERIFY(XXH64_update(m_hashState, &stride, sizeof(stride))); + XXHASH_VERIFY(XXH64_update(hashState, &stride, sizeof(stride))); } } - HashType hash = XXH64_digest(m_hashState); + HashType hash = XXH64_digest(hashState); return hash; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp index 7123442a..446ab557 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp @@ -326,46 +326,48 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool forceNearestFiltering, Bool singleLevelView, const ResolvedBorderColor& borderColor) const { MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null"); - XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion)); + // P15: one read of the thread_local per hash (emulated TLS: every read is a call). + XXH64_state_t* const hashState = m_hashState; + XXHASH_VERIFY(XXH64_reset(hashState, m_config->CacheVersion)); - XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering))); - XXHASH_VERIFY(XXH64_update(m_hashState, &singleLevelView, sizeof(singleLevelView))); + XXHASH_VERIFY(XXH64_update(hashState, &forceNearestFiltering, sizeof(forceNearestFiltering))); + XXHASH_VERIFY(XXH64_update(hashState, &singleLevelView, sizeof(singleLevelView))); const auto minFilter = sampler.GetMinFilter(); - XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter))); + XXHASH_VERIFY(XXH64_update(hashState, &minFilter, sizeof(minFilter))); const auto magFilter = sampler.GetMagFilter(); - XXHASH_VERIFY(XXH64_update(m_hashState, &magFilter, sizeof(magFilter))); + XXHASH_VERIFY(XXH64_update(hashState, &magFilter, sizeof(magFilter))); const auto mipmapMode = sampler.GetMipmapMode(); - XXHASH_VERIFY(XXH64_update(m_hashState, &mipmapMode, sizeof(mipmapMode))); + XXHASH_VERIFY(XXH64_update(hashState, &mipmapMode, sizeof(mipmapMode))); const auto wrapS = sampler.GetWrapS(); - XXHASH_VERIFY(XXH64_update(m_hashState, &wrapS, sizeof(wrapS))); + XXHASH_VERIFY(XXH64_update(hashState, &wrapS, sizeof(wrapS))); const auto wrapT = sampler.GetWrapT(); - XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT))); + XXHASH_VERIFY(XXH64_update(hashState, &wrapT, sizeof(wrapT))); const auto wrapR = sampler.GetWrapR(); - XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR))); + XXHASH_VERIFY(XXH64_update(hashState, &wrapR, sizeof(wrapR))); const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView); const auto minLod = ResolveEffectiveMinLod(sampler, maxLod); - XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod))); - XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod))); + XXHASH_VERIFY(XXH64_update(hashState, &minLod, sizeof(minLod))); + XXHASH_VERIFY(XXH64_update(hashState, &maxLod, sizeof(maxLod))); const auto lodBias = sampler.GetLodBias(); - XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias))); + XXHASH_VERIFY(XXH64_update(hashState, &lodBias, sizeof(lodBias))); // The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan // will not apply (NEAREST filtering, or requests past the device limit) must still share one // VkSampler, while two samplers that really do differ must not collide onto the first one's. const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering); - XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy))); + XXHASH_VERIFY(XXH64_update(hashState, &maxAnisotropy, sizeof(maxAnisotropy))); const auto compareMode = sampler.GetCompareMode(); - XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode))); + XXHASH_VERIFY(XXH64_update(hashState, &compareMode, sizeof(compareMode))); const auto compareFunc = sampler.GetSamplerCompareFunc(); - XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc))); + XXHASH_VERIFY(XXH64_update(hashState, &compareFunc, sizeof(compareFunc))); // The resolved enum AND, when it is one of the *_CUSTOM_EXT values, the sixteen bytes of the // colour itself: two samplers that differ only in a custom border colour carry the same enum // and would otherwise collide onto whichever one was created first. - XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor.color, sizeof(borderColor.color))); + XXHASH_VERIFY(XXH64_update(hashState, &borderColor.color, sizeof(borderColor.color))); if (borderColor.isCustom) { - XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor.customValue, sizeof(borderColor.customValue))); + XXHASH_VERIFY(XXH64_update(hashState, &borderColor.customValue, sizeof(borderColor.customValue))); } - return XXH64_digest(m_hashState); + return XXH64_digest(hashState); } template