// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*************************************************************************
BattleToads
Video hardware emulation
**************************************************************************/
#include "emu.h"
#include "includes/btoads.h"
#define BT_DEBUG 0
/*************************************
*
* Video system start
*
*************************************/
void btoads_state::video_start()
{
/* initialize the swapped pointers */
m_vram_fg_draw = m_vram_fg0;
m_vram_fg_display = m_vram_fg1;
save_item(NAME(m_xscroll0));
save_item(NAME(m_xscroll1));
save_item(NAME(m_yscroll0));
save_item(NAME(m_yscroll1));
save_item(NAME(m_screen_control));
save_item(NAME(m_sprite_source_offs));
save_item(NAME(m_sprite_dest_offs));
save_item(NAME(m_misc_control));
}
/*************************************
*
* Control registers
*
*************************************/
WRITE16_MEMBER( btoads_state::misc_control_w )
{
COMBINE_DATA(&m_misc_control);
/* bit 3 controls sound reset line */
m_audiocpu->set_input_line(INPUT_LINE_RESET, (m_misc_control & 8) ? CLEAR_LINE : ASSERT_LINE);
}
WRITE16_MEMBER( btoads_state::display_control_w )
{
if (ACCESSING_BITS_8_15)
{
/* allow multiple changes during display */
int scanline = m_screen->vpos();
if (scanline > 0)
m_screen->update_partial(scanline - 1);
/* bit 15 controls which page is rendered and which page is displayed */
if (data & 0x8000)
{
m_vram_fg_draw = m_vram_fg1;
m_vram_fg_display = m_vram_fg0;
}
else
{
m_vram_fg_draw = m_vram_fg0;
m_vram_fg_display = m_vram_fg1;
}
/* stash the remaining data for later */
m_screen_control = data >> 8;
}
}
/*************************************
*
* Scroll registers
*
*************************************/
WRITE16_MEMBER( btoads_state::scroll0_w )
{
/* allow multiple changes during display */
// m_screen->update_now();
m_screen->update_partial(m_screen->vpos());
/* upper bits are Y scroll, lower bits are X scroll */
if (ACCESSING_BITS_8_15)
m_yscroll0 = data >> 8;
if (ACCESSING_BITS_0_7)
m_xscroll0 = data & 0xff;
}
WRITE16_MEMBER( btoads_state::scroll1_w )
{
/* allow multiple changes during display */
// m_screen->update_now();
m_screen->update_partial(m_screen->vpos());
/* upper bits are Y scroll, lower bits are X scroll */
if (ACCESSING_BITS_8_15)
m_yscroll1 = data >> 8;
if (ACCESSING_BITS_0_7)
m_xscroll1 = data & 0xff;
}
/*************************************
*
* Palette RAM
*
*************************************/
WRITE16_MEMBER( btoads_state::paletteram_w )
{
m_tlc34076->write(offset/2, data);
}
READ16_MEMBER( btoads_state::paletteram_r )
{
return m_tlc34076->read(offset/2);
}
/*************************************
*
* Background video RAM
*
*************************************/
WRITE16_MEMBER( btoads_state::vram_bg0_w )
{
COMBINE_DATA(&m_vram_bg0[offset & 0x3fcff]);
}
WRITE16_MEMBER( btoads_state::vram_bg1_w )
{
COMBINE_DATA(&m_vram_bg1[offset & 0x3fcff]);
}
READ16_MEMBER( btoads_state::vram_bg0_r )
{
return m_vram_bg0[offset & 0x3fcff];
}
READ16_MEMBER( btoads_state::vram_bg1_r )
{
return m_vram_bg1[offset & 0x3fcff];
}
/*************************************
*
* Foreground video RAM
*
*************************************/
WRITE16_MEMBER( btoads_state::vram_fg_display_w )
{
if (ACCESSING_BITS_0_7)
m_vram_fg_display[offset] = data;
}
WRITE16_MEMBER( btoads_state::vram_fg_draw_w )
{
if (ACCESSING_BITS_0_7)
m_vram_fg_draw[offset] = data;
}
READ16_MEMBER( btoads_state::vram_fg_display_r )
{
return m_vram_fg_display[offset];
}
READ16_MEMBER( btoads_state::vram_fg_draw_r )
{
return m_vram_fg_draw[offset];
}
/*************************************
*
* Sprite rendering
*
*************************************/
void btoads_state::render_sprite_row(uint16_t *sprite_source, uint32_t address)
{
int flipxor = ((*m_sprite_control >> 10) & 1) ? 0xffff : 0x0000;
int width = (~*m_sprite_control & 0x1ff) + 2;
int color = (~*m_sprite_control >> 8) & 0xf0;
int srcoffs = m_sprite_source_offs << 8;
int srcend = srcoffs + (width << 8);
int srcstep = 0x100 - m_sprite_scale[0];
int dststep = 0x100 - m_sprite_scale[8];
int dstoffs = m_sprite_dest_offs << 8;
/* non-shadow case */
if (!(m_misc_control & 0x10))
{
for ( ; srcoffs < srcend; srcoffs += srcstep, dstoffs += dststep)
{
uint16_t src = sprite_source[(srcoffs >> 10) & 0x1ff];
if (src)
{
src = (src >> (((srcoffs ^ flipxor) >> 6) & 0x0c)) & 0x0f;
if (src)
m_sprite_dest_base[(dstoffs >> 8) & 0x1ff] = src | color;
}
}
}
/* shadow case */
else
{
for ( ; srcoffs < srcend; srcoffs += srcstep, dstoffs += dststep)
{
uint16_t src = sprite_source[(srcoffs >> 10) & 0x1ff];
if (src)
{
src = (src >> (((srcoffs ^ flipxor) >> 6) & 0x0c)) & 0x0f;
if (src)
m_sprite_dest_base[(dstoffs >> 8) & 0x1ff] = color;
}
}
}
m_sprite_source_offs += width;
m_sprite_dest_offs = dstoffs >> 8;
}
/*************************************
*
* Shift register read/write
*
*************************************/
TMS340X0_TO_SHIFTREG_CB_MEMBER(btoads_state::to_shiftreg)
{
address &= ~0x40000000;
/* reads from this first region are usual shift register reads */
if (address >= 0xa0000000 && address <= 0xa3ffffff)
memcpy(shiftreg, &m_vram_fg_display[(address & 0x3fffff) >> 4], 0x200);
/* reads from this region set the sprite destination address */
else if (address >= 0xa4000000 && address <= 0xa7ffffff)
{
m_sprite_dest_base = &m_vram_fg_draw[(address & 0x3fc000) >> 4];
m_sprite_dest_offs = (address & 0x003fff) >> 5;
}
/* reads from this region set the sprite source address */
else if (address >= 0xa8000000 && address <= 0xabffffff)
{
memcpy(shiftreg, &m_vram_fg_data[(address & 0x7fc000) >> 4], 0x400);
m_sprite_source_offs = (address & 0x003fff) >> 3;
}
else
logerror("%s:btoads_to_shiftreg(%08X)\n", machine().describe_context(), address);
}
TMS340X0_FROM_SHIFTREG_CB_MEMBER(btoads_state::from_shiftreg)
{
address &= ~0x40000000;
/* writes to this first region are usual shift register writes */
if (address >= 0xa0000000 && address <= 0xa3ffffff)
memcpy(&m_vram_fg_display[(address & 0x3fc000) >> 4], shiftreg, 0x200);
/* writes to this region are ignored for our purposes */
else if (address >= 0xa4000000 && address <= 0xa7ffffff)
;
/* writes to this region copy standard data */
else if (address >= 0xa8000000 && address <= 0xabffffff)
memcpy(&m_vram_fg_data[(address & 0x7fc000) >> 4], shiftreg, 0x400);
/* writes to this region render the current sprite data */
else if (address >= 0xac000000 && address <= 0xafffffff)
render_sprite_row(shiftreg, address);
else
logerror("%s:btoads_from_shiftreg(%08X)\n", machine().describe_context(), address);
}
/*************************************
*
* Main refresh
*
*************************************/
TMS340X0_SCANLINE_RGB32_CB_MEMBER(btoads_state::scanline_update)
{
uint32_t fulladdr = ((params->rowaddr << 16) | params->coladdr) >> 4;
uint16_t *bg0_base = &m_vram_bg0[(fulladdr + (m_yscroll0 << 10)) & 0x3fc00];
uint16_t *bg1_base = &m_vram_bg1[(fulladdr + (m_yscroll1 << 10)) & 0x3fc00];
uint8_t *spr_base = &m_vram_fg_display[fulladdr & 0x3fc00];
uint32_t *dst = &bitmap.pix32(scanline);
const pen_t *pens = m_tlc34076->pens();
int coladdr = fulladdr & 0x3ff;
int x;
/* for each scanline, switch off the render mode */
switch (m_screen_control & 3)
{
/* mode 0: used in ship level, snake boss, title screen (free play) */
/* priority is:
1. Sprite pixels with high bit clear
2. BG1 pixels with the high bit set
3. Sprites
4. BG1
5. BG0
*/
case 0:
for (x = params->heblnk; x < params->hsblnk; x += 2, coladdr++)
{
uint8_t sprpix = spr_base[coladdr & 0xff];
if (sprpix && !(sprpix & 0x80))
{
dst[x + 0] = pens[sprpix];
dst[x + 1] = pens[sprpix];
}
else
{
uint16_t bg0pix = bg0_base[(coladdr + m_xscroll0) & 0xff];
uint16_t bg1pix = bg1_base[(coladdr + m_xscroll1) & 0xff];
uint8_t sprpix = spr_base[coladdr & 0xff];
if (bg1pix & 0x80)
dst[x + 0] = pens[bg1pix & 0xff];
else if (sprpix)
dst[x + 0] = pens[sprpix];
else if (bg1pix & 0xff)
dst[x + 0] = pens[bg1pix & 0xff];
else
dst[x + 0] = pens[bg0pix & 0xff];
if (bg1pix & 0x8000)
dst[x + 1] = pens[bg1pix >> 8];
else if (sprpix)
dst[x + 1] = pens[sprpix];
else if (bg1pix >> 8)
dst[x + 1] = pens[bg1pix >> 8];
else
dst[x + 1] = pens[bg0pix >> 8];
}
}
break;
/* mode 1: used in snow level, title screen (free play), top part of rolling ball level */
/* priority is:
1. Sprite pixels with high bit clear
2. BG0
3. BG1 pixels with high bit set
4. Sprites
5. BG1
*/
case 1:
for (x = params->heblnk; x < params->hsblnk; x += 2, coladdr++)
{
uint8_t sprpix = spr_base[coladdr & 0xff];
if (sprpix && !(sprpix & 0x80))
{
dst[x + 0] = pens[sprpix];
dst[x + 1] = pens[sprpix];
}
else
{
uint16_t bg0pix = bg0_base[(coladdr + m_xscroll0) & 0xff];
uint16_t bg1pix = bg1_base[(coladdr + m_xscroll1) & 0xff];
if (bg0pix & 0xff)
dst[x + 0] = pens[bg0pix & 0xff];
else if (bg1pix & 0x80)
dst[x + 0] = pens[bg1pix & 0xff];
else if (sprpix)
dst[x + 0] = pens[sprpix];
else
dst[x + 0] = pens[bg1pix & 0xff];
if (bg0pix >> 8)
dst[x + 1] = pens[bg0pix >> 8];
else if (bg1pix & 0x8000)
dst[x + 1] = pens[bg1pix >> 8];
else if (sprpix)
dst[x + 1] = pens[sprpix];
else
dst[x + 1] = pens[bg1pix >> 8];
}
}
break;
/* mode 2: used in EOA screen, jetpack level, first level, high score screen */
/* priority is:
1. Sprites
2. BG1
3. BG0
*/
case 2:
for (x = params->heblnk; x < params->hsblnk; x += 2, coladdr++)
{
uint8_t sprpix = spr_base[coladdr & 0xff];
if (sprpix)
{
dst[x + 0] = pens[sprpix];
dst[x + 1] = pens[sprpix];
}
else
{
uint16_t bg0pix = bg0_base[(coladdr + m_xscroll0) & 0xff];
uint16_t bg1pix = bg1_base[(coladdr + m_xscroll1) & 0xff];
if (bg1pix & 0xff)
dst[x + 0] = pens[bg1pix & 0xff];
else
dst[x + 0] = pens[bg0pix & 0xff];
if (bg1pix >> 8)
dst[x + 1] = pens[bg1pix >> 8];
else
dst[x + 1] = pens[bg0pix >> 8];
}
}
break;
/* mode 3: used in toilet level, toad intros, bottom of rolling ball level */
/* priority is:
1. BG1 pixels with the high bit set
2. Sprite pixels with the high bit set
3. BG1
4. Sprites
5. BG0
*/
case 3:
for (x = params->heblnk; x < params->hsblnk; x += 2, coladdr++)
{
uint16_t bg0pix = bg0_base[(coladdr + m_xscroll0) & 0xff];
uint16_t bg1pix = bg1_base[(coladdr + m_xscroll1) & 0xff];
uint8_t sprpix = spr_base[coladdr & 0xff];
if (bg1pix & 0x80)
dst[x + 0] = pens[bg1pix & 0xff];
else if (sprpix & 0x80)
dst[x + 0] = pens[sprpix];
else if (bg1pix & 0xff)
dst[x + 0] = pens[bg1pix & 0xff];
else if (sprpix)
dst[x + 0] = pens[sprpix];
else
dst[x + 0] = pens[bg0pix & 0xff];
if (bg1pix & 0x8000)
dst[x + 1] = pens[bg1pix >> 8];
else if (sprpix & 0x80)
dst[x + 1] = pens[sprpix];
else if (bg1pix >> 8)
dst[x + 1] = pens[bg1pix >> 8];
else if (sprpix)
dst[x + 1] = pens[sprpix];
else
dst[x + 1] = pens[bg0pix >> 8];
}
break;
}
/* debugging - dump the screen contents to a file */
#if BT_DEBUG
popmessage("screen_control = %02X", m_screen_control & 0x7f);
if (machine().input().code_pressed(KEYCODE_X))
{
char name[10];
FILE *f;
int i;
while (machine().input().code_pressed(KEYCODE_X)) ;
sprintf(name, "disp%d.log", m_xcount++);
f = fopen(name, "w");
fprintf(f, "screen_control = %04X\n\n", m_screen_control << 8);
for (i = 0; i < 3; i++)
{
uint16_t *base = (i == 0) ? (uint16_t *)m_vram_fg_display : (i == 1) ? m_vram_bg0 : m_vram_bg1;
int xscr = (i == 0) ? 0 : (i == 1) ? m_xscroll0 : m_xscroll1;
int yscr = (i == 0) ? 0 : (i == 1) ? m_yscroll0 : m_yscroll1;
int y;
for (y = 0; y < 224; y++)
{
uint32_t offs = ((y + yscr) & 0xff) * TOWORD(0x4000);
for (x = 0; x < 256; x++)
{
uint16_t pix = base[offs + ((x + xscr) & 0xff)];
fprintf(f, "%02X%02X", pix & 0xff, pix >> 8);
if (x % 16 == 15) fprintf(f, " ");
}
fprintf(f, "\n");
}
fprintf(f, "\n\n");
}
fclose(f);
}
logerror("---VBLANK---\n");
#endif
}