#include #include "ppu.h" //#define DEBUG "PPU" #include "log.h" 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; } } } 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); /* // Emulate the happy part of the backdrop override quirk int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? (mem->addr & 0x1FU) : 0; // Don't use the rendering palette (masked transparency) memset(ptr, mem->pal_bank[0x300 + pal_idx], 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) { // This time, we do need overflow protection // uint8_t sprite_data[nes_ppu_render_w + 8] = {0}; const int sprite_height = (mem->ctrl & ppu_Control_Sprite_Size) ? 16 : 8; const int scanline = ppu->scanline - nes_ppu_visible_line; int bank = !!(mem->ctrl & ppu_Control_Sprite_Bank) << 2; 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 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]; const int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); const int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); const uint8_t* pal = &mem->palette[0x10 + ((sprite->attr & oam_Attr_Pal_Mask) << 2)]; uint8_t* dst = scanline_ptr + sprite->x; const int over_x = ((int)sprite->x + 8) - nes_ppu_render_w; // TODO: X Flip switch (over_x) { default: { int pal_idx = (pat1 << 0) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[7] == 0xFFU) && pal_idx) { dst[7] = pal[pal_idx]; } } case 1: { int pal_idx = (pat0 << 0) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[6] == 0xFFU) && pal_idx) { dst[6] = pal[pal_idx]; } } case 2: { int pal_idx = (pat1 << 2) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[5] == 0xFFU) && pal_idx) { dst[5] = pal[pal_idx]; } } case 3: { int pal_idx = (pat0 << 2) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[4] == 0xFFU) && pal_idx) { dst[4] = pal[pal_idx]; } } case 4: { int pal_idx = (pat1 << 4) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[3] == 0xFFU) && pal_idx) { dst[3] = pal[pal_idx]; } } case 5: { int pal_idx = (pat0 << 4) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[2] == 0xFFU) && pal_idx) { dst[2] = pal[pal_idx]; } } case 6: { int pal_idx = (pat1 << 6) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[1] == 0xFFU) && pal_idx) { dst[1] = pal[pal_idx]; } } case 7: { int pal_idx = (pat0 << 6) & 3; if ( ( !(sprite->attr & oam_Attr_Background) || dst[0] == 0xFFU) && pal_idx) { dst[0] = pal[pal_idx]; } } } ++n_sprites; } if (n_sprites >= 8) { mem->status |= ppu_Status_Overflow; } else { mem->status &= ~ppu_Status_Overflow; } } */ // 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); } }