fix(agc): trace the title threads that register shaders

Shader registration and the shader ABI resolves run on the title's own
threads, which never bound a trace buffer, so their Shader.Register and
Shader.Resolve* scopes were missing from every trace and summary. Those
entry points now bind the calling thread as `title` with the 32,768-event
buffer the shader preparation threads use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
milescovielloandClaude Opus 5.5 committed 2026-10-17 21:33:53 +00:00
1 parent e815fb4170
commit f7cd8df309
4 files changed
+108 -1

No files matched your search

@@ -1650,6 +1650,18 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_untracked_snapshot_hint PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_driver_registration_trace PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_TRACE_FRAMES=0:1;APS5_TRACE_EVENTS=64" TIMEOUT 30)
set_property(TEST agc_driver_registration_trace APPEND PROPERTY ENVIRONMENT_MODIFICATION
"APS5_TRACE_SECONDS=unset:" "APS5_TRACE_SUMMARY_EVERY=unset:")
add_test(NAME agc_driver_registration_trace_disabled COMMAND ${CMAKE_COMMAND} -E env
--unset=APS5_TRACE_FRAMES --unset=APS5_TRACE_SECONDS --unset=APS5_TRACE_SUMMARY_EVERY
$<TARGET_FILE:agc_driver_registration_trace_tests> disabled)
set_tests_properties(agc_driver_registration_trace_disabled PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_registration_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_sparse_buffer_holes COMMAND agc_driver_graphics_tests --sparse-buffer-holes)
set_tests_properties(agc_driver_native_multisample_depth PROPERTIES TIMEOUT 30)
add_test(NAME agc_driver_native_depth_retirement COMMAND agc_driver_native_multisample_depth_tests --retirement)
@@ -1245,7 +1245,16 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
return prepared;
}
namespace {
void BindTitleThread() {
if (PerformanceTrace::Configured()) PerformanceTrace::BindThread("title", TraceThread::DefaultCapacity / 8);
}
}
void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
BindTitleThread();
PerformanceContext timingContext(FrameTiming::Preparation());
PerformanceTimer timing("Shader.ResolveGraphicsStagesAbi");
ShaderPreparationTransaction transaction;
@@ -1322,6 +1331,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
}
void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
BindTitleThread();
PerformanceContext timingContext(FrameTiming::Preparation());
PerformanceTimer timing("Shader.ResolveAbi");
ShaderPreparationTransaction transaction;
@@ -1393,6 +1403,7 @@ std::uint64_t NullPixelProgramAddress() {
}
void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
BindTitleThread();
PerformanceContext timingContext(FrameTiming::Preparation());
PerformanceTimer timing("Shader.ResolveGraphicsAbi");
CheckFailure();
@@ -1519,6 +1530,7 @@ std::shared_ptr<const ShaderRegistry> Driver::RegisteredShaders() {
}
void Driver::RegisterShader(const Shader* shader) {
BindTitleThread();
PerformanceContext timingContext(FrameTiming::Preparation());
PerformanceTimer timing("Shader.Register");
ShaderPreparationTransaction transaction;
@@ -0,0 +1,83 @@
#include "VulkanTestDevice.hpp"
#include "SceShaders.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
namespace {
alignas(256) const std::array<std::uint32_t, 1> PixelCode{0xbf810000u};
struct PixelHeader {
Shader shader{};
std::array<ShaderRegister, 4> registers{};
std::array<ShaderRegister, 3> context{{{0x1b3, 2}, {0x1b4, 2}, {0x1b6, 1}}};
PixelHeader() {
const auto address = reinterpret_cast<std::uintptr_t>(PixelCode.data());
shader.file_header = 0x34333231u;
shader.version = 0x18;
shader.header_size = sizeof(PixelHeader);
shader.shader_size = sizeof(PixelCode);
shader.code = PixelCode.data();
shader.type = 1;
registers = {{{0x008, static_cast<std::uint32_t>(address >> 8u)}, {0x009, static_cast<std::uint32_t>(address >> 40u)}, {0x00a, 0}, {0x00b, 0}}};
shader.sh_registers = registers.data();
shader.num_sh_registers = registers.size();
shader.cx_registers = context.data();
shader.num_cx_registers = context.size();
}
};
const char* CheckRegistration(bool disabled) {
PixelHeader pixel;
AgcDriverRegisterShader_nid_postfix(&pixel.shader);
auto* trace = AgcDriver::PerformanceTrace::Thread();
if (disabled) return trace != nullptr ? "an unconfigured trace bound a buffer to a registering thread" : nullptr;
if (trace == nullptr) return "a title thread that registered a shader has no trace buffer";
const auto snapshot = trace->Collect();
if (snapshot.name != "title" || snapshot.dropped) return "the registering thread's trace identity or capacity is incorrect";
const auto registration = [](const auto& event) { return event.scope != nullptr && std::string_view(event.scope) == "Shader.Register" && event.stage == nullptr && event.length > std::chrono::steady_clock::duration::zero(); };
if (std::none_of(snapshot.slices.begin(), snapshot.slices.end(), registration) || std::none_of(snapshot.totals.begin(), snapshot.totals.end(), registration)) return "the registration was missing from the title thread's trace slices or totals";
return nullptr;
}
}
int main(int argc, char** argv) {
try {
if (!OpenVulkanTestDevice()) return VulkanTestSkipped;
const bool disabled = argc > 1 && std::string_view(argv[1]) == "disabled";
if (AgcDriver::PerformanceTrace::Configured() == disabled) throw std::runtime_error("unexpected trace configuration");
if (!disabled) AgcDriver::PerformanceTrace::Present(1, 1, nullptr);
std::string failure = "the registering thread did not run";
std::thread([&] {
try {
const char* message = CheckRegistration(disabled);
failure = message != nullptr ? message : "";
} catch (const std::exception& error) {
failure = error.what();
}
}).join();
if (!failure.empty()) throw std::runtime_error(failure);
AgcDriverShutdown_nid_postfix();
std::cout << "registration tracing checked\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
try {
AgcDriverShutdown_nid_postfix();
} catch (const std::exception& shutdownError) {
std::cerr << shutdownError.what() << '\n';
}
return 1;
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ Set `APS5_SAMPLE_WORKER=<file>` to sample queue worker 0 every millisecond (in p
Set `APS5_TRACE_FRAMES=<start>:<count>` to record `<count>` presents from present `<start>` (counted from 0), or `APS5_TRACE_SECONDS=<start>:<length>` to record from the first present at least `<start>` seconds after the first present until the first present `<length>` seconds later. Both values are whole numbers and the length is above 0; a malformed value, or both switches together, fails the first present. The window opens and closes at presents.
Set `APS5_TRACE_EVENTS=<count>` to change the queue-worker and presenter buffers for a frame or seconds window. The count is a whole number from 1 through 4,194,304; the default is 262,144. An invalid value fails when one of these threads first binds to the trace, before recording. Each buffer uses `count * sizeof(TraceThread::Slice)` bytes (currently 32 bytes per event): 8 MiB at the default, 96 MiB at `3145728`, and 128 MiB at the maximum, per thread. The buffers are allocated even while waiting for the window to open, and formatting the trace uses additional memory. Shader preparation keeps its explicitly sized 32,768-event buffers, and the GPU range limit stays 262,144. Summary-only sessions allocate no event buffers and do not read this setting. A larger buffer can still fill; use a shorter window or increase the count within the bound.
Set `APS5_TRACE_EVENTS=<count>` to change the queue-worker and presenter buffers for a frame or seconds window. The count is a whole number from 1 through 4,194,304; the default is 262,144. An invalid value fails when one of these threads first binds to the trace, before recording. Each buffer uses `count * sizeof(TraceThread::Slice)` bytes (currently 32 bytes per event): 8 MiB at the default, 96 MiB at `3145728`, and 128 MiB at the maximum, per thread. The buffers are allocated even while waiting for the window to open, and formatting the trace uses additional memory. Shader preparation keeps its explicitly sized 32,768-event buffers, as does a title thread named `title` that binds one the first time it registers a shader or resolves a shader ABI. The GPU range limit stays 262,144. Summary-only sessions allocate no event buffers and do not read this setting. A larger buffer can still fill; use a shorter window or increase the count within the bound.
Inside the window the queue workers and the presenter record every `PerformanceTimer` scope and stage as a complete event named after its frame timing key: `Driver.WorkerWait`, `Driver.Packet`, `Driver.Packet.draw`, `Graphics.Draw`, `Driver.Present`, `Driver.Present.inflight_wait`, `Vulkan.Retire` and so on. A slice shorter than 20 us is only counted: its time is in the thread's counter `<thread> folded` (milliseconds per present) and in the summary. A thread writes a present's counter sample at its first slice after the next present, so the last present of the window has no sample; its time is still in the summary. By default, each queue worker and presenter allocates a buffer for 262,144 events when it starts; a full buffer stops that thread's recording, sets `trace_dropped` and puts an instant event `trace buffer full` on the thread's track at the start of the first lost slice (slices are appended when their outer scope ends, so that can precede the last recorded slice by up to one outer-scope length), with the number of lost slices in `args.events_lost`. When the window closes, the presenter formats the trace once and a `ProfileOutput` writer thread writes `aps5-trace-<commit>-<pid>.json.gz` (Chrome trace-event JSON, opened by ui.perfetto.dev or chrome://tracing) and appends one line to `aps5-summary.jsonl`, both in `APS5_TRACE_DIR` or the working directory. A window that is still open when the title exits writes nothing.