diff --git a/CMakeLists.txt b/CMakeLists.txt index a527b1c08c..e7b39fe238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1317,6 +1317,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Reporting.h Core/SaveState.cpp Core/SaveState.h + Core/Screenshot.cpp + Core/Screenshot.h Core/System.cpp Core/System.h Core/Util/GameManager.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index bd98dc80e2..d136a4e845 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -70,7 +70,7 @@ Level3 Disabled - ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../native;../native/ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../native;../native/ext/glew;../ext/zlib;../native/ext USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) StreamingSIMDExtensions2 Fast @@ -90,7 +90,7 @@ Level3 Disabled - ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../native;../native/ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../native;../native/ext/glew;../ext/zlib;../native/ext USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) NotSet Fast @@ -113,7 +113,7 @@ MaxSpeed true true - ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../native;../native/ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../native;../native/ext/glew;../ext/zlib;../native/ext false StreamingSIMDExtensions2 Fast @@ -143,7 +143,7 @@ MaxSpeed true true - ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../native;../native/ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../native;../native/ext/glew;../ext/zlib;../native/ext NotSet Fast false @@ -372,6 +372,7 @@ + @@ -563,6 +564,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2c96f4474f..80e646b8ff 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -529,6 +529,7 @@ HLE\Libraries + @@ -999,6 +1000,7 @@ HLE\Libraries + diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 9c490f92ac..c7f716ce05 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -29,6 +29,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/Host.h" +#include "Core/Screenshot.h" #include "Core/System.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/ELF/ParamSFO.h" @@ -55,6 +56,7 @@ namespace SaveState SAVESTATE_LOAD, SAVESTATE_VERIFY, SAVESTATE_REWIND, + SAVESTATE_SAVE_SCREENSHOT, }; struct Operation @@ -276,6 +278,11 @@ namespace SaveState Enqueue(Operation(SAVESTATE_REWIND, std::string(""), callback, cbUserData)); } + void SaveScreenshot(const std::string &filename, Callback callback, void *cbUserData) + { + Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, callback, cbUserData)); + } + bool CanRewind() { return !rewindStates.Empty(); @@ -326,6 +333,7 @@ namespace SaveState void SaveSlot(int slot, Callback callback, void *cbUserData) { std::string fn = GenerateSaveSlotFilename(slot, STATE_EXTENSION); + std::string shot = GenerateSaveSlotFilename(slot, SCREENSHOT_EXTENSION); if (!fn.empty()) { auto renameCallback = [=](bool status, void *data) { if (status) { @@ -338,6 +346,8 @@ namespace SaveState callback(status, data); } }; + // Let's also create a screenshot. + SaveScreenshot(shot, nullptr, 0); Save(fn + ".tmp", renameCallback, cbUserData); } else { I18NCategory *s = GetI18NCategory("Screen"); @@ -552,6 +562,10 @@ namespace SaveState } break; + case SAVESTATE_SAVE_SCREENSHOT: + TakeGameScreenshot(op.filename.c_str(), SCREENSHOT_JPG, SCREENSHOT_RENDER); + break; + default: ERROR_LOG(COMMON, "Savestate failure: unknown operation type %d", op.type); callbackResult = false; diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp new file mode 100644 index 0000000000..3d26fd9d1d --- /dev/null +++ b/Core/Screenshot.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2012- 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/. + +#ifdef USING_QT_UI +#include +#else +#include +#endif +#include "ext/jpge/jpge.h" + +#include "Core/Config.h" +#include "Core/Screenshot.h" +#include "GPU/Common/GPUDebugInterface.h" +#ifdef _WIN32 +#include "GPU/Directx9/GPU_DX9.h" +#endif +#include "GPU/GLES/GLES_GPU.h" +#include "GPU/GPUInterface.h" +#include "GPU/GPUState.h" + +bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type) { + GPUDebugBuffer buf; + bool success = false; + + if (type == SCREENSHOT_RENDER) { + if (gpuDebug) { + success = gpuDebug->GetCurrentFramebuffer(buf); + } + } else { + if (g_Config.iGPUBackend == GPU_BACKEND_OPENGL) { + success = GLES_GPU::GetDisplayFramebuffer(buf); + } else if (g_Config.iGPUBackend == GPU_BACKEND_DIRECT3D9) { + success = DX9::DIRECTX9_GPU::GetDisplayFramebuffer(buf); + } + } + +#ifdef USING_QT_UI + if (success) { + // TODO: Handle other formats (e.g. Direct3D.) Would only happen on Qt/Windows. + const u8 *buffer = buf.GetData(); + QImage image(buffer, buf.GetStride(), buf.GetHeight(), QImage::Format_RGB888); + image = image.mirrored(); + success = image.save(filename, fmt == SCREENSHOT_PNG ? "PNG" : "JPG"); + } +#else + if (success) { + const u8 *buffer = buf.GetData(); + u8 *flipbuffer = nullptr; + if (buf.GetFlipped()) { + // Silly OpenGL reads upside down, we flip to another buffer for simplicity. + flipbuffer = new u8[3 * buf.GetStride() * buf.GetHeight()]; + if (buf.GetFormat() == GPU_DBG_FORMAT_888_RGB) { + for (u32 y = 0; y < buf.GetHeight(); y++) { + memcpy(flipbuffer + y * buf.GetStride() * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, buf.GetStride() * 3); + } + } else if (buf.GetFormat() == GPU_DBG_FORMAT_8888) { + for (u32 y = 0; y < buf.GetHeight(); y++) { + for (u32 x = 0; x < buf.GetStride(); x++) { + u8 *dst = &flipbuffer[(buf.GetHeight() - y - 1) * buf.GetStride() * 3 + x * 3]; + const u8 *src = &buffer[y * buf.GetStride() * 4 + x * 4]; + memcpy(dst, src, 3); + } + } + } else { + success = false; + } + buffer = flipbuffer; + } else if (buf.GetFormat() == GPU_DBG_FORMAT_8888_BGRA) { + // Yay, we need to swap AND remove alpha. + flipbuffer = new u8[3 * buf.GetStride() * buf.GetHeight()]; + for (u32 y = 0; y < buf.GetHeight(); y++) { + for (u32 x = 0; x < buf.GetStride(); x++) { + u8 *dst = &flipbuffer[y * buf.GetStride() * 3 + x * 3]; + const u8 *src = &buffer[y * buf.GetStride() * 4 + x * 4]; + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + } + } + buffer = flipbuffer; + } + + 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 = buf.GetStride(); + png.height = buf.GetHeight(); + png_image_write_to_file(&png, filename, 0, flipbuffer, buf.GetStride() * 3, NULL); + png_image_free(&png); + + success = png.warning_or_error < 2; + } else if (success && fmt == SCREENSHOT_JPG) { + jpge::params params; + params.m_quality = 90; + success = compress_image_to_jpeg_file(filename, buf.GetStride(), buf.GetHeight(), 3, flipbuffer, params); + } else { + success = false; + } + delete [] flipbuffer; + } +#endif + return success; +} diff --git a/Core/Screenshot.h b/Core/Screenshot.h new file mode 100644 index 0000000000..50883f36b0 --- /dev/null +++ b/Core/Screenshot.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012- 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 + +enum ScreenshotFormat { + SCREENSHOT_PNG, + SCREENSHOT_JPG, +}; + +enum ScreenshotType { + // What's begin show on screen (e.g. including FPS, etc.) + SCREENSHOT_DISPLAY, + // What the game rendered (e.g. at render resolution.) + // Can only be used while in game. + SCREENSHOT_RENDER, +}; + +bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 5782ec9093..d01858b65d 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -35,8 +35,6 @@ #include #if defined(_WIN32) -#include -#include "ext/jpge/jpge.h" #include "Windows/DSoundStream.h" #include "Windows/WndMainWindow.h" #include "Windows/D3D9Base.h" @@ -50,9 +48,8 @@ #include "file/zip_read.h" #include "thread/thread.h" #include "net/http_client.h" -#include "gfx_es2/gl_state.h" // only for screenshot! +#include "gfx_es2/gl_state.h" // should've been only for screenshot - but actually not, cleanup? #include "gfx_es2/draw_text.h" -#include "gfx_es2/draw_buffer.h" #include "gfx/gl_lost_manager.h" #include "gfx/texture.h" #include "i18n/i18n.h" @@ -76,13 +73,10 @@ #include "Core/Host.h" #include "Core/PSPMixer.h" #include "Core/SaveState.h" +#include "Core/Screenshot.h" #include "Core/System.h" #include "Core/HLE/sceCtrl.h" #include "Core/Util/GameManager.h" -#include "GPU/GLES/GLES_GPU.h" -#ifdef _WIN32 -#include "GPU/Directx9/GPU_DX9.h" -#endif #include "ui_atlas.h" #include "EmuScreen.h" @@ -604,7 +598,7 @@ void NativeShutdownGraphics() { void TakeScreenshot() { g_TakeScreenshot = false; -#if defined(_WIN32) || (defined(USING_QT_UI) && !defined(MOBILE_DEVICE)) +#if defined(_WIN32) || (defined(USING_QT_UI) && !defined(MOBILE_DEVICE)) mkDir(g_Config.memStickDirectory + "/PSP/SCREENSHOT"); // First, find a free filename. @@ -627,73 +621,7 @@ void TakeScreenshot() { i++; } - GPUDebugBuffer buf; - bool success = true; - - if (g_Config.iGPUBackend == GPU_BACKEND_OPENGL) { - success = GLES_GPU::GetDisplayFramebuffer(buf); - } else if (g_Config.iGPUBackend == GPU_BACKEND_DIRECT3D9) { - success = DX9::DIRECTX9_GPU::GetDisplayFramebuffer(buf); - } else { - success = false; - } - -#ifdef USING_QT_UI - if (success) { - // TODO: Handle other formats (e.g. Direct3D.) Would only happen on Qt/Windows. - const u8 *buffer = buf.GetData(); - QImage image(buffer, buf.GetStride(), buf.GetHeight(), QImage::Format_RGB888); - image = image.mirrored(); - success = image.save(filename, g_Config.bScreenshotsAsPNG ? "PNG" : "JPG"); - } -#else - if (success) { - const u8 *buffer = buf.GetData(); - u8 *flipbuffer = nullptr; - if (buf.GetFlipped()) { - // Silly OpenGL reads upside down, we flip to another buffer for simplicity. - _assert_msg_(G3D, buf.GetFormat() == GPU_DBG_FORMAT_888_RGB, "Expecting RGB for flipped buffer (OpenGL.)"); - flipbuffer = new u8[3 * buf.GetStride() * buf.GetHeight()]; - for (u32 y = 0; y < buf.GetHeight(); y++) { - memcpy(flipbuffer + y * buf.GetStride() * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, buf.GetStride() * 3); - } - buffer = flipbuffer; - } - if (buf.GetFormat() == GPU_DBG_FORMAT_8888_BGRA) { - // Yay, we need to swap AND remove alpha. - flipbuffer = new u8[3 * buf.GetStride() * buf.GetHeight()]; - for (u32 y = 0; y < buf.GetHeight(); y++) { - for (u32 x = 0; x < buf.GetStride(); x++) { - u8 *dst = &flipbuffer[y * buf.GetStride() * 3 + x * 3]; - const u8 *src = &buffer[y * buf.GetStride() * 4 + x * 4]; - dst[0] = src[2]; - dst[1] = src[1]; - dst[2] = src[0]; - } - } - buffer = flipbuffer; - } - - if (g_Config.bScreenshotsAsPNG) { - png_image png; - memset(&png, 0, sizeof(png)); - png.version = PNG_IMAGE_VERSION; - png.format = PNG_FORMAT_RGB; - png.width = buf.GetStride(); - png.height = buf.GetHeight(); - png_image_write_to_file(&png, filename, 0, flipbuffer, buf.GetStride() * 3, NULL); - png_image_free(&png); - - success = png.warning_or_error >= 2; - } else { - jpge::params params; - params.m_quality = 90; - success = compress_image_to_jpeg_file(filename, buf.GetStride(), buf.GetHeight(), 3, flipbuffer, params); - } - delete [] flipbuffer; - } -#endif - + bool success = TakeGameScreenshot(filename, g_Config.bScreenshotsAsPNG ? SCREENSHOT_PNG : SCREENSHOT_JPG, SCREENSHOT_DISPLAY); if (success) { osm.Show(filename); } else { diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 59b9fa2d41..b49c2f5a2b 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -199,6 +199,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/MemMapFunctions.cpp \ $(SRC)/Core/Reporting.cpp \ $(SRC)/Core/SaveState.cpp \ + $(SRC)/Core/Screenshot.cpp \ $(SRC)/Core/System.cpp \ $(SRC)/Core/PSPMixer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \