shader: add native 64-bit LDS OR with shared typed storage views

This commit is contained in:
nmzik
2026-09-29 18:41:22 +04:00
parent 35c4dca2ce
commit 6799ecbc5b
14 changed files with 221 additions and 16 deletions
+114 -7
View File
@@ -16198,6 +16198,8 @@ private:
(vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) {
vk::PhysicalDeviceVulkan12Features features12{};
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout{};
features12.pNext = &workgroup_layout;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &features12;
@@ -16207,6 +16209,11 @@ private:
physical.getFeatures2(&features);
if (barycentric.fragmentShaderBarycentric != true ||
features.features.shaderInt64 != true ||
features12.samplerMirrorClampToEdge != true ||
features12.shaderOutputViewportIndex != true ||
features12.shaderBufferInt64Atomics != true ||
features12.shaderSharedInt64Atomics != true ||
workgroup_layout.workgroupMemoryExplicitLayout != true ||
features12.bufferDeviceAddress != true) {
continue;
}
@@ -16220,7 +16227,7 @@ private:
}
}
Require("VulkanHarness", "dispatch", m_physical_device != nullptr,
"no Vulkan graphics+compute device with fragment barycentrics");
"no Vulkan graphics+compute device with fragment barycentrics and 64-bit LDS atomics");
m_physical_device.getMemoryProperties(&m_memory_properties);
vk::PhysicalDeviceFeatures available_features{};
@@ -16305,12 +16312,11 @@ private:
device_info.sType = vk::StructureType::eDeviceCreateInfo;
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
vk::PhysicalDeviceVulkan12Features device_features12{};
device_features12.sType =
vk::StructureType::ePhysicalDeviceVulkan12Features;
device_features12.timelineSemaphore = true;
device_features12.bufferDeviceAddress = true;
device_features12.shaderOutputLayer = true;
auto device_features12 = WindowContext::RequiredVulkan12Features();
device_features12.shaderSharedInt64Atomics = true;
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout{};
workgroup_layout.workgroupMemoryExplicitLayout = true;
device_features12.pNext = &workgroup_layout;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &device_features12;
@@ -16365,6 +16371,7 @@ private:
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME,
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME};
if (m_rasterization_supported) {
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
@@ -26858,6 +26865,95 @@ TestCase DsAtomicReturnVariants() {
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase DsOrB64Bounds() {
using O = ShaderOpcode;
TestCase test;
test.name = "DsOrB64Bounds";
auto &code = test.code;
AppendVMovU32(&code, 0, 0x1234);
AppendVMovU32(&code, 7, 0);
const u32 initial[] = {0x100, 0x200, 0x400, 0x800, 0x13579bdf};
for (u32 i = 0; i < std::size(initial); ++i) {
AppendVMovLiteral(&code, 3, initial[i]);
code.push_back(EncodeDs0(0x0d, i * 4u));
code.push_back(EncodeDs1(0, 3, 7));
}
AppendVMovU32(&code, 3, 1);
AppendVMovU32(&code, 4, 2);
AppendVMovU32(&code, 7, 3); // The 64-bit address ignores the low three bits.
code.insert(code.end(), {0xd9280000u, 0x00000307u});
AppendVMovLiteral(&code, 7, 0x10007);
code.push_back(EncodeDs0(0x4a, 2)); // Offset before 16-bit wrap/alignment -> 8.
code.push_back(EncodeDs1(0, 3, 7));
AppendVMovLiteral(&code, 3, 0xffffffffu);
AppendVMovLiteral(&code, 4, 0xffffffffu);
AppendVMovU32(&code, 7, 16); // Only one DWORD remains: ignore the entire OR.
code.insert(code.end(), {0xd9280000u, 0x00000307u});
AppendVMovU32(&code, 7, 0);
code.push_back(EncodeSMovB32(126, InlineU32(0)));
code.insert(code.end(), {0xd9280000u, 0x00000307u});
code.push_back(EncodeSMovB32(126, InlineU32(1)));
for (u32 i = 0; i < std::size(initial); ++i) {
code.push_back(EncodeDs0(0x36, i * 4u));
code.push_back(EncodeDs1(8, 0, 7));
AppendStoreVgpr(&code, 8, i);
}
AppendStoreVgpr(&code, 0, 5); // No-return DS must not overwrite the encoded VDst.
AppendEnd(&code);
test.expected = {0x101, 0x202, 0x401, 0x802, 0x13579bdf, 0x1234};
test.compute_info.lds_size_dwords = 5;
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::DS_WRITE_B32, O::DS_OR_B64,
O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpAtomicOr %ulong", "WorkgroupMemoryExplicitLayoutKHR", "Aliased"};
return test;
}
TestCase DsOrB64Contention(u32 wave_size) {
using O = ShaderOpcode;
TestCase test;
test.name = wave_size == 32 ? "DsOrB64ContentionWave32" : "DsOrB64ContentionWave64";
auto &code = test.code;
AppendVMovU32(&code, 7, 0);
code.push_back(EncodeSop1(0x04, 10, 126));
code.push_back(EncodeVopc(0xc2, InlineU32(0), 0));
code.push_back(EncodeSop1(0x04, 126, 106));
for (u32 offset : {0, 4}) {
code.push_back(EncodeDs0(0x0d, offset));
code.push_back(EncodeDs1(0, 7, 7));
}
code.push_back(EncodeSop1(0x04, 126, 10));
code.push_back(EncodeSopp(0x0a, 0));
AppendVMovU32(&code, 5, 1);
AppendVMovU32(&code, 6, 2);
code.push_back(EncodeVop2(0x1a, 3, Vgpr(0), 5));
code.push_back(EncodeVop2(0x1a, 4, Vgpr(0), 6));
AppendSMovLiteral(&code, 126, 0x55555555u);
AppendSMovLiteral(&code, 127, 0xaaaaaaaau);
code.insert(code.end(), {0xd9280000u, 0x00000307u});
code.push_back(EncodeSop1(0x04, 126, 10));
code.push_back(EncodeSopp(0x0a, 0));
for (u32 half = 0; half < 2; ++half) {
code.push_back(EncodeDs0(0x36, half * 4));
code.push_back(EncodeDs1(8, 0, 7));
AppendStoreVgprAtLaneDwordOffset(&code, 8, 0, half * 128);
}
AppendEnd(&code);
test.expected.assign(128, wave_size == 32 ? 0x55555555u : 0xffffffffu);
test.expected.resize(256, wave_size == 32 ? 0xaaaaaaaau : 0xfffffffeu);
test.compute_info.threads_num[0] = 128;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.wave_size = wave_size;
test.compute_info.lds_size_dwords = 2;
test.has_compute_info = true;
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64, O::S_BARRIER,
O::V_CMP_EQ_U32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::DS_WRITE_B32, O::DS_OR_B64, O::DS_READ_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpAtomicOr %ulong", "WorkgroupMemoryExplicitLayoutKHR", "Aliased"};
return test;
}
TestCase DsBoundedAtomicBoundaries(bool decrement, bool gds) {
using O = ShaderOpcode;
constexpr u32 guard = 0x13579bdfu;
@@ -30392,6 +30488,8 @@ std::vector<TestCase> MakeCases() {
AddCase(DsWideGdsPartialBounds);
AddCase(DsAtomicNoReturnVariants);
AddCase(DsAtomicReturnVariants);
AddCase(DsOrB64Bounds);
for (u32 wave_size : {32, 64}) cases.push_back(DsOrB64Contention(wave_size));
for (bool decrement : {false, true}) {
for (bool gds : {false, true}) {
cases.push_back(DsBoundedAtomicBoundaries(decrement, gds));
@@ -32933,6 +33031,15 @@ void CheckShaderRecompilerFatalContracts() {
(void)CompileCase(test);
});
ExpectFatal("GdsOrB64Rejection", [] {
TestCase test;
test.name = "GdsOrB64Rejection";
test.code = {EncodeDs0(0x4a, 0, true), EncodeDs1(0, 3, 7)};
AppendEnd(&test.code);
test.opcodes = {ShaderOpcode::DS_OR_B64, ShaderOpcode::S_ENDPGM};
(void)CompileCase(test);
});
{
ShaderRecompiler::IR::Program program;
program.resource_tracking_complete = true;