Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
5664 views
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<float> frameTimes;
30
std::vector<float> 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"] = [p](DebuggerRequest &req) { p->Get(req); };
93
map["gpu.stats.feed"] = [p](DebuggerRequest &req) { p->Feed(req); };
94
95
return p;
96
}
97
98
WebSocketGPUStatsState::WebSocketGPUStatsState() {
99
__DisplayListenFlip(&WebSocketGPUStatsState::FlipForwarder, this);
100
}
101
102
WebSocketGPUStatsState::~WebSocketGPUStatsState() {
103
if (forced_)
104
PSP_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
float *sleepHistory;
127
float *history = __DisplayGetFrameTimes(&valid, &stats.frameTimePos, &sleepHistory);
128
129
stats.frameTimes.resize(valid);
130
stats.sleepTimes.resize(valid);
131
if (valid > 0) {
132
memcpy(&stats.frameTimes[0], history, sizeof(double) * valid);
133
memcpy(&stats.sleepTimes[0], sleepHistory, sizeof(double) * valid);
134
}
135
136
sendNext_ = false;
137
}
138
139
// Get next GPU stats (gpu.stats.get)
140
//
141
// No parameters.
142
//
143
// Response (same event name):
144
// - fps: object with "actual" and "target" properties, representing frames per second.
145
// - vblanksPerSecond: object with "actual" and "target" properties, for vblank cycles.
146
// - info: string, representation of backend-dependent statistics.
147
// - timing: object with properties:
148
// - frames: array of numbers, each representing the time taken for a frame.
149
// - sleep: array of numbers, each representing the delay time waiting for next frame.
150
// - pos: number, index of the current frame (not always last.)
151
//
152
// Note: stats are returned after the next flip completes (paused if CPU or GPU in break.)
153
// Note: info and timing may not be accurate if certain settings are disabled.
154
// Note: sending this event with no ticket will not trigger a response! (TODO: maybe fix this?)
155
void WebSocketGPUStatsState::Get(DebuggerRequest &req) {
156
if (PSP_GetBootState() != BootState::Complete)
157
return req.Fail("CPU not started");
158
159
std::lock_guard<std::mutex> guard(pendingLock_);
160
sendNext_ = true;
161
162
const JsonNode *value = req.data.get("ticket");
163
lastTicket_ = value ? json_stringify(value) : "";
164
}
165
166
// Setup GPU stats feed (gpu.stats.feed)
167
//
168
// Parameters:
169
// - enable: optional boolean, pass false to stop the feed.
170
//
171
// No immediate response. Events sent each frame (as gpu.stats.get.)
172
//
173
// Note: info and timing will be accurate after the first frame.
174
void WebSocketGPUStatsState::Feed(DebuggerRequest &req) {
175
if (PSP_GetBootState() != BootState::Complete)
176
return req.Fail("CPU not started");
177
bool enable = true;
178
if (!req.ParamBool("enable", &enable, DebuggerParamType::OPTIONAL))
179
return;
180
181
std::lock_guard<std::mutex> guard(pendingLock_);
182
sendFeed_ = enable;
183
if (forced_ != enable) {
184
PSP_ForceDebugStats(enable);
185
forced_ = enable;
186
}
187
}
188
189
void WebSocketGPUStatsState::Broadcast(net::WebSocketServer *ws) {
190
std::lock_guard<std::mutex> guard(pendingLock_);
191
if (lastTicket_.empty() && !sendFeed_) {
192
pendingStats_.clear();
193
return;
194
}
195
196
// To be safe, make sure we only send one if we're doing a get.
197
if (!sendFeed_ && pendingStats_.size() > 1)
198
pendingStats_.resize(1);
199
200
for (size_t i = 0; i < pendingStats_.size(); ++i) {
201
ws->Send(DebuggerGPUStatsEvent{ pendingStats_[i], lastTicket_ });
202
lastTicket_.clear();
203
}
204
pendingStats_.clear();
205
}
206
207