Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ CompiledVariant sampleVariant() {
image.depthBitsCompatible = false;
image.constantSwizzle = true;
image.constantSwizzleCompatible = false;
image.cubeFaceCompatible = false;
image.flatVolumeCompatible = false;
image.singleMipPlaneBiasCompatible = true;
image.flatLineCompatible = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#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 <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>

namespace {

using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;

constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Words = 32;
constexpr std::uint32_t Size = 8;
constexpr std::uint32_t Faces = 6;
constexpr std::uint32_t Format32Float = 22;
constexpr std::uint32_t TypeCube = 11;
constexpr std::uint32_t Result = 8;
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
alignas(256) std::array<std::uint8_t, 65536> Cube{};

alignas(256) constexpr std::array<std::uint32_t, 30> Code{
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c,
0x80000501, 0xe0301010, 0x80000601, 0xbf8c3f70, 0xf09c0108, 0x00610c02, 0xf0900108, 0x00610d02,
0xf0800108, 0x00610e02, 0xf0001108, 0x00010f05, 0xbf8c3f70, 0xe0701020, 0x80000c01, 0xe0701024,
0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, 0xbf810000,
};

struct Lane {
std::uint32_t x;
std::uint32_t y;
std::uint32_t loadX;
std::uint32_t loadY;
};

Lane LaneOf(std::uint32_t tid) {
return {tid % Size, (tid * 3u) % Size, (tid * 5u) % Size, (tid * 7u + 1u) % Size};
}

float TexelValue(std::uint32_t face, std::uint32_t x, std::uint32_t y) {
return static_cast<float>(face * 1000u + y * 16u + x);
}

std::array<std::uint32_t, 8> CubeDescriptor() {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Cube.data()));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Size - 1u) & 3u) << 30u),
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
0xfacu | (TypeCube << 28u),
Faces - 1u,
0u,
0u,
0u,
};
}

void FillCube() {
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(CubeDescriptor()));
Require(geometry.guestBytes <= Cube.size(), "image plane address on a cube: the texture does not fit the texel storage");
Require(geometry.layers == Faces, "image plane address on a cube: the cube does not have six faces");
const auto& mip = geometry.mips.at(0);
std::fill(Cube.begin(), Cube.end(), 0u);
for (std::uint32_t face = 0; face < Faces; ++face) {
for (std::uint32_t y = 0; y < Size; ++y) {
for (std::uint32_t x = 0; x < Size; ++x) {
const float value = TexelValue(face, x, y);
std::memcpy(&Cube[geometry.GuestLayerOffset(face) + mip.tiledOffset + static_cast<std::uint64_t>(y) * mip.pitchBytes + x * 4u], &value, 4u);
}
}
}
}

void FillInput() {
Buffer.fill(0xdeadbeefu);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto lane = LaneOf(tid);
const float u = 1.0f + (static_cast<float>(lane.x) + 0.5f) / static_cast<float>(Size);
const float v = 1.0f + (static_cast<float>(lane.y) + 0.5f) / static_cast<float>(Size);
const std::array<std::uint32_t, 5> words{std::bit_cast<std::uint32_t>(u), std::bit_cast<std::uint32_t>(v), 0u, lane.loadX, lane.loadY};
std::copy(words.begin(), words.end(), &Buffer[tid * Words]);
}
}

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};
}

