CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Views: 1401
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 && !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'.
266
// - alpha: boolean to include the alpha channel for 'uri' type (not normally useful for screenshots.)
267
//
268
// Response (same event name) for 'uri' type:
269
// - width: numeric width of screenshot.
270
// - height: numeric height of screenshot.
271
// - uri: data: URI of PNG image for display.
272
//
273
// Response (same event name) for 'base64' type:
274
// - width: numeric width of screenshot (also stride, in pixels, of binary data.)
275
// - height: numeric height of screenshot.
276
// - flipped: boolean to indicate whether buffer is vertically flipped.
277
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
278
// - base64: base64 encode of binary data.
279
void WebSocketGPUBufferScreenshot(DebuggerRequest &req) {
280
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
281
*isFramebuffer = false;
282
return GPUStepping::GPU_GetOutputFramebuffer(buf);
283
});
284
}
285
286
// Retrieve current color render buffer (gpu.buffer.renderColor)
287
//
288
// Parameters:
289
// - type: either 'uri' or 'base64'.
290
// - alpha: boolean to include the alpha channel for 'uri' type.
291
//
292
// Response (same event name) for 'uri' type:
293
// - width: numeric width of render buffer (may include stride.)
294
// - height: numeric height of render buffer.
295
// - uri: data: URI of PNG image for display.
296
//
297
// Response (same event name) for 'base64' type:
298
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
299
// - height: numeric height of render buffer.
300
// - flipped: boolean to indicate whether buffer is vertically flipped.
301
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
302
// - base64: base64 encode of binary data.
303
void WebSocketGPUBufferRenderColor(DebuggerRequest &req) {
304
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
305
*isFramebuffer = false;
306
return GPUStepping::GPU_GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);
307
});
308
}
309
310
// Retrieve current depth render buffer (gpu.buffer.renderDepth)
311
//
312
// Parameters:
313
// - type: either 'uri' or 'base64'.
314
// - alpha: true to use alpha to encode depth, otherwise red for 'uri' type.
315
//
316
// Response (same event name) for 'uri' type:
317
// - width: numeric width of render buffer (may include stride.)
318
// - height: numeric height of render buffer.
319
// - uri: data: URI of PNG image for display.
320
//
321
// Response (same event name) for 'base64' type:
322
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
323
// - height: numeric height of render buffer.
324
// - flipped: boolean to indicate whether buffer is vertically flipped.
325
// - format: string indicating format, such as 'D16', 'D24_X8' or 'D32F'.
326
// - base64: base64 encode of binary data.
327
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req) {
328
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
329
*isFramebuffer = false;
330
return GPUStepping::GPU_GetCurrentDepthbuffer(buf);
331
});
332
}
333
334
// Retrieve current stencil render buffer (gpu.buffer.renderStencil)
335
//
336
// Parameters:
337
// - type: either 'uri' or 'base64'.
338
// - alpha: true to use alpha to encode stencil, otherwise red for 'uri' type.
339
//
340
// Response (same event name) for 'uri' type:
341
// - width: numeric width of render buffer (may include stride.)
342
// - height: numeric height of render buffer.
343
// - uri: data: URI of PNG image for display.
344
//
345
// Response (same event name) for 'base64' type:
346
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
347
// - height: numeric height of render buffer.
348
// - flipped: boolean to indicate whether buffer is vertically flipped.
349
// - format: string indicating format, such as 'X24_S8' or 'S8'.
350
// - base64: base64 encode of binary data.
351
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req) {
352
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
353
*isFramebuffer = false;
354
return GPUStepping::GPU_GetCurrentStencilbuffer(buf);
355
});
356
}
357
358
// Retrieve current texture (gpu.buffer.texture)
359
//
360
// Parameters:
361
// - type: either 'uri' or 'base64'.
362
// - alpha: boolean to include the alpha channel for 'uri' type.
363
// - level: texture mip level, default 0.
364
//
365
// Response (same event name) for 'uri' type:
366
// - width: numeric width of the texture (often wider than visual.)
367
// - height: numeric height of the texture (often wider than visual.)
368
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
369
// - uri: data: URI of PNG image for display.
370
//
371
// Response (same event name) for 'base64' type:
372
// - width: numeric width and stride of the texture (often wider than visual.)
373
// - height: numeric height of the texture (often wider than visual.)
374
// - flipped: boolean to indicate whether buffer is vertically flipped.
375
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
376
// - isFramebuffer: optional, present and true if this came from a hardware framebuffer.
377
// - base64: base64 encode of binary data.
378
void WebSocketGPUBufferTexture(DebuggerRequest &req) {
379
u32 level = 0;
380
if (!req.ParamU32("level", &level, false, DebuggerParamType::OPTIONAL))
381
return;
382
383
GenericStreamBuffer(req, [level](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
384
return GPUStepping::GPU_GetCurrentTexture(buf, level, isFramebuffer);
385
});
386
}
387
388
// Retrieve current CLUT (gpu.buffer.clut)
389
//
390
// Parameters:
391
// - type: either 'uri' or 'base64'.
392
// - alpha: boolean to include the alpha channel for 'uri' type.
393
// - stackWidth: forced width for 'uri' type (increases height.)
394
//
395
// Response (same event name) for 'uri' type:
396
// - width: numeric width of CLUT.
397
// - height: numeric height of CLUT.
398
// - uri: data: URI of PNG image for display.
399
//
400
// Response (same event name) for 'base64' type:
401
// - width: number of pixels in CLUT.
402
// - height: always 1.
403
// - flipped: boolean to indicate whether buffer is vertically flipped.
404
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
405
// - base64: base64 encode of binary data.
406
void WebSocketGPUBufferClut(DebuggerRequest &req) {
407
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf, bool *isFramebuffer) {
408
// TODO: Or maybe it could be?
409
*isFramebuffer = false;
410
return GPUStepping::GPU_GetCurrentClut(buf);
411
});
412
}
413
414