#include #include "ppu.h" //#define NESE_DEBUG "PPU" #include "log.h" #include "serdes.h" void nes_ppu_init(nes_PPU* ppu, nes_PPU_Memory* mem) { uint8_t* pal = mem->palette; pal[0] = pal[4] = pal[8] = pal[12] = pal[16] = pal[20] = pal[24] = pal[28] = 0xFFU; ppu->hit_line = -1; } static const uint8_t mirror_schemes[][4] = { [nes_Mirror_Horizontal] = {0, 0, 1, 1}, [nes_Mirror_Vertical] = {0, 1, 0, 1}, [nes_Mirror_Only_0] = {0, 0, 0, 0}, [nes_Mirror_Only_1] = {1, 1, 1, 1}, [nes_Mirror_Four] = {0, 1, 2, 3}, }; void nes_ppu_set_mirroring(nes_PPU_Memory* mem, nes_Nametable_Mirroring mirror) { for (int i = 0; i < 4; ++i) { mem->bank[nes_PPU_Nametable_Bank_Index + i] = vram_page(mem, (int)mirror_schemes[mirror][i]); } } void nes_ppu_find_hit_line(nes_PPU* ppu, nes_PPU_Memory* mem) { int sprite_bank = !!(mem->ctrl & ppu_Control_Sprite_Bank); oam_sprite sprite = *(oam_sprite*)mem->oam; int sprite_height = (mem->ctrl & ppu_Control_Sprite_Size) ? 16 : 8; int stride = 1; int y_off = 0; if (sprite.attr & oam_Attr_Flip_Y) { stride = -1; y_off = sprite_height - 1; sprite_bank = (sprite.index & 1); sprite.index &= 0xFEU; } sprite_bank = (sprite_bank << 2) | (sprite.index >> 6); const int data_off = ((sprite.index & 0x3FU) << 4) + ((y_off & 8) << 1) + (y_off & 7); uint8_t* data = mem->bank[sprite_bank] + data_off; ppu->hit_line = -1; if (sprite.y + 1 <= nes_ppu_render_h && sprite.y > 0) { for (int line = 0; line < sprite_height; ++line) { if (data[0] | data[8]) { ppu->hit_line = sprite.y + 1 + line + nes_ppu_visible_line; break; } data += stride; } } } static const uint8_t sprite_rev[256] = { 0x00, 0x40, 0x80, 0xC0, 0x10, 0x50, 0x90, 0xD0, 0x20, 0x60, 0xA0, 0xE0, 0x30, 0x70, 0xB0, 0xF0, 0x04, 0x44, 0x84, 0xC4, 0x14, 0x54, 0x94, 0xD4, 0x24, 0x64, 0xA4, 0xE4, 0x34, 0x74, 0xB4, 0xF4, 0x08, 0x48, 0x88, 0xC8, 0x18, 0x58, 0x98, 0xD8, 0x28, 0x68, 0xA8, 0xE8, 0x38, 0x78, 0xB8, 0xF8, 0x0C, 0x4C, 0x8C, 0xCC, 0x1C, 0x5C, 0x9C, 0xDC, 0x2C, 0x6C, 0xAC, 0xEC, 0x3C, 0x7C, 0xBC, 0xFC, 0x01, 0x41, 0x81, 0xC1, 0x11, 0x51, 0x91, 0xD1, 0x21, 0x61, 0xA1, 0xE1, 0x31, 0x71, 0xB1, 0xF1, 0x05, 0x45, 0x85, 0xC5, 0x15, 0x55, 0x95, 0xD5, 0x25, 0x65, 0xA5, 0xE5, 0x35, 0x75, 0xB5, 0xF5, 0x09, 0x49, 0x89, 0xC9, 0x19, 0x59, 0x99, 0xD9, 0x29, 0x69, 0xA9, 0xE9, 0x39, 0x79, 0xB9, 0xF9, 0x0D, 0x4D, 0x8D, 0xCD, 0x1D, 0x5D, 0x9D, 0xDD, 0x2D, 0x6D, 0xAD, 0xED, 0x3D, 0x7D, 0xBD, 0xFD, 0x02, 0x42, 0x82, 0xC2, 0x12, 0x52, 0x92, 0xD2, 0x22, 0x62, 0xA2, 0xE2, 0x32, 0x72, 0xB2, 0xF2, 0x06, 0x46, 0x86, 0xC6, 0x16, 0x56, 0x96, 0xD6, 0x26, 0x66, 0xA6, 0xE6, 0x36, 0x76, 0xB6, 0xF6, 0x0A, 0x4A, 0x8A, 0xCA, 0x1A, 0x5A, 0x9A, 0xDA, 0x2A, 0x6A, 0xAA, 0xEA, 0x3A, 0x7A, 0xBA, 0xFA, 0x0E, 0x4E, 0x8E, 0xCE, 0x1E, 0x5E, 0x9E, 0xDE, 0x2E, 0x6E, 0xAE, 0xEE, 0x3E, 0x7E, 0xBE, 0xFE, 0x03, 0x43, 0x83, 0xC3, 0x13, 0x53, 0x93, 0xD3, 0x23, 0x63, 0xA3, 0xE3, 0x33, 0x73, 0xB3, 0xF3, 0x07, 0x47, 0x87, 0xC7, 0x17, 0x57, 0x97, 0xD7, 0x27, 0x67, 0xA7, 0xE7, 0x37, 0x77, 0xB7, 0xF7, 0x0B, 0x4B, 0x8B, 0xCB, 0x1B, 0x5B, 0x9B, 0xDB, 0x2B, 0x6B, 0xAB, 0xEB, 0x3B, 0x7B, 0xBB, 0xFB, 0x0F, 0x4F, 0x8F, 0xCF, 0x1F, 0x5F, 0x9F, 0xDF, 0x2F, 0x6F, 0xAF, 0xEF, 0x3F, 0x7F, 0xBF, 0xFF, }; void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { uint8_t* scanline_ptr = ppu->screen_data + ( nes_ppu_render_w * (ppu->scanline - nes_ppu_visible_line)); uint8_t* ptr = scanline_ptr; if (!(mem->mask & ppu_Mask_Back)) { memset(ptr, 0xFFU, nes_ppu_render_w); } else { int back_bank = nes_PPU_Nametable_Bank_Index + ((mem->addr >> 10) & 3); int y_coarse = (mem->addr >> 5) & 0x1F; int y_fine = mem->addr >> 12; int x_coarse = mem->addr & 0x1FU; const int bank_off = !!(mem->ctrl & ppu_Control_Back_Bank) << 2; const uint8_t* nametable = mem->bank[back_bank] + (y_coarse * 32) + x_coarse; const uint8_t* attrs = mem->bank[back_bank] + 0x3C0U + ((y_coarse >> 2) << 3); // Partial left column ptr += 8 - mem->x; // Omit if left column is masked if (mem->mask & ppu_Mask_Left_Back) { const uint8_t* pal = mem->palette + (((attrs[x_coarse >> 2] >> ( (x_coarse & 2) + ((y_coarse & 2) << 1))) & 3) << 2); const int ch = *nametable; const int bank = (ch >> 6) + bank_off; const int addr_off = ((ch & 0x3F) << 4) + y_fine; const uint8_t* data = mem->bank[bank] + addr_off; const uint8_t pl0 = data[0]; const uint8_t pl1 = data[8]; const int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); const int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); switch (mem->x) { case 0: ptr[-8] = pal[(pat1 >> 6) & 3]; case 1: ptr[-7] = pal[(pat0 >> 6) & 3]; case 2: ptr[-6] = pal[(pat1 >> 4) & 3]; case 3: ptr[-5] = pal[(pat0 >> 4) & 3]; case 4: ptr[-4] = pal[(pat1 >> 2) & 3]; case 5: ptr[-3] = pal[(pat0 >> 2) & 3]; case 6: ptr[-2] = pal[(pat1 >> 0) & 3]; case 7: ptr[-1] = pal[(pat0 >> 0) & 3]; } } // TODO: Mapper ppu_bus ++x_coarse; ++nametable; void __attribute__((always_inline)) inline render_bg(void) { const uint8_t* pal = mem->palette + (((attrs[x_coarse >> 2] >> ( (x_coarse & 2) + ((y_coarse & 2) << 1))) & 3) << 2); const int ch = *nametable; const int bank = (ch >> 6) + bank_off; const int addr_off = ((ch & 0x3F) << 4) + y_fine; const uint8_t* data = mem->bank[bank] + addr_off; const uint8_t pl0 = data[0]; const uint8_t pl1 = data[8]; const int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); const int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); ptr[0] = pal[(pat1 >> 6) & 3]; ptr[1] = pal[(pat0 >> 6) & 3]; ptr[2] = pal[(pat1 >> 4) & 3]; ptr[3] = pal[(pat0 >> 4) & 3]; ptr[4] = pal[(pat1 >> 2) & 3]; ptr[5] = pal[(pat0 >> 2) & 3]; ptr[6] = pal[(pat1 >> 0) & 3]; ptr[7] = pal[(pat0 >> 0) & 3]; ptr += 8; } // Left Nametable for (; x_coarse < 32; ++x_coarse) { render_bg(); // TODO: Mapper ppu_bus ++nametable; } // Right Nametable back_bank ^= 0b01; nametable = mem->bank[back_bank] + (y_coarse * 32); attrs = mem->bank[back_bank] + 0x3C0U + ((y_coarse >> 2) << 3); for (x_coarse = 0; x_coarse < (mem->addr & 0x1FU); ++x_coarse) { render_bg(); // TODO: Mapper ppu_bus ++nametable; } // Partial right column { const uint8_t* pal = mem->palette + (((attrs[x_coarse >> 2] >> ( (x_coarse & 2) + ((y_coarse & 2) << 1))) & 3) << 2); const int ch = *nametable; const int bank = (ch >> 6) + bank_off; const int addr_off = ((ch & 0x3F) << 4) + y_fine; const uint8_t* data = mem->bank[bank] + addr_off; const uint8_t pl0 = data[0]; const uint8_t pl1 = data[8]; const int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); const int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); switch (mem->x) { case 8: ptr[7] = pal[(pat0 >> 0) & 3]; case 7: ptr[6] = pal[(pat1 >> 0) & 3]; case 6: ptr[5] = pal[(pat0 >> 2) & 3]; case 5: ptr[4] = pal[(pat1 >> 2) & 3]; case 4: ptr[3] = pal[(pat0 >> 4) & 3]; case 3: ptr[2] = pal[(pat1 >> 4) & 3]; case 2: ptr[1] = pal[(pat0 >> 6) & 3]; case 1: ptr[0] = pal[(pat1 >> 6) & 3]; } } } // TODO: Mapper ppu_bus if (!(mem->mask & ppu_Mask_Left_Back)) { memset(scanline_ptr, 0xFFU, 8); } // TODO: Mapper VROM switch if (mem->mask & ppu_Mask_Sprite) { const int sprite_height = (mem->ctrl & ppu_Control_Sprite_Size) ? 16 : 8; const int scanline = ppu->scanline - nes_ppu_visible_line; int sprite_bank = !!(mem->ctrl & ppu_Control_Sprite_Bank) << 2; uint8_t foreground[nes_ppu_render_w]; memset(foreground, 0xFFU, nes_ppu_render_w); int n_sprites = 0; const oam_sprite* sprites = (oam_sprite*)mem->oam; const oam_sprite* sprite = sprites + NES_PPU_SPRITE_COUNT - 1; for ( ; sprite >= sprites && n_sprites < 8; --sprite) { int y = sprite->y + 1; if (y > scanline || y + sprite_height <= scanline) { continue; } int y_off = scanline - y; if (sprite->attr & oam_Attr_Flip_Y) { y_off = sprite_height - y_off - 1; } int bank = sprite_bank; int ch = sprite->index; if (mem->ctrl & ppu_Control_Sprite_Size) { bank = (ch & 1) << 2; ch &= 0xFEU; } bank += (ch >> 6); const int addr_off = ((ch & 0x3fU) << 4) + ((y_off & 8) << 1) + (y_off & 7); const uint8_t* data = mem->bank[bank] + addr_off; const uint8_t pl0 = data[0]; const uint8_t pl1 = data[8]; int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); const uint8_t* pal = &mem->palette[0x10 + ((sprite->attr & oam_Attr_Pal_Mask) << 2)]; const uint8_t* back = (sprite->attr & oam_Attr_Background) ? (scanline_ptr + sprite->x) : NULL; uint8_t* dst = foreground + sprite->x; const int over_x = ((int)sprite->x + 8) - nes_ppu_render_w; if (sprite->attr & oam_Attr_Flip_X) { uint8_t tmp = pat0; pat0 = sprite_rev[pat1]; pat1 = sprite_rev[tmp]; } #define do_sprite(idx, pi, shift) \ if (back && back[idx] != 0xFFU) { \ dst[idx] = back[idx]; \ } else { \ int pal_idx = (pat##pi >> shift) & 3; \ if (pal_idx) dst[idx] = pal[pal_idx]; \ } switch (over_x) { default: do_sprite(7, 0, 0); case 1: do_sprite(6, 1, 0); case 2: do_sprite(5, 0, 2); case 3: do_sprite(4, 1, 2); case 4: do_sprite(3, 0, 4); case 5: do_sprite(2, 1, 4); case 6: do_sprite(1, 0, 6); case 7: do_sprite(0, 1, 6); } ++n_sprites; } if (n_sprites >= 8) { mem->status |= ppu_Status_Overflow; } else { mem->status &= ~ppu_Status_Overflow; } // Mask column 0? if (!(mem->mask & ppu_Mask_Left_Sprite)) { memset(foreground, 0xFFU, 8); } // Overlay the sprites const uint8_t* fg = foreground; uint8_t* dst = scanline_ptr; for (int i = 0; i < nes_ppu_render_w; ++i) { if (*fg != 0xFFU) *dst = *fg; ++fg; ++dst; } } // Increment internal registers if (mem->mask & (ppu_Mask_Sprite | ppu_Mask_Back)) { uint16_t mask = 0b10000011111; mem->addr = (mem->addr & ~mask) | (mem->t & mask); int y_scroll = (mem->addr >> 12) | ((mem->addr >> 2) & 0xF8U); if (nes_ppu_render_h - 1 == y_scroll) { y_scroll = 0; mem->addr ^= 0x800; } else if (0xFFU == y_scroll) { y_scroll = 0; } else { ++y_scroll; } mem->addr = (mem->addr & ~0b111001111100000) | ((y_scroll & 7) << 12) | ((y_scroll & 0xF8U) << 2); } } /* static int nes_ppu_chr_ram_size(const void* _ppu, const void*) { const nes_PPU_Memory* ppu = (const nes_PPU_Memory*)_ppu; return (ppu->chr_ram ? ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE : 0); } static int nes_ppu_read_chr_ram(void* dst, const void* src, int avail, const void*) { int size = 0; nes_PPU_Memory* ppu = (nes_PPU_Memory*)dst; if (ppu->chr_ram) { size = ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE; if (size > avail) size = avail; memcpy(ppu->chr_ram, src, size); } return size; } static int nes_ppu_write_chr_ram(void* dst, const void* src, int avail, const void*) { int size = 0; const nes_PPU_Memory* ppu = (const nes_PPU_Memory*)src; if (ppu->chr_ram) { size = ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE; if (size > avail) size = avail; memcpy(dst, ppu->chr_ram, size); } return size; } static const Serdes_IO ppu_serdes_io = { .read = nes_ppu_read_chr_ram, .write = nes_ppu_write_chr_ram, .in_size = nes_ppu_chr_ram_size, .out_size = nes_ppu_chr_ram_size, }; */ static void* ppu_chr_ram_ptr(const void* _ppu) { return ((nes_PPU_Memory*)_ppu)->chr_ram; } static size_t ppu_chr_ram_size(const void* _ppu) { return ((nes_PPU_Memory*)_ppu)->n_chr_banks * NES_CHR_ROM_PAGE_SIZE; } static Serdes_Ptr_Ref ppu_chr_ram_ref = { .ptr = ppu_chr_ram_ptr, .size = ppu_chr_ram_size, }; const Serdes_Item nes_ppu_memory_serdes[] = { {"CRAM", 0, &serdes_mem_ptr, &ppu_chr_ram_ref}, {"VRAM", offsetof(nes_PPU_Memory, ctrl), &serdes_mem, (void*)(sizeof(nes_PPU_Memory) - offsetof(nes_PPU_Memory, ctrl))}, {0} };