feat(agc): report performance timer scopes to Tracy with APS5_ENABLE_TRACY
APS5_ENABLE_TRACY=ON builds the Tracy 0.14.1 client from the new 3rdparty/tracy submodule as a shared library next to the patched libraries. Every PerformanceTimer scope of libSceAgcDriver and libSceVideoOut becomes a Tracy zone, each presented flip ends a frame, and the queue workers and the presenter name their threads. The client listens only on localhost and records only while a profiler is connected; sampling, system tracing, call stacks, code transfer and its crash handler are off. Without the option nothing of Tracy is compiled. On MinGW the client is built without thread_local destructors and with TRACY_DELAYED_INIT, because emulated TLS frees thread_local objects before their destructors run; a thread's event producer then stays alive until the process exits instead of being destroyed on freed memory.
This commit is contained in:
parent
3e0e01f35f
commit
28ca53dc89
|
|
@ -28,6 +28,9 @@
|
|||
[submodule "3rdparty/stb"]
|
||||
path = 3rdparty/stb
|
||||
url = https://github.com/nothings/stb.git
|
||||
[submodule "3rdparty/tracy"]
|
||||
path = 3rdparty/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
||||
[submodule "3rdparty/libjpeg-turbo"]
|
||||
path = 3rdparty/libjpeg-turbo
|
||||
url = https://github.com/libjpeg-turbo/libjpeg-turbo.git
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 30997d5ca6bb632cc10807a1da8a6d3de0aeeb3c
|
||||
|
|
@ -26,6 +26,7 @@ endif()
|
|||
option(ANYPS5_ENABLE_SPIRV_TOOLS "Enable SPIRV-Tools validation and optimization" OFF)
|
||||
option(ANYPS5_ENABLE_ASAN "Build Linux targets with AddressSanitizer and use an ASan-compatible guest arena range" OFF)
|
||||
option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timing.log" OFF)
|
||||
option(APS5_ENABLE_TRACY "Build the Tracy profiler client and report performance timer scopes as Tracy zones" OFF)
|
||||
option(ANYPS5_USE_SYSTEM_DEPS "Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules" OFF)
|
||||
add_compile_definitions(APS5_ENABLE_TIMING_LOG=$<BOOL:${APS5_ENABLE_TIMING_LOG}>)
|
||||
include(cmake/SystemDeps.cmake)
|
||||
|
|
@ -165,5 +166,8 @@ endif()
|
|||
add_subdirectory(core/shader/recompiler)
|
||||
add_subdirectory(core/Decoder/Jpeg)
|
||||
add_subdirectory(core/Decoder/Png)
|
||||
if(APS5_ENABLE_TRACY)
|
||||
include(cmake/Tracy.cmake)
|
||||
endif()
|
||||
add_subdirectory(core/libs)
|
||||
add_subdirectory(core/relinker)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_STATIC OFF CACHE BOOL "" FORCE)
|
||||
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_ONLY_LOCALHOST ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_CODE_TRANSFER ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_SAMPLING ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_SYSTEM_TRACING ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_CALLSTACK ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_FRAME_IMAGE ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_VERIFY ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_DISABLE_FIND_PACKAGE_rocprofiler-sdk ON)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/tracy ${CMAKE_BINARY_DIR}/3rdparty/tracy EXCLUDE_FROM_ALL)
|
||||
|
||||
if(MINGW)
|
||||
target_sources(TracyClient PRIVATE ${CMAKE_CURRENT_LIST_DIR}/TracyThreadExit.cpp)
|
||||
target_compile_definitions(TracyClient PRIVATE TRACY_DELAYED_INIT)
|
||||
get_target_property(tracyInterfaceDefinitions TracyClient INTERFACE_COMPILE_DEFINITIONS)
|
||||
list(FILTER tracyInterfaceDefinitions EXCLUDE REGEX "^(WINVER|_WIN32_WINNT)=")
|
||||
set_target_properties(TracyClient PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${tracyInterfaceDefinitions}")
|
||||
endif()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
extern "C" int __cxa_thread_atexit(void (*)(void*), void*, void*) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -149,6 +149,24 @@ foreach(LIB_DIR ${LIBS_DIRS})
|
|||
endif()
|
||||
endforeach()
|
||||
|
||||
if(APS5_ENABLE_TRACY)
|
||||
set_target_properties(TracyClient PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${UNPATCHED_DIR}
|
||||
LIBRARY_OUTPUT_DIRECTORY ${UNPATCHED_DIR}
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${IMPLIB_DIR}
|
||||
)
|
||||
set(TRACY_CLIENT_COPIED ${CMAKE_CURRENT_BINARY_DIR}/TracyClient.copied)
|
||||
add_custom_command(
|
||||
OUTPUT ${TRACY_CLIENT_COPIED}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PATCHED_DIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:TracyClient> ${PATCHED_DIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${TRACY_CLIENT_COPIED}
|
||||
DEPENDS TracyClient
|
||||
COMMENT "Copying the Tracy client next to the patched libraries"
|
||||
)
|
||||
list(APPEND PATCHED_OUTPUTS ${TRACY_CLIENT_COPIED})
|
||||
endif()
|
||||
|
||||
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
|
|
|||
|
|
@ -265,6 +265,9 @@ target_link_libraries(libSceAgcDriver PRIVATE
|
|||
jpeg_turbo
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
if(APS5_ENABLE_TRACY)
|
||||
target_link_libraries(libSceAgcDriver PUBLIC TracyClient)
|
||||
endif()
|
||||
|
||||
set_target_properties(libSceAgcDriver PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(libSceAgcDriver)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETIMER_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
|
|
@ -275,6 +276,7 @@ public:
|
|||
}
|
||||
|
||||
void Finish() {
|
||||
zone.End();
|
||||
if (frame == nullptr && trace == nullptr) return;
|
||||
const auto end = Clock::now();
|
||||
if (trace != nullptr) trace->Add(scope, nullptr, start, end);
|
||||
|
|
@ -307,6 +309,7 @@ private:
|
|||
FrameTiming* frame;
|
||||
TraceThread* trace;
|
||||
const char* scope;
|
||||
[[no_unique_address]] TracyZone zone{scope};
|
||||
FrameTiming::Metric* total = nullptr;
|
||||
FrameTiming::Metric* tail = nullptr;
|
||||
Clock::time_point start{};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACYZONE_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACYZONE_HPP
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#include <tracy/TracyC.h>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
class TracyZone {
|
||||
public:
|
||||
explicit TracyZone([[maybe_unused]] const char* name) {
|
||||
#ifdef TRACY_ENABLE
|
||||
context = ___tracy_emit_zone_begin(location(name), 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TracyZone(const TracyZone&) = delete;
|
||||
TracyZone& operator=(const TracyZone&) = delete;
|
||||
|
||||
~TracyZone() {
|
||||
End();
|
||||
}
|
||||
|
||||
void End() {
|
||||
#ifdef TRACY_ENABLE
|
||||
if (context.active == 0) return;
|
||||
___tracy_emit_zone_end(context);
|
||||
context.active = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void MarkFrame() {
|
||||
#ifdef TRACY_ENABLE
|
||||
___tracy_emit_frame_mark(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void NameThread([[maybe_unused]] const char* name) {
|
||||
#ifdef TRACY_ENABLE
|
||||
___tracy_set_thread_name(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef TRACY_ENABLE
|
||||
static const ___tracy_source_location_data* location(const char* name) {
|
||||
static constexpr std::size_t capacity = 256;
|
||||
static std::array<std::atomic<const char*>, capacity> names{};
|
||||
static std::array<___tracy_source_location_data, capacity> locations{};
|
||||
static std::mutex mutex;
|
||||
const auto start = static_cast<std::size_t>(reinterpret_cast<std::uintptr_t>(name) >> 4);
|
||||
for (std::size_t probe = 0; probe < capacity; ++probe) {
|
||||
const auto index = (start + probe) % capacity;
|
||||
const auto* known = names[index].load(std::memory_order_acquire);
|
||||
if (known == name) return &locations[index];
|
||||
if (known != nullptr) continue;
|
||||
std::lock_guard lock(mutex);
|
||||
known = names[index].load(std::memory_order_relaxed);
|
||||
if (known == name) return &locations[index];
|
||||
if (known != nullptr) continue;
|
||||
locations[index] = {name, name, "", 0, 0};
|
||||
names[index].store(name, std::memory_order_release);
|
||||
return &locations[index];
|
||||
}
|
||||
throw std::runtime_error("Tracy: too many zone names");
|
||||
}
|
||||
|
||||
TracyCZoneCtx context;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
|
@ -127,6 +128,7 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff
|
|||
}
|
||||
gpuReady(context);
|
||||
timing.Mark("release_and_callback");
|
||||
TracyZone::MarkFrame();
|
||||
if (profile) reportPresents(waitedMs, inFlight);
|
||||
CheckFailure();
|
||||
} catch (const ProcessShutdown&) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
|
||||
#include "prx/libc/include/CpuTopology.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ std::uint64_t WorkerAffinityMask() {
|
|||
}
|
||||
|
||||
void PinWorkerThread(const char* role) {
|
||||
TracyZone::NameThread(role);
|
||||
const auto mask = WorkerAffinityMask();
|
||||
if (mask == 0) return;
|
||||
static std::once_flag summary;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ Project switches accept `ON` or `OFF`:
|
|||
| `-DANYPS5_ENABLE_ASAN=ON` | `OFF` | Build with AddressSanitizer on Linux and move the guest arena out of ASan's shadow gap. |
|
||||
| `-DANYPS5_USE_SYSTEM_DEPS=ON` | `OFF` | Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the submodules. |
|
||||
| `-DAPS5_ENABLE_TIMING_LOG=ON` | `OFF` | Compile frame timing logging. |
|
||||
| `-DAPS5_ENABLE_TRACY=ON` | `OFF` | Build the [Tracy](#tracy) profiler client into the agc driver. |
|
||||
| `-DAPS5_AGC_CREATE_LOG=OFF` | `ON` | Disable successful `sceAgcCreateShader` logging. |
|
||||
| `-DAGC_BUILD_VISUAL_TEST=ON` | `OFF` | Build the standalone AGC SPIR-V visual test. |
|
||||
|
||||
|
|
@ -92,6 +93,12 @@ ctest --test-dir build/write-watch --output-on-failure
|
|||
|
||||
With `CMAKE_OSX_ARCHITECTURES=x86_64` Apple silicon runs the tests through Rosetta, which is how relinked titles run; without it they run natively. The full `BUILD_TESTING` build on macOS also registers them.
|
||||
|
||||
## Tracy
|
||||
|
||||
`-DAPS5_ENABLE_TRACY=ON` builds the [Tracy](https://github.com/wolfpld/tracy) 0.14.1 client from `3rdparty/tracy` as a shared library next to the patched `.prx` files (`libTracyClient.dll`, `libTracyClient.so.0.14.1` on Linux); copy it with them. Every `PerformanceTimer` scope of libSceAgcDriver and libSceVideoOut is a zone, every presented flip ends a frame, and the queue workers and the presenter are named threads. Connect the Tracy 0.14.1 profiler to `127.0.0.1`: the client listens only on localhost and records only while a profiler is connected. Sampling, system tracing, call stacks, code transfer and Tracy's crash handler are disabled. Without the flag nothing of Tracy is built.
|
||||
|
||||
On Windows MinGW frees `thread_local` objects before their destructors run, so the Tracy client is built without `thread_local` destructors and never destroys its event queue: a thread that emitted a zone and ended keeps its event blocks (2 MiB each) until the process exits.
|
||||
|
||||
## Compute pipeline replay
|
||||
|
||||
Build `agc_compute_pipeline_replay` in the full build with `-DANYPS5_ENABLE_SPIRV_TOOLS=ON`; replay validates the captured module before calling Vulkan. Set `APS5_DUMP_COMPUTE_PIPELINES` to a dedicated empty directory before starting a title to save each uncached compute pipeline's final SPIR-V, descriptor layouts, push constant ranges, specialization values, subgroup size and creation flags before compilation. Files are named `compute-<identity>/capture.aps5cp`; identical records are deduplicated. Capture stops with an error at 128 records, 256 MiB total or 36 MiB per record (at most 8,388,608 SPIR-V words, or 32 MiB of shader code). Capture files contain shader code and device metadata, but no guest buffers or textures; review them before sharing.
|
||||
|
|
|
|||
Loading…
Reference in New Issue