Skip to content
Open
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
37 changes: 31 additions & 6 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,11 +855,23 @@ class DepthSurface {
if ((aspects & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) stencilFill.reset();
}

void MarkStencilStale() {
void TakeStencilBaseline() {
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)) {
stencilBaseline.clear();
return;
}
stencilBaseline.resize(bytes);
GuestMemory::Read(begin, stencilBaseline);
}

void MarkStencilStale(bool written = false) {
if (!StencilMirrorable()) return;
const auto [begin, end] = staleStencilRange(*this);
if (written || stencilBaseline.size() != static_cast<std::size_t>(end - begin)) TakeStencilBaseline();
stencilStale = true;
stencilMark = nextPlaneSerial();
const auto [begin, end] = staleStencilRange(*this);
noteStalePlane(begin, end);
}

Expand All @@ -877,7 +889,7 @@ class DepthSurface {
NoteDepthWork();
} else {
stencilFill = stencil;
MarkStencilStale();
MarkStencilStale(true);
}
}

Expand Down Expand Up @@ -1418,10 +1430,12 @@ class DepthSurface {

void NoteStencilStored(std::uint64_t generation) {
stencilGeneration = generation;
if (StencilMirrorable()) TakeStencilBaseline();
}

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

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

bool stencilWrittenSinceBaseline(const DepthSurface& surface, std::uint64_t begin, std::size_t bytes) {
if (GuestMemory::Watched(begin, bytes)) {
GuestMemory::CollectWritesUncached(begin, bytes);
return surface.stencilGeneration != 0u && GuestMemory::WrittenSince(begin, bytes, surface.stencilGeneration);
}
return surface.stencilBaseline.size() == bytes && !GuestMemory::EqualsCommittedUnsynced(begin, surface.stencilBaseline);
}

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) || stencilWrittenSinceBaseline(surface, begin, bytes)) {
retireStencil(surface);
return;
}
Expand Down Expand Up @@ -3138,7 +3160,7 @@ VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const
surface->MarkAllStale(planeBytes);
noteStalePlane(plane, plane + planeBytes);
}
if (stencilWritten || created || stencilRebound) surface->MarkStencilStale();
if (stencilWritten || created || stencilRebound) surface->MarkStencilStale(stencilWritten);
drawnDepth = surface;
return surface->view;
}
Expand Down Expand Up @@ -3221,7 +3243,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) ||
stencilWrittenSinceBaseline(*surface, stencilBegin, stencilBytes)) {
surface->stencilStale = false;
} else {
auto mirror = surface->StoreStencilPlane(stencilResource(surface->target));
Expand Down
164 changes: 164 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#endif
#include <windows.h>
#else
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <array>
Expand Down Expand Up @@ -118,6 +120,130 @@ void CheckConsumedStencilClear(AgcDriver::VulkanDevice& device, const DepthFastC
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "the consumed stencil clear was stored twice");
}

void CheckGuestWriteBeforeRepeatedClears(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& shaders, std::uint32_t swizzle) {
auto* blocks = static_cast<std::byte*>(AllocateBlocks(DepthBytes + StencilBytes + Block));
auto* depth = reinterpret_cast<float*>(blocks);
auto* stencil = reinterpret_cast<std::uint8_t*>(blocks + DepthBytes);
auto* htile = reinterpret_cast<std::uint32_t*>(blocks + DepthBytes + StencilBytes);
const auto htileBytes = AgcDriver::Graphics::HtileMetadataBytes(PlaneExtent);
std::fill(depth, depth + PlaneTexels, 1.0f);
Fill(stencil, 0u);
std::fill(htile, htile + htileBytes / sizeof(*htile), 0xffffffffu);
AgcDriver::GuestMemory::BumpCollectEpoch();
const auto stencilBase = reinterpret_cast<std::uintptr_t>(stencil);
const Surface surface{reinterpret_cast<std::uintptr_t>(depth), stencilBase, reinterpret_cast<std::uintptr_t>(htile), true, VK_FORMAT_D32_SFLOAT_S8_UINT, 0, 0, swizzle};
Require(Draw(device, shaders, surface, {.depthTest = true, .depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x18u)}, PlaneExtent, Colors) == PlaneTexels, "the draw before the guest write did not cover the plane");
Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "the stencil drawn before the guest write was not stored");
device.WaitIdle();
Require(Holding(stencil, 0x18u) == StencilBytes, "the stencil drawn before the guest write did not reach guest memory");
Fill(stencil, 0x77u);
AgcDriver::GuestMemory::BumpCollectEpoch();
for (int clear = 0; clear < 2; ++clear) {
std::fill(htile, htile + htileBytes / sizeof(*htile), 0u);
AgcDriver::GuestMemory::BumpCollectEpoch();
AgcDriver::Graphics::NoteDepthMetadataFill(surface.htile, htileBytes, 0u);
}
static_cast<void>(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes));
device.WaitIdle();
Require(Holding(stencil, 0x77u) == StencilBytes, "stale stencil planes re-marked by HTILE clears were stored over the guest's write after their last store");
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a stencil plane the guest wrote after its last store stayed stale");
}

#ifdef __linux__
struct SharedPlanes {
int file = -1;
std::uintptr_t begin = 0;
std::size_t bytes = 0;
std::uint64_t offset = 0;
};
SharedPlanes sharedPlanes;

bool ResolveSharedPlanes(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset) {
if (sharedPlanes.file < 0 || bytes == 0 || address < sharedPlanes.begin || address - sharedPlanes.begin >= sharedPlanes.bytes || bytes > sharedPlanes.bytes - (address - sharedPlanes.begin)) return false;
*file = fcntl(sharedPlanes.file, F_DUPFD_CLOEXEC, 0);
*offset = sharedPlanes.offset + (address - sharedPlanes.begin);
return *file >= 0;
}
#endif

