shader: rework resource specialization and optimize pipeline caching

This commit is contained in:
nmzik
2026-09-03 02:58:11 +02:00
parent c48a8bd6ae
commit 98200fed19
39 changed files with 2281 additions and 2235 deletions
@@ -81,13 +81,13 @@ void ValidateNativeProgram(const IR::Program& program) {
const bool uses_flattened_runtime =
!program.srt_reads.empty() ||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
return image.indirect_mapping_capacity != 0u;
return image.indirect_search_iterations != 0u;
});
if (uses_flattened_runtime) {
Expect(Kind::FlattenedSrt);
}
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
Expect(Kind::UserData);
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
Expect(Kind::ShaderData);
}
std::array<bool, KindCount> seen {};
@@ -104,21 +104,19 @@ void ValidateNativeProgram(const IR::Program& program) {
Fail(program, "native shader plan is missing a required descriptor group");
}
}
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
IR::NativePushConstantSize ||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
if ((program.bindings.UsesPushData() &&
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.memory_offset_count != program.info.buffers.size() ||
(!has_user_storage &&
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
(has_user_storage && program.bindings.push_constant_size != 0) ||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
!std::is_sorted(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) ||
std::adjacent_find(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) !=
program.bindings.user_data_registers.end()) {
Fail(program, "native user-data layout is inconsistent");
Fail(program, "native shader-data layout is inconsistent");
}
const auto planning_only_handle = [&](const IR::Inst& handle) {
@@ -302,7 +300,6 @@ void AnalyzeProgramRequirements(IR::Program& program) {
}
std::vector<uint32_t> EmitProgram(const IR::Program& program,
const IR::ResourceSnapshot& resources,
ShaderStageInputInfo input_info) {
using namespace Emitter;
@@ -315,15 +312,9 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
!program.spirv_requirements.has_value()) {
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
}
if (!IR::ValidateResourceSnapshot(program, resources)) {
Fail(program, "resource snapshot is inconsistent with the native shader plan");
}
if (!IR::ValidateResourceSpecialization(program, resources)) {
Fail(program, "resource specialization is inconsistent with the native shader plan");
}
ValidateNativeProgram(program);
IR::ValidateProgram(program, true);
EmitterState state(program, resources, input_info);
EmitterState state(program, input_info);
state.stage = program.stage;
state.wave_size = program.wave_size;
state.inputs.reserve(program.info.inputs.size());
@@ -3,7 +3,7 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <vector>
@@ -12,7 +12,6 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
void AnalyzeProgramRequirements(IR::Program& program);
std::vector<uint32_t> EmitProgram(const IR::Program& program,
const IR::ResourceSnapshot& resources,
ShaderStageInputInfo input_info);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -806,7 +806,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
return true;
@@ -824,7 +824,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
auto low = ConstantU32(state, 0u);
auto high = LoadMapping(mapping);
auto selected = ConstantU32(state, 0u);
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
for (uint32_t iteration = 0; iteration < image.indirect_search_iterations;
iteration++) {
const auto active = Binary(state, OpULessThan, TypeBool(state), low, high);
const auto mid =
@@ -341,14 +341,12 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
struct EmitterState {
EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_,
ShaderStageInputInfo input_info_)
: program(program_), resources(resources_), input_info(input_info_),
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
: program(program_), input_info(input_info_),
requirements(*program_.spirv_requirements) {}
Builder builder;
const IR::Program& program;
const IR::ResourceSnapshot& resources;
ShaderStageInputInfo input_info;
const IR::SpirvRequirements& requirements;
ShaderType stage = ShaderType::Unknown;
@@ -361,7 +359,7 @@ struct EmitterState {
uint32_t gds_variable = 0;
uint32_t gds_length = 0;
uint32_t push_constant_variable = 0;
uint32_t vsharp_storage_variable = 0;
uint32_t shader_data_storage_variable = 0;
uint32_t flattened_srt_variable = 0;
uint32_t lds_variable = 0;
uint32_t scratch_variable = 0;
@@ -4,6 +4,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
if (state.push_constant_variable != 0) {
dword_index += state.program.bindings.push_data_start_dword;
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypePushConstantElementPointer(state), pointer,
@@ -12,11 +13,11 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
return value;
}
if (state.vsharp_storage_variable != 0) {
if (state.shader_data_storage_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.vsharp_storage_variable, ConstantU32(state, 0),
state.shader_data_storage_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
return value;
@@ -119,8 +119,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class) {
namespace {
uint32_t PushConstantArrayType(EmitterState& state) {
const auto count =
ConstantU32(state, state.program.bindings.push_constant_size / sizeof(uint32_t));
const auto count = ConstantU32(state, IR::PushData::DwordCount);
return state.builder.DecoratedType(OpTypeArray, {TypeU32(state), count},
{{OpDecorate, {DecorationArrayStride, sizeof(uint32_t)}}});
}
@@ -128,7 +127,7 @@ uint32_t PushConstantArrayType(EmitterState& state) {
uint32_t PushConstantBlockType(EmitterState& state) {
return state.builder.DecoratedType(
OpTypeStruct, {PushConstantArrayType(state)},
{{OpMemberDecorate, {0, DecorationOffset, state.program.bindings.push_constant_offset}},
{{OpMemberDecorate, {0, DecorationOffset, 0}},
{OpDecorate, {DecorationBlock}}});
}
@@ -173,14 +172,14 @@ void DefineDescriptorVariables(EmitterState& state) {
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
}
if (state.program.bindings.push_constant_size != 0) {
if (state.program.bindings.UsesPushData()) {
const auto pointer_type =
TypePointer(state, StorageClassPushConstant, PushConstantBlockType(state));
state.push_constant_variable =
state.builder.DefineGlobalVariable(pointer_type, StorageClassPushConstant);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::UserData) != nullptr) {
state.vsharp_storage_variable = state.builder.DefineGlobalVariable(
if (DescriptorBinding(state, IR::DescriptorBindingKind::ShaderData) != nullptr) {
state.shader_data_storage_variable = state.builder.DefineGlobalVariable(
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
@@ -604,9 +603,9 @@ void AddVsharpAnnotationsAndNames(EmitterState& state) {
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
state.builder.AddName(state.push_constant_variable, "vsharp");
}
if (state.vsharp_storage_variable != 0) {
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
IR::DescriptorBindingKind::UserData);
if (state.shader_data_storage_variable != 0) {
DecorateDescriptor(state, state.shader_data_storage_variable, "shader_data",
IR::DescriptorBindingKind::ShaderData);
}
}