// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
Sega 16-bit sprite hardware
***************************************************************************/
#include "emu.h"
#include "sega16sp.h"
#include "segaic16.h"
//****************************************************************************
// CONSTANTS
//****************************************************************************
// device type definition
DEFINE_DEVICE_TYPE(SEGA_HANGON_SPRITES, sega_hangon_sprite_device, "sega_hangon_sprite", "Sega Custom Sprites (Hang On)")
DEFINE_DEVICE_TYPE(SEGA_SHARRIER_SPRITES, sega_sharrier_sprite_device, "sega_sharrier_sprite", "Sega Custom Sprites (Space Harrier)")
DEFINE_DEVICE_TYPE(SEGA_OUTRUN_SPRITES, sega_outrun_sprite_device, "sega_outrun_sprite", "Sega Custom Sprites (Out Run)")
DEFINE_DEVICE_TYPE(SEGA_SYS16A_SPRITES, sega_sys16a_sprite_device, "sega_sys16a_sprite", "Sega System 16A Sprites")
DEFINE_DEVICE_TYPE(BOOTLEG_SYS16A_SPRITES, bootleg_sys16a_sprite_device, "bootleg_sys16a_sprite", "Sega System 16A Sprites (bootleg)")
DEFINE_DEVICE_TYPE(SEGA_SYS16B_SPRITES, sega_sys16b_sprite_device, "sega_sys16b_sprite", "Sega System 16B Sprites")
DEFINE_DEVICE_TYPE(SEGA_XBOARD_SPRITES, sega_xboard_sprite_device, "sega_xboard_sprite", "Sega X-Board Sprites")
DEFINE_DEVICE_TYPE(SEGA_YBOARD_SPRITES, sega_yboard_sprite_device, "sega_yboard_sprite", "Sega Y-Board Sprites")
//****************************************************************************
// DEVICE INTERFACE
//****************************************************************************
//-------------------------------------------------
// sega_16bit_sprite_device -- core constructor
//-------------------------------------------------
sega_16bit_sprite_device::sega_16bit_sprite_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner)
: sprite16_device_ind16(mconfig, type, tag, owner)
, m_flip(false)
{
// default to 1:1 bank mapping
for (int bank = 0; bank < ARRAY_LENGTH(m_bank); bank++)
m_bank[bank] = bank;
}
//-------------------------------------------------
// device_start -- device startup
//-------------------------------------------------
void sega_16bit_sprite_device::device_start()
{
// let the parent do its work
sprite16_device_ind16::device_start();
// save states
save_item(NAME(m_flip));
save_item(NAME(m_bank));
}
//-------------------------------------------------
// draw_write -- trigger a buffer flip
//-------------------------------------------------
WRITE16_MEMBER( sega_16bit_sprite_device::draw_write )
{
uint32_t *src = reinterpret_cast<uint32_t *>(spriteram());
uint32_t *dst = reinterpret_cast<uint32_t *>(buffer());
// swap the halves of the sprite RAM
for (int i = 0; i < spriteram_bytes()/4; i++)
{
uint32_t temp = *src;
*src++ = *dst;
*dst++ = temp;
}
// hack for thunderblade
*spriteram() = 0xffff;
// we will render the sprites when the video update happens
}
//****************************************************************************
// HANG ON-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_hangon_sprite_device -- constructor
//-------------------------------------------------
sega_hangon_sprite_device::sega_hangon_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, SEGA_HANGON_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(189, -1);
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_hangon_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// Hang On-style sprites
//
// Offs Bits Usage
// +0 bbbbbbbb -------- Bottom scanline of sprite - 1
// +0 -------- tttttttt Top scanline of sprite - 1
// +2 bbbb---- -------- Sprite bank
// +2 -------x xxxxxxxx X position of sprite (position $BD is screen position 0)
// +4 pppppppp pppppppp Signed 16-bit pitch value between scanlines
// +6 -ooooooo oooooooo Offset within selected sprite bank
// +6 f------- -------- Horizontal flip: read the data backwards if set
// +8 --cccccc -------- Sprite color palette
// +8 -------- zzzzzz-- Zoom factor
// +8 -------- ------pp Sprite priority
// +E dddddddd dddddddd Scratch space for current address
//
// Final bitmap format:
//
// ----pp-- -------- Sprite priority
// ------cc cccc---- Sprite color palette
// -------- ----llll 4-bit pixel data
//
// Special notes:
//
// There is an interaction between the horizonal flip bit and the offset.
// The offset is maintained as a 16-bit value, even though only the lower
// 15 bits are used for the address. The top bit is used to control flipping.
// This means that if the low 15 bits overflow during rendering, the sprite
// data will be read backwards after the overflow. This is important to
// emulate correctly as many games make use of this feature to render sprites
// at the beginning of a bank.
//
// render the sprites in order
const uint16_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x10000;
const uint8_t *zoom = memregion("zoom")->base();
uint16_t *ramend = spriteram() + spriteram_elements();
for (uint16_t *data = spriteram(); data < ramend; data += 8)
{
// fetch the bottom; stop when we get something out of range
int bottom = data[0] >> 8;
if (bottom > 0xf0)
break;
// extract remaining parameters
int top = data[0] & 0xff;
int bank = m_bank[(data[1] >> 12) & 0xf];
int xpos = data[1] & 0x1ff;
int pitch = int16_t(data[2]);
uint16_t addr = data[3];
int colpri = (((data[4] >> 8) & 0x3f) << 4) | (((data[4] >> 0) & 0x3) << 10);
int vzoom = (data[4] >> 2) & 0x3f;
int hzoom = vzoom << 1;
// initialize the end address to the start address
data[7] = addr;
// if top greater than/equal to bottom, or invalid bank, punt
if (top >= bottom || bank == 255)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint16_t *spritedata = spritebase + 0x8000 * bank;
// determine the starting zoom address and mask
int zaddr = (vzoom & 0x38) << 5;
int zmask = 1 << (vzoom & 7);
// loop from top to bottom
int minx = xpos;
int maxx = cliprect.min_x - 1;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
for (int y = top; y < bottom; y++)
{
// advance a row
addr += pitch;
// if the zoom bit says so, add pitch a second time
if (zoom[zaddr++] & zmask)
addr += pitch;
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int xacc = 0x00;
int x;
// note that the System 16A sprites have a design flaw that allows the address
// to carry into the flip flag, which is the topmost bit -- it is very important
// to emulate this as the games compensate for it
// non-flipped case
if (!(addr & 0x8000))
{
// start at the word before because we preincrement below
data[7] = addr - 1;
for (x = xpos; x <= cliprect.max_x; )
{
uint16_t pixels = spritedata[++data[7] & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data[7] = addr + 1;
for (x = xpos; x <= cliprect.max_x; )
{
uint16_t pixels = spritedata[--data[7] & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (y < miny) miny = y;
maxy = y;
}
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// SPACE HARRIER-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_sharrier_sprite_device -- constructor
//-------------------------------------------------
sega_sharrier_sprite_device::sega_sharrier_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, SEGA_SHARRIER_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(189, -1);
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_sharrier_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// Space Harrier-style sprites
//
// Offs Bits Usage
// +0 bbbbbbbb -------- Bottom scanline of sprite - 1
// +0 -------- tttttttt Top scanline of sprite - 1
// +2 bbbb---- -------- Sprite bank
// +2 -------x xxxxxxxx X position of sprite (position $BD is screen position 0)
// +4 s------- -------- Sprite shadow disable (0=enable, 1=disable)
// +4 -p------ -------- Sprite priority
// +4 --cccccc -------- Sprite color palette
// +4 -------- -ppppppp Signed 7-bit pitch value between scanlines
// +6 f------- -------- Horizontal flip: read the data backwards if set
// +6 -ooooooo oooooooo Offset within selected sprite bank
// +8 --zzzzzz -------- Horizontal zoom factor
// +8 -------- --zzzzzz Vertical zoom factor
// +E dddddddd dddddddd Scratch space for current address
//
// Final bitmap format:
//
// ----s--- -------- Sprite shadow disable
// -----p-- -------- Sprite priority
// ------cc cccc---- Sprite color palette
// -------- ----llll 4-bit pixel data
//
// Special notes:
//
// There is an interaction between the horizonal flip bit and the offset.
// The offset is maintained as a 16-bit value, even though only the lower
// 15 bits are used for the address. The top bit is used to control flipping.
// This means that if the low 15 bits overflow during rendering, the sprite
// data will be read backwards after the overflow. This is important to
// emulate correctly as many games make use of this feature to render sprites
// at the beginning of a bank.
//
// render the sprites in order
const uint32_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x20000;
const uint8_t *zoom = memregion("zoom")->base();
uint16_t *ramend = spriteram() + spriteram_elements();
for (uint16_t *data = spriteram(); data < ramend; data += 8)
{
// fetch the bottom; stop when we get something out of range
int bottom = data[0] >> 8;
if (bottom > 0xf0)
break;
// extract remaining parameters
int top = data[0] & 0xff;
int bank = m_bank[(data[1] >> 12) & 0x7];
int xpos = data[1] & 0x1ff;
int colpri = ((data[2] >> 8) & 0xff) << 4;
int pitch = int16_t(data[2] << 9) >> 9;
uint16_t addr = data[3];
int hzoom = ((data[4] >> 8) & 0x3f) << 1;
int vzoom = (data[4] >> 0) & 0x3f;
// initialize the end address to the start address
data[7] = addr;
// if top greater than/equal to bottom, or invalid bank, punt
if (top >= bottom || bank == 255)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint32_t *spritedata = spritebase + 0x8000 * bank;
// determine the starting zoom address and mask
int zaddr = (vzoom & 0x38) << 5;
int zmask = 1 << (vzoom & 7);
// loop from top to bottom
int minx = xpos;
int maxx = cliprect.min_x - 1;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
for (int y = top; y < bottom; y++)
{
// advance a row
addr += pitch;
// if the zoom bit says so, add pitch a second time
if (zoom[zaddr++] & zmask)
addr += pitch;
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int xacc = 0x00;
int x;
// note that the System 16A sprites have a design flaw that allows the address
// to carry into the flip flag, which is the topmost bit -- it is very important
// to emulate this as the games compensate for it
// non-flipped case
if (!(addr & 0x8000))
{
// start at the word before because we preincrement below
data[7] = addr - 1;
for (x = xpos; x <= cliprect.max_x; )
{
uint32_t pixels = spritedata[++data[7] & 0x7fff];
// draw 8 pixels
int pix;
pix = (pixels >> 28) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 24) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 20) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 16) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data[7] = addr + 1;
for (x = xpos; x <= cliprect.max_x; )
{
uint32_t pixels = spritedata[--data[7] & 0x7fff];
// draw 8 pixels
int pix;
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 16) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 20) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 24) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
pix = (pixels >> 28) & 0xf; xacc = (xacc & 0xff) + hzoom; if (xacc < 0x100) { if (x >= cliprect.min_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x++; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (y < miny) miny = y;
maxy = y;
}
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// SYSTEM 16A-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_sys16a_sprite_device -- constructor
//-------------------------------------------------
sega_sys16a_sprite_device::sega_sys16a_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, SEGA_SYS16A_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(189, -1, -189, -1);
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_sys16a_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// System 16A-style sprites
//
// Offs Bits Usage
// +0 bbbbbbbb -------- Bottom scanline of sprite - 1
// +0 -------- tttttttt Top scanline of sprite - 1
// +2 -------x xxxxxxxx X position of sprite (position $BD is screen position 0)
// +4 pppppppp pppppppp Signed 16-bit pitch value between scanlines
// +6 -ooooooo oooooooo Offset within selected sprite bank
// +6 f------- -------- Horizontal flip: read the data backwards if set
// +8 --cccccc -------- Sprite color palette
// +8 -------- -bbb---- Sprite bank
// +8 -------- ------pp Sprite priority
// +E dddddddd dddddddd Scratch space for current address
//
// Final bitmap format:
//
// ----pp-- -------- Sprite priority
// ------cc cccc---- Sprite color palette
// -------- ----llll 4-bit pixel data
//
// Special notes:
//
// There is an interaction between the horizonal flip bit and the offset.
// The offset is maintained as a 16-bit value, even though only the lower
// 15 bits are used for the address. The top bit is used to control flipping.
// This means that if the low 15 bits overflow during rendering, the sprite
// data will be read backwards after the overflow. This is important to
// emulate correctly as many games make use of this feature to render sprites
// at the beginning of a bank.
//
// render the sprites in order
const uint16_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x10000;
uint16_t *ramend = spriteram() + spriteram_elements();
for (uint16_t *data = spriteram(); data < ramend; data += 8)
{
// fetch the bottom; stop when we get something out of range
int bottom = data[0] >> 8;
if (bottom > 0xf0)
break;
// extract remaining parameters
int top = data[0] & 0xff;
int xpos = data[1] & 0x1ff;
int pitch = int16_t(data[2]);
uint16_t addr = data[3];
int colpri = (((data[4] >> 8) & 0x3f) << 4) | (((data[4] >> 0) & 0x3) << 10);
int bank = m_bank[(data[4] >> 4) & 0x7];
// initialize the end address to the start address
data[7] = addr;
// if top greater than/equal to bottom, or invalid bank, punt
if (top >= bottom || bank == 255)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint16_t *spritedata = spritebase + 0x8000 * bank;
// adjust positions for screen flipping
int xdelta = 1;
if (m_flip)
{
int temp = top;
top = 224 - bottom;
bottom = 224 - temp;
xpos = 320 - xpos;
xdelta = -1;
set_origin(m_xoffs_flipped, m_yoffs_flipped);
}
else
{
set_origin(m_xoffs, m_yoffs);
}
// loop from top to bottom
int minx = xpos;
int maxx = xpos;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
for (int y = top; y < bottom; y++)
{
// advance a row
addr += pitch;
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int x;
// note that the System 16A sprites have a design flaw that allows the address
// to carry into the flip flag, which is the topmost bit -- it is very important
// to emulate this as the games compensate for it
// non-flipped case
if (!(addr & 0x8000))
{
// start at the word before because we preincrement below
data[7] = addr - 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[++data[7] & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 12) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 8) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 4) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 0) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data[7] = addr + 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[--data[7] & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 0) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 4) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 8) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 12) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y < miny) miny = y;
maxy = y;
}
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// BOOTLEG SYSTEM 16A-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// bootleg_sys16a_sprite_device -- constructor
//-------------------------------------------------
bootleg_sys16a_sprite_device::bootleg_sys16a_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, BOOTLEG_SYS16A_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
m_addrmap[0] = 0;
m_addrmap[1] = 1;
m_addrmap[2] = 2;
m_addrmap[3] = 3;
m_addrmap[4] = 4;
m_addrmap[5] = 5;
m_addrmap[6] = 6;
m_addrmap[7] = 7;
set_local_origin(189, -1);
}
//-------------------------------------------------
// set_remap -- configure sprite address
// remapping
//-------------------------------------------------
void bootleg_sys16a_sprite_device::set_remap(uint8_t offs0, uint8_t offs1, uint8_t offs2, uint8_t offs3, uint8_t offs4, uint8_t offs5, uint8_t offs6, uint8_t offs7)
{
m_addrmap[0] = offs0;
m_addrmap[1] = offs1;
m_addrmap[2] = offs2;
m_addrmap[3] = offs3;
m_addrmap[4] = offs4;
m_addrmap[5] = offs5;
m_addrmap[6] = offs6;
m_addrmap[7] = offs7;
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void bootleg_sys16a_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// Bootleg System 16A-style sprites
//
// These are identical to regular System 16A sprites (see above), with two exceptions:
//
// 1. Addresses within each sprite entry are generally shuffled relative
// to the original, and
//
// 2. The pitch increment happens at the end, not at the beginning of
// the loop.
//
// render the sprites in order
const uint16_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x10000;
uint16_t *ramend = spriteram() + spriteram_elements();
for (uint16_t *data = spriteram(); data < ramend; data += 8)
{
// fetch the bottom; stop when we get something out of range
int bottom = data[m_addrmap[0]] >> 8;
if (bottom > 0xf0)
break;
// extract remaining parameters
int top = data[m_addrmap[0]] & 0xff;
int xpos = data[m_addrmap[1]] & 0x1ff;
int pitch = int16_t(data[m_addrmap[2]]);
uint16_t addr = data[m_addrmap[3]];
int colpri = (((data[m_addrmap[4]] >> 8) & 0x3f) << 4) | (((data[m_addrmap[4]] >> 0) & 0x3) << 10);
int bank = m_bank[(data[m_addrmap[4]] >> 4) & 0x7];
// initialize the end address to the start address
uint16_t &data7 = data[m_addrmap[7]];
data7 = addr;
// if top greater than/equal to bottom, or invalid bank, punt
if (top >= bottom || bank == 255)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint16_t *spritedata = spritebase + 0x8000 * bank;
// adjust positions for screen flipping
int xdelta = 1;
if (m_flip)
{
int temp = top;
top = 224 - bottom;
bottom = 224 - temp;
xpos = 320 - xpos;
xdelta = -1;
set_origin(m_xoffs_flipped, m_yoffs_flipped);
}
else
{
set_origin(m_xoffs, m_yoffs);
}
// loop from top to bottom
int minx = xpos;
int maxx = xpos;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
for (int y = top; y < bottom; y++)
{
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int x;
// note that the System 16A sprites have a design flaw that allows the address
// to carry into the flip flag, which is the topmost bit -- it is very important
// to emulate this as the games compensate for it
// non-flipped case
if (!(addr & 0x8000))
{
// start at the word before because we preincrement below
data7 = addr - 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[++data7 & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 12) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 8) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 4) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 0) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data7 = addr + 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[--data7 & 0x7fff];
// draw four pixels
int pix;
pix = (pixels >> 0) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 4) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 8) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
pix = (pixels >> 12) & 0xf; if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta;
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y < miny) miny = y;
maxy = y;
}
// advance a row - must be done at the end on the bootlegs!
addr += pitch;
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// SYSTEM 16B-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_sys16b_sprite_device -- constructor
//-------------------------------------------------
sega_sys16b_sprite_device::sega_sys16b_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, SEGA_SYS16B_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(184, 0x00, -184, 0);
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_sys16b_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// System 16B-style sprites
//
// Offs Bits Usage
// +0 bbbbbbbb -------- Bottom scanline of sprite - 1
// +0 -------- tttttttt Top scanline of sprite - 1
// +2 -------x xxxxxxxx X position of sprite (position $BD is screen position 0)
// +2 ---iiii- -------- Sprite/sprite priority for Y-board
// +4 e------- -------- Signify end of sprite list
// +4 -h------ -------- Hide this sprite
// +4 -------f -------- Horizontal flip: read the data backwards if set
// +4 -------- pppppppp Signed 8-bit pitch value between scanlines
// +6 oooooooo oooooooo Offset within selected sprite bank
// +8 ----bbbb -------- Sprite bank
// +8 -------- pp------ Sprite priority, relative to tilemaps
// +8 -------- --cccccc Sprite color palette
// +A ------vv vvv----- Vertical zoom factor (0 = full size, 0x10 = half size)
// +A -------- ---hhhhh Horizontal zoom factor (0 = full size, 0x10 = half size)
// +E dddddddd dddddddd Scratch space for current address
//
// Final bitmap format:
//
// iiii---- -------- Sprite/sprite priority for Y-board
// ----pp-- -------- Sprite priority
// ------cc cccc---- Sprite color palette
// -------- ----llll 4-bit pixel data
//
// Note that the zooming described below is 100% accurate to the real board.
//
// render the sprites in order
const uint16_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x20000;
uint16_t *ramend = spriteram() + spriteram_elements();
for (uint16_t *data = spriteram(); data < ramend; data += 8)
{
// stop when we hit the end of sprite list
if (data[2] & 0x8000)
break;
// extract parameters
int bottom = data[0] >> 8;
int top = data[0] & 0xff;
int xpos = data[1] & 0x1ff;
int hide = data[2] & 0x4000;
int flip = data[2] & 0x100;
int pitch = int8_t(data[2] & 0xff);
uint16_t addr = data[3];
int bank = m_bank[(data[4] >> 8) & 0xf];
int colpri = ((data[4] & 0xff) << 4) | (((data[1] >> 9) & 0xf) << 12);
int vzoom = (data[5] >> 5) & 0x1f;
int hzoom = data[5] & 0x1f;
const uint16_t *spritedata;
xpos &= 0x1ff;
// initialize the end address to the start address
data[7] = addr;
// if hidden, or top greater than/equal to bottom, or invalid bank, punt
if (hide || top >= bottom || bank == 255)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
spritedata = spritebase + 0x10000 * bank;
// reset the yzoom counter
data[5] &= 0x03ff;
// adjust positions for screen flipping
int xdelta = 1;
if (m_flip)
{
int temp = top;
top = 224 - bottom;
bottom = 224 - temp;
xpos = 320 - xpos;
xdelta = -1;
set_origin(m_xoffs_flipped, m_yoffs_flipped);
}
else
{
set_origin(m_xoffs, m_yoffs);
}
// loop from top to bottom
int minx = xpos;
int maxx = xpos;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
for (int y = top; y < bottom; y++)
{
// advance a row
addr += pitch;
// accumulate zoom factors; if we carry into the high bit, skip an extra row
data[5] += vzoom << 10;
if (data[5] & 0x8000)
{
addr += pitch;
data[5] &= ~0x8000;
}
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int x;
// compute the initial X zoom accumulator; this is verified on the real PCB
int xacc = 4 * hzoom;
// non-flipped case
if (!flip)
{
// start at the word before because we preincrement below
data[7] = addr - 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[++data[7]];
// draw four pixels
int pix;
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data[7] = addr + 1;
for (x = xpos; ((xpos - x) & 0x1ff) != 1; )
{
uint16_t pixels = spritedata[--data[7]];
// draw four pixels
int pix;
pix = (pixels >> 0) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 4) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 8) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
pix = (pixels >> 12) & 0xf; xacc = (xacc & 0x3f) + hzoom; if (xacc < 0x40) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; }
// stop if the last pixel in the group was 0xf
if (pix == 15)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y < miny) miny = y;
maxy = y;
}
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// OUT RUN/X-BOARD-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_outrun_sprite_device -- constructor
//-------------------------------------------------
sega_outrun_sprite_device::sega_outrun_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_outrun_sprite_device(mconfig, SEGA_OUTRUN_SPRITES, tag, owner, clock, false)
{
}
sega_outrun_sprite_device::sega_outrun_sprite_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, bool xboard_variant)
: sega_16bit_sprite_device(mconfig, type, tag, owner)
, m_is_xboard(xboard_variant)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(xboard_variant ? 190 : 189, 0x00);
}
//-------------------------------------------------
// sega_xboard_sprite_device -- constructor
//-------------------------------------------------
sega_xboard_sprite_device::sega_xboard_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_outrun_sprite_device(mconfig, SEGA_XBOARD_SPRITES, tag, owner, clock, true)
{
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_outrun_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// Out Run/X-Board-style sprites
//
// Offs Bits Usage
// +0 e------- -------- Signify end of sprite list
// +0 -h-h---- -------- Hide this sprite if either bit is set
// +0 ----bbb- -------- Sprite bank
// +0 -------t tttttttt Top scanline of sprite + 256
// +2 oooooooo oooooooo Offset within selected sprite bank
// +4 ppppppp- -------- Signed 7-bit pitch value between scanlines
// +4 -------x xxxxxxxx X position of sprite (position $BE is screen position 0)
// +6 -s------ -------- Enable shadows
// +6 --pp---- -------- Sprite priority, relative to tilemaps
// +6 ------vv vvvvvvvv Vertical zoom factor (0x200 = full size, 0x100 = half size, 0x300 = 2x size)
// +8 y------- -------- Render from top-to-bottom (1) or bottom-to-top (0) on screen
// +8 -f------ -------- Horizontal flip: read the data backwards if set
// +8 --x----- -------- Render from left-to-right (1) or right-to-left (0) on screen
// +8 ------hh hhhhhhhh Horizontal zoom factor (0x200 = full size, 0x100 = half size, 0x300 = 2x size)
// +E dddddddd dddddddd Scratch space for current address
//
// Out Run only:
// +A hhhhhhhh -------- Height in scanlines - 1
// +A -------- -ccccccc Sprite color palette
//
// X-Board only:
// +A ----hhhh hhhhhhhh Height in scanlines - 1
// +C -------- cccccccc Sprite color palette
//
// Final bitmap format:
//
// -s------ -------- Shadow control
// --pp---- -------- Sprite priority
// ----cccc cccc---- Sprite color palette
// -------- ----llll 4-bit pixel data
//
set_origin(m_xoffs, m_yoffs);
// render the sprites in order
const uint32_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x40000;
uint16_t *ramend = buffer() + spriteram_elements();
for (uint16_t *data = buffer(); data < ramend; data += 8)
{
// stop when we hit the end of sprite list
if (data[0] & 0x8000)
break;
// extract parameters
int hide = (data[0] & 0x5000);
int bank = (data[0] >> 9) & 7;
int top = (data[0] & 0x1ff) - 0x100;
uint16_t addr = data[1];
int pitch = int16_t((data[2] >> 1) | ((data[4] & 0x1000) << 3)) >> 8;
int xpos = data[2] & 0x1ff;
int vzoom = data[3] & 0x7ff;
int ydelta = (data[4] & 0x8000) ? 1 : -1;
int flip = (~data[4] >> 14) & 1;
int xdelta = (data[4] & 0x2000) ? 1 : -1;
int hzoom = data[4] & 0x7ff;
int height = (m_is_xboard ? (data[5] & 0xfff) : (data[5] >> 8)) + 1;
int colpri = ((m_is_xboard ? (data[6] & 0xff) : (data[5] & 0x7f)) << 4) | (((data[3] >> 12) & 7) << 12);
// adjust X coordinate
// note: the threshhold below is a guess. If it is too high, rachero will draw garbage
// If it is too low, smgp won't draw the bottom part of the road
if (xpos < 0x80 && xdelta < 0)
xpos += 0x200;
// initialize the end address to the start address
data[7] = addr;
// if hidden, punt
if (hide)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint32_t *spritedata = spritebase + 0x10000 * bank;
// clamp to a maximum of 8x (not 100% confirmed)
if (vzoom < 0x40) vzoom = 0x40;
if (hzoom < 0x40) hzoom = 0x40;
// loop from top to bottom
int minx = xpos;
int maxx = xpos;
int miny = cliprect.max_y + 1;
int maxy = cliprect.min_y - 1;
int yacc = 0;
int ytarget = top + ydelta * height;
for (int y = top; y != ytarget; y += ydelta)
{
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int xacc = 0;
int x;
// non-flipped case
if (!flip)
{
// start at the word before because we preincrement below
data[7] = addr - 1;
for (x = xpos; (xdelta > 0 && x <= cliprect.max_x) || (xdelta < 0 && x >= cliprect.min_x); )
{
uint32_t pixels = spritedata[++data[7]];
// draw four pixels
int pix;
pix = (pixels >> 28) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 24) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 20) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 16) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 12) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 8) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 4) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 0) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
// stop if the second-to-last pixel in the group was 0xf
if ((pixels & 0x000000f0) == 0x000000f0)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
data[7] = addr + 1;
for (x = xpos; (xdelta > 0 && x <= cliprect.max_x) || (xdelta < 0 && x >= cliprect.min_x); )
{
uint32_t pixels = spritedata[--data[7]];
// draw four pixels
int pix;
pix = (pixels >> 0) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 4) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 8) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 12) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 16) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 20) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 24) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
pix = (pixels >> 28) & 0xf; while (xacc < 0x200) { if (x >= cliprect.min_x && x <= cliprect.max_x && pix != 0 && pix != 15) dest[x] = colpri | pix; x += xdelta; xacc += hzoom; } xacc -= 0x200;
// stop if the second-to-last pixel in the group was 0xf
if ((pixels & 0x0f000000) == 0x0f000000)
break;
}
}
// update bounds
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y < miny) miny = y;
if (y > maxy) maxy = y;
}
// accumulate zoom factors; if we carry into the high bit, skip an extra row
yacc += vzoom;
addr += pitch * (yacc >> 9);
yacc &= 0x1ff;
}
// mark dirty
if (minx <= maxx && miny <= maxy)
mark_dirty(minx, maxx, miny, maxy);
}
}
//****************************************************************************
// Y BOARD-STYLE SPRITES
//****************************************************************************
//-------------------------------------------------
// sega_yboard_sprite_device -- constructor
//-------------------------------------------------
sega_yboard_sprite_device::sega_yboard_sprite_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: sega_16bit_sprite_device(mconfig, SEGA_YBOARD_SPRITES, tag, owner)
, m_sprite_region_ptr(*this, DEVICE_SELF)
{
set_local_origin(0x600, 0x600);
}
//-------------------------------------------------
// draw -- render the sprites within the cliprect
//-------------------------------------------------
void sega_yboard_sprite_device::draw(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
//
// Y-Board-style sprites
//
// Offs Bits Usage
// +0 e------- -------- Signify end of sprite list
// +0 -----iii iiiiiiii Address of indirection table (/16)
// +2 bbbb---- -------- Upper 4 bits of bank index
// +2 ----xxxx xxxxxxxx X position of sprite (position $600 is screen position 0)
// +4 bbbb---- -------- Lower 4 bits of bank index
// +4 ----yyyy yyyyyyyy Y position of sprite (position $600 is screen position 0)
// +6 oooooooo oooooooo Offset within selected sprite bank
// +8 hhhhhhhh hhhhhhhh Height of sprite
// +A -y------ -------- Render from top-to-bottom (1) or bottom-to-top (0) on screen
// +A --f----- -------- Horizontal flip: read the data backwards if set
// +A ---x---- -------- Render from left-to-right (1) or right-to-left (0) on screen
// +A -----zzz zzzzzzzz Zoom factor
// +C -ccc---- -------- Sprite color
// +C ----rrrr -------- Sprite priority
// +C -------- pppppppp Signed 8-bit pitch value between scanlines
// +E ----nnnn nnnnnnnn Index of next sprite
//
// Final bitmap format:
//
// ccc----- -------- Sprite color
// ---rrrr- -------- Sprite priority
// -------i iiiiiiii Indirected color data
//
// In addition to these parameters, the sprite area is clipped using scanline extents
// stored for every pair of scanlines in the rotation RAM. It's a bit of a cheat for us
// to poke our nose into the rotation structure, but there are no known cases of Y-board
// sprites without rotation RAM.
//
set_origin(m_xoffs, m_yoffs);
// clear out any scanlines we might be using
const uint16_t *rotatebase = m_segaic16_rotate[0].buffer ? m_segaic16_rotate[0].buffer.get() : m_segaic16_rotate[0].rotateram;
rotatebase -= yorigin();
for (int y = cliprect.min_y; y <= cliprect.max_y; y++)
if (!(rotatebase[y & ~1] & 0xc000))
memset(&bitmap.pix(y, cliprect.min_x), 0xff, cliprect.width() * sizeof(uint16_t));
// reset the visited list
uint8_t visited[0x1000];
memset(visited, 0, sizeof(visited));
// render the sprites in order
const uint64_t *spritebase = &m_sprite_region_ptr[0];
uint8_t numbanks = m_sprite_region_ptr.bytes() / 0x80000;
int next = 0;
for (uint16_t *data = spriteram(); !(data[0] & 0x8000) && !visited[next]; data = spriteram() + next * 8)
{
int hide = (data[0] & 0x5000);
const uint16_t *indirect = spriteram() + ((data[0] & 0x7ff) << 4);
int bank = ((data[1] >> 8) & 0x10) | ((data[2] >> 12) & 0x0f);
int xpos = data[1] & 0xfff;
int top = data[2] & 0xfff;
uint16_t addr = data[3];
int height = data[4];
int ydelta = (data[5] & 0x4000) ? 1 : -1;
int flip = (~data[5] >> 13) & 1;
int xdelta = (data[5] & 0x1000) ? 1 : -1;
int zoom = data[5] & 0x7ff;
int colpri = (data[6] << 1) & 0xfe00;
int pitch = int8_t(data[6]);
// note that we've visited this entry and get the offset of the next one
visited[next] = 1;
next = data[7] & 0xfff;
// if hidden, or invalid height, punt
if (hide || height == 0)
continue;
// clamp to within the memory region size
if (numbanks)
bank %= numbanks;
const uint64_t *spritedata = spritebase + 0x10000 * bank;
// clamp to a maximum of 8x (not 100% confirmed)
if (zoom == 0) zoom = 1;
// loop from top to bottom
int dminx = xpos;
int dmaxx = xpos;
int dminy = cliprect.max_y + 1;
int dmaxy = cliprect.min_y - 1;
int ytarget = top + ydelta * height;
int yacc = 0;
for (int y = top; y != ytarget; y += ydelta)
{
// skip drawing if not within the cliprect
if (y >= cliprect.min_y && y <= cliprect.max_y)
{
uint16_t *dest = &bitmap.pix(y);
int minx = rotatebase[y & ~1];
int maxx = rotatebase[y | 1];
int xacc = 0;
// bit 0x8000 from rotate RAM means that Y is above the top of the screen
if ((minx & 0x8000) && ydelta < 0)
break;
// bit 0x4000 from rotate RAM means that Y is below the bottom of the screen
if ((minx & 0x4000) && ydelta > 0)
break;
// if either bit is set, skip the rest for this scanline
if (!(minx & 0xc000))
{
// clamp min/max to the cliprect
if (minx < cliprect.min_x)
minx = cliprect.min_x;
if (maxx > cliprect.max_x)
maxx = cliprect.max_x;
// non-flipped case
int x;
if (!flip)
{
// start at the word before because we preincrement below
uint16_t offs = addr - 1;
for (x = xpos; (xdelta > 0 && x <= maxx) || (xdelta < 0 && x >= minx); )
{
uint64_t pixels = spritedata[++offs];
// draw 16 pixels
int pix, ind;
pix = (pixels >> 60) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 56) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 52) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 48) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 44) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 40) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 36) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 32) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 28) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 24) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 20) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 16) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 12) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 8) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 4) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 0) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
// stop if the last pixel in the group was 0xf
if (pix == 0x0f)
break;
}
}
// flipped case
else
{
// start at the word after because we predecrement below
uint16_t offs = addr + 1;
for (x = xpos; (xdelta > 0 && x <= maxx) || (xdelta < 0 && x >= minx); )
{
uint64_t pixels = spritedata[--offs];
// draw 16 pixels
int pix, ind;
pix = (pixels >> 0) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 4) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 8) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 12) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 16) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 20) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 24) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 28) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 32) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 36) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 40) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 44) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 48) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 52) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 56) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
pix = (pixels >> 60) & 0xf; ind = indirect[pix]; while (xacc < 0x200) { if (x >= minx && x <= maxx && ind < 0x1fe) dest[x] = colpri | ind; x += xdelta; xacc += zoom; } xacc -= 0x200;
// stop if the last pixel in the group was 0xf
if (pix == 0x0f)
break;
}
}
// update bounds
if (x > dmaxx) dmaxx = x;
if (x < dminx) dminx = x;
if (y < dminy) dminy = y;
if (y > dmaxy) dmaxy = y;
}
}
// accumulate zoom factors; if we carry into the high bit, skip an extra row
yacc += zoom;
addr += pitch * (yacc >> 9);
yacc &= 0x1ff;
}
// mark dirty
if (dminx <= dmaxx && dminy <= dmaxy)
mark_dirty(dminx, dmaxx, dminy, dmaxy);
}
}