Reimplemented magnification and aspect ratio handling
authorIngo Ruhnke <grumbel@gmail.com>
Fri, 1 Aug 2014 19:01:22 +0000 (21:01 +0200)
committerIngo Ruhnke <grumbel@gmail.com>
Sat, 2 Aug 2014 21:59:14 +0000 (23:59 +0200)
src/video/gl/gl_renderer.cpp
src/video/sdl/sdl_renderer.cpp
src/video/sdl/sdl_renderer.hpp
src/video/util.cpp [new file with mode: 0644]
src/video/util.hpp [new file with mode: 0644]

index 599eb53..a1b9790 100644 (file)
@@ -27,6 +27,8 @@
 #include "video/drawing_request.hpp"
 #include "video/gl/gl_surface_data.hpp"
 #include "video/gl/gl_texture.hpp"
+#include "video/util.hpp"
+
 #define LIGHTMAP_DIV 5
 
 #ifdef GL_VERSION_ES_CM_1_0
@@ -508,83 +510,32 @@ GLRenderer::apply_config()
 
   apply_video_mode(screen_size, g_config->use_fullscreen);
 
-  if (target_aspect > 1.0f)
-  {
-    SCREEN_WIDTH  = static_cast<int>(screen_size.width * (target_aspect / desktop_aspect));
-    SCREEN_HEIGHT = static_cast<int>(screen_size.height);
-  }
-  else
-  {
-    SCREEN_WIDTH  = static_cast<int>(screen_size.width);
-    SCREEN_HEIGHT = static_cast<int>(screen_size.height  * (target_aspect / desktop_aspect));
-  }
-
   Size max_size(1280, 800);
   Size min_size(640, 480);
 
-  if (g_config->magnification == 0.0f) // Magic value that means 'minfill'
-  {
-    // This scales SCREEN_WIDTH/SCREEN_HEIGHT so that they never excede
-    // max_size.width/max_size.height resp. min_size.width/min_size.height
-    if (SCREEN_WIDTH > max_size.width || SCREEN_HEIGHT > max_size.height)
-    {
-      float scale1  = float(max_size.width)/SCREEN_WIDTH;
-      float scale2  = float(max_size.height)/SCREEN_HEIGHT;
-      float scale   = (scale1 < scale2) ? scale1 : scale2;
-      SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * scale);
-      SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * scale);
-    }
-    else if (SCREEN_WIDTH < min_size.width || SCREEN_HEIGHT < min_size.height)
-    {
-      float scale1  = float(min_size.width)/SCREEN_WIDTH;
-      float scale2  = float(min_size.height)/SCREEN_HEIGHT;
-      float scale   = (scale1 < scale2) ? scale1 : scale2;
-      SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * scale);
-      SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * scale);
-    }
+  Vector scale;
+  Size logical_size;
+  calculate_viewport(min_size, max_size,
+                     screen_size,
+                     target_aspect / desktop_aspect, g_config->magnification,
+                     scale,
+                     logical_size,
+                     viewport);
 
-    viewport.x = 0;
-    viewport.y = 0;
-    viewport.w = screen_size.width;
-    viewport.h = screen_size.height;
+  SCREEN_WIDTH = logical_size.width;
+  SCREEN_HEIGHT = logical_size.height;
 
-    glViewport(viewport.x, viewport.y, viewport.w, viewport.h);
-  }
-  else
+  if (viewport.x != 0 || viewport.y != 0)
   {
-    SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  / g_config->magnification);
-    SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT / g_config->magnification);
-
-    // This works by adding black borders around the screen to limit
-    // SCREEN_WIDTH/SCREEN_HEIGHT to max_size.width/max_size.height
-    Size new_size = screen_size;
-
-    if (SCREEN_WIDTH > max_size.width)
-    {
-      new_size.width = static_cast<int>((float) new_size.width * float(max_size.width)/SCREEN_WIDTH);
-      SCREEN_WIDTH = static_cast<int>(max_size.width);
-    }
-
-    if (SCREEN_HEIGHT > max_size.height)
-    {
-      new_size.height = static_cast<int>((float) new_size.height * float(max_size.height)/SCREEN_HEIGHT);
-      SCREEN_HEIGHT = static_cast<int>(max_size.height);
-    }
-
     // Clear both buffers so that we get a clean black border without junk
     glClear(GL_COLOR_BUFFER_BIT);
     SDL_GL_SwapWindow(window);
     glClear(GL_COLOR_BUFFER_BIT);
     SDL_GL_SwapWindow(window);
-
-    viewport.x = std::max(0, (screen_size.width  - new_size.width)  / 2);
-    viewport.y = std::max(0, (screen_size.height - new_size.height) / 2);
-    viewport.w = std::min(new_size.width,  screen_size.width);
-    viewport.h = std::min(new_size.height, screen_size.height);
-
-    glViewport(viewport.x, viewport.y, viewport.w, viewport.h);
   }
 
