diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 60a2012476..1f79ebc143 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -689,7 +689,7 @@ namespace SaveState break; case SAVESTATE_SAVE_SCREENSHOT: - callbackResult = TakeGameScreenshot(op.filename.c_str(), SCREENSHOT_JPG, SCREENSHOT_DISPLAY); + callbackResult = TakeGameScreenshot(op.filename.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY); if (!callbackResult) { ERROR_LOG(SAVESTATE, "Failed to take a screenshot for the savestate! %s", op.filename.c_str()); } diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index c423fab9cf..bc5b046056 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -125,33 +125,129 @@ static bool WriteScreenshotToPNG(png_imagep image, const char *filename, int con } #endif -const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { - // The temp buffer will be freed by the caller if set, and can be the return value. - temp = nullptr; +static bool ConvertPixelTo888RGB(GPUDebugBufferFormat fmt, u8 &r, u8 &g, u8 &b, const void *buffer, int offset, bool rev) { + const u8 *buf8 = (const u8 *)buffer; + const u16 *buf16 = (const u16 *)buffer; + const u32 *buf32 = (const u32 *)buffer; + const float *fbuf = (const float *)buffer; + u32 src; + double fsrc; + switch (fmt) { + case GPU_DBG_FORMAT_565: + src = buf16[offset]; + if (rev) { + src = bswap16(src); + } + r = Convert5To8((src >> 0) & 0x1F); + g = Convert6To8((src >> 5) & 0x3F); + b = Convert5To8((src >> 11) & 0x1F); + break; + case GPU_DBG_FORMAT_5551: + src = buf16[offset]; + if (rev) { + src = bswap16(src); + } + r = Convert5To8((src >> 0) & 0x1F); + g = Convert5To8((src >> 5) & 0x1F); + b = Convert5To8((src >> 10) & 0x1F); + break; + case GPU_DBG_FORMAT_4444: + src = buf16[offset]; + if (rev) { + src = bswap16(src); + } + r = Convert4To8((src >> 0) & 0xF); + g = Convert4To8((src >> 4) & 0xF); + b = Convert4To8((src >> 8) & 0xF); + break; + case GPU_DBG_FORMAT_8888: + src = buf32[offset]; + if (rev) { + src = bswap32(src); + } + r = (src >> 0) & 0xFF; + g = (src >> 8) & 0xFF; + b = (src >> 16) & 0xFF; + break; + case GPU_DBG_FORMAT_FLOAT: + fsrc = fbuf[offset]; + r = fsrc >= 1.0 ? 255 : (fsrc < 0.0 ? 0 : (int)(fsrc * 255.0)); + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_16BIT: + src = buf16[offset]; + r = src >> 8; + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_8BIT: + src = buf8[offset]; + r = src; + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_24BIT_8X: + src = buf32[offset]; + r = (src >> 16) & 0xFF; + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_24X_8BIT: + src = buf32[offset]; + r = (src >> 24) & 0xFF; + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_24BIT_8X_DIV_256: + src = buf32[offset]& 0x00FFFFFF; + src = src - 0x800000 + 0x8000; + r = (src >> 8) & 0xFF; + g = 0; + b = 0; + break; + case GPU_DBG_FORMAT_FLOAT_DIV_256: + fsrc = fbuf[offset]; + src = (int)(fsrc * 16777215.0); + src = src - 0x800000 + 0x8000; + r = (src >> 8) & 0xFF; + g = 0; + b = 0; + break; + default: + _assert_msg_(SYSTEM, false, "Unsupported framebuffer format for screenshot: %d", fmt); + return false; + } + + return true; +} + +const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) { w = std::min(w, buf.GetStride()); h = std::min(h, buf.GetHeight()); + // The temp buffer will be freed by the caller if set, and can be the return value. + if (buf.GetFlipped() || buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) { + temp = new u8[3 * w * h]; + } else { + temp = nullptr; + } + const u8 *buffer = buf.GetData(); if (buf.GetFlipped() && buf.GetFormat() == GPU_DBG_FORMAT_888_RGB) { // Silly OpenGL reads upside down, we flip to another buffer for simplicity. - temp = new u8[3 * w * h]; for (u32 y = 0; y < h; y++) { memcpy(temp + y * w * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, w * 3); } - buffer = temp; - } else if (buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) { + } else if (buf.GetFormat() < GPU_DBG_FORMAT_FLOAT) { // Let's boil it down to how we need to interpret the bits. int baseFmt = buf.GetFormat() & ~(GPU_DBG_FORMAT_REVERSE_FLAG | GPU_DBG_FORMAT_BRSWAP_FLAG); bool rev = (buf.GetFormat() & GPU_DBG_FORMAT_REVERSE_FLAG) != 0; bool brswap = (buf.GetFormat() & GPU_DBG_FORMAT_BRSWAP_FLAG) != 0; bool flip = buf.GetFlipped(); - temp = new u8[3 * w * h]; - // This is pretty inefficient. - const u16 *buf16 = (const u16 *)buffer; - const u32 *buf32 = (const u32 *)buffer; for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { u8 *dst; @@ -165,54 +261,32 @@ const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u3 u8 &g = dst[1]; u8 &b = brswap ? dst[0] : dst[2]; - u32 src; - switch (baseFmt) { - case GPU_DBG_FORMAT_565: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert5To8((src >> 0) & 0x1F); - g = Convert6To8((src >> 5) & 0x3F); - b = Convert5To8((src >> 11) & 0x1F); - break; - case GPU_DBG_FORMAT_5551: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert5To8((src >> 0) & 0x1F); - g = Convert5To8((src >> 5) & 0x1F); - b = Convert5To8((src >> 10) & 0x1F); - break; - case GPU_DBG_FORMAT_4444: - src = buf16[y * buf.GetStride() + x]; - if (rev) { - src = bswap16(src); - } - r = Convert4To8((src >> 0) & 0xF); - g = Convert4To8((src >> 4) & 0xF); - b = Convert4To8((src >> 8) & 0xF); - break; - case GPU_DBG_FORMAT_8888: - src = buf32[y * buf.GetStride() + x]; - if (rev) { - src = bswap32(src); - } - r = (src >> 0) & 0xFF; - g = (src >> 8) & 0xFF; - b = (src >> 16) & 0xFF; - break; - default: - ERROR_LOG(G3D, "Unsupported framebuffer format for screenshot: %d", buf.GetFormat()); + if (!ConvertPixelTo888RGB(GPUDebugBufferFormat(baseFmt), r, g, b, buffer, y * buf.GetStride() + x, rev)) { + return nullptr; + } + } + } + } else if (buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) { + bool flip = buf.GetFlipped(); + + // This is pretty inefficient. + for (u32 y = 0; y < h; y++) { + for (u32 x = 0; x < w; x++) { + u8 *dst; + if (flip) { + dst = &temp[(h - y - 1) * w * 3 + x * 3]; + } else { + dst = &temp[y * w * 3 + x * 3]; + } + + if (!ConvertPixelTo888RGB(buf.GetFormat(), dst[0], dst[1], dst[2], buffer, y * buf.GetStride() + x, false)) { return nullptr; } } } - buffer = temp; } - return buffer; + return temp ? temp : buffer; } bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type, int *width, int *height, int maxRes) { @@ -240,54 +314,53 @@ bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotTy return false; } -#ifdef USING_QT_UI + u8 *flipbuffer = nullptr; if (success) { - u8 *flipbuffer = nullptr; const u8 *buffer = ConvertBufferTo888RGB(buf, flipbuffer, w, h); - // TODO: Handle other formats (e.g. Direct3D, raw framebuffers.) - QImage image(buffer, w, h, QImage::Format_RGB888); - success = image.save(filename, fmt == SCREENSHOT_PNG ? "PNG" : "JPG"); - delete [] flipbuffer; - } -#else - if (success) { - u8 *flipbuffer = nullptr; - const u8 *buffer = ConvertBufferTo888RGB(buf, flipbuffer, w, h); - if (buffer == nullptr) { - success = false; + success = buffer != nullptr; + if (success) { + if (width) + *width = w; + if (height) + *height = h; + + success = Save888RGBScreenshot(filename, fmt, buffer, w, h); } - - if (width) - *width = w; - if (height) - *height = h; - - if (success && fmt == SCREENSHOT_PNG) { - png_image png; - memset(&png, 0, sizeof(png)); - png.version = PNG_IMAGE_VERSION; - png.format = PNG_FORMAT_RGB; - png.width = w; - png.height = h; - success = WriteScreenshotToPNG(&png, filename, 0, buffer, w * 3, nullptr); - png_image_free(&png); - - if (png.warning_or_error >= 2) { - ERROR_LOG(SYSTEM, "Saving screenshot to PNG produced errors."); - success = false; - } - } else if (success && fmt == SCREENSHOT_JPG) { - jpge::params params; - params.m_quality = 90; - success = WriteScreenshotToJPEG(filename, w, h, 3, buffer, params); - } else { - success = false; - } - delete [] flipbuffer; } -#endif + delete [] flipbuffer; + if (!success) { ERROR_LOG(SYSTEM, "Failed to write screenshot."); } return success; } + +bool Save888RGBScreenshot(const char *filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h) { +#ifdef USING_QT_UI + QImage image(bufferRGB888, w, h, QImage::Format_RGB888); + return image.save(filename, fmt == ScreenshotFormat::PNG ? "PNG" : "JPG"); +#else + if (fmt == ScreenshotFormat::PNG) { + png_image png; + memset(&png, 0, sizeof(png)); + png.version = PNG_IMAGE_VERSION; + png.format = PNG_FORMAT_RGB; + png.width = w; + png.height = h; + bool success = WriteScreenshotToPNG(&png, filename, 0, bufferRGB888, w * 3, nullptr); + png_image_free(&png); + + if (png.warning_or_error >= 2) { + ERROR_LOG(SYSTEM, "Saving screenshot to PNG produced errors."); + success = false; + } + return success; + } else if (fmt == ScreenshotFormat::JPG) { + jpge::params params; + params.m_quality = 90; + return WriteScreenshotToJPEG(filename, w, h, 3, bufferRGB888, params); + } else { + return false; + } +#endif +} diff --git a/Core/Screenshot.h b/Core/Screenshot.h index b529fad494..75214b54a2 100644 --- a/Core/Screenshot.h +++ b/Core/Screenshot.h @@ -19,9 +19,9 @@ struct GPUDebugBuffer; -enum ScreenshotFormat { - SCREENSHOT_PNG, - SCREENSHOT_JPG, +enum class ScreenshotFormat { + PNG, + JPG, }; enum ScreenshotType { @@ -37,3 +37,4 @@ const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u3 // Can only be used while in game. bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type, int *width = nullptr, int *height = nullptr, int maxRes = -1); +bool Save888RGBScreenshot(const char *filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 1cc8c8aaeb..bf866a211d 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -763,7 +763,7 @@ void TakeScreenshot() { i++; } - bool success = TakeGameScreenshot(filename, g_Config.bScreenshotsAsPNG ? SCREENSHOT_PNG : SCREENSHOT_JPG, SCREENSHOT_OUTPUT); + bool success = TakeGameScreenshot(filename, g_Config.bScreenshotsAsPNG ? ScreenshotFormat::PNG : ScreenshotFormat::JPG, SCREENSHOT_OUTPUT); if (success) { osm.Show(filename); } else { diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index 60003b25ad..ce2556e5f7 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -247,7 +247,7 @@ void ReportScreen::CreateViews() { } screenshotFilename_ = path + ".reporting.jpg"; int shotWidth = 0, shotHeight = 0; - if (TakeGameScreenshot(screenshotFilename_.c_str(), SCREENSHOT_JPG, SCREENSHOT_DISPLAY, &shotWidth, &shotHeight, 4)) { + if (TakeGameScreenshot(screenshotFilename_.c_str(), ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, &shotWidth, &shotHeight, 4)) { float scale = 340.0f * (1.0f / g_dpi_scale_y) * (1.0f / shotHeight); leftColumnItems->Add(new CheckBox(&includeScreenshot_, rp->T("FeedbackIncludeScreen", "Include a screenshot")))->SetEnabledPtr(&enableReporting_); screenshot_ = leftColumnItems->Add(new AsyncImageFileView(screenshotFilename_, IS_DEFAULT, nullptr, new LinearLayoutParams(shotWidth * scale, shotHeight * scale, Margins(12, 0)))); diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index cb68ae915a..325b346a38 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -21,12 +21,15 @@ #include #include "Common/ColorConv.h" +#include "Core/Config.h" +#include "Core/Screenshot.h" #include "Windows/GEDebugger/GEDebugger.h" #include "Windows/GEDebugger/SimpleGLWindow.h" #include "Windows/GEDebugger/CtrlDisplayListView.h" #include "Windows/GEDebugger/TabDisplayLists.h" #include "Windows/GEDebugger/TabState.h" #include "Windows/GEDebugger/TabVertices.h" +#include "Windows/W32Util/ShellUtil.h" #include "Windows/InputBox.h" #include "Windows/WindowsHost.h" #include "Windows/MainWindow.h" @@ -38,10 +41,11 @@ #include "GPU/Debugger/Breakpoints.h" #include "GPU/Debugger/Record.h" #include "GPU/Debugger/Stepping.h" -#include "Core/Config.h" #include #include +const int POPUP_SUBMENU_ID_GEDBG_PREVIEW = 10; + using namespace GPUBreakpoints; using namespace GPUStepping; @@ -145,19 +149,45 @@ CGEDebugger::~CGEDebugger() { void CGEDebugger::SetupPreviews() { if (primaryWindow == nullptr) { + HMENU subMenu = GetSubMenu(g_hPopupMenus, POPUP_SUBMENU_ID_GEDBG_PREVIEW); + primaryWindow = SimpleGLWindow::GetFrom(GetDlgItem(m_hDlg, IDC_GEDBG_FRAME)); primaryWindow->Initialize(SimpleGLWindow::ALPHA_IGNORE | SimpleGLWindow::RESIZE_SHRINK_CENTER); primaryWindow->SetHoverCallback([&] (int x, int y) { PrimaryPreviewHover(x, y); }); + primaryWindow->SetRightClickMenu(subMenu, [&] (int cmd) { + switch (cmd) { + case ID_GEDBG_EXPORT_IMAGE: + PreviewExport(primaryBuffer_); + break; + default: + break; + } + + return true; + }); primaryWindow->Clear(); } if (secondWindow == nullptr) { + HMENU subMenu = GetSubMenu(g_hPopupMenus, POPUP_SUBMENU_ID_GEDBG_PREVIEW); + secondWindow = SimpleGLWindow::GetFrom(GetDlgItem(m_hDlg, IDC_GEDBG_TEX)); secondWindow->Initialize(SimpleGLWindow::ALPHA_BLEND | SimpleGLWindow::RESIZE_SHRINK_CENTER); secondWindow->SetHoverCallback([&] (int x, int y) { SecondPreviewHover(x, y); }); + secondWindow->SetRightClickMenu(subMenu, [&] (int cmd) { + switch (cmd) { + case ID_GEDBG_EXPORT_IMAGE: + PreviewExport(secondBuffer_); + break; + default: + break; + } + + return true; + }); secondWindow->Clear(); } } @@ -194,6 +224,23 @@ void CGEDebugger::DescribeSecondPreview(const GPUgstate &state, wchar_t desc[256 } } +void CGEDebugger::PreviewExport(const GPUDebugBuffer *dbgBuffer) { + const TCHAR *filter = L"PNG Image (*.png)\0*.png\0JPEG Image (*.jpg)\0*.jpg\0All files\0*.*\0\0"; + std::string fn; + if (W32Util::BrowseForFileName(false, GetDlgHandle(), L"Save Preview Image...", nullptr, filter, L"png", fn)) { + ScreenshotFormat fmt = fn.find(".jpg") != fn.npos ? ScreenshotFormat::JPG : ScreenshotFormat::PNG; + + u8 *flipbuffer = nullptr; + u32 w = (u32)-1; + u32 h = (u32)-1; + const u8 *buffer = ConvertBufferTo888RGB(*dbgBuffer, flipbuffer, w, h); + if (buffer != nullptr) { + Save888RGBScreenshot(fn.c_str(), fmt, buffer, w, h); + } + delete [] flipbuffer; + } +} + void CGEDebugger::UpdatePreviews() { auto memLock = Memory::Lock(); if (!PSP_IsInited()) { diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index f4f6aa3db5..d520931054 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -80,6 +80,7 @@ private: void DescribeSecondPreview(const GPUgstate &state, wchar_t desc[256]); void PrimaryPreviewHover(int x, int y); void SecondPreviewHover(int x, int y); + void PreviewExport(const GPUDebugBuffer *buffer); void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]); void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]); diff --git a/Windows/GEDebugger/SimpleGLWindow.cpp b/Windows/GEDebugger/SimpleGLWindow.cpp index 1734beaacb..cd848cb03a 100644 --- a/Windows/GEDebugger/SimpleGLWindow.cpp +++ b/Windows/GEDebugger/SimpleGLWindow.cpp @@ -541,6 +541,22 @@ bool SimpleGLWindow::Leave() { return true; } +bool SimpleGLWindow::RightClick(int mouseX, int mouseY) { + if (rightClickCallback_ == nullptr) { + return false; + } + + POINT pt{mouseX, mouseY}; + ClientToScreen(hWnd_, &pt); + + int result = TrackPopupMenuEx(rightClickMenu_, TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, hWnd_, 0); + if (result != 0) { + rightClickCallback_(result); + } + + return true; +} + const u8 *SimpleGLWindow::Reformat(const u8 *data, Format fmt, u32 numPixels) { if (!reformatBuf_ || reformatBufSize_ < numPixels) { delete [] reformatBuf_; @@ -587,6 +603,7 @@ LRESULT CALLBACK SimpleGLWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPA switch (msg) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: + case WM_RBUTTONUP: case WM_MOUSEMOVE: mouseX = GET_X_LPARAM(lParam); mouseY = GET_Y_LPARAM(lParam); @@ -633,6 +650,12 @@ LRESULT CALLBACK SimpleGLWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPA } break; + case WM_RBUTTONUP: + if (win->RightClick(mouseX, mouseY)) { + return 0; + } + break; + case WM_MOUSELEAVE: if (win->Leave()) { return 0; @@ -645,4 +668,4 @@ LRESULT CALLBACK SimpleGLWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPA } return DefWindowProc(hwnd, msg, wParam, lParam); -} \ No newline at end of file +} diff --git a/Windows/GEDebugger/SimpleGLWindow.h b/Windows/GEDebugger/SimpleGLWindow.h index efd5763d80..5b08fcb95c 100644 --- a/Windows/GEDebugger/SimpleGLWindow.h +++ b/Windows/GEDebugger/SimpleGLWindow.h @@ -110,6 +110,11 @@ struct SimpleGLWindow { hoverCallback_ = hoverCallback; } + void SetRightClickMenu(HMENU menu, std::function callback) { + rightClickCallback_ = callback; + rightClickMenu_ = menu; + } + static void RegisterClass(); protected: void SetupGL(); @@ -122,6 +127,7 @@ protected: bool DragEnd(int mouseX, int mouseY); bool Hover(int mouseX, int mouseY); bool Leave(); + bool RightClick(int mouseX, int mouseY); bool ToggleZoom(); const u8 *Reformat(const u8 *data, Format fmt, u32 numPixels); @@ -157,4 +163,6 @@ protected: u32 reformatBufSize_; std::function hoverCallback_; + std::function rightClickCallback_; + HMENU rightClickMenu_; }; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index a324f42896..58b2d4ed4b 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -654,6 +654,10 @@ BEGIN MENUITEM "Toggle Breakpoint", ID_DISASM_TOGGLEBREAKPOINT MENUITEM "Add Watch", ID_GEDBG_WATCH END + POPUP "gepreviewoptions" + BEGIN + MENUITEM "Export Image...", ID_GEDBG_EXPORT_IMAGE + END END #endif // English (United States) resources diff --git a/Windows/resource.h b/Windows/resource.h index f105eb9739..32710e3859 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -334,6 +334,7 @@ #define ID_FILE_DUMPAUDIO 40167 #define ID_HELP_GITHUB 40168 #define IDC_GEDBG_RECORD 40169 +#define ID_GEDBG_EXPORT_IMAGE 40170 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 @@ -346,7 +347,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40170 +#define _APS_NEXT_COMMAND_VALUE 40171 #define _APS_NEXT_CONTROL_VALUE 1200 #define _APS_NEXT_SYMED_VALUE 101 #endif