From daf11963b165390e623fbb0e2d986565138f851f Mon Sep 17 00:00:00 2001 From: ekeeke31 Date: Mon, 28 Jun 2010 22:49:17 +0000 Subject: [PATCH] .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 --- HISTORY.txt | 19 ++-- source/render.c | 236 ++++++++++++++++++++++++------------------------ source/render.h | 6 +- source/system.c | 55 ++++++----- source/system.h | 1 - source/vdp.c | 64 ++++++++----- 6 files changed, 204 insertions(+), 177 deletions(-) diff --git a/HISTORY.txt b/HISTORY.txt index fc2aac1..e621c6f 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -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] --------------- diff --git a/source/render.c b/source/render.c index 0866f01..da50ba3 100644 --- a/source/render.c +++ b/source/render.c @@ -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; + memset(&lb[0x20 - x_offset], 0x40, x_offset); + memset(&lb[0x20 + width], 0x40, 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; @@ -1913,38 +1914,41 @@ 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; + status |= 0x40; + break; } - // 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; + /* 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; + if(link == 0) break; } while (--total); + + /* Update sprite count for next line */ + object_count[object_which^1] = count; } diff --git a/source/render.h b/source/render.h index 2b9442f..32c2cdc 100644 --- a/source/render.h +++ b/source/render.h @@ -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); diff --git a/source/system.c b/source/system.c index 812c2c6..e6f2acb 100644 --- a/source/system.c +++ b/source/system.c @@ -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 */ - render_line(line); + 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; diff --git a/source/system.h b/source/system.h index 6d8e263..e93ac1d 100644 --- a/source/system.h +++ b/source/system.h @@ -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 */ diff --git a/source/vdp.c b/source/vdp.c index a371c2f..54ebb69 100644 --- a/source/vdp.c +++ b/source/vdp.c @@ -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,34 +829,56 @@ 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; case 2: /* NTAB */ @@ -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