📄 nes_ppu.cpp
字号:
/*
** nester - NES emulator
** Copyright (C) 2000 Darren Ranalli
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** 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.
*/
/* $Id: Nes_ppu.cpp,v 1.8 2003/03/24 14:47:34 Rick Exp $ */
#include <string.h>
#include "NES_PPU.h"
#include "NES.h"
#include "pixmap.h"
#include "settings.h"
#include "debug.h"
#include "profile.h"
CompiledTiles::CompiledTiles(int maxTiles) : _maxTiles(maxTiles)
{
_tiles = new uint8[maxTiles * BYTES_PER_MERGED_TILE];
//memset(_tiles, 0, maxTiles * BYTES_PER_MERGED_TILE);
}
CompiledTiles::~CompiledTiles()
{
delete _tiles;
}
#define EXTRACT_4_PIXELS() \
col = 0; \
if(pattern_lo & pattern_mask) col |= (0x01 << 6); \
if(pattern_hi & pattern_mask) col |= (0x02 << 6); \
pattern_mask >>= 1; \
if(pattern_lo & pattern_mask) col |= (0x01 << 4); \
if(pattern_hi & pattern_mask) col |= (0x02 << 4); \
pattern_mask >>= 1; \
if(pattern_lo & pattern_mask) col |= (0x01 << 2); \
if(pattern_hi & pattern_mask) col |= (0x02 << 2); \
pattern_mask >>= 1; \
if(pattern_lo & pattern_mask) col |= (0x01 << 0); \
if(pattern_hi & pattern_mask) col |= (0x02 << 0); \
*p++= col;
void CompiledTiles::compile(int start, int count, uint8 *data)
{
//assert(start >= 0 && start + count <= _maxTiles);
uint8 *p = _tiles + start * BYTES_PER_MERGED_TILE;
CompiledTiles::compile(count, data, p);
}
void CompiledTiles::compile(int count, uint8 *src, uint8 *dest)
{
uint8 *p = dest;
uint8 col;
for (int i = 0; i < count; i++) {
for (int line = 0; line < 8; line++) {
uint8 pattern_lo = *src;
uint8 pattern_hi = *(src + 8);
int pattern_mask = 0x80;
EXTRACT_4_PIXELS();
pattern_mask >>= 1;
EXTRACT_4_PIXELS();
src++;
}
src += 8;
}
}
#define UPDATE_PIXEL() \
if(data & pattern_mask) *p |= bit; \
p++;
#define VRAM(addr) \
PPU_VRAM_banks[(addr) >> 10][(addr) & 0x3FF]
#define TILE(addr) \
(PPU_tile_banks[(addr) >> 10] + ((addr) & 0x3FF) / 16 * BYTES_PER_MERGED_TILE)
#define TILE_OFFSET(line) \
((line) * (BYTES_PER_MERGED_TILE / 8))
/*
scanline start (if background or sprites are enabled):
v:0000010000011111=t:0000010000011111
*/
#define LOOPY_SCANLINE_START(v,t) \
{ \
v = (v & 0xFBE0) | (t & 0x041F); \
}
/*
bits 12-14 are the tile Y offset.
you can think of bits 5,6,7,8,9 as the "y scroll"(*8). this functions
slightly different from the X. it wraps to 0 and bit 11 is switched when
it's incremented from _29_ instead of 31. there are some odd side effects
from this.. if you manually set the value above 29 (from either 2005 or
2006), the wrapping from 29 obviously won't happen, and attrib data will be
used as name table data. the "y scroll" still wraps to 0 from 31, but
without switching bit 11. this explains why writing 240+ to 'Y' in 2005
appeared as a negative scroll value.
*/
#define LOOPY_NEXT_LINE(v) \
{ \
if((v & 0x7000) == 0x7000) /* is subtile y offset == 7? */ \
{ \
v &= 0x8FFF; /* subtile y offset = 0 */ \
if((v & 0x03E0) == 0x03A0) /* name_tab line == 29? */ \
{ \
v ^= 0x0800; /* switch nametables (bit 11) */ \
v &= 0xFC1F; /* name_tab line = 0 */ \
} \
else \
{ \
if((v & 0x03E0) == 0x03E0) /* line == 31? */ \
{ \
v &= 0xFC1F; /* name_tab line = 0 */ \
} \
else \
{ \
v += 0x0020; \
} \
} \
} \
else \
{ \
v += 0x1000; /* next subtile y offset */ \
} \
}
/*
you can think of bits 0,1,2,3,4 of the vram address as the "x scroll"(*8)
that the ppu increments as it draws. as it wraps from 31 to 0, bit 10 is
switched. you should see how this causes horizontal wrapping between name
tables (0,1) and (2,3).
*/
#define LOOPY_NEXT_TILE(v) \
{ \
if((v & 0x001F) == 0x001F) \
{ \
v ^= 0x0400; /* switch nametables (bit 10) */ \
v &= 0xFFE0; /* tile x = 0 */ \
} \
else \
{ \
v++; /* next tile */ \
} \
}
#define LOOPY_NEXT_PIXEL(v,x) \
{ \
if(x == 0x07) \
{ \
LOOPY_NEXT_TILE(v); \
x = 0x00; \
} \
else \
{ \
x++; \
} \
}
#define CHECK_MMC2(addr) \
if(((addr) & 0x0FC0) == 0x0FC0) \
{ \
if((((addr) & 0x0FF0) == 0x0FD0) || (((addr) & 0x0FF0) == 0x0FE0)) \
{ \
parent_NES->mapper->PPU_Latch_FDFE(addr); \
} \
}
NES_PPU::NES_PPU(NES* parent)
{
parent_NES = parent;
}
void NES_PPU::reset()
{
// reset registers
memset(LowRegs, 0x00, sizeof(LowRegs));
HighReg0x4014 = 0x00;
// clear sprite RAM
memset(spr_ram, 0x00, sizeof(spr_ram));
// clear palettes
memset(bg_pal, 0x00, sizeof(bg_pal));
memset(spr_pal, 0x00, sizeof(spr_pal));
// clear solid buffer
memset(solid_buf, 0x00, sizeof(solid_buf));
// clear pattern tables
/*
memset(PPU_patterntable0, 0x00, sizeof(PPU_patterntable0));
memset(PPU_patterntable1, 0x00, sizeof(PPU_patterntable1));
*/
memset(PPU_patterntables, 0x00, sizeof(PPU_patterntables));
memset(PPU_patterntype, 0x00, sizeof(PPU_patterntype));
memset(PPU_tile_tables, 0, sizeof(PPU_tile_tables));
// clear internal name tables
memset(PPU_nametables, 0x00, sizeof(PPU_nametables));
// clear VRAM page table
memset(PPU_VRAM_banks, 0x00, sizeof(PPU_VRAM_banks));
memset(PPU_tile_banks, 0, sizeof(PPU_tile_banks));
// set up PPU memory space table
/*
PPU_VRAM_banks[0x00] = PPU_patterntable0 + (0*0x400);
PPU_VRAM_banks[0x01] = PPU_patterntable0 + (1*0x400);
PPU_VRAM_banks[0x02] = PPU_patterntable0 + (2*0x400);
PPU_VRAM_banks[0x03] = PPU_patterntable0 + (3*0x400);
PPU_VRAM_banks[0x04] = PPU_patterntable1 + (0*0x400);
PPU_VRAM_banks[0x05] = PPU_patterntable1 + (1*0x400);
PPU_VRAM_banks[0x06] = PPU_patterntable1 + (2*0x400);
PPU_VRAM_banks[0x07] = PPU_patterntable1 + (3*0x400);
*/
PPU_VRAM_banks[0x00] = PPU_patterntables + (0*0x400);
PPU_VRAM_banks[0x01] = PPU_patterntables + (1*0x400);
PPU_VRAM_banks[0x02] = PPU_patterntables + (2*0x400);
PPU_VRAM_banks[0x03] = PPU_patterntables + (3*0x400);
PPU_VRAM_banks[0x04] = PPU_patterntables + (4*0x400);
PPU_VRAM_banks[0x05] = PPU_patterntables + (5*0x400);
PPU_VRAM_banks[0x06] = PPU_patterntables + (6*0x400);
PPU_VRAM_banks[0x07] = PPU_patterntables + (7*0x400);
// point nametables at internal name table 0
PPU_VRAM_banks[0x08] = PPU_nametables;
PPU_VRAM_banks[0x09] = PPU_nametables;
PPU_VRAM_banks[0x0A] = PPU_nametables;
PPU_VRAM_banks[0x0B] = PPU_nametables;
PPU_tile_banks[0x00] = PPU_tile_tables + (0*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x01] = PPU_tile_tables + (1*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x02] = PPU_tile_tables + (2*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x03] = PPU_tile_tables + (3*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x04] = PPU_tile_tables + (4*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x05] = PPU_tile_tables + (5*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x06] = PPU_tile_tables + (6*0x400/16*BYTES_PER_MERGED_TILE);
PPU_tile_banks[0x07] = PPU_tile_tables + (7*0x400/16*BYTES_PER_MERGED_TILE);
read_2007_buffer = 0x00;
in_vblank = 0;
bg_pattern_table_addr = 0;
spr_pattern_table_addr = 0;
ppu_addr_inc = 0;
loopy_v = 0;
loopy_t = 0;
loopy_x = 0;
toggle_2005_2006 = 0;
spr_ram_rw_ptr = 0;
read_2007_buffer = 0;
current_frame_line = 0;
// set mirroring
set_mirroring(parent_NES->ROM->get_mirroring());
// Rick
ppu_buf = 0;
tile_bank_switched = 0;
memcpy(lazy_tile_banks, PPU_tile_banks, sizeof(PPU_tile_banks));
name_table_switched = FALSE;
memcpy(lazy_VRAM_banks, PPU_VRAM_banks, sizeof(PPU_VRAM_banks));
lazy_v = lazy_t = lazy_x = 0;
}
void NES_PPU::set_mirroring(uint32 nt0, uint32 nt1, uint32 nt2, uint32 nt3)
{
ASSERT(nt0 < 4); ASSERT(nt1 < 4); ASSERT(nt2 < 4); ASSERT(nt3 < 4);
PPU_VRAM_banks[0x08] = PPU_nametables + (nt0 << 10); // * 0x0400
PPU_VRAM_banks[0x09] = PPU_nametables + (nt1 << 10);
PPU_VRAM_banks[0x0A] = PPU_nametables + (nt2 << 10);
PPU_VRAM_banks[0x0B] = PPU_nametables + (nt3 << 10);
name_table_switched = TRUE;
}
void NES_PPU::set_mirroring(mirroring_type m)
{
if(MIRROR_FOUR_SCREEN == m)
{
set_mirroring(0,1,2,3);
}
else if(MIRROR_HORIZ == m)
{
set_mirroring(0,0,1,1);
}
else if(MIRROR_VERT == m)
{
set_mirroring(0,1,0,1);
}
else
{
LOG("Invalid mirroring type" << endl);
set_mirroring(MIRROR_FOUR_SCREEN);
}
}
void NES_PPU::start_frame()
{
current_frame_line = 0;
if(spr_enabled() || bg_enabled())
{
loopy_v = loopy_t;
}
}
uint8 NES_PPU::getBGColor() { return NES::NES_COLOR_BASE + bg_pal[0]; }
void NES_PPU::do_scanline_and_draw(uint8* buf)
{
if(!bg_enabled())
{
// set to background color
memset(buf, NES::NES_COLOR_BASE + bg_pal[0], NES_BACKBUF_WIDTH);
}
if(spr_enabled() || bg_enabled())
{
LOOPY_SCANLINE_START(loopy_v, loopy_t);
if(bg_enabled())
{
// draw background
render_bg(buf);
}
else
{
// clear out solid buffer
memset(solid_buf, 0x00, sizeof(solid_buf));
}
if(spr_enabled())
{
// draw sprites
render_spr(buf);
}
LOOPY_NEXT_LINE(loopy_v);
}
current_frame_line++;
}
void NES_PPU::do_scanline_and_dont_draw()
{
// mmc2 / punchout -- we must simulate the ppu for every line
/*if(parent_NES->ROM->get_mapper_num() == 9)
{
do_scanline_and_draw(dummy_buffer);
}
else*/
{
// if sprite 0 flag not set and sprite 0 on current line
if((!sprite0_hit()) &&
(current_frame_line >= ((uint32)(spr_ram[0]+1))) &&
(current_frame_line < ((uint32)(spr_ram[0]+1+(sprites_8x16()?16:8))))
)
{
// render line to dummy buffer
do_scanline_and_draw(dummy_buffer);
}
else
{
if(spr_enabled() || bg_enabled())
{
LOOPY_SCANLINE_START(loopy_v, loopy_t);
LOOPY_NEXT_LINE(loopy_v);
}
current_frame_line++;
}
}
}
void NES_PPU::end_frame()
{
}
void NES_PPU::start_frame(uint8 *buf, int ypitch)
{
ppu_buf = buf;
ppu_ypitch = ypitch;
current_frame_line = 0;
lazy_bg_start_line = 0;
lazy_spr_start_line = 0;
lazy_bg_need_update = 0;
lazy_spr_need_update = 0;
if (tile_bank_switched) {
memcpy(lazy_tile_banks, PPU_tile_banks, sizeof(PPU_tile_banks));
tile_bank_switched = FALSE;
}
if (name_table_switched) {
lazy_VRAM_banks[8] = PPU_VRAM_banks[8];
lazy_VRAM_banks[9] = PPU_VRAM_banks[9];
lazy_VRAM_banks[10] = PPU_VRAM_banks[10];
lazy_VRAM_banks[11] = PPU_VRAM_banks[11];
name_table_switched = FALSE;
}
lazy_v = loopy_v;
lazy_t = loopy_t;
lazy_x = loopy_x;
lazy_bg_pattern_table_addr = bg_pattern_table_addr;
lazy_spr_pattern_table_addr = spr_pattern_table_addr;
memcpy(lazy_bg_pal, bg_pal, sizeof(lazy_bg_pal));
lazy_bg_pal[4] = lazy_bg_pal[8] = lazy_bg_pal[12] = lazy_bg_pal[0];
memcpy(lazy_spr_pal, spr_pal, sizeof(lazy_spr_pal));
lazy_spr_pal[4] = lazy_spr_pal[8] = lazy_spr_pal[12] = lazy_spr_pal[0];
lazy_bg_enabled = bg_enabled();
lazy_spr_enabled = spr_enabled();
if(lazy_bg_enabled || lazy_spr_enabled) {
loopy_v = loopy_t;
lazy_v = lazy_t;
}
mapper_num = parent_NES->ROM->get_mapper_num();
}
void NES_PPU::lazy_do_scanline()
{
// mmc2 / punchout -- we must simulate the ppu for every line
/*
if(parent_NES->ROM->get_mapper_num() == 9)
{
do_scanline_and_draw(dummy_buffer);
}
else
{*/
if (!lazy_bg_enabled) {
memset(ppu_buf + current_frame_line * ppu_ypitch,
NES::NES_COLOR_BASE + bg_pal[0], NES_BACKBUF_WIDTH);
}
if (tile_bank_switched) {
if (memcmp(lazy_tile_banks, PPU_tile_banks, sizeof(PPU_tile_banks))) {
lazy_bg_need_update = TRUE;
lazy_spr_need_update = TRUE;
} else {
tile_bank_switched = FALSE;
}
}
if (name_table_switched) {
if (memcmp(&lazy_VRAM_banks[8], &PPU_VRAM_banks[8], sizeof(lazy_VRAM_banks[0]) * 4)) {
lazy_bg_need_update = TRUE;
}
}
//if (lazy_bg_need_update) {
// we should always update BG before SPR
if (lazy_bg_need_update || lazy_spr_need_update) {
if (lazy_bg_enabled) {
lazy_update_bg();
}
lazy_bg_start_line = current_frame_line;
lazy_bg_need_update = FALSE;
lazy_bg_enabled = bg_enabled();
lazy_bg_pattern_table_addr = bg_pattern_table_addr;
lazy_v = loopy_v;
lazy_t = loopy_t;
lazy_x = loopy_x;
memcpy(lazy_bg_pal, bg_pal, sizeof(lazy_bg_pal));
lazy_bg_pal[4] = lazy_bg_pal[8] = lazy_bg_pal[12] = lazy_bg_pal[0];
}
if (lazy_spr_need_update) {
if (lazy_spr_enabled) {
lazy_update_spr();
}
lazy_spr_start_line = current_frame_line;
lazy_spr_need_update = FALSE;
lazy_spr_enabled = spr_enabled();
lazy_spr_pattern_table_addr = spr_pattern_table_addr;
memcpy(lazy_spr_pal, spr_pal, sizeof(lazy_spr_pal));
lazy_spr_pal[4] = lazy_spr_pal[8] = lazy_spr_pal[12] = lazy_spr_pal[0];
}
if (tile_bank_switched) {
memcpy(lazy_tile_banks, PPU_tile_banks, sizeof(PPU_tile_banks));
tile_bank_switched = FALSE;
}
if (name_table_switched) {
lazy_VRAM_banks[8] = PPU_VRAM_banks[8];
lazy_VRAM_banks[9] = PPU_VRAM_banks[9];
lazy_VRAM_banks[10] = PPU_VRAM_banks[10];
lazy_VRAM_banks[11] = PPU_VRAM_banks[11];
name_table_switched = FALSE;
}
// if sprite 0 flag not set and sprite 0 on current line
/*
if((!sprite0_hit()) &&
(current_frame_line >= ((uint32)(spr_ram[0]+1))) &&
(current_frame_line < ((uint32)(spr_ram[0]+1+(sprites_8x16()?16:8))))
)
{
// render line to dummy buffer
do_scanline_and_draw(dummy_buffer);
}
else
{*/
if(spr_enabled() || bg_enabled())
{
LOOPY_SCANLINE_START(loopy_v, loopy_t);
// if sprite 0 flag not set and sprite 0 on current line
if(!sprite0_hit()
&& spr_enabled()
&& bg_enabled()
&& (current_frame_line >= ((uint32)(spr_ram[0]+1)))
&& (current_frame_line < ((uint32)(spr_ram[0]+1+(sprites_8x16()?16:8))))
)
{
fast_test_sprite0_hit();
}
LOOPY_NEXT_LINE(loopy_v);
}
current_frame_line++;
//}
}
void NES_PPU::lazy_do_scanline_dont_draw()
{
// mmc2 / punchout -- we must simulate the ppu for every line
/*if(parent_NES->ROM->get_mapper_num() == 9)
{
do_scanline_and_draw(dummy_buffer);
}
else*/
if(spr_enabled() || bg_enabled())
{
LOOPY_SCANLINE_START(loopy_v, loopy_t);
// if sprite 0 flag not set and sprite 0 on current line
if(!sprite0_hit()
&& spr_enabled()
&& bg_enabled()
&& (current_frame_line >= ((uint32)(spr_ram[0]+1)))
&& (current_frame_line < ((uint32)(spr_ram[0]+1+(sprites_8x16()?16:8))))
)
{
fast_test_sprite0_hit();
}
LOOPY_NEXT_LINE(loopy_v);
}
current_frame_line++;
}
void NES_PPU::end_frame(uint8 *buf)
{
if (lazy_bg_enabled) {
lazy_update_bg();
}
if (lazy_spr_enabled) {
lazy_update_spr();
}
ppu_buf = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -