SDL: Added SDL_SetRenderTextureAddressMode() and SDL_GetRenderTextureAddressMode()

From 633b9f6fb1efd1e582872f847865f9eb17d136cb Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 20 Jul 2024 09:34:34 -0700
Subject: [PATCH] Added SDL_SetRenderTextureAddressMode() and
 SDL_GetRenderTextureAddressMode()

Fixes https://github.com/libsdl-org/SDL/issues/4820
Fixes https://github.com/libsdl-org/SDL/issues/12610
---
 include/SDL3/SDL_render.h                     |  49 ++++++
 src/dynapi/SDL_dynapi.sym                     |   2 +
 src/dynapi/SDL_dynapi_overrides.h             |   2 +
 src/dynapi/SDL_dynapi_procs.h                 |   2 +
 src/render/SDL_render.c                       |  94 +++++++++---
 src/render/SDL_sysrender.h                    |  20 +--
 src/render/direct3d/SDL_render_d3d.c          |  52 ++++---
 src/render/direct3d11/SDL_render_d3d11.c      | 135 ++++++++---------
 src/render/direct3d12/SDL_render_d3d12.c      | 124 ++++++++-------
 src/render/gpu/SDL_render_gpu.c               | 141 ++++++++----------
 src/render/metal/SDL_render_metal.m           | 135 +++++++++--------
 src/render/opengl/SDL_render_gl.c             |  54 +++----
 src/render/opengles2/SDL_render_gles2.c       |  54 +++----
 src/render/psp/SDL_render_psp.c               |  55 ++++---
 src/render/software/SDL_render_sw.c           |   3 +-
 src/render/software/SDL_triangle.c            |  65 ++++----
 src/render/software/SDL_triangle.h            |   5 +-
 src/render/vitagxm/SDL_render_vita_gxm.c      |  35 +++--
 .../vitagxm/SDL_render_vita_gxm_types.h       |   3 +-
 src/render/vulkan/SDL_render_vulkan.c         | 124 +++++----------
 20 files changed, 623 insertions(+), 531 deletions(-)

diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 33b6e1feb9c0e..63dbfa50be321 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -98,6 +98,21 @@ typedef enum SDL_TextureAccess
     SDL_TEXTUREACCESS_TARGET     /**< Texture can be used as a render target */
 } SDL_TextureAccess;
 
