245 lines
8.3 KiB
C
245 lines
8.3 KiB
C
|
|
/**
|
||
|
|
* @file graphics_akgl.c
|
||
|
|
* @brief Wires the graphics backend record to libakgl's immediate-mode drawing.
|
||
|
|
*
|
||
|
|
* A thin adaptor and deliberately so: the interesting decisions about what a
|
||
|
|
* BASIC graphics verb means live in src/runtime_graphics.c, and everything here
|
||
|
|
* is coordinate and colour conversion plus the shape pool that SSHAPE hands out
|
||
|
|
* handles into.
|
||
|
|
*
|
||
|
|
* The akgl_draw_* family is new in libakgl 42b60f7. Every one of its entry
|
||
|
|
* points takes the akgl_RenderBackend the host already initialized rather than
|
||
|
|
* reaching for a global, which is exactly what goal 3 needs.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akgl/draw.h>
|
||
|
|
#include <akgl/error.h>
|
||
|
|
|
||
|
|
#include <akbasic/akgl.h>
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
|
||
|
|
/** @brief The colour conversion, which is the whole impedance mismatch. */
|
||
|
|
static SDL_Color to_sdl(akbasic_Color color)
|
||
|
|
{
|
||
|
|
SDL_Color out;
|
||
|
|
|
||
|
|
out.r = color.r;
|
||
|
|
out.g = color.g;
|
||
|
|
out.b = color.b;
|
||
|
|
out.a = color.a;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Recover the backend's own state, or say that it has none. */
|
||
|
|
static akerr_ErrorContext *state_of(akbasic_GraphicsBackend *self, akbasic_AkglGraphics **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (self != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in akgl graphics backend");
|
||
|
|
*dest = (akbasic_AkglGraphics *)self->self;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"akgl graphics backend has no state");
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_point(akbasic_GraphicsBackend *self, double x, double y, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
PASS(errctx, akgl_draw_point(state->renderer, (float32_t)x, (float32_t)y, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_line(akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
PASS(errctx, akgl_draw_line(state->renderer, (float32_t)x1, (float32_t)y1,
|
||
|
|
(float32_t)x2, (float32_t)y2, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Turn two opposite corners into the rect SDL wants.
|
||
|
|
*
|
||
|
|
* BASIC gives two corners in whatever order the program felt like; SDL_FRect is
|
||
|
|
* an origin and a size and does nothing useful with a negative one.
|
||
|
|
*/
|
||
|
|
static SDL_FRect corners_to_rect(double x1, double y1, double x2, double y2)
|
||
|
|
{
|
||
|
|
SDL_FRect rect;
|
||
|
|
|
||
|
|
rect.x = (float32_t)((x1 < x2) ? x1 : x2);
|
||
|
|
rect.y = (float32_t)((y1 < y2) ? y1 : y2);
|
||
|
|
rect.w = (float32_t)((x1 < x2) ? (x2 - x1) : (x1 - x2));
|
||
|
|
rect.h = (float32_t)((y1 < y2) ? (y2 - y1) : (y1 - y2));
|
||
|
|
return rect;
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_rect(akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
SDL_FRect rect;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
rect = corners_to_rect(x1, y1, x2, y2);
|
||
|
|
PASS(errctx, akgl_draw_rect(state->renderer, &rect, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_filled_rect(akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
SDL_FRect rect;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
rect = corners_to_rect(x1, y1, x2, y2);
|
||
|
|
PASS(errctx, akgl_draw_filled_rect(state->renderer, &rect, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_paint(akbasic_GraphicsBackend *self, int x, int y, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
/*
|
||
|
|
* AKERR_OUTOFBOUNDS out of here means the fill exhausted its span stack and
|
||
|
|
* left the region partly filled. It is passed straight through: PAINT is
|
||
|
|
* where that is turned into something a BASIC program can see, because PAINT
|
||
|
|
* is the only thing that knows a program is watching.
|
||
|
|
*/
|
||
|
|
PASS(errctx, akgl_draw_flood_fill(state->renderer, x, y, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_clear(akbasic_GraphicsBackend *self, akbasic_Color color)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
SDL_FRect rect;
|
||
|
|
int w = 0;
|
||
|
|
int h = 0;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
FAIL_ZERO_RETURN(errctx,
|
||
|
|
SDL_GetCurrentRenderOutputSize(state->renderer->sdl_renderer, &w, &h),
|
||
|
|
AKGL_ERR_SDL, "%s", SDL_GetError());
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A filled rectangle over the output rather than SDL_RenderClear, because
|
||
|
|
* clearing is the host's prerogative: a game that draws a background and
|
||
|
|
* then hands the renderer to a script does not expect GRAPHIC to wipe it to
|
||
|
|
* a colour of the script's choosing outside the area the script owns. This
|
||
|
|
* covers exactly the render output and nothing beyond it.
|
||
|
|
*/
|
||
|
|
rect.x = 0.0f;
|
||
|
|
rect.y = 0.0f;
|
||
|
|
rect.w = (float32_t)w;
|
||
|
|
rect.h = (float32_t)h;
|
||
|
|
PASS(errctx, akgl_draw_filled_rect(state->renderer, &rect, to_sdl(color)));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_save_shape(akbasic_GraphicsBackend *self, int x1, int y1, int x2, int y2, int *handle)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
SDL_Rect region;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (handle != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL handle in save_shape");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (state->shapecount < AKBASIC_AKGL_MAX_SHAPES),
|
||
|
|
AKBASIC_ERR_DEVICE,
|
||
|
|
"SSHAPE pool is full at %d saved regions; GRAPHIC CLR releases them",
|
||
|
|
AKBASIC_AKGL_MAX_SHAPES);
|
||
|
|
|
||
|
|
region.x = (x1 < x2) ? x1 : x2;
|
||
|
|
region.y = (y1 < y2) ? y1 : y2;
|
||
|
|
region.w = (x1 < x2) ? (x2 - x1) : (x1 - x2);
|
||
|
|
region.h = (y1 < y2) ? (y2 - y1) : (y1 - y2);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* NULL through dest asks akgl_draw_copy_region to allocate the surface. That
|
||
|
|
* is the one allocation in this file and it belongs to the pool below, which
|
||
|
|
* is bounded -- so it is a fixed ceiling on memory, not an open one.
|
||
|
|
*/
|
||
|
|
state->shapes[state->shapecount] = NULL;
|
||
|
|
PASS(errctx, akgl_draw_copy_region(state->renderer, ®ion,
|
||
|
|
&state->shapes[state->shapecount]));
|
||
|
|
*handle = state->shapecount;
|
||
|
|
state->shapecount += 1;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_paste_shape(akbasic_GraphicsBackend *self, int handle, double x, double y)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (handle >= 0 && handle < state->shapecount),
|
||
|
|
AKBASIC_ERR_BOUNDS, "No saved shape %d", handle);
|
||
|
|
FAIL_ZERO_RETURN(errctx, (state->shapes[handle] != NULL), AKBASIC_ERR_STATE,
|
||
|
|
"Saved shape %d has been released", handle);
|
||
|
|
PASS(errctx, akgl_draw_paste_region(state->renderer, state->shapes[handle],
|
||
|
|
(float32_t)x, (float32_t)y));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static akerr_ErrorContext *gfx_free_shapes(akbasic_GraphicsBackend *self)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_AkglGraphics *state = NULL;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
PASS(errctx, state_of(self, &state));
|
||
|
|
for ( i = 0; i < state->shapecount; i++ ) {
|
||
|
|
if ( state->shapes[i] != NULL ) {
|
||
|
|
SDL_DestroySurface(state->shapes[i]);
|
||
|
|
state->shapes[i] = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
state->shapecount = 0;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akbasic_AkglGraphics *state, akgl_RenderBackend *renderer)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && state != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in graphics_init_akgl");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (renderer != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL renderer in graphics_init_akgl: the host creates it, not this");
|
||
|
|
PASS(errctx, akgl_error_init());
|
||
|
|
|
||
|
|
memset(state, 0, sizeof(*state));
|
||
|
|
state->renderer = renderer;
|
||
|
|
|
||
|
|
obj->self = state;
|
||
|
|
obj->point = gfx_point;
|
||
|
|
obj->line = gfx_line;
|
||
|
|
obj->rect = gfx_rect;
|
||
|
|
obj->filled_rect = gfx_filled_rect;
|
||
|
|
obj->paint = gfx_paint;
|
||
|
|
obj->clear = gfx_clear;
|
||
|
|
obj->save_shape = gfx_save_shape;
|
||
|
|
obj->paste_shape = gfx_paste_shape;
|
||
|
|
obj->free_shapes = gfx_free_shapes;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|