pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_frame.c File Reference

pico9918-core - frame module More...

#include "impl/pico9918_priv.h"
#include "pico9918_frame.h"
#include "impl/pico9918_gpu_priv.h"
#include "overlay/diag.h"
#include "overlay/splash.h"
+ Include dependency graph for pico9918_frame.c:

Go to the source code of this file.

Macros

#define CONFIG_RELOAD_CB   tms9918->configReload
 

Functions

 PICO9918_SECTION_SCRATCH_Y (buffer)
 
static void configReloadFire (pico9918_t *tms9918)
 
void pico9918_frame_update_interrupts (pico9918_t *tms9918, uint8_t tempStatus)
 merge newly raised status flags into the SR0 latch, publish it, and bring the /INT pin into agreement.
 
void pico9918_frame_porch (pico9918_t *tms9918)
 see the header.
 
void pico9918_frame_raise_end_of_frame_int (pico9918_t *tms9918)
 see the header.
 
void pico9918_frame_end_of_scanline (pico9918_t *tms9918)
 see the header.
 
pico9918_frame_geometry_t pico9918_frame_geometry (pico9918_t *tms9918, pico9918_frame_display_t *display)
 see the header.
 
pico9918_frame_geometry_t pico9918_frame_end (pico9918_t *tms9918, float tempC, float frameRateHz, pico9918_frame_display_t *display)
 see the header.
 
