-- FINDINGS 46.6: does the PACKED layout display correctly? -- -- The claim under test is that a 256-colour frame can be delivered at 1.0 byte -- per pixel instead of 2.0, by writing FULL 16-bit words into GVRAM and letting -- the two 256-colour pages show different halves of the screen: -- -- * CRTC R20 bit 11 ("G-VRAM set to buffer") stops the write path masking the -- CPU's high byte away, so one word write lands TWO picture bytes. -- MAME x68k_crtc.cpp gvram_w; px68k GVRAM_Write's CRTC_Regs[0x28]&8. -- * word value = (page1 << 8) | page0 -- page 0 is the LOW byte, page 1 the -- HIGH byte (gvram_w writes `data & 0x00ff` for page 0 and -- `(data & 0x00ff) << 8` for page 1). -- * page 0 is the OPAQUE bottom layer, unscrolled: it carries screen columns -- 0..127 from the low bytes of words 0..127. -- * page 1 is the TRANSPARENT top layer, X-scrolled by 384 (= -128 mod 512). -- Column c fetches page1[(c + 384) & 511]: -- c = 128..255 -> storage 0..127 -> the high bytes of words 0..127, -- which carry the right half. -- c = 0..127 -> storage 384..511 -> zeroed, so transparent, so the -- opaque page 0 shows through. -- * so words 0..127 of each row hold the WHOLE row: 128 words = 256 bytes for -- 256 pixels. 1.0 byte/pixel against 2.0. -- -- Which page is on top is set by video controller R1 (0xE82500). MEASURED: -- 0x0000 puts page 0 on top (its zeros then cover page 1 and the right half is -- black -- this was the first failure); 0x0002 puts page 1 on top, which is -- what this needs. -- -- The transparency is why the blob is built with --pack-transparent: the top -- page's index 0 is the key, so index 0 must never appear in the picture -- -- black lives at 255 instead. -- -- PASS = the snapshot is pixel-identical to what the ordinary unpacked -- 256-colour path produces, which tools/bench/verify_frame256.py already checks. M=manager.machine; SP=M.devices[":maincpu"].spaces["program"]; SUB=nil local function load_mode() for _,p in ipairs{"../tools/bench/crtc_mode.lua","tools/bench/crtc_mode.lua","crtc_mode.lua"} do local f=loadfile(p); if f then return f() end end error("crtc_mode.lua not found") end local MODE = load_mode() local GVRAM, GPAL = 0xC00000, 0xE82000 local CRTC = 0xE80000 local f=io.open("frame256p.bin","rb"); local d=f:read("a"); f:close() local function B(i) return string.byte(d,i) end local W,H = B(5)*256+B(6), B(7)*256+B(8) local PAL0, PIX0 = 9, 9+256*3 local YOFF = (MODE.height - H) // 2 local BLACK = 255 -- letterbox index, NOT 0 local function pal6(v) return ((v<<2)|(v>>4)) & 0xff end local function pack(r,g,b) local fl = {r>>3, g>>3, b>>3} local best, bestI = nil, 1 for I=0,1 do local e=0 for c=1,3 do local want = ({r,g,b})[c] local dd = pal6((fl[c]<<1)|I) - want e = e + dd*dd end if best==nil or e= YOFF+H then return BLACK end return B(PIX0 + (y-YOFF)*W + x) end local function T() local t=M.time; return t.seconds+t.attoseconds/1e18 end local st,tp="wait",nil SUB = emu.add_machine_frame_notifier(function() local t=T() if st=="wait" then if t<3.0 then return end MODE.apply(SP) -- R20 with bit 11 SET: unmasked full-word writes into GVRAM. local R20 = MODE.r20 | 0x0800 SP:write_u16(CRTC + 20*2, R20) -- Graphic scroll. A 256-colour page is assembled from TWO nibble planes -- with independent scroll registers (px68k Grp_DrawLine8 reads scroll sets -- page*2 and page*2+1), so BOTH of a page's registers must agree or the -- page tears between its low and high nibble. -- R12/R13 = set 0 X/Y, R14/R15 = set 1 -> page 0 -- R16/R17 = set 2 X/Y, R18/R19 = set 3 -> page 1 SP:write_u16(CRTC + 12*2, 0); SP:write_u16(CRTC + 13*2, 0) SP:write_u16(CRTC + 14*2, 0); SP:write_u16(CRTC + 15*2, 0) SP:write_u16(CRTC + 16*2, 384); SP:write_u16(CRTC + 17*2, 0) SP:write_u16(CRTC + 18*2, 384); SP:write_u16(CRTC + 19*2, 0) -- Priority: page 1 ON TOP of page 0, index 0 transparent. Measured on -- MAME (tools/bench/probe_page1.lua): 0x0000 -> page 0 on top, screen right -- half black; 0x0002 -> page 1 on top, right half correct. SP:write_u16(0xE82500, 0x0002) for c=0,255 do local o=PAL0+c*3 SP:write_u16(GPAL+c*2, pack(B(o),B(o+1),B(o+2))) end -- The whole 256x256 screen, packed. Words 0..127 carry columns i (page 0, -- low byte) and i+128 (page 1, high byte). -- -- Words 128..511 are zeroed ONCE and never touched per frame: what matters -- there is page 1's storage at 384..511, which the +384 scroll puts under -- screen columns 0..127 and which must read 0 so the opaque page 0 shows -- through. This is static setup, not part of the 1.0 B/pixel payload. for y=0,MODE.height-1 do local base = GVRAM + y*1024 for i=128,511 do SP:write_u16(base + i*2, 0) end for i=0,127 do SP:write_u16(base + i*2, (pix(y, i+128) << 8) | pix(y, i)) end end -- Buffer mode BLANKS the graphics layer (measured: the screen is black -- while bit 11 is set), so it is a write window, not a display mode. -- Clear it now that the packed words are in and let the display read them. SP:write_u16(CRTC + 20*2, MODE.r20) print(string.format("[PACK] R20=%04X (bit11=%d) scrollX p0=%d p1=%d " .."wrote %d words/row for %d px/row yoff=%d", SP:read_u16(CRTC+20*2), (SP:read_u16(CRTC+20*2)>>11)&1, SP:read_u16(CRTC+12*2), SP:read_u16(CRTC+16*2), 128, 256, YOFF)) st,tp="painted",t elseif st=="painted" and t>tp+0.30 then M.video:snapshot(); print("[PACK] snapshot"); st="done"; M:exit() end end)