diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index b8c914b8f42f..95628d123850 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2347,6 +2347,7 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB packBits(binding.imageAtomic); packBits(binding.samplerUnnormalized); packBits(binding.samplerIntegerBorder); + packBits(binding.samplerCoordinatesIgnored); packBits(binding.imageUnnormalized); packBits(binding.imageUnnormalizedBaseLevel); // Read-only elements are bound without a write set: an object built for one written set @@ -2368,11 +2369,11 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha words += count; }; for (const auto& binding : shader.program->bindings) { - addWords(19); + addWords(20); addWords(binding.imageSamplers.size()); addWords(binding.imageShapes.size()); if ((dataWords && !movableBuffers) || !DataRole(binding.role)) addWords(binding.guestDescriptor.size()); - for (const auto* bits : {&binding.imageWritten, &binding.samplerDepthCompare, &binding.imageDepthCompare, &binding.imageAtomic, &binding.samplerUnnormalized, &binding.samplerIntegerBorder, &binding.imageUnnormalized, &binding.imageUnnormalizedBaseLevel, &binding.bufferWritten}) addWords(bits->size() / 32u + (bits->size() % 32u != 0 ? 1u : 0u)); + for (const auto* bits : {&binding.imageWritten, &binding.samplerDepthCompare, &binding.imageDepthCompare, &binding.imageAtomic, &binding.samplerUnnormalized, &binding.samplerIntegerBorder, &binding.samplerCoordinatesIgnored, &binding.imageUnnormalized, &binding.imageUnnormalizedBaseLevel, &binding.bufferWritten}) addWords(bits->size() / 32u + (bits->size() % 32u != 0 ? 1u : 0u)); } key.reserve(words); VisitContentKey(shader, dataWords, movableBuffers, [&](std::uint32_t word) { key.push_back(word); }); @@ -4082,7 +4083,14 @@ void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding& Require(binding.samplerDepthCompare.size() == binding.count, "guest sampler binding is missing depth comparison metadata"); Require(binding.samplerUnnormalized.empty() || binding.samplerUnnormalized.size() == binding.count, "guest sampler binding has unnormalized coordinate metadata of another size"); for (std::uint32_t element = 0; element < binding.count; ++element) { - const auto words = std::span(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); + auto guestWords = std::span(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); + std::array normalizedWords{}; + if (element < binding.samplerCoordinatesIgnored.size() && binding.samplerCoordinatesIgnored[element]) { + std::copy(guestWords.begin(), guestWords.end(), normalizedWords.begin()); + normalizedWords[0] &= ~(1u << 15u); + guestWords = normalizedWords; + } + const auto words = guestWords; const bool compareEnable = binding.samplerDepthCompare.at(element); const bool unnormalized = element < binding.samplerUnnormalized.size() && binding.samplerUnnormalized[element]; const bool integerBorder = element < binding.samplerIntegerBorder.size() && binding.samplerIntegerBorder[element]; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index e8f3b6a3ce40..6c9257ac77d4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -84,7 +84,7 @@ void removeCacheDirectory(const std::filesystem::path& directory) { } bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) { - return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.imageShapes == right.imageShapes && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten && left.bufferRead == right.bufferRead && left.samplerUnnormalized == right.samplerUnnormalized && left.samplerIntegerBorder == right.samplerIntegerBorder && left.imageUnnormalized == right.imageUnnormalized && left.imageUnnormalizedBaseLevel == right.imageUnnormalizedBaseLevel && left.imageSamplers == right.imageSamplers; + return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.imageShapes == right.imageShapes && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten && left.bufferRead == right.bufferRead && left.samplerUnnormalized == right.samplerUnnormalized && left.samplerIntegerBorder == right.samplerIntegerBorder && left.samplerCoordinatesIgnored == right.samplerCoordinatesIgnored && left.imageUnnormalized == right.imageUnnormalized && left.imageUnnormalizedBaseLevel == right.imageUnnormalizedBaseLevel && left.imageSamplers == right.imageSamplers; } bool sameBindings(const std::vector& left, const std::vector& right) { @@ -176,6 +176,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.bufferWritten = {false, false, true, true, false}; binding.samplerUnnormalized = {false, true, true}; binding.samplerIntegerBorder = {true, false, seed % 2 == 1}; + binding.samplerCoordinatesIgnored = {false, seed % 2 == 0, true}; binding.imageUnnormalized = {true, false, seed % 2 == 0}; binding.imageUnnormalizedBaseLevel = {false, true, seed % 3 == 0}; binding.imageSamplers = {0x5u, 0u, 0x80000000u}; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 7847b0d021b6..20860dd912bc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -4880,6 +4880,47 @@ void verifyUnnormalizedSamplers() { require(proven(pixelOffsetNormalized, false) && implicitMasks.size() == 1u && (implicitMasks[0] & (spv::ImageOperandsConstOffsetMask | spv::ImageOperandsOffsetMask)) != 0u, "unnormalized texel offsets: a normalized pixel image_sample_o is not an implicit-LOD sample with its offset"); } +void verifyNullVolumeUnnormalizedSampler() { + using namespace ShaderRecompiler; + ImageResource volume{}; + volume.resourceClass = ImageResourceClass::Sampled; + volume.numericClass = IrTextureNumericClass::Float; + volume.dimension = RdnaImageDimension::Dim3D; + volume.read = true; + ImageResource plane = volume; + plane.dimension = RdnaImageDimension::Dim2D; + SamplerResource sampler{}; + sampler.uses = SamplerUseExplicitLod; + const DescriptorValue nullDescriptor{{0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}, 8u}; + const DescriptorValue volumeDescriptor{{0x00001000u, 0x03800000u, 0x0000c000u, 0xa0000facu, 3u, 0u, 0u, 0u}, 8u}; + const DescriptorValue planeDescriptor{{0x00002000u, 0x03800000u, 0x0000c000u, 0x90000facu, 0u, 0u, 0u, 0u}, 8u}; + const DescriptorValue unnormalized{{0x00008092u, 0x00fff000u, 0x02500000u, 0u}, 4u}; + const auto populate = [&](std::vector images, std::vector descriptors) { + ShaderInfo info; + info.images = images; + info.samplers = {sampler}; + for (std::uint32_t index = 0; index < images.size(); ++index) { + info.sampledPairs.push_back({index, 0u, 0u}); + info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(images[index])); + } + ResourceSnapshot snapshot; + snapshot.images = descriptors; + snapshot.samplers = {unnormalized}; + BindingAllocationResult allocation; + for (std::uint32_t index = 0; index < images.size(); ++index) allocation.layout.descriptors.push_back({DescriptorBindingForImage(images[index]), {index}}); + allocation.layout.descriptors.push_back({DescriptorBindingKind::Samplers, {0u}}); + DescriptorBindingBuilder{}.Populate(allocation, info, IrShaderStage::Compute, 0u, snapshot, {}); + return allocation.bindings.back(); + }; + const auto nullOnly = populate({volume}, {nullDescriptor}); + require(nullOnly.samplerUnnormalized == std::vector{false} && nullOnly.samplerCoordinatesIgnored == std::vector{true}, "unnormalized samplers: an S# paired only with a null 3D T# was refused or kept its unnormalized coordinates"); + const DescriptorValue zeroSwizzle{{0x00003000u, 0x00100000u, 0u, 0xa0000000u, 0u, 0u, 0u, 0u}, 8u}; + const auto constantAndNull = populate({volume, volume}, {zeroSwizzle, nullDescriptor}); + require(constantAndNull.samplerUnnormalized == std::vector{false} && constantAndNull.samplerCoordinatesIgnored == std::vector{true}, "unnormalized samplers: an S# paired with a null 3D T# and a 3D T# whose channels all select constants was refused or kept its unnormalized coordinates"); + expectFailure([&] { static_cast(populate({volume}, {volumeDescriptor})); }, "samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: a 3D T# was accepted"); + expectFailure([&] { static_cast(populate({volume, plane}, {nullDescriptor, planeDescriptor})); }, "samples a 1D-array, 2D-array, 3D, cube or multisampled image", "unnormalized samplers: an S# shared by a null 3D T# and a 2D T# was accepted"); +} + void verifyUnusedUnnormalizedSampler() { using namespace ShaderRecompiler; ImageResource image{}; @@ -5955,6 +5996,7 @@ int main(int argc, char** argv) { verifyCoherentBufferAccesses(); verifyUnnormalizedSamplers(); verifyUnusedUnnormalizedSampler(); + verifyNullVolumeUnnormalizedSampler(); verifyWaveUniformValues(); verifyTwoLaneUniformValues(); verifyBdaReadFallbackFunctions(); diff --git a/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp b/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp index 2a33e8a6d887..24b61dcff416 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp @@ -56,6 +56,7 @@ class ResourceMaterializer { static std::uint32_t RuntimeImageMode(const ImageResource& image, const DescriptorValue& descriptor, std::span modes); static void PrepareImageModes(ShaderInfo& info); static const char* UnnormalizedSampleMismatch(const ImageResource& image); + static bool NullImageDescriptor(const ImageResource& image, const DescriptorValue& descriptor); static ResolvedImageTables ResolveImageTables(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint64_t memoKey = 0); [[nodiscard]] IrResourcePlan ExtractPlan(const IrProgram& program) const; static void CompactPlan(IrResourcePlan& plan); diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 79e73779e2cb..e4544a55d9ee 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -182,10 +182,11 @@ struct UnnormalizedProof { std::vector samplers; std::vector images; std::vector imagesAtBaseLevel; + std::vector samplersIgnoreCoordinates; }; UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapshot& snapshot) { - UnnormalizedProof proof{std::vector(info.samplers.size()), std::vector(info.images.size()), std::vector(info.images.size())}; + UnnormalizedProof proof{std::vector(info.samplers.size()), std::vector(info.images.size()), std::vector(info.images.size()), std::vector(info.samplers.size())}; for (std::uint32_t r = 0; r < info.samplers.size(); r++) { if (snapshot.samplers.at(r).dwordCount != 4u) fail("sampler descriptor must contain four dwords"); const auto& sampler = info.samplers[r]; @@ -212,6 +213,8 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh if (sampler.depthCompare) { failUnnormalized("is used with depth comparison"); } + bool sampled = false; + const char* nullMismatch = nullptr; for (const auto& pair : info.sampledPairs) { if (pair.sampler != r) { continue; @@ -221,12 +224,23 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh failUnnormalized("samples an image selected at run time"); } const auto& image = info.runtimeImageModes.at(pair.image).at(ResourceMaterializer::RuntimeImageMode(base, snapshot.images.at(pair.image), info.runtimeImageModes.at(pair.image))); - if (const auto* reason = ResourceMaterializer::UnnormalizedSampleMismatch(image)) { + const auto* reason = ResourceMaterializer::UnnormalizedSampleMismatch(image); + if (reason != nullptr && ResourceMaterializer::NullImageDescriptor(base, snapshot.images.at(pair.image))) { + if (nullMismatch == nullptr) nullMismatch = reason; + continue; + } + if (reason != nullptr) { failUnnormalized(reason); } if (image.constantSwizzle) continue; + sampled = true; proof.images[pair.image] = true; } + if (!sampled && nullMismatch != nullptr) { + proof.samplersIgnoreCoordinates[r] = true; + continue; + } + if (nullMismatch != nullptr) failUnnormalized(nullMismatch); proof.samplers[r] = true; } for (std::uint32_t resource = 0; resource < info.images.size(); resource++) { @@ -451,6 +465,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l for (const auto& pair : info.sampledPairs) if (pair.sampler == resource) compare |= info.images.at(pair.image).depthCompare && compareStates.at(pair.image) == 0u; physical.samplerDepthCompare.push_back(compare); physical.samplerUnnormalized.push_back(unnormalized.samplers.at(resource)); + physical.samplerCoordinatesIgnored.push_back(unnormalized.samplersIgnoreCoordinates.at(resource)); const auto mode = 1u << (originals[element] & 1u); physical.samplerIntegerBorder.push_back((samplerIntegerModes.at(resource) & mode) != 0u && (samplerFloatModes.at(resource) & mode) == 0u); const auto& words = snapshot.samplers.at(resource).dwords; diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index ca0eef25c81b..abb8a15e5e28 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -1623,6 +1623,10 @@ std::vector ResourceMaterializer::RuntimeImageModes(const ImageRe return modes; } +bool ResourceMaterializer::NullImageDescriptor(const ImageResource& image, const DescriptorValue& descriptor) { + return nullImageDescriptor(image, descriptor); +} + const char* ResourceMaterializer::UnnormalizedSampleMismatch(const ImageResource& image) { if (image.table != NoTable) return "samples an image selected at run time"; if (image.constantSwizzle) return nullptr; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 38de551273d5..9f9133417d9e 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -428,6 +428,7 @@ struct DescriptorBinding { std::vector bufferWritten; std::vector samplerUnnormalized; std::vector samplerIntegerBorder; + std::vector samplerCoordinatesIgnored; std::vector imageUnnormalized; std::vector imageUnnormalizedBaseLevel; std::vector imageSamplers; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 3c9e663e942b..6724f0458d60 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -87,7 +87,7 @@ namespace ShaderRecompiler::ShaderDiskCache { static_assert(sizeof(CompiledShaderArtifact) == 240, "CompiledShaderArtifact changed: update the artifact encoder"); static_assert(sizeof(ShaderInvocation) == 200, "ShaderInvocation changed: update the invocation encoder"); static_assert(sizeof(RecompileResult) == 472, "RecompileResult changed: update EncodeResult and DecodeResult"); -static_assert(sizeof(DescriptorBinding) == 592, "DescriptorBinding changed: update the binding encoder"); +static_assert(sizeof(DescriptorBinding) == 632, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 32, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(VertexInput) == 16, "VertexInput changed: update the vertex input encoder"); static_assert(sizeof(FragmentParameter) == 16, "FragmentParameter changed: update the parameter encoder"); @@ -427,6 +427,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.imageUnnormalized); writer.Flags(binding.imageUnnormalizedBaseLevel); writer.Flags(binding.samplerIntegerBorder); + writer.Flags(binding.samplerCoordinatesIgnored); writer.Values(std::span(binding.imageSamplers)); writer.Flags(binding.bufferRead); } @@ -454,6 +455,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) { reader.Flags(binding.imageUnnormalized); reader.Flags(binding.imageUnnormalizedBaseLevel); reader.Flags(binding.samplerIntegerBorder); + reader.Flags(binding.samplerCoordinatesIgnored); reader.Values(binding.imageSamplers); reader.Flags(binding.bufferRead); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index f9840a09635a..bcb99a770a27 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -13,7 +13,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 37; +inline constexpr std::uint32_t FormatVersion = 38; enum class LoadStatus { Loaded,