Merge pull request #2526 from Celegans12/perf/agc-registration-frontend-pair
perf(agc): reuse frontend within shader registration pairs
This commit is contained in:
commit
03bf8ff335
|
|
@ -817,8 +817,12 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
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:$<TARGET_FILE_DIR:libc>")
|
||||
set_tests_properties(agc_shader_bindless agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
foreach(agcTest IN ITEMS agc_shared_write_tests 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 agc_driver_cmask_fast_clear_tests ${agcExecutionTests})
|
||||
if(agcTest STREQUAL "agc_driver_constant_store_tests")
|
||||
|
|
|
|||
|
|
@ -406,9 +406,10 @@ std::vector<PreparedShaders::Entry> 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<PreparedShaders::Entry> 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) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
|
|
@ -79,6 +80,258 @@ void verifyResult(const ShaderRecompiler::RecompileResult& first, const ShaderRe
|
|||
}
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileRequest FrontendRequest(std::span<const std::uint32_t> code, std::span<const std::uint32_t> 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<std::uint32_t> code{0xbf800000u, 0xbf810000u};
|
||||
const std::array<std::uint32_t, 2> 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<std::uint32_t, 2> 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<const std::uint32_t>(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<void>(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<std::uint64_t>() == y->Flags<std::uint64_t>(), "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<std::uint32_t> code{0xe0700000u, 0x80000000u, 0xbf810000u};
|
||||
const std::array<std::uint32_t, 4> 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<const IrValue*> 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<const IrValue*> 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<std::uint32_t>(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<const PreparedControlFlow> 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<std::uint32_t>(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<std::uint32_t, 1> color{0x3f800000u};
|
||||
for (const bool zeroFirst : {false, true}) {
|
||||
const std::vector<std::uint32_t> pixelCode{0xbf800000u | static_cast<std::uint32_t>(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;
|
||||
|
|
@ -2221,6 +2474,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";
|
||||
|
|
@ -2229,6 +2487,7 @@ int main(int argc, char** argv) {
|
|||
require(argc == 1, "unknown shader memory test arguments");
|
||||
verifyRegisterSources();
|
||||
verifyEvaluatedValues();
|
||||
verifyFrontendPair();
|
||||
verifyPureFlatSlots();
|
||||
verifyBindlessTable();
|
||||
verifyDescriptorPhis();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,17 @@
|
|||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
struct PreparedControlFlow;
|
||||
class ShaderPreparationContext {
|
||||
public:
|
||||
[[nodiscard]] std::shared_ptr<const PreparedControlFlow> AcquireFrontend(const RecompileRequest& request);
|
||||
|
||||
private:
|
||||
std::shared_ptr<const PreparedControlFlow> frontend;
|
||||
};
|
||||
|
||||
[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request);
|
||||
[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation);
|
||||
[[nodiscard]] std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request);
|
||||
|
||||
struct SourceEntry;
|
||||
|
|
@ -55,6 +65,7 @@ private:
|
|||
};
|
||||
[[nodiscard]] std::span<const std::uint32_t> GetPreparedCode(const SourceHandle& handle);
|
||||
[[nodiscard]] std::shared_ptr<const SourceHandle> PrepareShader(const RecompileRequest& request);
|
||||
[[nodiscard]] std::shared_ptr<const SourceHandle> PrepareShader(const RecompileRequest& request, ShaderPreparationContext* preparation);
|
||||
[[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle);
|
||||
void BuildPreparedShaderKey(const RecompileRequest& request, std::vector<std::uint64_t>& key);
|
||||
[[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle, std::span<const std::uint64_t> key);
|
||||
|
|
|
|||
|
|
@ -100,22 +100,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<std::uint32_t> code;
|
||||
RdnaProgram decoded;
|
||||
ControlFlowGraph cfg;
|
||||
};
|
||||
|
||||
std::shared_ptr<const PreparedControlFlow> ShaderPreparationContext::AcquireFrontend(const RecompileRequest& request) {
|
||||
const auto inputInfo = RequestInputInfo(request);
|
||||
const SwappcInfo swappc{inputInfo.vertex != nullptr, request.context.userDataBaseRegister, static_cast<std::uint32_t>(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<const PreparedControlFlow>(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<std::uint32_t>(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;
|
||||
|
|
@ -238,7 +270,7 @@ struct SourceEntry {
|
|||
namespace {
|
||||
|
||||
struct ResourceProgram {
|
||||
explicit ResourceProgram(const RecompileRequest& request) : program(std::make_unique<IrProgram>(PrepareResourceProgram(request))), plan(std::make_shared<const IrResourcePlan>(ResourceMaterializer{}.ExtractPlan(*program))) {}
|
||||
explicit ResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr) : program(std::make_unique<IrProgram>(PrepareResourceProgram(request, preparation))), plan(std::make_shared<const IrResourcePlan>(ResourceMaterializer{}.ExtractPlan(*program))) {}
|
||||
|
||||
std::unique_ptr<IrProgram> program;
|
||||
std::shared_ptr<const IrResourcePlan> plan;
|
||||
|
|
@ -264,7 +296,7 @@ bool FailureMemo() {
|
|||
return memo;
|
||||
}
|
||||
|
||||
std::shared_ptr<SourceEntry> getSource(const RecompileRequest& request) {
|
||||
std::shared_ptr<SourceEntry> 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<std::uint64_t>, std::vector<std::shared_ptr<SourceEntry>>, SourceKeyHash> sources;
|
||||
|
|
@ -308,7 +340,7 @@ std::shared_ptr<SourceEntry> 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 (...) {
|
||||
|
|
@ -735,7 +767,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<const CompiledVariant> findOrCompileVariant(SourceEntry& source, const RecompileRequest& request, bool& cacheHit) {
|
||||
std::shared_ptr<const CompiledVariant> 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;
|
||||
|
|
@ -759,7 +791,7 @@ std::shared_ptr<const CompiledVariant> 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 {
|
||||
|
|
@ -1031,15 +1063,19 @@ std::shared_ptr<const SourceHandle> ResolveSource(const RecompileRequest& reques
|
|||
}
|
||||
|
||||
std::shared_ptr<const SourceHandle> PrepareShader(const RecompileRequest& request) {
|
||||
return PrepareShader(request, nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<const SourceHandle> PrepareShader(const RecompileRequest& request, ShaderPreparationContext* preparation) {
|
||||
return recompileReporting(request, [&]() -> std::shared_ptr<const SourceHandle> {
|
||||
static_cast<void>(RequestInputInfo(request));
|
||||
auto handle = std::make_shared<SourceHandle>();
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -655,6 +655,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",
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue