diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 4022508ff10..685e1567bcf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -21,6 +21,7 @@ struct Decoration { std::optional stride; std::optional index; bool block = false; + bool aliased = false; bool patch = false; bool perPrimitive = false; bool perVertex = false; @@ -363,6 +364,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi decoration.perVertex = true; } if (kind == spv::DecorationBlock) decoration.block = true; + if (kind == spv::DecorationAliased) decoration.aliased = true; break; } case spv::OpMemberDecorate: @@ -432,6 +434,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthReplacing || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater || name == spv::ExecutionModePixelInterlockOrderedEXT || name == spv::ExecutionModeStencilRefReplacingEXT), "unsupported fragment execution mode"); } else Require(module.modes.empty(), "unsupported vertex execution mode"); std::set> descriptors; + std::map, bool> bufferViews; bool push = false; for (const auto& [id, variable] : module.variables) { if (variable.storage == spv::StorageClassFunction || variable.storage == spv::StorageClassPrivate) continue; @@ -518,12 +521,13 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi } else { Require(variable.storage == spv::StorageClassStorageBuffer && decoration.set && decoration.binding, "unsupported or unbound shader resource"); const auto key = std::make_pair(*decoration.set, *decoration.binding); - Require(descriptors.insert(key).second, "duplicate SPIR-V resource binding"); const auto binding = std::find_if(shader.bindings.begin(), shader.bindings.end(), [&](const auto& item) { return item.descriptorSet == key.first && item.binding == key.second; }); + const bool array = binding != shader.bindings.end() && binding->role == ShaderRecompiler::DescriptorRole::GuestBuffers; + const auto [view, first] = bufferViews.emplace(key, decoration.aliased); + Require(descriptors.insert(key).second || (!first && view->second && decoration.aliased && array), "duplicate SPIR-V resource binding"); Require(binding != shader.bindings.end(), "SPIR-V resource is absent from recompiler binding metadata"); Require(binding->kind == ShaderRecompiler::DescriptorKind::StorageBuffer, "SPIR-V descriptor type disagrees with recompiler binding metadata"); Require(!binding->readOnly, "read-only descriptor metadata is unsupported because the recompiler emits no NonWritable decoration"); - const bool array = binding->role == ShaderRecompiler::DescriptorRole::GuestBuffers; Require(array || (shader.bdaAbiVersion == ShaderRecompiler::BdaAbi::Version && (binding->role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding->role == ShaderRecompiler::DescriptorRole::FaultBuffer)) || binding->role == ShaderRecompiler::DescriptorRole::ShaderData || binding->role == ShaderRecompiler::DescriptorRole::FlattenedSrt || binding->role == ShaderRecompiler::DescriptorRole::Gds, "SPIR-V descriptor role is unsupported"); auto blockId = typeId; if (array) { @@ -537,7 +541,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi const auto& block = module.Type(blockId); Require(block.size() == 3 && (block[0] & 0xffffu) == spv::OpTypeStruct && module.decorations[blockId].block, "descriptor must be a Block struct with exactly one member"); const auto& words = module.Type(block[2]); - Require(words.size() == 3 && (words[0] & 0xffffu) == spv::OpTypeRuntimeArray && module.Signature(words[2]) == "u32", "descriptor block member must be a runtime array of u32"); + Require(words.size() == 3 && (words[0] & 0xffffu) == spv::OpTypeRuntimeArray, "descriptor block member must be a runtime array"); + const auto& element = module.Type(words[2]); + const bool unsignedInteger = element.size() == 4 && (element[0] & 0xffffu) == spv::OpTypeInt && element[3] == 0u; + Require(unsignedInteger && (element[2] == 32u || (array && decoration.aliased && element[2] == 64u)), "descriptor block member must be a runtime array of u32, or of u64 in an aliased guest buffer view"); const auto member = module.offsets.find({blockId, 0}); Require(member != module.offsets.end() && member->second == 0, "descriptor block member must have offset zero"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 22b5c62dd68..fcc2e665ccd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -4076,6 +4076,54 @@ void stencilRefExportTests() { static_cast(AgcDriver::Graphics::DecodeState(queue)); } +std::set bufferViews(std::span words) { + std::set views; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + if (static_cast(words[at] & 0xffffu) != spv::OpName) continue; + const std::string name(reinterpret_cast(&words[at + 2])); + if (name.starts_with("buffers")) views.insert(name); + } + return views; +} + +void aliasedBufferViewTests() { + constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityInt64Atomics}; + constexpr std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + constexpr std::array userData{0u, 0u, 0u, 0u, 0x10000000u, 0u, 0x1000u, 0x31016facu, 0x20000000u, 0u, 0x1000u, 0x31038facu}; + const auto code = [](std::uint32_t store, std::uint32_t resource) { + return std::array{0xe0000000u, 0x80010a00u, 0xbf8c3f70u, store, resource, 0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u}; + }; + const std::array, std::set>, 2> cases{{ + {code(0xe0104000u, 0x80010a00u), {"buffers", "buffers_coherent"}}, + {code(0xe1480000u, 0x80010a00u), {"buffers", "buffers_u64"}}, + }}; + AgcDriver::Graphics::State state{}; + state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex; + ShaderRecompiler::RecompileResult vertex; + vertex.spirv = makeModule({}); + const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; + for (const auto& [words, views] : cases) { + auto request = orderedPixelRequest(0x20600u, capabilities, words); + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + auto pixel = ShaderRecompiler::Recompile(request); + const auto declared = bufferViews(pixel.spirv.Words()); + std::string names; + for (const auto& name : declared) names += " " + name; + Require(declared == views, "the pixel shader declared the guest buffers as" + names + ", not as the expected " + std::to_string(views.size()) + " aliased variables"); + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &pixel, 0}}}; + AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, true); + auto unaliased = pixel.spirv.Words(); + for (std::size_t at = 5; at < unaliased.size() && (unaliased[at] >> 16u) != 0; at += unaliased[at] >> 16u) { + if (static_cast(unaliased[at] & 0xffffu) == spv::OpDecorate && unaliased[at + 2] == spv::DecorationAliased) unaliased[at + 2] = spv::DecorationRestrict; + } + pixel.spirv = std::move(unaliased); + expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, true); }, "duplicate SPIR-V resource binding"); + } +} + void ProvokingVertexTests() { using namespace AgcDriver::Graphics; constexpr std::uint32_t lastVertex = 1u << 19u; @@ -5124,6 +5172,7 @@ int main(int argc, char** argv) { conservativeZExportTests(); orderedPixelShaderTests(); stencilRefExportTests(); + aliasedBufferViewTests(); ProvokingVertexTests(); ProvokingVertexRenderPassTests(); ConservativeRasterizationTests();