Joypad, sprite stuff, palette mirroring => Super Mario Bros working!
This commit is contained in:
parent
77ac51e9f2
commit
8cba7a818e
4 changed files with 94 additions and 30 deletions
11
src/cpu.cpp
11
src/cpu.cpp
|
@ -1,6 +1,7 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include "cartridge.hpp"
|
||||
#include "io.hpp"
|
||||
#include "ppu.hpp"
|
||||
#include "cpu.hpp"
|
||||
|
||||
|
@ -33,11 +34,13 @@ template<bool wr> inline u8 access(u16 addr, u8 v = 0)
|
|||
{
|
||||
case 0x0000 ... 0x1FFF: r = &ram[addr % 0x800]; if (wr) *r = v; return *r; // RAM.
|
||||
case 0x2000 ... 0x3FFF: return PPU::access<wr>(addr % 8, v); // PPU.
|
||||
case 0x4000 ... 0x4013: return 0;
|
||||
case 0x4014: if (wr) dma_oam(v); return 0; // OAM DMA.
|
||||
case 0x4015 ... 0x4020: return 0;
|
||||
default: return Cartridge::access<wr>(addr, v); // Cartridge.
|
||||
case 0x4014: if (wr) dma_oam(v); break; // OAM DMA.
|
||||
case 0x4016: if (wr) { IO::write_joypad_strobe(v & 1); break; } // Joypad strobe.
|
||||
else return IO::read_joypad(0); // Joypad 0.
|
||||
case 0x4017: if (!wr) return IO::read_joypad(1); break; // Joypad 1.
|
||||
case 0x4018 ... 0xFFFF: return Cartridge::access<wr>(addr, v); // Cartridge.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
inline u8 wr(u16 a, u8 v) { return access<1>(a, v); }
|
||||
inline u8 rd(u16 a) { return access<0>(a); }
|
||||
|
|
52
src/io.cpp
52
src/io.cpp
|
@ -4,13 +4,19 @@
|
|||
|
||||
namespace IO {
|
||||
|
||||
// Screen size:
|
||||
const unsigned width = 256;
|
||||
const unsigned height = 240;
|
||||
|
||||
// SDL structures:
|
||||
SDL_Window* window;
|
||||
SDL_Renderer* renderer;
|
||||
SDL_Texture* texture;
|
||||
u32 pixels[width * height];
|
||||
u8 const* keys;
|
||||
|
||||
u32 pixels[width * height]; // Video buffer.
|
||||
u8 joypad_bits[2]; // Joypad shift registers.
|
||||
bool strobe; // Joypad strobe latch.
|
||||
|
||||
void init()
|
||||
{
|
||||
|
@ -27,9 +33,51 @@ void init()
|
|||
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
|
||||
width, height);
|
||||
|
||||
keys = SDL_GetKeyboardState(0);
|
||||
signal(SIGINT, SIG_DFL);
|
||||
}
|
||||
|
||||
u8 get_joypad_state(int n)
|
||||
{
|
||||
u8 j = 0;
|
||||
SDL_PumpEvents();
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
j |= (keys[SDL_SCANCODE_A]) << 0; // A.
|
||||
j |= (keys[SDL_SCANCODE_S]) << 1; // B.
|
||||
j |= (keys[SDL_SCANCODE_SPACE]) << 2; // Select.
|
||||
j |= (keys[SDL_SCANCODE_RETURN]) << 3; // Start.
|
||||
j |= (keys[SDL_SCANCODE_UP]) << 4; // Up.
|
||||
j |= (keys[SDL_SCANCODE_DOWN]) << 5; // Down.
|
||||
j |= (keys[SDL_SCANCODE_LEFT]) << 6; // Left.
|
||||
j |= (keys[SDL_SCANCODE_RIGHT]) << 7; // Right.
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
u8 read_joypad(int n)
|
||||
{
|
||||
// When strobe is high, it keeps reading A:
|
||||
if (strobe)
|
||||
return 0x40 | (get_joypad_state(n) & 1);
|
||||
|
||||
// Get the status of a button and shift the register:
|
||||
u8 j = 0x40 | (joypad_bits[n] & 1);
|
||||
joypad_bits[n] = 0x80 | (joypad_bits[n] >> 1);
|
||||
return j;
|
||||
}
|
||||
|
||||
void write_joypad_strobe(bool v)
|
||||
{
|
||||
// Read the joypad data on strobe's transition 1 -> 0.
|
||||
if (strobe and !v)
|
||||
for (int i = 0; i < 2; i++)
|
||||
joypad_bits[i] = get_joypad_state(i);
|
||||
|
||||
strobe = v;
|
||||
}
|
||||
|
||||
void draw_pixel(unsigned x, unsigned y, u32 rgb)
|
||||
{
|
||||
pixels[y*width + x] = rgb;
|
||||
|
@ -37,7 +85,7 @@ void draw_pixel(unsigned x, unsigned y, u32 rgb)
|
|||
|
||||
void flush_screen()
|
||||
{
|
||||
SDL_UpdateTexture(texture, NULL, pixels, width * sizeof (u32));
|
||||
SDL_UpdateTexture(texture, NULL, pixels, width * sizeof(u32));
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace IO {
|
|||
|
||||
|
||||
void init();
|
||||
u8 read_joypad(int n);
|
||||
void write_joypad_strobe(bool v);
|
||||
void draw_pixel(unsigned x, unsigned y, u32 rgb);
|
||||
void flush_screen();
|
||||
|
||||
|
|
59
src/ppu.cpp
59
src/ppu.cpp
|
@ -8,7 +8,7 @@ namespace PPU {
|
|||
|
||||
|
||||
u8 ciRam[0x800]; // VRAM for nametables.
|
||||
u8 palette[0x20]; // VRAM for palettes.
|
||||
u8 cgRam[0x20]; // VRAM for palettes.
|
||||
u8 oamMem[0x100]; // VRAM for sprite properties.
|
||||
Sprite oam[8], secOam[8]; // Sprite buffers.
|
||||
|
||||
|
@ -31,18 +31,21 @@ int scanline, cycle;
|
|||
bool frameOdd;
|
||||
|
||||
inline bool rendering() { return mask.bg || mask.spr; }
|
||||
inline int spr_height() { return ctrl.sprSz ? 16 : 8; }
|
||||
|
||||
/* Access PPU memory */
|
||||
template <bool wr> u8 mem_access(u16 addr, u8 v = 0)
|
||||
{
|
||||
u8* ref;
|
||||
|
||||
if (addr < 0x2000) return Cartridge::chr_access<wr>(addr, v); // CHR-ROM/RAM.
|
||||
else if (addr < 0x2800) ref = &ciRam[addr - 0x2000]; // Nametables.
|
||||
else if (addr < 0x3000) return 0x00;
|
||||
else if (addr < 0x3F00) ref = &ciRam[addr - 0x3000]; // Nametables (mirror).
|
||||
else ref = &palette[(addr % 4) ? addr % 0x20 : 0]; // Palettes.
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0000 ... 0x1FFF: return Cartridge::chr_access<wr>(addr, v); // CHR-ROM/RAM.
|
||||
case 0x2000 ... 0x3EFF: ref = &ciRam[addr - 0x2000]; break; // Nametables.
|
||||
case 0x3F00 ... 0x3FFF: // Palettes:
|
||||
if ((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
ref = &cgRam[addr & 0x1F];
|
||||
break;
|
||||
}
|
||||
if (wr) return *ref = v;
|
||||
else return *ref;
|
||||
}
|
||||
|
@ -152,7 +155,8 @@ void eval_sprites()
|
|||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
int line = (scanline == 261 ? -1 : scanline) - oamMem[i*4 + 0];
|
||||
if (line >= 0 and line < (ctrl.sprSz ? 16 : 8))
|
||||
// If the sprite is in the scanline, copy its properties into secondary OAM:
|
||||
if (line >= 0 and line < spr_height())
|
||||
{
|
||||
secOam[n].id = i;
|
||||
secOam[n].y = oamMem[i*4 + 0];
|
||||
|
@ -172,14 +176,20 @@ void eval_sprites()
|
|||
/* Load the sprite info into primary OAM and fetch their tile data. */
|
||||
void load_sprites()
|
||||
{
|
||||
u16 addr;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
oam[i] = secOam[i];
|
||||
oam[i] = secOam[i]; // Copy secondary OAM into primary.
|
||||
|
||||
u16 addr = (ctrl.sprTbl * 0x1000) + oam[i].tile * 16;
|
||||
unsigned sprY = (scanline - oam[i].y) & 7;
|
||||
if (oam[i].attr & 0x80) sprY ^= 7;
|
||||
addr += sprY;
|
||||
// Different address modes depending on the sprite height:
|
||||
if (spr_height() == 16)
|
||||
addr = ((oam[i].tile & 1) * 0x1000) + ((oam[i].tile & ~1) * 16);
|
||||
else
|
||||
addr = ( ctrl.sprTbl * 0x1000) + ( oam[i].tile * 16);
|
||||
|
||||
unsigned sprY = (scanline - oam[i].y) % spr_height(); // Line inside the sprite.
|
||||
if (oam[i].attr & 0x80) sprY ^= spr_height() - 1; // Vertical flip.
|
||||
addr += sprY + (sprY & 8); // Select the second tile if on 8x16.
|
||||
|
||||
oam[i].dataL = rd(addr + 0);
|
||||
oam[i].dataH = rd(addr + 8);
|
||||
|
@ -189,38 +199,39 @@ void load_sprites()
|
|||
/* Process a pixel, draw it if it's on screen */
|
||||
void pixel()
|
||||
{
|
||||
u8 bgBits, atBits, palette, objPalette = 0;
|
||||
u8 palette, objPalette = 0;
|
||||
bool objPriority = 0;
|
||||
|
||||
if (scanline < 240 and cycle >= 1 and cycle <= 256)
|
||||
{
|
||||
// Background:
|
||||
bgBits = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
|
||||
palette = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
|
||||
NTH_BIT(bgShiftL, 15 - fX);
|
||||
atBits = (NTH_BIT(atShiftH, 7 - fX) << 1) |
|
||||
NTH_BIT(atShiftL, 7 - fX);
|
||||
palette = (atBits << 2) | bgBits;
|
||||
if (palette)
|
||||
palette |= ((NTH_BIT(atShiftH, 7 - fX) << 1) |
|
||||
NTH_BIT(atShiftL, 7 - fX)) << 2;
|
||||
|
||||
// Sprites:
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
if (oam[i].id == 64) continue;
|
||||
if (oam[i].id == 64) continue; // Void entry.
|
||||
|
||||
unsigned sprX = cycle - oam[i].x;
|
||||
if (sprX >= 8) continue;
|
||||
if (oam[i].attr & 0x40) sprX ^= 7;
|
||||
if (oam[i].attr & 0x40) sprX ^= 7; // Horizontal flip.
|
||||
|
||||
u8 sprPalette = (NTH_BIT(oam[i].dataH, 7 - sprX) << 1) |
|
||||
NTH_BIT(oam[i].dataL, 7 - sprX);
|
||||
if (sprPalette == 0) continue;
|
||||
sprPalette |= (oam[i].attr & 3) << 2;
|
||||
|
||||
if (oam[i].id == 0 && palette && cycle != 255) status.sprHit = true;
|
||||
sprPalette |= (oam[i].attr & 3) << 2;
|
||||
objPalette = sprPalette + 16;
|
||||
objPriority = oam[i].attr & 0x20;
|
||||
}
|
||||
if (objPalette && (palette == 0 || objPriority == 0)) palette = objPalette;
|
||||
|
||||
IO::draw_pixel(cycle - 1, scanline, nesRgb[rd(0x3F00 | (rendering() ? palette : 0))]);
|
||||
IO::draw_pixel(cycle - 1, scanline, nesRgb[rd(0x3F00 + (rendering() ? palette : 0))]);
|
||||
}
|
||||
|
||||
bgShiftL <<= 1; bgShiftH <<= 1;
|
||||
|
@ -240,7 +251,7 @@ template<Scanline s> void scanline_cycle()
|
|||
// Sprites:
|
||||
switch (cycle)
|
||||
{
|
||||
case 1: clear_oam(); break;
|
||||
case 1: clear_oam(); if (s == PRE) { status.sprOvf = status.sprHit = false; } break;
|
||||
case 257: eval_sprites(); break;
|
||||
case 321: load_sprites(); break;
|
||||
}
|
||||
|
|
Reference in a new issue