// license:BSD-3-Clause
// copyright-holders:Phil Stroffolino
/* Namco System NA1/2 Video Hardware */
/*
Notes:
- fa/fghtatck: Global screen window effect cut one line from top/bottom of screen, especially noticeable with credit display.
It's a btanb according to a PCB video I've seen -AS.
TODO:
- background color currently follows what xday2 tries to set up, it's not sure if this is correct for everything else.
Maybe there's a setting that actually switches between that and black pen?
- non-shadow pixels for sprites flagged to enable shadows have bad colors
- xday2: after choosing life (right) mode and set up date of birth, a joystick screen shows up.
Bottom yellow frame doesn't show up correctly.
- xday2: at the end of life event, there's an unemulated screen shutdown event (might be posirq?)
*/
#include "emu.h"
#include "includes/namcona1.h"
void namcona1_state::tilemap_get_info(
tile_data &tileinfo,
int tile_index,
const u16 *tilemap_videoram,
bool use_4bpp_gfx)
{
const u16 data = tilemap_videoram[tile_index];
const u32 tile = data & 0xfff;
const u8 gfx = use_4bpp_gfx ? 1 : 0;
const u32 color = use_4bpp_gfx ? (data & 0x7000) >> 12 : 0;
if (data & 0x8000)
{
SET_TILE_INFO_MEMBER(gfx, tile, color, TILE_FORCE_LAYER0);
}
else
{
SET_TILE_INFO_MEMBER(gfx, tile, color, 0);
tileinfo.mask_data = &m_shaperam[tile << 3];
}
} /* tilemap_get_info */
TILE_GET_INFO_MEMBER(namcona1_state::tilemap_get_info0)
{
tilemap_get_info(tileinfo, tile_index, 0 * 0x1000 + m_videoram, m_vreg[0xbc / 2] & 0x1);
}
TILE_GET_INFO_MEMBER(namcona1_state::tilemap_get_info1)
{
tilemap_get_info(tileinfo, tile_index, 1 * 0x1000 + m_videoram, m_vreg[0xbc / 2] & 0x2);
}
TILE_GET_INFO_MEMBER(namcona1_state::tilemap_get_info2)
{
tilemap_get_info(tileinfo, tile_index, 2 * 0x1000 + m_videoram, m_vreg[0xbc / 2] & 0x4);
}
TILE_GET_INFO_MEMBER(namcona1_state::tilemap_get_info3)
{
tilemap_get_info(tileinfo, tile_index, 3 * 0x1000 + m_videoram, m_vreg[0xbc / 2] & 0x8);
}
TILE_GET_INFO_MEMBER(namcona1_state::roz_get_info)
{
/* each logical tile is constructed from 4*4 normal tiles */
const bool use_4bpp_gfx = m_vreg[0xbc / 2] & 0x10; /* ? */
const u16 c = tile_index & 0x3f;
const u16 r = tile_index >> 6;
const u16 data = m_videoram[0x8000 / 2 + ((r >> 2) << 6) + (c >> 2)];
const u32 tile = ((data & 0xfbf) + (c & 3) + ((r & 3) << 6)) & 0xfff; /* mask out bit 0x40 - patch for Emeraldia Japan */
const u8 gfx = use_4bpp_gfx ? 1 : 0;
const u32 color = use_4bpp_gfx ? (data & 0x7000) >> 12 : 0;
if (data & 0x8000)
{
SET_TILE_INFO_MEMBER(gfx, tile, color, TILE_FORCE_LAYER0);
}
else
{
SET_TILE_INFO_MEMBER(gfx, tile, color, 0);
tileinfo.mask_data = &m_shaperam[tile << 3];
}
} /* roz_get_info */
/*************************************************************************/
void namcona1_state::videoram_w(offs_t offset, u16 data, u16 mem_mask)
{
COMBINE_DATA(&m_videoram[offset]);
if (offset < 0x8000 / 2)
{
m_bg_tilemap[offset >> 12]->mark_tile_dirty(offset & 0xfff);
}
else if (offset < 0x8800 / 2)
{
if (offset & ~0x30)
{
for (int i = 0; i < 4; i++)
{
m_bg_tilemap[4]->mark_tile_dirty(((offset & 0x3cf) << 2) + i);
m_bg_tilemap[4]->mark_tile_dirty(((offset & 0x3cf) << 2) + 0x40 + i);
m_bg_tilemap[4]->mark_tile_dirty(((offset & 0x3cf) << 2) + 0x80 + i);
m_bg_tilemap[4]->mark_tile_dirty(((offset & 0x3cf) << 2) + 0xc0 + i);
}
}
}
} /* videoram_w */
/*************************************************************************/
void namcona1_state::UpdatePalette(int offset)
{
const u16 data = m_paletteram[offset]; /* -RRRRRGG GGGBBBBB */
/**
* sprites can be configured to use an alternate interpretation of palette ram
* (used in-game in Emeraldia)
*
* RRRGGGBB RRRGGGBB
*/
int r = (((data & 0x00e0) >> 5) + ((data & 0xe000) >> 13) * 2) * 0xff / (0x7 * 3);
int g = (((data & 0x001c) >> 2) + ((data & 0x1c00) >> 10) * 2) * 0xff / (0x7 * 3);
int b = (((data & 0x0003) >> 0) + ((data & 0x0300) >> 8) * 2) * 0xff / (0x3 * 3);
m_palette->set_pen_color(offset + 0x1000, r, g, b);
m_palette->set_pen_color(offset, pal5bit(data >> 10), pal5bit(data >> 5), pal5bit(data >> 0));
}
void namcona1_state::paletteram_w(offs_t offset, u16 data, u16 mem_mask)
{
COMBINE_DATA(&m_paletteram[offset]);
if (m_vreg[0x8e / 2])
{ /* graphics enabled; update palette immediately */
UpdatePalette(offset);
}
else
{
m_palette_is_dirty = 1;
}
}
u16 namcona1_state::gfxram_r(offs_t offset)
{
const u16 type = m_vreg[0x0c / 2];
if (type == 0x03)
{
if (offset < 0x4000)
{
offset *= 2;
return (m_shaperam[offset] << 8) | m_shaperam[offset+1];
}
}
else if (type == 0x02)
{
return m_cgram[offset];
}
return 0x0000;
} /* gfxram_r */
void namcona1_state::gfxram_w(offs_t offset, u16 data, u16 mem_mask)
{
const u16 type = m_vreg[0x0c / 2];
if (type == 0x03)
{
if (offset < 0x4000)
{
offset *= 2;
if (ACCESSING_BITS_8_15)
m_shaperam[offset] = data >> 8;
if (ACCESSING_BITS_0_7)
m_shaperam[offset + 1] = data;
m_gfxdecode->gfx(2)->mark_dirty(offset >> 3);
}
}
else if (type == 0x02)
{
const u16 old_word = m_cgram[offset];
COMBINE_DATA(&m_cgram[offset]);
if (m_cgram[offset] != old_word)
{
m_gfxdecode->gfx(0)->mark_dirty(offset >> 5);
m_gfxdecode->gfx(1)->mark_dirty(offset >> 5);
}
}
} /* gfxram_w */
void namcona1_state::video_start()
{
// normal tilemaps
m_bg_tilemap[0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(namcona1_state::tilemap_get_info0)), TILEMAP_SCAN_ROWS, 8,8,64,64);
m_bg_tilemap[1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(namcona1_state::tilemap_get_info1)), TILEMAP_SCAN_ROWS, 8,8,64,64);
m_bg_tilemap[2] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(namcona1_state::tilemap_get_info2)), TILEMAP_SCAN_ROWS, 8,8,64,64);
m_bg_tilemap[3] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(namcona1_state::tilemap_get_info3)), TILEMAP_SCAN_ROWS, 8,8,64,64);
// roz tilemap
m_bg_tilemap[4] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(namcona1_state::roz_get_info)), TILEMAP_SCAN_ROWS, 8,8,64,64);
m_shaperam.resize(0x8000);
m_gfxdecode->gfx(2)->set_source(&m_shaperam[0]);
save_item(NAME(m_shaperam));
save_item(NAME(m_palette_is_dirty));
} /* video_start */
void namcona1_state::device_post_load()
{
for (int i = 0; i < 3; i++)
m_gfxdecode->gfx(i)->mark_all_dirty();
}
/*************************************************************************/
void namcona1_state::pdraw_tile(
screen_device &screen,
bitmap_ind16 &dest_bmp,
const rectangle &clip,
u32 code,
u32 color,
int sx, int sy,
bool flipx, bool flipy,
u8 priority,
bool bShadow,
bool bOpaque,
u8 gfx_region)
{
gfx_element *gfx = m_gfxdecode->gfx(gfx_region);
gfx_element *mask = m_gfxdecode->gfx(2);
const u16 pal_base = gfx->colorbase() + gfx->granularity() * (color % gfx->colors());
const u8 *source_base = gfx->get_data((code % gfx->elements()));
const u8 *mask_base = mask->get_data((code % mask->elements()));
/* compute sprite increment per screen pixel */
int dx, dy;
int ex = sx + gfx->width();
int ey = sy + gfx->height();
int x_index_base;
int y_index;
if (flipx)
{
x_index_base = gfx->width() - 1;
dx = -1;
}
else
{
x_index_base = 0;
dx = 1;
}
if (flipy)
{
y_index = gfx->height() - 1;
dy = -1;
}
else
{
y_index = 0;
dy = 1;
}
if (sx < clip.min_x)
{ /* clip left */
int pixels = clip.min_x - sx;
sx += pixels;
x_index_base += pixels * dx;
}
if (sy < clip.min_y)
{ /* clip top */
int pixels = clip.min_y - sy;
sy += pixels;
y_index += pixels * dy;
}
/* NS 980211 - fixed incorrect clipping */
if (ex > clip.max_x + 1)
{ /* clip right */
int pixels = ex - clip.max_x - 1;
ex -= pixels;
}
if (ey > clip.max_y + 1)
{ /* clip bottom */
int pixels = ey - clip.max_y - 1;
ey -= pixels;
}
if (ex > sx)
{ /* skip if inner loop doesn't draw anything */
for (int y = sy; y < ey; y++)
{
const u8 *source = source_base + y_index * gfx->rowbytes();
const u8 *mask_addr = mask_base + y_index * mask->rowbytes();
u16 *dest = &dest_bmp.pix16(y);
u8 *pri = &screen.priority().pix8(y);
int x_index = x_index_base;
for (int x = sx; x < ex; x++)
{
if (bOpaque)
{
if (pri[x] <= priority)
{
const u8 c = source[x_index];
dest[x] = pal_base + c;
}
pri[x] = 0xff;
}
else
{
/* sprite pixel is opaque */
if (mask_addr[x_index] != 0)
{
if (pri[x] <= priority)
{
const u8 c = source[x_index];
/* render a shadow only if the sprites color is $F (8bpp) or $FF (4bpp) */
if (bShadow)
{
if ((gfx_region == 0 && color == 0x0f) ||
(gfx_region == 1 && color == 0xff))
{
pen_t *palette_shadow_table = m_palette->shadow_table();
dest[x] = palette_shadow_table[dest[x]];
}
else
{
dest[x] = pal_base + c + 0x1000;
}
}
else
{
dest[x] = pal_base + c;
}
}
pri[x] = 0xff;
}
}
x_index += dx;
}
y_index += dy;
}
}
} /* pdraw_tile */
void namcona1_state::draw_sprites(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
const u16 *source = m_spriteram;
const u16 sprite_control = m_vreg[0x22 / 2];
if (sprite_control & 1) source += 0x400; /* alternate spriteram bank */
for (int which = 0; which < 0x100; which++)
{ /* max 256 sprites */
int bpp4,palbase;
const u16 ypos = source[0]; /* FHHH---Y YYYYYYYY flipy, height, ypos */
const u16 tile = source[1]; /* O???TTTT TTTTTTTT opaque tile */
const u16 color = source[2]; /* FSWWOOOO CCCCBPPP flipx, shadow, width, color offset for 4bpp, color, 4bbp - 8bpp mode, pri*/
const u16 xpos = source[3]; /* -------X XXXXXXXX xpos */
const u8 priority = color & 0x7;
const u16 width = ((color >> 12) & 0x3) + 1;
const u16 height = ((ypos >> 12) & 0x7) + 1;
bool flipy = ypos & 0x8000;
bool flipx = color & 0x8000;
if (color & 8)
{
palbase = (color & 0xf0) | ((color & 0xf00) >> 8);
bpp4 = 1;
}
else
{
palbase = (color & 0xf0) >> 4;
bpp4 = 0;
}
for (int row = 0; row < height; row++)
{
int sy = (ypos & 0x1ff) - 30 + 32;
if (flipy)
{
sy += (height - 1 - row) << 3;
}
else
{
sy += row << 3;
}
sy = ((sy + 8) & 0x1ff) - 8;
for (int col = 0; col < width; col++)
{
int sx = (xpos & 0x1ff) - 10;
if (flipx)
{
sx += (width - 1 - col) << 3;
}
else
{
sx += col << 3;
}
sx = ((sx + 16) & 0x1ff) - 8;
pdraw_tile(screen,
bitmap,
cliprect,
(tile & 0xfff) + (row << 6) + col,
palbase,
sx,sy,flipx,flipy,
priority,
color & 0x4000, /* shadow */
tile & 0x8000, /* opaque */
bpp4);
} /* next col */
} /* next row */
source += 4;
}
} /* draw_sprites */
void namcona1_state::draw_pixel_line(const rectangle &cliprect, u16 *pDest, u8 *pPri, u16 *pSource, const pen_t *paldata)
{
for (int x = 0; x < 38 << 3; x += 2)
{
u16 data = *pSource++;
pPri[x + 0] = 0xff;
pPri[x + 1] = 0xff;
if (x >= cliprect.min_x && x <= cliprect.max_x)
pDest[x+0] = paldata[data>>8];
if (x+1 >= cliprect.min_x && x+1 <= cliprect.max_x)
pDest[x+1] = paldata[data&0xff];
} /* next x */
} /* draw_pixel_line */
void namcona1_state::draw_background(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int which, int primask)
{
if (which == 4)
{
/* draw the roz tilemap all at once */
int incxx = ((s16)m_vreg[0xc0 / 2])<<8;
int incxy = ((s16)m_vreg[0xc2 / 2])<<8;
int incyx = ((s16)m_vreg[0xc4 / 2])<<8;
int incyy = ((s16)m_vreg[0xc6 / 2])<<8;
s16 xoffset = m_vreg[0xc8 / 2];
s16 yoffset = m_vreg[0xca / 2];
int dx = 46; /* horizontal adjust */
int dy = -8; /* vertical adjust */
u32 startx = (xoffset<<12) + incxx * dx + incyx * dy;
u32 starty = (yoffset<<12) + incxy * dx + incyy * dy;
m_bg_tilemap[4]->draw_roz(screen, bitmap, cliprect,
startx, starty, incxx, incxy, incyx, incyy, 0, 0, primask, 0);
}
else
{
/* draw one scanline at a time */
/* scrollx lineselect
* tmap0 ffe000 ffe200
* tmap1 ffe400 ffe600
* tmap2 ffe800 ffea00
* tmap3 ffec00 ffee00
*/
const u16 *scroll = &m_scroll[which * 0x400 / 2];
rectangle clip = cliprect;
int xadjust = 0x3a - which*2;
int scrollx = xadjust;
int scrolly = 0;
for (int line = 0; line < 256; line++)
{
clip.min_y = line;
clip.max_y = line;
int xdata = scroll[line];
int ydata = scroll[line + 0x200 / 2];
if (xdata)
{
/* screenwise linescroll */
if (xdata & 0x4000) // resets current xscroll value (knuckle head)
scrollx = xadjust + xdata;
else
scrollx += xdata & 0x1ff;
}
if (ydata & 0x4000)
{
/* line select: dword offset from 0xff000 or tilemap source line */
scrolly = (ydata - line) & 0x1ff;
}
if (line >= cliprect.min_y && line <= cliprect.max_y)
{
// TODO: not convinced about this trigger
if (xdata == 0xc001)
{
/* This is a simplification, but produces the correct behavior for the only game that uses this
* feature, Numan Athletics.
*/
// TODO: with this it breaks colors in VS Express event, likely pal bank is somewhere else in this mode, assuming it has one anyway?
//const pen_t *paldata = &m_palette->pen(m_bg_tilemap[which]->palette_offset());
const pen_t *paldata = &m_palette->pen(0);
draw_pixel_line(cliprect, &bitmap.pix16(line),
&screen.priority().pix8(line),
m_videoram + ydata + 25,
paldata);
}
else
{
m_bg_tilemap[which]->set_scrollx(0, scrollx);
m_bg_tilemap[which]->set_scrolly(0, scrolly);
m_bg_tilemap[which]->draw(screen, bitmap, clip, 0, primask, 0);
}
}
}
}
} /* draw_background */
// CRTC safety checks
bool namcona1_state::screen_enabled(const rectangle &cliprect)
{
if (cliprect.min_x < 0)
return false;
if (cliprect.max_x < 0)
return false;
if (cliprect.min_x > cliprect.max_x)
return false;
if (cliprect.min_y > cliprect.max_y)
return false;
return true;
}
u32 namcona1_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
// CRTC visible area parameters
// (used mostly by Numan Athletics for global screen window effects, cfr. start of events/title screen to demo mode transitions)
rectangle display_rect;
display_rect.min_x = m_vreg[0x80 / 2]-0x48;
display_rect.max_x = m_vreg[0x82 / 2]-0x48-1;
display_rect.min_y = std::max((int)m_vreg[0x84 / 2], cliprect.min_y);
display_rect.max_y = std::min((int)m_vreg[0x86 / 2] - 1, cliprect.max_y);
/* int flipscreen = m_vreg[0x98 / 2]; (TBA) */
screen.priority().fill(0, cliprect);
// guess for X-Day 2 (flames in attract), seems wrong for Emeraldia but unsure
// bitmap.fill(0xff, cliprect); /* background color? */
bitmap.fill((m_vreg[0xba / 2] & 0xf) << 8, cliprect);
if (m_vreg[0x8e / 2] && screen_enabled(display_rect) == true)
{
/* gfx enabled */
if (m_palette_is_dirty)
{
/* palette updates are delayed when graphics are disabled */
for (int which = 0; which < 0x1000; which++)
{
UpdatePalette(which);
}
m_palette_is_dirty = 0;
}
for (int priority = 0; priority < 8; priority++)
{
for (int which = 4; which >= 0; which--)
{
int pri;
if (which == 4)
{
pri = m_vreg[0xa0 / 2 + 5] & 0x7;
}
else
{
pri = m_vreg[0xa0 / 2 + which] & 0x7;
}
if (pri == priority)
{
draw_background(screen, bitmap, display_rect, which, priority);
}
} /* next tilemap */
} /* next priority level */
draw_sprites(screen, bitmap, display_rect);
} /* gfx enabled */
return 0;
}
/*
roz bad in emeraldaj - blit param?
$efff20: sprite control: 0x3a,0x3e,0x3f
bit 0x01 selects spriteram bank
0 2 4 6 8 a c e
$efff00: src0 src1 src2 dst0 dst1 dst2 BANK [src
$efff10: src] [dst dst] #BYT BLIT eINT 001f 0001
$efff20: 003f 003f IACK ---- ---- ---- ---- ----
...
$efff80: 0050 0170 0020 0100 0170 POS? 0000 GFXE
$efff90: 0000 0001 0002 0003 FLIP ---- ---- ----
$efffa0: PRI PRI PRI PRI ???? PRI 00c0 ---- priority (0..7)
$efffb0: CLR CLR CLR CLR 0001 CLR BPP ---- color (0..f), bpp flag per layer
$efffc0: RZXX RZXY RZYX RZYY RZX0 RZY0 0044 ---- ROZ
Emeralda: 0048 0177 0020 0100 0000 00f0 0000 0001 in-game
0050 0170 0020 0100 0000 00f0 0000 0001 self test
NumanAth: 0050 0170 0020 0100 0170 00d8 0000 0001 in-game
*/