From 33b4a9d915947d2482366622fc4f97c944254775 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 13 Sep 2026 11:32:31 -0700
Subject: [PATCH] Added floating point versions of cliprect and viewport render
functions
Fixes https://github.com/libsdl-org/SDL/issues/14654
---
include/SDL3/SDL_render.h | 97 ++++++++++++++++++++++++++++++
src/dynapi/SDL_dynapi.exports | 4 ++
src/dynapi/SDL_dynapi.sym | 4 ++
src/dynapi/SDL_dynapi_overrides.h | 4 ++
src/dynapi/SDL_dynapi_procs.h | 4 ++
src/render/SDL_render.c | 98 +++++++++++++++++++++++++------
src/render/SDL_sysrender.h | 4 +-
7 files changed, 194 insertions(+), 21 deletions(-)
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 579cea3dac0af..bb2a298139c3f 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -1823,6 +1823,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ConvertEventToRenderCoordinates(SDL_Rendere
*
* \sa SDL_GetRenderViewport
* \sa SDL_RenderViewportSet
+ * \sa SDL_SetRenderViewportFloat
*/
extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect);
@@ -1841,11 +1842,59 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderViewport(SDL_Renderer *renderer, c
*
* \since This function is available since SDL 3.2.0.
*
+ * \sa SDL_GetRenderViewportFloat
* \sa SDL_RenderViewportSet
* \sa SDL_SetRenderViewport
*/
extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect);
+/**
+ * Set the drawing area for rendering on the current target.
+ *
+ * Drawing will clip to this area (separately from any clipping done with
+ * SDL_SetRenderClipRect), and the top left of the area will become coordinate
+ * (0, 0) for future drawing commands.
+ *
+ * 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_FRect structure representing the drawing area, or NULL
+ * to set the viewport to the entire target.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_GetRenderViewportFloat
+ * \sa SDL_RenderViewportSet
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderViewportFloat(SDL_Renderer *renderer, const SDL_FRect *rect);
+
+/**
+ * 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_FRect structure filled in with the current drawing area.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_RenderViewportSet
+ * \sa SDL_SetRenderViewportFloat
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewportFloat(SDL_Renderer *renderer, SDL_FRect *rect);
+
/**
* Return whether an explicit rectangle was set as the viewport.
*
@@ -1864,7 +1913,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_GetRenderViewport
+ * \sa SDL_GetRenderViewportFloat
* \sa SDL_SetRenderViewport
+ * \sa SDL_SetRenderViewportFloat
*/
extern SDL_DECLSPEC bool SDLCALL SDL_RenderViewportSet(SDL_Renderer *renderer);
@@ -1908,6 +1959,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderSafeArea(SDL_Renderer *renderer, S
*
* \sa SDL_GetRenderClipRect
* \sa SDL_RenderClipEnabled
+ * \sa SDL_SetRenderClipRectFloat
*/
extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect);
@@ -1927,11 +1979,54 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRect(SDL_Renderer *renderer, c
*
* \since This function is available since SDL 3.2.0.
*
+ * \sa SDL_GetRenderClipRectFloat
* \sa SDL_RenderClipEnabled
* \sa SDL_SetRenderClipRect
*/
extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect);
+/**
+ * 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_FRect structure representing the clip area, relative to
+ * the viewport, or NULL to disable clipping.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_GetRenderClipRectFloat
+ * \sa SDL_RenderClipEnabled
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderClipRectFloat(SDL_Renderer *renderer, const SDL_FRect *rect);
+
+/**
+ * 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_FRect structure filled in with the current clipping area
+ * or an empty rectangle if clipping is disabled.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_RenderClipEnabled
+ * \sa SDL_SetRenderClipRectFloat
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderClipRectFloat(SDL_Renderer *renderer, SDL_FRect *rect);
+
/**
* Get whether clipping is enabled on the given render target.
*
@@ -1947,7 +2042,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderClipRect(SDL_Renderer *renderer, S
* \since This function is available since SDL 3.2.0.
*
* \sa SDL_GetRenderClipRect
+ * \sa SDL_GetRenderClipRectFloat
* \sa SDL_SetRenderClipRect
+ * \sa SDL_SetRenderClipRectFloat
*/
extern SDL_DECLSPEC bool SDLCALL SDL_RenderClipEnabled(SDL_Renderer *renderer);
diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports
index a06f0f21d6434..5d5df5d17c520 100644
--- a/src/dynapi/SDL_dynapi.exports
+++ b/src/dynapi/SDL_dynapi.exports
@@ -1307,3 +1307,7 @@ _SDL_SetJoystickSensorEnabled
_SDL_JoystickSensorEnabled
_SDL_GetJoystickSensorDataRate
_SDL_GetJoystickSensorData
+_SDL_SetRenderViewportFloat
+_SDL_GetRenderViewportFloat
+_SDL_SetRenderClipRectFloat
+_SDL_GetRenderClipRectFloat
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 6e0354148122d..63864e372b4d7 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1308,6 +1308,10 @@ SDL3_0.0.0 {
SDL_JoystickSensorEnabled;
SDL_GetJoystickSensorDataRate;
SDL_GetJoystickSensorData;
+ SDL_SetRenderViewportFloat;
+ SDL_GetRenderViewportFloat;
+ SDL_SetRenderClipRectFloat;
+ SDL_GetRenderClipRectFloat;
# 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 1f8ba18a01e59..d8deffc20e275 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1334,3 +1334,7 @@
#define SDL_JoystickSensorEnabled SDL_JoystickSensorEnabled_REAL
#define SDL_GetJoystickSensorDataRate SDL_GetJoystickSensorDataRate_REAL
#define SDL_GetJoystickSensorData SDL_GetJoystickSensorData_REAL
+#define SDL_SetRenderViewportFloat SDL_SetRenderViewportFloat_REAL
+#define SDL_GetRenderViewportFloat SDL_GetRenderViewportFloat_REAL
+#define SDL_SetRenderClipRectFloat SDL_SetRenderClipRectFloat_REAL
+#define SDL_GetRenderClipRectFloat SDL_GetRenderClipRectFloat_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 6bf988441a21b..cd2fc200a2784 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1342,3 +1342,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetJoystickSensorEnabled,(SDL_Joystick *a,SDL_SensorTyp
SDL_DYNAPI_PROC(bool,SDL_JoystickSensorEnabled,(SDL_Joystick *a,SDL_SensorType b),(a,b),return)
SDL_DYNAPI_PROC(float,SDL_GetJoystickSensorDataRate,(SDL_Joystick *a,SDL_SensorType b),(a,b),return)
SDL_DYNAPI_PROC(bool,SDL_GetJoystickSensorData,(SDL_Joystick *a,SDL_SensorType b,float *c,int d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(bool,SDL_SetRenderViewportFloat,(SDL_Renderer *a,const SDL_FRect *b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_GetRenderViewportFloat,(SDL_Renderer *a,SDL_FRect *b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_SetRenderClipRectFloat,(SDL_Renderer *a,const SDL_FRect *b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_GetRenderClipRectFloat,(SDL_Renderer *a,SDL_FRect *b),(a,b),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 059eaf5f6d804..51516b6ef6975 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -1165,8 +1165,8 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
renderer->main_view.pixel_w = surface->w;
renderer->main_view.pixel_h = surface->h;
}
- renderer->main_view.viewport.w = -1;
- renderer->main_view.viewport.h = -1;
+ renderer->main_view.viewport.w = -1.0f;
+ renderer->main_view.viewport.h = -1.0f;
renderer->main_view.scale.x = 1.0f;
renderer->main_view.scale.y = 1.0f;
renderer->main_view.logical_scale.x = 1.0f;
@@ -1558,8 +1558,8 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert
texture->scaleMode = renderer->scale_mode;
texture->view.pixel_w = w;
texture->view.pixel_h = h;
- texture->view.viewport.w = -1;
- texture->view.viewport.h = -1;
+ texture->view.viewport.w = -1.0f;
+ texture->view.viewport.h = -1.0f;
texture->view.scale.x = 1.0f;
texture->view.scale.y = 1.0f;
texture->view.logical_scale.x = 1.0f;
@@ -3160,25 +3160,56 @@ bool SDL_ConvertEventToRenderCoordinates(SDL_Renderer *renderer, SDL_Event *even
}
bool SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect)
+{
+ if (rect) {
+ SDL_FRect frect;
+ SDL_RectToFRect(rect, &frect);
+ return SDL_SetRenderViewportFloat(renderer, &frect);
+ } else {
+ return SDL_SetRenderViewportFloat(renderer, NULL);
+ }
+}
+
+bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)
+{
+ if (rect) {
+ SDL_zerop(rect);
+ }
+
+ SDL_FRect frect;
+ if (SDL_GetRenderViewportFloat(renderer, &frect)) {
+ if (rect) {
+ rect->x = (int)SDL_floorf(frect.x);
+ rect->y = (int)SDL_floorf(frect.y);
+ rect->w = (int)SDL_ceilf(frect.w);
+ rect->h = (int)SDL_ceilf(frect.h);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool SDL_SetRenderViewportFloat(SDL_Renderer *renderer, const SDL_FRect *rect)
{
CHECK_RENDERER_MAGIC(renderer, false);
SDL_RenderViewState *view = renderer->view;
if (rect) {
- if ((rect->w < 0) || (rect->h < 0)) {
+ if ((rect->w < 0.0f) || (rect->h < 0.0f)) {
return SDL_SetError("rect has a negative size");
}
SDL_copyp(&view->viewport, rect);
} else {
- view->viewport.x = view->viewport.y = 0;
- view->viewport.w = view->viewport.h = -1;
+ view->viewport.x = view->viewport.y = 0.0f;
+ view->viewport.w = view->viewport.h = -1.0f;
}
UpdatePixelViewport(renderer, view);
return QueueCmdSetViewport(renderer);
}
-bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)
+bool SDL_GetRenderViewportFloat(SDL_Renderer *renderer, SDL_FRect *rect)
{
if (rect) {
SDL_zerop(rect);
@@ -3190,15 +3221,15 @@ bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)
const SDL_RenderViewState *view = renderer->view;
rect->x = view->viewport.x;
rect->y = view->viewport.y;
- if (view->viewport.w >= 0) {
+ if (view->viewport.w >= 0.0f) {
rect->w = view->viewport.w;
} else {
- rect->w = (int)SDL_ceilf(view->pixel_w / view->current_scale.x);
+ rect->w = view->pixel_w / view->current_scale.x;
}
- if (view->viewport.h >= 0) {
+ if (view->viewport.h >= 0.0f) {
rect->h = view->viewport.h;
} else {
- rect->h = (int)SDL_ceilf(view->pixel_h / view->current_scale.y);
+ rect->h = view->pixel_h / view->current_scale.y;
}
}
return true;
@@ -3209,7 +3240,7 @@ bool SDL_RenderViewportSet(SDL_Renderer *renderer)
CHECK_RENDERER_MAGIC(renderer, false);
const SDL_RenderViewState *view = renderer->view;
- return (view->viewport.w >= 0 && view->viewport.h >= 0);
+ return (view->viewport.w >= 0.0f && view->viewport.h >= 0.0f);
}
static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect)
@@ -3221,14 +3252,14 @@ static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect)
rect->x = 0.0f;
rect->y = 0.0f;
- if (view->viewport.w >= 0) {
- rect->w = (float)view->viewport.w;
+ if (view->viewport.w >= 0.0f) {
+ rect->w = view->viewport.w;
} else {
rect->w = view->pixel_w / scale_x;
}
- if (view->viewport.h >= 0) {
- rect->h = (float)view->viewport.h;
+ if (view->viewport.h >= 0.0f) {
+ rect->h = view->viewport.h;
} else {
rect->h = view->pixel_h / scale_y;
}
@@ -3282,11 +3313,40 @@ bool SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect)
}
bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)
+{
+ if (rect) {
+ SDL_FRect frect;
+ SDL_RectToFRect(rect, &frect);
+ return SDL_SetRenderClipRectFloat(renderer, &frect);
+ } else {
+ return SDL_SetRenderClipRectFloat(renderer, NULL);
+ }
+}
+
+bool SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect)
+{
+ if (rect) {
+ SDL_zerop(rect);
+ }
+
+ SDL_FRect frect;
+ if (SDL_GetRenderClipRectFloat(renderer, &frect)) {
+ rect->x = (int)SDL_floorf(frect.x);
+ rect->y = (int)SDL_floorf(frect.y);
+ rect->w = (int)SDL_ceilf(frect.w);
+ rect->h = (int)SDL_ceilf(frect.h);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool SDL_SetRenderClipRectFloat(SDL_Renderer *renderer, const SDL_FRect *rect)
{
CHECK_RENDERER_MAGIC(renderer, false);
SDL_RenderViewState *view = renderer->view;
- if (rect && rect->w >= 0 && rect->h >= 0) {
+ if (rect && rect->w >= 0.0f && rect->h >= 0.0f) {
view->clipping_enabled = true;
SDL_copyp(&view->clip_rect, rect);
} else {
@@ -3298,7 +3358,7 @@ bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)
return QueueCmdSetClipRect(renderer);
}
-bool SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect)
+bool SDL_GetRenderClipRectFloat(SDL_Renderer *renderer, SDL_FRect *rect)
{
if (rect) {
SDL_zerop(rect);
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 6642fe693ab11..552999ad46bfc 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -52,9 +52,9 @@ typedef struct SDL_RenderViewState
{
int pixel_w;
int pixel_h;
- SDL_Rect viewport;
+ SDL_FRect viewport;
SDL_Rect pixel_viewport;
- SDL_Rect clip_rect;
+ SDL_FRect clip_rect;
SDL_Rect pixel_clip_rect;
bool clipping_enabled;
SDL_FPoint scale;