Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
5668 views
1
// Copyright (c) 2018- 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 <algorithm>
19
#ifndef USING_QT_UI
20
#include <png.h>
21
#include <zlib.h>
22
#endif
23
#include "Common/Data/Encoding/Base64.h"
24
#include "Common/StringUtils.h"
25
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
26
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
27
#include "Core/MIPS/MIPSDebugInterface.h"
28
#include "Core/Screenshot.h"
29
#include "GPU/Debugger/Stepping.h"
30
31
DebuggerSubscriber *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map) {
32
// No need to bind or alloc state, these are all global.
33
map["gpu.buffer.screenshot"] = &WebSocketGPUBufferScreenshot;
34
map["gpu.buffer.renderColor"] = &WebSocketGPUBufferRenderColor;
35
map["gpu.buffer.renderDepth"] = &WebSocketGPUBufferRenderDepth;
36
map["gpu.buffer.renderStencil"] = &WebSocketGPUBufferRenderStencil;
37
map["gpu.buffer.texture"] = &WebSocketGPUBufferTexture;
38
map["gpu.buffer.clut"] = &WebSocketGPUBufferClut;
39
40
return nullptr;
41
}
42
43
// Note: Calls req.Respond(). Other data can be added afterward.
44
static bool StreamBufferToDataURI(DebuggerRequest &req, const GPUDebugBuffer &buf, bool isFramebuffer, bool includeAlpha, int stackWidth) {
45
#ifdef USING_QT_UI
46
req.Fail("Not supported on Qt yet, pull requests accepted");
47
return false;
48
#else
49
u8 *flipbuffer = nullptr;
50
u32 w = (u32)-1;
51
u32 h = (u32)-1;
52
const u8 *buffer = ConvertBufferToScreenshot(buf, includeAlpha, flipbuffer, w, h);
53
if (!buffer) {
54
req.Fail("Internal error converting buffer for PNG encode");
55
return false;
56
}
57
58
if (stackWidth > 0) {
59
u32 totalPixels = w * h;
60
w = stackWidth;
61
while ((totalPixels % w) != 0)
62
--w;
63
h = totalPixels / w;
64
}
65
66
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
67
if (!png_ptr) {
68
req.Fail("Internal error setting up PNG encoder (png_ptr)");
69
return false;
70
}
71
png_infop info_ptr = png_create_info_struct(png_ptr);
72
if (!info_ptr) {
73
png_destroy_write_struct(&png_ptr, nullptr);
74
req.Fail("Internal error setting up PNG encoder (info_ptr)");
75
return false;
76
}
77
78
// Speed. Wireless N should give 35 KB/ms. For most devices, zlib/filters will cost more.
79
png_set_compression_strategy(png_ptr, Z_RLE);
80
png_set_compression_level(png_ptr, 1);
81
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
82
83
auto &json = req.Respond();
84
json.writeInt("width", w);
85
json.writeInt("height", h);
86
if (isFramebuffer) {
87
json.writeBool("isFramebuffer", isFramebuffer);
88
}
89
90
// Start a value...
91
json.writeRaw("uri", "");
92
req.Flush();
93
// Now we'll write it directly to the stream.
94
req.ws->AddFragment(false, "\"data:image/png;base64,");
95
96
struct Context {
97
DebuggerRequest *req;
98
uint8_t buf[3];
99
size_t bufSize;
100
};
101
Context ctx = { &req, {}, 0 };
102
103
auto write = [](png_structp png_ptr, png_bytep data, png_size_t length) {
104
auto ctx = (Context *)png_get_io_ptr(png_ptr);
105
auto &req = *ctx->req;
106
107
// If we buffered some bytes, fill to 3 bytes for a clean base64 encode.
108
// This way we don't have padding.
109
while (length > 0 && ctx->bufSize > 0 && ctx->bufSize != 3) {
110
ctx->buf[ctx->bufSize++] = data[0];
111
data++;
112
length--;
113
}
114
115
if (ctx->bufSize == 3) {
116
req.ws->AddFragment(false, Base64Encode(ctx->buf, ctx->bufSize));
117
ctx->bufSize = 0;
118
}
119
_assert_(ctx->bufSize == 0 || length == 0);
120
121
// Save bytes that would result in padding for next time.
122
size_t toBuffer = length % 3;
123
for (size_t i = 0; i < toBuffer; ++i) {
124
ctx->buf[i] = data[length - toBuffer + i];
125
ctx->bufSize++;
126
}
127
128
if (length > toBuffer) {
129
req.ws->AddFragment(false, Base64Encode(data, length - toBuffer));
130
}
131
};
132
auto flush = [](png_structp png_ptr) {
133
// Nothing, just here to prevent stdio flush.
134
};
135
136
png_bytep *row_pointers = new png_bytep[h];
137
u32 stride = includeAlpha ? w * 4 : w * 3;
138
for (u32 i = 0; i < h; ++i) {
139
row_pointers[i] = (u8 *)buffer + stride * i;
140
}
141
142
png_set_write_fn(png_ptr, &ctx, write, flush);
143
int colorType = includeAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;
144
png_set_IHDR(png_ptr, info_ptr, w, h, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
145
png_set_rows(png_ptr, info_ptr, row_pointers);
146
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
147
148
png_destroy_write_struct(&png_ptr, &info_ptr);
149
delete [] row_pointers;
150
delete [] flipbuffer;
151
152
if (ctx.bufSize > 0) {
153
req.ws->AddFragment(false, Base64Encode(ctx.buf, ctx.bufSize));
154
ctx.bufSize = 0;
155
}
156
157
// End the string.
158
req.ws->AddFragment(false, "\"");
159
return true;
160
#endif
161
}
162
163
static std::string DescribeFormat(GPUDebugBufferFormat fmt) {
164
switch (fmt) {
165
case GPU_DBG_FORMAT_565: return "B5G6R5_UNORM_PACK16";
166
case GPU_DBG_FORMAT_5551: return "A1B5G5R5_UNORM_PACK16";
167
case GPU_DBG_FORMAT_4444: return "A4B4G4R4_UNORM_PACK16";
168
case GPU_DBG_FORMAT_8888: return "R8G8B8A8_UNORM";
169
170
case GPU_DBG_FORMAT_565_REV: return "R5G6B5_UNORM_PACK16";
171
case GPU_DBG_FORMAT_5551_REV: return "R5G5B5A1_UNORM_PACK16";
172
case GPU_DBG_FORMAT_4444_REV: return "R4G4B4A4_UNORM_PACK16";
173
174
case GPU_DBG_FORMAT_5551_BGRA: return "A1R5G5B5_UNORM_PACK16";
175
case GPU_DBG_FORMAT_4444_BGRA: return "A4R4G4B4_UNORM_PACK16";
176
case GPU_DBG_FORMAT_8888_BGRA: return "B8G8R8A8_UNORM";
177
178
case GPU_DBG_FORMAT_FLOAT: return "D32F";
179
case GPU_DBG_FORMAT_16BIT: return "D16";
180
case GPU_DBG_FORMAT_8BIT: return "S8";
181
case GPU_DBG_FORMAT_24BIT_8X: return "D24_X8";
182
case GPU_DBG_FORMAT_24X_8BIT: return "X24_S8";
183
184
case GPU_DBG_FORMAT_FLOAT_DIV_256: return "D32F_DIV_256";
185
case GPU_DBG_FORMAT_24BIT_8X_DIV_256: return "D32F_X8_DIV_256";
186
187
case GPU_DBG_FORMAT_888_RGB: return "R8G8B8_UNORM";
188
189
case GPU_DBG_FORMAT_INVALID:
190
case GPU_DBG_FORMAT_BRSWAP_FLAG:
191
default:
192
return "UNDEFINED";
193
}
194
}
195
196
// Note: Calls req.Respond(). Other data can be added afterward.
197
static bool StreamBufferToBase64(DebuggerRequest &req, const GPUDebugBuffer &buf, bool isFramebuffer) {
198
size_t length = buf.GetStride() * buf.GetHeight();
199
200
auto &json = req.Respond();
201
json.writeInt("width", buf.GetStride());
202
json.writeInt("height", buf.GetHeight());
203
json.writeBool("flipped", buf.GetFlipped());
204
json.writeString("format", DescribeFormat(buf.GetFormat()));
205
if (isFramebuffer) {
206
json.writeBool("isFramebuffer", isFramebuffer);
207
}
208
209
// Start a value without any actual data yet...
210
json.writeRaw("base64", "");
211
req.Flush();
212
213
// Now we'll write it directly to the stream.
214
req.ws->AddFragment(false, "\"");
215
// 65535 is an "even" number of base64 characters.
216
static const size_t CHUNK_SIZE = 65535;
217
for (size_t i = 0; i < length; i += CHUNK_SIZE) {
218
size_t left = std::min(length - i, CHUNK_SIZE);
219
req.ws->AddFragment(false, Base64Encode(buf.GetData() + i, left));
220
}
221
req.ws->AddFragment(false, "\"");
222
223
return true;
224
}
225
226
static void GenericStreamBuffer(DebuggerRequest &req, std::function<bool(const GPUDebugBuffer *&, bool *isFramebuffer)> func) {
227
if (!currentDebugMIPS->isAlive()) {
228
return req.Fail("CPU not started");
229
}
230
if (coreState != CORE_STEPPING_CPU && !GPUStepping::IsStepping()) {
231
return req.Fail("Neither CPU or GPU is stepping");
232
}
233
234
bool includeAlpha = false;
235
if (!req.ParamBool("alpha", &includeAlpha, DebuggerParamType::OPTIONAL))
236
return;
237
u32 stackWidth = 0;
238
if (!req.ParamU32("stackWidth", &stackWidth, false, DebuggerParamType::OPTIONAL))
239
return;
240
std::string type = "uri";
241
if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))
242
return;
243
if (type != "uri" && type != "base64")
244
return req.Fail("Parameter 'type' must be either 'uri' or 'base64'");
245
246
const GPUDebugBuffer *buf = nullptr;
247
bool isFramebuffer = false;
248
if (!func(buf, &isFramebuffer)) {
249
return req.Fail("Could not download output");
250
}
251
_assert_(buf != nullptr);
252
253
if (type == "base64") {
254
StreamBufferToBase64(req, *buf, isFramebuffer);
255
} else if (type == "uri") {
256
StreamBufferToDataURI(req, *buf, isFramebuffer, includeAlpha, stackWidth);
257
} else {
258
_assert_(false);
259
}
260
}
261
262
// Retrieve a screenshot (gpu.buffer.screenshot)
263
//
264
// Parameters:
265
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
266
// - alpha: boolean to include the alpha channel for 'uri' type (not normally useful for screenshots.)
267
// - stackWidth: optional, forced width for 'uri' type (increases height.)
268
//
269
// Response (same event name) for 'uri' type:
270
// - width: numeric width of screenshot.
271
// - height: numeric height of screenshot.
272
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
273
// - uri: data: URI of PNG image for display.
274
//
275
// Response (same event name) for 'base64' type:
276
// - width: numeric width of screenshot (also stride, in pixels, of binary data.)
277
// - height: numeric height of screenshot.
278
// - flipped: boolean to indicate whether buffer is vertically flipped.
279
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
280
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
281
// - base64: base64 encode of binary data.
282
void WebSocketGPUBufferScreenshot(DebuggerRequest &req) {
283
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
284
*isFramebuffer = false;
285
return GPUStepping::GPU_GetOutputFramebuffer(buf);
286
});
287
}
288
289
// Retrieve current color render buffer (gpu.buffer.renderColor)
290
//
291
// Parameters:
292
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
293
// - alpha: boolean to include the alpha channel for 'uri' type.
294
// - stackWidth: optional, forced width for 'uri' type (increases height.)
295
//
296
// Response (same event name) for 'uri' type:
297
// - width: numeric width of render buffer (may include stride.)
298
// - height: numeric height of render buffer.
299
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
300
// - uri: data: URI of PNG image for display.
301
//
302
// Response (same event name) for 'base64' type:
303
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
304
// - height: numeric height of render buffer.
305
// - flipped: boolean to indicate whether buffer is vertically flipped.
306
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
307
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
308
// - base64: base64 encode of binary data.
309
void WebSocketGPUBufferRenderColor(DebuggerRequest &req) {
310
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
311
*isFramebuffer = false;
312
return GPUStepping::GPU_GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);
313
});
314
}
315
316
// Retrieve current depth render buffer (gpu.buffer.renderDepth)
317
//
318
// Parameters:
319
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
320
// - alpha: true to use alpha to encode depth, otherwise red for 'uri' type.
321
// - stackWidth: optional, forced width for 'uri' type (increases height.)
322
//
323
// Response (same event name) for 'uri' type:
324
// - width: numeric width of render buffer (may include stride.)
325
// - height: numeric height of render buffer.
326
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
327
// - uri: data: URI of PNG image for display.
328
//
329
// Response (same event name) for 'base64' type:
330
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
331
// - height: numeric height of render buffer.
332
// - flipped: boolean to indicate whether buffer is vertically flipped.
333
// - format: string indicating format, such as 'D16', 'D24_X8' or 'D32F'.
334
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
335
// - base64: base64 encode of binary data.
336
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req) {
337
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
338
*isFramebuffer = false;
339
return GPUStepping::GPU_GetCurrentDepthbuffer(buf);
340
});
341
}
342
343
// Retrieve current stencil render buffer (gpu.buffer.renderStencil)
344
//
345
// Parameters:
346
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
347
// - alpha: true to use alpha to encode stencil, otherwise red for 'uri' type.
348
// - stackWidth: optional, forced width for 'uri' type (increases height.)
349
//
350
// Response (same event name) for 'uri' type:
351
// - width: numeric width of render buffer (may include stride.)
352
// - height: numeric height of render buffer.
353
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
354
// - uri: data: URI of PNG image for display.
355
//
356
// Response (same event name) for 'base64' type:
357
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
358
// - height: numeric height of render buffer.
359
// - flipped: boolean to indicate whether buffer is vertically flipped.
360
// - format: string indicating format, such as 'X24_S8' or 'S8'.
361
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
362
// - base64: base64 encode of binary data.
363
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req) {
364
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
365
*isFramebuffer = false;
366
return GPUStepping::GPU_GetCurrentStencilbuffer(buf);
367
});
368
}
369
370
// Retrieve current texture (gpu.buffer.texture)
371
//
372
// Parameters:
373
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
374
// - alpha: boolean to include the alpha channel for 'uri' type.
375
// - level: optional texture mip level, default 0.
376
// - stackWidth: optional, forced width for 'uri' type (increases height.)
377
//
378
// Response (same event name) for 'uri' type:
379
// - width: numeric width of the texture (often wider than visual.)
380
// - height: numeric height of the texture (often wider than visual.)
381
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
382
// - uri: data: URI of PNG image for display.
383
//
384
// Response (same event name) for 'base64' type:
385
// - width: numeric width and stride of the texture (often wider than visual.)
386
// - height: numeric height of the texture (often wider than visual.)
387
// - flipped: boolean to indicate whether buffer is vertically flipped.
388
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
389
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
390
// - base64: base64 encode of binary data.
391
void WebSocketGPUBufferTexture(DebuggerRequest &req) {
392
u32 level = 0;
393
if (!req.ParamU32("level", &level, false, DebuggerParamType::OPTIONAL))
394
return;
395
396
GenericStreamBuffer(req, [level](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
397
return GPUStepping::GPU_GetCurrentTexture(buf, level, isFramebuffer);
398
});
399
}
400
401
// Retrieve current CLUT (gpu.buffer.clut)
402
//
403
// Parameters:
404
// - type: either 'uri' or 'base64' (optional, defaults to 'uri').
405
// - alpha: boolean to include the alpha channel for 'uri' type.
406
// - stackWidth: optional, forced width for 'uri' type (increases height.)
407
//
408
// Response (same event name) for 'uri' type:
409
// - width: numeric width of CLUT.
410
// - height: numeric height of CLUT.
411
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
412
// - uri: data: URI of PNG image for display.
413
//
414
// Response (same event name) for 'base64' type:
415
// - width: number of pixels in CLUT.
416
// - height: always 1.
417
// - flipped: boolean to indicate whether buffer is vertically flipped.
418
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
419
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
420
// - base64: base64 encode of binary data.
421
void WebSocketGPUBufferClut(DebuggerRequest &req) {
422
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
423
// TODO: Or maybe it could be?
424
*isFramebuffer = false;
425
return GPUStepping::GPU_GetCurrentClut(buf);
426
});
427
}
428
429