ST-V: faster VDP2 and VDP1 drawing, SH-2 polling loops skipped, FTCSR as in MAME

VDP2 tiles:
- vdp2_drawgfx_alpha drew every pixel through vdp2_window_process; with a
  window it now draws the runs of each line the windows show, from the line
  cache of vdp2_drawgfx_transpen (same pixels), and with no window it
  copies without checks. Both work out the window key once per tile and
  check the cached line inline.
- Each decoded element keeps two masks of its rows (a pixel other than
  pen 0; no pen 0). With transparency vdp2_drawgfx_transpen skips tiles
  and rows of pen 0 only and copies whole the rows without it.

VDP1 sprites: without color calculation draw_sprites walks the window runs
of the line instead of calling vdp2_window_process for every pixel.

SH-2:
- Only a read that includes FRC resyncs it (MAME frc_r); TIER and FTCSR
  reads (MAME ftcsr_r) do not.
- The recompiler skips the iterations of polling loops that only read, as
  it already did for the SGL idle loop: a block that starts with a short
  loop back to itself which leaves every register as it found it and read
  only RAM or TIER/FTCSR repeats the same iteration until the end of the
  timeslice, so those iterations are counted at once.

Makefile (unix): -fvisibility=hidden, only retro_* leaves the library.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013Vje3pEtpLHubqCGwCgoQX
This commit is contained in:
VanniandClaude Opus 5.5 committed 2026-11-02 22:34:25 +01:00
1 parent cc4bfcdf71
commit 40dab5fe44
4 files changed
+246 -27

No files matched your search

+3 -1
View File
@@ -100,7 +100,9 @@ endif
ifeq ($(platform), unix)
TARGET = $(TARGET_NAME)_libretro.so
fpic = -fPIC
CFLAGS += $(fpic)
# only the retro_* entry points (RETRO_API) leave the library: the rest is
# reached directly instead of through the GOT and PLT
CFLAGS += $(fpic) -fvisibility=hidden
LDFLAGS += $(fpic) -shared -Wl,--version-script=link.T
# Linux Portable
+5 -1
View File
@@ -2893,7 +2893,11 @@ READ32_HANDLER( sh2_internal_r )
switch( offset )
{
case 0x04: /* TIER, FTCSR, FRC*/
sh2_timer_resync();
/* only a read of FRC brings it up to date, as in current MAME (frc_r):
reading TIER or FTCSR does not (a slave CPU waiting for its input
capture flag reads FTCSR tens of thousands of times a frame) */
if ((mem_mask & 0xffff) != 0xffff)
sh2_timer_resync();
return (sh2.m[4] & 0xffff0000) | sh2.frc;
case 0x05: /* OCRx, TCR, TOCR*/
if(sh2.m[5] & 0x10)
+95 -1
View File
@@ -84,6 +84,7 @@ struct jit_block
UINT8 valid;
UINT8 len; /* instructions */
UINT8 idle; /* the SGL idle counting loop starts here (see jit_idle_skip) */
UINT8 spin; /* a polling loop that only reads: see jit_spin_skip */
UINT16 op0; /* first opcode, to retry an untranslatable block once it changes */
};
@@ -1269,11 +1270,18 @@ static void jit_emit_mem_access(struct sljit_compiler *C, UINT16 op, const struc
*/
static sljit_u32 jit_slow_break;
static int jit_spin_io; /* a read of the spin check went to a device (see jit_spin_skip) */
static sljit_s32 SLJIT_FUNC jit_slow_read(sljit_u32 ea, sljit_u32 size)
{
int before = sh2_icount;
INT32 v;
/* only TIER and FTCSR read the same all through a timeslice without side
effects; any other device read stops a spin from being skipped */
if (!((size == 1 && (ea == 0xfffffe10 || ea == 0xfffffe11)) || (size == 2 && ea == 0xfffffe10)))
jit_spin_io = 1;
jit_in_block = 1;
if (size == 1)
v = (INT8)RB(ea);
@@ -1893,6 +1901,86 @@ static void jit_idle_skip(void)
sh2_icount -= k * cost;
}
/*
Polling loops: a block that starts with a short loop back to its own
start with BT or BF, which in between only reads memory (no
post-increment), moves, tests and compares, for example the Winter Heat
slave waiting for its input capture flag:
loop: MOV.B @R1,R0 ; FTCSR
TST #0x80,R0
BT loop
As for the SGL idle loop, nothing it reads can change within a
timeslice: if one iteration leaves every register as it found it and
read only RAM (or TIER/FTCSR), every later iteration of the timeslice
is the same, so they are counted at once. A whole iteration and the
part of one that ends the timeslice are left to the code.
*/
#define JIT_SPIN_MAX 8
static int jit_spin_op(UINT16 op)
{
switch (op >> 12)
{
case 0x0: return op == 0x0009; /* NOP */
case 0x2: { int k = op & 15; return k >= 8 && k <= 0xc; } /* TST AND XOR OR CMP/STR */
case 0x3: { int k = op & 15; return k == 0 || k == 2 || k == 3 || k == 6 || k == 7; } /* CMP/xx */
case 0x4: return (op & 0xff) == 0x11 || (op & 0xff) == 0x15; /* CMP/PZ CMP/PL */
case 0x5: return 1; /* MOV.L @(disp,Rm),Rn */
case 0x6: { int k = op & 15; return k <= 3 || k >= 7; } /* loads, MOV, NOT, SWAP, NEG(C), EXT */
case 0x8: { int k = (op >> 8) & 15; return k == 4 || k == 5 || k == 8; } /* MOV.B/W @(disp,Rm),R0, CMP/EQ #imm */
case 0x9: return 1; /* MOV.W @(disp,PC),Rn */
case 0xc: { int k = (op >> 8) & 15; return (k >= 4 && k <= 6) || (k >= 8 && k <= 0xb); } /* GBR loads, TST AND XOR OR #imm */
case 0xd: return 1; /* MOV.L @(disp,PC),Rn */
case 0xe: return 1; /* MOV #imm,Rn */
}
return 0;
}
static int jit_spin_block(const UINT16 *ops, UINT32 count, UINT32 pc)
{
UINT32 i;
/* the loop: from the start to a BT or BF back to it; the rest of the
block only runs once the loop is left */
for (i = 0; i < count && i < JIT_SPIN_MAX; i++)
{
UINT16 op = ops[i];
if ((op & 0xfd00) == 0x8900) /* BT or BF, no delay slot */
return i > 0 && pc + i * 2 + 4 + (INT8)(op & 0xff) * 2 == pc;
if (!jit_spin_op(op))
return 0;
}
return 0;
}
static struct { UINT32 r[16], sr, gbr, mach, macl, pr; } jit_spin_regs;
static void jit_spin_save(void)
{
memcpy(jit_spin_regs.r, sh2.r, sizeof(jit_spin_regs.r));
jit_spin_regs.sr = sh2.sr;
jit_spin_regs.gbr = sh2.gbr;
jit_spin_regs.mach = sh2.mach;
jit_spin_regs.macl = sh2.macl;
jit_spin_regs.pr = sh2.pr;
jit_spin_io = 0;
}
/* after one iteration from 'before' cycles: counts the iterations the rest of the timeslice would repeat */
static void jit_spin_skip(const struct jit_block *block, int before)
{
int cost = before - sh2_icount, k;
if (cost <= 0 || jit_spin_io || sh2.pc != block->pc || sh2.delay || sh2.test_irq
|| memcmp(jit_spin_regs.r, sh2.r, sizeof(jit_spin_regs.r)) || jit_spin_regs.sr != sh2.sr
|| jit_spin_regs.gbr != sh2.gbr || jit_spin_regs.mach != sh2.mach || jit_spin_regs.macl != sh2.macl
|| jit_spin_regs.pr != sh2.pr)
return;
k = (sh2_icount - 1) / cost - 1;
if (k > 0)
sh2_icount -= k * cost;
}
static void jit_compile(struct jit_block *block, UINT32 pc)
{
static struct jit_ctx ctx;
@@ -1911,6 +1999,7 @@ static void jit_compile(struct jit_block *block, UINT32 pc)
block->code = NULL;
block->entry = NULL;
block->idle = 0;
block->spin = 0;
block->op0 = jit_fetch(pc);
/* code must sit in one fast RAM region, below the special areas */
@@ -1951,6 +2040,7 @@ static void jit_compile(struct jit_block *block, UINT32 pc)
}
if (count == 0)
return;
block->spin = jit_spin_block(ops, count, pc);
/* cycles the block takes before each instruction, and at most in all */
ctx.pre[0] = 0;
@@ -2021,7 +2111,7 @@ static void jit_compile(struct jit_block *block, UINT32 pc)
{
sljit_uw n_entry = sljit_get_label_addr(entry);
/* the idle loop goes through the dispatcher, where it is skipped */
block->entry = block->idle ? NULL : (void *)n_entry;
block->entry = (block->idle || block->spin) ? NULL : (void *)n_entry;
jit_code_bytes += sljit_get_generated_code_size(C);
}
sljit_free_compiler(C);
@@ -2093,11 +2183,15 @@ static INLINE int sh2_jit_run(void)
jit_idle_skip();
if (block->cost > sh2_icount)
return sh2_icount != before; /* the iterations skipped, if any: the rest steps */
if (block->spin)
jit_spin_save();
#ifdef SH2JIT_VERIFY
sh2.pc = jit_verify_block(block);
#else
sh2.pc = ((jit_func)block->code)();
#endif
if (block->spin)
jit_spin_skip(block, before);
if (jit_flush_pending)
{
jit_flush_pending = 0;
+143 -24
View File
@@ -198,6 +198,8 @@ struct stv_gfx
int colors; /* palette banks */
UINT8 *cache;
UINT8 *dirty;
UINT16 *rows_any; /* per element, a bit per row: the row has a pixel other than 0 */
UINT16 *rows_full; /* per element, a bit per row: the row has no pixel 0 */
};
static struct stv_gfx stv_gfx[4];
@@ -217,6 +219,8 @@ static void gfx_setup(struct stv_gfx *g, int width, int height, int bpp, int inc
g->cache = auto_malloc(g->elements * width * height);
g->dirty = auto_malloc(g->elements);
memset(g->dirty, 1, g->elements);
g->rows_any = auto_malloc(g->elements * sizeof(UINT16));
g->rows_full = auto_malloc(g->elements * sizeof(UINT16));
}
/* a VRAM byte at addr changed: the elements that contain it are stale */
@@ -238,6 +242,7 @@ static void gfx_decode_element(struct stv_gfx *g, int code)
{
const UINT8 *src = vdp2_gfx_source + code * g->inc;
UINT8 *dst = g->cache + code * g->width * g->height;
UINT32 any = 0, full = 0;
int x, y;
/* the 16x16 tiles are four 8x8 cells: left top, right top, left bottom, right bottom */
@@ -262,6 +267,21 @@ static void gfx_decode_element(struct stv_gfx *g, int code)
}
*dst++ = pix;
}
/* which rows have something to draw, and which are drawn whole */
dst = g->cache + code * g->width * g->height;
for (y = 0; y < g->height; y++, dst += g->width)
{
int n0 = 0;
for (x = 0; x < g->width; x++)
n0 += (dst[x] == 0);
if (n0 < g->width)
any |= 1 << y;
if (n0 == 0)
full |= 1 << y;
}
g->rows_any[code] = (UINT16)any;
g->rows_full[code] = (UINT16)full;
}
#ifdef STV_VIDEO_THREADS
@@ -712,6 +732,8 @@ static void vdp2_drawgfxzoom_rgb555( struct mame_bitmap *dest_bmp,const struct r
static void vdp2_drawgfx_rgb555(struct mame_bitmap *dest_bmp, const struct rectangle *clip, UINT32 code, int flipx, int flipy, int sx, int sy, int transparency, int alpha);
static void vdp2_drawgfx_rgb888( struct mame_bitmap *dest_bmp, const struct rectangle *clip, UINT32 code, int flipx, int flipy, int sx, int sy, int transparency, int alpha);
static void vdp2_drawgfx_alpha(struct mame_bitmap *dest_bmp,const struct rectangle *clip,gfx_element *gfx, UINT32 code,UINT32 color, int flipx,int flipy,int offsx,int offsy, int transparency, int alpha);
static void vdp2_drawgfx_alpha_spans(struct mame_bitmap *dest_bmp, const pen_t *pal, const UINT8 *source_base, int width,
int x_index_base, int y_index, int xinc, int yinc, int sx, int sy, int ex, int ey, int opaque, int alpha);
static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rectangle *clip,gfx_element *gfx, UINT32 code,UINT32 color, int flipx,int flipy,int offsx,int offsy, int transparency);
static void draw_4bpp_bitmap(struct mame_bitmap *bitmap, const struct rectangle *cliprect);
static void draw_8bpp_bitmap(struct mame_bitmap *bitmap, const struct rectangle *cliprect);
@@ -5901,26 +5923,31 @@ static void vdp2_drawgfx_alpha(struct mame_bitmap *dest_bmp,const struct rectang
}
/* skip if inner loop doesn't draw anything */
if (ex > sx)
{
for (y = sy; y < ey; y++)
{
UINT8 const *const source = source_base + y_index*gfx->width;
UINT32 *const dest = BMP32(dest_bmp, y);
int x_index = x_index_base;
for (x = sx; x < ex; x++)
{
if(vdp2_window_process(x,y))
{
int c = (source[x_index]);
if ((transparency & STV_TRANSPARENCY_NONE) || (c != 0))
dest[x] = stv_alpha_blend_r32( dest[x], pal[c], alpha );
}
if (ex <= sx)
return;
x_index += xinc;
}
y_index += yinc;
/* a window on this layer: only the runs of each line it shows */
if (current_tilemap.window_control.enabled[0] || current_tilemap.window_control.enabled[1])
{
vdp2_drawgfx_alpha_spans(dest_bmp, pal, source_base, gfx->width, x_index_base, y_index,
xinc, yinc, sx, sy, ex, ey, (transparency & STV_TRANSPARENCY_NONE) ? 1 : 0, alpha);
return;
}
for (y = sy; y < ey; y++)
{
UINT8 const *const source = source_base + y_index*gfx->width;
UINT32 *const dest = BMP32(dest_bmp, y);
int x_index = x_index_base;
for (x = sx; x < ex; x++)
{
int c = (source[x_index]);
if ((transparency & STV_TRANSPARENCY_NONE) || (c != 0))
dest[x] = stv_alpha_blend_r32( dest[x], pal[c], alpha );
x_index += xinc;
}
y_index += yinc;
}
}
@@ -6023,6 +6050,64 @@ static const struct vdp2_window_span *vdp2_window_spans_get(int y)
return sp;
}
/* the settings of the windows of the current layer, as the key of the cache */
static INLINE int vdp2_window_spans_key(void)
{
return (current_tilemap.window_control.enabled[0] ? 1 : 0)
| (current_tilemap.window_control.enabled[1] ? 2 : 0)
| (current_tilemap.window_control.area[0] ? 0 : 4)
| (current_tilemap.window_control.area[1] ? 0 : 8)
| ((current_tilemap.window_control.logic & 1) ? 16 : 0);
}
/* vdp2_window_spans_get for a line, with the key of the layer worked out by
the caller once per tile: the cached line is checked here, inline */
static INLINE const struct vdp2_window_span *vdp2_window_spans_line(int y, int key)
{
if (y >= 0 && y < VDP2_WINDOW_LINES)
{
const struct vdp2_window_span *sp = &vdp2_window_spans[y];
if (sp->gen == vdp2_window_cache_gen && sp->key == key)
return sp;
}
return vdp2_window_spans_get(y);
}
/* vdp2_drawgfx_alpha with a window: the runs of pixels each line shows, from
the cache of the line, as vdp2_drawgfx_transpen (same pixels as
vdp2_window_process for each one) */
static void vdp2_drawgfx_alpha_spans(struct mame_bitmap *dest_bmp, const pen_t *pal, const UINT8 *source_base, int width,
int x_index_base, int y_index, int xinc, int yinc, int sx, int sy, int ex, int ey, int opaque, int alpha)
{
const int key = vdp2_window_spans_key();
int x, y;
for (y = sy; y < ey; y++)
{
UINT8 const *const source = source_base + y_index*width;
UINT32 *const dest = BMP32(dest_bmp, y);
const struct vdp2_window_span *sp = vdp2_window_spans_line(y, key);
int i;
for (i = 0; i < sp->n; i++)
{
int a = sp->a[i], b = sp->b[i], x_index;
if (a < sx) a = sx;
if (b > ex) b = ex;
if (a >= b)
continue;
x_index = x_index_base + (a - sx) * xinc;
for (x = a; x < b; x++, x_index += xinc)
{
const int c = source[x_index];
if (opaque || c != 0)
dest[x] = stv_alpha_blend_r32(dest[x], pal[c], alpha);
}
}
y_index += yinc;
}
}
static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rectangle *clip,gfx_element *gfx,
UINT32 code,UINT32 color, int flipx,int flipy,int offsx,int offsy,
int transparency)
@@ -6031,6 +6116,8 @@ static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rect
int x;
const pen_t *pal = &PEN(0 + gfx->granularity * (color % gfx->colors));
const UINT8 *source_base = gfx_get_data(gfx, code % gfx->elements);
const UINT32 rows_any = gfx->rows_any[code % gfx->elements];
const UINT32 rows_full = gfx->rows_full[code % gfx->elements];
int x_index_base, y_index, sx, sy, ex, ey;
int xinc, yinc;
@@ -6076,6 +6163,9 @@ static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rect
/* skip if inner loop doesn't draw anything */
if (ex <= sx)
return;
/* a tile with nothing but pen 0 draws nothing */
if (!(transparency & STV_TRANSPARENCY_NONE) && !rows_any)
return;
/* no window on this layer: plain copy loops (the RBG0 cache is drawn this way) */
if (!current_tilemap.window_control.enabled[0] && !current_tilemap.window_control.enabled[1])
@@ -6089,7 +6179,13 @@ static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rect
UINT32 *dest = BMP32(dest_bmp, y) + sx;
int n;
if (opaque)
/* rows of pen 0 only are skipped, rows without it copied whole */
if (!opaque && !((rows_any >> y_index) & 1))
{
y_index += yinc;
continue;
}
if (opaque || ((rows_full >> y_index) & 1))
{
for (n = 0; n < width; n++, source += xinc)
dest[n] = pal[*source];
@@ -6134,13 +6230,23 @@ static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rect
the line (vdp2_window_spans) */
{
const int opaque = (transparency & STV_TRANSPARENCY_NONE) ? 1 : 0;
const int key = vdp2_window_spans_key();
for (y = sy; y < ey; y++)
{
UINT8 const *const source = source_base + y_index*gfx->width;
UINT32 *const dest = BMP32(dest_bmp, y);
const struct vdp2_window_span *sp = vdp2_window_spans_get(y);
int i;
const struct vdp2_window_span *sp;
int i, whole;
/* rows of pen 0 only are skipped, rows without it copied whole */
if (!opaque && !((rows_any >> y_index) & 1))
{
y_index += yinc;
continue;
}
whole = opaque || ((rows_full >> y_index) & 1);
sp = vdp2_window_spans_line(y, key);
for (i = 0; i < sp->n; i++)
{
@@ -6150,7 +6256,7 @@ static void vdp2_drawgfx_transpen(struct mame_bitmap *dest_bmp,const struct rect
if (a >= b)
continue;
x_index = x_index_base + (a - sx) * xinc;
if (opaque)
if (whole)
{
for (x = a; x < b; x++, x_index += xinc)
dest[x] = pal[source[x_index]];
@@ -9675,9 +9781,16 @@ static void draw_sprites(struct mame_bitmap *bitmap, const struct rectangle *cli
{
if ( alpha_enabled == 0 )
{
/* the window: the runs of each line it shows (vdp2_window_spans), walked
along the line instead of vdp2_window_process for every pixel */
const int win = current_tilemap.window_control.enabled[0] || current_tilemap.window_control.enabled[1];
const int wkey = vdp2_window_spans_key();
for ( y = cliprect->min_y; y <= cliprect->max_y; y++ )
{
int x0 = cliprect->min_x, x1 = cliprect->max_x;
const struct vdp2_window_span *wsp;
int wi = 0;
if ( vdp1_sprite_priorities_usage_valid )
{
if (vdp1_sprite_priorities_in_fb_line[y][pri] == 0)
@@ -9689,6 +9802,7 @@ static void draw_sprites(struct mame_bitmap *bitmap, const struct rectangle *cli
framebuffer_line = vdp1.framebuffer_display_lines[y];
bitmap_line = BMP32(bitmap, y);
wsp = win ? vdp2_window_spans_line(y, wkey) : NULL;
for ( x = x0; x <= x1; x++ )
{
@@ -9707,8 +9821,13 @@ static void draw_sprites(struct mame_bitmap *bitmap, const struct rectangle *cli
}
continue;
}
if(!vdp2_window_process(x,y))
continue;
if (wsp)
{
while (wi < wsp->n && x >= wsp->b[wi])
wi++;
if (wi >= wsp->n || x < wsp->a[wi])
continue;
}
/* pukunpa, no alpha no framebuffer bumps */
if(sprite_window && pix == 0x8000)