Screenshot support by copying data from the 3ds framebuffer memory

This commit is contained in:
dave 2017-08-23 13:14:36 +12:00
parent f4d3669309
commit c64b1c04f3
10 changed files with 156 additions and 0 deletions

View File

@ -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<ScreenshotData> 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);
}

View File

@ -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<ScreenshotData> screenshot) override;
private:
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);

View File

@ -1,5 +1,6 @@
#include <QApplication>
#include <QHBoxLayout>
#include <QImage>
#include <QKeyEvent>
#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<ScreenshotData> 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),

View File

@ -113,6 +113,7 @@ public:
void MakeCurrent() override;
void DoneCurrent() override;
void PollEvents() override;
void ReceiveScreenshot(std::unique_ptr<ScreenshotData> screenshot) override;
void BackupGeometry();
void RestoreGeometry();

View File

@ -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

View File

@ -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<ScreenshotData> 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.
*/

View File

@ -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 <array>
#include <vector>
#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<u8> 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<ScreenshotScreen, 2> screens;
};

View File

@ -4,6 +4,9 @@
#include <atomic>
#include <memory>
#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<ScreenshotData> screenshot = std::make_unique<ScreenshotData>(
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<u8, 3> 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<u8> 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));
}

View File

@ -59,6 +59,8 @@ protected:
u32 GetColorFillForFramebuffer(int framebuffer_index);
void SaveScreenshot();
private:
bool opengl_rasterizer_active = false;
};

View File

@ -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();