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>
174 lines
9.4 KiB
C
174 lines
9.4 KiB
C
/**
|
|
* @file renderer.h
|
|
* @brief The pluggable rendering backend: a record of function pointers plus an initializer.
|
|
*
|
|
* There is one backend shipped, the 2D SDL one, and `akgl_render_init2d` is its
|
|
* initializer -- it creates the window and the `SDL_Renderer` and then fills in
|
|
* the six `akgl_render_2d_*` entry points. A different renderer is a different
|
|
* initializer populating the same struct, not a branch inside these functions.
|
|
*
|
|
* Callers do not normally name the `akgl_render_2d_*` functions directly; they
|
|
* go through the pointers on the backend (`renderer->frame_start(renderer)`),
|
|
* which is what makes the swap possible. The global `renderer` in game.h is the
|
|
* instance the rest of the library draws through.
|
|
*
|
|
* A frame is `frame_start` (clear), any number of `draw_*` calls, then
|
|
* `frame_end` (present).
|
|
*/
|
|
|
|
#ifndef _RENDERER_H_
|
|
#define _RENDERER_H_
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akgl/iterator.h>
|
|
|
|
/** @brief Defines a pluggable renderer backend and drawing callbacks. */
|
|
typedef struct akgl_RenderBackend {
|
|
SDL_Renderer *sdl_renderer; /**< The SDL renderer, created by the initializer. Owned by SDL, not freed here. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*shutdown)(struct akgl_RenderBackend *self); /**< Tear the backend down. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*frame_start)(struct akgl_RenderBackend *self); /**< Begin a frame: clear the target. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*frame_end)(struct akgl_RenderBackend *self); /**< End a frame: present it. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*draw_texture)(struct akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip); /**< Blit one texture. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*draw_mesh)(struct akgl_RenderBackend *self); /**< Reserved for a 3D backend; the 2D one refuses. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*draw_world)(struct akgl_RenderBackend *self, akgl_Iterator *opflags); /**< Draw the whole scene, layer by layer. */
|
|
} akgl_RenderBackend;
|
|
|
|
/**
|
|
* @brief Tear the 2D backend down.
|
|
*
|
|
* A no-op placeholder that always succeeds. The window and `SDL_Renderer` this
|
|
* backend created are still owned by SDL and are reclaimed by `SDL_Quit`, so
|
|
* there is nothing here to release yet; the hook exists so a backend that *does*
|
|
* own resources has somewhere to free them.
|
|
*
|
|
* @param self The backend to shut down. Ignored -- not even dereferenced, so
|
|
* `NULL` is currently harmless.
|
|
* @return `NULL` always. It has no failure path today; check it anyway, because
|
|
* a backend that acquires anything will grow one.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *self);
|
|
/**
|
|
* @brief Begin a frame: clear the render target to opaque black.
|
|
* @param self The backend to draw through. Required, and dereferenced *before*
|
|
* it is checked -- a `NULL` @p self is a crash, not an error.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
|
|
* backend was never run through akgl_render_init2d.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self);
|
|
/**
|
|
* @brief End a frame: present everything drawn since akgl_render_2d_frame_start.
|
|
* @param self The backend to draw through. Required, and dereferenced *before*
|
|
* it is checked -- a `NULL` @p self is a crash, not an error.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
|
|
* backend was never run through akgl_render_init2d.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self);
|
|
/**
|
|
* @brief Blit a texture, rotating it only when asked to.
|
|
*
|
|
* A rotation of exactly 0 takes the plain `SDL_RenderTexture` path, which is why
|
|
* @p center and @p flip are only consulted when @p angle is non-zero.
|
|
*
|
|
* @param self The backend to draw through. Required.
|
|
* @param texture The texture to blit. Required.
|
|
* @param src The rectangle of @p texture to take, in pixels. Optional --
|
|
* `NULL` means the whole texture.
|
|
* @param dest Where to put it on the render target, in pixels. Optional --
|
|
* `NULL` means stretch to fill the whole target. Not an output
|
|
* parameter despite the name.
|
|
* @param angle Clockwise rotation in degrees. Exactly 0 skips rotation
|
|
* entirely, along with @p center and @p flip.
|
|
* @param center Pivot for the rotation, relative to @p dest. Required *when*
|
|
* @p angle is non-zero, ignored otherwise. SDL's own "NULL means
|
|
* the centre of dest" convention is not available here.
|
|
* @param flip Horizontal/vertical mirroring. Applied only on the rotated
|
|
* path; ignored when @p angle is 0.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p self or @p texture is `NULL`; if @p angle is
|
|
* non-zero and @p center is `NULL`; or if the SDL draw call itself
|
|
* fails, in which case the message carries `SDL_GetError()`. That last
|
|
* case is a reused status rather than a pointer problem.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip);
|
|
/**
|
|
* @brief Draw geometry. Not implemented by the 2D backend.
|
|
*
|
|
* The hook exists so a 3D backend has a slot to fill; this one refuses every
|
|
* call. It is not a stub that quietly does nothing -- it fails loudly, so a
|
|
* caller that reaches it finds out at once.
|
|
*
|
|
* @param self The backend. Ignored.
|
|
* @return Never `NULL`.
|
|
* @throws AKERR_API Always, with the message "Not implemented".
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *self);
|
|
/**
|
|
* @brief Draw the whole scene: every tilemap layer, and the actors standing on it.
|
|
*
|
|
* Walks layers from 0 to #AKGL_TILEMAP_MAX_LAYERS. For each, it draws that layer
|
|
* of the global `gamemap` through `camera` (if the map has that many layers),
|
|
* then sweeps the actor pool and calls `renderfunc` on every live actor whose
|
|
* `layer` matches. Sweeping in layer order is what puts actors in front of the
|
|
* scenery they stand on and behind the scenery they walk under.
|
|
*
|
|
* It reads the globals `gamemap`, `camera`, and `HEAP_ACTOR` directly rather
|
|
* than taking them as arguments, so there is exactly one world to draw.
|
|
*
|
|
* @param self The backend to draw through. Required.
|
|
* @param opflags Iterator flags. Optional -- `NULL` substitutes a zeroed set.
|
|
* Currently ignored either way: this function sweeps the actor
|
|
* pool itself instead of going through
|
|
* akgl_registry_iterate_actor, so no `AKGL_ITERATOR_OP_*` bit
|
|
* reaches anything. The parameter is here for the backend
|
|
* signature and for the layer-mask support that has yet to be
|
|
* wired up.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
|
* @throws AKERR_* Whatever akgl_tilemap_draw or an actor's own `renderfunc`
|
|
* raises; the first failure aborts the frame and propagates unchanged.
|
|
*
|
|
* @warning Neither the global `gamemap` nor a live actor's `renderfunc` is
|
|
* checked before use, so drawing before akgl_tilemap_load, or with a
|
|
* hand-built actor that was never run through akgl_actor_initialize,
|
|
* is a crash rather than an error context.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags);
|
|
|
|
/**
|
|
* @brief Create the window and SDL renderer, and bind the 2D backend's methods.
|
|
*
|
|
* Reads `game.screenwidth` and `game.screenheight` from the property registry
|
|
* (both defaulting to the string "0", which asks SDL for a zero-sized window),
|
|
* creates the window and renderer with `game.uri` as the title, points `camera`
|
|
* at the full screen rectangle, and installs the six `akgl_render_2d_*` function
|
|
* pointers on @p self.
|
|
*
|
|
* Because the dimensions come from the registry, akgl_registry_init_properties
|
|
* and the property writes have to happen first -- see the note on
|
|
* akgl_registry_init about the properties registry not being initialized for
|
|
* callers that skip akgl_game_init.
|
|
*
|
|
* @param self The backend to initialize. Required. Its `sdl_renderer` and method
|
|
* pointers are overwritten; anything it held before is not released.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
|
* @throws AKERR_VALUE If `game.screenwidth` or `game.screenheight` is set to
|
|
* something that is not a base-10 integer.
|
|
* @throws ERANGE If either dimension does not fit in an `int`.
|
|
* @throws AKGL_ERR_SDL If the window and renderer cannot be created. The message
|
|
* carries `SDL_GetError()`.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted while reading the two
|
|
* properties.
|
|
*
|
|
* @note The two pooled strings holding the dimensions are only released on the
|
|
* success path, so each failed initialization leaks two string slots.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self);
|
|
|
|
#endif // _RENDERER_H_
|