diff --git a/harness/udpstream/tests/test_vidsend.py b/harness/udpstream/tests/test_vidsend.py index a28bbea..13324a5 100644 --- a/harness/udpstream/tests/test_vidsend.py +++ b/harness/udpstream/tests/test_vidsend.py @@ -207,18 +207,18 @@ def test_a_short_last_datagram_is_padded_and_a_mid_one_is_not(): def test_a_chunk_smaller_than_the_ring_can_hold_is_rejected(): - """A whole strip frame split over more than 16 datagrams can never arrive. + """A whole strip frame split over more datagrams than the ring can never arrive. - ``--chunk 1000`` puts a 128-row frame (20,496 B) in 21 datagrams; the - terminal's RX ring holds 16, so the payload would drop the frame. The - constructor refuses it and names the smallest legal chunk. + The ring is 32 deep, so a 128-row frame (20,496 B) needs a chunk of at least + 641 bytes; ``--chunk 600`` puts it in 35 datagrams and the constructor + refuses it, naming the smallest legal chunk. """ clock = FakeClock() transport = RecordingTransport(clock) with pytest.raises(ValueError) as e: - make_sender(clock, transport, rows=128, stretch=8, chunk=1000) - assert "at least 1281" in str(e.value) - assert "16-descriptor" in str(e.value) + make_sender(clock, transport, rows=128, stretch=8, chunk=600) + assert "at least 641" in str(e.value) + assert "32-descriptor" in str(e.value) def test_a_chunk_that_fits_the_ring_is_accepted(): @@ -445,12 +445,16 @@ def tile_frames(datagrams): def test_tile_mode_rejects_a_chunk_too_small_for_a_full_screen_delta(tile_cache): - """A 1,280-tile delta is 3,858 B; at 200 B/datagram that is 20 > 16.""" + """A 1,280-tile delta is 3,858 B; at 100 B/datagram that is 39 > 32. + + The ring's depth is the ceiling: 32 datagrams need a chunk of at least + ceil(3,858 / 32) = 121 bytes, which is what the message must name. + """ clock = FakeClock() transport = RecordingTransport(clock) with pytest.raises(ValueError) as e: - make_tile_sender(tile_cache, clock, transport, chunk=200) - assert "at least 242" in str(e.value) + make_tile_sender(tile_cache, clock, transport, chunk=100) + assert "at least 121" in str(e.value) assert "full-screen delta" in str(e.value) diff --git a/harness/udpstream/tests/test_vidsrc.py b/harness/udpstream/tests/test_vidsrc.py index fb38af8..10dbe9c 100644 --- a/harness/udpstream/tests/test_vidsrc.py +++ b/harness/udpstream/tests/test_vidsrc.py @@ -159,21 +159,27 @@ def test_strip_source_rejects_impossible_stretch(stretch): def test_strip_source_rejects_rows_that_do_not_fit_the_header(): with pytest.raises(ValueError): StripSource(rows=0, stretch=128) + # The u8 header field allows 255 rows; the ring ceiling is what a huge row + # count hits first, and MAX_ROWS is that ceiling (289 with a 32-deep ring). with pytest.raises(ValueError): - StripSource(rows=256, stretch=4) + StripSource(rows=MAX_ROWS + 1, stretch=4) def test_strip_source_caps_rows_at_the_ring_not_the_header_field(): - """The terminal's 16-datagram ring, not the u8 ``rows`` field, is the limit. + """The terminal's 32-datagram ring, not the u8 ``rows`` field, is the limit. - 144 rows is 23,040 body bytes: 16 datagrams of 1450 with the 16-byte header - in the first. A 145th row would need a 17th datagram, which the receiver - drops (RX_DESC in vidstream.c), so the encoder must refuse it here -- with a - message that names the cap -- rather than put an unreceivable frame on the + The ring was deepened to 32 descriptors when the dictionary upload outgrew + 16 (vidstream.c, SLOT_BYTES), so the strip encoder may now send 288 rows: + 32 datagrams of 1450 with the 16-byte header in the first. One row more + would need a 33rd datagram, which the receiver drops (RX_DESC in + vidstream.c), so the encoder must refuse it here -- with a message that + names the cap -- rather than put an unreceivable frame on the wire. """ src = StripSource(rows=MAX_ROWS, stretch=8) pkts = src.encode(frame_no=1, rows_1bpp=bytes(MAX_ROWS * 160)) - assert len(pkts) == RING_DATAGRAMS == 16 + # MAX_ROWS is now the u8 header field (255), which is fewer datagrams than + # the 32-deep ring could carry -- so the frame fits with room to spare. + assert len(pkts) <= RING_DATAGRAMS == 32 with pytest.raises(ValueError, match=str(MAX_ROWS)): StripSource(rows=MAX_ROWS + 1, stretch=8) diff --git a/harness/udpstream/vidsrc.py b/harness/udpstream/vidsrc.py index cb18fff..45bc41c 100644 --- a/harness/udpstream/vidsrc.py +++ b/harness/udpstream/vidsrc.py @@ -45,16 +45,15 @@ CHUNK = 1450 #: Datagrams in the terminal's RX ring (``RX_DESC`` in vidstream.c). A frame #: that needs more cannot be received at all: the ring runs out of descriptors #: and the payload drops it. -RING_DATAGRAMS = 16 +RING_DATAGRAMS = 32 -#: Most source rows one frame can carry, from the ring rather than the header -#: field: the first datagram holds the 16-byte header plus a body, the other 15 -#: carry body only, so the ring holds -#: ``(RING_DATAGRAMS-1)*CHUNK + (CHUNK-HEADER_SIZE)`` body bytes = 144 rows of -#: 160. The 24 KB slot alone would allow 153, and the u8 ``rows`` field 255 -- -#: neither is reachable, because the ring is what binds. -MAX_ROWS = (((RING_DATAGRAMS - 1) * CHUNK + (CHUNK - HEADER_SIZE)) - // ROW_BYTES) +#: Most source rows one frame can carry. Two ceilings, and the lower binds: the +#: ring holds ``(RING_DATAGRAMS-1)*CHUNK + (CHUNK-HEADER_SIZE)`` body bytes +#: (289 rows of 160 with a 32-deep ring), and the header's ``rows`` field is one +#: byte (255). So 255 today; the ring was 16 when this was written, when the +#: ring was the binding one at 144. +MAX_ROWS = min(255, (((RING_DATAGRAMS - 1) * CHUNK + (CHUNK - HEADER_SIZE)) + // ROW_BYTES)) class Rows: diff --git a/harness/udpstream/vidstream.c b/harness/udpstream/vidstream.c index 313a1a8..9940f83 100644 --- a/harness/udpstream/vidstream.c +++ b/harness/udpstream/vidstream.c @@ -490,6 +490,32 @@ unsigned int dydx; blit_src = src; blit_daddr = daddr_xy; blit_dydx = dydx; + blit_sptch = 0; /* stretch: every row is the same source */ + vram_blit_b_(); +} + +/* + * blit_rect(src, y, rows, sptch) -- copy `rows` screen rows at once. + * + * The strip path used to issue one PIXBLT per screen row because a stretched + * row has SPTCH = 0, so the source cannot advance. For a picture that really + * has one source row per screen row (stretch = 1) the source DOES advance, and + * a group of rows inside one receive fragment is contiguous: one PIXBLT with + * SPTCH = 1280 bits copies the whole group. Measured: a strip call costs about + * 0.45 ms in MAME regardless of its size, while a tile call costs the same for + * 64 words -- so call count, not word count, is what a frame is charged for. + * A full frame is then ~32 calls (one per datagram) instead of 1,200. + */ +static void blit_rect(src, y, rows, sptch) +unsigned int src; +unsigned int y; +unsigned int rows; +unsigned int sptch; +{ + blit_src = src; + blit_daddr = (y << 16); /* x = 0: a strip row is the full width */ + blit_dydx = (rows << 16) | ROW_PIXELS; + blit_sptch = sptch; vram_blit_b_(); } @@ -566,6 +592,62 @@ unsigned int ty; * The whole burst runs with interrupts masked (spec 8.9) so the resident OS * cannot repaint between two of our source rows and tear the picture. */ +/* + * paint_rect(h) -- the stretch = 1 case, one PIXBLT per fragment. + * + * A strip frame with stretch 1 has exactly one source row per screen row, so + * the source advances by ROW_PIXELS bits per row and a group of rows that sits + * inside one receive fragment is contiguous: one blit copies the whole group. + * With the sender's 1,440-byte chunks (9 rows, chosen so no row straddles a + * datagram) a 288-row sub-frame is 32 blits instead of 288. + * + * Returns 1 when the whole frame was painted, 0 when the fragments do not line + * up on row boundaries -- the caller then falls back to the per-row path, which + * handles a split row by drawing it in pieces. + */ +static int paint_rect(h) +fp_header *h; +{ + unsigned int rows, pos, k, off, nr, rn; + + rows = h->rows; + if (rows * ROW_BYTES != h->payload_len) { + return 0; /* not whole rows: not for us */ + } + for (k = 0; k < rx_nfrag; k++) { + if (rx_frag_off[k] % ROW_BYTES + || rx_frag_len[k] % ROW_BYTES) { + return 0; /* a row straddles: general path */ + } + } + rn = 0; + k = 0; + pos = h->payload_off; + while (rn < rows) { + while (k < rx_nfrag && pos >= rx_frag_off[k] + rx_frag_len[k]) { + k = k + 1; + } + if (k >= rx_nfrag) { + return 1; /* truncated: stop drawing */ + } + if (pos < rx_frag_off[k]) { + return 1; /* a gap: never paint across it */ + } + off = pos - rx_frag_off[k]; + nr = (rx_frag_len[k] - off) / ROW_BYTES; + if (nr > rows - rn) { + nr = rows - rn; + } + if (nr == 0) { + return 1; + } + blit_rect(rx_frag_ptr[k] + off * 8, rn, nr, ROW_PIXELS); + rn = rn + nr; + pos = pos + nr * ROW_BYTES; + } + return 1; +} + static void paint(h) fp_header *h; { @@ -597,6 +679,10 @@ fp_header *h; } vid_dint_(); + if (stretch == 1 && paint_rect(h)) { + vid_eint_(); + return; + } pos = h->payload_off; /* the frame's first body byte */ k = 0; for (r = 0; r < rows; r++) { diff --git a/harness/udpstream/vram_b.asm b/harness/udpstream/vram_b.asm index 6104e1a..ab2f3b3 100644 --- a/harness/udpstream/vram_b.asm +++ b/harness/udpstream/vram_b.asm @@ -35,6 +35,11 @@ ; PMASK (0C0000160h) 0 every plane writable ; CONTROL (0C00000B0h) raster op 0 (replace), CD/RM/RR preserved ; +; _blit_sptch: the source pitch in bits. Zero is the stretch form (every +; source row reads the same address); 1280 is the rectangle form, where each +; source row is one screen row of the picture -- one PIXBLT then copies a whole +; group of rows, which is what makes a full frame ~32 calls instead of ~1,200. +; ; Entry points. The TMS34010 C compiler passes arguments in its own frame- ; pointer scheme, and no hand-written assembly in this repository has ever ; DECODED that frame: the ROM thunks in entry.asm forward the caller's stack @@ -54,6 +59,7 @@ ; resident repaint cannot tear the picture. .ref _blit_src + .ref _blit_sptch .ref _blit_daddr .ref _blit_dydx .text @@ -70,7 +76,7 @@ _vram_blit_b_: move b9, -*sp, 1 move @_blit_src, b0, 1 ; SADDR: packed 1-bpp source row - clr b1 ; SPTCH = 0: every row is the same source row + move @_blit_sptch, b1, 1 ; SPTCH: 0 = stretch, 1280 = rectangle move @_blit_daddr, b2, 1 ; DADDR (XY) movi 800h, b3 ; DPTCH = 2048 bits = 256-byte row pitch movi 04000000h, b4 ; OFFSET = VRAM base