/** * @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. * * Those are two separable jobs, and a host that already owns an `SDL_Renderer` * -- an embedded interpreter, which must not create the window -- wants only the * second. akgl_render_bind2d() is that half on its own; akgl_render_init2d() * makes the window and then calls it. * * 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 #include #include /** @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 Install the 2D backend's methods on a backend the caller owns. * * The vtable half of akgl_render_init2d(), with no window, no renderer, and no * property registry involved: it points @p self's six function pointers at the * `akgl_render_2d_*` entry points and returns. `sdl_renderer` is not touched, * so a caller who has already put its own `SDL_Renderer` there keeps it, and a * caller who has not gets a backend whose entry points all report * `AKERR_NULLPOINTER` rather than crash. * * This is the entry point for a host that owns its own window -- it can drive * akgl_actor_render(), akgl_tilemap_draw() and the rest through a backend * libakgl never created. akgl_render_init2d() is the same thing with a window * in front of it. * * @param self The backend to bind. Required. Only the method pointers are * written. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self is `NULL`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self); /** * @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 then calls akgl_render_bind2d() to install * 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_