kobodl/graphics/sprite.c
Ville Lindholm 762b095462
WIP: delete glSDL shim, replace with sdl_compat.h
Step 1+2 of the SDL 3 migration. The 2300-line graphics/glSDL.{c,h}
OpenGL-on-top-of-SDL-1.2 shim is obsolete under SDL 3 (SDL_Renderer
does what glSDL did natively), and its #define overrides of SDL
surface APIs collided directly with SDL 3's SDL_oldnames.h.

- Add graphics/sdl_compat.h: a one-line umbrella that just pulls in
  <SDL3/SDL.h>.
- Replace every #include "glSDL.h" with #include "sdl_compat.h" (15
  files across the tree).
- Delete graphics/glSDL.c, graphics/glSDL.h.
- Drop glSDL.c from graphics/CMakeLists.txt.
- Remove GFX_DRIVER_GLSDL from the gfx_drivers_t enum and from all
  switches/conditions in gfxengine.cpp.
- Drop the glSDL_VSync() call site (will be re-introduced as
  SDL_SetRenderVSync once gfxengine is ported to SDL_Renderer).
- Drop the two glSDL_Invalidate() calls in window.cpp (texture
  re-upload hints with no equivalent in a pure-surface compositor).
- options.cpp: remove the now-empty "OpenGL/glSDL" menu item; force
  videodriver to GFX_DRIVER_SDL2D.
- prefs.cpp: collapse legacy GLSDL setting onto SDL2D on load.

After this commit the macro-collision wall is gone (renamed_-token
errors drop from many to zero). The build still fails on the next
layer of work: SDL 1.2 surface APIs that no longer exist
(SDL_FreeSurface, SDL_SetAlpha, SDL_CreateRGBSurface, SDL_DisplayFormat,
SDL_PixelFormat struct member access, SDL audio callback API, etc.).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 21:37:01 +03:00

613 lines
12 KiB
C

