Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Source/Core/Core/HW/MMIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Common/CommonTypes.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/MMIOHandlers.h"
#include "Core/HW/MmioObserver.h"

namespace Core
{
Expand Down Expand Up @@ -136,12 +137,16 @@ class Mapping
template <typename Unit>
Unit Read(Core::System& system, u32 addr)
{
if (const auto* obs = GetMmioObservers(); obs && obs->reads)
obs->reads->fetch_add(1, std::memory_order_relaxed);
return GetHandlerForRead<Unit>(addr).Read(system, addr);
}

template <typename Unit>
void Write(Core::System& system, u32 addr, Unit val)
{
if (const auto* obs = GetMmioObservers(); obs && obs->writes)
obs->writes->fetch_add(1, std::memory_order_relaxed);
GetHandlerForWrite<Unit>(addr).Write(system, addr, val);
}

Expand Down
28 changes: 28 additions & 0 deletions Source/Core/Core/HW/MmioObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RecompCore: optional MMIO tallies for an embedder.
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>

#include "Common/CommonTypes.h"

// MMIO is far hotter than the GX paths, so these stay null unless an embedder
// asks for them; an unobserved build pays one predictable branch per access.
struct MmioObservers
{
std::atomic<u64>* reads = nullptr;
std::atomic<u64>* writes = nullptr;
};

inline const MmioObservers* g_mmio_observers = nullptr;

inline void SetMmioObservers(const MmioObservers* observers)
{
g_mmio_observers = observers;
}

inline const MmioObservers* GetMmioObservers()
{
return g_mmio_observers;
}
32 changes: 32 additions & 0 deletions Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore_Run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Core/PowerPC/StaticRecomp/StaticRecompCore.h"
#include "Core/PowerPC/StaticRecomp/StaticRecompObserver.h"

#include <chrono>
#include "Core/System.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Interpreter/Interpreter.h"
Expand Down Expand Up @@ -117,12 +120,18 @@ void StaticRecompCore::Run()
return;
}

const StaticRecompObservers* const observers = GetStaticRecompObservers();
std::atomic<u32>* const observed_pc = observers ? observers->guest_pc : nullptr;
const bool time_guest = observers != nullptr && observers->guest_cpu_ns != nullptr;

while (*state_ptr == CPU::State::Running)
{
core_timing.Advance();
const std::string current_game_id = SConfig::GetInstance().GetGameID();
m_module_active = m_module && (current_game_id.empty() || current_game_id == m_module->game_id);

const auto slice_start = time_guest ? std::chrono::steady_clock::now()
: std::chrono::steady_clock::time_point{};
do
{
// MSR.FP needs no gate here: generated FPU instructions raise the
Expand Down Expand Up @@ -155,6 +164,9 @@ void StaticRecompCore::Run()
if (m_has_rel_modules)
ResolveNativeAddress(runtime_dispatch_address, &linked_dispatch_address, nullptr);
m_guest.pc = linked_dispatch_address;
// Same cadence as the histogram above, but independent of its flag.
if (observed_pc && (m_native_dispatches & 4095u) == 0)
observed_pc->store(m_guest.pc, std::memory_order_relaxed);
m_module->dispatch(&m_guest, linked_dispatch_address);
if (m_has_rel_modules)
m_guest.pc = TranslateRelAddress(m_guest.pc);
Expand Down Expand Up @@ -263,6 +275,26 @@ void StaticRecompCore::Run()
}
}
} while (ppc.downcount > 0 && *state_ptr == CPU::State::Running);

if (observers)
{
// Plain counters the core already keeps; republished per slice.
if (observers->dispatches)
observers->dispatches->store(m_native_dispatches, std::memory_order_relaxed);
if (observers->interpreter_fallbacks)
observers->interpreter_fallbacks->store(m_fallback_steps, std::memory_order_relaxed);
if (observers->exceptions)
observers->exceptions->store(m_native_exceptions, std::memory_order_relaxed);
}
if (time_guest)
{
// One clock pair per timing slice rather than per dispatch.
const auto elapsed = std::chrono::steady_clock::now() - slice_start;
observers->guest_cpu_ns->fetch_add(
static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count()),
std::memory_order_relaxed);
}
}
}

Expand Down
39 changes: 39 additions & 0 deletions Source/Core/Core/PowerPC/StaticRecomp/StaticRecompObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RecompCore: optional profiling observation points for an embedder.
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>

