Tue, 17 Jun 2025 20:11:53 +0200
implement rounded corners
for the time being this should be enough to close issue #384
later we add anti-aliasing, glow effects, etc.
shader/rectangle_frag.glsl | file | annotate | diff | comparison | revisions | |
src/2d.c | file | annotate | diff | comparison | revisions | |
src/ascension/2d.h | file | annotate | diff | comparison | revisions | |
src/ascension/constants.h | file | annotate | diff | comparison | revisions | |
src/ascension/datatypes.h | file | annotate | diff | comparison | revisions | |
src/ascension/shader.h | file | annotate | diff | comparison | revisions | |
src/shader.c | file | annotate | diff | comparison | revisions | |
src/sprite.c | file | annotate | diff | comparison | revisions | |
test/snake/snake.c | file | annotate | diff | comparison | revisions |
--- a/shader/rectangle_frag.glsl Tue Jun 17 19:00:20 2025 +0200 +++ b/shader/rectangle_frag.glsl Tue Jun 17 20:11:53 2025 +0200 @@ -6,15 +6,91 @@ #ifndef FILL uniform float thickness; #endif +#ifdef ROUNDED_CORNERS +uniform float radius; +#endif void main(void) { -#ifdef FILL +#ifdef ROUNDED_CORNERS + // Calculate distances from each corner + vec2 corner_distances[4]; + corner_distances[0] = uvcoord - vec2(radius, radius); // top-left + corner_distances[1] = uvcoord - vec2(size.x - radius, radius); // top-right + corner_distances[2] = uvcoord - vec2(radius, size.y - radius); // bottom-left + corner_distances[3] = uvcoord - vec2(size.x - radius, size.y - radius); // bottom-right + + // Check if we're in a corner region + bool in_corner_region = false; + int corner_idx = -1; + + for (int i = 0; i < 4; i++) { + vec2 test_point = corner_distances[i]; + vec2 corner_test = vec2(0.0); + + if (i == 0 && test_point.x < 0.0 && test_point.y < 0.0) { // top-left + corner_test = test_point; + corner_idx = i; + in_corner_region = true; + break; + } else if (i == 1 && test_point.x > 0.0 && test_point.y < 0.0) { // top-right + corner_test = test_point; + corner_idx = i; + in_corner_region = true; + break; + } else if (i == 2 && test_point.x < 0.0 && test_point.y > 0.0) { // bottom-left + corner_test = test_point; + corner_idx = i; + in_corner_region = true; + break; + } else if (i == 3 && test_point.x > 0.0 && test_point.y > 0.0) { // bottom-right + corner_test = test_point; + corner_idx = i; + in_corner_region = true; + break; + } + } + + #ifdef FILL + // For filled rectangle with rounded corners + if (in_corner_region) { + float dist = length(corner_distances[corner_idx]); + if (dist > radius) { + discard; // Outside the rounded corner + } + } else if (uvcoord.x < 0.0 || uvcoord.y < 0.0 || uvcoord.x > size.x || uvcoord.y > size.y) { + discard; // Outside the rectangle + } diffuse = color; -#else + #else // no FILL + // For outlined rectangle with rounded corners + if (in_corner_region) { + float dist = length(corner_distances[corner_idx]); + if (dist > radius) { + discard; // Outside the rounded corner + } else if (dist < radius - thickness) { + discard; // Inside the outline + } + diffuse = color; + } else if (any(lessThan(uvcoord, vec2(thickness))) || any(greaterThan(uvcoord, size - thickness))) { + // On a straight edge + if (uvcoord.x >= 0.0 && uvcoord.y >= 0.0 && uvcoord.x <= size.x && uvcoord.y <= size.y) { + diffuse = color; + } else { + discard; + } + } else { + discard; // Inside the outline + } + #endif // FILL +#else // no ROUNDED_CORNERS + #ifdef FILL + diffuse = color; + #else // no FILL if (any(notEqual(1.0-step(thickness, uvcoord)+step(size-thickness, uvcoord), vec2(0.0)))) { diffuse = color; } else { discard; } -#endif + #endif // FILL +#endif // ROUNDED_CORNERS }
--- a/src/2d.c Tue Jun 17 19:00:20 2025 +0200 +++ b/src/2d.c Tue Jun 17 20:11:53 2025 +0200 @@ -39,14 +39,24 @@ GLint color; GLint size; GLint thickness; + GLint radius; } AscRectangleShader; -static void *asc_rectangle_shader_create(bool fill) { +#define ASC_RECTANGLE_SHADER_FLAG_FILL 1 +#define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 + +static AscShaderProgram *asc_rectangle_shader_create(int flags) { AscShaderCodes codes; + const char * const defines[] = { + "", + "#define FILL", + "#define ROUNDED_CORNERS", + "#define FILL\n#define ROUNDED_CORNERS", + }; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "rectangle_frag.glsl", - .defines.frag = fill ? "#define FILL" : NULL, + .defines.frag = defines[flags], }, &codes)) { asc_error("Loading sprite shader failed."); return NULL; @@ -58,32 +68,21 @@ } shader->color = glGetUniformLocation(shader->program.gl_id, "color"); shader->size = glGetUniformLocation(shader->program.gl_id, "size"); - if (fill) { + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { shader->thickness = -1; } else { shader->thickness = glGetUniformLocation(shader->program.gl_id, "thickness"); } + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { + shader->radius = glGetUniformLocation(shader->program.gl_id, "radius"); + } else { + shader->radius = -1; + } asc_shader_free_codes(codes); asc_error_catch_all_gl(); - return shader; -} - -static AscShaderProgram *asc_rectangle_shader_fill_create() { - return asc_rectangle_shader_create(true); -} - -static AscShaderProgram *asc_rectangle_shader_draw_create() { - return asc_rectangle_shader_create(false); -} - -static const AscRectangleShader *asc_rectangle_shader_draw(void) { - return asc_shader_lookup_or_create(ASC_SHADER_RECTANGLE_DRAW, asc_rectangle_shader_draw_create); -} - -static const AscRectangleShader *asc_rectangle_shader_fill(void) { - return asc_shader_lookup_or_create(ASC_SHADER_RECTANGLE_FILL, asc_rectangle_shader_fill_create); + return (AscShaderProgram*) shader; } static void asc_rectangle_destroy(AscSceneNode *node) { @@ -99,13 +98,25 @@ static void asc_rectangle_draw(const AscCamera *camera, const AscSceneNode *node) { asc_ptr_cast(AscRectangle, rectangle, node); - bool filled = asc_test_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + const bool filled = asc_test_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + const bool round = rectangle->radius > 0; + + // Compute shader flags + int shader_flags = 0; + if (filled) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_FILL; + if (round) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_ROUND; - // Activate shader - // TODO: scene should know which shader we are going to activate s.t. it can pre-sort nodes - const AscRectangleShader *shader = filled - ? asc_rectangle_shader_fill() - : asc_rectangle_shader_draw(); + // Compute shader ID + const int shader_ids[] = { + ASC_SHADER_RECTANGLE_DRAW, + ASC_SHADER_RECTANGLE_FILL, + ASC_SHADER_RECTANGLE_DRAW_ROUND, + ASC_SHADER_RECTANGLE_FILL_ROUND, + }; + + // Look up and activate shader + const AscRectangleShader *shader = asc_shader_lookup_or_create( + shader_ids[shader_flags], asc_rectangle_shader_create, shader_flags); asc_shader_use(&shader->program, camera); // Upload uniforms @@ -119,11 +130,13 @@ rectangle->color.blue, rectangle->color.alpha ); - glUniform2f(shader->size, (float) rectangle->width, (float) rectangle->height); + glUniform2f(shader->size, rectangle->width, rectangle->height); if (!filled) { - // TODO: implement thickness - glUniform1f(shader->thickness, 1); + glUniform1f(shader->thickness, rectangle->thickness); + } + if (round) { + glUniform1f(shader->radius, rectangle->radius); } // Draw mesh @@ -136,15 +149,17 @@ if (args.bounds.size.width + args.bounds.size.height > 0) { rectangle->node.position.x = (float) args.bounds.pos.x; rectangle->node.position.y = (float) args.bounds.pos.y; - rectangle->width = args.bounds.size.width; - rectangle->height = args.bounds.size.height; + rectangle->width = (float) args.bounds.size.width; + rectangle->height = (float) args.bounds.size.height; } else { rectangle->node.position.x = (float) args.x; rectangle->node.position.y = (float) args.y; - rectangle->width = args.width; - rectangle->height = args.height; + rectangle->width = (float) args.width; + rectangle->height = (float) args.height; } + rectangle->thickness = ASC_NONZERO_OR(1.f, args.thickness); + rectangle->radius = (float)args.radius; rectangle->color = asc_col_itof(asc_context.ink); if (args.filled) {
--- a/src/ascension/2d.h Tue Jun 17 19:00:20 2025 +0200 +++ b/src/ascension/2d.h Tue Jun 17 20:11:53 2025 +0200 @@ -37,8 +37,10 @@ AscSceneNode node; AscMesh mesh; asc_col4f color; - unsigned int width; - unsigned int height; + float width; + float height; + float radius; + float thickness; int flags; } AscRectangle; @@ -48,6 +50,14 @@ int y; unsigned int width; unsigned int height; + /** + * Corner radius. + */ + unsigned int radius; + /** + * Border thickness + */ + unsigned int thickness; bool filled; };
--- a/src/ascension/constants.h Tue Jun 17 19:00:20 2025 +0200 +++ b/src/ascension/constants.h Tue Jun 17 20:11:53 2025 +0200 @@ -32,12 +32,14 @@ // Internally used shader IDs. // -------------------------------------- -#define ASC_SHADER_INTERNAL_ID(id) (1000000000u+id) +#define ASC_SHADER_INTERNAL_ID(id) (1000000000u+id) -#define ASC_SHADER_SPRITE_RECT ASC_SHADER_INTERNAL_ID(1) -#define ASC_SHADER_SPRITE_UV ASC_SHADER_INTERNAL_ID(2) -#define ASC_SHADER_RECTANGLE_DRAW ASC_SHADER_INTERNAL_ID(3) -#define ASC_SHADER_RECTANGLE_FILL ASC_SHADER_INTERNAL_ID(4) +#define ASC_SHADER_SPRITE_RECT ASC_SHADER_INTERNAL_ID(1) +#define ASC_SHADER_SPRITE_UV ASC_SHADER_INTERNAL_ID(2) +#define ASC_SHADER_RECTANGLE_DRAW ASC_SHADER_INTERNAL_ID(3) +#define ASC_SHADER_RECTANGLE_FILL ASC_SHADER_INTERNAL_ID(4) +#define ASC_SHADER_RECTANGLE_DRAW_ROUND ASC_SHADER_INTERNAL_ID(5) +#define ASC_SHADER_RECTANGLE_FILL_ROUND ASC_SHADER_INTERNAL_ID(6) #endif // ASC_CONSTANTS_H
--- a/src/ascension/datatypes.h Tue Jun 17 19:00:20 2025 +0200 +++ b/src/ascension/datatypes.h Tue Jun 17 20:11:53 2025 +0200 @@ -52,7 +52,7 @@ * @param x the value that shall be tested * @return x if nonzero, y otherwise */ -#define ASC_NONZERO_OR(y, x) (x ? x : y) +#define ASC_NONZERO_OR(y, x) ((x) != 0 ? x : y) #define asc_test_flag(reg, flag) ((reg & flag) == flag) #define asc_test_flag_masked(reg, mask, flag) ((reg & mask) == flag)
--- a/src/ascension/shader.h Tue Jun 17 19:00:20 2025 +0200 +++ b/src/ascension/shader.h Tue Jun 17 20:11:53 2025 +0200 @@ -80,13 +80,13 @@ } AscShaderProgram; /** - * The maximum ID for a user defined shader. + * The maximum ID for a user-defined shader. * * The IDs above this number are reserved for the engine. */ #define ASC_SHADER_ID_USER_MAX 1'000'000'000u -typedef AscShaderProgram*(*asc_shader_create_func)(void); +typedef AscShaderProgram*(*asc_shader_create_func)(int); /** * Loads shader codes from files. @@ -143,10 +143,11 @@ * * @param id the custom ID of the shader * @param create_func the function that creates the shader + * @param create_flags flags passed to create_func * @return the shader created by the @c create_func * @see asc_shader_lookup() */ -const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func); +const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func, int create_flags); /** * Looks up a shader by ID. @@ -172,11 +173,12 @@ * * @param id the custom ID of the shader * @param create_func the function to create the shader + * @param create_flags flags passed to create_func * @return the found shader or the newly created shader * @see asc_shader_lookup() * @see asc_shader_register() */ -const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func); +const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func, int create_flags); /**
--- a/src/shader.c Tue Jun 17 19:00:20 2025 +0200 +++ b/src/shader.c Tue Jun 17 20:11:53 2025 +0200 @@ -207,7 +207,7 @@ cxFreeDefault(codes.frag); } -const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func) { +const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func, int create_flags) { AscGLContext *glctx = asc_active_glctx; #ifndef NDEBUG { @@ -219,7 +219,7 @@ } } #endif - AscShaderProgram *prog = create_func(); + AscShaderProgram *prog = create_func(create_flags); prog->id = id; cxListAdd(glctx->shaders, prog); return prog; @@ -233,10 +233,10 @@ return NULL; } -const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func) { +const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func, int create_flags) { const AscShaderProgram *prog = asc_shader_lookup(id); if (prog == NULL) { - return asc_shader_register(id, create_func); + return asc_shader_register(id, create_func, create_flags); } return prog; }
--- a/src/sprite.c Tue Jun 17 19:00:20 2025 +0200 +++ b/src/sprite.c Tue Jun 17 20:11:53 2025 +0200 @@ -42,7 +42,7 @@ GLint tex; } AscSpriteShader; -static void *asc_sprite_shader_create(bool rect) { +static AscShaderProgram *asc_sprite_shader_create(int rect) { AscShaderCodes codes; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", @@ -62,23 +62,7 @@ asc_error_catch_all_gl(); - return shader; -} - -static AscShaderProgram *asc_sprite_shader_rect_create() { - return asc_sprite_shader_create(true); -} - -static AscShaderProgram *asc_sprite_shader_uv_create() { - return asc_sprite_shader_create(false); -} - -static const AscSpriteShader *asc_sprite_shader_rect(void) { - return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_RECT, asc_sprite_shader_rect_create); -} - -static const AscSpriteShader *asc_sprite_shader_uv(void) { - return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_UV, asc_sprite_shader_uv_create); + return (AscShaderProgram*) shader; } static void asc_sprite_destroy(AscSceneNode *node) { @@ -113,8 +97,8 @@ // TODO: scene should know which shader we are going to activate s.t. it can pre-sort nodes const AscSpriteShader *shader = sprite->texture->target == GL_TEXTURE_RECTANGLE - ? asc_sprite_shader_rect() - : asc_sprite_shader_uv(); + ? asc_shader_lookup_or_create(ASC_SHADER_SPRITE_RECT, asc_sprite_shader_create, 1) + : asc_shader_lookup_or_create(ASC_SHADER_SPRITE_UV, asc_sprite_shader_create, 0); asc_shader_use(&shader->program, camera); // Upload model matrix
--- a/test/snake/snake.c Tue Jun 17 19:00:20 2025 +0200 +++ b/test/snake/snake.c Tue Jun 17 20:11:53 2025 +0200 @@ -199,7 +199,8 @@ // TODO: play around with the test rectangle asc_ink_rgb(255, 0, 0); asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 200, .y = 250, .width = 100, .height = 75) + asc_rectangle(.x = 200, .y = 250, .width = 100, .height = 75, + .thickness = 4, .radius = 15) ); // Main Loop