/*(LGPL)
----------------------------------------------------------------------
sprite.c - Sprite engine for use with cs.h
----------------------------------------------------------------------
* Copyright (C) 2001, 2003, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
TODO: Automatic extension of the bank and sprite
TODO: tables as needed when loading banks.
*/
#define DBG(x) x
#include <stdlib.h>
#include <string.h>
#include "logger.h"
#include "sdl_compat.h"
#include <SDL3_image/SDL_image.h>
#include "sprite.h"
#include "filters.h"
s_blitmodes_t s_blitmode = S_BLITMODE_AUTO;
pix_t s_colorkey = {0, 0, 0, 0};
pix_t s_clampcolor = {0, 0, 0, 0};
unsigned char s_alpha = SDL_ALPHA_OPAQUE;
int s_filter_flags = 0;
s_filter_t *filters = NULL;
/*
----------------------------------------------------------------------
Filter Plugin Interface
----------------------------------------------------------------------
*/
s_filter_t *s_insert_filter(s_filter_cb_t callback)
{
s_filter_t *nf = calloc(1, sizeof(s_filter_t));
if(!nf)
return NULL;
nf->callback = callback;
nf->next = filters;
filters = nf;
return nf;
}
s_filter_t *s_add_filter(s_filter_cb_t callback)
{
s_filter_t *nf = calloc(1, sizeof(s_filter_t));
if(!nf)
return NULL;
nf->callback = callback;
if(!filters)
filters = nf;
else
{
s_filter_t *f = filters;
while(f->next)
f = f->next;
f->next = nf;
}
return nf;
}
/* filter == NULL means "remove all plugins" */
void s_remove_filter(s_filter_t *filter)
{
s_filter_t *f;
if(!filters)
return;
f = filters;
while(f->next)
{
if(f->next == filter || !filter)
{
s_filter_t *df = f->next;
f->next = f->next->next;
free(df);
}
else
f = f->next;
}
if(filters == filter || !filter)
{
s_filter_t *df = filters;
filters = filters->next;
free(df);
}
}
static void __run_plugins(s_bank_t *b, unsigned first, unsigned frames)
{
s_filter_t *f = filters;
while(f)
{
int oflags = f->args.flags;
f->args.flags |= s_filter_flags;
f->callback(b, first, frames, &f->args);
f->args.flags = oflags;
f = f->next;
}
}
/*
----------------------------------------------------------------------
Basic Container Management
----------------------------------------------------------------------
*/
static int __alloc_bank_table(s_container_t *c, unsigned int count)
{
c->max = count - 1;
c->banks = (s_bank_t **)calloc((unsigned)count, sizeof(s_bank_t *));
return -(c->banks == NULL);
}
s_container_t *s_new_container(unsigned banks)
{
s_container_t *ret = (s_container_t *)calloc(1, sizeof(s_container_t));
if(!ret)
return NULL;
if(__alloc_bank_table(ret, banks) < 0)
return NULL;
return ret;
}
void s_delete_container(s_container_t *c)
{
s_delete_all_banks(c);
free(c->banks);
free(c);
}
/*
* Allocates a new sprite.
* If the sprite exists already, it's surface will be removed, so
* that one can safely expect to get an *empty* sprite.
*/
s_sprite_t *s_new_sprite_b(s_bank_t *b, unsigned frame)
{
s_sprite_t *s = NULL;
if(frame > b->max)
return NULL;
if(!b->sprites[frame])
{
s = calloc(1, sizeof(s_sprite_t));
if(!s)
return NULL;
b->sprites[frame] = s;
}
else
{
if(s->surface)
SDL_FreeSurface(s->surface);
s->surface = NULL;
}
return s;
}
/*
* Allocates a new sprite.
* If the sprite exists already, it's surface will be removed, so
* that one can safely expect to get an *empty* sprite.
*
* If the bank does not exist, or if the operation failed for
* other reasons, this call returns NULL.
*/
s_sprite_t *s_new_sprite(s_container_t *c, unsigned bank, unsigned frame)
{
if(bank > c->max)
return NULL;
if(!c->banks[bank])
return NULL;
return s_new_sprite_b(c->banks[bank], frame);
}
void s_delete_sprite_b(s_bank_t *b, unsigned frame)
{
if(!b->sprites) /* This can happen when failing to create a new bank */
return;
if(frame > b->max)
return;
if(!b->sprites[frame])
return;
if(b->sprites[frame]->surface)
SDL_FreeSurface(b->sprites[frame]->surface);
b->sprites[frame]->surface = NULL;
free(b->sprites[frame]);
b->sprites[frame] = NULL;
}
void s_delete_sprite(s_container_t *c, unsigned bank, unsigned frame)
{
s_bank_t *b = s_get_bank(c, bank);
if(b)
s_delete_sprite_b(b, frame);
}
static int __alloc_sprite_table(s_bank_t *b, unsigned frames)
{
b->max = frames - 1;
b->sprites = (s_sprite_t **)calloc(frames, sizeof(s_sprite_t *));
if(!b->sprites)
return -1;
return 0;
}
s_bank_t *s_new_bank(s_container_t *c, unsigned bank, unsigned frames,
unsigned w, unsigned h)
{
s_bank_t *b;
DBG(log_printf(DLOG, "s_new_bank(%p, %d, %d, %d, %d)\n",
c, bank, frames, w, h);)
if(bank > c->max)
return NULL;
if(c->banks[bank])
s_delete_bank(c, bank);
c->banks[bank] = (s_bank_t *)calloc(1, sizeof(s_bank_t));
if(!c->banks[bank])
return NULL;
if(__alloc_sprite_table(c->banks[bank], frames) < 0)
{
s_delete_bank(c, bank);
return NULL;
}
b = c->banks[bank];
b->max = frames - 1;
b->w = w;
b->h = h;
return b;
}
void s_delete_bank(s_container_t *c, unsigned bank)
{
unsigned i;
s_bank_t *b;
if(!c->banks)
return;
if(bank > c->max)
return;
b = c->banks[bank];
if(b)
{
for(i = 0; i <= b->max; ++i)
s_delete_sprite_b(b, i);
free(b->sprites);
free(b);
c->banks[bank] = NULL;
}
}
void s_delete_all_banks(s_container_t *c)
{
unsigned i;
for(i = 0; i <= c->max; ++i)
s_delete_bank(c, i);
}
/*
----------------------------------------------------------------------
Getting data
----------------------------------------------------------------------
*/
s_bank_t *s_get_bank(s_container_t *c, unsigned bank)
{
if(bank > c->max)
return NULL;
if(!c->banks[bank])
return NULL;
return c->banks[bank];
}
s_sprite_t *s_get_sprite(s_container_t *c, unsigned bank, unsigned frame)
{
s_bank_t *b = s_get_bank(c, bank);
if(!b)
return NULL;
if(frame > b->max)
return NULL;
return b->sprites[frame];
}
s_sprite_t *s_get_sprite_b(s_bank_t *b, unsigned frame)
{
if(!b)
return NULL;
if(frame > b->max)
return NULL;
return b->sprites[frame];
}
void s_detach_sprite(s_container_t *c, unsigned bank, unsigned frame)
{
s_sprite_t *s = s_get_sprite(c, bank, frame);
if(!s)
return;
s->surface = NULL;
}
/*
----------------------------------------------------------------------
File tools
----------------------------------------------------------------------
*/
static int extract_sprite(s_bank_t *bank, unsigned frame,
SDL_Surface *src, SDL_Rect *from)
{
int y;
SDL_Surface *tmp;
if(frame > bank->max)
{
log_printf(ELOG, "sprite: Too many frames!\n");
return -1;
}
if(!s_new_sprite_b(bank, frame))
return -2;
tmp = SDL_CreateRGBSurface(src->flags,
from->w, from->h,
src->format->BitsPerPixel,
src->format->Rmask,
src->format->Gmask,
src->format->Bmask,
src->format->Amask );
if(!tmp)
return -3;
/* Copy the pixel data */
if(SDL_LockSurface(src) < 0)
{
log_printf(ELOG, "sprite: extract_sprite() failed to lock surface!\n");
SDL_FreeSurface(tmp);
return -4;
}
for(y = 0; y < tmp->h; ++y)
{
char *s = (char *)src->pixels + src->pitch * (y + from->y) +
from->x * src->format->BytesPerPixel;
char *d = (char *)tmp->pixels + tmp->pitch * y;
memcpy(d, s, src->format->BytesPerPixel * tmp->w);
}
SDL_UnlockSurface(src);
/* Copy palette, if any */
if(src->format->palette)
SDL_SetColors(tmp, src->format->palette->colors, 0,
src->format->palette->ncolors);
/* Copy alpha and colorkey */
if(src->flags & SDL_SRCALPHA)
SDL_SetAlpha(tmp, src->flags & (SDL_SRCALPHA | SDL_RLEACCEL),
src->format->alpha);
if(src->flags & SDL_SRCCOLORKEY)
SDL_SetColorKey(tmp, src->flags & (SDL_SRCCOLORKEY | SDL_RLEACCEL),
src->format->colorkey);
bank->sprites[frame]->surface = tmp;
DBG(log_printf(DLOG, "image %d: (%d,%d)/%dx%d @ %p\n", frame,
from->x, from->y, from->w, from->h,
bank->sprites[frame]->surface);)
return 0;
}
int s_copy_rect(s_container_t *c, unsigned bank,
unsigned frombank, unsigned fromframe, SDL_Rect *from)
{
#if 0
s_filter_args_t args;
#endif
SDL_Surface *src;
s_sprite_t *s;
s_bank_t *b;
/* Get source image */
s = s_get_sprite(c, frombank, fromframe);
if(!s)
{
log_printf(ELOG, "sprite: Couldn't get source sprite %d:%d!\n",
frombank, fromframe);
return -1;
}
src = s->surface;
if(!src)
{
log_printf(ELOG, "sprite: Sprite %d:%d"
" does not have a surface!\n",
frombank, fromframe);
return -2;
}
/* Create destination bank */
b = s_new_bank(c, bank, 1, from->w, from->h);
if(!b)
{
log_printf(ELOG, "sprite: Failed to allocate bank %d!\n", bank);
return -3;
}
if(extract_sprite(b, 0, src, from) < 0)
{
log_printf(ELOG, "sprite: Something went wrong while "
"copying from sprite %d:%d.\n",
frombank, fromframe);
return -4;
}
#if 0
memset(&args, 0, sizeof(args));
args.x = 0;
s_filter_displayformat(b, 0, 1, &args);
#endif
return 0;
}
int s_load_image(s_container_t *c, unsigned bank, const char *name)
{
SDL_Surface *src;
s_bank_t *b;
src = IMG_Load(name);
if(!src)
{
log_printf(ELOG, "sprite: Failed to load sprite \"%s\"!\n", name);
return -1;
}
b = s_new_bank(c, bank, 1, src->clip_rect.w, src->clip_rect.h);
if(!b)
{
log_printf(ELOG, "sprite: Failed to allocate bank for \"%s\"!\n", name);
return -2;
}
if(extract_sprite(b, 0, src, &src->clip_rect) < 0)
{
log_printf(ELOG, "sprite: Something went wrong while"
" extracting sprite \"%s\".\n", name);
return -3;
}
SDL_FreeSurface(src);
__run_plugins(b, 0, 1);
return 0;
}
int s_load_sprite(s_container_t *c, unsigned bank, unsigned frame,
const char *name)
{
SDL_Surface *src;
SDL_Rect from;
s_bank_t *b = s_get_bank(c, bank);
if(!b)
{
log_printf(ELOG, "sprite: While loading \"%s\":"
" Bank %d does not exist!\n", name, bank);
return -2;
}
src = IMG_Load(name);
if(!src)
{
log_printf(ELOG, "sprite: Failed to load sprite \"%s\"!\n", name);
return -1;
}
from = src->clip_rect;
if( (from.w != b->w) || (from.h != b->h) )
{
log_printf(ELOG, "sprite: Warning: Sprite \"%s\" cropped"
" to fit in bank %d.\n", name, bank);
from.w = b->w;
from.h = b->h;
}
if(extract_sprite(b, frame, src, &from) < 0)
{
log_printf(ELOG, "sprite: Something went wrong while"
" extracting sprite \"%s\".\n", name);
return -3;
}
SDL_FreeSurface(src);
__run_plugins(b, frame, 1);
return 0;
}
int s_load_bank(s_container_t *c, unsigned bank, unsigned w, unsigned h,
const char *name)
{
SDL_Surface *src;
s_bank_t *b;
int x, y;
unsigned frame = 0;
unsigned frames;
DBG(log_printf(DLOG, "s_load_bank(%p, %d, %d, %d, %s)\n",
c, bank, w, h, name);)
src = IMG_Load(name);
if(!src)
{
log_printf(ELOG, "sprite: Failed to load sprite palette %s!\n", name);
return -1;
}
if(w > src->w)
{
log_printf(ELOG, "sprite: Source image %s not wide enough!\n", name);
return -4;
}
if(h > src->h)
{
log_printf(ELOG, "sprite: Source image %s not high enough!\n", name);
return -5;
}
frames = (src->w / w) * (src->h / h);
b = s_new_bank(c, bank, frames, w, h);
if(!b)
{
log_printf(ELOG, "sprite: Failed to allocate bank for \"%s\"!\n", name);
return -2;
}
for(y = 0; y <= src->h - h; y += h)
for(x = 0; x <= src->w - w; x += w)
{
SDL_Rect r;
r.x = x;
r.y = y;
r.w = w;
r.h = h;
if(extract_sprite(b, frame, src, &r) < 0)
{
log_printf(ELOG, "sprite: Something went wrong while"
" extracting sprites.\n");
return -3;
}
++frame;
}
SDL_FreeSurface(src);
__run_plugins(b, 0, frames);
return 0;
}
int s_set_hotspot(s_container_t *c, unsigned bank, int frame, int x, int y)
{
if(frame < 0)
{
frame = 0;
while(1)
{
s_sprite_t *s = s_get_sprite(c, bank, frame++);
if(!s)
break;
s->x = x;
s->y = y;
}
}
else
{
s_sprite_t *s = s_get_sprite(c, bank, frame);
s->x = x;
s->y = y;
}
return 0;
}