Skip to content
Closed
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
67 changes: 42 additions & 25 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <array>
#include <bit>
#include <chrono>
#include <condition_variable>
#include <cmath>
#include <cstdio>
#include <cstdint>
Expand Down Expand Up @@ -1488,6 +1489,7 @@ struct PendingWrites {
PendingList textures;
// Images taken out of `textures` by a FlushPending still storing them (see adjacentPendingUnchanged).
std::vector<StorageTexture*> flushing;
std::condition_variable_any flushingChanged;
};

PendingWrites& Pending() {
Expand Down Expand Up @@ -3641,32 +3643,44 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
// The images stay alive across the scan: their last owner may be a batch's kept list, which
// another thread releases outside the GPU mutex once the batch completed.
std::vector<std::shared_ptr<StorageTexture>> flush;
{
auto& pending = Pending();
std::lock_guard lock(pending.mutex);
const auto* exempt = refreshing;
for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) {
auto* texture = *it;
if (texture != except && texture != exempt && texture->overlaps(address, bytes)) {
if (!texture->pendingUnitInside(address, bytes)) {
pretestSkipped.fetch_add(1, std::memory_order_relaxed);
bool waitForFlush = false;
for (;;) {
{
auto& pending = Pending();
std::lock_guard lock(pending.mutex);
const auto* exempt = refreshing;
for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) {
auto* texture = *it;
if (texture != except && texture != exempt && texture->overlaps(address, bytes)) {
if (!texture->pendingUnitInside(address, bytes)) {
pretestSkipped.fetch_add(1, std::memory_order_relaxed);
++it;
continue;
}
texture->dirty = false;
if (const auto skips = texture->hookSkips.exchange(0, std::memory_order_relaxed); skips != 0 && texture->skippedResultsInside(address, bytes)) {
flushedAfterSkip.fetch_add(1, std::memory_order_relaxed);
if (PublishReasonFor(reason) == PublishReason::Hook && texture->hookSkipSite.load(std::memory_order_relaxed) == static_cast<std::uint8_t>(GuestMemory::CurrentReadSite())) flushedAfterSkipSameSite.fetch_add(1, std::memory_order_relaxed);
}
if (auto alive = texture->weak_from_this().lock()) flush.push_back(std::move(alive));
it = pending.textures.erase(it);
} else {
++it;
continue;
}
texture->dirty = false;
if (const auto skips = texture->hookSkips.exchange(0, std::memory_order_relaxed); skips != 0 && texture->skippedResultsInside(address, bytes)) {
flushedAfterSkip.fetch_add(1, std::memory_order_relaxed);
if (PublishReasonFor(reason) == PublishReason::Hook && texture->hookSkipSite.load(std::memory_order_relaxed) == static_cast<std::uint8_t>(GuestMemory::CurrentReadSite())) flushedAfterSkipSameSite.fetch_add(1, std::memory_order_relaxed);
}
if (auto alive = texture->weak_from_this().lock()) flush.push_back(std::move(alive));
it = pending.textures.erase(it);
} else {
++it;
}
for (const auto& texture : flush) pending.flushing.push_back(texture.get());
if (!flush.empty()) BumpPendingSerial();
if (flush.empty() && !GuestMemory::GpuMutex().HeldByThisThread()) {
waitForFlush = std::any_of(pending.flushing.begin(), pending.flushing.end(), [&](const auto* texture) { return texture->overlaps(address, bytes); });
}
}
// Still pending for the adjacency rule of writeBack until each one is stored.
for (const auto& texture : flush) pending.flushing.push_back(texture.get());
if (!flush.empty()) BumpPendingSerial();
if (!flush.empty() || !waitForFlush) break;
auto& pending = Pending();
std::unique_lock lock(pending.mutex);
pending.flushingChanged.wait(lock, [&] {
return std::none_of(pending.flushing.begin(), pending.flushing.end(), [&](const auto* texture) { return texture->overlaps(address, bytes); });
});
waitForFlush = false;
}
if (flush.empty()) {
// Units shadowed over the range (another image's results retiled into the import's unit
Expand All @@ -3679,9 +3693,12 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
const std::vector<std::shared_ptr<StorageTexture>>& flush;
~Unregister() {
auto& pending = Pending();
std::lock_guard lock(pending.mutex);
for (const auto& texture : flush) std::erase(pending.flushing, texture.get());
BumpPendingSerial();
{
std::lock_guard lock(pending.mutex);
for (const auto& texture : flush) std::erase(pending.flushing, texture.get());
BumpPendingSerial();
}
pending.flushingChanged.notify_all();
}
} unregister{flush};
// Debug aid: APS5_TRACE_FLUSH names what forces pending results to guest memory.
Expand Down
67 changes: 67 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/StorageFileWrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
#include <limits>
#include <span>
#include <string>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
Expand Down Expand Up @@ -240,6 +244,68 @@ bool CheckWrite(Api api, const char* name, GuestSurface& surface) {
return differences == 0;
}

void CheckConcurrentFlushWait(GuestSurface& surface) {
Produce(surface.Data());
const auto address = reinterpret_cast<std::uintptr_t>(surface.Data());
Require(AgcDriver::Graphics::StorageTexture::FindPending(address, SurfaceBytes) != nullptr, "file write: concurrent flush setup has no pending image");
std::mutex mutex;
std::condition_variable changed;
bool gpuLocked = false;
bool releaseGpu = false;
std::thread holder([&] {
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
{
std::lock_guard lock(mutex);
gpuLocked = true;
}
changed.notify_all();
std::unique_lock lock(mutex);
changed.wait(lock, [&] { return releaseGpu; });
});
{
std::unique_lock lock(mutex);
changed.wait(lock, [&] { return gpuLocked; });
}
std::exception_ptr flushFailure;
std::thread flusher([&] {
try { AgcDriver::Graphics::StorageTexture::FlushPending(address, SurfaceBytes, nullptr, "concurrent flush test"); }
catch (...) { flushFailure = std::current_exception(); }
});
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (AgcDriver::Graphics::StorageTexture::FindPending(address, SurfaceBytes) != nullptr && std::chrono::steady_clock::now() < deadline) std::this_thread::yield();
const bool taken = AgcDriver::Graphics::StorageTexture::FindPending(address, SurfaceBytes) == nullptr;
std::vector<std::byte> seen(SurfaceBytes);
std::atomic<bool> readDone = false;
std::exception_ptr readFailure;
std::thread reader;
if (taken) {
reader = std::thread([&] {
try {
AgcDriver::Graphics::StorageTexture::FlushPending(address, SurfaceBytes, nullptr, "concurrent read test");
AgcDriver::GuestMemory::Read(address, seen);
} catch (...) {
readFailure = std::current_exception();
}
readDone.store(true, std::memory_order_release);
changed.notify_all();
});
std::unique_lock lock(mutex);
changed.wait_for(lock, std::chrono::milliseconds(50), [&] { return readDone.load(std::memory_order_acquire); });
releaseGpu = true;
} else {
std::lock_guard lock(mutex);
releaseGpu = true;
}
changed.notify_all();
holder.join();
flusher.join();
if (reader.joinable()) reader.join();
Require(taken, "file write: concurrent flush did not take the pending image");
if (flushFailure != nullptr) std::rethrow_exception(flushFailure);
if (readFailure != nullptr) std::rethrow_exception(readFailure);
Require(std::any_of(seen.begin(), seen.end(), [](std::byte value) { return value != std::byte{}; }), "file write: concurrent guest read returned before an overlapping image was stored");
}

void CheckShutdownAndGuard() {
OutputFile file;
std::vector<std::byte> source(TransferBytes, std::byte{0x5a});
Expand All @@ -266,6 +332,7 @@ int main() {
}
GuestSurface surface;
GuestSurface unrelated;
CheckConcurrentFlushWait(surface);
Produce(unrelated.Data());
bool correct = true;
const std::array<std::pair<Api, const char*>, 12> cases{{
Expand Down
Loading