Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
24 changes: 21 additions & 3 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,9 +857,17 @@ class DepthSurface {

void MarkStencilStale() {
if (!StencilMirrorable()) return;
const auto [begin, end] = staleStencilRange(*this);
const auto bytes = static_cast<std::size_t>(end - begin);
if (GuestMemory::Watched(begin, bytes) || !GuestMemory::Accessible(reinterpret_cast<const void*>(begin), bytes)) {
stencilStaleSnapshot.clear();
} else if (!stencilStale || stencilStaleSnapshot.size() != bytes || !GuestMemory::EqualsCommittedUnsynced(begin, stencilStaleSnapshot)) {
stencilStaleSnapshot.resize(bytes);
GuestMemory::Read(begin, stencilStaleSnapshot);
}
stencilStale = true;
stencilStaleGeneration = GuestMemory::TrackerGeneration();
stencilMark = nextPlaneSerial();
const auto [begin, end] = staleStencilRange(*this);
noteStalePlane(begin, end);
}

Expand Down Expand Up @@ -1422,6 +1430,8 @@ class DepthSurface {

bool stencilStale = false;
std::uint64_t stencilGeneration = 0;
std::uint64_t stencilStaleGeneration = 0;
std::vector<std::byte> stencilStaleSnapshot;

std::vector<bool> staleUnits;
std::size_t staleCount = 0;
Expand Down Expand Up @@ -2823,12 +2833,17 @@ void retireStencil(DepthSurface& surface) {
forgetStaleStencil(surface);
}

bool stencilWrittenSinceStale(const DepthSurface& surface, std::uint64_t begin, std::size_t bytes) {
if (GuestMemory::Watched(begin, bytes)) return !GuestMemory::UnchangedSinceCollected(begin, bytes, surface.stencilStaleGeneration);
return surface.stencilStaleSnapshot.size() == bytes && !GuestMemory::EqualsCommittedUnsynced(begin, surface.stencilStaleSnapshot);
}

void storeStaleStencil(DepthSurface& surface) {
if (!surface.stencilStale || surface.syncing || !surface.StencilMirrorable()) return;
const auto [begin, end] = staleStencilRange(surface);
const auto bytes = static_cast<std::size_t>(end - begin);
if (GuestMemory::CommittedRanges(begin, bytes).empty()) return;
if (unmappedSince(begin, end, surface.stencilMark)) {
if (unmappedSince(begin, end, surface.stencilMark) || stencilWrittenSinceStale(surface, begin, bytes)) {
retireStencil(surface);
return;
}
Expand Down Expand Up @@ -3221,7 +3236,10 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint6
if (surface->stencilStale && !surface->syncing) {
const auto [stencilBegin, stencilEnd] = staleStencilRange(*surface);
if (stencilBegin < end && address < stencilEnd) {
if (GuestMemory::CommittedRanges(stencilBegin, static_cast<std::size_t>(stencilEnd - stencilBegin)).empty() || unmappedSince(stencilBegin, stencilEnd, surface->stencilMark)) {
const auto stencilBytes = static_cast<std::size_t>(stencilEnd - stencilBegin);
if (GuestMemory::CommittedRanges(stencilBegin, stencilBytes).empty() ||
unmappedSince(stencilBegin, stencilEnd, surface->stencilMark) ||
stencilWrittenSinceStale(*surface, stencilBegin, stencilBytes)) {
surface->stencilStale = false;
} else {
auto mirror = surface->StoreStencilPlane(stencilResource(surface->target));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,41 @@ int main() {
Require(Holding(stencilPlane, 0x2au) == StencilBytes, "retiring the surface for a write over its depth plane only dropped the stencil 0x2a a draw wrote");
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane stored after its surface retired stored it again");

Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x3cu)}) == PlaneTexels, "the draw writing stencil 0x3c did not cover the plane");
device->WaitIdle();
Fill(stencilPlane, 0x77u);
AgcDriver::Graphics::RetireDepthSurfaces(device->graphicsContext().device, reinterpret_cast<std::uintptr_t>(depthPlane), DepthBytes);
device->WaitIdle();
Require(Holding(stencilPlane, 0x77u) == StencilBytes, "retiring the surface stored its stale stencil 0x3c over the guest's later write of 0x77");
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane the guest overwrote stored the stale stencil");

Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x3cu)}) == PlaneTexels, "the draw writing stencil 0x3c before a guest write and a second draw did not cover the plane");
device->WaitIdle();
Fill(stencilPlane, 0x44u);
Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x55u)}) == PlaneTexels, "the draw writing stencil 0x55 after the guest write did not cover the plane");
device->WaitIdle();
AgcDriver::Graphics::RetireDepthSurfaces(device->graphicsContext().device, reinterpret_cast<std::uintptr_t>(depthPlane), DepthBytes);
device->WaitIdle();
Require(Holding(stencilPlane, 0x55u) == StencilBytes, "retiring the surface kept the guest's 0x44 over the stencil 0x55 a later draw wrote");

Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x3cu)}) == PlaneTexels, "the draw writing stencil 0x3c before a guest write and a read did not cover the plane");
device->WaitIdle();
Fill(stencilPlane, 0x77u);
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane the guest overwrote after the last draw stored the stale stencil");
device->WaitIdle();
Require(Holding(stencilPlane, 0x77u) == StencilBytes, "a read of the stencil plane replaced the guest's write after the last draw");

Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x3cu)}) == PlaneTexels, "the draw writing stencil 0x3c before a guest write, a second draw and a read did not cover the plane");
device->WaitIdle();
Fill(stencilPlane, 0x66u);
Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x55u)}) == PlaneTexels, "the draw writing stencil 0x55 after the guest write and before a read did not cover the plane");
device->WaitIdle();
Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane a draw wrote after the guest's write stored nothing");
device->WaitIdle();
Require(Holding(stencilPlane, 0x55u) == StencilBytes, "a read of the stencil plane kept the guest's 0x66 over the stencil 0x55 a later draw wrote");
AgcDriver::Graphics::RetireDepthSurfaces(device->graphicsContext().device, reinterpret_cast<std::uintptr_t>(depthPlane), DepthBytes);
device->WaitIdle();

