SDL: add SDL_RenderTextureAffine

From d0f19109185cb67f36b447601d9370a95d0a10bc Mon Sep 17 00:00:00 2001
From: expikr <[EMAIL REDACTED]>
Date: Mon, 28 Oct 2024 10:48:47 +0800
Subject: [PATCH] add SDL_RenderTextureAffine

---
 include/SDL3/SDL_render.h |  30 ++++++++++
 src/render/SDL_render.c   | 117 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+)

diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index d76f32cc68e68..3229c295c36ff 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -2076,6 +2076,36 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureRotated(SDL_Renderer *renderer
                                                      double angle, const SDL_FPoint *center,
                                                      SDL_FlipMode flip);
 
+/**
+ * Copy a portion of the source texture to the current rendering target, with
+ * affine transform, at subpixel precision.
+ *
+ * \param renderer the renderer which should copy parts of a texture.
+ * \param texture the source texture.
+ * \param srcrect a pointer to the source rectangle, or NULL for the entire
+ *                texture.
+ * \param origin a pointer to a point indicating where the top-left corner of 
+                 srcrect should be mapped to, or NULL for the rendering 
+                 target's origin.
+ * \param right a pointer to a point indicating where the top-right corner of
+                srcrect should be mapped to, or NULL for the rendering 
+                target's top-right corner.
+ * \param down a pointer to a point indicating where the bottom-left corner 
+               of srcrect should be mapped to, or NULL for the rendering 
+               target's bottom-left corner.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \threadsafety You may only call this function from the main thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_RenderTexture
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
+                                                     const SDL_FRect *srcrect, const SDL_FPoint *origin,
+                                                     const SDL_FPoint *right, const SDL_FPoint *down);
+
 /**
  * Tile a portion of the texture to the current rendering target at subpixel
  * precision.
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 93d3aee46010b..a742700c250ae 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -3918,6 +3918,123 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F
     return SDL_RenderTextureInternal(renderer, texture, &real_srcrect, &real_dstrect);
 }
 
+bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
+    const SDL_FRect *srcrect, const SDL_FPoint *origin, const SDL_FPoint *right, const SDL_FPoint *down)
+{
+    SDL_FRect real_srcrect;
+    SDL_FRect real_dstrect;
+    bool result;
+
+    CHECK_RENDERER_MAGIC(renderer, false);
+    CHECK_TEXTURE_MAGIC(texture, false);
+
+    if (renderer != texture->renderer) {
+        return SDL_SetError("Texture was not created with this renderer");
+    }
+    if (!renderer->QueueCopyEx && !renderer->QueueGeometry) {
+        return SDL_SetError("Renderer does not support RenderCopyEx");
+    }
+
+#if DONT_DRAW_WHILE_HIDDEN
+    // Don't draw while we're hidden
+    if (renderer->hidden) {
+        return true;
+    }
+#endif
+
+    real_srcrect.x = 0.0f;
+    real_srcrect.y = 0.0f;
+    real_srcrect.w = (float)texture->w;
+    real_srcrect.h = (float)texture->h;
+    if (srcrect) {
+        if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) {
+            return true;
+        }
+    }
+
+    GetRenderViewportSize(renderer, &real_dstrect);
+
+    if (texture->native) {
+        texture = texture->native;
+    }
+
+    texture->last_command_generation = renderer->render_command_generation;
+
+    const float scale_x = renderer->view->current_scale.x;
+    const float scale_y = renderer->view->current_scale.y;
+
+    {
+        float xy[8];
+        const int xy_stride = 2 * sizeof(float);
+        float uv[8];
+        const int uv_stride = 2 * sizeof(float);
+        const int num_vertices = 4;
+        const int *indices = rect_index_order;
+        const int num_indices = 6;
+        const int size_indices = 4;
+
+        float minu = real_srcrect.x / texture->w;
+        float minv = real_srcrect.y / texture->h;
+        float maxu = (real_srcrect.x + real_srcrect.w) / texture->w;
+        float maxv = (real_srcrect.y + real_srcrect.h) / texture->h;
+
+        uv[0] = minu;
+        uv[1] = minv;
+        uv[2] = maxu;
+        uv[3] = minv;
+        uv[4] = maxu;
+        uv[5] = maxv;
+        uv[6] = minu;
+        uv[7] = maxv;
+
+        // (minx, miny)
+        if (origin) {
+            xy[0] = origin->x;
+            xy[1] = origin->y;
+        } else {
+            xy[0] = real_dstrect.x;
+            xy[1] = real_dstrect.y;
+        }
+        
+        // (maxx, miny)
+        if (right) {
+            xy[2] = right->x;
+            xy[3] = right->y;
+        } else {
+            xy[2] = real_dstrect.x + real_dstrect.w;
+            xy[3] = real_dstrect.y;
+        }
+
+        // (minx, maxy)
+        if (down) {
+            xy[6] = down->x;
+            xy[7] = down->y;
+        } else {
+            xy[6] = real_dstrect.x;
+            xy[7] = real_dstrect.y + real_dstrect.h;
+        }
+
+        // (maxx, maxy)
+        if (origin || right || down) {
+            xy[4] = xy[2] + xy[6] - xy[0];
+            xy[5] = xy[3] + xy[7] - xy[1];
+        } else {
+            xy[4] = real_dstrect.x + real_dstrect.w;
+            xy[5] = real_dstrect.y + real_dstrect.h;
+        }
+
+        result = QueueCmdGeometry(
+            renderer, texture,
+            xy, xy_stride, 
+            &texture->color, 0 /* color_stride */,
+            uv, uv_stride,
+            num_vertices, indices, num_indices, size_indices,
+            scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP
+        );
+    }
+    return result;
+}
+
 bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture,
                       const SDL_FRect *srcrect, const SDL_FRect *dstrect,
                       const double angle, const SDL_FPoint *center, const SDL_FlipMode flip)