From b3b774292f6f5ed79f9c832914353a1bf2885ecb Mon Sep 17 00:00:00 2001 From: Arkadiusz Lach Date: Fri, 24 Jan 2025 10:17:44 +0100 Subject: [PATCH 1/2] Added tiled 9-grid texture rendering function * New function SDL_RenderTexture9GridTiled, borders and center is tiled instead of stretched --- include/SDL3/SDL_render.h | 36 +++++++ src/render/SDL_render.c | 135 +++++++++++++++++++++++++ test/testautomation_render.c | 188 +++++++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index 891e9945240a5..fe9aa9c0802bb 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -2182,6 +2182,42 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer, */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect); +/** + * Perform a scaled copy using the 9-grid algorithm to the current rendering + * target at subpixel precision. + * + * The pixels in the texture are split into a 3x3 grid, using the different + * corner sizes for each corner, and the sides and center making up the + * remaining pixels. The corners are then scaled using `scale` and fit into + * the corners of the destination rectangle. The sides and center are then + * tiled into place to cover the remaining destination rectangle. + * + * \param renderer the renderer which should copy parts of a texture. + * \param texture the source texture. + * \param srcrect the SDL_Rect structure representing the rectangle to be used + * for the 9-grid, or NULL to use the entire texture. + * \param left_width the width, in pixels, of the left corners in `srcrect`. + * \param right_width the width, in pixels, of the right corners in `srcrect`. + * \param top_height the height, in pixels, of the top corners in `srcrect`. + * \param bottom_height the height, in pixels, of the bottom corners in + * `srcrect`. + * \param scale the scale used to transform the corner of `srcrect` into the + * corner of `dstrect`, or 0.0f for an unscaled copy. + * \param dstrect a pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \param tileScale the scale used to transform the borders and center of `srcrect` into the + * borders and middle of `dstrect`, or 1.0f for an unscaled copy. + * \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.2.0. + * + * \sa SDL_RenderTexture + */ +extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale); + /** * Render a list of triangles, optionally using a texture and indices into the * vertex array Color and alpha modulation is done per vertex diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 45f3686a31380..8f72c28ad1666 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -4483,6 +4483,141 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const return true; } +bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale) +{ + SDL_FRect full_src, full_dst; + SDL_FRect curr_src, curr_dst; + float dst_left_width; + float dst_right_width; + float dst_top_height; + float dst_bottom_height; + + 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 (!srcrect) { + full_src.x = 0; + full_src.y = 0; + full_src.w = (float)texture->w; + full_src.h = (float)texture->h; + srcrect = &full_src; + } + + if (!dstrect) { + GetRenderViewportSize(renderer, &full_dst); + dstrect = &full_dst; + } + + if (scale <= 0.0f || scale == 1.0f) { + dst_left_width = SDL_ceilf(left_width); + dst_right_width = SDL_ceilf(right_width); + dst_top_height = SDL_ceilf(top_height); + dst_bottom_height = SDL_ceilf(bottom_height); + } else { + dst_left_width = SDL_ceilf(left_width * scale); + dst_right_width = SDL_ceilf(right_width * scale); + dst_top_height = SDL_ceilf(top_height * scale); + dst_bottom_height = SDL_ceilf(bottom_height * scale); + } + + // Center + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y + top_height; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Upper-left corner + curr_src.x = srcrect->x; + curr_src.y = srcrect->y; + curr_src.w = left_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x; + curr_dst.y = dstrect->y; + curr_dst.w = dst_left_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Upper-right corner + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-right corner + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-left corner + curr_src.x = srcrect->x; + curr_src.w = left_width; + curr_dst.x = dstrect->x; + curr_dst.w = dst_left_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Left + curr_src.y = srcrect->y + top_height; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Right + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Top + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Bottom + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + return true; +} + bool SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, diff --git a/test/testautomation_render.c b/test/testautomation_render.c index 25572ee001a6c..1850744a424dd 100644 --- a/test/testautomation_render.c +++ b/test/testautomation_render.c @@ -616,6 +616,189 @@ static int SDLCALL render_testBlit9Grid(void *arg) return TEST_COMPLETED; } +/** + * Tests tiled 9-grid blitting. + */ +static int SDLCALL render_testBlit9GridTiled(void *arg) +{ + SDL_Surface *referenceSurface = NULL; + SDL_Surface *source = NULL; + SDL_Texture *texture; + int x, y; + SDL_FRect rect; + int ret = 0; + + /* Create source surface */ + source = SDL_CreateSurface(3, 3, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL"); + for (y = 0; y < 3; ++y) { + for (x = 0; x < 3; ++x) { + SDL_WriteSurfacePixel(source, x, y, (Uint8)((1 + x) * COLOR_SEPARATION), (Uint8)((1 + y) * COLOR_SEPARATION), 0, 255); + } + } + texture = SDL_CreateTextureFromSurface(renderer, source); + SDLTest_AssertCheck(texture != NULL, "Verify source texture is not NULL"); + ret = SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret); + + /* Tiled 9-grid blit - 1.0 scale */ + { + SDLTest_Log("tiled 9-grid blit - 1.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 1, 1, 1, 1); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, &rect, 1.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Tiled 9-grid blit - 2.0 scale */ + { + SDLTest_Log("tiled 9-grid blit - 2.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 2, 2, 2, 2); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, &rect, 2.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Clean up. */ + SDL_DestroySurface(source); + SDL_DestroyTexture(texture); + + /* Create complex source surface */ + source = SDL_CreateSurface(5, 5, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL"); + SDL_WriteSurfacePixel(source, 0, 0, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 1, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 2, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 3, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 4, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + + texture = SDL_CreateTextureFromSurface(renderer, source); + SDLTest_AssertCheck(texture != NULL, "Verify source texture is not NULL"); + ret = SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret); + + /* complex tiled 9-grid blit - 1.0 scale */ + { + SDLTest_Log("complex tiled 9-grid blit - 1.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 1, 2, 1, 2); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, &rect, 1.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* complex tiled 9-grid blit - 2.0 scale */ + { + SDLTest_Log("complex tiled 9-grid blit - 2.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 2, 4, 2, 4); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 2.0f, &rect, 2.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Clean up. */ + SDL_DestroySurface(referenceSurface); + SDL_DestroySurface(source); + SDL_DestroyTexture(texture); + + return TEST_COMPLETED; +} + /** * Blits doing color tests. * @@ -1539,6 +1722,10 @@ static const SDLTest_TestCaseReference renderTestBlit9Grid = { render_testBlit9Grid, "render_testBlit9Grid", "Tests 9-grid blitting", TEST_ENABLED }; +static const SDLTest_TestCaseReference renderTestBlit9GridTiled = { + render_testBlit9GridTiled, "render_testBlit9GridTiled", "Tests tiled 9-grid blitting", TEST_ENABLED +}; + static const SDLTest_TestCaseReference renderTestBlitColor = { render_testBlitColor, "render_testBlitColor", "Tests blitting with color", TEST_ENABLED }; @@ -1571,6 +1758,7 @@ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestBlit, &renderTestBlitTiled, &renderTestBlit9Grid, + &renderTestBlit9GridTiled, &renderTestBlitColor, &renderTestBlendModes, &renderTestViewport, From e50ed5bc59c2132b9ee2dc900409c284ed2062ab Mon Sep 17 00:00:00 2001 From: Arkadiusz Lach Date: Sun, 2 Feb 2025 01:19:39 +0100 Subject: [PATCH 2/2] Fix from #12118 --- src/render/SDL_render.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 8f72c28ad1666..dac71638a81b0 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -4561,6 +4561,7 @@ bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, c // Lower-right corner curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; curr_dst.h = dst_bottom_height; if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { @@ -4609,6 +4610,7 @@ bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, c // Bottom curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; curr_dst.h = dst_bottom_height; if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) {