CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
Views: 1401
1
// Copyright (c) 2021- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <mutex>
19
#include <vector>
20
#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"
21
#include "Core/HW/Display.h"
22
#include "Core/System.h"
23
24
struct CollectedStats {
25
float vps;
26
float fps;
27
float actual_fps;
28
char statbuf[4096];
29
std::vector<double> frameTimes;
30
std::vector<double> sleepTimes;
31
int frameTimePos;
32
};
33
34
struct DebuggerGPUStatsEvent {
35
const CollectedStats &s;
36
const std::string &ticket;
37
38
operator std::string() {
39
JsonWriter j;
40
j.begin();
41
j.writeString("event", "gpu.stats.get");
42
if (!ticket.empty())
43
j.writeRaw("ticket", ticket);
44
j.pushDict("fps");
45
j.writeFloat("actual", s.actual_fps);
46
j.writeFloat("target", s.fps);
47
j.pop();
48
j.pushDict("vblanksPerSecond");
49
j.writeFloat("actual", s.vps);
50
j.writeFloat("target", 60.0 / 1.001);
51
j.pop();
52
j.writeString("info", s.statbuf);
53
j.pushDict("timing");
54
j.pushArray("frames");
55
for (double t : s.frameTimes)
56
j.writeFloat(t);
57
j.pop();
58
j.pushArray("sleep");
59
for (double t : s.sleepTimes)
60
j.writeFloat(t);
61
j.pop();
62
j.writeInt("pos", s.frameTimePos);
63
j.pop();
64
j.end();
65
return j.str();
66
}
67
};
68
69
struct WebSocketGPUStatsState : public DebuggerSubscriber {
70
WebSocketGPUStatsState();
71
~WebSocketGPUStatsState();
72
void Get(DebuggerRequest &req);
73
void Feed(DebuggerRequest &req);
74
75
void Broadcast(net::WebSocketServer *ws) override;
76
77
static void FlipForwarder(void *thiz);
78
void FlipListener();
79
80
protected:
81
bool forced_ = false;
82
bool sendNext_ = false;
83
bool sendFeed_ = false;
84
85
std::string lastTicket_;
86
std::mutex pendingLock_;
87
std::vector<CollectedStats> pendingStats_;
88
};
89
90
DebuggerSubscriber *WebSocketGPUStatsInit(DebuggerEventHandlerMap &map) {
91
auto p = new WebSocketGPUStatsState();
92
map["gpu.stats.get"] = std::bind(&WebSocketGPUStatsState::Get, p, std::placeholders::_1);
93
map["gpu.stats.feed"] = std::bind(&WebSocketGPUStatsState::Feed, p, std::placeholders::_1);
94
95
return p;
96
}
97
98
WebSocketGPUStatsState::WebSocketGPUStatsState() {
99
__DisplayListenFlip(&WebSocketGPUStatsState::FlipForwarder, this);
100
}
101
102
WebSocketGPUStatsState::~WebSocketGPUStatsState() {
103
if (forced_)
104
Core_ForceDebugStats(false);
105
__DisplayForgetFlip(&WebSocketGPUStatsState::FlipForwarder, this);
106
}
107
108
void WebSocketGPUStatsState::FlipForwarder(void *thiz) {
109
WebSocketGPUStatsState *p = (WebSocketGPUStatsState *)thiz;
110
p->FlipListener();
111
}
112
113
void WebSocketGPUStatsState::FlipListener() {
114
if (!sendNext_ && !sendFeed_)
115
return;
116
117
// Okay, collect the data (we'll actually send at next Broadcast.)
118
std::lock_guard<std::mutex> guard(pendingLock_);
119
pendingStats_.resize(pendingStats_.size() + 1);
120
CollectedStats &stats = pendingStats_[pendingStats_.size() - 1];
121
122
__DisplayGetFPS(&stats.vps, &stats.fps, &stats.actual_fps);
123
__DisplayGetDebugStats(stats.statbuf, sizeof(stats.statbuf));
124
125
int valid;
126
double *sleepHistory;
127
double *history = __DisplayGetFrameTimes(&valid, &stats.frameTimePos, &sleepHistory);
128
129
stats.frameTimes.resize(valid);
130
stats.sleepTimes.resize(valid);
131
memcpy(&stats.frameTimes[0], history, sizeof(double) * valid);
132
memcpy(&stats.sleepTimes[0], sleepHistory, sizeof(double) * valid);
133
134
sendNext_ = false;
135
}
136
137
// Get next GPU stats (gpu.stats.get)
138
//
139
// No parameters.
140
//
141
// Response (same event name):
142
// - fps: object with "actual" and "target" properties, representing frames per second.
143
// - vblanksPerSecond: object with "actual" and "target" properties, for vblank cycles.
144
// - info: string, representation of backend-dependent statistics.
145
// - timing: object with properties:
146
// - frames: array of numbers, each representing the time taken for a frame.
147
// - sleep: array of numbers, each representing the delay time waiting for next frame.
148
// - pos: number, index of the current frame (not always last.)
149
//
150
// Note: stats are returned after the next flip completes (paused if CPU or GPU in break.)
151
// Note: info and timing may not be accurate if certain settings are disabled.
152
void WebSocketGPUStatsState::Get(DebuggerRequest &req) {
153
if (!PSP_IsInited())
154
return req.Fail("CPU not started");
155
156
std::lock_guard<std::mutex> guard(pendingLock_);
157
sendNext_ = true;
158
159
const JsonNode *value = req.data.get("ticket");
160
lastTicket_ = value ? json_stringify(value) : "";
161
}
162
163
// Setup GPU stats feed (gpu.stats.feed)
164
//
165
// Parameters:
166
// - enable: optional boolean, pass false to stop the feed.
167
//
168
// No immediate response. Events sent each frame (as gpu.stats.get.)
169
//
170
// Note: info and timing will be accurate after the first frame.
171
void WebSocketGPUStatsState::Feed(DebuggerRequest &req) {
172
if (!PSP_IsInited())
173
return req.Fail("CPU not started");
174
bool enable = true;
175
if (!req.ParamBool("enable", &enable, DebuggerParamType::OPTIONAL))
176
return;
177
178
std::lock_guard<std::mutex> guard(pendingLock_);
179
sendFeed_ = enable;
180
if (forced_ != enable) {
181
Core_ForceDebugStats(enable);
182
forced_ = enable;
183
}
184
}
185
186
void WebSocketGPUStatsState::Broadcast(net::WebSocketServer *ws) {
187
std::lock_guard<std::mutex> guard(pendingLock_);
188
if (lastTicket_.empty() && !sendFeed_) {
189
pendingStats_.clear();
190
return;
191
}
192
193
// To be safe, make sure we only send one if we're doing a get.
194
if (!sendFeed_ && pendingStats_.size() > 1)
195
pendingStats_.resize(1);
196
197
for (size_t i = 0; i < pendingStats_.size(); ++i) {
198
ws->Send(DebuggerGPUStatsEvent{ pendingStats_[i], lastTicket_ });
199
lastTicket_.clear();
200
}
201
pendingStats_.clear();
202
}
203
204