Browse Source
Merge pull request #538 from yuriks/perf-stat
Merge pull request #538 from yuriks/perf-stat
Add profiling infrastructure and widgetpull/15/merge
16 changed files with 798 additions and 0 deletions
-
3src/citra_qt/CMakeLists.txt
-
138src/citra_qt/debugger/profiler.cpp
-
50src/citra_qt/debugger/profiler.h
-
33src/citra_qt/debugger/profiler.ui
-
6src/citra_qt/main.cpp
-
2src/citra_qt/main.h
-
4src/common/CMakeLists.txt
-
182src/common/profiler.cpp
-
152src/common/profiler.h
-
108src/common/profiler_reporting.h
-
69src/common/synchronized_wrapper.h
-
19src/common/thread.h
-
8src/core/arm/dyncom/arm_dyncom_interpreter.cpp
-
6src/core/hle/hle.cpp
-
6src/video_core/command_processor.cpp
-
12src/video_core/renderer_opengl/renderer_opengl.cpp
@ -0,0 +1,138 @@ |
|||
// Copyright 2015 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "profiler.h"
|
|||
|
|||
#include "common/profiler_reporting.h"
|
|||
|
|||
using namespace Common::Profiling; |
|||
|
|||
static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) |
|||
{ |
|||
static auto duration_to_float = [](Duration dur) -> float { |
|||
using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; |
|||
return std::chrono::duration_cast<FloatMs>(dur).count(); |
|||
}; |
|||
|
|||
switch (col) { |
|||
case 1: return duration_to_float(duration.avg); |
|||
case 2: return duration_to_float(duration.min); |
|||
case 3: return duration_to_float(duration.max); |
|||
default: return QVariant(); |
|||
} |
|||
} |
|||
|
|||
static const TimingCategoryInfo* GetCategoryInfo(int id) |
|||
{ |
|||
const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); |
|||
if (id >= categories.size()) { |
|||
return nullptr; |
|||
} else { |
|||
return &categories[id]; |
|||
} |
|||
} |
|||
|
|||
ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) |
|||
{ |
|||
updateProfilingInfo(); |
|||
const auto& categories = GetProfilingManager().GetTimingCategoriesInfo(); |
|||
results.time_per_category.resize(categories.size()); |
|||
} |
|||
|
|||
QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const |
|||
{ |
|||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { |
|||
switch (section) { |
|||
case 0: return tr("Category"); |
|||
case 1: return tr("Avg"); |
|||
case 2: return tr("Min"); |
|||
case 3: return tr("Max"); |
|||
} |
|||
} |
|||
|
|||
return QVariant(); |
|||
} |
|||
|
|||
QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const |
|||
{ |
|||
return createIndex(row, column); |
|||
} |
|||
|
|||
QModelIndex ProfilerModel::parent(const QModelIndex& child) const |
|||
{ |
|||
return QModelIndex(); |
|||
} |
|||
|
|||
int ProfilerModel::columnCount(const QModelIndex& parent) const |
|||
{ |
|||
return 4; |
|||
} |
|||
|
|||
int ProfilerModel::rowCount(const QModelIndex& parent) const |
|||
{ |
|||
if (parent.isValid()) { |
|||
return 0; |
|||
} else { |
|||
return results.time_per_category.size() + 2; |
|||
} |
|||
} |
|||
|
|||
QVariant ProfilerModel::data(const QModelIndex& index, int role) const |
|||
{ |
|||
if (role == Qt::DisplayRole) { |
|||
if (index.row() == 0) { |
|||
if (index.column() == 0) { |
|||
return tr("Frame"); |
|||
} else { |
|||
return GetDataForColumn(index.column(), results.frame_time); |
|||
} |
|||
} else if (index.row() == 1) { |
|||
if (index.column() == 0) { |
|||
return tr("Frame (with swapping)"); |
|||
} else { |
|||
return GetDataForColumn(index.column(), results.interframe_time); |
|||
} |
|||
} else { |
|||
if (index.column() == 0) { |
|||
const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2); |
|||
return info != nullptr ? QString(info->name) : QVariant(); |
|||
} else { |
|||
if (index.row() - 2 < results.time_per_category.size()) { |
|||
return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]); |
|||
} else { |
|||
return QVariant(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return QVariant(); |
|||
} |
|||
|
|||
void ProfilerModel::updateProfilingInfo() |
|||
{ |
|||
results = GetTimingResultsAggregator()->GetAggregatedResults(); |
|||
emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); |
|||
} |
|||
|
|||
ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) |
|||
{ |
|||
ui.setupUi(this); |
|||
|
|||
model = new ProfilerModel(this); |
|||
ui.treeView->setModel(model); |
|||
|
|||
connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); |
|||
connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); |
|||
} |
|||
|
|||
void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) |
|||
{ |
|||
if (enable) { |
|||
update_timer.start(100); |
|||
model->updateProfilingInfo(); |
|||
} else { |
|||
update_timer.stop(); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright 2015 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <QAbstractItemModel> |
|||
#include <QDockWidget> |
|||
#include <QTimer> |
|||
#include "ui_profiler.h" |
|||
|
|||
#include "common/profiler_reporting.h" |
|||
|
|||
class ProfilerModel : public QAbstractItemModel |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
ProfilerModel(QObject* parent); |
|||
|
|||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; |
|||
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; |
|||
QModelIndex parent(const QModelIndex& child) const override; |
|||
int columnCount(const QModelIndex& parent = QModelIndex()) const override; |
|||
int rowCount(const QModelIndex& parent = QModelIndex()) const override; |
|||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; |
|||
|
|||
public slots: |
|||
void updateProfilingInfo(); |
|||
|
|||
private: |
|||
Common::Profiling::AggregatedFrameResult results; |
|||
}; |
|||
|
|||
class ProfilerWidget : public QDockWidget |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
ProfilerWidget(QWidget* parent = 0); |
|||
|
|||
private slots: |
|||
void setProfilingInfoUpdateEnabled(bool enable); |
|||
|
|||
private: |
|||
Ui::Profiler ui; |
|||
ProfilerModel* model; |
|||
|
|||
QTimer update_timer; |
|||
}; |
|||
@ -0,0 +1,33 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<ui version="4.0"> |
|||
<class>Profiler</class> |
|||
<widget class="QDockWidget" name="Profiler"> |
|||
<property name="geometry"> |
|||
<rect> |
|||
<x>0</x> |
|||
<y>0</y> |
|||
<width>400</width> |
|||
<height>300</height> |
|||
</rect> |
|||
</property> |
|||
<property name="windowTitle"> |
|||
<string>Profiler</string> |
|||
</property> |
|||
<widget class="QWidget" name="dockWidgetContents"> |
|||
<layout class="QVBoxLayout" name="verticalLayout"> |
|||
<item> |
|||
<widget class="QTreeView" name="treeView"> |
|||
<property name="alternatingRowColors"> |
|||
<bool>true</bool> |
|||
</property> |
|||
<property name="uniformRowHeights"> |
|||
<bool>true</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</widget> |
|||
</widget> |
|||
<resources/> |
|||
<connections/> |
|||
</ui> |
|||
@ -0,0 +1,182 @@ |
|||
// Copyright 2015 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/profiler.h"
|
|||
#include "common/profiler_reporting.h"
|
|||
#include "common/assert.h"
|
|||
|
|||
#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013.
|
|||
#define NOMINMAX
|
|||
#define WIN32_LEAN_AND_MEAN
|
|||
#include <Windows.h> // For QueryPerformanceCounter/Frequency
|
|||
#endif
|
|||
|
|||
namespace Common { |
|||
namespace Profiling { |
|||
|
|||
#if ENABLE_PROFILING
|
|||
thread_local Timer* Timer::current_timer = nullptr; |
|||
#endif
|
|||
|
|||
#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013
|
|||
QPCClock::time_point QPCClock::now() { |
|||
static LARGE_INTEGER freq; |
|||
// Use this dummy local static to ensure this gets initialized once.
|
|||
static BOOL dummy = QueryPerformanceFrequency(&freq); |
|||
|
|||
LARGE_INTEGER ticks; |
|||
QueryPerformanceCounter(&ticks); |
|||
|
|||
// This is prone to overflow when multiplying, which is why I'm using micro instead of nano. The
|
|||
// correct way to approach this would be to just return ticks as a time_point and then subtract
|
|||
// and do this conversion when creating a duration from two time_points, however, as far as I
|
|||
// could tell the C++ requirements for these types are incompatible with this approach.
|
|||
return time_point(duration(ticks.QuadPart * std::micro::den / freq.QuadPart)); |
|||
} |
|||
#endif
|
|||
|
|||
TimingCategory::TimingCategory(const char* name, TimingCategory* parent) |
|||
: accumulated_duration(0) { |
|||
|
|||
ProfilingManager& manager = GetProfilingManager(); |
|||
category_id = manager.RegisterTimingCategory(this, name); |
|||
if (parent != nullptr) |
|||
manager.SetTimingCategoryParent(category_id, parent->category_id); |
|||
} |
|||
|
|||
ProfilingManager::ProfilingManager() |
|||
: last_frame_end(Clock::now()), this_frame_start(Clock::now()) { |
|||
} |
|||
|
|||
unsigned int ProfilingManager::RegisterTimingCategory(TimingCategory* category, const char* name) { |
|||
TimingCategoryInfo info; |
|||
info.category = category; |
|||
info.name = name; |
|||
info.parent = TimingCategoryInfo::NO_PARENT; |
|||
|
|||
unsigned int id = (unsigned int)timing_categories.size(); |
|||
timing_categories.push_back(std::move(info)); |
|||
|
|||
return id; |
|||
} |
|||
|
|||
void ProfilingManager::SetTimingCategoryParent(unsigned int category, unsigned int parent) { |
|||
ASSERT(category < timing_categories.size()); |
|||
ASSERT(parent < timing_categories.size()); |
|||
|
|||
timing_categories[category].parent = parent; |
|||
} |
|||
|
|||
void ProfilingManager::BeginFrame() { |
|||
this_frame_start = Clock::now(); |
|||
} |
|||
|
|||
void ProfilingManager::FinishFrame() { |
|||
Clock::time_point now = Clock::now(); |
|||
|
|||
results.interframe_time = now - last_frame_end; |
|||
results.frame_time = now - this_frame_start; |
|||
|
|||
results.time_per_category.resize(timing_categories.size()); |
|||
for (size_t i = 0; i < timing_categories.size(); ++i) { |
|||
results.time_per_category[i] = timing_categories[i].category->GetAccumulatedTime(); |
|||
} |
|||
|
|||
last_frame_end = now; |
|||
} |
|||
|
|||
TimingResultsAggregator::TimingResultsAggregator(size_t window_size) |
|||
: max_window_size(window_size), window_size(0) { |
|||
interframe_times.resize(window_size, Duration::zero()); |
|||
frame_times.resize(window_size, Duration::zero()); |
|||
} |
|||
|
|||
void TimingResultsAggregator::Clear() { |
|||
window_size = cursor = 0; |
|||
} |
|||
|
|||
void TimingResultsAggregator::SetNumberOfCategories(size_t n) { |
|||
size_t old_size = times_per_category.size(); |
|||
if (n == old_size) |
|||
return; |
|||
|
|||
times_per_category.resize(n); |
|||
|
|||
for (size_t i = old_size; i < n; ++i) { |
|||
times_per_category[i].resize(max_window_size, Duration::zero()); |
|||
} |
|||
} |
|||
|
|||
void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) { |
|||
SetNumberOfCategories(frame_result.time_per_category.size()); |
|||
|
|||
interframe_times[cursor] = frame_result.interframe_time; |
|||
frame_times[cursor] = frame_result.frame_time; |
|||
for (size_t i = 0; i < frame_result.time_per_category.size(); ++i) { |
|||
times_per_category[i][cursor] = frame_result.time_per_category[i]; |
|||
} |
|||
|
|||
++cursor; |
|||
if (cursor == max_window_size) |
|||
cursor = 0; |
|||
if (window_size < max_window_size) |
|||
++window_size; |
|||
} |
|||
|
|||
static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) { |
|||
AggregatedDuration result; |
|||
result.avg = Duration::zero(); |
|||
|
|||
result.min = result.max = (len == 0 ? Duration::zero() : v[0]); |
|||
|
|||
for (size_t i = 1; i < len; ++i) { |
|||
Duration value = v[i]; |
|||
result.avg += value; |
|||
result.min = std::min(result.min, value); |
|||
result.max = std::max(result.max, value); |
|||
} |
|||
if (len != 0) |
|||
result.avg /= len; |
|||
|
|||
return result; |
|||
} |
|||
|
|||
static float tof(Common::Profiling::Duration dur) { |
|||
using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; |
|||
return std::chrono::duration_cast<FloatMs>(dur).count(); |
|||
} |
|||
|
|||
AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const { |
|||
AggregatedFrameResult result; |
|||
|
|||
result.interframe_time = AggregateField(interframe_times, window_size); |
|||
result.frame_time = AggregateField(frame_times, window_size); |
|||
|
|||
if (result.interframe_time.avg != Duration::zero()) { |
|||
result.fps = 1000.0f / tof(result.interframe_time.avg); |
|||
} else { |
|||
result.fps = 0.0f; |
|||
} |
|||
|
|||
result.time_per_category.resize(times_per_category.size()); |
|||
for (size_t i = 0; i < times_per_category.size(); ++i) { |
|||
result.time_per_category[i] = AggregateField(times_per_category[i], window_size); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
ProfilingManager& GetProfilingManager() { |
|||
// Takes advantage of "magic" static initialization for race-free initialization.
|
|||
static ProfilingManager manager; |
|||
return manager; |
|||
} |
|||
|
|||
SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() { |
|||
static SynchronizedWrapper<TimingResultsAggregator> aggregator(30); |
|||
return SynchronizedRef<TimingResultsAggregator>(aggregator); |
|||
} |
|||
|
|||
} // namespace Profiling
|
|||
} // namespace Common
|
|||
@ -0,0 +1,152 @@ |
|||
// Copyright 2015 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include <chrono> |
|||
|
|||
#include "common/assert.h" |
|||
#include "common/thread.h" |
|||
|
|||
namespace Common { |
|||
namespace Profiling { |
|||
|
|||
// If this is defined to 0, it turns all Timers into no-ops. |
|||
#ifndef ENABLE_PROFILING |
|||
#define ENABLE_PROFILING 1 |
|||
#endif |
|||
|
|||
#if defined(_MSC_VER) && _MSC_VER <= 1800 // MSVC 2013 |
|||
// MSVC up to 2013 doesn't use QueryPerformanceCounter for high_resolution_clock, so it has bad |
|||
// precision. We manually implement a clock based on QPC to get good results. |
|||
|
|||
struct QPCClock { |
|||
using duration = std::chrono::microseconds; |
|||
using time_point = std::chrono::time_point<QPCClock>; |
|||
using rep = duration::rep; |
|||
using period = duration::period; |
|||
static const bool is_steady = false; |
|||
|
|||
static time_point now(); |
|||
}; |
|||
|
|||
using Clock = QPCClock; |
|||
#else |
|||
using Clock = std::chrono::high_resolution_clock; |
|||
#endif |
|||
|
|||
using Duration = Clock::duration; |
|||
|
|||
/** |
|||
* Represents a timing category that measured time can be accounted towards. Should be declared as a |
|||
* global variable and passed to Timers. |
|||
*/ |
|||
class TimingCategory final { |
|||
public: |
|||
TimingCategory(const char* name, TimingCategory* parent = nullptr); |
|||
|
|||
unsigned int GetCategoryId() const { |
|||
return category_id; |
|||
} |
|||
|
|||
/// Adds some time to this category. Can safely be called from multiple threads at the same time. |
|||
void AddTime(Duration amount) { |
|||
std::atomic_fetch_add_explicit( |
|||
&accumulated_duration, amount.count(), |
|||
std::memory_order_relaxed); |
|||
} |
|||
|
|||
/** |
|||
* Atomically retrieves the accumulated measured time for this category and resets the counter |
|||
* to zero. Can be safely called concurrently with AddTime. |
|||
*/ |
|||
Duration GetAccumulatedTime() { |
|||
return Duration(std::atomic_exchange_explicit( |
|||
&accumulated_duration, (Duration::rep)0, |
|||
std::memory_order_relaxed)); |
|||
} |
|||
|
|||
private: |
|||
unsigned int category_id; |
|||
std::atomic<Duration::rep> accumulated_duration; |
|||
}; |
|||
|
|||
/** |
|||
* Measures time elapsed between a call to Start and a call to Stop and attributes it to the given |
|||
* TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be |
|||
* appropriately paired. |
|||
* |
|||
* When a Timer is started, it automatically pauses a previously running timer on the same thread, |
|||
* which is resumed when it is stopped. As such, no special action needs to be taken to avoid |
|||
* double-accounting of time on two categories. |
|||
*/ |
|||
class Timer { |
|||
public: |
|||
Timer(TimingCategory& category) : category(category) { |
|||
} |
|||
|
|||
void Start() { |
|||
#if ENABLE_PROFILING |
|||
ASSERT(!running); |
|||
previous_timer = current_timer; |
|||
current_timer = this; |
|||
if (previous_timer != nullptr) |
|||
previous_timer->StopTiming(); |
|||
|
|||
StartTiming(); |
|||
#endif |
|||
} |
|||
|
|||
void Stop() { |
|||
#if ENABLE_PROFILING |
|||
ASSERT(running); |
|||
StopTiming(); |
|||
|
|||
if (previous_timer != nullptr) |
|||
previous_timer->StartTiming(); |
|||
current_timer = previous_timer; |
|||
#endif |
|||
} |
|||
|
|||
private: |
|||
#if ENABLE_PROFILING |
|||
void StartTiming() { |
|||
start = Clock::now(); |
|||
running = true; |
|||
} |
|||
|
|||
void StopTiming() { |
|||
auto duration = Clock::now() - start; |
|||
running = false; |
|||
category.AddTime(std::chrono::duration_cast<Duration>(duration)); |
|||
} |
|||
|
|||
Clock::time_point start; |
|||
bool running = false; |
|||
|
|||
Timer* previous_timer; |
|||
static thread_local Timer* current_timer; |
|||
#endif |
|||
|
|||
TimingCategory& category; |
|||
}; |
|||
|
|||
/** |
|||
* A Timer that automatically starts timing when created and stops at the end of the scope. Should |
|||
* be used in the majority of cases. |
|||
*/ |
|||
class ScopeTimer : public Timer { |
|||
public: |
|||
ScopeTimer(TimingCategory& category) : Timer(category) { |
|||
Start(); |
|||
} |
|||
|
|||
~ScopeTimer() { |
|||
Stop(); |
|||
} |
|||
}; |
|||
|
|||
} // namespace Profiling |
|||
} // namespace Common |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright 2015 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <chrono> |
|||
#include <mutex> |
|||
#include <utility> |
|||
#include <vector> |
|||
|
|||
#include "common/profiler.h" |
|||
#include "common/synchronized_wrapper.h" |
|||
|
|||
namespace Common { |
|||
namespace Profiling { |
|||
|
|||
struct TimingCategoryInfo { |
|||
static const unsigned int NO_PARENT = -1; |
|||
|
|||
TimingCategory* category; |
|||
const char* name; |
|||
unsigned int parent; |
|||
}; |
|||
|
|||
struct ProfilingFrameResult { |
|||
/// Time since the last delivered frame |
|||
Duration interframe_time; |
|||
|
|||
/// Time spent processing a frame, excluding VSync |
|||
Duration frame_time; |
|||
|
|||
/// Total amount of time spent inside each category in this frame. Indexed by the category id |
|||
std::vector<Duration> time_per_category; |
|||
}; |
|||
|
|||
class ProfilingManager final { |
|||
public: |
|||
ProfilingManager(); |
|||
|
|||
unsigned int RegisterTimingCategory(TimingCategory* category, const char* name); |
|||
void SetTimingCategoryParent(unsigned int category, unsigned int parent); |
|||
|
|||
const std::vector<TimingCategoryInfo>& GetTimingCategoriesInfo() const { |
|||
return timing_categories; |
|||
} |
|||
|
|||
/// This should be called after swapping screen buffers. |
|||
void BeginFrame(); |
|||
/// This should be called before swapping screen buffers. |
|||
void FinishFrame(); |
|||
|
|||
/// Get the timing results from the previous frame. This is updated when you call FinishFrame(). |
|||
const ProfilingFrameResult& GetPreviousFrameResults() const { |
|||
return results; |
|||
} |
|||
|
|||
private: |
|||
std::vector<TimingCategoryInfo> timing_categories; |
|||
Clock::time_point last_frame_end; |
|||
Clock::time_point this_frame_start; |
|||
|
|||
ProfilingFrameResult results; |
|||
}; |
|||
|
|||
struct AggregatedDuration { |
|||
Duration avg, min, max; |
|||
}; |
|||
|
|||
struct AggregatedFrameResult { |
|||
/// Time since the last delivered frame |
|||
AggregatedDuration interframe_time; |
|||
|
|||
/// Time spent processing a frame, excluding VSync |
|||
AggregatedDuration frame_time; |
|||
|
|||
float fps; |
|||
|
|||
/// Total amount of time spent inside each category in this frame. Indexed by the category id |
|||
std::vector<AggregatedDuration> time_per_category; |
|||
}; |
|||
|
|||
class TimingResultsAggregator final { |
|||
public: |
|||
TimingResultsAggregator(size_t window_size); |
|||
|
|||
void Clear(); |
|||
void SetNumberOfCategories(size_t n); |
|||
|
|||
void AddFrame(const ProfilingFrameResult& frame_result); |
|||
|
|||
AggregatedFrameResult GetAggregatedResults() const; |
|||
|
|||
size_t max_window_size; |
|||
size_t window_size; |
|||
size_t cursor; |
|||
|
|||
std::vector<Duration> interframe_times; |
|||
std::vector<Duration> frame_times; |
|||
std::vector<std::vector<Duration>> times_per_category; |
|||
}; |
|||
|
|||
ProfilingManager& GetProfilingManager(); |
|||
SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator(); |
|||
|
|||
} // namespace Profiling |
|||
} // namespace Common |
|||
@ -0,0 +1,69 @@ |
|||
// Copyright 2015 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <mutex> |
|||
|
|||
namespace Common { |
|||
|
|||
/** |
|||
* Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no |
|||
* one forgets to lock a mutex before acessing an object. To access the wrapped object construct a |
|||
* SyncronizedRef on this wrapper. Inspired by Rust's Mutex type (http://doc.rust-lang.org/std/sync/struct.Mutex.html). |
|||
*/ |
|||
template <typename T> |
|||
class SynchronizedWrapper { |
|||
public: |
|||
template <typename... Args> |
|||
SynchronizedWrapper(Args&&... args) : |
|||
data(std::forward<Args>(args)...) { |
|||
} |
|||
|
|||
private: |
|||
template <typename U> |
|||
friend class SynchronizedRef; |
|||
|
|||
std::mutex mutex; |
|||
T data; |
|||
}; |
|||
|
|||
/** |
|||
* Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This |
|||
* greatly reduces the chance that someone will access the wrapped resource without locking the |
|||
* mutex. |
|||
*/ |
|||
template <typename T> |
|||
class SynchronizedRef { |
|||
public: |
|||
SynchronizedRef(SynchronizedWrapper<T>& wrapper) : wrapper(&wrapper) { |
|||
wrapper.mutex.lock(); |
|||
} |
|||
|
|||
SynchronizedRef(SynchronizedRef&) = delete; |
|||
SynchronizedRef(SynchronizedRef&& o) : wrapper(o.wrapper) { |
|||
o.wrapper = nullptr; |
|||
} |
|||
|
|||
~SynchronizedRef() { |
|||
if (wrapper) |
|||
wrapper->mutex.unlock(); |
|||
} |
|||
|
|||
SynchronizedRef& operator=(SynchronizedRef&) = delete; |
|||
SynchronizedRef& operator=(SynchronizedRef&& o) { |
|||
std::swap(wrapper, o.wrapper); |
|||
} |
|||
|
|||
T& operator*() { return wrapper->data; } |
|||
const T& operator*() const { return wrapper->data; } |
|||
|
|||
T* operator->() { return &wrapper->data; } |
|||
const T* operator->() const { return &wrapper->data; } |
|||
|
|||
private: |
|||
SynchronizedWrapper<T>* wrapper; |
|||
}; |
|||
|
|||
} // namespace Common |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue