summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/frontend/framebuffer_layout.cpp15
-rw-r--r--src/core/frontend/framebuffer_layout.h7
-rw-r--r--src/video_core/renderer_base.cpp12
-rw-r--r--src/video_core/renderer_base.h27
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp41
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h9
-rw-r--r--src/video_core/video_core.cpp8
-rw-r--r--src/video_core/video_core.h2
-rw-r--r--src/yuzu/bootmanager.cpp18
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/config.cpp5
-rw-r--r--src/yuzu/main.cpp28
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui88
-rw-r--r--src/yuzu/ui_settings.h4
15 files changed, 228 insertions, 43 deletions
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 8f07aedc9..f8662d193 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -6,6 +6,7 @@
#include "common/assert.h"
#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
namespace Layout {
@@ -42,4 +43,18 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
return res;
}
+FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
+ int width, height;
+
+ if (Settings::values.use_docked_mode) {
+ width = ScreenDocked::WidthDocked * res_scale;
+ height = ScreenDocked::HeightDocked * res_scale;
+ } else {
+ width = ScreenUndocked::Width * res_scale;
+ height = ScreenUndocked::Height * res_scale;
+ }
+
+ return DefaultFrameLayout(width, height);
+}
+
} // namespace Layout
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index f3fddfa94..e06647794 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -9,6 +9,7 @@
namespace Layout {
enum ScreenUndocked : unsigned { Width = 1280, Height = 720 };
+enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 };
/// Describes the layout of the window framebuffer
struct FramebufferLayout {
@@ -34,4 +35,10 @@ struct FramebufferLayout {
*/
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height);
+/**
+ * Convenience method to get frame layout by resolution scale
+ * @param res_scale resolution scale factor
+ */
+FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
+
} // namespace Layout
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 1482cdb40..94223f45f 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -27,4 +27,16 @@ void RendererBase::UpdateCurrentFramebufferLayout() {
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height);
}
+void RendererBase::RequestScreenshot(void* data, std::function<void()> callback,
+ const Layout::FramebufferLayout& layout) {
+ if (renderer_settings.screenshot_requested) {
+ LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
+ return;
+ }
+ renderer_settings.screenshot_bits = data;
+ renderer_settings.screenshot_complete_callback = std::move(callback);
+ renderer_settings.screenshot_framebuffer_layout = layout;
+ renderer_settings.screenshot_requested = true;
+}
+
} // namespace VideoCore
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 669e26e15..1d54c3723 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -9,6 +9,7 @@
#include <optional>
#include "common/common_types.h"
+#include "core/frontend/emu_window.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
@@ -21,6 +22,12 @@ namespace VideoCore {
struct RendererSettings {
std::atomic_bool use_framelimiter{false};
std::atomic_bool set_background_color{false};
+
+ // Screenshot
+ std::atomic<bool> screenshot_requested{false};
+ void* screenshot_bits;
+ std::function<void()> screenshot_complete_callback;
+ Layout::FramebufferLayout screenshot_framebuffer_layout;
};
class RendererBase : NonCopyable {
@@ -57,9 +64,29 @@ public:
return *rasterizer;
}
+ Core::Frontend::EmuWindow& GetRenderWindow() {
+ return render_window;
+ }
+
+ const Core::Frontend::EmuWindow& GetRenderWindow() const {
+ return render_window;
+ }
+
+ RendererSettings& Settings() {
+ return renderer_settings;
+ }
+
+ const RendererSettings& Settings() const {
+ return renderer_settings;
+ }
+
/// Refreshes the settings common to all renderers
void RefreshBaseSettings();
+ /// Request a screenshot of the next frame
+ void RequestScreenshot(void* data, std::function<void()> callback,
+ const Layout::FramebufferLayout& layout);
+
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index f02415139..235732d86 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -138,7 +138,12 @@ void RendererOpenGL::SwapBuffers(
// Load the framebuffer from memory, draw it to the screen, and swap buffers
LoadFBToScreenInfo(*framebuffer);
- DrawScreen();
+
+ if (renderer_settings.screenshot_requested)
+ CaptureScreenshot();
+
+ DrawScreen(render_window.GetFramebufferLayout());
+
render_window.SwapBuffers();
}
@@ -383,14 +388,13 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
/**
* Draws the emulated screens to the emulator window.
*/
-void RendererOpenGL::DrawScreen() {
+void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
if (renderer_settings.set_background_color) {
// Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
}
- const auto& layout = render_window.GetFramebufferLayout();
const auto& screen = layout.screen;
glViewport(0, 0, layout.width, layout.height);
@@ -414,6 +418,37 @@ void RendererOpenGL::DrawScreen() {
/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
+void RendererOpenGL::CaptureScreenshot() {
+ // Draw the current 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{renderer_settings.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);
+
+ DrawScreen(layout);
+
+ glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ renderer_settings.screenshot_bits);
+
+ screenshot_framebuffer.Release();
+ state.draw.read_framebuffer = old_read_fb;
+ state.draw.draw_framebuffer = old_draw_fb;
+ state.Apply();
+ glDeleteRenderbuffers(1, &renderbuffer);
+
+ renderer_settings.screenshot_complete_callback();
+ renderer_settings.screenshot_requested = false;
+}
+
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index c0868c0e4..067fad81b 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -16,6 +16,10 @@ namespace Core::Frontend {
class EmuWindow;
}
+namespace Layout {
+class FramebufferLayout;
+}
+
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -66,10 +70,12 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture,
const Tegra::FramebufferConfig& framebuffer);
- void DrawScreen();
+ void DrawScreen(const Layout::FramebufferLayout& layout);
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
void UpdateFramerate();
+ void CaptureScreenshot();
+
// Loads framebuffer from emulated memory into the display information structure
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
// Fills active OpenGL texture with the given RGBA color.
@@ -82,6 +88,7 @@ private:
OGLVertexArray vertex_array;
OGLBuffer vertex_buffer;
OGLProgram shader;
+ OGLFramebuffer screenshot_framebuffer;
/// Display information for Switch screen
ScreenInfo screen_info;
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 07e3a7d24..f7de3471b 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,6 +3,8 @@
// Refer to the license.txt file included.
#include <memory>
+#include "core/core.h"
+#include "core/settings.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
@@ -13,4 +15,10 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
}
+u16 GetResolutionScaleFactor(const RendererBase& renderer) {
+ return !Settings::values.resolution_factor
+ ? renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio()
+ : Settings::values.resolution_factor;
+}
+
} // namespace VideoCore
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index f79f85dfe..5b373bcb1 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -22,4 +22,6 @@ class RendererBase;
*/
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window);
+u16 GetResolutionScaleFactor(const RendererBase& renderer);
+
} // namespace VideoCore
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 384e17921..40db7a5e9 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -14,6 +14,8 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
@@ -333,6 +335,22 @@ void GRenderWindow::InitRenderTarget() {
BackupGeometry();
}
+void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
+ auto& renderer = Core::System::GetInstance().Renderer();
+
+ if (!res_scale)
+ res_scale = VideoCore::GetResolutionScaleFactor(renderer);
+
+ const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
+ screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
+ renderer.RequestScreenshot(screenshot_image.bits(),
+ [=] {
+ screenshot_image.mirrored(false, true).save(screenshot_path);
+ LOG_INFO(Frontend, "The screenshot is saved.");
+ },
+ layout);
+}
+
void GRenderWindow::OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 873985564..4e3028215 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -8,6 +8,7 @@
#include <condition_variable>
#include <mutex>
#include <QGLWidget>
+#include <QImage>
#include <QThread>
#include "common/thread.h"
#include "core/core.h"
@@ -139,6 +140,8 @@ public:
void InitRenderTarget();
+ void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
+
public slots:
void moveContext(); // overridden
@@ -165,6 +168,9 @@ private:
EmuThread* emu_thread;
+ /// Temporary storage of the screenshot taken
+ QImage screenshot_image;
+
protected:
void showEvent(QShowEvent* event) override;
};
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 759362ce3..c4349ccc8 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -468,6 +468,8 @@ void Config::ReadValues() {
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
qt_config->value("enable_discord_presence", true).toBool();
+ UISettings::values.screenshot_resolution_factor =
+ static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -689,6 +691,8 @@ void Config::SaveValues() {
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
+ qt_config->setValue("screenshot_resolution_factor",
+ UISettings::values.screenshot_resolution_factor);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
@@ -710,6 +714,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Paths");
qt_config->setValue("romsPath", UISettings::values.roms_path);
qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
+ qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
qt_config->setValue("recentFiles", UISettings::values.recent_files);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index feb5572c6..01a0f94ab 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -358,6 +358,9 @@ void GMainWindow::InitializeHotkeys() {
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
Qt::ApplicationShortcut);
+ hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
+ QKeySequence(QKeySequence::Print));
+
hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@@ -417,6 +420,12 @@ void GMainWindow::InitializeHotkeys() {
OnLoadAmiibo();
}
});
+ connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
+ &QShortcut::activated, this, [&] {
+ if (emu_thread->IsRunning()) {
+ OnCaptureScreenshot();
+ }
+ });
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -514,6 +523,10 @@ void GMainWindow::ConnectMenuEvents() {
hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
+ // Movie
+ connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
+ &GMainWindow::OnCaptureScreenshot);
+
// Help
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
connect(ui.action_Rederive, &QAction::triggered, this,
@@ -751,6 +764,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
+ ui.action_Capture_Screenshot->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -1314,6 +1328,7 @@ void GMainWindow::OnStartGame() {
discord_rpc->Update();
ui.action_Load_Amiibo->setEnabled(true);
+ ui.action_Capture_Screenshot->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1322,6 +1337,7 @@ void GMainWindow::OnPauseGame() {
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
+ ui.action_Capture_Screenshot->setEnabled(false);
}
void GMainWindow::OnStopGame() {
@@ -1484,6 +1500,18 @@ void GMainWindow::OnToggleFilterBar() {
}
}
+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() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 4a982bb02..4e37f6a2d 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -189,6 +189,7 @@ private slots:
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
+ void OnCaptureScreenshot();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 75e96387f..ffcabb495 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -101,11 +101,13 @@
<addaction name="action_Show_Status_Bar"/>
<addaction name="menu_View_Debugging"/>
</widget>
- <widget class ="QMenu" name="menu_Tools">
+ <widget class="QMenu" name="menu_Tools">
<property name="title">
<string>Tools</string>
</property>
- <addaction name="action_Rederive" />
+ <addaction name="action_Rederive"/>
+ <addaction name="separator"/>
+ <addaction name="action_Capture_Screenshot"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
@@ -118,7 +120,7 @@
<addaction name="menu_File"/>
<addaction name="menu_Emulation"/>
<addaction name="menu_View"/>
- <addaction name="menu_Tools" />
+ <addaction name="menu_Tools"/>
<addaction name="menu_Help"/>
</widget>
<action name="action_Install_File_NAND">
@@ -173,11 +175,11 @@
<string>&amp;Stop</string>
</property>
</action>
- <action name="action_Rederive">
- <property name="text">
- <string>Reinitialize keys...</string>
- </property>
- </action>
+ <action name="action_Rederive">
+ <property name="text">
+ <string>Reinitialize keys...</string>
+ </property>
+ </action>
<action name="action_About">
<property name="text">
<string>About yuzu</string>
@@ -252,39 +254,47 @@
<string>Fullscreen</string>
</property>
</action>
- <action name="action_Restart">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Restart</string>
- </property>
- </action>
+ <action name="action_Restart">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Restart</string>
+ </property>
+ </action>
<action name="action_Load_Amiibo">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Load Amiibo...</string>
- </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Load Amiibo...</string>
+ </property>
</action>
- <action name="action_Report_Compatibility">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Report Compatibility</string>
- </property>
- <property name="visible">
- <bool>false</bool>
- </property>
- </action>
- <action name="action_Open_yuzu_Folder">
- <property name="text">
- <string>Open yuzu Folder</string>
- </property>
- </action>
- </widget>
+ <action name="action_Report_Compatibility">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Report Compatibility</string>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </action>
+ <action name="action_Open_yuzu_Folder">
+ <property name="text">
+ <string>Open yuzu Folder</string>
+ </property>
+ </action>
+ <action name="action_Capture_Screenshot">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Capture Screenshot</string>
+ </property>
+ </action>
+ </widget>
<resources/>
<connections/>
</ui>
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 2f434eab0..58ba240fd 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -10,6 +10,7 @@
#include <QByteArray>
#include <QString>
#include <QStringList>
+#include "common/common_types.h"
namespace UISettings {
@@ -42,8 +43,11 @@ struct Values {
// Discord RPC
bool enable_discord_presence;
+ u16 screenshot_resolution_factor;
+
QString roms_path;
QString symbols_path;
+ QString screenshot_path;
QString gamedir;
bool gamedir_deepscan;
QStringList recent_files;