// license:BSD-3-Clause
// copyright-holders:Bryan McPhail
/*****************************************************************************
Irem M90 system. There is 1 video chip - NANAO GA-25, it produces
2 tilemaps and sprites. 16 control bytes:
0: Playfield 1 X scroll
2: Playfield 1 Y scroll
4: Playfield 2 X scroll
6: Playfield 2 Y scroll
8: Bit 0x01 - unknown (set by hasamu)
10: Playfield 1 control
Bits0x03 - Playfield 1 VRAM base
Bit 0x04 - Playfield 1 width (0 is 64 tiles, 0x4 is 128 tiles)
Bit 0x10 - Playfield 1 disable
Bit 0x20 - Playfield 1 rowscroll enable
Bit 0x40 - Playfield 1 y-offset table enable
12: Playfield 2 control
Bits0x03 - Playfield 2 VRAM base
Bit 0x04 - Playfield 2 width (0 is 64 tiles, 0x4 is 128 tiles)
Bit 0x10 - Playfield 2 disable
Bit 0x20 - Playfield 2 rowscroll enable
Bit 0x40 - Playfield 2 y-offset table enable
14: Bits0x03 - Sprite/Tile Priority (related to sprite color)
Emulation by Bryan McPhail, mish@tendril.co.uk, thanks to Chris Hardy!
*****************************************************************************/
#include "emu.h"
#include "includes/m90.h"
#include "screen.h"
#include <algorithm>
TILE_GET_INFO_MEMBER(m90_state::get_tile_info)
{
uint16_t *vram = (uint16_t*)tilemap.user_data();
int tile,color;
tile=vram[tile_index<<1];
color=vram[(tile_index<<1)|1];
SET_TILE_INFO_MEMBER(0,
tile,
color&0xf,
TILE_FLIPYX((color & 0xc0) >> 6));
tileinfo.category = (color & 0x30) ? 1 : 0;
}
void m90_state::common_tilemap_init()
{
m_pf_layer[0][0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(m90_state::get_tile_info)), TILEMAP_SCAN_ROWS, 8,8, 64,64);
m_pf_layer[0][1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(m90_state::get_tile_info)), TILEMAP_SCAN_ROWS, 8,8, 128,64);
m_pf_layer[1][0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(m90_state::get_tile_info)), TILEMAP_SCAN_ROWS, 8,8, 64,64);
m_pf_layer[1][1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(m90_state::get_tile_info)), TILEMAP_SCAN_ROWS, 8,8, 128,64);
// fix for bootlegs
m_pf_layer[0][0]->set_user_data(&m_video_data[0x0000]);
m_pf_layer[0][1]->set_user_data(&m_video_data[0x0000]);
m_pf_layer[1][0]->set_user_data(&m_video_data[0x4000]);
m_pf_layer[1][1]->set_user_data(&m_video_data[0x4000]);
}
void m90_state::video_start()
{
common_tilemap_init();
m_pf_layer[0][0]->set_transparent_pen(0);
m_pf_layer[0][1]->set_transparent_pen(0);
m_last_pf[0] = 0;
m_last_pf[1] = 2;
}
VIDEO_START_MEMBER(m90_state,bomblord)
{
common_tilemap_init();
m_pf_layer[1][0]->set_transparent_pen(0);
m_pf_layer[1][1]->set_transparent_pen(0);
m_pf_layer[0][0]->set_transparent_pen(0);
m_pf_layer[0][1]->set_transparent_pen(0);
}
VIDEO_START_MEMBER(m90_state,dynablsb)
{
common_tilemap_init();
m_pf_layer[1][0]->set_transparent_pen(0);
m_pf_layer[1][1]->set_transparent_pen(0);
}
void m90_state::draw_sprites(screen_device &screen, bitmap_ind16 &bitmap,const rectangle &cliprect)
{
uint16_t *spriteram = m_video_data + 0xee00/2;
int offs;
for (offs = 0x1f2/2; offs >= 0; offs -= 3)
{
int x,y,sprite,colour,fx,fy,y_multi,i;
sprite = spriteram[offs+1];
colour = (spriteram[offs+0] >> 9) & 0x0f;
y = spriteram[offs+0] & 0x1ff;
x = spriteram[offs+2] & 0x1ff;
x = x - 16;
y = 512 - y;
fx = (spriteram[offs+2] >> 8) & 0x02;
fy = (spriteram[offs+0] >> 8) & 0x80;
y_multi = 1 << ((spriteram[offs+0] & 0x6000) >> 13);
y -= 16 * y_multi;
for (i = 0;i < y_multi;i++)
if (m_video_control_data[7] & 0x01)
m_gfxdecode->gfx(1)->prio_transpen(bitmap,cliprect,
sprite + (fy ? y_multi-1 - i : i),
colour,
fx,fy,
x,y+i*16,
screen.priority(),
(colour & 0x08) ? 0x00 : 0x02,0);
else if (m_video_control_data[7] & 0x02)
m_gfxdecode->gfx(1)->prio_transpen(bitmap,cliprect,
sprite + (fy ? y_multi-1 - i : i),
colour,
fx,fy,
x,y+i*16,
screen.priority(),
((colour & 0x0c)==0x0c) ? 0x00 : 0x02,0);
else
m_gfxdecode->gfx(1)->prio_transpen(bitmap,cliprect,
sprite + (fy ? y_multi-1 - i : i),
colour,
fx,fy,
x,y+i*16,
screen.priority(),
0x02,0);
}
}
void m90_state::bomblord_draw_sprites(screen_device &screen, bitmap_ind16 &bitmap,const rectangle &cliprect)
{
int offs = 0, last_sprite = 0;
int x,y,sprite,colour,fx,fy;
while ((offs < m_spriteram.bytes()/2) & (m_spriteram[offs+0] != 0x8000))
{
last_sprite = offs;
offs += 4;
}
for (offs = last_sprite; offs >= 0; offs -= 4)
{
sprite = m_spriteram[offs+1];
colour = (m_spriteram[offs+2] >> 9) & 0x0f;
y = (m_spriteram[offs+0] & 0x1ff) + 152;
x = (m_spriteram[offs+3] & 0x1ff) + 16;
x = x - 16;
y = 512 - y;
if (y < 0) y += 512;
fx = (m_spriteram[offs+3] >> 8) & 0x02;
fy = (m_spriteram[offs+2] >> 8) & 0x80;
m_gfxdecode->gfx(1)->prio_transpen(bitmap,cliprect,
sprite,
colour,
fx,fy,
x,y,
screen.priority(),
(colour & 0x08) ? 0x00 : 0x02,0);
}
}
void m90_state::dynablsb_draw_sprites(screen_device &screen, bitmap_ind16 &bitmap,const rectangle &cliprect)
{
int offs = 0, last_sprite = 0;
int x,y,sprite,colour,fx,fy;
while ((offs < m_spriteram.bytes()/2) & (m_spriteram[offs+0] != 0xffff))
{
last_sprite = offs;
offs += 4;
}
for (offs = last_sprite; offs >= 0; offs -= 4)
{
sprite = m_spriteram[offs+1];
colour = (m_spriteram[offs+2] >> 9) & 0x0f;
y = (m_spriteram[offs+0] & 0x1ff) + 288;
x = (m_spriteram[offs+3] & 0x1ff) - 64;
x = x - 16;
y = 512 - y;
if (y < 0) y += 512;
fx = (m_spriteram[offs+3] >> 8) & 0x02;
fy = (m_spriteram[offs+2] >> 8) & 0x80;
m_gfxdecode->gfx(1)->prio_transpen(bitmap,cliprect,
sprite,
colour,
fx,fy,
x,y,
screen.priority(),
(colour & 0x08) ? 0x00 : 0x02,0);
}
}
WRITE16_MEMBER(m90_state::m90_video_w)
{
COMBINE_DATA(&m_video_data[offset]);
int page[2] = { m_video_control_data[5] & 0x3, m_video_control_data[6] & 0x3 };
for (int i = 0; i < 2; i++)
{
if ((offset >> 13) == page[i])
m_pf_layer[i][0]->mark_tile_dirty((offset & 0x1fff) >> 1);
page[i] >>= 1;
if ((offset >> 14) == page[i])
m_pf_layer[i][1]->mark_tile_dirty((offset & 0x3fff) >> 1);
}
}
WRITE16_MEMBER(m90_state::bootleg_video_w)
{
COMBINE_DATA(&m_video_data[offset]);
int layer = offset >> 14;
if (!(offset & 0x2000))
m_pf_layer[layer][0]->mark_tile_dirty((offset & 0x1fff) >> 1);
m_pf_layer[layer][1]->mark_tile_dirty((offset & 0x3fff) >> 1);
}
uint32_t m90_state::screen_update_m90(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
int const layer_ctrl[2] = { m_video_control_data[5], m_video_control_data[6] };
bool const video_enable = !(m_video_control_data[7] & 0x04);
bool const pf_enable[2] = { !(m_video_control_data[5] & 0x10), !(m_video_control_data[6] & 0x10) };
int const clip_miny = std::min(cliprect.min_y, 511);
int const clip_maxy = std::min(cliprect.max_y, 511);
// m_pf_layer[0][0]->enable(pf_enable[0]);
// m_pf_layer[1][0]->enable(pf_enable[1]);
// m_pf_layer[0][1]->enable(pf_enable[0]);
// m_pf_layer[1][1]->enable(pf_enable[1]);
constexpr int rowscroll_offs[2] = { 0xf000 >> 1, 0xf400 >> 1 };
constexpr int rowscroll_bias[2] = { 2, -2 };
for (int layer = 0; layer < 2; layer++)
{
/* Dirty tilemaps if VRAM base changes */
uint8_t const pf_base = uint8_t(layer_ctrl[layer] & 0x3);
if (pf_base != m_last_pf[layer])
{
m_pf_layer[layer][0]->set_user_data(&m_video_data[pf_base << 13]);
m_pf_layer[layer][1]->set_user_data(&m_video_data[(pf_base & ~1) << 13]);
m_pf_layer[layer][0]->mark_all_dirty();
m_pf_layer[layer][1]->mark_all_dirty();
m_last_pf[layer] = pf_base;
}
/* Setup scrolling */
if (layer_ctrl[layer] & 0x20)
{
m_pf_layer[layer][0]->set_scroll_rows(512);
m_pf_layer[layer][1]->set_scroll_rows(512);
for (int i = 0; i < 512; i++)
{
m_pf_layer[layer][0]->set_scrollx(i, m_video_data[rowscroll_offs[layer] + i] + rowscroll_bias[layer]);
m_pf_layer[layer][1]->set_scrollx(i, m_video_data[rowscroll_offs[layer] + i] + rowscroll_bias[layer] + 256);
}
}
else
{
m_pf_layer[layer][0]->set_scroll_rows(1);
m_pf_layer[layer][1]->set_scroll_rows(1);
m_pf_layer[layer][0]->set_scrollx(0, m_video_control_data[1 | (layer<<1)] + rowscroll_bias[layer]);
m_pf_layer[layer][1]->set_scrollx(0, m_video_control_data[1 | (layer<<1)] + rowscroll_bias[layer] + 256);
}
}
if (!video_enable)
{
bitmap.fill(m_palette->black_pen(), cliprect);
return 0;
}
screen.priority().fill(0, cliprect);
if (pf_enable[1])
{
// use the playfield 2 y-offset table for each scanline
if (layer_ctrl[1] & 0x40)
{
rectangle clip;
clip.min_x = cliprect.min_x;
clip.max_x = cliprect.max_x;
for (int line = clip_miny; line <= clip_maxy; line++)
{
clip.min_y = clip.max_y = line;
if (layer_ctrl[1] & 0x4)
{
m_pf_layer[1][1]->set_scrolly(0, 0x200 + m_video_data[0xfc00/2 + line]);
m_pf_layer[1][1]->draw(screen, bitmap, clip, 0,0);
m_pf_layer[1][1]->draw(screen, bitmap, clip, 1,1);
}
else
{
m_pf_layer[1][0]->set_scrolly(0, 0x200 + m_video_data[0xfc00/2 + line]);
m_pf_layer[1][0]->draw(screen, bitmap, clip, 0,0);
m_pf_layer[1][0]->draw(screen, bitmap, clip, 1,1);
}
}
}
else
{
if (layer_ctrl[1] & 0x4)
{
m_pf_layer[1][1]->set_scrolly(0, m_video_control_data[2] );
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 1,1);
}
else
{
m_pf_layer[1][0]->set_scrolly(0, m_video_control_data[2] );
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 1,1);
}
}
}
else
{
bitmap.fill(0, cliprect);
}
if (pf_enable[0])
{
// use the playfield 1 y-offset table for each scanline
if (layer_ctrl[0] & 0x40)
{
rectangle clip;
clip.min_x = cliprect.min_x;
clip.max_x = cliprect.max_x;
for (int line = clip_miny; line <= clip_maxy; line++)
{
clip.min_y = clip.max_y = line;
if (layer_ctrl[0] & 0x4)
{
m_pf_layer[0][1]->set_scrolly(0, 0x200 + m_video_data[0xf800/2 + line]);
m_pf_layer[0][1]->draw(screen, bitmap, clip, 0,0);
m_pf_layer[0][1]->draw(screen, bitmap, clip, 1,1);
}
else
{
m_pf_layer[0][0]->set_scrolly(0, 0x200 + m_video_data[0xf800/2 + line]);
m_pf_layer[0][0]->draw(screen, bitmap, clip, 0,0);
m_pf_layer[0][0]->draw(screen, bitmap, clip, 1,1);
}
}
}
else
{
if (layer_ctrl[0] & 0x4)
{
m_pf_layer[0][1]->set_scrolly(0, m_video_control_data[0] );
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 1,1);
}
else
{
m_pf_layer[0][0]->set_scrolly(0, m_video_control_data[0] );
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 1,1);
}
}
}
draw_sprites(screen,bitmap,cliprect);
return 0;
}
uint32_t m90_state::screen_update_bomblord(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
int i;
screen.priority().fill(0, cliprect);
bitmap.fill(m_palette->black_pen(), cliprect);
/* Setup scrolling */
if (m_video_control_data[6]&0x20) {
m_pf_layer[0][0]->set_scroll_rows(512);
m_pf_layer[0][1]->set_scroll_rows(512);
for (i=std::min(cliprect.min_y,511); i<=std::min(cliprect.max_y,511); i++) {
m_pf_layer[0][0]->set_scrollx(i, m_video_data[0xf400/2+i]-12);
m_pf_layer[0][1]->set_scrollx(i, m_video_data[0xf400/2+i]-12+256);
}
} else {
m_pf_layer[0][0]->set_scroll_rows(1);
m_pf_layer[0][1]->set_scroll_rows(1);
m_pf_layer[0][0]->set_scrollx(0, m_video_data[0xf004/2]-12);
m_pf_layer[0][1]->set_scrollx(0, m_video_data[0xf004/2]-12);
}
if (m_video_control_data[6] & 0x02) {
m_pf_layer[1][1]->mark_all_dirty();
m_pf_layer[1][1]->set_scrollx(0, m_video_data[0xf000/2]-16 );
m_pf_layer[1][1]->set_scrolly(0, m_video_data[0xf008/2]+388);
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 1,1);
} else {
m_pf_layer[1][0]->mark_all_dirty();
m_pf_layer[1][0]->set_scrollx(0, m_video_data[0xf000/2]-16 );
m_pf_layer[1][0]->set_scrolly(0, m_video_data[0xf008/2]-120);
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 1,1);
}
if (m_video_control_data[6] & 0x04) {
m_pf_layer[0][1]->mark_all_dirty();
m_pf_layer[0][1]->set_scrolly(0, m_video_data[0xf00c/2]+392);
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 1,1);
} else {
m_pf_layer[0][0]->mark_all_dirty();
m_pf_layer[0][0]->set_scrolly(0, m_video_data[0xf00c/2]-116);
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 1,1);
}
bomblord_draw_sprites(screen,bitmap,cliprect);
return 0;
}
uint32_t m90_state::screen_update_dynablsb(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
screen.priority().fill(0, cliprect);
bitmap.fill(m_palette->black_pen(), cliprect);
if (!(m_video_data[0xf008/2] & 0x4000)) {
m_pf_layer[0][1]->mark_all_dirty();
m_pf_layer[0][1]->set_scroll_rows(1);
m_pf_layer[0][1]->set_scrollx(0, m_video_data[0xf004/2]+64);
m_pf_layer[0][1]->set_scrolly(0, m_video_data[0xf006/2]+512);
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][1]->draw(screen, bitmap, cliprect, 1,1);
} else {
m_pf_layer[0][0]->mark_all_dirty();
m_pf_layer[0][0]->set_scroll_rows(1);
m_pf_layer[0][0]->set_scrollx(0, m_video_data[0xf004/2]+64);
m_pf_layer[0][0]->set_scrolly(0, m_video_data[0xf006/2]+4);
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[0][0]->draw(screen, bitmap, cliprect, 1,1);
}
if (!(m_video_data[0xf008/2] & 0x8000)) {
m_pf_layer[1][1]->mark_all_dirty();
m_pf_layer[1][1]->set_scroll_rows(1);
m_pf_layer[1][1]->set_scrollx(0, m_video_data[0xf000/2]+68);
m_pf_layer[1][1]->set_scrolly(0, m_video_data[0xf002/2]+512);
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][1]->draw(screen, bitmap, cliprect, 1,1);
} else {
m_pf_layer[1][0]->mark_all_dirty();
m_pf_layer[1][0]->set_scroll_rows(1);
m_pf_layer[1][0]->set_scrollx(0, m_video_data[0xf000/2]+68);
m_pf_layer[1][0]->set_scrolly(0, m_video_data[0xf002/2]+4);
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 0,0);
m_pf_layer[1][0]->draw(screen, bitmap, cliprect, 1,1);
}
dynablsb_draw_sprites(screen,bitmap,cliprect);
return 0;
}