#include "tracing/tracing.h" #include "graphics/2d.h" #include "globalincs/systemvars.h" #include "parse/parselo.h" #include "io/timer.h" #include "cmdline/cmdline.h" #include "TraceEventWriter.h" #include "MainFrameTimer.h" #include "FrameProfiler.h" #include #include #include #include // A function for getting the id of the current thread #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include static int64_t get_tid() { return (int64_t) GetCurrentThreadId(); } #elif __LINUX__ #include static int64_t get_tid() { return (int64_t) syscall(SYS_gettid); } #else #include static int64_t get_tid() { // This is not a reliable way of getting the tid but it's better than nothing return (int64_t) pthread_self(); } #endif // A function for getting the id of the current process #ifdef WIN32 static int64_t get_pid() { return (int64_t)GetCurrentProcessId(); } #else #include static int64_t get_pid() { return (int64_t) getpid(); } #endif namespace { using namespace tracing; std::unique_ptr traceEventWriter; std::unique_ptr mainFrameTimer; std::unique_ptr frameProfiler; SCP_vector query_objects; // Free list for backends where queries are immediately reusable (OpenGL). // When queries are NOT reusable (Vulkan), the free list is bypassed and // handles are returned to the backend. SCP_queue free_query_objects; bool do_gpu_queries = true; bool queries_reusable = true; int get_query_object() { if (!free_query_objects.empty()) { auto id = free_query_objects.front(); free_query_objects.pop(); return id; } auto id = gr_create_query_object(); if (queries_reusable) { // Track for bulk cleanup at shutdown. When not reusable, the backend // owns the lifecycle — handles are returned via gr_delete_query_object // and the backend's own shutdown destroys the pool. query_objects.push_back(id); } return id; } int get_gpu_timestamp_query() { GR_DEBUG_SCOPE("Query tracing timestamp"); auto query = get_query_object(); gr_query_value(query, QueryType::Timestamp); return query; } void free_query_object(int obj) { if (queries_reusable) { free_query_objects.push(obj); } else { // Backend manages reset lifecycle internally — hand it back. gr_delete_query_object(obj); } } struct gpu_trace_event { trace_event base_evt; int gpu_begin_query = -1; int gpu_end_query = -1; }; SCP_queue gpu_events; bool initialized = false; bool do_trace_events = false; bool do_async_events = false; bool do_counter_events = false; std::int64_t main_thread_id = -1; int gpu_start_query = -1; std::uint64_t gpu_start_time = 0; std::uint64_t cpu_start_time = 0; std::uint64_t current_id = 0; void submit_event(trace_event* evt) { if (evt->pid == GPU_PID) { evt->timestamp -= gpu_start_time; } else { evt->timestamp -= cpu_start_time; } if (traceEventWriter) { // Trace event writer receives all events traceEventWriter->processEvent(evt); } if (mainFrameTimer) { mainFrameTimer->processEvent(evt); } if (frameProfiler) { frameProfiler->processEvent(evt); } } void process_gpu_events() { Assertion(get_tid() == main_thread_id, "This function must be called from the main thread!"); if (gpu_start_query >= 0) { if (gr_query_value_available(gpu_start_query)) { gpu_start_time = gr_get_query_value(gpu_start_query); gpu_start_query = -1; } else { // Wait until query is finished return; } } GR_DEBUG_SCOPE("Query GPU timestamps"); while (!gpu_events.empty()) { auto& first = gpu_events.front(); auto result_available = true; if (first.gpu_begin_query != -1) { if (gr_query_value_available(first.gpu_begin_query)) { first.base_evt.timestamp = gr_get_query_value(first.gpu_begin_query); free_query_object(first.gpu_begin_query); first.gpu_begin_query = -1; } else { // Query not processed yet, try again later... result_available = false; } } switch (first.base_evt.type) { case EventType::Complete: // For complete events, check the end query if (gr_query_value_available(first.gpu_end_query)) { // All queries are finished, get the values and submit the event auto finished_evt = first.base_evt; auto val = gr_get_query_value(first.gpu_end_query); finished_evt.duration = val - finished_evt.timestamp; free_query_object(first.gpu_end_query); submit_event(&finished_evt); gpu_events.pop(); } else { result_available = false; } break; case EventType::Begin: case EventType::End: if (result_available) { submit_event(&first.base_evt); gpu_events.pop(); } break; default: UNREACHABLE("Invalid event type %d!", static_cast(first.base_evt.type)); gpu_events.pop(); break; } if (!result_available) { // GPU result not available, try again next frame break; } } } void init_event(const Category& category, trace_event* evt) { evt->category = &category; evt->timestamp = timer_get_nanoseconds(); evt->pid = get_pid(); evt->tid = get_tid(); } } namespace tracing { void init() { do_trace_events = false; do_async_events = false; do_counter_events = false; if (Cmdline_json_profiling) { traceEventWriter.reset(new ThreadedTraceEventWriter()); do_trace_events = true; do_async_events = true; do_counter_events = true; } if (Cmdline_profile_write_file) { mainFrameTimer.reset(new ThreadedMainFrameTimer()); do_async_events = true; } if (Cmdline_frame_profile) { frameProfiler.reset(new FrameProfiler()); do_trace_events = true; } do_gpu_queries = gr_is_capable(gr_capability::CAPABILITY_TIMESTAMP_QUERY); queries_reusable = gr_is_capable(gr_capability::CAPABILITY_QUERIES_REUSABLE); if (do_gpu_queries) { gpu_start_query = get_gpu_timestamp_query(); } cpu_start_time = timer_get_nanoseconds(); main_thread_id = get_tid(); initialized = true; } void process_events() { if (do_gpu_queries) { // Process pending GPU events process_gpu_events(); } } void frame_profile_process_frame() { Assertion(frameProfiler, "Frame profiling must be enabled for this function!"); return frameProfiler->processFrame(); } SCP_string get_frame_profile_output() { Assertion(frameProfiler, "Frame profiling must be enabled for this function!"); return frameProfiler->getContent(); } void shutdown() { if (queries_reusable) { while (!gpu_events.empty()) { process_events(); // Don't do busy waiting... os_sleep(5); } } else { // Discard remaining GPU events — no more frames will // be submitted, so unsubmitted queries can never become // available. while (!gpu_events.empty()) { auto& first = gpu_events.front(); gr_delete_query_object(first.gpu_begin_query); gr_delete_query_object(first.gpu_end_query); gpu_events.pop(); } } for (auto query : query_objects) { gr_delete_query_object(query); } query_objects.clear(); while (!free_query_objects.empty()) { free_query_objects.pop(); } mainFrameTimer = nullptr; traceEventWriter = nullptr; initialized = false; } namespace complete { void start(const Category& category, trace_event* evt) { if (!do_trace_events) { // No one to process the event is here return; } if (!initialized) { return; } init_event(category, evt); evt->duration = 0; evt->type = EventType::Complete; evt->event_id = ++current_id; if (do_gpu_queries && category.usesGPUCounter()) { Assertion(get_tid() == main_thread_id, "This function must be called from the main thread!"); gpu_trace_event gpu_event; gpu_event.base_evt.category = &category; gpu_event.base_evt.tid = 1; gpu_event.base_evt.pid = GPU_PID; gpu_event.base_evt.type = EventType::Begin; gpu_event.gpu_begin_query = get_gpu_timestamp_query(); // This does not need to be synchronized since GPU queries are only allowed on the main thread. gpu_events.push(gpu_event); } } void end(trace_event* evt) { if (!do_trace_events) { // No one to process the event is here return; } if (!initialized) { return; } Assertion(evt->pid == get_pid(), "Complete events must be generated from the same process!"); Assertion(evt->tid == get_tid(), "Complete events must be generated from the same thread!"); evt->duration = timer_get_nanoseconds() - evt->timestamp; evt->end_event_id = ++current_id; // Process CPU events submit_event(evt); // Create GPU events if (do_gpu_queries && evt->category->usesGPUCounter()) { Assertion(get_tid() == main_thread_id, "This function must be called from the main thread!"); gpu_trace_event gpu_event; gpu_event.base_evt.category = evt->category; gpu_event.base_evt.tid = 1; gpu_event.base_evt.pid = GPU_PID; gpu_event.base_evt.type = EventType::End; gpu_event.gpu_begin_query = get_gpu_timestamp_query(); // This does not need to be synchronized since GPU queries are only allowed on the main thread. gpu_events.push(gpu_event); } } } namespace async { void begin(const Category& category, const Scope& async_scope) { if (!do_async_events) { return; } trace_event evt; init_event(category, &evt); evt.type = EventType::AsyncBegin; evt.scope = &async_scope; evt.event_id = ++current_id; submit_event(&evt); } void step(const Category& category, const Scope& async_scope) { if (!do_async_events) { return; } trace_event evt; init_event(category, &evt); evt.type = EventType::AsyncStep; evt.scope = &async_scope; evt.event_id = ++current_id; submit_event(&evt); } void end(const Category& category, const Scope& async_scope) { if (!do_async_events) { return; } trace_event evt; init_event(category, &evt); evt.type = EventType::AsyncEnd; evt.scope = &async_scope; evt.event_id = ++current_id; submit_event(&evt); } } namespace counter { void value(const Category& category, float value) { if (!do_counter_events) { return; } trace_event evt; init_event(category, &evt); evt.type = EventType::Counter; evt.value = value; evt.event_id = ++current_id; submit_event(&evt); } } }