diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index e75a424dd5a0..b0c899b46211 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2079,6 +2079,8 @@ FrameDumps& Dumps() { } bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { + static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP")); + if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip"); if (buffer.tilingMode == 1) { const auto pixels = ReadDisplayBuffer(buffer); return present(buffer.width, buffer.height, true, pixels); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 80f87bd66bfb..0f6efa20fa61 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -152,6 +152,7 @@ class StorageTexture : public std::enable_shared_from_this { // overlapping pending image, as before). static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr); static void FlushAllPending(const char* reason); + static bool StoreAtFlipRequested(const char* value); // See PendingSerial: a change of a surface's source outside the registry (a unit shadow // retile) moves it too. static void BumpPendingSerial(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 135c06137947..cbf7cdfa18a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -2520,6 +2520,12 @@ void StorageTexture::FlushAllPending(const char* reason) { static_cast(FlushPending(0, std::numeric_limits::max(), nullptr, reason)); } +bool StorageTexture::StoreAtFlipRequested(const char* value) { + if (value == nullptr) return false; + if (std::strcmp(value, "1") == 0) return true; + throw std::runtime_error(std::string("APS5_STORE_AT_FLIP=") + value + ": expected 1"); +} + std::shared_ptr StorageTexture::FindPending(std::uint64_t address, std::uint64_t bytes) { auto& pending = Pending(); std::lock_guard lock(pending.mutex); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index b3ad04b7c890..f1fe408181bb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3461,6 +3461,13 @@ void highestDrawIndexTests() { expectFailure([&] { HighestDrawIndex(bytesOf(narrow), 1, false); }, "unsupported index size"); } +void storeAtFlipTests() { + using AgcDriver::Graphics::StorageTexture; + Require(!StorageTexture::StoreAtFlipRequested(nullptr), "an unset APS5_STORE_AT_FLIP stored at each flip"); + Require(StorageTexture::StoreAtFlipRequested("1"), "APS5_STORE_AT_FLIP=1 did not store at each flip"); + for (const char* value : {"0", "", "true", "11"}) expectFailure([&] { StorageTexture::StoreAtFlipRequested(value); }, "expected 1"); +} + void vertexCopyTests() { using AgcDriver::Graphics::PlanVertexCopies; using AgcDriver::Graphics::VertexFetch; @@ -3586,6 +3593,7 @@ int main() { validationTests(); vertexCopyTests(); highestDrawIndexTests(); + storeAtFlipTests(); pixelParameterSlotTests(); rectListTests(); floatControlsModeTests(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index fccfd977d865..61e66b5e217b 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -271,6 +271,7 @@ Throughout the project, every function at every stage either **does exactly what - a system call that writes into a protected page fails with `EFAULT` instead of faulting. `GuestArena::HostWrite` lifts the protection for the calls it brackets (the file reads); a host call that writes guest memory without it (sockets, `clock_gettime` and the like into a watched page) fails; - code that changes the protection of watched pages, or maps over them, has to call `GuestWriteWatchProtectionChanged`, and code that asks the kernel for a page's protection has to ask `GuestWriteWatchProtection` first, since a watched page is read-only between a collection and the next write. Nothing calls either yet: the macOS memory paths are not in the tree; - the handler is installed when libc loads and passes other faults to the handler installed before it. A handler installed later has to do the same. +- [Pending storage-image results](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). `APS5_STORE_AT_FLIP=1` stores every pending result at each flip; storing them at the title's GPU waits (labels, fences) is unimplemented. - Windows host-pointer imports of the guest mapping itself are compared instead of write-watched: a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. [GuestMemory](../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. This preserves CPU edits through byte comparisons but disables snapshot reuse and unit shadows for excluded blocks; restoring tracking on import retirement without replacing the backing and measuring the comparison cost remain unimplemented. - [shm_open](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) creates only anonymous objects (`SHM_ANON`), each backed by a host anonymous file: a `memfd` on Linux, a delete-on-close temporary file on Windows. The flag checks follow FreeBSD 11's `kern_shm_open`; the mode is ignored. Named objects throw. The descriptor cannot be mapped yet: `mmap` accepts only private anonymous mappings and fails a descriptor mapping with `EOPNOTSUPP`, so the known caller (the bundled Mesa in PPSA59322, which truncates the object and maps it `MAP_SHARED`) gets that failure - [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index d284149a86e7..f640c074f73e 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -108,6 +108,10 @@ Games that open the console's system font sets (`sceFontOpenFontSet`) need font The game runs on the first Vulkan 1.1 device with graphics and compute queues and swapchain presentation, preferring a discrete GPU over an integrated one. Set `ANYPS5_GPU` to a part of a device name, compared without regard to case, to run on another device; the names are printed at start-up in the `Physical device candidate` lines. When no usable device contains the text, the start fails and the error lists the device names. +### Storing GPU results at each flip + +GPU results in storage images and render targets stay on the GPU until something reads their memory. A title that frees such an image after waiting for its GPU work and reuses the memory can have its new data overwritten when the results are stored later ([TechnicalDebt](../dev/TechnicalDebt.md)). `APS5_STORE_AT_FLIP=1` stores every pending result when a display buffer is flipped, at the cost of one write-back per pending image and frame. Any other value stops the title at its first flip. + ## Exit codes `0`: conversion succeeded. `1`: invalid arguments. `2`: conversion failed; the error is printed to stderr. With `--autorun`, successful conversion returns the launched application's exit code.