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 @@ -1095,6 +1095,8 @@ if(BUILD_TESTING)
set_tests_properties(agc_descriptor_pool_recycling PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_draw_snapshot_cap COMMAND agc_driver_recorder_tests --draw-snapshot-cap-only)
set_tests_properties(agc_draw_snapshot_cap PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_draw_snapshot_epoch COMMAND agc_driver_recorder_tests --draw-snapshot-epoch-only)
set_tests_properties(agc_draw_snapshot_epoch PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_multisample_guest_access COMMAND agc_driver_recorder_tests --multisample-only)
set_tests_properties(agc_multisample_guest_access PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_draw_inputs_in_place COMMAND agc_driver_recorder_tests --draw-inputs-in-place-only)
Expand All @@ -1113,7 +1115,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_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
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_draw_snapshot_epoch 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
12 changes: 12 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PassHazards.hpp"
#include <unordered_map>
#include <atomic>
#include <chrono>
#include <cstddef>
Expand Down Expand Up @@ -96,8 +97,11 @@ class Recorder {
enum class SnapshotUse : std::uint8_t { Storage, Vertex, Index16, Index32, Index16Restart, Index32Restart };
static constexpr std::size_t DrawSnapshotBudget = std::size_t{1024} << 20u;
static constexpr std::size_t DrawSnapshotEntries = 1024;
static constexpr std::size_t UnwrittenSnapshotEntries = 4096;
static constexpr std::size_t DrawInputBudget = std::size_t{1024} << 20u;
static constexpr std::size_t DrawInputEntries = 16384;
std::shared_ptr<Buffer> UnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp);
void NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp, std::shared_ptr<Buffer> buffer);
std::shared_ptr<Buffer> ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use = SnapshotUse::Storage, std::uint32_t* derived = nullptr, std::uint64_t generation = 0);
void KeepDrawSnapshot(std::uint64_t address, std::size_t bytes, std::uint64_t generation, std::uint64_t registryGeneration, std::shared_ptr<Buffer> buffer, SnapshotUse use = SnapshotUse::Storage, std::uint32_t derived = 0);
static constexpr std::size_t IndexRangeEntries = 16384;
Expand Down Expand Up @@ -825,6 +829,14 @@ class Recorder {
std::size_t bytes = 0;
};
std::map<DrawSnapshotKey, DrawSnapshot> drawSnapshots;
struct UnwrittenSnapshot {
std::size_t bytes;
std::shared_ptr<Buffer> buffer;
};
std::unordered_map<std::uint64_t, UnwrittenSnapshot> unwrittenSnapshots;
std::array<std::uint64_t, 3> unwrittenStamp{};
std::size_t unwrittenBytes = 0;
void retireUnwrittenSnapshots(const std::array<std::uint64_t, 3>& stamp);
std::array<DrawSnapshotPool, 2> drawSnapshotPools;
void eraseDrawSnapshot(std::map<DrawSnapshotKey, DrawSnapshot>::iterator entry);
struct IndexRange {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,15 @@ class ShaderResources {
std::vector<VkDescriptorBufferInfo> descriptorBuffers;
std::vector<VkDescriptorImageInfo> descriptorImages;
std::vector<VkWriteDescriptorSet> descriptorWrites;
struct MovedWordBuffer {
std::size_t allocation;
std::vector<std::uint32_t> words;
std::size_t size;
std::shared_ptr<Buffer> buffer;
};
mutable std::vector<MovedWordBuffer> movedWordBuffers;
mutable std::size_t movedWordBufferNext = 0;
mutable std::vector<VkDescriptorPoolSize> drawPoolSizes;
// Guest buffer elements bound read-only: each use of this object skips that many pending-write
// notes (counted in MarkGpuWrites for the [buffers] line).
std::size_t readOnlyBuffers = 0;
Expand Down
25 changes: 25 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2516,6 +2516,31 @@ void Recorder::eraseDrawSnapshot(std::map<DrawSnapshotKey, DrawSnapshot>::iterat
drawSnapshots.erase(entry);
}

void Recorder::retireUnwrittenSnapshots(const std::array<std::uint64_t, 3>& stamp) {
if (stamp == unwrittenStamp) return;
unwrittenSnapshots.clear();
unwrittenBytes = 0;
unwrittenStamp = stamp;
}

std::shared_ptr<Buffer> Recorder::UnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp) {
retireUnwrittenSnapshots(stamp);
const auto found = unwrittenSnapshots.find(address);
if (found == unwrittenSnapshots.end() || found->second.bytes != bytes) return {};
return found->second.buffer;
}

void Recorder::NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp, std::shared_ptr<Buffer> buffer) {
retireUnwrittenSnapshots(stamp);
if (const auto found = unwrittenSnapshots.find(address); found != unwrittenSnapshots.end()) {
unwrittenBytes -= found->second.bytes;
unwrittenSnapshots.erase(found);
}
if (unwrittenSnapshots.size() >= UnwrittenSnapshotEntries || bytes > DrawSnapshotBudget - unwrittenBytes) return;
unwrittenSnapshots.emplace(address, UnwrittenSnapshot{bytes, std::move(buffer)});
unwrittenBytes += bytes;
}

