// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria,Stephane Humbert
/***************************************************************************
Sega Zaxxon hardware
***************************************************************************/
#include "emu.h"
#include "video/resnet.h"
#include "includes/zaxxon.h"
/*************************************
*
* Palette conversion
*
*************************************/
void zaxxon_state::zaxxon_palette(palette_device &palette)
{
uint8_t const *const color_prom = memregion("proms")->base();
static constexpr int resistances[3] = { 1000, 470, 220 };
// compute the color output resistor weights
double rweights[3], gweights[3], bweights[2];
compute_resistor_weights(0, 255, -1.0,
3, &resistances[0], rweights, 470, 0,
3, &resistances[0], gweights, 470, 0,
2, &resistances[1], bweights, 470, 0);
// initialize the palette with these colors
for (int i = 0; i < palette.entries(); i++)
{
int bit0, bit1, bit2;
// red component
bit0 = BIT(color_prom[i], 0);
bit1 = BIT(color_prom[i], 1);
bit2 = BIT(color_prom[i], 2);
int const r = combine_weights(rweights, bit0, bit1, bit2);
// green component
bit0 = BIT(color_prom[i], 3);
bit1 = BIT(color_prom[i], 4);
bit2 = BIT(color_prom[i], 5);
int const g = combine_weights(gweights, bit0, bit1, bit2);
// blue component
bit0 = BIT(color_prom[i], 6);
bit1 = BIT(color_prom[i], 7);
int const b = combine_weights(bweights, bit0, bit1);
palette.set_pen_color(i, rgb_t(r, g, b));
}
// color_prom now points to the beginning of the character color codes
m_color_codes = &color_prom[256];
}
/*************************************
*
* Tilemap callbacks
*
*************************************/
TILE_GET_INFO_MEMBER(zaxxon_state::get_bg_tile_info)
{
const uint8_t *source = memregion("tilemap_dat")->base();
int size = memregion("tilemap_dat")->bytes() / 2;
int eff_index = tile_index & (size - 1);
int code = source[eff_index] + 256 * (source[eff_index + size] & 3);
int color = source[eff_index + size] >> 4;
SET_TILE_INFO_MEMBER(1, code, color, 0);
}
TILE_GET_INFO_MEMBER(zaxxon_state::zaxxon_get_fg_tile_info)
{
int sx = tile_index % 32;
int sy = tile_index / 32;
int code = m_videoram[tile_index];
int color = m_color_codes[sx + 32 * (sy / 4)] & 0x0f;
SET_TILE_INFO_MEMBER(0, code, color * 2, 0);
}
TILE_GET_INFO_MEMBER(zaxxon_state::razmataz_get_fg_tile_info)
{
int code = m_videoram[tile_index];
int color = m_color_codes[code] & 0x0f;
SET_TILE_INFO_MEMBER(0, code, color * 2, 0);
}
TILE_GET_INFO_MEMBER(zaxxon_state::congo_get_fg_tile_info)
{
int code = m_videoram[tile_index] + (m_congo_fg_bank << 8);
int color = m_colorram[tile_index] & 0x1f;
SET_TILE_INFO_MEMBER(0, code, color * 2, 0);
}
/*************************************
*
* Video startup
*
*************************************/
void zaxxon_state::video_start_common(tilemap_get_info_delegate &&fg_tile_info)
{
/* reset globals */
m_bg_enable = 0;
m_bg_color = 0;
m_bg_position = 0;
m_fg_color = 0;
m_flip_screen = false;
m_congo_fg_bank = 0;
m_congo_color_bank = 0;
memset(m_congo_custom, 0, sizeof(m_congo_custom));
// create a background and foreground tilemap
m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(zaxxon_state::get_bg_tile_info)), TILEMAP_SCAN_ROWS, 8,8, 32,512);
m_fg_tilemap = &machine().tilemap().create(*m_gfxdecode, std::move(fg_tile_info), TILEMAP_SCAN_ROWS, 8,8, 32,32);
// configure the foreground tilemap
m_fg_tilemap->set_transparent_pen(0);
// register for save states
save_item(NAME(m_bg_enable));
save_item(NAME(m_bg_color));
save_item(NAME(m_bg_position));
save_item(NAME(m_fg_color));
save_item(NAME(m_flip_screen));
}
void zaxxon_state::video_start()
{
video_start_common(tilemap_get_info_delegate(*this, FUNC(zaxxon_state::zaxxon_get_fg_tile_info)));
}
VIDEO_START_MEMBER(zaxxon_state,razmataz)
{
video_start_common(tilemap_get_info_delegate(*this, FUNC(zaxxon_state::razmataz_get_fg_tile_info)));
}
VIDEO_START_MEMBER(zaxxon_state,congo)
{
/* allocate our own spriteram since it is not accessible by the main CPU */
m_spriteram.allocate(0x100);
/* register for save states */
save_item(NAME(m_congo_fg_bank));
save_item(NAME(m_congo_color_bank));
save_item(NAME(m_congo_custom));
video_start_common(tilemap_get_info_delegate(*this, FUNC(zaxxon_state::congo_get_fg_tile_info)));
}
/*************************************
*
* Video latches and controls
*
*************************************/
WRITE_LINE_MEMBER(zaxxon_state::flipscreen_w)
{
/* low bit controls flip; background and sprite flip are handled at render time */
m_flip_screen = !state;
m_fg_tilemap->set_flip(m_flip_screen ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0);
}
WRITE_LINE_MEMBER(zaxxon_state::fg_color_w)
{
/* low bit selects high color palette index */
m_fg_color = state * 0x80;
m_fg_tilemap->set_palette_offset(m_fg_color + (m_congo_color_bank << 8));
}
WRITE8_MEMBER(zaxxon_state::bg_position_w)
{
/* 11 bits of scroll position are stored */
if (offset == 0)
m_bg_position = (m_bg_position & 0x700) | ((data << 0) & 0x0ff);
else
m_bg_position = (m_bg_position & 0x0ff) | ((data << 8) & 0x700);
}
WRITE_LINE_MEMBER(zaxxon_state::bg_color_w)
{
/* low bit selects high color palette index */
m_bg_color = state * 0x80;
}
WRITE_LINE_MEMBER(zaxxon_state::bg_enable_w)
{
/* low bit enables/disables the background layer */
m_bg_enable = state;
}
WRITE_LINE_MEMBER(zaxxon_state::congo_fg_bank_w)
{
/* low bit controls the topmost character bit */
m_congo_fg_bank = state;
m_fg_tilemap->mark_all_dirty();
}
WRITE_LINE_MEMBER(zaxxon_state::congo_color_bank_w)
{
/* low bit controls the topmost bit into the color PROM */
m_congo_color_bank = state;
m_fg_tilemap->set_palette_offset(m_fg_color + (m_congo_color_bank << 8));
}
/*************************************
*
* Foreground tilemap access
*
*************************************/
WRITE8_MEMBER(zaxxon_state::zaxxon_videoram_w)
{
m_videoram[offset] = data;
m_fg_tilemap->mark_tile_dirty(offset);
}
WRITE8_MEMBER(zaxxon_state::congo_colorram_w)
{
m_colorram[offset] = data;
m_fg_tilemap->mark_tile_dirty(offset);
}
/*************************************
*
* Congo Bongo custom sprite DMA
*
*************************************/
WRITE8_MEMBER(zaxxon_state::congo_sprite_custom_w)
{
uint8_t *spriteram = m_spriteram;
m_congo_custom[offset] = data;
/* seems to trigger on a write of 1 to the 4th byte */
if (offset == 3 && data == 0x01)
{
uint16_t saddr = m_congo_custom[0] | (m_congo_custom[1] << 8);
int count = m_congo_custom[2];
/* count cycles (just a guess) */
m_maincpu->adjust_icount(-count * 5);
/* this is just a guess; the chip is hardwired to the spriteram */
while (count-- >= 0)
{
uint8_t daddr = space.read_byte(saddr + 0) * 4;
spriteram[(daddr + 0) & 0xff] = space.read_byte(saddr + 1);
spriteram[(daddr + 1) & 0xff] = space.read_byte(saddr + 2);
spriteram[(daddr + 2) & 0xff] = space.read_byte(saddr + 3);
spriteram[(daddr + 3) & 0xff] = space.read_byte(saddr + 4);
saddr += 0x20;
}
}
}
/*************************************
*
* Background pixmap drawing
*
*************************************/
void zaxxon_state::draw_background(bitmap_ind16 &bitmap, const rectangle &cliprect, int skew)
{
/* only draw if enabled */
if (m_bg_enable)
{
bitmap_ind16 &pixmap = m_bg_tilemap->pixmap();
int colorbase = m_bg_color + (m_congo_color_bank << 8);
int xmask = pixmap.width() - 1;
int ymask = pixmap.height() - 1;
int flipmask = m_flip_screen ? 0xff : 0x00;
int flipoffs = m_flip_screen ? 0x38 : 0x40;
int x, y;
/* the starting X value is offset by 1 pixel (normal) or 7 pixels */
/* (flipped) due to a delay in the loading */
if (!m_flip_screen)
flipoffs -= 1;
else
flipoffs += 7;
/* loop over visible rows */
for (y = cliprect.min_y; y <= cliprect.max_y; y++)
{
uint16_t *dst = &bitmap.pix16(y);
int srcx, srcy, vf;
uint16_t *src;
/* VF = flipped V signals */
vf = y ^ flipmask;
/* base of the source row comes from VF plus the scroll value */
/* this is done by the 3 4-bit adders at U56, U74, U75 */
srcy = vf + ((m_bg_position << 1) ^ 0xfff) + 1;
src = &pixmap.pix16(srcy & ymask);
/* loop over visible columns */
for (x = cliprect.min_x; x <= cliprect.max_x; x++)
{
/* start with HF = flipped H signals */
srcx = x ^ flipmask;
if (skew)
{
/* position within source row is a two-stage addition */
/* first stage is HF plus half the VF, done by the 2 4-bit */
/* adders at U53, U54 */
srcx += ((vf >> 1) ^ 0xff) + 1;
/* second stage is first stage plus a constant based on the flip */
/* value is 0x40 for non-flipped, or 0x38 for flipped */
srcx += flipoffs;
}
/* store the pixel, offset by the color offset */
dst[x] = src[srcx & xmask] + colorbase;
}
}
}
/* if not enabled, fill the background with black */
else
bitmap.fill(m_palette->black_pen(), cliprect);
}
/*************************************
*
* Sprite drawing
*
*************************************/
inline int zaxxon_state::find_minimum_y(uint8_t value, int flip)
{
int flipmask = flip ? 0xff : 0x00;
int flipconst = flip ? 0xef : 0xf1;
int y;
/* the sum of the Y position plus a constant based on the flip state */
/* is added to the current flipped VF; if the top 3 bits are 1, we hit */
/* first find a 16-pixel bucket where we hit */
for (y = 0; y < 256; y += 16)
{
int sum = (value + flipconst + 1) + (y ^ flipmask);
if ((sum & 0xe0) == 0xe0)
break;
}
/* then scan backwards until we no longer match */
while (1)
{
int sum = (value + flipconst + 1) + ((y - 1) ^ flipmask);
if ((sum & 0xe0) != 0xe0)
break;
y--;
}
/* add one line since we draw sprites on the previous line */
return (y + 1) & 0xff;
}
inline int zaxxon_state::find_minimum_x(uint8_t value, int flip)
{
int flipmask = flip ? 0xff : 0x00;
int x;
/* the sum of the X position plus a constant specifies the address within */
/* the line bufer; if we're flipped, we will write backwards */
x = (value + 0xef + 1) ^ flipmask;
if (flipmask)
x -= 31;
return x & 0xff;
}
void zaxxon_state::draw_sprites(bitmap_ind16 &bitmap, const rectangle &cliprect, uint16_t flipxmask, uint16_t flipymask)
{
uint8_t *spriteram = m_spriteram;
gfx_element *gfx = m_gfxdecode->gfx(2);
int flip = m_flip_screen;
int flipmask = flip ? 0xff : 0x00;
int offs;
/* only the lower half of sprite RAM is read during rendering */
for (offs = 0x7c; offs >= 0; offs -= 4)
{
int sy = find_minimum_y(spriteram[offs], flip);
int flipy = (spriteram[offs + (flipymask >> 8)] ^ flipmask) & flipymask;
int flipx = (spriteram[offs + (flipxmask >> 8)] ^ flipmask) & flipxmask;
int code = spriteram[offs + 1];
int color = (spriteram[offs + 2] & 0x1f) + (m_congo_color_bank << 5);
int sx = find_minimum_x(spriteram[offs + 3], flip);
/* draw with 256 pixel offsets to ensure we wrap properly */
gfx->transpen(bitmap,cliprect, code, color, flipx, flipy, sx, sy, 0);
gfx->transpen(bitmap,cliprect, code, color, flipx, flipy, sx, sy - 0x100, 0);
gfx->transpen(bitmap,cliprect, code, color, flipx, flipy, sx - 0x100, sy, 0);
gfx->transpen(bitmap,cliprect, code, color, flipx, flipy, sx - 0x100, sy - 0x100, 0);
}
}
/*************************************
*
* Core video updates
*
*************************************/
uint32_t zaxxon_state::screen_update_zaxxon(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
draw_background(bitmap, cliprect, true);
draw_sprites(bitmap, cliprect, 0x140, 0x180);
m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
return 0;
}
uint32_t zaxxon_state::screen_update_futspy(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
draw_background(bitmap, cliprect, true);
draw_sprites(bitmap, cliprect, 0x180, 0x180);
m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
return 0;
}
uint32_t zaxxon_state::screen_update_razmataz(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
draw_background(bitmap, cliprect, false);
draw_sprites(bitmap, cliprect, 0x140, 0x180);
m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
return 0;
}
uint32_t zaxxon_state::screen_update_congo(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
draw_background(bitmap, cliprect, true);
draw_sprites(bitmap, cliprect, 0x280, 0x180);
m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
return 0;
}