| @@ -1,4 +1,5 @@ | |||||
| #include <stdbool.h> | #include <stdbool.h> | ||||
| #include <string.h> | |||||
| #include "f6502.h" | #include "f6502.h" | ||||
| #include "f6502_consts.h" | #include "f6502_consts.h" | ||||
| @@ -196,12 +197,14 @@ static inline bool f6502_write(nes_Memory* mem, | |||||
| if (addr >= NES_PPU_PAL_START) { | if (addr >= NES_PPU_PAL_START) { | ||||
| // Copy to render reference | // Copy to render reference | ||||
| addr &= 0x1FU; | addr &= 0x1FU; | ||||
| mem->ppu.palette[addr] = val; | |||||
| uint8_t* pal = mem->ppu.palette; | |||||
| if (0 == (addr & 0xF)) { | if (0 == (addr & 0xF)) { | ||||
| uint8_t* pal = mem->ppu.palette; | |||||
| // TODO: Just initialize this | |||||
| pal[0] = pal[4] = pal[8] = pal[12] = | pal[0] = pal[4] = pal[8] = pal[12] = | ||||
| pal[16] = pal[20] = pal[24] = | pal[16] = pal[20] = pal[24] = | ||||
| pal[28] = val; | |||||
| pal[28] = 0xFFU; | |||||
| } else { | |||||
| pal[addr] = val; | |||||
| } | } | ||||
| // Memory-mapped mirroring | // Memory-mapped mirroring | ||||
| @@ -225,6 +228,35 @@ static inline bool f6502_write(nes_Memory* mem, | |||||
| case 0x4000: | case 0x4000: | ||||
| // TODO: APU | // 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; | break; | ||||
| case 0x6000: | case 0x6000: | ||||
| @@ -34,7 +34,7 @@ typedef struct { | |||||
| SDL_Renderer* renderer; | SDL_Renderer* renderer; | ||||
| SDL_Texture* texture; | SDL_Texture* texture; | ||||
| SDL_Surface* target; | SDL_Surface* target; | ||||
| SDL_Surface* scanline; | |||||
| SDL_Surface* screen; | |||||
| } platform_data; | } platform_data; | ||||
| // TODO: Return an action enum? | // 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} | {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) { | int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { | ||||
| /* | |||||
| platform_data* plat = (platform_data*)plat_data; | platform_data* plat = (platform_data*)plat_data; | ||||
| SDL_Rect rect = { | 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); | SDL_BlitSurface(plat->scanline, NULL, plat->target, &rect); | ||||
| */ | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -112,6 +124,8 @@ int nese_frame_ready(void* plat_data) { | |||||
| t_last = t_now; | t_last = t_now; | ||||
| */ | */ | ||||
| SDL_BlitSurface(plat->screen, NULL, plat->target, NULL); | |||||
| SDL_UnlockTexture(plat->texture); | SDL_UnlockTexture(plat->texture); | ||||
| SDL_RenderCopy(plat->renderer, plat->texture, | SDL_RenderCopy(plat->renderer, plat->texture, | ||||
| NULL, NULL); | NULL, NULL); | ||||
| @@ -160,8 +174,8 @@ static int plat_init(platform_data* plat) { | |||||
| if (0 == status) { | if (0 == status) { | ||||
| plat->renderer = SDL_CreateRenderer( | plat->renderer = SDL_CreateRenderer( | ||||
| plat->window, -1, | plat->window, -1, | ||||
| SDL_RENDERER_ACCELERATED | | |||||
| SDL_RENDERER_PRESENTVSYNC | |||||
| SDL_RENDERER_ACCELERATED/* | | |||||
| SDL_RENDERER_PRESENTVSYNC*/ | |||||
| ); | ); | ||||
| if (NULL == plat->renderer) { | if (NULL == plat->renderer) { | ||||
| LOGE("SDL: Failed to create renderer"); | LOGE("SDL: Failed to create renderer"); | ||||
| @@ -186,19 +200,21 @@ static int plat_init(platform_data* plat) { | |||||
| } | } | ||||
| if (0 == status) { | 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, | 8, nes_ppu_render_w, | ||||
| SDL_PIXELFORMAT_INDEX8 | 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; | status = -1; | ||||
| } else { | } else { | ||||
| SDL_SetPaletteColors( | SDL_SetPaletteColors( | ||||
| plat->scanline->format->palette, | |||||
| plat->screen->format->palette, | |||||
| nes_palette, 0U, 64U | 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, | nes_ppu_render_line(&sys->ppu, | ||||
| &sys->core.memory.ppu); | &sys->core.memory.ppu); | ||||
| nese_line_ready( | 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 | sys->ppu.scanline - nes_ppu_visible_line | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -73,11 +77,20 @@ static int nes_hsync(nes* sys, void* plat) { | |||||
| switch (sys->ppu.scanline) { | switch (sys->ppu.scanline) { | ||||
| case nes_ppu_prerender_line: | case nes_ppu_prerender_line: | ||||
| { nes_PPU_Memory* mem = &sys->core.memory.ppu; | |||||
| f6502_set_NMI(&sys->core, 0); | 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: | case nes_ppu_postrender_line: | ||||
| status = nese_frame_ready(plat); | status = nese_frame_ready(plat); | ||||
| @@ -9,6 +9,7 @@ | |||||
| void* nese_map_file(FILE* file, int size); | void* nese_map_file(FILE* file, int size); | ||||
| int nese_unmap_file(void* addr, 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_line_ready(void*, uint8_t* buffer, int line); | ||||
| int nese_frame_ready(void*); | int nese_frame_ready(void*); | ||||
| int nese_update_input(void*, nes_Input*); | 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) { | 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)) { | if (!(mem->mask & ppu_Mask_Back)) { | ||||
| memset(ptr, 0xFFU, nes_ppu_render_w); | |||||
| /* | |||||
| // Emulate the happy part of the backdrop override quirk | // Emulate the happy part of the backdrop override quirk | ||||
| int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | ||||
| (mem->addr & 0x1FU) : 0; | (mem->addr & 0x1FU) : 0; | ||||
| // Don't use the rendering palette (masked transparency) | // Don't use the rendering palette (masked transparency) | ||||
| memset(ptr, mem->pal_bank[0x300 + pal_idx], | memset(ptr, mem->pal_bank[0x300 + pal_idx], | ||||
| nes_ppu_render_w); | nes_ppu_render_w); | ||||
| */ | |||||
| } else { | } else { | ||||
| int back_bank = nes_PPU_Nametable_Bank_Index + | int back_bank = nes_PPU_Nametable_Bank_Index + | ||||
| ((mem->addr >> 10) & 3); | ((mem->addr >> 10) & 3); | ||||
| @@ -187,11 +193,133 @@ void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { | |||||
| // TODO: Mapper ppu_bus | // TODO: Mapper ppu_bus | ||||
| if (!(mem->mask & ppu_Mask_Left_Back)) { | 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)) { | if (mem->mask & (ppu_Mask_Sprite | ppu_Mask_Back)) { | ||||
| uint16_t mask = 0b10000011111; | uint16_t mask = 0b10000011111; | ||||
| mem->addr = (mem->addr & ~mask) | (mem->t & mask); | mem->addr = (mem->addr & ~mask) | (mem->t & mask); | ||||
| @@ -74,10 +74,12 @@ typedef enum { | |||||
| #define NES_CHR_ROM_PAGE_SIZE (0x0400U) | #define NES_CHR_ROM_PAGE_SIZE (0x0400U) | ||||
| #define NES_VRAM_PAGE_SIZE (0x0400U) | #define NES_VRAM_PAGE_SIZE (0x0400U) | ||||
| #define NES_PPU_SPRITE_COUNT (64U) | |||||
| #define NES_PPU_CHR_SIZE (0x2000U) | #define NES_PPU_CHR_SIZE (0x2000U) | ||||
| #define NES_PPU_VRAM_SIZE (0x1000U) | #define NES_PPU_VRAM_SIZE (0x1000U) | ||||
| #define NES_PPU_RAM_SIZE (0x4000U) | #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) | #define NES_PPU_PAL_START (0x3F00U) | ||||
| typedef struct { | typedef struct { | ||||
| @@ -131,7 +133,7 @@ void nes_ppu_set_mirroring(nes_PPU_Memory* mem, | |||||
| typedef struct { | typedef struct { | ||||
| int scanline; | int scanline; | ||||
| int hit_line; | 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; | } nes_PPU; | ||||
| void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*); | void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*); | ||||