mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 22:01:34 +02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
4.8 KiB
C++
122 lines
4.8 KiB
C++
// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See timeline.h.
|
|
#include <stdafx.h>
|
|
#include "timeline.h"
|
|
#include <cpu/guest_time.h>
|
|
#include <kernel/function.h>
|
|
|
|
namespace timeline
|
|
{
|
|
namespace
|
|
{
|
|
struct Event { int64_t ns; uint32_t arg; uint8_t kind; uint8_t thread; };
|
|
constexpr uint32_t kMax = 1 << 20;
|
|
constexpr int64_t kWindowNs = 200'000'000;
|
|
const char* kNames[Count] = { "wait+", "wait-", "present+", "present-", "batch+", "batch-",
|
|
"submit", "complete", "write/ev", "write/done", "swap", "regmem+", "regmem-", "interrupt", "submit+", "waittarget" };
|
|
|
|
std::chrono::steady_clock::time_point s_launch = std::chrono::steady_clock::now();
|
|
int64_t s_fromNs = [] {
|
|
const char* v = std::getenv("NFSMW_TIMELINE");
|
|
return v ? int64_t(std::atof(v) * 1e9) : int64_t(-1);
|
|
}();
|
|
std::unique_ptr<Event[]> s_events;
|
|
std::atomic<uint32_t> s_next{ 0 };
|
|
std::atomic<bool> s_dumped{ false };
|
|
std::atomic<uint8_t> s_threadIds{ 0 };
|
|
|
|
void Dump()
|
|
{
|
|
uint32_t n = std::min(s_next.load(), kMax);
|
|
std::vector<Event> ev(s_events.get(), s_events.get() + n);
|
|
std::sort(ev.begin(), ev.end(), [](auto& a, auto& b) { return a.ns < b.ns; });
|
|
FILE* f = fopen("build/timeline.txt", "w");
|
|
if (!f)
|
|
return;
|
|
// Summary: game wait share, GPU queue occupancy, per-kind counts.
|
|
int64_t t0 = ev.empty() ? 0 : ev.front().ns;
|
|
int64_t waitNs = 0, waitStart = -1, busyNs = 0, busyStart = -1;
|
|
int inFlight = 0;
|
|
uint32_t counts[Count] = {};
|
|
for (auto& e : ev)
|
|
{
|
|
counts[e.kind]++;
|
|
if (e.kind == GameWaitBegin && waitStart < 0) waitStart = e.ns;
|
|
if (e.kind == GameWaitEnd && waitStart >= 0) { waitNs += e.ns - waitStart; waitStart = -1; }
|
|
if (e.kind == Submit && inFlight++ == 0) busyStart = e.ns;
|
|
if (e.kind == Complete && inFlight > 0 && --inFlight == 0) busyNs += e.ns - busyStart;
|
|
}
|
|
double span = ev.empty() ? 1 : double(ev.back().ns - t0);
|
|
fprintf(f, "# %.1f ms: game waiting %.0f%%, GPU queue non-empty %.0f%%\n", span / 1e6, 100.0 * waitNs / span, 100.0 * busyNs / span);
|
|
for (int k = 0; k < Count; k++)
|
|
fprintf(f, "# %-10s %u\n", kNames[k], counts[k]);
|
|
for (auto& e : ev)
|
|
fprintf(f, "%10.3f t%u %-10s %08X\n", (e.ns - t0) / 1e6, e.thread, kNames[e.kind], e.arg);
|
|
fclose(f);
|
|
fprintf(stderr, "[timeline] wrote build/timeline.txt (%u events)\n", n);
|
|
}
|
|
}
|
|
|
|
bool g_enabled = s_fromNs >= 0;
|
|
|
|
void Record(Kind kind, uint32_t arg)
|
|
{
|
|
int64_t ns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - s_launch).count();
|
|
if (ns < s_fromNs)
|
|
return;
|
|
if (ns > s_fromNs + kWindowNs)
|
|
{
|
|
if (!s_dumped.exchange(true))
|
|
{
|
|
g_enabled = false;
|
|
Dump();
|
|
}
|
|
return;
|
|
}
|
|
static std::once_flag once;
|
|
std::call_once(once, [] { s_events.reset(new Event[kMax]); });
|
|
thread_local uint8_t thread = s_threadIds.fetch_add(1);
|
|
uint32_t i = s_next.fetch_add(1);
|
|
if (i < kMax)
|
|
s_events[i] = { ns, arg, uint8_t(kind), thread };
|
|
}
|
|
}
|
|
|
|
// The game's D3D wait for GPU progress, and its Present. Timed in guest
|
|
// time (the host's on desktop), which stands still while the game is
|
|
// suspended: a wait or a frame across a suspension counts only the time the
|
|
// game ran, as the command processor's frame times do.
|
|
namespace
|
|
{
|
|
int64_t NowNs()
|
|
{
|
|
return guesttime::NowNs();
|
|
}
|
|
}
|
|
|
|
extern "C" PPC_FUNC(__imp__sub_82597498);
|
|
PPC_FUNC(sub_82597498)
|
|
{
|
|
timeline::Mark(timeline::GameWaitBegin, ctx.r4.u32);
|
|
timeline::t_waitTargetMarked = false;
|
|
int64_t start = NowNs();
|
|
__imp__sub_82597498(ctx, base);
|
|
timeline::g_gameWaitNs.fetch_add(uint64_t(NowNs() - start), std::memory_order_relaxed);
|
|
timeline::Mark(timeline::GameWaitEnd);
|
|
}
|
|
|
|
extern "C" PPC_FUNC(__imp__sub_82593A58);
|
|
PPC_FUNC(sub_82593A58)
|
|
{
|
|
timeline::Mark(timeline::GamePresentBegin);
|
|
static std::atomic<int64_t> lastPresent{ 0 };
|
|
int64_t start = NowNs();
|
|
if (int64_t previous = lastPresent.exchange(start))
|
|
{
|
|
uint64_t frame = uint64_t(start - previous), longest = timeline::g_gameLongestFrameNs.load(std::memory_order_relaxed);
|
|
while (frame > longest && !timeline::g_gameLongestFrameNs.compare_exchange_weak(longest, frame)) {}
|
|
}
|
|
__imp__sub_82593A58(ctx, base);
|
|
timeline::g_gamePresentNs.fetch_add(uint64_t(NowNs() - start), std::memory_order_relaxed);
|
|
timeline::Mark(timeline::GamePresentEnd);
|
|
}
|