From 631f77e696bfe85fe0cfa6e32c1a6cf0a02b05af Mon Sep 17 00:00:00 2001 From: SP4C3B4R-8 <209078228+SP4C3B4R-8@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:28:10 +1300 Subject: [PATCH 1/4] perf(agc): time each recorded draw on the GPU by its fragment program Opt-in with APS5_PROFILE_GPU_DRAWS=1 together with APS5_PROFILE_GPU=1: a [gputime] range around each recorded draw command keyed by the fragment program's variant id, and a [drawvariant] line naming the code address behind each variant id once. --- .../libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp | 9 +++++++++ .../prx/libSceAgcDriver/Graphics/include/Recorder.hpp | 1 + core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp | 10 ++++++++++ .../libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp | 5 +++++ docs/dev/ENVIRONMENT.md | 1 + 5 files changed, 26 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 4d85df3c00..9c7df82eca 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -1231,6 +1231,15 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { if (drawParameters.indirect) drainBeforeInline(); lockForDraw(); noteDrawWriters(stages, submission.queue); + if (static const bool nameVariants = std::getenv("APS5_PROFILE_GPU_DRAWS") != nullptr; nameVariants && stages.size() == programs.size()) { + static std::mutex namedMutex; + static std::set named; + std::lock_guard namedLock(namedMutex); + for (std::size_t i = 0; i < stages.size(); ++i) { + if (stages[i].program == nullptr || !named.insert(stages[i].program->variantId).second) continue; + std::fprintf(stderr, "[drawvariant] 0x%llx stage %d code 0x%llx\n", static_cast(stages[i].program->variantId), static_cast(stages[i].stage), static_cast(programs[i].binary.codeAddress)); + } + } phaseTiming.Phase(DrawRowVectors); if (recipe != nullptr) { if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe, tracePtr) == RecipeOutcome::Recorded) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index f76e061983..4d0e718906 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -475,6 +475,7 @@ public: static bool GpuTimingEnabled(); static bool BatchStampsEnabled(); std::uint32_t BeginGpuTiming(std::uint64_t key); + std::uint32_t BeginInPassGpuTiming(std::uint64_t key); // `bytes`: what the range moved (a fill's, a copy's), summed per key on the [gputime] line. void EndGpuTiming(std::uint32_t index, std::uint64_t bytes = 0); // The key of the whole-batch range (first to last command; reported as "batch" on the [gputime] diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index f3dd7f8885..0baaf865f8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -2325,6 +2325,15 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty()); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); + static const bool timeEachDraw = std::getenv("APS5_PROFILE_GPU_DRAWS") != nullptr; + auto eachDrawTiming = Recorder::NoTiming; + if (timeEachDraw) { + std::uint64_t key = 0; + for (const auto& shader : shaders) { + if (shader.program != nullptr && (key == 0 || shader.stage == ShaderRecompiler::ShaderStage::Fragment)) key = shader.program->variantId; + } + eachDrawTiming = recorder->BeginInPassGpuTiming(key | (1ull << 63)); + } if (record.stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); else { recordStencilReplay(context, commands, state, draw, inputs, record.stencilReplay, shaders, resources, record.pushBytes, record.pushStages); @@ -2338,6 +2347,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam record.pipeline->ContinueSecondChannel(commands); recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); } + recorder->EndGpuTiming(eachDrawTiming); if (meshArguments != nullptr) recorder->Keep(meshArguments); if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten); auto checkRecords = indirectRecordCheck(record.indirect); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 3abc0aec26..e9ebefd599 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -2017,6 +2017,11 @@ std::uint32_t Recorder::BeginGpuTiming(std::uint64_t key) { return beginTiming(key); } +std::uint32_t Recorder::BeginInPassGpuTiming(std::uint64_t key) { + if (!GpuTimingEnabled() || open == nullptr || open->queries == VK_NULL_HANDLE) return NoTiming; + return beginTiming(key); +} + std::uint32_t Recorder::beginTiming(std::uint64_t key) { const bool full = GpuTimingEnabled(); if (!full && !(key == BatchTimingKey && BatchStampsEnabled())) return NoTiming; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index d54b9c8fd1..56a5c334e5 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -367,6 +367,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_PROBE_DISPATCH` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | probes one dispatch at a hex program address (optionally `:index`) with debug output | | `APS5_PROFILE_DRAW` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | prints the per-phase driver and draw timing totals | | `APS5_PROFILE_GPU` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records the GPU time of recorded work by key from batch timestamps | +| `APS5_PROFILE_GPU_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp`, `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | with APS5_PROFILE_GPU, records the GPU time of each recorded draw by its fragment program's variant id and prints a [drawvariant] line with the code address behind each variant id once (draws that do not use a recipe only) | | `APS5_REAP_EACH_PACKET` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | reaps deferred labels after every packet | | `APS5_RELEASE_ON_UNLOCK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | destroys deferred batches on the unlocking thread instead of a release thread | | `APS5_RELEASE_QUEUE_MAX` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | 256 | maximum deferred batches handed to the release thread before a thread destroys its own | From 3cd49f9b9b1e8cbf85758af8bf607800100bf3c5 Mon Sep 17 00:00:00 2001 From: SP4C3B4R-8 <209078228+SP4C3B4R-8@users.noreply.github.com> Date: Thu, 8 Oct 2026 12:02:50 +0200 Subject: [PATCH 2/4] chore(agc): dump a draw's programs by code address APS5_DUMP_DRAW_SHADERS also matches a program's code address, so a program named by [drawvariant] or [gputime] can be dumped directly. --- .../libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp | 2 +- docs/dev/ENVIRONMENT.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp index a6f6b5ebd9..443174fec5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -133,7 +133,7 @@ std::shared_ptr Driver::materializeDraw if (dumpTarget != 0) { const auto slot0 = (static_cast(readRegister(queue.context, 0x390)) << 40u) | (static_cast(readRegister(queue.context, 0x318)) << 8u); - if ((graphics.hasColorTarget && graphics.color.address == dumpTarget) || slot0 == dumpTarget) static_cast(dumpRequest(program.binary.codeAddress, request)); + if ((graphics.hasColorTarget && graphics.color.address == dumpTarget) || slot0 == dumpTarget || program.binary.codeAddress == dumpTarget) static_cast(dumpRequest(program.binary.codeAddress, request)); } if (dumpSlot1 != 0) { const auto value = [&](std::uint32_t offset) -> std::uint64_t { const auto it = queue.context.find(offset); return it == queue.context.end() ? 0u : it->second; }; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 56a5c334e5..2105a8e00c 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -134,7 +134,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_DRAW_UPLOAD_MAX` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 4096 | largest draw snapshot in bytes copied into the per-recorder upload chunk; 0 turns the chunk off | | `APS5_DUMP_COMPUTE_MIN_WORDS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | only captures compute pipelines whose SPIR-V has at least this many words | | `APS5_DUMP_COMPUTE_PIPELINES` | string | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | directory in which captured compute pipelines are written | -| `APS5_DUMP_DRAW_SHADERS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | 0 | hex color-target or slot-0 address whose draw's shaders are dumped | +| `APS5_DUMP_DRAW_SHADERS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | 0 | hex color-target or slot-0 address, or a program's code address, whose draw's shaders are dumped | | `APS5_DUMP_DRAW_SLOT1` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | 0 | hex slot-1 address whose draw's shaders and register state are dumped | | `APS5_DUMP_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 0 | saves the first n presented frames as frame_.bmp | | `APS5_DUMP_FRAMES_EVERY` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 1 | dumps every n-th presented frame | From 8c45681cc301b7a43055d6aaacfb7b7cbcbd7cfe Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 26 Oct 2026 22:33:53 +1300 Subject: [PATCH 3/4] fix(agc): join gputime keys to drawvariant ids and name queued draws The [gputime] line printed a draw's range key with the draw flag still set, so an ordinary variant id 0x2 appeared as 0x8000000000000002 next to [drawvariant] 0x2. A draw's key now comes from Recorder::DrawTimingKey and the line prints it through Recorder::TimingKeyLabel as "draw 0x2", the id [drawvariant] names. The [drawvariant] line was only printed when a draw was recorded inline, so a queued draw's first-seen variant had a timing range with no address line. It is now printed before the draw is handed to the pipeline. The APS5_PROFILE_GPU_DRAWS row states the label, the shared per-batch query cap and that draws using a recipe are not timed. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 1 + .../Execution/src/Driver/Draw/Draw.cpp | 18 +++++++++--------- .../Graphics/include/Recorder.hpp | 4 ++++ .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 +- .../libSceAgcDriver/Graphics/src/Recorder.cpp | 8 +++++++- .../prx/libSceAgcDriver/tests/Recorder.cpp | 17 +++++++++++++++++ docs/dev/ENVIRONMENT.md | 2 +- 7 files changed, 40 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 521a3f93bd..7a43ea2221 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1559,6 +1559,7 @@ configure_windows_unwind(agc_driver_recorder_tests) if(BUILD_TESTING) set_target_properties(agc_driver_recorder_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_recorder_device_selection COMMAND agc_driver_recorder_tests --device-selection-only) + add_test(NAME agc_gpu_timing_key COMMAND agc_driver_recorder_tests --gpu-timing-key-only) add_test(NAME agc_gpu_timing_complete COMMAND agc_driver_recorder_tests --gpu-timing-complete-only) add_test(NAME agc_gpu_timing_incomplete COMMAND agc_driver_recorder_tests --gpu-timing-incomplete-only) set_tests_properties(agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 9c7df82eca..f1e01ef9ec 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -1210,6 +1210,15 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { return DrawVerdict::Rejected; } } + if (static const bool nameVariants = std::getenv("APS5_PROFILE_GPU_DRAWS") != nullptr; nameVariants && stages.size() == programs.size()) { + static std::mutex namedMutex; + static std::set named; + std::lock_guard namedLock(namedMutex); + for (std::size_t i = 0; i < stages.size(); ++i) { + if (stages[i].program == nullptr || !named.insert(stages[i].program->variantId).second) continue; + std::fprintf(stderr, "[drawvariant] 0x%llx stage %d code 0x%llx\n", static_cast(stages[i].program->variantId), static_cast(stages[i].stage), static_cast(programs[i].binary.codeAddress)); + } + } if (pipelined && !drawParameters.indirect && (tail.queue == nullptr || deferredLabels().labels.empty())) { phaseTiming.Phase(DrawRowVectors); auto writes = drawWriteRanges(graphics, stages); @@ -1231,15 +1240,6 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { if (drawParameters.indirect) drainBeforeInline(); lockForDraw(); noteDrawWriters(stages, submission.queue); - if (static const bool nameVariants = std::getenv("APS5_PROFILE_GPU_DRAWS") != nullptr; nameVariants && stages.size() == programs.size()) { - static std::mutex namedMutex; - static std::set named; - std::lock_guard namedLock(namedMutex); - for (std::size_t i = 0; i < stages.size(); ++i) { - if (stages[i].program == nullptr || !named.insert(stages[i].program->variantId).second) continue; - std::fprintf(stderr, "[drawvariant] 0x%llx stage %d code 0x%llx\n", static_cast(stages[i].program->variantId), static_cast(stages[i].stage), static_cast(programs[i].binary.codeAddress)); - } - } phaseTiming.Phase(DrawRowVectors); if (recipe != nullptr) { if (localDevice->DrawFromRecipe(graphics, drawParameters, stages, snapshots, recipe, tracePtr) == RecipeOutcome::Recorded) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 4d0e718906..826dc8cb7d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -472,6 +473,9 @@ public: // queries around each timed range; the totals per key are reported every 10 s. Begin returns the // range index to pass to End, or NoTiming when timing is off or the batch's queries are used up. static constexpr std::uint32_t NoTiming = 0xffffffffu; + static constexpr std::uint64_t DrawTimingFlag = std::uint64_t{1} << 63u; + static constexpr std::uint64_t DrawTimingKey(std::uint64_t variantId) { return variantId | DrawTimingFlag; } + static std::string TimingKeyLabel(std::uint64_t key); static bool GpuTimingEnabled(); static bool BatchStampsEnabled(); std::uint32_t BeginGpuTiming(std::uint64_t key); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 0baaf865f8..8b2dcf4eb5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -2332,7 +2332,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam for (const auto& shader : shaders) { if (shader.program != nullptr && (key == 0 || shader.stage == ShaderRecompiler::ShaderStage::Fragment)) key = shader.program->variantId; } - eachDrawTiming = recorder->BeginInPassGpuTiming(key | (1ull << 63)); + eachDrawTiming = recorder->BeginInPassGpuTiming(Recorder::DrawTimingKey(key)); } if (record.stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset, trace, subdraw); else { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index e9ebefd599..c657013f21 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -2017,6 +2017,12 @@ std::uint32_t Recorder::BeginGpuTiming(std::uint64_t key) { return beginTiming(key); } +std::string Recorder::TimingKeyLabel(std::uint64_t key) { + char text[32]; + std::snprintf(text, sizeof(text), (key & DrawTimingFlag) != 0 ? "draw 0x%llx" : "0x%llx", static_cast(key & ~DrawTimingFlag)); + return text; +} + std::uint32_t Recorder::BeginInPassGpuTiming(std::uint64_t key) { if (!GpuTimingEnabled() || open == nullptr || open->queries == VK_NULL_HANDLE) return NoTiming; return beginTiming(key); @@ -2693,7 +2699,7 @@ void Recorder::readGpuTiming(Batch& batch) { std::sort(hot.begin(), hot.end(), [](const auto& a, const auto& b) { return a.second.ms > b.second.ms; }); // The first field stays the program sum: the measure scripts match on it. AgcDriver::ProfilePrint_nid_no_patch("[gputime] %.0f ms of GPU time in %llu batches over 10 s (batch %.0f ms first to last command; classes %.0f ms, all ranges %.0f ms, untimed %.0f ms outside every range; %llu presents; %llu ranges dropped at the %u cap); by program:", timingProgramMs, static_cast(timingBatches), timingBatchMs, timingClassMs, timingUnionMs, timingBatchMs - timingUnionMs, static_cast(presents), static_cast(timingDropped.exchange(0, std::memory_order_relaxed)), MaxTimedRanges); - for (std::size_t i = 0; i < hot.size() && i < 12; ++i) AgcDriver::ProfilePrint_nid_no_patch(" 0x%llx x%llu %.0fms", static_cast(hot[i].first), static_cast(hot[i].second.count), hot[i].second.ms); + for (std::size_t i = 0; i < hot.size() && i < 12; ++i) AgcDriver::ProfilePrint_nid_no_patch(" %s x%llu %.0fms", Recorder::TimingKeyLabel(hot[i].first).c_str(), static_cast(hot[i].second.count), hot[i].second.ms); AgcDriver::ProfilePrint_nid_no_patch("; by class:%s\n", classes.c_str()); timingByKey.clear(); timingProgramMs = timingClassMs = timingUnionMs = timingBatchMs = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 412903f163..ef13edeba2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -249,6 +249,18 @@ bool hintMappingHistoryTests() { #endif } +void gpuTimingKeyTests() { + constexpr std::uint64_t variant = 0x2; + const auto key = Recorder::DrawTimingKey(variant); + Require(key != variant && (key & Recorder::DrawTimingFlag) != 0, "a draw's timing key is not flagged apart from the other keys"); + char named[64]; + std::snprintf(named, sizeof(named), "0x%llx", static_cast(variant)); + Require(Recorder::TimingKeyLabel(key) == std::string("draw ") + named, "a draw's timing label is not 'draw ' plus the id [drawvariant] prints"); + Require(Recorder::TimingKeyLabel(variant) == named, "a key that is not a draw's is labelled with the draw prefix"); + Require(Recorder::TimingKeyLabel(Recorder::DrawTimingKey(0)) == "draw 0x0", "a draw with no program is not labelled as a draw"); + Require(Recorder::TimingKeyLabel(Recorder::DrawTimingKey(0x123456789abcdefull)) == "draw 0x123456789abcdef", "a wide variant id was cut in the label"); +} + void deviceSelectionTests() { const auto properties = [](const char* name, VkPhysicalDeviceType type) { VkPhysicalDeviceProperties result{}; @@ -11432,6 +11444,11 @@ int main(int argc, char** argv) { std::cout << "partial private write-watch restoration tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--gpu-timing-key-only") { + gpuTimingKeyTests(); + std::cout << "gpu timing key tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--device-selection-only") { deviceSelectionTests(); std::cout << "recorder device selection tests passed\n"; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 2105a8e00c..30a209d303 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -367,7 +367,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_PROBE_DISPATCH` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | probes one dispatch at a hex program address (optionally `:index`) with debug output | | `APS5_PROFILE_DRAW` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | prints the per-phase driver and draw timing totals | | `APS5_PROFILE_GPU` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records the GPU time of recorded work by key from batch timestamps | -| `APS5_PROFILE_GPU_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp`, `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | with APS5_PROFILE_GPU, records the GPU time of each recorded draw by its fragment program's variant id and prints a [drawvariant] line with the code address behind each variant id once (draws that do not use a recipe only) | +| `APS5_PROFILE_GPU_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp`, `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | with APS5_PROFILE_GPU, records the GPU time of each recorded draw by its fragment program's variant id and prints a [drawvariant] line with the code address behind each variant id once (draws that do not use a recipe only; the [gputime] line labels each `draw 0x`, the id [drawvariant] prints, and the ranges share the batch's query cap) | | `APS5_REAP_EACH_PACKET` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | reaps deferred labels after every packet | | `APS5_RELEASE_ON_UNLOCK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | destroys deferred batches on the unlocking thread instead of a release thread | | `APS5_RELEASE_QUEUE_MAX` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | 256 | maximum deferred batches handed to the release thread before a thread destroys its own | From 8e38bcf06d97f5f7ecaa1701b79415bf25a3d9f0 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Tue, 27 Oct 2026 01:54:36 +1300 Subject: [PATCH 4/4] fix(agc): set the Windows runtime path for the gpu timing key test agc_gpu_timing_key ran without the PATH entry for the libc DLL that the other recorder tests set, so it exited with STATUS_DLL_NOT_FOUND on Windows. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 7a43ea2221..d6f8864006 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1565,7 +1565,7 @@ if(BUILD_TESTING) set_tests_properties(agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT "APS5_PROFILE_GPU=1" TIMEOUT 15 SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_recorder_device_selection agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_device_selection agc_gpu_timing_key agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_command_memory_reuse COMMAND agc_driver_recorder_tests --command-reuse-only)