citra_qt, video_core: Screenshot functionality
Allows capturing screenshot at the current internal resolution (native for software renderer), but a setting is available to capture it in other resolutions. The screenshot is saved to a single PNG in the current layout.
This commit is contained in:
parent
7e90abec78
commit
071b41cb61
|
@ -15,6 +15,7 @@
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
#include "input_common/motion_emu.h"
|
#include "input_common/motion_emu.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
||||||
|
|
||||||
|
@ -335,6 +336,19 @@ void GRenderWindow::InitRenderTarget() {
|
||||||
BackupGeometry();
|
BackupGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
|
||||||
|
if (!res_scale)
|
||||||
|
res_scale = VideoCore::GetResolutionScaleFactor();
|
||||||
|
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
|
||||||
|
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||||
|
VideoCore::RequestScreenshot(screenshot_image.bits(),
|
||||||
|
[=] {
|
||||||
|
screenshot_image.mirrored(false, true).save(screenshot_path);
|
||||||
|
LOG_INFO(Frontend, "The screenshot is saved.");
|
||||||
|
},
|
||||||
|
layout);
|
||||||
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
||||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <QGLWidget>
|
#include <QGLWidget>
|
||||||
|
#include <QImage>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
@ -139,6 +140,8 @@ public:
|
||||||
|
|
||||||
void InitRenderTarget();
|
void InitRenderTarget();
|
||||||
|
|
||||||
|
void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void moveContext(); // overridden
|
void moveContext(); // overridden
|
||||||
|
|
||||||
|
@ -165,6 +168,9 @@ private:
|
||||||
|
|
||||||
EmuThread* emu_thread;
|
EmuThread* emu_thread;
|
||||||
|
|
||||||
|
/// Temporary storage of the screenshot taken
|
||||||
|
QImage screenshot_image;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -206,6 +206,8 @@ void Config::ReadValues() {
|
||||||
UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
|
UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
|
||||||
UISettings::values.enable_discord_presence =
|
UISettings::values.enable_discord_presence =
|
||||||
ReadSetting("enable_discord_presence", true).toBool();
|
ReadSetting("enable_discord_presence", true).toBool();
|
||||||
|
UISettings::values.screenshot_resolution_factor =
|
||||||
|
static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
|
||||||
|
|
||||||
qt_config->beginGroup("Updater");
|
qt_config->beginGroup("Updater");
|
||||||
UISettings::values.check_for_update_on_start =
|
UISettings::values.check_for_update_on_start =
|
||||||
|
@ -251,6 +253,7 @@ void Config::ReadValues() {
|
||||||
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
|
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
|
||||||
UISettings::values.movie_record_path = ReadSetting("movieRecordPath").toString();
|
UISettings::values.movie_record_path = ReadSetting("movieRecordPath").toString();
|
||||||
UISettings::values.movie_playback_path = ReadSetting("moviePlaybackPath").toString();
|
UISettings::values.movie_playback_path = ReadSetting("moviePlaybackPath").toString();
|
||||||
|
UISettings::values.screenshot_path = ReadSetting("screenshotPath").toString();
|
||||||
UISettings::values.game_dir_deprecated = ReadSetting("gameListRootDir", ".").toString();
|
UISettings::values.game_dir_deprecated = ReadSetting("gameListRootDir", ".").toString();
|
||||||
UISettings::values.game_dir_deprecated_deepscan =
|
UISettings::values.game_dir_deprecated_deepscan =
|
||||||
ReadSetting("gameListDeepScan", false).toBool();
|
ReadSetting("gameListDeepScan", false).toBool();
|
||||||
|
@ -460,6 +463,8 @@ void Config::SaveValues() {
|
||||||
qt_config->beginGroup("UI");
|
qt_config->beginGroup("UI");
|
||||||
WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
|
WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
|
||||||
WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
|
WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
|
||||||
|
WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
|
||||||
|
0);
|
||||||
|
|
||||||
qt_config->beginGroup("Updater");
|
qt_config->beginGroup("Updater");
|
||||||
WriteSetting("check_for_update_on_start", UISettings::values.check_for_update_on_start, true);
|
WriteSetting("check_for_update_on_start", UISettings::values.check_for_update_on_start, true);
|
||||||
|
@ -487,6 +492,7 @@ void Config::SaveValues() {
|
||||||
WriteSetting("symbolsPath", UISettings::values.symbols_path);
|
WriteSetting("symbolsPath", UISettings::values.symbols_path);
|
||||||
WriteSetting("movieRecordPath", UISettings::values.movie_record_path);
|
WriteSetting("movieRecordPath", UISettings::values.movie_record_path);
|
||||||
WriteSetting("moviePlaybackPath", UISettings::values.movie_playback_path);
|
WriteSetting("moviePlaybackPath", UISettings::values.movie_playback_path);
|
||||||
|
WriteSetting("screenshotPath", UISettings::values.screenshot_path);
|
||||||
qt_config->beginWriteArray("gamedirs");
|
qt_config->beginWriteArray("gamedirs");
|
||||||
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
|
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
|
||||||
qt_config->setArrayIndex(i);
|
qt_config->setArrayIndex(i);
|
||||||
|
|
|
@ -365,6 +365,7 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
Qt::ApplicationShortcut);
|
Qt::ApplicationShortcut);
|
||||||
hotkey_registry.RegisterHotkey("Main Window", "Remove Amiibo", QKeySequence(Qt::Key_F3),
|
hotkey_registry.RegisterHotkey("Main Window", "Remove Amiibo", QKeySequence(Qt::Key_F3),
|
||||||
Qt::ApplicationShortcut);
|
Qt::ApplicationShortcut);
|
||||||
|
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot", QKeySequence(tr("CTRL+P")));
|
||||||
|
|
||||||
hotkey_registry.LoadHotkeys();
|
hotkey_registry.LoadHotkeys();
|
||||||
|
|
||||||
|
@ -439,6 +440,12 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
OnRemoveAmiibo();
|
OnRemoveAmiibo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
|
||||||
|
&QShortcut::activated, this, [&] {
|
||||||
|
if (emu_thread->IsRunning()) {
|
||||||
|
OnCaptureScreenshot();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::ShowUpdaterWidgets() {
|
void GMainWindow::ShowUpdaterWidgets() {
|
||||||
|
@ -588,6 +595,8 @@ void GMainWindow::ConnectMenuEvents() {
|
||||||
Core::System::GetInstance().frame_limiter.AdvanceFrame();
|
Core::System::GetInstance().frame_limiter.AdvanceFrame();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
|
||||||
|
&GMainWindow::OnCaptureScreenshot);
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
connect(ui.action_Open_Citra_Folder, &QAction::triggered, this,
|
connect(ui.action_Open_Citra_Folder, &QAction::triggered, this,
|
||||||
|
@ -882,6 +891,7 @@ void GMainWindow::ShutdownGame() {
|
||||||
ui.action_Enable_Frame_Advancing->setEnabled(false);
|
ui.action_Enable_Frame_Advancing->setEnabled(false);
|
||||||
ui.action_Enable_Frame_Advancing->setChecked(false);
|
ui.action_Enable_Frame_Advancing->setChecked(false);
|
||||||
ui.action_Advance_Frame->setEnabled(false);
|
ui.action_Advance_Frame->setEnabled(false);
|
||||||
|
ui.action_Capture_Screenshot->setEnabled(false);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
if (game_list->isEmpty())
|
if (game_list->isEmpty())
|
||||||
game_list_placeholder->show();
|
game_list_placeholder->show();
|
||||||
|
@ -1169,6 +1179,7 @@ void GMainWindow::OnStartGame() {
|
||||||
ui.action_Load_Amiibo->setEnabled(true);
|
ui.action_Load_Amiibo->setEnabled(true);
|
||||||
ui.action_Report_Compatibility->setEnabled(true);
|
ui.action_Report_Compatibility->setEnabled(true);
|
||||||
ui.action_Enable_Frame_Advancing->setEnabled(true);
|
ui.action_Enable_Frame_Advancing->setEnabled(true);
|
||||||
|
ui.action_Capture_Screenshot->setEnabled(true);
|
||||||
|
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
}
|
}
|
||||||
|
@ -1179,6 +1190,7 @@ void GMainWindow::OnPauseGame() {
|
||||||
ui.action_Start->setEnabled(true);
|
ui.action_Start->setEnabled(true);
|
||||||
ui.action_Pause->setEnabled(false);
|
ui.action_Pause->setEnabled(false);
|
||||||
ui.action_Stop->setEnabled(true);
|
ui.action_Stop->setEnabled(true);
|
||||||
|
ui.action_Capture_Screenshot->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnStopGame() {
|
void GMainWindow::OnStopGame() {
|
||||||
|
@ -1539,6 +1551,18 @@ void GMainWindow::OnStopRecordingPlayback() {
|
||||||
ui.action_Stop_Recording_Playback->setEnabled(false);
|
ui.action_Stop_Recording_Playback->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnCaptureScreenshot() {
|
||||||
|
OnPauseGame();
|
||||||
|
const QString path =
|
||||||
|
QFileDialog::getSaveFileName(this, tr("Capture Screenshot"),
|
||||||
|
UISettings::values.screenshot_path, tr("PNG Image (*.png)"));
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
UISettings::values.screenshot_path = QFileInfo(path).path();
|
||||||
|
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
|
||||||
|
}
|
||||||
|
OnStartGame();
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::UpdateStatusBar() {
|
void GMainWindow::UpdateStatusBar() {
|
||||||
if (emu_thread == nullptr) {
|
if (emu_thread == nullptr) {
|
||||||
status_bar_update_timer.stop();
|
status_bar_update_timer.stop();
|
||||||
|
|
|
@ -183,6 +183,7 @@ private slots:
|
||||||
void OnRecordMovie();
|
void OnRecordMovie();
|
||||||
void OnPlayMovie();
|
void OnPlayMovie();
|
||||||
void OnStopRecordingPlayback();
|
void OnStopRecordingPlayback();
|
||||||
|
void OnCaptureScreenshot();
|
||||||
void OnCoreError(Core::System::ResultStatus, std::string);
|
void OnCoreError(Core::System::ResultStatus, std::string);
|
||||||
/// Called whenever a user selects Help->About Citra
|
/// Called whenever a user selects Help->About Citra
|
||||||
void OnMenuAboutCitra();
|
void OnMenuAboutCitra();
|
||||||
|
|
|
@ -131,6 +131,8 @@
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_Enable_Frame_Advancing"/>
|
<addaction name="action_Enable_Frame_Advancing"/>
|
||||||
<addaction name="action_Advance_Frame"/>
|
<addaction name="action_Advance_Frame"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="action_Capture_Screenshot"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menu_Multiplayer">
|
<widget class="QMenu" name="menu_Multiplayer">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
|
@ -315,6 +317,14 @@
|
||||||
<string>Advance Frame</string>
|
<string>Advance Frame</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Capture_Screenshot">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Capture Screenshot</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_View_Lobby">
|
<action name="action_View_Lobby">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace UISettings {
|
namespace UISettings {
|
||||||
|
|
||||||
|
@ -78,10 +79,13 @@ struct Values {
|
||||||
GameListText game_list_row_2;
|
GameListText game_list_row_2;
|
||||||
bool game_list_hide_no_icon;
|
bool game_list_hide_no_icon;
|
||||||
|
|
||||||
|
u16 screenshot_resolution_factor;
|
||||||
|
|
||||||
QString roms_path;
|
QString roms_path;
|
||||||
QString symbols_path;
|
QString symbols_path;
|
||||||
QString movie_record_path;
|
QString movie_record_path;
|
||||||
QString movie_playback_path;
|
QString movie_playback_path;
|
||||||
|
QString screenshot_path;
|
||||||
QString game_dir_deprecated;
|
QString game_dir_deprecated;
|
||||||
bool game_dir_deprecated_deepscan;
|
bool game_dir_deprecated_deepscan;
|
||||||
QList<UISettings::GameDir> game_dirs;
|
QList<UISettings::GameDir> game_dirs;
|
||||||
|
|
|
@ -191,4 +191,50 @@ FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
|
||||||
res.bottom_screen = bot_screen;
|
res.bottom_screen = bot_screen;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
|
||||||
|
FramebufferLayout layout;
|
||||||
|
if (Settings::values.custom_layout == true) {
|
||||||
|
layout = CustomFrameLayout(
|
||||||
|
std::max(Settings::values.custom_top_right, Settings::values.custom_bottom_right),
|
||||||
|
std::max(Settings::values.custom_top_bottom, Settings::values.custom_bottom_bottom));
|
||||||
|
} else {
|
||||||
|
int width, height;
|
||||||
|
switch (Settings::values.layout_option) {
|
||||||
|
case Settings::LayoutOption::SingleScreen:
|
||||||
|
if (Settings::values.swap_screen) {
|
||||||
|
width = Core::kScreenBottomWidth * res_scale;
|
||||||
|
height = Core::kScreenBottomHeight * res_scale;
|
||||||
|
} else {
|
||||||
|
width = Core::kScreenTopWidth * res_scale;
|
||||||
|
height = Core::kScreenTopHeight * res_scale;
|
||||||
|
}
|
||||||
|
layout = SingleFrameLayout(width, height, Settings::values.swap_screen);
|
||||||
|
break;
|
||||||
|
case Settings::LayoutOption::LargeScreen:
|
||||||
|
if (Settings::values.swap_screen) {
|
||||||
|
width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
|
||||||
|
height = Core::kScreenBottomHeight * res_scale;
|
||||||
|
} else {
|
||||||
|
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
|
||||||
|
height = Core::kScreenTopHeight * res_scale;
|
||||||
|
}
|
||||||
|
layout = LargeFrameLayout(width, height, Settings::values.swap_screen);
|
||||||
|
break;
|
||||||
|
case Settings::LayoutOption::SideScreen:
|
||||||
|
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
|
||||||
|
height = Core::kScreenTopHeight * res_scale;
|
||||||
|
layout = SideFrameLayout(width, height, Settings::values.swap_screen);
|
||||||
|
break;
|
||||||
|
case Settings::LayoutOption::Default:
|
||||||
|
default:
|
||||||
|
width = Core::kScreenTopWidth * res_scale;
|
||||||
|
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
|
||||||
|
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Layout
|
} // namespace Layout
|
||||||
|
|
|
@ -72,4 +72,11 @@ FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool is_swapp
|
||||||
*/
|
*/
|
||||||
FramebufferLayout CustomFrameLayout(unsigned width, unsigned height);
|
FramebufferLayout CustomFrameLayout(unsigned width, unsigned height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to get frame layout by resolution scale
|
||||||
|
* Read from the current settings to determine which layout to use.
|
||||||
|
* @param res_scale resolution scale factor
|
||||||
|
*/
|
||||||
|
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
|
||||||
|
|
||||||
} // namespace Layout
|
} // namespace Layout
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/settings.h"
|
|
||||||
#include "video_core/pica_state.h"
|
#include "video_core/pica_state.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||||
|
@ -78,12 +77,6 @@ constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||||
return boost::make_iterator_range(map.equal_range(interval));
|
return boost::make_iterator_range(map.equal_range(interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
static u16 GetResolutionScaleFactor() {
|
|
||||||
return !Settings::values.resolution_factor
|
|
||||||
? VideoCore::g_renderer->GetRenderWindow().GetFramebufferLayout().GetScalingRatio()
|
|
||||||
: Settings::values.resolution_factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <bool morton_to_gl, PixelFormat format>
|
template <bool morton_to_gl, PixelFormat format>
|
||||||
static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
|
static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
|
||||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8;
|
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8;
|
||||||
|
@ -1360,9 +1353,9 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
|
||||||
const auto& config = regs.framebuffer.framebuffer;
|
const auto& config = regs.framebuffer.framebuffer;
|
||||||
|
|
||||||
// update resolution_scale_factor and reset cache if changed
|
// update resolution_scale_factor and reset cache if changed
|
||||||
static u16 resolution_scale_factor = GetResolutionScaleFactor();
|
static u16 resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
|
||||||
if (resolution_scale_factor != GetResolutionScaleFactor()) {
|
if (resolution_scale_factor != VideoCore::GetResolutionScaleFactor()) {
|
||||||
resolution_scale_factor = GetResolutionScaleFactor();
|
resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
|
||||||
FlushAll();
|
FlushAll();
|
||||||
while (!surface_cache.empty())
|
while (!surface_cache.empty())
|
||||||
UnregisterSurface(*surface_cache.begin()->second.begin());
|
UnregisterSurface(*surface_cache.begin()->second.begin());
|
||||||
|
|
|
@ -140,7 +140,39 @@ void RendererOpenGL::SwapBuffers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawScreens();
|
if (VideoCore::g_renderer_screenshot_requested) {
|
||||||
|
// Draw this frame to the screenshot framebuffer
|
||||||
|
screenshot_framebuffer.Create();
|
||||||
|
GLuint old_read_fb = state.draw.read_framebuffer;
|
||||||
|
GLuint old_draw_fb = state.draw.draw_framebuffer;
|
||||||
|
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
|
||||||
|
state.Apply();
|
||||||
|
|
||||||
|
Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout};
|
||||||
|
|
||||||
|
GLuint renderbuffer;
|
||||||
|
glGenRenderbuffers(1, &renderbuffer);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
|
renderbuffer);
|
||||||
|
|
||||||
|
DrawScreens(layout);
|
||||||
|
|
||||||
|
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||||
|
VideoCore::g_screenshot_bits);
|
||||||
|
|
||||||
|
screenshot_framebuffer.Release();
|
||||||
|
state.draw.read_framebuffer = old_read_fb;
|
||||||
|
state.draw.draw_framebuffer = old_draw_fb;
|
||||||
|
state.Apply();
|
||||||
|
glDeleteRenderbuffers(1, &renderbuffer);
|
||||||
|
|
||||||
|
VideoCore::g_screenshot_complete_callback();
|
||||||
|
VideoCore::g_renderer_screenshot_requested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawScreens(render_window.GetFramebufferLayout());
|
||||||
|
|
||||||
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
||||||
|
|
||||||
|
@ -386,14 +418,13 @@ void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, floa
|
||||||
/**
|
/**
|
||||||
* Draws the emulated screens to the emulator window.
|
* Draws the emulated screens to the emulator window.
|
||||||
*/
|
*/
|
||||||
void RendererOpenGL::DrawScreens() {
|
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
|
||||||
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
|
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
|
||||||
// Update background color before drawing
|
// Update background color before drawing
|
||||||
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
|
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
|
||||||
0.0f);
|
0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto layout = render_window.GetFramebufferLayout();
|
|
||||||
const auto& top_screen = layout.top_screen;
|
const auto& top_screen = layout.top_screen;
|
||||||
const auto& bottom_screen = layout.bottom_screen;
|
const auto& bottom_screen = layout.bottom_screen;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
|
||||||
|
namespace Layout {
|
||||||
|
class FramebufferLayout;
|
||||||
|
}
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
/// Structure used for storing information about the textures for each 3DS screen
|
/// Structure used for storing information about the textures for each 3DS screen
|
||||||
|
@ -50,7 +54,7 @@ private:
|
||||||
void InitOpenGLObjects();
|
void InitOpenGLObjects();
|
||||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
const GPU::Regs::FramebufferConfig& framebuffer);
|
||||||
void DrawScreens();
|
void DrawScreens(const Layout::FramebufferLayout& layout);
|
||||||
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||||
void UpdateFramerate();
|
void UpdateFramerate();
|
||||||
|
|
||||||
|
@ -66,6 +70,7 @@ private:
|
||||||
OGLVertexArray vertex_array;
|
OGLVertexArray vertex_array;
|
||||||
OGLBuffer vertex_buffer;
|
OGLBuffer vertex_buffer;
|
||||||
OGLProgram shader;
|
OGLProgram shader;
|
||||||
|
OGLFramebuffer screenshot_framebuffer;
|
||||||
|
|
||||||
/// Display information for top and bottom screens respectively
|
/// Display information for top and bottom screens respectively
|
||||||
std::array<ScreenInfo, 3> screen_infos;
|
std::array<ScreenInfo, 3> screen_infos;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "video_core/pica.h"
|
#include "video_core/pica.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||||
|
@ -22,6 +23,11 @@ std::atomic<bool> g_hw_shader_enabled;
|
||||||
std::atomic<bool> g_hw_shader_accurate_gs;
|
std::atomic<bool> g_hw_shader_accurate_gs;
|
||||||
std::atomic<bool> g_hw_shader_accurate_mul;
|
std::atomic<bool> g_hw_shader_accurate_mul;
|
||||||
std::atomic<bool> g_renderer_bg_color_update_requested;
|
std::atomic<bool> g_renderer_bg_color_update_requested;
|
||||||
|
// Screenshot
|
||||||
|
std::atomic<bool> g_renderer_screenshot_requested;
|
||||||
|
void* g_screenshot_bits;
|
||||||
|
std::function<void()> g_screenshot_complete_callback;
|
||||||
|
Layout::FramebufferLayout g_screenshot_framebuffer_layout;
|
||||||
|
|
||||||
/// Initialize the video core
|
/// Initialize the video core
|
||||||
Core::System::ResultStatus Init(EmuWindow& emu_window) {
|
Core::System::ResultStatus Init(EmuWindow& emu_window) {
|
||||||
|
@ -48,4 +54,27 @@ void Shutdown() {
|
||||||
LOG_DEBUG(Render, "shutdown OK");
|
LOG_DEBUG(Render, "shutdown OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||||
|
const Layout::FramebufferLayout& layout) {
|
||||||
|
if (g_renderer_screenshot_requested) {
|
||||||
|
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_screenshot_bits = data;
|
||||||
|
g_screenshot_complete_callback = std::move(callback);
|
||||||
|
g_screenshot_framebuffer_layout = layout;
|
||||||
|
g_renderer_screenshot_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 GetResolutionScaleFactor() {
|
||||||
|
if (g_hw_renderer_enabled) {
|
||||||
|
return !Settings::values.resolution_factor
|
||||||
|
? g_renderer->GetRenderWindow().GetFramebufferLayout().GetScalingRatio()
|
||||||
|
: Settings::values.resolution_factor;
|
||||||
|
} else {
|
||||||
|
// Software renderer always render at native resolution
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/emu_window.h"
|
||||||
|
|
||||||
class EmuWindow;
|
class EmuWindow;
|
||||||
class RendererBase;
|
class RendererBase;
|
||||||
|
@ -26,6 +27,11 @@ extern std::atomic<bool> g_hw_shader_enabled;
|
||||||
extern std::atomic<bool> g_hw_shader_accurate_gs;
|
extern std::atomic<bool> g_hw_shader_accurate_gs;
|
||||||
extern std::atomic<bool> g_hw_shader_accurate_mul;
|
extern std::atomic<bool> g_hw_shader_accurate_mul;
|
||||||
extern std::atomic<bool> g_renderer_bg_color_update_requested;
|
extern std::atomic<bool> g_renderer_bg_color_update_requested;
|
||||||
|
// Screenshot
|
||||||
|
extern std::atomic<bool> g_renderer_screenshot_requested;
|
||||||
|
extern void* g_screenshot_bits;
|
||||||
|
extern std::function<void()> g_screenshot_complete_callback;
|
||||||
|
extern Layout::FramebufferLayout g_screenshot_framebuffer_layout;
|
||||||
|
|
||||||
/// Initialize the video core
|
/// Initialize the video core
|
||||||
Core::System::ResultStatus Init(EmuWindow& emu_window);
|
Core::System::ResultStatus Init(EmuWindow& emu_window);
|
||||||
|
@ -33,4 +39,10 @@ Core::System::ResultStatus Init(EmuWindow& emu_window);
|
||||||
/// Shutdown the video core
|
/// Shutdown the video core
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
/// Request a screenshot of the next frame
|
||||||
|
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||||
|
const Layout::FramebufferLayout& layout);
|
||||||
|
|
||||||
|
u16 GetResolutionScaleFactor();
|
||||||
|
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
|
Reference in New Issue