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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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);
|
|
|
|
|
}
|