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
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct SurfaceGeometry {
};
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor);
bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels);
GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed);
GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed, bool keysReadAsStored);
GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::uint32_t maxArrayLayers);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,14 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
normalized.format = unorm16;
return cachedTexture(context, words, normalized, components, guestBytes, depthCompare);
}
if (const auto window = ViewedArrayWindow(resource); window.baseAddress != resource.baseAddress) {
const auto viewedWindow = [&] {
const auto window = ViewedArrayWindow(resource, false);
if (window.baseAddress != resource.baseAddress || resource.dccAddress == 0) return window;
if (ViewedArrayWindow(resource, true).baseAddress == resource.baseAddress) return window;
if (TextureClearKeys(resource, guestBytes != 0 ? guestBytes : DescribeSurface(resource).guestBytes) != DccKeys::Uncompressed) return window;
return ViewedArrayWindow(resource, true);
};
if (const auto window = viewedWindow(); window.baseAddress != resource.baseAddress) {
static std::atomic<std::uint64_t> windows{0};
const auto count = windows.fetch_add(1) + 1;
if (count <= 5 || (count & (count - 1)) == 0) std::fprintf(stderr, "[gpu] sampled array view 0x%llx (%ux%u format %u, dimension %d, slices %u..%u) served from its viewed slices at 0x%llx; %llu such views\n", static_cast<unsigned long long>(resource.baseAddress), resource.width, resource.height, resource.format, static_cast<int>(resource.dimension), resource.baseArray, resource.depthOrLastArray, static_cast<unsigned long long>(window.baseAddress), static_cast<unsigned long long>(count));
Expand Down
17 changes: 10 additions & 7 deletions core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,23 +466,26 @@ bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t leve
return true;
}

GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed) {
GuestTextureResource ViewedArrayWindow(const GuestTextureResource& viewed, bool keysReadAsStored) {
const bool layered = viewed.dimension == TextureDimension::k2DArray || viewed.dimension == TextureDimension::k1DArray || viewed.dimension == TextureDimension::kCube;
if (!layered || viewed.baseArray == 0 || viewed.baseArray > viewed.depthOrLastArray || viewed.dccAddress != 0) return viewed;
if (!layered || viewed.baseArray == 0 || viewed.baseArray > viewed.depthOrLastArray || (viewed.dccAddress != 0 && !keysReadAsStored)) return viewed;
std::uint32_t sliceMask = 0;
if (const auto xorMode = XorSwizzleMode(viewed.tileMode); xorMode != 0u) {
const auto* equation = FindTextureSwizzleEquation(xorMode, BytesPerElement(viewed.format));
if (equation == nullptr) return viewed;
std::uint32_t sliceBits = 0;
for (const auto bit : equation->bits) sliceBits |= bit >> 24u;
const auto sliceMask = sliceBits == 0 ? 0u : (1u << std::bit_width(sliceBits)) - 1u;
if ((viewed.baseArray & sliceMask) != 0) return viewed;
sliceMask = sliceBits == 0 ? 0u : (1u << std::bit_width(sliceBits)) - 1u;
}
const auto first = viewed.baseArray & ~sliceMask;
if (first == 0) return viewed;
const auto geometry = DescribeSurface(viewed);
if (geometry.thick || geometry.layers != viewed.depthOrLastArray + 1u) return viewed;
auto window = viewed;
window.baseAddress = viewed.baseAddress + geometry.GuestLayerOffset(viewed.baseArray);
window.depthOrLastArray = viewed.depthOrLastArray - viewed.baseArray;
window.baseArray = 0;
window.baseAddress = viewed.baseAddress + geometry.GuestLayerOffset(first);
window.depthOrLastArray = viewed.depthOrLastArray - first;
window.baseArray = viewed.baseArray - first;
window.dccAddress = 0;
return window;
}

Expand Down
57 changes: 50 additions & 7 deletions core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "GraphicsTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
Expand Down Expand Up @@ -424,26 +426,35 @@ void RunTextureTilingTests() {
array.depthOrLastArray = 1536;
array.baseArray = 1536;
const auto layerBytes = DescribeSurface(array).layerBytes;
const auto window = ViewedArrayWindow(array);
const auto window = ViewedArrayWindow(array, false);
Require(window.baseAddress == array.baseAddress + 1536ull * layerBytes && window.depthOrLastArray == 0 && window.baseArray == 0, "a sampled view of one slice at 1536 of an R_X array must start at that slice");
Require(DescribeSurface(window).layerBytes == layerBytes && DescribeSurface(window).layers == 1, "a sampled array window must keep the slice layout");

auto unaligned = array;
unaligned.baseArray = 1530;
Require(ViewedArrayWindow(unaligned).baseAddress == array.baseAddress, "an R_X window at a slice the swizzle mixes in must keep the whole array");
const auto mixed = ViewedArrayWindow(unaligned, false);
Require(mixed.baseAddress == array.baseAddress + 1520ull * layerBytes && mixed.baseArray == 10 && mixed.depthOrLastArray == 16, "an R_X view at a slice the swizzle mixes in must start its window at the last slice before it with those bits clear");
auto low = array;
low.baseArray = 9;
Require(ViewedArrayWindow(low, false).baseAddress == array.baseAddress, "an R_X view below the first slice with those bits clear keeps the whole array");

auto plain = array;
plain.tileMode = TextureTileMode::kStandard64KB;
plain.baseArray = 1530;
Require(ViewedArrayWindow(plain).baseAddress == array.baseAddress + 1530ull * DescribeSurface(plain).layerBytes && ViewedArrayWindow(plain).depthOrLastArray == 6, "a non-XOR window may start at any slice");
Require(ViewedArrayWindow(plain, false).baseAddress == array.baseAddress + 1530ull * DescribeSurface(plain).layerBytes && ViewedArrayWindow(plain, false).depthOrLastArray == 6, "a non-XOR window may start at any slice");

auto fromZero = array;
fromZero.baseArray = 0;
Require(ViewedArrayWindow(fromZero).baseAddress == array.baseAddress && ViewedArrayWindow(fromZero).depthOrLastArray == 1536, "a view from slice 0 keeps its descriptor");
Require(ViewedArrayWindow(fromZero, false).baseAddress == array.baseAddress && ViewedArrayWindow(fromZero, false).depthOrLastArray == 1536, "a view from slice 0 keeps its descriptor");

auto compressed = array;
compressed.dccAddress = 0x20000000ull;
Require(ViewedArrayWindow(compressed).baseAddress == array.baseAddress, "a view with DCC metadata keeps its descriptor");
Require(ViewedArrayWindow(compressed, false).baseAddress == array.baseAddress, "a view with DCC metadata keeps its descriptor");
const auto stored = ViewedArrayWindow(compressed, true);
Require(stored.baseAddress == window.baseAddress && stored.depthOrLastArray == 0 && stored.baseArray == 0 && stored.dccAddress == 0, "a view whose DCC keys read as stored is cut to its slices without metadata");
auto compressedFromZero = compressed;
compressedFromZero.baseArray = 0;
Require(ViewedArrayWindow(compressedFromZero, true).baseAddress == array.baseAddress && ViewedArrayWindow(compressedFromZero, true).dccAddress == compressed.dccAddress, "a view with DCC metadata from slice 0 keeps its descriptor");

GuestTextureResource cube{};
cube.baseAddress = 0x30000000ull;
Expand All @@ -455,13 +466,45 @@ void RunTextureTilingTests() {
cube.tileMode = TextureTileMode::kStandard4KB;
cube.depthOrLastArray = 7232;
cube.baseArray = 7227;
const auto faces = ViewedArrayWindow(cube);
const auto faces = ViewedArrayWindow(cube, false);
Require(faces.baseAddress == cube.baseAddress + 7227ull * DescribeSurface(cube).layerBytes && faces.depthOrLastArray == 5 && faces.baseArray == 0 && faces.dimension == TextureDimension::kCube, "a cube view of six faces at 7227 must start at its first face");

auto compressedVolume = cube;
compressedVolume.dimension = TextureDimension::k3D;
compressedVolume.baseArray = 0;
compressedVolume.dccAddress = 0x20000000ull;
Require(ViewedArrayWindow(compressedVolume, true).dccAddress == compressedVolume.dccAddress, "a volume with DCC metadata keeps its descriptor");

auto volume = cube;
volume.dimension = TextureDimension::k3D;
volume.baseArray = 0;
Require(ViewedArrayWindow(volume).baseAddress == cube.baseAddress, "a volume keeps its descriptor");
Require(ViewedArrayWindow(volume, false).baseAddress == cube.baseAddress, "a volume keeps its descriptor");
}

{
const std::array<std::uint32_t, 8> words{0x027c0000u, 0xc1600000u, 0x00ffc0ffu, 0xd1800924u, 0x027cc27cu, 0xc1400000u, 0x00704018u, 0x91b00204u};
const auto view = DecodeTextureResource(words);
Require(view.baseAddress == 0x27c000000ull && view.dccAddress == 0x91b002040000ull && view.dimension == TextureDimension::k2DArray && view.baseArray == 636 && view.depthOrLastArray == 636 && view.width == 1024 && view.height == 1024 && view.format == 22, "the title's DCC array view decodes to slice 636 of 637");
const auto whole = DescribeSurface(view);
Require(whole.guestBytes == 2671771648ull, "the uncut DCC array view covers all 637 slices (" + std::to_string(whole.guestBytes) + " bytes)");
Require(TextureClearKeys(view, whole.guestBytes) == DccKeys::Uncompressed, "unreadable DCC keys of the uncut view read the texels as stored");
Require(ViewedArrayWindow(view, false).baseAddress == view.baseAddress, "a DCC view whose keys are not known to read as stored keeps its descriptor");
const auto window = ViewedArrayWindow(view, true);
const auto cut = DescribeSurface(window);
Require(window.baseAddress == view.baseAddress + whole.GuestLayerOffset(624) && window.dccAddress == 0 && window.baseArray == 12 && window.depthOrLastArray == 12, "the window of a stored-keys DCC view starts at the slice-swizzle boundary below its slice without metadata");
Require(cut.layers == 13 && cut.layerBytes == whole.layerBytes && window.baseAddress + cut.guestBytes == view.baseAddress + whole.guestBytes, "the window covers slices 624..636 (" + std::to_string(cut.guestBytes) + " bytes)");
for (std::uint32_t slice = 0; slice < cut.layers; ++slice) Require(window.baseAddress + cut.GuestLayerOffset(slice) == view.baseAddress + whole.GuestLayerOffset(624 + slice), "a window slice lies where the uncut surface keeps it");
const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(view.tileMode), 4);
const auto block = ThinBlockLayout(view.tileMode, 4);
Require(equation != nullptr && whole.mips.size() == 1 && !whole.mips[0].tail, "the title's DCC array is one untailed Z_X level");
const auto texel = [&](const GuestTextureResource& surface, const SurfaceGeometry& geometry, std::uint32_t x, std::uint32_t y, std::uint32_t slice) {
const auto& mip = geometry.mips[0];
return surface.baseAddress + geometry.GuestLayerOffset(slice) + mip.tiledOffset + (static_cast<std::uint64_t>(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, slice);
};
for (std::uint32_t y = 0; y < 1024; y += 7) {
for (std::uint32_t x = 0; x < 1024; x += 5) Require(texel(window, cut, x, y, window.baseArray) == texel(view, whole, x, y, view.baseArray), "a texel of the viewed slice detiles from the same guest byte in the window as in the uncut surface");
}
Require(TextureClearKeys(window, cut.guestBytes) == DccKeys::Uncompressed, "the window reads its texels as stored, as the uncut view does");
}

{
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@
- [Viewport depth range remap](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - on a device without `VK_EXT_depth_range_unrestricted` a viewport depth range outside [0, 1] (Minecraft PPSA17221 draws its world with `PA_CL_VPORT_ZSCALE` 1, `ZOFFSET` 0 in negative-one-to-one clip space) is folded into the vertex shader: `RemapPositionDepth` scales clip z so z/w is the console's depth, the pipeline's depth clamp applies the console's `PA_SC_VPORT_ZMIN/ZMAX` clipped to [0, 1] (exact for a normalized depth target or none; Minecraft clamps its float depth target to [-1, 1], so a depth below 0 is stored as 0 there, which only a later test or read at exactly 0 can tell, and the first such draw is reported once), and two extra `gl_ClipDistance` elements restore the near and far planes depth clamping removes. The mode is a pipeline specialization constant (`PipelineSpecialization::DepthRemapBase`); scale and offset are read from the draw's push block (two words at byte 104, `DepthRemapPushOffsetBytes`, so a remapped draw's stage push data ends before it), so draws that differ only in them share one vertex module and one pipeline. The remap serves the vertex path only; mesh and tessellation draws with such a range still throw. Whether the console clamps before or after its depth bias and how it treats a clamp range narrower than the transform's are not measured.
- [Benchmark rewrite failure test off Windows](../../../core/libs/prx/libSceAgcDriver/tests/PerformanceTrace.cpp) (libSceAgcDriver) - `ReplaceGzipFile` checks that a write failing at `<name>.tmp` keeps the previous `aps5-bench.json.gz` only on non-Windows builds: the `std::ios_base::failure` thrown inside the MinGW ofstream did not unwind to the test's catch, so Windows CI covers only the failed move.

- [Sampled array windows](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver) - a sampled 2D array, 1D array or cube view whose base array slice is past 0 is built from its viewed slices only (`ViewedArrayWindow`), at the guest address of its base slice. The window is taken only when the surface is thin, has no DCC metadata, and, for a 64 KiB XOR swizzle, when the base slice keeps every slice bit of the swizzle equation at zero (R_X 1-byte elements: a base slice that is a multiple of 16). Any other view still builds slices 0 to its last slice. A window and a whole-array view of the same surface are separate images over overlapping memory, kept coherent by the existing overlap rules, at the cost of the memory and uploads of both. PPSA26344 samples a 256x256 cube array at slices 4299..7232, built at 1467 MiB instead of about 3.8 GB.
- [Sampled array windows](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) (libSceAgcDriver) - a sampled 2D array, 1D array or cube view past slice 0 is built from its viewed slices only (`ViewedArrayWindow`). The window needs a thin surface without DCC metadata, or with keys that resolve over the whole surface to Uncompressed (the window then drops the metadata; keys are rescanned at each lookup). For a 64 KiB XOR swizzle it starts at the last slice at or before the base whose swizzle slice bits are zero (R_X 1-byte and Z_X 4-byte: a multiple of 16); when that is slice 0 the view builds slices 0 to its last slice. A window and a whole-array view of the same surface are separate images over overlapping memory, kept coherent by the existing overlap rules, at the cost of both. PPSA26344 samples a cube array at slices 4299..7232 at 1467 MiB instead of about 3.8 GB; PPSA01341 samples slice 636 of a DCC 2D array at 52 MiB instead of 2.5 GiB.
- [2D reads of 2D arrays](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a sampled image whose shader shape is 2D and whose T# is a 2D array is built from its base slice alone: the T# is narrowed to that slice (`FirstLayerDescriptor`) before its decode, cache key and image, in stage A records, stage B bindings, revalidation and image tables. A whole-array read of the same T# keeps its own cache entry, so both images exist when a title uses both. Storage images read or written through a 2D shape are not narrowed and still build every slice the T# names. PPSA26344 reads a 3328x1872 R_X T# of 1537 slices this way: the whole array was a 12.4 GiB image that did not fit the device-local budget of an RTX 4090.
- [Sampled and storage views of 1 GiB or more](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) that stay whole are only logged (sampled views by `cachedTexture`, storage views by `cachedStorageTexture`), with the registered guest range holding their base and how many of their slices fit in it; nothing is capped to that range. PPSA26344 samples a 1467 MiB cube array window of which only 838 of 2934 slices lie inside its 3380 MiB range: the rest is read from whatever follows the range. Whether such slices are ever sampled is not measured.
- [Color export type against the target format](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - the export-format check refuses a `UINT16_ABGR` export (`SPI_SHADER_COL_FORMAT` 7) into a target whose color-buffer number format is not unsigned integer, but not the reverse. A 32-bit export (`32_R`, `32_GR`, `32_AR` or `32_ABGR`) into an unsigned-integer target leaves `uintExport` clear, so the recompiler declares the fragment output `float4`; `DecodeColorBuffer` maps a number format of 4 to `R32_UINT` from the color buffer alone, independent of the export format, so the float output reaches an integer attachment. Desktop drivers keep the raw bits, but the Khronos validation layer reports `Undefined-Value-ShaderFragmentOutputMismatch` and MoltenVK's Metal compiler refuses the pipeline. Which export format a title pairs with a 32-bit integer target is not measured, so the output type is left following the export format rather than the attachment.
Expand Down
Loading