// license:BSD-3-Clause // copyright-holders:Nicola Salmoria /* Taito TC0480SCP --------- Tilemap generator. Manages four background tilemaps with 16x16 tiles fetched from ROM, and one foreground text tilemap with 8x8 tiles fetched from RAM. All four background tilemaps support zooming and rowscroll, and two of them additionally support per-row zooming and column scroll. The five tilemaps are mixed internally (the text tilemap is always on top, the order of the other four is selectable) and output as 16 bits of pixel data. The TC0480SCP uses 0x10000 bytes of RAM. It seems to be able to address up to 0x800000 bytes of ROM (0x10000 tiles) as it has 21 address lines and 32 data lines, but no known game uses more than 0x400000 bytes. Inputs and outputs (based on Gunbuster schematics): - CPU address bus (VA1-VA17) - CPU data bus (VD0-VD15) - CPU control lines (CS, UDS, LDS, R/W, DTACK) - RAM address bus (RA0-RA14) - RAM data bus (RAD0-RAD15) - RAM control lines (RWAH, RWAL, RAOE) - ROM address bus (CH0-CH20) - ROM data bus (RD0-RD31) - Pixel output (SD0-SD15) - Clocks and video sync (HSYNC, HBLANK, VSYNC, VBLANK) Standard memory layout (four 32x32 bg tilemaps, one 64x64 fg tilemap) 0000-0fff BG0 1000-1fff BG1 2000-2fff BG2 3000-3fff BG3 4000-43ff BG0 rowscroll 4400-47ff BG1 rowscroll 4800-4bff BG2 rowscroll 4c00-4fff BG3 rowscroll 5000-53ff BG0 rowscroll low order bytes (see info below) 5400-57ff BG1 rowscroll low order bytes 5800-5bff BG2 rowscroll low order bytes 5c00-5fff BG3 rowscroll low order bytes 6000-63ff BG2 row zoom 6400-67ff BG3 row zoom 6800-6bff BG2 source colscroll 6c00-6fff BG3 source colscroll 7000-bfff unknown/unused? c000-dfff FG0 e000-ffff gfx data for FG0 (4bpp) Double width tilemaps memory layout (four 64x32 bg tilemaps, one 64x64 fg tilemap) 0000-1fff BG0 2000-3fff BG1 4000-5fff BG2 6000-7fff BG3 8000-83ff BG0 rowscroll 8400-87ff BG1 rowscroll 8800-8bff BG2 rowscroll 8c00-8fff BG3 rowscroll 9000-93ff BG0 rowscroll low order bytes (used for accuracy with row zoom or layer zoom) 9400-97ff BG1 rowscroll low order bytes [*] 9800-9bff BG2 rowscroll low order bytes 9c00-9fff BG3 rowscroll low order bytes a000-a3ff BG2 row zoom [+] a400-a7ff BG3 row zoom a800-abff BG2 source colscroll ac00-afff BG3 source colscroll b000-bfff unknown (Slapshot and Superchs poke in TBA OVER error message in FG0 format) c000-dfff FG0 e000-ffff gfx data for FG0 (4bpp) [* Gunbustr suggests that high bytes are irrelevant: it leaves them all zeroed. Superchs is the only game which uses high bytes that aren't the low byte of the main rowscroll (Footchmp/Undrfire have this verified in the code).] [+ Usual row zoom values are 0 - 0x7f. Gunbustr also uses 0x80-d0 approx. Undrfire keeps to the 0-0x7f range but oddly also uses the high byte with a mask of 0x3f. Meaning of this high byte is unknown.] Bg layers tile word layout +0x00 %yx..bbbb cccccccc b=control bits(?) c=color .=unused(?) +0x02 tilenum [y=yflip x=xflip b=unknown seen in Metalb] Control registers 000-001 BG0 x scroll (layer priority order is definable) 002-003 BG1 x scroll 004-005 BG2 x scroll 006-007 BG3 x scroll 008-009 BG0 y scroll 00a-00b BG1 y scroll 00c-00d BG2 y scroll 00e-00f BG3 y scroll 010-011 BG0 zoom (high byte = X zoom, low byte = Y zoom, 012-013 BG1 zoom compression is allowed on Y axis only) 014-015 BG2 zoom 016-017 BG3 zoom 018-019 Text layer x scroll 01a-01b Text layer y scroll 01c-01d Unused (not written) 01e-01f Layer Control register x------- Double width tilemaps (4 bg tilemaps become 64x32, and the memory layout changes). Slapshot changes this on the fly. -x------ Flip screen --x----- unknown Set in Metalb init by whether a byte in prg ROM $7fffe is zero. Subsequently Metalb changes it for some screen layer layouts. Footchmp clears it, Hthero sets it [then both leave it alone]. Deadconx code at $10e2 is interesting, with possible values of: 0x0, 0x20, 0x40, 0x60 poked in (via ram buffer) to control reg, dependent on byte in prg ROM $7fffd and whether screen is flipped. ---xxx-- BG layer priority order ...000.. 0 1 2 3 ...001.. 1 2 3 0 (no evidence of this) ...010.. 2 3 0 1 (no evidence of this) ...011.. 3 0 1 2 ...100.. 3 2 1 0 ...101.. 2 1 0 3 [Gunbustr attract and Metalb (c) screen] ...110.. 1 0 3 2 (no evidence of this) ...111.. 0 3 2 1 ------x- BG3 row zoom enable -------x BG2 row zoom enable 020-021 BG0 dx (provides extra precision to x-scroll, only changed with xscroll) 022-023 BG1 dx 024-025 BG2 dx 026-027 BG3 dx 028-029 BG0 dy (provides extra precision to y-scroll, only changed with yscroll) 02a-02b BG1 dy 02c-02d BG2 dy 02e-02f BG3 dy [see code at $1b4a in Slapshot and $xxxxx in Undrfire for evidence of row areas] */ #include "emu.h" #include "tc0480scp.h" #include "video/taito_helper.h" #include "screen.h" #define TC0480SCP_RAM_SIZE 0x10000 #define TC0480SCP_TOTAL_CHARS 256 DEFINE_DEVICE_TYPE(TC0480SCP, tc0480scp_device, "tc0480scp", "Taito TC0480SCP") tc0480scp_device::tc0480scp_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, TC0480SCP, tag, owner, clock) , device_gfx_interface(mconfig, *this) , m_pri_reg(0) , m_dblwidth(0) , m_gfxlayout(TC0480SCP_LAYOUT_COMMON) , m_x_offset(0) , m_y_offset(0) , m_text_xoffs(0) , m_text_yoffs(0) , m_flip_xoffs(0) , m_flip_yoffs(0) , m_col_base(0) { memset(m_ctrl, 0, sizeof(m_ctrl)); for (int i = 0; i < 4; i++) { m_bgscroll_ram[i] = nullptr; m_rowzoom_ram[i] = nullptr; m_bgcolumn_ram[i] = nullptr; m_bgscrollx[i] = 0; m_bgscrolly[i] = 0; } } /************************************* * * Graphics definitions * *************************************/ GFXDECODE_MEMBER(tc0480scp_device::gfxinfo_default) GFXDECODE_DEVICE(DEVICE_SELF, 0, gfx_16x16x4_packed_lsb, 0, 256) GFXDECODE_END static const gfx_layout bootleg_tilelayout = { 16,16, /* 16*16 tiles */ RGN_FRAC(1,4), 4, /* 4 bits per pixel */ { RGN_FRAC(0,4),RGN_FRAC(1,4),RGN_FRAC(2,4),RGN_FRAC(3,4) }, { STEP16(0,1) }, { STEP16(0,16) }, 16*16 /* every tile takes 128 consecutive bytes */ }; static const gfx_layout bootleg_charlayout = { 8,8, /* 8*8 tiles */ 256, /* the ROMs are mostly empty */ 4, /* 4 bits per pixel */ { RGN_FRAC(0,4),RGN_FRAC(1,4),RGN_FRAC(2,4),RGN_FRAC(3,4) }, { STEP8(0,1) }, { STEP8(0,8) }, 8*8 /* every tile takes 8 consecutive bytes */ }; GFXDECODE_MEMBER(tc0480scp_device::gfxinfo_bootleg) GFXDECODE_DEVICE(DEVICE_SELF, 0, bootleg_tilelayout, 0, 256) GFXDECODE_DEVICE("textrom", 0, bootleg_charlayout, 0, 64) // bootleg should clearly use this instead of the uploaded tiles GFXDECODE_END //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void tc0480scp_device::device_start() { switch (m_gfxlayout) { case TC0480SCP_LAYOUT_COMMON: default: decode_gfx(gfxinfo_default); break; case TC0480SCP_LAYOUT_BOOTLEG: decode_gfx(gfxinfo_bootleg); gfx(1)->set_colorbase(m_col_base); break; } gfx(0)->set_colorbase(m_col_base); static const gfx_layout tc0480scp_charlayout = { 8,8, /* 8*8 characters */ 256, /* 256 characters */ 4, /* 4 bits per pixel */ { 0, 1, 2, 3 }, { 3*4, 2*4, 1*4, 0*4, 7*4, 6*4, 5*4, 4*4 }, { 0*32, 1*32, 2*32, 3*32, 4*32, 5*32, 6*32, 7*32 }, 32*8 /* every sprite takes 32 consecutive bytes */ }; /* Single width versions */ m_tilemap[0][0] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x0000>)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32); m_tilemap[1][0] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x0800>)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32); m_tilemap[2][0] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x1000>)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32); m_tilemap[3][0] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x1800>)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32); m_tilemap[4][0] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_tx_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 64, 64); /* Double width versions */ m_tilemap[0][1] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x0000>)), TILEMAP_SCAN_ROWS, 16, 16, 64, 32); m_tilemap[1][1] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x1000>)), TILEMAP_SCAN_ROWS, 16, 16, 64, 32); m_tilemap[2][1] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x2000>)), TILEMAP_SCAN_ROWS, 16, 16, 64, 32); m_tilemap[3][1] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_bg_tile_info<0x3000>)), TILEMAP_SCAN_ROWS, 16, 16, 64, 32); m_tilemap[4][1] = &machine().tilemap().create(*this, tilemap_get_info_delegate(*this, FUNC(tc0480scp_device::get_tx_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 64, 64); for (int i = 0; i < 2; i++) { m_tilemap[0][i]->set_transparent_pen(0); m_tilemap[1][i]->set_transparent_pen(0); m_tilemap[2][i]->set_transparent_pen(0); m_tilemap[3][i]->set_transparent_pen(0); m_tilemap[4][i]->set_transparent_pen(0); } const int xd = -m_x_offset; const int yd = m_y_offset; /* Metalb and Deadconx have minor screenflip issues: blue planet is off on x axis by 1 and in Deadconx the dark blue screen between stages also seems off by 1 pixel. */ /* It's not possible to get the text scrolldx calculations harmonised with the other layers: xd-2, 315-xd is the next valid pair:- the numbers diverge from xd, 319-xd */ /* Single width offsets */ m_tilemap[0][0]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[0][0]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[1][0]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[1][0]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[2][0]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[2][0]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[3][0]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[3][0]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[4][0]->set_scrolldx(xd - 3, 316 - xd); /* text layer */ m_tilemap[4][0]->set_scrolldy(yd, 256 - yd); /* text layer */ /* Double width offsets */ m_tilemap[0][1]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[0][1]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[1][1]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[1][1]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[2][1]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[2][1]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[3][1]->set_scrolldx(xd, 320 - xd + m_flip_xoffs); m_tilemap[3][1]->set_scrolldy(yd, 256 - yd + m_flip_yoffs); m_tilemap[4][1]->set_scrolldx(xd - 3, 317 - xd); /* text layer */ m_tilemap[4][1]->set_scrolldy(yd, 256 - yd); /* text layer */ for (int i = 0; i < 2; i++) { /* Both sets of bg tilemaps scrollable per pixel row */ m_tilemap[0][i]->set_scroll_rows(512); m_tilemap[1][i]->set_scroll_rows(512); m_tilemap[2][i]->set_scroll_rows(512); m_tilemap[3][i]->set_scroll_rows(512); } m_ram.resize(TC0480SCP_RAM_SIZE / 2); memset(&m_ram[0], 0, TC0480SCP_RAM_SIZE); set_layer_ptrs(); /* create the char set (gfx will then be updated dynamically from RAM) */ if (!gfx(1)) set_gfx(1, std::make_unique(&palette(), tc0480scp_charlayout, (u8 *)&m_ram[0x7000], NATIVE_ENDIAN_VALUE_LE_BE(8,0), 64, m_col_base)); //e000 save_item(NAME(m_ram)); save_item(NAME(m_ctrl)); save_item(NAME(m_bgscrollx)); save_item(NAME(m_bgscrolly)); save_item(NAME(m_pri_reg)); save_item(NAME(m_dblwidth)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void tc0480scp_device::device_reset() { m_dblwidth = 0; for (auto & elem : m_ctrl) elem = 0; } /***************************************************************************** DEVICE HANDLERS *****************************************************************************/ template TILE_GET_INFO_MEMBER(tc0480scp_device::get_bg_tile_info) { const u32 code = m_ram[(2 * tile_index) + 1 + Offset] & 0x7fff; const u16 attr = m_ram[(2 * tile_index) + Offset]; SET_TILE_INFO_MEMBER(0, code, (attr & 0xff), TILE_FLIPYX((attr & 0xc000) >> 14)); } TILE_GET_INFO_MEMBER(tc0480scp_device::get_tx_tile_info) { const u16 attr = m_ram[0x6000 + tile_index]; //c000 SET_TILE_INFO_MEMBER(1, attr & 0xff, ((attr & 0x3f00) >> 8), TILE_FLIPYX((attr & 0xc000) >> 14)); } void tc0480scp_device::set_layer_ptrs() { if (!m_dblwidth) { m_bgscroll_ram[0] = &m_ram[0x2000]; //4000 m_bgscroll_ram[1] = &m_ram[0x2200]; //4400 m_bgscroll_ram[2] = &m_ram[0x2400]; //4800 m_bgscroll_ram[3] = &m_ram[0x2600]; //4c00 m_rowzoom_ram[2] = &m_ram[0x3000]; //6000 m_rowzoom_ram[3] = &m_ram[0x3200]; //6400 m_bgcolumn_ram[2] = &m_ram[0x3400]; //6800 m_bgcolumn_ram[3] = &m_ram[0x3600]; //6c00 } else { m_bgscroll_ram[0] = &m_ram[0x4000]; //8000 m_bgscroll_ram[1] = &m_ram[0x4200]; //8400 m_bgscroll_ram[2] = &m_ram[0x4400]; //8800 m_bgscroll_ram[3] = &m_ram[0x4600]; //8c00 m_rowzoom_ram[2] = &m_ram[0x5000]; //a000 m_rowzoom_ram[3] = &m_ram[0x5200]; //a400 m_bgcolumn_ram[2] = &m_ram[0x5400]; //a800 m_bgcolumn_ram[3] = &m_ram[0x5600]; //ac00 } } u16 tc0480scp_device::ram_r(offs_t offset) { return m_ram[offset]; } void tc0480scp_device::ram_w(offs_t offset, u16 data, u16 mem_mask) { COMBINE_DATA(&m_ram[offset]); if (offset < 0x2000) { m_tilemap[(offset / 0x800)][0]->mark_tile_dirty(((offset % 0x800) / 2)); m_tilemap[(offset / 0x1000)][1]->mark_tile_dirty(((offset % 0x1000) / 2)); } else if (offset < 0x4000) { m_tilemap[(offset / 0x1000)][1]->mark_tile_dirty(((offset % 0x1000) / 2)); } else if (offset < 0x6000) { /* do nothing */ } else if (offset < 0x7000) { m_tilemap[4][0]->mark_tile_dirty((offset - 0x6000)); m_tilemap[4][1]->mark_tile_dirty((offset - 0x6000)); } else if (offset <= 0x7fff) { if (m_gfxlayout != TC0480SCP_LAYOUT_BOOTLEG) gfx(1)->mark_dirty((offset - 0x7000) / 16); } } u16 tc0480scp_device::ctrl_r(offs_t offset) { return m_ctrl[offset]; } void tc0480scp_device::ctrl_w(offs_t offset, u16 data, u16 mem_mask) { int flip = m_pri_reg & 0x40; COMBINE_DATA(&m_ctrl[offset]); data = m_ctrl[offset]; switch (offset) { /* The x offsets of the four bg layers are staggered by intervals of 4 pixels */ case 0x00: /* bg0 x */ if (!flip) data = -data; m_bgscrollx[0] = data; break; case 0x01: /* bg1 x */ data += 4; if (!flip) data = -data; m_bgscrollx[1] = data; break; case 0x02: /* bg2 x */ data += 8; if (!flip) data = -data; m_bgscrollx[2] = data; break; case 0x03: /* bg3 x */ data += 12; if (!flip) data = -data; m_bgscrollx[3] = data; break; case 0x04: /* bg0 y */ if (flip) data = -data; m_bgscrolly[0] = data; break; case 0x05: /* bg1 y */ if (flip) data = -data; m_bgscrolly[1] = data; break; case 0x06: /* bg2 y */ if (flip) data = -data; m_bgscrolly[2] = data; break; case 0x07: /* bg3 y */ if (flip) data = -data; m_bgscrolly[3] = data; break; case 0x08: /* bg0 zoom */ case 0x09: /* bg1 zoom */ case 0x0a: /* bg2 zoom */ case 0x0b: /* bg3 zoom */ break; case 0x0c: /* fg (text) x */ /* Text layer can be offset from bg0 (e.g. Metalb) */ if (!flip) data -= m_text_xoffs; if (flip) data += m_text_xoffs; m_tilemap[4][0]->set_scrollx(0, -data); m_tilemap[4][1]->set_scrollx(0, -data); break; case 0x0d: /* fg (text) y */ /* Text layer can be offset from bg0 (e.g. Slapshot) */ if (!flip) data -= m_text_yoffs; if (flip) data += m_text_yoffs; m_tilemap[4][0]->set_scrolly(0, -data); m_tilemap[4][1]->set_scrolly(0, -data); break; /* offset 0x0e unused */ case 0x0f: /* control register */ { int old_width = (m_pri_reg & 0x80) >> 7; flip = (data & 0x40) ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0; m_pri_reg = data; m_tilemap[0][0]->set_flip(flip); m_tilemap[1][0]->set_flip(flip); m_tilemap[2][0]->set_flip(flip); m_tilemap[3][0]->set_flip(flip); m_tilemap[4][0]->set_flip(flip); m_tilemap[0][1]->set_flip(flip); m_tilemap[1][1]->set_flip(flip); m_tilemap[2][1]->set_flip(flip); m_tilemap[3][1]->set_flip(flip); m_tilemap[4][1]->set_flip(flip); m_dblwidth = (m_pri_reg & 0x80) >> 7; if (m_dblwidth != old_width) /* tilemap width is changing */ { /* Reinitialise layer pointers */ set_layer_ptrs(); } break; } /* Rest are layer specific delta x and y, used while scrolling that layer */ } } void tc0480scp_device::tilemap_update() { int layer, zoom, i, j; int flip = m_pri_reg & 0x40; for (layer = 0; layer < 4; layer++) { m_tilemap[layer][m_dblwidth]->set_scrolly(0, m_bgscrolly[layer]); zoom = 0x10000 + 0x7f - m_ctrl[0x08 + layer]; if (zoom != 0x10000) /* can't use scroll rows when zooming */ { m_tilemap[layer][m_dblwidth]->set_scrollx(0, m_bgscrollx[layer]); } else { for (j = 0; j < 512; j++) { i = m_bgscroll_ram[layer][j]; if (!flip) m_tilemap[layer][m_dblwidth]->set_scrollx(j & 0x1ff, m_bgscrollx[layer] - i); else m_tilemap[layer][m_dblwidth]->set_scrollx(j & 0x1ff, m_bgscrollx[layer] + i); } } } } /********************************************************************* BG0,1 LAYER DRAW TODO ---- Wouldn't work if y needs to be > 255 (i.e. if some game uses a bigger than usual vertical visible area). Refer to tc0080vco custom draw routine for an example of dealing with this. Historical Issues ----------------- 1) bg layers got too far left and down, the greater the magnification. Largely fixed by adding offsets (to sx&y) which get bigger as we zoom in (why we have *zoomx and *zoomy in the calculations). 2) Hthero and Footchmp bg layers behaved differently when zoomed. Fixed by bringing tc0480scp_x&y_offs into calculations. 3) Metalb "TAITO" text in attract too far to the right. Fixed by bringing (layer*4) into offset calculations. But might be possible to avoid this by stepping the scroll deltas for the four layers - currently they are the same, and we have to kludge the offsets in TC0480SCP_ctrl_word_write. 4) Zoom movement was jagged: improved by bringing in scroll delta values... but the results are noticeably imperfect. **********************************************************************/ void tc0480scp_device::bg01_draw(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int layer, int flags, u8 priority, u8 pmask) { /* X-axis zoom offers expansion only: 0 = no zoom, 0xff = max Y-axis zoom offers expansion/compression: 0x7f = no zoom, 0xff = max (0x1a in Footchmp hiscore = shrunk) */ int zoomx = 0x10000 - (m_ctrl[0x08 + layer] & 0xff00); int zoomy = 0x10000 - (((m_ctrl[0x08 + layer] & 0xff) - 0x7f) * 512); if ((zoomx == 0x10000) && (zoomy == 0x10000)) /* no zoom, simple */ { /* Prevent bad things */ m_tilemap[layer][m_dblwidth]->draw(screen, bitmap, cliprect, flags, priority, pmask); } else /* zoom */ { u16 *dst16, *src16; u8 *tsrc; u16 scanline[512]; u32 sx; bitmap_ind16 &srcbitmap = m_tilemap[layer][m_dblwidth]->pixmap(); bitmap_ind8 &flagsbitmap = m_tilemap[layer][m_dblwidth]->flagsmap(); int flip = m_pri_reg & 0x40; int y_index, src_y_index, row_index; int x_index, x_step; u16 screen_width = 512; //cliprect.width(); u16 min_y = cliprect.min_y; u16 max_y = cliprect.max_y; int width_mask = 0x1ff; if (m_dblwidth) width_mask = 0x3ff; if (!flip) { sx = ((m_bgscrollx[layer] + 15 + layer * 4) << 16) + ((255 - (m_ctrl[0x10 + layer] & 0xff)) << 8); sx += (m_x_offset - 15 - layer * 4) * zoomx; y_index = (m_bgscrolly[layer] << 16) + ((m_ctrl[0x14 + layer] & 0xff) << 8); y_index -= (m_y_offset - min_y) * zoomy; } else /* TC0480SCP tiles flipscreen */ { sx = ((-m_bgscrollx[layer] + 15 + layer * 4 + m_flip_xoffs ) << 16) + ((255-(m_ctrl[0x10 + layer] & 0xff)) << 8); sx += (m_x_offset - 15 - layer * 4) * zoomx; y_index = ((-m_bgscrolly[layer] + m_flip_yoffs) << 16) + ((m_ctrl[0x14 + layer] & 0xff) << 8); y_index -= (m_y_offset - min_y) * zoomy; } for (int y = min_y; y <= max_y; y++) { src_y_index = (y_index >> 16) & 0x1ff; /* row areas are the same in flipscreen, so we must read in reverse */ row_index = src_y_index; if (flip) row_index = 0x1ff - row_index; x_index = sx - ((m_bgscroll_ram[layer][row_index] << 16)) - ((m_bgscroll_ram[layer][row_index + 0x800] << 8) & 0xffff); src16 = &srcbitmap.pix16(src_y_index); tsrc = &flagsbitmap.pix8(src_y_index); dst16 = scanline; x_step = zoomx; if (flags & TILEMAP_DRAW_OPAQUE) { for (int i = 0; i < screen_width; i++) { *dst16++ = src16[(x_index >> 16) & width_mask]; x_index += x_step; } } else { for (int i = 0; i < screen_width; i++) { if (tsrc[(x_index >> 16) & width_mask]) *dst16++ = src16[(x_index >> 16) & width_mask]; else *dst16++ = 0x8000; x_index += x_step; } } taitoic_drawscanline(bitmap, cliprect, 0, y, scanline, (flags & TILEMAP_DRAW_OPAQUE) ? false : true, ROT0, screen.priority(), priority, pmask); y_index += zoomy; } } } /**************************************************************** BG2,3 LAYER DRAW TODO ---- Wouldn't work if y needs to be > 255 (i.e. if some game uses a bigger than usual vertical visible area). Refer to tc0080vco custom draw routine for an example of dealing with this. Low order words for overall layer zoom are not really understood. In Metalbj initial text screen zoom you can see they ARE words (not separate bytes); however, I just use the low byte to smooth the zooming sequences. This is noticeably imperfect on the Y axis. Verify behaviour of Taito logo (Gunbustr) against real machine to perfect the row zoom emulation. What do high bytes of row zoom do - if anything - in UndrFire? There is still jaggedness to the road in this game and Superchs. Historical Issues ----------------- Sometimes BG2/3 were misaligned by 1 pixel horizontally: this was due to low order byte of 0 causing different (sx >> 16) than when it was 1-255. To prevent this we use (255-byte) so (sx >> 16) no longer depends on the low order byte. In flipscreen we have to bring in extra offsets, since various games don't have exactly (320-,256-) tilemap scroll deltas in flipscreen. ****************************************************************/ void tc0480scp_device::bg23_draw(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int layer, int flags, u8 priority, u8 pmask) { bitmap_ind16 &srcbitmap = m_tilemap[layer][m_dblwidth]->pixmap(); bitmap_ind8 &flagsbitmap = m_tilemap[layer][m_dblwidth]->flagsmap(); u16 *dst16, *src16; u8 *tsrc; int y_index, src_y_index, row_index, row_zoom; int sx, x_index, x_step; u32 zoomx, zoomy; u16 scanline[512]; int flipscreen = m_pri_reg & 0x40; u16 screen_width = 512; //cliprect.width(); u16 min_y = cliprect.min_y; u16 max_y = cliprect.max_y; int width_mask = 0x1ff; if (m_dblwidth) width_mask = 0x3ff; /* X-axis zoom offers expansion only: 0 = no zoom, 0xff = max Y-axis zoom offers expansion/compression: 0x7f = no zoom, 0xff = max (0x1a in Footchmp hiscore = shrunk) */ zoomx = 0x10000 - (m_ctrl[0x08 + layer] & 0xff00); zoomy = 0x10000 - (((m_ctrl[0x08 + layer] & 0xff) - 0x7f) * 512); if (!flipscreen) { sx = ((m_bgscrollx[layer] + 15 + layer * 4) << 16) + ((255-(m_ctrl[0x10 + layer] & 0xff)) << 8); sx += (m_x_offset - 15 - layer * 4) * zoomx; y_index = (m_bgscrolly[layer] << 16) + ((m_ctrl[0x14 + layer] & 0xff) << 8); y_index -= (m_y_offset - min_y) * zoomy; } else /* TC0480SCP tiles flipscreen */ { sx = ((-m_bgscrollx[layer] + 15 + layer * 4 + m_flip_xoffs ) << 16) + ((255 - (m_ctrl[0x10 + layer] & 0xff)) << 8); sx += (m_x_offset - 15 - layer * 4) * zoomx; y_index = ((-m_bgscrolly[layer] + m_flip_yoffs) << 16) + ((m_ctrl[0x14 + layer] & 0xff) << 8); y_index -= (m_y_offset - min_y) * zoomy; } for (int y = min_y; y <= max_y; y++) { if (!flipscreen) src_y_index = ((y_index>>16) + m_bgcolumn_ram[layer][(y - m_y_offset) & 0x1ff]) & 0x1ff; else /* colscroll area is back to front in flipscreen */ src_y_index = ((y_index>>16) + m_bgcolumn_ram[layer][0x1ff - ((y - m_y_offset) & 0x1ff)]) & 0x1ff; /* row areas are the same in flipscreen, so we must read in reverse */ row_index = src_y_index; if (flipscreen) row_index = 0x1ff - row_index; if (m_pri_reg & (layer - 1)) /* bit0 enables for BG2, bit1 for BG3 */ row_zoom = m_rowzoom_ram[layer][row_index]; else row_zoom = 0; x_index = sx - ((m_bgscroll_ram[layer][row_index] << 16)) - ((m_bgscroll_ram[layer][row_index + 0x800] << 8) & 0xffff); /* flawed calc ?? */ x_index -= (m_x_offset - 0x1f + layer * 4) * ((row_zoom & 0xff) << 8); /* We used to kludge 270 multiply factor, before adjusting x_index instead */ x_step = zoomx; if (row_zoom) /* need to reduce x_step */ { if (!(row_zoom & 0xff00)) x_step -= ((row_zoom * 256) & 0xffff); else /* Undrfire uses the hi byte, why? */ x_step -= (((row_zoom & 0xff) * 256) & 0xffff); } src16 = &srcbitmap.pix16(src_y_index); tsrc = &flagsbitmap.pix8(src_y_index); dst16 = scanline; if (flags & TILEMAP_DRAW_OPAQUE) { for (int i = 0; i < screen_width; i++) { *dst16++ = src16[(x_index >> 16) & width_mask]; x_index += x_step; } } else { for (int i = 0; i < screen_width; i++) { if (tsrc[(x_index >> 16) & width_mask]) *dst16++ = src16[(x_index >> 16) & width_mask]; else *dst16++ = 0x8000; x_index += x_step; } } taitoic_drawscanline(bitmap, cliprect, 0, y, scanline, (flags & TILEMAP_DRAW_OPAQUE) ? false : true, ROT0, screen.priority(), priority, pmask); y_index += zoomy; } } void tc0480scp_device::tilemap_draw(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int layer, int flags, u8 priority, u8 pmask) { /* no layer disable bits */ switch (layer) { case 0: bg01_draw(screen, bitmap, cliprect, 0, flags, priority, pmask); break; case 1: bg01_draw(screen, bitmap, cliprect, 1, flags, priority, pmask); break; case 2: bg23_draw(screen, bitmap, cliprect, 2, flags, priority, pmask); break; case 3: bg23_draw(screen, bitmap, cliprect, 3, flags, priority, pmask); break; case 4: m_tilemap[4][m_dblwidth]->draw(screen, bitmap, cliprect, flags, priority, pmask); break; } } /* For evidence table of TC0480SCP bg layer priorities, refer to mame55 source */ static const u16 tc0480scp_bg_pri_lookup[8] = { 0x0123, 0x1230, 0x2301, 0x3012, 0x3210, 0x2103, 0x1032, 0x0321 }; int tc0480scp_device::get_bg_priority() { return tc0480scp_bg_pri_lookup[(m_pri_reg & 0x1c) >> 2]; } // undrfire.cpp also needs to directly access the priority reg u8 tc0480scp_device::pri_reg_r() { return m_pri_reg; } //------------------------------------------------- // device_post_load - device-specific postload //------------------------------------------------- void tc0480scp_device::device_post_load() { int reg; int flip = m_ctrl[0xf] & 0x40; set_layer_ptrs(); m_tilemap[0][0]->set_flip(flip); m_tilemap[1][0]->set_flip(flip); m_tilemap[2][0]->set_flip(flip); m_tilemap[3][0]->set_flip(flip); m_tilemap[4][0]->set_flip(flip); m_tilemap[0][1]->set_flip(flip); m_tilemap[1][1]->set_flip(flip); m_tilemap[2][1]->set_flip(flip); m_tilemap[3][1]->set_flip(flip); m_tilemap[4][1]->set_flip(flip); reg = m_ctrl[0]; if (!flip) reg = -reg; m_bgscrollx[0] = reg; reg = m_ctrl[1] + 4; if (!flip) reg = -reg; m_bgscrollx[1] = reg; reg = m_ctrl[2] + 8; if (!flip) reg = -reg; m_bgscrollx[2] = reg; reg = m_ctrl[3] + 12; if (!flip) reg = -reg; m_bgscrollx[3] = reg; reg = m_ctrl[4]; if (flip) reg = -reg; m_bgscrolly[0] = reg; reg = m_ctrl[5]; if (flip) reg = -reg; m_bgscrolly[1] = reg; reg = m_ctrl[6]; if (flip) reg = -reg; m_bgscrolly[2] = reg; reg = m_ctrl[7]; if (flip) reg = -reg; m_bgscrolly[3] = reg; reg = m_ctrl[0x0c]; if (!flip) reg -= m_text_xoffs; if (flip) reg += m_text_xoffs; m_tilemap[4][0]->set_scrollx(0, -reg); m_tilemap[4][1]->set_scrollx(0, -reg); reg = m_ctrl[0x0d]; if (!flip) reg -= m_text_yoffs; if (flip) reg += m_text_yoffs; m_tilemap[4][0]->set_scrolly(0, -reg); m_tilemap[4][1]->set_scrolly(0, -reg); }