void* AllocateSharedBlocks(std::size_t bytes) {
#ifdef __linux__
const int file = memfd_create("stencil-plane-memory", MFD_CLOEXEC | MFD_ALLOW_SEALING);
Require(file >= 0, "cannot create the shared depth and stencil planes");
Require(ftruncate(file, static_cast<off_t>(bytes + Block)) == 0 && fcntl(file, F_ADD_SEALS, F_SEAL_SHRINK) == 0, "cannot size the shared depth and stencil planes");
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0);
Require(raw != MAP_FAILED, "cannot map the shared depth and stencil planes");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
sharedPlanes = {file, aligned, bytes, aligned - begin};
GuestArena::GuestArenaSetSharedBacking_nid_postfix(ResolveSharedPlanes);
void* block = reinterpret_cast<void*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
GuestAllocations::Mutation().Add(block, bytes, true, true, true);
return block;
#else
return AllocateBlocks(bytes);
#endif
}

void CheckReusedStencilPlane(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& shaders, std::uint32_t swizzle) {
auto* blocks = static_cast<std::byte*>(AllocateSharedBlocks(DepthBytes + StencilBytes));
auto* depth = reinterpret_cast<float*>(blocks);
auto* stencil = reinterpret_cast<std::uint8_t*>(blocks + DepthBytes);
std::fill(depth, depth + DepthBytes / sizeof(float), 1.0f);
Fill(stencil, 0u);
const auto stencilBase = reinterpret_cast<std::uintptr_t>(stencil);
const Surface surface{reinterpret_cast<std::uintptr_t>(depth), stencilBase, 0, false, VK_FORMAT_D32_SFLOAT_S8_UINT, 0, 0, swizzle};
const auto draw = [&](std::uint8_t reference) {
Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, reference)}, PlaneExtent, Colors) == PlaneTexels, "a draw writing stencil over the reused plane did not cover it");
};
draw(0x18u);
Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the shared stencil plane after a draw stored nothing");
device.WaitIdle();
Require(Holding(stencil, 0x18u) == StencilBytes, "the stencil drawn into the shared plane did not reach guest memory");
if (!AgcDriver::GuestMemory::Watched(stencilBase, StencilBytes)) {
std::puts("the shared stencil plane is not write-watched here: a guest reusing the plane's memory is not exercised");
return;
}
constexpr std::size_t RecordBytes = 0x30;
constexpr std::array<std::size_t, 3> RecordOffsets{0x1000, 0x7dd0, StencilBytes - RecordBytes};
const auto writeRecords = [&](std::uint8_t value) {
for (const auto offset : RecordOffsets) std::fill(stencil + offset, stencil + offset + RecordBytes, value);
AgcDriver::GuestMemory::BumpCollectEpoch();
};
const auto recordsHold = [&](std::uint8_t value) {
return std::all_of(RecordOffsets.begin(), RecordOffsets.end(), [&](std::size_t offset) { return std::all_of(stencil + offset, stencil + offset + RecordBytes, [&](std::uint8_t byte) { return byte == value; }); });
};

draw(0x66u);
Fill(stencil, 0x77u);
const bool storedOverFill = !AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty();
device.WaitIdle();
Require(Holding(stencil, 0x77u) == StencilBytes, "a read of a stencil plane the guest wrote over after a draw stored the stencil drawn before the write over the guest's bytes");
Require(!storedOverFill, "a read of a stencil plane the guest wrote over after a draw stored the stencil drawn before the write");

draw(0x2bu);
writeRecords(0x7au);
AgcDriver::Graphics::RetireDepthSurfaces(device.graphicsContext().device, surface.depth, DepthBytes);
device.WaitIdle();
Require(recordsHold(0x7au), "retiring the surface stored the stencil drawn before the guest wrote records into the plane over them");
Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane whose retired surface's stencil was dropped stored it");

draw(0x4du);
writeRecords(0x3cu);
static_cast<void>(AgcDriver::Graphics::FlushDepthPlanes(surface.depth, DepthBytes + StencilBytes, AgcDriver::Graphics::DepthPlaneScope::Whole));
device.WaitIdle();
Require(recordsHold(0x3cu), "a whole read of the planes stored the stencil drawn before the guest wrote records into the plane over them");

writeRecords(0x5eu);
draw(0x18u);
Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane drawn after the guest's records stored nothing");
device.WaitIdle();
Require(Holding(stencil, 0x18u) == StencilBytes, "the stencil drawn after the guest wrote records did not reach guest memory over them");
}

}

int main() {
Expand Down Expand Up @@ -145,6 +271,7 @@ int main() {

CheckConsumedStencilClear(*device, shaders, swizzle, true);
CheckConsumedStencilClear(*device, shaders, swizzle, false);
CheckGuestWriteBeforeRepeatedClears(*device, shaders, swizzle);

std::size_t readOnlyFailures = 0;
const auto checkReadOnly = [&](const DrawOptions& options, const char* name) {
Expand All @@ -167,6 +294,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 All @@ -190,6 +352,8 @@ int main() {
Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane drawn after the unmap stored nothing");
device->WaitIdle();
Require(Holding(stencilPlane, 0x18u) == StencilBytes, "the stencil drawn after the unmap did not reach guest memory");

CheckReusedStencilPlane(*device, shaders, swizzle);
std::puts("stencil plane memory tests passed");
return 0;
} catch (const std::exception& error) {
Expand Down
Loading
Loading