+  glViewport(viewport.x, viewport.y, viewport.w, viewport.h);
+
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
 
index 736b299..41019e9 100644 (file)
@@ -30,6 +30,8 @@
 #include <stdexcept>
 #include "SDL2/SDL_video.h"
 
+#include "video/util.hpp"
+
 SDLRenderer::SDLRenderer() :
   window(),
   renderer(),
@@ -39,7 +41,7 @@ SDLRenderer::SDLRenderer() :
   Renderer::instance_ = this;
 
   SDL_DisplayMode mode;
-  SDL_GetCurrentDisplayMode(0, &mode);
+  SDL_GetDesktopDisplayMode(0, &mode);
   desktop_size = Size(mode.w, mode.h);
 
   log_info << "creating SDLRenderer" << std::endl;
@@ -59,8 +61,8 @@ SDLRenderer::SDLRenderer() :
 
   viewport.x = 0;
   viewport.y = 0;
-  viewport.w = SCREEN_WIDTH;
-  viewport.h = SCREEN_HEIGHT;
+  viewport.w = width;
+  viewport.h = height;
 
   SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
 
@@ -99,6 +101,7 @@ SDLRenderer::SDLRenderer() :
   if(texture_manager == 0)
     texture_manager = new TextureManager();
 
+  g_config->window_size = Size(width, height);
   apply_config();
 }
 
@@ -219,44 +222,8 @@ SDLRenderer::resize(int w , int h)
 }
 
 void
-SDLRenderer::apply_config()
+SDLRenderer::apply_video_mode()
 {
-  if (false)
-  {
-    log_info << "Applying Config:"
-             << "\n  Desktop: " << desktop_size.width << "x" << desktop_size.height
-             << "\n  Window:  " << g_config->window_size
-             << "\n  FullRes: " << g_config->fullscreen_size
-             << "\n  Aspect:  " << g_config->aspect_size
-             << "\n  Magnif:  " << g_config->magnification
-             << std::endl;
-  }
-
-  float target_aspect = static_cast<float>(desktop_size.width) / static_cast<float>(desktop_size.height);
-  if (g_config->aspect_size != Size(0, 0))
-  {
-    target_aspect = float(g_config->aspect_size.width) / float(g_config->aspect_size.height);
-  }
-
-  float desktop_aspect = 4.0f / 3.0f; // random default fallback guess
-  if (desktop_size.width != -1 && desktop_size.height != -1)
-  {
-    desktop_aspect = float(desktop_size.width) / float(desktop_size.height);
-  }
-
-  Size screen_size;
-
-  // Get the screen width
-  if (g_config->use_fullscreen)
-  {
-    screen_size = g_config->fullscreen_size;
-    desktop_aspect = float(screen_size.width) / float(screen_size.height);
-  }
-  else
-  {
-    screen_size = g_config->window_size;
-  }
-
   if (!g_config->use_fullscreen)
   {
     SDL_SetWindowFullscreen(window, 0);
@@ -282,73 +249,57 @@ SDLRenderer::apply_config()
     }
   }
 
-  if (target_aspect > 1.0f)
+}
+
+void
+SDLRenderer::apply_viewport()
+{
+  // calculate the aspect ratio
+  float target_aspect = static_cast<float>(desktop_size.width) / static_cast<float>(desktop_size.height);
+  if (g_config->aspect_size != Size(0, 0))
   {
-    SCREEN_WIDTH  = static_cast<int>(screen_size.width * (target_aspect / desktop_aspect));
-    SCREEN_HEIGHT = static_cast<int>(screen_size.height);
+    target_aspect = float(g_config->aspect_size.width) / float(g_config->aspect_size.height);
   }
-  else
+
+  float desktop_aspect = 4.0f / 3.0f; // random default fallback guess
+  if (desktop_size.width != -1 && desktop_size.height != -1)
   {
-    SCREEN_WIDTH  = static_cast<int>(screen_size.width);
-    SCREEN_HEIGHT = static_cast<int>(screen_size.height  * (target_aspect / desktop_aspect));
+    desktop_aspect = float(desktop_size.width) / float(desktop_size.height);
   }
 
-  Size max_size(1280, 800);
-  Size min_size(640, 480);
+  Size screen_size;
 
-  if (g_config->magnification == 0.0f) // Magic value that means 'minfill'
+  // Get the screen width
+  if (g_config->use_fullscreen)
   {
-    float magnification = 1.0f;
-
-    // This scales SCREEN_WIDTH/SCREEN_HEIGHT so that they never excede
-    // max_size.width/max_size.height resp. min_size.width/min_size.height
-    if (SCREEN_WIDTH > max_size.width || SCREEN_HEIGHT > max_size.height)
-    {
-      float scale1  = float(max_size.width)/SCREEN_WIDTH;
-      float scale2  = float(max_size.height)/SCREEN_HEIGHT;
-      magnification = (scale1 < scale2) ? scale1 : scale2;
-      SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * magnification);
-      SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * magnification);
-    }
-    else if (SCREEN_WIDTH < min_size.width || SCREEN_HEIGHT < min_size.height)
-    {
-      float scale1  = float(min_size.width)/SCREEN_WIDTH;
-      float scale2  = float(min_size.height)/SCREEN_HEIGHT;
-      magnification = (scale1 < scale2) ? scale1 : scale2;
-      SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  * magnification);
-      SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT * magnification);
-    }
-
-    viewport.x = 0;
-    viewport.y = 0;
-    viewport.w = screen_size.width;
-    viewport.h = screen_size.height;
-
-    SDL_RenderSetScale(renderer, 1.0f, 1.0f);
-    SDL_RenderSetViewport(renderer, &viewport);
-    SDL_RenderSetScale(renderer, magnification, magnification);
+    screen_size = g_config->fullscreen_size;
+    desktop_aspect = float(screen_size.width) / float(screen_size.height);
   }
   else
   {
-    SCREEN_WIDTH  = static_cast<int>(SCREEN_WIDTH  / g_config->magnification);
-    SCREEN_HEIGHT = static_cast<int>(SCREEN_HEIGHT / g_config->magnification);
+    screen_size = g_config->window_size;
+  }
 
