Completes phases 3-6 of the migration plan. After this commit, the
codebase compiles and links against pure SDL 3 (no compat shim). The
binary is unverified at runtime but the build is clean.
Renderer (graphics/gfxengine.{cpp,h}):
- Replace SDL_SetVideoMode + SDL_Flip + page-flip machinery with
SDL_CreateWindow + SDL_CreateRenderer + a streaming SDL_Texture.
The CPU-side back buffer (softbuf) is now always ARGB8888; legacy
HW/double-buffer/page flags are dead.
- flip() does a single full-frame SDL_UpdateTexture + SDL_RenderClear
+ SDL_RenderTexture + SDL_RenderPresent each frame. The dirtyrect
iteration is retained only to drive widget phys_refresh() callbacks.
- SDL_SetRenderLogicalPresentation gives us letterbox scaling for free.
- SDL_SetRenderVSync replaces glSDL_VSync.
- title()/cursor() ported to SDL 3 window/cursor APIs.
Surface API (graphics/sprite.c, filters.c, window.cpp, sofont.cpp,
region.c):
- SDL_FreeSurface → SDL_DestroySurface
- SDL_FillRect → SDL_FillSurfaceRect
- SDL_SetClipRect → SDL_SetSurfaceClipRect
- SDL_SetColorKey(s, flags, key) → SDL_SetSurfaceColorKey(s, bool, key)
- SDL_SetAlpha(s, flags, a) → SDL_SetSurfaceAlphaMod + BlendMode
- SDL_SetColors → SDL_SetSurfacePalette
- SDL_CreateRGBSurface(...) → SDL_CreateSurface(w, h, format_enum)
- SDL_DisplayFormat[Alpha] → SDL_ConvertSurface(s, ARGB8888)
- SDL_MapRGB(surf->format, ...) → SDL_MapSurfaceRGB(surf, ...)
- surface->format->Field → SDL_GetPixelFormatDetails(surf->format)->field
- surface->format->Amask test → SDL_ISPIXELFORMAT_ALPHA
- surface->clip_rect → SDL_GetSurfaceClipRect
Events (kobo.cpp):
- SDL_{KEYDOWN,KEYUP,QUIT,VIDEOEXPOSE,MOUSE*,JOY*} → SDL_EVENT_*
- SDL_ACTIVEEVENT (with gain check) → SDL_EVENT_WINDOW_FOCUS_LOST
- ev.key.keysym.sym → ev.key.key (.unicode → text input, deferred)
- SDLK_PRINT/SYSREQ → SDLK_PRINTSCREEN; lowercase SDLK_p/y/n → uppercase
- ev.motion.x/y for button events → ev.button.x/y (struct layout differs)
- KMOD_META → SDL_KMOD_GUI
- SDL_INIT_NOPARACHUTE → SDL_Init(0) (no parachute by default in SDL 3)
Keymap (gamectl.{cpp,h}):
- SDLKey → SDL_Keycode
- SDLK_KP{1..9} → SDLK_KP_{1..9}
Stubs deferred for later phases (binary will run but with degraded
behavior):
- sound/audio.c: _start_SDL_output returns -1 immediately; SDL 1.2
audio callback model needs full rewrite onto SDL_AudioStream.
- kobo.cpp init_js/close_js: SDL 3 joystick API uses instance IDs;
re-port deferred.
- states.cpp SDL_WM_GrabInput/SDL_EnableUNICODE: need window pointer
exposed from gfxengine; stubbed.
- gamectl.cpp SDL_EnableKeyRepeat: removed; relies on ev.key.repeat.
Other adjustments:
- options.cpp: GFX_DRIVER_GLSDL menu paths removed; show vsync option
always (renderer handles it).
- sound/a_wave.c: AUDIO_S8/AUDIO_S16SYS → SDL_AUDIO_S8/S16, SDL_FreeWAV
→ SDL_free.
- gfxengine.cpp: SDL_putenv("SDL_VIDEO_CENTERED=1") obsolete (handled
by SDL_CreateWindow defaults).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1355 lines
25 KiB
C++
1355 lines
25 KiB
C++
/*(LGPL)
|
|
----------------------------------------------------------------------
|
|
gfxengine.cpp - Graphics Engine
|
|
----------------------------------------------------------------------
|
|
* Copyright (C) 2001-2003, 2006-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.
|
|
*/
|
|
|
|
#define DBG(x) x
|
|
|
|
#include "logger.h"
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "gfxengine.h"
|
|
#include "filters.h"
|
|
#include <SDL3_image/SDL_image.h>
|
|
#include "sdl_compat.h"
|
|
#include "sofont.h"
|
|
#include "window.h"
|
|
|
|
gfxengine_t *gfxengine;
|
|
|
|
|
|
gfxengine_t::gfxengine_t()
|
|
{
|
|
gfxengine = this; /* Uurgh! Kludge. */
|
|
|
|
sdl_window = NULL;
|
|
sdl_renderer = NULL;
|
|
fb_texture = NULL;
|
|
screen_surface = NULL;
|
|
softbuf = NULL;
|
|
fullwin = NULL;
|
|
window = NULL;
|
|
windows = NULL;
|
|
wx = wy = 0;
|
|
xs = ys = 256; // 1.0
|
|
sxs = sys = 256; // 1.0
|
|
sf1 = df = dsf = acf = NULL;
|
|
gfx = NULL;
|
|
csengine = NULL;
|
|
_driver = GFX_DRIVER_SDL2D;
|
|
_shadow = 1;
|
|
_doublebuf = 1;
|
|
_pages = -1;
|
|
_vsync = 1;
|
|
_fullscreen = 0;
|
|
_centered = 0;
|
|
use_interpolation = 1;
|
|
_depth = 0;
|
|
_title = "GfxEngine v0.4";
|
|
_icontitle = "GfxEngine";
|
|
_cursor = 1;
|
|
_width = 320;
|
|
_height = 240;
|
|
_autoinvalidate = 1;
|
|
_scalemode = GFX_SCALE_NEAREST;
|
|
_clamping = 0;
|
|
xflags = 0;
|
|
|
|
_dither = 0;
|
|
_dither_type = 0;
|
|
broken_rgba8 = 0;
|
|
alpha_threshold = 0;
|
|
|
|
_brightness = 1.0;
|
|
_contrast = 1.0;
|
|
|
|
last_tick = -1000000;
|
|
ticks_per_frame = 1000.0/60.0;
|
|
|
|
is_running = 0;
|
|
is_showing = 0;
|
|
is_open = 0;
|
|
|
|
memset(fonts, 0, sizeof(fonts));
|
|
|
|
xscroll = yscroll = 0;
|
|
for(int i = 0; i < CS_LAYERS ; ++i)
|
|
xratio[i] = yratio[i] = 0.0;
|
|
|
|
dirtyrects[0] = 0;
|
|
dirtyrects[1] = 0;
|
|
frontpage = 0;
|
|
backpage = 1;
|
|
screenshot_count = 0;
|
|
}
|
|
|
|
|
|
gfxengine_t::~gfxengine_t()
|
|
{
|
|
stop();
|
|
hide();
|
|
close();
|
|
window_t *w = windows;
|
|
while(w)
|
|
{
|
|
w->engine = NULL;
|
|
w = w->next;
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::output(window_t *outwin)
|
|
{
|
|
window = outwin;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Initialization
|
|
----------------------------------------------------------*/
|
|
|
|
void gfxengine_t::size(int w, int h)
|
|
{
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_width = w;
|
|
_height = h;
|
|
if(csengine)
|
|
cs_engine_set_size(csengine, w, h);
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::centered(int c)
|
|
{
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_centered = c;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::scale(float x, float y)
|
|
{
|
|
xs = (int)(x * 256.f);
|
|
ys = (int)(y * 256.f);
|
|
log_printf(DLOG, "gfxengine: Setting scale to %d:256 x %d:256.\n", xs, ys);
|
|
}
|
|
|
|
void gfxengine_t::mode(int bits, int fullscreen)
|
|
{
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
int olddepth = _depth;
|
|
_depth = bits;
|
|
if(_depth != olddepth)
|
|
reload();
|
|
_fullscreen = fullscreen;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::driver(gfx_drivers_t drv)
|
|
{
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_driver = drv;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::doublebuffer(int use)
|
|
{
|
|
if(_doublebuf == use)
|
|
return;
|
|
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_doublebuf = use;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::pages(int np)
|
|
{
|
|
_pages = np;
|
|
}
|
|
|
|
void gfxengine_t::vsync(int use)
|
|
{
|
|
if(_vsync == use)
|
|
return;
|
|
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_vsync = use;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::shadow(int use)
|
|
{
|
|
if(_shadow == use)
|
|
return;
|
|
|
|
int was_showing = is_showing;
|
|
hide();
|
|
|
|
_shadow = use;
|
|
|
|
if(was_showing)
|
|
show();
|
|
}
|
|
|
|
void gfxengine_t::autoinvalidate(int use)
|
|
{
|
|
_autoinvalidate = use;
|
|
}
|
|
|
|
void gfxengine_t::period(float frameduration)
|
|
{
|
|
if(frameduration > 0)
|
|
{
|
|
ticks_per_frame = frameduration;
|
|
}
|
|
else
|
|
log_printf(DLOG, "gfxengine: Time line reset.\n");
|
|
if(csengine)
|
|
cs_engine_advance(csengine, 0);
|
|
}
|
|
|
|
void gfxengine_t::wrap(int x, int y)
|
|
{
|
|
wx = x;
|
|
wy = y;
|
|
if(csengine)
|
|
cs_engine_set_wrap(csengine, x, y);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Data management
|
|
----------------------------------------------------------*/
|
|
|
|
void gfxengine_t::reset_filters()
|
|
{
|
|
/* Remove all filters */
|
|
s_remove_filter(NULL);
|
|
sf1 = df = dsf = acf = NULL;
|
|
|
|
/* Set up filter pipeline */
|
|
s_remove_filter(NULL);
|
|
|
|
s_add_filter(s_filter_rgba8);
|
|
#if 0
|
|
s_filter_t *fi;
|
|
fi = s_add_filter(s_filter_key2alpha);
|
|
fi->args.max = 1;
|
|
#endif
|
|
sf1 = s_add_filter(s_filter_scale);
|
|
sf2 = s_add_filter(s_filter_scale);
|
|
acf = s_add_filter(s_filter_cleanalpha);
|
|
bcf = s_add_filter(s_filter_brightness);
|
|
df = s_add_filter(s_filter_dither);
|
|
dsf = s_add_filter(s_filter_displayformat);
|
|
|
|
/* Set default parameters */
|
|
// colorkey(0, 0, 0);
|
|
clampcolor(0, 0, 0, 0);
|
|
scalemode(GFX_SCALE_NEAREST);
|
|
dither(0, 0);
|
|
noalpha(0);
|
|
brightness(1.0, 1.0);
|
|
filterflags(0);
|
|
}
|
|
|
|
|
|
void gfxengine_t::filterflags(int fgs)
|
|
{
|
|
s_filter_flags = fgs;
|
|
}
|
|
|
|
|
|
void gfxengine_t::scalemode(gfx_scalemodes_t sm, int clamping)
|
|
{
|
|
_scalemode = sm;
|
|
_clamping = clamping;
|
|
if(!sf1 || !df)
|
|
return;
|
|
|
|
int rxs = (xs * 256 + 128) / sxs;
|
|
int rys = (ys * 256 + 128) / sys;
|
|
#if 1
|
|
// Filter 2 is off in most cases
|
|
sf2->args.x = 0;
|
|
sf2->args.fx = 0.0f;
|
|
sf2->args.fy = 0.0f;
|
|
#endif
|
|
if((rxs == 256) && (rys == 256))
|
|
{
|
|
sf1->args.x = SF_SCALE_NEAREST;
|
|
sf1->args.fx = 1.0f;
|
|
sf1->args.fy = 1.0f;
|
|
return;
|
|
}
|
|
|
|
switch(clamping)
|
|
{
|
|
default:
|
|
case 0:
|
|
filterflags(0);
|
|
break;
|
|
case 1:
|
|
filterflags(SF_CLAMP_EXTEND);
|
|
break;
|
|
case 2:
|
|
filterflags(SF_CLAMP_SFONT);
|
|
break;
|
|
}
|
|
|
|
sf1->args.fx = rxs * (1.0f/256.0f);
|
|
sf1->args.fy = rys * (1.0f/256.0f);
|
|
|
|
switch(_scalemode)
|
|
{
|
|
case GFX_SCALE_NEAREST: //Nearest
|
|
sf1->args.x = SF_SCALE_NEAREST;
|
|
break;
|
|
case GFX_SCALE_BILINEAR: //Bilinear
|
|
sf1->args.x = SF_SCALE_BILINEAR;
|
|
break;
|
|
case GFX_SCALE_BILIN_OVER: //Bilinear + Oversampling
|
|
#if 0
|
|
sf1->args.x = SF_SCALE_BILINEAR;
|
|
sf2->args.x = SF_SCALE_BILINEAR;
|
|
sf1->args.fx = rxs * (1.5f/256.f);
|
|
sf1->args.fy = rys * (1.5f/256.f);
|
|
sf2->args.fx = 1.0f/1.5f;
|
|
sf2->args.fy = 1.0f/1.5f;
|
|
break;
|
|
/* Hack for better bilinear [+ oversampling] scaling... */
|
|
switch(clamping)
|
|
{
|
|
case 2:
|
|
sf1->args.x = SF_SCALE_NEAREST;
|
|
sf2->args.x = SF_SCALE_BILINEAR;
|
|
switch((rxs + 128) / 256)
|
|
{
|
|
case 2:
|
|
sf1->args.fx = 4.0f;
|
|
sf1->args.fy = 4.0f;
|
|
sf2->args.fx = 0.5f;
|
|
sf2->args.fy = 0.5f;
|
|
break;
|
|
case 3:
|
|
sf1->args.fx = 2.0f;
|
|
sf1->args.fy = 2.0f;
|
|
sf2->args.fx = 1.5f;
|
|
sf2->args.fy = 1.5f;
|
|
break;
|
|
case 4:
|
|
sf1->args.fx = 2.0f;
|
|
sf1->args.fy = 2.0f;
|
|
sf2->args.fx = 2.0f;
|
|
sf2->args.fy = 2.0f;
|
|
break;
|
|
default:
|
|
sf1->args.fx = rxs * (2.f/256.f);
|
|
sf1->args.fy = rys * (2.f/256.f);
|
|
sf2->args.fx = 0.5f;
|
|
sf2->args.fy = 0.5f;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
sf1->args.x = SF_SCALE_BILINEAR;
|
|
break;
|
|
}
|
|
#endif
|
|
sf1->args.x = SF_SCALE_BILINEAR;
|
|
break;
|
|
case GFX_SCALE_SCALE2X: //Scale2x
|
|
sf1->args.x = SF_SCALE_SCALE2X;
|
|
break;
|
|
case GFX_SCALE_DIAMOND: //Diamond2x
|
|
sf1->args.x = SF_SCALE_DIAMOND;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::source_scale(float x, float y)
|
|
{
|
|
sxs = (int)(x * 256.f);
|
|
sys = (int)(y * 256.f);
|
|
scalemode(_scalemode, _clamping);
|
|
}
|
|
|
|
#if 0
|
|
void gfxengine_t::colorkey(Uint8 r, Uint8 g, Uint8 b)
|
|
{
|
|
s_colorkey.r = r;
|
|
s_colorkey.g = g;
|
|
s_colorkey.b = b;
|
|
}
|
|
#endif
|
|
|
|
void gfxengine_t::clampcolor(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
|
|
{
|
|
s_clampcolor.r = r;
|
|
s_clampcolor.g = g;
|
|
s_clampcolor.b = b;
|
|
s_clampcolor.a = b;
|
|
}
|
|
|
|
|
|
void gfxengine_t::dither(int type, int _broken_rgba8)
|
|
{
|
|
_dither = type >= 0;
|
|
_dither_type = type;
|
|
broken_rgba8 = _broken_rgba8;
|
|
|
|
if(!df)
|
|
return;
|
|
if(!screen_surface)
|
|
return;
|
|
|
|
if(_dither)
|
|
{
|
|
// SDL 3: back buffer is always ARGB8888 (8 bits per channel,
|
|
// no loss). The legacy formula was 1 << (Rloss - 1); for 8 bpc
|
|
// the dithering amplitude collapses to 0.
|
|
df->args.x = 0;
|
|
df->args.r = 0;
|
|
df->args.g = 0;
|
|
df->args.b = 0;
|
|
}
|
|
else
|
|
df->args.x = df->args.r = df->args.g = df->args.b = 0;
|
|
df->args.y = type;
|
|
}
|
|
|
|
|
|
void gfxengine_t::noalpha(int threshold)
|
|
{
|
|
alpha_threshold = threshold;
|
|
if(!dsf || !acf)
|
|
return;
|
|
|
|
if(threshold > 0)
|
|
{
|
|
acf->args.min = 128;
|
|
acf->args.max = 128;
|
|
acf->args.fx = 1.0;
|
|
acf->args.x = threshold;
|
|
|
|
dsf->args.x = 1;
|
|
}
|
|
else
|
|
{
|
|
acf->args.min = 16;
|
|
acf->args.max = 255-16;
|
|
acf->args.fx = 1.3;
|
|
acf->args.x = -16;
|
|
|
|
dsf->args.x = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::brightness(float bright, float contr)
|
|
{
|
|
_brightness = bright;
|
|
_contrast = contr;
|
|
if(!bcf)
|
|
return;
|
|
|
|
bcf->args.fx = _brightness;
|
|
bcf->args.fy = _contrast;
|
|
bcf->args.min = 0;
|
|
bcf->args.max = 255;
|
|
}
|
|
|
|
|
|
int gfxengine_t::loadimage(int bank, const char *name)
|
|
{
|
|
if(!csengine)
|
|
{
|
|
log_printf(ELOG, "loadimage: Engine must be open!\n");
|
|
return -10;
|
|
}
|
|
s_blitmode = S_BLITMODE_AUTO;
|
|
log_printf(DLOG, "Loading image %s (bank %d)...\n", name, bank);
|
|
if(s_load_image(gfx, bank, name))
|
|
{
|
|
log_printf(ELOG, " Failed to load %s!\n", name);
|
|
return -1;
|
|
}
|
|
|
|
s_bank_t *b = s_get_bank(gfx, bank);
|
|
if(!b)
|
|
{
|
|
log_printf(ELOG, " gfxengine: Internal error 1!\n");
|
|
return -15;
|
|
}
|
|
cs_engine_set_image_size(csengine, bank, b->w, b->h);
|
|
|
|
log_printf(DLOG, " Ok.\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int gfxengine_t::loadtiles(int bank, int w, int h, const char *name)
|
|
{
|
|
if(!csengine)
|
|
{
|
|
log_printf(ELOG, "loadtiles: Engine must be open!\n");
|
|
return -10;
|
|
}
|
|
s_blitmode = S_BLITMODE_AUTO;
|
|
log_printf(DLOG, "Loading tiles %s (bank %d; %dx%d)...\n",
|
|
name, bank, w, h);
|
|
if(s_load_bank(gfx, bank, w, h, name))
|
|
{
|
|
log_printf(ELOG, " Failed to load %s!\n", name);
|
|
return -2;
|
|
}
|
|
|
|
cs_engine_set_image_size(csengine, bank, w, h);
|
|
|
|
log_printf(DLOG, " Ok. (%d frames)\n", s_get_bank(gfx, bank)->max+1);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int gfxengine_t::loadfont(int bank, const char *name)
|
|
{
|
|
if(!csengine)
|
|
{
|
|
log_printf(ELOG, "loadfont: Engine must be open!\n");
|
|
return -10;
|
|
}
|
|
s_blitmode = S_BLITMODE_AUTO;
|
|
scalemode(_scalemode, 2);
|
|
log_printf(DLOG, "Loading font %s (bank %d)...\n", name, bank);
|
|
if(s_load_image(gfx, bank, name))
|
|
{
|
|
log_printf(ELOG, " Failed to load %s!\n", name);
|
|
return -2;
|
|
}
|
|
if(bank >= GFX_BANKS)
|
|
{
|
|
log_printf(ELOG, " Too high bank #!\n");
|
|
return -3;
|
|
}
|
|
|
|
if(!fonts[bank])
|
|
fonts[bank] = new SoFont;
|
|
if(!fonts[bank])
|
|
{
|
|
log_printf(ELOG, " Failed to instantiate SoFont!\n");
|
|
return -4;
|
|
}
|
|
|
|
fonts[bank]->ExtraSpace((xs + 127) / 256);
|
|
if(fonts[bank]->load(s_get_sprite(gfx, bank, 0)->surface))
|
|
{
|
|
s_detach_sprite(gfx, bank, 0);
|
|
log_printf(DLOG, " Ok.\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
log_printf(ELOG, " SoFont::load() failed!\n");
|
|
return -5;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int gfxengine_t::copyrect(int bank, int sbank, int sframe, SDL_Rect *r)
|
|
{
|
|
SDL_Rect sr = *r;
|
|
if(!csengine)
|
|
{
|
|
log_printf(ELOG, "copyrect: Engine must be open!\n");
|
|
return -10;
|
|
}
|
|
log_printf(DLOG, "Copying rect from %d:%d (bank %d)...\n",
|
|
sbank, sframe, bank);
|
|
int x2 = (int)((sr.x + sr.w) * xs + 128) >> 8;
|
|
int y2 = (int)((sr.y + sr.h) * ys + 128) >> 8;
|
|
sr.x = (int)(sr.x * xs + 128) >> 8;
|
|
sr.y = (int)(sr.y * ys + 128) >> 8;
|
|
sr.w = x2 - sr.x;
|
|
sr.h = y2 - sr.y;
|
|
if(s_copy_rect(gfx, bank, sbank, sframe, &sr) < 0)
|
|
{
|
|
log_printf(ELOG, " s_copy_rect() failed!\n");
|
|
return -1;
|
|
}
|
|
log_printf(DLOG, " Ok.\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int gfxengine_t::is_loaded(int bank)
|
|
{
|
|
if(!csengine)
|
|
return 0;
|
|
if(!s_get_bank(gfx, (unsigned)bank))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
void gfxengine_t::reload()
|
|
{
|
|
log_printf(DLOG, "Reloading all banks. (Not implemented!)\n");
|
|
// s_reload_all_banks(gfx);
|
|
}
|
|
|
|
|
|
void gfxengine_t::unload(int bank)
|
|
{
|
|
if(bank < 0)
|
|
{
|
|
log_printf(DLOG, "Unloading all banks.\n");
|
|
for(int i = 0; i < GFX_BANKS; ++i)
|
|
{
|
|
delete fonts[i];
|
|
fonts[i] = NULL;
|
|
}
|
|
if(gfx)
|
|
s_delete_all_banks(gfx);
|
|
}
|
|
else
|
|
{
|
|
log_printf(DLOG, "Unloading bank %d.\n", bank);
|
|
if(bank < GFX_BANKS)
|
|
{
|
|
delete fonts[bank];
|
|
fonts[bank] = NULL;
|
|
}
|
|
if(gfx)
|
|
s_delete_bank(gfx, bank);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine open/close
|
|
----------------------------------------------------------*/
|
|
|
|
void gfxengine_t::on_frame(cs_engine_t *e)
|
|
{
|
|
gfxengine->__frame();
|
|
gfxengine->frame();
|
|
}
|
|
|
|
|
|
int gfxengine_t::open(int objects, int extraflags)
|
|
{
|
|
xflags = extraflags;
|
|
|
|
if(is_open)
|
|
return show();
|
|
|
|
log_printf(DLOG, "Opening engine...\n");
|
|
csengine = cs_engine_create(_width, _height, objects);
|
|
if(!csengine)
|
|
{
|
|
log_printf(ELOG, "Failed to set up control system engine!\n");
|
|
return -1;
|
|
}
|
|
|
|
csengine->on_frame = on_frame;
|
|
cs_engine_set_wrap(csengine, wx, wy);
|
|
|
|
gfx = s_new_container(GFX_BANKS);
|
|
if(!gfx)
|
|
{
|
|
log_printf(ELOG, "Failed to set up graphics container!\n");
|
|
cs_engine_delete(csengine);
|
|
return -2;
|
|
}
|
|
|
|
reset_filters();
|
|
|
|
is_open = 1;
|
|
return show();
|
|
}
|
|
|
|
|
|
void gfxengine_t::close()
|
|
{
|
|
if(!is_open)
|
|
return;
|
|
|
|
log_printf(DLOG ,"Closing engine...\n");
|
|
stop();
|
|
hide();
|
|
unload();
|
|
cs_engine_delete(csengine);
|
|
csengine = NULL;
|
|
s_remove_filter(NULL);
|
|
sf1 = df = dsf = acf = NULL;
|
|
s_delete_container(gfx);
|
|
gfx = NULL;
|
|
is_open = 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Settings
|
|
----------------------------------------------------------*/
|
|
|
|
void gfxengine_t::title(const char *win, const char *icon)
|
|
{
|
|
_title = win;
|
|
_icontitle = icon;
|
|
if(sdl_window)
|
|
SDL_SetWindowTitle(sdl_window, _title);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Display show/hide
|
|
----------------------------------------------------------*/
|
|
|
|
int gfxengine_t::show()
|
|
{
|
|
if(!is_open)
|
|
return -1;
|
|
|
|
if(is_showing)
|
|
return 0;
|
|
|
|
log_printf(DLOG, "Opening screen...\n");
|
|
if(!SDL_WasInit(SDL_INIT_VIDEO))
|
|
if(!SDL_InitSubSystem(SDL_INIT_VIDEO))
|
|
{
|
|
log_printf(ELOG, "Failed to initialize SDL: %s\n",
|
|
SDL_GetError());
|
|
return -2;
|
|
}
|
|
|
|
// SDL 3 window + renderer model.
|
|
//
|
|
// The kobo engine renders into a CPU-side back buffer (softbuf) via
|
|
// SDL_BlitSurface/SDL_FillSurfaceRect, and uploads the result to a
|
|
// streaming SDL_Texture once per frame. This preserves the entire
|
|
// software-compositing code path inherited from SDL 1.2 (window.cpp,
|
|
// sprite.c, filters.c, sofont.cpp), while letting the GPU handle
|
|
// scaling and presentation. SDL_DOUBLEBUF / SDL_HWSURFACE / page-
|
|
// flipping flags are obsolete and ignored.
|
|
Uint64 win_flags = 0;
|
|
if(_fullscreen)
|
|
win_flags |= SDL_WINDOW_FULLSCREEN;
|
|
|
|
sdl_window = SDL_CreateWindow(_title ? _title : "Kobo Deluxe",
|
|
_width, _height, win_flags);
|
|
if(!sdl_window)
|
|
{
|
|
log_printf(ELOG, "Failed to open window: %s\n", SDL_GetError());
|
|
return -3;
|
|
}
|
|
|
|
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
|
|
if(!sdl_renderer)
|
|
{
|
|
log_printf(ELOG, "Failed to create renderer: %s\n",
|
|
SDL_GetError());
|
|
SDL_DestroyWindow(sdl_window);
|
|
sdl_window = NULL;
|
|
return -4;
|
|
}
|
|
|
|
// Letterbox the back buffer when the window is resized / fullscreen.
|
|
SDL_SetRenderLogicalPresentation(sdl_renderer, _width, _height,
|
|
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
|
SDL_SetRenderVSync(sdl_renderer, _vsync ? 1 : 0);
|
|
|
|
// The back buffer is always a CPU-side ARGB8888 surface.
|
|
softbuf = SDL_CreateSurface(_width, _height, SDL_PIXELFORMAT_ARGB8888);
|
|
if(!softbuf)
|
|
{
|
|
log_printf(ELOG, "Failed to create back buffer: %s\n",
|
|
SDL_GetError());
|
|
SDL_DestroyRenderer(sdl_renderer);
|
|
SDL_DestroyWindow(sdl_window);
|
|
sdl_renderer = NULL;
|
|
sdl_window = NULL;
|
|
return -5;
|
|
}
|
|
screen_surface = softbuf; // alias for legacy code paths
|
|
|
|
fb_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888,
|
|
SDL_TEXTUREACCESS_STREAMING, _width, _height);
|
|
if(!fb_texture)
|
|
{
|
|
log_printf(ELOG, "Failed to create framebuffer texture: %s\n",
|
|
SDL_GetError());
|
|
SDL_DestroySurface(softbuf);
|
|
SDL_DestroyRenderer(sdl_renderer);
|
|
SDL_DestroyWindow(sdl_window);
|
|
softbuf = screen_surface = NULL;
|
|
sdl_renderer = NULL;
|
|
sdl_window = NULL;
|
|
return -6;
|
|
}
|
|
SDL_SetTextureScaleMode(fb_texture,
|
|
(_scalemode == GFX_SCALE_NEAREST) ?
|
|
SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
|
|
|
if(_cursor)
|
|
SDL_ShowCursor();
|
|
else
|
|
SDL_HideCursor();
|
|
|
|
cs_engine_set_size(csengine, _width, _height);
|
|
csengine->filter = use_interpolation;
|
|
|
|
dither(_dither);
|
|
noalpha(alpha_threshold);
|
|
is_showing = 1;
|
|
|
|
fullwin = new window_t;
|
|
fullwin->init(this);
|
|
fullwin->place(0, 0, (int)(_width / xscale()),
|
|
(int)(_height / yscale()));
|
|
|
|
clear();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void gfxengine_t::clear(Uint32 _color)
|
|
{
|
|
if(!fullwin)
|
|
return;
|
|
|
|
fullwin->background(fullwin->map_rgb(_color));
|
|
fullwin->clear();
|
|
flip();
|
|
fullwin->clear();
|
|
flip();
|
|
fullwin->clear();
|
|
}
|
|
|
|
|
|
void gfxengine_t::hide(void)
|
|
{
|
|
if(!is_showing)
|
|
return;
|
|
|
|
log_printf(DLOG, "Closing screen...\n");
|
|
stop();
|
|
delete fullwin;
|
|
fullwin = NULL;
|
|
|
|
// Make sure no windows keep old surface pointers!
|
|
window_t *w = windows;
|
|
while(w)
|
|
{
|
|
if((w->surface == screen_surface) ||
|
|
(w->surface == softbuf))
|
|
w->surface = NULL;
|
|
w = w->next;
|
|
}
|
|
|
|
if(fb_texture)
|
|
{
|
|
SDL_DestroyTexture(fb_texture);
|
|
fb_texture = NULL;
|
|
}
|
|
if(softbuf)
|
|
{
|
|
SDL_DestroySurface(softbuf);
|
|
softbuf = NULL;
|
|
}
|
|
screen_surface = NULL;
|
|
if(sdl_renderer)
|
|
{
|
|
SDL_DestroyRenderer(sdl_renderer);
|
|
sdl_renderer = NULL;
|
|
}
|
|
if(sdl_window)
|
|
{
|
|
SDL_DestroyWindow(sdl_window);
|
|
sdl_window = NULL;
|
|
}
|
|
|
|
is_showing = 0;
|
|
}
|
|
|
|
|
|
void gfxengine_t::invalidate(SDL_Rect *rect, window_t *window)
|
|
{
|
|
switch(_pages)
|
|
{
|
|
case -1:
|
|
if(_doublebuf)
|
|
__invalidate(1, rect, window);
|
|
__invalidate(0, rect, window);
|
|
break;
|
|
case 0:
|
|
__invalidate(0, NULL, NULL);
|
|
break;
|
|
case 3:
|
|
__invalidate(2, rect, window);
|
|
// Fallthrough!
|
|
case 2:
|
|
__invalidate(1, rect, window);
|
|
// Fallthrough!
|
|
case 1:
|
|
__invalidate(0, rect, window);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::__invalidate(int page, SDL_Rect *rect, window_t *window)
|
|
{
|
|
if(!screen_surface)
|
|
return;
|
|
|
|
if(!rect || (_pages == 0))
|
|
{
|
|
dirtyrects[page] = 1;
|
|
dirtytable[page][0].x = 0;
|
|
dirtytable[page][0].y = 0;
|
|
dirtytable[page][0].w = screen_surface->w;
|
|
dirtytable[page][0].h = screen_surface->h;
|
|
dirtywtable[page][0] = NULL;
|
|
return;
|
|
}
|
|
|
|
/* Clip to screen (stolen from SDL_surface.c) */
|
|
SDL_Rect dr = *rect;
|
|
SDL_Rect screen_clip;
|
|
SDL_GetSurfaceClipRect(screen_surface, &screen_clip);
|
|
int Amin, Amax, Bmin, Bmax;
|
|
|
|
/* Horizontal intersection */
|
|
Amin = dr.x;
|
|
Amax = Amin + dr.w;
|
|
Bmin = screen_clip.x;
|
|
Bmax = Bmin + screen_clip.w;
|
|
if(Bmin > Amin)
|
|
Amin = Bmin;
|
|
dr.x = Amin;
|
|
if(Bmax < Amax)
|
|
Amax = Bmax;
|
|
dr.w = Amax - Amin > 0 ? Amax - Amin : 0;
|
|
|
|
/* Vertical intersection */
|
|
Amin = dr.y;
|
|
Amax = Amin + dr.h;
|
|
Bmin = screen_clip.y;
|
|
Bmax = Bmin + screen_clip.h;
|
|
if(Bmin > Amin)
|
|
Amin = Bmin;
|
|
dr.y = Amin;
|
|
if(Bmax < Amax)
|
|
Amax = Bmax;
|
|
dr.h = Amax - Amin > 0 ? Amax - Amin : 0;
|
|
|
|
if(!dr.w || !dr.h)
|
|
return;
|
|
|
|
for(int i = 0; i < dirtyrects[page]; ++i)
|
|
if(memcmp(&dirtytable[page][i], &dr, sizeof(dr)) == 0)
|
|
return;
|
|
|
|
if(dirtyrects[page] < MAX_DIRTYRECTS - 1)
|
|
{
|
|
dirtytable[page][dirtyrects[page]] = dr;
|
|
dirtywtable[page][dirtyrects[page]] = window;
|
|
++dirtyrects[page];
|
|
}
|
|
else
|
|
log_printf(ELOG, "gfxengine: Page %d out of dirtyrects!\n",
|
|
page);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine start/stop
|
|
----------------------------------------------------------*/
|
|
|
|
void gfxengine_t::start_engine()
|
|
{
|
|
last_tick = -1000000;
|
|
}
|
|
|
|
|
|
void gfxengine_t::stop_engine()
|
|
{
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Control
|
|
----------------------------------------------------------*/
|
|
|
|
/*
|
|
* Run engine until stop() is called.
|
|
*
|
|
* The virtual member frame() will be called once for
|
|
* each control system frame. pre_render() and
|
|
* post_render() will be called before/after the engine
|
|
* renders each video frame.
|
|
*/
|
|
void gfxengine_t::run()
|
|
{
|
|
open();
|
|
show();
|
|
start_engine();
|
|
is_running = 1;
|
|
double toframe = 0.0f;
|
|
double fdt = 1.0f;
|
|
cs_engine_advance(csengine, 0);
|
|
while(is_running)
|
|
{
|
|
int t = (int)SDL_GetTicks();
|
|
int dt = t - last_tick;
|
|
last_tick = t;
|
|
if(abs(dt) > 500)
|
|
last_tick = t;
|
|
if(dt > 250)
|
|
dt = (int)ticks_per_frame;
|
|
if(_timefilter)
|
|
fdt += (dt - fdt) * _timefilter;
|
|
else
|
|
fdt = ticks_per_frame;
|
|
toframe += fdt / ticks_per_frame;
|
|
cs_engine_advance(csengine, toframe);
|
|
pre_render();
|
|
window->select();
|
|
cs_engine_render(csengine);
|
|
post_render();
|
|
if(_autoinvalidate)
|
|
window->invalidate();
|
|
flip();
|
|
}
|
|
stop_engine();
|
|
}
|
|
|
|
|
|
void gfxengine_t::stop()
|
|
{
|
|
if(!is_running)
|
|
return;
|
|
|
|
log_printf(DLOG, "Stopping engine...\n");
|
|
is_running = 0;
|
|
}
|
|
|
|
|
|
void gfxengine_t::cursor(int csr)
|
|
{
|
|
_cursor = csr;
|
|
if(screen_surface)
|
|
{
|
|
if(csr)
|
|
SDL_ShowCursor();
|
|
else
|
|
SDL_HideCursor();
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::interpolation(int inter)
|
|
{
|
|
use_interpolation = inter;
|
|
if(!csengine)
|
|
return;
|
|
|
|
csengine->filter = use_interpolation;
|
|
}
|
|
|
|
|
|
void gfxengine_t::timefilter(float coeff)
|
|
{
|
|
if(coeff < 0.001f)
|
|
_timefilter = 0.0f;
|
|
else if(coeff > 1.0f)
|
|
_timefilter = 1.0f;
|
|
else
|
|
_timefilter = coeff;
|
|
}
|
|
|
|
|
|
void gfxengine_t::scroll_ratio(int layer, float xr, float yr)
|
|
{
|
|
if(layer < 0)
|
|
return;
|
|
if(layer >= CS_LAYERS)
|
|
return;
|
|
|
|
xratio[layer] = xr;
|
|
yratio[layer] = yr;
|
|
}
|
|
|
|
|
|
void gfxengine_t::scroll(int xscr, int yscr)
|
|
{
|
|
xscroll = xscr;
|
|
yscroll = yscr;
|
|
|
|
/* Apply current scroll pos to layers */
|
|
for(int i = 0; i < CS_LAYERS ; ++i)
|
|
{
|
|
csengine->offsets[i].v.x = (int)floor(xscroll * xratio[i]);
|
|
csengine->offsets[i].v.y = (int)floor(yscroll * yratio[i]);
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::force_scroll()
|
|
{
|
|
if(csengine)
|
|
for(int i = 0; i < CS_LAYERS ; ++i)
|
|
cs_point_force(&csengine->offsets[i]);
|
|
}
|
|
|
|
|
|
int gfxengine_t::xoffs(int layer)
|
|
{
|
|
if(layer < 0)
|
|
return 0;
|
|
if(layer >= CS_LAYERS)
|
|
return 0;
|
|
return csengine->offsets[layer].gx;
|
|
}
|
|
|
|
|
|
int gfxengine_t::yoffs(int layer)
|
|
{
|
|
if(layer < 0)
|
|
return 0;
|
|
if(layer >= CS_LAYERS)
|
|
return 0;
|
|
return csengine->offsets[layer].gy;
|
|
}
|
|
|
|
|
|
SDL_Surface *gfxengine_t::surface()
|
|
{
|
|
if(softbuf)
|
|
return softbuf;
|
|
else
|
|
return screen_surface;
|
|
}
|
|
|
|
|
|
void gfxengine_t::screenshot()
|
|
{
|
|
char filename[1024];
|
|
snprintf(filename, sizeof(filename), "screen%d.bmp", screenshot_count++);
|
|
SDL_SaveBMP(screen_surface, filename);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Internal stuff
|
|
----------------------------------------------------------*/
|
|
|
|
/* Default frame handler */
|
|
void gfxengine_t::frame()
|
|
{
|
|
SDL_Event ev;
|
|
while(SDL_PollEvent(&ev))
|
|
{
|
|
if(ev.type == SDL_EVENT_KEY_DOWN)
|
|
if(ev.key.key == SDLK_ESCAPE)
|
|
stop();
|
|
}
|
|
}
|
|
|
|
|
|
/* Internal frame handler */
|
|
void gfxengine_t::__frame()
|
|
{
|
|
}
|
|
|
|
|
|
void gfxengine_t::pre_render()
|
|
{
|
|
}
|
|
|
|
|
|
void gfxengine_t::post_render()
|
|
{
|
|
}
|
|
|
|
|
|
void gfxengine_t::refresh_rect(SDL_Rect *r)
|
|
{
|
|
window_t *w = windows;
|
|
for(w = windows; w; w = w->next)
|
|
{
|
|
if(!w->visible())
|
|
continue;
|
|
|
|
SDL_Rect dr = *r;
|
|
|
|
/* Clip to window */
|
|
int Amin, Amax, Bmin, Bmax;
|
|
|
|
/* Horizontal intersection */
|
|
Amin = dr.x;
|
|
Amax = Amin + dr.w;
|
|
Bmin = w->phys_rect.x;
|
|
Bmax = Bmin + w->phys_rect.w;
|
|
if(Bmin > Amin)
|
|
Amin = Bmin;
|
|
dr.x = Amin;
|
|
if(Bmax < Amax)
|
|
Amax = Bmax;
|
|
dr.w = Amax - Amin > 0 ? Amax - Amin : 0;
|
|
|
|
/* Vertical intersection */
|
|
Amin = dr.y;
|
|
Amax = Amin + dr.h;
|
|
Bmin = w->phys_rect.y;
|
|
Bmax = Bmin + w->phys_rect.h;
|
|
if(Bmin > Amin)
|
|
Amin = Bmin;
|
|
dr.y = Amin;
|
|
if(Bmax < Amax)
|
|
Amax = Bmax;
|
|
dr.h = Amax - Amin > 0 ? Amax - Amin : 0;
|
|
|
|
if(dr.w && dr.h)
|
|
w->phys_refresh(&dr);
|
|
}
|
|
}
|
|
|
|
|
|
void gfxengine_t::flip()
|
|
{
|
|
if(!screen_surface || !sdl_renderer || !fb_texture)
|
|
return;
|
|
|
|
// SDL 3 has no hardware page flipping; we always present one back
|
|
// buffer per frame. Keep the dirtyrect iteration only to drive the
|
|
// phys_refresh() callbacks that window widgets rely on; the upload
|
|
// is a single full-surface copy regardless.
|
|
frontpage = backpage = 0;
|
|
for(int i = 0; i < dirtyrects[backpage]; ++i)
|
|
{
|
|
if(dirtywtable[backpage][i])
|
|
{
|
|
if(!dirtywtable[backpage][i]->visible())
|
|
continue;
|
|
SDL_Rect dr = dirtytable[backpage][i];
|
|
dirtywtable[backpage][i]->phys_refresh(&dr);
|
|
}
|
|
else
|
|
refresh_rect(&dirtytable[backpage][i]);
|
|
}
|
|
dirtyrects[backpage] = 0;
|
|
|
|
SDL_UpdateTexture(fb_texture, NULL, softbuf->pixels, softbuf->pitch);
|
|
SDL_RenderClear(sdl_renderer);
|
|
SDL_RenderTexture(sdl_renderer, fb_texture, NULL, NULL);
|
|
SDL_RenderPresent(sdl_renderer);
|
|
}
|
|
|
|
|
|
/*
|
|
* Generic render() callback for sprites and tiles.
|
|
*/
|
|
void gfxengine_t::render_sprite(cs_obj_t *o)
|
|
{
|
|
SDL_Rect dest_rect;
|
|
s_sprite_t *s = gfxengine->get_sprite(o->anim.bank, o->anim.frame);
|
|
if(!s || !s->surface)
|
|
return;
|
|
|
|
int x = o->point.gx - (s->x << 8);
|
|
int y = o->point.gy - (s->y << 8);
|
|
dest_rect.x = CS2PIXEL((x * gfxengine->xs + 128) >> 8);
|
|
dest_rect.y = CS2PIXEL((y * gfxengine->ys + 128) >> 8);
|
|
dest_rect.x += (gfxengine->window->x() * gfxengine->xs + 128) >> 8;
|
|
dest_rect.y += (gfxengine->window->y() * gfxengine->xs + 128) >> 8;
|
|
SDL_BlitSurface(s->surface, NULL, gfxengine->surface(), &dest_rect);
|
|
|
|
if(!gfxengine->_autoinvalidate)
|
|
{
|
|
dest_rect.w = s->surface->w;
|
|
dest_rect.h = s->surface->h;
|
|
gfxengine->invalidate(&dest_rect);
|
|
}
|
|
}
|
|
|
|
|
|
cs_obj_t *gfxengine_t::get_obj(int layer)
|
|
{
|
|
cs_obj_t *o = cs_engine_get_obj(csengine);
|
|
if(o)
|
|
{
|
|
o->render = render_sprite;
|
|
cs_obj_layer(o, layer);
|
|
cs_obj_activate(o);
|
|
}
|
|
return o;
|
|
}
|
|
|
|
|
|
void gfxengine_t::free_obj(cs_obj_t *obj)
|
|
{
|
|
cs_obj_free(obj);
|
|
}
|
|
|
|
|
|
int gfxengine_t::objects_in_use()
|
|
{
|
|
if(!csengine)
|
|
return 0;
|
|
|
|
return csengine->pool_total - csengine->pool_free;
|
|
}
|
|
|
|
|
|
SoFont *gfxengine_t::get_font(unsigned int f)
|
|
{
|
|
if(f < GFX_BANKS)
|
|
return fonts[f];
|
|
else
|
|
return NULL;
|
|
}
|