+/**
+ * The addressing mode for a texture when used in SDL_RenderGeometry().
+ *
+ * This affects how texture coordinates are interpreted outside of [0, 1]
+ *
+ * \since This enum is available since SDL 3.4.0.
+ */
+typedef enum SDL_TextureAddressMode
+{
+    SDL_TEXTURE_ADDRESS_INVALID = -1,
+    SDL_TEXTURE_ADDRESS_AUTO,   /**< Wrapping is enabled if texture coordinates are outside [0, 1], this is the default */
+    SDL_TEXTURE_ADDRESS_CLAMP,  /**< Texture coordinates are clamped to the [0, 1] range */
+    SDL_TEXTURE_ADDRESS_WRAP,   /**< The texture is repeated (tiled) */
+} SDL_TextureAddressMode;
+
 /**
  * How the logical size is mapped to the output.
  *
@@ -2294,6 +2309,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *rende
  * \since This function is available since SDL 3.2.0.
  *
  * \sa SDL_RenderGeometryRaw
+ * \sa SDL_SetRenderTextureAddressMode
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer,
                                                SDL_Texture *texture,
@@ -2326,6 +2342,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer,
  * \since This function is available since SDL 3.2.0.
  *
  * \sa SDL_RenderGeometry
+ * \sa SDL_SetRenderTextureAddressMode
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer,
                                                SDL_Texture *texture,
@@ -2335,6 +2352,38 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer,
                                                int num_vertices,
                                                const void *indices, int num_indices, int size_indices);
 
+/**
+ * Set the texture addressing mode used in SDL_RenderGeometry().
+ *
+ * \param renderer the rendering context.
+ * \param u_mode the SDL_TextureAddressMode to use for horizontal texture coordinates in SDL_RenderGeometry().
+ * \param v_mode the SDL_TextureAddressMode to use for vertical texture coordinates in SDL_RenderGeometry().
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \since This function is available since SDL 3.4.0.
+ *
+ * \sa SDL_RenderGeometry
+ * \sa SDL_RenderGeometryRaw
+ * \sa SDL_GetRenderTextureAddressMode
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode);
+
+/**
+ * Get the texture addressing mode used in SDL_RenderGeometry().
+ *
+ * \param renderer the rendering context.
+ * \param u_mode a pointer filled in with the SDL_TextureAddressMode to use for horizontal texture coordinates in SDL_RenderGeometry(), may be NULL.
+ * \param v_mode a pointer filled in with the SDL_TextureAddressMode to use for vertical texture coordinates in SDL_RenderGeometry(), may be NULL.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \since This function is available since SDL 3.4.0.
+ *
+ * \sa SDL_SetRenderTextureAddressMode
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode);
+
 /**
  * Read pixels from the current rendering target.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 9f84fbbc91fdf..6ec8127dfa490 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1246,6 +1246,8 @@ SDL3_0.0.0 {
     SDL_SetWindowProgressValue;
     SDL_GetWindowProgressState;
     SDL_GetWindowProgressValue;
+    SDL_SetRenderTextureAddressMode;
+    SDL_GetRenderTextureAddressMode;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 7ebb71721cae3..c00526490019d 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1271,3 +1271,5 @@
 #define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL
 #define SDL_GetWindowProgressState SDL_GetWindowProgressState_REAL
 #define SDL_GetWindowProgressValue SDL_GetWindowProgressValue_REAL
+#define SDL_SetRenderTextureAddressMode SDL_SetRenderTextureAddressMode_REAL
+#define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index fa12a5815aca5..31f56143e35c1 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1279,3 +1279,5 @@ SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState
 SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_ProgressState,SDL_GetWindowProgressState,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return)
+SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 15949f75d6505..ca23ce5714ce8 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -584,7 +584,8 @@ static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_Ren
             if (texture) {
                 cmd->data.draw.texture_scale_mode = texture->scaleMode;
             }
-            cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
+            cmd->data.draw.texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP;
+            cmd->data.draw.texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP;
             cmd->data.draw.gpu_render_state = renderer->gpu_render_state;
             if (renderer->gpu_render_state) {
                 renderer->gpu_render_state->last_command_generation = renderer->render_command_generation;
@@ -727,13 +728,15 @@ static bool QueueCmdGeometry(SDL_Renderer *renderer, SDL_Texture *texture,
                             const float *uv, int uv_stride,
                             int num_vertices,
                             const void *indices, int num_indices, int size_indices,
-                            float scale_x, float scale_y, SDL_TextureAddressMode texture_address_mode)
+                            float scale_x, float scale_y,
+                            SDL_TextureAddressMode texture_address_mode_u, SDL_TextureAddressMode texture_address_mode_v)
 {
     SDL_RenderCommand *cmd;
     bool result = false;
     cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_GEOMETRY, texture);
     if (cmd) {
-        cmd->data.draw.texture_address_mode = texture_address_mode;
+        cmd->data.draw.texture_address_mode_u = texture_address_mode_u;
+        cmd->data.draw.texture_address_mode_v = texture_address_mode_v;
         result = renderer->QueueGeometry(renderer, cmd, texture,
                                          xy, xy_stride,
                                          color, color_stride, uv, uv_stride,
@@ -3739,7 +3742,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count
             result = QueueCmdGeometry(renderer, NULL,
                                       xy, xy_stride, &renderer->color, 0 /* color_stride */, NULL, 0,
                                       num_vertices, indices, num_indices, size_indices,
-                                      1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP);
+                                      1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
         }
 
         SDL_small_free(xy, isstack1);
@@ -3920,7 +3923,7 @@ static bool SDL_RenderTextureInternal(SDL_Renderer *renderer, SDL_Texture *textu
         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);
+                                  scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
     } else {
         const SDL_FRect rect = { dstrect->x * scale_x, dstrect->y * scale_y, dstrect->w * scale_x, dstrect->h * scale_y };
         result = QueueCmdCopy(renderer, texture, srcrect, &rect);
@@ -4083,7 +4086,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
             &texture->color, 0 /* color_stride */,
             uv, uv_stride,
             num_vertices, indices, num_indices, size_indices,
-            scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP
+            scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP
         );
     }
     return result;
@@ -4233,7 +4236,7 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture,
         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);
