// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** Cinemat/Leland driver Leland video hardware ***************************************************************************/ #include "emu.h" #include "includes/leland.h" #include "audio/leland.h" /* constants */ static constexpr int VRAM_SIZE = 0x10000; static constexpr int QRAM_SIZE = 0x10000; /* debugging */ #define LOG_COMM 0 /************************************* * * Scanline callback * *************************************/ TIMER_CALLBACK_MEMBER(leland_state::scanline_callback) { int scanline = param; /* update the DACs */ if (!(m_dac_control & 0x01)) m_dac[0]->write(m_video_ram[(m_last_scanline) * 256 + 160]); if (!(m_dac_control & 0x02)) m_dac[1]->write(m_video_ram[(m_last_scanline) * 256 + 161]); m_last_scanline = scanline; scanline = (scanline+1) % 256; /* come back at the next appropriate scanline */ m_scanline_timer->adjust(m_screen->time_until_pos(scanline), scanline); } /************************************* * * ROM-based tilemap * *************************************/ TILEMAP_MAPPER_MEMBER(leland_state::leland_scan) { /* logical (col,row) -> memory offset */ return (col & 0xff) | ((row & 0x1f) << 8) | ((row & 0xe0) << 9); } TILE_GET_INFO_MEMBER(leland_state::leland_get_tile_info) { int char_bank = ((m_gfxbank >> 4) & 0x03) << 10; int prom_bank = ((m_gfxbank >> 3) & 0x01) << 13; int tile = m_bg_prom[prom_bank | tile_index] | ((tile_index >> 7) & 0x300) | char_bank; SET_TILE_INFO_MEMBER(0, tile, m_bg_prom[prom_bank | tile_index] >> 5, 0); } /************************************* * * RAM-based tilemap * *************************************/ TILEMAP_MAPPER_MEMBER(ataxx_state::ataxx_scan) { /* logical (col,row) -> memory offset */ return (col & 0xff) | ((row & 0x3f) << 8) | ((row & 0x40) << 9); } TILE_GET_INFO_MEMBER(ataxx_state::ataxx_get_tile_info) { uint16_t tile = m_ataxx_qram[tile_index] | ((m_ataxx_qram[0x4000 | tile_index] & 0x7f) << 8); SET_TILE_INFO_MEMBER(0, tile, 0, 0); } /************************************* * * Start video hardware * *************************************/ void leland_state::video_start() { /* tilemap */ m_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(leland_state::leland_get_tile_info),this), tilemap_mapper_delegate(FUNC(leland_state::leland_scan),this), 8, 8, 256, 256); /* allocate memory */ m_video_ram = make_unique_clear(VRAM_SIZE); /* scanline timer */ m_scanline_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(leland_state::scanline_callback),this)); m_scanline_timer->adjust(m_screen->time_until_pos(0)); save_item(NAME(m_gfx_control)); save_pointer(NAME(m_video_ram), VRAM_SIZE); save_item(NAME(m_xscroll)); save_item(NAME(m_yscroll)); save_item(NAME(m_gfxbank)); save_item(NAME(m_last_scanline)); for (uint8_t i = 0; i < 2; i++) { save_item(NAME(m_vram_state[i].m_addr), i); save_item(NAME(m_vram_state[i].m_latch), i); } } void ataxx_state::video_start() { // TODO: further untangle driver so the base class doesn't have stuff that isn't common and this can call the base implementation /* tilemap */ m_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(ataxx_state::ataxx_get_tile_info),this), tilemap_mapper_delegate(FUNC(ataxx_state::ataxx_scan),this), 8, 8, 256, 128); /* first do the standard stuff */ m_video_ram = make_unique_clear(VRAM_SIZE); /* allocate memory */ m_ataxx_qram = make_unique_clear(QRAM_SIZE); save_pointer(NAME(m_video_ram), VRAM_SIZE); save_pointer(NAME(m_ataxx_qram), QRAM_SIZE); save_item(NAME(m_xscroll)); save_item(NAME(m_yscroll)); for (uint8_t i = 0; i < 2; i++) { save_item(NAME(m_vram_state[i].m_addr), i); save_item(NAME(m_vram_state[i].m_latch), i); } } /************************************* * * Scrolling and banking * *************************************/ WRITE8_MEMBER(leland_state::leland_scroll_w) { int scanline = m_screen->vpos(); if (scanline > 0) m_screen->update_partial(scanline - 1); /* adjust the proper scroll value */ switch (offset) { case 0: m_xscroll = (m_xscroll & 0xff00) | (data & 0x00ff); break; case 1: m_xscroll = (m_xscroll & 0x00ff) | ((data << 8) & 0xff00); break; case 2: m_yscroll = (m_yscroll & 0xff00) | (data & 0x00ff); break; case 3: m_yscroll = (m_yscroll & 0x00ff) | ((data << 8) & 0xff00); break; default: fatalerror("Unexpected leland_gfx_port_w\n"); } } WRITE8_MEMBER(leland_state::leland_gfx_port_w) { int scanline = m_screen->vpos(); if (scanline > 0) m_screen->update_partial(scanline - 1); if (m_gfxbank != data) { m_gfxbank = data; m_tilemap->mark_all_dirty(); } } /************************************* * * Video address setting * *************************************/ void leland_state::leland_video_addr_w(address_space &space, int offset, int data, int num) { struct vram_state_data &state = m_vram_state[num]; if (!offset) state.m_addr = (state.m_addr & 0xfe00) | ((data << 1) & 0x01fe); else state.m_addr = ((data << 9) & 0xfe00) | (state.m_addr & 0x01fe); } /************************************* * * Common video RAM read * *************************************/ int leland_state::leland_vram_port_r(address_space &space, int offset, int num) { struct vram_state_data *state = m_vram_state + num; int addr = state->m_addr; int inc = (offset >> 2) & 2; int ret; switch (offset & 7) { case 3: /* read hi/lo (alternating) */ ret = m_video_ram[addr]; addr += inc & (addr << 1); addr ^= 1; break; case 5: /* read hi */ ret = m_video_ram[addr | 1]; addr += inc; break; case 6: /* read lo */ ret = m_video_ram[addr & ~1]; addr += inc; break; default: logerror("%s: Warning: Unknown video port %02x read (address=%04x)\n", machine().describe_context(), offset, addr); ret = 0; break; } state->m_addr = addr; if (LOG_COMM && addr >= 0xf000) logerror("%s:%s comm read %04X = %02X\n", machine().describe_context(), num ? "slave" : "master", addr, ret); return ret; } /************************************* * * Common video RAM write * *************************************/ void leland_state::leland_vram_port_w(address_space &space, int offset, int data, int num) { uint8_t *video_ram = m_video_ram.get(); struct vram_state_data *state = m_vram_state + num; int addr = state->m_addr; int inc = (offset >> 2) & 2; int trans = (offset >> 4) & num; /* don't fully understand why this is needed. Isn't the video RAM just one big RAM? */ int scanline = m_screen->vpos(); if (scanline > 0) m_screen->update_partial(scanline - 1); if (LOG_COMM && addr >= 0xf000) logerror("%s:%s comm write %04X = %02X\n", machine().describe_context(), num ? "slave" : "master", addr, data); /* based on the low 3 bits of the offset, update the destination */ switch (offset & 7) { case 1: /* write hi = data, lo = latch */ video_ram[addr & ~1] = state->m_latch[0]; video_ram[addr | 1] = data; addr += inc; break; case 2: /* write hi = latch, lo = data */ video_ram[addr & ~1] = data; video_ram[addr | 1] = state->m_latch[1]; addr += inc; break; case 3: /* write hi/lo = data (alternating) */ if (trans) { if (!(data & 0xf0)) data |= video_ram[addr] & 0xf0; if (!(data & 0x0f)) data |= video_ram[addr] & 0x0f; } video_ram[addr] = data; addr += inc & (addr << 1); addr ^= 1; break; case 5: /* write hi = data */ state->m_latch[1] = data; if (trans) { if (!(data & 0xf0)) data |= video_ram[addr | 1] & 0xf0; if (!(data & 0x0f)) data |= video_ram[addr | 1] & 0x0f; } video_ram[addr | 1] = data; addr += inc; break; case 6: /* write lo = data */ state->m_latch[0] = data; if (trans) { if (!(data & 0xf0)) data |= video_ram[addr & ~1] & 0xf0; if (!(data & 0x0f)) data |= video_ram[addr & ~1] & 0x0f; } video_ram[addr & ~1] = data; addr += inc; break; default: logerror("%s:Warning: Unknown video port write (address=%04x value=%02x)\n", machine().describe_context(), offset, addr); break; } /* update the address and plane */ state->m_addr = addr; } /************************************* * * Master video RAM read/write * *************************************/ WRITE8_MEMBER(leland_state::leland_master_video_addr_w) { leland_video_addr_w(space, offset, data, 0); } TIMER_CALLBACK_MEMBER(leland_state::leland_delayed_mvram_w) { address_space &space = m_master->space(AS_PROGRAM); int num = (param >> 16) & 1; int offset = (param >> 8) & 0xff; int data = param & 0xff; leland_vram_port_w(space, offset, data, num); } WRITE8_MEMBER(leland_state::leland_mvram_port_w) { machine().scheduler().synchronize(timer_expired_delegate(FUNC(leland_state::leland_delayed_mvram_w),this), 0x00000 | (offset << 8) | data); } READ8_MEMBER(leland_state::leland_mvram_port_r) { return leland_vram_port_r(space, offset, 0); } /************************************* * * Slave video RAM read/write * *************************************/ WRITE8_MEMBER(leland_state::leland_slave_video_addr_w) { leland_video_addr_w(space, offset, data, 1); } WRITE8_MEMBER(leland_state::leland_svram_port_w) { leland_vram_port_w(space, offset, data, 1); } READ8_MEMBER(leland_state::leland_svram_port_r) { return leland_vram_port_r(space, offset, 1); } /************************************* * * Ataxx master video RAM read/write * *************************************/ WRITE8_MEMBER(ataxx_state::ataxx_mvram_port_w) { offset = ((offset >> 1) & 0x07) | ((offset << 3) & 0x08) | (offset & 0x10); machine().scheduler().synchronize(timer_expired_delegate(FUNC(ataxx_state::leland_delayed_mvram_w),this), 0x00000 | (offset << 8) | data); } WRITE8_MEMBER(ataxx_state::ataxx_svram_port_w) { offset = ((offset >> 1) & 0x07) | ((offset << 3) & 0x08) | (offset & 0x10); leland_vram_port_w(space, offset, data, 1); } /************************************* * * Ataxx slave video RAM read/write * *************************************/ READ8_MEMBER(ataxx_state::ataxx_mvram_port_r) { offset = ((offset >> 1) & 0x07) | ((offset << 3) & 0x08) | (offset & 0x10); return leland_vram_port_r(space, offset, 0); } READ8_MEMBER(ataxx_state::ataxx_svram_port_r) { offset = ((offset >> 1) & 0x07) | ((offset << 3) & 0x08) | (offset & 0x10); return leland_vram_port_r(space, offset, 1); } /************************************* * * Refresh routine * *************************************/ uint32_t leland_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { m_tilemap->set_scrollx(0, m_xscroll); m_tilemap->set_scrolly(0, m_yscroll); m_tilemap->draw(screen, bitmap, cliprect, 0); /* for each scanline in the visible region */ for (int y = cliprect.min_y; y <= cliprect.max_y; y++) { uint16_t *const dst = &bitmap.pix16(y); uint8_t const *const fg_src = &m_video_ram[y << 8]; /* for each pixel on the scanline */ for (int x = cliprect.min_x; x <= cliprect.max_x; x++) { /* build the pen, background is d0-d5 */ pen_t pen = dst[x] & 0x3f; /* foreground is d6-d9 */ if (x & 0x01) pen = pen | ((fg_src[x >> 1] & 0x0f) << 6); else pen = pen | ((fg_src[x >> 1] & 0xf0) << 2); dst[x] = pen; } } return 0; } /************************************* * * Machine drivers * *************************************/ static const gfx_layout leland_layout = { 8,8, RGN_FRAC(1,3), 3, { RGN_FRAC(0,3), RGN_FRAC(1,3), RGN_FRAC(2,3) }, { STEP8(0,1) }, { STEP8(0,8) }, 8*8 }; static const gfx_layout ataxx_layout = { 8,8, RGN_FRAC(1,6), 6, { RGN_FRAC(5,6), RGN_FRAC(4,6), RGN_FRAC(3,6), RGN_FRAC(2,6), RGN_FRAC(1,6), RGN_FRAC(0,6) }, { STEP8(0,1) }, { STEP8(0,8) }, 8*8 }; static GFXDECODE_START( gfx_leland ) GFXDECODE_ENTRY( "bg_gfx", 0, leland_layout, 0, 8*16) // *16 is foreground GFXDECODE_END static GFXDECODE_START( gfx_ataxx ) GFXDECODE_ENTRY( "bg_gfx", 0, ataxx_layout, 0, 16) // 16 is foreground GFXDECODE_END MACHINE_CONFIG_START(leland_state::leland_video) MCFG_DEVICE_ADD("gfxdecode", GFXDECODE, "palette", gfx_leland) MCFG_PALETTE_ADD("palette", 1024) MCFG_PALETTE_FORMAT(BBGGGRRR) MCFG_SCREEN_ADD("screen", RASTER) MCFG_SCREEN_VIDEO_ATTRIBUTES(VIDEO_ALWAYS_UPDATE) MCFG_SCREEN_SIZE(40*8, 32*8) MCFG_SCREEN_VISIBLE_AREA(0*8, 40*8-1, 0*8, 30*8-1) MCFG_SCREEN_REFRESH_RATE(60) MCFG_SCREEN_UPDATE_DRIVER(leland_state, screen_update) MCFG_SCREEN_PALETTE("palette") MACHINE_CONFIG_END MACHINE_CONFIG_START(ataxx_state::ataxx_video) leland_video(config); MCFG_DEVICE_REPLACE("gfxdecode", GFXDECODE, "palette", gfx_ataxx) MCFG_PALETTE_MODIFY("palette") MCFG_PALETTE_FORMAT(xxxxRRRRGGGGBBBB) MACHINE_CONFIG_END