diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 590cf2750..80b56997d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -116,6 +116,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su product->pixel.dualSourceBlend = product->state.dualSourceBlend; product->pixel.alphaToCoverage = product->state.alphaToCoverage; if (product->pixel.ancillary) product->pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product->state); + if (product->pixel.ancillary) product->pixel.layerExported = product->state.layerExports != 0; return product; } diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 46fc6edf7..f6921d805 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1842,12 +1842,14 @@ void verifyPixelRequestSerialization() { minimal.context.waveSize = 64; minimal.context.pixel = ShaderPixelStageInfo{}; minimal.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle; + minimal.context.pixel->layerExported = true; const auto encoded = serializer.Serialize(minimal); require(requestPrefix(encoded, 8u) == "NVNQQRYAAAA=", "new requests did not use serialization version 22"); require(serializer.Deserialize(encoded).request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "the pixel primitive class did not round-trip"); + require(serializer.Deserialize(encoded).request.context.pixel->layerExported, "the pixel layer export did not round-trip"); constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u; - for (std::size_t bytes = 0; bytes < 21u; ++bytes) { - expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag or primitive class was accepted"); + for (std::size_t bytes = 0; bytes < 22u; ++bytes) { + expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class or layer export flag was accepted"); } for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRcAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); @@ -1960,11 +1962,20 @@ void verifyVertexInfoVersion19() { pixel.context.pixel = ShaderPixelStageInfo{}; pixel.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle; const auto encodedPixel = serializer.Serialize(pixel); + auto exportedPixel = pixel; + exportedPixel.context.pixel->layerExported = true; + const auto exportedBytes = decodeBase64(serializer.Serialize(exportedPixel)); auto legacyPixel = decodeBase64(encodedPixel); - legacyPixel[4] = 19u; - const auto replayPixel = serializer.Deserialize(encodeBase64(legacyPixel)); - require(replayPixel.request.context.pixel.has_value() && replayPixel.request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "a main version 19 request lost its pixel primitive class"); - require(serializer.Serialize(replayPixel.request) == encodedPixel, "a main version 19 request misread the fields after its pixel primitive class"); + std::size_t flagOffset = 0; + while (flagOffset < legacyPixel.size() && legacyPixel[flagOffset] == exportedBytes[flagOffset]) ++flagOffset; + require(flagOffset < legacyPixel.size(), "the pixel layer export flag is not serialized"); + legacyPixel.erase(legacyPixel.begin() + static_cast(flagOffset)); + for (const auto version : {19u, 20u, 21u}) { + legacyPixel[4] = static_cast(version); + const auto replayPixel = serializer.Deserialize(encodeBase64(legacyPixel)); + require(replayPixel.request.context.pixel.has_value() && replayPixel.request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle && !replayPixel.request.context.pixel->layerExported, "a main version 19 to 21 request lost its pixel primitive class or read a layer export flag"); + require(serializer.Serialize(replayPixel.request) == encodedPixel, "a main version 19 to 21 request misread the fields after its pixel primitive class"); + } } void verifyLegacyPixelRequests() { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp index bd4888a96..8f419f216 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp @@ -66,6 +66,13 @@ alignas(256) constexpr std::array PixelCode{ 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000, }; +alignas(256) constexpr std::array LayerFieldPixelCode{ + 0x2c020090, 0x7e020d01, 0x100202f0, 0xf800180f, 0x01010101, 0xbf810000, +}; + +constexpr std::array WhiteBands{255, 255, 255}; +constexpr std::array LayerFieldBands{0, 128, 255}; + struct Vertex { std::array position; std::array color; @@ -142,7 +149,7 @@ bool DeclaresLayer(const std::vector& words) { return false; } -void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool mesh) { +void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool mesh, std::span pixelCode, bool layerField) { const auto target = device.Target(); const auto vertexBuffer = BufferDescriptor(Vertices.data(), sizeof(Vertex), static_cast(Vertices.size())); const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Vertices.size()), 0, 1, 0, false}; @@ -167,10 +174,15 @@ void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget ShaderRecompiler::ShaderPixelStageInfo pixel{}; pixel.targetOutputMode[0] = 9; pixel.targetExportMapping.fill(0xe4u); + if (layerField) { + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); + pixel.ancillary = true; + pixel.layerExported = true; + } const std::vector pixelUserData(8, 0u); - const std::array pixelMemory{{{reinterpret_cast(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}}; + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; ShaderRecompiler::RecompileRequest fragment{ - {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, target, PixelPushLayout(geometryPush, target) @@ -226,7 +238,7 @@ std::uint64_t BlockOffset(const AgcDriver::Graphics::TextureSwizzleEquation& equ return offset; } -void Check(const std::byte* volume, const AgcDriver::Graphics::ColorTarget& color, const std::string& what) { +void Check(const std::byte* volume, const AgcDriver::Graphics::ColorTarget& color, const std::string& what, const std::array& bands) { const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::SurfaceForTarget(color)); Require(!geometry.thick, "a 64 KiB render-target volume is not stored as thin slices"); const auto block = AgcDriver::Graphics::ThinBlockLayout(AgcDriver::Graphics::TextureTileMode::kR64KBX, 4); @@ -238,11 +250,11 @@ void Check(const std::byte* volume, const AgcDriver::Graphics::ColorTarget& colo for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { const bool drawn = slice >= FirstSlice && x / BandWidth == slice - FirstSlice; - const auto expected = drawn ? std::uint8_t{255} : Kept; + const auto expected = drawn ? bands[slice - FirstSlice] : Kept; const auto* texel = base + BlockOffset(*equation, x, y, slice); for (std::uint32_t channel = 0; channel < 4; ++channel) { const auto actual = std::to_integer(texel[channel]); - Require(actual == expected, what + ": slice " + std::to_string(slice) + " texel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected) + (drawn ? " (the band of layer " + std::to_string(slice - FirstSlice) + ")" : slice < FirstSlice ? " (outside the color view)" : " (another layer's band)")); + Require(actual == expected || (drawn && expected == 128 && actual == 127), what + ": slice " + std::to_string(slice) + " texel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected) + (drawn ? " (the band of layer " + std::to_string(slice - FirstSlice) + ")" : slice < FirstSlice ? " (outside the color view)" : " (another layer's band)")); } } } @@ -265,10 +277,14 @@ int main(int argc, char** argv) { BuildBands(); const GuestVolume volume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); const auto color = VolumeTarget(volume.Data()); - Run(*device, color, false); - Check(volume.Data(), color, "vertex draw"); + Run(*device, color, false, PixelCode, false); + Check(volume.Data(), color, "vertex draw", WhiteBands); + const GuestVolume fieldVolume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); + const auto fieldColor = VolumeTarget(fieldVolume.Data()); + Run(*device, fieldColor, false, LayerFieldPixelCode, true); + Check(fieldVolume.Data(), fieldColor, "a pixel shader reading the render target index of its packed ancillary word", LayerFieldBands); if (vertexOnly) std::puts("layered volume vertex draw tests passed"); - else if (target.mesh.has_value()) Run(*device, color, true); + else if (target.mesh.has_value()) Run(*device, color, true, PixelCode, false); else std::puts("mesh case skipped, the device has no VK_EXT_mesh_shader"); std::puts("layered volume draw tests passed"); return 0; diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 5bc5ba3a3..d84f2714a 100644 --- a/core/libs/tests/PixelAncillary.cpp +++ b/core/libs/tests/PixelAncillary.cpp @@ -20,13 +20,16 @@ struct Row { std::uint32_t layer; bool frontFacing; RasterPrimitive primitive = RasterPrimitive::Triangle; + bool layerExported = false; }; -constexpr std::array Rows {{{0u, 0u, false}, {0u, 0u, true}, {15u, 0x1FFFu, false}, {15u, 0x1FFFu, true}, {0u, 0x1FFFu, true}, {15u, 0u, false}}}; +constexpr std::array Rows {{{0u, 0u, false}, {0u, 0u, true}, {15u, 0x1FFFu, false}, {15u, 0x1FFFu, true}, {0u, 0x1FFFu, true}, {15u, 0u, false}, + {0u, 0x1FFFu, false, RasterPrimitive::Triangle, true}, {15u, 0x1FFFu, true, RasterPrimitive::Triangle, true}, {15u, 3u, false, RasterPrimitive::Triangle, true}, {0u, 0u, true, RasterPrimitive::Triangle, true}}}; static std::uint32_t Evaluate(const IrValue* value, const Row& row); +static std::uint32_t BitField(std::uint32_t word, std::uint32_t offset, std::uint32_t count, bool isSigned); static void RequireField(const IrValue& user, StageInputKind kind, std::uint32_t fieldOffset, std::uint32_t count) { if (kind == StageInputKind::Layer) { - for (const Row& row : Rows) Require(Evaluate(&user, row) == 0u); + for (const Row& row : Rows) Require(Evaluate(&user, row) == (row.layerExported ? BitField(row.layer, fieldOffset, count, user.Opcode() == IrOpcode::BitFieldSExtract) : 0u)); return; } const IrValue* field = user.Argument(0)->Resolve(); @@ -157,11 +160,13 @@ static std::uint32_t Evaluate(const IrValue* value, const Row& row) { case StageInputKind::Layer: return row.layer; case StageInputKind::FrontFacing: return row.frontFacing ? 1u : 0u; case StageInputKind::RasterPrimitive: return static_cast(row.primitive); + case StageInputKind::LayerExported: return row.layerExported ? 1u : 0u; default: break; } break; case IrOpcode::ShiftLeftLogical32: return Evaluate(value->Argument(0), row) << Evaluate(value->Argument(1), row); case IrOpcode::BitwiseOr32: return Evaluate(value->Argument(0), row) | Evaluate(value->Argument(1), row); + case IrOpcode::IMul32: return Evaluate(value->Argument(0), row) * Evaluate(value->Argument(1), row); case IrOpcode::BitFieldUExtract: case IrOpcode::BitFieldSExtract: return BitField(Evaluate(value->Argument(0), row), Evaluate(value->Argument(1), row), Evaluate(value->Argument(2), row), value->Opcode() == IrOpcode::BitFieldSExtract); @@ -172,7 +177,7 @@ static std::uint32_t Evaluate(const IrValue* value, const Row& row) { throw std::runtime_error("packed pixel ancillary evaluation reached an unsupported value"); } static std::uint32_t PackedWord(const Row& row) { - return UnmodelledBits | (row.sample << 8u); + return UnmodelledBits | (row.sample << 8u) | (row.layerExported ? row.layer << 16u : 0u); } static void Values(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect) { IrProgram program; @@ -471,11 +476,12 @@ static void ExportedWholeWord() { static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); LowerMasked(program, true); Require(!AncillaryRemains(program) && program.Info().rasterPrimitive); - Require(!BuiltinRemains(program, StageInputKind::Layer)); + Require(BuiltinRemains(program, StageInputKind::LayerExported)); const IrValue* word = carried.Argument(0)->Resolve(); - constexpr std::array words {{{0u, 0u, false, RasterPrimitive::Triangle}, {0u, 0u, false, RasterPrimitive::Rectangle}, {0u, 0u, false, RasterPrimitive::Line}, {15u, 0x1FFFu, false, RasterPrimitive::Point}, {3u, 0x7FFu, false, RasterPrimitive::Triangle}}}; + constexpr std::array words {{{0u, 0u, false, RasterPrimitive::Triangle}, {0u, 0u, false, RasterPrimitive::Rectangle}, {0u, 0u, false, RasterPrimitive::Line}, {15u, 0x1FFFu, false, RasterPrimitive::Point}, {3u, 0x7FFu, false, RasterPrimitive::Triangle}, + {15u, 0x7FFu, false, RasterPrimitive::Point, true}, {3u, 5u, false, RasterPrimitive::Triangle, true}}}; for (const Row& row : words) { - Require(Evaluate(word, row) == (static_cast(row.primitive) | (row.sample << 8u))); + Require(Evaluate(word, row) == (static_cast(row.primitive) | (row.sample << 8u) | (row.layerExported ? row.layer << 16u : 0u))); } } static void WholeWordReplacesRefusal(HelperRead read) { diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index c798752ff..80843a195 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -335,6 +335,7 @@ void writePixelInfo(Writer& writer, const ShaderPixelStageInfo& info) { writer.WriteBool(info.alphaToCoverage); writer.WriteBool(info.stencilRefExportEnable); writer.WriteU8(static_cast(info.rasterPrimitive)); + writer.WriteBool(info.layerExported); } ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) { @@ -385,6 +386,7 @@ ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) { if (version >= 16u) info.alphaToCoverage = reader.ReadBool(); if (version >= 17u) info.stencilRefExportEnable = reader.ReadBool(); if (version >= 19u) info.rasterPrimitive = readRasterPrimitive(reader); + if (version >= 22u) info.layerExported = reader.ReadBool(); if (version < 5u) { const auto input = [](PixelInput value, bool present) { return present ? PixelInputBit(value) : 0u; }; info.inputAddr = input(PixelInput::PerspectiveSample, info.hasPerspectiveCenterVgpr && inputAddrOrCenterVgpr == 2u) | input(PixelInput::PerspectiveCenter, info.hasPerspectiveCenterVgpr) | diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp index 089d299cf..8879b166b 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp @@ -36,6 +36,7 @@ struct ShaderInfo { bool usesFaultBuffer = false; bool dispatchThreadLimit = false; bool rasterPrimitive = false; + bool layerExported = false; bool operator==(const ShaderInfo& other) const = default; }; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp index 75ac2a8e4..96c7d5153 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp @@ -45,6 +45,7 @@ enum class StageInputKind { Parameter, HelperInvocation, RasterPrimitive, + LayerExported, }; enum class StageOutputKind { diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index 8c9645912..3505f640f 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -384,7 +384,7 @@ void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil } std::array fields {}; const auto field = [&](std::size_t index) -> IrValue& { - if (fields[index] == nullptr && index == 1u) fields[index] = &builder.Constant(0u); + if (fields[index] == nullptr && index == 1u) fields[index] = &insertBefore(program, ancillary, IrOpcode::IMul32, insertBuiltin(program, builder, ancillary, StageInputKind::Layer), insertBuiltin(program, builder, ancillary, StageInputKind::LayerExported)); if (fields[index] == nullptr) { fields[index] = &insertBuiltin(program, builder, ancillary, StageInputKind::SampleId); } @@ -859,7 +859,10 @@ bool ConstantFolder::ComposePackedAncillary(IrProgram& program) const { for (IrValue* word : words) { IrValue& primitive = insertBuiltin(program, builder, *word, StageInputKind::RasterPrimitive); IrValue& sample = insertBefore(program, *word, IrOpcode::ShiftLeftLogical32, insertBuiltin(program, builder, *word, StageInputKind::SampleId), builder.Constant(8u)); - word->ReplaceAllUsesWith(&insertBefore(program, *word, IrOpcode::BitwiseOr32, primitive, sample)); + IrValue& exportedLayer = insertBefore(program, *word, IrOpcode::IMul32, insertBuiltin(program, builder, *word, StageInputKind::Layer), insertBuiltin(program, builder, *word, StageInputKind::LayerExported)); + IrValue& layer = insertBefore(program, *word, IrOpcode::ShiftLeftLogical32, exportedLayer, builder.Constant(16u)); + IrValue& fields = insertBefore(program, *word, IrOpcode::BitwiseOr32, sample, layer); + word->ReplaceAllUsesWith(&insertBefore(program, *word, IrOpcode::BitwiseOr32, primitive, fields)); } return !words.empty(); } diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index 6d60d53a3..419c21976 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -144,6 +144,7 @@ void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inpu case StageInputKind::Layer: case StageInputKind::SampleId: case StageInputKind::RasterPrimitive: + case StageInputKind::LayerExported: if (program.Resources().stage != IrShaderStage::Pixel || component != 0u) { return Fail("typed pixel scalar input is invalid"); } @@ -390,6 +391,9 @@ void CollectBuiltinInputs(const IrProgram& program, ShaderInfo& info) { case StageInputKind::RasterPrimitive: info.rasterPrimitive = true; break; + case StageInputKind::LayerExported: + info.layerExported = true; + break; case StageInputKind::PackedAncillary: case StageInputKind::Parameter: break; diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index 9293e3f65..44e1e996f 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -27,6 +27,7 @@ inline constexpr std::uint32_t ExportBase = 8192u; inline constexpr std::uint32_t ExportPackingBase = 8224u; inline constexpr std::uint32_t DualSourceBlend = 8232u; inline constexpr std::uint32_t RasterPrimitive = 8233u; +inline constexpr std::uint32_t LayerExported = 8234u; inline constexpr std::uint32_t HeapCountBase = 8256u; inline constexpr std::uint32_t MipCountBase = 8320u; inline constexpr std::uint32_t DescriptorIndexBase = 16384u; diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index e66bc8460..e15fdf163 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -777,6 +777,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil for (const auto packing : request.context.pixel->targetExportPacking) moduleKey.push_back(static_cast(packing)); moduleKey.push_back(request.context.pixel->dualSourceBlend ? 1u : 0u); if (variant.info.info.rasterPrimitive) moduleKey.push_back(static_cast(request.context.pixel->rasterPrimitive)); + if (variant.info.info.layerExported) moduleKey.push_back(request.context.pixel->layerExported ? 1u : 0u); } result.vertexAttributes.reserve(result.vertexInputs.size()); std::array vertexClasses{}; @@ -824,6 +825,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil for (std::uint32_t target = 0; target < request.context.pixel->targetExportPacking.size(); ++target) constants.push_back({PipelineSpecialization::ExportPackingBase + target, moduleKey[index++]}); constants.push_back({PipelineSpecialization::DualSourceBlend, moduleKey[index++]}); if (variant.info.info.rasterPrimitive) constants.push_back({PipelineSpecialization::RasterPrimitive, moduleKey[index++]}); + if (variant.info.info.layerExported) constants.push_back({PipelineSpecialization::LayerExported, moduleKey[index++]}); } if (!artifact.vertexInputPatches.empty()) { for (const auto& input : result.vertexInputs) { @@ -1025,6 +1027,7 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh for (const auto packing : request.context.pixel->targetExportPacking) mix(static_cast(packing)); mix(request.context.pixel->dualSourceBlend); mix(static_cast(request.context.pixel->rasterPrimitive)); + mix(request.context.pixel->layerExported); } if (request.context.vertex) { const auto& vertex = *request.context.vertex; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index f6f051203..9a22de525 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -150,6 +150,7 @@ struct ShaderPixelStageInfo { bool alphaToCoverage; bool stencilRefExportEnable; RasterPrimitive rasterPrimitive = RasterPrimitive::Triangle; + bool layerExported = false; }; struct ShaderVertexBufferResource { diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index d4e2ea016..f25dc3096 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -557,6 +557,7 @@ void encodeInfo(Writer& writer, const CompiledShaderInfo& compiled) { writer.Value(info.usesFaultBuffer); writer.Value(info.dispatchThreadLimit); writer.Value(info.rasterPrimitive); + writer.Value(info.layerExported); encodeLayout(writer, compiled.bindings); } @@ -667,6 +668,7 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) { reader.Value(info.usesFaultBuffer); reader.Value(info.dispatchThreadLimit); reader.Value(info.rasterPrimitive); + reader.Value(info.layerExported); decodeLayout(reader, compiled.bindings); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 7a9a2d321..81bbd87be 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 28; +inline constexpr std::uint32_t FormatVersion = 29; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 3600c7f20..35bdbd6ae 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -48,6 +48,9 @@ std::uint32_t EmitBuiltinU32(SpirvEmitterState& state, StageInputKind kind, std: if (kind == StageInputKind::RasterPrimitive) { return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::RasterPrimitive, static_cast(RasterPrimitive::Triangle)); } + if (kind == StageInputKind::LayerExported) { + return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::LayerExported, 0u); + } if (state.laneCount == 2 && (kind == StageInputKind::LocalInvocationId || kind == StageInputKind::GlobalInvocationId)) { const auto* workgroup = ShaderWorkgroupInputFor(state); if (workgroup == nullptr) { diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index 0a8ed6c1b..57aadb650 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -6,7 +6,7 @@ ### Unknown function info -- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin, an equality inferred, not measured. The render target index is 0: vertex layer exports are ignored, so no pre-rasterization stage writes Layer and the first layer is used. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800). Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places +- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin, an equality inferred, not measured. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places - [Color comparison descriptor forms](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp): emulated color comparison supports UNORM, SNORM and float formats for 2D, 2D-array and cube views. 8-bit sRGB and UNORM (`Format8Srgb`, `Format8_8Srgb`, `Format8_8_8_8Srgb`, `Format8UNorm`, `Format8_8UNorm`, `Format8_8_8_8UNorm`) return 0 for every compare function, measured with `image_sample_c_lz` on gfx1035 over references -0.5..1.5 and texel codes 0..255. Anisotropic filters are refused, except on those 8-bit formats, where every texel comparison is 0 (not measured with an anisotropic S#). Bilinear cube comparisons of float, snorm and the other unorm formats without `DISABLE_CUBE_WRAP` are refused; on that hardware a 4x4 R32 float cube blends across faces (46 of 90 edge rows differ from a per-face clamp; face 0, ref 0.1, u=3.9 v=2.5 is 0.6016 and u=1.5 v=0.1 is 0.3984), and with `DISABLE_CUBE_WRAP` the per-face clamp matches. An 8-bit sRGB or UNORM bilinear cube without that bit returns 0, since every texel comparison is already 0 for every compare function and the selected face cannot change the result. Cube-edge sampling behavior and console coverage on PS5's gfx1013 remain unverified. - [s_setkill](../../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws