From fea4505ddaca1a3272a4a49ee1eb3d77cf953958 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Fri, 29 Aug 2014 22:23:12 -0700 Subject: [PATCH] Viewport scaling and display density independence The view is scaled to be as large as possible, without changing the aspect, within the bounds of the window. On "retina" displays, or other displays where window units != pixels, the view should no longer draw incorrectly. --- src/citra/emu_window/emu_window_glfw.cpp | 5 +++ src/citra/emu_window/emu_window_glfw.h | 5 ++- src/citra_qt/bootmanager.cpp | 18 +++++++++ src/citra_qt/bootmanager.hxx | 1 + src/common/emu_window.h | 3 ++ .../renderer_opengl/renderer_opengl.cpp | 40 ++++++++++++++++++- .../renderer_opengl/renderer_opengl.h | 15 +++++++ 7 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp index 02f524e03..7c43214cc 100644 --- a/src/citra/emu_window/emu_window_glfw.cpp +++ b/src/citra/emu_window/emu_window_glfw.cpp @@ -18,6 +18,10 @@ static void OnWindowSizeEvent(GLFWwindow* win, int width, int height) { emu_window->SetClientAreaHeight(height); } +void EmuWindow_GLFW::GetFramebufferSize(int* fbWidth, int* fbHeight) { + glfwGetFramebufferSize(m_render_window, fbWidth, fbHeight); +} + /// EmuWindow_GLFW constructor EmuWindow_GLFW::EmuWindow_GLFW() { // Initialize the window @@ -48,6 +52,7 @@ EmuWindow_GLFW::EmuWindow_GLFW() { //glfwSetKeyCallback(m_render_window, OnKeyEvent); //glfwSetWindowSizeCallback(m_render_window, OnWindowSizeEvent); + DoneCurrent(); } diff --git a/src/citra/emu_window/emu_window_glfw.h b/src/citra/emu_window/emu_window_glfw.h index c1b41203b..ee7c03251 100644 --- a/src/citra/emu_window/emu_window_glfw.h +++ b/src/citra/emu_window/emu_window_glfw.h @@ -17,7 +17,7 @@ public: void SwapBuffers(); /// Polls window events - void PollEvents(); + void PollEvents(); /// Makes the graphics context current for the caller thread void MakeCurrent(); @@ -25,6 +25,9 @@ public: /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread void DoneCurrent(); + /// Gets the size of the window in pixels + void GetFramebufferSize(int* fbWidth, int* fbHeight); + GLFWwindow* m_render_window; ///< Internal GLFW render window private: diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 573060d30..8a770925b 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -179,6 +179,24 @@ void GRenderWindow::PollEvents() { */ } +// On Qt 5.1+, this correctly gets the size of the framebuffer (pixels). +// +// Older versions get the window size (density independent pixels), +// and hence, do not support DPI scaling ("retina" displays). +// The result will be a viewport that is smaller than the extent of the window. +void GRenderWindow::GetFramebufferSize(int* fbWidth, int* fbHeight) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) + int pixelRatio = child->QPaintDevice::devicePixelRatio(); + + *fbWidth = child->QPaintDevice::width() * pixelRatio; + *fbHeight = child->QPaintDevice::height() * pixelRatio; +#else + *fbWidth = child->QPaintDevice::width(); + *fbHeight = child->QPaintDevice::height(); +#endif +} + void GRenderWindow::BackupGeometry() { geometry = ((QGLWidget*)this)->saveGeometry(); diff --git a/src/citra_qt/bootmanager.hxx b/src/citra_qt/bootmanager.hxx index 51cb781e9..0cd9b3ed5 100644 --- a/src/citra_qt/bootmanager.hxx +++ b/src/citra_qt/bootmanager.hxx @@ -96,6 +96,7 @@ public: void MakeCurrent(); void DoneCurrent(); void PollEvents(); + void GetFramebufferSize(int* fbWidth, int* fbHeight); void BackupGeometry(); void RestoreGeometry(); diff --git a/src/common/emu_window.h b/src/common/emu_window.h index 5e2c33d7a..bb1f5aecf 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -32,6 +32,9 @@ public: /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread virtual void DoneCurrent() = 0; + /// Gets the size of the window in pixels + virtual void GetFramebufferSize(int* fbWidth, int* fbHeight) = 0; + Config GetConfig() const { return m_config; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 0e4e06517..4f0eb8213 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -191,7 +191,8 @@ void RendererOpenGL::InitFramebuffer() { } void RendererOpenGL::RenderFramebuffer() { - glViewport(0, 0, resolution_width, resolution_height); + UpdateViewportExtent(); + glViewport(viewport_extent.x, viewport_extent.y, viewport_extent.width, viewport_extent.height); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program_id); @@ -243,6 +244,43 @@ void RendererOpenGL::SetWindow(EmuWindow* window) { render_window = window; } +// TODO: This should happen on window resize callback from EmuWindow. +// It should be done when EmuWindow is refactored. +void RendererOpenGL::UpdateViewportExtent() { + int width_in_pixels; + int height_in_pixels; + + render_window->GetFramebufferSize(&width_in_pixels, &height_in_pixels); + + // No update needed if framebuffer size hasn't changed + if (width_in_pixels == framebuffer_size.width && height_in_pixels == framebuffer_size.height) { + return; + } + + // Letterbox the emulation content into the window + framebuffer_size.width = width_in_pixels; + framebuffer_size.height = height_in_pixels; + + float windowRatio = static_cast(height_in_pixels) / width_in_pixels; + float emulationRatio = static_cast(resolution_height) / resolution_width; + + // If the window is more narrow than the emulation content, borders are applied on the + // top and bottom of the window + if (windowRatio > emulationRatio) { + viewport_extent.width = width_in_pixels; + viewport_extent.height = emulationRatio * viewport_extent.width; + viewport_extent.x = 0; + viewport_extent.y = (height_in_pixels - viewport_extent.height) / 2; + + // Otherwise, borders are applied on the left and right sides of the window + } else { + viewport_extent.height = height_in_pixels; + viewport_extent.width = (1 / emulationRatio) * viewport_extent.height; + viewport_extent.x = (width_in_pixels - viewport_extent.width) / 2; + viewport_extent.y = 0; + } +} + /// Initialize the renderer void RendererOpenGL::Init() { render_window->MakeCurrent(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index eac91df51..fe7375fca 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -52,6 +52,9 @@ private: /// Updates the framerate void UpdateFramerate(); + /// Updates the viewport rectangle + void UpdateViewportExtent(); + /// Structure used for storing information for rendering each 3DS screen struct ScreenInfo { // Properties @@ -81,6 +84,18 @@ private: int resolution_width; ///< Current resolution width int resolution_height; ///< Current resolution height + struct { + int width; + int height; + } framebuffer_size; ///< Current framebuffer size + + struct { + int x; + int y; + int width; + int height; + } viewport_extent; ///< Current viewport rectangle + // OpenGL global object IDs GLuint vertex_array_id; GLuint program_id;