diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/CmaskClear.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/CmaskClear.comp index 0b2dde1f1..935a0e81a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/shaders/CmaskClear.comp +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/CmaskClear.comp @@ -29,10 +29,10 @@ void main() { uint nibble = ((y / 512u) * push.blocksPerRow + x / 1024u) * 8192u + offset; uint byteAddress = push.base + nibble / 2u; uint code = (cmask.words[byteAddress / 4u] >> ((byteAddress % 4u) * 8u + (nibble % 2u) * 4u)) & 0xfu; - if (code == 0xfu) return; - if (code == 0u && push.write != 0u) { + if (code != 0u) return; + if (push.write != 0u) { imageStore(target, ivec2(pixel), uvec4(push.clear0, push.clear1, 0u, 0u)); return; } - if ((pixel.x & 7u) == 0u && (pixel.y & 7u) == 0u) atomicOr(errors.codes, 1u << code); + if ((pixel.x & 7u) == 0u && (pixel.y & 7u) == 0u) atomicOr(errors.codes, 1u); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index b21d0850b..8cfef3f52 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -189,20 +189,8 @@ void proveCmask(std::uint64_t address, std::size_t bytes, std::uint64_t generati else cmaskProofs()[address] = {bytes, generation}; } -std::string hexAddress(std::uint64_t address) { - char text[24]; - std::snprintf(text, sizeof(text), "0x%llx", static_cast(address)); - return text; -} - void requireCmaskCodes(std::uint32_t codes) { - if (codes == 0) return; Require((codes & 1u) == 0, "a CMASK fast clear of a DCC color target, whose clear belongs in its DCC keys, was recorded"); - std::string listed; - for (std::uint32_t code = 1; code < 15; ++code) { - if ((codes >> code) & 1u) listed += (listed.empty() ? "" : ", ") + std::to_string(code); - } - Require(false, "CMASK codes " + listed + " were recorded, which are not a single-sample fast-clear code (0 cleared, 15 expanded)"); } CmaskState readCmask(const ColorTarget& color, const CmaskLayout& layout, std::vector& cleared) { @@ -232,7 +220,6 @@ CmaskState readCmask(const ColorTarget& color, const CmaskLayout& layout, std::v const auto nibble = layout.Nibble(tileX, tileY); const auto code = (std::to_integer(mask[nibble / 2u]) >> ((nibble % 2u) * 4u)) & 0xfu; if (code == 0u) cleared.push_back(tileY * layout.TilesX() + tileX); - else Require(code == 0xfu, "CMASK code " + std::to_string(code) + " of color target " + hexAddress(color.address) + " is not a single-sample fast-clear code (0 cleared, 15 expanded)"); } } if (cleared.empty()) return CmaskState::Expanded; diff --git a/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp b/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp index ffb977d6e..dfadd4aab 100644 --- a/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/CmaskFastClear.cpp @@ -145,22 +145,14 @@ void run(AgcDriver::VulkanDevice& device, ColorTileMode mode, const std::string& expect(device, color, [](std::uint32_t x, std::uint32_t y) { return y < 4 ? Drawn : y >= 8 && x >= 24 ? Clear : Stale; }, name + " fast-cleared bottom edge tiles under a draw of the top rows"); } -void invalidCode(AgcDriver::VulkanDevice& device) { +void unknownCode(AgcDriver::VulkanDevice& device) { const auto color = target(ColorTileMode::RenderTarget); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX == 2 && tileY == 0; }, 3); eliminate(device, color); - { - std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); - device.WaitIdle(); - } + expect(device, color, [](std::uint32_t, std::uint32_t) { return Stale; }, "a tile with CMASK code 3 eliminated"); prepare(color, [](std::uint32_t, std::uint32_t) { return true; }); - try { - eliminate(device, color); - } catch (const std::runtime_error& error) { - Require(std::string(error.what()).find("were recorded, which are not a single-sample fast-clear code") != std::string::npos, std::string("unexpected failure: ") + error.what()); - return; - } - throw std::runtime_error("a CMASK code 3 read by the GPU pass did not throw"); + eliminate(device, color); + expect(device, color, [](std::uint32_t, std::uint32_t) { return Clear; }, "fast-cleared target eliminated after a CMASK code 3"); } } @@ -182,7 +174,7 @@ int main() { } run(*device, ColorTileMode::Linear, "linear"); run(*device, ColorTileMode::RenderTarget, "SW_64KB_R_X"); - invalidCode(*device); + unknownCode(*device); device->WaitIdle(); device.reset(); { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 6338e2462..a6c4230d0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1923,8 +1923,8 @@ void cmaskTests() { cmaskMemory.fill(0xff); setTile(2, 0); setTile(3, 3); - expectFailure([&] { AgcDriver::Graphics::RunColorMetadataPass(context, *pass); }, "not a single-sample fast-clear code"); - Require(texels(0x5a5a5a5au), "a refused pass changed the texels"); + AgcDriver::Graphics::RunColorMetadataPass(context, *pass); + Require(tiles([](std::uint32_t tile) { return tile == 2; }) && cmaskIs(0xff), "a tile with a CMASK code other than 0 or 15 did not keep its texels"); queue.context[0x31c] |= 0x10000000; const auto keysAddress = reinterpret_cast(dccKeys.data()); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 096a40be4..c9e77d176 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -67,7 +67,7 @@ - [DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) becomes `FragStencilRefEXT`, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it ([`emit_ps_mrtz_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L194)); the console was not measured. The host value is the test value of both faces and what `REPLACE_TEST` writes, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout, `STENCIL_OP_VAL_EXPORT_ENABLE` and a device without `VK_EXT_shader_stencil_export` (NVIDIA's driver 610.57 offers none). - [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. - [Descriptor pool chain](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - each pool holds two storage buffer descriptors more than a set can take, as a workaround: RADV (Mesa 26.0 and later) lets a `FREE_DESCRIPTOR_SET` pool place a set up to 32 bytes past the pool's buffer ([`util_vma_heap_init`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_descriptor_pool.c) is given `bo_size + RADV_POOL_HEAP_OFFSET` as a size), and with a page-multiple pool size those bytes are an unmapped page -- [CMASK fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as `CB_COLOR_CLEAR_WORD` into its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, codes other than 0 and 0xF, a cleared tile of a DCC target and a fast clear of a mipmapped, 3D or array color target throw. When the clear runs as a compute pass (a resident target whose CMASK is host-imported), an invalid code throws at the next fast-clear materialization, not at the one that read it +- [CMASK fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as `CB_COLOR_CLEAR_WORD` into its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, a cleared tile of a DCC target and a fast clear of a mipmapped, 3D or array color target throw. A tile with a code other than 0 and 0xF keeps its texels like an expanded tile: PPSA18391 (engine: Clickteam Fusion) fast clears a target whose CMASK it never initialized, so some of its nibbles hold leftover values such as 1 and 10; what the console does with such codes on a single-sample target is unverified on hardware - [HTILE depth fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - HTILE is not modeled. Only RADV's `clear_htile_mask` compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (`user[4]` and `user[5]` low nibbles both 0); the dispatch still runs, and the host depth image is cleared to `DB_DEPTH_CLEAR` the next time the surface with that `DB_HTILE_DATA_BASE` address is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, one `buffer_store_format_x` per thread through the stride-4 V# in `user[0..3]`, the value read by `s_buffer_load_dword` through the V# in `user[4..7]`), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) ([`MatchMemoryValueFill`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp), then the whole-HTILE and ZMask rules of `NoteDepthMetadataFill`). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. - [Host imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses `VK_EXT_external_memory_host` imports of read-only or executable mappings (and shared mappings of regular files) with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, after which every `vkAllocateMemory` on the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. On Windows, an alias of adjacent registered ranges is refused if any page is not a shared view; a range made entirely of shared views is aliased run by run, including across sections or nonconsecutive section offsets. Adjacent registered ranges are joined into one import only where they fill a whole 8 MiB aligned window; ranges of a partly filled window are imported one at a time, and a buffer that crosses two imports is copied. Adjacent registered ranges are joined into one import only where they fill a whole 8 MiB aligned window; ranges of a partly filled window are imported one at a time, and a buffer that crosses two imports is copied. The 8 MiB window is a choice: unbounded runs were re-imported whole on every growth of a heap mapped in small pieces, and the NVIDIA driver (610.57.04, 615.78.08) then refused imports or lost the device. Not measured on RADV. - [Cube texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) with equal `BASE_ARRAY` and `DEPTH` expose six faces starting at `BASE_ARRAY`. Measured with `image_load`, `image_sample` and `image_get_resinfo` on RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces or incomplete cube arrays still throw: hardware load bounds and filtered-sample addressing differ. Out-of-range cube-array sampling remains unimplemented. Samplers with `DISABLE_CUBE_WRAP` are created with `VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT`, the flag RADV maps to that bit (Mesa `src/amd/vulkan/radv_sampler.c`, `src/amd/common/ac_descriptors.c`); without `VK_EXT_non_seamless_cube_map` a cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware.