shader: keep fragment layer values under lane masks

Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
nmzik
2026-09-05 11:43:49 +04:00
parent 2ec0e9e8f3
commit b96a30ed88
16 changed files with 458 additions and 25 deletions
+123 -9
View File
@@ -1032,6 +1032,9 @@ struct GraphicsCase {
std::vector<u32> pixel_interpolator_settings;
bool pixel_no_perspective = false;
std::vector<u32> vertices;
bool pixel_ancillary = false;
bool pixel_front_face = false;
u32 layers = 1;
};
struct CompiledShader {
@@ -1373,6 +1376,9 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
? 1u
: static_cast<u32>(test.pixel_interpolator_settings.size());
pixel_info.ps_no_perspective = test.pixel_no_perspective;
pixel_info.ps_ancillary = test.pixel_ancillary;
pixel_info.ps_front_face = test.pixel_front_face;
pixel_info.ps_system_input_base = 2;
for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) {
pixel_info.interpolator_settings[i] = i;
}
@@ -1457,12 +1463,13 @@ enum : u32 {
OpReturn = 253,
};
std::vector<u32> MakePassthroughVertexSpirv() {
std::vector<u32> MakePassthroughVertexSpirv(bool layered) {
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});
@@ -1489,6 +1496,20 @@ std::vector<u32> MakePassthroughVertexSpirv() {
b.DefineGlobalVariable(ptr_output_vec4, StorageClassOutput);
const auto per_vertex =
b.DefineGlobalVariable(ptr_output_per_vertex, 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);
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.RequireVersion(0x00010500u);
b.RequireCapability(69); // ShaderLayer
interfaces.insert(interfaces.end(), {instance, layer});
}
const auto main = b.AllocateId();
const auto label = b.AllocateId();
const auto pos2 = b.AllocateId();
@@ -1500,8 +1521,7 @@ std::vector<u32> MakePassthroughVertexSpirv() {
b.RequireCapability(CapabilityShader);
b.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
b.AddEntryPoint(ExecutionModelVertex, main, "main",
{in_pos, in_color, per_vertex, out_color});
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});
@@ -1518,6 +1538,11 @@ std::vector<u32> MakePassthroughVertexSpirv() {
{OpAccessChain, ptr_output_vec4, position_ptr, per_vertex, const_u32_0});
b.AddFunction({OpStore, position_ptr, position});
b.AddFunction({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({OpReturn});
b.AddFunction({OpFunctionEnd});
return b.Build();
@@ -10343,13 +10368,15 @@ public:
std::vector<u32> RenderFragment(const GraphicsCase &test,
const CompiledShader &fragment) {
const auto vertex_spirv = TestSpv::MakePassthroughVertexSpirv();
const auto vertex_spirv = TestSpv::MakePassthroughVertexSpirv(test.layers > 1);
ValidateSpirv(test.name, vertex_spirv);
Image target =
CreateImage2D(test.name, 1, 1, vk::Format::eR32G32B32A32Sfloat,
CreateImageMips(test.name, 1, 1, vk::Format::eR32G32B32A32Sfloat,
vk::ImageUsageFlagBits::eColorAttachment, {}, 4,
vk::ImageLayout::eGeneral);
vk::ImageLayout::eGeneral, vk::ImageType::e2D,
test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D,
test.layers);
const std::vector<u32> default_vertices = {
0xbf800000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u,
0x3f800000u, 0x40400000u, 0xbf800000u, 0x3e800000u, 0x3f000000u,
@@ -10507,7 +10534,7 @@ public:
vk::RenderingInfo rendering{};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {1, 1};
rendering.layerCount = 1;
rendering.layerCount = test.layers;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &color;
cmd.beginRendering(rendering);
@@ -10521,13 +10548,13 @@ public:
}
vk::DeviceSize offset = 0;
cmd.bindVertexBuffers(0, 1, &vertex_buffer.buffer, &offset);
cmd.draw(3, 1, 0, 0);
cmd.draw(3, test.layers, 0, 0);
cmd.endRendering();
EndSubmitAndFree(test.name, "graphics", cmd);
target.layout = vk::ImageLayout::eGeneral;
auto pixel = ReadImage(test.name, &target);
pixel.resize(4);
pixel.resize(4 * test.layers);
m_device.destroyPipeline(pipeline, nullptr);
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
@@ -11762,6 +11789,8 @@ private:
Require("VulkanHarness", "dispatch",
available_features12.bufferDeviceAddress == true,
"bufferDeviceAddress is not supported");
Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true,
"vertex layer output is not supported");
float priority = 1.0f;
vk::DeviceQueueCreateInfo queue_info{};
@@ -11779,6 +11808,7 @@ private:
vk::StructureType::ePhysicalDeviceVulkan12Features;
device_features12.timelineSemaphore = true;
device_features12.bufferDeviceAddress = true;
device_features12.shaderOutputLayer = true;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &device_features12;
@@ -13822,6 +13852,50 @@ TestCase ScalarWqmB64SelectsSccDomain() {
O::S_ENDPGM}};
}
TestCase ScalarWqmB64PreservesPartialMasks() {
using O = ShaderOpcode;
TestCase test;
test.name = "ScalarWqmB64PreservesPartialMasks";
auto &code = test.code;
code.push_back(EncodeSop1(0x04, 16, 126)); // Save full EXEC.
AppendVMovU32(&code, 1, 3);
AppendVMovU32(&code, 2, 9);
u32 offset = 0;
for (const u32 destination : {126u, 106u, 8u}) {
code.push_back(EncodeVopc(0xc2, Vgpr(0), 1)); // Only lane 3.
code.push_back(EncodeSop1(0x04, destination, 106));
code.push_back(EncodeSop1(0x0a, destination, destination));
code.push_back(EncodeSop1(0x04, 126, destination));
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset);
code.push_back(EncodeSop1(0x04, 126, 16));
offset += 64;
}
code.push_back(EncodeSMovB32(8, InlineU32(32))); // Raw bits 5 and 33.
code.push_back(EncodeSMovB32(9, InlineU32(2)));
code.push_back(EncodeSop1(0x0a, 10, 8));
code.push_back(EncodeSop1(0x04, 126, 10));
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset);
AppendEnd(&code);
test.initial.assign(256, 0xdeadbeef);
test.expected = test.initial;
for (u32 lane = 0; lane < 64; ++lane) {
for (u32 region = 0; region < 3; ++region) {
if (lane < 4) test.expected[region * 64 + lane] = 9;
}
if ((lane >= 4 && lane < 8) || (lane >= 32 && lane < 36)) {
test.expected[192 + lane] = 9;
}
}
test.opcodes = {O::S_MOV_B64, O::S_MOV_B32, O::S_WQM_B64, O::V_MOV_B32,
O::V_CMP_EQ_U32, O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase ScalarMaskProvenanceOverlapAndMixedBinary() {
using O = ShaderOpcode;
@@ -21701,6 +21775,42 @@ GraphicsCase GraphicsInterpolationExport() {
O::V_MOV_B32, O::EXP, O::S_ENDPGM}};
}
GraphicsCase GraphicsAncillaryLayer(bool front_face) {
GraphicsCase test;
test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer";
test.pixel_ancillary = true;
test.pixel_front_face = front_face;
test.layers = 8;
const auto ancillary = Vgpr(front_face ? 3 : 2);
test.fragment_code.push_back(EncodeSop1(0x04, 8, 126));
test.fragment_code.push_back(EncodeSop1(0x0a, 126, 126));
AppendVop3(&test.fragment_code, 0x148, 4, ancillary, InlineU32(16), InlineU32(11));
AppendVop3(&test.fragment_code, 0x149, 5, ancillary, InlineU32(16), InlineU32(3));
test.fragment_code.push_back(EncodeSop1(0x04, 126, 8));
AppendVMovU32(&test.fragment_code, 6, test.layers);
test.fragment_code.push_back(EncodeVopc(0xc1, Vgpr(4), 6)); // Dynamic layer < count.
test.fragment_code.push_back(EncodeSop1(0x04, 126, 106));
test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4))); // float(unsigned layer)
test.fragment_code.push_back(EncodeVop1(0x05, 1, Vgpr(5))); // float(signed low 3 bits)
AppendVMovLiteral(&test.fragment_code, 2, 0);
AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u);
test.fragment_code.push_back(EncodeExp0(0x00, 0xf));
test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3));
AppendEnd(&test.fragment_code);
test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_BFE_I32,
ShaderOpcode::V_CVT_F32_U32, ShaderOpcode::V_CVT_F32_I32,
ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64,
ShaderOpcode::S_WQM_B64, ShaderOpcode::V_CMP_LT_U32,
ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM};
for (u32 layer = 0; layer < test.layers; ++layer) {
const auto signed_bits = layer < 4 ? static_cast<int>(layer) : static_cast<int>(layer) - 8;
test.expected_pixel.insert(test.expected_pixel.end(),
{std::bit_cast<u32>(static_cast<float>(layer)),
std::bit_cast<u32>(static_cast<float>(signed_bits)), 0, 0x3f800000u});
}
return test;
}
GraphicsCase GraphicsFlatInterpolatorExport() {
using O = ShaderOpcode;
@@ -22000,6 +22110,7 @@ std::vector<TestCase> MakeCases() {
AddCase(ScalarMaskHighWriteInvalidatesProvenance);
AddCase(ScalarSelectB64PreservesMaskProvenance);
AddCase(ScalarWqmB64SelectsSccDomain);
AddCase(ScalarWqmB64PreservesPartialMasks);
AddCase(ScalarMaskProvenanceOverlapAndMixedBinary);
AddCase(ScalarLiteral);
AddCase(VectorMoves);
@@ -22257,6 +22368,8 @@ std::vector<TestCase> MakeCases() {
std::vector<GraphicsCase> MakeGraphicsCases() {
return {
GraphicsInterpolationExport(),
GraphicsAncillaryLayer(false),
GraphicsAncillaryLayer(true),
GraphicsFlatInterpolatorExport(),
GraphicsDsAddtidScratchExport(),
GraphicsDirectSgprPushConstantExport(),
@@ -26535,6 +26648,7 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ScalarNotB64UpdatesScc());
RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance());
RunCase(&vulkan, ScalarWqmB64SelectsSccDomain());
RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks());
RunCase(&vulkan, ScalarMaskProvenanceOverlapAndMixedBinary());
RunCase(&vulkan, ScratchIsPrivatePerInvocation());
RunCase(&vulkan, Vop1MoveRelDestination());
+166 -4
View File
@@ -4778,7 +4778,6 @@ void TestImageAddressOperands() {
}
}
void TestNewShaderRecompilerImageSampleVariants() {
const uint32_t shader[] = {
EncodeMimg0(0x24, 0xf),
@@ -5932,6 +5931,166 @@ void TestPsInputCountRegisterDecode() {
"SPI_PS_IN_CONTROL NUM_INTERP decoding failed");
}
void TestPixelAncillaryLayerInput() {
using namespace ShaderRecompiler::IR;
std::vector<uint32_t> shader = {
EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
EncodeVop3Word0(0x149, 7), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 3),
EncodeExp0(0x00, 0x3), EncodeExp1(6, 7, 0, 0), EncodeSopp(0x01),
};
HW::PixelShaderInfo regs{};
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
ShaderMappedData mapped{};
mapped.code_size_bytes = shader.size() * sizeof(uint32_t);
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
sh.ps_input_ena = sh.ps_input_addr = 0x3320; // Linear I/J, X/Y, front-face, ancillary.
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
ShaderPixelInputInfo pixel{};
(void)PrepareProgram(regs, sh, mappings, pixel);
Check(pixel.ps_system_input_base == 2 && pixel.ps_front_face && pixel.ps_ancillary,
"pixel ancillary register flags were not retained");
const auto ancillary_key = MakeStageStaticKey(pixel);
pixel.ps_ancillary = false;
Check(ancillary_key != MakeStageStaticKey(pixel),
"pixel ancillary input is missing from the shader cache key");
pixel.ps_ancillary = true;
auto options = MakeCompileOptions(ShaderType::Pixel);
options.input_info.pixel = &pixel;
auto result = RecompileForTest(shader, options);
Check(ProgramHasInput(result.program, StageInputKind::Layer),
"ancillary after X/Y and front-face did not provide the layer input");
Check(!ProgramHasInput(result.program, StageInputKind::PackedAncillary),
"symbolic ancillary input survived its layer extraction");
Check(SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 9u, 14u) &&
result.spirv[1] == 0x00010500u &&
SpirvContainsCapability(result.spirv, 69u) &&
!SpirvContainsCapability(result.spirv, 5254u),
"fragment layer input lacks its builtin, flat decoration or capability");
const auto source = DisassembleSpirvBinary(result.spirv);
Check(SpirvSourceHasInstructionUsing(source, "OpLoad", "%int %gl_Layer") &&
SpirvInstructionOpcodeCount(result.spirv, 202u) == 1u &&
SpirvInstructionOpcodeCount(result.spirv, 203u) == 1u,
"layer extraction lost its scalar integer load or signedness");
Check(!SpirvHasDecorationValue(result.spirv, 11u, 18u),
"layer extraction unexpectedly enabled SampleId");
CheckSpirvBinaryValidates(result.spirv);
for (const uint32_t destination : {126u, 106u, 0u}) {
std::vector<uint32_t> wqm = {
EncodeSop1(0x04, 8, 126), // Save the incoming EXEC predicate.
EncodeSop1(0x0a, destination, 126),
EncodeSop1(0x04, 126, destination),
EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
EncodeVop1(0x01, 5, 6 + 256), // Overwrite the packed ancillary register.
EncodeSop1(0x04, 126, 8),
EncodeExp0(0x00, 0x1), EncodeExp1(5, 0, 0, 0), EncodeSopp(0x01),
};
auto wqm_result = RecompileForTest(wqm, options);
Check(ProgramHasInput(wqm_result.program, StageInputKind::Layer) &&
!ProgramHasInput(wqm_result.program, StageInputKind::PackedAncillary),
"WQM retained an unreachable old ancillary register value");
CheckSpirvBinaryValidates(wqm_result.spirv);
}
const uint32_t unused[] = {EncodeSopp(0x01)};
auto unused_result = RecompileForTest(unused, options);
Check(!ProgramHasInput(unused_result.program, StageInputKind::Layer) &&
unused_result.spirv[1] == 0x00010300u &&
!SpirvContainsCapability(unused_result.spirv, 69u),
"unused ancillary input changed the module requirements");
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
shader[1] = EncodeVop3Word1(5 + 256, 128 + 8, 128 + 4);
ExpectFatal([&] { (void)RecompileForTest(shader, options); },
"unsupported live ancillary field was silently replaced");
#endif
}
void TestMaskedValueDemand() {
using namespace ShaderRecompiler::IR;
enum class Use { Guarded, Conjunction, TrueArm, Unguarded, OtherGuard, Predicate,
ReadLane, ExplicitLod, ExplicitGradient, LevelZero, ImplicitLod,
Mixed, Phi, Loop };
for (const auto use : {Use::Guarded, Use::Conjunction, Use::TrueArm, Use::Unguarded,
Use::OtherGuard, Use::Predicate, Use::ReadLane,
Use::ExplicitLod, Use::ExplicitGradient, Use::LevelZero,
Use::ImplicitLod, Use::Mixed, Use::Phi, Use::Loop}) {
Program program;
for (uint32_t i = 0; i < 4; ++i) {
program.block_storage.push_back(std::make_unique<Block>());
program.blocks.push_back(program.block_storage.back().get());
}
auto &entry = *program.blocks[0];
auto &join = *program.blocks[3];
entry.AddBranch(program.blocks[1]);
entry.AddBranch(program.blocks[2]);
program.blocks[1]->AddBranch(&join);
program.blocks[2]->AddBranch(&join);
if (use == Use::Loop) join.AddBranch(&entry);
const auto input = Value(&entry.AppendNewInst(ValueOpcode::GetUserData,
{Value(static_cast<ScalarReg>(0))}));
const auto predicate = Value(&entry.AppendNewInst(ValueOpcode::INotEqual32,
{input, Value(0u)}));
const auto other = Value(&entry.AppendNewInst(ValueOpcode::IEqual32,
{input, Value(2u)}));
auto &masked = entry.AppendNewInst(ValueOpcode::SelectU32,
{predicate, Value(10u), Value(20u)});
Value value(&masked);
if (use == Use::Phi || use == Use::Loop) {
auto &phi = join.AppendNewInst(ValueOpcode::Phi, {}, static_cast<uint64_t>(Type::U32));
phi.AddPhiOperand(program.blocks[1], value);
phi.AddPhiOperand(program.blocks[2], Value(0u));
value = Value(&phi);
}
value = Value(&join.AppendNewInst(ValueOpcode::BitCastF32U32, {value}));
value = Value(&join.AppendNewInst(ValueOpcode::FPMul32, {value, Value::F32(2.f)}));
value = Value(&join.AppendNewInst(ValueOpcode::BitCastU32F32, {value}));
if (use == Use::ReadLane) {
value = Value(&join.AppendNewInst(ValueOpcode::ReadLane, {value, Value(0u)}));
}
if (use == Use::ExplicitLod || use == Use::ExplicitGradient ||
use == Use::LevelZero || use == Use::ImplicitLod) {
const auto zero = Value(0u);
auto &address = join.AppendNewInst(ValueOpcode::MakeImageAddress,
{value, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero});
auto &image = entry.AppendNewInst(ValueOpcode::GetImageResource,
{zero, zero, zero, zero, zero, zero, zero, zero});
auto &sampler = entry.AppendNewInst(ValueOpcode::GetSamplerResource,
{zero, zero, zero, zero});
program.memory_info.emplace_back().image_sample_flags =
use == Use::ExplicitLod ? ShaderRecompiler::Decoder::ImageSampleFlagLod :
use == Use::ExplicitGradient ? ShaderRecompiler::Decoder::ImageSampleFlagDerivative :
use == Use::LevelZero ? ShaderRecompiler::Decoder::ImageSampleFlagLevelZero : 0u;
value = Value(&join.AppendNewInst(ValueOpcode::ImageSampleRaw,
{Value(&image), Value(&sampler), Value(&address)}));
value = Value(&join.AppendNewInst(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}));
}
auto guard = predicate;
if (use == Use::Conjunction) {
guard = Value(&join.AppendNewInst(ValueOpcode::LogicalAnd, {predicate, other}));
} else if (use == Use::OtherGuard) {
guard = other;
} else if (use == Use::Unguarded) {
guard = Value(true);
} else if (use == Use::Predicate) {
guard = Value(&join.AppendNewInst(ValueOpcode::INotEqual32, {value, Value(0u)}));
} else if (use == Use::TrueArm) {
value = Value(&join.AppendNewInst(ValueOpcode::SelectU32, {predicate, value, Value(0u)}));
guard = Value(true);
}
value = Value(&join.AppendNewInst(ValueOpcode::CompositeConstructU32x4,
{value, Value(0u), Value(0u), Value(0u)}));
join.AppendNewInst(ValueOpcode::SetAttribute, {value, guard});
if (use == Use::Mixed) join.AppendNewInst(ValueOpcode::SetAttribute, {value, Value(true)});
EliminateMaskedValues(program);
const bool removed = Value(&masked).Resolve() == Value(10u);
const bool safe = use == Use::Guarded || use == Use::Conjunction || use == Use::TrueArm ||
use == Use::ExplicitLod || use == Use::ExplicitGradient ||
use == Use::LevelZero || use == Use::Phi;
Check(removed == safe, "masked value demand crossed an unguarded or nonlocal use");
}
}
void TestGraphicsCreateInterpolantMapping() {
ShaderRegister regs[32] = {};
@@ -9170,12 +9329,13 @@ void TestNewShaderRecompilerAuxPositionExports() {
SpirvHasDecorationValue(all.spirv, 11u, 9u) &&
SpirvContainsCapability(all.spirv, 32u) &&
SpirvContainsCapability(all.spirv, 33u) &&
SpirvContainsCapability(all.spirv, 5254u),
SpirvContainsCapability(all.spirv, 69u),
"auxiliary vertex BuiltIns or capabilities are missing");
const auto all_source = DisassembleSpirvBinary(all.spirv);
Check(Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") &&
Check(all.spirv[1] == 0x00010500u &&
!Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") &&
SpirvBuiltInStoreUsesAndConstant(all.spirv, 9u, 0x7ffu),
"layer export extension or GFX10 layer mask is missing");
"layer export module version or GFX10 layer mask is incorrect");
const auto positions = std::count_if(
all.program.info.outputs.begin(), all.program.info.outputs.end(),
[](const auto &output) {
@@ -12004,6 +12164,8 @@ int main() {
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
TestNewShaderRecompilerRejectsDppOn64BitCompares();
TestPsInputCountRegisterDecode();
TestPixelAncillaryLayerInput();
TestMaskedValueDemand();
TestNewShaderRecompilerUnbasedFlatUsesBda();
TestNewShaderRecompilerFlatUserPointerUsesDma();
TestNewShaderRecompilerFlatAddressDomainsUseDma();