bool pico9918_frame_scanline (pico9918_t *tms9918, uint16_t y, const pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
 see the header.
 
static bool dimLine (pico9918_t *tms9918, PICO9918_PIXEL_T *pixels, uint32_t count, uint32_t outputLine)
 
bool pico9918_frame_output_line (pico9918_t *tms9918, uint32_t outputLine, pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
 see the header.
 

Variables

int pico9918_frame_count = 0
 
int pico9918_dropped_frames_count = 0
 
static bool dropped_frames [16] = {0}
 
int pico9918_v_pixels = 192
 
uint32_t pico9918_v_border = 0
 
bool pico9918_valid_writes = false
 
uint8_t pico9918_v_scale = 2
 
uint16_t pico9918_v_virtual = 240
 

Detailed Description

pico9918-core - frame module

Copyright (c) 2021 Troy Schrapel

This code is licensed under the MIT license

https://github.com/visrealm/pico9918-core

Per-frame / per-scanline sequencing: the interrupt/status latch merge, the vertical porch, the end-of-frame interrupt and trigger line, the true end of frame, and the vertical geometry it derives.

It lands in a .c rather than on the Impl inline surface because a real function here costs nothing extra: every call site already reaches it with a bl. Inlining it into its three callers - one of them the per-scanline path - would ADD per-scanline code, not remove a call. The entries the host reaches through a VGA function pointer are calls for the same reason.

Definition in file pico9918_frame.c.

Macro Definition Documentation

◆ CONFIG_RELOAD_CB

#define CONFIG_RELOAD_CB   tms9918->configReload

Definition at line 66 of file pico9918_frame.c.

Function Documentation

◆ PICO9918_SECTION_SCRATCH_Y()

PICO9918_SECTION_SCRATCH_Y ( buffer  )

Definition at line 78 of file pico9918_frame.c.

◆ configReloadFire()

static void configReloadFire ( pico9918_t *  tms9918)
inlinestatic

Definition at line 86 of file pico9918_frame.c.

◆ pico9918_frame_update_interrupts()

void pico9918_frame_update_interrupts ( pico9918_t *  tms9918,
uint8_t  tempStatus 
)

merge newly raised status flags into the SR0 latch, publish it, and bring the /INT pin into agreement.

merge newly raised status flags into the SR0 latch, publish it, and bring the /INT pin into agreement

See the header for the parameter contract.

The internal sequence is load-bearing at two points and must not be reordered:

  • the three-way merge below is the v1.2.0 semantics, not a simplification of it. Each branch differs in what it lets through and what it preserves;
  • the status publish (PICO9918_HOST_STATUS_VISIBLE) happens BEFORE the pin sync, so a host CPU that takes the interrupt cannot read a stale status.

Definition at line 102 of file pico9918_frame.c.

References pico9918_frame_sync_int_impl(), PICO9918_INST, PICO9918_INST_ONLY, pico9918_set_status_impl(), PICO9918_SR0_5S, PICO9918_SR0_COLLISION, and PICO9918_SR0_INT.

Referenced by pico9918_frame_raise_end_of_frame_int(), and pico9918_frame_scanline().

◆ pico9918_frame_porch()

void pico9918_frame_porch ( pico9918_t *  tms9918)

see the header.

vertical porch: blank the display and park the scanline counter at the F18A vsync value

Definition at line 131 of file pico9918_frame.c.

References PICO9918_SR_RASTER_LINE.

◆ pico9918_frame_raise_end_of_frame_int()

void pico9918_frame_raise_end_of_frame_int ( pico9918_t *  tms9918)

see the header.

raise this frame's end-of-frame interrupt: latch doneInt, set the SR1 vsync bit, trigger the GPU if R50 bit 5 asks for it, consume a pending config change, then merge PICO9918_SR0_INT into the SR0 latch

The configDirty consumption is a CHECK-THEN-CLEAR on a non-volatile flag written from the other core (the register path sets it on core 1, the host's config load on core 0) and consumed here. Deliberately left alone: the worst case is a config apply deferred by one frame.

Definition at line 146 of file pico9918_frame.c.

References pico9918_config_apply(), pico9918_diag_config_updated(), pico9918_frame_update_interrupts(), PICO9918_INST, PICO9918_INST_ONLY, PICO9918_R50_GPU_VSYNC, PICO9918_REG_ENHANCED2, PICO9918_SR0_INT, PICO9918_SR1_BLANK, and PICO9918_SR_IDENT.

Referenced by pico9918_frame_end(), and pico9918_frame_end_of_scanline().

◆ pico9918_frame_end_of_scanline()

void pico9918_frame_end_of_scanline ( pico9918_t *  tms9918)

see the header.

end-of-frame trigger line: if this frame has not raised its interrupt yet, account for the dropped frame and raise the interrupt

It takes no display line: the body never reads one.

Definition at line 168 of file pico9918_frame.c.

References pico9918_frame_raise_end_of_frame_int(), PICO9918_INST_ONLY, and PICO9918_SR0_INT.

◆ pico9918_frame_geometry()

pico9918_frame_geometry_t pico9918_frame_geometry ( pico9918_t *  tms9918,
pico9918_frame_display_t display 
)

see the header.

recompute the vertical display geometry from R0's double-rows bit and R49's row-30 bit, and publish it as this module's vPixels / vBorder

Three things about this expression are deliberately NOT tidied:

  • the yScale-conditional structure. vPixelScale and vVirtualPixels are rewritten only when yScale > 1, and the vPixels doubling is gated on the SAME condition. Under interlace the host owns the first two and vPixels must NOT double - the two fields already supply the second set of lines;
  • the shift-by-bool algebra (yScale - (bool)doubleRows, << (bool)doubleRows). The golden frame surface's reference deliberately decomposes this into explicit cases so the two do not share the algebra; rewriting it here to look like the reference would destroy that independence;
  • the SIGNED intermediate for the border. SCART NTSC in row-30 mode makes it negative and the narrowing to uint32_t is the shipping behaviour, pinned at geom-scart-ntsc-row30. A known defect, ruled won't-fix: the host's unsigned border test then sends all 220 lines down the border path. Do not "fix" it here.

yScale is derived from interlaced rather than from a build-time DISPLAY_YSCALE. A non-SCART build's DISPLAY_YSCALE is always 2 with interlaced false, so the interlace-derived form covers both cases exactly and the library needs no build-time host macro.

Definition at line 204 of file pico9918_frame.c.

References PICO9918_R49_ROW30, PICO9918_REG_ENHANCED1, TMS_R0_DOUBLE_ROWS, pico9918_frame_geometry_t::triggerScanline, pico9918_frame_geometry_t::vBorder, and pico9918_frame_geometry_t::vPixels.

Referenced by pico9918_frame_end().

◆ pico9918_frame_end()

pico9918_frame_geometry_t pico9918_frame_end ( pico9918_t *  tms9918,
float  tempC,
float  frameRateHz,
pico9918_frame_display_t display 
)

see the header.

true end of frame: advance the frame counter, fold in this frame's temperature reading, latch the first display enable, refresh the diagnostics panel, raise a still-owed end-of-frame interrupt, and recompute the geometry

It takes no frame number: the cadence and the thresholds all run off this module's own frame counter, which - unlike the VGA layer's frame number - resets on console reset.

The frame count is re-READ at each use rather than cached in a local. That is not a style choice: the host's tier-1 critical section deliberately does not mask the reset GPIO IRQ, which zeroes the counter, so a console reset landing mid-function is observable and a cached copy would hide it.

Definition at line 241 of file pico9918_frame.c.

References pico9918_diag_set_frame_rate(), pico9918_diag_set_temperature(), pico9918_diag_update(), pico9918_frame_geometry(), pico9918_frame_raise_end_of_frame_int(), PICO9918_FRAME_STARTUP_FRAMES, PICO9918_INST, PICO9918_INST_ONLY, pico9918_splash_allow_hide(), PICO9918_SR_GPU, PICO9918_SR_TEMPERATURE, TMS_R1_DISP_ACTIVE, and pico9918_frame_display_t::vVirtualPixels.

◆ pico9918_frame_scanline()

bool pico9918_frame_scanline ( pico9918_t *  tms9918,
uint16_t  y,
const pico9918_scanline_params_t params,
PICO9918_PIXEL_T *  pixels 
)

see the header.

generate one display scanline: border fill or active render, the F18A scanline and blanking registers, the R19 line interrupt, the GPU trigger, the splash and the palette LUT maintenance

The per-scanline path - the function every gate in this project exists to protect.

The host's pending-display banner is deliberately not here. It is host code - host flash state, a host trigger byte, centring against the host's own buffer width - and its only ordering constraints are pixel ones: after the border fill and the splash, before the diagnostics overlay. The host's overlay tail already sits there, and what runs between touches no pixels, so the framebuffer is bit-identical either way.

The return value is the border flag, and the host needs it to place that banner. "y is small" is not the same test: in row-30 progressive mode vBorder is 0, so the rows the banner occupies are active ones and a host that guessed would paint over the display.

FIVE THINGS HERE MUST NOT BE TIDIED:

  • the bg store BEFORE the border WAIT. The previous line's right-border fill may still be reading the source word when this line overwrites it. Benign - the value is the same on all but the frame a background register changes - and "fixing" it by waiting first adds a per-scanline stall the goldens cannot see;
  • the row-30 border test. The TRAP: below is a recorded, user-ruled won't-fix;
  • the SCART-NTSC negative-border underflow. vBorder is unsigned and row-30 on that timing makes it 4294967286, so this test sends all 220 lines down the border path and renders none. Pinned deliberately by the golden frame surface at geom-scart-ntsc-row30. Do not fix it here;
  • the frame count re-READ at each use rather than cached, for the reason pico9918_frame_end above states: the reset GPIO IRQ is not masked and zeroes it;
  • the y -= vBorder in BOTH arms. It looks like it belongs after the branch, but the border arm's own body reads the unadjusted y (the bottom-border scanline register, the palette-regenerate trigger and the splash all do), so hoisting it would change all three.

Definition at line 326 of file pico9918_frame.c.

References pico9918_diag_render(), pico9918_diag_update_render_time(), PICO9918_FRAME_STARTUP_FRAMES, pico9918_frame_update_interrupts(), PICO9918_INST, PICO9918_INST_ONLY, PICO9918_R24_TILE1_PS, PICO9918_R50_GPU_HSYNC, PICO9918_REG_ENHANCED2, PICO9918_REG_HORZ_INT_LINE, PICO9918_REG_PALETTE_SELECT, pico9918_scan_line(), pico9918_splash_render(), PICO9918_SR1_BLANK, PICO9918_SR1_HF, PICO9918_SR_IDENT, PICO9918_SR_RASTER_LINE, and TMS9918_PIXELS_X.

Referenced by pico9918_frame_output_line().

◆ dimLine()

static bool dimLine ( pico9918_t *  tms9918,
PICO9918_PIXEL_T *  pixels,
uint32_t  count,
uint32_t  outputLine 
)
static

Definition at line 481 of file pico9918_frame.c.

◆ pico9918_frame_output_line()

bool pico9918_frame_output_line ( pico9918_t *  tms9918,
uint32_t  outputLine,
pico9918_scanline_params_t params,
PICO9918_PIXEL_T *  pixels 
)

see the header.

generate one OUTPUT line - the entry for a host that scans out a fixed frame

A repeat re-reads the host's buffer rather than re-rendering.

Definition at line 494 of file pico9918_frame.c.

References pico9918_scanline_params_t::hVirtualPixels, PICO9918_CHIP_F18A, pico9918_diag_render(), pico9918_frame_scanline(), PICO9918_INST, PICO9918_INST_ONLY, and pico9918_scanline_params_t::vVirtualPixels.

Variable Documentation

◆ pico9918_frame_count

int pico9918_frame_count = 0

Definition at line 34 of file pico9918_frame.c.

◆ pico9918_dropped_frames_count

int pico9918_dropped_frames_count = 0

Definition at line 35 of file pico9918_frame.c.

◆ dropped_frames

bool dropped_frames[16] = {0}
static

Definition at line 36 of file pico9918_frame.c.

◆ pico9918_v_pixels

int pico9918_v_pixels = 192

Definition at line 40 of file pico9918_frame.c.

◆ pico9918_v_border

uint32_t pico9918_v_border = 0

Definition at line 41 of file pico9918_frame.c.

◆ pico9918_valid_writes

bool pico9918_valid_writes = false

Definition at line 42 of file pico9918_frame.c.

◆ pico9918_v_scale

uint8_t pico9918_v_scale = 2

Definition at line 45 of file pico9918_frame.c.

◆ pico9918_v_virtual

uint16_t pico9918_v_virtual = 240

Definition at line 46 of file pico9918_frame.c.