test(agc): exercise VCC-backed sampler words on Vulkan

This commit is contained in:
Romans Pokrovskis committed 2026-10-16 15:59:37 +03:00
1 parent 82ddae75b6
commit 7b55f6ffcc
2 files changed
+167

No files matched your search

@@ -0,0 +1,166 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Size = 8;
constexpr std::uint32_t Format32SInt = 21;
constexpr std::uint32_t Format8888UNorm = 56;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t SwizzleX111 = 0x24cu;
constexpr std::uint32_t IdentitySwizzle = 0xfacu;
constexpr std::uint32_t SampleX = 3;
constexpr std::uint32_t SampleY = 5;
constexpr float Tolerance = 1.0f / 64.0f;
constexpr std::array<std::uint32_t, 4> LinearSampler{0x00000092u, 0x00fff000u, 0x00500000u, 0u};
alignas(256) std::array<std::uint32_t, Threads * 8> Output{};
alignas(256) std::array<std::uint8_t, 4096> Integers{};
alignas(256) std::array<std::uint8_t, 4096> Colors{};
alignas(256) constexpr std::array<std::uint32_t, 16> Code{
0x34060085u, 0x7e2802ffu, 0x3ef00000u, 0x7e2a02ffu, 0x3f300000u, 0xf09c0f08u, 0x00610a14u, 0xf09c0f08u,
0x00640e14u, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xe0781010u, 0x80000e03u, 0xbf810000u, 0xbf810000u,
};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::uint32_t PitchBytes(std::uint32_t format, std::size_t storage) {
const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, format, Size, Size, 1u);
Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= storage, "image sample VCC sampler: a texture does not fit its storage");
return mips[0].pitchBytes;
}
std::int32_t Integer(std::uint32_t x, std::uint32_t y) {
return -static_cast<std::int32_t>(100u * x + 7u * y + 1u);
}
void Fill() {
const auto integerPitch = PitchBytes(Format32SInt, Integers.size());
const auto colorPitch = PitchBytes(Format8888UNorm, Colors.size());
Integers.fill(0x5au);
Colors.fill(0x5au);
for (std::uint32_t y = 0; y < Size; ++y) {
for (std::uint32_t x = 0; x < Size; ++x) {
const auto bits = static_cast<std::uint32_t>(Integer(x, y));
auto* integer = &Integers[static_cast<std::size_t>(y) * integerPitch + x * 4u];
for (std::uint32_t byte = 0; byte < 4u; ++byte) integer[byte] = static_cast<std::uint8_t>(bits >> (byte * 8u));
auto* color = &Colors[static_cast<std::size_t>(y) * colorPitch + x * 4u];
color[0] = x > SampleX ? 255u : 0u;
color[1] = 0u;
color[2] = 0u;
color[3] = 255u;
}
}
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::array<std::uint32_t, 8> TextureDescriptor(const void* texels, std::uint32_t format, std::uint32_t swizzle) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texels));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | (((Size - 1u) & 3u) << 30u),
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
swizzle | (Type2D << 28u),
0u,
0u,
0u,
0u,
};
}
void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool border) {
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(24, 0u);
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
const auto integers = TextureDescriptor(Integers.data(), Format32SInt, SwizzleX111);
const auto colors = TextureDescriptor(Colors.data(), Format8888UNorm, IdentitySwizzle);
std::copy(output.begin(), output.end(), userData.begin());
std::copy(integers.begin(), integers.end(), userData.begin() + 4);
auto sampler = LinearSampler;
if (border) { sampler[0] = 0x1b6u; sampler[3] = 2u << 30u; }
std::copy(sampler.begin(), sampler.end(), userData.begin() + 12);
std::copy(colors.begin(), colors.end(), userData.begin() + 16);
alignas(256) std::array<std::uint32_t, Code.size() + 4u> words{};
words[0] = 0xbee8030cu;
words[1] = 0xbee9030du;
words[2] = 0xbeea030eu;
words[3] = 0xbeeb030fu;
std::copy(Code.begin(), Code.end(), words.begin() + 4);
for (const auto index : {10u, 12u}) words[index] = (words[index] & ~(31u << 21u)) | (26u << 21u);
if (border) { words[6] = 0xbf800000u; words[8] = 0xbf800000u; }
const std::span<const std::uint32_t> code(words);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool border) {
Fill();
Run(device, waveSize, border);
const std::array<std::uint32_t, 4> integer{border ? 1u : static_cast<std::uint32_t>(Integer(SampleX, SampleY)), 1u, 1u, 1u};
const std::array<float, 4> color = border ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : std::array<float, 4>{0.25f, 0.0f, 0.0f, 1.0f};
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
for (std::uint32_t component = 0; component < 4u; ++component) {
const auto actual = Output[lane * 8u + component];
Require(actual == integer[component], "32_SINT image_sample_lz through a bilinear sampler shared with 8_8_8_8_UNORM: lane " + std::to_string(lane) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + Hex(integer[component]));
}
for (std::uint32_t component = 0; component < 4u; ++component) {
const auto bits = Output[lane * 8u + 4u + component];
const auto actual = std::bit_cast<float>(bits);
Require(std::fabs(actual - color[component]) <= Tolerance, "8_8_8_8_UNORM image_sample_lz through a bilinear sampler shared with 32_SINT: lane " + std::to_string(lane) + " component " + std::to_string(component) + " is " + Hex(bits) + ", expected " + std::to_string(color[component]));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
for (const auto waveSize : {32u, 64u}) {
Check(*device, waveSize, false);
Check(*device, waveSize, true);
}
std::puts("image sample VCC sampler tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
+1
View File
@@ -8,6 +8,7 @@
### Unknown function info
- [VCC-backed MIMG samplers](../../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp): the s104/s105/vcc_lo/vcc_hi tuple follows LLVM's [SGPR register classes](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L452) and [gfx10 sampler operand](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/MIMGInstructions.td#L1379), using the existing VCC word reads. Recompiled wave32/wave64 reads are checked by [bilinear and white-border cases](../../../core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp); raw RDNA execution of this tuple on gfx1013 or gfx1036 is unmeasured.
- [MUBUF/MTBUF descriptors ending in VCC](../../../core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp): the four dword descriptor at s104 is s104, s105, vcc_lo and vcc_hi, as LLVM's [VCCTuple/SReg_128](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L422) and [buffer resource operands](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/BUFInstructions.td) describe. Existing translation maps scalar codes106/107 to the raw VCC words. Recompiled loads/stores, both words, bounds, formats and wave32/wave64 execute on NVIDIA and AMD Vulkan; raw gfx10/gfx1013 hardware is not measured. Higher descriptor bases remain refused; the normal SGPR bound is unchanged.
- [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
- [Front-face input with `FRONT_FACE_ALL_BITS`](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (shader recompiler) - with `SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS` set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit ([radv_pipeline.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/vulkan/radv_pipeline.c#L5953)) and ACO tests the input with `v_cmp_lg_u32 0` ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/compiler/aco_instruction_selection.cpp#L8123-L8128)), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with `v_cmp_lt_u32 0`. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (`fgt 0`, `fsign`; [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))