--- a/src/mame/sharp/x68k_crtc.h 2026-08-04 16:07:13.182675756 -0500 +++ b/src/mame/sharp/x68k_crtc.h 2026-08-04 16:32:58.889623301 -0500 @@ -61,7 +61,7 @@ private: // internal helpers void text_copy(unsigned src, unsigned dest, u8 planes); - TIMER_CALLBACK_MEMBER(operation_end); + void do_fast_clear(); void refresh_mode(); TIMER_CALLBACK_MEMBER(hsync); TIMER_CALLBACK_MEMBER(raster_end); @@ -84,6 +84,12 @@ // internal state u16 m_reg[24]; // registers u8 m_operation; // operation port (0xe80481) + // Graphic high-speed clear (operation port bit 1). Writing 1 only + // RESERVES the clear; it starts at the next vertical display start and + // runs for one frame (two when interlaced). Reads of bit 1 report + // m_clear_frames != 0, so software can wait for start then for end. + bool m_clear_standby; // clear reserved, waiting for vertical display start + u8 m_clear_frames; // frames remaining in the running clear (0 = idle) bool m_vblank; // true if in VBlank bool m_hblank; // true if in HBlank u16 m_htotal; // Horizontal Total (in characters) @@ -111,7 +117,6 @@ emu_timer *m_raster_irq_timer; emu_timer *m_vblank_irq_timer; emu_timer *m_raster_end_timer; - emu_timer *m_operation_end_timer; }; class vinas_device : public x68k_crtc_device --- a/src/mame/sharp/x68k_crtc.cpp 2026-08-04 16:07:13.181675761 -0500 +++ b/src/mame/sharp/x68k_crtc.cpp 2026-08-04 16:32:58.889623301 -0500 @@ -25,6 +25,8 @@ , m_clock_69m(0) , m_clock_50m(0) , m_operation(0) + , m_clear_standby(false) + , m_clear_frames(0) , m_vblank(false) , m_hblank(false) , m_htotal(0) @@ -58,7 +60,6 @@ void x68k_crtc_device::device_start() { m_scanline_timer = timer_alloc(FUNC(x68k_crtc_device::hsync), this); - m_operation_end_timer = timer_alloc(FUNC(x68k_crtc_device::operation_end), this); m_raster_end_timer = timer_alloc(FUNC(x68k_crtc_device::raster_end), this); m_raster_irq_timer = timer_alloc(FUNC(x68k_crtc_device::raster_irq), this); m_vblank_irq_timer = timer_alloc(FUNC(x68k_crtc_device::vblank_irq), this); @@ -66,6 +67,8 @@ // save state save_item(NAME(m_reg)); save_item(NAME(m_operation)); + save_item(NAME(m_clear_standby)); + save_item(NAME(m_clear_frames)); save_item(NAME(m_vblank)); save_item(NAME(m_hblank)); save_item(NAME(m_htotal)); @@ -97,6 +100,10 @@ m_reg[7] = 552; // Vertical end m_reg[8] = 27; // Horizontal adjust + // No graphic high-speed clear is reserved or running out of reset. + m_clear_standby = false; + m_clear_frames = 0; + //m_scanline = screen().vpos();// = m_reg[6]; // Vertical start // start VBlank timer @@ -127,15 +134,58 @@ } } -TIMER_CALLBACK_MEMBER(x68k_crtc_device::operation_end) -{ - if(!(m_operation & param)) +// Graphic high-speed clear, performed when the operation actually starts +// (at a vertical display start), not when the register is written. +// +// Range is determined by the screen size, the real screen size and PAGE 0's +// scroll position. When the real screen is 1024x1024 the page select is +// ignored and every page is cleared. +void x68k_crtc_device::do_fast_clear() +{ + // this is based on the docs except for the higher color depth modes which isn't + // explicitly described this way but is likely based on how the plane scroll works + // XXX: not sufficiently tested especially in hires modes + // it seems that it only uses the 0 page scroll registers for where to clear see atomrobo + uint16_t xscr = xscr_gfx(0) & 0x1ff; + uint16_t yscr = yscr_gfx(0) & 0x1ff; + uint16_t mask = 0; + for (int page = 0; page < 4; page++) { - m_operation |= param; - m_operation_end_timer->adjust(attotime::from_msec(5), param); + if (!(m_reg[21] & (1 << page))) + mask |= (0xf << (page * 4)); + } + for (int y = yscr; y < (m_height + yscr); y++) + { + if (is_1024x1024()) + { + if (m_width > 256) + { + for (int x = 0; x < 512; x++) + { + uint16_t data = m_gvram_read_callback(((y * 512) + x) & 0x3ffff, 0xffff); + m_gvram_write_callback(((y * 512) + x) & 0x3ffff, data & mask, 0xffff); + } + } + else + { + for (int x = 0; x < 256; x++) + { + uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff); + m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff); + data = m_gvram_read_callback(((y * 512) + x + xscr + 256) & 0x3ffff, 0xffff); + m_gvram_write_callback(((y * 512) + x + xscr + 256) & 0x3ffff, data & mask, mask); + } + } + } + else + { + for (int x = 0; x < m_width; x++) + { + uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff); + m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff); + } + } } - else - m_operation &= ~param; } void x68k_crtc_device::refresh_mode() @@ -215,9 +265,6 @@ m_hblank = hstate; m_hsync_callback(!m_hblank); - if (m_operation & 8) - text_copy((m_reg[22] & 0xff00) >> 8, (m_reg[22] & 0x00ff), (m_reg[21] & 0xf)); - int scan = screen().vpos(); if (hstate == 1) { @@ -228,6 +275,18 @@ } if (hstate == 0) { + // Text raster copy, done ONCE per horizontal period at the start of + // the horizontal front porch (this callback fires at m_hend, the + // horizontal display end). Operation port bit 3 is a level-sensitive + // switch that the CRTC never clears and that has no busy indication: + // while it is set, one raster block is copied every scanline. Running + // this at the top of the callback instead copied on BOTH hsync edges, + // i.e. twice per scanline -- harmless while R22 is stable, but wrong + // for the documented idiom of leaving bit 3 on and pacing R22 updates + // against hsync. + if (m_operation & 8) + text_copy((m_reg[22] & 0xff00) >> 8, (m_reg[22] & 0x00ff), (m_reg[21] & 0xf)); + if (scan == (m_vtotal - 1)) scan = 0; else @@ -278,6 +337,30 @@ if (val == 0) // V-DISP off { m_vblank = 0; + // This branch is the VDISP 0->1 edge (it asserts m_vdisp_callback + // below), which is where the graphic high-speed clear is serviced. + // A running clear counts down one frame; otherwise a reservation + // made via the operation port starts here. Only one of the two + // happens per frame, so a clear reserved during the frame in which + // a previous clear is still running does not start until that one + // has finished -- which is what makes a write immediately after + // busy drops miss the next frame on hardware. + if (m_clear_frames != 0) + { + m_clear_frames--; + } + else if (m_clear_standby) + { + m_clear_standby = false; + // One vertical period, and no interlace special case: refresh_mode() + // divides the vertical timing by m_vmultiple (0.5 when interlaced), + // so an interlaced screen already spans BOTH fields. One VDISP-to- + // VDISP here is therefore already the two vertical periods an + // interlaced clear occupies on hardware. + m_clear_frames = 1; + if (m_reg[21] & 0xf) + do_fast_clear(); + } vblank_line = m_vend; if (vblank_line > m_vtotal) vblank_line = m_vtotal; @@ -412,54 +495,14 @@ break; case 576: // operation register m_operation = data & ~2; - if ((data & 0x02) && (m_reg[21] & 0xf)) // high-speed graphic screen clear - { - // this is based on the docs except for the higher color depth modes which isn't - // explicitly described this way but is likely based on how the plane scroll works - // XXX: not sufficiently tested especially in hires modes - // it seems that it only uses the 0 page scroll registers for where to clear see atomrobo - uint16_t xscr = xscr_gfx(0) & 0x1ff; - uint16_t yscr = yscr_gfx(0) & 0x1ff; - uint16_t mask = 0; - for (int page = 0; page < 4; page++) - { - if (!(m_reg[21] & (1 << page))) - mask |= (0xf << (page * 4)); - } - for (int y = yscr; y < (m_height + yscr); y++) - { - if (is_1024x1024()) - { - if (m_width > 256) - { - for (int x = 0; x < 512; x++) - { - uint16_t data = m_gvram_read_callback(((y * 512) + x) & 0x3ffff, 0xffff); - m_gvram_write_callback(((y * 512) + x) & 0x3ffff, data & mask, 0xffff); - } - } - else - { - for (int x = 0; x < 256; x++) - { - uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff); - m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff); - data = m_gvram_read_callback(((y * 512) + x + xscr + 256) & 0x3ffff, 0xffff); - m_gvram_write_callback(((y * 512) + x + xscr + 256) & 0x3ffff, data & mask, mask); - } - } - } - else - { - for (int x = 0; x < m_width; x++) - { - uint16_t data = m_gvram_read_callback(((y * 512) + x + xscr) & 0x3ffff, 0xffff); - m_gvram_write_callback(((y * 512) + x + xscr) & 0x3ffff, data & mask, 0xffff); - } - } - } - } - if (data & 0x02) m_operation_end_timer->adjust(attotime::from_msec(5), 0x02); // time taken to do operation is a complete guess. + // Bit 1 only RESERVES the graphic high-speed clear -- it does not + // perform it. Hardware latches the request and starts the clear at the + // next vertical display start; see vblank_irq(). Writing 0 cannot + // cancel or abort a reservation that has already been made, and a + // request made while a clear is already running is dropped rather than + // queued: the reservation only takes when bit 1 reads back as 0. + if ((data & 0x02) && m_clear_frames == 0) + m_clear_standby = true; break; } // LOG("%s CRTC: Wrote %04x to CRTC register %i\n",machine().describe_context(), data, offset); @@ -493,7 +536,14 @@ } } if (offset == 576) // operation port, operation bits are set to 0 when operation is complete - return m_operation; + { + // Bit 1 reads as the graphic high-speed clear's busy state: it is set + // from the vertical display start at which the clear begins until the + // vertical display start one frame later (two when interlaced). The + // documented wait sequence is to poll for bit 1 becoming set (start) + // and then for it becoming clear again (end). + return m_operation | (m_clear_frames != 0 ? 0x02 : 0x00); + } // LOG("CRTC: [%08x] Read from unknown CRTC register %i\n",activecpu_get_pc(),offset); return 0xffff; }