Merge pull request #3412 from pushkarreddyy/feat/agc-draw-snapshot-cap
perf(libSceAgcDriver): cap draw input snapshot allocation size
This commit is contained in:
commit
a9a34e91cc
|
|
@ -788,8 +788,10 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(agc_unit_shadow PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000")
|
||||
add_test(NAME agc_descriptor_pool_recycling COMMAND agc_driver_recorder_tests --descriptor-pool-only)
|
||||
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)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_host_import_unmap agc_unit_shadow agc_descriptor_pool_recycling PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(agc_host_import_unmap agc_unit_shadow agc_descriptor_pool_recycling agc_draw_snapshot_cap PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ public:
|
|||
};
|
||||
std::shared_ptr<DrawBindings> PrepareDrawBindings(Recorder& recorder, std::span<const MovedBuffer> moved = {}) const;
|
||||
std::optional<std::vector<MovedBuffer>> MovedReadOnlyBuffers(std::span<const CompiledShader> shaders, Recorder& recorder) const;
|
||||
static std::size_t MaxDrawSnapshotBytes();
|
||||
void WriteBack();
|
||||
// Deferred completion: MarkGpuWrites registers the results the recorded work leaves on the GPU
|
||||
// (storage images stay there; buffer ranges are noted so CPU reads wait); WriteBackBuffers runs
|
||||
|
|
|
|||
|
|
@ -3383,9 +3383,17 @@ std::optional<std::vector<ShaderResources::MovedBuffer>> ShaderResources::MovedR
|
|||
return moved;
|
||||
}
|
||||
|
||||
std::size_t ShaderResources::MaxDrawSnapshotBytes() {
|
||||
const char* text = std::getenv("APS5_DRAW_SNAPSHOT_MAX_MIB");
|
||||
const auto mib = text != nullptr ? std::strtoull(text, nullptr, 10) : 64ull;
|
||||
return static_cast<std::size_t>(mib * 1024ull * 1024ull);
|
||||
}
|
||||
|
||||
std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span<const MovedBuffer> moved) const {
|
||||
if (_set == VK_NULL_HANDLE || usesBda) return {};
|
||||
const auto reads = guestMemory.InPlaceReads();
|
||||
const auto maxSnapshotBytes = MaxDrawSnapshotBytes();
|
||||
std::size_t totalSnapshotBytes = 0;
|
||||
auto result = std::make_shared<DrawBindings>();
|
||||
std::vector<std::size_t> selected;
|
||||
for (std::size_t index = 0; index < allocations.size(); ++index) {
|
||||
|
|
@ -3413,6 +3421,7 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
|
|||
}
|
||||
const auto begin = address - item.adjustment;
|
||||
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);
|
||||
|
|
@ -3423,6 +3432,7 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
|
|||
}
|
||||
selected.push_back(index);
|
||||
result->snapshots.push_back({begin, std::move(buffer)});
|
||||
totalSnapshotBytes += bytes;
|
||||
CaptureTrace::Log("draw-snapshot batch=%llu address=%llx bytes=%zu", static_cast<unsigned long long>(recorder.Submissions() + 1), static_cast<unsigned long long>(begin), bytes);
|
||||
}
|
||||
if (selected.empty()) return {};
|
||||
|
|
|
|||
|
|
@ -1764,6 +1764,150 @@ void drawSnapshotPatchTests(const Device& device) {
|
|||
}
|
||||
}
|
||||
|
||||
void drawSnapshotCapTests(const Device& device, Recorder& recorder) {
|
||||
using namespace AgcDriver::GuestMemory;
|
||||
const auto& context = device.GetContext();
|
||||
const char* previous = std::getenv("APS5_DRAW_SNAPSHOT_MAX_MIB");
|
||||
const std::string saved = previous != nullptr ? previous : "";
|
||||
#ifdef _WIN32
|
||||
Require(_putenv_s("APS5_DRAW_SNAPSHOT_MAX_MIB", "1") == 0, "cannot set snapshot cap");
|
||||
#else
|
||||
Require(setenv("APS5_DRAW_SNAPSHOT_MAX_MIB", "1", 1) == 0, "cannot set snapshot cap");
|
||||
#endif
|
||||
struct RestoreCap {
|
||||
const char* previous;
|
||||
std::string saved;
|
||||
~RestoreCap() {
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_DRAW_SNAPSHOT_MAX_MIB", saved.c_str());
|
||||
#else
|
||||
if (previous != nullptr) {
|
||||
setenv("APS5_DRAW_SNAPSHOT_MAX_MIB", saved.c_str(), 1);
|
||||
} else {
|
||||
unsetenv("APS5_DRAW_SNAPSHOT_MAX_MIB");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} restore{previous, saved};
|
||||
constexpr std::size_t bytes = 3 * 1024 * 1024;
|
||||
void* block = context.hostImportAlignment != 0 ? AllocateWatched(bytes, 65536) : nullptr;
|
||||
if (block == nullptr) {
|
||||
std::cout << "host imports or write watching unavailable: draw snapshot cap not tested\n";
|
||||
return;
|
||||
}
|
||||
struct Release {
|
||||
void* block;
|
||||
~Release() { ReleaseWatched(block, bytes); }
|
||||
} release{block};
|
||||
std::memset(block, 0x11, bytes);
|
||||
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 watched block refused: draw snapshot cap not tested\n";
|
||||
return;
|
||||
}
|
||||
const auto smallAddress = address + 4096;
|
||||
constexpr std::size_t smallBytes = 1024 * 1024 - 4096;
|
||||
const auto largeAddress = address + 1024 * 1024 + 8192;
|
||||
constexpr std::size_t largeBytes = 1024 * 1024 + 4096;
|
||||
ShaderRecompiler::RecompileResult program;
|
||||
ShaderRecompiler::DescriptorBinding smallBinding;
|
||||
smallBinding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
|
||||
smallBinding.role = ShaderRecompiler::DescriptorRole::GuestBuffers;
|
||||
smallBinding.descriptorSet = 0;
|
||||
smallBinding.binding = 0;
|
||||
smallBinding.count = 1;
|
||||
smallBinding.guestDescriptor = {static_cast<std::uint32_t>(smallAddress), static_cast<std::uint32_t>(smallAddress >> 32u) & 0xffffu, static_cast<std::uint32_t>(smallBytes), 0x31000000u};
|
||||
smallBinding.bufferWritten = {false};
|
||||
program.bindings.push_back(smallBinding);
|
||||
ShaderRecompiler::DescriptorBinding largeBinding;
|
||||
largeBinding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
|
||||
largeBinding.role = ShaderRecompiler::DescriptorRole::GuestBuffers;
|
||||
largeBinding.descriptorSet = 0;
|
||||
largeBinding.binding = 1;
|
||||
largeBinding.count = 1;
|
||||
largeBinding.guestDescriptor = {static_cast<std::uint32_t>(largeAddress), static_cast<std::uint32_t>(largeAddress >> 32u) & 0xffffu, static_cast<std::uint32_t>(largeBytes), 0x31000000u};
|
||||
largeBinding.bufferWritten = {false};
|
||||
program.bindings.push_back(largeBinding);
|
||||
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 bindings = resources.PrepareDrawBindings(snapshotRecorder);
|
||||
Require(bindings != nullptr, "read-only draw bindings were not prepared");
|
||||
Require(bindings->snapshots.size() == 1, "an input exceeding the draw snapshot cap was snapshotted");
|
||||
Require(bindings->snapshots[0].address == smallAddress, "the snapshotted input does not match the smaller buffer below the cap");
|
||||
Require(snapshotRecorder.ReusableDrawSnapshot(largeAddress, largeBytes) == nullptr, "an input exceeding the draw snapshot cap was kept in the snapshot cache");
|
||||
if (Watched(address, bytes)) {
|
||||
Require(snapshotRecorder.ReusableDrawSnapshot(smallAddress, smallBytes) != nullptr, "an input below the draw snapshot cap was not kept in the snapshot cache");
|
||||
}
|
||||
const auto contents = bindings->snapshots[0].buffer->Bytes();
|
||||
Require(contents.size() == smallBytes && std::all_of(contents.begin(), contents.end(), [](std::byte value) { return value == std::byte{0x11}; }), "a draw snapshot does not hold the guest bytes of its draw");
|
||||
std::memset(reinterpret_cast<void*>(smallAddress), 0x22, smallBytes);
|
||||
std::memset(reinterpret_cast<void*>(largeAddress), 0x33, largeBytes);
|
||||
Require(std::all_of(contents.begin(), contents.end(), [](std::byte value) { return value == std::byte{0x11}; }), "snapshotted input observed CPU overwrite");
|
||||
const auto* inPlace = reinterpret_cast<const std::byte*>(largeAddress);
|
||||
Require(inPlace[0] == std::byte{0x33} && std::all_of(inPlace, inPlace + largeBytes, [](std::byte value) { return value == std::byte{0x33}; }), "in-place bound input did not observe CPU write");
|
||||
std::memset(block, 0x11, bytes);
|
||||
snapshotRecorder.Sync();
|
||||
}
|
||||
const auto firstAddress = address + 4096;
|
||||
constexpr std::size_t firstBytes = 600 * 1024;
|
||||
const auto secondAddress = address + 600 * 1024 + 8192;
|
||||
constexpr std::size_t secondBytes = 600 * 1024;
|
||||
ShaderRecompiler::RecompileResult cumulativeProgram;
|
||||
ShaderRecompiler::DescriptorBinding firstBinding;
|
||||
firstBinding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
|
||||
firstBinding.role = ShaderRecompiler::DescriptorRole::GuestBuffers;
|
||||
firstBinding.descriptorSet = 0;
|
||||
firstBinding.binding = 0;
|
||||
firstBinding.count = 1;
|
||||
firstBinding.guestDescriptor = {static_cast<std::uint32_t>(firstAddress), static_cast<std::uint32_t>(firstAddress >> 32u) & 0xffffu, static_cast<std::uint32_t>(firstBytes), 0x31000000u};
|
||||
firstBinding.bufferWritten = {false};
|
||||
cumulativeProgram.bindings.push_back(firstBinding);
|
||||
ShaderRecompiler::DescriptorBinding secondBinding;
|
||||
secondBinding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
|
||||
secondBinding.role = ShaderRecompiler::DescriptorRole::GuestBuffers;
|
||||
secondBinding.descriptorSet = 0;
|
||||
secondBinding.binding = 1;
|
||||
secondBinding.count = 1;
|
||||
secondBinding.guestDescriptor = {static_cast<std::uint32_t>(secondAddress), static_cast<std::uint32_t>(secondAddress >> 32u) & 0xffffu, static_cast<std::uint32_t>(secondBytes), 0x31000000u};
|
||||
secondBinding.bufferWritten = {false};
|
||||
cumulativeProgram.bindings.push_back(secondBinding);
|
||||
const CompiledShader cumulativeCompute{ShaderRecompiler::ShaderStage::Compute, &cumulativeProgram, 0};
|
||||
{
|
||||
auto snapshotContext = context;
|
||||
DescriptorCache cache(snapshotContext);
|
||||
snapshotContext.descriptorCache = &cache;
|
||||
Recorder snapshotRecorder(snapshotContext);
|
||||
snapshotRecorder.Activate();
|
||||
ShaderResources resources(snapshotContext, cumulativeCompute);
|
||||
const auto bindings = resources.PrepareDrawBindings(snapshotRecorder);
|
||||
Require(bindings != nullptr, "read-only draw bindings were not prepared for cumulative test");
|
||||
Require(bindings->snapshots.size() == 1, "cumulative cap permitted both inputs to snapshot");
|
||||
Require(bindings->snapshots[0].address == firstAddress, "the earlier binding in the layout was not prioritised for the snapshot budget");
|
||||
snapshotRecorder.Sync();
|
||||
}
|
||||
recorder.Activate();
|
||||
}
|
||||
|
||||
void drawSnapshotEvictionTests(const Device& device) {
|
||||
using namespace AgcDriver::GuestMemory;
|
||||
|
|
@ -4979,6 +5123,11 @@ int main(int argc, char** argv) {
|
|||
std::cout << "Draw descriptor writes and snapshot overrides passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--draw-snapshot-cap-only") {
|
||||
drawSnapshotCapTests(device, recorder);
|
||||
std::cout << "Draw snapshot cap tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
readTrackingTests(device, recorder);
|
||||
writeSettledTests(device, recorder);
|
||||
|
|
@ -5001,6 +5150,7 @@ int main(int argc, char** argv) {
|
|||
misalignedRegionTests(device, recorder);
|
||||
drawSnapshotReuseTests(device, recorder);
|
||||
drawDescriptorWriteTests(device, recorder);
|
||||
drawSnapshotCapTests(device, recorder);
|
||||
drawSnapshotEvictionTests(device);
|
||||
drawInputReuseTests(device, recorder);
|
||||
drawInputInPlaceTests(device, recorder);
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Wide-character conversions](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported
|
||||
- [realpath](../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target.
|
||||
- [nan and nanf](../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep.
|
||||
- [Draw input snapshots](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): `PrepareDrawBindings` caps read-only input buffer snapshots at 64 MiB (overridable via `APS5_DRAW_SNAPSHOT_MAX_MIB`). Larger inputs (streaming heaps, bindless pools) or draws exceeding the cumulative budget skip the per-draw snapshot and bind in place, avoiding multi-gigabyte CPU copy stalls (such as Ratchet & Clank: Rift Apart PPSA01474 stalling at frame ~380 copying 36 GB every 5 s). If the title rewrites those large ranges on the CPU before GPU completion, the in-place binding observes the modification, as verified by `drawSnapshotCapTests`.
|
||||
- [SPIRV-Tools optimization](../../core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp) skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe.
|
||||
- [SRT signed comparison](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp) evaluates `SLessThan32` as signed 32-bit less-than, matching the existing SPIR-V backend and [SPIR-V `OpSLessThan`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSLessThan). Signed boundary descriptor expressions are tested on the host; native RDNA2/PS5 execution and title behavior remain unverified.
|
||||
- On macOS the [write watch](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so:
|
||||
|
|
|
|||
Loading…
Reference in New Issue