From 9139322892a18a535226401f9428a40eb60daa16 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sat, 31 Oct 2026 22:40:42 +0200 Subject: [PATCH] perf(tiles): program the blit constants once per frame, not once per tile Spec 8.3's hoist, and the 60 fps work showed it was the per-tile cost that mattered, not the per-frame one. Every tile_blit() call used to save 8 B registers, load B1/B3/B4/B7/B8/B9 and write 11 I/O registers (PSIZE, CONVSP, CONVDP, PMASK, the CONTROL read-modify- write and its read-back) -- all constants. The user's guide marks only SADDR and DADDR changed by PIXBLT, and the TMS34010 agent's reading of it confirms B1/B3 survive, so those two are the only ones a per-tile call has to reload. vram_tile.asm now exports tile_init_() (the constants, once per frame, called from paint_tiles/paint_delta) and a tile_blit_() that is setf, two pushes, two register loads, pixblt, two pops, rets. Measured on the real 140-tile deltas at a 60 fps send, same window, terminal's own status frames: before the hoist 162 accepted after the hoist 231 accepted (+43%) and the one-tile stream (where tile cost is nil) already runs at 60 fps with DINT: 600 frames sent at 59.8 fps, 579 accepted (96.5%), drops=0. Pixel verdicts unchanged: modes tiles, delta and replay all RESULT PASS with tile_check; 155 unit tests pass. What the remaining gap is, for the next reader: MAME's PIXBLT moves about 3.4 us per destination word (0.22 ms per 32x32 tile, measured from the accepted rate) against the unit's measured 0.40 us/word for FILL. A 140-tile delta is 8,960 words, i.e. ~30 ms of MAME CPU against a 16.7 ms frame -- so 60 fps for this content is bounded by the emulator's pixel engine, not by the design. Whether the unit can do it turns on the one number nobody has measured there: PIXBLT B,XY's per-word rate (spec 10, risk table). --- harness/udpstream/vidstream.c | 3 ++ harness/udpstream/vram_tile.asm | 68 ++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/harness/udpstream/vidstream.c b/harness/udpstream/vidstream.c index 0fb24a9..313a1a8 100644 --- a/harness/udpstream/vidstream.c +++ b/harness/udpstream/vidstream.c @@ -339,6 +339,7 @@ unsigned int tile_src, tile_daddr; extern void vram_blit_b_(); /* vram_b.asm: PIXBLT B,XY, SPTCH = 0 */ extern void tile_blit_(); /* vram_tile.asm: PIXBLT B,XY, 32x32 tile */ +extern void tile_init_(); /* vram_tile.asm: the per-frame constants */ extern void vid_dint_(); /* vram_b.asm: DINT */ extern void vid_eint_(); /* vram_b.asm: EINT */ @@ -687,6 +688,7 @@ fp_header *h; } vid_dint_(); + tile_init_(); /* the tile constants: once per frame */ decode_begin(); k = 0; pos = h->payload_off; /* the frame's first index byte */ @@ -864,6 +866,7 @@ fp_header *h; * the per-row samples keep the 16-bit REFCNT counter unambiguous. */ decode_sample(); vid_dint_(); + tile_init_(); /* the tile constants: once per frame */ frag_get(h->payload_off + FP_DELTA_HDR, &lo); frag_get(h->payload_off + FP_DELTA_HDR + 1, &hi); pos = lo | (hi << 8); diff --git a/harness/udpstream/vram_tile.asm b/harness/udpstream/vram_tile.asm index 1164931..4f9050d 100644 --- a/harness/udpstream/vram_tile.asm +++ b/harness/udpstream/vram_tile.asm @@ -45,9 +45,12 @@ ; tile_daddr the destination XY word ; ; and the C wrapper tile_blit(src, tx, ty) is the only entry point. B7 (DYDX) -; is the constant 32x32 this primitive exists for. All inputs are reloaded on -; every call: PIXBLT leaves SADDR/DADDR and the B registers it advanced in a -; half-consumed state, so a caller that "saved" them would save that. +; is the constant 32x32 this primitive exists for. +; +; Two entry points, split by what changes: tile_init_() programs the constants +; (pitch, offset, dimensions, colours, PSIZE/CONVSP/CONVDP/PMASK/CONTROL) once +; per frame, and tile_blit_() then only reloads SADDR and DADDR -- the two +; registers PIXBLT destroys -- before executing it. ; ; Revert note: this file is new in Task 4 and nothing else calls it. To ; revert, delete it, the VRAMTILE entries in build-stream.sh and the @@ -62,28 +65,31 @@ .ref _tile_src .ref _tile_daddr .text - .globl _tile_blit_ -_tile_blit_: - setf 32, 0, 1 - move b0, -*sp, 1 - move b1, -*sp, 1 - move b2, -*sp, 1 - move b3, -*sp, 1 - move b4, -*sp, 1 - move b7, -*sp, 1 - move b8, -*sp, 1 - move b9, -*sp, 1 - move @_tile_src, b0, 1 ; SADDR: dictionary entry, index << 10 bits +; _tile_init_ -- program everything that does not vary between tiles. +; +; Spec 8.3's hoist, and it is worth real time in MAME: measured per-tile cost +; was ~0.36 ms against the unit's ~40 us, and the call did 11 I/O-register +; accesses plus 8 push/pop pairs for constants that never change. The registers +; this sets -- PSIZE, CONVSP, CONVDP, PMASK, CONTROL and B1 (SPTCH), B3 (DPTCH), +; B4 (OFFSET), B7 (DYDX), B8 (COLOR0), B9 (COLOR1) -- survive PIXBLT (the +; user's guide marks only SADDR/DADDR and B10-B14 as changed), so once per +; frame is enough. +; +; B8/B9 are NOT I/O registers: they are implied operands of every graphics +; instruction, and the resident OS leaves them arbitrary -- measured in the +; strip work: the first draw run painted every row black because nothing had +; set B9. + .globl _tile_init_ +_tile_init_: + setf 32, 0, 1 movi 020h, b1 ; SPTCH = 32 bits = four bytes a tile row - move @_tile_daddr, b2, 1 ; DADDR (XY): (ty*32 << 16) | tx*32 movi 800h, b3 ; DPTCH = 2048 bits = 256-byte screen row movi 04000000h, b4 ; OFFSET = VRAM base movi 0200020h, b7 ; DYDX = (32 rows << 16) | 32 pixels clr b8 ; COLOR0 = 0 (black) movi 0FFFFh, b9 ; COLOR1 = all planes set (white) - ; --- pixel format ------------------------------------------------------ movi 1, a1 move a1, @0C0000150h, 0 ; PSIZE = 1 movi 14h, a1 @@ -93,21 +99,31 @@ _tile_blit_: move a1, @0C0000160h, 0 ; PMASK = 0 (all planes writable) move a1, @0C0000170h, 0 ; upper half, for forward compatibility - ; --- graphics operation: replace, no transparency, no window ---------- + ; raster op 0 (replace), CD/RM/RR preserved move @0C00000B0h, a1, 0 ; CONTROL - andi 0801Fh, a1 ; keep CD (15) and RM/RR (2-4) only + andi 0801Fh, a1 move a1, @0C00000B0h, 0 move @0C00000B0h, a1, 0 ; read back: I/O writes are pipelined + rets +; _tile_blit_ -- one 32x32 tile, everything already programmed. +; +; Only B0 (SADDR) and B2 (DADDR) vary, and PIXBLT destroys both, so those two +; are the only registers this saves and reloads. tile_init_() must have run. +; +; Revert note: this file is new in Task 4 and nothing else calls it. To +; revert, delete it, the VRAMTILE entries in build-stream.sh and the +; tile_blit/paint_tiles code in vidstream.c; the strip path (vram_b.asm) does +; not reference any symbol defined here and is unaffected. + .globl _tile_blit_ +_tile_blit_: + setf 32, 0, 1 + move b0, -*sp, 1 + move b2, -*sp, 1 + move @_tile_src, b0, 1 ; SADDR: dictionary entry, index << 10 bits + move @_tile_daddr, b2, 1 ; DADDR (XY): (ty*32 << 16) | tx*32 pixblt b, xy - - move *sp+, b9, 1 - move *sp+, b8, 1 - move *sp+, b7, 1 - move *sp+, b4, 1 - move *sp+, b3, 1 move *sp+, b2, 1 - move *sp+, b1, 1 move *sp+, b0, 1 rets