diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index 78fc1f928..5885d4c0d 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -1443,6 +1443,26 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, SD */ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_RenderViewportSet(SDL_Renderer *renderer); +/** + * Get the safe area for rendering within the current viewport. + * + * Some devices have portions of the screen which are partially obscured or + * not interactive, possibly due to on-screen controls, curved edges, camera + * notches, TV overscan, etc. This function provides the area of the current + * viewport which is safe to have interactible content. You should continue rendering + * into the rest of the render target, but it should not contain visually important + * or interactible content. + * + * \param renderer the rendering context. + * \param rect a pointer filled in with the area that is safe for + * interactive content. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect); + /** * Set the clip rectangle for rendering on the specified target. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index b8771177c..6ea636438 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -401,6 +401,7 @@ SDL3_0.0.0 { SDL_GetRenderMetalCommandEncoder; SDL_GetRenderMetalLayer; SDL_GetRenderOutputSize; + SDL_GetRenderSafeArea; SDL_GetRenderScale; SDL_GetRenderTarget; SDL_GetRenderVSync; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 3db9caa9f..9dc1131f1 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -426,6 +426,7 @@ #define SDL_GetRenderMetalCommandEncoder SDL_GetRenderMetalCommandEncoder_REAL #define SDL_GetRenderMetalLayer SDL_GetRenderMetalLayer_REAL #define SDL_GetRenderOutputSize SDL_GetRenderOutputSize_REAL +#define SDL_GetRenderSafeArea SDL_GetRenderSafeArea_REAL #define SDL_GetRenderScale SDL_GetRenderScale_REAL #define SDL_GetRenderTarget SDL_GetRenderTarget_REAL #define SDL_GetRenderVSync SDL_GetRenderVSync_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 05bab9386..7db3f4818 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -446,6 +446,7 @@ SDL_DYNAPI_PROC(int,SDL_GetRenderLogicalPresentationRect,(SDL_Renderer *a, SDL_F SDL_DYNAPI_PROC(void*,SDL_GetRenderMetalCommandEncoder,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(void*,SDL_GetRenderMetalLayer,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetRenderOutputSize,(SDL_Renderer *a, int *b, int *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_GetRenderSafeArea,(SDL_Renderer *a, SDL_Rect *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetRenderScale,(SDL_Renderer *a, float *b, float *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Texture*,SDL_GetRenderTarget,(SDL_Renderer *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetRenderVSync,(SDL_Renderer *a, int *b),(a,b),return) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 04974aeaf..0bc1d4b79 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -2999,6 +2999,53 @@ static void GetRenderViewportSize(SDL_Renderer *renderer, SDL_FRect *rect) } } +int SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect) +{ + if (rect) { + SDL_zerop(rect); + } + + CHECK_RENDERER_MAGIC(renderer, -1); + + if (renderer->target || !renderer->window) { + // The entire viewport is safe for rendering + return SDL_GetRenderViewport(renderer, rect); + } + + if (rect) { + // Get the window safe rect + SDL_Rect safe; + if (SDL_GetWindowSafeArea(renderer->window, &safe) < 0) { + return -1; + } + + // Convert the coordinates into the render space + float minx = (float)safe.x; + float miny = (float)safe.y; + float maxx = (float)safe.x + safe.w; + float maxy = (float)safe.y + safe.h; + if (SDL_RenderCoordinatesFromWindow(renderer, minx, miny, &minx, &miny) < 0 || + SDL_RenderCoordinatesFromWindow(renderer, maxx, maxy, &maxx, &maxy) < 0) { + return -1; + } + + rect->x = (int)SDL_ceilf(minx); + rect->y = (int)SDL_ceilf(miny); + rect->w = (int)SDL_ceilf(maxx - minx); + rect->h = (int)SDL_ceilf(maxy - miny); + + // Clip with the viewport + SDL_Rect viewport; + if (SDL_GetRenderViewport(renderer, &viewport) < 0) { + return -1; + } + if (!SDL_GetRectIntersection(rect, &viewport, rect)) { + return SDL_SetError("No safe area within viewport"); + } + } + return 0; +} + int SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect) { CHECK_RENDERER_MAGIC(renderer, -1) diff --git a/test/testsprite.c b/test/testsprite.c index 98f6a3276..63ffc43c5 100644 --- a/test/testsprite.c +++ b/test/testsprite.c @@ -81,7 +81,9 @@ static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite) SDL_FRect *position, *velocity; /* Query the sizes */ - SDL_GetRenderViewport(renderer, &viewport); + SDL_SetRenderViewport(renderer, NULL); + SDL_GetRenderSafeArea(renderer, &viewport); + SDL_SetRenderViewport(renderer, &viewport); /* Cycle the color and alpha, if desired */ if (cycle_color) { @@ -424,6 +426,7 @@ int SDL_AppIterate(void *appstate) int SDL_AppInit(void **appstate, int argc, char *argv[]) { + SDL_Rect safe_area; int i; Uint64 seed; const char *icon = "icon.bmp"; @@ -556,6 +559,8 @@ int SDL_AppInit(void **appstate, int argc, char *argv[]) } /* Position sprites and set their velocities using the fuzzer */ + /* Really we should be using per-window safe area, but this is fine for a simple test */ + SDL_GetRenderSafeArea(state->renderers[0], &safe_area); if (iterations >= 0) { /* Deterministic seed - used for visual tests */ seed = (Uint64)iterations; @@ -565,8 +570,8 @@ int SDL_AppInit(void **appstate, int argc, char *argv[]) } SDLTest_FuzzerInit(seed); for (i = 0; i < num_sprites; ++i) { - positions[i].x = (float)SDLTest_RandomIntegerInRange(0, (int)(state->window_w - sprite_w)); - positions[i].y = (float)SDLTest_RandomIntegerInRange(0, (int)(state->window_h - sprite_h)); + positions[i].x = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.w - sprite_w)); + positions[i].y = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.h - sprite_h)); positions[i].w = sprite_w; positions[i].h = sprite_h; velocities[i].x = 0; diff --git a/test/testwm.c b/test/testwm.c index f0838cccf..517e308bd 100644 --- a/test/testwm.c +++ b/test/testwm.c @@ -226,7 +226,9 @@ static void loop(void) SDL_Rect viewport; SDL_FRect menurect; - SDL_GetRenderViewport(renderer, &viewport); + SDL_SetRenderViewport(renderer, NULL); + SDL_GetRenderSafeArea(renderer, &viewport); + SDL_SetRenderViewport(renderer, &viewport); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer);