core: video pipeline - SW framebuffer, hoist invariants, drop NTSC memcpy

Rework retro_run_blit to:

* Try GET_CURRENT_SOFTWARE_FRAMEBUFFER for zero-copy. If the frontend
  hands us its own scanout buffer with a compatible pixel format, the
  blit writes the palette-LUT conversion directly into it instead of
  routing through fceu_video_out. Falls back to the existing buffer
  when the frontend declines or returns a mismatched format. The
  pixel format negotiated at retro_load_game is now cached in a file-
  scope active_pixformat for this validation.

* Drop the per-frame memcpy in the NTSC path. nes_ntsc_blit writes
  into ntsc_video_out with a wider stride (NES_NTSC_WIDTH includes
  right-margin padding); the previous code copied row-by-row into a
  tightly packed fceu_video_out before video_cb. The pitch parameter
  to video_cb already lets the frontend skip past padding per
  scanline, so the copy was redundant. Saves ~580 KB / frame at 32
  bpp - around 35 MB/s of memory traffic at 60 fps.

* Hoist loop-invariant decisions out of the per-pixel inner loop.
  GameInfo->type != GIT_NSF and use_raw_palette do not change mid-
  frame; emphasis (XDBuf) is uniform per scanline because the PPU
  writes one colour_emphasis byte to all 256 entries of dtarget at
  ppu.c:683-685. The deemphasis branch is now once per row instead
  of once per pixel.

* Replace fceu_video_out[y * width + x] indexing with a running
  out_row pointer (strength reduction).

* Remove the unused incr local.

Bit-exact verified against the baseline across 4 test ROMs (silent,
idle, active gameplay, all-channel max-volume stress) for non-NTSC,
SW-framebuffer-granted, and NTSC composite paths - 1200+ frames
total. Audio output also bit-identical.

Determinism audit pass over the video pipeline turned up no issues:
no rand/srand/time/clock/gettimeofday in any core path; PPU writes
to XBuf are scanline-deterministic; nes_ntsc_blit is stateless;
burst_phase initializes to zero and toggles deterministically; and
the existing FCEU_MemoryRand and rt-01 weakbits state machines from
earlier passes remain in place.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre
2026-05-04 07:16:54 +02:00
parent b5b43ad2be
commit 6ffed08948

View File

