Implement RDNA2 64-bit image atomic maximum

Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
BlackCAT
2026-10-03 06:08:05 +04:00
committed by nmzik
parent e6cb880adb
commit ef4c5baf3a
19 changed files with 244 additions and 57 deletions
+105 -8
View File
@@ -13541,14 +13541,16 @@ public:
Image CreateImage2D(const char *shader_name, u32 width, u32 height,
vk::Format format, vk::ImageUsageFlags usage,
const std::vector<u32> &initial, u32 dwords_per_pixel,
vk::ImageLayout final_layout) {
vk::ImageLayout final_layout,
vk::Format view_format = vk::Format::eUndefined) {
std::vector<std::vector<u32>> mips;
if (!initial.empty()) {
mips.push_back(initial);
}
return CreateImageMips(shader_name, width, height, format, usage, mips,
dwords_per_pixel, final_layout, vk::ImageType::e2D,
vk::ImageViewType::e2D, 1);
vk::ImageViewType::e2D, 1, 0, 0,
vk::SampleCountFlagBits::e1, view_format);
}
static u32 MipExtent(u32 value, u32 level) {
@@ -13572,7 +13574,8 @@ public:
vk::ImageType image_type, vk::ImageViewType view_type,
u32 layers, u32 view_base_layer = 0,
u32 view_layers = 0,
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1) {
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1,
vk::Format view_format = vk::Format::eUndefined) {
Image ret;
ret.format = format;
ret.width = width;
@@ -13596,6 +13599,9 @@ public:
vk::ImageUsageFlagBits::eTransferSrc;
image_info.sharingMode = vk::SharingMode::eExclusive;
image_info.initialLayout = vk::ImageLayout::eUndefined;
if (view_format != vk::Format::eUndefined && view_format != format) {
image_info.flags |= vk::ImageCreateFlagBits::eMutableFormat;
}
RequireVk(shader_name, "dispatch",
m_device.createImage(&image_info, nullptr, &ret.image),
"vkCreateImage");
@@ -13626,7 +13632,7 @@ public:
view_info.sType = vk::StructureType::eImageViewCreateInfo;
view_info.image = ret.image;
view_info.viewType = view_type;
view_info.format = format;
view_info.format = view_format == vk::Format::eUndefined ? format : view_format;
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = ret.mip_levels;
@@ -16421,6 +16427,7 @@ private:
m_runtime_context.physical_device_memory_properties = m_memory_properties;
m_runtime_context.queue_family = m_queue_family;
m_runtime_context.queue = m_queue;
m_runtime_context.shader_image_int64_atomics_enabled = true;
m_runtime_context.attachment_feedback_loop_enabled = m_rasterization_supported;
m_runtime_context.provoking_vertex_last_enabled = m_rasterization_supported;
const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{
@@ -16502,6 +16509,8 @@ private:
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout{};
features12.pNext = &workgroup_layout;
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic64{};
workgroup_layout.pNext = &image_atomic64;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &features12;
@@ -16515,6 +16524,7 @@ private:
features12.shaderOutputViewportIndex != true ||
features12.shaderBufferInt64Atomics != true ||
features12.shaderSharedInt64Atomics != true ||
image_atomic64.shaderImageInt64Atomics != true ||
workgroup_layout.workgroupMemoryExplicitLayout != true ||
features12.bufferDeviceAddress != true) {
continue;
@@ -16529,7 +16539,7 @@ private:
}
}
Require("VulkanHarness", "dispatch", m_physical_device != nullptr,
"no Vulkan graphics+compute device with fragment barycentrics and 64-bit LDS atomics");
"no Vulkan graphics+compute device with fragment barycentrics and 64-bit LDS/image atomics");
m_physical_device.getMemoryProperties(&m_memory_properties);
vk::PhysicalDeviceFeatures available_features{};
@@ -16619,6 +16629,9 @@ private:
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout{};
workgroup_layout.workgroupMemoryExplicitLayout = true;
device_features12.pNext = &workgroup_layout;
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic64{};
image_atomic64.shaderImageInt64Atomics = true;
workgroup_layout.pNext = &image_atomic64;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &device_features12;
@@ -16674,6 +16687,7 @@ private:
VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME,
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_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);
@@ -17124,15 +17138,20 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
test.sampled_image_view_layers);
}
if (needs_storage_image) {
const bool atomic64 = std::ranges::any_of(
compiled.program.info.images,
[](const auto &image) { return image.atomic64; });
storage_image = vulkan->CreateImage2D(
test.name, test.image_width, test.image_height,
test.storage_image_format, vk::ImageUsageFlagBits::eStorage,
test.storage_image_rgba, test.storage_image_dwords_per_pixel,
vk::ImageLayout::eGeneral);
storage_image_uint = vulkan->CreateImage2D(
test.name, test.image_width, test.image_height, vk::Format::eR32Uint,
vk::ImageUsageFlagBits::eStorage, test.storage_image_r32ui, 1,
vk::ImageLayout::eGeneral);
test.name, test.image_width, test.image_height,
atomic64 ? vk::Format::eR32G32Uint : vk::Format::eR32Uint,
vk::ImageUsageFlagBits::eStorage, test.storage_image_r32ui, atomic64 ? 2 : 1,
vk::ImageLayout::eGeneral,
atomic64 ? vk::Format::eR64Uint : vk::Format::eR32Uint);
}
if (needs_sampler) {
sampler = vulkan->CreateNearestSampler(test.name, sampled_image.mip_levels);
@@ -30701,6 +30720,76 @@ TestCase ImageAtomicVariants() {
return test;
}
template <bool wide> TestCase ImageAtomicUMaxGlcAndExec() {
// Input and old texel: high DWORD ordering, unsigned sign boundary, and
// equal-high-DWORD ordering must all survive the RG32_UINT -> R64_UINT view.
constexpr uint64_t values[][2] = {
{0x0000000100000001ull, 0x00000000ffffffffull},
{0x00000000ffffffffull, 0x0000000100000001ull},
{0x8000000000000000ull, 0x7fffffffffffffffull},
{0x12345678ffffffffull, 0x1234567800000001ull},
{0xffffffffffffffffull, 0x8000000000000000ull},
};
constexpr u32 texel_dwords = wide ? 2 : 1;
TestCase test;
test.name = wide ? "ImageAtomicUMax64CapturedGlcAndExec"
: "ImageAtomicUMax32GlcAndExec";
test.user_data = MakeStorageTextureData(wide ? Prospero::BufferFormat::k32_32UInt
: Prospero::BufferFormat::k32UInt);
test.has_user_data = true;
test.storage_image_r32ui.assign(16 * texel_dwords, 0);
test.expected_storage_image_r32ui = test.storage_image_r32ui;
auto &code = test.code;
code.push_back(EncodeSop1(0x04, 12, 126)); // Preserve EXEC.
for (u32 i = 0; i < std::size(values); ++i) {
const auto input = values[i][0];
const auto old = values[i][1];
const bool glc = i != 0 && i != 4;
AppendVMovU32(&code, 0, i & 3u);
AppendVMovU32(&code, 1, i >> 2u);
AppendVMovLiteral(&code, 2, static_cast<u32>(input));
AppendVMovLiteral(&code, 3, static_cast<u32>(input >> 32u));
// Captured PS5 instruction: f05c0308 00000200, DMASK=3, VDATA=v[2:3].
code.insert(code.end(), {0xf05c0108u | (wide ? 0x200u : 0u) |
(glc ? 0x2000u : 0u),
0x00000200u});
AppendStoreVgpr(&code, 2, i * 2);
AppendStoreVgpr(&code, 3, i * 2 + 1);
test.expected.push_back(static_cast<u32>(glc ? old : input));
test.expected.push_back(static_cast<u32>((wide && glc ? old : input) >> 32u));
const uint64_t result = wide ? std::max(input, old)
: std::max(static_cast<u32>(input),
static_cast<u32>(old));
for (u32 word = 0; word < texel_dwords; ++word) {
test.storage_image_r32ui[i * texel_dwords + word] =
static_cast<u32>(old >> (word * 32u));
test.expected_storage_image_r32ui[i * texel_dwords + word] =
static_cast<u32>(result >> (word * 32u));
}
}
AppendVMovU32(&code, 0, 1);
AppendVMovU32(&code, 1, 1); // Untouched zero texel, so an EXEC leak is observable.
AppendVMovLiteral(&code, 2, 0xabcdef01u);
AppendVMovLiteral(&code, 3, 0x87654321u);
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
code.insert(code.end(), {0xf05c2108u | (wide ? 0x200u : 0u), 0x00000200u});
code.push_back(EncodeSop1(0x04, 126, 12));
AppendStoreVgpr(&code, 2, std::size(values) * 2);
AppendStoreVgpr(&code, 3, std::size(values) * 2 + 1);
test.expected.insert(test.expected.end(), {0xabcdef01u, 0x87654321u});
AppendEnd(&code);
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64,
ShaderOpcode::IMAGE_ATOMIC_UMAX, ShaderOpcode::BUFFER_STORE_DWORD,
ShaderOpcode::S_ENDPGM};
test.required_spirv = {"OpAtomicUMax", "OpImageTexelPointer",
wide ? "R64ui" : "R32ui"};
if constexpr (wide) {
test.required_spirv.push_back("Int64ImageEXT");
test.required_spirv.push_back("SPV_EXT_shader_image_int64");
}
return test;
}
template <bool max_value> TestCase ImageAtomicSignedMinMax() {
using O = ShaderOpcode;
// Input, old value, signed minimum, signed maximum.
@@ -31734,6 +31823,8 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageAtomicSwapReturnsPreviousTexel);
AddCase(ImageStoreAndAtomicUseSeparateBindings);
AddCase(ImageAtomicVariants);
AddCase(ImageAtomicUMaxGlcAndExec<false>);
AddCase(ImageAtomicUMaxGlcAndExec<true>);
AddCase(ImageAtomicFloatGlcAndExec<true>);
AddCase(ImageAtomicFloatGlcAndExec<false>);
AddCase(ImageAtomicFloatSpecialValues<true>);
@@ -36769,6 +36860,12 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ImageAtomicVariants());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-atomic64-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ImageAtomicUMaxGlcAndExec<false>());
RunCase(&vulkan, ImageAtomicUMaxGlcAndExec<true>());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--ds-atomics-only") == 0) {
VulkanHarness vulkan;
for (const auto &test : MakeCases()) {