SDL: render: allow render targets to use logical presentation.

From 35e8cf8ee6a953bba618ba7bd08969101a34d10a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 27 Feb 2025 17:28:47 -0500
Subject: [PATCH] render: allow render targets to use logical presentation.

Fixes https://github.com/libsdl-org/sdl2-compat/issues/279
---
 include/SDL3/SDL_render.h  |  75 ++++++-
 src/render/SDL_render.c    | 430 ++++++++++++++++++++-----------------
 src/render/SDL_sysrender.h |  16 +-
 3 files changed, 299 insertions(+), 222 deletions(-)

diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 2c4bceedaa6b3..e67dfc4244c92 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -490,6 +490,9 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Rende
  * This returns the true output size in pixels, ignoring any render targets or
  * logical size and presentation.
  *
+ * For the output size of the current rendering target, with logical size
+ * adjustments, use SDL_GetCurrentRenderOutputSize() instead.
+ *
  * \param renderer the rendering context.
  * \param w a pointer filled in with the width in pixels.
  * \param h a pointer filled in with the height in pixels.
@@ -508,9 +511,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderOutputSize(SDL_Renderer *renderer,
  * Get the current output size in pixels of a rendering context.
  *
  * If a rendering target is active, this will return the size of the rendering
- * target in pixels, otherwise if a logical size is set, it will return the
- * logical size, otherwise it will return the value of
- * SDL_GetRenderOutputSize().
+ * target in pixels, otherwise return the value of SDL_GetRenderOutputSize().
+ *
+ * Rendering target or not, the output will be adjusted by the current
+ * logical presentation state, dictated by SDL_SetRenderLogicalPresentation().
  *
  * \param renderer the rendering context.
  * \param w a pointer filled in with the current width.
@@ -1318,6 +1322,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockTexture(SDL_Texture *texture);
  * To stop rendering to a texture and render to the window again, call this
  * function with a NULL `texture`.
  *
+ * Viewport, cliprect, scale, and logical presentation are unique to each
+ * render target. Get and set functions for these states apply to the current
+ * render target set by this function, and those states persist on each target
+ * when the current render target changes.
+ *
  * \param renderer the rendering context.
  * \param texture the targeted texture, which must be created with the
  *                `SDL_TEXTUREACCESS_TARGET` flag, or NULL to render to the
@@ -1351,25 +1360,39 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer, SDL
 extern SDL_DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *renderer);
 
 /**
- * Set a device independent resolution and presentation mode for rendering.
+ * Set a device-independent resolution and presentation mode for rendering.
  *
  * This function sets the width and height of the logical rendering output.
- * The renderer will act as if the window is always the requested dimensions,
- * scaling to the actual window resolution as necessary.
+ * The renderer will act as if the current render target is always the
+ * requested dimensions, scaling to the actual resolution as necessary.
  *
  * This can be useful for games that expect a fixed size, but would like to
  * scale the output to whatever is available, regardless of how a user resizes
  * a window, or if the display is high DPI.
  *
+ * Logical presentation can be used with both render target textures
+ * and the renderer's window; the state is unique to each render target, and
+ * this function sets the state for the current render target. It might be
+ * useful to draw to a texture that matches the window dimensions with logical
+ * presentation enabled, and then draw that texture across the entire window
+ * with logical presentation disabled. Be careful not to render both with
+ * logical presentation enabled, however, as this could produce
+ * double-letterboxing, etc.
+ *
  * You can disable logical coordinates by setting the mode to
  * SDL_LOGICAL_PRESENTATION_DISABLED, and in that case you get the full pixel
- * resolution of the output window; it is safe to toggle logical presentation
+ * resolution of the render target; it is safe to toggle logical presentation
  * during the rendering of a frame: perhaps most of the rendering is done to
  * specific dimensions but to make fonts look sharp, the app turns off logical
- * presentation while drawing text.
+ * presentation while drawing text, for example.
  *
- * Letterboxing will only happen if logical presentation is enabled during
- * SDL_RenderPresent; be sure to reenable it first if you were using it.
+ * For the renderer's window, letterboxing is drawn into the framebuffer
+ * if logical presentation is enabled during SDL_RenderPresent; be sure to
+ * reenable it before presenting if you were toggling it, otherwise the
+ * letterbox areas might have artifacts from previous frames (or artifacts
+ * from external overlays, etc). Letterboxing is never drawn into texture
+ * render targets; be sure to call SDL_RenderClear() before drawing into
+ * the texture so the letterboxing areas are cleared, if appropriate.
  *
  * You can convert coordinates in an event into rendering coordinates using
  * SDL_ConvertEventToRenderCoordinates().
@@ -1397,6 +1420,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderLogicalPresentation(SDL_Renderer *
  * This function gets the width and height of the logical rendering output, or
  * the output size in pixels if a logical resolution is not enabled.
  *
+ * Each render target has its own logical presentation state. This function
+ * gets the state for the current render target.
+ *
  * \param renderer the rendering context.
  * \param w an int to be filled with the width.
  * \param h an int to be filled with the height.
@@ -1420,6 +1446,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderLogicalPresentation(SDL_Renderer *
  * presentation is disabled, it will fill the rectangle with the output size,
  * in pixels.
  *
+ * Each render target has its own logical presentation state. This function
+ * gets the rectangle for the current render target.
+ *
  * \param renderer the rendering context.
  * \param rect a pointer filled in with the final presentation rectangle, may
  *             be NULL.
@@ -1536,6 +1565,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ConvertEventToRenderCoordinates(SDL_Rendere
  *
  * The area's width and height must be >= 0.
  *
+ * Each render target has its own viewport. This function sets the viewport
+ * for the current render target.
+ *
  * \param renderer the rendering context.
  * \param rect the SDL_Rect structure representing the drawing area, or NULL
  *             to set the viewport to the entire target.
@@ -1554,6 +1586,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderViewport(SDL_Renderer *renderer, c
 /**
  * Get the drawing area for the current target.
  *
+ * Each render target has its own viewport. This function gets the viewport
+ * for the current render target.
+ *
  * \param renderer the rendering context.
  * \param rect an SDL_Rect structure filled in with the current drawing area.
  * \returns true on success or false on failure; call SDL_GetError() for more
@@ -1575,6 +1610,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S
  * whether you should restore a specific rectangle or NULL. Note that the
  * viewport is always reset when changing rendering targets.
  *
+ * Each render target has its own viewport. This function checks the viewport
+ * for the current render target.
+ *
  * \param renderer the rendering context.
  * \returns true if the viewport was set to a specific rectangle, or false if
  *          it was set to NULL (the entire target).
@@ -1613,6 +1651,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderSafeArea(SDL_Renderer *renderer, S
 /**
  * Set the clip rectangle for rendering on the specified target.
  *
+ * Each render target has its own clip rectangle. This function
+ * sets the cliprect for the current render target.
+ *
  * \param renderer the rendering context.
  * \param rect an SDL_Rect structure representing the clip area, relative to
  *             the viewport, or NULL to disable clipping.
@@ -1631,6 +1672,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, c
 /**
  * Get the clip rectangle for the current target.
  *
+ * Each render target has its own clip rectangle. This function
+ * gets the cliprect for the current render target.
+ *
  * \param renderer the rendering context.
  * \param rect an SDL_Rect structure filled in with the current clipping area
  *             or an empty rectangle if clipping is disabled.
@@ -1647,7 +1691,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, c
 extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect);
 
 /**
- * Get whether clipping is enabled on the given renderer.
+ * Get whether clipping is enabled on the given render target.
+ *
+ * Each render target has its own clip rectangle. This function
+ * checks the cliprect for the current render target.
  *
  * \param renderer the rendering context.
  * \returns true if clipping is enabled or false if not; call SDL_GetError()
@@ -1673,6 +1720,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderClipEnabled(SDL_Renderer *renderer);
  * will be handled using the appropriate quality hints. For best results use
  * integer scaling factors.
  *
+ * Each render target has its own scale. This function sets the scale for the
+ * current render target.
+ *
  * \param renderer the rendering context.
  * \param scaleX the horizontal scaling factor.
  * \param scaleY the vertical scaling factor.
@@ -1690,6 +1740,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderScale(SDL_Renderer *renderer, floa
 /**
  * Get the drawing scale for the current target.
  *
+ * Each render target has its own scale. This function gets the scale for the
+ * current render target.
+ *
  * \param renderer the rendering context.
  * \param scaleX a pointer filled in with the horizontal scaling factor.
  * \param scaleY a pointer filled in with the vertical scaling factor.
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 0622a52c1c053..a66552f36c34e 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -472,17 +472,18 @@ static bool QueueCmdSetClipRect(SDL_Renderer *renderer)
 {
     bool result = true;
 
-    SDL_Rect clip_rect = renderer->view->pixel_clip_rect;
+    const SDL_RenderViewState *view = renderer->view;
+    SDL_Rect clip_rect = view->pixel_clip_rect;
     if (!renderer->cliprect_queued ||
-        renderer->view->clipping_enabled != renderer->last_queued_cliprect_enabled ||
+        view->clipping_enabled != renderer->last_queued_cliprect_enabled ||
         SDL_memcmp(&clip_rect, &renderer->last_queued_cliprect, sizeof(clip_rect)) != 0) {
         SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
         if (cmd) {
             cmd->command = SDL_RENDERCMD_SETCLIPRECT;
-            cmd->data.cliprect.enabled = renderer->view->clipping_enabled;
+            cmd->data.cliprect.enabled = view->clipping_enabled;
             SDL_copyp(&cmd->data.cliprect.rect, &clip_rect);
             SDL_copyp(&renderer->last_queued_cliprect, &clip_rect);
-            renderer->last_queued_cliprect_enabled = renderer->view->clipping_enabled;
+            renderer->last_queued_cliprect_enabled = view->clipping_enabled;
             renderer->cliprect_queued = true;
         } else {
             result = false;
@@ -737,9 +738,9 @@ static void UpdateMainViewDimensions(SDL_Renderer *renderer)
     if (renderer->window) {
         SDL_GetWindowSize(renderer->window, &window_w, &window_h);
     }
-    SDL_GetRenderOutputSize(renderer, &renderer->output_pixel_w, &renderer->output_pixel_h);
-    renderer->main_view.pixel_w = renderer->output_pixel_w;
-    renderer->main_view.pixel_h = renderer->output_pixel_h;
+
+    SDL_GetRenderOutputSize(renderer, &renderer->main_view.pixel_w, &renderer->main_view.pixel_h);
+
     if (window_w > 0 && window_h > 0) {
         renderer->dpi_scale.x = (float)renderer->main_view.pixel_w / window_w;
         renderer->dpi_scale.y = (float)renderer->main_view.pixel_h / window_h;
@@ -833,7 +834,10 @@ static bool SDL_RendererEventWatch(void *userdata, SDL_Event *event)
     if (event->type == SDL_EVENT_WINDOW_RESIZED ||
         event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
         event->type == SDL_EVENT_WINDOW_METAL_VIEW_RESIZED) {
-        UpdateLogicalPresentation(renderer);
+            SDL_RenderViewState *view = renderer->view;
+            renderer->view = &renderer->main_view;  // only update the main_view (the window framebuffer) for window changes.
+            UpdateLogicalPresentation(renderer);
+            renderer->view = view;  // put us back on whatever the current render target's actual view is.
     } else if (event->type == SDL_EVENT_WINDOW_HIDDEN) {
         renderer->hidden = true;
     } else if (event->type == SDL_EVENT_WINDOW_SHOWN) {
@@ -1245,11 +1249,12 @@ bool SDL_GetCurrentRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)
 
     CHECK_RENDERER_MAGIC(renderer, false);
 
+    const SDL_RenderViewState *view = renderer->view;
     if (w) {
-        *w = renderer->view->pixel_w;
+        *w = view->pixel_w;
     }
     if (h) {
-        *h = renderer->view->pixel_h;
+        *h = view->pixel_h;
     }
     return true;
 }
@@ -2565,121 +2570,134 @@ SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer)
 
 static void UpdateLogicalPresentation(SDL_Renderer *renderer)
 {
-    if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) {
-        renderer->main_view.logical_offset.x = renderer->main_view.logical_offset.y = 0.0f;
-        renderer->main_view.logical_scale.x = renderer->main_view.logical_scale.y = 1.0f;
-        renderer->main_view.current_scale.x = renderer->main_view.scale.x;  // skip the multiplications against 1.0f.
-        renderer->main_view.current_scale.y = renderer->main_view.scale.y;
-        UpdateMainViewDimensions(renderer);
-        UpdatePixelClipRect(renderer, &renderer->main_view);
-        return;  // All done!
-    }
-
+    SDL_RenderViewState *view = renderer->view;
+    const bool is_main_view = (view == &renderer->main_view);
+    const float logical_w = view->logical_w;
+    const float logical_h = view->logical_h;
     int iwidth, iheight;
-    SDL_GetRenderOutputSize(renderer, &iwidth, &iheight);
-
-    const float output_w = (float)iwidth;
-    const float output_h = (float)iheight;
-    const float logical_w = renderer->logical_w;
-    const float logical_h = renderer->logical_h;
-    const float want_aspect = logical_w / logical_h;
-    const float real_aspect = output_w / output_h;
-
-    renderer->logical_src_rect.x = 0.0f;
-    renderer->logical_src_rect.y = 0.0f;
-    renderer->logical_src_rect.w = logical_w;
-    renderer->logical_src_rect.h = logical_h;
-
-    if ((logical_w <= 0.0f) || (logical_h <= 0.0f)) {
-        renderer->logical_dst_rect.x = 0.0f;
-        renderer->logical_dst_rect.y = 0.0f;
-        renderer->logical_dst_rect.w = output_w;
-        renderer->logical_dst_rect.h = output_h;
-    } else if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
-        float scale;
-        if (want_aspect > real_aspect) {
-            scale = (float)((int)output_w / (int)logical_w); // This an integer division!
+
+    if (renderer->target) {
+        iwidth = (int)renderer->target->w;
+        iheight = (int)renderer->target->h;
+    } else {
+        SDL_GetRenderOutputSize(renderer, &iwidth, &iheight);
+    }
+
+    view->logical_src_rect.x = 0.0f;
+    view->logical_src_rect.y = 0.0f;
+    view->logical_src_rect.w = logical_w;
+    view->logical_src_rect.h = logical_h;
+
+    if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) {
+        view->logical_dst_rect.x = 0.0f;
+        view->logical_dst_rect.y = 0.0f;
+        view->logical_dst_rect.w = iwidth;
+        view->logical_dst_rect.h = iheight;
+        view->logical_offset.x = view->logical_offset.y = 0.0f;
+        view->logical_scale.x = view->logical_scale.y = 1.0f;
+        view->current_scale.x = view->scale.x;  // skip the multiplications against 1.0f.
+        view->current_scale.y = view->scale.y;
+    } else {
+        const float output_w = (float)iwidth;
+        const float output_h = (float)iheight;
+        const float want_aspect = logical_w / logical_h;
+        const float real_aspect = output_w / output_h;
+
+        if ((logical_w <= 0.0f) || (logical_h <= 0.0f)) {
+            view->logical_dst_rect.x = 0.0f;
+            view->logical_dst_rect.y = 0.0f;
+            view->logical_dst_rect.w = output_w;
+            view->logical_dst_rect.h = output_h;
+        } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
+            float scale;
+            if (want_aspect > real_aspect) {
+                scale = (float)((int)output_w / (int)logical_w); // This an integer division!
+            } else {
+                scale = (float)((int)output_h / (int)logical_h); // This an integer division!
+            }
+
+            if (scale < 1.0f) {
+                scale = 1.0f;
+            }
+
+            view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
+            view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
+            view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
+            view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
+
+        } else if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_STRETCH || SDL_fabsf(want_aspect - real_aspect) < 0.0001f) {
+            view->logical_dst_rect.x = 0.0f;
+            view->logical_dst_rect.y = 0.0f;
+            view->logical_dst_rect.w = output_w;
+            view->logical_dst_rect.h = output_h;
+
+        } else if (want_aspect > real_aspect) {
+            if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
+                // We want a wider aspect ratio than is available - letterbox it
+                const float scale = output_w / logical_w;
+                view->logical_dst_rect.x = 0.0f;
+                view->logical_dst_rect.w = output_w;
+                view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
+                view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
+            } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
+                /* We want a wider aspect ratio than is available -
+                   zoom so logical height matches the real height
+                   and the width will grow off the screen
+                 */
+                const float scale = output_h / logical_h;
+                view->logical_dst_rect.y = 0.0f;
+                view->logical_dst_rect.h = output_h;
+                view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
+                view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
+            }
         } else {
-            scale = (float)((int)output_h / (int)logical_h); // This an integer division!
-        }
-
-        if (scale < 1.0f) {
-            scale = 1.0f;
-        }
-
-        renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale);
-        renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f;
-        renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale);
-        renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f;
-
-    } else if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_STRETCH ||
-               SDL_fabsf(want_aspect - real_aspect) < 0.0001f) {
-        renderer->logical_dst_rect.x = 0.0f;
-        renderer->logical_dst_rect.y = 0.0f;
-        renderer->logical_dst_rect.w = output_w;
-        renderer->logical_dst_rect.h = output_h;
-
-    } else if (want_aspect > real_aspect) {
-        if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
-            // We want a wider aspect ratio than is available - letterbox it
-            const float scale = output_w / logical_w;
-            renderer->logical_dst_rect.x = 0.0f;
-            renderer->logical_dst_rect.w = output_w;
-            renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale);
-            renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f;
-        } else { // renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
-            /* We want a wider aspect ratio than is available -
-               zoom so logical height matches the real height
-               and the width will grow off the screen
-             */
-            const float scale = output_h / logical_h;
-            renderer->logical_dst_rect.y = 0.0f;
-            renderer->logical_dst_rect.h = output_h;
-            renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale);
-            renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f;
+            if (view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
+                // We want a narrower aspect ratio than is available - use side-bars
+                const float scale = output_h / logical_h;
+                view->logical_dst_rect.y = 0.0f;
+                view->logical_dst_rect.h = output_h;
+                view->logical_dst_rect.w = SDL_floorf(logical_w * scale);
+                view->logical_dst_rect.x = (output_w - view->logical_dst_rect.w) / 2.0f;
+            } else { // view->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
+                /* We want a narrower aspect ratio than is available -
+                   zoom so logical width matches the real width
+                   and the height will grow off the screen
+                 */
+                const float scale = output_w / logical_w;
+                view->logical_dst_rect.x = 0.0f;
+                view->logical_dst_rect.w = output_w;
+                view->logical_dst_rect.h = SDL_floorf(logical_h * scale);
+                view->logical_dst_rect.y = (output_h - view->logical_dst_rect.h) / 2.0f;
+            }
         }
