shader: centralize program compilation and permutation caching

This commit is contained in:
nmzik
2026-08-27 13:37:46 +02:00
parent 65871a53a6
commit c167375fe1
11 changed files with 716 additions and 1035 deletions
+148 -86
View File
@@ -25,6 +25,7 @@
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include "graphics/shader/shader.h"
#include "graphics/shader/shaderCompiler.h"
#include "libs/agc.h"
#include "spirv-tools/libspirv.hpp"
@@ -76,6 +77,46 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
return options;
}
bool ReadHostTestMemory(void *, uint64_t address, uint32_t *value) {
if (address == 0 || value == nullptr) {
return false;
}
std::memcpy(value, reinterpret_cast<const void *>(address), sizeof(*value));
return true;
}
bool CompilePixelRuntime(const ShaderParams &params,
ShaderPixelInputInfo &input_info,
std::string *error) {
auto options = MakeCompileOptions(ShaderType::Pixel);
options.shader_hash = params.hash;
options.shader_base = params.Base();
options.user_data = params.user_data.data();
options.user_data_count = static_cast<uint32_t>(params.user_data.size());
options.scratch_dwords = input_info.scratch_size_dwords;
options.push_constant_offset = input_info.push_constant_offset;
options.read_specialization_memory = ReadHostTestMemory;
options.input_info.pixel = &input_info;
ShaderRecompiler::CompileResult result;
if (!ShaderRecompiler::TryRecompile(params.code, options, result, error)) {
return false;
}
input_info.stage.program =
std::make_shared<const ShaderRecompiler::IR::Program>(
std::move(result.program));
input_info.stage.resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(
std::move(result.resources));
return true;
}
template <typename InputInfo>
std::vector<uint32_t> MakeStageStaticKey(const InputInfo &input_info) {
std::vector<uint32_t> key;
BuildStageStaticKey(input_info, key);
return key;
}
std::vector<uint32_t>
CfgInstructionCoverage(const ShaderRecompiler::CFG::Graph &graph,
size_t instruction_count) {
@@ -9107,7 +9148,7 @@ void TestNewShaderRecompilerAuxPositionExports() {
ShaderVertexInputInfo key0{};
ShaderVertexInputInfo key1{};
key1.pa_cl_vs_out_cntl = 1;
Check(ShaderGetIdVS(regs, key0, false) != ShaderGetIdVS(regs, key1, false),
Check(MakeStageStaticKey(key0) != MakeStageStaticKey(key1),
"PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key");
}
@@ -10513,8 +10554,7 @@ void TestRenderTargetReverseExportMapping() {
reversed_info.target_export_mapping[0] = gr32.export_mapping;
HW::PixelShaderInfo regs{};
Check(ShaderGetIdPS(regs, identity_info, false) !=
ShaderGetIdPS(regs, reversed_info, false),
Check(MakeStageStaticKey(identity_info) != MakeStageStaticKey(reversed_info),
"pixel shader cache identity omitted the render-target export mapping");
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
@@ -10527,18 +10567,13 @@ void TestRenderTargetReverseExportMapping() {
mappings[0] = gr32.export_mapping;
ShaderPixelInputInfo compiled_info{};
ShaderVertexInputInfo vertex_info{};
std::span<const uint32_t> compiled_spirv;
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
PrepareProgram(regs, sh, vertex_info, mappings, compiled_info);
Check(compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
"cache key");
sh.target_output_mode[0] = 4;
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0] == gr32.export_mapping,
PrepareProgram(regs, sh, vertex_info, mappings, compiled_info);
Check(compiled_info.target_export_mapping[0] == gr32.export_mapping,
"active reverse MRT mapping was lost before shader specialization");
}
@@ -11517,15 +11552,12 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
ShaderVertexInputInfo vertex_info{};
ShaderPixelInputInfo input_info{};
std::vector<uint32_t> spirv;
Check(ShaderCompileSpirvPS(regs, sh, input_info, spirv),
"new pixel shader recompiler wrapper did not produce SPIR-V");
Check(!spirv.empty(),
"new pixel shader recompiler wrapper returned empty SPIR-V");
Check(spirv.front() == 0x07230203u,
"new pixel shader recompiler wrapper did not emit SPIR-V binary");
CheckSpirvBinaryValidates(spirv);
const auto params = PrepareProgram(regs, sh, vertex_info, mappings, input_info);
Check(params.hash == regs.ps_regs.chksum && params.code.data() == shader,
"pixel shader program parameters lost source identity");
const uint32_t vcc_load_shader[] = {
EncodeSMovB32(106, 27), EncodeSMovB32(107, 28), EncodeSmem0(0x02, 44, 53),
@@ -11551,10 +11583,10 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
ShaderMapUserData(vcc_regs.ps_regs.data_addr, vcc_mapped);
ShaderPixelInputInfo vcc_input{};
std::vector<uint32_t> vcc_spirv;
Check(ShaderCompileSpirvPS(vcc_regs, sh, vcc_input, vcc_spirv),
"VCC raw-load pointer was lost");
CheckSpirvBinaryValidates(vcc_spirv);
const auto vcc_params =
PrepareProgram(vcc_regs, sh, vertex_info, mappings, vcc_input);
std::string error;
Check(CompilePixelRuntime(vcc_params, vcc_input, &error), error.c_str());
}
void TestComputeLdsAllocationIdentity() {
@@ -11577,19 +11609,28 @@ void TestComputeLdsAllocationIdentity() {
uint32_t expected_dwords) {
regs.cs_regs.lds_size = encoded_lds_size;
ShaderComputeInputInfo input_info{};
std::span<const uint32_t> spirv;
Check(ShaderCompileInfoCS(regs, sh, false, input_info, spirv),
"compute LDS allocation shader did not compile");
const auto params = PrepareProgram(regs, sh, input_info);
Check(input_info.lds_size_dwords == expected_dwords,
"COMPUTE_PGM_RSRC2 LDS allocation units were not decoded");
const std::vector<uint32_t> binary(spirv.begin(), spirv.end());
Check(MeasureSpirv(binary).workgroup_variables == 1u,
auto options = MakeCompileOptions(ShaderType::Compute);
options.shader_hash = params.hash;
options.shader_base = params.Base();
options.user_data = params.user_data.data();
options.user_data_count = static_cast<uint32_t>(params.user_data.size());
options.input_info.compute = &input_info;
options.wave_size = input_info.wave_size;
options.scratch_dwords = input_info.scratch_size_dwords;
ShaderRecompiler::CompileResult result;
std::string error;
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(MeasureSpirv(result.spirv).workgroup_variables == 1u,
"LDS shader did not declare exactly one Workgroup variable");
Check(SpirvArrayLengthCount(binary, expected_dwords) == 1u,
Check(SpirvArrayLengthCount(result.spirv, expected_dwords) == 1u,
"SPIR-V workgroup array did not use the declared LDS allocation");
Check(SpirvUnsignedLessThanBoundCount(binary, expected_dwords) == 1u,
Check(SpirvUnsignedLessThanBoundCount(result.spirv, expected_dwords) == 1u,
"LDS bounds check did not use the declared allocation");
CheckSpirvBinaryValidates(binary);
CheckSpirvBinaryValidates(result.spirv);
return input_info;
};
@@ -11602,21 +11643,19 @@ void TestComputeLdsAllocationIdentity() {
};
const auto lds_1152 = compile(decode_lds_field(lds_1152_rsrc2), 1152u);
const auto lds_896 = compile(decode_lds_field(lds_896_rsrc2), 896u);
Check(ShaderGetIdCS(regs, lds_1152, true) !=
ShaderGetIdCS(regs, lds_896, true),
Check(MakeStageStaticKey(lds_1152) != MakeStageStaticKey(lds_896),
"compute pipeline identity omitted the LDS allocation");
auto split_wave = lds_896;
split_wave.needs_lds_barriers = true;
Check(ShaderGetIdCS(regs, lds_896, true) !=
ShaderGetIdCS(regs, split_wave, true),
Check(MakeStageStaticKey(lds_896) != MakeStageStaticKey(split_wave),
"compute shader identity omitted split-wave LDS synchronization");
auto tg_size_disabled = lds_896;
auto tg_size_enabled = lds_896;
tg_size_enabled.tg_size_en = true;
Check(ShaderGetIdCS(regs, tg_size_disabled, false) !=
ShaderGetIdCS(regs, tg_size_enabled, false),
Check(MakeStageStaticKey(tg_size_disabled) !=
MakeStageStaticKey(tg_size_enabled),
"compute shader identity omitted TG_SIZE semantics");
auto dispatch_disabled = lds_896;
@@ -11625,32 +11664,42 @@ void TestComputeLdsAllocationIdentity() {
dispatch_disabled.dispatch_threads_num[2] = 1;
auto dispatch_enabled = dispatch_disabled;
dispatch_enabled.dispatch_thread_dimensions = true;
Check(ShaderGetIdCS(regs, dispatch_disabled, false) ==
ShaderGetIdCS(regs, dispatch_enabled, false),
"runtime dispatch enable created a compute shader variant");
Check(MakeStageStaticKey(dispatch_disabled) !=
MakeStageStaticKey(dispatch_enabled),
"dispatch-dimension mode did not select a compute shader variant");
auto second_extent = dispatch_enabled;
second_extent.dispatch_threads_num[0] = 1920;
second_extent.dispatch_threads_num[1] = 1080;
second_extent.dispatch_threads_num[2] = 4;
Check(ShaderGetIdCS(regs, dispatch_enabled, false) ==
ShaderGetIdCS(regs, second_extent, false),
Check(MakeStageStaticKey(dispatch_enabled) ==
MakeStageStaticKey(second_extent),
"runtime dispatch extent created a compute shader variant");
mapped.scratch_size_dwords = 7;
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
regs.cs_regs.lds_size = decode_lds_field(lds_896_rsrc2);
ShaderComputeInputInfo scratch_info{};
std::span<const uint32_t> scratch_spirv;
Check(ShaderCompileInfoCS(regs, sh, false, scratch_info, scratch_spirv),
"compute scratch metadata shader did not compile");
const auto scratch_params = PrepareProgram(regs, sh, scratch_info);
Check(scratch_info.scratch_size_dwords == 7,
"AGC per-thread scratch size was not propagated");
Check(scratch_info.stage.program != nullptr &&
scratch_info.stage.program->scratch_dwords == 7,
auto scratch_options = MakeCompileOptions(ShaderType::Compute);
scratch_options.shader_hash = scratch_params.hash;
scratch_options.shader_base = scratch_params.Base();
scratch_options.user_data = scratch_params.user_data.data();
scratch_options.user_data_count =
static_cast<uint32_t>(scratch_params.user_data.size());
scratch_options.input_info.compute = &scratch_info;
scratch_options.wave_size = scratch_info.wave_size;
scratch_options.scratch_dwords = scratch_info.scratch_size_dwords;
ShaderRecompiler::CompileResult scratch_result;
std::string scratch_error;
Check(ShaderRecompiler::TryRecompile(scratch_params.code, scratch_options,
scratch_result, &scratch_error),
scratch_error.c_str());
Check(scratch_result.program.scratch_dwords == 7,
"AGC per-thread scratch size did not reach the compiler program");
Check(ShaderGetIdCS(regs, scratch_info, true) !=
ShaderGetIdCS(regs, lds_896, true),
Check(MakeStageStaticKey(scratch_info) != MakeStageStaticKey(lds_896),
"compute pipeline identity omitted the scratch allocation");
const uint32_t append_shader[] = {
@@ -11810,20 +11859,18 @@ void TestPixelProgramCacheBindingIdentity() {
const uint32_t shader_10[] = {0xbf810000u};
HW::ShaderRegisters sh{};
HW::PixelShaderInfo identity_regs{};
ShaderPixelInputInfo no_depth_export{};
auto depth_export = no_depth_export;
depth_export.ps_depth_export_enable = true;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, depth_export, false),
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(depth_export),
"pixel shader identity omitted depth-export semantics");
auto shifted_push = no_depth_export;
shifted_push.push_constant_offset = 36;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, shifted_push, false),
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(shifted_push),
"pixel shader identity omitted graphics push-bank placement");
auto check_cache_identity = [&](const uint32_t *shader, uint64_t checksum) {
auto check_program_identity = [&](const uint32_t *shader,
uint64_t checksum) {
HW::PixelShaderInfo regs{};
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
regs.ps_regs.chksum = checksum;
@@ -11834,26 +11881,29 @@ void TestPixelProgramCacheBindingIdentity() {
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderVertexInputInfo vertex_info{};
ShaderPixelInputInfo first_info{};
std::span<const uint32_t> first_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, first_info,
first_spirv),
"pixel program-cache fixture failed to compile");
const auto first_params =
PrepareProgram(regs, sh, vertex_info, identity_mappings, first_info);
const auto first_key = MakeStageStaticKey(first_info);
std::string error;
Check(CompilePixelRuntime(first_params, first_info, &error), error.c_str());
ShaderPixelInputInfo second_info{};
std::span<const uint32_t> second_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, second_info,
second_spirv),
"pixel program-cache lookup failed");
const auto second_params =
PrepareProgram(regs, sh, vertex_info, identity_mappings, second_info);
Check(
first_info.stage.program != nullptr &&
second_info.stage.program == first_info.stage.program &&
second_spirv.data() == first_spirv.data() &&
second_spirv.size() == first_spirv.size(),
"pixel program cache did not reuse an identical graphics placement");
first_params.hash == second_params.hash &&
first_key == MakeStageStaticKey(second_info) &&
first_info.stage.program != nullptr &&
MaterializeProgram(first_info.stage.program, second_params, second_info) &&
second_info.stage.program == first_info.stage.program,
"pixel program matching did not reuse an identical graphics placement");
return std::pair {first_params.hash, first_key};
};
check_cache_identity(shader_01, 0x91a27e6300000001ull);
check_cache_identity(shader_10, 0x91a27e6300000002ull);
const auto request_01 = check_program_identity(shader_01, 0);
const auto request_10 = check_program_identity(shader_10, 0);
Check(request_01 == request_10,
"relocated identical pixel programs did not share their source identity");
const uint32_t push_shader[] = {
EncodeVop1(0x01, 0, 0), EncodeVop1(0x01, 1, 1),
@@ -11883,24 +11933,36 @@ void TestPixelProgramCacheBindingIdentity() {
ShaderPixelInputInfo at_zero{};
ShaderPixelInputInfo at_36{};
ShaderPixelInputInfo at_zero_again{};
std::span<const uint32_t> spirv_at_zero;
std::span<const uint32_t> spirv_at_36;
std::span<const uint32_t> spirv_at_zero_again;
Check(ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero, spirv_at_zero) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_36, mappings,
at_36, spirv_at_36) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero_again, spirv_at_zero_again),
"pixel program-cache placement fixture failed to compile");
const auto params_at_zero =
PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings, at_zero);
const auto key_at_zero = MakeStageStaticKey(at_zero);
std::string error;
Check(CompilePixelRuntime(params_at_zero, at_zero, &error), error.c_str());
const auto params_at_36 =
PrepareProgram(push_regs, push_sh, vertex_at_36, mappings, at_36);
const auto key_at_36 = MakeStageStaticKey(at_36);
Check(CompilePixelRuntime(params_at_36, at_36, &error), error.c_str());
const auto params_at_zero_again =
PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings,
at_zero_again);
auto shifted_options = MakeCompileOptions(ShaderType::Pixel);
shifted_options.input_info.pixel = &at_36;
shifted_options.push_constant_offset = at_36.push_constant_offset;
ShaderRecompiler::CompileResult shifted_result;
std::string shifted_error;
Check(ShaderRecompiler::TryRecompile(push_shader, shifted_options,
shifted_result, &shifted_error),
shifted_error.c_str());
Check(at_zero.push_constant_offset == 0 &&
at_36.push_constant_offset == 36 &&
key_at_zero != key_at_36 &&
MakeStageStaticKey(at_zero_again) == key_at_zero &&
at_zero.stage.program != at_36.stage.program &&
spirv_at_zero.data() != spirv_at_36.data() &&
MaterializeProgram(at_zero.stage.program, params_at_zero_again,
at_zero_again) &&
at_zero_again.stage.program == at_zero.stage.program &&
spirv_at_zero_again.data() == spirv_at_zero.data() &&
SpirvHasMemberDecorationValue(spirv_at_36, 35u, 36u),
"pixel program cache did not partition and reuse graphics push placement");
SpirvHasMemberDecorationValue(shifted_result.spirv, 35u, 36u),
"pixel program matching did not partition and reuse graphics push placement");
}
void TestGraphicsPushConstantPlacement() {