-    // This works by adding black borders around the screen to limit
-    // SCREEN_WIDTH/SCREEN_HEIGHT to max_size.width/max_size.height
-    Size new_size = screen_size;
+  // calculate the viewport
+  Size max_size(1280, 800);
+  Size min_size(640, 480);
 
-    if (SCREEN_WIDTH > max_size.width)
-    {
-      new_size.width = static_cast<int>((float) new_size.width * float(max_size.width)/SCREEN_WIDTH);
-      SCREEN_WIDTH = static_cast<int>(max_size.width);
-    }
+  // FIXME: don't do this, save window size
+  Size window_size;
+  SDL_GetWindowSize(window, &window_size.width, &window_size.height);
 
-    if (SCREEN_HEIGHT > max_size.height)
-    {
-      new_size.height = static_cast<int>((float) new_size.height * float(max_size.height)/SCREEN_HEIGHT);
-      SCREEN_HEIGHT = static_cast<int>(max_size.height);
-    }
+  Vector scale;
+  Size logical_size;
+  calculate_viewport(min_size, max_size, window_size,
+                     target_aspect / desktop_aspect,
+                     g_config->magnification,
+                     scale, logical_size, viewport);
+
+  SCREEN_WIDTH = logical_size.width;
+  SCREEN_HEIGHT = logical_size.height;
 
+  if (viewport.x != 0 || viewport.y != 0)
+  {
     // Clear the screen to avoid garbage in unreachable areas after we
     // reset the coordinate system
     SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
@@ -356,16 +307,18 @@ SDLRenderer::apply_config()
     SDL_RenderClear(renderer);
     SDL_RenderPresent(renderer);
     SDL_RenderClear(renderer);
+  }
 
-    viewport.x = std::max(0, (screen_size.width  - new_size.width)  / 2);
-    viewport.y = std::max(0, (screen_size.height - new_size.height) / 2);
-    viewport.w = std::min(new_size.width,  screen_size.width);
-    viewport.h = std::min(new_size.height, screen_size.height);
+  SDL_RenderSetScale(renderer, 1.0f, 1.0f);
+  SDL_RenderSetViewport(renderer, &viewport);
+  SDL_RenderSetScale(renderer, scale.x, scale.y);
+}
 
-    SDL_RenderSetScale(renderer, 1.0f, 1.0f);
-    SDL_RenderSetViewport(renderer, &viewport);
-    SDL_RenderSetScale(renderer, g_config->magnification, g_config->magnification);
-  }
+void
+SDLRenderer::apply_config()
+{
+  apply_video_mode();
+  apply_viewport();
 }
 
 Vector
index ef8607e..0206e84 100644 (file)
@@ -38,10 +38,13 @@ public:
   Vector to_logical(int physical_x, int physical_y);
   void set_gamma(float gamma);
   SDL_Window* get_window() const { return window; }
-
   SDL_Renderer* get_sdl_renderer() const { return renderer; };
 
 private:
+  void apply_video_mode();
+  void apply_viewport();
+
+private:
   SDL_Window* window;
   SDL_Renderer* renderer;
   SDL_Rect viewport;
