Joypad, sprite stuff, palette mirroring => Super Mario Bros working!

This commit is contained in:
Andrea Orrù 2014-02-17 23:14:51 +01:00
parent 77ac51e9f2
commit 8cba7a818e
4 changed files with 94 additions and 30 deletions

View file

@ -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); }

View file

@ -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);

View file

@ -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();

View file

@ -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;
}