@@ -153,6 +153,12 @@ static int crop_overscan_v_bottom;
static bool use_raw_palette; static bool use_raw_palette;
static int aspect_ratio_par; static int aspect_ratio_par;
/* Pixel format negotiated with the frontend at retro_load_game. Cached
* here for retro_run_blit so it can validate that
* GET_CURRENT_SOFTWARE_FRAMEBUFFER returned a format we can write into
* directly. */
static enum retro_pixel_format active_pixformat = RETRO_PIXEL_FORMAT_UNKNOWN;
/* /*
* Flags to keep track of whether turbo * Flags to keep track of whether turbo
* buttons toggled on or off. * buttons toggled on or off.
@@ -2925,7 +2931,6 @@ static void retro_run_blit(uint8_t *gfx)
static unsigned int __attribute__((aligned(16))) d_list[32]; static unsigned int __attribute__((aligned(16))) d_list[32];
void* texture_vram_p = NULL; void* texture_vram_p = NULL;
#endif #endif
unsigned incr = 0;
unsigned width = 256; unsigned width = 256;
unsigned height = 240; unsigned height = 240;
unsigned pitch = width * sizeof(bpp_t); unsigned pitch = width * sizeof(bpp_t);
@@ -3008,52 +3013,86 @@ static void retro_run_blit(uint8_t *gfx)
width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right; width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right;
width = NES_NTSC_OUT_WIDTH(width); width = NES_NTSC_OUT_WIDTH(width);
height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom; height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom;
pitch = width * sizeof(bpp_t); pitch = NES_NTSC_WIDTH * sizeof(bpp_t);
/* Pass ntsc_video_out directly to the frontend with the wider
* source pitch instead of memcpy'ing into a tightly-packed
* fceu_video_out. video_cb's pitch parameter already lets the
* frontend skip past the unused right margin per scanline; the
* memcpy was redundant. Saves ~580 KB per frame at 32 bpp full
* NES_NTSC_OUT_WIDTH * NES_HEIGHT - i.e. ~35 MB/s of bandwidth
* at 60 fps. */
{ {
int32_t h_offset = (crop_overscan_h_left ? NES_NTSC_OUT_WIDTH(crop_overscan_h_left) : 0); int32_t h_offset = (crop_overscan_h_left ? NES_NTSC_OUT_WIDTH(crop_overscan_h_left) : 0);
int32_t v_offset = crop_overscan_v_top; int32_t v_offset = crop_overscan_v_top;
const bpp_t *in = ntsc_video_out + h_offset + NES_NTSC_WIDTH * v_offset; const bpp_t *in = ntsc_video_out + h_offset + NES_NTSC_WIDTH * v_offset;
bpp_t *out = fceu_video_out; video_cb(in, width, height, pitch);
for (y = 0; y < height; y++)
{
memcpy(out, in, pitch);
in += NES_NTSC_WIDTH;
out += width;
}
} }
video_cb(fceu_video_out, width, height, pitch);
} }
else else
#endif /* HAVE_NTSC_FILTER */ #endif /* HAVE_NTSC_FILTER */
{ {
incr += (crop_overscan_h_left + crop_overscan_h_right);
width -= (crop_overscan_h_left + crop_overscan_h_right); width -= (crop_overscan_h_left + crop_overscan_h_right);
height -= (crop_overscan_v_top + crop_overscan_v_bottom); height -= (crop_overscan_v_top + crop_overscan_v_bottom);
pitch -= (crop_overscan_h_left + crop_overscan_h_right) * sizeof(bpp_t); pitch = width * sizeof(bpp_t);
gfx += (crop_overscan_v_top * 256) + crop_overscan_h_left; gfx += (crop_overscan_v_top * 256) + crop_overscan_h_left;
{ {
uint8_t *deemp = XDBuf + (gfx - XBuf); /* Try GET_CURRENT_SOFTWARE_FRAMEBUFFER for zero-copy: if the
for (y = 0; y < height; y++, gfx += incr, deemp += incr) * frontend can hand us its own scanout buffer, we write the
* pixel-format conversion directly into it and skip a round
* trip through fceu_video_out. */
struct retro_framebuffer fb = {0};
bpp_t *target = fceu_video_out;
size_t target_stride = (size_t)width; /* in pixels */
fb.width = width;
fb.height = height;
fb.access_flags = RETRO_MEMORY_ACCESS_WRITE;
if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb)
&& fb.format == active_pixformat
&& fb.data
&& (fb.pitch % sizeof(bpp_t)) == 0)
{ {
for (x = 0; x < width; x++, gfx++, deemp++) target = (bpp_t *)fb.data;
target_stride = fb.pitch / sizeof(bpp_t);
pitch = fb.pitch;
}
/* Hoist loop-invariant decisions out of the hot per-pixel
* loop. The PPU writes a uniform colour_emphasis byte to
* every entry of XDBuf for a given scanline (ppu.c:683-685),
* so emphasis is constant per row - branch once per row, not
* per pixel. NSF and use_raw_palette flags don't change mid-
* frame either. */
{
const uint8_t *deemp_row = XDBuf + (gfx - XBuf);
const bool is_nsf = (GameInfo->type == GIT_NSF);
const uint8_t pixel_mask = use_raw_palette ? 0x3F : 0xFF;
for (y = 0; y < height; y++)
{ {
if (*deemp != 0 && GameInfo->type != GIT_NSF) bpp_t *out_row = target + (size_t)y * target_stride;
uint8_t deemp = is_nsf ? 0 : deemp_row[0];
if (deemp != 0)
{ {
fceu_video_out[y * width + x] = retro_palette[256 + (*gfx & 0x3F) + ((*deemp & 0x07) << 6)]; unsigned base = 256u + ((unsigned)(deemp & 0x07) << 6);
for (x = 0; x < width; x++)
out_row[x] = retro_palette[base + (gfx[x] & 0x3F)];
} }
else else
{ {
uint8_t pixel_mask = use_raw_palette ? 0x3F : 0xFF; for (x = 0; x < width; x++)
fceu_video_out[y * width + x] = retro_palette[*gfx & pixel_mask]; out_row[x] = retro_palette[gfx[x] & pixel_mask];
} }
gfx += 256;
deemp_row += 256;
} }
} }
}
video_cb(fceu_video_out, width, height, pitch); video_cb(target, width, height, pitch);
}
} }
#endif #endif
} }
@@ -3655,12 +3694,18 @@ bool retro_load_game(const struct retro_game_info *info)
#ifdef FRONTEND_SUPPORTS_RGB888 #ifdef FRONTEND_SUPPORTS_RGB888
pixformat = RETRO_PIXEL_FORMAT_XRGB8888; pixformat = RETRO_PIXEL_FORMAT_XRGB8888;
if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat)) if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat))
{
log_cb.log(RETRO_LOG_INFO, "Frontend supports RGBX888 - will use that instead of XRGB1555.\n"); log_cb.log(RETRO_LOG_INFO, "Frontend supports RGBX888 - will use that instead of XRGB1555.\n");
active_pixformat = pixformat;
}
#else #else
#if FRONTEND_SUPPORTS_RGB565 #if FRONTEND_SUPPORTS_RGB565
pixformat = RETRO_PIXEL_FORMAT_RGB565; pixformat = RETRO_PIXEL_FORMAT_RGB565;
if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat)) if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat))
{
log_cb.log(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n"); log_cb.log(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n");
active_pixformat = pixformat;
}
#endif #endif
#endif #endif