mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge pull request #11186 from unknownbrackets/debugger
Debugger: Add APIs to retrieve render image
This commit is contained in:
commit
da5f0f7f2b
22 changed files with 583 additions and 15 deletions
|
@ -1414,6 +1414,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
||||||
Core/Debugger/WebSocket/GameBroadcaster.h
|
Core/Debugger/WebSocket/GameBroadcaster.h
|
||||||
Core/Debugger/WebSocket/GameSubscriber.cpp
|
Core/Debugger/WebSocket/GameSubscriber.cpp
|
||||||
Core/Debugger/WebSocket/GameSubscriber.h
|
Core/Debugger/WebSocket/GameSubscriber.h
|
||||||
|
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
|
||||||
|
Core/Debugger/WebSocket/GPUBufferSubscriber.h
|
||||||
Core/Debugger/WebSocket/HLESubscriber.cpp
|
Core/Debugger/WebSocket/HLESubscriber.cpp
|
||||||
Core/Debugger/WebSocket/HLESubscriber.h
|
Core/Debugger/WebSocket/HLESubscriber.h
|
||||||
Core/Debugger/WebSocket/LogBroadcaster.cpp
|
Core/Debugger/WebSocket/LogBroadcaster.cpp
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
#include "Core/Debugger/Breakpoints.h"
|
#include "Core/Debugger/Breakpoints.h"
|
||||||
#include "Core/MIPS/MIPS.h"
|
#include "Core/MIPS/MIPS.h"
|
||||||
|
#include "GPU/Debugger/Stepping.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "Common/CommonWindows.h"
|
#include "Common/CommonWindows.h"
|
||||||
|
@ -280,6 +281,9 @@ void Core_ProcessStepping() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Or any GPU actions.
|
||||||
|
GPUStepping::SingleStep();
|
||||||
|
|
||||||
// We're not inside jit now, so it's safe to clear the breakpoints.
|
// We're not inside jit now, so it's safe to clear the breakpoints.
|
||||||
CBreakPoints::ClearTemporaryBreakPoints();
|
CBreakPoints::ClearTemporaryBreakPoints();
|
||||||
host->UpdateDisassembly();
|
host->UpdateDisassembly();
|
||||||
|
|
|
@ -189,6 +189,7 @@
|
||||||
<ClCompile Include="Debugger\WebSocket\CPUCoreSubscriber.cpp" />
|
<ClCompile Include="Debugger\WebSocket\CPUCoreSubscriber.cpp" />
|
||||||
<ClCompile Include="Debugger\WebSocket\GameBroadcaster.cpp" />
|
<ClCompile Include="Debugger\WebSocket\GameBroadcaster.cpp" />
|
||||||
<ClCompile Include="Debugger\WebSocket\GameSubscriber.cpp" />
|
<ClCompile Include="Debugger\WebSocket\GameSubscriber.cpp" />
|
||||||
|
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp" />
|
||||||
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
|
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
|
||||||
<ClCompile Include="Debugger\WebSocket\LogBroadcaster.cpp" />
|
<ClCompile Include="Debugger\WebSocket\LogBroadcaster.cpp" />
|
||||||
<ClCompile Include="Debugger\WebSocket\DisasmSubscriber.cpp" />
|
<ClCompile Include="Debugger\WebSocket\DisasmSubscriber.cpp" />
|
||||||
|
@ -547,6 +548,7 @@
|
||||||
<ClInclude Include="Debugger\WebSocket\BreakpointSubscriber.h" />
|
<ClInclude Include="Debugger\WebSocket\BreakpointSubscriber.h" />
|
||||||
<ClInclude Include="Debugger\WebSocket\GameSubscriber.h" />
|
<ClInclude Include="Debugger\WebSocket\GameSubscriber.h" />
|
||||||
<ClInclude Include="Debugger\WebSocket\DisasmSubscriber.h" />
|
<ClInclude Include="Debugger\WebSocket\DisasmSubscriber.h" />
|
||||||
|
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h" />
|
||||||
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
|
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
|
||||||
<ClInclude Include="Debugger\WebSocket\SteppingSubscriber.h" />
|
<ClInclude Include="Debugger\WebSocket\SteppingSubscriber.h" />
|
||||||
<ClInclude Include="Debugger\WebSocket\WebSocketUtils.h" />
|
<ClInclude Include="Debugger\WebSocket\WebSocketUtils.h" />
|
||||||
|
|
|
@ -728,6 +728,9 @@
|
||||||
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp">
|
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp">
|
||||||
<Filter>Debugger\WebSocket</Filter>
|
<Filter>Debugger\WebSocket</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp">
|
||||||
|
<Filter>Debugger\WebSocket</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="ELF\ElfReader.h">
|
<ClInclude Include="ELF\ElfReader.h">
|
||||||
|
@ -1346,6 +1349,9 @@
|
||||||
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h">
|
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h">
|
||||||
<Filter>Debugger\WebSocket</Filter>
|
<Filter>Debugger\WebSocket</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h">
|
||||||
|
<Filter>Debugger\WebSocket</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="CMakeLists.txt" />
|
<None Include="CMakeLists.txt" />
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
|
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
|
||||||
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
|
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
|
||||||
#include "Core/Debugger/WebSocket/GameSubscriber.h"
|
#include "Core/Debugger/WebSocket/GameSubscriber.h"
|
||||||
|
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
|
||||||
#include "Core/Debugger/WebSocket/HLESubscriber.h"
|
#include "Core/Debugger/WebSocket/HLESubscriber.h"
|
||||||
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
|
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ static const std::vector<SubscriberInfo> subscribers({
|
||||||
{ &WebSocketCPUCoreInit, nullptr },
|
{ &WebSocketCPUCoreInit, nullptr },
|
||||||
{ &WebSocketDisasmInit, &WebSocketDisasmShutdown },
|
{ &WebSocketDisasmInit, &WebSocketDisasmShutdown },
|
||||||
{ &WebSocketGameInit, nullptr },
|
{ &WebSocketGameInit, nullptr },
|
||||||
|
{ &WebSocketGPUBufferInit, nullptr },
|
||||||
{ &WebSocketHLEInit, nullptr },
|
{ &WebSocketHLEInit, nullptr },
|
||||||
{ &WebSocketSteppingInit, &WebSocketSteppingShutdown },
|
{ &WebSocketSteppingInit, &WebSocketSteppingShutdown },
|
||||||
});
|
});
|
||||||
|
|
|
@ -121,6 +121,18 @@ struct WebSocketCPUBreakpointParams {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a new CPU instruction breakpoint (cpu.breakpoint.add)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address of instruction to break at.
|
||||||
|
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
|
||||||
|
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
|
//
|
||||||
|
// Note: will replace any breakpoint at the same address.
|
||||||
void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
|
void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
|
||||||
WebSocketCPUBreakpointParams params;
|
WebSocketCPUBreakpointParams params;
|
||||||
if (!params.Parse(req))
|
if (!params.Parse(req))
|
||||||
|
@ -131,6 +143,16 @@ void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update a CPU instruction breakpoint (cpu.breakpoint.update)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address of instruction to break at.
|
||||||
|
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
|
||||||
|
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
|
void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
|
||||||
WebSocketCPUBreakpointParams params;
|
WebSocketCPUBreakpointParams params;
|
||||||
if (!params.Parse(req))
|
if (!params.Parse(req))
|
||||||
|
@ -143,6 +165,12 @@ void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove a CPU instruction breakpoint (cpu.breakpoint.remove)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address of instruction to break at.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
|
void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
|
||||||
if (!currentDebugMIPS->isAlive()) {
|
if (!currentDebugMIPS->isAlive()) {
|
||||||
return req.Fail("CPU not started");
|
return req.Fail("CPU not started");
|
||||||
|
@ -156,6 +184,19 @@ void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List all CPU instruction breakpoints (cpu.breakpoint.list)
|
||||||
|
//
|
||||||
|
// No parameters.
|
||||||
|
//
|
||||||
|
// Response (same event name):
|
||||||
|
// - breakpoints: array of objects, each with properties:
|
||||||
|
// - address: unsigned integer address of instruction to break at.
|
||||||
|
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - condition: null, or string expression to evaluate - breakpoint does not trip if false.
|
||||||
|
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
// - symbol: null, or string label or symbol at breakpoint address.
|
||||||
|
// - code: string disassembly of breakpoint address.
|
||||||
void WebSocketCPUBreakpointList(DebuggerRequest &req) {
|
void WebSocketCPUBreakpointList(DebuggerRequest &req) {
|
||||||
if (!currentDebugMIPS->isAlive()) {
|
if (!currentDebugMIPS->isAlive()) {
|
||||||
return req.Fail("CPU not started");
|
return req.Fail("CPU not started");
|
||||||
|
@ -278,6 +319,22 @@ struct WebSocketMemoryBreakpointParams {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a new memory breakpoint (memory.breakpoint.add)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address for the start of the memory range.
|
||||||
|
// - size: unsigned integer specifying size of memory range.
|
||||||
|
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - read: optional boolean, whether to trip on any read to this address.
|
||||||
|
// - write: optional boolean, whether to trip on any write to this address.
|
||||||
|
// - change: optional boolean, whether to trip on a write to this address which modifies data
|
||||||
|
// (or any write that may modify data.)
|
||||||
|
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
|
//
|
||||||
|
// Note: will replace any breakpoint that has the same start address and size.
|
||||||
void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
|
void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
|
||||||
WebSocketMemoryBreakpointParams params;
|
WebSocketMemoryBreakpointParams params;
|
||||||
if (!params.Parse(req))
|
if (!params.Parse(req))
|
||||||
|
@ -288,6 +345,20 @@ void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update a memory breakpoint (memory.breakpoint.update)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address for the start of the memory range.
|
||||||
|
// - size: unsigned integer specifying size of memory range.
|
||||||
|
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - read: optional boolean, whether to trip on any read to this address.
|
||||||
|
// - write: optional boolean, whether to trip on any write to this address.
|
||||||
|
// - change: optional boolean, whether to trip on a write to this address which modifies data
|
||||||
|
// (or any write that may modify data.)
|
||||||
|
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
|
void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
|
||||||
WebSocketMemoryBreakpointParams params;
|
WebSocketMemoryBreakpointParams params;
|
||||||
if (!params.Parse(req))
|
if (!params.Parse(req))
|
||||||
|
@ -302,6 +373,13 @@ void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove a memory breakpoint (memory.breakpoint.remove)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - address: unsigned integer address for the start of the memory range.
|
||||||
|
// - size: unsigned integer specifying size of memory range.
|
||||||
|
//
|
||||||
|
// Response (same event name) with no extra data.
|
||||||
void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
|
void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
|
||||||
if (!currentDebugMIPS->isAlive()) {
|
if (!currentDebugMIPS->isAlive()) {
|
||||||
return req.Fail("CPU not started");
|
return req.Fail("CPU not started");
|
||||||
|
@ -318,6 +396,22 @@ void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
|
||||||
req.Respond();
|
req.Respond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List all memory breakpoints (memory.breakpoint.list)
|
||||||
|
//
|
||||||
|
// No parameters.
|
||||||
|
//
|
||||||
|
// Response (same event name):
|
||||||
|
// - breakpoints: array of objects, each with properties:
|
||||||
|
// - address: unsigned integer address for the start of the memory range.
|
||||||
|
// - size: unsigned integer specifying size of memory range.
|
||||||
|
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
|
||||||
|
// - log: optional boolean, whether to log when this breakpoint trips.
|
||||||
|
// - read: optional boolean, whether to trip on any read to this address.
|
||||||
|
// - write: optional boolean, whether to trip on any write to this address.
|
||||||
|
// - change: optional boolean, whether to trip on a write to this address which modifies data
|
||||||
|
// (or any write that may modify data.)
|
||||||
|
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
|
||||||
|
// - symbol: null, or string label or symbol at breakpoint address.
|
||||||
void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
|
void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
|
||||||
if (!currentDebugMIPS->isAlive()) {
|
if (!currentDebugMIPS->isAlive()) {
|
||||||
return req.Fail("CPU not started");
|
return req.Fail("CPU not started");
|
||||||
|
|
332
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Normal file
332
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
// 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 <algorithm>
|
||||||
|
#ifndef USING_QT_UI
|
||||||
|
#include <libpng17/png.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#endif
|
||||||
|
#include "data/base64.h"
|
||||||
|
#include "Common/StringUtils.h"
|
||||||
|
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
|
||||||
|
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
||||||
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
||||||
|
#include "Core/Screenshot.h"
|
||||||
|
#include "GPU/Debugger/Stepping.h"
|
||||||
|
|
||||||
|
void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map) {
|
||||||
|
// No need to bind or alloc state, these are all global.
|
||||||
|
map["gpu.buffer.screenshot"] = &WebSocketGPUBufferScreenshot;
|
||||||
|
map["gpu.buffer.renderColor"] = &WebSocketGPUBufferRenderColor;
|
||||||
|
map["gpu.buffer.renderDepth"] = &WebSocketGPUBufferRenderDepth;
|
||||||
|
map["gpu.buffer.renderStencil"] = &WebSocketGPUBufferRenderStencil;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Calls req.Respond(). Other data can be added afterward.
|
||||||
|
static bool StreamBufferToDataURI(DebuggerRequest &req, const GPUDebugBuffer &buf, bool includeAlpha) {
|
||||||
|
#ifdef USING_QT_UI
|
||||||
|
req.Fail("Not supported on Qt yet, pull requests accepted");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
u8 *flipbuffer = nullptr;
|
||||||
|
u32 w = (u32)-1;
|
||||||
|
u32 h = (u32)-1;
|
||||||
|
const u8 *buffer = ConvertBufferToScreenshot(buf, includeAlpha, flipbuffer, w, h);
|
||||||
|
if (!buffer) {
|
||||||
|
req.Fail("Internal error converting buffer for PNG encode");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
if (!png_ptr) {
|
||||||
|
req.Fail("Internal error setting up PNG encoder (png_ptr)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||||
|
if (!info_ptr) {
|
||||||
|
png_destroy_write_struct(&png_ptr, nullptr);
|
||||||
|
req.Fail("Internal error setting up PNG encoder (info_ptr)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed. Wireless N should give 35 KB/ms. For most devices, zlib/filters will cost more.
|
||||||
|
png_set_compression_strategy(png_ptr, Z_RLE);
|
||||||
|
png_set_compression_level(png_ptr, 1);
|
||||||
|
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
|
||||||
|
|
||||||
|
auto &json = req.Respond();
|
||||||
|
json.writeInt("width", w);
|
||||||
|
json.writeInt("height", h);
|
||||||
|
|
||||||
|
// Start a value...
|
||||||
|
json.writeRaw("uri", "");
|
||||||
|
req.Flush();
|
||||||
|
// Now we'll write it directly to the stream.
|
||||||
|
req.ws->AddFragment(false, "\"data:image/png;base64,");
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
DebuggerRequest *req;
|
||||||
|
uint8_t buf[3];
|
||||||
|
size_t bufSize;
|
||||||
|
};
|
||||||
|
Context ctx = { &req, {}, 0 };
|
||||||
|
|
||||||
|
auto write = [](png_structp png_ptr, png_bytep data, png_size_t length) {
|
||||||
|
auto ctx = (Context *)png_get_io_ptr(png_ptr);
|
||||||
|
auto &req = *ctx->req;
|
||||||
|
|
||||||
|
// If we buffered some bytes, fill to 3 bytes for a clean base64 encode.
|
||||||
|
// This way we don't have padding.
|
||||||
|
while (length > 0 && ctx->bufSize > 0 && ctx->bufSize != 3) {
|
||||||
|
ctx->buf[ctx->bufSize++] = data[0];
|
||||||
|
data++;
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->bufSize == 3) {
|
||||||
|
req.ws->AddFragment(false, Base64Encode(ctx->buf, ctx->bufSize));
|
||||||
|
ctx->bufSize = 0;
|
||||||
|
}
|
||||||
|
assert(ctx->bufSize == 0 || length == 0);
|
||||||
|
|
||||||
|
// Save bytes that would result in padding for next time.
|
||||||
|
size_t toBuffer = length % 3;
|
||||||
|
for (size_t i = 0; i < toBuffer; ++i) {
|
||||||
|
ctx->buf[i] = data[length - toBuffer + i];
|
||||||
|
ctx->bufSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > toBuffer) {
|
||||||
|
req.ws->AddFragment(false, Base64Encode(data, length - toBuffer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto flush = [](png_structp png_ptr) {
|
||||||
|
// Nothing, just here to prevent stdio flush.
|
||||||
|
};
|
||||||
|
|
||||||
|
png_bytep *row_pointers = new png_bytep[h];
|
||||||
|
u32 stride = includeAlpha ? w * 4 : w * 3;
|
||||||
|
for (u32 i = 0; i < h; ++i) {
|
||||||
|
row_pointers[i] = (u8 *)buffer + stride * i;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_set_write_fn(png_ptr, &ctx, write, flush);
|
||||||
|
int colorType = includeAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;
|
||||||
|
png_set_IHDR(png_ptr, info_ptr, w, h, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
png_set_rows(png_ptr, info_ptr, row_pointers);
|
||||||
|
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
|
||||||
|
|
||||||
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
|
delete [] row_pointers;
|
||||||
|
delete [] flipbuffer;
|
||||||
|
|
||||||
|
if (ctx.bufSize > 0) {
|
||||||
|
req.ws->AddFragment(false, Base64Encode(ctx.buf, ctx.bufSize));
|
||||||
|
ctx.bufSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the string.
|
||||||
|
req.ws->AddFragment(false, "\"");
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string DescribeFormat(GPUDebugBufferFormat fmt) {
|
||||||
|
switch (fmt) {
|
||||||
|
case GPU_DBG_FORMAT_565: return "B5G6R5_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_5551: return "A1B5G5R5_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_4444: return "A4B4G4R4_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_8888: return "R8G8B8A8_UNORM";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_565_REV: return "R5G6B5_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_5551_REV: return "R5G5B5A1_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_4444_REV: return "R4G4B4A4_UNORM_PACK16";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_5551_BGRA: return "A1R5G5B5_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_4444_BGRA: return "A4R4G4B4_UNORM_PACK16";
|
||||||
|
case GPU_DBG_FORMAT_8888_BGRA: return "B8G8R8A8_UNORM";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_FLOAT: return "D32F";
|
||||||
|
case GPU_DBG_FORMAT_16BIT: return "D16";
|
||||||
|
case GPU_DBG_FORMAT_8BIT: return "S8";
|
||||||
|
case GPU_DBG_FORMAT_24BIT_8X: return "D24_X8";
|
||||||
|
case GPU_DBG_FORMAT_24X_8BIT: return "X24_S8";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_FLOAT_DIV_256: return "D32F_DIV_256";
|
||||||
|
case GPU_DBG_FORMAT_24BIT_8X_DIV_256: return "D32F_X8_DIV_256";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_888_RGB: return "R8G8B8_UNORM";
|
||||||
|
|
||||||
|
case GPU_DBG_FORMAT_INVALID:
|
||||||
|
case GPU_DBG_FORMAT_BRSWAP_FLAG:
|
||||||
|
default:
|
||||||
|
return "UNDEFINED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Calls req.Respond(). Other data can be added afterward.
|
||||||
|
static bool StreamBufferToBase64(DebuggerRequest &req, const GPUDebugBuffer &buf) {
|
||||||
|
size_t length = buf.GetStride() * buf.GetHeight();
|
||||||
|
|
||||||
|
auto &json = req.Respond();
|
||||||
|
json.writeInt("width", buf.GetStride());
|
||||||
|
json.writeInt("height", buf.GetHeight());
|
||||||
|
json.writeBool("flipped", buf.GetFlipped());
|
||||||
|
json.writeString("format", DescribeFormat(buf.GetFormat()));
|
||||||
|
|
||||||
|
// Start a value without any actual data yet...
|
||||||
|
json.writeRaw("base64", "");
|
||||||
|
req.Flush();
|
||||||
|
|
||||||
|
// Now we'll write it directly to the stream.
|
||||||
|
req.ws->AddFragment(false, "\"");
|
||||||
|
// 65535 is an "even" number of base64 characters.
|
||||||
|
static const size_t CHUNK_SIZE = 65535;
|
||||||
|
for (size_t i = 0; i < length; i += CHUNK_SIZE) {
|
||||||
|
size_t left = std::min(length - i, CHUNK_SIZE);
|
||||||
|
req.ws->AddFragment(false, Base64Encode(buf.GetData() + i, left));
|
||||||
|
}
|
||||||
|
req.ws->AddFragment(false, "\"");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenericStreamBuffer(DebuggerRequest &req, std::function<bool(const GPUDebugBuffer *&)> func) {
|
||||||
|
if (!currentDebugMIPS->isAlive()) {
|
||||||
|
return req.Fail("CPU not started");
|
||||||
|
}
|
||||||
|
if (coreState != CORE_STEPPING && !GPUStepping::IsStepping()) {
|
||||||
|
return req.Fail("Neither CPU or GPU is stepping");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool includeAlpha = false;
|
||||||
|
if (!req.ParamBool("alpha", &includeAlpha, DebuggerParamType::OPTIONAL))
|
||||||
|
return;
|
||||||
|
std::string type = "uri";
|
||||||
|
if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))
|
||||||
|
return;
|
||||||
|
if (type != "uri" && type != "base64")
|
||||||
|
return req.Fail("Parameter 'type' must be either 'uri' or 'base64'");
|
||||||
|
|
||||||
|
const GPUDebugBuffer *buf = nullptr;
|
||||||
|
if (!func(buf)) {
|
||||||
|
return req.Fail("Could not download output");
|
||||||
|
}
|
||||||
|
assert(buf != nullptr);
|
||||||
|
|
||||||
|
if (type == "base64") {
|
||||||
|
StreamBufferToBase64(req, *buf);
|
||||||
|
} else if (type == "uri") {
|
||||||
|
StreamBufferToDataURI(req, *buf, includeAlpha);
|
||||||
|
} else {
|
||||||
|
_assert_(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a screenshot (gpu.buffer.screenshot)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - type: either 'uri' or 'base64'.
|
||||||
|
// - alpha: boolean to include the alpha channel for 'uri' type (not normally useful for screenshots.)
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'uri' type:
|
||||||
|
// - width: numeric width of screenshot.
|
||||||
|
// - height: numeric height of screenshot.
|
||||||
|
// - uri: data: URI of PNG image for display.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'base64' type:
|
||||||
|
// - width: numeric width of screenshot (also stride, in pixels, of binary data.)
|
||||||
|
// - height: numeric height of screenshot.
|
||||||
|
// - flipped: boolean to indicate whether buffer is vertically flipped.
|
||||||
|
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
|
||||||
|
// - base64: base64 encode of binary data.
|
||||||
|
void WebSocketGPUBufferScreenshot(DebuggerRequest &req) {
|
||||||
|
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
|
||||||
|
return GPUStepping::GPU_GetOutputFramebuffer(buf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve current color render buffer (gpu.buffer.renderColor)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - type: either 'uri' or 'base64'.
|
||||||
|
// - alpha: boolean to include the alpha channel for 'uri' type.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'uri' type:
|
||||||
|
// - width: numeric width of render buffer (may include stride.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - uri: data: URI of PNG image for display.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'base64' type:
|
||||||
|
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - flipped: boolean to indicate whether buffer is vertically flipped.
|
||||||
|
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
|
||||||
|
// - base64: base64 encode of binary data.
|
||||||
|
void WebSocketGPUBufferRenderColor(DebuggerRequest &req) {
|
||||||
|
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
|
||||||
|
return GPUStepping::GPU_GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve current depth render buffer (gpu.buffer.renderDepth)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - type: either 'uri' or 'base64'.
|
||||||
|
// - alpha: true to use alpha to encode depth, otherwise red for 'uri' type.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'uri' type:
|
||||||
|
// - width: numeric width of render buffer (may include stride.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - uri: data: URI of PNG image for display.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'base64' type:
|
||||||
|
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - flipped: boolean to indicate whether buffer is vertically flipped.
|
||||||
|
// - format: string indicating format, such as 'D16', 'D24_X8' or 'D32F'.
|
||||||
|
// - base64: base64 encode of binary data.
|
||||||
|
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req) {
|
||||||
|
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
|
||||||
|
return GPUStepping::GPU_GetCurrentDepthbuffer(buf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve current stencil render buffer (gpu.buffer.renderStencil)
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - type: either 'uri' or 'base64'.
|
||||||
|
// - alpha: true to use alpha to encode stencil, otherwise red for 'uri' type.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'uri' type:
|
||||||
|
// - width: numeric width of render buffer (may include stride.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - uri: data: URI of PNG image for display.
|
||||||
|
//
|
||||||
|
// Response (same event name) for 'base64' type:
|
||||||
|
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
|
||||||
|
// - height: numeric height of render buffer.
|
||||||
|
// - flipped: boolean to indicate whether buffer is vertically flipped.
|
||||||
|
// - format: string indicating format, such as 'X24_S8' or 'S8'.
|
||||||
|
// - base64: base64 encode of binary data.
|
||||||
|
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req) {
|
||||||
|
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
|
||||||
|
return GPUStepping::GPU_GetCurrentStencilbuffer(buf);
|
||||||
|
});
|
||||||
|
}
|
27
Core/Debugger/WebSocket/GPUBufferSubscriber.h
Normal file
27
Core/Debugger/WebSocket/GPUBufferSubscriber.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// 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/.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
||||||
|
|
||||||
|
void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map);
|
||||||
|
|
||||||
|
void WebSocketGPUBufferScreenshot(DebuggerRequest &req);
|
||||||
|
void WebSocketGPUBufferRenderColor(DebuggerRequest &req);
|
||||||
|
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req);
|
||||||
|
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req);
|
|
@ -1937,7 +1937,7 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB
|
||||||
|
|
||||||
// TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow?
|
// TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow?
|
||||||
bool flipY = (GetGPUBackend() == GPUBackend::OPENGL && !useBufferedRendering_) ? true : false;
|
bool flipY = (GetGPUBackend() == GPUBackend::OPENGL && !useBufferedRendering_) ? true : false;
|
||||||
buffer.Allocate(w, h, GE_FORMAT_8888, flipY, true);
|
buffer.Allocate(w, h, GE_FORMAT_8888, flipY);
|
||||||
bool retval = draw_->CopyFramebufferToMemorySync(bound, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w);
|
bool retval = draw_->CopyFramebufferToMemorySync(bound, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w);
|
||||||
gpuStats.numReadbacks++;
|
gpuStats.numReadbacks++;
|
||||||
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
|
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
|
||||||
|
@ -2015,8 +2015,12 @@ bool FramebufferManagerCommon::GetStencilbuffer(u32 fb_address, int fb_stride, G
|
||||||
bool FramebufferManagerCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) {
|
bool FramebufferManagerCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) {
|
||||||
int w, h;
|
int w, h;
|
||||||
draw_->GetFramebufferDimensions(nullptr, &w, &h);
|
draw_->GetFramebufferDimensions(nullptr, &w, &h);
|
||||||
buffer.Allocate(w, h, GE_FORMAT_8888, false, true);
|
Draw::DataFormat fmt = draw_->PreferredFramebufferReadbackFormat(nullptr);
|
||||||
bool retval = draw_->CopyFramebufferToMemorySync(nullptr, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w);
|
// Ignore preferred formats other than BGRA.
|
||||||
|
if (fmt != Draw::DataFormat::B8G8R8A8_UNORM)
|
||||||
|
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
|
||||||
|
buffer.Allocate(w, h, fmt == Draw::DataFormat::R8G8B8A8_UNORM ? GPU_DBG_FORMAT_8888 : GPU_DBG_FORMAT_8888_BGRA, false);
|
||||||
|
bool retval = draw_->CopyFramebufferToMemorySync(nullptr, Draw::FB_COLOR_BIT, 0, 0, w, h, fmt, buffer.GetData(), w);
|
||||||
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
|
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
|
||||||
RebindFramebuffer();
|
RebindFramebuffer();
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
@ -28,6 +28,7 @@ namespace GPUStepping {
|
||||||
enum PauseAction {
|
enum PauseAction {
|
||||||
PAUSE_CONTINUE,
|
PAUSE_CONTINUE,
|
||||||
PAUSE_BREAK,
|
PAUSE_BREAK,
|
||||||
|
PAUSE_GETOUTPUTBUF,
|
||||||
PAUSE_GETFRAMEBUF,
|
PAUSE_GETFRAMEBUF,
|
||||||
PAUSE_GETDEPTHBUF,
|
PAUSE_GETDEPTHBUF,
|
||||||
PAUSE_GETSTENCILBUF,
|
PAUSE_GETSTENCILBUF,
|
||||||
|
@ -66,6 +67,9 @@ static void SetPauseAction(PauseAction act, bool waitComplete = true) {
|
||||||
pauseAction = act;
|
pauseAction = act;
|
||||||
pauseLock.unlock();
|
pauseLock.unlock();
|
||||||
|
|
||||||
|
if (coreState == CORE_STEPPING && act != PAUSE_CONTINUE)
|
||||||
|
Core_UpdateSingleStep();
|
||||||
|
|
||||||
actionComplete = false;
|
actionComplete = false;
|
||||||
pauseWait.notify_all();
|
pauseWait.notify_all();
|
||||||
while (waitComplete && !actionComplete) {
|
while (waitComplete && !actionComplete) {
|
||||||
|
@ -84,6 +88,10 @@ static void RunPauseAction() {
|
||||||
case PAUSE_BREAK:
|
case PAUSE_BREAK:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PAUSE_GETOUTPUTBUF:
|
||||||
|
bufferResult = gpuDebug->GetOutputFramebuffer(bufferFrame);
|
||||||
|
break;
|
||||||
|
|
||||||
case PAUSE_GETFRAMEBUF:
|
case PAUSE_GETFRAMEBUF:
|
||||||
bufferResult = gpuDebug->GetCurrentFramebuffer(bufferFrame, bufferType);
|
bufferResult = gpuDebug->GetCurrentFramebuffer(bufferFrame, bufferType);
|
||||||
break;
|
break;
|
||||||
|
@ -117,13 +125,41 @@ static void RunPauseAction() {
|
||||||
pauseAction = PAUSE_BREAK;
|
pauseAction = PAUSE_BREAK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SingleStep() {
|
||||||
|
std::unique_lock<std::mutex> guard(pauseLock);
|
||||||
|
if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && coreState != CORE_STEPPING) {
|
||||||
|
// Shutting down, don't try to step.
|
||||||
|
actionComplete = true;
|
||||||
|
actionWait.notify_all();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!gpuDebug || pauseAction == PAUSE_CONTINUE) {
|
||||||
|
actionComplete = true;
|
||||||
|
actionWait.notify_all();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpuDebug->NotifySteppingEnter();
|
||||||
|
isStepping = true;
|
||||||
|
|
||||||
|
RunPauseAction();
|
||||||
|
|
||||||
|
gpuDebug->NotifySteppingExit();
|
||||||
|
isStepping = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool EnterStepping(std::function<void()> callback) {
|
bool EnterStepping(std::function<void()> callback) {
|
||||||
std::unique_lock<std::mutex> guard(pauseLock);
|
std::unique_lock<std::mutex> guard(pauseLock);
|
||||||
if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME) {
|
if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME) {
|
||||||
// Shutting down, don't try to step.
|
// Shutting down, don't try to step.
|
||||||
|
actionComplete = true;
|
||||||
|
actionWait.notify_all();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!gpuDebug) {
|
if (!gpuDebug) {
|
||||||
|
actionComplete = true;
|
||||||
|
actionWait.notify_all();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +188,7 @@ bool IsStepping() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPUDebugBuffer &resultBuffer) {
|
static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPUDebugBuffer &resultBuffer) {
|
||||||
if (!isStepping) {
|
if (!isStepping && coreState != CORE_STEPPING) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +197,10 @@ static bool GetBuffer(const GPUDebugBuffer *&buffer, PauseAction type, const GPU
|
||||||
return bufferResult;
|
return bufferResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GPU_GetOutputFramebuffer(const GPUDebugBuffer *&buffer) {
|
||||||
|
return GetBuffer(buffer, PAUSE_GETOUTPUTBUF, bufferFrame);
|
||||||
|
}
|
||||||
|
|
||||||
bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type) {
|
bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type) {
|
||||||
bufferType = type;
|
bufferType = type;
|
||||||
return GetBuffer(buffer, PAUSE_GETFRAMEBUF, bufferFrame);
|
return GetBuffer(buffer, PAUSE_GETFRAMEBUF, bufferFrame);
|
||||||
|
@ -184,7 +224,7 @@ bool GPU_GetCurrentClut(const GPUDebugBuffer *&buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU_SetCmdValue(u32 op) {
|
bool GPU_SetCmdValue(u32 op) {
|
||||||
if (!isStepping) {
|
if (!isStepping && coreState != CORE_STEPPING) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,10 @@ namespace GPUStepping {
|
||||||
// Begins stepping and calls callback while inside a lock preparing stepping.
|
// Begins stepping and calls callback while inside a lock preparing stepping.
|
||||||
// This would be a good place to deliver a message to code that stepping is ready.
|
// This would be a good place to deliver a message to code that stepping is ready.
|
||||||
bool EnterStepping(std::function<void()> callback);
|
bool EnterStepping(std::function<void()> callback);
|
||||||
|
bool SingleStep();
|
||||||
bool IsStepping();
|
bool IsStepping();
|
||||||
|
|
||||||
|
bool GPU_GetOutputFramebuffer(const GPUDebugBuffer *&buffer);
|
||||||
bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type);
|
bool GPU_GetCurrentFramebuffer(const GPUDebugBuffer *&buffer, GPUDebugFramebufferType type);
|
||||||
bool GPU_GetCurrentDepthbuffer(const GPUDebugBuffer *&buffer);
|
bool GPU_GetCurrentDepthbuffer(const GPUDebugBuffer *&buffer);
|
||||||
bool GPU_GetCurrentStencilbuffer(const GPUDebugBuffer *&buffer);
|
bool GPU_GetCurrentStencilbuffer(const GPUDebugBuffer *&buffer);
|
||||||
|
|
|
@ -307,6 +307,7 @@ EXEC_AND_LIB_FILES := \
|
||||||
$(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \
|
$(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \
|
||||||
$(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \
|
$(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \
|
||||||
$(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \
|
$(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \
|
||||||
|
$(SRC)/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp \
|
||||||
$(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \
|
$(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \
|
||||||
$(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \
|
$(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \
|
||||||
$(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \
|
$(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
std::string Base64Encode(const uint8_t *p, size_t sz) {
|
std::string Base64Encode(const uint8_t *p, size_t sz) {
|
||||||
const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
size_t unpaddedLength = (4 * sz + 3) / 3;
|
size_t unpaddedLength = (4 * sz + 2) / 3;
|
||||||
std::string result;
|
std::string result;
|
||||||
result.resize((unpaddedLength + 3) & ~3, '=');
|
result.resize((unpaddedLength + 3) & ~3, '=');
|
||||||
|
|
||||||
|
|
|
@ -341,6 +341,12 @@ bool OutputSink::Flush(bool allowBlock) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OutputSink::Discard() {
|
||||||
|
read_ = 0;
|
||||||
|
write_ = 0;
|
||||||
|
valid_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void OutputSink::Drain() {
|
void OutputSink::Drain() {
|
||||||
// Avoid small reads if possible.
|
// Avoid small reads if possible.
|
||||||
if (valid_ > PRESSURE) {
|
if (valid_ > PRESSURE) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
bool Printf(const char *fmt, ...);
|
bool Printf(const char *fmt, ...);
|
||||||
|
|
||||||
bool Flush(bool allowBlock = true);
|
bool Flush(bool allowBlock = true);
|
||||||
|
void Discard();
|
||||||
|
|
||||||
bool Empty();
|
bool Empty();
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,8 @@ bool WebSocketServer::Process(float timeout) {
|
||||||
// Since select said it was readable, we assume this means disconnect.
|
// Since select said it was readable, we assume this means disconnect.
|
||||||
closeReason_ = WebSocketClose::ABNORMAL;
|
closeReason_ = WebSocketClose::ABNORMAL;
|
||||||
open_ = false;
|
open_ = false;
|
||||||
|
// Kill any remaining output too.
|
||||||
|
out_->Discard();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1085,13 +1085,27 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) {
|
||||||
|
|
||||||
CHECK_GL_ERROR_IF_DEBUG();
|
CHECK_GL_ERROR_IF_DEBUG();
|
||||||
|
|
||||||
// Always read back in 8888 format.
|
// Always read back in 8888 format for the color aspect.
|
||||||
const GLuint internalFormat = GL_RGBA;
|
GLuint internalFormat = GL_RGBA;
|
||||||
const GLuint format = GL_RGBA;
|
GLuint format = GL_RGBA;
|
||||||
const GLuint type = GL_UNSIGNED_BYTE;
|
GLuint type = GL_UNSIGNED_BYTE;
|
||||||
const int srcAlignment = 4;
|
int srcAlignment = 4;
|
||||||
int dstAlignment = (int)DataFormatSizeInBytes(pass.readback.dstFormat);
|
int dstAlignment = (int)DataFormatSizeInBytes(pass.readback.dstFormat);
|
||||||
|
|
||||||
|
#ifndef USING_GLES2
|
||||||
|
if (pass.readback.aspectMask & GL_DEPTH_BUFFER_BIT) {
|
||||||
|
internalFormat = GL_DEPTH_COMPONENT;
|
||||||
|
format = GL_DEPTH_COMPONENT;
|
||||||
|
type = GL_FLOAT;
|
||||||
|
srcAlignment = 4;
|
||||||
|
} else if (pass.readback.aspectMask & GL_STENCIL_BUFFER_BIT) {
|
||||||
|
internalFormat = GL_STENCIL_INDEX;
|
||||||
|
format = GL_STENCIL_INDEX;
|
||||||
|
type = GL_UNSIGNED_BYTE;
|
||||||
|
srcAlignment = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int pixelStride = pass.readback.srcRect.w;
|
int pixelStride = pass.readback.srcRect.w;
|
||||||
// Apply the correct alignment.
|
// Apply the correct alignment.
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, srcAlignment);
|
glPixelStorei(GL_PACK_ALIGNMENT, srcAlignment);
|
||||||
|
@ -1102,7 +1116,7 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) {
|
||||||
|
|
||||||
GLRect2D rect = pass.readback.srcRect;
|
GLRect2D rect = pass.readback.srcRect;
|
||||||
|
|
||||||
bool convert = pass.readback.dstFormat != DataFormat::R8G8B8A8_UNORM;
|
bool convert = internalFormat == GL_RGBA && pass.readback.dstFormat != DataFormat::R8G8B8A8_UNORM;
|
||||||
|
|
||||||
int tempSize = srcAlignment * rect.w * rect.h;
|
int tempSize = srcAlignment * rect.w * rect.h;
|
||||||
int readbackSize = dstAlignment * rect.w * rect.h;
|
int readbackSize = dstAlignment * rect.w * rect.h;
|
||||||
|
|
|
@ -457,8 +457,7 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a
|
||||||
case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break;
|
case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break;
|
||||||
default: _assert_(false);
|
default: _assert_(false);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Backbuffer.
|
// Backbuffer.
|
||||||
if (!(vulkan_->GetSurfaceCapabilities().supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) {
|
if (!(vulkan_->GetSurfaceCapabilities().supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) {
|
||||||
ELOG("Copying from backbuffer not supported, can't take screenshots");
|
ELOG("Copying from backbuffer not supported, can't take screenshots");
|
||||||
|
|
|
@ -421,7 +421,18 @@ void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u
|
||||||
// Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP.
|
// Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP.
|
||||||
const uint32_t *src32 = (const uint32_t *)src;
|
const uint32_t *src32 = (const uint32_t *)src;
|
||||||
|
|
||||||
if (format == Draw::DataFormat::R8G8B8A8_UNORM) {
|
if (format == Draw::DataFormat::B8G8R8A8_UNORM) {
|
||||||
|
uint32_t *dst32 = (uint32_t *)dst;
|
||||||
|
if (src == dst) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
for (uint32_t y = 0; y < height; ++y) {
|
||||||
|
memcpy(dst32, src32, width * 4);
|
||||||
|
src32 += srcStride;
|
||||||
|
dst32 += dstStride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (format == Draw::DataFormat::R8G8B8A8_UNORM) {
|
||||||
uint32_t *dst32 = (uint32_t *)dst;
|
uint32_t *dst32 = (uint32_t *)dst;
|
||||||
for (uint32_t y = 0; y < height; ++y) {
|
for (uint32_t y = 0; y < height; ++y) {
|
||||||
ConvertBGRA8888ToRGBA8888(dst32, src32, width);
|
ConvertBGRA8888ToRGBA8888(dst32, src32, width);
|
||||||
|
|
|
@ -567,6 +567,9 @@ public:
|
||||||
virtual bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) {
|
virtual bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
virtual DataFormat PreferredFramebufferReadbackFormat(Framebuffer *src) {
|
||||||
|
return DataFormat::R8G8B8A8_UNORM;
|
||||||
|
}
|
||||||
|
|
||||||
// These functions should be self explanatory.
|
// These functions should be self explanatory.
|
||||||
// Binding a zero render target means binding the backbuffer.
|
// Binding a zero render target means binding the backbuffer.
|
||||||
|
|
|
@ -720,6 +720,10 @@ static void LogReadPixelsError(GLenum error) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) {
|
bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) {
|
||||||
|
if (gl_extensions.IsGLES && (channelBits & FB_COLOR_BIT) == 0) {
|
||||||
|
// Can't readback depth or stencil on GLES.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src;
|
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src;
|
||||||
GLuint aspect = 0;
|
GLuint aspect = 0;
|
||||||
if (channelBits & FB_COLOR_BIT)
|
if (channelBits & FB_COLOR_BIT)
|
||||||
|
|
|
@ -379,6 +379,7 @@ public:
|
||||||
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) override;
|
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) override;
|
||||||
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override;
|
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override;
|
||||||
bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) override;
|
bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) override;
|
||||||
|
DataFormat PreferredFramebufferReadbackFormat(Framebuffer *src) override;
|
||||||
|
|
||||||
// These functions should be self explanatory.
|
// These functions should be self explanatory.
|
||||||
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override;
|
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override;
|
||||||
|
@ -1407,6 +1408,17 @@ bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits,
|
||||||
return renderManager_.CopyFramebufferToMemorySync(src ? src->GetFB() : nullptr, aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride);
|
return renderManager_.CopyFramebufferToMemorySync(src ? src->GetFB() : nullptr, aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataFormat VKContext::PreferredFramebufferReadbackFormat(Framebuffer *src) {
|
||||||
|
if (src) {
|
||||||
|
return DrawContext::PreferredFramebufferReadbackFormat(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vulkan_->GetSwapchainFormat() == VK_FORMAT_B8G8R8A8_UNORM) {
|
||||||
|
return Draw::DataFormat::B8G8R8A8_UNORM;
|
||||||
|
}
|
||||||
|
return DrawContext::PreferredFramebufferReadbackFormat(src);
|
||||||
|
}
|
||||||
|
|
||||||
void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {
|
void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {
|
||||||
VKFramebuffer *fb = (VKFramebuffer *)fbo;
|
VKFramebuffer *fb = (VKFramebuffer *)fbo;
|
||||||
VKRRenderPassAction color = (VKRRenderPassAction)rp.color;
|
VKRRenderPassAction color = (VKRRenderPassAction)rp.color;
|
||||||
|
|
Loading…
Add table
Reference in a new issue