+                                  scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
     } else {
         result = QueueCmdCopyEx(renderer, texture, &real_srcrect, dstrect, angle, &real_center, flip, scale_x, scale_y);
     }
@@ -4285,7 +4288,8 @@ static bool SDL_RenderTextureTiled_Wrap(SDL_Renderer *renderer, SDL_Texture *tex
     return QueueCmdGeometry(renderer, texture,
                             xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride,
                             num_vertices, indices, num_indices, size_indices,
-                            view->current_scale.x, view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP);
+                            view->current_scale.x, view->current_scale.y,
+                            SDL_TEXTURE_ADDRESS_WRAP, SDL_TEXTURE_ADDRESS_WRAP);
 }
 
 static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)
@@ -5032,7 +5036,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer,
                 result = QueueCmdGeometry(renderer, texture,
                                           xy, xy_stride, color, color_stride, uv, uv_stride,
                                           num_vertices, prev, 3, 4,
-                                          scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
+                                          scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
                 if (!result) {
                     goto end;
                 }
@@ -5052,7 +5056,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer,
         result = QueueCmdGeometry(renderer, texture,
                                   xy, xy_stride, color, color_stride, uv, uv_stride,
                                   num_vertices, prev, 3, 4,
-                                  scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP);
+                                  scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
         if (!result) {
             goto end;
         }
@@ -5077,7 +5081,8 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer,
 {
     int i;
     int count = indices ? num_indices : num_vertices;
-    SDL_TextureAddressMode texture_address_mode;
+    SDL_TextureAddressMode texture_address_mode_u;
+    SDL_TextureAddressMode texture_address_mode_v;
 
     CHECK_RENDERER_MAGIC(renderer, false);
 
@@ -5132,18 +5137,38 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer,
         texture = texture->native;
     }
 
-    texture_address_mode = renderer->texture_address_mode;
-    if (texture_address_mode == SDL_TEXTURE_ADDRESS_AUTO && texture) {
-        texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP;
+    texture_address_mode_u = renderer->texture_address_mode_u;
+    texture_address_mode_v = renderer->texture_address_mode_v;
+    if (texture &&
+        (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO ||
+         texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO)) {
         for (i = 0; i < num_vertices; ++i) {
             const float *uv_ = (const float *)((const char *)uv + i * uv_stride);
             float u = uv_[0];
             float v = uv_[1];
-            if (u < 0.0f || v < 0.0f || u > 1.0f || v > 1.0f) {
-                texture_address_mode = SDL_TEXTURE_ADDRESS_WRAP;
-                break;
+            if (u < 0.0f || u > 1.0f) {
+                if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) {
+                    texture_address_mode_u = SDL_TEXTURE_ADDRESS_WRAP;
+                    if (texture_address_mode_v != SDL_TEXTURE_ADDRESS_AUTO) {
+                        break;
+                    }
+                }
+            }
+            if (v < 0.0f || v > 1.0f) {
+                if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) {
+                    texture_address_mode_v = SDL_TEXTURE_ADDRESS_WRAP;
+                    if (texture_address_mode_u != SDL_TEXTURE_ADDRESS_AUTO) {
+                        break;
+                    }
+                }
             }
         }
+        if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) {
+            texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP;
+        }
+        if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) {
+            texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP;
+        }
     }
 
     if (indices) {
@@ -5168,7 +5193,9 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer,
 
     // For the software renderer, try to reinterpret triangles as SDL_Rect
 #ifdef SDL_VIDEO_RENDER_SW
-    if (renderer->software && texture_address_mode == SDL_TEXTURE_ADDRESS_CLAMP) {
+    if (renderer->software &&
+        texture_address_mode_u == SDL_TEXTURE_ADDRESS_CLAMP &&
+        texture_address_mode_v == SDL_TEXTURE_ADDRESS_CLAMP) {
         return SDL_SW_RenderGeometryRaw(renderer, texture,
                                         xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices,
                                         indices, num_indices, size_indices);
@@ -5180,7 +5207,36 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer,
                             xy, xy_stride, color, color_stride, uv, uv_stride,
                             num_vertices, indices, num_indices, size_indices,
                             view->current_scale.x, view->current_scale.y,
-                            texture_address_mode);
+                            texture_address_mode_u, texture_address_mode_v);
+}
+
+bool SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode)
+{
+    CHECK_RENDERER_MAGIC(renderer, false);
+
+    renderer->texture_address_mode_u = u_mode;
+    renderer->texture_address_mode_v = v_mode;
+    return true;
+}
+
+bool SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode)
+{
+    if (u_mode) {
+        *u_mode = SDL_TEXTURE_ADDRESS_INVALID;
+    }
+    if (v_mode) {
+        *v_mode = SDL_TEXTURE_ADDRESS_INVALID;
+    }
+
+    CHECK_RENDERER_MAGIC(renderer, false);
+
+    if (u_mode) {
+        *u_mode = renderer->texture_address_mode_u;
+    }
+    if (v_mode) {
+        *v_mode = renderer->texture_address_mode_v;
+    }
+    return true;
 }
 
 SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 2e6cc76cde051..5109b93451648 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -32,14 +32,6 @@
 extern "C" {
 #endif
 
-typedef enum SDL_TextureAddressMode
-{
-    SDL_TEXTURE_ADDRESS_INVALID = -1,
-    SDL_TEXTURE_ADDRESS_AUTO,
-    SDL_TEXTURE_ADDRESS_CLAMP,
-    SDL_TEXTURE_ADDRESS_WRAP,
-} SDL_TextureAddressMode;
-
 /**
  * A rectangle, with the origin at the upper left (double precision).
  */
@@ -187,7 +179,8 @@ typedef struct SDL_RenderCommand
             SDL_BlendMode blend;
             SDL_Texture *texture;
             SDL_ScaleMode texture_scale_mode;
-            SDL_TextureAddressMode texture_address_mode;
+            SDL_TextureAddressMode texture_address_mode_u;
+            SDL_TextureAddressMode texture_address_mode_v;
             SDL_GPURenderState *gpu_render_state;
         } draw;
         struct
