diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp
index 0c774bbc51..d0f6e9a9ea 100644
--- a/src/citra/emu_window/emu_window_glfw.cpp
+++ b/src/citra/emu_window/emu_window_glfw.cpp
@@ -34,6 +34,10 @@ const bool EmuWindow_GLFW::IsOpen() {
     return glfwWindowShouldClose(m_render_window) == 0;
 }
 
+void EmuWindow_GLFW::GetFramebufferSize(int* fbWidth, int* fbHeight) {
+    glfwGetFramebufferSize(m_render_window, fbWidth, fbHeight);
+}
+
 /// EmuWindow_GLFW constructor
 EmuWindow_GLFW::EmuWindow_GLFW() {
     keyboard_id = KeyMap::NewDeviceId();
@@ -64,6 +68,7 @@ EmuWindow_GLFW::EmuWindow_GLFW() {
     glfwSetWindowUserPointer(m_render_window, this);
     glfwSetKeyCallback(m_render_window, OnKeyEvent);
 
+
     DoneCurrent();
 }
 
diff --git a/src/citra/emu_window/emu_window_glfw.h b/src/citra/emu_window/emu_window_glfw.h
index 7c3072145f..e962287652 100644
--- a/src/citra/emu_window/emu_window_glfw.h
+++ b/src/citra/emu_window/emu_window_glfw.h
@@ -21,7 +21,7 @@ public:
 
     /// Makes the graphics context current for the caller thread
     void MakeCurrent() override;
-    
+
     /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
     void DoneCurrent() override;
 
@@ -32,6 +32,9 @@ public:
 
     void ReloadSetKeymaps() override;
 
+    /// Gets the size of the window in pixels
+    void GetFramebufferSize(int* fbWidth, int* fbHeight);
+
 private:
     GLFWwindow* m_render_window; ///< Internal GLFW render window
 
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 20824692d8..516e115fd1 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -2,6 +2,12 @@
 #include <QKeyEvent>
 #include <QApplication>
 
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+// Required for screen DPI information
+#include <QScreen>
+#include <QWindow>
+#endif
+
 #include "common/common.h"
 #include "bootmanager.hxx"
 
@@ -176,6 +182,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 f8afc403eb..ec3e1fe71c 100644
--- a/src/citra_qt/bootmanager.hxx
+++ b/src/citra_qt/bootmanager.hxx
@@ -96,6 +96,7 @@ public:
     void MakeCurrent() override;
     void DoneCurrent() override;
     void PollEvents() override;
+    void GetFramebufferSize(int* fbWidth, int* fbHeight) override;
 
     void BackupGeometry();
     void RestoreGeometry();
diff --git a/src/common/emu_window.h b/src/common/emu_window.h
index 6c2b598f6d..ba9d4fa768 100644
--- a/src/common/emu_window.h
+++ b/src/common/emu_window.h
@@ -49,8 +49,11 @@ public:
     void SetConfig(const WindowConfig& val) {
         m_config = val;
     }
-    
-    int GetClientAreaWidth() const { 
+
+    /// Gets the size of the window in pixels
+    virtual void GetFramebufferSize(int* fbWidth, int* fbHeight) = 0;
+
+    int GetClientAreaWidth() const {
         return m_client_area_width;
     }
 
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 8483f79bee..3757482db1 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::DrawSingleScreenRotated(const TextureInfo& texture, float x
  * Draws the emulated screens to the emulator window.
  */
 void RendererOpenGL::DrawScreens() {
-    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);
@@ -228,6 +229,39 @@ void RendererOpenGL::SetWindow(EmuWindow* window) {
     render_window = window;
 }
 
+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;
+    }
+
+    framebuffer_size.width = width_in_pixels;
+    framebuffer_size.height = height_in_pixels;
+
+    float window_aspect_ratio = static_cast<float>(height_in_pixels) / width_in_pixels;
+    float emulation_aspect_ratio = static_cast<float>(resolution_height) / resolution_width;
+
+    if (window_aspect_ratio > emulation_aspect_ratio) {
+        // If the window is more narrow than the emulation content, borders are applied on the
+        // top and bottom of the window.
+        viewport_extent.width = width_in_pixels;
+        viewport_extent.height = emulation_aspect_ratio * viewport_extent.width;
+        viewport_extent.x = 0;
+        viewport_extent.y = (height_in_pixels - viewport_extent.height) / 2;
+    } else {
+        // Otherwise, borders are applied on the left and right sides of the window.
+        viewport_extent.height = height_in_pixels;
+        viewport_extent.width = (1 / emulation_aspect_ratio) * 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 eed201a950..d440e2bc79 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -52,12 +52,27 @@ private:
     static void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer,
                                         const TextureInfo& texture);
 
+    /// Updates the viewport rectangle
+    void UpdateViewportExtent();
+
     EmuWindow*  render_window;                    ///< Handle to render window
     u32         last_mode;                        ///< Last render mode
 
     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 object IDs
     GLuint vertex_array_handle;
     GLuint vertex_buffer_handle;