// license:BSD-3-Clause
// copyright-holders:David Haywood,Paul Priest
/* video/macrossp.c */
#include "emu.h"
#include "includes/macrossp.h"
//#define DEBUG_KEYS 1
/*
Sprite list is drawn backwards, and priorities with backgrounds are not transitive
=== Vid Registers ===
[0] - tiles
0x000003ff - global scrollx
0x00000c00 - color mode
0x0000c000 - priority
0x03ff0000 - global scrolly
0x90000000 - enable? Always 0x9
[1] - ???
0xffff0000 - another scrolly register, mainly used when zooming. unused by emulation
0x0000ffff - another scrollx register, mainly used when zooming. unused by emulation
[2] - zoom params
0xf0000000 - zoom enable (== 0xe, not == 0x2). Presumably one bit for x and y enable
0x01ff0000 - incy (0x40 is 1:1, incx is in lineram. might be more bits)
Interesting test cases (macrossp, quizmoon doesn't use tilemap zoom):
1) Title screen logo zoom
2) Second level, as zoom into end of canyon
3) Second level, as doors open to revels tracks/blue background for boss
4) Boss should go under bridge on level 4 when he first appears
*/
/*** SCR A LAYER ***/
WRITE32_MEMBER(macrossp_state::macrossp_scra_videoram_w)
{
COMBINE_DATA(&m_scra_videoram[offset]);
m_scra_tilemap->mark_tile_dirty(offset);
}
TILE_GET_INFO_MEMBER(macrossp_state::get_macrossp_scra_tile_info)
{
uint32_t attr, tileno, color;
attr = m_scra_videoram[tile_index];
tileno = attr & 0x0000ffff;
switch (m_scra_videoregs[0] & 0x00000c00)
{
case 0x00000800:
color = (attr & 0x000e0000) >> 15;
break;
case 0x00000400:
color = (attr & 0x003e0000) >> 17;
break;
default:
color = machine().rand() & 7;
break;
}
SET_TILE_INFO_MEMBER(1, tileno, color, TILE_FLIPYX((attr & 0xc0000000) >> 30));
}
/*** SCR B LAYER ***/
WRITE32_MEMBER(macrossp_state::macrossp_scrb_videoram_w)
{
COMBINE_DATA(&m_scrb_videoram[offset]);
m_scrb_tilemap->mark_tile_dirty(offset);
}
TILE_GET_INFO_MEMBER(macrossp_state::get_macrossp_scrb_tile_info)
{
uint32_t attr, tileno, color;
attr = m_scrb_videoram[tile_index];
tileno = attr & 0x0000ffff;
switch (m_scrb_videoregs[0] & 0x00000c00)
{
case 0x00000800:
color = (attr & 0x000e0000) >> 15;
break;
case 0x00000400:
color = (attr & 0x003e0000) >> 17;
break;
default:
color = machine().rand() & 7;
break;
}
SET_TILE_INFO_MEMBER(2, tileno, color, TILE_FLIPYX((attr & 0xc0000000) >> 30));
}
/*** SCR C LAYER ***/
WRITE32_MEMBER(macrossp_state::macrossp_scrc_videoram_w)
{
COMBINE_DATA(&m_scrc_videoram[offset]);
m_scrc_tilemap->mark_tile_dirty(offset);
}
TILE_GET_INFO_MEMBER(macrossp_state::get_macrossp_scrc_tile_info)
{
uint32_t attr, tileno, color;
attr = m_scrc_videoram[tile_index];
tileno = attr & 0x0000ffff;
switch (m_scrc_videoregs[0] & 0x00000c00)
{
case 0x00000800:
color = (attr & 0x000e0000) >> 15;
break;
case 0x00000400:
color = (attr & 0x003e0000) >> 17;
break;
default:
color = machine().rand() & 7;
break;
}
SET_TILE_INFO_MEMBER(3, tileno, color, TILE_FLIPYX((attr & 0xc0000000) >> 30));
}
/*** TEXT LAYER ***/
WRITE32_MEMBER(macrossp_state::macrossp_text_videoram_w)
{
COMBINE_DATA(&m_text_videoram[offset]);
m_text_tilemap->mark_tile_dirty(offset);
}
TILE_GET_INFO_MEMBER(macrossp_state::get_macrossp_text_tile_info)
{
uint32_t tileno, colour;
tileno = m_text_videoram[tile_index] & 0x0000ffff;
colour = (m_text_videoram[tile_index] & 0x00fe0000) >> 17;
SET_TILE_INFO_MEMBER(4, tileno, colour, 0);
}
/*** VIDEO START / UPDATE ***/
void macrossp_state::video_start()
{
m_spriteram_old = make_unique_clear<uint32_t[]>(m_spriteram.bytes() / 4);
m_spriteram_old2 = make_unique_clear<uint32_t[]>(m_spriteram.bytes() / 4);
m_text_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(macrossp_state::get_macrossp_text_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 64, 64);
m_scra_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(macrossp_state::get_macrossp_scra_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 64, 64);
m_scrb_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(macrossp_state::get_macrossp_scrb_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 64, 64);
m_scrc_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(macrossp_state::get_macrossp_scrc_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 64, 64);
m_text_tilemap->set_transparent_pen(0);
m_scra_tilemap->set_transparent_pen(0);
m_scrb_tilemap->set_transparent_pen(0);
m_scrc_tilemap->set_transparent_pen(0);
m_gfxdecode->gfx(0)->set_granularity(64);
m_gfxdecode->gfx(1)->set_granularity(64);
m_gfxdecode->gfx(2)->set_granularity(64);
m_gfxdecode->gfx(3)->set_granularity(64);
save_pointer(NAME(m_spriteram_old), m_spriteram.bytes() / 4);
save_pointer(NAME(m_spriteram_old2), m_spriteram.bytes() / 4);
}
void macrossp_state::draw_sprites(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect )
{
gfx_element *gfx = m_gfxdecode->gfx(0);
// uint32_t *source = m_spriteram;
uint32_t *source = (m_spriteram_old2.get() + m_spriteram.bytes() / 4) - 3; /* buffers by two frames */
uint32_t *finish = m_spriteram_old2.get();
/* reverse order */
while (source >= finish)
{
/*
--hh hhyy yyyy yyyy CCww wwxx xxxx xxxx
---- --zz zzzz zzzz ---- --ZZ ZZZZ ZZZZ
fFa- pp-- cccc c--- tttt tttt tttt tttt
*/
int wide = (source[0] & 0x00003c00) >> 10;
int high = (source[0] & 0x3c000000) >> 26;
int xpos = (source[0] & 0x000003ff) >> 0;
int ypos = (source[0] & 0x03ff0000) >> 16;
int xzoom = (source[1] & 0x000003ff) >> 0; /* 0x100 is zoom factor of 1.0 */
int yzoom = (source[1] & 0x03ff0000) >> 16;
int col;
int tileno = (source[2] & 0x0000ffff) >> 0;
int flipx = (source[2] & 0x40000000) >> 30;
int flipy = (source[2] & 0x80000000) >> 31;
int alpha = (source[2] & 0x20000000)?0x80:0xff; /* alpha blending enable? */
int loopno = 0;
int xcnt, ycnt;
int xoffset, yoffset;
int pri = (source[2] & 0x0c000000) >> 26;
int primask = 0;
if(pri <= 0) primask |= GFX_PMASK_1;
if(pri <= 1) primask |= GFX_PMASK_2;
if(pri <= 2) primask |= GFX_PMASK_4;
if(pri <= 3) primask |= GFX_PMASK_8;
switch (source[0] & 0x0000c000)
{
case 0x00008000:
col = (source[2] & 0x00380000) >> 17;
break;
case 0x00004000:
col = (source[2] & 0x00f80000) >> 19;
break;
default:
col = machine().rand();
break;
}
if (xpos > 0x1ff) xpos -=0x400;
if (ypos > 0x1ff) ypos -=0x400;
/* loop params */
int ymin = 0;
int ymax = high+1;
int yinc = 1;
int yoffst = 0;
if(flipy) {
yoffst = (high * yzoom * 16);
ymin = high;
ymax = -1;
yinc = -1;
}
int xmin = 0;
int xmax = wide+1;
int xinc = 1;
int xoffst = 0;
if(flipx) {
xoffst = (wide * xzoom * 16);
xmin = wide;
xmax = -1;
xinc = -1;
}
yoffset = yoffst;
for (ycnt = ymin; ycnt != ymax; ycnt += yinc)
{
xoffset = xoffst;
for (xcnt = xmin; xcnt != xmax; xcnt += xinc)
{
int fudged_xzoom = xzoom<<8;
int fudged_yzoom = yzoom<<8;
/* cover seams as don't know exactly how many pixels on target will cover, and can't specify fractional offsets to start */
if(xzoom < 0x100) fudged_xzoom += 0x600;
if(yzoom < 0x100) fudged_yzoom += 0x600;
gfx->prio_zoom_alpha(bitmap,cliprect,tileno+loopno,col,
flipx,flipy,xpos+(xoffset>>8),ypos+(yoffset>>8),
fudged_xzoom,fudged_yzoom,
screen.priority(),primask,0,alpha);
xoffset += ((xzoom*16) * xinc);
loopno++;
}
yoffset += ((yzoom*16) * yinc);
}
source -= 3;
}
}
void macrossp_state::draw_layer( screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect, int layer, int line, int pri )
{
tilemap_t *tm;
uint32_t *vr;
uint32_t *lr;
switch (layer)
{
case 0:
default:
tm = m_scra_tilemap;
vr = m_scra_videoregs;
lr = m_scra_linezoom;
break;
case 1:
tm = m_scrb_tilemap;
vr = m_scrb_videoregs;
lr = m_scrb_linezoom;
break;
case 2:
tm = m_scrc_tilemap;
vr = m_scrc_videoregs;
lr = m_scrc_linezoom;
break;
}
if ((vr[2] & 0xf0000000) == 0xe0000000) /* zoom enable (guess, surely wrong) */
{
int startx=0, starty=0, incy, incx;
startx = ((vr[0] & 0x000003ff) << 16 );
starty = ((vr[0] & 0x03ff0000) >> 0);
incy = (vr[2] & 0x01ff0000) >> 6;
if (line&1)
incx = (lr[line/2] & 0x0000ffff)>>0;
else
incx = (lr[line/2] & 0xffff0000)>>16;
incx <<= 10;
/* scroll register contain position relative to the center of the screen, so adjust */
startx -= (368/2) * (incx - 0x10000);
starty -= (240/2) * (incy - 0x10000);
// previous logic, which gives mostly comparable results, vr[1] is now unused
// startx = (vr[1] & 0x0000ffff) << 16;
// starty = (vr[1] & 0xffff0000) >> 0;
// startx -= (368/2) * incx;
// starty -= (240/2) * incy;
tm->draw_roz(screen, bitmap, cliprect,
startx,starty,incx,0,0,incy,
1, /* wraparound */
0, 1<<pri);
}
else
{
tm->set_scrollx(0, ((vr[0] & 0x000003ff) >> 0 ) );
tm->set_scrolly(0, ((vr[0] & 0x03ff0000) >> 16) );
tm->draw(screen, bitmap, cliprect, 0, 1<<pri);
}
}
uint32_t macrossp_state::screen_update_macrossp(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
int layerpri[3];
int sprites = true;
int backgrounds = true;
rectangle clip;
const rectangle &visarea = screen.visible_area();
clip = visarea;
/* 0 <= layerpri <= 2 */
layerpri[0] = (m_scra_videoregs[0] & 0x0000c000) >> 14;
layerpri[1] = (m_scrb_videoregs[0] & 0x0000c000) >> 14;
layerpri[2] = (m_scrc_videoregs[0] & 0x0000c000) >> 14;
screen.priority().fill(0, cliprect);
bitmap.fill(m_palette->black_pen(), cliprect);
#ifdef DEBUG_KEYS
const input_code lay_keys[8] = {KEYCODE_Q, KEYCODE_W, KEYCODE_E, KEYCODE_R, KEYCODE_T};
bool lay_debug = false;
for (int pri = 0; pri <= 4; pri++)
{
if(machine().input().code_pressed(lay_keys[pri])) {
lay_debug = true;
}
}
if(machine().input().code_pressed(KEYCODE_G)) {
sprites = false;
}
if(machine().input().code_pressed(KEYCODE_H)) {
backgrounds = false;
}
#endif
for(int pri = 0; pri <= 3; pri++) {
#ifdef DEBUG_KEYS
if(lay_debug && !machine().input().code_pressed(lay_keys[pri]))
continue;
#endif
if(!backgrounds)
continue;
for (int y=0; y<240; y++) {
clip.min_y = clip.max_y = y;
/* quizmoon map requires that layer 2 be drawn over layer 3 when same pri */
for(int layer = 2; layer >= 0; layer--) {
if(layerpri[layer] == pri) {
draw_layer(screen, bitmap, clip, layer, y, pri);
}
}
}
}
#ifdef DEBUG_KEYS
if(!lay_debug && !machine().input().code_pressed(lay_keys[4]))
#endif
m_text_tilemap->draw(screen, bitmap, cliprect, 0, 8);
if(sprites) draw_sprites(screen, bitmap, cliprect);
#if 0
popmessage ("scra - %08x %08x %08x\nscrb - %08x %08x %08x\nscrc - %08x %08x %08x",
m_scra_videoregs[0]&0xffffffff, // yyyyxxxx
m_scra_videoregs[1], // ??? more scrolling?
m_scra_videoregs[2], // 08 - 0b
m_scrb_videoregs[0]&0xffffffff, // 00 - 03
m_scrb_videoregs[1], // 04 - 07
m_scrb_videoregs[2], // 08 - 0b
m_scrc_videoregs[0]&0xffffffff, // 00 - 03
m_scrc_videoregs[1], // 04 - 07
m_scrc_videoregs[2]);// 08 - 0b
#endif
return 0;
}
WRITE_LINE_MEMBER(macrossp_state::screen_vblank_macrossp)
{
// rising edge
if (state)
{
/* looks like sprites are *two* frames ahead, like nmk16 */
memcpy(m_spriteram_old2.get(), m_spriteram_old.get(), m_spriteram.bytes());
memcpy(m_spriteram_old.get(), m_spriteram, m_spriteram.bytes());
}
}