@@ -312,7 +305,8 @@ struct SDL_Renderer
     float color_scale;
     SDL_FColor color;        /**< Color for drawing operations values */
     SDL_BlendMode blendMode; /**< The drawing blend mode */
-    SDL_TextureAddressMode texture_address_mode;
+    SDL_TextureAddressMode texture_address_mode_u;
+    SDL_TextureAddressMode texture_address_mode_v;
     SDL_GPURenderState *gpu_render_state;
 
     SDL_RenderCommand *render_commands;
@@ -373,6 +367,12 @@ extern SDL_RenderDriver GPU_RenderDriver;
 // Clean up any renderers at shutdown
 extern void SDL_QuitRender(void);
 
+#define RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v)    \
+    (((scale_mode == SDL_SCALEMODE_NEAREST) << 0) |                 \
+     ((address_u == SDL_TEXTURE_ADDRESS_WRAP) << 1) |               \
+     ((address_v == SDL_TEXTURE_ADDRESS_WRAP) << 2))
+#define RENDER_SAMPLER_COUNT (((1 << 0) | (1 << 1) | (1 << 2)) + 1)
+
 // Add a supported texture format to a renderer
 extern bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat format);
 
diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c
index b816abfb1e0ae..5132a23226402 100644
--- a/src/render/direct3d/SDL_render_d3d.c
+++ b/src/render/direct3d/SDL_render_d3d.c
@@ -61,7 +61,8 @@ typedef struct
     bool beginScene;
     bool enableSeparateAlphaBlend;
     SDL_ScaleMode scaleMode[3];
-    SDL_TextureAddressMode addressMode[3];
+    SDL_TextureAddressMode addressModeU[3];
+    SDL_TextureAddressMode addressModeV[3];
     IDirect3DSurface9 *defaultRenderTarget;
     IDirect3DSurface9 *currentRenderTarget;
     void *d3dxDLL;
@@ -278,8 +279,11 @@ static void D3D_InitRenderState(D3D_RenderData *data)
     }
 
     // Reset our current address mode
-    for (int i = 0; i < SDL_arraysize(data->addressMode); ++i) {
-        data->addressMode[i] = SDL_TEXTURE_ADDRESS_INVALID;
+    for (int i = 0; i < SDL_arraysize(data->addressModeU); ++i) {
+        data->addressModeU[i] = SDL_TEXTURE_ADDRESS_INVALID;
+    }
+    for (int i = 0; i < SDL_arraysize(data->addressModeV); ++i) {
+        data->addressModeV[i] = SDL_TEXTURE_ADDRESS_INVALID;
     }
 
     // Start the render with beginScene
@@ -937,22 +941,28 @@ static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode
     }
 }
 