-    } else {
-        if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
-            // We want a narrower aspect ratio than is available - use side-bars
-            const float scale = output_h / logical_h;
-            renderer->logical_dst_rect.y = 0.0f;
-            renderer->logical_dst_rect.h = output_h;
-            renderer->logical_dst_rect.w = SDL_floorf(logical_w * scale);
-            renderer->logical_dst_rect.x = (output_w - renderer->logical_dst_rect.w) / 2.0f;
-        } else { // renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_OVERSCAN
-            /* We want a narrower aspect ratio than is available -
-               zoom so logical width matches the real width
-               and the height will grow off the screen
-             */
-            const float scale = output_w / logical_w;
-            renderer->logical_dst_rect.x = 0.0f;
-            renderer->logical_dst_rect.w = output_w;
-            renderer->logical_dst_rect.h = SDL_floorf(logical_h * scale);
-            renderer->logical_dst_rect.y = (output_h - renderer->logical_dst_rect.h) / 2.0f;
-        }
-    }
-
-    renderer->main_view.logical_scale.x = (logical_w > 0.0f) ? renderer->logical_dst_rect.w / logical_w : 0.0f;
-    renderer->main_view.logical_scale.y = (logical_h > 0.0f) ? renderer->logical_dst_rect.h / logical_h : 0.0f;
-    renderer->main_view.current_scale.x = renderer->main_view.scale.x * renderer->main_view.logical_scale.x;
-    renderer->main_view.current_scale.y = renderer->main_view.scale.y * renderer->main_view.logical_scale.y;
-    renderer->main_view.logical_offset.x = renderer->logical_dst_rect.x;
-    renderer->main_view.logical_offset.y = renderer->logical_dst_rect.y;
-
-    UpdateMainViewDimensions(renderer);  // this will replace pixel_w and pixel_h while making sure the dpi_scale is right.
-    renderer->main_view.pixel_w = (int) renderer->logical_dst_rect.w;
-    renderer->main_view.pixel_h = (int) renderer->logical_dst_rect.h;
-    UpdatePixelViewport(renderer, &renderer->main_view);
-    UpdatePixelClipRect(renderer, &renderer->main_view);
+
+        view->logical_scale.x = (logical_w > 0.0f) ? view->logical_dst_rect.w / logical_w : 0.0f;
+        view->logical_scale.y = (logical_h > 0.0f) ? view->logical_dst_rect.h / logical_h : 0.0f;
+        view->current_scale.x = view->scale.x * view->logical_scale.x;
+        view->current_scale.y = view->scale.y * view->logical_scale.y;
+        view->logical_offset.x = view->logical_dst_rect.x;
+        view->logical_offset.y = view->logical_dst_rect.y;
+    }
+
+    if (is_main_view) {
+        // This makes sure the dpi_scale is right. It also sets pixel_w and pixel_h, but we're going to change them directly below here.
+        UpdateMainViewDimensions(renderer);
+    }
+
+    view->pixel_w = (int) view->logical_dst_rect.w;
+    view->pixel_h = (int) view->logical_dst_rect.h;
+    UpdatePixelViewport(renderer, view);
+    UpdatePixelClipRect(renderer, view);
 }
 
 bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode)
 {
     CHECK_RENDERER_MAGIC(renderer, false);
 
-    renderer->logical_presentation_mode = mode;
-    renderer->logical_w = w;
-    renderer->logical_h = h;
+    SDL_RenderViewState *view = renderer->view;
+    view->logical_presentation_mode = mode;
+    view->logical_w = w;
+    view->logical_h = h;
 
     UpdateLogicalPresentation(renderer);
 
@@ -2696,9 +2714,10 @@ bool SDL_GetRenderLogicalPresentation(SDL_Renderer *renderer, int *w, int *h, SD
 
     CHECK_RENDERER_MAGIC(renderer, false);
 
-    SETVAL(w, renderer->logical_w);
-    SETVAL(h, renderer->logical_h);
-    SETVAL(mode, renderer->logical_presentation_mode);
+    const SDL_RenderViewState *view = renderer->view;
+    SETVAL(w, view->logical_w);
+    SETVAL(h, view->logical_h);
+    SETVAL(mode, view->logical_presentation_mode);
 
     #undef SETVAL
 
@@ -2714,21 +2733,14 @@ bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rec
     CHECK_RENDERER_MAGIC(renderer, false);
 
     if (rect) {
-        if (renderer->logical_presentation_mode == SDL_LOGICAL_PRESENTATION_DISABLED) {
-            rect->x = 0.0f;
-            rect->y = 0.0f;
-            rect->w = (float)renderer->output_pixel_w;
-            rect->h = (float)renderer->output_pixel_h;
-        } else {
-            SDL_copyp(rect, &renderer->logical_dst_rect);
-        }
+        SDL_copyp(rect, &renderer->view->logical_dst_rect);
     }
     return true;
 }
 
-static void SDL_RenderLogicalBorders(SDL_Renderer *renderer)
+static void SDL_RenderLogicalBorders(SDL_Renderer *renderer, const SDL_FRect *dst)
 {
-    const SDL_FRect *dst = &renderer->logical_dst_rect;
+    const SDL_RenderViewState *view = renderer->view;
 
     if (dst->x > 0.0f || dst->y > 0.0f) {
         SDL_BlendMode saved_blend_mode = renderer->blendMode;
@@ -2743,11 +2755,11 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer)
             rect.x = 0.0f;
             rect.y = 0.0f;
             rect.w = dst->x;
-            rect.h = (float)renderer->view->pixel_h;
+            rect.h = (float)view->pixel_h;
             SDL_RenderFillRect(renderer, &rect);
 
             rect.x = dst->x + dst->w;
-            rect.w = (float)renderer->view->pixel_w - rect.x;
+            rect.w = (float)view->pixel_w - rect.x;
             SDL_RenderFillRect(renderer, &rect);
         }
 
@@ -2756,12 +2768,12 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer)
 
             rect.x = 0.0f;
             rect.y = 0.0f;
