From c78a5a9fdfa7e5134cc2702a79dccdad9b74fe2e Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 3 Sep 2026 05:45:45 +0200 Subject: [PATCH] graphics: unify atomic feature and width handling --- .../presentation/window/vulkanWindow.cpp | 64 +++++++++++++------ .../presentation/window/windowInternal.h | 1 + .../recompiler/frontend/translate/Memory.cpp | 12 +--- tests/shaderCfgTests.cpp | 35 +++++++++- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index b6e30018b..88bde4280 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -63,6 +63,17 @@ struct VulkanExtensions { std::vector available_layers; }; +vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noexcept { + vk::PhysicalDeviceVulkan12Features features {}; + features.sType = vk::StructureType::ePhysicalDeviceVulkan12Features; + features.samplerMirrorClampToEdge = VK_TRUE; + features.timelineSemaphore = VK_TRUE; + features.shaderOutputLayer = VK_TRUE; + features.bufferDeviceAddress = VK_TRUE; + features.shaderBufferInt64Atomics = VK_TRUE; + return features; +} + vk::PhysicalDeviceVulkan13Features WindowContext::RequiredVulkan13Features() noexcept { vk::PhysicalDeviceVulkan13Features features {}; features.sType = vk::StructureType::ePhysicalDeviceVulkan13Features; @@ -217,6 +228,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa device_features2.pNext = &features13; device.getFeatures2(&device_features2); + const auto required_features12 = WindowContext::RequiredVulkan12Features(); const auto required_features13 = WindowContext::RequiredVulkan13Features(); const auto queue_family = VulkanFindQueueFamily(device, surface); @@ -249,14 +261,31 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa } #endif - if (features12.samplerMirrorClampToEdge != VK_TRUE) { + if (required_features12.samplerMirrorClampToEdge == VK_TRUE && + features12.samplerMirrorClampToEdge != VK_TRUE) { LOGF("samplerMirrorClampToEdge is not supported\n"); skip_device = true; } - if (features12.timelineSemaphore != VK_TRUE) { + if (required_features12.timelineSemaphore == VK_TRUE && + features12.timelineSemaphore != VK_TRUE) { LOGF("timelineSemaphore is not supported\n"); skip_device = true; } + if (required_features12.shaderOutputLayer == VK_TRUE && + features12.shaderOutputLayer != VK_TRUE) { + LOGF("shaderOutputLayer is not supported\n"); + skip_device = true; + } + if (required_features12.bufferDeviceAddress == VK_TRUE && + features12.bufferDeviceAddress != VK_TRUE) { + LOGF("bufferDeviceAddress is not supported\n"); + skip_device = true; + } + if (required_features12.shaderBufferInt64Atomics == VK_TRUE && + features12.shaderBufferInt64Atomics != VK_TRUE) { + LOGF("shaderBufferInt64Atomics is not supported\n"); + skip_device = true; + } if (features13.robustImageAccess != VK_TRUE) { LOGF("robustImageAccess is not supported\n"); skip_device = true; @@ -291,11 +320,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa LOGF("largePoints is not supported\n"); skip_device = true; } - if (features12.shaderOutputLayer != VK_TRUE) { - LOGF("shaderOutputLayer is not supported\n"); - skip_device = true; - } - if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) { LOGF("fragmentStoresAndAtomics is not supported\n"); skip_device = true; @@ -547,10 +571,8 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V #endif depth_clip_control.depthClipControl = VK_TRUE; - vk::PhysicalDeviceVulkan12Features features12 {}; - features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features; - features12.pNext = &depth_clip_control; - features12.samplerMirrorClampToEdge = VK_TRUE; + auto features12 = WindowContext::RequiredVulkan12Features(); + features12.pNext = &depth_clip_control; vk::PhysicalDeviceVulkan13Features supported_features13 {}; supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features; @@ -586,8 +608,12 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; supported_features2.pNext = &supported_features13; physical_device.getFeatures2(&supported_features2); + const auto required_features12 = WindowContext::RequiredVulkan12Features(); const auto required_features13 = WindowContext::RequiredVulkan13Features(); - EXIT_NOT_IMPLEMENTED(supported_features12.timelineSemaphore != VK_TRUE); + EXIT_NOT_IMPLEMENTED(required_features12.samplerMirrorClampToEdge == VK_TRUE && + supported_features12.samplerMirrorClampToEdge != VK_TRUE); + EXIT_NOT_IMPLEMENTED(required_features12.timelineSemaphore == VK_TRUE && + supported_features12.timelineSemaphore != VK_TRUE); EXIT_NOT_IMPLEMENTED(required_features13.dynamicRendering == VK_TRUE && supported_features13.dynamicRendering != VK_TRUE); EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE && @@ -599,17 +625,15 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE); - EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE); - EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE); - EXIT_NOT_IMPLEMENTED(supported_features12.shaderBufferInt64Atomics != VK_TRUE); + EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputLayer == VK_TRUE && + supported_features12.shaderOutputLayer != VK_TRUE); + EXIT_NOT_IMPLEMENTED(required_features12.bufferDeviceAddress == VK_TRUE && + supported_features12.bufferDeviceAddress != VK_TRUE); + EXIT_NOT_IMPLEMENTED(required_features12.shaderBufferInt64Atomics == VK_TRUE && + supported_features12.shaderBufferInt64Atomics != VK_TRUE); #if !defined(__APPLE__) EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE); #endif - features12.timelineSemaphore = VK_TRUE; - features12.shaderOutputLayer = VK_TRUE; - features12.bufferDeviceAddress = VK_TRUE; - features12.shaderBufferInt64Atomics = VK_TRUE; - vk::PhysicalDeviceFeatures device_features {}; device_features.fragmentStoresAndAtomics = VK_TRUE; device_features.samplerAnisotropy = VK_TRUE; diff --git a/src/graphics/presentation/window/windowInternal.h b/src/graphics/presentation/window/windowInternal.h index 0a2cc6748..66ceb9052 100644 --- a/src/graphics/presentation/window/windowInternal.h +++ b/src/graphics/presentation/window/windowInternal.h @@ -35,6 +35,7 @@ struct WindowContext { ~WindowContext(); KYTY_CLASS_NO_COPY(WindowContext); + [[nodiscard]] static vk::PhysicalDeviceVulkan12Features RequiredVulkan12Features() noexcept; [[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept; [[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept; void CreateVulkan(); diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index b34470c35..8bddcd424 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -523,20 +523,14 @@ bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode const auto resource = GetBufferResource(memory); const auto address = ReadBufferAddress(inst, 1); const auto data_src = MemorySourceAt(inst, 0); - IR::Value data = ReadU32(data_src); - if (memory.data_dwords == 2u) { - data = ir.ConstructU64(ReadU32(data_src), ReadU32(OffsetOperand(data_src, 1u))); - } + const IR::Value data = + memory.data_dwords == 2u ? IR::Value(ReadU64(data_src)) : IR::Value(ReadU32(data_src)); const auto result = ir.Emit(opcode, {resource, address.index, address.offset, address.soffset, data, ir.GetExec()}, AddMemoryInfo(memory, inst.pc)); if (inst.glc) { - WriteOperand(inst.dst, - memory.data_dwords == 1u ? result : ir.CompositeExtract(result, 0u)); - if (memory.data_dwords == 2u) { - WriteOperand(OffsetOperand(inst.dst, 1u), ir.CompositeExtract(result, 1u)); - } + WriteOperand(inst.dst, result); } return true; } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 1912a2a8f..378969b98 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -8618,10 +8618,41 @@ void TestCapturedBufferAtomicOrX2() { "64-bit buffer atomic storage view does not use eight-byte elements"); Check(CountSourceOccurrences(source, "Aliased") == 2u, "both storage-buffer views must declare that they alias"); + const auto array_length = + Common::FindIndex(source, std::string("OpArrayLength"), 0); + const auto bounds_branch = Common::FindIndex( + source, std::string("OpBranchConditional"), array_length); + const auto atomic = Common::FindIndex(source, std::string("OpAtomicOr"), 0); + const auto memory_barrier = + Common::FindIndex(source, std::string("OpMemoryBarrier"), atomic); + Check(array_length != Common::FIND_INVALID_INDEX && + bounds_branch != Common::FIND_INVALID_INDEX && + atomic != Common::FIND_INVALID_INDEX && bounds_branch < atomic, + "64-bit buffer atomic was not guarded by storage-buffer bounds"); Check(SpirvInstructionOpcodeCount(result.spirv, 241u) == 1u, "buffer_atomic_or_x2 must lower to exactly one OpAtomicOr"); - Check(Common::ContainsStr(source, "OpMemoryBarrier"), - "64-bit buffer atomic SPIR-V lacks its device-memory barrier"); + Check(memory_barrier != Common::FIND_INVALID_INDEX && atomic < memory_barrier, + "64-bit buffer atomic lacks a following device-memory barrier"); + + const std::array glc_shader = { + shader[0] | (1u << 14u), shader[1], + EncodeMubuf0(0x1d, 32, false), + EncodeMubuf1(0, 0, 0), // preserve returned v0:v1 through a dwordx2 store + shader[2], + }; + auto glc_result = RecompileForTest(glc_shader, options); + const ShaderRecompiler::IR::Inst *glc_atomic = nullptr; + for (const auto *block : glc_result.program.blocks) { + for (const auto &inst : *block) { + if (inst.GetOpcode() == + ShaderRecompiler::IR::ValueOpcode::BufferAtomicOr64) { + glc_atomic = &inst; + } + } + } + Check(glc_atomic != nullptr && glc_atomic->UseCount() == 2u, + "GLC=1 buffer_atomic_or_x2 did not feed both returned dwords"); + CheckSpirvBinaryValidates(glc_result.spirv); } void TestNewShaderRecompilerBranchConditionForms() {