-static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressMode, unsigned index)
+static DWORD TranslateAddressMode(SDL_TextureAddressMode addressMode)
 {
-    if (addressMode != data->addressMode[index]) {
-        switch (addressMode) {
-        case SDL_TEXTURE_ADDRESS_CLAMP:
-            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
-            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
-            break;
-        case SDL_TEXTURE_ADDRESS_WRAP:
-            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
-            IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
-            break;
-        default:
-            break;
-        }
-        data->addressMode[index] = addressMode;
+    switch (addressMode) {
+    case SDL_TEXTURE_ADDRESS_CLAMP:
+        return D3DTADDRESS_CLAMP;
+    case SDL_TEXTURE_ADDRESS_WRAP:
+        return D3DTADDRESS_WRAP;
+    default:
+        SDL_assert(!"Unknown texture address mode");
+        return D3DTADDRESS_CLAMP;
+    }
+}
+
+static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV, unsigned index)
+{
+    if (addressModeU != data->addressModeU[index]) {
+        IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, TranslateAddressMode(addressModeU));
+        data->addressModeU[index] = addressModeU;
+    }
+    if (addressModeV != data->addressModeV[index]) {
+        IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, TranslateAddressMode(addressModeV));
+        data->addressModeV[index] = addressModeV;
     }
 }
 
@@ -1047,15 +1057,15 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd)
 
     if (texture) {
         UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 0);
-        UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 0);
+        UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 0);
 
 #ifdef SDL_HAVE_YUV
         D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal;
         if (texturedata && texturedata->yuv) {
             UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 1);
             UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 2);
-            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 1);
-            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 2);
+            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 1);
+            UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 2);
         }
 #endif // SDL_HAVE_YUV
     }
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index 77d2faf828896..12fd5abc452c0 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -50,16 +50,6 @@
 /* !!! FIXME: vertex buffer bandwidth could be lower; only use UV coords when
    !!! FIXME:  textures are needed. */
 
-// Sampler types
-typedef enum
-{
-    D3D11_SAMPLER_NEAREST_CLAMP,
-    D3D11_SAMPLER_NEAREST_WRAP,
-    D3D11_SAMPLER_LINEAR_CLAMP,
-    D3D11_SAMPLER_LINEAR_WRAP,
-    D3D11_SAMPLER_COUNT
-} D3D11_Sampler;
-
 // Vertex shader, common values
 typedef struct
 {
@@ -178,7 +168,7 @@ typedef struct
     ID3D11PixelShader *pixelShaders[NUM_SHADERS];
     int blendModesCount;
     D3D11_BlendMode *blendModes;
-    ID3D11SamplerState *samplers[D3D11_SAMPLER_COUNT];
+    ID3D11SamplerState *samplers[RENDER_SAMPLER_COUNT];
     D3D_FEATURE_LEVEL featureLevel;
     bool pixelSizeChanged;
 
@@ -538,7 +528,6 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer)
     };
 
     D3D11_BUFFER_DESC constantBufferDesc;
-    D3D11_SAMPLER_DESC samplerDesc;
     D3D11_RASTERIZER_DESC rasterDesc;
 
     // See if we need debug interfaces
@@ -727,38 +716,6 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer)
         goto done;
     }
 
-    // Create samplers to use when drawing textures:
-    static struct
-    {
-        D3D11_FILTER filter;
-        D3D11_TEXTURE_ADDRESS_MODE address;
-    } samplerParams[] = {
-        { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_CLAMP },
-        { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_WRAP },
-        { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_CLAMP },
-        { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_WRAP },
-    };
-    SDL_COMPILE_TIME_ASSERT(samplerParams_SIZE, SDL_arraysize(samplerParams) == D3D11_SAMPLER_COUNT);
-    SDL_zero(samplerDesc);
-    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
-    samplerDesc.MipLODBias = 0.0f;
-    samplerDesc.MaxAnisotropy = 1;
-    samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
-    samplerDesc.MinLOD = 0.0f;
-    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
-    for (int i = 0; i < SDL_arraysize(samplerParams); ++i) {
-        samplerDesc.Filter = samplerParams[i].filter;
-        samplerDesc.AddressU = samplerParams[i].address;
-        samplerDesc.AddressV = samplerParams[i].address;
-        result = ID3D11Device_CreateSamplerState(data->d3dDevice,
-                                                 &samplerDesc,
-                                                 &data->samplers[i]);
-        if (FAILED(result)) {
-            WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateSamplerState [nearest-pixel filter]"), result);
-            goto done;
-        }
-    }
-
     // Setup Direct3D rasterizer states
     SDL_zero(rasterDesc);
     rasterDesc.AntialiasedLineEnable = FALSE;
