Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
295 lines
8.5 KiB
C++
295 lines
8.5 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "Common/PerformanceTrace.h"
|
|
|
|
#include <atomic>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#include <TraceLoggingProvider.h>
|
|
#pragma comment(lib, "advapi32.lib")
|
|
TRACELOGGING_DEFINE_PROVIDER(s_dolphin_perf, "Dolphin.Performance",
|
|
(0x54ee9231, 0x28be, 0x4ab0, 0x98, 0x1e, 0xb2, 0x6f, 0x97, 0x9c, 0x56,
|
|
0xa4));
|
|
#endif
|
|
|
|
namespace Common::PerformanceTrace
|
|
{
|
|
namespace
|
|
{
|
|
constexpr u32 SCHEMA = 1;
|
|
constexpr u32 SAMPLE_MASK = 1023;
|
|
struct ThreadState
|
|
{
|
|
bool sampling = false;
|
|
u32 rng = 0x9e3779b9;
|
|
u64 roots = 0;
|
|
u64 selected = 0;
|
|
u64 last_snapshot = 0;
|
|
ScopeStack stack;
|
|
std::array<u64, static_cast<size_t>(Counter::Count)> counters{};
|
|
};
|
|
thread_local ThreadState s_thread;
|
|
|
|
#ifdef _WIN32
|
|
struct Registration
|
|
{
|
|
Registration() : status(TraceLoggingRegister(s_dolphin_perf)) {}
|
|
~Registration()
|
|
{
|
|
if (status == ERROR_SUCCESS)
|
|
TraceLoggingUnregister(s_dolphin_perf);
|
|
}
|
|
ULONG status;
|
|
};
|
|
#endif
|
|
} // namespace
|
|
|
|
bool ScopeStack::Begin(Region region, u64 now)
|
|
{
|
|
if (m_depth == m_stack.size() || static_cast<size_t>(region) >= m_timings.size())
|
|
{
|
|
++m_errors;
|
|
return false;
|
|
}
|
|
m_stack[m_depth++] = {region, now, 0};
|
|
return true;
|
|
}
|
|
|
|
bool ScopeStack::End(u64 now)
|
|
{
|
|
if (m_depth == 0)
|
|
{
|
|
++m_errors;
|
|
return false;
|
|
}
|
|
const Entry entry = m_stack[--m_depth];
|
|
if (now < entry.start || now - entry.start < entry.children)
|
|
{
|
|
++m_errors;
|
|
return false;
|
|
}
|
|
const u64 elapsed = now - entry.start;
|
|
auto& timing = m_timings[static_cast<size_t>(entry.region)];
|
|
++timing.calls;
|
|
timing.inclusive += elapsed;
|
|
timing.exclusive += elapsed - entry.children;
|
|
if (m_depth != 0)
|
|
m_stack[m_depth - 1].children += elapsed;
|
|
return true;
|
|
}
|
|
|
|
void ScopeStack::Reset()
|
|
{
|
|
m_depth = 0;
|
|
m_errors = 0;
|
|
m_timings = {};
|
|
}
|
|
|
|
bool Requested()
|
|
{
|
|
#ifdef _WIN32
|
|
static const bool requested = [] {
|
|
const char* value = std::getenv("DOLPHIN_PERF_TRACE");
|
|
return value && std::strcmp(value, "1") == 0;
|
|
}();
|
|
return requested;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Enabled()
|
|
{
|
|
#ifdef _WIN32
|
|
if (!Requested())
|
|
return false;
|
|
static Registration registration;
|
|
return registration.status == ERROR_SUCCESS && TraceLoggingProviderEnabled(s_dolphin_perf, 0, 0);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u64 Now()
|
|
{
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER value;
|
|
QueryPerformanceCounter(&value);
|
|
return static_cast<u64>(value.QuadPart);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
u64 Frequency()
|
|
{
|
|
#ifdef _WIN32
|
|
static const u64 frequency = [] {
|
|
LARGE_INTEGER value;
|
|
QueryPerformanceFrequency(&value);
|
|
return static_cast<u64>(value.QuadPart);
|
|
}();
|
|
return frequency;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void Add(Counter counter, u64 value)
|
|
{
|
|
if (Requested() && static_cast<size_t>(counter) < s_thread.counters.size())
|
|
s_thread.counters[static_cast<size_t>(counter)] += value;
|
|
}
|
|
|
|
u64 NewGeneration()
|
|
{
|
|
static std::atomic<u64> generation{0};
|
|
return Requested() ? generation.fetch_add(1, std::memory_order_relaxed) + 1 : 0;
|
|
}
|
|
|
|
SampleRoot::SampleRoot(bool force_for_test)
|
|
{
|
|
m_denominator = force_for_test ? 1 : SAMPLE_MASK + 1;
|
|
if (!Enabled() || s_thread.sampling)
|
|
return;
|
|
++s_thread.roots;
|
|
auto& x = s_thread.rng;
|
|
x ^= x << 13;
|
|
x ^= x >> 17;
|
|
x ^= x << 5;
|
|
if (!force_for_test && (x & SAMPLE_MASK) != 0)
|
|
return;
|
|
++s_thread.selected;
|
|
s_thread.stack.Reset();
|
|
m_start = Now();
|
|
m_active = s_thread.stack.Begin(Region::Slice, m_start);
|
|
s_thread.sampling = m_active;
|
|
}
|
|
|
|
SampleRoot::~SampleRoot()
|
|
{
|
|
if (!m_active)
|
|
return;
|
|
const u64 end = Now();
|
|
s_thread.stack.End(end);
|
|
s_thread.sampling = false;
|
|
#ifdef _WIN32
|
|
// Sampled wall times, NOT exact total CPU times. Nested regions are exclusive here.
|
|
for (size_t i = 0; i < s_thread.stack.Timings().size(); ++i)
|
|
{
|
|
const auto& timing = s_thread.stack.Timings()[i];
|
|
if (timing.calls == 0)
|
|
continue;
|
|
TraceLoggingWrite(s_dolphin_perf, "ScopeSample", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(m_start, "BeginQPC"), TraceLoggingUInt64(end, "EndQPC"),
|
|
TraceLoggingUInt64(s_thread.selected, "Sample"),
|
|
TraceLoggingUInt32(m_denominator, "SamplingDenominator"),
|
|
TraceLoggingUInt32(static_cast<u32>(i), "Region"),
|
|
TraceLoggingUInt64(timing.calls, "Calls"),
|
|
TraceLoggingUInt64(timing.inclusive, "InclusiveQPC"),
|
|
TraceLoggingUInt64(timing.exclusive, "ExclusiveQPC"),
|
|
TraceLoggingUInt64(s_thread.stack.Errors(), "AccountingErrors"));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Scope::Scope(Region region)
|
|
{
|
|
if (s_thread.sampling)
|
|
m_active = s_thread.stack.Begin(region, Now());
|
|
}
|
|
Scope::~Scope()
|
|
{
|
|
if (m_active)
|
|
s_thread.stack.End(Now());
|
|
}
|
|
|
|
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
|
|
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting)
|
|
{
|
|
if (!Enabled())
|
|
return;
|
|
const u64 now = Now();
|
|
if (now - s_thread.last_snapshot < Frequency())
|
|
return;
|
|
s_thread.last_snapshot = now;
|
|
#ifdef _WIN32
|
|
TraceLoggingWrite(
|
|
s_dolphin_perf, "Snapshot", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(now, "QPC"), TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
|
TraceLoggingUInt64(core_id, "CoreId"), TraceLoggingUInt64(instructions, "Instructions"),
|
|
TraceLoggingUInt64(native_instructions, "NativeInstructions"),
|
|
TraceLoggingUInt64(slow_reads, "SlowReads"), TraceLoggingUInt64(slow_writes, "SlowWrites"),
|
|
TraceLoggingUInt64(compiles, "Compiles"), TraceLoggingUInt64(fallbacks, "Fallbacks"),
|
|
TraceLoggingUInt32(pc, "GuestPC"), TraceLoggingBool(waiting, "Waiting"),
|
|
TraceLoggingUInt64(s_thread.roots, "Roots"),
|
|
TraceLoggingUInt64(s_thread.selected, "SelectedRoots"));
|
|
for (size_t i = 0; i < s_thread.counters.size(); ++i)
|
|
{
|
|
TraceLoggingWrite(s_dolphin_perf, "Counter", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(now, "QPC"),
|
|
TraceLoggingUInt32(static_cast<u32>(i), "Counter"),
|
|
TraceLoggingUInt64(s_thread.counters[i], "Value"));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
|
|
u32 guest_pc, u64 identity, const char* kind)
|
|
{
|
|
if (!begin || reinterpret_cast<uintptr_t>(end) <= reinterpret_cast<uintptr_t>(begin) ||
|
|
!Enabled())
|
|
return;
|
|
#ifdef _WIN32
|
|
TraceLoggingWrite(s_dolphin_perf, "CodeMap", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingString(domain, "Domain"),
|
|
TraceLoggingPointer(owner, "Owner"), TraceLoggingPointer(begin, "Begin"),
|
|
TraceLoggingPointer(end, "End"), TraceLoggingUInt32(guest_pc, "GuestPC"),
|
|
TraceLoggingUInt64(identity, "Identity"), TraceLoggingString(kind, "Kind"));
|
|
#endif
|
|
}
|
|
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end)
|
|
{
|
|
CodeMap(domain, owner, begin, end, 0, 0, "invalidate");
|
|
}
|
|
void Frame(const char* kind, s64 guest_ticks, u32 guest_frequency)
|
|
{
|
|
if (!Enabled())
|
|
return;
|
|
#ifdef _WIN32
|
|
TraceLoggingWrite(s_dolphin_perf, "Frame", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(Now(), "QPC"),
|
|
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
|
TraceLoggingString(kind, "Kind"), TraceLoggingInt64(guest_ticks, "GuestTicks"),
|
|
TraceLoggingUInt32(guest_frequency, "GuestFrequency"));
|
|
#endif
|
|
}
|
|
void Marker(const char* name)
|
|
{
|
|
if (!Enabled())
|
|
return;
|
|
#ifdef _WIN32
|
|
TraceLoggingWrite(s_dolphin_perf, "Marker", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(Now(), "QPC"),
|
|
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
|
TraceLoggingString(name, "Name"));
|
|
#endif
|
|
}
|
|
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak)
|
|
{
|
|
if (!Enabled())
|
|
return;
|
|
#ifdef _WIN32
|
|
TraceLoggingWrite(s_dolphin_perf, "USBQueue", TraceLoggingUInt32(SCHEMA, "Schema"),
|
|
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingPointer(owner, "Owner"),
|
|
TraceLoggingUInt64(frames, "Frames"), TraceLoggingUInt64(bytes, "Bytes"),
|
|
TraceLoggingUInt64(oldest_qpc, "OldestQPC"),
|
|
TraceLoggingUInt64(peak, "PeakFrames"));
|
|
#endif
|
|
}
|
|
} // namespace Common::PerformanceTrace
|