-            rect.w = (float)renderer->view->pixel_w;
+            rect.w = (float)view->pixel_w;
             rect.h = dst->y;
             SDL_RenderFillRect(renderer, &rect);
 
             rect.y = dst->y + dst->h;
-            rect.h = (float)renderer->view->pixel_h - rect.y;
+            rect.h = (float)view->pixel_h - rect.y;
             SDL_RenderFillRect(renderer, &rect);
         }
 
@@ -2772,17 +2784,19 @@ static void SDL_RenderLogicalBorders(SDL_Renderer *renderer)
 
 static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer)
 {
-    const SDL_RendererLogicalPresentation mode = renderer->logical_presentation_mode;
+    SDL_assert(renderer->view == &renderer->main_view);
+
+    SDL_RenderViewState *view = &renderer->main_view;
+    const SDL_RendererLogicalPresentation mode = view->logical_presentation_mode;
     if (mode == SDL_LOGICAL_PRESENTATION_LETTERBOX) {
         // save off some state we're going to trample.
-        SDL_assert(renderer->view == &renderer->main_view);
-        SDL_RenderViewState *view = &renderer->main_view;
-        const int logical_w = renderer->logical_w;
-        const int logical_h = renderer->logical_h;
+        const int logical_w = view->logical_w;
+        const int logical_h = view->logical_h;
         const float scale_x = view->scale.x;
         const float scale_y = view->scale.y;
         const bool clipping_enabled = view->clipping_enabled;
         SDL_Rect orig_viewport, orig_cliprect;
+        const SDL_FRect logical_dst_rect = view->logical_dst_rect;
 
         SDL_copyp(&orig_viewport, &view->viewport);
         if (clipping_enabled) {
@@ -2798,10 +2812,10 @@ static void SDL_RenderLogicalPresentation(SDL_Renderer *renderer)
         SDL_SetRenderScale(renderer, 1.0f, 1.0f);
 
         // draw the borders.
-        SDL_RenderLogicalBorders(renderer);
+        SDL_RenderLogicalBorders(renderer, &logical_dst_rect);
 
         // now set everything back.
-        renderer->logical_presentation_mode = mode;
+        view->logical_presentation_mode = mode;
         SDL_SetRenderViewport(renderer, &orig_viewport);
         if (clipping_enabled) {
             SDL_SetRenderClipRect(renderer, &orig_cliprect);
@@ -2819,14 +2833,14 @@ static bool SDL_RenderVectorFromWindow(SDL_Renderer *renderer, float window_dx,
     window_dy *= renderer->dpi_scale.y;
 
     // Convert from pixels within the window to pixels within the view
-    if (renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
-        const SDL_FRect *src = &renderer->logical_src_rect;
-        const SDL_FRect *dst = &renderer->logical_dst_rect;
+    const SDL_RenderViewState *view = &renderer->main_view;
+    if (view->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
+        const SDL_FRect *src = &view->logical_src_rect;
+        const SDL_FRect *dst = &view->logical_dst_rect;
         window_dx = (window_dx * src->w) / dst->w;
         window_dy = (window_dy * src->h) / dst->h;
     }
 
-    const SDL_RenderViewState *view = &renderer->main_view;
     window_dx /= view->scale.x;
     window_dy /= view->scale.y;
 
@@ -2846,14 +2860,14 @@ bool SDL_RenderCoordinatesFromWindow(SDL_Renderer *renderer, float window_x, flo
     render_y = window_y * renderer->dpi_scale.y;
 
     // Convert from pixels within the window to pixels within the view
-    if (renderer->logical_presentation_mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
-        const SDL_FRect *src = &rende

(Patch may be truncated, please check the link at the top of this post.)