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
+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();