# 08. Rendering

SDL3 already owns the renderer. `SDL_Renderer`, `SDL_Texture`, `SDL_FRect`, blend
modes and the batching behaviour behind `SDL_RenderPresent` are documented in the
[SDL3 wiki](https://wiki.libsdl.org/SDL3/CategoryRender), and this chapter does not
restate any of it.

What libakgl adds is three things, and only three:

1. **A backend struct** — a record of function pointers, so a renderer can be
   replaced without a branch anywhere in the library.
2. **A frame contract** — `frame_start`, then any number of `draw_*` calls, then
   `frame_end`.
3. **A scene walk** — `draw_world`, which interleaves tilemap layers with the
   actors standing on them.

Per-function reference is generated from the headers by Doxygen and is CI-gated;
this chapter teaches the shape and links there for signatures.

## The backend is a record of function pointers

There is no renderer base class, no vtable pointer, and no dynamic dispatch. There
is a struct with an `SDL_Renderer *` and six function pointers on it:

```c excerpt=include/akgl/renderer.h
typedef struct akgl_RenderBackend {
    SDL_Renderer *sdl_renderer;
    akerr_ErrorContext AKERR_NOIGNORE *(*shutdown)(struct akgl_RenderBackend *self);
    akerr_ErrorContext AKERR_NOIGNORE *(*frame_start)(struct akgl_RenderBackend *self);
    akerr_ErrorContext AKERR_NOIGNORE *(*frame_end)(struct akgl_RenderBackend *self);
    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);
    akerr_ErrorContext AKERR_NOIGNORE *(*draw_mesh)(struct akgl_RenderBackend *self);
    akerr_ErrorContext AKERR_NOIGNORE *(*draw_world)(struct akgl_RenderBackend *self, akgl_Iterator *opflags);
} akgl_RenderBackend;
```

A *backend* is therefore an initializer that fills those six slots in. One is
shipped — the 2D SDL one, whose entry points are the `akgl_render_2d_*` functions —
and a second renderer would be a second initializer pointing the same six slots at
different functions. Nothing in the library tests "which renderer is this".

**Call through the pointers, not the names.** `akgl_render_2d_frame_start(b)` and
`b->frame_start(b)` do the same thing today and stop doing the same thing the
moment anybody swaps a backend in. Every call site inside libakgl goes through the
pointer, and so should yours:

```c
#include <akgl/game.h>
#include <akgl/renderer.h>

akerr_ErrorContext *draw_one_frame(void)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
    PASS(errctx, akgl_renderer->draw_world(akgl_renderer, NULL));
    PASS(errctx, akgl_renderer->frame_end(akgl_renderer));
    SUCCEED_RETURN(errctx);
}
```

`akgl_renderer` is the global the rest of the library draws through. It is a
pointer into `akgl_default_renderer`, set by `akgl_game_init`; see
[Chapter 7](07-the-game-and-the-frame.md). Reassigning `akgl_renderer` is how a
program installs its own backend — there is exactly one world, so there is exactly
one of these.

Every entry point returns `akerr_ErrorContext AKERR_NOIGNORE *`. Statuses and what
they mean are [Chapter 4](04-errors.md); the protocol itself is libakerror's and is
documented in `deps/libakerror`.

## The frame contract

```text
    frame_start(self)           clear the target to opaque black
        |
        +--> draw_world(self, opflags)      the tilemap and its actors
        +--> draw_texture(self, ...)        one blit
        +--> akgl_draw_* (chapter 09)       lines, boxes, circles, fills
        |
    frame_end(self)             present
```

`frame_start` sets the draw colour to opaque black and calls `SDL_RenderClear`
itself. **You do not need to clear again.** The FAQ half of the old `README.md`
showed this:

```c norun
PASS(e, akgl_renderer->frame_start(akgl_renderer));
SDL_RenderClear(akgl_renderer->sdl_renderer);
```

The second line is redundant — it clears an already-cleared target, unchecked, and
at whatever draw colour was left behind. Drop it. (`norun` because it is quoted as
an example of what *not* to write; it compiles, and that is the problem.)

Both check `self` and then `self->sdl_renderer` before touching either, so a `NULL`
backend and a backend that was bound but never given a renderer both report
`AKERR_NULLPOINTER` rather than crashing. `renderer.h` says the opposite — its
`@param self` for `frame_start`, `frame_end` and `draw_texture` reads "dereferenced
*before* it is checked -- a `NULL` @p self is a crash, not an error". Read
`src/renderer.c`: the first statement in each is a `FAIL_ZERO_RETURN` on `self`.

## The camera

`akgl_camera` is an `SDL_FRect *` in **map coordinates**: `x`/`y` are the top-left
corner of the view into the world, `w`/`h` its size. It is a plain pointer into
`akgl_default_camera`, and everything that draws world content subtracts it.

`akgl_render_2d_init` points it at the full screen rectangle at startup. Scrolling
is just writing to it:

```c
#include <akgl/actor.h>
#include <akgl/game.h>

/* Centre the view on an actor, and do not scroll past the top-left of the map. */
akerr_ErrorContext *camera_follow(akgl_Actor *obj)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");

    akgl_camera->x = obj->x - (akgl_camera->w / 2.0f);
    akgl_camera->y = obj->y - (akgl_camera->h / 2.0f);
    if ( akgl_camera->x < 0.0f ) {
	akgl_camera->x = 0.0f;
    }
    if ( akgl_camera->y < 0.0f ) {
	akgl_camera->y = 0.0f;
    }
    SUCCEED_RETURN(errctx);
}
```

There is no camera object and no camera API — a rectangle is the whole model. A
game that wants two views draws twice with the pointer moved between them.

## draw_world walks sixteen layers

`draw_world` is the one call that draws a scene rather than a thing. For each
layer index `i` from 0 to `AKGL_TILEMAP_MAX_LAYERS` (16), it:

1. draws layer `i` of the global `akgl_gamemap` through `akgl_camera`, **if the map
   has that many layers**; then
2. sweeps all `AKGL_MAX_HEAP_ACTOR` (64) actor pool slots and calls `renderfunc` on
   every live actor whose `layer` equals `i`.

```text
    layer 0   tilemap layer 0 ....... actors with layer == 0
    layer 1   tilemap layer 1 ....... actors with layer == 1
    layer 2   (map has only 2)        actors with layer == 2
      ...                               ...
    layer 15  --                      actors with layer == 15
```

That interleaving is the point: it is what puts an actor in front of the ground it
stands on and behind the archway it walks under. An actor's `layer` field is set
from the object layer it was placed in — see [Chapter 12](12-actors.md) and
[Chapter 13](13-tilemaps.md).

The map layer is skipped once `i` reaches `akgl_gamemap->numlayers`, but the actor
sweep is not: **an actor on layer 9 of a two-layer map still draws.** It just draws
over bare background.

`draw_world` reads `akgl_gamemap`, `akgl_camera` and `akgl_heap_actors` directly
rather than taking them as arguments. There is one world.

### opflags is accepted and ignored

The `akgl_Iterator *opflags` argument is part of the backend signature and is
currently **ignored entirely**. `NULL` substitutes a zeroed iterator, and neither
that nor a set of flags you built yourself reaches anything: `draw_world` sweeps
the actor pool itself instead of going through `akgl_registry_iterate_actor`, so no
`AKGL_ITERATOR_OP_*` bit — including `AKGL_ITERATOR_OP_LAYERMASK` — has any effect
here. Pass `NULL`. When layer masking is wired up, the parameter is where it will
land.

### Two things draw_world does not check

- **`akgl_gamemap` is dereferenced unguarded.** Calling `draw_world` before
  `akgl_tilemap_load` is a crash, not an error context.
- **A live actor's `renderfunc` is called unguarded.** A hand-built actor that was
  never run through `akgl_actor_initialize` has a `NULL` there. Always initialize
  actors through the library.

The first failure aborts the frame and propagates unchanged; there is no
draw-what-you-can behaviour.

> **Known defect (issue #26).** The layer loop always runs
> all 16 iterations and rescans all 64 actor slots on each — 1024 refcount checks
> per frame for a one-layer map. Invisible at 60 fps under the software renderer,
> measurable on a 2 ms GPU frame. Bounding the walk by `numlayers` and building
> per-layer actor lists in one pool pass is the recorded fix.

## The embedding seam: akgl_render_2d_bind vs akgl_render_2d_init

This is a first-class use case, not a footnote. Creating a window and populating a
vtable are two separable jobs, and libakgl separates them.

| | `akgl_render_2d_init` | `akgl_render_2d_bind` |
|---|---|---|
| Reads configuration properties | yes — `game.screenwidth`, `game.screenheight` | no |
| Creates a window and `SDL_Renderer` | yes | **no** |
| Touches `self->sdl_renderer` | overwrites it | **never touches it** |
| Points `akgl_camera` at the screen | yes | no |
| Installs the six function pointers | yes, by calling `_bind` | yes |
| Needs the registry initialized first | yes | no |

`akgl_render_2d_init` is the ordinary path for a program that owns its whole
process. It 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, sets `akgl_camera` to the full screen
rectangle, and then calls `akgl_render_2d_bind` to fill in the vtable. Because it
reads properties, `akgl_registry_init_properties` and your property writes have to
happen first; see [Chapter 6](06-the-registry.md).

**The window title is `akgl_game.uri`.** Not `akgl_game.name` — the reverse-DNS
identifier is what `SDL_CreateWindowAndRenderer` is handed. If your title bar reads
`tech.starfort.mygame`, that is why.

`akgl_render_2d_bind` is the other half on its own. It writes six pointers and
returns. It deliberately does not touch `sdl_renderer`, which is exactly what makes
it usable by a host that already has one — an embedded interpreter, a level editor,
an application with its own window that wants libakgl to draw inside it. This is
what the sibling `akbasic` consumer uses.

```c
#include <SDL3/SDL.h>
#include <akgl/game.h>
#include <akgl/renderer.h>

/*
 * A host that already owns its window and SDL_Renderer. It never calls
 * akgl_render_2d_init, so libakgl never creates a second window.
 */
akerr_ErrorContext *host_bind_renderer(SDL_Renderer *mine, int w, int h)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, mine, AKERR_NULLPOINTER, "mine");

    /* _bind never writes sdl_renderer, so put yours there first. */
    akgl_default_renderer.sdl_renderer = mine;
    PASS(errctx, akgl_render_2d_bind(&akgl_default_renderer));

    /* Everything else in the library draws through the global. */
    akgl_renderer = &akgl_default_renderer;

    /* _bind does not set the camera; _init would have. */
    akgl_camera = &akgl_default_camera;
    akgl_camera->x = 0.0f;
    akgl_camera->y = 0.0f;
    akgl_camera->w = (float)w;
    akgl_camera->h = (float)h;
    SUCCEED_RETURN(errctx);
}
```

### Bind the global, not a backend of your own

This is the trap in the seam, and it is worth stating flatly:

**`draw_world` hands `self` to the tilemap and to each actor's `renderfunc`, but
neither of them uses it.** `akgl_actor_render` and `akgl_tilemap_draw` both reach
straight for the global `akgl_renderer`. So does `akgl_spritesheet_initialize`, and
so does every texture load in the tilemap loader.

A host that binds a *private* `akgl_RenderBackend` and calls
`mine.draw_world(&mine, NULL)` gets its tilemap and its actors drawn through
whatever `akgl_renderer` points at — which, if `akgl_game_init` ran, is
`akgl_default_renderer` with a `NULL` `sdl_renderer`. The failure is an
`AKERR_NULLPOINTER` from deep inside a texture load, and it reads like a missing
asset.

Set `akgl_renderer` to the backend you bound. The example above does.

A backend that has been bound but never given an `SDL_Renderer` refuses cleanly
rather than crashing: every entry point checks `self->sdl_renderer` and reports
`AKERR_NULLPOINTER`. That is the designed-for state and is what makes `_bind` safe
to call before you have a renderer.

> **A header claim that is no longer true.** `renderer.h` ends
> `akgl_render_2d_init`'s documentation with a `@note` saying the two pooled
> strings holding the dimensions "are only released on the success path, so each
> failed initialization leaks two string slots". They are released in a `CLEANUP`
> block that runs on both paths, and `src/renderer.c` carries a comment saying
> that is exactly why it was moved there. **There is no leak on the current
> code.** Correcting the header comment is a separate commit; this chapter
> documents what the code does.

## What the 2D backend refuses

**`draw_mesh` always fails.** Every call raises `AKERR_API` with the message "Not
implemented". The hook exists so a 3D backend has a slot to fill. It is not a stub
that quietly does nothing — a caller that reaches it finds out on the first call,
which is the intent:

```c
#include <akgl/game.h>
#include <akgl/renderer.h>

akerr_ErrorContext *try_the_mesh_hook(void)
{
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, akgl_renderer->draw_mesh(akgl_renderer));
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, AKERR_API) {
	/* The 2D backend has no geometry path. Draw it as textures instead. */
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}
```

**`akgl_render_2d_shutdown` is a documented no-op.** It validates `self` and
succeeds. The window and `SDL_Renderer` are still SDL's and are reclaimed by
`SDL_Quit`, so there is nothing here to release yet. Call it anyway — a backend
that acquires anything will grow a failure path, and the hook is where it will go.

## Where to look next

- [Chapter 09](09-drawing.md) — the immediate-mode primitives that draw through
  the same backend.
- [Chapter 12](12-actors.md) — `renderfunc`, `layer`, and what `draw_world`
  actually calls.
- [Chapter 13](13-tilemaps.md) — layers, and what `akgl_tilemap_draw` does with
  the camera.
- [Chapter 07](07-the-game-and-the-frame.md) — where `frame_start` and `frame_end`
  sit inside `akgl_game_update`.
