mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(shader): zero the resinfo layer count for DIMs without layers
image_get_resinfo returns 0 in its third component when the instruction's DIM has no layers (1D, 2D, 2D MSAA) and the T# is not 3D, as measured on RDNA 2 in #7550. Keep 1D-array T#s on their first layer and MSAA T#s refused, and record the unmeasured forms in the shader debt file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3ba1d39db6
commit
7b4b9d9814
5 files changed
+19
-11
No files matched your search
@@ -81,11 +81,13 @@ std::string Text(const std::array<std::uint32_t, 4>& value) {
|
||||
return std::to_string(value[0]) + "x" + std::to_string(value[1]) + "x" + std::to_string(value[2]) + " levels " + std::to_string(value[3]);
|
||||
}
|
||||
|
||||
void Check(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> native, const std::array<std::uint32_t, 8>& texture, const char* what) {
|
||||
const auto expected = Query(device, native, texture);
|
||||
Require(expected[0] == Size && expected[1] == Size && expected[3] == 1u, std::string("image_get_resinfo: the ") + what + " query returned " + Text(expected));
|
||||
const auto oneDimensional = Query(device, Query1D, texture);
|
||||
Require(oneDimensional == expected, std::string("image_get_resinfo: DIM 1D on a ") + what + " T# returned " + Text(oneDimensional) + ", its own DIM returns " + Text(expected));
|
||||
using Result = std::array<std::uint32_t, 4>;
|
||||
|
||||
void Check(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> native, const std::array<std::uint32_t, 8>& texture, const char* what, const Result& own, const Result& oneDimensional) {
|
||||
const auto nativeResult = Query(device, native, texture);
|
||||
Require(nativeResult == own, std::string("image_get_resinfo: the ") + what + " query returned " + Text(nativeResult) + ", RDNA 2 returns " + Text(own));
|
||||
const auto lineResult = Query(device, Query1D, texture);
|
||||
Require(lineResult == oneDimensional, std::string("image_get_resinfo: DIM 1D on a ") + what + " T# returned " + Text(lineResult) + ", RDNA 2 returns " + Text(oneDimensional));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -94,9 +96,9 @@ int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Check(*device, QueryCube, Descriptor(TypeCube, 6u), "cube");
|
||||
Check(*device, Query3D, Descriptor(Type3D, 4u), "3D");
|
||||
Check(*device, Query2DArray, Descriptor(Type2DArray, 3u), "2D array");
|
||||
Check(*device, QueryCube, Descriptor(TypeCube, 6u), "cube", {Size, Size, 6u, 1u}, {Size, Size, 0u, 1u});
|
||||
Check(*device, Query3D, Descriptor(Type3D, 4u), "3D", {Size, Size, 4u, 1u}, {Size, Size, 4u, 1u});
|
||||
Check(*device, Query2DArray, Descriptor(Type2DArray, 3u), "2D array", {Size, Size, 3u, 1u}, {Size, Size, 0u, 1u});
|
||||
std::puts("image resinfo dimension tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -207,7 +207,8 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag
|
||||
if (!validImageDescriptor(descriptor, base.r128)) {
|
||||
return rejectImage(invalidImageReason(descriptor), "image descriptor is invalid");
|
||||
}
|
||||
decoded.dimension = descriptorDimension(descriptor, base.dimension == RdnaImageDimension::Dim1D && base.dimensionQueryOnly ? RdnaImageDimension::Dim2DArray : base.dimension);
|
||||
const bool queryLayers = base.dimension == RdnaImageDimension::Dim1D && base.dimensionQueryOnly && rawImageType(descriptor) == ImageType::Color2DArray;
|
||||
decoded.dimension = descriptorDimension(descriptor, queryLayers ? RdnaImageDimension::Dim2DArray : base.dimension);
|
||||
if (base.depthCompare) {
|
||||
if (decoded.dimension == RdnaImageDimension::Dim1D) {
|
||||
decoded.dimension = RdnaImageDimension::Dim2D;
|
||||
@@ -1947,7 +1948,7 @@ std::vector<ImageResource> ResourceMaterializer::RuntimeImageModes(const ImageRe
|
||||
modes.push_back(plane);
|
||||
}
|
||||
if (image.dimension == RdnaImageDimension::Dim1D && image.dimensionQueryOnly && !storage) {
|
||||
for (const auto dimension : {RdnaImageDimension::Dim2D, RdnaImageDimension::Dim1DArray, RdnaImageDimension::Dim2DArray, RdnaImageDimension::Dim3D}) {
|
||||
for (const auto dimension : {RdnaImageDimension::Dim2D, RdnaImageDimension::Dim2DArray, RdnaImageDimension::Dim3D}) {
|
||||
if (dimension == RdnaImageDimension::Dim2D && planeAdded) continue;
|
||||
auto view = mode;
|
||||
view.dimension = dimension;
|
||||
|
||||
@@ -95,7 +95,7 @@ static_assert(sizeof(FragmentParameter) == 16, "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) == 92, "ImageResource changed: update the info encoder");
|
||||
static_assert(sizeof(ImageResource) == 96, "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");
|
||||
|
||||
@@ -453,6 +453,10 @@ std::uint32_t QueryDimensions(SpirvValueEmitContext& ctx, const ImageEmitAccess&
|
||||
if (access.mem.imageDimension == RdnaImageDimension::Dim3D && dimension == RdnaImageDimension::Dim2D) {
|
||||
result[2] = ConstantU32(state, 1);
|
||||
}
|
||||
const auto requested = access.mem.imageDimension;
|
||||
if ((requested == RdnaImageDimension::Dim1D || requested == RdnaImageDimension::Dim2D || requested == RdnaImageDimension::Dim2DMsaa) && dimension != RdnaImageDimension::Dim3D) {
|
||||
result[2] = ConstantU32(state, 0);
|
||||
}
|
||||
if (info.multisampled == 0u) {
|
||||
result[3] = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpImageQueryLevels, TypeU32(state), result[3], image);
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
- [MIMG BY2/BY4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_by2/by4`, opcodes 0x42, 0x43, 0x4a and 0x4b) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. They read N texels (BY2: 2, BY4: 4) from `x & ~(N - 1)` on row y, texel by texel and component by component within each texel, with each texel converted like `image_load`. Every register 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 the N texels fit in one dword: R8 and R16 (DMASK 0x3) or RG8 (0xf) for BY2, and R8 (0xf) for BY4. With 32-bit texels (R32, RG32, RG16, RGBA8) the hardware returns zeros. Other formats, masks, dimensions, swizzles, D16/A16 and indirect descriptors throw. The stores (0x52, 0x53, 0x5a and 0x5b) decode but throw at translation. Measured on hardware, they write from the unaligned x up to the end of its dword, which is not implemented.
|
||||
- [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).
|
||||
- [`image_get_resinfo` through DIM 1D on a cube, 2D-array or 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follows thuanquang's measurement on RDNA2 (RX 6700 XT, gfx1031, #7550), not the PS5's gfx1013: width, height and the level count come from the T#, and the third component is the 3D T#'s depth, or 0 for a cube or 2D array, as for every DIM without layers (1D, 2D, 2D MSAA). Such a query on a 1D-array T# still reads it as its first layer, whose height is not measured. On a 2D MSAA or 2D MSAA-array T# it throws: gfx1031 was measured only with one fragment, which the driver cannot bind, and the level component for 2, 4 or 8 fragments is not measured.
|
||||
- [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-array samples through a 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (`image_sample`, `image_sample_l` and `image_sample_lz` with DIM 2D_ARRAY) sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, addressed by the sampler's Z mode (only wrap was measured), not a layer index, and a plain sample in a pixel shader takes its LOD from the derivatives of all three coordinates. Measured on RDNA2 (RX 6700 XT, gfx1031, driver 32.0.21045.5002, Windows 11) with the [hardware oracle](../HW_ORACLE.md) template run through the driver's comgr and HIP runtime, not on the PS5's gfx1013: on 16x16x4 R32_FLOAT 3D T#s with one and three levels, the three instructions with DIM 2D_ARRAY returned the same value as with DIM 3D in all 256 lanes of 66 cases (point, bilinear and z-linear filters, wrap addressing, r from -0.125 to 3, quad steps of 1 to 4 texels in u and v and of 0 to 4 slices in r, LOD 1). Other samples of such a pair (bias, LOD clamp, derivatives, offsets, comparison, 16-bit addresses), gathers, `image_get_lod` and a sample mixed with a load of the same image keep throwing because they are not measured. The host sampler ignores `Z_FILTER`, as for every 3D sample, so r is filtered with the XY filter.
|
||||
- [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`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, measured on RDNA2 (gfx1036, [by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6217609589), not the PS5's gfx1013) with the [hardware oracle](../HW_ORACLE.md) on 8x8 mip-less cube arrays (`DEPTH` 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT ([the last also by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6224023050), with exactly representable half values) with `BASE_ARRAY` 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw.
|
||||
|
||||
Reference in new issue
Block a user