/******************************************************************************
Nintendo 2C0x PPU emulation.
Written by Ernesto Corvi.
This code is heavily based on Brad Oliver's MESS implementation.
2009-04: Changed NES PPU to be a device (Nathan Woods)
2009-07: Changed NES PPU to use a device memory map (Robert Bohms)
Current known bugs
General:
* PPU timing is imprecise for updates that happen mid-scanline. Some games
may demand more precision.
NES-specific:
* Micro Machines has minor rendering glitches (needs better timing).
* Mach Rider has minor road rendering glitches (needs better timing).
* Rad Racer demonstrates road glitches: it changes horizontal scrolling mid-line.
******************************************************************************/
#include "emu.h"
#include "profiler.h"
#include "video/ppu2c0x.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
/* constant definitions */
#define VISIBLE_SCREEN_WIDTH (32*8) /* Visible screen width */
#define VISIBLE_SCREEN_HEIGHT (30*8) /* Visible screen height */
#define VIDEOMEM_SIZE 0x1000 /* videomem size */
#define VIDEOMEM_PAGE_SIZE 0x400 /* videomem page size */
#define SPRITERAM_SIZE 0x100 /* spriteram size */
#define SPRITERAM_MASK (0x100-1) /* spriteram size */
#define CHARGEN_NUM_CHARS 512 /* max number of characters handled by the chargen */
/* default monochromatic colortable */
static const pen_t default_colortable_mono[] =
{
0,1,2,3,
0,1,2,3,
0,1,2,3,
0,1,2,3,
0,1,2,3,
0,1,2,3,
0,1,2,3,
0,1,2,3,
};
/* default colortable */
static const pen_t default_colortable[] =
{
0,1,2,3,
0,5,6,7,
0,9,10,11,
0,13,14,15,
0,17,18,19,
0,21,22,23,
0,25,26,27,
0,29,30,31,
};
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
typedef struct _ppu2c0x_state ppu2c0x_state;
struct _ppu2c0x_state
{
address_space *space; /* memory space */
bitmap_t *bitmap; /* target bitmap */
UINT8 *spriteram; /* sprite ram */
pen_t *colortable; /* color table modified at run time */
pen_t *colortable_mono; /* monochromatic color table modified at run time */
emu_timer *scanline_timer; /* scanline timer */
emu_timer *hblank_timer; /* hblank period at end of each scanline */
emu_timer *nmi_timer; /* NMI timer */
int scanline; /* scanline count */
ppu2c0x_scanline_cb scanline_callback_proc; /* optional scanline callback */
ppu2c0x_hblank_cb hblank_callback_proc; /* optional hblank callback */
ppu2c0x_vidaccess_cb vidaccess_callback_proc; /* optional video access callback */
ppu2c0x_nmi_cb nmi_callback_proc; /* nmi access callback from interface */
int regs[PPU_MAX_REG]; /* registers */
int refresh_data; /* refresh-related */
int refresh_latch; /* refresh-related */
int x_fine; /* refresh-related */
int toggle; /* used to latch hi-lo scroll */
int add; /* vram increment amount */
int videomem_addr; /* videomem address pointer */
int addr_latch; /* videomem address latch */
int data_latch; /* latched videomem data */
int buffered_data;
int tile_page; /* current tile page */
int sprite_page; /* current sprite page */
int back_color; /* background color */
int color_base;
UINT8 palette_ram[0x20]; /* shouldn't be in main memory! */
int scan_scale; /* scan scale */
int scanlines_per_frame; /* number of scanlines per frame */
rgb_t palette[64*4]; /* palette for this chip */
int security_value; /* 2C05 protection */
void (*latch)( device_t *device, offs_t offset );
};
/***************************************************************************
PROTOTYPES
***************************************************************************/
static void update_scanline( device_t *device );
static TIMER_CALLBACK( scanline_callback );
static TIMER_CALLBACK( hblank_callback );
static TIMER_CALLBACK( nmi_callback );
/* palette handlers */
static WRITE8_HANDLER( ppu2c0x_palette_write );
static READ8_HANDLER( ppu2c0x_palette_read );
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
INLINE ppu2c0x_state *get_token( device_t *device )
{
assert(device != NULL);
assert((device->type() == PPU_2C02) || (device->type() == PPU_2C03B)
|| (device->type() == PPU_2C04) || (device->type() == PPU_2C05_01)
|| (device->type() == PPU_2C05_02) || (device->type() == PPU_2C05_03)
|| (device->type() == PPU_2C05_04) || (device->type() == PPU_2C07));
return (ppu2c0x_state *) downcast<legacy_device_base *>(device)->token();
}
INLINE const ppu2c0x_interface *get_interface( device_t *device )
{
assert(device != NULL);
assert((device->type() == PPU_2C02) || (device->type() == PPU_2C03B)
|| (device->type() == PPU_2C04) || (device->type() == PPU_2C05_01)
|| (device->type() == PPU_2C05_02) || (device->type() == PPU_2C05_03)
|| (device->type() == PPU_2C05_04) || (device->type() == PPU_2C07));
return (const ppu2c0x_interface *) device->static_config();
}
/***************************************************************************
IMPLEMENTATION
***************************************************************************/
/*************************************
*
* PPU Palette Initialization
*
*************************************/
/* default address map */
// make this INTERNAL, default should just be enough to avoid compile errors, print error messages!
static ADDRESS_MAP_START( ppu2c0x, AS_0, 8 )
AM_RANGE(0x3f00, 0x3fff) AM_READWRITE(ppu2c0x_palette_read, ppu2c0x_palette_write)
ADDRESS_MAP_END
void ppu2c0x_init_palette( running_machine &machine, int first_entry )
{
/* This routine builds a palette using a transformation from */
/* the YUV (Y, B-Y, R-Y) to the RGB color space */
/* The NES has a 64 color palette */
/* 16 colors, with 4 luminance levels for each color */
/* The 16 colors circle around the YUV color space, */
int color_intensity, color_num, color_emphasis;
double R, G, B;
double tint = 0.22; /* adjust to taste */
double hue = 287.0;
double Kr = 0.2989;
double Kb = 0.1145;
double Ku = 2.029;
double Kv = 1.140;
static const double brightness[3][4] =
{
{ 0.50, 0.75, 1.0, 1.0 },
{ 0.29, 0.45, 0.73, 0.9 },
{ 0, 0.24, 0.47, 0.77 }
};
/* Loop through the emphasis modes (8 total) */
for (color_emphasis = 0; color_emphasis < 8; color_emphasis++)
{
/*
double r_mod = 0.0;
double g_mod = 0.0;
double b_mod = 0.0;
switch (color_emphasis)
{
case 0: r_mod = 1.0; g_mod = 1.0; b_mod = 1.0; break;
case 1: r_mod = 1.24; g_mod = .915; b_mod = .743; break;
case 2: r_mod = .794; g_mod = 1.09; b_mod = .882; break;
case 3: r_mod = .905; g_mod = 1.03; b_mod = 1.28; break;
case 4: r_mod = .741; g_mod = .987; b_mod = 1.0; break;
case 5: r_mod = 1.02; g_mod = .908; b_mod = .979; break;
case 6: r_mod = 1.02; g_mod = .98; b_mod = .653; break;
case 7: r_mod = .75; g_mod = .75; b_mod = .75; break;
}
*/
/* loop through the 4 intensities */
for (color_intensity = 0; color_intensity < 4; color_intensity++)
{
/* loop through the 16 colors */
for (color_num = 0; color_num < 16; color_num++)
{
double sat;
double y, u, v;
double rad;
switch (color_num)
{
case 0:
sat = 0; rad = 0;
y = brightness[0][color_intensity];
break;
case 13:
sat = 0; rad = 0;
y = brightness[2][color_intensity];
break;
case 14:
case 15:
sat = 0; rad = 0; y = 0;
break;
default:
sat = tint;
rad = M_PI * ((color_num * 30 + hue) / 180.0);
y = brightness[1][color_intensity];
break;
}
u = sat * cos(rad);
v = sat * sin(rad);
/* Transform to RGB */
R = (y + Kv * v) * 255.0;
G = (y - (Kb * Ku * u + Kr * Kv * v) / (1 - Kb - Kr)) * 255.0;
B = (y + Ku * u) * 255.0;
/* Clipping, in case of saturation */
if (R < 0)
R = 0;
if (R > 255)
R = 255;
if (G < 0)
G = 0;
if (G > 255)
G = 255;
if (B < 0)
B = 0;
if (B > 255)
B = 255;
/* Round, and set the value */
palette_set_color_rgb(machine, first_entry++, floor(R + .5), floor(G + .5), floor(B + .5));
}
}
}
/* color tables are modified at run-time, and are initialized on 'ppu2c0x_reset' */
}
void ppu2c0x_init_palette_rgb( running_machine &machine, int first_entry )
{
int color_emphasis, color_num;
int R, G, B;
UINT8 *palette_data = machine.region("palette")->base();
/* Loop through the emphasis modes (8 total) */
for (color_emphasis = 0; color_emphasis < 8; color_emphasis++)
{
for (color_num = 0; color_num < 64; color_num++)
{
R = ((color_emphasis & 1) ? 7 : palette_data[color_num * 3]);
G = ((color_emphasis & 2) ? 7 : palette_data[color_num * 3 + 1]);
B = ((color_emphasis & 4) ? 7 : palette_data[color_num * 3 + 2]);
palette_set_color_rgb(machine, first_entry++, pal3bit(R), pal3bit(G), pal3bit(B));
}
}
/* color tables are modified at run-time, and are initialized on 'ppu2c0x_reset' */
}
/* the charlayout we use for the chargen */
static const gfx_layout ppu_charlayout =
{
8, 8, /* 8*8 characters */
0,
2, /* 2 bits per pixel */
{ 8*8, 0 }, /* the two bitplanes are separated */
{ 0, 1, 2, 3, 4, 5, 6, 7 },
{ 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 },
16*8 /* every char takes 16 consecutive bytes */
};
/*************************************
*
* PPU Initialization and Disposal
*
*************************************/
void ppu2c0x_set_latch( device_t *device, void (*latch)(device_t *device, offs_t offset))
{
ppu2c0x_state *ppu2c0x = get_token(device);
ppu2c0x->latch = latch;
}
static TIMER_CALLBACK( hblank_callback )
{
device_t *device = (device_t *)ptr;
ppu2c0x_state *ppu2c0x = get_token(device);
int *ppu_regs = &ppu2c0x->regs[0];
int blanked = (ppu_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES)) == 0;
int vblank = ((ppu2c0x->scanline >= PPU_VBLANK_FIRST_SCANLINE - 1) && (ppu2c0x->scanline < ppu2c0x->scanlines_per_frame - 1)) ? 1 : 0;
// update_scanline(device);
if (ppu2c0x->hblank_callback_proc)
(*ppu2c0x->hblank_callback_proc) (device, ppu2c0x->scanline, vblank, blanked);
ppu2c0x->hblank_timer->adjust(attotime::never);
}
static TIMER_CALLBACK( nmi_callback )
{
device_t *device = (device_t *)ptr;
ppu2c0x_state *ppu2c0x = get_token(device);
int *ppu_regs = &ppu2c0x->regs[0];
// Actually fire the VMI
if (ppu2c0x->nmi_callback_proc != NULL)
(*ppu2c0x->nmi_callback_proc) (device, ppu_regs);
ppu2c0x->nmi_timer->adjust(attotime::never);
}
static void draw_background( device_t *device, UINT8 *line_priority )
{
ppu2c0x_state *ppu2c0x = get_token(device);
/* cache some values locally */
bitmap_t *bitmap = ppu2c0x->bitmap;
const int *ppu_regs = &ppu2c0x->regs[0];
const int scanline = ppu2c0x->scanline;
const int refresh_data = ppu2c0x->refresh_data;
const int tile_page = ppu2c0x->tile_page;
int start_x = (ppu2c0x->x_fine ^ 0x07) - 7;
UINT16 back_pen;
UINT16 *dest;
UINT8 scroll_x_coarse, scroll_y_coarse, scroll_y_fine, color_mask;
int x, tile_index, i;
const pen_t *color_table;
const pen_t *paldata;
int tilecount = 0;
/* setup the color mask and colortable to use */
if (ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
{
color_mask = 0xf0;
color_table = ppu2c0x->colortable_mono;
}
else
{
color_mask = 0xff;
color_table = ppu2c0x->colortable;
}
/* cache the background pen */
back_pen = (ppu2c0x->back_color & color_mask) + ppu2c0x->color_base;
/* determine where in the nametable to start drawing from */
/* based on the current scanline and scroll regs */
scroll_x_coarse = refresh_data & 0x1f;
scroll_y_coarse = (refresh_data & 0x3e0) >> 5;
scroll_y_fine = (refresh_data & 0x7000) >> 12;
x = scroll_x_coarse;
/* get the tile index */
tile_index = ((refresh_data & 0xc00) | 0x2000) + scroll_y_coarse * 32;
/* set up dest */
dest = ((UINT16 *) bitmap->base) + (bitmap->rowpixels * scanline) + start_x;
/* draw the 32 or 33 tiles that make up a line */
while (tilecount < 34)
{
int color_byte;
int color_bits;
int pos;
int index1;
int page, page2, address;
UINT16 pen;
index1 = tile_index + x;
// this is attribute table stuff! (actually read 2 in PPUspeak)!
/* Figure out which byte in the color table to use */
pos = ((index1 & 0x380) >> 4) | ((index1 & 0x1f) >> 2);
page = (index1 & 0x0c00) >> 10;
address = 0x3c0 + pos;
color_byte = ppu2c0x->space->read_byte((((page * 0x400) + address) & 0xfff) + 0x2000);
/* figure out which bits in the color table to use */
color_bits = ((index1 & 0x40) >> 4) + (index1 & 0x02);
// page2 is the output of the nametable read (this section is the FIRST read per tile!)
address = index1 & 0x3ff;
page2 = ppu2c0x->space->read_byte(index1);
// 27/12/2002
if (ppu2c0x->latch)
{
(*ppu2c0x->latch)(device, (tile_page << 10) | (page2 << 4));
}
if (start_x < VISIBLE_SCREEN_WIDTH)
{
UINT8 plane1, plane2;
paldata = &color_table[4 * (((color_byte >> color_bits) & 0x03))];
// need to read 0x0000 or 0x1000 + 16*nametable data
address = ((ppu2c0x->tile_page) ? 0x1000 : 0) + (page2 * 16);
// plus something that accounts for y
address += scroll_y_fine;
plane1 = ppu2c0x->space->read_byte((address & 0x1fff));
plane2 = ppu2c0x->space->read_byte((address + 8) & 0x1fff);
/* render the pixel */
for (i = 0; i < 8; i++)
{
UINT8 pix;
pix = ((plane1 >> 7) & 1) | (((plane2 >> 7) & 1) << 1);
plane1 = plane1 << 1;
plane2 = plane2 << 1;
if ((start_x + i) >= 0 && (start_x + i) < VISIBLE_SCREEN_WIDTH)
{
if (pix)
{
pen = paldata[pix];
line_priority[start_x + i] |= 0x02;
}
else
{
pen = back_pen;
}
*dest = pen;
}
dest++;
}
start_x += 8;
/* move to next tile over and toggle the horizontal name table if necessary */
x++;
if (x > 31)
{
x = 0;
tile_index ^= 0x400;
}
}
tilecount++;
}
/* if the left 8 pixels for the background are off, blank 'em */
if (!(ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_BACKGROUND_L8))
{
dest = ((UINT16 *) bitmap->base) + (bitmap->rowpixels * scanline);
for (i = 0; i < 8; i++)
{
*(dest++) = back_pen;
line_priority[i] ^= 0x02;
}
}
}
static void draw_sprites( device_t *device, UINT8 *line_priority )
{
ppu2c0x_state *ppu2c0x = get_token(device);
/* cache some values locally */
bitmap_t *bitmap = ppu2c0x->bitmap;
const int scanline = ppu2c0x->scanline;
const int sprite_page = ppu2c0x->sprite_page;
const UINT8 *sprite_ram = ppu2c0x->spriteram;
pen_t *color_table = ppu2c0x->colortable;
int *ppu_regs = &ppu2c0x->regs[0];
int sprite_xpos, sprite_ypos, sprite_index;
int tile, index1;
int pri;
int flipx, flipy, color;
int size;
int sprite_count = 0;
int sprite_line;
int first_pixel;
const pen_t *paldata;
int pixel;
/* determine if the sprites are 8x8 or 8x16 */
size = (ppu_regs[PPU_CONTROL0] & PPU_CONTROL0_SPRITE_SIZE) ? 16 : 8;
first_pixel = (ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_SPRITES_L8)? 0: 8;
for (sprite_index = 0; sprite_index < SPRITERAM_SIZE; sprite_index += 4)
{
UINT8 plane1;
UINT8 plane2;
sprite_ypos = sprite_ram[sprite_index] + 1;
sprite_xpos = sprite_ram[sprite_index + 3];
// The sprite collision acts funny on the last pixel of a scanline.
// The various scanline latches update while the last few pixels
// are being drawn. Since we don't do cycle-by-cycle PPU emulation,
// we fudge it a bit here so that sprite 0 collisions are detected
// when, e.g., sprite x is 254, sprite y is 29 and we're rendering
// at the end of scanline 28.
// Battletoads needs this level of precision to be playable.
if ((sprite_index == 0) && (sprite_xpos == 254))
{
sprite_ypos--;
/* set the "sprite 0 hit" flag if appropriate */
if (line_priority[sprite_xpos] & 0x02)
ppu_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
}
/* if the sprite isn't visible, skip it */
if ((sprite_ypos + size <= scanline) || (sprite_ypos > scanline))
continue;
tile = sprite_ram[sprite_index + 1];
color = (sprite_ram[sprite_index + 2] & 0x03) + 4;
pri = sprite_ram[sprite_index + 2] & 0x20;
flipx = sprite_ram[sprite_index + 2] & 0x40;
flipy = sprite_ram[sprite_index + 2] & 0x80;
if (size == 16)
{
/* if it's 8x16 and odd-numbered, draw the other half instead */
if (tile & 0x01)
{
tile &= ~0x01;
tile |= 0x100;
}
}
if (ppu2c0x->latch)
(*ppu2c0x->latch)(device, (sprite_page << 10) | ((tile & 0xff) << 4));
/* compute the character's line to draw */
sprite_line = scanline - sprite_ypos;
if (flipy)
sprite_line = (size - 1) - sprite_line;
paldata = &color_table[4 * color];
if (size == 16 && sprite_line > 7)
{
tile++;
sprite_line -= 8;
}
index1 = tile * 16;
if (size == 8)
index1 += ((sprite_page == 0) ? 0 : 0x1000);
plane1 = ppu2c0x->space->read_byte((index1 + sprite_line + 0) & 0x1fff);
plane2 = ppu2c0x->space->read_byte((index1 + sprite_line + 8) & 0x1fff);
/* if there are more than 8 sprites on this line, set the flag */
if (sprite_count == 8)
{
ppu_regs[PPU_STATUS] |= PPU_STATUS_8SPRITES;
// logerror ("> 8 sprites, scanline: %d\n", scanline);
/* the real NES only draws up to 8 sprites - the rest should be invisible */
break;
}
sprite_count++;
/* abort drawing if sprites aren't rendered */
if (!(ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_SPRITES))
continue;
if (pri)
{
/* draw the low-priority sprites */
for (pixel = 0; pixel < 8; pixel++)
{
UINT8 pixel_data;
if (flipx)
{
pixel_data = (plane1 & 1) + ((plane2 & 1) << 1);
plane1 = plane1 >> 1;
plane2 = plane2 >> 1;
}
else
{
pixel_data = ((plane1 >> 7) & 1) | (((plane2 >> 7) & 1) << 1);
plane1 = plane1 << 1;
plane2 = plane2 << 1;
}
/* is this pixel non-transparent? */
if (sprite_xpos + pixel >= first_pixel)
{
if (pixel_data)
{
/* has the background (or another sprite) already been drawn here? */
if (!line_priority[sprite_xpos + pixel])
{
/* no, draw */
if ((sprite_xpos + pixel) < VISIBLE_SCREEN_WIDTH)
*BITMAP_ADDR16(bitmap, scanline, sprite_xpos + pixel) = paldata[pixel_data];
}
/* indicate that a sprite was drawn at this location, even if it's not seen */
if ((sprite_xpos + pixel) < VISIBLE_SCREEN_WIDTH)
line_priority[sprite_xpos + pixel] |= 0x01;
}
/* set the "sprite 0 hit" flag if appropriate */
if (sprite_index == 0 && (pixel_data & 0x03) && ((sprite_xpos + pixel) < 255) && (line_priority[sprite_xpos + pixel] & 0x02))
ppu_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
}
}
}
else
{
/* draw the high-priority sprites */
for (pixel = 0; pixel < 8; pixel++)
{
UINT8 pixel_data;
if (flipx)
{
pixel_data = (plane1 & 1) + ((plane2 & 1) << 1);
plane1 = plane1 >> 1;
plane2 = plane2 >> 1;
}
else
{
pixel_data = ((plane1 >> 7) & 1) | (((plane2 >> 7) & 1) << 1);
plane1 = plane1 << 1;
plane2 = plane2 << 1;
}
/* is this pixel non-transparent? */
if (sprite_xpos + pixel >= first_pixel)
{
if (pixel_data)
{
/* has another sprite been drawn here? */
if (!(line_priority[sprite_xpos + pixel] & 0x01))
{
/* no, draw */
if ((sprite_xpos + pixel) < VISIBLE_SCREEN_WIDTH)
{
*BITMAP_ADDR16(bitmap, scanline, sprite_xpos + pixel) = paldata[pixel_data];
line_priority[sprite_xpos + pixel] |= 0x01;
}
}
}
/* set the "sprite 0 hit" flag if appropriate */
if (sprite_index == 0 && (pixel_data & 0x03) && ((sprite_xpos + pixel) < 255) && (line_priority[sprite_xpos + pixel] & 0x02))
ppu_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
}
}
}
}
}
/*************************************
*
* Scanline Rendering and Update
*
*************************************/
static void render_scanline( device_t *device )
{
ppu2c0x_state *ppu2c0x = get_token(device);
UINT8 line_priority[VISIBLE_SCREEN_WIDTH];
int *ppu_regs = &ppu2c0x->regs[0];
/* lets see how long it takes */
g_profiler.start(PROFILER_USER1);
/* clear the line priority for this scanline */
memset(line_priority, 0, VISIBLE_SCREEN_WIDTH);
/* see if we need to render the background */
if (ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_BACKGROUND)
draw_background(device, line_priority);
else
{
bitmap_t *bitmap = ppu2c0x->bitmap;
const int scanline = ppu2c0x->scanline;
UINT8 color_mask;
UINT16 back_pen;
int i;
/* setup the color mask and colortable to use */
if (ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
color_mask = 0xf0;
else
color_mask = 0xff;
/* cache the background pen */
back_pen = (ppu2c0x->back_color & color_mask) + ppu2c0x->color_base;
// Fill this scanline with the background pen.
for (i = 0; i < bitmap->width; i++)
*BITMAP_ADDR16(bitmap, scanline, i) = back_pen;
}
/* if sprites are on, draw them, but we call always to process them */
draw_sprites(device, line_priority);
/* done updating, whew */
g_profiler.stop();
}
static void update_scanline( device_t *device )
{
ppu2c0x_state *ppu2c0x = get_token(device);
const int scanline = ppu2c0x->scanline;
int *ppu_regs = &ppu2c0x->regs[0];
if (scanline <= PPU_BOTTOM_VISIBLE_SCANLINE)
{
/* Render this scanline if appropriate */
if (ppu_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES))
{
/* If background or sprites are enabled, copy the ppu address latch */
/* Copy only the scroll x-coarse and the x-overflow bit */
ppu2c0x->refresh_data &= ~0x041f;
ppu2c0x->refresh_data |= (ppu2c0x->refresh_latch & 0x041f);
// logerror("updating refresh_data: %04x (scanline: %d)\n", ppu2c0x->refresh_data, ppu2c0x->scanline);
render_scanline(device);
}
else
{
bitmap_t *bitmap = ppu2c0x->bitmap;
UINT8 color_mask;
UINT16 back_pen;
int i;
/* setup the color mask and colortable to use */
if (ppu_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
color_mask = 0xf0;
else
color_mask = 0xff;
/* cache the background pen */
if (ppu2c0x->videomem_addr >= 0x3f00)
{
// If the PPU's VRAM address happens to point into palette ram space while
// both the sprites and background are disabled, the PPU paints the scanline
// with the palette entry at the VRAM address instead of the usual background
// pen. Micro Machines makes use of this feature.
int pen_num;
if (ppu2c0x->videomem_addr & 0x03)
pen_num = ppu2c0x->palette_ram[ppu2c0x->videomem_addr & 0x1f];
else
pen_num = ppu2c0x->palette_ram[0];
back_pen = pen_num + ppu2c0x->color_base;
}
else
back_pen = (ppu2c0x->back_color & color_mask) + ppu2c0x->color_base;
// Fill this scanline with the background pen.
for (i = 0; i < bitmap->width; i++)
*BITMAP_ADDR16(bitmap, scanline, i) = back_pen;
}
/* increment the fine y-scroll */
ppu2c0x->refresh_data += 0x1000;
/* if it's rolled, increment the coarse y-scroll */
if (ppu2c0x->refresh_data & 0x8000)
{
UINT16 tmp;
tmp = (ppu2c0x->refresh_data & 0x03e0) + 0x20;
ppu2c0x->refresh_data &= 0x7c1f;
/* handle bizarro scrolling rollover at the 30th (not 32nd) vertical tile */
if (tmp == 0x03c0)
ppu2c0x->refresh_data ^= 0x0800;
else
ppu2c0x->refresh_data |= (tmp & 0x03e0);
// logerror("updating refresh_data: %04x\n", ppu2c0x->refresh_data);
}
}
}
static TIMER_CALLBACK( scanline_callback )
{
device_t *device = (device_t *)ptr;
ppu2c0x_state *ppu2c0x = get_token(device);
int *ppu_regs = &ppu2c0x->regs[0];
int blanked = (ppu_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES)) == 0;
int vblank = ((ppu2c0x->scanline >= PPU_VBLANK_FIRST_SCANLINE - 1) && (ppu2c0x->scanline < ppu2c0x->scanlines_per_frame - 1)) ? 1 : 0;
int next_scanline;
/* if a callback is available, call it */
if (ppu2c0x->scanline_callback_proc != NULL)
(*ppu2c0x->scanline_callback_proc)(device, ppu2c0x->scanline, vblank, blanked);
/* update the scanline that just went by */
update_scanline(device);
/* increment our scanline count */
ppu2c0x->scanline++;
// logerror("starting scanline %d (MAME %d, beam %d)\n", ppu2c0x->scanline, device->machine().primary_screen->vpos(), device->machine().primary_screen->hpos());
/* Note: this is called at the _end_ of each scanline */
if (ppu2c0x->scanline == PPU_VBLANK_FIRST_SCANLINE)
{
// logerror("vblank starting\n");
/* We just entered VBLANK */
ppu_regs[PPU_STATUS] |= PPU_STATUS_VBLANK;
/* If NMI's are set to be triggered, go for it */
if (ppu_regs[PPU_CONTROL0] & PPU_CONTROL0_NMI)
{
// We need an ever-so-slight delay between entering vblank and firing an NMI - enough so that
// a game can read the high bit of $2002 before the NMI is called (potentially resetting the bit
// via a read from $2002 in the NMI handler).
// B-Wings is an example game that needs this.
ppu2c0x->nmi_timer->adjust(device->machine().device<cpu_device>("maincpu")->cycles_to_attotime(4));
}
}
if (ppu2c0x->scanline == ppu2c0x->scanlines_per_frame - 1)
{
// logerror("vblank ending\n");
/* clear the vblank & sprite hit flag */
ppu_regs[PPU_STATUS] &= ~(PPU_STATUS_VBLANK | PPU_STATUS_SPRITE0_HIT | PPU_STATUS_8SPRITES);
}
/* see if we rolled */
else if (ppu2c0x->scanline == ppu2c0x->scanlines_per_frame)
{
/* if background or sprites are enabled, copy the ppu address latch */
if (!blanked)
ppu2c0x->refresh_data = ppu2c0x->refresh_latch;
/* reset the scanline count */
ppu2c0x->scanline = 0;
// logerror("sprite 0 x: %d y: %d num: %d\n", ppu2c0x->spriteram[3], ppu2c0x->spriteram[0] + 1, ppu2c0x->spriteram[1]);
}
next_scanline = ppu2c0x->scanline + 1;
if (next_scanline == ppu2c0x->scanlines_per_frame)
next_scanline = 0;
// Call us back when the hblank starts for this scanline
ppu2c0x->hblank_timer->adjust(device->machine().device<cpu_device>("maincpu")->cycles_to_attotime(86.67)); // ??? FIXME - hardcoding NTSC, need better calculation
// trigger again at the start of the next scanline
ppu2c0x->scanline_timer->adjust(device->machine().primary_screen->time_until_pos(next_scanline * ppu2c0x->scan_scale));
}
/*************************************
*
* PPU Memory functions
*
*************************************/
static WRITE8_HANDLER( ppu2c0x_palette_write )
{
ppu2c0x_state *ppu2c0x = get_token(&space->device());
int color_base = ppu2c0x->color_base;
int color_emphasis = (ppu2c0x->regs[PPU_CONTROL1] & PPU_CONTROL1_COLOR_EMPHASIS) * 2;
// palette RAM is only 6 bits wide
data &= 0x3f;
// transparent pens are mirrored!
if (offset & 0x3)
{
ppu2c0x->palette_ram[offset & 0x1f] = data;
ppu2c0x->colortable[offset & 0x1f] = color_base + data + color_emphasis;
ppu2c0x->colortable_mono[offset & 0x1f] = color_base + (data & 0xf0) + color_emphasis;
}
else
{
int i;
if (0 == (offset & 0xf))
{
ppu2c0x->back_color = data;
for (i = 0; i < 32; i += 4)
{
ppu2c0x->colortable[i] = color_base + data + color_emphasis;
ppu2c0x->colortable_mono[i] = color_base + (data & 0xf0) + color_emphasis;
}
}
ppu2c0x->palette_ram[offset & 0xf] = ppu2c0x->palette_ram[(offset & 0xf) + 0x10] = data;
}
}
static READ8_HANDLER( ppu2c0x_palette_read )
{
ppu2c0x_state *ppu2c0x = get_token(&space->device());
{
if (ppu2c0x->regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
return (ppu2c0x->palette_ram[offset & 0x1f] & 0x30);
else
return (ppu2c0x->palette_ram[offset & 0x1f]);
}
}
/*************************************
*
* PPU Registers Read
*
*************************************/
READ8_DEVICE_HANDLER( ppu2c0x_r )
{
ppu2c0x_state *ppu2c0x = get_token(device);
if (offset >= PPU_MAX_REG)
{
logerror("PPU %s: Attempting to read past the chip: offset %x\n", device->tag(), offset);
offset &= PPU_MAX_REG - 1;
}
// see which register to read
switch (offset & 7)
{
case PPU_STATUS: /* 2 */
// The top 3 bits of the status register are the only ones that report data. The
// remainder contain whatever was last in the PPU data latch, except on the RC2C05 (protection)
if (ppu2c0x->security_value)
ppu2c0x->data_latch = (ppu2c0x->regs[PPU_STATUS] & 0xc0) | ppu2c0x->security_value;
else
ppu2c0x->data_latch = ppu2c0x->regs[PPU_STATUS] | (ppu2c0x->data_latch & 0x1f);
// Reset hi/lo scroll toggle
ppu2c0x->toggle = 0;
// If the vblank bit is set, clear all status bits but the 2 sprite flags
if (ppu2c0x->data_latch & PPU_STATUS_VBLANK)
ppu2c0x->regs[PPU_STATUS] &= 0x60;
break;
case PPU_SPRITE_DATA: /* 4 */
ppu2c0x->data_latch = ppu2c0x->spriteram[ppu2c0x->regs[PPU_SPRITE_ADDRESS]];
break;
case PPU_DATA: /* 7 */
if (ppu2c0x->latch)
(*ppu2c0x->latch)(device, ppu2c0x->videomem_addr & 0x3fff);
if (ppu2c0x->videomem_addr >= 0x3f00)
{
ppu2c0x->data_latch = ppu2c0x->space->read_byte(ppu2c0x->videomem_addr);
// buffer the mirrored NT data
ppu2c0x->buffered_data = ppu2c0x->space->read_byte(ppu2c0x->videomem_addr & 0x2fff);
}
else
{
ppu2c0x->data_latch = ppu2c0x->buffered_data;
ppu2c0x->buffered_data = ppu2c0x->space->read_byte(ppu2c0x->videomem_addr);
}
ppu2c0x->videomem_addr += ppu2c0x->add;
break;
default:
break;
}
return ppu2c0x->data_latch;
}
/*************************************
*
* PPU Registers Write
*
*************************************/
WRITE8_DEVICE_HANDLER( ppu2c0x_w )
{
ppu2c0x_state *ppu2c0x = get_token(device);
int color_base;
color_base = ppu2c0x->color_base;
if (offset >= PPU_MAX_REG)
{
logerror("PPU %s: Attempting to write past the chip: offset %x, data %x\n", device->tag(), offset, data);
offset &= PPU_MAX_REG - 1;
}
#ifdef MAME_DEBUG
if (ppu2c0x->scanline <= PPU_BOTTOM_VISIBLE_SCANLINE)
{
screen_device *screen = device->machine().primary_screen;
logerror("PPU register %d write %02x during non-vblank scanline %d (MAME %d, beam pos: %d)\n", offset, data, ppu2c0x->scanline, screen->vpos(), screen->hpos());
}
#endif
/* on the RC2C05, PPU_CONTROL0 and PPU_CONTROL1 are swapped (protection) */
if ((ppu2c0x->security_value) && !(offset & 6))
offset ^= 1;
switch (offset & 7)
{
case PPU_CONTROL0: /* 0 */
ppu2c0x->regs[PPU_CONTROL0] = data;
/* update the name table number on our refresh latches */
ppu2c0x->refresh_latch &= 0x73ff;
ppu2c0x->refresh_latch |= (data & 3) << 10;
/* the char ram bank points either 0x0000 or 0x1000 (page 0 or page 4) */
ppu2c0x->tile_page = (data & PPU_CONTROL0_CHR_SELECT) >> 2;
ppu2c0x->sprite_page = (data & PPU_CONTROL0_SPR_SELECT) >> 1;
ppu2c0x->add = (data & PPU_CONTROL0_INC) ? 32 : 1;
// logerror("control0 write: %02x (scanline: %d)\n", data, ppu2c0x->scanline);
break;
case PPU_CONTROL1: /* 1 */
/* if color intensity has changed, change all the color tables to reflect them */
if ((data & PPU_CONTROL1_COLOR_EMPHASIS) != (ppu2c0x->regs[PPU_CONTROL1] & PPU_CONTROL1_COLOR_EMPHASIS))
{
int i;
for (i = 0; i <= 0x1f; i++)
{
UINT8 oldColor = ppu2c0x->palette_ram[i];
ppu2c0x->colortable[i] = color_base + oldColor + (data & PPU_CONTROL1_COLOR_EMPHASIS) * 2;
}
}
// logerror("control1 write: %02x (scanline: %d)\n", data, ppu2c0x->scanline);
ppu2c0x->regs[PPU_CONTROL1] = data;
break;
case PPU_SPRITE_ADDRESS: /* 3 */
ppu2c0x->regs[PPU_SPRITE_ADDRESS] = data;
break;
case PPU_SPRITE_DATA: /* 4 */
// If the PPU is currently rendering the screen, 0xff is written instead of the desired data.
if (ppu2c0x->scanline <= PPU_BOTTOM_VISIBLE_SCANLINE)
data = 0xff;
ppu2c0x->spriteram[ppu2c0x->regs[PPU_SPRITE_ADDRESS]] = data;
ppu2c0x->regs[PPU_SPRITE_ADDRESS] = (ppu2c0x->regs[PPU_SPRITE_ADDRESS] + 1) & 0xff;
break;
case PPU_SCROLL: /* 5 */
if (ppu2c0x->toggle)
{
/* second write */
ppu2c0x->refresh_latch &= 0x0c1f;
ppu2c0x->refresh_latch |= (data & 0xf8) << 2;
ppu2c0x->refresh_latch |= (data & 0x07) << 12;
// logerror("scroll write 2: %d, %04x (scanline: %d)\n", data, ppu2c0x->refresh_latch, ppu2c0x->scanline);
}
else
{
/* first write */
ppu2c0x->refresh_latch &= 0x7fe0;
ppu2c0x->refresh_latch |= (data & 0xf8) >> 3;
ppu2c0x->x_fine = data & 7;
// logerror("scroll write 1: %d, %04x (scanline: %d)\n", data, ppu2c0x->refresh_latch, ppu2c0x->scanline);
}
ppu2c0x->toggle ^= 1;
break;
case PPU_ADDRESS: /* 6 */
if (ppu2c0x->toggle)
{
/* second write */
ppu2c0x->refresh_latch &= 0x7f00;
ppu2c0x->refresh_latch |= data;
ppu2c0x->refresh_data = ppu2c0x->refresh_latch;
ppu2c0x->videomem_addr = ppu2c0x->refresh_latch;
// logerror("vram addr write 2: %02x, %04x (scanline: %d)\n", data, ppu2c0x->refresh_latch, ppu2c0x->scanline);
}
else
{
/* first write */
ppu2c0x->refresh_latch &= 0x00ff;
ppu2c0x->refresh_latch |= (data & 0x3f) << 8;
// logerror("vram addr write 1: %02x, %04x (scanline: %d)\n", data, ppu2c0x->refresh_latch, ppu2c0x->scanline);
}
ppu2c0x->toggle ^= 1;
break;
case PPU_DATA: /* 7 */
{
int tempAddr = ppu2c0x->videomem_addr & 0x3fff;
if (ppu2c0x->latch)
(*ppu2c0x->latch)(device, tempAddr);
/* if there's a callback, call it now */
if (ppu2c0x->vidaccess_callback_proc)
data = (*ppu2c0x->vidaccess_callback_proc)(device, tempAddr, data);
/* see if it's on the chargen portion */
if (tempAddr < 0x2000)
{
/* store the data */
ppu2c0x->space->write_byte(tempAddr, data);
}
else
{
ppu2c0x->space->write_byte(tempAddr, data);
}
/* increment the address */
ppu2c0x->videomem_addr += ppu2c0x->add;
}
break;
default:
/* ignore other registers writes */
break;
}
ppu2c0x->data_latch = data;
}
/*************************************
*
* Sprite DMA
*
*************************************/
void ppu2c0x_spriteram_dma( address_space *space, device_t *device, const UINT8 page )
{
int i;
int address = page << 8;
for (i = 0; i < SPRITERAM_SIZE; i++)
{
UINT8 spriteData = space->read_byte(address + i);
space->write_byte(0x2004, spriteData);
}
// should last 513 CPU cycles.
device_adjust_icount(&space->device(), -513);
}
/*************************************
*
* PPU Rendering
*
*************************************/
void ppu2c0x_render( device_t *device, bitmap_t *bitmap, int flipx, int flipy, int sx, int sy )
{
ppu2c0x_state *ppu2c0x = get_token(device);
copybitmap(bitmap, ppu2c0x->bitmap, flipx, flipy, sx, sy, 0);
}
/*************************************
*
* Utility functions
*
*************************************/
int ppu2c0x_get_pixel( device_t *device, int x, int y )
{
ppu2c0x_state *ppu2c0x = get_token(device);
if (x >= VISIBLE_SCREEN_WIDTH)
x = VISIBLE_SCREEN_WIDTH - 1;
if (y >= VISIBLE_SCREEN_HEIGHT)
y = VISIBLE_SCREEN_HEIGHT - 1;
return *BITMAP_ADDR16(ppu2c0x->bitmap, y, x);
}
int ppu2c0x_get_colorbase( device_t *device )
{
ppu2c0x_state *ppu2c0x = get_token(device);
return ppu2c0x->color_base;
}
int ppu2c0x_get_current_scanline( device_t *device )
{
ppu2c0x_state *ppu2c0x = get_token(device);
return ppu2c0x->scanline;
}
// MMC5 has to be able to check this
int ppu2c0x_is_sprite_8x16( device_t *device )
{
ppu2c0x_state *ppu2c0x = get_token(device);
return BIT(ppu2c0x->regs[0], 5);
}
void ppu2c0x_set_scanline_callback( device_t *device, ppu2c0x_scanline_cb cb )
{
ppu2c0x_state *ppu2c0x = get_token(device);
ppu2c0x->scanline_callback_proc = cb;
}
void ppu2c0x_set_hblank_callback( device_t *device, ppu2c0x_hblank_cb cb )
{
ppu2c0x_state *ppu2c0x = get_token(device);
ppu2c0x->hblank_callback_proc = cb;
}
void ppu2c0x_set_vidaccess_callback( device_t *device, ppu2c0x_vidaccess_cb cb )
{
ppu2c0x_state *ppu2c0x = get_token(device);
ppu2c0x->vidaccess_callback_proc = cb;
}
void ppu2c0x_set_scanlines_per_frame( device_t *device, int scanlines )
{
ppu2c0x_state *ppu2c0x = get_token(device);
ppu2c0x->scanlines_per_frame = scanlines;
}
/*************************************
*
* PPU Start
*
*************************************/
static DEVICE_START( ppu2c0x )
{
ppu2c0x_state *ppu2c0x = get_token(device);
const ppu2c0x_interface *intf = get_interface(device);
memset(ppu2c0x, 0, sizeof(*ppu2c0x));
ppu2c0x->space = device->memory().space();
ppu2c0x->scanlines_per_frame = (device->type() != PPU_2C07) ? PPU_NTSC_SCANLINES_PER_FRAME : PPU_PAL_SCANLINES_PER_FRAME;
/* usually, no security value... */
ppu2c0x->security_value = 0;
/* ...except for VS. games which specific PPU types */
if (device->type() == PPU_2C05_01)
ppu2c0x->security_value = 0x1b; // game (jajamaru) doesn't seem to ever actually check it
if (device->type() == PPU_2C05_02)
ppu2c0x->security_value = 0x3d;
if (device->type() == PPU_2C05_03)
ppu2c0x->security_value = 0x1c;
if (device->type() == PPU_2C05_04)
ppu2c0x->security_value = 0x1b;
/* initialize the scanline handling portion */
ppu2c0x->scanline_timer = device->machine().scheduler().timer_alloc(FUNC(scanline_callback), (void *) device);
ppu2c0x->scanline_timer->adjust(device->machine().primary_screen->time_until_pos(1));
ppu2c0x->hblank_timer = device->machine().scheduler().timer_alloc(FUNC(hblank_callback), (void *) device);
ppu2c0x->hblank_timer->adjust(device->machine().device<cpu_device>("maincpu")->cycles_to_attotime(86.67)); // ??? FIXME - hardcoding NTSC, need better calculation
ppu2c0x->nmi_timer = device->machine().scheduler().timer_alloc(FUNC(nmi_callback), (void *) device);
ppu2c0x->nmi_timer->adjust(attotime::never);
ppu2c0x->nmi_callback_proc = intf->nmi_handler;
ppu2c0x->color_base = intf->color_base;
/* allocate a screen bitmap, videomem and spriteram, a dirtychar array and the monochromatic colortable */
ppu2c0x->bitmap = auto_bitmap_alloc(device->machine(), VISIBLE_SCREEN_WIDTH, VISIBLE_SCREEN_HEIGHT, device->machine().primary_screen->format());
ppu2c0x->spriteram = auto_alloc_array_clear(device->machine(), UINT8, SPRITERAM_SIZE);
ppu2c0x->colortable = auto_alloc_array(device->machine(), pen_t, ARRAY_LENGTH(default_colortable));
ppu2c0x->colortable_mono = auto_alloc_array(device->machine(), pen_t, ARRAY_LENGTH(default_colortable_mono));
device->save_item(NAME(ppu2c0x->scanline));
device->save_item(NAME(ppu2c0x->refresh_data));
device->save_item(NAME(ppu2c0x->refresh_latch));
device->save_item(NAME(ppu2c0x->x_fine));
device->save_item(NAME(ppu2c0x->toggle));
device->save_item(NAME(ppu2c0x->add));
device->save_item(NAME(ppu2c0x->videomem_addr));
device->save_item(NAME(ppu2c0x->addr_latch));
device->save_item(NAME(ppu2c0x->data_latch));
device->save_item(NAME(ppu2c0x->buffered_data));
device->save_item(NAME(ppu2c0x->tile_page));
device->save_item(NAME(ppu2c0x->sprite_page));
device->save_item(NAME(ppu2c0x->back_color));
device->save_item(NAME(ppu2c0x->scan_scale));
device->save_item(NAME(ppu2c0x->scanlines_per_frame));
device->save_item(NAME(ppu2c0x->regs));
device->save_item(NAME(ppu2c0x->palette_ram));
device->save_pointer(NAME(ppu2c0x->spriteram), SPRITERAM_SIZE);
device->save_pointer(NAME(ppu2c0x->colortable), ARRAY_LENGTH(default_colortable));
device->save_pointer(NAME(ppu2c0x->colortable_mono), ARRAY_LENGTH(default_colortable_mono));
device->save_item(NAME(*ppu2c0x->bitmap));
}
/*************************************
*
* PPU Reset
*
*************************************/
static DEVICE_RESET( ppu2c0x )
{
ppu2c0x_state *ppu2c0x = get_token(device);
const ppu2c0x_interface *intf = get_interface(device);
int i;
/* reset the scanline count */
ppu2c0x->scanline = 0;
/* set the scan scale (this is for dual monitor vertical setups) */
ppu2c0x->scan_scale = 1;
/* reset the callbacks */
ppu2c0x->scanline_callback_proc = NULL;
ppu2c0x->vidaccess_callback_proc = NULL;
for (i = 0; i < PPU_MAX_REG; i++)
ppu2c0x->regs[i] = 0;
memset(ppu2c0x->palette_ram, 0, ARRAY_LENGTH(ppu2c0x->palette_ram));
/* initialize the rest of the members */
ppu2c0x->refresh_data = 0;
ppu2c0x->refresh_latch = 0;
ppu2c0x->x_fine = 0;
ppu2c0x->toggle = 0;
ppu2c0x->add = 1;
ppu2c0x->videomem_addr = 0;
ppu2c0x->addr_latch = 0;
ppu2c0x->data_latch = 0;
ppu2c0x->tile_page = 0;
ppu2c0x->sprite_page = 0;
ppu2c0x->back_color = 0;
ppu2c0x->buffered_data = 0;
/* initialize the color tables */
{
int color_base = intf->color_base;
for (i = 0; i < ARRAY_LENGTH(default_colortable_mono); i++)
{
/* monochromatic table */
ppu2c0x->colortable_mono[i] = default_colortable_mono[i] + color_base;
/* color table */
ppu2c0x->colortable[i] = default_colortable[i] + color_base;
}
}
}
/***************************************************************************
GET INFO FUNCTIONS
***************************************************************************/
DEVICE_GET_INFO(ppu2c02)
{
switch (state)
{
/* --- the following bits of info are returned as 64-bit signed integers --- */
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(ppu2c0x_state); break;
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = 0; break;
case DEVINFO_INT_DATABUS_WIDTH_0: info->i = 8; break;
case DEVINFO_INT_ADDRBUS_WIDTH_0: info->i = 14; break;
case DEVINFO_INT_ADDRBUS_SHIFT_0: info->i = 0; break;
/* --- the following bits of info are returned as pointers to data --- */
case DEVINFO_PTR_DEFAULT_MEMORY_MAP_0: info->default_map8 = ADDRESS_MAP_NAME(ppu2c0x);break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(ppu2c0x); break;
case DEVINFO_FCT_STOP: /* Nothing */ break;
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(ppu2c0x); break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: strcpy(info->s, "2C02 PPU"); break;
case DEVINFO_STR_FAMILY: strcpy(info->s, "2C0X PPU"); break;
case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break;
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
case DEVINFO_STR_CREDITS: /* Nothing */ break;
}
}
DEVICE_GET_INFO(ppu2c03b)
{
switch (state)
{
case DEVINFO_STR_NAME: strcpy(info->s, "2C02B PPU"); break;
default: DEVICE_GET_INFO_CALL(ppu2c02); break;
}
}
DEVICE_GET_INFO(ppu2c04)
{
switch (state)
{
case DEVINFO_STR_NAME: strcpy(info->s, "2C04 PPU"); break;
default: DEVICE_GET_INFO_CALL(ppu2c02); break;
}
}
DEVICE_GET_INFO(ppu2c05_01)
{
switch (state)
{
case DEVINFO_STR_NAME: strcpy(info->s, "2C05 PPU"); break;
default: DEVICE_GET_INFO_CALL(ppu2c02); break;
}
}
DEVICE_GET_INFO(ppu2c05_02)
{
DEVICE_GET_INFO_CALL(ppu2c05_01);
}
DEVICE_GET_INFO(ppu2c05_03)
{
DEVICE_GET_INFO_CALL(ppu2c05_01);
}
DEVICE_GET_INFO(ppu2c05_04)
{
DEVICE_GET_INFO_CALL(ppu2c05_01);
}
DEVICE_GET_INFO(ppu2c07)
{
switch (state)
{
case DEVINFO_STR_NAME: strcpy(info->s, "2C07 PPU"); break;
default: DEVICE_GET_INFO_CALL(ppu2c02); break;
}
}
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C02, ppu2c02); // NTSC NES
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C03B, ppu2c03b); // Playchoice 10
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C04, ppu2c04); // Vs. Unisystem
// The PPU_2C05 variants have different protection value, set at DEVICE_START, but otherwise are all the same...
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C05_01, ppu2c05_01); // Vs. Unisystem (Ninja Jajamaru Kun)
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C05_02, ppu2c05_02); // Vs. Unisystem (Mighty Bomb Jack)
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C05_03, ppu2c05_03); // Vs. Unisystem (Gumshoe)
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C05_04, ppu2c05_04); // Vs. Unisystem (Top Gun)
DEFINE_LEGACY_MEMORY_DEVICE(PPU_2C07, ppu2c07); // PAL NES