kopia lustrzana https://github.com/Jean-MarcHarvengt/MCUME
1341 wiersze
35 KiB
C
1341 wiersze
35 KiB
C
/*
|
|
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
|
|
**
|
|
**
|
|
** This program is free software; you can redistribute it and/or
|
|
** modify it under the terms of version 2 of the GNU Library General
|
|
** Public License as published by the Free Software Foundation.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
** Library General Public License for more details. To obtain a
|
|
** copy of the GNU Library General Public License, write to the Free
|
|
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
**
|
|
** Any permitted reproduction of these routines, in whole or in part,
|
|
** must bear this legend.
|
|
**
|
|
**
|
|
** nes_ppu.c
|
|
**
|
|
** NES PPU emulation
|
|
** $Id: nes_ppu.c,v 1.2 2001/04/27 14:37:11 neil Exp $
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "noftypes.h"
|
|
#include "nes_ppu.h"
|
|
#include "nes.h"
|
|
#include "nes6502.h"
|
|
#include "log.h"
|
|
#include "nes_mmc.h"
|
|
|
|
#include "bitmap.h"
|
|
#include "vid_drv.h"
|
|
#include "nes_pal.h"
|
|
#include "nesinput.h"
|
|
|
|
|
|
/* PPU access */
|
|
#define PPU_MEM(x) ppu.page[(x) >> 10][(x)]
|
|
|
|
/* Background (color 0) and solid sprite pixel flags */
|
|
#define BG_TRANS 0x80
|
|
#define SP_PIXEL 0x40
|
|
#define BG_CLEAR(V) ((V) & BG_TRANS)
|
|
#define BG_SOLID(V) (0 == BG_CLEAR(V))
|
|
#define SP_CLEAR(V) (0 == ((V) & SP_PIXEL))
|
|
|
|
/* Full BG color */
|
|
#define FULLBG (ppu.palette[0] | BG_TRANS)
|
|
|
|
/* the NES PPU */
|
|
static ppu_t ppu;
|
|
|
|
|
|
void ppu_displaysprites(bool display)
|
|
{
|
|
ppu.drawsprites = display;
|
|
}
|
|
|
|
void ppu_setcontext(ppu_t *src_ppu)
|
|
{
|
|
int nametab[4];
|
|
ASSERT(src_ppu);
|
|
ppu = *src_ppu;
|
|
|
|
/* we can't just copy contexts here, because more than likely,
|
|
** the top 8 pages of the ppu are pointing to internal PPU memory,
|
|
** which means we need to recalculate the page pointers.
|
|
** TODO: we can either get rid of the page pointing in the code,
|
|
** or add more robust checks to make sure that pages 8-15 are
|
|
** definitely pointing to internal PPU RAM, not just something
|
|
** that some crazy mapper paged in.
|
|
*/
|
|
nametab[0] = (src_ppu->page[8] - src_ppu->nametab + 0x2000) >> 10;
|
|
nametab[1] = (src_ppu->page[9] - src_ppu->nametab + 0x2400) >> 10;
|
|
nametab[2] = (src_ppu->page[10] - src_ppu->nametab + 0x2800) >> 10;
|
|
nametab[3] = (src_ppu->page[11] - src_ppu->nametab + 0x2C00) >> 10;
|
|
|
|
ppu.page[8] = ppu.nametab + (nametab[0] << 10) - 0x2000;
|
|
ppu.page[9] = ppu.nametab + (nametab[1] << 10) - 0x2400;
|
|
ppu.page[10] = ppu.nametab + (nametab[2] << 10) - 0x2800;
|
|
ppu.page[11] = ppu.nametab + (nametab[3] << 10) - 0x2C00;
|
|
ppu.page[12] = ppu.page[8] - 0x1000;
|
|
ppu.page[13] = ppu.page[9] - 0x1000;
|
|
ppu.page[14] = ppu.page[10] - 0x1000;
|
|
ppu.page[15] = ppu.page[11] - 0x1000;
|
|
}
|
|
|
|
void ppu_getcontext(ppu_t *dest_ppu)
|
|
{
|
|
int nametab[4];
|
|
|
|
ASSERT(dest_ppu);
|
|
*dest_ppu = ppu;
|
|
|
|
/* we can't just copy contexts here, because more than likely,
|
|
** the top 8 pages of the ppu are pointing to internal PPU memory,
|
|
** which means we need to recalculate the page pointers.
|
|
** TODO: we can either get rid of the page pointing in the code,
|
|
** or add more robust checks to make sure that pages 8-15 are
|
|
** definitely pointing to internal PPU RAM, not just something
|
|
** that some crazy mapper paged in.
|
|
*/
|
|
nametab[0] = (ppu.page[8] - ppu.nametab + 0x2000) >> 10;
|
|
nametab[1] = (ppu.page[9] - ppu.nametab + 0x2400) >> 10;
|
|
nametab[2] = (ppu.page[10] - ppu.nametab + 0x2800) >> 10;
|
|
nametab[3] = (ppu.page[11] - ppu.nametab + 0x2C00) >> 10;
|
|
|
|
dest_ppu->page[8] = dest_ppu->nametab + (nametab[0] << 10) - 0x2000;
|
|
dest_ppu->page[9] = dest_ppu->nametab + (nametab[1] << 10) - 0x2400;
|
|
dest_ppu->page[10] = dest_ppu->nametab + (nametab[2] << 10) - 0x2800;
|
|
dest_ppu->page[11] = dest_ppu->nametab + (nametab[3] << 10) - 0x2C00;
|
|
dest_ppu->page[12] = dest_ppu->page[8] - 0x1000;
|
|
dest_ppu->page[13] = dest_ppu->page[9] - 0x1000;
|
|
dest_ppu->page[14] = dest_ppu->page[10] - 0x1000;
|
|
dest_ppu->page[15] = dest_ppu->page[11] - 0x1000;
|
|
}
|
|
|
|
ppu_t *ppu_create(void)
|
|
{
|
|
static bool pal_generated = false;
|
|
ppu_t *temp;
|
|
|
|
temp = malloc(sizeof(ppu_t));
|
|
if (NULL == temp)
|
|
return NULL;
|
|
|
|
memset(temp, 0, sizeof(ppu_t));
|
|
|
|
temp->latchfunc = NULL;
|
|
temp->vromswitch = NULL;
|
|
temp->vram_present = false;
|
|
temp->drawsprites = true;
|
|
|
|
/* TODO: probably a better way to do this... */
|
|
if (false == pal_generated)
|
|
{
|
|
pal_generate();
|
|
pal_generated = true;
|
|
}
|
|
|
|
ppu_setdefaultpal(temp);
|
|
|
|
return temp;
|
|
}
|
|
|
|
void ppu_destroy(ppu_t **src_ppu)
|
|
{
|
|
if (*src_ppu)
|
|
{
|
|
free(*src_ppu);
|
|
*src_ppu = NULL;
|
|
}
|
|
}
|
|
|
|
void ppu_setpage(int size, int page_num, uint8 *location)
|
|
{
|
|
/* deliberately fall through */
|
|
switch (size)
|
|
{
|
|
case 8:
|
|
ppu.page[page_num++] = location;
|
|
ppu.page[page_num++] = location;
|
|
ppu.page[page_num++] = location;
|
|
ppu.page[page_num++] = location;
|
|
case 4:
|
|
ppu.page[page_num++] = location;
|
|
ppu.page[page_num++] = location;
|
|
case 2:
|
|
ppu.page[page_num++] = location;
|
|
case 1:
|
|
ppu.page[page_num++] = location;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* make sure $3000-$3F00 mirrors $2000-$2F00 */
|
|
void ppu_mirrorhipages(void)
|
|
{
|
|
ppu.page[12] = ppu.page[8] - 0x1000;
|
|
ppu.page[13] = ppu.page[9] - 0x1000;
|
|
ppu.page[14] = ppu.page[10] - 0x1000;
|
|
ppu.page[15] = ppu.page[11] - 0x1000;
|
|
}
|
|
|
|
void ppu_mirror(int nt1, int nt2, int nt3, int nt4)
|
|
{
|
|
ppu.page[8] = ppu.nametab + (nt1 << 10) - 0x2000;
|
|
ppu.page[9] = ppu.nametab + (nt2 << 10) - 0x2400;
|
|
ppu.page[10] = ppu.nametab + (nt3 << 10) - 0x2800;
|
|
ppu.page[11] = ppu.nametab + (nt4 << 10) - 0x2C00;
|
|
ppu.page[12] = ppu.page[8] - 0x1000;
|
|
ppu.page[13] = ppu.page[9] - 0x1000;
|
|
ppu.page[14] = ppu.page[10] - 0x1000;
|
|
ppu.page[15] = ppu.page[11] - 0x1000;
|
|
}
|
|
|
|
/* bleh, for snss */
|
|
uint8 *ppu_getpage(int page)
|
|
{
|
|
return ppu.page[page];
|
|
}
|
|
|
|
static void mem_trash(uint8 *buffer, int length)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < length; i++)
|
|
buffer[i] = (uint8) rand();
|
|
}
|
|
|
|
/* reset state of ppu */
|
|
void ppu_reset(int reset_type)
|
|
{
|
|
if (HARD_RESET == reset_type)
|
|
mem_trash(ppu.oam, 256);
|
|
|
|
ppu.ctrl0 = 0;
|
|
ppu.ctrl1 = PPU_CTRL1F_OBJON | PPU_CTRL1F_BGON;
|
|
ppu.stat = 0;
|
|
ppu.flipflop = 0;
|
|
ppu.vaddr = ppu.vaddr_latch = 0x2000;
|
|
ppu.oam_addr = 0;
|
|
ppu.tile_xofs = 0;
|
|
|
|
ppu.latch = 0;
|
|
ppu.vram_accessible = true;
|
|
}
|
|
|
|
/* we render a scanline of graphics first so we know exactly
|
|
** where the sprite 0 strike is going to occur (in terms of
|
|
** cpu cycles), using the relation that 3 pixels == 1 cpu cycle
|
|
*/
|
|
static void ppu_setstrike(int x_loc)
|
|
{
|
|
if (false == ppu.strikeflag)
|
|
{
|
|
ppu.strikeflag = true;
|
|
|
|
/* 3 pixels per cpu cycle */
|
|
ppu.strike_cycle = nes6502_getcycles(false) + (x_loc / 3);
|
|
}
|
|
}
|
|
|
|
static void ppu_oamdma(uint8 value)
|
|
{
|
|
uint32 cpu_address;
|
|
uint8 oam_loc;
|
|
|
|
cpu_address = (uint32) (value << 8);
|
|
|
|
/* Sprite DMA starts at the current SPRRAM address */
|
|
oam_loc = ppu.oam_addr;
|
|
do
|
|
{
|
|
ppu.oam[oam_loc++] = nes6502_getbyte(cpu_address++);
|
|
}
|
|
while (oam_loc != ppu.oam_addr);
|
|
|
|
/* TODO: enough with houdini */
|
|
cpu_address -= 256;
|
|
/* Odd address in $2003 */
|
|
if ((ppu.oam_addr >> 2) & 1)
|
|
{
|
|
for (oam_loc = 4; oam_loc < 8; oam_loc++)
|
|
ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
|
|
cpu_address += 248;
|
|
for (oam_loc = 0; oam_loc < 4; oam_loc++)
|
|
ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
|
|
}
|
|
/* Even address in $2003 */
|
|
else
|
|
{
|
|
for (oam_loc = 0; oam_loc < 8; oam_loc++)
|
|
ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
|
|
}
|
|
|
|
/* make the CPU spin for DMA cycles */
|
|
nes6502_burn(513);
|
|
nes6502_release();
|
|
}
|
|
|
|
/* TODO: this isn't the PPU! */
|
|
void ppu_writehigh(uint32 address, uint8 value)
|
|
{
|
|
switch (address)
|
|
{
|
|
case PPU_OAMDMA:
|
|
ppu_oamdma(value);
|
|
break;
|
|
|
|
case PPU_JOY0:
|
|
/* VS system VROM switching - bleh!*/
|
|
if (ppu.vromswitch)
|
|
ppu.vromswitch(value);
|
|
|
|
/* see if we need to strobe them joypads */
|
|
value &= 1;
|
|
|
|
if (0 == value && ppu.strobe)
|
|
input_strobe();
|
|
|
|
ppu.strobe = value;
|
|
break;
|
|
|
|
case PPU_JOY1: /* frame IRQ control */
|
|
nes_setfiq(value);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* TODO: this isn't the PPU! */
|
|
uint8 ppu_readhigh(uint32 address)
|
|
{
|
|
uint8 value;
|
|
|
|
switch (address)
|
|
{
|
|
case PPU_JOY0:
|
|
value = input_get(INP_JOYPAD0);
|
|
break;
|
|
|
|
case PPU_JOY1:
|
|
/* TODO: better input handling */
|
|
value = input_get(INP_ZAPPER | INP_JOYPAD1
|
|
/*| INP_ARKANOID*/
|
|
/*| INP_POWERPAD*/);
|
|
break;
|
|
|
|
default:
|
|
value = 0xFF;
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/* Read from $2000-$2007 */
|
|
uint8 ppu_read(uint32 address)
|
|
{
|
|
uint8 value;
|
|
|
|
/* handle mirrored reads up to $3FFF */
|
|
switch (address & 0x2007)
|
|
{
|
|
case PPU_STAT:
|
|
value = (ppu.stat & 0xE0) | (ppu.latch & 0x1F);
|
|
|
|
if (ppu.strikeflag)
|
|
{
|
|
if (nes6502_getcycles(false) >= ppu.strike_cycle)
|
|
value |= PPU_STATF_STRIKE;
|
|
}
|
|
|
|
/* clear both vblank flag and vram address flipflop */
|
|
ppu.stat &= ~PPU_STATF_VBLANK;
|
|
ppu.flipflop = 0;
|
|
break;
|
|
|
|
case PPU_VDATA:
|
|
/* buffered VRAM reads */
|
|
value = ppu.latch = ppu.vdata_latch;
|
|
|
|
/* VRAM only accessible during VBL */
|
|
if ((ppu.bg_on || ppu.obj_on) && !ppu.vram_accessible)
|
|
{
|
|
ppu.vdata_latch = 0xFF;
|
|
log_printf("VRAM read at $%04X, scanline %d\n",
|
|
ppu.vaddr, nes_getcontextptr()->scanline);
|
|
}
|
|
else
|
|
{
|
|
uint32 addr = ppu.vaddr;
|
|
if (addr >= 0x3000)
|
|
addr -= 0x1000;
|
|
ppu.vdata_latch = PPU_MEM(addr);
|
|
}
|
|
|
|
ppu.vaddr += ppu.vaddr_inc;
|
|
ppu.vaddr &= 0x3FFF;
|
|
break;
|
|
|
|
case PPU_OAMDATA:
|
|
case PPU_CTRL0:
|
|
case PPU_CTRL1:
|
|
case PPU_OAMADDR:
|
|
case PPU_SCROLL:
|
|
case PPU_VADDR:
|
|
default:
|
|
value = ppu.latch;
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/* Write to $2000-$2007 */
|
|
void ppu_write(uint32 address, uint8 value)
|
|
{
|
|
/* write goes into ppu latch... */
|
|
ppu.latch = value;
|
|
|
|
switch (address & 0x2007)
|
|
{
|
|
case PPU_CTRL0:
|
|
ppu.ctrl0 = value;
|
|
|
|
ppu.obj_height = (value & PPU_CTRL0F_OBJ16) ? 16 : 8;
|
|
ppu.bg_base = (value & PPU_CTRL0F_BGADDR) ? 0x1000 : 0;
|
|
ppu.obj_base = (value & PPU_CTRL0F_OBJADDR) ? 0x1000 : 0;
|
|
ppu.vaddr_inc = (value & PPU_CTRL0F_ADDRINC) ? 32 : 1;
|
|
ppu.tile_nametab = value & PPU_CTRL0F_NAMETAB;
|
|
|
|
/* Mask out bits 10 & 11 in the ppu latch */
|
|
ppu.vaddr_latch &= ~0x0C00;
|
|
ppu.vaddr_latch |= ((value & 3) << 10);
|
|
break;
|
|
|
|
case PPU_CTRL1:
|
|
ppu.ctrl1 = value;
|
|
|
|
ppu.obj_on = (value & PPU_CTRL1F_OBJON) ? true : false;
|
|
ppu.bg_on = (value & PPU_CTRL1F_BGON) ? true : false;
|
|
ppu.obj_mask = (value & PPU_CTRL1F_OBJMASK) ? false : true;
|
|
ppu.bg_mask = (value & PPU_CTRL1F_BGMASK) ? false : true;
|
|
break;
|
|
|
|
case PPU_OAMADDR:
|
|
ppu.oam_addr = value;
|
|
break;
|
|
|
|
case PPU_OAMDATA:
|
|
ppu.oam[ppu.oam_addr++] = value;
|
|
break;
|
|
|
|
case PPU_SCROLL:
|
|
if (0 == ppu.flipflop)
|
|
{
|
|
/* Mask out bits 4 - 0 in the ppu latch */
|
|
ppu.vaddr_latch &= ~0x001F;
|
|
ppu.vaddr_latch |= (value >> 3); /* Tile number */
|
|
ppu.tile_xofs = (value & 7); /* Tile offset (0-7 pix) */
|
|
}
|
|
else
|
|
{
|
|
/* Mask out bits 14-12 and 9-5 in the ppu latch */
|
|
ppu.vaddr_latch &= ~0x73E0;
|
|
ppu.vaddr_latch |= ((value & 0xF8) << 2); /* Tile number */
|
|
ppu.vaddr_latch |= ((value & 7) << 12); /* Tile offset (0-7 pix) */
|
|
}
|
|
|
|
ppu.flipflop ^= 1;
|
|
|
|
break;
|
|
|
|
case PPU_VADDR:
|
|
if (0 == ppu.flipflop)
|
|
{
|
|
/* Mask out bits 15-8 in ppu latch */
|
|
ppu.vaddr_latch &= ~0xFF00;
|
|
ppu.vaddr_latch |= ((value & 0x3F) << 8);
|
|
}
|
|
else
|
|
{
|
|
/* Mask out bits 7-0 in ppu latch */
|
|
ppu.vaddr_latch &= ~0x00FF;
|
|
ppu.vaddr_latch |= value;
|
|
ppu.vaddr = ppu.vaddr_latch;
|
|
}
|
|
|
|
ppu.flipflop ^= 1;
|
|
|
|
break;
|
|
|
|
case PPU_VDATA:
|
|
if (ppu.vaddr < 0x3F00)
|
|
{
|
|
/* VRAM only accessible during scanlines 241-260 */
|
|
if ((ppu.bg_on || ppu.obj_on) && !ppu.vram_accessible)
|
|
{
|
|
log_printf("VRAM write to $%04X, scanline %d\n",
|
|
ppu.vaddr, nes_getcontextptr()->scanline);
|
|
PPU_MEM(ppu.vaddr) = 0xFF; /* corrupt */
|
|
}
|
|
else
|
|
{
|
|
uint32 addr = ppu.vaddr;
|
|
|
|
if (false == ppu.vram_present && addr >= 0x3000)
|
|
ppu.vaddr -= 0x1000;
|
|
|
|
PPU_MEM(addr) = value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (0 == (ppu.vaddr & 0x0F))
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i ++)
|
|
ppu.palette[i << 2] = (value & 0x3F) | BG_TRANS;
|
|
}
|
|
else if (ppu.vaddr & 3)
|
|
{
|
|
ppu.palette[ppu.vaddr & 0x1F] = value & 0x3F;
|
|
}
|
|
}
|
|
|
|
ppu.vaddr += ppu.vaddr_inc;
|
|
ppu.vaddr &= 0x3FFF;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Builds a 256 color 8-bit palette based on a 64-color NES palette
|
|
** Note that we set it up 3 times so that we flip bits on the primary
|
|
** NES buffer for priorities
|
|
*/
|
|
static void ppu_buildpalette(ppu_t *src_ppu, rgb_t *pal)
|
|
{
|
|
int i;
|
|
|
|
/* Set it up 3 times, for sprite priority/BG transparency trickery */
|
|
for (i = 0; i < 64; i++)
|
|
{
|
|
src_ppu->curpal[i].r = src_ppu->curpal[i + 64].r
|
|
= src_ppu->curpal[i + 128].r = pal[i].r;
|
|
src_ppu->curpal[i].g = src_ppu->curpal[i + 64].g
|
|
= src_ppu->curpal[i + 128].g = pal[i].g;
|
|
src_ppu->curpal[i].b = src_ppu->curpal[i + 64].b
|
|
= src_ppu->curpal[i + 128].b = pal[i].b;
|
|
}
|
|
}
|
|
|
|
/* build the emulator specific palette based on a 64-entry palette
|
|
** input palette can be either nes_palette or a 64-entry RGB palette
|
|
** read in from disk (i.e. for VS games)
|
|
*/
|
|
void ppu_setpal(ppu_t *src_ppu, rgb_t *pal)
|
|
{
|
|
ppu_buildpalette(src_ppu, pal);
|
|
vid_setpalette(src_ppu->curpal);
|
|
}
|
|
|
|
void ppu_setdefaultpal(ppu_t *src_ppu)
|
|
{
|
|
ppu_setpal(src_ppu, nes_palette);
|
|
}
|
|
|
|
void ppu_setlatchfunc(ppulatchfunc_t func)
|
|
{
|
|
ppu.latchfunc = func;
|
|
}
|
|
|
|
void ppu_setvromswitch(ppuvromswitch_t func)
|
|
{
|
|
ppu.vromswitch = func;
|
|
}
|
|
|
|
/* rendering routines */
|
|
INLINE void draw_bgtile(uint8 *surface, uint8 pat1, uint8 pat2,
|
|
const uint8 *colors)
|
|
{
|
|
uint32 pattern = ((pat2 & 0xAA) << 8) | ((pat2 & 0x55) << 1)
|
|
| ((pat1 & 0xAA) << 7) | (pat1 & 0x55);
|
|
|
|
*surface++ = colors[(pattern >> 14) & 3];
|
|
*surface++ = colors[(pattern >> 6) & 3];
|
|
*surface++ = colors[(pattern >> 12) & 3];
|
|
*surface++ = colors[(pattern >> 4) & 3];
|
|
*surface++ = colors[(pattern >> 10) & 3];
|
|
*surface++ = colors[(pattern >> 2) & 3];
|
|
*surface++ = colors[(pattern >> 8) & 3];
|
|
*surface = colors[pattern & 3];
|
|
}
|
|
|
|
INLINE int draw_oamtile(uint8 *surface, uint8 attrib, uint8 pat1,
|
|
uint8 pat2, const uint8 *col_tbl, bool check_strike)
|
|
{
|
|
int strike_pixel = -1;
|
|
uint32 color = ((pat2 & 0xAA) << 8) | ((pat2 & 0x55) << 1)
|
|
| ((pat1 & 0xAA) << 7) | (pat1 & 0x55);
|
|
|
|
/* sprite is not 100% transparent */
|
|
if (color)
|
|
{
|
|
uint8 colors[8];
|
|
|
|
/* swap pixels around if our tile is flipped */
|
|
if (0 == (attrib & OAMF_HFLIP))
|
|
{
|
|
colors[0] = (color >> 14) & 3;
|
|
colors[1] = (color >> 6) & 3;
|
|
colors[2] = (color >> 12) & 3;
|
|
colors[3] = (color >> 4) & 3;
|
|
colors[4] = (color >> 10) & 3;
|
|
colors[5] = (color >> 2) & 3;
|
|
colors[6] = (color >> 8) & 3;
|
|
colors[7] = color & 3;
|
|
}
|
|
else
|
|
{
|
|
colors[7] = (color >> 14) & 3;
|
|
colors[6] = (color >> 6) & 3;
|
|
colors[5] = (color >> 12) & 3;
|
|
colors[4] = (color >> 4) & 3;
|
|
colors[3] = (color >> 10) & 3;
|
|
colors[2] = (color >> 2) & 3;
|
|
colors[1] = (color >> 8) & 3;
|
|
colors[0] = color & 3;
|
|
}
|
|
|
|
/* check for solid sprite pixel overlapping solid bg pixel */
|
|
if (check_strike)
|
|
{
|
|
if (colors[0] && BG_SOLID(surface[0]))
|
|
strike_pixel = 0;
|
|
else if (colors[1] && BG_SOLID(surface[1]))
|
|
strike_pixel = 1;
|
|
else if (colors[2] && BG_SOLID(surface[2]))
|
|
strike_pixel = 2;
|
|
else if (colors[3] && BG_SOLID(surface[3]))
|
|
strike_pixel = 3;
|
|
else if (colors[4] && BG_SOLID(surface[4]))
|
|
strike_pixel = 4;
|
|
else if (colors[5] && BG_SOLID(surface[5]))
|
|
strike_pixel = 5;
|
|
else if (colors[6] && BG_SOLID(surface[6]))
|
|
strike_pixel = 6;
|
|
else if (colors[7] && BG_SOLID(surface[7]))
|
|
strike_pixel = 7;
|
|
}
|
|
|
|
/* draw the character */
|
|
if (attrib & OAMF_BEHIND)
|
|
{
|
|
if (colors[0])
|
|
surface[0] = SP_PIXEL | (BG_CLEAR(surface[0]) ? col_tbl[colors[0]] : surface[0]);
|
|
if (colors[1])
|
|
surface[1] = SP_PIXEL | (BG_CLEAR(surface[1]) ? col_tbl[colors[1]] : surface[1]);
|
|
if (colors[2])
|
|
surface[2] = SP_PIXEL | (BG_CLEAR(surface[2]) ? col_tbl[colors[2]] : surface[2]);
|
|
if (colors[3])
|
|
surface[3] = SP_PIXEL | (BG_CLEAR(surface[3]) ? col_tbl[colors[3]] : surface[3]);
|
|
if (colors[4])
|
|
surface[4] = SP_PIXEL | (BG_CLEAR(surface[4]) ? col_tbl[colors[4]] : surface[4]);
|
|
if (colors[5])
|
|
surface[5] = SP_PIXEL | (BG_CLEAR(surface[5]) ? col_tbl[colors[5]] : surface[5]);
|
|
if (colors[6])
|
|
surface[6] = SP_PIXEL | (BG_CLEAR(surface[6]) ? col_tbl[colors[6]] : surface[6]);
|
|
if (colors[7])
|
|
surface[7] = SP_PIXEL | (BG_CLEAR(surface[7]) ? col_tbl[colors[7]] : surface[7]);
|
|
}
|
|
else
|
|
{
|
|
if (colors[0] && SP_CLEAR(surface[0]))
|
|
surface[0] = SP_PIXEL | col_tbl[colors[0]];
|
|
if (colors[1] && SP_CLEAR(surface[1]))
|
|
surface[1] = SP_PIXEL | col_tbl[colors[1]];
|
|
if (colors[2] && SP_CLEAR(surface[2]))
|
|
surface[2] = SP_PIXEL | col_tbl[colors[2]];
|
|
if (colors[3] && SP_CLEAR(surface[3]))
|
|
surface[3] = SP_PIXEL | col_tbl[colors[3]];
|
|
if (colors[4] && SP_CLEAR(surface[4]))
|
|
surface[4] = SP_PIXEL | col_tbl[colors[4]];
|
|
if (colors[5] && SP_CLEAR(surface[5]))
|
|
surface[5] = SP_PIXEL | col_tbl[colors[5]];
|
|
if (colors[6] && SP_CLEAR(surface[6]))
|
|
surface[6] = SP_PIXEL | col_tbl[colors[6]];
|
|
if (colors[7] && SP_CLEAR(surface[7]))
|
|
surface[7] = SP_PIXEL | col_tbl[colors[7]];
|
|
}
|
|
}
|
|
|
|
return strike_pixel;
|
|
}
|
|
|
|
static void ppu_renderbg(uint8 *vidbuf)
|
|
{
|
|
uint8 *bmp_ptr, *data_ptr, *tile_ptr, *attrib_ptr;
|
|
uint32 refresh_vaddr, bg_offset, attrib_base;
|
|
int tile_count;
|
|
uint8 tile_index, x_tile, y_tile;
|
|
uint8 col_high, attrib, attrib_shift;
|
|
|
|
/* draw a line of transparent background color if bg is disabled */
|
|
if (false == ppu.bg_on)
|
|
{
|
|
memset(vidbuf, FULLBG, NES_SCREEN_WIDTH);
|
|
return;
|
|
}
|
|
|
|
bmp_ptr = vidbuf - ppu.tile_xofs; /* scroll x */
|
|
refresh_vaddr = 0x2000 + (ppu.vaddr & 0x0FE0); /* mask out x tile */
|
|
x_tile = ppu.vaddr & 0x1F;
|
|
y_tile = (ppu.vaddr >> 5) & 0x1F; /* to simplify calculations */
|
|
bg_offset = ((ppu.vaddr >> 12) & 7) + ppu.bg_base; /* offset in y tile */
|
|
|
|
/* calculate initial values */
|
|
tile_ptr = &PPU_MEM(refresh_vaddr + x_tile); /* pointer to tile index */
|
|
attrib_base = (refresh_vaddr & 0x2C00) + 0x3C0 + ((y_tile & 0x1C) << 1);
|
|
attrib_ptr = &PPU_MEM(attrib_base + (x_tile >> 2));
|
|
attrib = *attrib_ptr++;
|
|
attrib_shift = (x_tile & 2) + ((y_tile & 2) << 1);
|
|
col_high = ((attrib >> attrib_shift) & 3) << 2;
|
|
|
|
/* ppu fetches 33 tiles */
|
|
tile_count = 33;
|
|
while (tile_count--)
|
|
{
|
|
/* Tile number from nametable */
|
|
tile_index = *tile_ptr++;
|
|
data_ptr = &PPU_MEM(bg_offset + (tile_index << 4));
|
|
|
|
/* Handle $FD/$FE tile VROM switching (PunchOut) */
|
|
if (ppu.latchfunc)
|
|
ppu.latchfunc(ppu.bg_base, tile_index);
|
|
|
|
draw_bgtile(bmp_ptr, data_ptr[0], data_ptr[8], ppu.palette + col_high);
|
|
bmp_ptr += 8;
|
|
|
|
x_tile++;
|
|
|
|
if (0 == (x_tile & 1)) /* check every 2 tiles */
|
|
{
|
|
if (0 == (x_tile & 3)) /* check every 4 tiles */
|
|
{
|
|
if (32 == x_tile) /* check every 32 tiles */
|
|
{
|
|
x_tile = 0;
|
|
refresh_vaddr ^= (1 << 10); /* switch nametable */
|
|
attrib_base ^= (1 << 10);
|
|
|
|
/* recalculate pointers */
|
|
tile_ptr = &PPU_MEM(refresh_vaddr);
|
|
attrib_ptr = &PPU_MEM(attrib_base);
|
|
}
|
|
|
|
/* Get the attribute byte */
|
|
attrib = *attrib_ptr++;
|
|
}
|
|
|
|
attrib_shift ^= 2;
|
|
col_high = ((attrib >> attrib_shift) & 3) << 2;
|
|
}
|
|
}
|
|
|
|
/* Blank left hand column if need be */
|
|
if (ppu.bg_mask)
|
|
{
|
|
uint32 *buf_ptr = (uint32 *) vidbuf;
|
|
uint32 bg_clear = FULLBG | FULLBG << 8 | FULLBG << 16 | FULLBG << 24;
|
|
|
|
((uint32 *) buf_ptr)[0] = bg_clear;
|
|
((uint32 *) buf_ptr)[1] = bg_clear;
|
|
}
|
|
}
|
|
|
|
/* OAM entry */
|
|
typedef struct obj_s
|
|
{
|
|
uint8 y_loc;
|
|
uint8 tile;
|
|
uint8 atr;
|
|
uint8 x_loc;
|
|
} obj_t;
|
|
|
|
/* TODO: fetch valid OAM a scanline before, like the Real Thing */
|
|
static void ppu_renderoam(uint8 *vidbuf, int scanline)
|
|
{
|
|
uint8 *buf_ptr;
|
|
uint32 vram_offset, savecol[2];
|
|
int sprite_num, spritecount;
|
|
obj_t *sprite_ptr;
|
|
uint8 sprite_height;
|
|
|
|
if (false == ppu.obj_on)
|
|
return;
|
|
|
|
/* Get our buffer pointer */
|
|
buf_ptr = vidbuf;
|
|
|
|
/* Save left hand column? */
|
|
if (ppu.obj_mask)
|
|
{
|
|
savecol[0] = ((uint32 *) buf_ptr)[0];
|
|
savecol[1] = ((uint32 *) buf_ptr)[1];
|
|
}
|
|
|
|
sprite_height = ppu.obj_height;
|
|
vram_offset = ppu.obj_base;
|
|
spritecount = 0;
|
|
|
|
sprite_ptr = (obj_t *) ppu.oam;
|
|
|
|
for (sprite_num = 0; sprite_num < 64; sprite_num++, sprite_ptr++)
|
|
{
|
|
uint8 *data_ptr, *bmp_ptr;
|
|
uint32 vram_adr;
|
|
int y_offset;
|
|
uint8 tile_index, attrib, col_high;
|
|
uint8 sprite_y, sprite_x;
|
|
bool check_strike;
|
|
int strike_pixel;
|
|
|
|
sprite_y = sprite_ptr->y_loc + 1;
|
|
|
|
/* Check to see if sprite is out of range */
|
|
if ((sprite_y > scanline) || (sprite_y <= (scanline - sprite_height))
|
|
|| (0 == sprite_y) || (sprite_y >= 240))
|
|
continue;
|
|
|
|
sprite_x = sprite_ptr->x_loc;
|
|
tile_index = sprite_ptr->tile;
|
|
attrib = sprite_ptr->atr;
|
|
|
|
bmp_ptr = buf_ptr + sprite_x;
|
|
|
|
/* Handle $FD/$FE tile VROM switching (PunchOut) */
|
|
if (ppu.latchfunc)
|
|
ppu.latchfunc(vram_offset, tile_index);
|
|
|
|
/* Get upper two bits of color */
|
|
col_high = ((attrib & 3) << 2);
|
|
|
|
/* 8x16 even sprites use $0000, odd use $1000 */
|
|
if (16 == ppu.obj_height)
|
|
vram_adr = ((tile_index & 1) << 12) | ((tile_index & 0xFE) << 4);
|
|
else
|
|
vram_adr = vram_offset + (tile_index << 4);
|
|
|
|
/* Get the address of the tile */
|
|
data_ptr = &PPU_MEM(vram_adr);
|
|
|
|
/* Calculate offset (line within the sprite) */
|
|
y_offset = scanline - sprite_y;
|
|
if (y_offset > 7)
|
|
y_offset += 8;
|
|
|
|
/* Account for vertical flippage */
|
|
if (attrib & OAMF_VFLIP)
|
|
{
|
|
if (16 == ppu.obj_height)
|
|
y_offset -= 23;
|
|
else
|
|
y_offset -= 7;
|
|
|
|
data_ptr -= y_offset;
|
|
}
|
|
else
|
|
{
|
|
data_ptr += y_offset;
|
|
}
|
|
|
|
/* if we're on sprite 0 and sprite 0 strike flag isn't set,
|
|
** check for a strike
|
|
*/
|
|
check_strike = (0 == sprite_num) && (false == ppu.strikeflag);
|
|
strike_pixel = draw_oamtile(bmp_ptr, attrib, data_ptr[0], data_ptr[8], ppu.palette + 16 + col_high, check_strike);
|
|
if (strike_pixel >= 0)
|
|
ppu_setstrike(strike_pixel);
|
|
|
|
/* maximum of 8 sprites per scanline */
|
|
if (++spritecount == PPU_MAXSPRITE)
|
|
{
|
|
ppu.stat |= PPU_STATF_MAXSPRITE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Restore lefthand column */
|
|
if (ppu.obj_mask)
|
|
{
|
|
((uint32 *) buf_ptr)[0] = savecol[0];
|
|
((uint32 *) buf_ptr)[1] = savecol[1];
|
|
}
|
|
}
|
|
|
|
/* Fake rendering a line */
|
|
/* This is needed for sprite 0 hits when we're skipping drawing a frame */
|
|
static void ppu_fakeoam(int scanline)
|
|
{
|
|
uint8 *data_ptr;
|
|
obj_t *sprite_ptr;
|
|
uint32 vram_adr, color;
|
|
int y_offset;
|
|
uint8 pat1, pat2;
|
|
uint8 tile_index, attrib;
|
|
uint8 sprite_height, sprite_y, sprite_x;
|
|
|
|
/* we don't need to be here if strike flag is set */
|
|
|
|
if (false == ppu.obj_on || ppu.strikeflag)
|
|
return;
|
|
|
|
sprite_height = ppu.obj_height;
|
|
sprite_ptr = (obj_t *) ppu.oam;
|
|
sprite_y = sprite_ptr->y_loc + 1;
|
|
|
|
/* Check to see if sprite is out of range */
|
|
if ((sprite_y > scanline) || (sprite_y <= (scanline - sprite_height))
|
|
|| (0 == sprite_y) || (sprite_y > 240))
|
|
return;
|
|
|
|
sprite_x = sprite_ptr->x_loc;
|
|
tile_index = sprite_ptr->tile;
|
|
attrib = sprite_ptr->atr;
|
|
|
|
/* 8x16 even sprites use $0000, odd use $1000 */
|
|
if (16 == ppu.obj_height)
|
|
vram_adr = ((tile_index & 1) << 12) | ((tile_index & 0xFE) << 4);
|
|
else
|
|
vram_adr = ppu.obj_base + (tile_index << 4);
|
|
|
|
data_ptr = &PPU_MEM(vram_adr);
|
|
|
|
/* Calculate offset (line within the sprite) */
|
|
y_offset = scanline - sprite_y;
|
|
if (y_offset > 7)
|
|
y_offset += 8;
|
|
|
|
/* Account for vertical flippage */
|
|
if (attrib & OAMF_VFLIP)
|
|
{
|
|
if (16 == ppu.obj_height)
|
|
y_offset -= 23;
|
|
else
|
|
y_offset -= 7;
|
|
data_ptr -= y_offset;
|
|
}
|
|
else
|
|
{
|
|
data_ptr += y_offset;
|
|
}
|
|
|
|
/* check for a solid sprite 0 pixel */
|
|
pat1 = data_ptr[0];
|
|
pat2 = data_ptr[8];
|
|
color = ((pat2 & 0xAA) << 8) | ((pat2 & 0x55) << 1)
|
|
| ((pat1 & 0xAA) << 7) | (pat1 & 0x55);
|
|
|
|
if (color)
|
|
{
|
|
uint8 colors[8];
|
|
|
|
/* buckle up, it's going to get ugly... */
|
|
if (0 == (attrib & OAMF_HFLIP))
|
|
{
|
|
colors[0] = (color >> 14) & 3;
|
|
colors[1] = (color >> 6) & 3;
|
|
colors[2] = (color >> 12) & 3;
|
|
colors[3] = (color >> 4) & 3;
|
|
colors[4] = (color >> 10) & 3;
|
|
colors[5] = (color >> 2) & 3;
|
|
colors[6] = (color >> 8) & 3;
|
|
colors[7] = color & 3;
|
|
}
|
|
else
|
|
{
|
|
colors[7] = (color >> 14) & 3;
|
|
colors[6] = (color >> 6) & 3;
|
|
colors[5] = (color >> 12) & 3;
|
|
colors[4] = (color >> 4) & 3;
|
|
colors[3] = (color >> 10) & 3;
|
|
colors[2] = (color >> 2) & 3;
|
|
colors[1] = (color >> 8) & 3;
|
|
colors[0] = color & 3;
|
|
}
|
|
|
|
if (colors[0])
|
|
ppu_setstrike(sprite_x + 0);
|
|
else if (colors[1])
|
|
ppu_setstrike(sprite_x + 1);
|
|
else if (colors[2])
|
|
ppu_setstrike(sprite_x + 2);
|
|
else if (colors[3])
|
|
ppu_setstrike(sprite_x + 3);
|
|
else if (colors[4])
|
|
ppu_setstrike(sprite_x + 4);
|
|
else if (colors[5])
|
|
ppu_setstrike(sprite_x + 5);
|
|
else if (colors[6])
|
|
ppu_setstrike(sprite_x + 6);
|
|
else if (colors[7])
|
|
ppu_setstrike(sprite_x + 7);
|
|
}
|
|
}
|
|
|
|
bool ppu_enabled(void)
|
|
{
|
|
return (ppu.bg_on || ppu.obj_on);
|
|
}
|
|
|
|
static uint8 line[320];
|
|
|
|
static void ppu_renderscanline(bitmap_t *bmp, int scanline, bool draw_flag)
|
|
{
|
|
uint8 *buf = &line[0]; //bmp->line[scanline];
|
|
|
|
/* start scanline - transfer ppu latch into vaddr */
|
|
if (ppu.bg_on || ppu.obj_on)
|
|
{
|
|
if (0 == scanline)
|
|
{
|
|
ppu.vaddr = ppu.vaddr_latch;
|
|
}
|
|
else
|
|
{
|
|
ppu.vaddr &= ~0x041F;
|
|
ppu.vaddr |= (ppu.vaddr_latch & 0x041F);
|
|
}
|
|
}
|
|
|
|
if (draw_flag)
|
|
ppu_renderbg(buf);
|
|
|
|
/* TODO: fetch obj data 1 scanline before */
|
|
if (true == ppu.drawsprites && true == draw_flag)
|
|
ppu_renderoam(buf, scanline);
|
|
else
|
|
ppu_fakeoam(scanline);
|
|
|
|
if (draw_flag)
|
|
emu_DrawLinePal16(buf, 256, 240, scanline);
|
|
}
|
|
|
|
|
|
void ppu_endscanline(int scanline)
|
|
{
|
|
/* modify vram address at end of scanline */
|
|
if (scanline < 240 && (ppu.bg_on || ppu.obj_on))
|
|
{
|
|
int ytile;
|
|
|
|
/* check for max 3 bit y tile offset */
|
|
if (7 == (ppu.vaddr >> 12))
|
|
{
|
|
ppu.vaddr &= ~0x7000; /* clear y tile offset */
|
|
ytile = (ppu.vaddr >> 5) & 0x1F;
|
|
|
|
if (29 == ytile)
|
|
{
|
|
ppu.vaddr &= ~0x03E0; /* clear y tile */
|
|
ppu.vaddr ^= 0x0800; /* toggle nametable */
|
|
}
|
|
else if (31 == ytile)
|
|
{
|
|
ppu.vaddr &= ~0x03E0; /* clear y tile */
|
|
}
|
|
else
|
|
{
|
|
ppu.vaddr += 0x20; /* increment y tile */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ppu.vaddr += 0x1000; /* increment tile y offset */
|
|
}
|
|
}
|
|
}
|
|
|
|
void ppu_checknmi(void)
|
|
{
|
|
if (ppu.ctrl0 & PPU_CTRL0F_NMI)
|
|
nes_nmi();
|
|
}
|
|
|
|
void ppu_scanline(bitmap_t *bmp, int scanline, bool draw_flag)
|
|
{
|
|
if (scanline < 240)
|
|
{
|
|
/* Lower the Max Sprite per scanline flag */
|
|
ppu.stat &= ~PPU_STATF_MAXSPRITE;
|
|
ppu_renderscanline(bmp, scanline, draw_flag);
|
|
}
|
|
else if (241 == scanline)
|
|
{
|
|
ppu.stat |= PPU_STATF_VBLANK;
|
|
ppu.vram_accessible = true;
|
|
}
|
|
else if (261 == scanline)
|
|
{
|
|
ppu.stat &= ~PPU_STATF_VBLANK;
|
|
ppu.strikeflag = false;
|
|
ppu.strike_cycle = (uint32) -1;
|
|
|
|
ppu.vram_accessible = false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Stuff for the OAM viewer */
|
|
static void draw_sprite(bitmap_t *bmp, int x, int y, uint8 tile_num, uint8 attrib)
|
|
{
|
|
int line, height;
|
|
int col_high, vram_adr;
|
|
uint8 *vid, *data_ptr;
|
|
|
|
vid = bmp->line[y] + x;
|
|
|
|
/* Get upper two bits of color */
|
|
col_high = ((attrib & 3) << 2);
|
|
|
|
/* 8x16 even sprites use $0000, odd use $1000 */
|
|
height = ppu.obj_height;
|
|
if (16 == height)
|
|
vram_adr = ((tile_num & 1) << 12) | ((tile_num & 0xFE) << 4);
|
|
/* else just use the offset from $2000 */
|
|
else
|
|
vram_adr = ppu.obj_base + (tile_num << 4);
|
|
|
|
data_ptr = &PPU_MEM(vram_adr);
|
|
|
|
for (line = 0; line < height; line++)
|
|
{
|
|
if (line == 8)
|
|
data_ptr += 8;
|
|
|
|
draw_bgtile(vid, data_ptr[0], data_ptr[8], ppu.palette + 16 + col_high);
|
|
//draw_oamtile(vid, attrib, data_ptr[0], data_ptr[8], ppu.palette + 16 + col_high);
|
|
|
|
data_ptr++;
|
|
vid += bmp->pitch;
|
|
}
|
|
}
|
|
|
|
void ppu_dumpoam(bitmap_t *bmp, int x_loc, int y_loc)
|
|
{
|
|
int sprite, x_pos, y_pos, height;
|
|
obj_t *spr_ptr;
|
|
|
|
spr_ptr = (obj_t *) ppu.oam;
|
|
height = ppu.obj_height;
|
|
|
|
for (sprite = 0; sprite < 64; sprite++)
|
|
{
|
|
x_pos = ((sprite & 0x0F) << 3) + (sprite & 0x0F) + x_loc;
|
|
if (height == 16)
|
|
y_pos = (sprite & 0xF0) + (sprite >> 4) + y_loc;
|
|
else
|
|
y_pos = ((sprite & 0xF0) >> 1) + (sprite >> 4) + y_loc;
|
|
|
|
draw_box(bmp, x_pos, y_pos, height);
|
|
|
|
if (spr_ptr->y_loc && spr_ptr->y_loc < 240)
|
|
draw_sprite(bmp, x_pos + 1, y_pos + 1, spr_ptr->tile, spr_ptr->atr);
|
|
else
|
|
draw_deadsprite(bmp, x_pos + 1, y_pos + 1, height);
|
|
|
|
spr_ptr++;
|
|
}
|
|
}
|
|
|
|
/* More of a debugging thing than anything else */
|
|
void ppu_dumppattern(bitmap_t *bmp, int table_num, int x_loc, int y_loc, int col)
|
|
{
|
|
int x_tile, y_tile;
|
|
uint8 *bmp_ptr, *data_ptr, *ptr;
|
|
int tile_num, line;
|
|
uint8 col_high;
|
|
|
|
tile_num = 0;
|
|
col_high = col << 2;
|
|
|
|
for (y_tile = 0; y_tile < 16; y_tile++)
|
|
{
|
|
/* Get our pointer to the bitmap */
|
|
bmp_ptr = bmp->line[y_loc] + x_loc;
|
|
|
|
for (x_tile = 0; x_tile < 16; x_tile++)
|
|
{
|
|
data_ptr = &PPU_MEM((table_num << 12) + (tile_num << 4));
|
|
ptr = bmp_ptr;
|
|
|
|
for (line = 0; line < 8; line ++)
|
|
{
|
|
draw_bgtile(ptr, data_ptr[0], data_ptr[8], ppu.palette + col_high);
|
|
data_ptr++;
|
|
ptr += bmp->pitch;
|
|
}
|
|
|
|
bmp_ptr += 8;
|
|
tile_num++;
|
|
}
|
|
y_loc += 8;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** $Log: nes_ppu.c,v $
|
|
** Revision 1.2 2001/04/27 14:37:11 neil
|
|
** wheeee
|
|
**
|
|
** Revision 1.1.1.1 2001/04/27 07:03:54 neil
|
|
** initial
|
|
**
|
|
** Revision 1.14 2000/11/29 12:58:23 matt
|
|
** timing/fiq fixes
|
|
**
|
|
** Revision 1.13 2000/11/27 19:36:15 matt
|
|
** more timing fixes
|
|
**
|
|
** Revision 1.12 2000/11/26 15:51:13 matt
|
|
** frame IRQ emulation
|
|
**
|
|
** Revision 1.11 2000/11/25 20:30:39 matt
|
|
** scanline emulation simplifications/timing fixes
|
|
**
|
|
** Revision 1.10 2000/11/24 14:56:02 matt
|
|
** fixed a long-standing sprite 0 strike bug
|
|
**
|
|
** Revision 1.9 2000/11/20 13:23:17 matt
|
|
** PPU fixes
|
|
**
|
|
** Revision 1.8 2000/11/19 13:47:30 matt
|
|
** problem with frame irqs fixed
|
|
**
|
|
** Revision 1.7 2000/11/19 13:40:19 matt
|
|
** more accurate ppu behavior
|
|
**
|
|
** Revision 1.6 2000/11/14 12:09:37 matt
|
|
** only generate the palette once, please
|
|
**
|
|
** Revision 1.5 2000/11/11 14:51:43 matt
|
|
** context get/set fixed
|
|
**
|
|
** Revision 1.4 2000/11/09 12:35:50 matt
|
|
** fixed timing problem with VRAM reads/writes
|
|
**
|
|
** Revision 1.3 2000/11/05 16:35:41 matt
|
|
** rolled rgb.h into bitmap.h
|
|
**
|
|
** Revision 1.2 2000/10/27 12:55:03 matt
|
|
** palette generating functions now take *this pointers
|
|
**
|
|
** Revision 1.1 2000/10/24 12:20:28 matt
|
|
** changed directory structure
|
|
**
|
|
** Revision 1.33 2000/10/23 15:53:08 matt
|
|
** better system handling
|
|
**
|
|
** Revision 1.32 2000/10/22 15:02:32 matt
|
|
** simplified mirroring
|
|
**
|
|
** Revision 1.31 2000/10/21 21:36:04 matt
|
|
** ppu cleanups / fixes
|
|
**
|
|
** Revision 1.30 2000/10/21 19:26:59 matt
|
|
** many more cleanups
|
|
**
|
|
** Revision 1.29 2000/10/10 13:58:15 matt
|
|
** stroustrup squeezing his way in the door
|
|
**
|
|
** Revision 1.28 2000/10/08 17:54:32 matt
|
|
** reject VRAM access out of VINT period
|
|
**
|
|
** Revision 1.27 2000/09/15 04:58:07 matt
|
|
** simplifying and optimizing APU core
|
|
**
|
|
** Revision 1.26 2000/09/08 11:57:29 matt
|
|
** no more nes_fiq
|
|
**
|
|
** Revision 1.25 2000/09/07 21:57:31 matt
|
|
** api change
|
|
**
|
|
** Revision 1.24 2000/07/31 04:27:59 matt
|
|
** one million cleanups
|
|
**
|
|
** Revision 1.23 2000/07/30 06:13:12 matt
|
|
** default to no FIQs on startup
|
|
**
|
|
** Revision 1.22 2000/07/30 04:32:32 matt
|
|
** emulation of the NES frame IRQ
|
|
**
|
|
** Revision 1.21 2000/07/25 02:25:53 matt
|
|
** safer xxx_destroy calls
|
|
**
|
|
** Revision 1.20 2000/07/23 15:12:43 matt
|
|
** removed unused variables, changed INLINE
|
|
**
|
|
** Revision 1.19 2000/07/21 04:50:39 matt
|
|
** moved palette calls out of nofrendo.c and into ppu_create
|
|
**
|
|
** Revision 1.18 2000/07/17 05:12:55 matt
|
|
** nes_ppu.c is no longer a scary place to be-- cleaner & faster
|
|
**
|
|
** Revision 1.17 2000/07/17 01:52:28 matt
|
|
** made sure last line of all source files is a newline
|
|
**
|
|
** Revision 1.16 2000/07/11 04:42:39 matt
|
|
** updated for new screen dimension defines
|
|
**
|
|
** Revision 1.15 2000/07/10 19:10:16 matt
|
|
** should bomb out now if a game tries to write to VROM
|
|
**
|
|
** Revision 1.14 2000/07/10 05:28:30 matt
|
|
** moved joypad/oam dma from apu to ppu
|
|
**
|
|
** Revision 1.13 2000/07/10 03:03:16 matt
|
|
** added ppu_getcontext() routine
|
|
**
|
|
** Revision 1.12 2000/07/09 03:46:05 matt
|
|
** using pitch instead of width...
|
|
**
|
|
** Revision 1.11 2000/07/06 16:42:40 matt
|
|
** better palette setting interface
|
|
**
|
|
** Revision 1.10 2000/07/05 22:49:25 matt
|
|
** changed mmc2 (punchout) tile-access switching
|
|
**
|
|
** Revision 1.9 2000/07/04 23:13:26 matt
|
|
** added an irq line drawing debug feature hack
|
|
**
|
|
** Revision 1.8 2000/06/26 04:58:08 matt
|
|
** accuracy changes
|
|
**
|
|
** Revision 1.7 2000/06/22 02:13:49 matt
|
|
** more accurate emulation of $2002
|
|
**
|
|
** Revision 1.6 2000/06/20 20:42:47 matt
|
|
** accuracy changes
|
|
**
|
|
** Revision 1.5 2000/06/20 00:05:12 matt
|
|
** tested and verified STAT quirk, added code
|
|
**
|
|
** Revision 1.4 2000/06/09 15:12:26 matt
|
|
** initial revision
|
|
**
|
|
*/
|