@@ -2305,6 +2262,64 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *
     return true;
 }
 
+static ID3D11SamplerState *D3D11_GetSamplerState(D3D11_RenderData *data, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v)
+{
+    Uint32 key = RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v);
+    SDL_assert(key < SDL_arraysize(data->samplers));
+    if (!data->samplers[key]) {
+        D3D11_SAMPLER_DESC samplerDesc;
+        SDL_zero(samplerDesc);
+        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
+        samplerDesc.MipLODBias = 0.0f;
+        samplerDesc.MaxAnisotropy = 1;
+        samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
+        samplerDesc.MinLOD = 0.0f;
+        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
+        switch (scale_mode) {
+        case SDL_SCALEMODE_NEAREST:
+            samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
+            break;
+        case SDL_SCALEMODE_PIXELART:    // Uses linear sampling
+        case SDL_SCALEMODE_LINEAR:
+            samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+            break;
+        default:
+            SDL_SetError("Unknown scale mode: %d", scale_mode);
+            return NULL;
+        }
+        switch (address_u) {
+        case SDL_TEXTURE_ADDRESS_CLAMP:
+            samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
+            break;
+        case SDL_TEXTURE_ADDRESS_WRAP:
+            samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
+            break;
+        default:
+            SDL_SetError("Unknown texture address mode: %d", address_u);
+            return NULL;
+        }
+        switch (address_v) {
+        case SDL_TEXTURE_ADDRESS_CLAMP:
+            samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
+            break;
+        case SDL_TEXTURE_ADDRESS_WRAP:
+            samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
+            break;
+        default:
+            SDL_SetError("Unknown texture address mode: %d", address_v);
+            return NULL;
+        }
+        HRESULT result = ID3D11Device_CreateSamplerState(data->d3dDevice,
+                                                 &samplerDesc,
+                                                 &data->samplers[key]);
+        if (FAILED(result)) {
+            WIN_SetErrorFromHRESULT("ID3D11Device::CreateSamplerState", result);
+            return NULL;
+        }
+    }
+    return data->samplers[key];
+}
+
 static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const Float4X4 *matrix)
 {
     SDL_Texture *texture = cmd->data.draw.texture;
@@ -2319,35 +2334,11 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *
 
     D3D11_SetupShaderConstants(renderer, cmd, texture, &constants);
 
-    switch (cmd->data.draw.texture_scale_mode) {
-    case SDL_SCALEMODE_NEAREST:
-        switch (cmd->data.draw.texture_address_mode) {
-        case SDL_TEXTURE_ADDRESS_CLAMP:
-            textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_CLAMP];
-            break;
-        case SDL_TEXTURE_ADDRESS_WRAP:
-            textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_WRAP];
-            break;
-        default:
-            return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
-        }
-        break;
-    case SDL_SCALEMODE_PIXELART:    // Uses linear sampling
-    case SDL_SCALEMODE_LINEAR:
-        switch (cmd->data.draw.texture_address_mode) {
-        case SDL_TEXTURE_ADDRESS_CLAMP:
-            textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP];
-            break;
-        case SDL_TEXTURE_ADDRESS_WRAP:
-            textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_WRAP];
-            break;
-        default:
-            return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
-        }
-        break;
-    default:
-        return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode);
+    textureSampler = D3D11_GetSamplerState(rendererData, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v);
+    if (!textureSampler) {
+        return false;
     }
+
 #ifdef SDL_HAVE_YUV
     if (textureData->yuv) {
         ID3D11ShaderResourceView *shaderResources[3];
diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c
index c7ba5e9fb007d..7b21e96354deb 100644
--- a/src/render/direct3d12/SDL_render_d3d12.c
+++ b/src/render/direct3d12/SDL_render_d3d12.c
@@ -56,16 +56,6 @@ extern "C" {
 /* !!! FIXME: vertex buffer bandwidth coul

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