// license:BSD-3-Clause
// copyright-holders:Bryan McPhail
/*
The MLC graphics hardware is quite complicated - the usual method of having 'object ram' that
controls sprites is expanded into object ram that controls sprite blocks that may be stored
in RAM or ROM. Each tile in a block may be specified explicitly via a display list in ROM or
calculated as part of a block offset.
Blocks can be scaled and subpositioned, and are usually 4-6bpp but two 4bpp blocks can be
combined into 8bpp with a flag.
*/
#include "emu.h"
#include "includes/deco_mlc.h"
#include <algorithm>
/******************************************************************************/
void deco_mlc_state::video_start()
{
int max_color = (0x800 / m_gfxdecode->gfx(0)->granularity());
m_colour_mask=max_color - 1;
m_shadow_mask=max_color << 1;
// temp_bitmap = std::make_unique<bitmap_rgb32>(512, 512 );
m_buffered_spriteram = std::make_unique<uint16_t[]>(0x3000/4);
m_spriteram_spare = std::make_unique<uint16_t[]>(0x3000/4);
m_spriteram = std::make_unique<uint16_t[]>(0x3000/4);
save_pointer(NAME(m_spriteram), 0x3000/4);
save_pointer(NAME(m_spriteram_spare), 0x3000/4);
save_pointer(NAME(m_buffered_spriteram), 0x3000/4);
}
void deco_mlc_state::drawgfxzoomline(uint32_t* dest,const rectangle &clip,gfx_element *gfx,
uint32_t code1,uint32_t code2, uint32_t color,int flipx,int sx,
int transparent_color,int use8bpp,
int scalex, int alpha, int srcline, int shadowMode, int alphaMode )
{
if (!scalex) return;
uint32_t shadowval = shadowMode ? 0xffffffff : 0;
/*
scalex and scaley are 16.16 fixed point numbers
1<<15 : shrink to 50%
1<<16 : uniform scale
1<<17 : double to 200%
*/
/* KW 991012 -- Added code to force clip to bitmap boundary */
int sprite_screen_width = (scalex*16+(sx&0xffff))>>16;
sx>>=16;
if (sprite_screen_width)
{
/* compute sprite increment per screen pixel */
int dx = (16<<16)/sprite_screen_width;
int ex = sx+sprite_screen_width;
int x_index_base;
if( flipx )
{
x_index_base = (sprite_screen_width-1)*dx;
dx = -dx;
}
else
{
x_index_base = 0;
}
if( sx < clip.min_x)
{ /* clip left */
int pixels = clip.min_x-sx;
sx += pixels;
x_index_base += pixels*dx;
}
/* NS 980211 - fixed incorrect clipping */
if( ex > clip.max_x+1 )
{ /* clip right */
int pixels = ex-clip.max_x-1;
ex -= pixels;
}
if( ex>sx )
{ /* skip if inner loop doesn't draw anything */
const pen_t *pal = &m_palette->pen(gfx->colorbase() + gfx->granularity() * (color % gfx->colors()));
const uint8_t *code_base1 = gfx->get_data(code1 % gfx->elements());
const uint8_t *code_base2 = gfx->get_data(code2 % gfx->elements());
const uint8_t *source1 = code_base1 + (srcline) * gfx->rowbytes();
const uint8_t *source2 = code_base2 + (srcline) * gfx->rowbytes();
/* no alpha */
if ((alpha == 0xff) && (!shadowMode))
{
int x, x_index = x_index_base;
for( x=sx; x<ex; x++ )
{
int c = source1[x_index>>16];
if (use8bpp)
c=(c<<4)|source2[x_index>>16];
if( c != transparent_color ) dest[x] = pal[c];
x_index += dx;
}
}
else
{ /* TODO : 8bpp and alpha can use simultaneously? */
int x, x_index = x_index_base;
for( x=sx; x<ex; x++ )
{
int c = source1[x_index>>16];
if (use8bpp)
c=(c<<4)|source2[x_index>>16];
// alphaMode & 0xc0 = 0xc0 : Shadow, 0 : Alpha, Other bits unknown
if( c != transparent_color ) dest[x] = alpha_blend_r32(dest[x], (alphaMode & 0xc0) ? shadowval : pal[c], shadowMode ? 0x80 : alpha);
x_index += dx;
}
}
}
}
}
void deco_mlc_state::draw_sprites( const rectangle &cliprect, int scanline, uint32_t* dest)
{
uint32_t *index_ptr=nullptr;
int offs,fx=0,fy=0,x,y,color,colorOffset,sprite,indx,h,w,bx,by,fx1,fy1;
int xoffs,yoffs;
uint8_t *rom = m_gfx2 + 0x20000, *index_ptr8;
uint8_t *rawrom = m_gfx2;
int blockIsTilemapIndex=0;
int sprite2=0,indx2=0,use8bppMode=0;
int yscale,xscale;
int alpha;
int useIndicesInRom=0;
int hibits=0;
int tileFormat=0;
int rasterMode=0;
int shadowMode=0;
int clipper=0;
rectangle user_clip;
uint16_t* spriteram=m_buffered_spriteram.get();
//printf("%d - (%08x %08x %08x) (%08x %08x %08x) (%08x %08x %08x)\n", scanline, m_irq_ram[6], m_irq_ram[7], m_irq_ram[8], m_irq_ram[9], m_irq_ram[10], m_irq_ram[11] , m_irq_ram[12] , m_irq_ram[13] , m_irq_ram[14]);
for (offs = (0x3000/4)-8; offs>=0; offs-=8)
{
if ((spriteram[offs+0]&0x8000)==0)
continue;
if ((spriteram[offs+1]&0x2000) && (m_screen->frame_number() & 1))
continue;
/*
Spriteram (1) format (16 bit):
Word 0: 0x8000 - Sprite enable
0x4000 - Use ROM or RAM for spriteram 2 (really top bit of index)
0x3fff - Index into spriteram 2
Word 1: 0x8000 - X flip
0x4000 - Y flip
0x2000 - Auto-flicker (display sprite only every other frame)
0x1000 - If set combine this 4bpp sprite & next one, into 8bpp sprite
0x0800 - upper clip bit (stadhr96 view of bases)
0x0400 - Use raster IRQ lookup table when drawing object - or not? (OK for stadhr96, NOT enabled for avengrgs)
0x0300 - lower clipping window to use (swapped) - and upper bits of raster select (stadhr96)
0x0080 - seems to be both the lower bit of the raster select (stadhr96) AND the upper bit of colour / alpha (avngrgs?) - might depend on other bits?
0x007f - Colour/alpha shadow enable
Word 2: 0x07ff - Y position
Word 3: 0x07ff - X position
Word 4: 0x03ff - X scale
Word 5: 0x03ff - Y scale
Spriteram (2) format (16 bit):
Word 0: 0xe000 - ? (Always 0x2000?)
0x1000 - X flip
0x0f00 - Width in tiles (0==16)
0x00ff - X position offset
Word 1: 0xe000 - ? (Always 0x2000?)
0x1000 - Y flip
0x0f00 - Height in tiles (0==16)
0x00ff - Y position offset
Word 2: 0xff00 - ? (Always 0?)
0x00c0 - If set use tile index as pointer into tile index array, else use as tile index directly
0x0080 - If set tile index array format is 12 bit tile, 4 bit colour
0x0040 - If set tile index array is 16 bit tile, 0 bit colour
0x003c - Hi-bits of tile index after array lookup
0x0003 - Hi-bits of tile index, selects ROM or RAM for array
Word 3: 0xffff - Low-bits of tile index
*/
y = spriteram[offs+2]&0x7ff;
x = spriteram[offs+3]&0x7ff;
if (x&0x400) x=-(0x400-(x&0x3ff));
if (y&0x400) y=-(0x400-(y&0x3ff));
fx = spriteram[offs+1]&0x8000;
fy = spriteram[offs+1]&0x4000;
color = spriteram[offs+1]&0xff;
int raster_select = (spriteram[offs+1]&0x0180)>>7;
// there are 3 different sets of raster values, must be a way to selects
// between them? furthermore avengrgs doesn't even enable this
// although it doesn't seem to set the scroll values very often either
// so the irq mechanism might be wrong
//
// actually avengrgs has our current clipper&1 set on the areas that should
// have the scroll effect applied to them. all clip windows are the same
// so there is no reason to select a clip window other than to be using it
// to select a set of raster-set scroll regs?
rasterMode = (spriteram[offs+1]>>10)&0x1;
clipper = (spriteram[offs+1]>>8)&0x3;
indx = spriteram[offs+0]&0x3fff;
yscale = spriteram[offs+4]&0x3ff;
xscale = spriteram[offs+5]&0x3ff;
colorOffset = 0;
/* Clip windows - this mapping seems odd, but is correct for Skull Fang and StadHr96,
however there are space for 8 clipping windows, where is the high bit? (Or is it ~0x400?) */
clipper=((clipper&2)>>1)|((clipper&1)<<1); // Swap low two bits
int upperclip = (spriteram[offs+1]>>10)&0x2;
// this is used on some ingame gfx in stadhr96
// to clip the images of your guys on the bases
if (upperclip)
clipper |= 0x4;
int min_y = m_clip_ram[(clipper*4)+0];
int max_y = m_clip_ram[(clipper*4)+1];
if (scanline<min_y)
continue;
if (scanline>max_y)
continue;
user_clip.min_x=m_clip_ram[(clipper*4)+2];
user_clip.max_x=m_clip_ram[(clipper*4)+3];
user_clip &= cliprect;
/* Any colours out of range (for the bpp value) trigger 'shadow' mode */
shadowMode = (color & (m_shadow_mask)) ? 1 : 0; // shadow mode (OK for skullfng)
if ((color & (m_colour_mask+1)) || (shadowMode))
alpha=0x80;
else
alpha=0xff;
color&=m_colour_mask;
/* If this bit is set, combine this block with the next one */
if (spriteram[offs+1]&0x1000) {
use8bppMode=1;
/* In 8bpp the palette base is stored in the next block */
if (offs-8>=0) {
color = (spriteram[offs+1-8]&0x7f);
indx2 = spriteram[offs+0-8]&0x3fff;
}
} else
use8bppMode=0;
/* Lookup tiles/size in sprite index ram OR in the lookup rom */
if (spriteram[offs+0]&0x4000) {
index_ptr8=rom + indx*8; /* Byte ptr */
h=(index_ptr8[1]>>0)&0xf;
w=(index_ptr8[3]>>0)&0xf;
if (!h) h=16;
if (!w) w=16;
sprite = (index_ptr8[7]<<8)|index_ptr8[6];
sprite |= (index_ptr8[4]&3)<<16;
if (use8bppMode) {
uint8_t* index_ptr28=rom + indx2*8;
sprite2=(index_ptr28[7]<<8)|index_ptr28[6];
}
//unused byte 5
yoffs=index_ptr8[0]&0xff;
xoffs=index_ptr8[2]&0xff;
fy1=(index_ptr8[1]&0x10)>>4;
fx1=(index_ptr8[3]&0x10)>>4;
tileFormat=index_ptr8[4]&0x80;
if (index_ptr8[4]&0xc0)
blockIsTilemapIndex=1;
else
blockIsTilemapIndex=0;
useIndicesInRom=0;
} else {
indx&=0x1fff;
index_ptr=m_vram + indx*4;
h=(index_ptr[0]>>8)&0xf;
w=(index_ptr[1]>>8)&0xf;
if (!h) h=16;
if (!w) w=16;
if (use8bppMode) {
uint32_t* index_ptr2=m_vram + ((indx2*4)&0x7fff);
sprite2=((index_ptr2[2]&0x3)<<16) | (index_ptr2[3]&0xffff);
}
sprite = ((index_ptr[2]&0x3)<<16) | (index_ptr[3]&0xffff);
if (index_ptr[2]&0xc0)
blockIsTilemapIndex=1;
else
blockIsTilemapIndex=0;
tileFormat=index_ptr[2]&0x80;
hibits=(index_ptr[2]&0x3c)<<10;
useIndicesInRom=index_ptr[2]&3;
yoffs=index_ptr[0]&0xff;
xoffs=index_ptr[1]&0xff;
fy1=(index_ptr[0]&0x1000)>>12;
fx1=(index_ptr[1]&0x1000)>>12;
}
if(fx1) fx^=0x8000;
if(fy1) fy^=0x4000;
int extra_x_scale = 0x100;
// I think we need some hardware tests..
// see notes about how this can't be our enable register (avengrgs doesn't touch it
// and relies on something else, probably just the bits we use to select the window)
// (although as previously noted, it isn't writing valid per-scanline values either)
if (rasterMode)
{
// use of these is a bit weird. (it's probably just 16-bit .. like spriteram so the upper dupes are ignored?)
// -ZZZ -yyy ---- -xxx -YYY -zzz
// xxx = x offset?
// yyy = y offset?
// zzz = xzoom (confirmed? stadium hero)
// 0x100 = no zoom
//
// YYY = duplicate bits of yyy?
// ZZZ = (sometimes) duplicate bits of zzz
if (raster_select==1 || raster_select==2 || raster_select==3)
{
shadowMode = 0; // TODO : Raster enable and shadow mode can't use simultaneously?
int irq_base_reg; /* 6, 9, 12 are possible */
if (raster_select== 1) irq_base_reg = 6; // OK upper screen.. left?
else if (raster_select== 2) irq_base_reg = 9; // OK upper screen.. main / center
else irq_base_reg = 12;
int extra_y_off = m_irq_ram[irq_base_reg+0] & 0x7ff;
int extra_x_off = m_irq_ram[irq_base_reg+1] & 0x7ff;
extra_x_scale = (m_irq_ram[irq_base_reg+2]>>0) & 0x3ff;
if (extra_x_off & 0x400) { extra_x_off -= 0x800; }
if (extra_y_off & 0x400) { extra_y_off -= 0x800; }
x += extra_x_off;
y += extra_y_off;
}
else if (raster_select==0x0)
{
// possibly disabled?
}
}
xscale *= extra_x_scale;
int ybase=y<<16;
int yinc=(yscale<<8)*16;
if (fy)
ybase+=(yoffs-15) * (yscale<<8) - ((h-1)*yinc);
else
ybase-=yoffs * (yscale<<8);
int xbase=x<<16;
int xinc=(xscale)*16;
if (fx)
xbase+=(xoffs-15) * (xscale) - ((w-1)*xinc);
else
xbase-=xoffs * (xscale);
int full_realybase = ybase;
int full_sprite_screen_height = ((yscale<<8)*(h*16)+(0));
int full_sprite_screen_height_unscaled = ((1)*(h*16)+(0));
if (!full_sprite_screen_height_unscaled)
continue;
int ratio = full_sprite_screen_height / full_sprite_screen_height_unscaled;
if (!ratio)
continue;
int bby = scanline - (full_realybase>>16);
if (bby < 0)
continue;
if (bby >= full_sprite_screen_height>>16)
continue;
// color = machine().rand();
int srcline = ((bby<<16) / ratio);
by = srcline >> 4;
srcline &=0xf;
if( fy )
{
srcline = 15 - srcline;
}
for (bx=0; bx<w; bx++) {
int realxbase = xbase + bx * xinc;
int count = 0;
if (fx)
{
if (fy)
count = (h-1-by) * w + (w-1-bx);
else
count = by * w + (w-1-bx);
}
else
{
if (fy)
count = (h-1-by) * w + bx;
else
count = by * w + bx;
}
int tile=sprite + count;
int tile2=sprite2 + count;
if (blockIsTilemapIndex) {
if (useIndicesInRom)
{
const uint8_t* ptr=rawrom+(tile*2);
tile=(*ptr) + ((*(ptr+1))<<8);
if (use8bppMode) {
const uint8_t* ptr2=rawrom+(tile2*2);
tile2=(*ptr2) + ((*(ptr2+1))<<8);
}
else
{
tile2=0;
}
if (tileFormat)
{
colorOffset=(tile&0xf000)>>12;
tile=(tile&0x0fff)|hibits;
tile2=(tile2&0x0fff)|hibits;
}
else
{
colorOffset=0;
tile=(tile&0xffff)|(hibits<<2);
tile2=(tile2&0xffff)|(hibits<<2);
}
}
else
{
const uint32_t* ptr=m_vram + ((tile)&0x7fff);
tile=(*ptr)&0xffff;
if (tileFormat)
{
colorOffset=(tile&0xf000)>>12;
tile=(tile&0x0fff)|hibits;
}
else
{
colorOffset=0;
tile=(tile&0xffff)|(hibits<<2);
}
tile2=0;
}
}
drawgfxzoomline(dest,user_clip,m_gfxdecode->gfx(0),
tile,tile2,
color + colorOffset,fx,realxbase,
0,
use8bppMode,(xscale),alpha, srcline,
shadowMode, m_irq_ram[0x04/4]);
}
if (use8bppMode)
offs-=8;
}
}
WRITE_LINE_MEMBER(deco_mlc_state::screen_vblank_mlc)
{
// rising edge
if (state)
{
/* Spriteram is definitely double buffered, as the vram lookup tables
are often updated a frame after spriteram is setup to point to a new
lookup table. Without buffering incorrect one frame glitches are seen
in several places, especially in Hoops.
*/
std::copy(&m_spriteram[0], &m_spriteram[0x3000/4], &m_buffered_spriteram[0]);
}
}
uint32_t deco_mlc_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
// temp_bitmap->fill(0, cliprect);
bitmap.fill(m_palette->pen(0), cliprect); /* Pen 0 fill colour confirmed from Skull Fang level 2 */
for (int i=cliprect.min_y;i<=cliprect.max_y;i++)
{
uint32_t *dest = &bitmap.pix32(i);
/*
printf("%d -", i);
for (int j=0;j<0x20;j++)
{
printf("%08x, ",m_irq_ram[j]);
}
printf("\n");
*/
draw_sprites(cliprect, i, dest);
}
return 0;
}