2026-07-30 01:10:31 -04:00
|
|
|
/**
|
|
|
|
|
* @file draw.c
|
|
|
|
|
* @brief Implements the draw subsystem.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
#include <akerror.h>
|
|
|
|
|
#include <akgl/draw.h>
|
|
|
|
|
#include <akgl/error.h>
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/game.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
/** @brief One horizontal run of pixels the flood fill has still to examine. */
|
|
|
|
|
typedef struct {
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 11:02:20 -04:00
|
|
|
int x1; /**< Leftmost column of the run, inclusive. */
|
|
|
|
|
int x2; /**< Rightmost column of the run, inclusive. */
|
|
|
|
|
int y; /**< The row the run is on. */
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
} FloodSpan;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The flood fill's working stack. File scope and fixed size rather than a local
|
|
|
|
|
* array because AKGL_DRAW_MAX_FLOOD_SPANS spans is 48 KB, which does not belong
|
|
|
|
|
* on the stack of a function a game may call every frame. The consequence is
|
|
|
|
|
* that akgl_draw_flood_fill is not reentrant -- it is a single-threaded
|
|
|
|
|
* immediate-mode operation on a single render target, and so is everything else
|
|
|
|
|
* that touches an SDL_Renderer.
|
|
|
|
|
*/
|
|
|
|
|
static FloodSpan floodspans[AKGL_DRAW_MAX_FLOOD_SPANS];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Remember the renderer's draw color and replace it with @p color.
|
|
|
|
|
*
|
|
|
|
|
* @p previous is written before anything that can fail, so a caller may restore
|
|
|
|
|
* it unconditionally from a CLEANUP block.
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 11:02:20 -04:00
|
|
|
*
|
|
|
|
|
* @param self The backend. Assumed non-`NULL` with a live `sdl_renderer`;
|
|
|
|
|
* every caller has already checked both.
|
|
|
|
|
* @param color The colour to install.
|
|
|
|
|
* @param previous Receives the colour that was in place. Assumed non-`NULL`.
|
|
|
|
|
* Pre-filled with opaque black, so it is safe to restore from
|
|
|
|
|
* even if the query below fails.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_SDL If the current draw colour cannot be read or the new one
|
|
|
|
|
* cannot be set. The message carries `SDL_GetError()`.
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
*/
|
|
|
|
|
static akerr_ErrorContext *push_draw_color(akgl_RenderBackend *self, SDL_Color color, SDL_Color *previous)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
previous->r = 0x00;
|
|
|
|
|
previous->g = 0x00;
|
|
|
|
|
previous->b = 0x00;
|
|
|
|
|
previous->a = SDL_ALPHA_OPAQUE;
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetRenderDrawColor(self->sdl_renderer, &previous->r, &previous->g, &previous->b, &previous->a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetRenderDrawColor(self->sdl_renderer, color.r, color.g, color.b, color.a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 11:02:20 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Put back the draw color push_draw_color() recorded.
|
|
|
|
|
*
|
|
|
|
|
* Called from `CLEANUP` blocks under `IGNORE()`, so its error is logged rather
|
|
|
|
|
* than propagated -- failing to restore a colour must not mask the failure the
|
|
|
|
|
* cleanup is unwinding from.
|
|
|
|
|
*
|
|
|
|
|
* @param self The backend. Assumed non-`NULL` with a live `sdl_renderer`.
|
|
|
|
|
* @param previous The colour push_draw_color() recorded. Assumed non-`NULL`.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_SDL If the draw colour cannot be set.
|
|
|
|
|
*/
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
static akerr_ErrorContext *pop_draw_color(akgl_RenderBackend *self, SDL_Color *previous)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetRenderDrawColor(self->sdl_renderer, previous->r, previous->g, previous->b, previous->a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Fix the type, macro and state-table defects, and the leftover debris
Closes internal-consistency items 19 through 36, 38 and 41. Item 37, the ~180
redundant casts, is deliberately left open with its reasoning in TODO.md: the
benefit only arrives once the build turns on the warnings those casts suppress,
and doing it before that is churn across the two files with the most
outstanding functional defects.
The two that were real bugs are in the actor state table.
AKGL_ACTOR_STATE_STRING_NAMES was declared [AKGL_ACTOR_MAX_STATES+1] and
defined [32], so a consumer trusting the declared bound read past the object;
and indices 11 and 12 were named UNDEFINED_11 and UNDEFINED_12 where actor.h
has MOVING_IN and MOVING_OUT, so no character JSON could bind a sprite to
either state. tests/registry.c now walks the whole table -- every entry
non-NULL, every entry resolving to its own bit, no two entries sharing a name.
The bitmask macros are parenthesized and AKGL_BITMASK_CLEAR has lost the
semicolon inside its body. Writing tests/bitmasks.c for that turned up
something worth knowing: the obvious test does not catch it. For a bit that is
set, the misparse `!(mask & bit) == bit` gives the same answer as the correct
one. It only diverges for an unset bit whose value is not 1, and that is the
shape the suite uses now.
akgl_draw_background was the last public function outside the error protocol.
It takes a backend like everything else in draw.h, restores the draw colour it
found, and is tested -- TODO.md had it filed under "needs the offscreen
renderer harness", which was never true; what it needed was to stop reading the
global.
All eight registry initializers go through one helper, so the seven that leaked
an SDL_PropertiesID on every call after the first no longer do. Fixed in the
same place because it is the same function: akgl_registry_init never called
akgl_registry_init_properties, which made akgl_set_property a silent no-op for
anyone not going through akgl_game_init -- Defects, Known and still open item 3.
Also: AKGL_COLLIDE_RECTANGLES (three open parens, two closes) and akgl_Frame
deleted, float32_t/float64_t used consistently, the developer-specific debug
logging removed from the controller inner loop, the abandoned SDL_GetBasePath
comments removed, nine unused locals removed, and dst renamed to dest.
akgl_game_update's default flags no longer OR the same bit twice. That changes
nothing today, and the reason is Performance item 32: the loop never reads
either bit, which is why every actor is updated sixteen times a frame. Still
open.
25/25 pass, memcheck clean, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-01 00:15:36 -04:00
|
|
|
/* Draw a Gimpish background pattern to show transparency in the image */
|
|
|
|
|
akerr_ErrorContext *akgl_draw_background(akgl_RenderBackend *self, int w, int h)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
SDL_Color col[2] = {
|
|
|
|
|
{ 0x66, 0x66, 0x66, 0xff },
|
|
|
|
|
{ 0x99, 0x99, 0x99, 0xff },
|
|
|
|
|
};
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
SDL_FRect rect;
|
|
|
|
|
const int dx = 8, dy = 8;
|
|
|
|
|
bool pushed = false;
|
|
|
|
|
bool drawfailed = false;
|
|
|
|
|
int i = 0;
|
|
|
|
|
int x = 0;
|
|
|
|
|
int y = 0;
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
|
|
|
|
|
rect.w = (float)dx;
|
|
|
|
|
rect.h = (float)dy;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, col[0], &previous));
|
|
|
|
|
pushed = true;
|
|
|
|
|
|
|
|
|
|
// The two SDL calls are checked through a flag rather than with
|
|
|
|
|
// FAIL_ZERO_BREAK. Inside these loops a `break` would leave the loop,
|
|
|
|
|
// not the ATTEMPT block, and the fill would carry on with the failure
|
|
|
|
|
// unnoticed -- the hazard AGENTS.md describes for CATCH inside a loop.
|
|
|
|
|
for ( y = 0; (y < h) && (drawfailed == false); y += dy ) {
|
|
|
|
|
for ( x = 0; x < w; x += dx ) {
|
|
|
|
|
/* use an 8x8 checkerboard pattern */
|
|
|
|
|
i = (((x ^ y) >> 3) & 1);
|
|
|
|
|
if ( SDL_SetRenderDrawColor(self->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a) == false ) {
|
|
|
|
|
drawfailed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
rect.x = (float)x;
|
|
|
|
|
rect.y = (float)y;
|
|
|
|
|
if ( SDL_RenderFillRect(self->sdl_renderer, &rect) == false ) {
|
|
|
|
|
drawfailed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FAIL_NONZERO_BREAK(errctx, drawfailed, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( pushed == true ) {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Fill the four-connected region of @p oldpixel around a seed pixel.
|
|
|
|
|
*
|
|
|
|
|
* A scanline fill: each entry on the stack is a run of pixels on one row that
|
|
|
|
|
* still has to be examined. Finding a matching pixel expands it to the whole
|
|
|
|
|
* run it belongs to, fills that run, and pushes the rows above and below.
|
|
|
|
|
* Filled pixels no longer match @p oldpixel, which is what terminates it.
|
|
|
|
|
*
|
|
|
|
|
* @p surface must be SDL_PIXELFORMAT_RGBA32; the fill compares and writes whole
|
|
|
|
|
* 32-bit words rather than going through SDL_ReadSurfacePixel per pixel.
|
|
|
|
|
*
|
|
|
|
|
* @p dirty is set to the bounding box of everything written, so the caller can
|
|
|
|
|
* put back only the pixels that changed.
|
|
|
|
|
*
|
|
|
|
|
* Running out of stack leaves the region partially filled and reports
|
|
|
|
|
* AKERR_OUTOFBOUNDS. There is no way to unwind a partial fill short of keeping
|
|
|
|
|
* a copy of the whole surface, and the caller asked for a bounded operation.
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 11:02:20 -04:00
|
|
|
*
|
|
|
|
|
* @param surface The pixels to fill, in SDL_PIXELFORMAT_RGBA32. Assumed
|
|
|
|
|
* non-`NULL` and locked-or-lockless; the caller converts.
|
|
|
|
|
* @param x Seed column. Assumed inside the surface -- akgl_draw_flood_fill
|
|
|
|
|
* has already range-checked it.
|
|
|
|
|
* @param y Seed row, likewise.
|
|
|
|
|
* @param oldpixel The packed pixel value the region is made of. Everything else
|
|
|
|
|
* is a boundary.
|
|
|
|
|
* @param newpixel The packed pixel value to write. Must differ from @p oldpixel:
|
|
|
|
|
* if they are equal the fill never terminates making progress,
|
|
|
|
|
* which is why the caller checks that case first.
|
|
|
|
|
* @param dirty Receives the bounding box of everything written, so the caller
|
|
|
|
|
* can put back only the pixels that changed. Assumed non-`NULL`.
|
|
|
|
|
* Left describing an empty box if nothing matched.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_OUTOFBOUNDS If the region needs more than
|
|
|
|
|
* #AKGL_DRAW_MAX_FLOOD_SPANS pending spans. @p dirty is then not written
|
|
|
|
|
* and the surface is partially filled.
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
*/
|
|
|
|
|
static akerr_ErrorContext *flood_region(SDL_Surface *surface, int x, int y, uint32_t oldpixel, uint32_t newpixel, SDL_Rect *dirty)
|
|
|
|
|
{
|
|
|
|
|
uint32_t *pixels = (uint32_t *)surface->pixels;
|
|
|
|
|
int pitch = surface->pitch / (int)sizeof(uint32_t);
|
|
|
|
|
int count = 0;
|
|
|
|
|
int col = 0;
|
|
|
|
|
int left = 0;
|
|
|
|
|
int right = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
int minx = surface->w;
|
|
|
|
|
int miny = surface->h;
|
|
|
|
|
int maxx = -1;
|
|
|
|
|
int maxy = -1;
|
|
|
|
|
FloodSpan span;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
floodspans[0].x1 = x;
|
|
|
|
|
floodspans[0].x2 = x;
|
|
|
|
|
floodspans[0].y = y;
|
|
|
|
|
count = 1;
|
|
|
|
|
|
|
|
|
|
while ( count > 0 ) {
|
|
|
|
|
count -= 1;
|
|
|
|
|
span = floodspans[count];
|
|
|
|
|
col = span.x1;
|
|
|
|
|
while ( col <= span.x2 ) {
|
|
|
|
|
if ( pixels[(span.y * pitch) + col] != oldpixel ) {
|
|
|
|
|
col += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
left = col;
|
|
|
|
|
while ( left > 0 && pixels[(span.y * pitch) + (left - 1)] == oldpixel ) {
|
|
|
|
|
left -= 1;
|
|
|
|
|
}
|
|
|
|
|
right = col;
|
|
|
|
|
while ( right < (surface->w - 1) && pixels[(span.y * pitch) + (right + 1)] == oldpixel ) {
|
|
|
|
|
right += 1;
|
|
|
|
|
}
|
|
|
|
|
for ( i = left; i <= right; i++ ) {
|
|
|
|
|
pixels[(span.y * pitch) + i] = newpixel;
|
|
|
|
|
}
|
|
|
|
|
if ( left < minx ) {
|
|
|
|
|
minx = left;
|
|
|
|
|
}
|
|
|
|
|
if ( right > maxx ) {
|
|
|
|
|
maxx = right;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y < miny ) {
|
|
|
|
|
miny = span.y;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y > maxy ) {
|
|
|
|
|
maxy = span.y;
|
|
|
|
|
}
|
|
|
|
|
// Two pushes per run, so the check is for room for both.
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
((count + 2) > AKGL_DRAW_MAX_FLOOD_SPANS),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region needs more than %d pending spans; it is partially filled",
|
|
|
|
|
AKGL_DRAW_MAX_FLOOD_SPANS);
|
|
|
|
|
if ( span.y > 0 ) {
|
|
|
|
|
floodspans[count].x1 = left;
|
|
|
|
|
floodspans[count].x2 = right;
|
|
|
|
|
floodspans[count].y = span.y - 1;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y < (surface->h - 1) ) {
|
|
|
|
|
floodspans[count].x1 = left;
|
|
|
|
|
floodspans[count].x2 = right;
|
|
|
|
|
floodspans[count].y = span.y + 1;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
col = right + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dirty->x = minx;
|
|
|
|
|
dirty->y = miny;
|
|
|
|
|
dirty->w = (maxx - minx) + 1;
|
|
|
|
|
dirty->h = (maxy - miny) + 1;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderPoint(self->sdl_renderer, x, y),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderLine(self->sdl_renderer, x1, y1, x2, y2),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "rect");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderRect(self->sdl_renderer, rect),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "rect");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderFillRect(self->sdl_renderer, rect),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
SDL_FPoint octants[8];
|
|
|
|
|
int centerx = 0;
|
|
|
|
|
int centery = 0;
|
|
|
|
|
int r = 0;
|
|
|
|
|
int offsetx = 0;
|
|
|
|
|
int offsety = 0;
|
|
|
|
|
int decision = 0;
|
|
|
|
|
bool plotted = true;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (radius < 0), AKERR_OUTOFBOUNDS, "Negative radius %f", radius);
|
|
|
|
|
|
|
|
|
|
centerx = (int)SDL_lroundf(x);
|
|
|
|
|
centery = (int)SDL_lroundf(y);
|
|
|
|
|
r = (int)SDL_lroundf(radius);
|
|
|
|
|
offsety = r;
|
|
|
|
|
// The midpoint decision variable, started so the first step chooses between
|
|
|
|
|
// (0, r) and (1, r-1) correctly.
|
|
|
|
|
decision = 1 - r;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
while ( offsety >= offsetx ) {
|
|
|
|
|
// Eight-way symmetry: one computed point in the second octant gives
|
|
|
|
|
// the seven others by reflection.
|
|
|
|
|
octants[0].x = (float)(centerx + offsetx);
|
|
|
|
|
octants[0].y = (float)(centery + offsety);
|
|
|
|
|
octants[1].x = (float)(centerx - offsetx);
|
|
|
|
|
octants[1].y = (float)(centery + offsety);
|
|
|
|
|
octants[2].x = (float)(centerx + offsetx);
|
|
|
|
|
octants[2].y = (float)(centery - offsety);
|
|
|
|
|
octants[3].x = (float)(centerx - offsetx);
|
|
|
|
|
octants[3].y = (float)(centery - offsety);
|
|
|
|
|
octants[4].x = (float)(centerx + offsety);
|
|
|
|
|
octants[4].y = (float)(centery + offsetx);
|
|
|
|
|
octants[5].x = (float)(centerx - offsety);
|
|
|
|
|
octants[5].y = (float)(centery + offsetx);
|
|
|
|
|
octants[6].x = (float)(centerx + offsety);
|
|
|
|
|
octants[6].y = (float)(centery - offsetx);
|
|
|
|
|
octants[7].x = (float)(centerx - offsety);
|
|
|
|
|
octants[7].y = (float)(centery - offsetx);
|
|
|
|
|
// A CATCH here would break this loop rather than leave the function,
|
|
|
|
|
// so failure is recorded and reported once the loop is done.
|
|
|
|
|
if ( !SDL_RenderPoints(self->sdl_renderer, octants, 8) ) {
|
|
|
|
|
plotted = false;
|
|
|
|
|
}
|
|
|
|
|
offsetx += 1;
|
|
|
|
|
if ( decision < 0 ) {
|
|
|
|
|
decision += (2 * offsetx) + 1;
|
|
|
|
|
} else {
|
|
|
|
|
offsety -= 1;
|
|
|
|
|
decision += 2 * (offsetx - offsety) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, plotted, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Surface *target = NULL;
|
|
|
|
|
SDL_Surface *rgba = NULL;
|
|
|
|
|
SDL_Texture *patch = NULL;
|
2026-07-31 07:15:08 -04:00
|
|
|
// Only written by a successful flood_region(), and only read after one, but
|
|
|
|
|
// the paths in between are far enough apart that the compiler cannot see it.
|
|
|
|
|
SDL_Rect dirty = { 0, 0, 0, 0 };
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
SDL_FRect src;
|
|
|
|
|
SDL_FRect dest;
|
|
|
|
|
uint32_t *pixels = NULL;
|
|
|
|
|
uint32_t oldpixel = 0;
|
|
|
|
|
uint32_t newpixel = 0;
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetCurrentRenderOutputSize(self->sdl_renderer, &width, &height),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((x < 0) || (y < 0) || (x >= width) || (y >= height)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Seed pixel %d,%d is outside the %dx%d render target",
|
|
|
|
|
x, y, width, height);
|
|
|
|
|
|
|
|
|
|
target = SDL_RenderReadPixels(self->sdl_renderer, NULL);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, target, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// The fill works on 32-bit words, so the layout has to be known rather
|
|
|
|
|
// than whatever the render target happens to use.
|
|
|
|
|
rgba = SDL_ConvertSurface(target, SDL_PIXELFORMAT_RGBA32);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, rgba, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
pixels = (uint32_t *)rgba->pixels;
|
|
|
|
|
oldpixel = pixels[(y * (rgba->pitch / (int)sizeof(uint32_t))) + x];
|
|
|
|
|
newpixel = SDL_MapSurfaceRGBA(rgba, color.r, color.g, color.b, color.a);
|
|
|
|
|
if ( oldpixel == newpixel ) {
|
|
|
|
|
// Already the requested color. Walking it would compare filled
|
|
|
|
|
// pixels against themselves and find nothing, so say so up front.
|
|
|
|
|
SUCCEED_BREAK(errctx);
|
|
|
|
|
}
|
|
|
|
|
CATCH(errctx, flood_region(rgba, x, y, oldpixel, newpixel, &dirty));
|
|
|
|
|
|
|
|
|
|
patch = SDL_CreateTextureFromSurface(self->sdl_renderer, rgba);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, patch, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// Replace rather than blend: this is a framebuffer operation, and the
|
|
|
|
|
// pixels being put back are the ones that were just read out of it.
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetTextureBlendMode(patch, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
|
|
|
|
|
// Only the bounding box of what changed goes back to the target.
|
|
|
|
|
src.x = (float)dirty.x;
|
|
|
|
|
src.y = (float)dirty.y;
|
|
|
|
|
src.w = (float)dirty.w;
|
|
|
|
|
src.h = (float)dirty.h;
|
|
|
|
|
dest = src;
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderTexture(self->sdl_renderer, patch, &src, &dest),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( patch != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(patch);
|
|
|
|
|
}
|
|
|
|
|
if ( rgba != NULL ) {
|
|
|
|
|
SDL_DestroySurface(rgba);
|
|
|
|
|
}
|
|
|
|
|
if ( target != NULL ) {
|
|
|
|
|
SDL_DestroySurface(target);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Surface *saved = NULL;
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, src, AKERR_NULLPOINTER, "src");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((src->w <= 0) || (src->h <= 0)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region %dx%d has no area",
|
|
|
|
|
src->w, src->h);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetCurrentRenderOutputSize(self->sdl_renderer, &width, &height),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
// SDL clips a read to the target and hands back a smaller surface than
|
|
|
|
|
// was asked for, which a caller pasting it back would not notice.
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((src->x < 0) || (src->y < 0) ||
|
|
|
|
|
((src->x + src->w) > width) || ((src->y + src->h) > height)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region %d,%d %dx%d does not fit inside the %dx%d render target",
|
|
|
|
|
src->x, src->y, src->w, src->h, width, height);
|
|
|
|
|
|
|
|
|
|
saved = SDL_RenderReadPixels(self->sdl_renderer, src);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, saved, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
if ( *dest == NULL ) {
|
|
|
|
|
*dest = saved;
|
|
|
|
|
// Ownership has moved to the caller; CLEANUP must not free it.
|
|
|
|
|
saved = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(((*dest)->w != src->w) || ((*dest)->h != src->h)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Destination surface is %dx%d, region is %dx%d",
|
|
|
|
|
(*dest)->w, (*dest)->h, src->w, src->h);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetSurfaceBlendMode(saved, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_BlitSurface(saved, NULL, *dest, NULL),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( saved != NULL ) {
|
|
|
|
|
SDL_DestroySurface(saved);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.
The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.
The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.
scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.
The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.
Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.
24/24 pass, reindent --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 23:44:38 -04:00
|
|
|
akerr_ErrorContext *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y)
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:05:36 -04:00
|
|
|
{
|
|
|
|
|
SDL_Texture *patch = NULL;
|
|
|
|
|
SDL_FRect dest;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, src, AKERR_NULLPOINTER, "src");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
patch = SDL_CreateTextureFromSurface(self->sdl_renderer, src);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, patch, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// Replace what is on the target, the way GSHAPE does by default.
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetTextureBlendMode(patch, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
dest.x = x;
|
|
|
|
|
dest.y = y;
|
|
|
|
|
dest.w = (float)src->w;
|
|
|
|
|
dest.h = (float)src->h;
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderTexture(self->sdl_renderer, patch, NULL, &dest),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( patch != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(patch);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
Add rounded-rect fill, arc stroke and clip-rect drawing primitives
The UI subsystem's render executor needs all three -- clay emits rectangles
with corner radii, per-side borders, and scissor commands -- but they earn
their place in draw.h on the same terms as everything else there: a rounded
panel, a ring gauge and a clipped viewport are things a game wants with or
without a layout engine.
akgl_draw_filled_rounded_rect decomposes into three band fills plus four
quarter-circle triangle fans through SDL_RenderGeometry, with the radius
clamped to half the shorter side. akgl_draw_arc strokes between an outer and
inner radius as one triangle strip, stepping in proportion to the swept angle
under a fixed vertex cap -- no VLAs, unlike clay's own SDL3 reference
renderer. akgl_draw_set_clip wraps SDL_SetRenderClipRect and is the one draw
entry point where a NULL rectangle is legal, because that is how a clip is
cleared.
Pixel-readback tests cover the corner geometry (inside the arc filled,
outside it untouched), the radius clamp, the zero-radius fall-through, ring
and quarter-arc coverage at mid-stroke, sweep bounds, and clip set/clear
round trips.
Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 10:57:23 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Fill one quarter-circle corner as a triangle fan.
|
|
|
|
|
*
|
|
|
|
|
* `SDL_RenderGeometry` colours from its vertices rather than the renderer's
|
|
|
|
|
* draw colour, so this neither pushes nor pops it.
|
|
|
|
|
*
|
|
|
|
|
* @param self The backend. Assumed non-`NULL` with a live `sdl_renderer`;
|
|
|
|
|
* the caller has already checked both.
|
|
|
|
|
* @param cx Horizontal position of the corner's centre -- the point the
|
|
|
|
|
* fan radiates from, one radius inside the rectangle.
|
|
|
|
|
* @param cy Vertical position of the centre.
|
|
|
|
|
* @param radius Radius of the quarter circle. Assumed positive.
|
|
|
|
|
* @param start_deg Angle the quarter starts at; it sweeps 90 degrees clockwise
|
|
|
|
|
* from there.
|
|
|
|
|
* @param color Colour to fill with.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_SDL If the fan cannot be drawn.
|
|
|
|
|
*/
|
|
|
|
|
static akerr_ErrorContext *fill_corner_fan(akgl_RenderBackend *self, float32_t cx, float32_t cy, float32_t radius, float32_t start_deg, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Vertex vertices[AKGL_DRAW_ROUNDED_RECT_SEGMENTS + 2];
|
|
|
|
|
int indices[AKGL_DRAW_ROUNDED_RECT_SEGMENTS * 3];
|
|
|
|
|
SDL_FColor fcolor;
|
|
|
|
|
float32_t angle = 0.0f;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
fcolor.r = (float)color.r / 255.0f;
|
|
|
|
|
fcolor.g = (float)color.g / 255.0f;
|
|
|
|
|
fcolor.b = (float)color.b / 255.0f;
|
|
|
|
|
fcolor.a = (float)color.a / 255.0f;
|
|
|
|
|
|
|
|
|
|
vertices[0].position.x = cx;
|
|
|
|
|
vertices[0].position.y = cy;
|
|
|
|
|
vertices[0].color = fcolor;
|
|
|
|
|
vertices[0].tex_coord.x = 0.0f;
|
|
|
|
|
vertices[0].tex_coord.y = 0.0f;
|
|
|
|
|
for ( i = 0; i <= AKGL_DRAW_ROUNDED_RECT_SEGMENTS; i++ ) {
|
|
|
|
|
angle = (start_deg + ((90.0f / AKGL_DRAW_ROUNDED_RECT_SEGMENTS) * (float)i)) * (SDL_PI_F / 180.0f);
|
|
|
|
|
vertices[i + 1].position.x = cx + (radius * SDL_cosf(angle));
|
|
|
|
|
vertices[i + 1].position.y = cy + (radius * SDL_sinf(angle));
|
|
|
|
|
vertices[i + 1].color = fcolor;
|
|
|
|
|
vertices[i + 1].tex_coord.x = 0.0f;
|
|
|
|
|
vertices[i + 1].tex_coord.y = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < AKGL_DRAW_ROUNDED_RECT_SEGMENTS; i++ ) {
|
|
|
|
|
indices[(i * 3) + 0] = 0;
|
|
|
|
|
indices[(i * 3) + 1] = i + 1;
|
|
|
|
|
indices[(i * 3) + 2] = i + 2;
|
|
|
|
|
}
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderGeometry(
|
|
|
|
|
self->sdl_renderer,
|
|
|
|
|
NULL,
|
|
|
|
|
vertices,
|
|
|
|
|
AKGL_DRAW_ROUNDED_RECT_SEGMENTS + 2,
|
|
|
|
|
indices,
|
|
|
|
|
AKGL_DRAW_ROUNDED_RECT_SEGMENTS * 3),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext *akgl_draw_filled_rounded_rect(akgl_RenderBackend *self, SDL_FRect *rect, float32_t radius, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
SDL_FRect band;
|
|
|
|
|
float32_t half = 0.0f;
|
|
|
|
|
bool pushed = false;
|
|
|
|
|
bool drawfailed = false;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL rectangle");
|
|
|
|
|
if ( (rect->w <= 0.0f) || (rect->h <= 0.0f) ) {
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
if ( radius <= 0.0f ) {
|
|
|
|
|
PASS(errctx, akgl_draw_filled_rect(self, rect, color));
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
half = (((rect->w < rect->h) ? rect->w : rect->h) / 2.0f);
|
|
|
|
|
if ( radius > half ) {
|
|
|
|
|
radius = half;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
pushed = true;
|
|
|
|
|
|
|
|
|
|
// The body is three bands: one the full width between the corner rows,
|
|
|
|
|
// and one between the corners along each of the top and bottom edges.
|
|
|
|
|
// A radius of exactly half the shorter side leaves one or more of them
|
|
|
|
|
// with no area, which SDL fills as nothing -- at the limit the whole
|
|
|
|
|
// shape is the four fans.
|
|
|
|
|
band.x = rect->x;
|
|
|
|
|
band.y = rect->y + radius;
|
|
|
|
|
band.w = rect->w;
|
|
|
|
|
band.h = rect->h - (radius * 2.0f);
|
|
|
|
|
if ( SDL_RenderFillRect(self->sdl_renderer, &band) == false ) {
|
|
|
|
|
drawfailed = true;
|
|
|
|
|
}
|
|
|
|
|
band.x = rect->x + radius;
|
|
|
|
|
band.y = rect->y;
|
|
|
|
|
band.w = rect->w - (radius * 2.0f);
|
|
|
|
|
band.h = radius;
|
|
|
|
|
if ( SDL_RenderFillRect(self->sdl_renderer, &band) == false ) {
|
|
|
|
|
drawfailed = true;
|
|
|
|
|
}
|
|
|
|
|
band.y = rect->y + rect->h - radius;
|
|
|
|
|
if ( SDL_RenderFillRect(self->sdl_renderer, &band) == false ) {
|
|
|
|
|
drawfailed = true;
|
|
|
|
|
}
|
|
|
|
|
FAIL_NONZERO_BREAK(errctx, drawfailed, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
CATCH(errctx, fill_corner_fan(self, rect->x + radius, rect->y + radius, radius, 180.0f, color));
|
|
|
|
|
CATCH(errctx, fill_corner_fan(self, rect->x + rect->w - radius, rect->y + radius, radius, 270.0f, color));
|
|
|
|
|
CATCH(errctx, fill_corner_fan(self, rect->x + rect->w - radius, rect->y + rect->h - radius, radius, 0.0f, color));
|
|
|
|
|
CATCH(errctx, fill_corner_fan(self, rect->x + radius, rect->y + rect->h - radius, radius, 90.0f, color));
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( pushed == true ) {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext *akgl_draw_arc(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, float32_t start_deg, float32_t end_deg, float32_t thickness, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Vertex vertices[AKGL_DRAW_ARC_MAX_POINTS];
|
|
|
|
|
int indices[(AKGL_DRAW_ARC_MAX_POINTS - 2) * 3];
|
|
|
|
|
SDL_FColor fcolor;
|
|
|
|
|
float32_t span = 0.0f;
|
|
|
|
|
float32_t inner = 0.0f;
|
|
|
|
|
float32_t angle = 0.0f;
|
|
|
|
|
int segments = 0;
|
|
|
|
|
int base = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (radius <= 0.0f), AKERR_OUTOFBOUNDS, "Arc radius %f is not positive", (double)radius);
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (thickness <= 0.0f), AKERR_OUTOFBOUNDS, "Arc thickness %f is not positive", (double)thickness);
|
|
|
|
|
span = end_deg - start_deg;
|
|
|
|
|
if ( span <= 0.0f ) {
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
if ( span > 360.0f ) {
|
|
|
|
|
span = 360.0f;
|
|
|
|
|
}
|
|
|
|
|
inner = radius - thickness;
|
|
|
|
|
if ( inner < 0.0f ) {
|
|
|
|
|
inner = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Steps in proportion to the angle swept, so a sliver of arc does not
|
|
|
|
|
// spend the whole vertex budget and a full circle uses all of it: the
|
|
|
|
|
// quarter-circle density of AKGL_DRAW_ROUNDED_RECT_SEGMENTS, capped by
|
|
|
|
|
// what AKGL_DRAW_ARC_MAX_POINTS vertices can carry at two per step.
|
|
|
|
|
segments = (int)((span / 90.0f) * (float)AKGL_DRAW_ROUNDED_RECT_SEGMENTS) + 1;
|
|
|
|
|
if ( segments > ((AKGL_DRAW_ARC_MAX_POINTS / 2) - 1) ) {
|
|
|
|
|
segments = (AKGL_DRAW_ARC_MAX_POINTS / 2) - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fcolor.r = (float)color.r / 255.0f;
|
|
|
|
|
fcolor.g = (float)color.g / 255.0f;
|
|
|
|
|
fcolor.b = (float)color.b / 255.0f;
|
|
|
|
|
fcolor.a = (float)color.a / 255.0f;
|
|
|
|
|
for ( i = 0; i <= segments; i++ ) {
|
|
|
|
|
angle = (start_deg + ((span / (float)segments) * (float)i)) * (SDL_PI_F / 180.0f);
|
|
|
|
|
vertices[i * 2].position.x = x + (radius * SDL_cosf(angle));
|
|
|
|
|
vertices[i * 2].position.y = y + (radius * SDL_sinf(angle));
|
|
|
|
|
vertices[i * 2].color = fcolor;
|
|
|
|
|
vertices[i * 2].tex_coord.x = 0.0f;
|
|
|
|
|
vertices[i * 2].tex_coord.y = 0.0f;
|
|
|
|
|
vertices[(i * 2) + 1].position.x = x + (inner * SDL_cosf(angle));
|
|
|
|
|
vertices[(i * 2) + 1].position.y = y + (inner * SDL_sinf(angle));
|
|
|
|
|
vertices[(i * 2) + 1].color = fcolor;
|
|
|
|
|
vertices[(i * 2) + 1].tex_coord.x = 0.0f;
|
|
|
|
|
vertices[(i * 2) + 1].tex_coord.y = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < segments; i++ ) {
|
|
|
|
|
base = i * 2;
|
|
|
|
|
indices[(i * 6) + 0] = base;
|
|
|
|
|
indices[(i * 6) + 1] = base + 2;
|
|
|
|
|
indices[(i * 6) + 2] = base + 1;
|
|
|
|
|
indices[(i * 6) + 3] = base + 1;
|
|
|
|
|
indices[(i * 6) + 4] = base + 2;
|
|
|
|
|
indices[(i * 6) + 5] = base + 3;
|
|
|
|
|
}
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderGeometry(
|
|
|
|
|
self->sdl_renderer,
|
|
|
|
|
NULL,
|
|
|
|
|
vertices,
|
|
|
|
|
(segments + 1) * 2,
|
|
|
|
|
indices,
|
|
|
|
|
segments * 6),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext *akgl_draw_set_clip(akgl_RenderBackend *self, SDL_Rect *rect)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
// NULL rect deliberately passes straight through: it is how the clip is
|
|
|
|
|
// cleared, and the header says so.
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetRenderClipRect(self->sdl_renderer, rect),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|