From 0eebe23a1214e9b1a03bb4c107482caa0e966262 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:53:50 +0200 Subject: [PATCH] perf(agc): reuse frontend within shader registration pairs Retain an immutable decoded program and structured control flow while preparing the registration variants for one shader. Match owned code, stage and the complete SWAPPC inputs before reuse. Translate and plan each variant separately using its current inputs. Keep cache keys and persisted artifact layouts unchanged. Provide a cache-neutral off switch for controlled comparisons. Cover identity, lifetime, failed replacements and variant equivalence. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 6 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 3 +- .../libSceAgcDriver/tests/ShaderMemory.cpp | 259 ++++++++++++++++++ .../include/Optimization/ResourceProgram.hpp | 11 + core/shader/recompiler/Recompiler.cpp | 76 +++-- core/shader/recompiler/ShaderDiskCache.cpp | 1 + 6 files changed, 334 insertions(+), 22 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index e6db17bf8..c57cb8af8 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -667,8 +667,12 @@ target_include_directories(agc_driver_prepared_shaders_tests PRIVATE if(BUILD_TESTING) add_test(NAME agc_shader_bindless COMMAND agc_shader_memory_tests --bindless) set_tests_properties(agc_shader_bindless PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_shader_frontend_pair COMMAND agc_shader_memory_tests --frontend-pair) + add_test(NAME agc_shader_frontend_pair_off COMMAND agc_shader_memory_tests --frontend-pair) + set_tests_properties(agc_shader_frontend_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + set_tests_properties(agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_FRONTEND_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) - set_tests_properties(agc_shader_bindless PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_shader_bindless agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests ${agcExecutionTests}) if(agcTest STREQUAL "agc_driver_constant_store_tests") diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 109b381a7..90c4f3892 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -372,9 +372,10 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap ShaderRecompiler::RecompileRequest request{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, userData, compute, pixel, vertex, memory, RegisteredFloatMode(snapshot)}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics}; if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes; std::vector entries; + ShaderRecompiler::ShaderPreparationContext preparation; const auto append = [&] { PerformanceTimer timing("Shader.PrepareArtifact"); - entries.push_back({codeOffset, ShaderRecompiler::PrepareShader(request)}); + entries.push_back({codeOffset, ShaderRecompiler::PrepareShader(request, &preparation)}); }; append(); if (compute) { diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 08c42d610..9a18d2fc5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,258 @@ void verifyResult(const ShaderRecompiler::RecompileResult& first, const ShaderRe } } +ShaderRecompiler::RecompileRequest FrontendRequest(std::span code, std::span userData) { + using namespace ShaderRecompiler; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x100000u, code, 0, {}}; + request.context.waveSize = 64; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target = BufferTarget(); + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32u; + request.target.fragmentShaderBarycentricEnabled = false; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + return request; +} + +void verifyFrontendIdentity() { + using namespace ShaderRecompiler; + const bool reuse = std::getenv("APS5_NO_PERF_FRONTEND_PAIR") == nullptr; + const std::vector code{0xbf800000u, 0xbf810000u}; + const std::array userData{}; + const auto request = FrontendRequest(code, userData); + ShaderPreparationContext preparation; + const auto first = preparation.AcquireFrontend(request); + require(first != nullptr, "frontend pair: no frontend was prepared"); + require((preparation.AcquireFrontend(request) == first) == reuse, "frontend pair: repeated acquisition ignored the reuse switch"); + + const auto relocatedCode = code; + auto relocated = request; + relocated.shader.code = relocatedCode; + relocated.shader.codeAddress += 0x1000u; + require((preparation.AcquireFrontend(relocated) == first) == reuse, "frontend pair: identical words at another allocation or address changed reuse"); + auto partial = request; + partial.context.compute->partialThreads = {33u, 1u, 1u}; + require((preparation.AcquireFrontend(partial) == first) == reuse, "frontend pair: partial-thread state changed the frontend identity"); + auto target = request; + target.target.subgroupSize = 64u; + target.context.waveSize = 32u; + target.context.compute->numThreads = {32u, 2u, 1u}; + require((preparation.AcquireFrontend(target) == first) == reuse, "frontend pair: translation-only state changed the frontend identity"); + const std::array changedUserData{1u, 2u}; + auto userValues = request; + userValues.context.userData = changedUserData; + require((preparation.AcquireFrontend(userValues) == first) == reuse, "frontend pair: user-data values changed the frontend identity"); + + const auto verifyMiss = [&](const RecompileRequest& changed, const char* message) { + ShaderPreparationContext isolated; + const auto original = isolated.AcquireFrontend(request); + const auto replacement = isolated.AcquireFrontend(changed); + require(replacement != original, message); + require((isolated.AcquireFrontend(changed) == replacement) == reuse, "frontend pair: a successful miss was not retained"); + require(isolated.AcquireFrontend(request) != original, "frontend pair: replacement retained more than the last frontend"); + }; + auto changed = request; + auto changedCode = code; + changedCode[0] = 0xbf800001u; + changed.shader.code = changedCode; + verifyMiss(changed, "frontend pair: changed code words reused the frontend"); + changedCode = code; + changedCode.push_back(0xbf800000u); + changed.shader.code = changedCode; + verifyMiss(changed, "frontend pair: changed code length reused the frontend"); + changed = request; + changed.context.userDataBaseRegister = 4u; + verifyMiss(changed, "frontend pair: changed user-data base reused the frontend"); + changed = request; + changed.context.userData = std::span(userData).first(1u); + verifyMiss(changed, "frontend pair: changed user-data count reused the frontend"); + changed = request; + changed.shader.stage = ShaderStage::Fragment; + changed.context.compute.reset(); + changed.context.pixel = ShaderPixelStageInfo{}; + verifyMiss(changed, "frontend pair: a different stage reused the frontend"); + changed = request; + changed.shader.stage = ShaderStage::Vertex; + changed.context.compute.reset(); + changed.context.vertex = ShaderVertexStageInfo{}; + verifyMiss(changed, "frontend pair: a fetch-capable stage reused the compute frontend"); + + auto invalid = request; + invalid.shader.code = {}; + bool failed = false; + try { + static_cast(preparation.AcquireFrontend(invalid)); + } catch (const std::out_of_range& error) { + failed = std::string_view(error.what()).find("before s_endpgm") != std::string_view::npos; + } + require(failed, "frontend pair: invalid code did not fail during frontend construction"); + require((preparation.AcquireFrontend(request) == first) == reuse, "frontend pair: a failed miss replaced the prior frontend"); +} + +void verifyFrontendProgram(const ShaderRecompiler::IrProgram& reused, const ShaderRecompiler::IrProgram& fresh) { + using namespace ShaderRecompiler; + require(ProgramToString(reused) == ProgramToString(fresh), "frontend pair: reused frontend changed the IR"); + require(reused.WaveSize() == fresh.WaveSize() && reused.Info() == fresh.Info(), "frontend pair: reused frontend changed shader information"); + const auto& left = reused.Resources(); + const auto& right = fresh.Resources(); + require(left.stage == right.stage && left.shaderHash == right.shaderHash && left.userDataBase == right.userDataBase && left.userDataCount == right.userDataCount && left.srgbDecodeFormats == right.srgbDecodeFormats, "frontend pair: resource identity changed"); + require(left.memoryInfo == right.memoryInfo && left.materializationSources == right.materializationSources && left.guardedSrtSlots == right.guardedSrtSlots && left.srtGuardOffset == right.srtGuardOffset && left.cleanFlatSlots == right.cleanFlatSlots && left.pureFlatSlots == right.pureFlatSlots, "frontend pair: resource planning changed"); + require(left.descriptorSources.size() == right.descriptorSources.size() && left.srtReads.size() == right.srtReads.size(), "frontend pair: resource counts changed"); + for (std::size_t index = 0; index < left.descriptorSources.size(); ++index) { + const auto& a = left.descriptorSources[index]; + const auto& b = right.descriptorSources[index]; + require(a.dwordCount == b.dwordCount && a.indirectImage == b.indirectImage, "frontend pair: descriptor planning changed"); + for (std::size_t word = 0; word < a.dwords.size(); ++word) { + require((a.dwords[word] == nullptr) == (b.dwords[word] == nullptr), "frontend pair: descriptor word presence changed"); + if (a.dwords[word] != nullptr) require(a.dwords[word] != b.dwords[word] && a.dwords[word]->Id() == b.dwords[word]->Id(), "frontend pair: descriptor values were shared or changed"); + } + } + require(reused.BlockOrder().size() == fresh.BlockOrder().size(), "frontend pair: block count changed"); + for (std::size_t block = 0; block < reused.BlockOrder().size(); ++block) { + const auto* a = reused.BlockOrder()[block]; + const auto* b = fresh.BlockOrder()[block]; + require(a != b && a->Instructions().size() == b->Instructions().size(), "frontend pair: mutable IR blocks were shared or changed"); + auto other = b->Instructions().begin(); + for (const auto* x : a->Instructions()) { + const auto* y = *other++; + require(x != y && x->Flags() == y->Flags(), "frontend pair: mutable IR values were shared or their flags changed"); + } + } +} + +void verifyFrontendPair() { + using namespace ShaderRecompiler; + verifyFrontendIdentity(); + const bool reuse = std::getenv("APS5_NO_PERF_FRONTEND_PAIR") == nullptr; + const std::vector code{0xe0700000u, 0x80000000u, 0xbf810000u}; + const std::array buffer{0x10000000u, 0u, 0x40u, 0x00027facu}; + const auto request = FrontendRequest(code, buffer); + auto partial = request; + partial.context.compute->partialThreads = {33u, 1u, 1u}; + const auto freshFull = PrepareResourceProgram(request); + const auto freshPartial = PrepareResourceProgram(partial); + require(ProgramToString(freshFull) != ProgramToString(freshPartial), "frontend pair: fixture does not distinguish full and partial threads"); + const auto storeThreadLimits = [](const IrProgram& program) { + std::vector pending; + for (const auto* block : program.BlockOrder()) { + for (const auto* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::StoreBufferU32) pending.push_back(inst->Argument(inst->ArgumentCount() - 1u)); + } + } + require(pending.size() == 1u, "frontend pair: fixture does not retain exactly one buffer store"); + std::vector visited; + std::uint32_t axes = 0u; + while (!pending.empty()) { + const auto* value = pending.back()->Resolve(); + pending.pop_back(); + if (std::find(visited.begin(), visited.end(), value) != visited.end()) continue; + visited.push_back(value); + if (value->Opcode() == IrOpcode::GetBuiltin && value->Argument(0)->Resolve()->ImmediateU32() == static_cast(StageInputKind::DispatchThreadLimit)) { + const auto axis = value->Argument(1)->Resolve()->ImmediateU32(); + require(axis < 3u, "frontend pair: invalid dispatch-limit axis"); + axes |= 1u << axis; + } + for (const auto* argument : value->Arguments()) pending.push_back(argument); + } + return axes; + }; + require(storeThreadLimits(freshFull) == 0u && storeThreadLimits(freshPartial) == 7u, "frontend pair: buffer-store predicate does not retain all partial-thread bounds"); + require(!freshFull.Resources().memoryInfo.empty() && !freshFull.Resources().descriptorSources.empty() && !freshFull.Info().buffers.empty(), "frontend pair: fixture has no buffer resources"); + + for (const bool partialFirst : {false, true}) { + ShaderPreparationContext preparation; + std::shared_ptr retained; + { + auto temporary = code; + auto borrowed = request; + borrowed.shader.code = temporary; + retained = preparation.AcquireFrontend(borrowed); + std::fill(temporary.begin(), temporary.end(), 0xffffffffu); + require((preparation.AcquireFrontend(request) == retained) == reuse, "frontend pair: mutating the caller's code changed the retained frontend"); + } + require((preparation.AcquireFrontend(request) == retained) == reuse, "frontend pair: destroying the caller's code changed the retained frontend"); + const auto first = PrepareResourceProgram(partialFirst ? partial : request, &preparation); + const auto second = PrepareResourceProgram(partialFirst ? request : partial, &preparation); + verifyFrontendProgram(first, partialFirst ? freshPartial : freshFull); + verifyFrontendProgram(second, partialFirst ? freshFull : freshPartial); + require(first.BlockOrder().front() != second.BlockOrder().front(), "frontend pair: full and partial variants share mutable IR"); + const auto repeated = PrepareResourceProgram(partialFirst ? partial : request, &preparation); + verifyFrontendProgram(repeated, first); + auto translated = request; + translated.context.waveSize = 32u; + translated.context.compute->numThreads = {32u, 2u, 1u}; + const auto changed = PrepareResourceProgram(translated, &preparation); + const auto freshChanged = PrepareResourceProgram(translated); + verifyFrontendProgram(changed, freshChanged); + require(changed.WaveSize() != freshFull.WaveSize(), "frontend pair: changed translation inputs were ignored"); + require((preparation.AcquireFrontend(request) == retained) == reuse, "frontend pair: translating a variant replaced the frontend"); + } + + for (const bool partialFirst : {false, true}) { + auto pairCode = code; + pairCode.insert(pairCode.begin(), 0xbf800000u | static_cast(partialFirst)); + auto full = request; + full.shader.code = pairCode; + auto limited = partial; + limited.shader.code = pairCode; + const auto fullReference = Recompile(full); + const auto partialReference = Recompile(limited); + ShaderPreparationContext preparation; + const auto first = PrepareShader(partialFirst ? limited : full, &preparation); + const auto second = PrepareShader(partialFirst ? full : limited, &preparation); + const auto& fullHandle = partialFirst ? second : first; + const auto& partialHandle = partialFirst ? first : second; + const auto& fullArtifact = GetPreparedArtifact(*fullHandle); + const auto& partialArtifact = GetPreparedArtifact(*partialHandle); + require(first->source != second->source, "frontend pair: full and partial fixtures did not exercise separate sources"); + require(fullArtifact.spirv != partialArtifact.spirv, "frontend pair: full and partial threads share the same artifact"); + SrtRuntime runtime{}; + runtime.userData = buffer; + runtime.shaderBase = full.shader.codeAddress; + const auto fullCapture = CaptureResources(full, runtime, *fullHandle); + const auto partialCapture = CaptureResources(limited, runtime, *partialHandle); + const auto fullResult = MaterializeShader(full, *fullCapture, *fullHandle); + const auto partialResult = MaterializeShader(limited, *partialCapture, *partialHandle); + verifyResult(fullReference, *fullResult); + verifyResult(partialReference, *partialResult); + } + + const std::array color{0x3f800000u}; + for (const bool zeroFirst : {false, true}) { + const std::vector pixelCode{0xbf800000u | static_cast(zeroFirst), 0x7e000200u, 0xf800180fu, 0u, 0xbf810000u}; + auto pixel = FrontendRequest(pixelCode, color); + pixel.shader.stage = ShaderStage::Fragment; + pixel.context.compute.reset(); + pixel.context.pixel = ShaderPixelStageInfo{}; + pixel.context.pixel->wave32 = true; + pixel.context.pixel->targetOutputMode[0] = 9u; + pixel.context.waveSize = 32u; + auto zero = pixel; + zero.layout.pushConstantSizeBytes = 0u; + const auto pixelReference = Recompile(pixel); + const auto zeroReference = Recompile(zero); + ShaderPreparationContext preparation; + const auto first = PrepareShader(zeroFirst ? zero : pixel, &preparation); + const auto second = PrepareShader(zeroFirst ? pixel : zero, &preparation); + const auto& pixelHandle = zeroFirst ? second : first; + const auto& zeroHandle = zeroFirst ? first : second; + require(first->source == second->source, "frontend pair: pixel capacities did not exercise the same source"); + require(GetPreparedArtifact(*pixelHandle).spirv != GetPreparedArtifact(*zeroHandle).spirv, "frontend pair: pixel fixture does not distinguish push-constant capacities"); + SrtRuntime runtime{}; + runtime.userData = color; + runtime.shaderBase = pixel.shader.codeAddress; + const auto pixelCapture = CaptureResources(pixel, runtime, *pixelHandle); + const auto zeroCapture = CaptureResources(zero, runtime, *zeroHandle); + verifyResult(pixelReference, *MaterializeShader(pixel, *pixelCapture, *pixelHandle)); + verifyResult(zeroReference, *MaterializeShader(zero, *zeroCapture, *zeroHandle)); + } +} + void verifyRegisterSources() { using namespace ShaderRecompiler; IrResourcePlan plan; @@ -2133,6 +2386,11 @@ void verifyGuardedNullPointers() { int main(int argc, char** argv) { try { using namespace ShaderRecompiler; + if (argc == 2 && std::string_view(argv[1]) == "--frontend-pair") { + verifyFrontendPair(); + std::cout << "Prepared frontend reuse preserves full and partial-thread programs\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--bindless") { verifyBindlessTable(); std::cout << "Bindless mapping, indexing capabilities and strict validation passed\n"; @@ -2141,6 +2399,7 @@ int main(int argc, char** argv) { require(argc == 1, "unknown shader memory test arguments"); verifyRegisterSources(); verifyEvaluatedValues(); + verifyFrontendPair(); verifyPureFlatSlots(); verifyBindlessTable(); verifyDescriptorPhis(); diff --git a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp index a0c470a7b..f23d1b8f6 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp @@ -9,7 +9,17 @@ namespace ShaderRecompiler { +struct PreparedControlFlow; +class ShaderPreparationContext { +public: + [[nodiscard]] std::shared_ptr AcquireFrontend(const RecompileRequest& request); + +private: + std::shared_ptr frontend; +}; + [[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request); +[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation); [[nodiscard]] std::shared_ptr GetResourcePlan(const RecompileRequest& request); struct SourceEntry; @@ -55,6 +65,7 @@ private: }; [[nodiscard]] std::span GetPreparedCode(const SourceHandle& handle); [[nodiscard]] std::shared_ptr PrepareShader(const RecompileRequest& request); +[[nodiscard]] std::shared_ptr PrepareShader(const RecompileRequest& request, ShaderPreparationContext* preparation); [[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle); void BuildPreparedShaderKey(const RecompileRequest& request, std::vector& key); [[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle, std::span key); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 92017bf68..a81e55f2e 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -97,22 +97,54 @@ ShaderStageInputInfo RequestInputInfo(const RecompileRequest& request) { } +struct PreparedControlFlow { + PreparedControlFlow(const RecompileRequest& request, const SwappcInfo& swappcInfo) + : stage(request.shader.stage), swappc(swappcInfo), code(request.shader.code.begin(), request.shader.code.end()), + decoded(RdnaInstructionDecoder{}.Decode(code)), cfg(GraphBuilder{}.Build(decoded, &swappc)) { + Structurizer{}.Structurize(cfg); + } + + PreparedControlFlow(const PreparedControlFlow&) = delete; + PreparedControlFlow& operator=(const PreparedControlFlow&) = delete; + + bool Matches(const RecompileRequest& request, const SwappcInfo& swappcInfo) const { + return stage == request.shader.stage && swappc.fetchCallAllowed == swappcInfo.fetchCallAllowed && + swappc.userDataBaseRegister == swappcInfo.userDataBaseRegister && swappc.userDataCount == swappcInfo.userDataCount && + std::ranges::equal(code, request.shader.code); + } + + ShaderStage stage; + SwappcInfo swappc; + std::vector code; + RdnaProgram decoded; + ControlFlowGraph cfg; +}; + +std::shared_ptr ShaderPreparationContext::AcquireFrontend(const RecompileRequest& request) { + const auto inputInfo = RequestInputInfo(request); + const SwappcInfo swappc{inputInfo.vertex != nullptr, request.context.userDataBaseRegister, static_cast(request.context.userData.size())}; + static const bool reuse = std::getenv("APS5_NO_PERF_FRONTEND_PAIR") == nullptr; + if (reuse && frontend != nullptr && frontend->Matches(request, swappc)) { + return frontend; + } + auto prepared = std::make_shared(request, swappc); + if (reuse) { + frontend = prepared; + } + return prepared; +} + IrProgram PrepareResourceProgram(const RecompileRequest& request) { + return PrepareResourceProgram(request, nullptr); +} + +IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation) { + ShaderPreparationContext local; + const auto frontend = (preparation != nullptr ? *preparation : local).AcquireFrontend(request); const auto stageKind = toShaderStageKind(request.shader.stage); const auto inputInfo = RequestInputInfo(request); - - constexpr RdnaInstructionDecoder decoder; - const auto decoded = decoder.Decode(request.shader.code); - - constexpr GraphBuilder graphBuilder; - SwappcInfo swappcInfo; - swappcInfo.fetchCallAllowed = inputInfo.vertex != nullptr; - swappcInfo.userDataBaseRegister = request.context.userDataBaseRegister; - swappcInfo.userDataCount = static_cast(request.context.userData.size()); - auto cfg = graphBuilder.Build(decoded, &swappcInfo); - - constexpr Structurizer structurizer; - structurizer.Structurize(cfg); + const auto& decoded = frontend->decoded; + const auto& cfg = frontend->cfg; TranslateOptions translateOptions {}; translateOptions.stage = stageKind; @@ -222,7 +254,7 @@ struct SourceEntry { namespace { struct ResourceProgram { - explicit ResourceProgram(const RecompileRequest& request) : program(std::make_unique(PrepareResourceProgram(request))), plan(std::make_shared(ResourceMaterializer{}.ExtractPlan(*program))) {} + explicit ResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr) : program(std::make_unique(PrepareResourceProgram(request, preparation))), plan(std::make_shared(ResourceMaterializer{}.ExtractPlan(*program))) {} std::unique_ptr program; std::shared_ptr plan; @@ -248,7 +280,7 @@ bool FailureMemo() { return memo; } -std::shared_ptr getSource(const RecompileRequest& request) { +std::shared_ptr getSource(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr) { static std::shared_mutex mutex; // Entries whose code hashes alike share a bucket; the code comparison picks the right one. static std::unordered_map, std::vector>, SourceKeyHash> sources; @@ -292,7 +324,7 @@ std::shared_ptr getSource(const RecompileRequest& request) { if (source->plan == nullptr) { if (FailureMemo() && source->planFailure) std::rethrow_exception(source->planFailure); try { - ResourceProgram resource(request); + ResourceProgram resource(request, preparation); source->plan = std::move(resource.plan); source->program = std::move(resource.program); } catch (...) { @@ -688,7 +720,7 @@ bool sameLayout(const BindingLayout& left, const BindingLayout& right) { return left.descriptorSet == right.descriptorSet && left.firstBinding == right.firstBinding && left.pushConstantOffsetBytes == right.pushConstantOffsetBytes && left.pushConstantSizeBytes == right.pushConstantSizeBytes; } -std::shared_ptr findOrCompileVariant(SourceEntry& source, const RecompileRequest& request, bool& cacheHit) { +std::shared_ptr findOrCompileVariant(SourceEntry& source, const RecompileRequest& request, bool& cacheHit, ShaderPreparationContext* preparation = nullptr) { for (const auto& candidate : source.variants) { if (sameLayout(candidate->layout, request.layout)) { cacheHit = true; @@ -712,7 +744,7 @@ std::shared_ptr findOrCompileVariant(SourceEntry& source, } } if (variant == nullptr) { - auto program = source.program != nullptr ? std::move(*source.program) : PrepareResourceProgram(request); + auto program = source.program != nullptr ? std::move(*source.program) : PrepareResourceProgram(request, preparation); source.program.reset(); std::exception_ptr emissionFailure; try { @@ -983,15 +1015,19 @@ std::shared_ptr ResolveSource(const RecompileRequest& reques } std::shared_ptr PrepareShader(const RecompileRequest& request) { + return PrepareShader(request, nullptr); +} + +std::shared_ptr PrepareShader(const RecompileRequest& request, ShaderPreparationContext* preparation) { return recompileReporting(request, [&]() -> std::shared_ptr { static_cast(RequestInputInfo(request)); auto handle = std::make_shared(); - handle->source = getSource(request); + handle->source = getSource(request, preparation); RecompileCacheKey::BuildInterface(request, handle->staticKey); handle->staticKey.push_back(HostSubgroupSize(request)); bool cacheHit = false; std::lock_guard lock(handle->source->mutex); - handle->artifact = findOrCompileVariant(*handle->source, request, cacheHit); + handle->artifact = findOrCompileVariant(*handle->source, request, cacheHit, preparation); return handle; }); } diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 40f10ab42..8ad5cde1a 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -645,6 +645,7 @@ constexpr std::string_view NeutralSwitches[] = { "APS5_DUMP_IR", "APS5_NO_CODE_HASH_KEY", "APS5_NO_FAILURE_MEMO", + "APS5_NO_PERF_FRONTEND_PAIR", "APS5_NO_RESULT_MEMO", };