.added pixel-accurate emulation of mid-line display on/off (Nigel Mansell World Championship PAL, Ren & Stimpy's Invention PAL,...)

.improved sprite processing accuracy (fixes Power Athlete / Deadly Moves)
.optimized sprite rendering
This commit is contained in:
ekeeke31 2010-06-28 22:49:17 +00:00
parent a2258d0011
commit daf11963b1
6 changed files with 204 additions and 177 deletions

View File

@ -15,25 +15,26 @@ of samples per frame and keeping PSG & FM chips in sync.
* fixed YM2612 state on reset.
* removed outdated & less accurate Gens YM2612 core
* added configurable YM2612 DAC resolution emulation.
* added configurable & faster FIR resampler (thanks to Blargg & AamirM), dropped libsamplerate support.
* added configurable & faster FIR resampler (thanks to Blargg & AamirM), removed libsamplerate support.
* added configurable Low-Pass filtering
* added configurable 3-Band Equalizer (thanks to Neil C).
* added support for SN76489 Noise Channel boost (optional).
* modified SN76489 cut-off frequency.
* added an option to boost SN76489 Noise Channel.
* adjusted SN76489 cut-off frequency.
[Core/VDP]
---------------
* added support for CRAM writes during horizontal blanking (Striker, Zero the Kamikaze Squirrel,...)
* added support for 2-Cell vertical scrolling in Interlaced 2 mode
* added proper HV Counter latch support (fixes Sunset Riders intro)
* improved VDP FIFO timings accuracy: fixes Sol Deace intro
* improved sprite masking accuracy (thanks to Nemesis for his sprite test program)
* improved HBLANK flag timing accuracy: fixes Mega Turrican (Sky level)
* added support for some undocumented mode register bits
* added proper emulation of HV Counter latch: fixes Sunset Riders intro
* added pixel-accurate emulation of mid-line display on/off (Nigel Mansell World Championship PAL, Ren & Stimpy's Invention PAL,...)
* improved FIFO timings accuracy: fixes Sol Deace intro
* improved sprite masking accuracy (thanks to Nemesis for his test program)
* improved sprites processing accuracy: fixes (un)masked sprites in Mickey Mania (3D level), Sonic 2 (VS mode).
* improved HBLANK flag timing accuracy: fixes Mega Turrican (Sky level)
* improved horizontal blanking & HINT/VINT occurence timing accuracy, as measured on real hardware.
* improved HCounter accuracy in 40-cell mode, as measured on real hardware.
* improved color accuracy in VDP highlight mode to match results observed on real hardware.
* improved color accuracy in VDP highlight mode to match results observed on real hardware
[Core/CPU]
---------------

View File

@ -302,7 +302,7 @@ static __inline__ void WRITE_LONG(void *address, uint32 data)
#define DRAW_SPRITE_TILE \
for(i=0; i<8; i++) \
{ \
if ((lb[i] & 0x80) && (lb[i] & 0x0F) && (src[i] & 0x0F)) status |= 0x20; \
if (((lb[i] & 0x8F) > 0x80) && src[i]) status |= 0x20; \
lb[i] = table[(lb[i] << 8) |(src[i] | palette)]; \
}
@ -348,14 +348,16 @@ static const uint32 atex_table[] = {
/* Sprite name look-up table */
static uint8 name_lut[0x400];
struct
typedef struct
{
uint16 ypos;
uint16 xpos;
uint16 attr;
uint8 size;
uint8 index; // unused
} object_info[20];
} object;
static object object_info[2][20];
/* Pixel look-up tables and table base address */
static uint8 *lut[5];
@ -387,7 +389,8 @@ static uint8 ntb_buf[0x200]; /* Plane B line buffer */
static uint8 obj_buf[0x200]; /* Object layer line buffer */
/* Sprite line buffer data */
uint32 object_index_count;
uint8 object_count[2];
uint8 object_which;
/*--------------------------------------------------------------------------*/
/* Look-up table functions (handles priority between layers pixels) */
@ -904,7 +907,7 @@ static void update_bg_pattern_cache(int index)
}
}
static inline uint32 get_hscroll(int line)
static uint32 get_hscroll(int line)
{
switch(reg[11] & 3)
{
@ -1455,33 +1458,25 @@ static int spr_over = 0;
static void render_obj(int line, uint8 *buf, uint8 *table)
{
uint16 ypos;
uint16 attr;
uint16 xpos;
uint8 sizetab[] = {8, 16, 24, 32};
uint8 size;
uint8 *src;
int count,i;
int pixelcount = 0;
int width;
int height;
int i, count, column;
int xpos;
int v_line;
int column;
int max = bitmap.viewport.w;
int left = 0x80;
int right = 0x80 + max;
int pixelmax = bitmap.viewport.w;
int pixelcount = 0;
int masked = 0;
uint8 *s, *lb;
uint16 name, index;
uint8 palette;
uint8 *src, *s, *lb;
uint32 size, width;
uint32 attr, attr_mask, name, palette, index;
int attr_mask, nt_row;
int mask = 0;
object *obj_info = object_info[object_which];
for(count = 0; count < object_index_count; count += 1)
for(count = 0; count < object_count[object_which]; count ++)
{
xpos = object_info[count].xpos & 0x1ff;
/* sprite horizontal position */
xpos = obj_info[count].xpos;
/* sprite masking (requires at least one sprite with xpos > 0) */
if (xpos)
@ -1491,43 +1486,47 @@ static void render_obj(int line, uint8 *buf, uint8 *table)
else if (spr_over)
{
spr_over = 0;
mask = 1;
masked = 1;
}
size = object_info[count].size & 0x0f;
/* sprite horizontal ofsfet */
xpos = xpos - 0x80;
/* sprite size */
size = obj_info[count].size;
width = sizetab[(size >> 2) & 3];
/* update pixel count (off-screen sprites included) */
/* update pixel count (off-screen sprites are included) */
pixelcount += width;
if(((xpos + width) >= left) && (xpos < right) && !mask)
/* draw visible sprites */
if (((xpos + width) >= 0) && (xpos < pixelmax) && !masked)
{
ypos = object_info[count].ypos;
attr = object_info[count].attr;
attr_mask = (attr & 0x1800);
height = sizetab[size & 3];
/* sprite attributes + pattern index */
attr = obj_info[count].attr;
attr_mask = attr & 0x1800;
palette = (attr >> 9) & 0x70;
name = attr & 0x07FF;
v_line = (line - ypos);
nt_row = (v_line >> 3) & 3;
/* sprite vertical offset */
v_line = line - obj_info[count].ypos;
s = &name_lut[((attr >> 3) & 0x300) | (size << 4) | ((v_line & 0x18) >> 1)];
v_line = (v_line & 7) << 3;
name = (attr & 0x07FF);
s = &name_lut[((attr >> 3) & 0x300) | (size << 4) | (nt_row << 2)];
/* pointer into line buffer */
lb = &buf[0x20 + xpos];
lb = (uint8 *)&buf[0x20 + (xpos - 0x80)];
/* number of tiles to draw */
/* adjusted for sprite limit */
if (pixelcount > max)
/* adjust width for sprite limit */
if (pixelcount > pixelmax)
{
width -= (pixelcount - max);
width = width - pixelcount + pixelmax;
}
width >>= 3;
/* number of tiles to draw */
width = width >> 3;
for(column = 0; column < width; column += 1, lb+=8)
/* render sprite cells (8-pixels column) */
for(column = 0; column < width; column++, lb+=8)
{
index = attr_mask | ((name + s[column]) & 0x07FF);
src = &bg_pattern_cache[(index << 6) | (v_line)];
@ -1536,7 +1535,7 @@ static void render_obj(int line, uint8 *buf, uint8 *table)
}
/* sprite limit (256 or 320 pixels) */
if (pixelcount >= max)
if (pixelcount >= pixelmax)
{
spr_over = 1;
return;
@ -1548,34 +1547,26 @@ static void render_obj(int line, uint8 *buf, uint8 *table)
static void render_obj_im2(int line, int odd, uint8 *buf, uint8 *table)
{
uint16 ypos;
uint16 attr;
uint16 xpos;
uint8 sizetab[] = {8, 16, 24, 32};
uint8 size;
uint8 *src;
int count,i;
int pixelcount = 0;
int width;
int height;
int i, count, column;
int xpos;
int v_line;
int column;
int max = bitmap.viewport.w;
int left = 0x80;
int right = 0x80 + max;
int pixelmax = bitmap.viewport.w;
int pixelcount = 0;
int masked = 0;
uint8 *s, *lb;
uint16 name, index;
uint8 palette;
uint8 *src, *s, *lb;
uint32 size, width;
uint32 attr, attr_mask, name, palette, index;
uint32 offs;
int attr_mask, nt_row;
int mask = 0;
object *obj_info = object_info[object_which];
for(count = 0; count < object_index_count; count += 1)
for(count = 0; count < object_count[object_which]; count ++)
{
xpos = object_info[count].xpos & 0x1ff;
/* sprite horizontal position */
xpos = obj_info[count].xpos;
/* sprite masking (requires at least one sprite with xpos > 0) */
if (xpos)
@ -1585,52 +1576,58 @@ static void render_obj_im2(int line, int odd, uint8 *buf, uint8 *table)
else if(spr_over)
{
spr_over = 0;
mask = 1;
masked = 1;
}
size = object_info[count].size & 0x0f;
/* sprite horizontal ofsfet */
xpos = xpos - 0x80;
/* sprite size */
size = obj_info[count].size;
width = sizetab[(size >> 2) & 3];
/* update pixel count (off-screen sprites included) */
/* update pixel count (off-screen sprites are included) */
pixelcount += width;
if(((xpos + width) >= left) && (xpos < right) && !mask)
/* draw visible sprites */
if (((xpos + width) >= 0) && (xpos < pixelmax) && !masked)
{
ypos = object_info[count].ypos;
attr = object_info[count].attr;
/* sprite attributes + pattern index */
attr = obj_info[count].attr;
attr_mask = (attr & 0x1800);
height = sizetab[size & 3];
palette = (attr >> 9) & 0x70;
name = (attr & 0x03FF);
v_line = (line - ypos);
nt_row = (v_line >> 3) & 3;
/* sprite vertical offset */
v_line = line - obj_info[count].ypos;
s = &name_lut[((attr >> 3) & 0x300) | (size << 4) | ((v_line & 0x18) >> 1)];
v_line = (((v_line & 7) << 1) | odd) << 3;
name = (attr & 0x03FF);
s = &name_lut[((attr >> 3) & 0x300) | (size << 4) | (nt_row << 2)];
/* pointer into line buffer */
lb = &buf[0x20 + xpos];
lb = (uint8 *)&buf[0x20 + (xpos - 0x80)];
/* adjust width for sprite limit */
if (pixelcount > pixelmax)
{
width = width - pixelcount + pixelmax;
}
/* number of tiles to draw */
/* adjusted for sprite limit */
if (pixelcount > max)
width -= (pixelcount - max);
width >>= 3;
width = width >> 3;
/* render sprite cells (8-pixels column) */
for(column = 0; column < width; column += 1, lb+=8)
{
index = (name + s[column]) & 0x3ff;
offs = index << 7 | attr_mask << 6 | v_line;
if(attr & 0x1000)
offs ^= 0x40;
if(attr & 0x1000) offs ^= 0x40;
src = &bg_pattern_cache[offs];
DRAW_SPRITE_TILE;
}
}
/* sprite limit (256 or 320 pixels) */
if (pixelcount >= max)
if (pixelcount >= pixelmax)
{
spr_over = 1;
return;
@ -1719,12 +1716,14 @@ void render_shutdown(void)
/*--------------------------------------------------------------------------*/
/* Line render function */
/*--------------------------------------------------------------------------*/
void blank_line(int line, int offset, int width)
{
memset(&tmp_buf[0x20 + offset], 0x40, width);
remap_buffer(line);
}
void render_line(int line)
{
/* display disabled */
if (reg[0] & 0x01) return;
uint8 *lb = tmp_buf;
int width = bitmap.viewport.w;
int x_offset = bitmap.viewport.x;
@ -1732,8 +1731,7 @@ void render_line(int line)
/* background color (blanked display or vertical borders) */
if (!(reg[1] & 0x40) || (status & 8))
{
width += 2 * x_offset;
memset(&lb[0x20 - x_offset], 0x40, width);
memset(&lb[0x20 - x_offset], 0x40, width + 2*x_offset);
}
else
{
@ -1793,24 +1791,27 @@ void render_line(int line)
}
/* left-most column blanking */
if(reg[0] & 0x20)
memset(&lb[0x20], 0x40, 0x08);
if(reg[0] & 0x20) memset(&lb[0x20], 0x40, 0x08);
/* horizontal borders */
if (x_offset)
{
memset(&lb[0x20 - x_offset], 0x40, x_offset);
memset(&lb[0x20 + width], 0x40, x_offset);
width += 2 * x_offset;
}
}
/* pixel color remapping */
remap_buffer(line,width);
remap_buffer(line);
}
void remap_buffer(int line, int width)
void remap_buffer(int line)
{
/* display disabled */
if (reg[0] & 0x01) return;
int width = bitmap.viewport.w + 2*bitmap.viewport.x;
/* get line offset from framebuffer */
line = (line + bitmap.viewport.y) % lines_per_frame;
@ -1914,37 +1915,40 @@ void parse_satb(int line)
uint16 *p = (uint16 *) &vram[satb];
uint16 *q = (uint16 *) &sat[0];
object_index_count = 0;
uint32 count = 0;
object *obj_info = object_info[object_which^1];
do
{
/* Read ypos & size from internal SAT */
ypos = (q[link] >> im2_flag) & 0x1FF;
size = q[link + 1] >> 8;
height = sizetab[size & 3];
if((line >= ypos) && (line < (ypos + height)))
{
/* sprite limit (max. 16 or 20 sprites displayed per line) */
if(object_index_count == limit)
/* Sprite limit (max. 16 or 20 sprites displayed per line) */
if(count == limit)
{
if(vint_pending == 0)
status |= 0x40;
return;
}
// using xpos from internal satb stops sprite x
// scrolling in bloodlin.bin,
// but this seems to go against the test prog
object_info[object_index_count].attr = p[link + 2];
object_info[object_index_count].xpos = p[link + 3];
object_info[object_index_count].ypos = ypos;
object_info[object_index_count].size = size;
++object_index_count;
}
link = (q[link + 1] & 0x7F) << 2;
if(link == 0)
break;
}
while (--total);
/* Update sprite list */
/* name, attribute & xpos are parsed from VRAM */
obj_info[count].attr = p[link + 2];
obj_info[count].xpos = p[link + 3] & 0x1ff;
obj_info[count].ypos = ypos;
obj_info[count].size = size & 0x0f;
++count;
}
/* Read link data from internal SAT */
link = (q[link + 1] & 0x7F) << 2;
if(link == 0) break;
}
while (--total);
/* Update sprite count for next line */
object_count[object_which^1] = count;
}

View File

@ -25,14 +25,16 @@
#define _RENDER_H_
/* Global variables */
extern uint32 object_index_count;
extern uint8 object_count[2];
extern uint8 object_which;
/* Function prototypes */
extern void render_init(void);
extern void render_reset(void);
extern void render_shutdown(void);
extern void render_line(int line);
extern void remap_buffer(int line,int width);
extern void remap_buffer(int line);
extern void blank_line(int line, int offset, int width);
extern void window_clip(void);
extern void parse_satb(int line);

View File

@ -31,7 +31,6 @@ t_snd snd;
uint32 mcycles_vdp;
uint32 mcycles_z80;
uint32 mcycles_68k;
uint32 hint_68k;
uint8 system_hw;
/****************************************************************
@ -338,7 +337,7 @@ void system_frame (int do_skip)
bitmap.viewport.changed = 1;
}
/* screen height */
/* active screen height */
if (reg[1] & 8)
{
bitmap.viewport.h = 240;
@ -350,7 +349,7 @@ void system_frame (int do_skip)
bitmap.viewport.y = (config.overscan & 1) ? (vdp_pal ? 32 : 8) : 0;
}
/* screen width */
/* active screen width */
if (reg[12] & 1)
{
bitmap.viewport.w = 320;
@ -374,8 +373,7 @@ void system_frame (int do_skip)
/* even/odd field flag (interlaced modes only) */
odd_frame ^= 1;
if (interlaced)
status |= (odd_frame << 4);
if (interlaced) status |= (odd_frame << 4);
/* reload HCounter */
int h_counter = reg[10];
@ -387,6 +385,10 @@ void system_frame (int do_skip)
/* reset line cycle count */
mcycles_vdp = 0;
/* parse sprites on line zero */
object_which = 1;
if (reg[1] & 0x40) parse_satb(0x80);
/* process scanlines */
for (line = 0; line < lines_per_frame; line ++)
{
@ -396,12 +398,8 @@ void system_frame (int do_skip)
/* update 6-Buttons or Menacer */
input_update();
/* 68k line cycle count */
hint_68k = mcycles_68k;
/* update VDP DMA */
if (dma_length)
vdp_update_dma();
if (dma_length) vdp_update_dma();
/* vertical blanking */
if (status & 8)
@ -413,8 +411,8 @@ void system_frame (int do_skip)
/* clear pending Z80 interrupt */
if (zirq)
{
zirq = 0;
z80_set_irq_line(0, CLEAR_LINE);
zirq = 0;
}
}
@ -453,15 +451,15 @@ void system_frame (int do_skip)
status |= 0x08;
/* render overscan */
if (!do_skip && bitmap.viewport.y)
if (!do_skip && (line < end))
render_line(line);
/* update inputs (doing this here fix Warriors of Eternal Sun) */
osd_input_Update();
/* Z80 interrupt is 16ms period (one frame) and 64us length (one scanline) */
zirq = 1;
z80_set_irq_line(0, ASSERT_LINE);
zirq = 1;
/* delay between VINT flag & V Interrupt (Ex-Mutants, Tyrant) */
m68k_run(mcycles_vdp + 588);
@ -469,37 +467,38 @@ void system_frame (int do_skip)
/* delay between VBLANK flag & V Interrupt (Dracula, OutRunners, VR Troopers) */
m68k_run(mcycles_vdp + 788);
if (zstate == 1)
z80_run(mcycles_vdp + 788);
else
mcycles_z80 = mcycles_vdp + 788;
if (zstate == 1) z80_run(mcycles_vdp + 788);
else mcycles_z80 = mcycles_vdp + 788;
/* V Interrupt */
vint_pending = 0x20;
if (reg[1] & 0x20)
irq_status = (irq_status & ~0x40) | 0x36;
}
else if (!do_skip)
else
{
/* sprites are processed during horizontal blanking */
if (reg[1] & 0x40)
parse_satb(0x80 + line);
/* swap sprite line buffers */
object_which ^= 1;
/* render scanline */
if (!do_skip)
{
render_line(line);
/* parse sprites on next line */
if ((reg[1] & 0x40) && (line < (bitmap.viewport.h - 1)))
parse_satb(0x81 + line);
}
}
}
/* process line */
m68k_run(mcycles_vdp + MCYCLES_PER_LINE);
if (zstate == 1)
z80_run(mcycles_vdp + MCYCLES_PER_LINE);
else
mcycles_z80 = mcycles_vdp + MCYCLES_PER_LINE;
if (zstate == 1) z80_run(mcycles_vdp + MCYCLES_PER_LINE);
else mcycles_z80 = mcycles_vdp + MCYCLES_PER_LINE;
/* SVP chip */
if (svp)
ssp1601_run(SVP_cycles);
if (svp) ssp1601_run(SVP_cycles);
/* update line cycle count */
mcycles_vdp += MCYCLES_PER_LINE;

View File

@ -77,7 +77,6 @@ extern t_snd snd;
extern uint32 mcycles_vdp;
extern uint32 mcycles_z80;
extern uint32 mcycles_68k;
extern uint32 hint_68k;
extern uint8 system_hw;
/* Function prototypes */

View File

@ -700,7 +700,7 @@ static void data_w(unsigned int data)
if (!(status & 8) && (reg[1]& 0x40) && (mcycles_68k <= (mcycles_vdp + 860)))
{
/* remap current line */
remap_buffer(v_counter,bitmap.viewport.w + 2*bitmap.viewport.x);
remap_buffer(v_counter);
#ifdef LOGVDP
error("Line remapped\n");
#endif
@ -829,33 +829,55 @@ static void reg_w(unsigned int r, unsigned int d)
}
}
/* Display status modified during HBLANK (Legend of Galahad, Lemmings 2, Formula 1 Championship, */
/* Nigel Mansell's World Championship Racing, ...) */
/* */
/* Note that this is not entirely correct since we are cheating with the HBLANK period limits and */
/* still redrawing the whole line. This is done because some games (forexample, the PAL version */
/* of Nigel Mansell's World Championship Racing) appear to disable display outside HBLANK. */
/* On real hardware, the raster line would appear partially blanked. */
/* Display status modified during active display (Legend of Galahad, Lemmings 2, */
/* Formula One Championship, Nigel Mansell's World Championship Racing, ...) */
if ((r & 0x40) && !(status & 8))
{
if (mcycles_68k <= (hint_68k + 860))
int offset = mcycles_68k - mcycles_vdp - 860;
if (offset <= 0)
{
/* If display was disabled during HBLANK (Mickey Mania 3D level), sprite processing is limited */
/* redraw entire line */
render_line(v_counter);
#ifdef LOGVDP
error("Line redrawn (%d sprites) \n",object_count[object_which^1]);
#endif
/* If display is was disabled during HBLANK (Mickey Mania 3D level), sprite processing is limited */
/* Below values have been deducted from testing on this game, accurate emulation would require */
/* to know exact sprite (pre)processing timings. Hopefully, they don't seem to break any other */
/* games, so they might not be so much inaccurate. */
if ((d&0x40) && (object_index_count > 5) && (mcycles_68k % MCYCLES_PER_LINE >= 360))
object_index_count = 5;
#ifdef LOGVDP
error("Line redrawn (%d sprites) \n",object_index_count);
#endif
/* redraw entire line */
render_line(v_counter);
if (d & 0x40)
{
parse_satb(0x81 + v_counter);
if ((object_count[object_which^1] > 5) && (mcycles_68k % MCYCLES_PER_LINE >= 360))
object_count[object_which^1] = 5;
}
}
#ifdef LOGVDP
else
error("Line NOT redrawn\n");
{
/* pixel offset */
if (reg[12] & 1) offset = offset / 8;
else offset = (offset / 10) + 16;
#ifdef LOGVDP
error("Line %d redrawn from pixel %d\n",v_counter,offset);
#endif
/* line is partially blanked */
if (offset < bitmap.viewport.w)
{
if (d & 0x40)
{
render_line(v_counter);
blank_line(v_counter, 0, offset);
}
else
{
blank_line(v_counter, offset, bitmap.viewport.w - offset);
}
}
}
}
break;
@ -896,7 +918,7 @@ static void reg_w(unsigned int r, unsigned int d)
if (!(status & 8) && (mcycles_68k <= (mcycles_vdp + 860)))
{
/* remap colors */
remap_buffer(v_counter,bitmap.viewport.w + 2*bitmap.viewport.x);
remap_buffer(v_counter);
#ifdef LOGVDP
error("--> Line remapped\n");
#endif