Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ class GuestBufferMemory {
// instead of the CPU (see Region::gpuCopy): live guest bytes, not sparse, at most the size
// APS5_GPU_COPY_MAX_KIB allows, and APS5_CPU_COPIES unset.
bool gpuCopyEligible(const Region& region) const;
bool copyBehindGpuWork(const Region& region, bool locked) const;
// Whether a region inside a host import is staged in device memory instead of bound in place
// (see GuestBufferMemory.cpp): staging allowed, not address-based unless an overlay region, a
// written element inside, and an atomic element or a size within the written-shadow window.
Expand Down
14 changes: 12 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5345,6 +5345,16 @@ bool GuestBufferMemory::gpuCopyEligible(const Region& region) const {
return region.end - region.begin <= gpuCopyLimit();
}

bool GuestBufferMemory::copyBehindGpuWork(const Region& region, bool locked) const {
if (!gpuCopiesEnabled() || region.sparse || !(region.hostBacked || region.writable)) return false;
const auto* recorder = Recorder::Active();
if (recorder == nullptr) return false;
const auto bytes = static_cast<std::size_t>(region.end - region.begin);
const std::array<std::pair<std::uint64_t, std::uint64_t>, 1> range{{{region.begin, region.end}}};
const bool written = locked ? recorder->PendingWriteOverlaps(region.begin, bytes) : Recorder::SnapshotWriteOverlaps(region.begin, bytes);
return written || StorageTexture::AnyPendingOverlaps(range) || AnyShadowedOverlaps(region.begin, bytes);
}

bool GuestBufferMemory::bindableInPlace(std::uint64_t offset, bool addressable) const {
if (adjustedRegions && !addressable) return offset % 4 == 0;
return offset % context.limits.minStorageBufferOffsetAlignment == 0;
Expand Down Expand Up @@ -5555,7 +5565,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) {
}
if (misaligned || staged) {
if (!staged) {
if (!gpuCopyEligible(region)) continue;
if (!gpuCopyEligible(region) && !copyBehindGpuWork(region, false)) continue;
const auto allocateStart = std::chrono::steady_clock::now();
region.buffer = std::make_shared<Buffer>(context, static_cast<std::size_t>(region.end - region.begin), gpuCopyUsage(addressable), copyBufferProperties(false));
if (profile) allocateUs.fetch_add(microsecondsSince(allocateStart), std::memory_order_relaxed);
Expand Down Expand Up @@ -5769,7 +5779,7 @@ void GuestBufferMemory::UploadFinish(bool addressable, std::optional<std::pair<s
// region in place (Descriptor prefers `direct`), and a kept buffer would count as copied.
region.buffer.reset();
region.deviceLocal = false;
} else if (entry != nullptr && recorder != nullptr && (staged || gpuCopyEligible(region))) {
} else if (entry != nullptr && recorder != nullptr && (staged || gpuCopyEligible(region) || copyBehindGpuWork(region, true))) {
// Misaligned in the import (or staged): the GPU copies the sub-range out of it. The
// handle and base are taken now, under the import lock: a reconcile by the flush
// below could retire the entry (the batch opened below keeps a retired import's
Expand Down
106 changes: 106 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4222,6 +4222,108 @@ bool sparseRunImportTests(const Device& device, bool copied) {
return true;
}

bool pendingUploadGpuCopyTests(const Device& device, Recorder& recorder) {
const auto& context = device.GetContext();
const auto alignment = context.limits.minStorageBufferOffsetAlignment;
if (alignment < 8 || alignment > 256) {
std::cout << "storage buffer offset alignment " << alignment << ": uploads behind a pending GPU write not tested\n";
return false;
}
constexpr std::size_t bytes = 4u << 20u;
void* block = context.hostImportAlignment != 0 ? AllocateWatched(bytes, 65536) : nullptr;
if (block == nullptr) {
std::cout << "host imports or write watching unavailable: uploads behind a pending GPU write not tested\n";
return false;
}
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 * 5u + 1u);
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};
const auto* import = HostImportFor(context, address, bytes);
if (import == nullptr) {
std::cout << "host import of the watched block refused: uploads behind a pending GPU write not tested\n";
return false;
}
constexpr std::size_t viewOffset = 4096 + 4;
constexpr std::size_t viewBytes = 3u << 20u;
constexpr std::size_t producedOffset = 1u << 20u;
constexpr std::size_t producedBytes = 1u << 20u;
const auto view = address + viewOffset;
const auto produced = address + producedOffset;
Require((view - import->base) % alignment != 0 && (view - import->base) % 4 == 0, "the test view is not a DWORD-aligned view off the storage buffer offset alignment");
std::vector<std::uint8_t> expected(guest + viewOffset, guest + viewOffset + viewBytes);
auto source = std::make_shared<Buffer>(context, producedBytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
auto* pattern = reinterpret_cast<std::uint8_t*>(source->Bytes().data());
for (std::size_t at = 0; at < producedBytes; ++at) {
pattern[at] = static_cast<std::uint8_t>((at * 7u + 3u) ^ 0xa5u);
expected[producedOffset - viewOffset + at] = pattern[at];
}
{
std::lock_guard lock(GpuMutex());
recorder.Sync();
const auto commands = recorder.Commands();
CopyBuffer(context, commands, source->Handle(), 0, import->buffer, HostImportOffset(*import, produced), producedBytes);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT);
recorder.Keep(source);
recorder.NotePendingWrite(produced, producedBytes);
}
Require(recorder.OpenWriteOverlaps(produced, producedBytes), "the producer's GPU write is not pending in the open batch");
{
GuestBufferMemory memory(context);
memory.AddReadable(view, viewBytes);
const auto waitsBefore = Recorder::ThreadHookWaits();
const auto submissionsBefore = recorder.Submissions();
{
std::lock_guard lock(GpuMutex());
memory.Upload(false);
}
const auto waits = Recorder::ThreadHookWaits() - waitsBefore;
const auto submissions = recorder.Submissions() - submissionsBefore;
Require(waits == 0 && submissions == 0 && recorder.OpenWriteOverlaps(produced, producedBytes), "the upload of a misaligned " + std::to_string(viewBytes >> 20u) + " MiB region over a pending GPU write waited for it on the CPU (" + std::to_string(waits) + " flush-hook waits, " + std::to_string(submissions) + " submissions)");
auto readback = std::make_shared<Buffer>(context, viewBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
{
std::lock_guard lock(GpuMutex());
std::uint32_t adjustment = 0;
const auto info = memory.Descriptor(view, viewBytes, adjustment);
const auto commands = recorder.Commands();
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
CopyBuffer(context, commands, info.buffer, info.offset + adjustment, readback->Handle(), 0, viewBytes);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
recorder.Keep(readback);
recorder.Submit();
}
device.WaitQueue();
{
std::lock_guard lock(GpuMutex());
recorder.Sync();
}
const auto* read = reinterpret_cast<const std::uint8_t*>(readback->Bytes().data());
const auto mismatch = std::mismatch(expected.begin(), expected.end(), read);
Require(mismatch.first == expected.end(), "the region uploaded behind the pending GPU write misses the producer's bytes at byte " + std::to_string(mismatch.first - expected.begin()) + " of the view");
Require(std::memcmp(guest + producedOffset, pattern, producedBytes) == 0, "the producer's GPU write did not reach guest memory");
}
std::cout << "uploads behind a pending GPU write tests passed\n";
return true;
}

void misalignedRegionTests(const Device& device, Recorder& recorder) {
const auto& context = device.GetContext();
const auto alignment = context.limits.minStorageBufferOffsetAlignment;
Expand Down Expand Up @@ -12464,6 +12566,9 @@ int main(int argc, char** argv) {
if (argc == 2 && std::string_view(argv[1]) == "--typed-fill-clear-only") {
return denormalClearTests(device, recorder) ? 0 : 77;
}
if (argc == 2 && std::string_view(argv[1]) == "--pending-upload-gpu-copy-only") {
return pendingUploadGpuCopyTests(device, recorder) ? 0 : 77;
}
if (argc == 2 && std::string_view(argv[1]) == "--integer-clear-only") {
integerClearTests(device);
return 0;
Expand Down Expand Up @@ -12722,6 +12827,7 @@ int main(int argc, char** argv) {
fmaskDecompressMsaaTests(device, recorder);
misalignedSnapshotTests(device, recorder);
misalignedRegionTests(device, recorder);
pendingUploadGpuCopyTests(device, recorder);
drawSnapshotReuseTests(device, recorder);
drawDescriptorWriteTests(device, recorder);
drawSnapshotTableSetTests(device, recorder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ if(BUILD_TESTING)
endif()
add_test(NAME agc_storage_typed_fill_clear COMMAND agc_driver_recorder_tests --typed-fill-clear-only)
set_tests_properties(agc_storage_typed_fill_clear PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_JOURNAL=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_buffer_upload_pending_gpu_copy COMMAND agc_driver_recorder_tests --pending-upload-gpu-copy-only)
set_tests_properties(agc_buffer_upload_pending_gpu_copy PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_buffer_upload_pending_gpu_copy PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_storage_integer_clear COMMAND agc_driver_recorder_tests --integer-clear-only)
set_tests_properties(agc_storage_integer_clear PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti
| `APS5_FRAME_CAPTURE_TRIGGER` | string | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | | file whose appearance starts the frame capture at the next frame boundary; a number in it replaces the frame count |
| `APS5_FULL_BARRIERS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every barrier instead of merging redundant ones |
| `APS5_GPU_CHECKPOINTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | attaches device diagnostic checkpoints through VK_NV_device_diagnostic_checkpoints, or buffer markers through VK_AMD_buffer_marker on a device without it |
| `APS5_GPU_COPY_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on a descriptor sub-range copied through the GPU path, larger ones staying on the CPU |
| `APS5_GPU_COPY_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on a descriptor sub-range copied through the GPU path, larger ones staying on the CPU unless a pending GPU write, pending storage results or a unit shadow overlaps them (the GPU then copies them whatever their size, so the CPU does not wait) |
| `APS5_GPU_NO_WRITEBACK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | skips writing GPU results back into guest memory |
| `APS5_GPU_WAIT_TIMEOUT_MS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | 1000 | milliseconds a GPU wait may run before a warning is printed |
| `APS5_GROUP_CAPTURE_ALL_QUEUES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp` | | resolves group-ahead capture on queue 0 as well |
Expand Down
Loading