Skip to content
Merged
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
4 changes: 3 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,8 @@ if(BUILD_TESTING)
set_tests_properties(agc_copied_direct_ordering PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_recorder_read_list COMMAND agc_driver_recorder_tests --read-list-only)
set_tests_properties(agc_recorder_read_list PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_host_import_unmap COMMAND agc_driver_recorder_tests --host-import-unmap-only)
set_tests_properties(agc_host_import_unmap PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_parked_import_unmap COMMAND agc_driver_recorder_tests --parked-import-unmap-only)
Expand Down Expand Up @@ -1056,7 +1058,7 @@ if(BUILD_TESTING)
add_test(NAME agc_metadata_pass COMMAND agc_driver_recorder_tests --metadata-pass-only)
set_tests_properties(agc_metadata_pass PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_multisample_guest_access agc_draw_inputs_in_place agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_multisample_guest_access agc_draw_inputs_in_place agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
add_test(NAME agc_host_import_second_mapping COMMAND agc_driver_recorder_tests --host-import-second-mapping-only)
set_tests_properties(agc_host_import_second_mapping PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ class GuestBufferMemory {
// the GPU copies out of an import (gpuCopy) notes its read itself when the copy is recorded.
// For the recorder's read tracking (ShaderResources::MarkGpuWrites); nothing once committed.
std::vector<std::pair<std::uint64_t, std::uint64_t>> InPlaceReads() const;
struct SplitReads {
std::shared_ptr<const std::vector<std::pair<std::uint64_t, std::uint64_t>>> space;
std::vector<std::pair<std::uint64_t, std::uint64_t>> own;
};
std::optional<SplitReads> SplitInPlaceReads() const;
std::vector<std::pair<std::uint64_t, std::uint64_t>> DeviceReads() const;

private:
Expand Down
13 changes: 11 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class Recorder {
static bool ReadTracking();
void NotePendingRead(std::uint64_t address, std::size_t bytes, ReadKind kind);
void NotePendingReads(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges, ReadKind kind);
using ReadList = std::vector<std::pair<std::uint64_t, std::uint64_t>>;
void NotePendingReadList(std::shared_ptr<const ReadList> ranges, ReadKind kind);
static bool ValidReadList(const ReadList& ranges);
// Whether an unexecuted batch (open, or in flight with its fence unsignaled) reads the range in
// place. A hit in an in-flight batch whose fence signaled meanwhile is a miss (one status
// query per hit). `ignoreSignaled` false: every in-flight batch counts, whatever its fence.
Expand Down Expand Up @@ -533,6 +536,12 @@ class Recorder {
ReadKind kind;
};
std::vector<Read> reads;
struct ReadListNote {
std::shared_ptr<const ReadList> ranges;
std::size_t position;
ReadKind kind;
};
std::vector<ReadListNote> readLists;
// Submission number (1-based): identifies a batch after its allocation may have been reused.
std::uint64_t serial = 0;
std::uint64_t traceId = 0;
Expand Down Expand Up @@ -738,8 +747,8 @@ class Recorder {
void finish(std::unique_ptr<Batch> batch, bool wait, int source, bool releaseNow = false);
void release(Batch& batch) noexcept;
static bool overlaps(const Batch& batch, std::uint64_t address, std::uint64_t end);
// The first noted read of `batch` overlapping [address, end), or null.
static const Batch::Read* readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end);
static std::optional<ReadKind> readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end);
static std::vector<std::pair<std::uint64_t, std::uint64_t>> notedReads(const Batch& batch);
bool signaled(const Batch& batch) const;
// Rebuilds the lock-free snapshot of pending writes from open, inFlight and finishing.
void publishPendingWrites(std::span<const std::pair<std::uint64_t, std::uint64_t>> additions = {}) const;
Expand Down
17 changes: 17 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ struct GuestBufferMemory::AddressSpace {
std::vector<CopiedRange> copied;
// The BDA table entries of `base`, in its order.
std::vector<ShaderRecompiler::BdaAbi::Range> ranges;
std::shared_ptr<const std::vector<std::pair<std::uint64_t, std::uint64_t>>> directReads;
mutable std::mutex tableMutex;
mutable std::map<std::vector<std::pair<std::uint64_t, std::uint64_t>>, std::pair<std::uint64_t, std::shared_ptr<const std::vector<ShaderRecompiler::BdaAbi::Range>>>> writeTables;
};
Expand Down Expand Up @@ -2616,6 +2617,13 @@ void GuestBufferMemory::AcquireRegistered() {
regions = std::move(extras);
lease.clear();
planSpaceMirrors(*built);
{
auto reads = std::make_shared<Recorder::ReadList>();
for (const auto& region : built->base) {
if (region.direct != nullptr) reads->emplace_back(region.begin, region.end);
}
if (Recorder::ValidReadList(*reads)) built->directReads = std::move(reads);
}
space = std::move(built);
spaces.droppedByWaiter.store(false, std::memory_order_relaxed);
spaces.current.store(space);
Expand Down Expand Up @@ -4118,6 +4126,15 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::InPlaceR
return result;
}

std::optional<GuestBufferMemory::SplitReads> GuestBufferMemory::SplitInPlaceReads() const {
if (!uploaded || committed || space == nullptr || space->directReads == nullptr) return std::nullopt;
SplitReads split{space->directReads, {}};
for (const auto& region : regions) {
if (region.direct != nullptr) split.own.emplace_back(region.begin, region.end);
}
return split;
}

std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::DeviceReads() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
if (!uploaded || committed) return result;
Expand Down
75 changes: 62 additions & 13 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2863,32 +2863,81 @@ void Recorder::NotePendingReads(std::span<const std::pair<std::uint64_t, std::ui
readsNoted.fetch_add(ranges.size(), std::memory_order_relaxed);
}

const Recorder::Batch::Read* Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end) {
for (const auto& read : batch.reads) {
if (address < read.end && read.begin < end) return &read;
bool Recorder::ValidReadList(const ReadList& ranges) {
for (std::size_t index = 0; index < ranges.size(); ++index) {
if (ranges[index].second <= ranges[index].first) return false;
if (index != 0 && ranges[index].first < ranges[index - 1].second) return false;
}
return nullptr;
return true;
}

void Recorder::NotePendingReadList(std::shared_ptr<const ReadList> ranges, ReadKind kind) {
if (ranges == nullptr || ranges->empty() || !ReadTrackingEnabled()) return;
if (CaptureTrace::Enabled() || FlipReadCheck()) {
NotePendingReads(*ranges, kind);
return;
}
ensureOpen();
readsNoted.fetch_add(ranges->size(), std::memory_order_relaxed);
for (const auto& note : open->readLists) {
if (note.ranges == ranges && note.kind == kind) return;
}
open->readLists.push_back({std::move(ranges), open->reads.size(), kind});
}

std::optional<Recorder::ReadKind> Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end) {
const auto listOverlaps = [&](const Batch::ReadListNote& note) {
const auto& list = *note.ranges;
const auto found = std::partition_point(list.begin(), list.end(), [&](const auto& range) { return range.second <= address; });
return found != list.end() && found->first < end;
};
std::size_t next = 0;
for (std::size_t index = 0; index < batch.reads.size(); ++index) {
for (; next < batch.readLists.size() && batch.readLists[next].position == index; ++next) {
if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind;
}
const auto& read = batch.reads[index];
if (address < read.end && read.begin < end) return read.kind;
}
for (; next < batch.readLists.size(); ++next) {
if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind;
}
return std::nullopt;
}

std::vector<std::pair<std::uint64_t, std::uint64_t>> Recorder::notedReads(const Batch& batch) {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
std::size_t next = 0;
const auto expand = [&](std::size_t position) {
for (; next < batch.readLists.size() && batch.readLists[next].position == position; ++next) result.insert(result.end(), batch.readLists[next].ranges->begin(), batch.readLists[next].ranges->end());
};
for (std::size_t index = 0; index < batch.reads.size(); ++index) {
expand(index);
result.emplace_back(batch.reads[index].begin, batch.reads[index].end);
}
expand(batch.reads.size());
return result;
}

bool Recorder::PendingReadOverlaps(std::uint64_t address, std::size_t bytes, bool ignoreSignaled) const {
if (bytes == 0) return false;
readQueries.fetch_add(1, std::memory_order_relaxed);
const auto end = address + bytes;
if (open != nullptr) {
if (const auto* read = readOverlap(*open, address, end)) {
readHits[static_cast<std::size_t>(read->kind)].fetch_add(1, std::memory_order_relaxed);
if (const auto kind = readOverlap(*open, address, end)) {
readHits[static_cast<std::size_t>(*kind)].fetch_add(1, std::memory_order_relaxed);
return true;
}
}
// Newest first: the batch most likely still running decides without a status query per batch.
for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) {
const auto* read = readOverlap(**it, address, end);
if (read == nullptr) continue;
const auto kind = readOverlap(**it, address, end);
if (!kind.has_value()) continue;
if (ignoreSignaled && signaled(**it)) {
readStaleIgnored.fetch_add(1, std::memory_order_relaxed);
continue;
}
readHits[static_cast<std::size_t>(read->kind)].fetch_add(1, std::memory_order_relaxed);
readHits[static_cast<std::size_t>(*kind)].fetch_add(1, std::memory_order_relaxed);
return true;
}
return false;
Expand All @@ -2898,10 +2947,10 @@ std::optional<Recorder::PendingReadInfo> Recorder::DescribePendingRead(std::uint
if (bytes == 0) return std::nullopt;
const auto end = address + bytes;
if (open != nullptr) {
if (const auto* read = readOverlap(*open, address, end)) return PendingReadInfo{submissions + 1, open->queue, read->kind, true, false};
if (const auto kind = readOverlap(*open, address, end)) return PendingReadInfo{submissions + 1, open->queue, *kind, true, false};
}
for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) {
if (const auto* read = readOverlap(**it, address, end)) return PendingReadInfo{(*it)->serial, (*it)->queue, read->kind, false, signaled(**it)};
if (const auto kind = readOverlap(**it, address, end)) return PendingReadInfo{(*it)->serial, (*it)->queue, *kind, false, signaled(**it)};
}
return std::nullopt;
}
Expand Down Expand Up @@ -3209,7 +3258,7 @@ void Recorder::Submit() {
if (FlipReadCheck()) {
// A CPU write into a noted read after this collect (and so possibly before the GPU read
// it) fails UnchangedSince at the presenter's check (VulkanDevice RetirePresents).
for (const auto& read : open->reads) open->readGeneration = std::max(open->readGeneration, GuestMemory::CollectWrites(read.begin, read.end - read.begin));
for (const auto& [begin, end] : notedReads(*open)) open->readGeneration = std::max(open->readGeneration, GuestMemory::CollectWrites(begin, end - begin));
}
auto batch = std::move(open);
Check(function(endCommandBuffer, "vkEndCommandBuffer")(batch->commands), "vkEndCommandBuffer recorder");
Expand Down Expand Up @@ -3708,7 +3757,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source, bool
entry.readGeneration = batch->readGeneration;
entry.reads.clear();
if (FlipReadCheck()) {
for (const auto& read : batch->reads) entry.reads.emplace_back(read.begin, read.end);
entry.reads = notedReads(*batch);
}
}
// Completions store GPU results to guest memory; a failing one is reported, the rest still run.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3860,7 +3860,13 @@ void ShaderResources::MarkGpuWrites(Recorder& recorder) {
// The ranges this use reads in place through their host imports (read-only and written elements
// alike, and an address-based build's whole leased heaps), before the writes: a CPU store into
// one of them (the copy HLE) must not land before the recorded work read it.
recorder.NotePendingReads(guestMemory.InPlaceReads(), guestMemory.HoldsLease() ? Recorder::ReadKind::AddressBased : Recorder::ReadKind::DispatchElement);
const auto kind = guestMemory.HoldsLease() ? Recorder::ReadKind::AddressBased : Recorder::ReadKind::DispatchElement;
if (auto split = guestMemory.SplitInPlaceReads()) {
recorder.NotePendingReadList(std::move(split->space), kind);
recorder.NotePendingReads(split->own, kind);
} else {
recorder.NotePendingReads(guestMemory.InPlaceReads(), kind);
}
if (SkipWriteBack()) return;
for (std::size_t index = 0; index < storageTextures.size(); ++index) {
if (!storageWritten[index]) continue;
Expand Down
59 changes: 59 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "BdaTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestHeap.hpp"
Expand All @@ -19,6 +20,7 @@
#include <initializer_list>
#include <iostream>
#include <limits>
#include <memory>
#include <new>
#include <string>

Expand Down Expand Up @@ -264,6 +266,62 @@ void spaceImportRunTests(const Context& context) {
::operator delete(block, std::align_val_t{piece});
}

void spaceReadListTests(const Context& context) {
if (!AddressSpaceCounters().enabled) return;
constexpr std::size_t piece = 65536;
void* block = ::operator new(3 * piece, std::align_val_t{piece});
std::memset(block, 0x5c, 3 * piece);
auto importing = context;
importing.hostImportAlignment = piece;
auto* guest = static_cast<std::uint8_t*>(block);
const auto change = [&](std::uint8_t* range, bool add) {
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, range, piece, true, true, true);
else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, range);
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
};
const auto build = [&](const char* step) {
GuestBufferMemory leased(importing);
leased.AcquireRegistered();
leased.Upload(true);
const auto split = leased.SplitInPlaceReads();
Require(split.has_value() && split->space != nullptr, std::string("an address-based build that took the space whole has no split reads: ") + step);
auto joined = *split->space;
joined.insert(joined.end(), split->own.begin(), split->own.end());
Require(joined == leased.InPlaceReads(), std::string("the split reads differ from InPlaceReads: ") + step);
Require(Recorder::ValidReadList(*split->space), std::string("the space's read list is not sorted and disjoint: ") + step);
leased.WriteBack();
Require(!leased.SplitInPlaceReads().has_value(), "a committed build still offers split reads");
return split->space;
};
const auto lists = [&](const std::shared_ptr<const Recorder::ReadList>& list, const std::uint8_t* range) {
const auto begin = reinterpret_cast<std::uintptr_t>(range);
return std::any_of(list->begin(), list->end(), [&](const auto& read) { return read.first <= begin && begin + piece <= read.second; });
};
change(guest, true);
change(guest + piece, true);
const auto first = build("first build");
Require(lists(first, guest) && lists(first, guest + piece) && !lists(first, guest + 2 * piece), "the space's read list does not hold exactly the registered pieces");
const auto again = build("unchanged registry");
Require(again == first, "a build in an unchanged registry did not share the space's read list");
change(guest + 2 * piece, true);
const auto added = build("range added");
Require(added != first && lists(added, guest + 2 * piece), "a registered range is missing from the read list after the registry changed");
change(guest + piece, false);
const auto removed = build("range removed");
Require(removed != added && !lists(removed, guest + piece) && lists(removed, guest), "a removed range is still in the read list");
{
GuestBufferMemory plain(importing);
plain.AddReadable(reinterpret_cast<std::uintptr_t>(guest), 256);
plain.Upload(false);
Require(!plain.SplitInPlaceReads().has_value(), "a build without the address space offers split reads");
plain.WriteBack();
}
change(guest, false);
change(guest + 2 * piece, false);
::operator delete(block, std::align_val_t{piece});
}

void importWindowTests(const Context& context) {
constexpr std::size_t piece = 65536;
constexpr std::size_t window = 2 * piece;
Expand Down Expand Up @@ -1607,6 +1665,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
failedImportRemapTests(context, access);
spaceImportRunTests(context);
spaceWritableWritesTests(context);
spaceReadListTests(context);
importWindowTests(context);
spaceReadAcrossImportsTests(context, access);
spaceReadAcrossImportsWrittenTests(context);
Expand Down
Loading
Loading