OXYGENHUB

video: keep the beam step per clock count instead of dividing each time 12673a41ba17

video_step() runs after every instruction and worked out how far the
beam moves, PIXEL_FREQ * steps / mhz, twice, in double precision, with
two divisions. The clock count is almost always a handful, so the
quotient for each count below 32 is now kept in a table filled with the
same expression whenever mhz changes, and the two counters share one
result. The same expression gives the same double, so the beam position
is bit-for-bit what it was; larger counts (after a HostFS call) still
use the formula.

Measured as before (CPU seconds per emulated second, median of 3):

    narvi  idle  2.032 -> 2.046  (49%, within run-to-run spread)
    narvi  busy  1.884 -> 1.846  (53% -> 54% of a real X16)

x86 was within this machine's noise. The determinism check matches on
x86 and riscv64.
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 3d452845da48
When
· 2026-10-21 01:41 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
12673a41ba171e8324048662eae7ffd77c14453bb43edca023026f60f34d4978
Depends on
03a3459e6372
Depended on by
8e8d9cad1884
In bookmarks
main riscv-1
Edits
4 atoms, 0 tree operations, 1 file

Browse the tree at this change

modified src/video.c +27 −2
…
1236 unchanged lines
1237 1237
	}
1238 1238
}
1239 1239
1240 +
// How far the beam moves in `steps` CPU clocks: PIXEL_FREQ * steps / mhz.
1241 +
// video_step() runs after every instruction, almost always for a handful
1242 +
// of clocks, so the quotient for each small count is kept rather than
1243 +
// divided out again; it is the same expression, so the same value.
1244 +
static double
1245 +
scan_pixels(float mhz, float steps)
1246 +
{
1247 +
	static float table_mhz;
1248 +
	static double table[32];
1249 +
	if (mhz != table_mhz) {
1250 +
		for (int i = 0; i < 32; i++) {
1251 +
			table[i] = PIXEL_FREQ * (float)i / mhz;
1252 +
		}
1253 +
		table_mhz = mhz;
1254 +
	}
1255 +
	if (steps >= 0 && steps < 32) {
1256 +
		uint32_t s = (uint32_t)steps;
1257 +
		if (s == steps) {
1258 +
			return table[s];
1259 +
		}
1260 +
	}
1261 +
	return PIXEL_FREQ * steps / mhz;
1262 +
}
1263 +
1240 1264
bool
1241 1265
video_step(float mhz, float steps, bool midline)
1242 1266
{
1243 1267
	uint16_t y = 0;
1244 1268
	bool ntsc_mode = reg_composer[0] & 2;
1245 1269
	bool new_frame = false;
1246 -
	vga_scan_pos_x += PIXEL_FREQ * steps / mhz;
1270 +
	const double pixels = scan_pixels(mhz, steps);
1271 +
	vga_scan_pos_x += pixels;
1247 1272
	if (vga_scan_pos_x > VGA_SCAN_WIDTH) {
1248 1273
		vga_scan_pos_x -= VGA_SCAN_WIDTH;
1249 1274
		if (!ntsc_mode) {
…
15 unchanged lines
1265 1290
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, vga_scan_pos_x);
1266 1291
		}
1267 1292
	}
1268 -
	ntsc_half_cnt += PIXEL_FREQ * steps / mhz;
1293 +
	ntsc_half_cnt += pixels;
1269 1294
	if (ntsc_half_cnt > NTSC_HALF_SCAN_WIDTH) {
1270 1295
		ntsc_half_cnt -= NTSC_HALF_SCAN_WIDTH;
1271 1296
		if (ntsc_mode) {
…
1186 unchanged lines