feat(shader): give reads the masked-select pass keeps the whole pixel ancillary word

A packed ancillary word that still reaches an export, or any other read
the masked-select pass cannot drop, no longer stops the recompile. After
that pass every remaining use gets the composed word: the primitive class
at bits 0-1, SampleId at bits 8-11 and Layer at bits 16-28, with every
other bit zero. Those lanes do export the ancillary VGPR on the console,
so the word has to be right, not just present.

The primitive class (0 point, 1 line, 2 triangle, 3 rectangle) comes from
the draw. RasterPrimitiveOf maps rect lists to 3, line lists on the vertex
path to 1 and everything else the driver accepts to 2, because its
geometry and tessellation paths only output triangles. It reaches the
shader as pipeline specialization constant 8233, the way dual-source
blending does: a request field that is folded into the specialized
module and is part of the result memo, the draw key and the serialized
request (version 17).

Only shaders that read the class pay for it. ShaderInfo records the read
(disk cache format 28), and only those shaders put the class in their
module key, so a shader that reads just the sample or layer field keeps
one module for every primitive class. The draw sets the class only when
the pixel shader loads the ancillary VGPR, so other draws keep the draw
cache entries they have today.

The VRS rates (bits 2-5) and the eye id (bit 7) are 0 because the driver
supports neither VRS nor multiview. SampleId is 0 because the driver
rejects MSAA and per-sample shading, and Layer is 0 while it ignores
vertex layer exports. TechnicalDebt.md records the layout sources and
these assumptions.

Tests:
- pixel_ancillary builds an export to target 0 whose two channels select
  between a packed value and a phi carrying the word. It checks the word
  for each primitive class, sample and layer, checks that the three
  shapes the masked-select pass keeps now compile with the composed word,
  and that a program whose select the pass drops reads no class.
- agc_driver_pixel_ancillary_word exports the word from a draw and reads
  back 2, 3 and 1 for triangle, rectangle and line. A shader that reads
  only the sample field shares one module across the classes, and the
  whole-word shader gets one per class. It also checks RasterPrimitiveOf.
- agc_shader_memory covers the version 17 round trip, and
  agc_shader_disk_cache the new flag.

Both ancillary tests fail with the old refusal when the composition is
disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlxcBTWandClaude Opus 5.5 authored and Dean Galvin committed 2026-10-13 13:06:21 +13:00
1 parent 1c5e62878a
commit 18d9874a06
22 files changed
+473 -20

No files matched your search

