mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-26 11:50:06 +00:00
Screenshot support by copying data from the 3ds framebuffer memory
This commit is contained in:
parent
f4d3669309
commit
c64b1c04f3
@ -13,6 +13,7 @@
|
|||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/3ds.h"
|
#include "core/3ds.h"
|
||||||
|
#include "core/frontend/screenshot_data.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
@ -176,3 +177,19 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
|||||||
|
|
||||||
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
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);
|
||||||
|
}
|
@ -30,6 +30,8 @@ public:
|
|||||||
/// Whether the window is still open, and a close request hasn't yet been sent
|
/// Whether the window is still open, and a close request hasn't yet been sent
|
||||||
bool IsOpen() const;
|
bool IsOpen() const;
|
||||||
|
|
||||||
|
void ReceiveScreenshot(std::unique_ptr<ScreenshotData> screenshot) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Called by PollEvents when a key is pressed or released.
|
/// Called by PollEvents when a key is pressed or released.
|
||||||
void OnKeyEvent(int key, u8 state);
|
void OnKeyEvent(int key, u8 state);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QImage>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||||
@ -14,6 +15,7 @@
|
|||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/3ds.h"
|
#include "core/3ds.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/screenshot_data.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
@ -154,6 +156,16 @@ void GRenderWindow::DoneCurrent() {
|
|||||||
|
|
||||||
void GRenderWindow::PollEvents() {}
|
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).
|
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
|
||||||
//
|
//
|
||||||
// Older versions get the window size (density independent pixels),
|
// Older versions get the window size (density independent pixels),
|
||||||
|
@ -113,6 +113,7 @@ public:
|
|||||||
void MakeCurrent() override;
|
void MakeCurrent() override;
|
||||||
void DoneCurrent() override;
|
void DoneCurrent() override;
|
||||||
void PollEvents() override;
|
void PollEvents() override;
|
||||||
|
void ReceiveScreenshot(std::unique_ptr<ScreenshotData> screenshot) override;
|
||||||
|
|
||||||
void BackupGeometry();
|
void BackupGeometry();
|
||||||
void RestoreGeometry();
|
void RestoreGeometry();
|
||||||
|
@ -225,6 +225,7 @@ set(HEADERS
|
|||||||
frontend/emu_window.h
|
frontend/emu_window.h
|
||||||
frontend/framebuffer_layout.h
|
frontend/framebuffer_layout.h
|
||||||
frontend/input.h
|
frontend/input.h
|
||||||
|
frontend/screenshot_data.h
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
hle/config_mem.h
|
hle/config_mem.h
|
||||||
hle/function_wrappers.h
|
hle/function_wrappers.h
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
#include "core/frontend/framebuffer_layout.h"
|
#include "core/frontend/framebuffer_layout.h"
|
||||||
|
|
||||||
|
class ScreenshotData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction class used to provide an interface between emulation code and the frontend
|
* Abstraction class used to provide an interface between emulation code and the frontend
|
||||||
* (e.g. SDL, QGLWidget, GLFW, etc...).
|
* (e.g. SDL, QGLWidget, GLFW, etc...).
|
||||||
@ -112,6 +114,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
|
void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
|
||||||
|
|
||||||
|
bool IsScreenshotRequested() const {
|
||||||
|
return screenshot_requested;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void ReceiveScreenshot(std::unique_ptr<ScreenshotData> screenshot) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EmuWindow() {
|
EmuWindow() {
|
||||||
// TODO: Find a better place to set this.
|
// TODO: Find a better place to set this.
|
||||||
@ -120,6 +128,7 @@ protected:
|
|||||||
touch_x = 0;
|
touch_x = 0;
|
||||||
touch_y = 0;
|
touch_y = 0;
|
||||||
touch_pressed = false;
|
touch_pressed = false;
|
||||||
|
screenshot_requested = false;
|
||||||
}
|
}
|
||||||
virtual ~EmuWindow() {}
|
virtual ~EmuWindow() {}
|
||||||
|
|
||||||
@ -182,6 +191,8 @@ private:
|
|||||||
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
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)
|
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.
|
* Clip the provided coordinates to be inside the touchscreen area.
|
||||||
*/
|
*/
|
||||||
|
36
src/core/frontend/screenshot_data.h
Normal file
36
src/core/frontend/screenshot_data.h
Normal 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;
|
||||||
|
};
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#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/hw.h"
|
||||||
#include "core/hw/lcd.h"
|
#include "core/hw/lcd.h"
|
||||||
#include "video_core/renderer_base.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);
|
LCD::Read(color_fill.raw, lcd_color_addr);
|
||||||
return color_fill.raw;
|
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));
|
||||||
|
}
|
||||||
|
@ -59,6 +59,8 @@ protected:
|
|||||||
|
|
||||||
u32 GetColorFillForFramebuffer(int framebuffer_index);
|
u32 GetColorFillForFramebuffer(int framebuffer_index);
|
||||||
|
|
||||||
|
void SaveScreenshot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool opengl_rasterizer_active = false;
|
bool opengl_rasterizer_active = false;
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "core/frontend/screenshot_data.h"
|
||||||
#include "core/hw/gpu.h"
|
#include "core/hw/gpu.h"
|
||||||
#include "core/hw/lcd.h"
|
#include "core/hw/lcd.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
@ -135,6 +136,9 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
|
|
||||||
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
||||||
|
|
||||||
|
if (render_window->IsScreenshotRequested())
|
||||||
|
SaveScreenshot();
|
||||||
|
|
||||||
// Swap buffers
|
// Swap buffers
|
||||||
render_window->PollEvents();
|
render_window->PollEvents();
|
||||||
render_window->SwapBuffers();
|
render_window->SwapBuffers();
|
||||||
|
Loading…
Reference in New Issue
Block a user