summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Yuri Kunde Schlesner2015-02-05 14:53:25 -0200
committerGravatar Yuri Kunde Schlesner2015-03-01 21:47:13 -0300
commitcd1fbfcf1b70e365d81480ec0f56db19ed02454f (patch)
treeb220b105d1b8016bb258047683bf2d03795c8881 /src
parentMerge pull request #616 from archshift/5551 (diff)
downloadyuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.tar.gz
yuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.tar.xz
yuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.zip
Add profiling infrastructure and widget
Diffstat (limited to 'src')
-rw-r--r--src/citra_qt/CMakeLists.txt3
-rw-r--r--src/citra_qt/debugger/profiler.cpp138
-rw-r--r--src/citra_qt/debugger/profiler.h50
-rw-r--r--src/citra_qt/debugger/profiler.ui33
-rw-r--r--src/citra_qt/main.cpp6
-rw-r--r--src/citra_qt/main.h2
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/profiler.cpp159
-rw-r--r--src/common/profiler.h134
-rw-r--r--src/common/profiler_reporting.h108
-rw-r--r--src/common/synchronized_wrapper.h69
-rw-r--r--src/common/thread.h19
-rw-r--r--src/core/arm/dyncom/arm_dyncom_interpreter.cpp8
-rw-r--r--src/core/hle/hle.cpp6
-rw-r--r--src/video_core/command_processor.cpp6
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp12
16 files changed, 757 insertions, 0 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 586bc84b0..a1ad00f57 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SRCS
13 debugger/graphics_cmdlists.cpp 13 debugger/graphics_cmdlists.cpp
14 debugger/graphics_framebuffer.cpp 14 debugger/graphics_framebuffer.cpp
15 debugger/graphics_vertex_shader.cpp 15 debugger/graphics_vertex_shader.cpp
16 debugger/profiler.cpp
16 debugger/ramview.cpp 17 debugger/ramview.cpp
17 debugger/registers.cpp 18 debugger/registers.cpp
18 util/spinbox.cpp 19 util/spinbox.cpp
@@ -35,6 +36,7 @@ set(HEADERS
35 debugger/graphics_cmdlists.h 36 debugger/graphics_cmdlists.h
36 debugger/graphics_framebuffer.h 37 debugger/graphics_framebuffer.h
37 debugger/graphics_vertex_shader.h 38 debugger/graphics_vertex_shader.h
39 debugger/profiler.h
38 debugger/ramview.h 40 debugger/ramview.h
39 debugger/registers.h 41 debugger/registers.h
40 util/spinbox.h 42 util/spinbox.h
@@ -48,6 +50,7 @@ set(UIS
48 config/controller_config.ui 50 config/controller_config.ui
49 debugger/callstack.ui 51 debugger/callstack.ui
50 debugger/disassembler.ui 52 debugger/disassembler.ui
53 debugger/profiler.ui
51 debugger/registers.ui 54 debugger/registers.ui
52 hotkeys.ui 55 hotkeys.ui
53 main.ui 56 main.ui
diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp
new file mode 100644
index 000000000..ae0568b6a
--- /dev/null
+++ b/src/citra_qt/debugger/profiler.cpp
@@ -0,0 +1,138 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "profiler.h"
6
7#include "common/profiler_reporting.h"
8
9using namespace Common::Profiling;
10
11static QVariant GetDataForColumn(int col, const AggregatedDuration& duration)
12{
13 static auto duration_to_float = [](Duration dur) -> float {
14 using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
15 return std::chrono::duration_cast<FloatMs>(dur).count();
16 };
17
18 switch (col) {
19 case 1: return duration_to_float(duration.avg);
20 case 2: return duration_to_float(duration.min);
21 case 3: return duration_to_float(duration.max);
22 default: return QVariant();
23 }
24}
25
26static const TimingCategoryInfo* GetCategoryInfo(int id)
27{
28 const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
29 if (id >= categories.size()) {
30 return nullptr;
31 } else {
32 return &categories[id];
33 }
34}
35
36ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent)
37{
38 updateProfilingInfo();
39 const auto& categories = GetProfilingManager().GetTimingCategoriesInfo();
40 results.time_per_category.resize(categories.size());
41}
42
43QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const
44{
45 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
46 switch (section) {
47 case 0: return tr("Category");
48 case 1: return tr("Avg");
49 case 2: return tr("Min");
50 case 3: return tr("Max");
51 }
52 }
53
54 return QVariant();
55}
56
57QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const
58{
59 return createIndex(row, column);
60}
61
62QModelIndex ProfilerModel::parent(const QModelIndex& child) const
63{
64 return QModelIndex();
65}
66
67int ProfilerModel::columnCount(const QModelIndex& parent) const
68{
69 return 4;
70}
71
72int ProfilerModel::rowCount(const QModelIndex& parent) const
73{
74 if (parent.isValid()) {
75 return 0;
76 } else {
77 return results.time_per_category.size() + 2;
78 }
79}
80
81QVariant ProfilerModel::data(const QModelIndex& index, int role) const
82{
83 if (role == Qt::DisplayRole) {
84 if (index.row() == 0) {
85 if (index.column() == 0) {
86 return tr("Frame");
87 } else {
88 return GetDataForColumn(index.column(), results.frame_time);
89 }
90 } else if (index.row() == 1) {
91 if (index.column() == 0) {
92 return tr("Frame (with swapping)");
93 } else {
94 return GetDataForColumn(index.column(), results.interframe_time);
95 }
96 } else {
97 if (index.column() == 0) {
98 const TimingCategoryInfo* info = GetCategoryInfo(index.row() - 2);
99 return info != nullptr ? QString(info->name) : QVariant();
100 } else {
101 if (index.row() - 2 < results.time_per_category.size()) {
102 return GetDataForColumn(index.column(), results.time_per_category[index.row() - 2]);
103 } else {
104 return QVariant();
105 }
106 }
107 }
108 }
109
110 return QVariant();
111}
112
113void ProfilerModel::updateProfilingInfo()
114{
115 results = GetTimingResultsAggregator()->GetAggregatedResults();
116 emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3));
117}
118
119ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent)
120{
121 ui.setupUi(this);
122
123 model = new ProfilerModel(this);
124 ui.treeView->setModel(model);
125
126 connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool)));
127 connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo()));
128}
129
130void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable)
131{
132 if (enable) {
133 update_timer.start(100);
134 model->updateProfilingInfo();
135 } else {
136 update_timer.stop();
137 }
138}
diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h
new file mode 100644
index 000000000..a6d87aa0f
--- /dev/null
+++ b/src/citra_qt/debugger/profiler.h
@@ -0,0 +1,50 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QAbstractItemModel>
8#include <QDockWidget>
9#include <QTimer>
10#include "ui_profiler.h"
11
12#include "common/profiler_reporting.h"
13
14class ProfilerModel : public QAbstractItemModel
15{
16 Q_OBJECT
17
18public:
19 ProfilerModel(QObject* parent);
20
21 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
22 QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
23 QModelIndex parent(const QModelIndex& child) const override;
24 int columnCount(const QModelIndex& parent = QModelIndex()) const override;
25 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
26 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
27
28public slots:
29 void updateProfilingInfo();
30
31private:
32 Common::Profiling::AggregatedFrameResult results;
33};
34
35class ProfilerWidget : public QDockWidget
36{
37 Q_OBJECT
38
39public:
40 ProfilerWidget(QWidget* parent = 0);
41
42private slots:
43 void setProfilingInfoUpdateEnabled(bool enable);
44
45private:
46 Ui::Profiler ui;
47 ProfilerModel* model;
48
49 QTimer update_timer;
50};
diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui
new file mode 100644
index 000000000..d3c9a9a1f
--- /dev/null
+++ b/src/citra_qt/debugger/profiler.ui
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>Profiler</class>
4 <widget class="QDockWidget" name="Profiler">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>300</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Profiler</string>
15 </property>
16 <widget class="QWidget" name="dockWidgetContents">
17 <layout class="QVBoxLayout" name="verticalLayout">
18 <item>
19 <widget class="QTreeView" name="treeView">
20 <property name="alternatingRowColors">
21 <bool>true</bool>
22 </property>
23 <property name="uniformRowHeights">
24 <bool>true</bool>
25 </property>
26 </widget>
27 </item>
28 </layout>
29 </widget>
30 </widget>
31 <resources/>
32 <connections/>
33</ui>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 881c7d337..e3db3c20a 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -35,6 +35,7 @@
35#include "debugger/graphics_cmdlists.h" 35#include "debugger/graphics_cmdlists.h"
36#include "debugger/graphics_framebuffer.h" 36#include "debugger/graphics_framebuffer.h"
37#include "debugger/graphics_vertex_shader.h" 37#include "debugger/graphics_vertex_shader.h"
38#include "debugger/profiler.h"
38 39
39#include "core/settings.h" 40#include "core/settings.h"
40#include "core/system.h" 41#include "core/system.h"
@@ -57,6 +58,10 @@ GMainWindow::GMainWindow()
57 render_window = new GRenderWindow; 58 render_window = new GRenderWindow;
58 render_window->hide(); 59 render_window->hide();
59 60
61 profilerWidget = new ProfilerWidget(this);
62 addDockWidget(Qt::BottomDockWidgetArea, profilerWidget);
63 profilerWidget->hide();
64
60 disasmWidget = new DisassemblerWidget(this, render_window->GetEmuThread()); 65 disasmWidget = new DisassemblerWidget(this, render_window->GetEmuThread());
61 addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); 66 addDockWidget(Qt::BottomDockWidgetArea, disasmWidget);
62 disasmWidget->hide(); 67 disasmWidget->hide();
@@ -90,6 +95,7 @@ GMainWindow::GMainWindow()
90 graphicsVertexShaderWidget->hide(); 95 graphicsVertexShaderWidget->hide();
91 96
92 QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); 97 QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
98 debug_menu->addAction(profilerWidget->toggleViewAction());
93 debug_menu->addAction(disasmWidget->toggleViewAction()); 99 debug_menu->addAction(disasmWidget->toggleViewAction());
94 debug_menu->addAction(registersWidget->toggleViewAction()); 100 debug_menu->addAction(registersWidget->toggleViewAction());
95 debug_menu->addAction(callstackWidget->toggleViewAction()); 101 debug_menu->addAction(callstackWidget->toggleViewAction());
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index dd53489dd..9b57c5772 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -11,6 +11,7 @@
11 11
12class GImageInfo; 12class GImageInfo;
13class GRenderWindow; 13class GRenderWindow;
14class ProfilerWidget;
14class DisassemblerWidget; 15class DisassemblerWidget;
15class RegistersWidget; 16class RegistersWidget;
16class CallstackWidget; 17class CallstackWidget;
@@ -54,6 +55,7 @@ private:
54 55
55 GRenderWindow* render_window; 56 GRenderWindow* render_window;
56 57
58 ProfilerWidget* profilerWidget;
57 DisassemblerWidget* disasmWidget; 59 DisassemblerWidget* disasmWidget;
58 RegistersWidget* registersWidget; 60 RegistersWidget* registersWidget;
59 CallstackWidget* callstackWidget; 61 CallstackWidget* callstackWidget;
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index b05c35546..daa2d59de 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SRCS
14 mem_arena.cpp 14 mem_arena.cpp
15 memory_util.cpp 15 memory_util.cpp
16 misc.cpp 16 misc.cpp
17 profiler.cpp
17 scm_rev.cpp 18 scm_rev.cpp
18 string_util.cpp 19 string_util.cpp
19 symbols.cpp 20 symbols.cpp
@@ -48,11 +49,14 @@ set(HEADERS
48 mem_arena.h 49 mem_arena.h
49 memory_util.h 50 memory_util.h
50 platform.h 51 platform.h
52 profiler.h
53 profiler_reporting.h
51 scm_rev.h 54 scm_rev.h
52 scope_exit.h 55 scope_exit.h
53 string_util.h 56 string_util.h
54 swap.h 57 swap.h
55 symbols.h 58 symbols.h
59 synchronized_wrapper.h
56 thread.h 60 thread.h
57 thread_queue_list.h 61 thread_queue_list.h
58 thunk.h 62 thunk.h
diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp
new file mode 100644
index 000000000..c37546af0
--- /dev/null
+++ b/src/common/profiler.cpp
@@ -0,0 +1,159 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/profiler.h"
6#include "common/profiler_reporting.h"
7#include "common/assert.h"
8
9namespace Common {
10namespace Profiling {
11
12#if ENABLE_PROFILING
13thread_local Timer* Timer::current_timer = nullptr;
14#endif
15
16TimingCategory::TimingCategory(const char* name, TimingCategory* parent)
17 : accumulated_duration(0) {
18
19 ProfilingManager& manager = GetProfilingManager();
20 category_id = manager.RegisterTimingCategory(this, name);
21 if (parent != nullptr)
22 manager.SetTimingCategoryParent(category_id, parent->category_id);
23}
24
25ProfilingManager::ProfilingManager()
26 : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {
27}
28
29unsigned int ProfilingManager::RegisterTimingCategory(TimingCategory* category, const char* name) {
30 TimingCategoryInfo info;
31 info.category = category;
32 info.name = name;
33 info.parent = TimingCategoryInfo::NO_PARENT;
34
35 unsigned int id = (unsigned int)timing_categories.size();
36 timing_categories.push_back(std::move(info));
37
38 return id;
39}
40
41void ProfilingManager::SetTimingCategoryParent(unsigned int category, unsigned int parent) {
42 ASSERT(category < timing_categories.size());
43 ASSERT(parent < timing_categories.size());
44
45 timing_categories[category].parent = parent;
46}
47
48void ProfilingManager::BeginFrame() {
49 this_frame_start = Clock::now();
50}
51
52void ProfilingManager::FinishFrame() {
53 Clock::time_point now = Clock::now();
54
55 results.interframe_time = now - last_frame_end;
56 results.frame_time = now - this_frame_start;
57
58 results.time_per_category.resize(timing_categories.size());
59 for (size_t i = 0; i < timing_categories.size(); ++i) {
60 results.time_per_category[i] = timing_categories[i].category->GetAccumulatedTime();
61 }
62
63 last_frame_end = now;
64}
65
66TimingResultsAggregator::TimingResultsAggregator(size_t window_size)
67 : max_window_size(window_size), window_size(0) {
68 interframe_times.resize(window_size, Duration::zero());
69 frame_times.resize(window_size, Duration::zero());
70}
71
72void TimingResultsAggregator::Clear() {
73 window_size = cursor = 0;
74}
75
76void TimingResultsAggregator::SetNumberOfCategories(size_t n) {
77 size_t old_size = times_per_category.size();
78 if (n == old_size)
79 return;
80
81 times_per_category.resize(n);
82
83 for (size_t i = old_size; i < n; ++i) {
84 times_per_category[i].resize(max_window_size, Duration::zero());
85 }
86}
87
88void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) {
89 SetNumberOfCategories(frame_result.time_per_category.size());
90
91 interframe_times[cursor] = frame_result.interframe_time;
92 frame_times[cursor] = frame_result.frame_time;
93 for (size_t i = 0; i < frame_result.time_per_category.size(); ++i) {
94 times_per_category[i][cursor] = frame_result.time_per_category[i];
95 }
96
97 ++cursor;
98 if (cursor == max_window_size)
99 cursor = 0;
100 if (window_size < max_window_size)
101 ++window_size;
102}
103
104static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) {
105 AggregatedDuration result;
106 result.avg = Duration::zero();
107
108 result.min = result.max = (len == 0 ? Duration::zero() : v[0]);
109
110 for (size_t i = 1; i < len; ++i) {
111 Duration value = v[i];
112 result.avg += value;
113 result.min = std::min(result.min, value);
114 result.max = std::max(result.max, value);
115 }
116 if (len != 0)
117 result.avg /= len;
118
119 return result;
120}
121
122static float tof(Common::Profiling::Duration dur) {
123 using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
124 return std::chrono::duration_cast<FloatMs>(dur).count();
125}
126
127AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const {
128 AggregatedFrameResult result;
129
130 result.interframe_time = AggregateField(interframe_times, window_size);
131 result.frame_time = AggregateField(frame_times, window_size);
132
133 if (result.interframe_time.avg != Duration::zero()) {
134 result.fps = 1000.0f / tof(result.interframe_time.avg);
135 } else {
136 result.fps = 0.0f;
137 }
138
139 result.time_per_category.resize(times_per_category.size());
140 for (size_t i = 0; i < times_per_category.size(); ++i) {
141 result.time_per_category[i] = AggregateField(times_per_category[i], window_size);
142 }
143
144 return result;
145}
146
147ProfilingManager& GetProfilingManager() {
148 // Takes advantage of "magic" static initialization for race-free initialization.
149 static ProfilingManager manager;
150 return manager;
151}
152
153SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() {
154 static SynchronizedWrapper<TimingResultsAggregator> aggregator(30);
155 return SynchronizedRef<TimingResultsAggregator>(aggregator);
156}
157
158} // namespace Profiling
159} // namespace Common
diff --git a/src/common/profiler.h b/src/common/profiler.h
new file mode 100644
index 000000000..53c4f6eaf
--- /dev/null
+++ b/src/common/profiler.h
@@ -0,0 +1,134 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <chrono>
9
10#include "common/assert.h"
11#include "common/thread.h"
12
13namespace Common {
14namespace Profiling {
15
16// If this is defined to 0, it turns all Timers into no-ops.
17#ifndef ENABLE_PROFILING
18#define ENABLE_PROFILING 1
19#endif
20
21using Duration = std::chrono::nanoseconds;
22using Clock = std::chrono::high_resolution_clock;
23
24/**
25 * Represents a timing category that measured time can be accounted towards. Should be declared as a
26 * global variable and passed to Timers.
27 */
28class TimingCategory final {
29public:
30 TimingCategory(const char* name, TimingCategory* parent = nullptr);
31
32 unsigned int GetCategoryId() const {
33 return category_id;
34 }
35
36 /// Adds some time to this category. Can safely be called from multiple threads at the same time.
37 void AddTime(Duration amount) {
38 std::atomic_fetch_add_explicit(
39 &accumulated_duration, amount.count(),
40 std::memory_order_relaxed);
41 }
42
43 /**
44 * Atomically retrieves the accumulated measured time for this category and resets the counter
45 * to zero. Can be safely called concurrently with AddTime.
46 */
47 Duration GetAccumulatedTime() {
48 return Duration(std::atomic_exchange_explicit(
49 &accumulated_duration, (Duration::rep)0,
50 std::memory_order_relaxed));
51 }
52
53private:
54 unsigned int category_id;
55 std::atomic<Duration::rep> accumulated_duration;
56};
57
58/**
59 * Measures time elapsed between a call to Start and a call to Stop and attributes it to the given
60 * TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be
61 * appropriately paired.
62 *
63 * When a Timer is started, it automatically pauses a previously running timer on the same thread,
64 * which is resumed when it is stopped. As such, no special action needs to be taken to avoid
65 * double-accounting of time on two categories.
66 */
67class Timer {
68public:
69 Timer(TimingCategory& category) : category(category) {
70 }
71
72 void Start() {
73#if ENABLE_PROFILING
74 ASSERT(!running);
75 previous_timer = current_timer;
76 current_timer = this;
77 if (previous_timer != nullptr)
78 previous_timer->StopTiming();
79
80 StartTiming();
81#endif
82 }
83
84 void Stop() {
85#if ENABLE_PROFILING
86 ASSERT(running);
87 StopTiming();
88
89 if (previous_timer != nullptr)
90 previous_timer->StartTiming();
91 current_timer = previous_timer;
92#endif
93 }
94
95private:
96#if ENABLE_PROFILING
97 void StartTiming() {
98 start = Clock::now();
99 running = true;
100 }
101
102 void StopTiming() {
103 auto duration = Clock::now() - start;
104 running = false;
105 category.AddTime(std::chrono::duration_cast<Duration>(duration));
106 }
107
108 Clock::time_point start;
109 bool running = false;
110
111 Timer* previous_timer;
112 static thread_local Timer* current_timer;
113#endif
114
115 TimingCategory& category;
116};
117
118/**
119 * A Timer that automatically starts timing when created and stops at the end of the scope. Should
120 * be used in the majority of cases.
121 */
122class ScopeTimer : public Timer {
123public:
124 ScopeTimer(TimingCategory& category) : Timer(category) {
125 Start();
126 }
127
128 ~ScopeTimer() {
129 Stop();
130 }
131};
132
133} // namespace Profiling
134} // namespace Common
diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h
new file mode 100644
index 000000000..3abb73315
--- /dev/null
+++ b/src/common/profiler_reporting.h
@@ -0,0 +1,108 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <chrono>
9#include <mutex>
10#include <utility>
11#include <vector>
12
13#include "common/profiler.h"
14#include "common/synchronized_wrapper.h"
15
16namespace Common {
17namespace Profiling {
18
19struct TimingCategoryInfo {
20 static const unsigned int NO_PARENT = -1;
21
22 TimingCategory* category;
23 const char* name;
24 unsigned int parent;
25};
26
27struct ProfilingFrameResult {
28 /// Time since the last delivered frame
29 Duration interframe_time;
30
31 /// Time spent processing a frame, excluding VSync
32 Duration frame_time;
33
34 /// Total amount of time spent inside each category in this frame. Indexed by the category id
35 std::vector<Duration> time_per_category;
36};
37
38class ProfilingManager final {
39public:
40 ProfilingManager();
41
42 unsigned int RegisterTimingCategory(TimingCategory* category, const char* name);
43 void SetTimingCategoryParent(unsigned int category, unsigned int parent);
44
45 const std::vector<TimingCategoryInfo>& GetTimingCategoriesInfo() const {
46 return timing_categories;
47 }
48
49 /// This should be called after swapping screen buffers.
50 void BeginFrame();
51 /// This should be called before swapping screen buffers.
52 void FinishFrame();
53
54 /// Get the timing results from the previous frame. This is updated when you call FinishFrame().
55 const ProfilingFrameResult& GetPreviousFrameResults() const {
56 return results;
57 }
58
59private:
60 std::vector<TimingCategoryInfo> timing_categories;
61 Clock::time_point last_frame_end;
62 Clock::time_point this_frame_start;
63
64 ProfilingFrameResult results;
65};
66
67struct AggregatedDuration {
68 Duration avg, min, max;
69};
70
71struct AggregatedFrameResult {
72 /// Time since the last delivered frame
73 AggregatedDuration interframe_time;
74
75 /// Time spent processing a frame, excluding VSync
76 AggregatedDuration frame_time;
77
78 float fps;
79
80 /// Total amount of time spent inside each category in this frame. Indexed by the category id
81 std::vector<AggregatedDuration> time_per_category;
82};
83
84class TimingResultsAggregator final {
85public:
86 TimingResultsAggregator(size_t window_size);
87
88 void Clear();
89 void SetNumberOfCategories(size_t n);
90
91 void AddFrame(const ProfilingFrameResult& frame_result);
92
93 AggregatedFrameResult GetAggregatedResults() const;
94
95 size_t max_window_size;
96 size_t window_size;
97 size_t cursor;
98
99 std::vector<Duration> interframe_times;
100 std::vector<Duration> frame_times;
101 std::vector<std::vector<Duration>> times_per_category;
102};
103
104ProfilingManager& GetProfilingManager();
105SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator();
106
107} // namespace Profiling
108} // namespace Common
diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h
new file mode 100644
index 000000000..946252b8c
--- /dev/null
+++ b/src/common/synchronized_wrapper.h
@@ -0,0 +1,69 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <mutex>
8
9namespace Common {
10
11/**
12 * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no
13 * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a
14 * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type (http://doc.rust-lang.org/std/sync/struct.Mutex.html).
15 */
16template <typename T>
17class SynchronizedWrapper {
18public:
19 template <typename... Args>
20 SynchronizedWrapper(Args&&... args) :
21 data(std::forward<Args>(args)...) {
22 }
23
24private:
25 template <typename U>
26 friend class SynchronizedRef;
27
28 std::mutex mutex;
29 T data;
30};
31
32/**
33 * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This
34 * greatly reduces the chance that someone will access the wrapped resource without locking the
35 * mutex.
36 */
37template <typename T>
38class SynchronizedRef {
39public:
40 SynchronizedRef(SynchronizedWrapper<T>& wrapper) : wrapper(&wrapper) {
41 wrapper.mutex.lock();
42 }
43
44 SynchronizedRef(SynchronizedRef&) = delete;
45 SynchronizedRef(SynchronizedRef&& o) : wrapper(o.wrapper) {
46 o.wrapper = nullptr;
47 }
48
49 ~SynchronizedRef() {
50 if (wrapper)
51 wrapper->mutex.unlock();
52 }
53
54 SynchronizedRef& operator=(SynchronizedRef&) = delete;
55 SynchronizedRef& operator=(SynchronizedRef&& o) {
56 std::swap(wrapper, o.wrapper);
57 }
58
59 T& operator*() { return wrapper->data; }
60 const T& operator*() const { return wrapper->data; }
61
62 T* operator->() { return &wrapper->data; }
63 const T* operator->() const { return &wrapper->data; }
64
65private:
66 SynchronizedWrapper<T>* wrapper;
67};
68
69} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index eaf1ba00c..a45728e1e 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -24,6 +24,25 @@
24#include <unistd.h> 24#include <unistd.h>
25#endif 25#endif
26 26
27// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very
28// recently. Fortunately, thread local variables have been well supported for compilers for a while,
29// but with semantics supporting only POD types, so we can use a few defines to get some amount of
30// backwards compat support.
31// WARNING: This only works correctly with POD types.
32#if defined(__clang__)
33# if !__has_feature(cxx_thread_local)
34# define thread_local __thread
35# endif
36#elif defined(__GNUC__)
37# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
38# define thread_local __thread
39# endif
40#elif defined(_MSC_VER)
41# if _MSC_VER < 1900
42# define thread_local __declspec(thread)
43# endif
44#endif
45
27namespace Common 46namespace Common
28{ 47{
29 48
diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
index d8a708b9e..c3dba8882 100644
--- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
+++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
@@ -9,6 +9,7 @@
9#include <unordered_map> 9#include <unordered_map>
10 10
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/profiler.h"
12 13
13#include "core/mem_map.h" 14#include "core/mem_map.h"
14#include "core/hle/hle.h" 15#include "core/hle/hle.h"
@@ -20,6 +21,9 @@
20#include "core/arm/skyeye_common/armmmu.h" 21#include "core/arm/skyeye_common/armmmu.h"
21#include "core/arm/skyeye_common/vfp/vfp.h" 22#include "core/arm/skyeye_common/vfp/vfp.h"
22 23
24Common::Profiling::TimingCategory profile_execute("DynCom::Execute");
25Common::Profiling::TimingCategory profile_decode("DynCom::Decode");
26
23enum { 27enum {
24 COND = (1 << 0), 28 COND = (1 << 0),
25 NON_BRANCH = (1 << 1), 29 NON_BRANCH = (1 << 1),
@@ -3569,6 +3573,8 @@ typedef struct instruction_set_encoding_item ISEITEM;
3569extern const ISEITEM arm_instruction[]; 3573extern const ISEITEM arm_instruction[];
3570 3574
3571static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, addr_t addr) { 3575static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, addr_t addr) {
3576 Common::Profiling::ScopeTimer timer_decode(profile_decode);
3577
3572 // Decode instruction, get index 3578 // Decode instruction, get index
3573 // Allocate memory and init InsCream 3579 // Allocate memory and init InsCream
3574 // Go on next, until terminal instruction 3580 // Go on next, until terminal instruction
@@ -3641,6 +3647,8 @@ static bool InAPrivilegedMode(ARMul_State* core) {
3641} 3647}
3642 3648
3643unsigned InterpreterMainLoop(ARMul_State* state) { 3649unsigned InterpreterMainLoop(ARMul_State* state) {
3650 Common::Profiling::ScopeTimer timer_execute(profile_execute);
3651
3644 #undef RM 3652 #undef RM
3645 #undef RS 3653 #undef RS
3646 3654
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp
index b0066e15e..c925279da 100644
--- a/src/core/hle/hle.cpp
+++ b/src/core/hle/hle.cpp
@@ -4,6 +4,8 @@
4 4
5#include <vector> 5#include <vector>
6 6
7#include "common/profiler.h"
8
7#include "core/arm/arm_interface.h" 9#include "core/arm/arm_interface.h"
8#include "core/mem_map.h" 10#include "core/mem_map.h"
9#include "core/hle/hle.h" 11#include "core/hle/hle.h"
@@ -19,6 +21,8 @@
19 21
20namespace HLE { 22namespace HLE {
21 23
24Common::Profiling::TimingCategory profiler_svc("SVC Calls");
25
22static std::vector<ModuleDef> g_module_db; 26static std::vector<ModuleDef> g_module_db;
23 27
24bool g_reschedule = false; ///< If true, immediately reschedules the CPU to a new thread 28bool g_reschedule = false; ///< If true, immediately reschedules the CPU to a new thread
@@ -33,6 +37,8 @@ static const FunctionDef* GetSVCInfo(u32 opcode) {
33} 37}
34 38
35void CallSVC(u32 opcode) { 39void CallSVC(u32 opcode) {
40 Common::Profiling::ScopeTimer timer_svc(profiler_svc);
41
36 const FunctionDef *info = GetSVCInfo(opcode); 42 const FunctionDef *info = GetSVCInfo(opcode);
37 43
38 if (!info) { 44 if (!info) {
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 586ad62b6..e031871e8 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -4,6 +4,8 @@
4 4
5#include <boost/range/algorithm/fill.hpp> 5#include <boost/range/algorithm/fill.hpp>
6 6
7#include "common/profiler.h"
8
7#include "clipper.h" 9#include "clipper.h"
8#include "command_processor.h" 10#include "command_processor.h"
9#include "math.h" 11#include "math.h"
@@ -25,6 +27,8 @@ static int float_regs_counter = 0;
25 27
26static u32 uniform_write_buffer[4]; 28static u32 uniform_write_buffer[4];
27 29
30Common::Profiling::TimingCategory category_drawing("Drawing");
31
28static inline void WritePicaReg(u32 id, u32 value, u32 mask) { 32static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
29 33
30 if (id >= registers.NumIds()) 34 if (id >= registers.NumIds())
@@ -53,6 +57,8 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
53 case PICA_REG_INDEX(trigger_draw): 57 case PICA_REG_INDEX(trigger_draw):
54 case PICA_REG_INDEX(trigger_draw_indexed): 58 case PICA_REG_INDEX(trigger_draw_indexed):
55 { 59 {
60 Common::Profiling::ScopeTimer scope_timer(category_drawing);
61
56 DebugUtils::DumpTevStageConfig(registers.GetTevStages()); 62 DebugUtils::DumpTevStageConfig(registers.GetTevStages());
57 63
58 if (g_debug_context) 64 if (g_debug_context)
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 272695174..db7538ddd 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -4,7 +4,10 @@
4 4
5#include "core/hw/gpu.h" 5#include "core/hw/gpu.h"
6#include "core/mem_map.h" 6#include "core/mem_map.h"
7
7#include "common/emu_window.h" 8#include "common/emu_window.h"
9#include "common/profiler_reporting.h"
10
8#include "video_core/video_core.h" 11#include "video_core/video_core.h"
9#include "video_core/renderer_opengl/renderer_opengl.h" 12#include "video_core/renderer_opengl/renderer_opengl.h"
10#include "video_core/renderer_opengl/gl_shader_util.h" 13#include "video_core/renderer_opengl/gl_shader_util.h"
@@ -75,9 +78,18 @@ void RendererOpenGL::SwapBuffers() {
75 78
76 DrawScreens(); 79 DrawScreens();
77 80
81 auto& profiler = Common::Profiling::GetProfilingManager();
82 profiler.FinishFrame();
83 {
84 auto aggregator = Common::Profiling::GetTimingResultsAggregator();
85 aggregator->AddFrame(profiler.GetPreviousFrameResults());
86 }
87
78 // Swap buffers 88 // Swap buffers
79 render_window->PollEvents(); 89 render_window->PollEvents();
80 render_window->SwapBuffers(); 90 render_window->SwapBuffers();
91
92 profiler.BeginFrame();
81} 93}
82 94
83/** 95/**