shader: use Khronos definitions and simplify compiler ownership

This commit is contained in:
nmzik
2026-09-11 18:42:05 +04:00
parent 47b26e497f
commit 1d1f00bdef
28 changed files with 2048 additions and 2153 deletions
+57 -84
View File
@@ -1392,7 +1392,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
compute_info.host_subgroup_size = host_subgroup_size;
options.input_info.compute = &compute_info;
options.user_data = user_data;
options.scratch_dwords = test.compute_info.scratch_size_dwords;
if (test.has_compute_info) {
options.wave_size = test.compute_info.wave_size;
}
@@ -1598,86 +1598,59 @@ std::array<u32, 64> MakeSampledTextureData(Prospero::BufferFormat format) {
namespace TestSpv {
enum : u32 {
ExecutionModelVertex = 0,
AddressingModelLogical = 0,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
StorageClassInput = 1,
StorageClassOutput = 3,
FunctionControlNone = 0,
DecorationBlock = 2,
DecorationBuiltIn = 11,
DecorationLocation = 30,
BuiltInPosition = 0,
OpTypeVoid = 19,
OpTypeInt = 21,
OpTypeFloat = 22,
OpTypeVector = 23,
OpTypeStruct = 30,
OpTypePointer = 32,
OpTypeFunction = 33,
OpConstant = 43,
OpFunction = 54,
OpFunctionEnd = 56,
OpVariable = 59,
OpLoad = 61,
OpStore = 62,
OpAccessChain = 65,
OpDecorate = 71,
OpMemberDecorate = 72,
OpCompositeConstruct = 80,
OpCompositeExtract = 81,
OpLabel = 248,
OpReturn = 253,
};
std::vector<u32> MakePassthroughVertexSpirv(bool layered, float clip_w = 1.0f) {
using ShaderRecompiler::Spirv::Builder;
Builder b;
const auto void_type = b.Type(OpTypeVoid);
const auto uint_type = b.Type(OpTypeInt, {32, 0});
const auto int_type = b.Type(OpTypeInt, {32, 1});
const auto float_type = b.Type(OpTypeFloat, {32});
const auto vec2_type = b.Type(OpTypeVector, {float_type, 2});
const auto vec4_type = b.Type(OpTypeVector, {float_type, 4});
const auto per_vertex_type = b.DecoratedType(
OpTypeStruct, {vec4_type},
{{OpDecorate, {DecorationBlock}},
{OpMemberDecorate, {0, DecorationBuiltIn, BuiltInPosition}}});
const auto void_type = b.Type(spv::OpTypeVoid);
const auto uint_type = b.Type(spv::OpTypeInt, {32, 0});
const auto int_type = b.Type(spv::OpTypeInt, {32, 1});
const auto float_type = b.Type(spv::OpTypeFloat, {32});
const auto vec2_type = b.Type(spv::OpTypeVector, {float_type, 2});
const auto vec4_type = b.Type(spv::OpTypeVector, {float_type, 4});
const auto per_vertex_type =
b.DecoratedType(spv::OpTypeStruct, {vec4_type},
{{spv::OpDecorate, {spv::DecorationBlock}},
{spv::OpMemberDecorate,
{0, spv::DecorationBuiltIn, spv::BuiltInPosition}}});
const auto ptr_input_vec2 =
b.Type(OpTypePointer, {StorageClassInput, vec2_type});
b.Type(spv::OpTypePointer, {spv::StorageClassInput, vec2_type});
const auto ptr_input_vec4 =
b.Type(OpTypePointer, {StorageClassInput, vec4_type});
b.Type(spv::OpTypePointer, {spv::StorageClassInput, vec4_type});
const auto ptr_output_vec4 =
b.Type(OpTypePointer, {StorageClassOutput, vec4_type});
b.Type(spv::OpTypePointer, {spv::StorageClassOutput, vec4_type});
const auto ptr_output_per_vertex =
b.Type(OpTypePointer, {StorageClassOutput, per_vertex_type});
const auto func_type = b.Type(OpTypeFunction, {void_type});
const auto const_u32_0 = b.Constant(OpConstant, uint_type, {0});
const auto const_f32_0 = b.Constant(OpConstant, float_type, {0x00000000u});
b.Type(spv::OpTypePointer, {spv::StorageClassOutput, per_vertex_type});
const auto func_type = b.Type(spv::OpTypeFunction, {void_type});
const auto const_u32_0 = b.Constant(spv::OpConstant, uint_type, {0});
const auto const_f32_0 =
b.Constant(spv::OpConstant, float_type, {0x00000000u});
const auto const_f32_w =
b.Constant(OpConstant, float_type, {std::bit_cast<u32>(clip_w)});
const auto in_pos = b.DefineGlobalVariable(ptr_input_vec2, StorageClassInput);
b.Constant(spv::OpConstant, float_type, {std::bit_cast<u32>(clip_w)});
const auto in_pos =
b.DefineGlobalVariable(ptr_input_vec2, spv::StorageClassInput);
const auto in_color =
b.DefineGlobalVariable(ptr_input_vec4, StorageClassInput);
b.DefineGlobalVariable(ptr_input_vec4, spv::StorageClassInput);
const auto out_color =
b.DefineGlobalVariable(ptr_output_vec4, StorageClassOutput);
b.DefineGlobalVariable(ptr_output_vec4, spv::StorageClassOutput);
const auto per_vertex =
b.DefineGlobalVariable(ptr_output_per_vertex, StorageClassOutput);
b.DefineGlobalVariable(ptr_output_per_vertex, spv::StorageClassOutput);
u32 instance = 0;
u32 layer = 0;
std::vector<u32> interfaces = {in_pos, in_color, per_vertex, out_color};
if (layered) {
instance = b.DefineGlobalVariable(
b.Type(OpTypePointer, {StorageClassInput, int_type}), StorageClassInput);
b.Type(spv::OpTypePointer, {spv::StorageClassInput, int_type}),
spv::StorageClassInput);
layer = b.DefineGlobalVariable(
b.Type(OpTypePointer, {StorageClassOutput, int_type}), StorageClassOutput);
b.AddAnnotation({OpDecorate, instance, DecorationBuiltIn, 43}); // InstanceIndex
b.AddAnnotation({OpDecorate, layer, DecorationBuiltIn, 9}); // Layer
b.Type(spv::OpTypePointer, {spv::StorageClassOutput, int_type}),
spv::StorageClassOutput);
b.AddAnnotation({spv::OpDecorate, instance, spv::DecorationBuiltIn,
spv::BuiltInInstanceIndex});
b.AddAnnotation(
{spv::OpDecorate, layer, spv::DecorationBuiltIn, spv::BuiltInLayer});
b.RequireVersion(0x00010500u);
b.RequireCapability(69); // ShaderLayer
b.RequireCapability(spv::CapabilityShaderLayer);
interfaces.insert(interfaces.end(), {instance, layer});
}
const auto main = b.AllocateId();
@@ -1689,32 +1662,33 @@ std::vector<u32> MakePassthroughVertexSpirv(bool layered, float clip_w = 1.0f) {
const auto position = b.AllocateId();
const auto position_ptr = b.AllocateId();
b.RequireCapability(CapabilityShader);
b.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
b.AddEntryPoint(ExecutionModelVertex, main, "main", interfaces);
b.AddAnnotation({OpDecorate, in_pos, DecorationLocation, 0});
b.AddAnnotation({OpDecorate, in_color, DecorationLocation, 1});
b.AddAnnotation({OpDecorate, out_color, DecorationLocation, 0});
b.RequireCapability(spv::CapabilityShader);
b.AddMemoryModel({spv::AddressingModelLogical, spv::MemoryModelGLSL450});
b.AddEntryPoint(spv::ExecutionModelVertex, main, "main", interfaces);
b.AddAnnotation({spv::OpDecorate, in_pos, spv::DecorationLocation, 0});
b.AddAnnotation({spv::OpDecorate, in_color, spv::DecorationLocation, 1});
b.AddAnnotation({spv::OpDecorate, out_color, spv::DecorationLocation, 0});
b.AddFunction({OpFunction, void_type, main, FunctionControlNone, func_type});
b.AddFunction({OpLabel, label});
b.AddFunction({OpLoad, vec2_type, pos2, in_pos});
b.AddFunction({OpLoad, vec4_type, color4, in_color});
b.AddFunction({OpCompositeExtract, float_type, pos_x, pos2, 0});
b.AddFunction({OpCompositeExtract, float_type, pos_y, pos2, 1});
b.AddFunction({OpCompositeConstruct, vec4_type, position, pos_x, pos_y,
b.AddFunction({spv::OpFunction, void_type, main, spv::FunctionControlMaskNone,
func_type});
b.AddFunction({spv::OpLabel, label});
b.AddFunction({spv::OpLoad, vec2_type, pos2, in_pos});
b.AddFunction({spv::OpLoad, vec4_type, color4, in_color});
b.AddFunction({spv::OpCompositeExtract, float_type, pos_x, pos2, 0});
b.AddFunction({spv::OpCompositeExtract, float_type, pos_y, pos2, 1});
b.AddFunction({spv::OpCompositeConstruct, vec4_type, position, pos_x, pos_y,
const_f32_0, const_f32_w});
b.AddFunction(
{OpAccessChain, ptr_output_vec4, position_ptr, per_vertex, const_u32_0});
b.AddFunction({OpStore, position_ptr, position});
b.AddFunction({OpStore, out_color, color4});
b.AddFunction({spv::OpAccessChain, ptr_output_vec4, position_ptr, per_vertex,
const_u32_0});
b.AddFunction({spv::OpStore, position_ptr, position});
b.AddFunction({spv::OpStore, out_color, color4});
if (layered) {
const auto index = b.AllocateId();
b.AddFunction({OpLoad, int_type, index, instance});
b.AddFunction({OpStore, layer, index});
b.AddFunction({spv::OpLoad, int_type, index, instance});
b.AddFunction({spv::OpStore, layer, index});
}
b.AddFunction({OpReturn});
b.AddFunction({OpFunctionEnd});
b.AddFunction({spv::OpReturn});
b.AddFunction({spv::OpFunctionEnd});
return b.Build();
}
@@ -23866,7 +23840,6 @@ void CheckIndirectImageKeySwitch() {
AllocateBindings(program);
ShaderComputeInputInfo compute{};
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
{.compute = &compute});
ValidateSpirv(name, spirv);
+234 -50
View File
@@ -154,7 +154,7 @@ void CompilePixelRuntime(const ShaderParams &params,
auto options = MakeCompileOptions(ShaderType::Pixel);
options.shader_hash = params.hash;
options.user_data = params.user_data;
options.scratch_dwords = input_info.scratch_size_dwords;
options.input_info.pixel = &input_info;
auto result = RecompileForTest(params.code, options);
static std::deque<ShaderRecompiler::IR::CompiledShaderInfo> programs;
@@ -1260,8 +1260,8 @@ void TestSpirvRequirementsAnalysis() {
block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)});
export_value.SetFlags(ExportFlags{.index = 0});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
const auto requirements = *program.spirv_requirements;
const auto requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
Check(requirements.subgroup_ballot && requirements.subgroup_shuffle &&
requirements.subgroup_local_invocation_id &&
requirements.compute_derivatives &&
@@ -1270,8 +1270,8 @@ void TestSpirvRequirementsAnalysis() {
"consolidated SPIR-V requirements missed an IR dependency");
program.stage = ShaderType::Compute;
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
const auto compute_requirements = *program.spirv_requirements;
const auto compute_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
Check(!compute_requirements.function_lds &&
!compute_requirements.pixel_valid_mask,
"stage-specific SPIR-V requirements leaked into compute");
@@ -1281,14 +1281,14 @@ void TestSpirvRequirementsAnalysis() {
[&] {
program.stage = ShaderType::Pixel;
shared.SetFlags(MemoryFlags{.index = 1});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
},
"invalid shared-memory requirement metadata did not terminate analysis");
ExpectFatal(
[&] {
program.stage = ShaderType::Pixel;
export_value.SetFlags(ExportFlags{.index = 1});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
},
"invalid export requirement metadata did not terminate analysis");
#endif
@@ -1309,8 +1309,8 @@ void TestSpirvRequirementsAnalysis() {
ValueOpcode::LoadBufferU32,
{Value(&buffer), Value(0u), Value(0u), Value(0u), Value(true)});
load.SetFlags(MemoryFlags{.index = 0});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid);
const auto add_tid_requirements = *add_tid.spirv_requirements;
const auto add_tid_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
Check(add_tid_requirements.subgroup_local_invocation_id &&
!add_tid_requirements.subgroup_ballot &&
!add_tid_requirements.subgroup_shuffle,
@@ -1320,20 +1320,20 @@ void TestSpirvRequirementsAnalysis() {
ExpectFatal(
[&] {
add_tid.memory_info[0].resource = 1;
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid);
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
},
"invalid buffer requirement metadata did not terminate analysis");
ExpectFatal(
[&] {
add_tid.stage = ShaderType::Vertex;
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid);
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
},
"graphics buffer ADD_TID did not terminate requirements analysis");
#endif
Program empty;
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(empty);
const auto empty_requirements = *empty.spirv_requirements;
const auto empty_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(empty);
Check(!empty_requirements.subgroup_ballot &&
!empty_requirements.subgroup_shuffle &&
!empty_requirements.subgroup_local_invocation_id &&
@@ -4383,7 +4383,7 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {
Frontend::TranslateOptions translate_options{};
translate_options.stage = ShaderType::Pixel;
translate_options.wave_size = 64u;
translate_options.pixel = &pixel;
translate_options.input_info.pixel = &pixel;
ShaderRecompiler::Decoder::DecodeProgram(shader, program);
graph = ShaderRecompiler::CFG::BuildGraph(program);
ir = ShaderRecompiler::Frontend::TranslateProgram(program, graph,
@@ -4431,7 +4431,7 @@ void TestNewShaderRecompilerIrLookupMissFailsExplicitly() {
Frontend::TranslateOptions options{};
options.stage = ShaderType::Compute;
options.wave_size = 64u;
options.compute = &compute;
options.input_info.compute = &compute;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
store.pc = 0u;
@@ -4807,7 +4807,7 @@ void TestImageAddressOperands() {
ShaderComputeInputInfo compute = RegressionComputeInputInfo();
Frontend::TranslateOptions options{};
options.stage = ShaderType::Compute;
options.compute = &compute;
options.input_info.compute = &compute;
auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options);
IR::RewriteToSsa(program.blocks);
IR::ConstantPropagationPass(program.blocks);
@@ -6211,7 +6211,7 @@ void TestNewShaderRecompilerNativeWideScalarMemoryIr() {
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
translate_options.stage = ShaderType::Compute;
translate_options.wave_size = 64u;
translate_options.compute = &compute;
translate_options.input_info.compute = &compute;
ShaderRecompiler::Decoder::DecodeProgram(shader, decoded);
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
@@ -6909,7 +6909,7 @@ void TestNewShaderRecompilerDsReadWrite2Translation() {
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
translate_options.stage = ShaderType::Compute;
translate_options.wave_size = 64u;
translate_options.compute = &compute;
translate_options.input_info.compute = &compute;
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
@@ -7057,7 +7057,7 @@ void TestNewShaderRecompilerDsWideAndAtomicTranslation() {
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
translate_options.stage = ShaderType::Compute;
translate_options.wave_size = 64u;
translate_options.compute = &compute;
translate_options.input_info.compute = &compute;
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
@@ -7348,7 +7348,6 @@ void TestNewShaderRecompilerStructuredU64Phi() {
IR::IREmitter use(join);
use.Emit(IR::ValueOpcode::CompositeExtractU64,
{IR::Value(&phi), IR::Value(1u)});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -8673,7 +8672,7 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
IR::IREmitter use(prologue_program.blocks[1]);
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(prologue_program);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(prologue_program,
options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -8692,7 +8691,6 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
IR::IREmitter use(program.blocks[2]);
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(1u)});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
options.input_info);
@@ -8834,7 +8832,6 @@ void TestNewShaderRecompilerU64PairTranslation() {
ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {base, IR::Value(count)});
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {base, IR::Value(count)});
}
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -8868,7 +8865,7 @@ void TestNewShaderRecompilerU64PairTranslation() {
{IR::Value(1u), IR::Value(2u)});
IR::IREmitter use(program.blocks[2]);
use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)});
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
spirv.clear();
spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -9429,7 +9426,7 @@ void TestEmbeddedVertexFormatSwizzle() {
options.stage = ShaderType::Vertex;
options.wave_size = 32;
options.user_data_count = 0;
options.vertex = &input;
options.input_info.vertex = &input;
options.embedded_fetch = &fetch;
auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options);
Check(program.info.vertex_fetch_components[0] == test.source_width,
@@ -9528,7 +9525,7 @@ void TestMeshInputAssembly() {
options.stage = ShaderType::Mesh;
options.wave_size = 64;
options.user_data_count = 0;
options.vertex = &input;
options.input_info.vertex = &input;
auto program = Frontend::TranslateProgram(decoded, graph, options);
const uint32_t draw[] = {test.count, test.base_vertex, 7, test.width,
test.address_low, 0x12};
@@ -9660,7 +9657,7 @@ void TestNewShaderRecompilerPrunesUnreachableSetpcMetadata() {
ShaderRecompiler::Frontend::TranslateOptions translate_options{};
translate_options.stage = ShaderType::Compute;
translate_options.wave_size = 64u;
translate_options.compute = &compute;
translate_options.input_info.compute = &compute;
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
translate_options);
size_t scalar_loads = 0;
@@ -9946,43 +9943,104 @@ void TestNewShaderRecompilerAuxPositionExports() {
"PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key");
}
void TestDeferredSpirvPhiPatching() {
ShaderRecompiler::Spirv::Builder builder;
const auto type = builder.Type(spv::OpTypeInt, {32u, 0u});
const auto zero = builder.Constant(spv::OpConstant, type, {0u});
const auto one = builder.Constant(spv::OpConstant, type, {1u});
const auto left = builder.AllocateId();
const auto right = builder.AllocateId();
const auto result = builder.AllocateId();
const auto phi = builder.AddDeferredPhi(type, result, 2);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
ExpectFatal([&] { (void)builder.Build(); }, "unpatched phi was serialized");
ExpectFatal([&] { builder.PatchDeferredPhi(phi, 2, one, right); },
"phi patch accepted an incoming outside its encoded length");
#endif
builder.PatchDeferredPhi(phi, 1, one, right);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
ExpectFatal([&] { builder.PatchDeferredPhi(phi, 1, zero, left); },
"phi incoming was patched twice");
ExpectFatal([&] { (void)builder.Build(); },
"partially patched phi was serialized");
#endif
// Appending another instruction must not change the first phi's patch
// location.
const auto second_result = builder.AllocateId();
const auto second_phi = builder.AddDeferredPhi(type, second_result, 1);
builder.PatchDeferredPhi(phi, 0, zero, left);
builder.PatchDeferredPhi(second_phi, 0, one, right);
const auto binary = builder.Build();
uint32_t phis = 0;
for (size_t offset = 5; offset < binary.size();) {
const auto words = binary[offset] >> spv::WordCountShift;
if ((binary[offset] & spv::OpCodeMask) == spv::OpPhi) {
if (phis++ == 0) {
Check(words == 7 && binary[offset + 2] == result &&
binary[offset + 3] == zero && binary[offset + 4] == left &&
binary[offset + 5] == one && binary[offset + 6] == right,
"out-of-order phi patches changed incoming values or parents");
} else {
Check(words == 5 && binary[offset + 2] == second_result &&
binary[offset + 3] == one && binary[offset + 4] == right,
"patching one phi corrupted its neighbor");
}
}
offset += words;
}
Check(phis == 2, "deferred phi instructions were lost");
}
void TestDemandDrivenSpirvDeclarations() {
using ShaderRecompiler::Spirv::Builder;
Builder declarations;
const auto uint_type = declarations.Type(21u, {32u, 0u});
Check(uint_type == declarations.Type(21u, {32u, 0u}),
const auto uint_type = declarations.Type(spv::OpTypeInt, {32u, 0u});
Check(uint_type == declarations.Type(spv::OpTypeInt, {32u, 0u}),
"structural type interning returned different IDs");
const auto zero = declarations.Constant(43u, uint_type, {0u});
Check(zero == declarations.Constant(43u, uint_type, {0u}),
const auto zero = declarations.Constant(spv::OpConstant, uint_type, {0u});
Check(zero == declarations.Constant(spv::OpConstant, uint_type, {0u}),
"constant interning returned different IDs");
Check(declarations.Import("GLSL.std.450") ==
declarations.Import("GLSL.std.450"),
"extended-instruction import was duplicated");
declarations.RequireCapability(1u);
declarations.RequireCapability(1u);
const auto plain_array = declarations.Type(28u, {uint_type, zero});
declarations.RequireCapability(spv::CapabilityShader);
declarations.RequireCapability(spv::CapabilityShader);
const auto plain_array =
declarations.Type(spv::OpTypeArray, {uint_type, zero});
const auto decorated_array = declarations.DecoratedType(
28u, {uint_type, zero}, {{71u, {6u, sizeof(uint32_t)}}});
spv::OpTypeArray, {uint_type, zero},
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}});
Check(decorated_array ==
declarations.DecoratedType(28u, {uint_type, zero},
{{71u, {6u, sizeof(uint32_t)}}}),
declarations.DecoratedType(
spv::OpTypeArray, {uint_type, zero},
{{spv::OpDecorate,
{spv::DecorationArrayStride, sizeof(uint32_t)}}}),
"decorated type interning returned different IDs");
Check(plain_array != decorated_array,
"decorated and undecorated aggregate types were aliased");
Check(declarations.DecoratedType(28u, {uint_type, zero}, {}) == plain_array,
Check(declarations.DecoratedType(spv::OpTypeArray, {uint_type, zero}, {}) ==
plain_array,
"empty decorated type request bypassed structural interning");
const auto ptr = declarations.Type(32u, {7u, uint_type});
Check(declarations.DefineGlobalVariable(ptr, 7u) !=
declarations.DefineGlobalVariable(ptr, 7u),
const auto ptr = declarations.Type(spv::OpTypePointer,
{spv::StorageClassFunction, uint_type});
Check(declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction) !=
declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction),
"distinct variables were structurally interned");
const auto declaration_binary = declarations.Build();
Check(SpirvInstructionOpcodeCount(declaration_binary, 17u) == 1u &&
SpirvInstructionOpcodeCount(declaration_binary, 11u) == 1u &&
SpirvInstructionOpcodeCount(declaration_binary, 21u) == 1u &&
SpirvInstructionOpcodeCount(declaration_binary, 28u) == 2u &&
SpirvInstructionOpcodeCount(declaration_binary, 43u) == 1u &&
SpirvInstructionOpcodeCount(declaration_binary, 71u) == 1u &&
SpirvInstructionOpcodeCount(declaration_binary, spv::OpTypeInt) ==
1u &&
SpirvInstructionOpcodeCount(declaration_binary, spv::OpTypeArray) ==
2u &&
SpirvInstructionOpcodeCount(declaration_binary, spv::OpConstant) ==
1u &&
SpirvInstructionOpcodeCount(declaration_binary, spv::OpDecorate) ==
1u &&
SpirvInstructionOpcodeCount(declaration_binary, 59u) == 2u,
"canonical builder emitted duplicate declarations");
@@ -10074,7 +10132,7 @@ void TestTypedEntryStateIsMinimal() {
translate_options.stage = ShaderType::Compute;
translate_options.wave_size = wave_size;
translate_options.user_data_count = 0;
translate_options.compute = &compute;
translate_options.input_info.compute = &compute;
values = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph,
translate_options);
@@ -11416,7 +11474,7 @@ void BuildTypedPlan(const uint32_t *code, uint32_t words,
ShaderRecompiler::Frontend::TranslateOptions options{};
options.stage = ShaderType::Compute;
options.wave_size = 64u;
options.compute = &compute;
options.input_info.compute = &compute;
ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options);
ShaderRecompiler::IR::RewriteToSsa(ir.blocks);
ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks);
@@ -12256,7 +12314,7 @@ void TestComputeLdsAllocationIdentity() {
options.user_data = params.user_data;
options.input_info.compute = &input_info;
options.wave_size = input_info.wave_size;
options.scratch_dwords = input_info.scratch_size_dwords;
auto result = RecompileForTest(shader, options);
Check(MeasureSpirv(result.spirv).workgroup_variables == 1u,
"LDS shader did not declare exactly one Workgroup variable");
@@ -12322,7 +12380,7 @@ void TestComputeLdsAllocationIdentity() {
scratch_options.user_data = scratch_params.user_data;
scratch_options.input_info.compute = &scratch_info;
scratch_options.wave_size = scratch_info.wave_size;
scratch_options.scratch_dwords = scratch_info.scratch_size_dwords;
auto scratch_result =
RecompileForTest(scratch_params.code, scratch_options);
Check(scratch_result.program.scratch_dwords == 7,
@@ -12482,7 +12540,10 @@ void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data;
options.scratch_dwords = 1;
ShaderComputeInputInfo compute{};
compute.scratch_size_dwords = 1;
options.input_info.compute = &compute;
auto result = RecompileForTest(segmented_shader, options);
Check(result.program.info.uses_dma,
"GLOBAL null-SADDR did not enable DMA");
@@ -12491,6 +12552,125 @@ void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
"SCRATCH incorrectly entered guest address tracking");
}
void TestCompilerStageInputOwnership() {
using namespace ShaderRecompiler;
const uint32_t shader[] = {EncodeSopp(0x01)};
ShaderVertexInputInfo vertex{};
vertex.scratch_size_dwords = 3;
vertex.mesh.scratch_size_dwords = 11;
vertex.mesh.threads_num[0] = 192;
vertex.mesh.threads_num[1] = vertex.mesh.threads_num[2] = 1;
vertex.mesh.primitives_per_group = 62;
vertex.mesh.vertices_per_group = 64;
ShaderPixelInputInfo pixel{};
pixel.scratch_size_dwords = 5;
ShaderComputeInputInfo compute{};
compute.scratch_size_dwords = 7;
for (const auto stage : {ShaderType::Vertex, ShaderType::Pixel,
ShaderType::Compute, ShaderType::Mesh}) {
CompileOptions options{};
options.stage = stage;
options.dump_ir = false;
uint32_t expected_scratch = 0;
switch (stage) {
case ShaderType::Vertex:
options.input_info.vertex = &vertex;
expected_scratch = 3;
break;
case ShaderType::Pixel:
options.input_info.pixel = &pixel;
expected_scratch = 5;
break;
case ShaderType::Compute:
options.input_info.compute = &compute;
expected_scratch = 7;
break;
case ShaderType::Mesh:
options.input_info.vertex = &vertex;
expected_scratch = 11;
break;
default:
std::abort();
}
const auto translated = TranslateProgram(shader, options);
Check(
translated.program.scratch_dwords == expected_scratch,
"translation did not use its stage's authoritative scratch allocation");
}
auto options = MakeCompileOptions(ShaderType::Compute);
options.shader_hash = 0x1234;
auto translated = TranslateProgram(shader, options);
options.stage = ShaderType::Unknown;
options.shader_hash = 0;
auto compiled = CompileProgram(std::move(translated), options, {});
Check(compiled.program.stage == ShaderType::Compute &&
compiled.program.shader_hash == 0x1234,
"compilation reinterpreted the translated program's identity");
CheckSpirvBinaryValidates(compiled.spirv);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
options.stage = ShaderType::Vertex;
options.input_info.vertex = nullptr;
ExpectFatal([&] { (void)TranslateProgram(shader, options); },
"missing stage metadata bypassed translation validation");
const uint32_t auxiliary_position[] = {
EncodeExp0(0x0d, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01)};
for (const auto stage : {ShaderType::Pixel, ShaderType::Compute}) {
ExpectFatal(
[&] {
(void)RecompileForTest(auxiliary_position, MakeCompileOptions(stage));
},
"auxiliary position export read another stage's input metadata");
}
Decoder::Program decoded;
Decoder::DecodeProgram(shader, decoded);
const auto cfg = CFG::BuildGraph(decoded);
Frontend::EmbeddedFetchPlan fetch;
Frontend::TranslateOptions invalid_fetch{.stage = ShaderType::Compute,
.input_info = {.compute = &compute},
.embedded_fetch = &fetch};
ExpectFatal(
[&] { (void)Frontend::TranslateProgram(decoded, cfg, invalid_fetch); },
"embedded vertex fetch accepted another stage's metadata");
#endif
}
void TestSpirvEmissionOwnsRequirements() {
using namespace ShaderRecompiler;
const uint32_t shader[] = {EncodeSopp(0x01)};
const auto options = MakeCompileOptions(ShaderType::Compute);
auto compiled = RecompileForTest(shader, options);
IR::IREmitter emitter(compiled.program.blocks.front());
emitter.Emit(IR::ValueOpcode::LaneId, {});
const auto binary = Spirv::EmitProgram(compiled.program, options.input_info);
CheckSpirvBinaryValidates(binary);
Check(Common::ContainsStr(DisassembleSpirvBinary(binary),
"BuiltIn SubgroupLocalInvocationId"),
"emission reused requirements from an earlier IR version");
}
void TestRepeatedExportsHaveOneInterface() {
const uint32_t shader[] = {EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
EncodeSopp(0x01)};
const auto result =
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
Check(result.program.info.outputs.size() == 1,
"repeated exports duplicated the collected shader interface");
CheckSpirvBinaryValidates(result.spirv);
uint32_t outputs = 0;
for (size_t offset = 5; offset < result.spirv.size();) {
if ((result.spirv[offset] & spv::OpCodeMask) == spv::OpVariable &&
result.spirv[offset + 3] == spv::StorageClassOutput) {
outputs++;
}
offset += result.spirv[offset] >> spv::WordCountShift;
}
Check(outputs == 1, "repeated exports produced duplicate output variables");
}
void TestNewShaderRecompilerSpirvSizeBaselines() {
const auto compile = [](const char *name, std::span<const uint32_t> shader,
const SpirvMetrics &budget,
@@ -12758,6 +12938,10 @@ int main() {
TestNativeShaderResourceDependencies();
TestNormalizedImageContracts();
TestSpirvRequirementsAnalysis();
TestDeferredSpirvPhiPatching();
TestCompilerStageInputOwnership();
TestSpirvEmissionOwnsRequirements();
TestRepeatedExportsHaveOneInterface();
TestNewShaderRecompilerSpirvSizeBaselines();
TestDemandDrivenSpirvDeclarations();
TestNewShaderRecompilerSMovB32();