mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
test(agc): exercise VCC-backed sampler words on Vulkan
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user