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 2af5a114da..590cf27509 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -115,6 +115,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); product->pixel.dualSourceBlend = product->state.dualSourceBlend; product->pixel.alphaToCoverage = product->state.alphaToCoverage; + if (product->pixel.ancillary) product->pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product->state); return product; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index 7b959293e6..e028edc5d1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -70,6 +70,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr(value)); mix(pixel.dualSourceBlend); mix(pixel.alphaToCoverage); + mix(static_cast(pixel.rasterPrimitive)); std::lock_guard cacheLock(drawCacheMutex); ++drawEntryCounters.lookups; const auto found = drawCache.find(drawKey); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index 6dec41300d..441756f627 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -105,7 +105,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { const auto& y = b.programs[i]; if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset) return false; } - if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.alphaToCoverage != q.alphaToCoverage) return false; + if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive) return false; return true; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 36a8348cc3..dd27811b74 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -125,6 +125,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); std::array SampleLocations(const State& state); std::array ExportMappings(const State& state); std::array ExportPackings(const State& state); +ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state); ColorTarget DecodeColorBuffer(const Registers& context, std::uint32_t slot); std::size_t CmaskBytes(std::uint32_t width, std::uint32_t height); std::uint32_t ColorWriteMask(const Registers& context); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index f151a5f0bb..dd8f4a5114 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -951,6 +951,16 @@ std::array ExportPackings(const State& return packings; } +ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state) { + if (state.rectList) return ShaderRecompiler::RasterPrimitive::Rectangle; + if (state.stages.mesh.has_value() || state.stages.tessellation.has_value()) return ShaderRecompiler::RasterPrimitive::Triangle; + switch (state.topology) { + case VK_PRIMITIVE_TOPOLOGY_POINT_LIST: return ShaderRecompiler::RasterPrimitive::Point; + case VK_PRIMITIVE_TOPOLOGY_LINE_LIST: return ShaderRecompiler::RasterPrimitive::Line; + default: return ShaderRecompiler::RasterPrimitive::Triangle; + } +} + std::array ExportMappings(const State& state) { std::array mappings{}; mappings.fill(0xe4u); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 063d4a4993..f3b6b8c484 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -246,6 +246,7 @@ CompiledVariant sampleVariant() { info.info.hasBitwiseXor = true; info.info.usesDma = true; info.info.usesFaultBuffer = true; + info.info.rasterPrimitive = true; info.bindings.pushDataStartDword = 2; info.bindings.memoryOffsetDword = 1; info.bindings.memoryOffsetCount = 5; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 112e80af0c..6ccfb4be3c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1807,11 +1807,13 @@ void verifyPixelRequestSerialization() { minimal.shader = {ShaderStage::Fragment, 0x30000u, code, 0, {}}; minimal.context.waveSize = 64; minimal.context.pixel = ShaderPixelStageInfo{}; + minimal.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle; const auto encoded = serializer.Serialize(minimal); require(requestPrefix(encoded, 8u) == "NVNQQRIAAAA=", "new requests did not use serialization version 18"); + require(serializer.Deserialize(encoded).request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "the pixel primitive class did not round-trip"); constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u; - for (std::size_t bytes = 0; bytes < 19u; ++bytes) { - expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage or stencil reference export flag was accepted"); + for (std::size_t bytes = 0; bytes < 20u; ++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 (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRMAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); @@ -1904,6 +1906,7 @@ void verifyLegacyPixelRequests() { require(!pixel.dualSourceBlend, "a legacy request enabled dual-source blending"); require(!pixel.alphaToCoverage, "a legacy request enabled alpha-to-coverage"); require(!pixel.stencilRefExportEnable, "a legacy request exported a stencil reference"); + require(pixel.rasterPrimitive == RasterPrimitive::Triangle, "a legacy request drew something other than triangles"); require(pixel.inputAddr == 2u && pixel.hasPerspectiveCenterVgpr && pixel.targetOutputMode[0] == 9u, "legacy pixel layout was misread"); require(pixel.conservativeZExport == (version >= 7u ? ConservativeZExport::GreaterThanZ : ConservativeZExport::AnyZ), "legacy conservative Z layout was misread"); require(!pixel.orderedPixelShader, "a legacy request became a primitive-ordered pixel shader"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelAncillaryWord.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelAncillaryWord.cpp new file mode 100644 index 0000000000..559a514ed7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelAncillaryWord.cpp @@ -0,0 +1,306 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Rgba32FloatInfo = 0x8738u; +constexpr std::uint32_t LinearAttrib3 = 0x09000000u; +constexpr std::uint32_t Float32AbgrExport = 9; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +constexpr std::array, 3> Triangle{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +alignas(256) std::array, 5> Programs{}; +std::size_t programCount = 0; + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +std::span Program(std::uint32_t ancillaryVgpr, bool wholeWord) { + Require(programCount < Programs.size(), "too many test programs"); + auto& code = Programs[programCount++]; + std::size_t at = 0; + if (wholeWord) { + code[at++] = 0x36000000u | (20u << 17u) | (ancillaryVgpr << 9u) | 0xffu; + code[at++] = 0x0000ffffu; + code[at++] = 0x2c000000u | (21u << 17u) | (ancillaryVgpr << 9u) | 0x90u; + } else { + code[at++] = 0xd5480000u | 20u; + code[at++] = (0x84u << 18u) | (0x88u << 9u) | (256u + ancillaryVgpr); + code[at++] = 0x7e000280u | (21u << 17u); + } + code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u); + code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u); + code[at++] = 0x7e000280u | (22u << 17u); + code[at++] = 0x7e0002f2u | (23u << 17u); + code[at++] = 0xf800180fu; + code[at++] = 0x17161514u; + code[at++] = 0xbf810000u; + return std::span(code.data(), at); +} + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "cannot map the color target"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "cannot allocate the color target"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + std::uint64_t Address() const { return reinterpret_cast(data); } + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = info; + cx[0x31d] = 0; + cx[0x323] = 0; + cx[0x324] = 0; + cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); + cx[0x3b8] = LinearAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + color.exportIndex = 0; + return color; +} + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, ShaderRecompiler::RasterPrimitive primitive, std::span pixelCode) { + const auto target = device.Target(); + constexpr std::uint32_t waveSize = 64; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = inputAddr; + pixel.ancillary = true; + pixel.rasterPrimitive = primitive; + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + VkPipelineColorBlendAttachmentState blend{}; + blend.blendEnable = VK_FALSE; + blend.colorWriteMask = 0xf; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.colors = {color}; + state.color = color; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blends = {blend}; + state.blend = blend; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); +} + +std::vector ReadBack(AgcDriver::VulkanDevice& device, std::uint64_t address, std::size_t bytes) { + AgcDriver::Graphics::StorageTexture::FlushPending(address, bytes, nullptr, "test read-back"); + device.WaitIdle(); + std::vector stored(bytes); + AgcDriver::GuestMemory::Read(address, std::as_writable_bytes(std::span(stored)), 1); + return stored; +} + +void Expect(AgcDriver::VulkanDevice& device, std::uint32_t inputAddr, std::uint32_t ancillaryVgpr, ShaderRecompiler::RasterPrimitive primitive, const std::string& what) { + constexpr std::size_t bytes = std::size_t{Width} * Height * 16u; + Require(AgcDriver::Graphics::ColorTargetLayout(Width, Height, AgcDriver::Graphics::ColorTileMode::Linear, 16).Bytes() == bytes, what + ": the linear target is padded"); + Block block(bytes); + std::fill(block.data, block.data + bytes, std::uint8_t{0xff}); + const auto color = DecodeTarget(block, Rgba32FloatInfo); + Require(color.format == VK_FORMAT_R32G32B32A32_SFLOAT && color.elementBytes == 16 && color.bytes == bytes, what + ": the target decoded to the wrong format"); + Draw(device, color, inputAddr, primitive, Program(ancillaryVgpr, true)); + const auto stored = ReadBack(device, block.Address(), bytes); + const auto word = static_cast(primitive); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + std::array texel{}; + std::memcpy(texel.data(), stored.data() + (static_cast(y) * Width + x) * 16u, sizeof(texel)); + const std::array expected{static_cast(word & 0xffffu), static_cast(word >> 16u), 0.0f, 1.0f}; + Require(texel == expected, what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")"); + } + } +} + +std::uint64_t PixelModule(const ShaderRecompiler::SpirvTarget& target, std::span code, ShaderRecompiler::RasterPrimitive primitive) { + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); + pixel.ancillary = true; + pixel.rasterPrimitive = primitive; + pixel.targetOutputMode.at(0) = Float32AbgrExport; + pixel.targetExportMapping.fill(0xe4u); + const std::vector userData(8, 0u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Fragment, reinterpret_cast(code.data()), code, 0, {}}, + {64, 0, userData, std::nullopt, pixel, std::nullopt, memory}, + target, + PixelPushLayout(0, target) + }; + return ShaderRecompiler::Recompile(request).PipelineVariantId(); +} + +void ExpectModules(const ShaderRecompiler::SpirvTarget& target, bool wholeWord, const std::string& what) { + using ShaderRecompiler::RasterPrimitive; + const auto code = Program(0u, wholeWord); + const auto triangle = PixelModule(target, code, RasterPrimitive::Triangle); + Require(PixelModule(target, code, RasterPrimitive::Triangle) == triangle, what + ": the same primitive class built a second module"); + for (const auto primitive : {RasterPrimitive::Line, RasterPrimitive::Rectangle}) { + Require((PixelModule(target, code, primitive) != triangle) == wholeWord, what + (wholeWord ? ": a line or rectangle draw reused the triangle module" : ": a line or rectangle draw built its own module")); + } +} + +void ExpectPrimitive(VkPrimitiveTopology topology, bool rectList, bool tessellated, ShaderRecompiler::RasterPrimitive expected, const std::string& what) { + AgcDriver::Graphics::State state{}; + state.topology = topology; + state.rectList = rectList; + if (tessellated) state.stages.tessellation = ShaderRecompiler::TessellationConfiguration{}; + Require(AgcDriver::Graphics::RasterPrimitiveOf(state) == expected, what + ": wrong primitive class"); +} + +} + +int main() { + try { + using ShaderRecompiler::PixelInput; + using ShaderRecompiler::PixelInputBit; + using ShaderRecompiler::RasterPrimitive; + ExpectPrimitive(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, false, false, RasterPrimitive::Triangle, "triangle strip"); + ExpectPrimitive(VK_PRIMITIVE_TOPOLOGY_LINE_LIST, false, false, RasterPrimitive::Line, "line list"); + ExpectPrimitive(VK_PRIMITIVE_TOPOLOGY_POINT_LIST, false, false, RasterPrimitive::Point, "point list"); + ExpectPrimitive(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, true, false, RasterPrimitive::Rectangle, "rect list"); + ExpectPrimitive(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, false, true, RasterPrimitive::Triangle, "tessellated patches"); + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Expect(*device, PixelInputBit(PixelInput::Ancillary), 0u, RasterPrimitive::Triangle, "ANCILLARY alone, triangle"); + Expect(*device, PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::Ancillary), 2u, RasterPrimitive::Rectangle, "ANCILLARY after the perspective center, rectangle"); + Expect(*device, PixelInputBit(PixelInput::Ancillary), 0u, RasterPrimitive::Line, "ANCILLARY alone, line"); + ExpectModules(device->Target(), true, "whole word"); + ExpectModules(device->Target(), false, "sample field only"); + std::puts("pixel ancillary word tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 69ce4d7c26..d1529fbc22 100644 --- a/core/libs/tests/PixelAncillary.cpp +++ b/core/libs/tests/PixelAncillary.cpp @@ -3,6 +3,7 @@ #include "Optimization/DeadCodeEliminator.hpp" #include "Optimization/MaskedSelectEliminator.hpp" #include "Optimization/ShaderInfoCollector.hpp" +#include "Recompiler.hpp" #include #include #include @@ -18,6 +19,7 @@ struct Row { std::uint32_t sample; std::uint32_t layer; bool frontFacing; + RasterPrimitive primitive = RasterPrimitive::Triangle; }; constexpr std::array Rows {{{0u, 0u, false}, {0u, 0u, true}, {15u, 0x1FFFu, false}, {15u, 0x1FFFu, true}, {0u, 0x1FFFu, true}, {15u, 0u, false}}}; @@ -153,6 +155,7 @@ static std::uint32_t Evaluate(const IrValue* value, const Row& row) { case StageInputKind::SampleId: return row.sample; case StageInputKind::Layer: return row.layer; case StageInputKind::FrontFacing: return row.frontFacing ? 1u : 0u; + case StageInputKind::RasterPrimitive: return static_cast(row.primitive); default: break; } break; @@ -305,19 +308,21 @@ static HelperProgram HelperOnly(IrProgram& program, HelperRead read, std::uint32 static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); return HelperProgram {&written, &layer, &value}; } -static void LowerMasked(IrProgram& program) { +static void LowerMasked(IrProgram& program, bool compose = false) { ConstantFolder().Fold(program); DeadCodeEliminator().RemoveIdentities(program); DeadCodeEliminator().Eliminate(program); static_cast(MaskedSelectEliminator().Eliminate(program)); DeadCodeEliminator().Eliminate(program); + if (compose && ConstantFolder().ComposePackedAncillary(program)) DeadCodeEliminator().Eliminate(program); const ShaderPixelInputInfo pixel {}; ShaderInfoCollector().Collect(program, ShaderStageInputInfo {nullptr, &pixel, nullptr}); } static void HelperLanesOnly(std::uint32_t chain = 0u) { IrProgram program; const HelperProgram built = HelperOnly(program, HelperRead::Export, chain); - LowerMasked(program); + LowerMasked(program, true); + Require(!program.Info().rasterPrimitive); for (const IrBlock* block : program.BlockOrder()) { for (const IrValue* inst : block->Instructions()) { Require(inst->Opcode() != IrOpcode::GetBuiltin || static_cast(inst->Argument(0)->Resolve()->ImmediateU32()) != StageInputKind::PackedAncillary); @@ -427,6 +432,62 @@ static void HelperLoopRefused(LoopRead read) { } Require(false); } +static bool AncillaryRemains(const IrProgram& program) { + for (const IrBlock* block : program.BlockOrder()) { + for (const IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::GetBuiltin && static_cast(inst->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::PackedAncillary) return true; + } + } + return false; +} +static void ExportedWholeWord() { + IrProgram program; + auto& entry = Begin(program); + auto& body = program.CreateBlock(); + auto& merge = program.CreateBlock(); + program.BlockOrder().push_back(&body); + program.BlockOrder().push_back(&merge); + entry.AddBranch(&body); + entry.AddBranch(&merge); + body.AddBranch(&merge); + IrBuilder builder(program); + builder.SetInsertionPoint(entry); + IrValue& helper = builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::HelperInvocation)), &builder.Constant(0u)}); + IrValue& live = builder.IEqual(helper, builder.Constant(0u)); + IrValue& ancillary = AncillaryBuiltin(builder); + IrValue& tested = builder.INotEqual(builder.Select(FrontFacingBuiltin(builder), builder.Constant(7u), builder.Constant(0u)), builder.Constant(0u)); + builder.SetInsertionPoint(body); + IrValue& written = builder.Select(live, builder.Constant(SelectOther), ancillary); + IrValue& carried = program.CreateValue(IrOpcode::Phi, IrType::U32); + merge.AppendInstruction(&carried); + carried.AddPhiOperand(&entry, &ancillary); + carried.AddPhiOperand(&body, &written); + builder.SetInsertionPoint(merge); + const auto channel = [&](float value) -> IrValue& { + IrValue& half = builder.ConstantF32(value); + return builder.Select(tested, builder.Emit(IrOpcode::PackFloat2x16Rtz, IrType::U32, {&half, &half}), carried); + }; + IrValue& data = builder.Emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&channel(0.25f), &channel(0.75f), &builder.Constant(0u), &builder.Constant(0u)}); + program.Metadata().exportInfo.push_back(ExportInfo {.kind = ExportTargetKind::Mrt, .index = 0u, .en = 0xFu, .done = true, .vm = true}); + static_cast(builder.Emit(IrOpcode::SetAttribute, IrType::Void, {&data, &live}, ExportFlags {0u, 0u})); + static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); + LowerMasked(program, true); + Require(!AncillaryRemains(program) && program.Info().rasterPrimitive); + 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}}}; + for (const Row& row : words) { + Require(Evaluate(word, row) == (static_cast(row.primitive) | (row.sample << 8u) | (row.layer << 16u))); + } +} +static void WholeWordReplacesRefusal(HelperRead read) { + IrProgram program; + const HelperProgram built = HelperOnly(program, read); + LowerMasked(program, true); + Require(!AncillaryRemains(program) && program.Info().rasterPrimitive); + Require(built.written->Parent() != nullptr); + const Row row {9u, 0x123u, false, RasterPrimitive::Rectangle}; + Require(Evaluate(built.written->Argument(2), row) == (3u | (9u << 8u) | (0x123u << 16u))); +} enum class StartingQuad { Kept, BallotAfterBranch, BallotOfOtherLanes, ComputeStage }; static IrValue& StartingQuadMasked(IrProgram& program, StartingQuad shape) { @@ -544,6 +605,10 @@ int main() { HelperLoopOnly(LoopRead::ExportInside); HelperLoopOnly(LoopRead::UniformLoop); HelperLoopRefused(LoopRead::RevivedExportInside); + ExportedWholeWord(); + WholeWordReplacesRefusal(HelperRead::FullWaveExport); + WholeWordReplacesRefusal(HelperRead::DataTestExport); + WholeWordReplacesRefusal(HelperRead::Ballot); StartingQuadOnly(); StartingQuadRefused(StartingQuad::BallotAfterBranch); StartingQuadRefused(StartingQuad::BallotOfOtherLanes); diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 84e55eeaf5..821df18ff4 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -243,6 +243,14 @@ ColorExportPacking readColorExportPacking(Reader& reader) { return static_cast(value); } +RasterPrimitive readRasterPrimitive(Reader& reader) { + const auto value = reader.ReadU8(); + if (value > static_cast(RasterPrimitive::Rectangle)) { + throw std::runtime_error("RequestSerializer: invalid RasterPrimitive value"); + } + return static_cast(value); +} + void writeShaderBinary(Writer& writer, const ShaderBinary& binary) { writer.WriteU8(static_cast(binary.stage)); writer.WriteU64(binary.codeAddress); @@ -326,6 +334,7 @@ void writePixelInfo(Writer& writer, const ShaderPixelStageInfo& info) { writer.WriteBool(info.dualSourceBlend); writer.WriteBool(info.alphaToCoverage); writer.WriteBool(info.stencilRefExportEnable); + writer.WriteU8(static_cast(info.rasterPrimitive)); } ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) { @@ -375,6 +384,7 @@ ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) { if (version >= 15u) info.dualSourceBlend = reader.ReadBool(); if (version >= 16u) info.alphaToCoverage = reader.ReadBool(); if (version >= 17u) info.stencilRefExportEnable = reader.ReadBool(); + if (version >= 18u) info.rasterPrimitive = readRasterPrimitive(reader); 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 aa1f183eea..089d299cf7 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp @@ -35,6 +35,7 @@ struct ShaderInfo { bool bdaWrites = false; bool usesFaultBuffer = false; bool dispatchThreadLimit = false; + bool rasterPrimitive = 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 2c5b7c3792..6431b6a408 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp @@ -43,6 +43,7 @@ enum class StageInputKind { DispatchThreadLimit, Parameter, HelperInvocation, + RasterPrimitive, }; enum class StageOutputKind { diff --git a/core/shader/recompiler/Optimization/include/Optimization/ConstantFolder.hpp b/core/shader/recompiler/Optimization/include/Optimization/ConstantFolder.hpp index 6be55fb231..d2df4ed3cc 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ConstantFolder.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ConstantFolder.hpp @@ -14,6 +14,8 @@ public: void Fold(IrProgram& program, std::span blocks) const; + [[nodiscard]] bool ComposePackedAncillary(IrProgram& program) const; + private: [[nodiscard]] bool tryFoldValue(IrProgram& program, IrValue& value) const; }; diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index b70fbab7b8..61679e1f56 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -321,9 +321,25 @@ std::optional packedField(std::uint32_t offset, std::uint32_t count return static_cast(field - packedFields.begin()); } +bool isPackedAncillary(const IrValue& builtin) { + auto& kind = resolveArg(builtin, 0); + return isImmediate(kind, IrType::U32) && static_cast(kind.ImmediateU32()) == StageInputKind::PackedAncillary; +} + +IrValue& insertBefore(IrProgram& program, IrValue& next, IrOpcode opcode, IrValue& lhs, IrValue& rhs) { + IrValue& created = program.CreateValue(opcode, IrType::U32); + created.AddArgument(&lhs); + created.AddArgument(&rhs); + next.Parent()->InsertInstructionBefore(&next, &created); + return created; +} + +IrValue& insertBuiltin(IrProgram& program, IrBuilder& builder, IrValue& next, StageInputKind kind) { + return insertBefore(program, next, IrOpcode::GetBuiltin, builder.Constant(static_cast(kind)), builder.Constant(0u)); +} + void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) { - auto& kind = resolveArg(ancillary, 0); - if (!isImmediate(kind, IrType::U32) || static_cast(kind.ImmediateU32()) != StageInputKind::PackedAncillary) { + if (!isPackedAncillary(ancillary)) { return; } const std::vector uses = ancillary.OperandUses(); @@ -369,21 +385,12 @@ void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil std::array fields {}; const auto field = [&](std::size_t index) -> IrValue& { if (fields[index] == nullptr) { - const auto builtin = index == 0u ? StageInputKind::SampleId : StageInputKind::Layer; - IrValue& created = program.CreateValue(IrOpcode::GetBuiltin, IrType::U32); - created.AddArgument(&builder.Constant(static_cast(builtin))); - created.AddArgument(&builder.Constant(0u)); - ancillary.Parent()->InsertInstructionBefore(&ancillary, &created); - fields[index] = &created; + fields[index] = &insertBuiltin(program, builder, ancillary, index == 0u ? StageInputKind::SampleId : StageInputKind::Layer); } return *fields[index]; }; const auto emit = [&](IrOpcode opcode, IrValue& lhs, IrValue& rhs) -> IrValue& { - IrValue& created = program.CreateValue(opcode, IrType::U32); - created.AddArgument(&lhs); - created.AddArgument(&rhs); - ancillary.Parent()->InsertInstructionBefore(&ancillary, &created); - return created; + return insertBefore(program, ancillary, opcode, lhs, rhs); }; if (!forwarded.empty()) { IrValue& sample = emit(IrOpcode::ShiftLeftLogical32, field(0u), builder.Constant(8u)); @@ -846,4 +853,24 @@ void ConstantFolder::Fold(IrProgram& program) const { Fold(program, program.BlockOrder()); } +bool ConstantFolder::ComposePackedAncillary(IrProgram& program) const { + std::vector words; + for (IrBlock* block : program.BlockOrder()) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::GetBuiltin && inst->HasUses() && isPackedAncillary(*inst)) { + words.push_back(inst); + } + } + } + IrBuilder builder(program); + 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)); + IrValue& layer = insertBefore(program, *word, IrOpcode::ShiftLeftLogical32, insertBuiltin(program, builder, *word, StageInputKind::Layer), builder.Constant(16u)); + IrValue& low = insertBefore(program, *word, IrOpcode::BitwiseOr32, primitive, sample); + word->ReplaceAllUsesWith(&insertBefore(program, *word, IrOpcode::BitwiseOr32, low, layer)); + } + return !words.empty(); +} + } diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index e9481485b6..6d60d53a37 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -143,6 +143,7 @@ void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inpu } case StageInputKind::Layer: case StageInputKind::SampleId: + case StageInputKind::RasterPrimitive: if (program.Resources().stage != IrShaderStage::Pixel || component != 0u) { return Fail("typed pixel scalar input is invalid"); } @@ -386,6 +387,9 @@ void CollectBuiltinInputs(const IrProgram& program, ShaderInfo& info) { case StageInputKind::DispatchThreadLimit: info.dispatchThreadLimit = true; break; + case StageInputKind::RasterPrimitive: + info.rasterPrimitive = true; + break; case StageInputKind::PackedAncillary: case StageInputKind::Parameter: break; diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index 878b0a74dd..ddae3a272f 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -26,6 +26,7 @@ inline constexpr std::uint32_t PushDataOffset = 8188u; 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 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 df79d3e838..c66b7726b0 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -237,6 +237,9 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat } deadCodeEliminator.Eliminate(program); } + if (constantFolder.ComposePackedAncillary(program)) { + deadCodeEliminator.Eliminate(program); + } }; simplify(); if (stageKind == ShaderStageKind::Pixel && !translateOptions.fragmentShaderBarycentricEnabled && !HostInterpolationChecker{}.Lower(program, *inputInfo.pixel)) { @@ -772,6 +775,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (request.context.pixel) { 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)); } result.vertexAttributes.reserve(result.vertexInputs.size()); std::array vertexClasses{}; @@ -818,6 +822,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (request.context.pixel) { 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 (!artifact.vertexInputPatches.empty()) { for (const auto& input : result.vertexInputs) { @@ -1018,6 +1023,7 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh for (const auto mapping : request.context.pixel->targetExportMapping) mix(mapping); for (const auto packing : request.context.pixel->targetExportPacking) mix(static_cast(packing)); mix(request.context.pixel->dualSourceBlend); + mix(static_cast(request.context.pixel->rasterPrimitive)); } 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 eb84c86ddc..79eafe04e2 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -113,6 +113,13 @@ enum class ColorExportPacking : std::uint8_t { Unorm10_11_11 }; +enum class RasterPrimitive : std::uint8_t { + Point, + Line, + Triangle, + Rectangle +}; + struct ShaderPixelStageInfo { std::uint32_t interpolatorCount; std::array interpolatorSettings; @@ -142,6 +149,7 @@ struct ShaderPixelStageInfo { bool dualSourceBlend; bool alphaToCoverage; bool stencilRefExportEnable; + RasterPrimitive rasterPrimitive = RasterPrimitive::Triangle; }; struct ShaderVertexBufferResource { diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 1d85b66683..8e1c707a91 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -546,6 +546,7 @@ void encodeInfo(Writer& writer, const CompiledShaderInfo& compiled) { writer.Value(info.bdaWrites); writer.Value(info.usesFaultBuffer); writer.Value(info.dispatchThreadLimit); + writer.Value(info.rasterPrimitive); encodeLayout(writer, compiled.bindings); } @@ -655,6 +656,7 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) { reader.Value(info.bdaWrites); reader.Value(info.usesFaultBuffer); reader.Value(info.dispatchThreadLimit); + reader.Value(info.rasterPrimitive); decodeLayout(reader, compiled.bindings); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 9747d875b6..7a9a2d3214 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 = 27; +inline constexpr std::uint32_t FormatVersion = 28; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 236724c004..1e10a6e903 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -45,6 +45,9 @@ std::uint32_t EmitBuiltinU32(SpirvEmitterState& state, StageInputKind kind, std: if (kind == StageInputKind::DispatchThreadLimit) { return EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.DispatchThreadLimitDword() + component); } + if (kind == StageInputKind::RasterPrimitive) { + return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::RasterPrimitive, static_cast(RasterPrimitive::Triangle)); + } 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 9b612aadd2..49772b4d20 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -2,7 +2,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, and their equality with the SampleId and Layer builtins is inferred, not measured. A value passed through a select, move or phi is rebuilt from those two fields with bits 0-7, 12-15 and 29-31 zero. That is exact only because every read of the word must be an extract inside one modelled field; any other read throws. The other bits are unmodelled and their RDNA 2 layout is unknown: the RDNA 3.5 ISA is reported to give prim type at bits 0-1, VRS rates at bits 2-5 and eye id at bit 7, which was not checked against RDNA 2 +- [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, and their equality with the SampleId and Layer builtins is inferred, not measured. 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 and layer fields, 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. The VRS rates and the eye id are zero because the driver supports neither variable-rate shading nor multiview; a title that enables either would read other bits on the console. 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`), and the layer field reads Layer, which is 0 while the driver ignores vertex layer exports - [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