kobodl/graphics/window.cpp
Ville Lindholm f11b6516da
WIP: port renderer + supporting layers to SDL 3 (builds clean)
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>
2026-05-28 21:54:55 +03:00

648 lines
12 KiB
C++

/*(LGPL)
---------------------------------------------------------------------------
window.cpp - Generic Rendering Window
---------------------------------------------------------------------------
* 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.
*/
#include "logger.h"
#include "window.h"
#include "gfxengine.h"
#include "sofont.h"
#define SELECT if(selected != this) _select();
window_t *window_t::selected = NULL;
window_t::window_t()
{
engine = NULL;
surface = NULL;
next = NULL;
prev = NULL;
phys_rect.x = phys_rect.y = 0;
phys_rect.w = 320;
phys_rect.h = 240;
fgcolor = bgcolor = 0;
selected = 0;
xs = ys = 256;
bg_bank = -1;
bg_frame = -1;
_visible = 1;
_offscreen = 0;
}
window_t::~window_t()
{
if(_offscreen && surface)
SDL_DestroySurface(surface);
if(selected == this)
selected = NULL;
unlink();
}
void window_t::init(gfxengine_t *e)
{
link(e);
xs = engine->xs;
ys = engine->ys;
if(_offscreen && surface)
{
SDL_DestroySurface(surface);
surface = NULL;
}
_offscreen = 0;
surface = engine->surface();
}
void window_t::visible(int vis)
{
if(_offscreen)
return; // Cannot be visible!
if(_visible == vis)
return;
_visible = vis;
invalidate();
}
void window_t::link(gfxengine_t *e)
{
unlink();
engine = e;
next = NULL;
prev = engine->windows;
if(prev)
{
while(prev->next)
prev = prev->next;
prev->next = this;
}
else
engine->windows = this;
}
void window_t::unlink(void)
{
if(engine)
{
if(engine->windows == this)
engine->windows = next;
if(engine->surface() == surface)
surface = NULL;
}
if(next)
next->prev = prev;
if(prev)
prev->next = next;
}
void window_t::place(int left, int top, int sizex, int sizey)
{
int x2 = ((left + sizex) * xs + 128) >> 8;
int y2 = ((top + sizey) * ys + 128) >> 8;
phys_rect.x = (left * xs + 128) >> 8;
phys_rect.y = (top * ys + 128) >> 8;
phys_rect.w = x2 - phys_rect.x;
phys_rect.h = y2 - phys_rect.y;
}
int window_t::offscreen()
{
if(!engine)
return -1;
if(!engine->surface())
return -1;
if(_offscreen)
return 0; // Already offscreen!
visible(0);
_offscreen = 1;
// SDL 3: back buffer is always ARGB8888; offscreen windows use
// the same format to allow direct blits without conversion.
surface = SDL_CreateSurface(phys_rect.w, phys_rect.h,
SDL_PIXELFORMAT_ARGB8888);
if(!surface)
return -1;
return 0;
}
void window_t::select()
{
if(engine)
_select();
}
void window_t::_select()
{
SDL_Rect r = phys_rect;
selected = this;
if(surface)
SDL_SetSurfaceClipRect(surface, &r);
}
void window_t::invalidate(SDL_Rect *r)
{
if(!engine)
return;
if(!engine->surface())
return;
SELECT
if(_offscreen)
{
if(!r)
{
SDL_Rect rr;
rr.x = 0;
rr.y = 0;
rr.w = phys_rect.w;
rr.h = phys_rect.h;
phys_refresh(&rr);
}
else
phys_refresh(r);
return;
}
if(!r)
engine->invalidate(&phys_rect, this);
else
{
/* Translate to screen coordinates */
SDL_Rect dr = *r;
dr.x = (dr.x * xs + 128) >> 8;
dr.y = (dr.y * ys + 128) >> 8;
dr.w = (((dr.w + dr.x) * xs + 128) >> 8) - dr.x;
dr.h = (((dr.h + dr.y) * ys + 128) >> 8) - dr.y;
dr.x += phys_rect.x;
dr.y += phys_rect.y;
/* Clip to window (stolen from SDL_surface.c) */
int Amin, Amax, Bmin, Bmax;
/* Horizontal intersection */
Amin = dr.x;
Amax = Amin + dr.w;
Bmin = phys_rect.x;
Bmax = Bmin + 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 = phys_rect.y;
Bmax = Bmin + 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)
engine->invalidate(&dr, this);
}
}
/*---------------------------------------------------------------
Rendering API
---------------------------------------------------------------*/
Uint32 window_t::map_rgb(Uint8 r, Uint8 g, Uint8 b)
{
if(!engine)
return 0;
if(surface)
return SDL_MapSurfaceRGB(surface, r, g, b);
else
return 0xffffff;
}
Uint32 window_t::map_rgb(Uint32 rgb)
{
if(!engine)
return 0;
Uint8 r = (rgb >> 16) & 0xff;
Uint8 g = (rgb >> 8) & 0xff;
Uint8 b = rgb & 0xff;
if(surface)
return SDL_MapSurfaceRGB(surface, r, g, b);
else
return 0xffffff;
}
void window_t::bgimage(int bank, int frame)
{
bg_bank = bank;
bg_frame = frame;
}
void window_t::colorkey(Uint32 color)
{
if(!engine)
return;
if(!engine->surface())
return;
if(!_offscreen)
return;
SDL_SetSurfaceColorKey(surface, true, color);
}
void window_t::colorkey()
{
if(!engine)
return;
if(!engine->surface())
return;
if(!_offscreen)
return;
SDL_SetSurfaceColorKey(surface, false, 0);
}
void window_t::alpha(float a)
{
if(!engine)
return;
if(!engine->surface())
return;
if(!_offscreen)
return;
SDL_SetSurfaceAlphaMod(surface, (Uint8)(a * 255.0f));
SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
}
void window_t::font(int fnt)
{
_font = fnt;
}
void window_t::string(int _x, int _y, const char *txt)
{
string_fxp(PIXEL2CS(_x), PIXEL2CS(_y), txt);
}
void window_t::center(int _y, const char *txt)
{
center_fxp(PIXEL2CS(_y), txt);
}
void window_t::center_token(int _x, int _y, const char *txt, char token)
{
center_token_fxp(PIXEL2CS(_x), PIXEL2CS(_y), txt, token);
}
void window_t::string_fxp(int _x, int _y, const char *txt)
{
if(!engine)
return;
_x = CS2PIXEL((_x * xs + 128) >> 8);
_y = CS2PIXEL((_y * ys + 128) >> 8);
SoFont *f = engine->get_font(_font);
if(!f)
return;
SELECT
_x += phys_rect.x;
_y += phys_rect.y;
if(surface)
f->PutString(surface, _x, _y, txt);
}
void window_t::center_fxp(int _y, const char *txt)
{
_y = CS2PIXEL((_y * ys + 128) >> 8);
if(!engine)
return;
SoFont *f = engine->get_font(_font);
if(!f)
return;
SELECT
int _x = (phys_rect.w - f->TextWidth(txt) + 1) / 2;
_x += phys_rect.x;
_y += phys_rect.y;
if(surface)
f->PutString(surface, _x, _y, txt);
}
void window_t::center_token_fxp(int _x, int _y, const char *txt, char token)
{
_x = CS2PIXEL((_x * xs + 128) >> 8);
_y = CS2PIXEL((_y * ys + 128) >> 8);
if(!engine)
return;
SoFont *f = engine->get_font(_font);
if(!f)
return;
SELECT
int _cx;
if(-1 == token)
_cx = _x - f->TextWidth(txt)/2;
else
{
int tokpos;
/*
* My docs won't say if strchr(???, 0) is legal
* or even defined, so I'm not taking any chances...
*/
if(token)
{
const char *tok = strchr(txt, token);
if(tok)
tokpos = tok-txt;
else
tokpos = 255;
}
else
tokpos = 255;
_cx = _x - f->TextWidth(txt, 0, tokpos);
}
_cx += phys_rect.x;
_y += phys_rect.y;
if(surface)
f->PutString(surface, _cx, _y, txt);
}
int window_t::textwidth_fxp(const char *txt, int min, int max)
{
if(!engine)
return strlen(txt);
SoFont *f = engine->get_font(_font);
if(!f)
return strlen(txt);
return (f->TextWidth(txt, min, max) << 16) / xs;
}
int window_t::textwidth(const char *txt, int min, int max)
{
if(!engine)
return strlen(txt);
SoFont *f = engine->get_font(_font);
if(!f)
return strlen(txt);
return (f->TextWidth(txt, min, max) << 8) / xs;
}
int window_t::fontheight()
{
if(!engine)
return 1;
SoFont *f = engine->get_font(_font);
if(!f)
return 1;
return (f->FontHeight() * 256) / ys;
}
void window_t::clear(SDL_Rect *r)
{
SDL_Rect sr, dr;
if(!engine)
return;
if(!surface)
return;
SELECT
if(!r)
{
sr = phys_rect;
sr.x = 0;
sr.y = 0;
dr = phys_rect;
}
else
{
sr.x = ((int)r->x * xs + 128) >> 8;
sr.y = ((int)r->y * ys + 128) >> 8;
sr.w = (((int)(r->x + r->w) * xs + 128) >> 8) - sr.x;
sr.h = (((int)(r->y + r->h) * ys + 128) >> 8) - sr.y;
dr = sr;
dr.x += phys_rect.x;
dr.y += phys_rect.y;
}
if((-1 == bg_bank) && (-1 == bg_frame))
SDL_FillSurfaceRect(surface, &dr, bgcolor);
else
{
s_sprite_t *s = engine->get_sprite(bg_bank, bg_frame);
if(!s || !s->surface)
{
SDL_FillSurfaceRect(surface, &dr, bgcolor);
return;
}
SDL_BlitSurface(s->surface, &sr, surface, &dr);
}
}
void window_t::point(int _x, int _y)
{
int x2 = ((_x + 1) * xs + 128) >> 8;
int y2 = ((_y + 1) * ys + 128) >> 8;
_x = (_x * xs + 128) >> 8;
_y = (_y * ys + 128) >> 8;
if(!engine)
return;
SELECT
/* Quick hack; slow */
SDL_Rect r;
r.x = phys_rect.x + _x;
r.y = phys_rect.y + _y;
r.w = x2 - _x;
r.h = y2 - _y;
if(surface)
SDL_FillSurfaceRect(surface, &r, fgcolor);
}
void window_t::fillrect(int _x, int _y, int w, int h)
{
int x2 = ((_x + w) * xs + 128) >> 8;
int y2 = ((_y + h) * ys + 128) >> 8;
_x = (_x * xs + 128) >> 8;
_y = (_y * ys + 128) >> 8;
if(!engine)
return;
SELECT
SDL_Rect r;
r.x = phys_rect.x + _x;
r.y = phys_rect.y + _y;
r.w = x2 - _x;
r.h = y2 - _y;
if(surface)
SDL_FillSurfaceRect(surface, &r, fgcolor);
}
void window_t::rectangle(int _x, int _y, int w, int h)
{
fillrect(_x, _y, w, 1);
fillrect(_x, _y + h - 1, w, 1);
fillrect(_x, _y + 1, 1, h - 2);
fillrect(_x + w - 1, _y + 1, 1, h - 2);
}
void window_t::fillrect_fxp(int _x, int _y, int w, int h)
{
int xx = CS2PIXEL((_x * xs + 128) >> 8);
int yy = CS2PIXEL((_y * ys + 128) >> 8);
w = CS2PIXEL(((w + _x) * xs + 128) >> 8) - xx;
h = CS2PIXEL(((h + _y) * ys + 128) >> 8) - yy;
if(!engine)
return;
SELECT
SDL_Rect r;
r.x = phys_rect.x + xx;
r.y = phys_rect.y + yy;
r.w = w;
r.h = h;
if(surface)
SDL_FillSurfaceRect(surface, &r, fgcolor);
}
void window_t::sprite(int _x, int _y, int bank, int frame, int inval)
{
sprite_fxp(PIXEL2CS(_x), PIXEL2CS(_y), bank, frame, inval);
}
void window_t::sprite_fxp(int _x, int _y, int bank, int frame, int inval)
{
if(!engine)
return;
s_sprite_t *s = engine->get_sprite(bank, frame);
if(!s || !s->surface)
return;
_x = CS2PIXEL(((_x - (s->x << 8)) * xs + 128) >> 8);
_y = CS2PIXEL(((_y - (s->y << 8)) * ys + 128) >> 8);
SDL_Rect dest_rect;
SELECT
dest_rect.x = phys_rect.x + _x;
dest_rect.y = phys_rect.y + _y;
if(surface)
SDL_BlitSurface(s->surface, NULL, surface, &dest_rect);
if(inval && !engine->autoinvalidate())
{
dest_rect.w = s->surface->w;
dest_rect.h = s->surface->h;
engine->invalidate(&dest_rect, this);
}
}
void window_t::blit(int dx, int dy,
int sx, int sy, int sw, int sh, window_t *src)
{
if(!engine)
return;
if(!src)
return;
if(!surface)
return;
if(!src->surface)
return;
SELECT
SDL_Rect src_rect;
int sx2 = ((sx + sw) * xs + 128) >> 8;
int sy2 = ((sy + sh) * ys + 128) >> 8;
src_rect.x = (sx * xs + 128) >> 8;
src_rect.y = (sy * ys + 128) >> 8;
src_rect.w = sx2 - src_rect.x;
src_rect.h = sy2 - src_rect.y;
SDL_Rect dest_rect;
dest_rect.x = phys_rect.x + ((dx * xs + 128) >> 8);
dest_rect.y = phys_rect.y + ((dy * ys + 128) >> 8);
SDL_BlitSurface(src->surface, &src_rect, surface, &dest_rect);
}
void window_t::blit(int dx, int dy, window_t *src)
{
if(!engine)
return;
if(!src)
return;
if(!surface)
return;
if(!src->surface)
return;
SELECT
dx = (dx * xs + 128) >> 8;
dy = (dy * ys + 128) >> 8;
SDL_Rect src_rect;
src_rect.x = 0;
src_rect.y = 0;
src_rect.w = src->surface->w;
src_rect.h = src->surface->h;
SDL_Rect dest_rect;
dest_rect.x = phys_rect.x + dx;
dest_rect.y = phys_rect.y + dy;
SDL_BlitSurface(src->surface, &src_rect, surface, &dest_rect);
}