std::shared_ptr<Buffer> Recorder::ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use, std::uint32_t* derived, std::uint64_t generation) {
auto found = use == SnapshotUse::Vertex ? drawSnapshots.lower_bound({address, use, bytes}) : drawSnapshots.find({address, use, bytes});
if (found == drawSnapshots.end() || std::get<0>(found->first) != address || std::get<1>(found->first) != use) return {};
Expand Down
47 changes: 38 additions & 9 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4398,14 +4398,26 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
std::size_t totalSnapshotBytes = 0;
auto result = std::make_shared<DrawBindings>();
std::vector<std::size_t> selected;
std::array<std::uint64_t, 3> stamp{};
bool stamped = false;
for (std::size_t index = 0; index < allocations.size(); ++index) {
const auto& item = allocations[index];
const auto override = std::find_if(moved.begin(), moved.end(), [&](const MovedBuffer& entry) { return entry.allocation == index; });
if (override != moved.end() && !override->words.empty()) {
auto buffer = std::make_shared<Buffer>(context, override->size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
std::memcpy(buffer->Bytes().data(), override->words.data(), override->size);
for (const auto& patch : dataPatches) {
if (patch.allocation == index && patch.byte < override->size) buffer->Bytes()[patch.byte] = static_cast<std::byte>(patch.adjustment);
constexpr std::size_t MovedWordBufferSlots = 8;
const auto held = std::find_if(movedWordBuffers.begin(), movedWordBuffers.end(), [&](const MovedWordBuffer& entry) { return entry.allocation == index && entry.size == override->size && entry.words == override->words; });
std::shared_ptr<Buffer> buffer;
if (held != movedWordBuffers.end()) {
buffer = held->buffer;
} else {
buffer = std::make_shared<Buffer>(context, override->size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
std::memcpy(buffer->Bytes().data(), override->words.data(), override->size);
for (const auto& patch : dataPatches) {
if (patch.allocation == index && patch.byte < override->size) buffer->Bytes()[patch.byte] = static_cast<std::byte>(patch.adjustment);
}
MovedWordBuffer entry{index, override->words, override->size, buffer};
if (movedWordBuffers.size() < MovedWordBufferSlots) movedWordBuffers.push_back(std::move(entry));
else movedWordBuffers[movedWordBufferNext++ % MovedWordBufferSlots] = std::move(entry);
}
selected.push_back(index);
result->snapshots.push_back({0, std::move(buffer)});
Expand All @@ -4425,12 +4437,29 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
const auto bytes = static_cast<std::size_t>(GuestBufferMemory::ViewBytes(size, item.adjustment));
if (override == moved.end() && (bytes > maxSnapshotBytes || totalSnapshotBytes + bytes > maxSnapshotBytes)) continue;
const auto registryGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
const auto generation = GuestMemory::CollectWrites(begin, bytes);
auto buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation);
if (!stamped) {
stamp = {GuestMemory::TrackerGeneration(), GuestMemory::CollectEpoch(), registryGeneration};
stamped = true;
}
auto buffer = recorder.UnwrittenDrawSnapshot(begin, bytes, stamp);
static const bool verifySnapshots = std::getenv("APS5_VERIFY_DRAW_SNAPSHOTS") != nullptr;
if (verifySnapshots && buffer != nullptr) {
static std::atomic<std::uint64_t> checked{0}, differing{0};
const auto slow = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, GuestMemory::CollectWrites(begin, bytes));
const bool same = slow == buffer || (slow != nullptr && slow->Bytes().size() == buffer->Bytes().size() && std::memcmp(slow->Bytes().data(), buffer->Bytes().data(), buffer->Bytes().size()) == 0) || (slow == nullptr && std::memcmp(buffer->Bytes().data(), reinterpret_cast<const void*>(begin), bytes) == 0);
if (!same) differing.fetch_add(1);
if (checked.fetch_add(1) % 200000 == 199999) std::fprintf(stderr, "[draw-snapshots] verify: %llu fast answers checked, %llu differing%c", static_cast<unsigned long long>(checked.load()), static_cast<unsigned long long>(differing.load()), 10);
}
if (buffer == nullptr) {
buffer = std::make_shared<Buffer>(context, bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
std::memcpy(buffer->Bytes().data(), reinterpret_cast<const void*>(begin), bytes);
recorder.KeepDrawSnapshot(begin, bytes, generation, registryGeneration, buffer);
const auto generation = GuestMemory::CollectWrites(begin, bytes);
buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation);
if (buffer == nullptr) {
buffer = std::make_shared<Buffer>(context, bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
std::memcpy(buffer->Bytes().data(), reinterpret_cast<const void*>(begin), bytes);
recorder.KeepDrawSnapshot(begin, bytes, generation, registryGeneration, buffer);
}
if (GuestMemory::TrackerGeneration() == stamp[0]) recorder.NoteUnwrittenDrawSnapshot(begin, bytes, stamp, buffer);
else stamped = false;
}
selected.push_back(index);
result->snapshots.push_back({begin, std::move(buffer)});
Expand Down
102 changes: 102 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2675,6 +2675,83 @@ void drawSnapshotPatchTests(const Device& device) {
}
}

void drawSnapshotEpochTests(const Device& device) {
const auto& context = device.GetContext();
constexpr std::size_t bytes = 4 * 65536;
std::unique_lock gpu(GpuMutex());
void* block = context.hostImportAlignment != 0 ? AllocateWatched(bytes, 65536) : nullptr;
if (block == nullptr) {
std::cout << "host imports unavailable: draw snapshot reuse within a collect epoch not tested\n";
return;
}
struct Release {
void* block;
~Release() { ReleaseWatched(block, bytes); }
} release{block};
auto* guest = static_cast<std::uint8_t*>(block);
for (std::size_t at = 0; at < bytes; ++at) guest[at] = static_cast<std::uint8_t>(at * 7u + 3u);
const auto address = reinterpret_cast<std::uint64_t>(block);
{
GuestAllocations::Mutation mutation;
mutation.Add(block, bytes, true, true, true);
}
struct Unregister {
const Context& context;
void* block;
~Unregister() {
{
GuestAllocations::Mutation mutation;
mutation.Remove(block);
}
HostImportFor(context, reinterpret_cast<std::uint64_t>(block), bytes);
}
} unregister{context, block};
if (HostImportFor(context, address, bytes) == nullptr) {
std::cout << "host import of the block refused: draw snapshot reuse within a collect epoch not tested\n";
return;
}
constexpr std::size_t elementOffset = 4096;
constexpr std::size_t elementBytes = 2 * 65536;
const auto element = address + elementOffset;
ShaderRecompiler::RecompileResult program;
ShaderRecompiler::DescriptorBinding binding;
binding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
binding.role = ShaderRecompiler::DescriptorRole::GuestBuffers;
binding.descriptorSet = 0;
binding.binding = 0;
binding.count = 1;
binding.guestDescriptor = {static_cast<std::uint32_t>(element), static_cast<std::uint32_t>(element >> 32u) & 0xffffu, static_cast<std::uint32_t>(elementBytes), 0x31000000u};
binding.bufferWritten = {false};
program.bindings.push_back(binding);
const CompiledShader compute{ShaderRecompiler::ShaderStage::Compute, &program, 0};
auto snapshotContext = context;
DescriptorCache cache(snapshotContext);
snapshotContext.descriptorCache = &cache;
Recorder snapshotRecorder(snapshotContext);
snapshotRecorder.Activate();
{
ShaderResources resources(snapshotContext, compute);
const auto snapshot = [&] {
auto bindings = resources.PrepareDrawBindings(snapshotRecorder);
Require(bindings != nullptr && bindings->snapshots.size() == 1, "read-only draw input was not snapshotted");
auto buffer = bindings->snapshots[0].buffer;
const auto contents = buffer->Bytes();
Require(contents.size() == elementBytes && std::memcmp(contents.data(), reinterpret_cast<const void*>(element), elementBytes) == 0, "a draw snapshot does not hold the guest bytes of its draw");
return buffer;
};
AgcDriver::GuestMemory::BumpCollectEpoch();
const auto first = snapshot();
Require(snapshot() == first, "a second draw in the same collect epoch copied its unchanged input again");
guest[elementOffset + 65536 + 17] ^= 0xffu;
AgcDriver::GuestMemory::BumpCollectEpoch();
const auto changed = snapshot();
Require(changed != first, "a draw after a store and a new collect epoch reused the snapshot taken before the store");
Require(snapshot() == changed, "a second draw after the store copied its input again");
snapshotRecorder.Sync();
}
std::cout << "draw snapshot reuse within a collect epoch: ok\n";
}

void drawSnapshotCapTests(const Device& device, Recorder& recorder) {
using namespace AgcDriver::GuestMemory;
const auto& context = device.GetContext();
Expand Down Expand Up @@ -2871,6 +2948,24 @@ void drawSnapshotEvictionTests(const Device& device) {
Require(cache.ReusableDrawSnapshot(address, 16) == nullptr && cache.ReusableDrawSnapshot(address, 16) == nullptr, "a snapshot outlived a CPU store");
}

void unwrittenSnapshotTests(const Device& device) {
Recorder cache(device.GetContext());
const auto buffer = std::make_shared<Buffer>(device.GetContext(), 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
constexpr std::uint64_t address = 0x10000000;
const std::array<std::uint64_t, 3> stamp{1, 1, 1};
const std::array<std::uint64_t, 3> next{1, 2, 1};
cache.NoteUnwrittenDrawSnapshot(address, 16, stamp, buffer);
Require(cache.UnwrittenDrawSnapshot(address, 16, stamp) == buffer, "a noted unwritten snapshot is not found under its stamp");
cache.NoteUnwrittenDrawSnapshot(address + 8192, Recorder::DrawSnapshotBudget, stamp, buffer);
Require(cache.UnwrittenDrawSnapshot(address + 8192, Recorder::DrawSnapshotBudget, stamp) == nullptr, "unwritten snapshots grew past the byte budget");
cache.NoteUnwrittenDrawSnapshot(address, Recorder::DrawSnapshotBudget, stamp, buffer);
Require(cache.UnwrittenDrawSnapshot(address, Recorder::DrawSnapshotBudget, stamp) == buffer, "replacing an unwritten snapshot kept the bytes of the old one");
Require(cache.UnwrittenDrawSnapshot(address, Recorder::DrawSnapshotBudget, next) == nullptr && cache.UnwrittenDrawSnapshot(address, Recorder::DrawSnapshotBudget, stamp) == nullptr, "an unwritten snapshot outlived a newer stamp");
for (std::size_t entry = 0; entry < Recorder::UnwrittenSnapshotEntries; ++entry) cache.NoteUnwrittenDrawSnapshot(address + entry * 16, 1, next, buffer);
cache.NoteUnwrittenDrawSnapshot(address + Recorder::UnwrittenSnapshotEntries * 16, 1, next, buffer);
Require(cache.UnwrittenDrawSnapshot(address, 1, next) == buffer && cache.UnwrittenDrawSnapshot(address + Recorder::UnwrittenSnapshotEntries * 16, 1, next) == nullptr, "unwritten snapshots grew past the entry cap");
}

void drawInputReuseTests(const Device& device, Recorder& recorder) {
using namespace AgcDriver::GuestMemory;
using Use = Recorder::SnapshotUse;
Expand Down Expand Up @@ -7602,9 +7697,14 @@ int main(int argc, char** argv) {
}
if (argc == 2 && std::string_view(argv[1]) == "--draw-snapshot-cap-only") {
drawSnapshotCapTests(device, recorder);
unwrittenSnapshotTests(device);
std::cout << "Draw snapshot cap tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--draw-snapshot-epoch-only") {
drawSnapshotEpochTests(device);
return 0;
}

if (argc == 2 && (std::string_view(argv[1]) == "--vertex-target-range-copied" || std::string_view(argv[1]) == "--vertex-target-range-imported")) {
return vertexTargetRangeTests(device, recorder, std::string_view(argv[1]) == "--vertex-target-range-imported") ? 0 : 77;
Expand Down Expand Up @@ -7644,6 +7744,7 @@ int main(int argc, char** argv) {
drawDescriptorWriteTests(device, recorder);
drawSnapshotCapTests(device, recorder);
drawSnapshotEvictionTests(device);
unwrittenSnapshotTests(device);
drawInputReuseTests(device, recorder);
drawInputInPlaceTests(device, recorder);
RunResidentPresentTests(device.GetContext());
Expand Down Expand Up @@ -7689,6 +7790,7 @@ int main(int argc, char** argv) {
gpu.unlock();
hostImportUnmapTests(device, recorder);
drawSnapshotPatchTests(device);
drawSnapshotEpochTests(device);
gpu.lock();
recorder.Activate();
std::cout << "Recorder read tracking and label tests passed\n";
Expand Down
Loading
Loading