shader: cache registered code identities instead of parsing legacy footers

Co-authored-by: Mac <me@itsmemac.com>
Co-authored-by: Senaxx <michelbeld@gmail.com>
This commit is contained in:
nmzik
2026-10-01 02:34:51 +04:00
parent c2f8e64f6d
commit 0ecd32299c
2 changed files with 49 additions and 70 deletions
+28 -66
View File
@@ -41,44 +41,33 @@
namespace Libs::Graphics { namespace Libs::Graphics {
struct ShaderBinaryInfo { struct ShaderMapEntry {
uint8_t signature[7]; ShaderMappedData data;
uint8_t version; uint64_t hash;
uint32_t pssl_or_cg : 1;
uint32_t cached : 1;
uint32_t type : 4;
uint32_t source_type : 2;
uint32_t length : 24;
uint8_t chunk_usage_base_offset_dw;
uint8_t num_input_usage_slots;
uint8_t is_srt : 1;
uint8_t is_srt_used_info_valid : 1;
uint8_t is_extended_usage_info : 1;
uint8_t reserved2 : 5;
uint8_t reserved3;
uint32_t hash0;
uint32_t hash1;
uint32_t crc32;
}; };
static std::unique_ptr<std::unordered_map<uint64_t, ShaderMappedData>> g_shader_map; static std::unique_ptr<std::unordered_map<uint64_t, ShaderMapEntry>> g_shader_map;
static std::mutex g_shader_map_mutex; static std::mutex g_shader_map_mutex;
void ShaderInit() { void ShaderInit() {
EXIT_IF(g_shader_map != nullptr); EXIT_IF(g_shader_map != nullptr);
g_shader_map = std::make_unique<std::unordered_map<uint64_t, ShaderMappedData>>(); g_shader_map = std::make_unique<std::unordered_map<uint64_t, ShaderMapEntry>>();
} }
void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data) { void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data) {
EXIT_IF(g_shader_map == nullptr); EXIT_IF(g_shader_map == nullptr);
if (addr == 0 || data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
EXIT("ShaderMapUserData shader=0x%016" PRIx64 " has invalid AGC shader_size=0x%08" PRIx32 "\n",
addr, data.code_size_bytes);
}
const auto hash = XXH3_64bits(reinterpret_cast<const void*>(addr), data.code_size_bytes);
std::scoped_lock lock(g_shader_map_mutex); std::scoped_lock lock(g_shader_map_mutex);
(*g_shader_map)[addr] = {data, hash};
(*g_shader_map)[addr] = data;
} }
static ShaderMappedData ShaderGetMappedData(uint64_t addr, const char* label) { static ShaderMapEntry ShaderGetMappedData(uint64_t addr, const char* label) {
EXIT_IF(g_shader_map == nullptr); EXIT_IF(g_shader_map == nullptr);
std::scoped_lock lock(g_shader_map_mutex); std::scoped_lock lock(g_shader_map_mutex);
@@ -90,37 +79,15 @@ static ShaderMappedData ShaderGetMappedData(uint64_t addr, const char* label) {
EXIT("%s shader=0x%016" PRIx64 " is missing from ShaderMap\n", label, addr); EXIT("%s shader=0x%016" PRIx64 " is missing from ShaderMap\n", label, addr);
} }
static const ShaderBinaryInfo* GetBinaryInfo(const uint32_t* code) { static ShaderParams GetShaderParams(uint64_t shader_addr, uint64_t hash,
EXIT_IF(code == nullptr);
if (code[0] == 0xBEEB03FF) {
return reinterpret_cast<const ShaderBinaryInfo*>(code +
static_cast<size_t>(code[1] + 1) * 2);
}
return nullptr;
}
static uint64_t GetDeclaredShaderHash(uint64_t shader_addr) {
const auto* header = GetBinaryInfo(reinterpret_cast<const uint32_t*>(shader_addr));
return header != nullptr ? (static_cast<uint64_t>(header->hash1) << 32u) | header->hash0 : 0;
}
static ShaderParams GetShaderParams(uint64_t shader_addr, const char* label, uint64_t declared_hash,
std::span<const uint32_t> user_data, std::span<const uint32_t> user_data,
const ShaderMappedData& data, uint32_t user_data_base = 0) { const ShaderMappedData& data, uint32_t user_data_base = 0) {
if (data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
EXIT("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64
" has invalid AGC shader_size=0x%08" PRIx32 "\n",
label, declared_hash, shader_addr, data.code_size_bytes);
}
const auto code_words = data.code_size_bytes / sizeof(uint32_t); const auto code_words = data.code_size_bytes / sizeof(uint32_t);
const auto code = std::span {reinterpret_cast<const uint32_t*>(shader_addr), code_words}; const auto code = std::span {reinterpret_cast<const uint32_t*>(shader_addr), code_words};
ShaderParams params { ShaderParams params {
.code = code, .code = code,
.user_data_count = static_cast<uint32_t>(user_data.size()) + user_data_base, .user_data_count = static_cast<uint32_t>(user_data.size()) + user_data_base,
.hash = declared_hash != 0 ? declared_hash .hash = hash,
: XXH3_64bits(code.data(), code.size_bytes()),
}; };
EXIT_IF(user_data.size() > HW::UserSgprInfo::SGPRS_MAX || EXIT_IF(user_data.size() > HW::UserSgprInfo::SGPRS_MAX ||
params.user_data_count > params.user_data.size()); params.user_data_count > params.user_data.size());
@@ -726,11 +693,10 @@ void BuildStageStaticKey(const ShaderComputeInputInfo& info, std::vector<uint32_
ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& context, ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& context,
const HW::UserConfig& user_config, ShaderVertexInputInfo& info) { const HW::UserConfig& user_config, ShaderVertexInputInfo& info) {
const auto& sh = context.GetShaderRegisters(); const auto& sh = context.GetShaderRegisters();
const auto data = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():"); const auto [data, hash] = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():");
const bool merged = (context.GetShaderStages() & 0x20u) != 0; const bool merged = (context.GetShaderStages() & 0x20u) != 0;
auto params = GetShaderParams( auto params = GetShaderParams(
regs.es_regs.data_addr, "ShaderRecompiler VS", regs.es_regs.data_addr, hash,
GetDeclaredShaderHash(regs.es_regs.data_addr),
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data, std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data,
merged ? 8u : 0u); merged ? 8u : 0u);
if (!merged) { if (!merged) {
@@ -755,10 +721,9 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
mesh.scratch_size_dwords = data.scratch_size_dwords; mesh.scratch_size_dwords = data.scratch_size_dwords;
if (data.type == Prospero::ShaderBinaryType::kGsFront) { if (data.type == Prospero::ShaderBinaryType::kGsFront) {
EXIT_IF(regs.gs_regs.data_addr == 0); EXIT_IF(regs.gs_regs.data_addr == 0);
const auto back = ShaderGetMappedData(regs.gs_regs.data_addr, "ShaderGetInputInfoGS():"); const auto [back, back_hash] = ShaderGetMappedData(regs.gs_regs.data_addr, "ShaderGetInputInfoGS():");
const auto back_params = const auto back_params =
GetShaderParams(regs.gs_regs.data_addr, "ShaderRecompiler GS", GetShaderParams(regs.gs_regs.data_addr, back_hash, {}, back);
GetDeclaredShaderHash(regs.gs_regs.data_addr), {}, back);
params.back_code = back_params.code; params.back_code = back_params.code;
params.user_data[0] = static_cast<uint32_t>(regs.gs_regs.user_data_addr); params.user_data[0] = static_cast<uint32_t>(regs.gs_regs.user_data_addr);
params.user_data[1] = static_cast<uint32_t>(regs.gs_regs.user_data_addr >> 32u); params.user_data[1] = static_cast<uint32_t>(regs.gs_regs.user_data_addr >> 32u);
@@ -797,9 +762,9 @@ std::array<ShaderParams, 3>
PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context, PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context,
std::array<ShaderVertexInputInfo, 3>& input_info) { std::array<ShaderVertexInputInfo, 3>& input_info) {
const auto& sh = context.GetShaderRegisters(); const auto& sh = context.GetShaderRegisters();
const auto local = ShaderGetMappedData(regs.ls_regs.data_addr, "ShaderGetInputInfoLS():"); const auto [local, local_hash] = ShaderGetMappedData(regs.ls_regs.data_addr, "ShaderGetInputInfoLS():");
const auto control = ShaderGetMappedData(regs.hs_regs.data_addr, "ShaderGetInputInfoHS():"); const auto [control, control_hash] = ShaderGetMappedData(regs.hs_regs.data_addr, "ShaderGetInputInfoHS():");
const auto evaluation = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoTES():"); const auto [evaluation, evaluation_hash] = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoTES():");
EXIT_NOT_IMPLEMENTED(local.type != Prospero::ShaderBinaryType::kHsFront || EXIT_NOT_IMPLEMENTED(local.type != Prospero::ShaderBinaryType::kHsFront ||
control.type != Prospero::ShaderBinaryType::kHsBack || control.type != Prospero::ShaderBinaryType::kHsBack ||
evaluation.type != Prospero::ShaderBinaryType::kGs); evaluation.type != Prospero::ShaderBinaryType::kGs);
@@ -810,12 +775,9 @@ PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context&
const auto local_users = std::span(regs.hs_user_sgpr.value, regs.hs_regs.rsrc2.user_sgpr); const auto local_users = std::span(regs.hs_user_sgpr.value, regs.hs_regs.rsrc2.user_sgpr);
const auto evaluation_users = std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr); const auto evaluation_users = std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr);
std::array<ShaderParams, 3> params { std::array<ShaderParams, 3> params {
GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS", GetShaderParams(regs.ls_regs.data_addr, local_hash, local_users, local),
GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local), GetShaderParams(regs.hs_regs.data_addr, control_hash, local_users, control, 8u),
GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS", GetShaderParams(regs.es_regs.data_addr, evaluation_hash, evaluation_users,
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control, 8u),
GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES",
GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users,
evaluation), evaluation),
}; };
// The fused HS back half receives its separate user-data address in s0:s1. // The fused HS back half receives its separate user-data address in s0:s1.
@@ -855,19 +817,19 @@ ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping, std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& ps_info) { ShaderPixelInputInfo& ps_info) {
const auto data = ShaderGetMappedData(regs.ps_regs.data_addr, "ShaderGetInputInfoPS():"); const auto [data, hash] = ShaderGetMappedData(regs.ps_regs.data_addr, "ShaderGetInputInfoPS():");
ShaderGetStaticInputInfoPS(regs, sh, target_export_mapping, data, ps_info); ShaderGetStaticInputInfoPS(regs, sh, target_export_mapping, data, ps_info);
return GetShaderParams( return GetShaderParams(
regs.ps_regs.data_addr, "ShaderRecompiler PS", GetDeclaredShaderHash(regs.ps_regs.data_addr), regs.ps_regs.data_addr, hash,
std::span<const uint32_t>(regs.ps_user_sgpr.value, regs.ps_regs.rsrc2.user_sgpr), data); std::span<const uint32_t>(regs.ps_user_sgpr.value, regs.ps_regs.rsrc2.user_sgpr), data);
} }
ShaderParams PrepareProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderParams PrepareProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& info) { ShaderComputeInputInfo& info) {
const auto data = ShaderGetMappedData(regs.cs_regs.data_addr, "ShaderGetInputInfoCS():"); const auto [data, hash] = ShaderGetMappedData(regs.cs_regs.data_addr, "ShaderGetInputInfoCS():");
ShaderGetStaticInputInfoCS(regs, sh, data, info); ShaderGetStaticInputInfoCS(regs, sh, data, info);
return GetShaderParams( return GetShaderParams(
regs.cs_regs.data_addr, "ShaderRecompiler CS", GetDeclaredShaderHash(regs.cs_regs.data_addr), regs.cs_regs.data_addr, hash,
std::span<const uint32_t>(regs.cs_user_sgpr.value, regs.cs_regs.user_sgpr), data); std::span<const uint32_t>(regs.cs_user_sgpr.value, regs.cs_regs.user_sgpr), data);
} }
+21 -4
View File
@@ -13503,11 +13503,11 @@ void TestPixelProgramCacheBindingIdentity() {
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(depth_export), Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(depth_export),
"pixel shader identity omitted depth-export semantics"); "pixel shader identity omitted depth-export semantics");
auto check_program_identity = [&](const uint32_t *shader) { auto check_program_identity = [&](std::span<const uint32_t> shader) {
HW::PixelShaderInfo regs{}; HW::PixelShaderInfo regs{};
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader); regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
ShaderMappedData mapped{}; ShaderMappedData mapped{};
mapped.code_size_bytes = sizeof(uint32_t); mapped.code_size_bytes = static_cast<uint32_t>(shader.size_bytes());
ShaderMapUserData(regs.ps_regs.data_addr, mapped); ShaderMapUserData(regs.ps_regs.data_addr, mapped);
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{}; const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
@@ -13522,7 +13522,8 @@ void TestPixelProgramCacheBindingIdentity() {
second_info); second_info);
CompilePixelRuntime(second_params, second_info); CompilePixelRuntime(second_params, second_info);
Check( Check(
first_params.hash == second_params.hash && first_params.hash == XXH3_64bits(shader.data(), shader.size_bytes()) &&
first_params.hash == second_params.hash &&
first_key == MakeStageStaticKey(second_info) && first_key == MakeStageStaticKey(second_info) &&
first_info.stage.program != nullptr && first_info.stage.program != nullptr &&
second_info.stage.program != nullptr, second_info.stage.program != nullptr,
@@ -13535,6 +13536,22 @@ void TestPixelProgramCacheBindingIdentity() {
Check(request_01 == request_10, Check(request_01 == request_10,
"relocated identical pixel programs did not share their source identity"); "relocated identical pixel programs did not share their source identity");
// Identical metadata after the scalar-literal prefix must not mask changes
// to the executable code when computing program identity.
std::array<uint32_t, 200> shader{};
shader[0] = 0xbeeb03ffu;
shader[1] = 0x5fu;
shader[2] = EncodeSMovB32(0, 129);
shader[3] = EncodeSopp(0x01);
shader[192] = 0x65726162u;
shader[193] = 0x746f6f66u;
shader[196] = 0x12345678u;
shader[197] = 0x9abcdef0u;
const auto first = check_program_identity(shader);
shader[2] = EncodeSMovB32(0, 130);
Check(check_program_identity(shader).first != first.first,
"re-registering changed code at the same address reused its old identity");
} }
void TestGraphicsPushConstantPlacement() { void TestGraphicsPushConstantPlacement() {