diff --git a/src/gb/memory.c b/src/gb/memory.c index ef529828c..7045d2018 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -57,6 +57,9 @@ void GBMemoryDeinit(struct GB* gb) { 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 @@ void GBMemoryReset(struct GB* gb) { 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 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { 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 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { 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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { 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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { 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 @@ static void _switchBank(struct GBMemory* memory, int bank) { 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 @@ void _GBMBC3(struct GBMemory* memory, uint16_t address, uint8_t value) { } _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); diff --git a/src/gb/memory.h b/src/gb/memory.h index 3cb5b48b6..1863b607f 100644 --- a/src/gb/memory.h +++ b/src/gb/memory.h @@ -20,6 +20,7 @@ enum { 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 diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index bd310f9cf..dc3f339fb 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -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, u 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 @@ void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { 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 @@ static void GBVideoSoftwareRendererReset(struct GBVideoRenderer* renderer) { 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, u 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 uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* 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 @@ static void GBVideoSoftwareRendererDrawScanline(struct GBVideoRenderer* renderer } 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 @@ static void GBVideoSoftwareRendererDrawScanline(struct GBVideoRenderer* renderer #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 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer } 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 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer 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)]; + } + } +} diff --git a/src/gb/renderers/software.h b/src/gb/renderers/software.h index 0ddfbfa32..9332b4487 100644 --- a/src/gb/renderers/software.h +++ b/src/gb/renderers/software.h @@ -35,6 +35,10 @@ struct GBVideoSoftwareRenderer { uint8_t wx; GBRegisterLCDC lcdc; + + struct GBObj* obj[40]; + int oamMax; + bool oamDirty; }; void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*); diff --git a/src/gb/video.c b/src/gb/video.c index e31cd0008..bc2e47360 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -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 @@ static struct GBVideoRenderer dummyRenderer = { .deinit = GBVideoDummyRendererDeinit, .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister, .writeVRAM = GBVideoDummyRendererWriteVRAM, + .writeOAM = GBVideoDummyRendererWriteOAM, .drawScanline = GBVideoDummyRendererDrawScanline, .finishFrame = GBVideoDummyRendererFinishFrame, .getPixels = GBVideoDummyRendererGetPixels @@ -53,6 +55,8 @@ void GBVideoReset(struct GBVideo* video) { } 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); @@ -200,6 +204,12 @@ static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint // Nothing to do } +static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam) { + UNUSED(renderer); + UNUSED(oam); + // Nothing to do +} + static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y) { UNUSED(renderer); UNUSED(y); diff --git a/src/gb/video.h b/src/gb/video.h index cf9771936..48674066f 100644 --- a/src/gb/video.h +++ b/src/gb/video.h @@ -29,6 +29,24 @@ enum { 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 @@ struct GBVideoRenderer { 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 @@ struct GBVideoRenderer { void (*putPixels)(struct GBVideoRenderer* renderer, unsigned stride, void* pixels); uint8_t* vram; + union GBOAM* oam; }; DECL_BITFIELD(GBRegisterLCDC, uint8_t); @@ -80,6 +100,8 @@ struct GBVideo { uint8_t* vram; uint8_t* vramBank; + union GBOAM oam; + int32_t frameCounter; int frameskip; int frameskipCounter;