@@ -115,6 +115,7 @@ std::shared_ptr<DrawDecode> 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;
}
@@ -70,6 +70,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
for (const auto value : pixel.targetExportPacking) mix(static_cast<std::uint64_t>(value));
mix(pixel.dualSourceBlend);
mix(pixel.alphaToCoverage);
mix(static_cast<std::uint64_t>(pixel.rasterPrimitive));
std::lock_guard cacheLock(drawCacheMutex);
++drawEntryCounters.lookups;
const auto found = drawCache.find(drawKey);
@@ -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;
}
@@ -125,6 +125,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup = false);
std::array<VkSampleLocationEXT, 8> SampleLocations(const State& state);
std::array<std::uint8_t, 8> ExportMappings(const State& state);
std::array<ShaderRecompiler::ColorExportPacking, 8> 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);
@@ -951,6 +951,16 @@ std::array<ShaderRecompiler::ColorExportPacking, 8> 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<std::uint8_t, 8> ExportMappings(const State& state) {
std::array<std::uint8_t, 8> mappings{};
mappings.fill(0xe4u);
@@ -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;
@@ -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<void>(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<void>(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<void>(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");
@@ -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 <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
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<std::uint32_t, 6> VertexCode{
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
};
constexpr std::array<std::array<float, 4>, 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<std::array<std::uint32_t, 16>, 5> Programs{};
std::size_t programCount = 0;
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
}
std::span<const std::uint32_t> 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<const std::uint32_t>(code.data(), at);
}
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
#ifdef _WIN32
if (watched) {
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
} else {
data = static_cast<std::uint8_t*>(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<std::uintptr_t>(raw);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
data = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
} else {
data = static_cast<std::uint8_t*>(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<std::uintptr_t>(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<std::uint32_t>(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<std::uint32_t>(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<const std::uint32_t> pixelCode) {
const auto target = device.Target();
constexpr std::uint32_t waveSize = 64;
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast<std::uint32_t>(Triangle.size()));
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
ShaderRecompiler::RecompileRequest vertex{
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(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<std::uint32_t>(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<std::uint32_t> pixelUserData(8, 0u);
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}}};
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(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<AgcDriver::Graphics::CompiledShader, 2> 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<float>(Height), static_cast<float>(Width), -static_cast<float>(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<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
}
std::vector<std::uint8_t> 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<std::uint8_t> 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<std::uint32_t>(primitive);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
std::array<float, 4> texel{};
std::memcpy(texel.data(), stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u, sizeof(texel));
const std::array<float, 4> expected{static_cast<float>(word & 0xffffu), static_cast<float>(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<const std::uint32_t> 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<std::uint32_t> userData(8, 0u);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::RecompileRequest request{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(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;
}
}
+67 -2
View File
@@ -3,6 +3,7 @@
#include "Optimization/DeadCodeEliminator.hpp"
#include "Optimization/MaskedSelectEliminator.hpp"
#include "Optimization/ShaderInfoCollector.hpp"
#include "Recompiler.hpp"
#include <array>
#include <cstdint>
#include <stdexcept>
@@ -18,6 +19,7 @@ struct Row {
std::uint32_t sample;
std::uint32_t layer;
bool frontFacing;
RasterPrimitive primitive = RasterPrimitive::Triangle;
};
constexpr std::array<Row, 6> 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<std::uint32_t>(row.primitive);
default: break;
}
break;
@@ -305,19 +308,21 @@ static HelperProgram HelperOnly(IrProgram& program, HelperRead read, std::uint32
static_cast<void>(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<void>(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<StageInputKind>(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<StageInputKind>(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<std::uint32_t>(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<void>(builder.Emit(IrOpcode::SetAttribute, IrType::Void, {&data, &live}, ExportFlags {0u, 0u}));
static_cast<void>(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<Row, 5> 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<std::uint32_t>(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);
@@ -243,6 +243,14 @@ ColorExportPacking readColorExportPacking(Reader& reader) {
return static_cast<ColorExportPacking>(value);
}
RasterPrimitive readRasterPrimitive(Reader& reader) {
const auto value = reader.ReadU8();
if (value > static_cast<std::uint8_t>(RasterPrimitive::Rectangle)) {
throw std::runtime_error("RequestSerializer: invalid RasterPrimitive value");
}
return static_cast<RasterPrimitive>(value);
}
void writeShaderBinary(Writer& writer, const ShaderBinary& binary) {
writer.WriteU8(static_cast<std::uint8_t>(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<std::uint8_t>(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) |
@@ -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;
};
@@ -43,6 +43,7 @@ enum class StageInputKind {
DispatchThreadLimit,
Parameter,
HelperInvocation,
RasterPrimitive,
};
enum class StageOutputKind {
@@ -14,6 +14,8 @@ public:
void Fold(IrProgram& program, std::span<IrBlock* const> blocks) const;
[[nodiscard]] bool ComposePackedAncillary(IrProgram& program) const;
private:
[[nodiscard]] bool tryFoldValue(IrProgram& program, IrValue& value) const;
};
@@ -321,9 +321,25 @@ std::optional<std::size_t> packedField(std::uint32_t offset, std::uint32_t count
return static_cast<std::size_t>(field - packedFields.begin());
}
bool isPackedAncillary(const IrValue& builtin) {
auto& kind = resolveArg(builtin, 0);
return isImmediate(kind, IrType::U32) && static_cast<StageInputKind>(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<std::uint32_t>(kind)), builder.Constant(0u));
}
void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) {
auto& kind = resolveArg(ancillary, 0);
if (!isImmediate(kind, IrType::U32) || static_cast<StageInputKind>(kind.ImmediateU32()) != StageInputKind::PackedAncillary) {
if (!isPackedAncillary(ancillary)) {
return;
}
const std::vector<IrUse> uses = ancillary.OperandUses();
@@ -369,21 +385,12 @@ void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil
std::array<IrValue*, 2> 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<std::uint32_t>(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<IrValue*> 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();
}
}
@@ -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;
@@ -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;
+6
View File
@@ -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<std::uint32_t>(packing));
moduleKey.push_back(request.context.pixel->dualSourceBlend ? 1u : 0u);
if (variant.info.info.rasterPrimitive) moduleKey.push_back(static_cast<std::uint32_t>(request.context.pixel->rasterPrimitive));
}
result.vertexAttributes.reserve(result.vertexInputs.size());
std::array<std::uint32_t, ShaderVertexStageInfo::MaxResources> 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<std::uint64_t>(packing));
mix(request.context.pixel->dualSourceBlend);
mix(static_cast<std::uint64_t>(request.context.pixel->rasterPrimitive));
}
if (request.context.vertex) {
const auto& vertex = *request.context.vertex;
+8
View File
@@ -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<std::uint32_t, 32> interpolatorSettings;
@@ -142,6 +149,7 @@ struct ShaderPixelStageInfo {
bool dualSourceBlend;
bool alphaToCoverage;
bool stencilRefExportEnable;
RasterPrimitive rasterPrimitive = RasterPrimitive::Triangle;
};
struct ShaderVertexBufferResource {
@@ -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);
}
+1 -1
View File
@@ -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,
@@ -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<std::uint32_t>(RasterPrimitive::Triangle));
}
if (state.laneCount == 2 && (kind == StageInputKind::LocalInvocationId || kind == StageInputKind::GlobalInvocationId)) {
const auto* workgroup = ShaderWorkgroupInputFor(state);
if (workgroup == nullptr) {
+1 -1
View File
@@ -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