mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-02-22 07:03:38 +00:00
Debugger: Add APIs for GPU stats.
This commit is contained in:
parent
1532a729d7
commit
83909f816e
@ -15,10 +15,188 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"
|
||||
#include "Core/HLE/sceDisplay.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
struct CollectedStats {
|
||||
float vps;
|
||||
float fps;
|
||||
float actual_fps;
|
||||
char statbuf[4096];
|
||||
std::vector<double> frameTimes;
|
||||
std::vector<double> sleepTimes;
|
||||
int frameTimePos;
|
||||
};
|
||||
|
||||
struct DebuggerGPUStatsEvent {
|
||||
const CollectedStats &s;
|
||||
const std::string &ticket;
|
||||
|
||||
operator std::string() {
|
||||
JsonWriter j;
|
||||
j.begin();
|
||||
j.writeString("event", "gpu.stats.get");
|
||||
if (!ticket.empty())
|
||||
j.writeRaw("ticket", ticket);
|
||||
j.pushDict("fps");
|
||||
j.writeFloat("actual", s.actual_fps);
|
||||
j.writeFloat("target", s.fps);
|
||||
j.pop();
|
||||
j.pushDict("vblanksPerSecond");
|
||||
j.writeFloat("actual", s.vps);
|
||||
j.writeFloat("target", 60.0 / 1.001);
|
||||
j.pop();
|
||||
j.writeString("info", s.statbuf);
|
||||
j.pushDict("timing");
|
||||
j.pushArray("frames");
|
||||
for (double t : s.frameTimes)
|
||||
j.writeFloat(t);
|
||||
j.pop();
|
||||
j.pushArray("sleep");
|
||||
for (double t : s.sleepTimes)
|
||||
j.writeFloat(t);
|
||||
j.pop();
|
||||
j.writeInt("pos", s.frameTimePos);
|
||||
j.pop();
|
||||
j.end();
|
||||
return j.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct WebSocketGPUStatsState : public DebuggerSubscriber {
|
||||
WebSocketGPUStatsState();
|
||||
~WebSocketGPUStatsState() override;
|
||||
void Get(DebuggerRequest &req);
|
||||
void Feed(DebuggerRequest &req);
|
||||
|
||||
void Broadcast(net::WebSocketServer *ws) override;
|
||||
|
||||
static void FlipForwarder(void *thiz);
|
||||
void FlipListener();
|
||||
|
||||
protected:
|
||||
bool forced_ = false;
|
||||
bool sendNext_ = false;
|
||||
bool sendFeed_ = false;
|
||||
|
||||
std::string lastTicket_;
|
||||
std::mutex pendingLock_;
|
||||
std::vector<CollectedStats> pendingStats_;
|
||||
};
|
||||
|
||||
DebuggerSubscriber *WebSocketGPUStatsInit(DebuggerEventHandlerMap &map) {
|
||||
// TODO
|
||||
return nullptr;
|
||||
auto p = new WebSocketGPUStatsState();
|
||||
map["gpu.stats.get"] = std::bind(&WebSocketGPUStatsState::Get, p, std::placeholders::_1);
|
||||
map["gpu.stats.feed"] = std::bind(&WebSocketGPUStatsState::Feed, p, std::placeholders::_1);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
WebSocketGPUStatsState::WebSocketGPUStatsState() {
|
||||
__DisplayListenFlip(&WebSocketGPUStatsState::FlipForwarder, this);
|
||||
}
|
||||
|
||||
WebSocketGPUStatsState::~WebSocketGPUStatsState() {
|
||||
if (forced_)
|
||||
Core_ForceDebugStats(false);
|
||||
__DisplayForgetFlip(&WebSocketGPUStatsState::FlipForwarder, this);
|
||||
}
|
||||
|
||||
void WebSocketGPUStatsState::FlipForwarder(void *thiz) {
|
||||
WebSocketGPUStatsState *p = (WebSocketGPUStatsState *)thiz;
|
||||
p->FlipListener();
|
||||
}
|
||||
|
||||
void WebSocketGPUStatsState::FlipListener() {
|
||||
if (!sendNext_ && !sendFeed_)
|
||||
return;
|
||||
|
||||
// Okay, collect the data (we'll actually send at next Broadcast.)
|
||||
std::lock_guard<std::mutex> guard(pendingLock_);
|
||||
pendingStats_.resize(pendingStats_.size() + 1);
|
||||
CollectedStats &stats = pendingStats_[pendingStats_.size() - 1];
|
||||
|
||||
__DisplayGetFPS(&stats.vps, &stats.fps, &stats.actual_fps);
|
||||
__DisplayGetDebugStats(stats.statbuf, sizeof(stats.statbuf));
|
||||
|
||||
int valid;
|
||||
double *sleepHistory;
|
||||
double *history = __DisplayGetFrameTimes(&valid, &stats.frameTimePos, &sleepHistory);
|
||||
|
||||
stats.frameTimes.resize(valid);
|
||||
stats.sleepTimes.resize(valid);
|
||||
memcpy(&stats.frameTimes[0], history, sizeof(double) * valid);
|
||||
memcpy(&stats.sleepTimes[0], sleepHistory, sizeof(double) * valid);
|
||||
|
||||
sendNext_ = false;
|
||||
}
|
||||
|
||||
// Get next GPU stats (gpu.stats.get)
|
||||
//
|
||||
// No parameters.
|
||||
//
|
||||
// Response (same event name):
|
||||
// - fps: object with "actual" and "target" properties, representing frames per second.
|
||||
// - vblanksPerSecond: object with "actual" and "target" properties, for vblank cycles.
|
||||
// - info: string, representation of backend-dependent statistics.
|
||||
// - timing: object with properties:
|
||||
// - frames: array of numbers, each representing the time taken for a frame.
|
||||
// - sleep: array of numbers, each representing the delay time waiting for next frame.
|
||||
// - pos: number, index of the current frame (not always last.)
|
||||
//
|
||||
// Note: stats are returned after the next flip completes (paused if CPU or GPU in break.)
|
||||
// Note: info and timing may not be accurate if certain settings are disabled.
|
||||
void WebSocketGPUStatsState::Get(DebuggerRequest &req) {
|
||||
if (!PSP_IsInited())
|
||||
return req.Fail("CPU not started");
|
||||
|
||||
std::lock_guard<std::mutex> guard(pendingLock_);
|
||||
sendNext_ = true;
|
||||
|
||||
const JsonNode *value = req.data.get("ticket");
|
||||
lastTicket_ = value ? json_stringify(value) : "";
|
||||
}
|
||||
|
||||
// Setup GPU stats feed (gpu.stats.feed)
|
||||
//
|
||||
// Parameters:
|
||||
// - enable: optional boolean, pass false to stop the feed.
|
||||
//
|
||||
// No immediate response. Events sent each frame (as gpu.stats.get.)
|
||||
//
|
||||
// Note: info and timing will be accurate after the first frame.
|
||||
void WebSocketGPUStatsState::Feed(DebuggerRequest &req) {
|
||||
if (!PSP_IsInited())
|
||||
return req.Fail("CPU not started");
|
||||
bool enable = true;
|
||||
if (!req.ParamBool("enable", &enable, DebuggerParamType::OPTIONAL))
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> guard(pendingLock_);
|
||||
sendFeed_ = enable;
|
||||
if (forced_ != enable) {
|
||||
Core_ForceDebugStats(enable);
|
||||
forced_ = enable;
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketGPUStatsState::Broadcast(net::WebSocketServer *ws) {
|
||||
std::lock_guard<std::mutex> guard(pendingLock_);
|
||||
if (lastTicket_.empty() && !sendFeed_) {
|
||||
pendingStats_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// To be safe, make sure we only send one if we're doing a get.
|
||||
if (!sendFeed_ && pendingStats_.size() > 1)
|
||||
pendingStats_.resize(1);
|
||||
|
||||
for (size_t i = 0; i < pendingStats_.size(); ++i) {
|
||||
ws->Send(DebuggerGPUStatsEvent{ pendingStats_[i], lastTicket_ });
|
||||
lastTicket_.clear();
|
||||
}
|
||||
pendingStats_.clear();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user