GB: Implement sprites, SRAM
@@ -57,6 +57,9 @@ mappedMemoryFree(gb->memory.wram, GB_SIZE_WORKING_RAM);
if (gb->memory.rom) { mappedMemoryFree(gb->memory.rom, gb->memory.romSize); } + if (gb->memory.sram) { + mappedMemoryFree(gb->memory.sram, 0x8000); + } } void GBMemoryReset(struct GB* gb) {@@ -67,6 +70,10 @@ gb->memory.wram = anonymousMemoryMap(GB_SIZE_WORKING_RAM);
gb->memory.wramBank = &gb->memory.wram[GB_SIZE_WORKING_RAM_BANK0]; gb->memory.romBank = &gb->memory.rom[GB_SIZE_CART_BANK0]; gb->memory.currentBank = 1; + gb->memory.sram = anonymousMemoryMap(0x8000); // TODO: Persist + gb->memory.sramCurrentBank = 0; + + memset(&gb->video.oam, 0, sizeof(gb->video.oam)); const struct GBCartridge* cart = &gb->memory.rom[0x100]; switch (cart->type) {@@ -138,8 +145,10 @@ case GB_REGION_VRAM + 1:
return gb->video.vram[address & (GB_SIZE_VRAM - 1)]; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1: - // TODO - return 0; + if (memory->sramAccess) { + return gb->memory.sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; + } + return 0xFF; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2: return memory->wram[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)];@@ -149,9 +158,14 @@ default:
if (address < GB_BASE_OAM) { return memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)]; } + if (address < GB_BASE_UNUSABLE) { + if (gb->video.mode < 2) { + return gb->video.oam.raw[address & 0xFF]; + } + return 0xFF; + } if (address < GB_BASE_IO) { - // TODO - return 0; + return 0xFF; } if (address < GB_BASE_HRAM) { return GBIORead(gb, address & (GB_SIZE_IO - 1));@@ -185,7 +199,9 @@ gb->video.renderer->writeVRAM(gb->video.renderer, address & (GB_SIZE_VRAM - 1));
return; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1: - // TODO + if (memory->sramAccess) { + gb->memory.sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; + } return; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2:@@ -197,8 +213,13 @@ return;
default: if (address < GB_BASE_OAM) { memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value; + } else if (address < GB_BASE_UNUSABLE) { + if (gb->video.mode < 2) { + gb->video.oam.raw[address & 0xFF] = value; + gb->video.renderer->writeOAM(gb->video.renderer, address & 0xFF); + } } else if (address < GB_BASE_IO) { - // TODO + // TODO: Log } else if (address < GB_BASE_HRAM) { GBIOWrite(gb, address & (GB_SIZE_IO - 1), value); } else if (address < GB_BASE_IE) {@@ -282,11 +303,29 @@ memory->romBank = &memory->rom[bankStart];
memory->currentBank = bank; } +static void _switchSramBank(struct GBMemory* memory, int bank) { + size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; + memory->sramBank = &memory->sram[bankStart]; + memory->sramCurrentBank = bank; +} + void _GBMBC1(struct GBMemory* memory, uint16_t address, uint8_t value) { int bank = value & 0x1F; switch (address >> 13) { case 0x0: - // TODO + switch (value) { + case 0: + memory->sramAccess = false; + break; + case 0xA: + memory->sramAccess = true; + _switchSramBank(memory, memory->sramCurrentBank); + break; + default: + // TODO + break; + } + break; break; case 0x1: if (!bank) {@@ -305,7 +344,18 @@ void _GBMBC3(struct GBMemory* memory, uint16_t address, uint8_t value) {
int bank = value & 0x7F; switch (address >> 13) { case 0x0: - // TODO + switch (value) { + case 0: + memory->sramAccess = false; + break; + case 0xA: + memory->sramAccess = true; + _switchSramBank(memory, memory->sramCurrentBank); + break; + default: + // TODO + break; + } break; case 0x1: if (!bank) {@@ -313,6 +363,11 @@ ++bank;
} _switchBank(memory, bank); break; + case 0x2: + if (value < 4) { + _switchSramBank(memory, value); + } + break; } }@@ -324,7 +379,19 @@ void _GBMBC5(struct GBMemory* memory, uint16_t address, uint8_t value) {
int bank = value & 0x7F; switch (address >> 13) { case 0x0: - // TODO + switch (value) { + case 0: + memory->sramAccess = false; + break; + case 0xA: + memory->sramAccess = true; + _switchSramBank(memory, memory->sramCurrentBank); + break; + default: + // TODO + break; + } + break; break; case 0x1: _switchBank(memory, bank);
@@ -20,6 +20,7 @@ GB_BASE_EXTERNAL_RAM = 0xA000,
GB_BASE_WORKING_RAM_BANK0 = 0xC000, GB_BASE_WORKING_RAM_BANK1 = 0xD000, GB_BASE_OAM = 0xFE00, + GB_BASE_UNUSABLE = 0xFEA0, GB_BASE_IO = 0xFF00, GB_BASE_HRAM = 0xFF80, GB_BASE_IE = 0xFFFF
@@ -12,6 +12,7 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer);
static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer); static void GBVideoSoftwareRendererReset(struct GBVideoRenderer* renderer); static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address); +static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam); static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); static void GBVideoSoftwareRendererDrawScanline(struct GBVideoRenderer* renderer, int y); static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);@@ -19,6 +20,9 @@ static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, unsigned stride, void* pixels); static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int x, int y, int sx, int sy); +static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int y); + +static void _cleanOAM(struct GBVideoSoftwareRenderer* renderer); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5@@ -36,6 +40,7 @@ renderer->d.reset = GBVideoSoftwareRendererReset;
renderer->d.deinit = GBVideoSoftwareRendererDeinit; renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister; renderer->d.writeVRAM = GBVideoSoftwareRendererWriteVRAM; + renderer->d.writeOAM = GBVideoSoftwareRendererWriteOAM; renderer->d.drawScanline = GBVideoSoftwareRendererDrawScanline; renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame; renderer->d.getPixels = 0;@@ -65,6 +70,8 @@ softwareRenderer->scy = 0;
softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->wx = 0; + softwareRenderer->oamMax = 0; + softwareRenderer->oamDirty = false; } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {@@ -76,6 +83,13 @@ static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; // TODO } + +static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam) { + struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; + UNUSED(oam); + softwareRenderer->oamDirty = true; +} + static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; switch (address) {@@ -119,7 +133,14 @@
static void GBVideoSoftwareRendererDrawScanline(struct GBVideoRenderer* renderer, int y) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; + size_t x; + for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { + softwareRenderer->row[x] = GB_PALETTE[0]; + } + + if (softwareRenderer->oamDirty) { + _cleanOAM(softwareRenderer); + } uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {@@ -127,15 +148,27 @@ maps += GB_SIZE_MAP;
} GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, 0, y, softwareRenderer->scx, softwareRenderer->scy); - if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc)) { + if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy < GB_VIDEO_VERTICAL_PIXELS) { maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, -7, y, softwareRenderer->wx - 7, softwareRenderer->wy); + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, 0, y, 7 - softwareRenderer->wx, -softwareRenderer->wy); } - size_t x; + int spriteHeight = 8; + if (GBRegisterLCDCIsObjSize(softwareRenderer->lcdc)) { + spriteHeight = 16; + } + int i; + for (i = 0; i < softwareRenderer->oamMax; ++i) { + // TODO: Sprite sizes + if (y >= softwareRenderer->obj[i]->y - 16 && y < softwareRenderer->obj[i]->y - 16 + spriteHeight) { + GBVideoSoftwareRendererDrawObj(softwareRenderer, softwareRenderer->obj[i], y); + } + } + + color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; #ifdef COLOR_16_BIT #if defined(__ARM_NEON) && !defined(__APPLE__) _to16Bit(row, softwareRenderer->row, GB_VIDEO_HORIZONTAL_PIXELS);@@ -149,6 +182,24 @@ memcpy(row, softwareRenderer->row, GB_VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
#endif } +static void _cleanOAM(struct GBVideoSoftwareRenderer* renderer) { + // TODO: GBC differences + renderer->oamMax = 0; + int o = 0; + int i; + for (i = 0; i < 40; ++i) { + uint8_t y = renderer->d.oam->obj[i].y; + if (y < 16 || y >= GB_VIDEO_VERTICAL_PIXELS + 16) { + continue; + } + // TODO: Sort + renderer->obj[o] = &renderer->d.oam->obj[i]; + ++o; + } + renderer->oamMax = o; + renderer->oamDirty = false; +} + static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;@@ -165,9 +216,6 @@ data += 0x1000;
} int topY = (((y + sy) >> 3) & 0x1F) * 0x20; int bottomY = (y + sy) & 7; - if (x < 0) { - x = 0; - } for (; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { int topX = ((x + sx) >> 3) & 0x1F; int bottomX = 7 - ((x + sx) & 7);@@ -184,3 +232,45 @@ tileDataLower >>= bottomX;
renderer->row[x] = renderer->bgPalette[((tileDataUpper & 1) << 1) | (tileDataLower & 1)]; } } + +static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int y) { + uint8_t* data = renderer->d.vram; + int tileOffset = 0; + int bottomY; + if (GBObjAttributesIsYFlip(obj->attr)) { + bottomY = 7 - ((y - obj->y - 16) & 7); + if (y - obj->y < -8) { + ++tileOffset; + } + } else { + bottomY = (y - obj->y - 16) & 7; + if (y - obj->y >= -8) { + ++tileOffset; + } + } + int end = GB_VIDEO_HORIZONTAL_PIXELS; + if (obj->x < end) { + end = obj->x; + } + int x = obj->x - 8; + if (x < 0) { + x = 0; + } + for (; x < end; ++x) { + int bottomX; + if (GBObjAttributesIsXFlip(obj->attr)) { + bottomX = (x - obj->x) & 7; + } else { + bottomX = 7 - ((x - obj->x) & 7); + } + int objTile = obj->tile + tileOffset; + uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2]; + uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1]; + tileDataUpper >>= bottomX; + tileDataLower >>= bottomX; + color_t current = renderer->row[x]; + if (((tileDataUpper | tileDataLower) & 1) && (!GBObjAttributesIsPriority(obj->attr) || current == GB_PALETTE[0])) { + renderer->row[x] = renderer->bgPalette[((tileDataUpper & 1) << 1) | (tileDataLower & 1)]; + } + } +}
@@ -35,6 +35,10 @@ uint8_t wy;
uint8_t wx; GBRegisterLCDC lcdc; + + struct GBObj* obj[40]; + int oamMax; + bool oamDirty; }; void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*);
@@ -15,6 +15,7 @@ static void GBVideoDummyRendererReset(struct GBVideoRenderer* renderer);
static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer); static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address); +static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam); static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y); static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer); static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);@@ -25,6 +26,7 @@ .reset = GBVideoDummyRendererReset,
.deinit = GBVideoDummyRendererDeinit, .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister, .writeVRAM = GBVideoDummyRendererWriteVRAM, + .writeOAM = GBVideoDummyRendererWriteOAM, .drawScanline = GBVideoDummyRendererDrawScanline, .finishFrame = GBVideoDummyRendererFinishFrame, .getPixels = GBVideoDummyRendererGetPixels@@ -53,6 +55,8 @@ mappedMemoryFree(video->vram, GB_SIZE_VRAM);
} video->vram = anonymousMemoryMap(GB_SIZE_VRAM); video->renderer->vram = video->vram; + memset(&video->oam, 0, sizeof(video->oam)); + video->renderer->oam = &video->oam; video->renderer->deinit(video->renderer); video->renderer->init(video->renderer);@@ -197,6 +201,12 @@
static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) { UNUSED(renderer); UNUSED(address); + // Nothing to do +} + +static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam) { + UNUSED(renderer); + UNUSED(oam); // Nothing to do }
@@ -29,6 +29,24 @@ GB_BASE_MAP = 0x1800,
GB_SIZE_MAP = 0x0400 }; +DECL_BITFIELD(GBObjAttributes, uint8_t); +DECL_BIT(GBObjAttributes, Palette, 4); +DECL_BIT(GBObjAttributes, XFlip, 5); +DECL_BIT(GBObjAttributes, YFlip, 6); +DECL_BIT(GBObjAttributes, Priority, 7); + +struct GBObj { + uint8_t y; + uint8_t x; + uint8_t tile; + GBObjAttributes attr; +}; + +union GBOAM { + struct GBObj obj[40]; + uint8_t raw[160]; +}; + struct GBVideoRenderer { void (*init)(struct GBVideoRenderer* renderer); void (*reset)(struct GBVideoRenderer* renderer);@@ -36,6 +54,7 @@ void (*deinit)(struct GBVideoRenderer* renderer);
uint8_t (*writeVideoRegister)(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); void (*writeVRAM)(struct GBVideoRenderer* renderer, uint16_t address); + void (*writeOAM)(struct GBVideoRenderer* renderer, uint8_t oam); void (*drawScanline)(struct GBVideoRenderer* renderer, int y); void (*finishFrame)(struct GBVideoRenderer* renderer);@@ -43,6 +62,7 @@ void (*getPixels)(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
void (*putPixels)(struct GBVideoRenderer* renderer, unsigned stride, void* pixels); uint8_t* vram; + union GBOAM* oam; }; DECL_BITFIELD(GBRegisterLCDC, uint8_t);@@ -79,6 +99,8 @@ int32_t nextMode;
uint8_t* vram; uint8_t* vramBank; + + union GBOAM oam; int32_t frameCounter; int frameskip;