shader: run merged geometry stages as mesh shaders

Co-authored-by: Brandon Strong <brandostrong@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
nmzik
2026-09-05 08:32:23 +04:00
parent 246212789f
commit 3213934d42
39 changed files with 1403 additions and 858 deletions
+4 -2
View File
@@ -640,7 +640,8 @@ void TestImagesSamplersAndAliases() {
void TestSampleAdjustSamplerScratch() {
Fixture fixture(ShaderType::Pixel);
const auto active = fixture.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto active = fixture.Emit(
ValueOpcode::IEqual32, {fixture.Emit(ValueOpcode::LaneId), Value(0u)});
const auto lane =
fixture.Emit(ValueOpcode::SelectU32, {active, Value(1u), Value(0u)});
const auto low =
@@ -686,7 +687,8 @@ void TestSampleAdjustSamplerScratch() {
const auto CheckRejected = [](uint32_t flags, uint32_t shift,
const char *message) {
Fixture rejected(ShaderType::Pixel);
const auto condition = rejected.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto condition = rejected.Emit(
ValueOpcode::IEqual32, {rejected.Emit(ValueOpcode::LaneId), Value(0u)});
const auto bit = rejected.Emit(ValueOpcode::SelectU32,
{condition, Value(1u), Value(0u)});
const auto dynamic =
+2 -1
View File
@@ -321,7 +321,8 @@ void TestRuntime64BitDescriptorOps() {
void TestUniformFirstLaneSamplerLod() {
Fixture fixture;
const auto active = fixture.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto active = fixture.Emit(
ValueOpcode::IEqual32, {fixture.Emit(ValueOpcode::LaneId), Value(0u)});
const auto stale = fixture.Emit(
ValueOpcode::GetVectorRegister, {Value(static_cast<VectorReg>(0))});
const auto write = [&](Value value, Value previous) {
+249 -29
View File
@@ -1198,7 +1198,7 @@ void CheckSpirvText(const TestCase &test, const std::vector<u32> &spirv) {
}
}
CompiledShader CompileCase(const TestCase &test) {
CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
auto user_data =
MakeNativeUserData(test.has_user_data ? &test.user_data : nullptr);
const auto uses_image =
@@ -1218,7 +1218,9 @@ CompiledShader CompileCase(const TestCase &test) {
ShaderRecompiler::CompileOptions options;
options.stage = ShaderType::Compute;
options.dump_ir = true;
options.input_info.compute = &test.compute_info;
auto compute_info = test.compute_info;
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) {
@@ -1553,6 +1555,13 @@ public:
};
[[nodiscard]] vk::Device Device() const { return m_device; }
[[nodiscard]] u32 SubgroupSize() const {
vk::PhysicalDeviceSubgroupProperties subgroup{};
vk::PhysicalDeviceProperties2 properties{};
properties.pNext = &subgroup;
m_physical_device.getProperties2(&properties);
return subgroup.subgroupSize;
}
[[nodiscard]] GraphicContext &RuntimeContext() {
EnsureRuntimeContext();
return m_runtime_context;
@@ -2278,7 +2287,11 @@ public:
return packet;
};
const auto finish_gpu = [&] {
gpu.SendCommandSync([&] { context.GetCommandScheduler().Finish(); });
gpu.SendCommandSync([&] {
auto &scheduler = context.GetCommandScheduler();
scheduler.Finish();
scheduler.WaitPriorityOperations(scheduler.CurrentTick() - 1);
});
};
const auto wait_for_interrupt =
[&](LibKernel::EventQueue::KernelEvent &event, int &count) {
@@ -12064,7 +12077,7 @@ void CompareGraphicsWords(const GraphicsCase &test,
}
void RunCase(VulkanHarness *vulkan, const TestCase &test) {
auto compiled = CompileCase(test);
auto compiled = CompileCase(test, vulkan != nullptr ? vulkan->SubgroupSize() : 64u);
if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) {
Require(test.name, "resource specialization",
!compiled.program.info.images.empty() &&
@@ -13207,7 +13220,7 @@ TestCase ScalarNotB64UpdatesScc() {
return {"ScalarNotB64UpdatesScc",
code,
{},
{0, 1, 0, 0, 1, 0, 0, 0xfffffffeu, 0xffffffffu},
{0, 1, 0, 0, 1, 0, 1, 0xfffffffeu, 0xffffffffu},
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_NOT_B64, O::S_CSELECT_B32,
O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
@@ -13341,8 +13354,8 @@ TestCase ScalarSaveExecOps() {
return {"ScalarSaveExecOps",
code,
{},
// EXEC and saved EXEC values are invocation-local Booleans.
{1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0},
{1, 0, 1, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu,
3, 0xffffffffu, 3, 2, 0xffffffffu, 1},
{O::S_MOV_B32, O::S_AND_SAVEEXEC_B64, O::S_ORN2_SAVEEXEC_B64,
O::S_ANDN1_SAVEEXEC_B64, O::S_AND_SAVEEXEC_B32,
O::S_ANDN1_SAVEEXEC_B32, O::S_MOV_B64, O::V_MOV_B32,
@@ -13369,7 +13382,7 @@ TestCase ScalarOrn2SaveexecUsesSourceOrNotExec() {
return {"ScalarOrn2SaveexecUsesSourceOrNotExec",
code,
{},
{1, 0, 1, 0, 1},
{0x0000000cu, 0x80000000u, 0xfffffff3u, 0x7fffffffu, 1},
{O::S_MOV_B32, O::S_ORN2_SAVEEXEC_B64, O::S_MOV_B64, O::V_MOV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
@@ -13688,7 +13701,7 @@ TestCase ScalarSelectB64PreservesMaskProvenance() {
return {"ScalarSelectB64PreservesMaskProvenance",
code,
{},
{1, 0},
{1, 1},
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CSELECT_B64, O::S_NOT_B64,
O::S_CSELECT_B32, O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
@@ -13720,7 +13733,7 @@ TestCase ScalarWqmB64SelectsSccDomain() {
return {"ScalarWqmB64SelectsSccDomain",
code,
{},
{1, 0},
{1, 1},
{O::S_MOV_B32, O::S_WQM_B64, O::S_CSELECT_B32, O::V_CMP_EQ_U32,
O::S_MOV_B64, O::S_NOT_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
@@ -13754,7 +13767,7 @@ TestCase ScalarMaskProvenanceOverlapAndMixedBinary() {
return {"ScalarMaskProvenanceOverlapAndMixedBinary",
code,
{},
{0, 1},
{1, 1},
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_NOT_B64, O::S_CSELECT_B32,
O::S_MOV_B32, O::S_NAND_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
@@ -14510,9 +14523,7 @@ TestCase VectorAddcWritesPerLaneCarryOut() {
test.name = "VectorAddcWritesPerLaneCarryOut";
test.code = code;
test.initial = {0x0000ffffu, 0u, 0x0000ffffu, 0u};
// VCC is represented as the current invocation's carry bit, not a shared
// four-lane ballot word.
test.expected = {1u, 0u, 1u, 0u};
test.expected = {5u, 5u, 5u, 5u};
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_CMP_T_U32,
O::BUFFER_LOAD_DWORD, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
@@ -14680,8 +14691,7 @@ TestCase VectorVop3BCarryOutWritesSgprMask() {
TestCase test;
test.name = "VectorVop3BCarryOutWritesSgprMask";
test.code = code;
// Scalar mask destinations keep the current invocation's bit.
test.expected = std::vector<u32>(12, 1u);
test.expected = std::vector<u32>(12, 15u);
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -14713,7 +14723,7 @@ TestCase VectorVop3BCarryOutUsesEncodedSdst() {
TestCase test;
test.name = "VectorVop3BCarryOutUsesEncodedSdst";
test.code = code;
test.expected = std::vector<u32>(12, 1u);
test.expected = std::vector<u32>(12, 15u);
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -14752,7 +14762,7 @@ TestCase VectorVop3BSubCoU32UsesRdna2Opcode310() {
TestCase test;
test.name = "VectorVop3BSubCoU32UsesRdna2Opcode310";
test.code = code;
test.expected = {0xffffffffu, 0, 0xfffffffeu, 0x80000001u, 1, 0, 0, 1};
test.expected = {0xffffffffu, 0, 0xfffffffeu, 0x80000001u, 9, 9, 9, 9};
test.opcodes = {O::V_MOV_B32, O::V_CMP_EQ_U32, O::V_CNDMASK_B32,
O::V_SUB_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -15667,10 +15677,11 @@ TestCase Vop1MoveRelDestination() {
AppendVMovLiteral(&code, 6, 0xbbbbbbbbu);
AppendVMovLiteral(&code, 7, 0xccccccccu);
code.push_back(EncodeSMovB32(124, InlineU32(2)));
code.push_back(EncodeSop1(0x04, 8, 126));
code.push_back(EncodeVopc(0xc2, InlineU32(0), 0));
code.push_back(EncodeSop1(0x04, 126, 106));
code.push_back(EncodeVop1(0x42, 5, Vgpr(1)));
code.push_back(EncodeSop1(0x04, 126, 193u));
code.push_back(EncodeSop1(0x04, 126, 8));
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
AppendBufferStoreDword(&code, 7, 4);
AppendEnd(&code);
@@ -17035,7 +17046,7 @@ TestCase SimpleLoop() {
O::S_BRANCH, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase BranchVccnzUsesInvocationMask() {
TestCase BranchVccnzUsesWaveMask() {
using O = ShaderOpcode;
std::vector<u32> code = {
@@ -17053,9 +17064,9 @@ TestCase BranchVccnzUsesInvocationMask() {
AppendEnd(&code);
TestCase test;
test.name = "BranchVccnzUsesInvocationMask";
test.name = "BranchVccnzUsesWaveMask";
test.code = code;
test.expected = {42, 11, 11, 11, 42, 11, 11, 11};
test.expected = std::vector<u32>(8, 42);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_EQ_U32, O::S_CBRANCH_VCCNZ, O::S_BRANCH,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
@@ -18747,7 +18758,7 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
return test;
}
TestCase BranchVccnzUsesCarryProducedInvocationMask() {
TestCase BranchVccnzUsesCarryProducedWaveMask() {
using O = ShaderOpcode;
std::vector<u32> code = {
@@ -18765,9 +18776,9 @@ TestCase BranchVccnzUsesCarryProducedInvocationMask() {
AppendEnd(&code);
TestCase test;
test.name = "BranchVccnzUsesCarryProducedInvocationMask";
test.name = "BranchVccnzUsesCarryProducedWaveMask";
test.code = code;
test.expected = {11, 42, 42, 42, 42, 42, 42, 42};
test.expected = std::vector<u32>(8, 42);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_F_U32, O::V_ADDC_U32, O::S_CBRANCH_VCCNZ,
O::S_BRANCH, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
@@ -18903,11 +18914,13 @@ TestCase ScratchIsPrivatePerInvocation() {
TestCase test;
test.name = "ScratchIsPrivatePerInvocation";
test.code = std::move(code);
test.expected = {0, 1, 2, 3};
for (u32 lane = 0; lane < 64; lane++) {
test.expected.push_back(lane);
}
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32,
O::FLAT_STORE_DWORD, O::FLAT_LOAD_DWORD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
@@ -19589,6 +19602,185 @@ TestCase DsBpermuteWave64UsesIndependentHalves() {
return test;
}
TestCase Wave64CrossHalfLaneAndLds() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 1, 100);
code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1));
AppendVop3(&code, 0x360, 8, Vgpr(1), InlineU32(47));
AppendStoreSgprAtLaneDwordOffset(&code, 8, 0, 0);
code.push_back(EncodeSop1(0x04, 10, 126));
code.push_back(EncodeVop2(0x1b, 2, InlineU32(63), 0));
code.push_back(EncodeVopc(0xc2, InlineU32(40), 2));
code.push_back(EncodeSop1(0x04, 126, 106));
code.push_back(EncodeVop1(0x02, 9, Vgpr(1)));
code.push_back(EncodeSop1(0x04, 126, 10));
AppendStoreSgprAtLaneDwordOffset(&code, 9, 0, 128);
AppendStoreSgprAtLaneDwordOffset(&code, 106, 0, 256);
AppendStoreSgprAtLaneDwordOffset(&code, 107, 0, 384);
code.push_back(EncodeVop2(0x1a, 2, InlineU32(2), 0));
code.push_back(EncodeDs0(0x0d));
code.push_back(EncodeDs1(0, 1, 2));
code.push_back(EncodeSopp(0x0a, 0));
code.push_back(EncodeVop2(0x1d, 3, InlineU32(64), 0));
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 3));
code.push_back(EncodeDs0(0x36));
code.push_back(EncodeDs1(4, 0, 3));
AppendStoreVgprAtLaneDwordOffset(&code, 4, 0, 512);
AppendEnd(&code);
TestCase test;
test.name = "Wave64CrossHalfLaneAndLds";
test.code = std::move(code);
test.initial.resize(640);
test.expected.insert(test.expected.end(), 64, 147);
test.expected.insert(test.expected.end(), 64, 211);
test.expected.insert(test.expected.end(), 64, 140);
test.expected.insert(test.expected.end(), 64, 204);
test.expected.insert(test.expected.end(), 128, 0);
test.expected.insert(test.expected.end(), 128, 256);
for (u32 lane = 0; lane < 128; lane++) {
test.expected.push_back(100 + (lane ^ 64));
}
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_READLANE_B32,
O::V_READFIRSTLANE_B32, O::V_CMP_EQ_U32, O::S_MOV_B64,
O::V_LSHLREV_B32, O::V_AND_B32, O::V_XOR_B32, O::DS_WRITE_B32,
O::S_BARRIER, O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpGroupNonUniformBallot", "OpGroupNonUniformShuffle",
"OpControlBarrier"};
test.ir_counts = {{"Barrier", 1}};
test.compute_info.threads_num[0] = 128;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase Wave64RawMasksAndScalarBranch() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 1, 7);
AppendSMovLiteral(&code, 8, 33u << 16u);
code.push_back(EncodeSop2(0x29, 126, 193u, 8)); // captured ES BFE EXEC,-1,width
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 0);
code.push_back(EncodeSop2(0x1f, 126, InlineU32(1), InlineU32(32)));
const auto exec_branch = code.size();
code.push_back(0);
code.push_back(EncodeSopp(0x0a, 0));
code.push_back(EncodeSop1(0x24, 10, 193u)); // s_and_saveexec_b64 s[10:11],-1
const auto scc_branch = code.size();
code.push_back(0);
AppendVMovU32(&code, 1, 9);
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 64);
const auto restore = code.size();
code[exec_branch] = EncodeSopp(0x08, restore - exec_branch - 1);
code[scc_branch] = EncodeSopp(0x04, restore - scc_branch - 1);
code.push_back(EncodeSop1(0x04, 126, 193u));
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 12, 106));
code.push_back(EncodeSMovB32(13, InlineU32(1)));
AppendVop3(&code, 0x101, 2, InlineU32(0), InlineU32(11), 12);
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 128);
code.push_back(EncodeSMovB32(127, InlineU32(1)));
AppendVMovU32(&code, 3, 13);
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0, 192);
code.push_back(EncodeSop1(0x04, 126, InlineU32(1)));
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0, 256);
code.push_back(EncodeSMovB32(107, InlineU32(2)));
code.push_back(EncodeSMovB32(127, 107));
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 320);
AppendEnd(&code);
TestCase test;
test.name = "Wave64RawMasksAndScalarBranch";
test.code = std::move(code);
test.initial.assign(384, 0xdeadbeef);
test.expected = test.initial;
for (u32 lane = 0; lane < 64; lane++) {
if (lane < 33) {
test.expected[lane] = 7;
test.expected[192 + lane] = 13;
}
test.expected[128 + lane] = lane < 33 ? 11 : 0;
}
test.expected[64 + 32] = 9;
test.expected[256] = 13;
test.expected[320] = 7;
test.expected[320 + 33] = 7;
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_BFE_U64, O::S_LSHL_B64,
O::S_CBRANCH_EXECZ, O::S_AND_SAVEEXEC_B64, O::S_CBRANCH_SCC0,
O::S_BARRIER, O::S_MOV_B64, O::V_CMP_EQ_U32, O::V_CNDMASK_B32,
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase Wave64PartialMultidimensionalWorkgroup() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop2(0x25, 3, Vgpr(2), 2));
code.push_back(EncodeVop2(0x25, 3, Vgpr(2), 3));
code.push_back(EncodeVop2(0x25, 3, Vgpr(1), 3));
code.push_back(EncodeVop2(0x1a, 3, InlineU32(3), 3));
code.push_back(EncodeVop2(0x25, 3, Vgpr(0), 3));
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 3));
AppendVMovU32(&code, 5, 1);
AppendBufferStoreOpcode(&code, 0x32, 5, 4, false);
AppendEnd(&code);
TestCase test;
test.name = "Wave64PartialMultidimensionalWorkgroup";
test.code = std::move(code);
test.initial.resize(128);
test.expected.assign(96, 1);
test.expected.resize(128);
test.opcodes = {O::V_ADD_NC_U32, O::V_LSHLREV_B32, O::V_MOV_B32,
O::BUFFER_ATOMIC_ADD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 8;
test.compute_info.threads_num[1] = 3;
test.compute_info.threads_num[2] = 4;
test.compute_info.thread_ids_num = 3;
test.has_compute_info = true;
return test;
}
TestCase Wave64AppendConsumeHighHalf() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendSMovLiteral(&code, 124, 4);
code.push_back(EncodeSop2(0x1f, 126, 193u, InlineU32(32)));
code.push_back(EncodeDs0(0x3e, 0, true));
code.push_back(EncodeDs1(1, 0, 0));
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 0);
code.push_back(EncodeDs0(0x3d, 0, true));
code.push_back(EncodeDs1(2, 0, 0));
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 64);
AppendEnd(&code);
TestCase test;
test.name = "Wave64AppendConsumeHighHalf";
test.code = std::move(code);
test.initial.assign(128, 0xdeadbeef);
test.expected = test.initial;
for (u32 lane = 32; lane < 64; lane++) {
test.expected[lane] = 10;
test.expected[64 + lane] = 42;
}
test.gds_initial = {10};
test.expected_gds = {10};
test.opcodes = {O::S_MOV_B32, O::S_LSHL_B64, O::DS_APPEND, O::DS_CONSUME,
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase BufferAtomicVariants() {
using O = ShaderOpcode;
@@ -21780,8 +21972,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorCompareInvertedMaskSelect);
AddCase(BranchSelect);
AddCase(SimpleLoop);
AddCase(BranchVccnzUsesInvocationMask);
AddCase(BranchVccnzUsesCarryProducedInvocationMask);
AddCase(BranchVccnzUsesWaveMask);
AddCase(BranchVccnzUsesCarryProducedWaveMask);
AddCase(ScalarMemoryLoadVariants);
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
@@ -21874,6 +22066,10 @@ std::vector<TestCase> MakeCases() {
AddCase(DsSwizzleInvalidSourceLaneZero);
AddCase(DsBpermuteCapturedExecOffsetAndWrap);
AddCase(DsBpermuteWave64UsesIndependentHalves);
AddCase(Wave64CrossHalfLaneAndLds);
AddCase(Wave64RawMasksAndScalarBranch);
AddCase(Wave64PartialMultidimensionalWorkgroup);
AddCase(Wave64AppendConsumeHighHalf);
AddCase(BufferAtomicVariants);
AddCase(BufferAtomicCmpSwapExactRaw);
AddCase(BufferAtomicGlc0DoesNotReturnOldValue);
@@ -26093,6 +26289,30 @@ int main(int argc, char **argv) {
return 0;
}
#endif
if (argc == 2 && std::strcmp(argv[1], "--wave64-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, DsBpermuteWave64UsesIndependentHalves());
RunCase(&vulkan, Wave64CrossHalfLaneAndLds());
RunCase(&vulkan, Wave64RawMasksAndScalarBranch());
RunCase(&vulkan, Wave64PartialMultidimensionalWorkgroup());
RunCase(&vulkan, Wave64AppendConsumeHighHalf());
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
RunCase(&vulkan, VectorVop3BCarryOutWritesSgprMask());
RunCase(&vulkan, VectorVop3BCarryOutUsesEncodedSdst());
RunCase(&vulkan, DispatcherIrreducibleControlFlow());
RunCase(&vulkan, ScalarSaveExecOps());
RunCase(&vulkan, ScalarOrn2SaveexecUsesSourceOrNotExec());
RunCase(&vulkan, ScalarNotB64UpdatesScc());
RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance());
RunCase(&vulkan, ScalarWqmB64SelectsSccDomain());
RunCase(&vulkan, ScalarMaskProvenanceOverlapAndMixedBinary());
RunCase(&vulkan, ScratchIsPrivatePerInvocation());
RunCase(&vulkan, Vop1MoveRelDestination());
RunCase(&vulkan, BranchVccnzUsesWaveMask());
RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask());
RunCase(&vulkan, ImageSampleAndGather());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {
CheckClipControlDepthClipState();
return 0;
+66 -162
View File
@@ -21,7 +21,6 @@
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ir/passes/SharedMemoryBarrier.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include "graphics/shader/shader.h"
@@ -7882,9 +7881,9 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() {
"EXEC/SCC shared-arm SPIR-V has the wrong selection-merge count");
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
"EXEC/SCC shared-arm SPIR-V unexpectedly used dispatcher OpSwitch");
Check(!Common::ContainsStr(DisassembleSpirvBinary(result.spirv),
Check(Common::ContainsStr(DisassembleSpirvBinary(result.spirv),
"OpGroupNonUniformBallot"),
"per-invocation EXEC/SCC branch reconstructed a native subgroup mask");
"scalar mask SCC did not reduce the complete wave mask");
CheckSpirvBinaryValidates(result.spirv);
}
@@ -8727,6 +8726,43 @@ void TestNewShaderRecompilerSetpcBranch() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestFusedShaderHandoffPreservesRegisters() {
using namespace ShaderRecompiler;
const uint32_t front[] = {
EncodeSMovB32(12, 255), 0x1003u, // three vertices and one primitive
EncodeSop1(0x20, 0, 6), // merged-stage handoff through s[6:7]
0xffffffffu, // front shader metadata must not be decoded
};
const uint32_t back[] = {
EncodeSMovB32(124, 12), // s_mov_b32 m0, s12
EncodeSopp(0x10, 9), // s_sendmsg MSG_GS_ALLOC_REQ
EncodeSopp(0x01),
};
ShaderVertexInputInfo input{};
input.mesh.threads_num[0] = 192;
input.mesh.threads_num[1] = input.mesh.threads_num[2] = 1;
input.mesh.primitives_per_group = 62;
input.mesh.vertices_per_group = 64;
CompileOptions options{};
options.stage = ShaderType::Mesh;
options.input_info.vertex = &input;
options.back_code = back;
auto translated = TranslateProgram(front, options);
uint32_t allocations = 0;
for (const auto* block: translated.program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::MeshAllocate) {
continue;
}
const auto value = inst.Arg(0).Resolve();
Check(value.IsImmediate() && value.U32() == 0x1003u,
"fused back shader lost the front shader's scalar register value");
allocations++;
}
}
Check(allocations == 1u, "fused shader omitted the back shader allocation");
}
void TestNewShaderRecompilerSetpcJumpTable() {
const uint32_t shader[] = {
EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
@@ -9237,6 +9273,7 @@ void TestTypedEntryStateIsMinimal() {
uint32_t set_exec_lo = 0;
uint32_t set_exec_hi = 0;
uint32_t ballots = 0;
const IR::Inst* ballot = nullptr;
IR::Value exec;
IR::Value exec_lo;
IR::Value exec_hi;
@@ -9245,6 +9282,7 @@ void TestTypedEntryStateIsMinimal() {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::Ballot:
ballots++;
ballot = &inst;
break;
case IR::ValueOpcode::SetExec:
set_exec++;
@@ -9274,11 +9312,19 @@ void TestTypedEntryStateIsMinimal() {
}
}
}
const auto is_mask_word = [&](IR::Value value, uint32_t word) {
const auto* extract = value.ResolveInstruction();
return extract != nullptr &&
extract->GetOpcode() == IR::ValueOpcode::CompositeExtractU32x4 &&
extract->Arg(0).ResolveInstruction() == ballot &&
extract->Arg(1).IsImmediate() && extract->Arg(1).U32() == word;
};
Check(set_exec == 1u && set_exec_lo == 1u && set_exec_hi == 1u &&
ballots == 0u && exec.IsImmediate() && exec.U1() &&
exec_lo.IsImmediate() && exec_lo.U32() == 1u &&
exec_hi.IsImmediate() && exec_hi.U32() == 0u,
"typed entry is not the local {1,0} invocation mask");
ballots == 1u && exec.IsImmediate() && exec.U1() &&
ballot->Arg(0) == exec && is_mask_word(exec_lo, 0u) &&
(wave_size == 64u ? is_mask_word(exec_hi, 1u)
: exec_hi.IsImmediate() && exec_hi.U32() == 0u),
"typed entry did not materialize the active invocation mask once");
IR::RewriteToSsa(values.blocks);
IR::RemoveIdentities(values.blocks);
@@ -9609,21 +9655,9 @@ void TestValuePhiValidation() {
#endif
}
void TestWqmMaskSignatureAndU64ShiftConstantPropagation() {
void TestU64ShiftConstantPropagation() {
using namespace ShaderRecompiler::IR;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
Program malformed;
malformed.block_storage.push_back(std::make_unique<Block>());
malformed.blocks.push_back(malformed.block_storage.back().get());
malformed.block_info.emplace_back();
IREmitter malformed_ir(malformed.blocks.front());
auto malformed_value = malformed_ir.Emit(ValueOpcode::WqmMask, {Value(true)});
malformed_value.TryInstruction()->SetArg(0, Value(uint64_t{1}));
ExpectFatal([&] { ValidateProgram(malformed, false); },
"invalid WqmMask operand type did not terminate IR validation");
#endif
Program shifts;
shifts.block_storage.push_back(std::make_unique<Block>());
shifts.blocks.push_back(shifts.block_storage.back().get());
@@ -10021,8 +10055,8 @@ void TestNewShaderRecompilerVertexExportUsesInvocationExecMask() {
auto result = RecompileForTest(shader, options);
CheckSpirvBinaryValidates(result.spirv);
const auto source = DisassembleSpirvBinary(result.spirv);
Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"vertex EXEC guard depends on the native subgroup lane");
Check(Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"raw EXEC=1 did not select guest lane zero");
Check(Common::ContainsStr(source, "OpBranchConditional"),
"vertex export lost its per-invocation EXEC guard");
}
@@ -10049,8 +10083,8 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
Check(
!Common::ContainsStr(source, "OpGroupNonUniformBallot"),
"per-invocation VCC producer still materialized a shared subgroup mask");
Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"per-invocation BFM EXEC prefix still selected native subgroup lanes");
Check(Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"BFM EXEC prefix did not select the four requested guest lanes");
Check(!Common::ContainsStr(source, "%vcc_lo") &&
!Common::ContainsStr(source, "%vcc_hi"),
"per-invocation comparison retained VCC register mirrors");
@@ -10067,8 +10101,8 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
Check(Common::ContainsStr(wqm_source, "OpCapability GroupNonUniformBallot") &&
Common::ContainsStr(wqm_source, "OpGroupNonUniformBallot"),
"per-invocation scalar WQM omitted its subgroup ballot capability");
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 2u,
"wave64 WQM did not compact exactly two ballot words");
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 1u,
"wave64 WQM did not expand its scalar word pair together");
auto wave32_options = options;
wave32_options.wave_size = 32u;
@@ -11352,9 +11386,9 @@ void TestComputeLdsAllocationIdentity() {
"compute pipeline identity omitted the LDS allocation");
auto split_wave = lds_896;
split_wave.needs_lds_barriers = true;
split_wave.host_subgroup_size = 32;
Check(MakeStageStaticKey(lds_896) != MakeStageStaticKey(split_wave),
"compute shader identity omitted split-wave LDS synchronization");
"compute shader identity omitted the host subgroup size");
auto tg_size_disabled = lds_896;
auto tg_size_enabled = lds_896;
@@ -11418,135 +11452,6 @@ void TestComputeLdsAllocationIdentity() {
CheckSpirvBinaryValidates(append_result.spirv);
}
void TestWave64LdsSynchronization() {
const uint32_t shader[] = {
EncodeDs0(0x0d), // ds_write_b32 v0, v1
EncodeDs1(0, 1, 0),
EncodeSopp(0x01),
};
const auto compile = [&](bool needs_lds_barriers, uint32_t wave_size,
uint32_t threads) {
ShaderComputeInputInfo input_info{};
input_info.lds_size_dwords = 128;
input_info.needs_lds_barriers = needs_lds_barriers;
input_info.threads_num[0] = threads;
input_info.threads_num[1] = 1;
input_info.threads_num[2] = 1;
auto options = MakeCompileOptions(ShaderType::Compute);
options.wave_size = wave_size;
options.input_info.compute = &input_info;
auto result = RecompileForTest(shader, options);
CheckSpirvBinaryValidates(result.spirv);
return result;
};
const auto native_wave64 = compile(false, 64, 64);
Check(!SpirvContainsOpcode(native_wave64.spirv, 224),
"native wave64 shader gained a synthetic workgroup barrier");
const auto split_wave64 = compile(true, 64, 64);
Check(SpirvContainsOpcode(split_wave64.spirv, 224),
"split wave64 LDS shader omitted workgroup synchronization");
const auto split_wave32 = compile(true, 32, 64);
Check(!SpirvContainsOpcode(split_wave32.spirv, 224),
"wave32 shader gained wave64 synchronization");
const auto larger_workgroup = compile(true, 64, 128);
Check(!SpirvContainsOpcode(larger_workgroup.spirv, 224),
"multi-wave workgroup gained unsupported synthetic synchronization");
}
void TestSharedMemoryBarrierSafety() {
using namespace ShaderRecompiler::IR;
const auto make_program = [](size_t block_count) {
Program program;
program.memory_info.push_back({.kind = ResourceKind::Lds});
for (size_t index = 0; index < block_count; index++) {
program.block_storage.push_back(std::make_unique<Block>());
program.blocks.push_back(program.block_storage.back().get());
program.block_info.emplace_back();
program.block_info.back().id = static_cast<uint32_t>(index);
}
return program;
};
const auto append_write = [](Block& block) {
auto& inst = block.AppendNewInst(
ValueOpcode::WriteSharedU32,
{Value(0u), Value(1u), Value(true)});
inst.SetFlags(MemoryFlags{.index = 0});
};
const auto append_read = [](Block& block) {
auto& inst = block.AppendNewInst(ValueOpcode::LoadSharedU32,
{Value(0u), Value(true)});
inst.SetFlags(MemoryFlags{.index = 0});
};
const auto count_barriers = [](const Block& block) {
return std::ranges::count_if(block, [](const Inst& inst) {
return inst.GetOpcode() == ValueOpcode::Barrier;
});
};
const auto divergent_condition = [](Program& program, size_t block) {
IREmitter ir(program.blocks[block]);
const auto local_id = U32(ir.Emit(
ValueOpcode::GetBuiltin,
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)),
Value(0u)}));
return ir.INotEqual(local_id, U32(Value(0u)));
};
ShaderComputeInputInfo compute_info{};
compute_info.needs_lds_barriers = true;
compute_info.lds_size_dwords = 128;
compute_info.threads_num[0] = 64;
compute_info.threads_num[1] = 1;
compute_info.threads_num[2] = 1;
auto phases = make_program(1);
append_write(*phases.blocks[0]);
append_read(*phases.blocks[0]);
const auto phase_stats = InsertSharedMemoryBarriers(phases, 64u, compute_info);
std::vector<ValueOpcode> phase_opcodes;
for (const auto& inst : *phases.blocks[0]) {
phase_opcodes.push_back(inst.GetOpcode());
}
Check(phase_stats.inserted_barriers == 2 && phase_opcodes.size() == 4 &&
phase_opcodes[0] == ValueOpcode::WriteSharedU32 &&
phase_opcodes[1] == ValueOpcode::Barrier &&
phase_opcodes[2] == ValueOpcode::LoadSharedU32 &&
phase_opcodes[3] == ValueOpcode::Barrier,
"LDS write/read phases were not separated by a barrier");
auto selection = make_program(3);
selection.block_info[0].terminator.kind =
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
selection.block_info[0].terminator.merge_block = 2;
selection.block_info[0].condition = divergent_condition(selection, 0);
append_write(*selection.blocks[1]);
append_read(*selection.blocks[2]);
const auto selection_stats =
InsertSharedMemoryBarriers(selection, 64u, compute_info);
Check(selection_stats.inserted_barriers == 2 &&
count_barriers(*selection.blocks[1]) == 0 &&
selection.blocks[2]->begin()->GetOpcode() ==
ValueOpcode::Barrier,
"workgroup barrier was placed inside divergent selection flow");
auto loop = make_program(1);
loop.block_info[0].terminator.kind =
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
loop.block_info[0].condition = divergent_condition(loop, 0);
append_write(*loop.blocks[0]);
append_read(*loop.blocks[0]);
const auto loop_stats = InsertSharedMemoryBarriers(loop, 64u, compute_info);
Check(loop_stats.inserted_barriers == 0 &&
count_barriers(*loop.blocks[0]) == 0,
"workgroup barrier was inserted into divergent loop control");
}
void TestPixelProgramCacheBindingIdentity() {
const uint32_t shader_01[] = {0xbf810000u};
const uint32_t shader_10[] = {0xbf810000u};
@@ -11914,8 +11819,8 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
.conditional_branches = 1,
.ballots = 1},
ShaderType::Vertex);
Check(Common::ContainsStr(wqm_result.ir_dump, "WqmMask"),
"WQM size fixture no longer reaches per-invocation WqmMask IR");
Check(Common::ContainsStr(wqm_result.ir_dump, "WqmU64"),
"WQM size fixture no longer reaches scalar mask expansion");
const uint32_t dispatcher[] = {
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
@@ -12028,6 +11933,7 @@ int main() {
TestNewShaderRecompilerPixelImageSampleLodSelection();
TestNewShaderRecompilerBranchConditionForms();
TestNewShaderRecompilerSetpcBranch();
TestFusedShaderHandoffPreservesRegisters();
TestNewShaderRecompilerSetpcJumpTable();
TestNewShaderRecompilerPrunesUnreachableSetpcMetadata();
TestNewShaderRecompilerSetpcDwordJumpTable();
@@ -12036,7 +11942,7 @@ int main() {
TestFinalSsaRejectsRegisterStatePseudos();
#endif
TestValuePhiValidation();
TestWqmMaskSignatureAndU64ShiftConstantPropagation();
TestU64ShiftConstantPropagation();
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
TestNativeWideValueValidation();
#endif
@@ -12064,8 +11970,6 @@ int main() {
TestGraphicsCreateInterpolantMapping();
TestNewShaderRecompilerPixelPipelineEntry();
TestComputeLdsAllocationIdentity();
TestWave64LdsSynchronization();
TestSharedMemoryBarrierSafety();
TestPixelProgramCacheBindingIdentity();
TestGraphicsPushConstantPlacement();
TestNewShaderRecompilerUnsupportedMemoryDecode();