if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(stencilBase, StencilBytes)) {
std::puts("guest memory is not write-watched here: a guest write into the stencil plane is not exercised");
} else {
Expand Down
2 changes: 2 additions & 0 deletions docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@

### Functional

- [Stale stencil guest writes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a CPU write after the last stencil draw drops the whole pending stencil store. Untouched bytes therefore retain their previous guest value instead of the drawn stencil; on write-watched memory a write anywhere in an overlapping tracked page can drop the store. The unwatched path compares the entire stencil plane on each subsequent stencil-writing bind or stale-marking clear/fill while the graphics locks are held, and copies it again when guest bytes changed. Bytewise merging and console ordering for partial CPU writes are not implemented.

- [Native multisampled depth import](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - refuses a depth HTILE that is not pipe-aligned (`0x2af` bit 18 is zero). Sonic Superstars (PPSA06888) uses a layout with iterate-256 also off (`DB_Z_INFO` bit 20 is zero) on its 2x depth targets, and the refusal stops presentation before its title screen. Not verified: whether the clear-proof is correct for a non-pipe-aligned layout, since the import reads the HTILE words as a flat array.
- [Written staging window on discrete GPUs](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) - a written buffer element of a dispatch that is not swept is staged device-local up to 8 MiB on a discrete GPU (`Context::discreteGpu`) and up to 2 MiB otherwise; `APS5_WRITTEN_SHADOW_MAX_KIB` replaces either bound. The 8 MiB figure comes from one title on one NVIDIA Windows system (PPSA09911: a 2.5 and a 3.3 MiB skin-cache output written in place cost 55 ms of GPU time per frame, 1.8 ms of extra staging copies once staged); the point where the two copies of a larger element cost more than its in-place stores, and whether AMD or Linux drivers move it, has not been measured. The window is per element and per use, so a kernel that streams once through an element inside the window pays the copy-in and the copy-back on every dispatch.
- [FS table calls on macOS](../../../core/libs/prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp) (libSceAgcDriver) - macOS keeps the console's 0xfe0000000 for itself, so the driver block, and the FS table at 0xfe0040000 inside it, moves to 0xee0000000 there; `sceAgc*StaticBuffer` and `AgcDriverStaticTable_nid_postfix` use the moved address. A shader that builds the table address from two `s_mov_b32` literals still loads from 0xfe0040000: `ReadTableCallees` (`Driver/Shaders/ShaderRegistry.cpp`) reads that address unchanged, and the shader also loads from it at run time, so those dispatches cannot link on macOS. #6160 lists titles that build this address. `ComputeFsTableCalls` is skipped on macOS as it is under `ANYPS5_ENABLE_ASAN`. Closes when: the driver rewrites that pointer in the shader code for both the callee lookup and the code the GPU runs, or the console address can be mapped on macOS.
Expand Down
Loading