From c64b1c04f34878a6bfd420a12a8f9c9838d60f57 Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 23 Aug 2017 13:14:36 +1200 Subject: [PATCH] Screenshot support by copying data from the 3ds framebuffer memory --- src/citra/emu_window/emu_window_sdl2.cpp | 17 +++++ src/citra/emu_window/emu_window_sdl2.h | 2 + src/citra_qt/bootmanager.cpp | 12 ++++ src/citra_qt/bootmanager.h | 1 + src/core/CMakeLists.txt | 1 + src/core/frontend/emu_window.h | 11 +++ src/core/frontend/screenshot_data.h | 36 ++++++++++ src/video_core/renderer_base.cpp | 70 +++++++++++++++++++ src/video_core/renderer_base.h | 2 + .../renderer_opengl/renderer_opengl.cpp | 4 ++ 10 files changed, 156 insertions(+) create mode 100644 src/core/frontend/screenshot_data.h diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 25643715a..df61bbf34 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -13,6 +13,7 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "core/3ds.h" +#include "core/frontend/screenshot_data.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" @@ -176,3 +177,19 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest( SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); } + +void EmuWindow_SDL2::ReceiveScreenshot(std::unique_ptr screenshot) { + SDL_Surface* image = SDL_CreateRGBSurfaceFrom( + screenshot->screens[0].data.data(), screenshot->screens[0].width, + screenshot->screens[0].height, 3 * 8, screenshot->screens[0].width * 3, 0x000000FF, + 0x0000FF00, 0x00FF0000, 0); + SDL_SaveBMP(image, "screenshot_0.bmp"); + SDL_FreeSurface(image); + + image = SDL_CreateRGBSurfaceFrom(screenshot->screens[1].data.data(), + screenshot->screens[1].width, screenshot->screens[1].height, + 3 * 8, screenshot->screens[1].width * 3, 0x000000FF, + 0x0000FF00, 0x00FF0000, 0); + SDL_SaveBMP(image, "screenshot_1.bmp"); + SDL_FreeSurface(image); +} \ No newline at end of file diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index 3664d2fbe..b17cbe291 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -30,6 +30,8 @@ public: /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; + void ReceiveScreenshot(std::unique_ptr screenshot) override; + private: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 7107bfc60..cb5a95b64 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) @@ -14,6 +15,7 @@ #include "common/string_util.h" #include "core/3ds.h" #include "core/core.h" +#include "core/frontend/screenshot_data.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" @@ -154,6 +156,16 @@ void GRenderWindow::DoneCurrent() { void GRenderWindow::PollEvents() {} +void GRenderWindow::ReceiveScreenshot(std::unique_ptr screenshot) { + QImage image(screenshot->screens[0].data.data(), screenshot->screens[0].width, + screenshot->screens[0].height, QImage::Format::Format_RGB888); + image.save("screenshot_0.png"); + + image = QImage(screenshot->screens[1].data.data(), screenshot->screens[1].width, + screenshot->screens[1].height, QImage::Format::Format_RGB888); + image.save("screenshot_1.png"); +} + // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). // // Older versions get the window size (density independent pixels), diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 6974edcbb..f61afadde 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -113,6 +113,7 @@ public: void MakeCurrent() override; void DoneCurrent() override; void PollEvents() override; + void ReceiveScreenshot(std::unique_ptr screenshot) override; void BackupGeometry(); void RestoreGeometry(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 53bd50eb2..cf2ec2201 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -225,6 +225,7 @@ set(HEADERS frontend/emu_window.h frontend/framebuffer_layout.h frontend/input.h + frontend/screenshot_data.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 7bdee251c..de1c6d23a 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -11,6 +11,8 @@ #include "common/math_util.h" #include "core/frontend/framebuffer_layout.h" +class ScreenshotData; + /** * Abstraction class used to provide an interface between emulation code and the frontend * (e.g. SDL, QGLWidget, GLFW, etc...). @@ -112,6 +114,12 @@ public: */ void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); + bool IsScreenshotRequested() const { + return screenshot_requested; + } + + virtual void ReceiveScreenshot(std::unique_ptr screenshot) = 0; + protected: EmuWindow() { // TODO: Find a better place to set this. @@ -120,6 +128,7 @@ protected: touch_x = 0; touch_y = 0; touch_pressed = false; + screenshot_requested = false; } virtual ~EmuWindow() {} @@ -182,6 +191,8 @@ private: u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) + bool screenshot_requested; + /** * Clip the provided coordinates to be inside the touchscreen area. */ diff --git a/src/core/frontend/screenshot_data.h b/src/core/frontend/screenshot_data.h new file mode 100644 index 000000000..8e7fd96da --- /dev/null +++ b/src/core/frontend/screenshot_data.h @@ -0,0 +1,36 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +/** + * Screenshot data for a screen. + * data is in RGB888 format, left to right then top to bottom + */ +class ScreenshotScreen { +public: + size_t width; + size_t height; + + std::vector data; +}; +class ScreenshotData { +public: + ScreenshotData(const size_t& top_width, const size_t& top_height, const size_t& bottom_width, + const size_t& bottom_height) { + screens[0].width = top_width; + screens[0].height = top_height; + screens[0].data.resize(top_width * top_height * 3); + + screens[1].width = bottom_width; + screens[1].height = bottom_height; + screens[1].data.resize(bottom_width * bottom_height * 3); + } + + std::array screens; +}; diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index ce9955648..9fd361c24 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -4,6 +4,9 @@ #include #include +#include "common/color.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/screenshot_data.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" #include "video_core/renderer_base.h" @@ -33,3 +36,70 @@ u32 RendererBase::GetColorFillForFramebuffer(int framebuffer_index) { LCD::Read(color_fill.raw, lcd_color_addr); return color_fill.raw; } + +void RendererBase::SaveScreenshot() { + // We swap width and height here as we are going to rotate the image as we copy it + // 3ds framebuffers are stored rotate 90 degrees + std::unique_ptr screenshot = std::make_unique( + GPU::g_regs.framebuffer_config[0].height, GPU::g_regs.framebuffer_config[0].width, + GPU::g_regs.framebuffer_config[1].height, GPU::g_regs.framebuffer_config[1].width); + + for (int i : {0, 1}) { + const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; + LCD::Regs::ColorFill color_fill{GetColorFillForFramebuffer(i)}; + + u8* dest_buffer = screenshot->screens[i].data.data(); + + if (color_fill.is_enabled) { + std::array source; + source[0] = color_fill.color_b; + source[1] = color_fill.color_g; + source[2] = color_fill.color_r; + + for (u32 y = 0; y < framebuffer.width; y++) { + for (u32 x = 0; x < framebuffer.height; x++) { + u8* px_dest = dest_buffer + 3 * (x + framebuffer.height * y); + std::memcpy(px_dest, source.data(), 3); + } + } + } else { + const PAddr framebuffer_addr = + framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2; + Memory::RasterizerFlushRegion(framebuffer_addr, + framebuffer.stride * framebuffer.height); + const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); + + int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); + + // x,y here are in destination pixels + for (u32 y = 0; y < framebuffer.width; y++) { + for (u32 x = 0; x < framebuffer.height; x++) { + u8* px_dest = + dest_buffer + 3 * (x + framebuffer.height * (framebuffer.width - y - 1)); + const u8* px_source = framebuffer_data + bpp * (y + framebuffer.width * x); + Math::Vec4 source; + switch (framebuffer.color_format) { + case GPU::Regs::PixelFormat::RGB8: + source = Color::DecodeRGB8(px_source); + break; + case GPU::Regs::PixelFormat::RGBA8: + source = Color::DecodeRGBA8(px_source); + break; + case GPU::Regs::PixelFormat::RGBA4: + source = Color::DecodeRGBA4(px_source); + break; + case GPU::Regs::PixelFormat::RGB5A1: + source = Color::DecodeRGB5A1(px_source); + break; + case GPU::Regs::PixelFormat::RGB565: + source = Color::DecodeRGB565(px_source); + break; + } + std::memcpy(px_dest, &source, 3); + } + } + } + } + + render_window->ReceiveScreenshot(std::move(screenshot)); +} diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 164ef8f10..f3f01c220 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -59,6 +59,8 @@ protected: u32 GetColorFillForFramebuffer(int framebuffer_index); + void SaveScreenshot(); + private: bool opengl_rasterizer_active = false; }; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 5479dcfb9..227a47524 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -13,6 +13,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/frontend/screenshot_data.h" #include "core/hw/gpu.h" #include "core/hw/lcd.h" #include "core/memory.h" @@ -135,6 +136,9 @@ void RendererOpenGL::SwapBuffers() { Core::System::GetInstance().perf_stats.EndSystemFrame(); + if (render_window->IsScreenshotRequested()) + SaveScreenshot(); + // Swap buffers render_window->PollEvents(); render_window->SwapBuffers();