mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 15:29:48 -10:00
This can be used for mark parts of the code which should be unreachable in normal execution. In debug mode, if such a statement is reached an Error dialog is shown which aborts the execution. In release mode the macro is replaced with a compiler specific expression that lets the compiler know that this part is unreachable which allows it to optimize the code better.
422 lines
8.8 KiB
C++
422 lines
8.8 KiB
C++
|
|
#include "tracing/tracing.h"
|
|
#include "graphics/2d.h"
|
|
#include "parse/parselo.h"
|
|
#include "io/timer.h"
|
|
|
|
#include "TraceEventWriter.h"
|
|
#include "MainFrameTimer.h"
|
|
#include "FrameProfiler.h"
|
|
|
|
#include <cinttypes>
|
|
#include <fstream>
|
|
#include <future>
|
|
#include <mutex>
|
|
|
|
// A function for getting the id of the current thread
|
|
#ifdef WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
static int64_t get_tid() {
|
|
return (int64_t) GetCurrentThreadId();
|
|
}
|
|
#elif __LINUX__
|
|
#include <sys/syscall.h>
|
|
static int64_t get_tid() {
|
|
return (int64_t) syscall(SYS_gettid);
|
|
}
|
|
#else
|
|
#include <pthread.h>
|
|
|
|
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 <unistd.h>
|
|
|
|
static int64_t get_pid() {
|
|
return (int64_t) getpid();
|
|
}
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
using namespace tracing;
|
|
|
|
std::unique_ptr<ThreadedTraceEventWriter> traceEventWriter;
|
|
std::unique_ptr<ThreadedMainFrameTimer> mainFrameTimer;
|
|
std::unique_ptr<FrameProfiler> frameProfiler;
|
|
|
|
SCP_vector<int> query_objects;
|
|
// The GPU timestamp queries use an internal free list to reduce the number of graphics API calls
|
|
SCP_queue<int> free_query_objects;
|
|
bool do_gpu_queries = 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();
|
|
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) {
|
|
free_query_objects.push(obj);
|
|
}
|
|
|
|
struct gpu_trace_event {
|
|
trace_event base_evt;
|
|
|
|
int gpu_begin_query = -1;
|
|
int gpu_end_query = -1;
|
|
};
|
|
|
|
SCP_queue<gpu_trace_event> 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!");
|
|
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(CAPABILITY_TIMESTAMP_QUERY);
|
|
|
|
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() {
|
|
while (!gpu_events.empty()) {
|
|
process_events();
|
|
|
|
// Don't do busy waiting...
|
|
os_sleep(5);
|
|
}
|
|
|
|
for (auto query : query_objects) {
|
|
gr_delete_query_object(query);
|
|
}
|
|
query_objects.clear();
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
}
|