diff --git a/src/video/util.cpp b/src/video/util.cpp
new file mode 100644 (file)
index 0000000..5b10009
--- /dev/null
@@ -0,0 +1,131 @@
+//  SuperTux
+//  Copyright (C) 2014 Ingo Ruhnke <grumbel@gmx.de>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "video/util.hpp"
+
+#include <algorithm>
+
+#include "math/size.hpp"
+#include "math/vector.hpp"
+
+namespace {
+
+inline Size
+apply_pixel_aspect_ratio_pre(const Size& window_size, float pixel_aspect_ratio)
+{
+  if (pixel_aspect_ratio < 1.0f)
+  {
+    return Size(window_size.width * pixel_aspect_ratio,
+                window_size.height);
+  }
+  else
+  {
+    return Size(window_size.width,
+                window_size.height * pixel_aspect_ratio);
+  }
+}
+
+inline void
+apply_pixel_aspect_ratio_post(const Size& real_window_size, const Size& window_size, float scale,
+                                   SDL_Rect& out_viewport, Vector& out_scale)
+{  
+  Vector transform(static_cast<float>(real_window_size.width) / window_size.width,
+                   static_cast<float>(real_window_size.height) / window_size.height);
+  out_viewport.x *= transform.x;
+  out_viewport.y *= transform.y;
+
+  out_viewport.w *= transform.x;
+  out_viewport.h *= transform.y;
+
+  out_scale.x = scale * transform.x;
+  out_scale.y = scale * transform.y;
+
+}
+
+inline float
+calculate_scale(const Size& min_size, const Size& max_size,
+                      const Size& window_size,
+                      float magnification)
+{
+  float scale = magnification;
+  if (scale == 0.0f) // magic value
+  {
+    scale = 1.0f;
+
+    // Find the minimum magnification that is needed to fill the screen
+    if (window_size.width > max_size.width ||
+        window_size.height > max_size.height)
+    {
+      scale = std::max(static_cast<float>(window_size.width) / max_size.width,
+                       static_cast<float>(window_size.height) / max_size.height);
+    }
+
+    // If the resulting area would violate min_size, scale it down
+    if (window_size.width / scale < min_size.width ||
+        window_size.height / scale < min_size.height)
+    {
+      scale = std::min(static_cast<float>(window_size.width) / min_size.width,
+                       static_cast<float>(window_size.height) / min_size.height);
+    }
+  }
+
+  return scale;
+}
+
+inline SDL_Rect
+calculate_viewport(const Size& max_size, const Size& window_size, float scale)
+{
+  SDL_Rect viewport;
+
+  viewport.w = std::min(window_size.width,
+                            static_cast<int>(scale * max_size.width));
+  viewport.h = std::min(window_size.height,
+                            static_cast<int>(scale * max_size.height));
+
+  // Center the viewport in the window
+  viewport.x = std::max(0, (window_size.width - viewport.w) / 2);
+  viewport.y = std::max(0, (window_size.height - viewport.h) / 2);
+
+  return viewport;
+}
+
+} // namespace
+
+void calculate_viewport(const Size& min_size, const Size& max_size,
+                        const Size& real_window_size,
+                        float pixel_aspect_ratio, float magnification,
+                        Vector& out_scale,
+                        Size& out_logical_size,
+                        SDL_Rect& out_viewport)
+{
+  // Transform the real window_size by the aspect ratio, then do
+  // calculations on that virtual window_size
+  Size window_size = apply_pixel_aspect_ratio_pre(real_window_size, pixel_aspect_ratio);
+
+  float scale = calculate_scale(min_size, max_size, window_size, magnification);
+
+  // Calculate the new viewport size
+  out_viewport = calculate_viewport(max_size, window_size, scale);
+
+  out_logical_size.width = static_cast<int>(out_viewport.w / scale);
+  out_logical_size.height = static_cast<int>(out_viewport.h / scale);
+
+  // Transform the virtual window_size back into real window coordinates
+  apply_pixel_aspect_ratio_post(real_window_size, window_size, scale,
+                                out_viewport, out_scale);
+}
+
+/* EOF */
diff --git a/src/video/util.hpp b/src/video/util.hpp
new file mode 100644 (file)
index 0000000..8073d9b
--- /dev/null
@@ -0,0 +1,34 @@
+//  SuperTux
+//  Copyright (C) 2013 Ingo Ruhnke <grumbel@gmx.de>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HEADER_SUPERTUX_VIDEO_UTIL_HPP
+#define HEADER_SUPERTUX_VIDEO_UTIL_HPP
+
+#include "SDL_rect.h"
+
+class Size;
+class Vector;
+
+void calculate_viewport(const Size& min_size, const Size& max_size,
+                        const Size& real_window_size,
+                        float pixel_aspect_ratio, float magnification,
+                        Vector& out_scale,
+                        Size& out_logical_size,
+                        SDL_Rect& out_viewport);
+
+#endif
+
+/* EOF */