CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
Views: 1401
// Copyright (c) 2021- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <mutex>18#include <vector>19#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"20#include "Core/HW/Display.h"21#include "Core/System.h"2223struct CollectedStats {24float vps;25float fps;26float actual_fps;27char statbuf[4096];28std::vector<double> frameTimes;29std::vector<double> sleepTimes;30int frameTimePos;31};3233struct DebuggerGPUStatsEvent {34const CollectedStats &s;35const std::string &ticket;3637operator std::string() {38JsonWriter j;39j.begin();40j.writeString("event", "gpu.stats.get");41if (!ticket.empty())42j.writeRaw("ticket", ticket);43j.pushDict("fps");44j.writeFloat("actual", s.actual_fps);45j.writeFloat("target", s.fps);46j.pop();47j.pushDict("vblanksPerSecond");48j.writeFloat("actual", s.vps);49j.writeFloat("target", 60.0 / 1.001);50j.pop();51j.writeString("info", s.statbuf);52j.pushDict("timing");53j.pushArray("frames");54for (double t : s.frameTimes)55j.writeFloat(t);56j.pop();57j.pushArray("sleep");58for (double t : s.sleepTimes)59j.writeFloat(t);60j.pop();61j.writeInt("pos", s.frameTimePos);62j.pop();63j.end();64return j.str();65}66};6768struct WebSocketGPUStatsState : public DebuggerSubscriber {69WebSocketGPUStatsState();70~WebSocketGPUStatsState();71void Get(DebuggerRequest &req);72void Feed(DebuggerRequest &req);7374void Broadcast(net::WebSocketServer *ws) override;7576static void FlipForwarder(void *thiz);77void FlipListener();7879protected:80bool forced_ = false;81bool sendNext_ = false;82bool sendFeed_ = false;8384std::string lastTicket_;85std::mutex pendingLock_;86std::vector<CollectedStats> pendingStats_;87};8889DebuggerSubscriber *WebSocketGPUStatsInit(DebuggerEventHandlerMap &map) {90auto p = new WebSocketGPUStatsState();91map["gpu.stats.get"] = std::bind(&WebSocketGPUStatsState::Get, p, std::placeholders::_1);92map["gpu.stats.feed"] = std::bind(&WebSocketGPUStatsState::Feed, p, std::placeholders::_1);9394return p;95}9697WebSocketGPUStatsState::WebSocketGPUStatsState() {98__DisplayListenFlip(&WebSocketGPUStatsState::FlipForwarder, this);99}100101WebSocketGPUStatsState::~WebSocketGPUStatsState() {102if (forced_)103Core_ForceDebugStats(false);104__DisplayForgetFlip(&WebSocketGPUStatsState::FlipForwarder, this);105}106107void WebSocketGPUStatsState::FlipForwarder(void *thiz) {108WebSocketGPUStatsState *p = (WebSocketGPUStatsState *)thiz;109p->FlipListener();110}111112void WebSocketGPUStatsState::FlipListener() {113if (!sendNext_ && !sendFeed_)114return;115116// Okay, collect the data (we'll actually send at next Broadcast.)117std::lock_guard<std::mutex> guard(pendingLock_);118pendingStats_.resize(pendingStats_.size() + 1);119CollectedStats &stats = pendingStats_[pendingStats_.size() - 1];120121__DisplayGetFPS(&stats.vps, &stats.fps, &stats.actual_fps);122__DisplayGetDebugStats(stats.statbuf, sizeof(stats.statbuf));123124int valid;125double *sleepHistory;126double *history = __DisplayGetFrameTimes(&valid, &stats.frameTimePos, &sleepHistory);127128stats.frameTimes.resize(valid);129stats.sleepTimes.resize(valid);130memcpy(&stats.frameTimes[0], history, sizeof(double) * valid);131memcpy(&stats.sleepTimes[0], sleepHistory, sizeof(double) * valid);132133sendNext_ = false;134}135136// Get next GPU stats (gpu.stats.get)137//138// No parameters.139//140// Response (same event name):141// - fps: object with "actual" and "target" properties, representing frames per second.142// - vblanksPerSecond: object with "actual" and "target" properties, for vblank cycles.143// - info: string, representation of backend-dependent statistics.144// - timing: object with properties:145// - frames: array of numbers, each representing the time taken for a frame.146// - sleep: array of numbers, each representing the delay time waiting for next frame.147// - pos: number, index of the current frame (not always last.)148//149// Note: stats are returned after the next flip completes (paused if CPU or GPU in break.)150// Note: info and timing may not be accurate if certain settings are disabled.151void WebSocketGPUStatsState::Get(DebuggerRequest &req) {152if (!PSP_IsInited())153return req.Fail("CPU not started");154155std::lock_guard<std::mutex> guard(pendingLock_);156sendNext_ = true;157158const JsonNode *value = req.data.get("ticket");159lastTicket_ = value ? json_stringify(value) : "";160}161162// Setup GPU stats feed (gpu.stats.feed)163//164// Parameters:165// - enable: optional boolean, pass false to stop the feed.166//167// No immediate response. Events sent each frame (as gpu.stats.get.)168//169// Note: info and timing will be accurate after the first frame.170void WebSocketGPUStatsState::Feed(DebuggerRequest &req) {171if (!PSP_IsInited())172return req.Fail("CPU not started");173bool enable = true;174if (!req.ParamBool("enable", &enable, DebuggerParamType::OPTIONAL))175return;176177std::lock_guard<std::mutex> guard(pendingLock_);178sendFeed_ = enable;179if (forced_ != enable) {180Core_ForceDebugStats(enable);181forced_ = enable;182}183}184185void WebSocketGPUStatsState::Broadcast(net::WebSocketServer *ws) {186std::lock_guard<std::mutex> guard(pendingLock_);187if (lastTicket_.empty() && !sendFeed_) {188pendingStats_.clear();189return;190}191192// To be safe, make sure we only send one if we're doing a get.193if (!sendFeed_ && pendingStats_.size() > 1)194pendingStats_.resize(1);195196for (size_t i = 0; i < pendingStats_.size(); ++i) {197ws->Send(DebuggerGPUStatsEvent{ pendingStats_[i], lastTicket_ });198lastTicket_.clear();199}200pendingStats_.clear();201}202203204