Files
ppsspp/Core/Debugger/WebSocket/GPURecordSubscriber.cpp
Henrik RydgårdandClaude Sonnet 5.5 cbb553ccc7 Build as C++20
Switch CMake (including the Core and GPU compile features and the Android
flag), the Visual Studio projects (stdcpp20), the legacy Android NDK
makefiles, the libretro makefiles and .ycm_extra_conf.py from C++17 to
C++20.

[=] lambdas that use this implicitly become [=, this], to avoid
-Wdeprecated-this-capture.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 00:02:06 +01:00

127 lines
3.7 KiB
C++

// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/Data/Encoding/Base64.h"
#include "Common/File/FileUtil.h"
#include "Core/Core.h"
#include "Core/Debugger/WebSocket/GPURecordSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/System.h"
#include "GPU/Debugger/Record.h"
#include "GPU/GPU.h"
#include "GPU/GPUCommon.h"
struct WebSocketGPURecordState : public DebuggerSubscriber {
~WebSocketGPURecordState();
void Dump(DebuggerRequest &req);
void Broadcast(net::WebSocketServer *ws) override;
protected:
bool pending_ = false;
std::string lastTicket_;
Path lastFilename_;
};
DebuggerSubscriber *WebSocketGPURecordInit(DebuggerEventHandlerMap &map) {
auto p = new WebSocketGPURecordState();
map["gpu.record.dump"] = [p](DebuggerRequest &req) { p->Dump(req); };
return p;
}
WebSocketGPURecordState::~WebSocketGPURecordState() {
// Clear the callback to hopefully avoid a crash. On the CPU thread, since gpu itself is
// destroyed over there - see Core_RunOnCPUThread() in Core.h.
if (pending_) {
Core_RunOnCPUThread([&] {
if (gpu)
gpu->GetRecorder()->ClearCallback();
});
}
}
// Begin recording (gpu.record.dump)
//
// No parameters.
//
// Response (same event name):
// - uri: data: URI containing debug dump data.
//
// Note: recording may take a moment.
void WebSocketGPURecordState::Dump(DebuggerRequest &req) {
// gpu is created and destroyed on the CPU thread, so ask it for a recording over there rather
// than dereferencing it from this WebSocket handler thread.
bool started = false;
bool haveGPU = false;
Core_RunOnCPUThread([&] {
haveGPU = PSP_GetBootState() == BootState::Complete && gpu != nullptr;
if (!haveGPU)
return;
started = gpu->GetRecorder()->RecordNextFrame([=, this](const Path &filename) {
lastFilename_ = filename;
pending_ = false;
});
});
if (!haveGPU) {
return req.Fail("CPU not started");
}
if (!started) {
return req.Fail("Recording already in progress");
}
pending_ = true;
const JsonNode *value = req.data.get("ticket");
lastTicket_ = value ? json_stringify(value) : "";
}
// This handles the asynchronous gpu.record.dump response.
void WebSocketGPURecordState::Broadcast(net::WebSocketServer *ws) {
if (!lastFilename_.empty()) {
FILE *fp = File::OpenCFile(lastFilename_, "rb");
if (!fp) {
lastFilename_.clear();
return;
}
// We write directly to the stream since this is a large chunk of data.
ws->AddFragment(false, R"({"event":"gpu.record.dump")");
if (!lastTicket_.empty()) {
ws->AddFragment(false, R"(,"ticket":)");
ws->AddFragment(false, lastTicket_);
}
ws->AddFragment(false, R"(,"uri":"data:application/octet-stream;base64,)");
// Divisible by 3 for base64 reasons.
const size_t BUF_SIZE = 16383;
std::vector<uint8_t> buf;
buf.resize(BUF_SIZE);
while (!feof(fp)) {
size_t bytes = fread(&buf[0], 1, BUF_SIZE, fp);
ws->AddFragment(false, Base64Encode(&buf[0], bytes));
}
fclose(fp);
ws->AddFragment(true, R"("})");
lastFilename_.clear();
lastTicket_.clear();
}
}