mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-26 09:20: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/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);
|
||||
}
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
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 <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));
|
||||
}
|
||||
|
@ -59,6 +59,8 @@ protected:
|
||||
|
||||
u32 GetColorFillForFramebuffer(int framebuffer_index);
|
||||
|
||||
void SaveScreenshot();
|
||||
|
||||
private:
|
||||
bool opengl_rasterizer_active = false;
|
||||
};
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user