| @@ -1,4 +1,5 @@ | |||
| #include <stdbool.h> | |||
| #include <string.h> | |||
| #include "f6502.h" | |||
| #include "f6502_consts.h" | |||
| @@ -196,12 +197,14 @@ static inline bool f6502_write(nes_Memory* mem, | |||
| if (addr >= NES_PPU_PAL_START) { | |||
| // Copy to render reference | |||
| addr &= 0x1FU; | |||
| mem->ppu.palette[addr] = val; | |||
| uint8_t* pal = mem->ppu.palette; | |||
| if (0 == (addr & 0xF)) { | |||
| uint8_t* pal = mem->ppu.palette; | |||
| // TODO: Just initialize this | |||
| pal[0] = pal[4] = pal[8] = pal[12] = | |||
| pal[16] = pal[20] = pal[24] = | |||
| pal[28] = val; | |||
| pal[28] = 0xFFU; | |||
| } else { | |||
| pal[addr] = val; | |||
| } | |||
| // Memory-mapped mirroring | |||
| @@ -225,6 +228,35 @@ static inline bool f6502_write(nes_Memory* mem, | |||
| case 0x4000: | |||
| // TODO: APU | |||
| switch (addr & 0x1FU) { | |||
| case 0x14: | |||
| { uint8_t* src = NULL; | |||
| // OAM DMA | |||
| switch (val >> 5) { | |||
| case 0: // 0x0000 - 0x1FFF RAM | |||
| src = &mem->ram[((int)val << 8) & 0x7FFU]; | |||
| break; | |||
| case 3: // 0x6000 - 0x7FFF SRAM | |||
| if (mem->sram_bank) { | |||
| src = &mem->sram_bank[((int)val << 8) & 0x1FFFU]; | |||
| } | |||
| break; | |||
| case 4: // 0x8000 - 0x9FFF ROM | |||
| case 5: // 0xA000 - 0xBFFF ROM | |||
| case 6: // 0xC000 - 0xDFFF ROM | |||
| case 7: // 0xE000 - 0xFFFF ROM | |||
| src = &mem->rom_bank[(val >> 5) & 3][((int)val << 8) & 0x1FFFU]; | |||
| break; | |||
| } | |||
| if (NULL != src) { | |||
| memcpy(mem->ppu.oam, src, NES_PPU_OAM_SIZE); | |||
| } | |||
| // TODO: Spend 513 cycles | |||
| } | |||
| break; | |||
| } | |||
| break; | |||
| case 0x6000: | |||
| @@ -34,7 +34,7 @@ typedef struct { | |||
| SDL_Renderer* renderer; | |||
| SDL_Texture* texture; | |||
| SDL_Surface* target; | |||
| SDL_Surface* scanline; | |||
| SDL_Surface* screen; | |||
| } platform_data; | |||
| // TODO: Return an action enum? | |||
| @@ -76,7 +76,18 @@ static SDL_Color nes_palette[64] = { | |||
| {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11} | |||
| }; | |||
| int nese_frame_start(void* plat_data, uint8_t background) { | |||
| platform_data* plat = (platform_data*)plat_data; | |||
| SDL_Color ext = nes_palette[background]; | |||
| SDL_FillRect(plat->target, NULL, ((int)ext.r << 16) | | |||
| ((int)ext.g << 8) | ext.b); | |||
| return 0; | |||
| } | |||
| int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { | |||
| /* | |||
| platform_data* plat = (platform_data*)plat_data; | |||
| SDL_Rect rect = { | |||
| @@ -87,6 +98,7 @@ int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { | |||
| }; | |||
| SDL_BlitSurface(plat->scanline, NULL, plat->target, &rect); | |||
| */ | |||
| return 0; | |||
| } | |||
| @@ -112,6 +124,8 @@ int nese_frame_ready(void* plat_data) { | |||
| t_last = t_now; | |||
| */ | |||
| SDL_BlitSurface(plat->screen, NULL, plat->target, NULL); | |||
| SDL_UnlockTexture(plat->texture); | |||
| SDL_RenderCopy(plat->renderer, plat->texture, | |||
| NULL, NULL); | |||
| @@ -160,8 +174,8 @@ static int plat_init(platform_data* plat) { | |||
| if (0 == status) { | |||
| plat->renderer = SDL_CreateRenderer( | |||
| plat->window, -1, | |||
| SDL_RENDERER_ACCELERATED | | |||
| SDL_RENDERER_PRESENTVSYNC | |||
| SDL_RENDERER_ACCELERATED/* | | |||
| SDL_RENDERER_PRESENTVSYNC*/ | |||
| ); | |||
| if (NULL == plat->renderer) { | |||
| LOGE("SDL: Failed to create renderer"); | |||
| @@ -186,19 +200,21 @@ static int plat_init(platform_data* plat) { | |||
| } | |||
| if (0 == status) { | |||
| plat->scanline = SDL_CreateRGBSurfaceWithFormatFrom( | |||
| plat->sys->ppu.line_data, nes_ppu_render_w, 1, | |||
| plat->screen = SDL_CreateRGBSurfaceWithFormatFrom( | |||
| plat->sys->ppu.screen_data, | |||
| nes_ppu_render_w, nes_ppu_render_h, | |||
| 8, nes_ppu_render_w, | |||
| SDL_PIXELFORMAT_INDEX8 | |||
| ); | |||
| if (NULL == plat->scanline) { | |||
| LOGE("SDL: Failed to create scanline"); | |||
| if (NULL == plat->screen) { | |||
| LOGE("SDL: Failed to create screen"); | |||
| status = -1; | |||
| } else { | |||
| SDL_SetPaletteColors( | |||
| plat->scanline->format->palette, | |||
| plat->screen->format->palette, | |||
| nes_palette, 0U, 64U | |||
| ); | |||
| SDL_SetColorKey(plat->screen, SDL_TRUE, 0xFFU); | |||
| } | |||
| } | |||
| @@ -60,7 +60,11 @@ static int nes_hsync(nes* sys, void* plat) { | |||
| nes_ppu_render_line(&sys->ppu, | |||
| &sys->core.memory.ppu); | |||
| nese_line_ready( | |||
| plat, sys->ppu.line_data, | |||
| plat, | |||
| sys->ppu.screen_data + | |||
| ( nes_ppu_render_w * | |||
| ( sys->ppu.scanline - | |||
| nes_ppu_visible_line)), | |||
| sys->ppu.scanline - nes_ppu_visible_line | |||
| ); | |||
| } | |||
| @@ -73,11 +77,20 @@ static int nes_hsync(nes* sys, void* plat) { | |||
| switch (sys->ppu.scanline) { | |||
| case nes_ppu_prerender_line: | |||
| { nes_PPU_Memory* mem = &sys->core.memory.ppu; | |||
| f6502_set_NMI(&sys->core, 0); | |||
| sys->core.memory.ppu.status &= ~( ppu_Status_VBlank | | |||
| ppu_Status_Hit); | |||
| nes_ppu_find_hit_line(&sys->ppu, &sys->core.memory.ppu); | |||
| break; | |||
| mem->status &= ~(ppu_Status_VBlank | ppu_Status_Hit); | |||
| nes_ppu_find_hit_line(&sys->ppu, mem); | |||
| // 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) | |||
| status = nese_frame_start(plat, mem->pal_bank[0x300 + pal_idx]); | |||
| } break; | |||
| case nes_ppu_postrender_line: | |||
| status = nese_frame_ready(plat); | |||
| @@ -9,6 +9,7 @@ | |||
| void* nese_map_file(FILE* file, int size); | |||
| int nese_unmap_file(void* addr, int size); | |||
| int nese_frame_start(void*, uint8_t background); | |||
| int nese_line_ready(void*, uint8_t* buffer, int line); | |||
| int nese_frame_ready(void*); | |||
| int nese_update_input(void*, nes_Input*); | |||
| @@ -58,15 +58,21 @@ void nes_ppu_find_hit_line(nes_PPU* ppu, nes_PPU_Memory* mem) { | |||
| void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { | |||
| uint8_t* ptr = ppu->line_data; | |||
| 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); | |||
| @@ -187,11 +193,133 @@ void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { | |||
| // TODO: Mapper ppu_bus | |||
| if (!(mem->mask & ppu_Mask_Left_Back)) { | |||
| memset(ppu->line_data, mem->palette[0], 8); | |||
| memset(scanline_ptr, 0xFFU, 8); | |||
| } | |||
| // TODO: Draw Sprites | |||
| // 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); | |||
| @@ -74,10 +74,12 @@ typedef enum { | |||
| #define NES_CHR_ROM_PAGE_SIZE (0x0400U) | |||
| #define NES_VRAM_PAGE_SIZE (0x0400U) | |||
| #define NES_PPU_SPRITE_COUNT (64U) | |||
| #define NES_PPU_CHR_SIZE (0x2000U) | |||
| #define NES_PPU_VRAM_SIZE (0x1000U) | |||
| #define NES_PPU_RAM_SIZE (0x4000U) | |||
| #define NES_PPU_OAM_SIZE (64U * 4U) | |||
| #define NES_PPU_OAM_SIZE (NES_PPU_SPRITE_COUNT * 4U) | |||
| #define NES_PPU_PAL_START (0x3F00U) | |||
| typedef struct { | |||
| @@ -131,7 +133,7 @@ void nes_ppu_set_mirroring(nes_PPU_Memory* mem, | |||
| typedef struct { | |||
| int scanline; | |||
| int hit_line; | |||
| uint8_t line_data[nes_ppu_render_w]; | |||
| uint8_t screen_data[nes_ppu_render_w * nes_ppu_render_h]; | |||
| } nes_PPU; | |||
| void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*); | |||