#include "Common/CommonTypes.h"

// The core already samples its own guest PC and knows how long it spends
// executing guest code, but had no way to hand either to an embedder: the
// dispatch histogram in StaticRecompCore was only ever printed to stderr at
// shutdown. An embedder installs these pointers to observe both live.
//
// Both are null unless installed, so an unobserved core pays one predictable
// null check per timing slice and per sampled dispatch -- never per dispatch.
struct StaticRecompObservers
{
// Live guest PC, published at the core's existing dispatch sample cadence.
std::atomic<u32>* guest_pc = nullptr;
// Cumulative nanoseconds spent executing guest code, summed per timing slice.
std::atomic<u64>* guest_cpu_ns = nullptr;
// Cumulative core tallies, republished per timing slice.
std::atomic<u64>* dispatches = nullptr;
std::atomic<u64>* interpreter_fallbacks = nullptr;
std::atomic<u64>* exceptions = nullptr;
};

inline const StaticRecompObservers* g_static_recomp_observers = nullptr;

inline void SetStaticRecompObservers(const StaticRecompObservers* observers)
{
g_static_recomp_observers = observers;
}

inline const StaticRecompObservers* GetStaticRecompObservers()
{
return g_static_recomp_observers;
}
8 changes: 7 additions & 1 deletion Source/Core/VideoCommon/OpcodeDecoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// vertices.

#include "VideoCommon/OpcodeDecoding.h"
#include "VideoCommon/VideoZoneObserver.h"

#include "Common/Assert.h"
#include "Common/Logging/Log.h"
Expand Down Expand Up @@ -261,7 +262,12 @@ u8* RunFifo(DataReader src, u32* cycles)
{
using CallbackT = RunCallback<is_preprocess>;
auto callback = CallbackT{};
u32 size = Run(src.GetPointer(), static_cast<u32>(src.size()), callback);
// One scope per FIFO batch, not per opcode.
u32 size;
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(command_processor_ns));
size = Run(src.GetPointer(), static_cast<u32>(src.size()), callback);
}

if (cycles != nullptr)
*cycles = callback.m_cycles;
Expand Down
13 changes: 13 additions & 0 deletions Source/Core/VideoCommon/ShaderCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/ShaderCache.h"
#include "VideoCommon/VideoZoneObserver.h"

#include <utility>

Expand Down Expand Up @@ -438,6 +439,9 @@ void ShaderCache::CompileMissingPipelines()

std::unique_ptr<AbstractShader> ShaderCache::CompileVertexShader(const VertexShaderUid& uid) const
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(shader_generation_ns));
if (const auto* obs = GetVideoZoneObservers(); obs && obs->shader_compilations)
obs->shader_compilations->fetch_add(1, std::memory_order_relaxed);
const ShaderCode source_code =
GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData(), {});
return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer());
Expand All @@ -446,6 +450,9 @@ std::unique_ptr<AbstractShader> ShaderCache::CompileVertexShader(const VertexSha
std::unique_ptr<AbstractShader>
ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) const
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(shader_generation_ns));
if (const auto* obs = GetVideoZoneObservers(); obs && obs->shader_compilations)
obs->shader_compilations->fetch_add(1, std::memory_order_relaxed);
const ShaderCode source_code =
UberShader::GenVertexShader(m_api_type, m_host_config, uid.GetUidData());
return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(), nullptr,
Expand All @@ -454,6 +461,9 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con

std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(shader_generation_ns));
if (const auto* obs = GetVideoZoneObservers(); obs && obs->shader_compilations)
obs->shader_compilations->fetch_add(1, std::memory_order_relaxed);
const ShaderCode source_code =
GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {});
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer());
Expand All @@ -462,6 +472,9 @@ std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShade
std::unique_ptr<AbstractShader>
ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(shader_generation_ns));
if (const auto* obs = GetVideoZoneObservers(); obs && obs->shader_compilations)
obs->shader_compilations->fetch_add(1, std::memory_order_relaxed);
const ShaderCode source_code =
UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData());
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), nullptr,
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/VideoCommon/TextureCacheBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/VideoZoneObserver.h"