void Run(AgcDriver::VulkanDevice& device) {
std::vector<std::uint32_t> userData(16, 0u);
const auto buffer = BufferDescriptor(Buffer.data(), static_cast<std::uint32_t>(Buffer.size() * 4u));
const auto texture = CubeDescriptor();
const std::array<std::uint32_t, 4> sampler{0u, 0xfffu << 12u, 0u, 0u};
std::copy(buffer.begin(), buffer.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
std::copy(sampler.begin(), sampler.end(), userData.begin() + 12);
const std::span<const std::uint32_t> code(Code);
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, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(Code.data()), code, 0, {}},
{32, 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() {
constexpr std::array<const char*, 4> names{"image_sample_lz", "image_sample_l", "image_sample", "image_load"};
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto lane = LaneOf(tid);
const std::array<float, 4> expected{TexelValue(0u, lane.x, lane.y), TexelValue(0u, lane.x, lane.y), TexelValue(0u, lane.x, lane.y), TexelValue(0u, lane.loadX, lane.loadY)};
for (std::uint32_t op = 0; op < expected.size(); ++op) {
const float value = std::bit_cast<float>(Buffer[tid * Words + Result + op]);
Require(value == expected[op], std::string("image plane address on a cube: ") + names[op] + " 2d on a cube texture, thread " + std::to_string(tid) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[op]));
}
}
}

}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
FillCube();
FillInput();
Run(*device);
Check();
std::puts("image plane address on cube tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ struct ImageResource {
bool depthBitsCompatible = true;
bool constantSwizzle = false;
bool constantSwizzleCompatible = true;
bool cubeFaceCompatible = true;
bool flatVolumeCompatible = true;
bool singleMipPlaneBiasCompatible = false;
bool flatLineCompatible = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,12 @@ std::vector<ImageResource> ResourceMaterializer::RuntimeImageModes(const ImageRe
line.dimension = RdnaImageDimension::Dim1D;
modes.push_back(line);
}
if (image.dimension == RdnaImageDimension::Dim2D && image.cubeFaceCompatible && !storage && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) {
auto face = mode;
face.dimension = RdnaImageDimension::Dim2DArray;
face.cube = true;
modes.push_back(face);
}
if (image.dimension == RdnaImageDimension::Dim2D && image.flatLineCompatible && !storage && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) {
auto line = mode;
line.dimension = RdnaImageDimension::Dim1D;
Expand Down
3 changes: 3 additions & 0 deletions core/shader/recompiler/Optimization/src/ResourceTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,9 @@ class Tracker {
image.singleMipPlaneBiasCompatible = image.singleMipPlaneBiasCompatible && op == IrOpcode::ImageSampleRaw && memory.imageSampleFlags == RdnaImageSampleFlagBias && memory.dataBits == 32u && !memory.imageR128 && !memory.imagePacked && memory.imageByElements == 0u;
image.depthBitsCompatible = image.depthBitsCompatible && memory.dataBits == 32u;
image.constantSwizzleCompatible = image.constantSwizzleCompatible && (op == IrOpcode::ImageSampleRaw || op == IrOpcode::ImageGatherRaw);
const bool cubeFaceLoad = op == IrOpcode::ImageRead && !memory.imageHasMip && memory.imageSampleFlags == 0u;
const bool cubeFaceSample = op == IrOpcode::ImageSampleRaw && (memory.imageSampleFlags == 0u || memory.imageSampleFlags == RdnaImageSampleFlagLevelZero || memory.imageSampleFlags == RdnaImageSampleFlagLod);
image.cubeFaceCompatible = image.cubeFaceCompatible && (cubeFaceLoad || cubeFaceSample) && !memory.imagePacked && memory.imageByElements == 0u;
const bool flatVolumeLoad = op == IrOpcode::ImageRead && memory.imageSampleFlags == 0u;
const bool flatVolumeSample = op == IrOpcode::ImageSampleRaw && memory.imageSampleFlags == RdnaImageSampleFlagLevelZero;
const bool flatVolumeQuery = op == IrOpcode::ImageQueryDimensions;
Expand Down
8 changes: 5 additions & 3 deletions core/shader/recompiler/ShaderDiskCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: updat
static_assert(sizeof(CompiledShaderInfo) == 352, "CompiledShaderInfo changed: update the info encoder");
static_assert(sizeof(ShaderInfo) == 232, "ShaderInfo changed: update the info encoder");
static_assert(sizeof(BufferResource) == 32, "BufferResource changed: update the info encoder");
static_assert(sizeof(ImageResource) == 88, "ImageResource changed: update the info encoder");
static_assert(sizeof(ImageResource) == 92, "ImageResource changed: update the info encoder");
static_assert(sizeof(SamplerResource) == 24, "SamplerResource changed: update the info encoder");
static_assert(sizeof(SampledResourcePair) == 12, "SampledResourcePair changed: update the info encoder");
static_assert(sizeof(StageInput) == 56, "StageInput changed: update the info encoder");
Expand Down Expand Up @@ -140,7 +140,7 @@ constexpr std::size_t fieldCount() {

static_assert(fieldCount<ShaderInfo>() == 23, "ShaderInfo changed: update the info encoder");
static_assert(fieldCount<BufferResource>() == 12, "BufferResource changed: update the info encoder");
static_assert(fieldCount<ImageResource>() == 37, "ImageResource changed: update the info encoder");
static_assert(fieldCount<ImageResource>() == 38, "ImageResource changed: update the info encoder");
static_assert(fieldCount<SamplerResource>() == 8, "SamplerResource changed: update the info encoder");
static_assert(fieldCount<SampledResourcePair>() == 3, "SampledResourcePair changed: update the info encoder");
static_assert(fieldCount<StageInput>() == 5, "StageInput changed: update the info encoder");
Expand Down Expand Up @@ -698,6 +698,7 @@ void encodeShaderInfo(Writer& writer, const ShaderInfo& info) {
out.Value(image.depthBitsCompatible);
out.Value(image.constantSwizzle);
out.Value(image.constantSwizzleCompatible);
out.Value(image.cubeFaceCompatible);
out.Value(image.flatVolumeCompatible);
out.Value(image.singleMipPlaneBiasCompatible);
out.Value(image.flatLineCompatible);
Expand Down Expand Up @@ -772,7 +773,7 @@ void decodeShaderInfo(Reader& reader, ShaderInfo& info) {
in.Value(buffer.scalar);
in.Value(buffer.typedAlignment);
});
reader.List(info.images, 76, [](Reader& in, ImageResource& image) {
reader.List(info.images, 77, [](Reader& in, ImageResource& image) {
in.Value(image.source);
in.Value(image.firstUsePc);
in.Value(image.resourceClass);
Expand All @@ -799,6 +800,7 @@ void decodeShaderInfo(Reader& reader, ShaderInfo& info) {
in.Value(image.depthBitsCompatible);
in.Value(image.constantSwizzle);
in.Value(image.constantSwizzleCompatible);
in.Value(image.cubeFaceCompatible);
in.Value(image.flatVolumeCompatible);
in.Value(image.singleMipPlaneBiasCompatible);
in.Value(image.flatLineCompatible);
Expand Down
2 changes: 1 addition & 1 deletion core/shader/recompiler/ShaderDiskCache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace ShaderRecompiler::ShaderDiskCache {

inline constexpr std::uint32_t FormatVersion = 35;
inline constexpr std::uint32_t FormatVersion = 36;

enum class LoadStatus {
Loaded,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ std::uint32_t CubeLayer(SpirvEmitterState& state, std::uint32_t value) {
std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components, std::uint32_t encoded, bool gradient = false) {
const bool cube = access.image.cube;
const auto present = std::min(encoded, components);
if (first == NoImageComponent || (encoded < components && (cube || encoded == 0u)) || access.mem.imageAddressComponents < first + present) {
const bool cubeFace = cube && access.mem.imageDimension == RdnaImageDimension::Dim2D;
if (first == NoImageComponent || (encoded < components && ((cube && !cubeFace) || encoded == 0u)) || access.mem.imageAddressComponents < first + present) {
ctx.Fail(access.inst, "has an image address with too few coordinate components");
}
const auto component = [&](std::uint32_t index) { return index < present ? AddressF32(ctx, access, first + index) : ZeroF32(ctx.state); };
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/shader.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
- [2D stores to 1D storage images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp): an `image_store` with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. Storage loads, atomics and `image_store_mip` through such a pair still throw
- [MIMG PCK2/PCK4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314).
- [3D image accesses bound to a 2D or 2D-array T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow CouchRiv's [hardware oracle](../HW_ORACLE.md) measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, `image_sample_lz` ignores r and reads the (u,v) texel, `image_load` and `image_load_mip` read the (x,y) texel when z is 0 and return zero otherwise, and `image_get_resinfo` returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and `image_get_resinfo` returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, `image_get_lod` and stores, keeps throwing because it is not measured.
- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l` and mip-less `image_load` take this mode; other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, storage, mip loads) still throw.
- [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#.
- [Unnormalized texel offsets](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp ([measured by adriadam10](https://github.com/boykopovar/AnyPS5/pull/822#issuecomment-6033192725)).
- [Cube image gradients](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (shader recompiler) - `image_sample_d` and `image_sample_cd` on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (`src/amd/common/nir/ac_nir_lower_tex_coords.c`, `prepare_cube_coords`: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md): on a 16x16 32_FLOAT cube with five levels, point mip filter, `image_sample_d` reads the level of `round(log2(16 * max(|ddx|, |ddy|)))` for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). `image_sample_cd` takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane.
Expand Down
Loading