#include <algorithm>
#include <chrono>
Expand Down Expand Up @@ -2132,6 +2133,8 @@ void TextureCacheBase::CopyRenderTargetToTexture(
float gamma, bool clamp_top, bool clamp_bottom,
const CopyFilterCoefficients::Values& filter_coefficients)
{
if (const auto* obs = GetVideoZoneObservers(); obs && obs->efb_copies)
obs->efb_copies->fetch_add(1, std::memory_order_relaxed);
// Emulation methods:
//
// - EFB to RAM:
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/VideoCommon/TextureDecoder_Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "VideoCommon/LookUpTables.h"
#include "VideoCommon/TextureDecoder.h"
#include "VideoCommon/VideoZoneObserver.h"
#include "VideoCommon/TextureDecoder_Util.h"
#include "VideoCommon/sfont.inc"

Expand Down Expand Up @@ -300,6 +301,9 @@ static void TexDecoder_DrawOverlay(u8* dst, int width, int height, TextureFormat
void TexDecoder_Decode(u8* dst, const u8* src, int width, int height, TextureFormat texformat,
const u8* tlut, TLUTFormat tlutfmt)
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(texture_decode_ns));
if (const auto* obs = GetVideoZoneObservers(); obs && obs->texture_decodes)
obs->texture_decodes->fetch_add(1, std::memory_order_relaxed);
_TexDecoder_DecodeImpl((u32*)dst, src, width, height, texformat, tlut, tlutfmt);

if (TexFmt_Overlay_Enable)
Expand Down
9 changes: 9 additions & 0 deletions Source/Core/VideoCommon/VertexLoaderManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VideoZoneObserver.h"

#include <algorithm>
#include <iterator>
Expand Down Expand Up @@ -398,6 +399,14 @@ static bool CanSplit(OpcodeDecoder::Primitive primitive)
template <bool IsPreprocess>
int RunVertices(int vtx_attr_group, OpcodeDecoder::Primitive primitive, int count, const u8* src)
{
const VideoZoneScope zone(VIDEO_ZONE_SINK(vertex_loader_ns));
if (const auto* obs = GetVideoZoneObservers())
{
if (obs->draw_calls)
obs->draw_calls->fetch_add(1, std::memory_order_relaxed);
if (obs->vertices_loaded)
obs->vertices_loaded->fetch_add(static_cast<u64>(count), std::memory_order_relaxed);
}
if (count == 0) [[unlikely]]
return 0;
ASSERT(count > 0);
Expand Down
70 changes: 70 additions & 0 deletions Source/Core/VideoCommon/VideoZoneObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// RecompCore: optional GX timing observation points for an embedder.
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>
#include <chrono>

#include "Common/CommonTypes.h"

// Cumulative nanoseconds per GX subsystem. Null unless an embedder installs
// them, so an unobserved build pays one predictable branch per scope.
struct VideoZoneObservers
{
std::atomic<u64>* command_processor_ns = nullptr;
std::atomic<u64>* vertex_loader_ns = nullptr;
std::atomic<u64>* texture_decode_ns = nullptr;
// Cumulative tallies for the same paths.
std::atomic<u64>* draw_calls = nullptr;
std::atomic<u64>* vertices_loaded = nullptr;
std::atomic<u64>* texture_decodes = nullptr;
std::atomic<u64>* shader_generation_ns = nullptr;
std::atomic<u64>* shader_compilations = nullptr;
std::atomic<u64>* efb_copies = nullptr;
};

inline const VideoZoneObservers* g_video_zone_observers = nullptr;

inline void SetVideoZoneObservers(const VideoZoneObservers* observers)
{
g_video_zone_observers = observers;
}

inline const VideoZoneObservers* GetVideoZoneObservers()
{
return g_video_zone_observers;
}

// Adds its lifetime to one accumulator. A null sink measures nothing.
class VideoZoneScope final
{
public:
explicit VideoZoneScope(std::atomic<u64>* sink)
: m_sink(sink), m_start(sink ? std::chrono::steady_clock::now()
: std::chrono::steady_clock::time_point{})
{
}

~VideoZoneScope()
{
if (m_sink == nullptr)
return;
const auto elapsed = std::chrono::steady_clock::now() - m_start;
m_sink->fetch_add(
static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count()),
std::memory_order_relaxed);
}

VideoZoneScope(const VideoZoneScope&) = delete;
VideoZoneScope& operator=(const VideoZoneScope&) = delete;

private:
std::atomic<u64>* m_sink;
std::chrono::steady_clock::time_point m_start;
};

// Picks one accumulator out of the installed set, or null when unobserved.
#define VIDEO_ZONE_SINK(field) \
(GetVideoZoneObservers() ? GetVideoZoneObservers()->field : nullptr)