GB Video: Change to dot-based renderer
@@ -203,7 +203,6 @@ case GB_REGION_VRAM:
case GB_REGION_VRAM + 1: // TODO: Block access in wrong modes gb->video.vram[address & (GB_SIZE_VRAM - 1)] = value; - gb->video.renderer->writeVRAM(gb->video.renderer, address & (GB_SIZE_VRAM - 1)); return; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1:@@ -224,7 +223,6 @@ 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) { mLOG(GB_MEM, GAME_ERROR, "Attempt to write to unusable memory: %04X:%02X", address, value);@@ -268,7 +266,6 @@ void _GBMemoryDMAService(struct GB* gb) {
uint8_t b = GBLoad8(gb->cpu, gb->memory.dmaSource); // TODO: Can DMA write OAM during modes 2-3? gb->video.oam.raw[gb->memory.dmaDest] = b; - gb->video.renderer->writeOAM(gb->video.renderer, gb->memory.dmaDest); ++gb->memory.dmaSource; ++gb->memory.dmaDest; --gb->memory.dmaRemaining;
@@ -11,18 +11,14 @@
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 GBVideoSoftwareRendererDrawDot(struct GBVideoRenderer* renderer, int x, int y, struct GBObj** obj, size_t oamMax); static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer); 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); +static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int x, int y); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5@@ -39,9 +35,7 @@ renderer->d.init = GBVideoSoftwareRendererInit;
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.drawDot = GBVideoSoftwareRendererDrawDot; renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame; renderer->d.getPixels = 0; renderer->d.putPixels = 0;@@ -70,8 +64,6 @@ softwareRenderer->scy = 0;
softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->wx = 0; - softwareRenderer->oamMax = 0; - softwareRenderer->oamDirty = false; } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {@@ -79,17 +71,6 @@ struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
UNUSED(softwareRenderer); } -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) {@@ -130,70 +111,37 @@ }
return value; } -static void GBVideoSoftwareRendererDrawScanline(struct GBVideoRenderer* renderer, int y) { +static void GBVideoSoftwareRendererDrawDot(struct GBVideoRenderer* renderer, int x, int y, struct GBObj** obj, size_t oamMax) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - - if (softwareRenderer->oamDirty) { - _cleanOAM(softwareRenderer); - } - uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } - size_t x; if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc)) { - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, 0, y, softwareRenderer->scx, softwareRenderer->scy); + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, x, y, softwareRenderer->scx, softwareRenderer->scy); if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y) { maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, y, 7 - softwareRenderer->wx, -softwareRenderer->wy); + if (x >= softwareRenderer->wx - 7) { + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, x, y, 7 - softwareRenderer->wx, -softwareRenderer->wy); + } } } else { - for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { - softwareRenderer->row[x] = 0; - } + softwareRenderer->row[x] = 0; } if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) { - 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); - } + size_t i; + for (i = 0; i < oamMax; ++i) { + GBVideoSoftwareRendererDrawObj(softwareRenderer, obj[i], x, y); } } color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; - for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { - row[x] = softwareRenderer->palette[softwareRenderer->row[x]]; - } -} - -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 == 0 || y >= GB_VIDEO_VERTICAL_PIXELS + 16) { - continue; - } - // TODO: Sort - renderer->obj[o] = &renderer->d.oam->obj[i]; - ++o; - } - renderer->oamMax = o; - renderer->oamDirty = false; + row[x] = softwareRenderer->palette[softwareRenderer->row[x]]; } static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {@@ -212,27 +160,22 @@ 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); - int bgTile; - if (GBRegisterLCDCIsTileData(renderer->lcdc)) { - bgTile = maps[topX + topY]; - } else { - bgTile = ((int8_t*) maps)[topX + topY]; - } - uint8_t tileDataLower = data[(bgTile * 8 + bottomY) * 2]; - uint8_t tileDataUpper = data[(bgTile * 8 + bottomY) * 2 + 1]; - tileDataUpper >>= bottomX; - tileDataLower >>= bottomX; - renderer->row[x] = ((tileDataUpper & 1) << 1) | (tileDataLower & 1); + int topX = ((x + sx) >> 3) & 0x1F; + int bottomX = 7 - ((x + sx) & 7); + int bgTile; + if (GBRegisterLCDCIsTileData(renderer->lcdc)) { + bgTile = maps[topX + topY]; + } else { + bgTile = ((int8_t*) maps)[topX + topY]; } + uint8_t tileDataLower = data[(bgTile * 8 + bottomY) * 2]; + uint8_t tileDataUpper = data[(bgTile * 8 + bottomY) * 2 + 1]; + tileDataUpper >>= bottomX; + tileDataLower >>= bottomX; + renderer->row[x] = ((tileDataUpper & 1) << 1) | (tileDataLower & 1); } -static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int y) { +static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int x, int y) { uint8_t* data = renderer->d.vram; int tileOffset = 0; int bottomY;@@ -251,27 +194,25 @@ int end = GB_VIDEO_HORIZONTAL_PIXELS;
if (obj->x < end) { end = obj->x; } - int x = obj->x - 8; - if (x < 0) { - x = 0; + int ix = obj->x - 8; + if (x < ix || x >= ix + 8) { + return; } uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x20; int p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4; - 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) && current <= mask) { - renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); - } + 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) && current <= mask) { + renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1); } }
@@ -34,10 +34,6 @@ uint8_t wy;
uint8_t wx; GBRegisterLCDC lcdc; - - struct GBObj* obj[40]; - int oamMax; - bool oamDirty; }; void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*);
@@ -14,20 +14,18 @@ static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer);
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 GBVideoDummyRendererDrawDot(struct GBVideoRenderer* renderer, int x, int y, struct GBObj** obj, size_t oamMax); static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer); static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels); +static void _cleanOAM(struct GBVideo* video, int y); + static struct GBVideoRenderer dummyRenderer = { .init = GBVideoDummyRendererInit, .reset = GBVideoDummyRendererReset, .deinit = GBVideoDummyRendererDeinit, .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister, - .writeVRAM = GBVideoDummyRendererWriteVRAM, - .writeOAM = GBVideoDummyRendererWriteOAM, - .drawScanline = GBVideoDummyRendererDrawScanline, + .drawDot = GBVideoDummyRendererDrawDot, .finishFrame = GBVideoDummyRendererFinishFrame, .getPixels = GBVideoDummyRendererGetPixels };@@ -47,6 +45,7 @@ video->nextEvent = INT_MAX;
video->eventDiff = 0; video->nextMode = INT_MAX; + video->nextDot = INT_MAX; video->frameCounter = 0; video->frameskipCounter = 0;@@ -83,6 +82,17 @@ }
if (video->nextEvent <= 0) { if (video->nextEvent != INT_MAX) { video->nextMode -= video->eventDiff; + video->nextDot -= video->eventDiff; + } + video->nextEvent = INT_MAX; + if (video->nextDot <= 0) { + video->renderer->drawDot(video->renderer, video->x, video->ly, video->objThisLine, video->objMax); + ++video->x; + if (video->x < GB_VIDEO_HORIZONTAL_PIXELS) { + video->nextDot = 1; + } else { + video->nextDot = INT_MAX; + } } if (video->nextMode <= 0) { int lyc = video->p->memory.io[REG_LYC];@@ -91,11 +101,7 @@ case 0:
++video->ly; video->p->memory.io[REG_LY] = video->ly; video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly); - if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - } if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { - video->renderer->drawScanline(video->renderer, video->ly); video->nextMode = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) {@@ -110,6 +116,9 @@ if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) {
video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); + } + if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } GBUpdateIRQs(video->p); break;@@ -121,7 +130,6 @@ video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
} if (video->ly >= GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->ly = 0; - video->renderer->drawScanline(video->renderer, video->ly); video->nextMode = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) {@@ -134,11 +142,15 @@ }
video->p->memory.io[REG_LY] = video->ly; break; case 2: - video->nextMode = GB_VIDEO_MODE_3_LENGTH; + _cleanOAM(video, video->ly); + video->nextDot = 1; + video->nextEvent = video->nextDot; + video->x = 0; + video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 8; video->mode = 3; break; case 3: - video->nextMode = GB_VIDEO_MODE_0_LENGTH; + video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 8; video->mode = 0; if (GBRegisterSTATIsHblankIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);@@ -149,11 +161,40 @@ }
video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->p->memory.io[REG_STAT] = video->stat; } - - video->nextEvent = video->nextMode; + if (video->nextDot < video->nextEvent) { + video->nextEvent = video->nextDot; + } + if (video->nextMode < video->nextEvent) { + video->nextEvent = video->nextMode; + } video->eventDiff = 0; } return video->nextEvent; +} + +static void _cleanOAM(struct GBVideo* video, int y) { + // TODO: GBC differences + // TODO: Optimize + video->objMax = 0; + int spriteHeight = 8; + if (GBRegisterLCDCIsObjSize(video->p->memory.io[REG_LCDC])) { + spriteHeight = 16; + } + int o = 0; + int i; + for (i = 0; i < 40; ++i) { + uint8_t oy = video->oam.obj[i].y; + if (y < oy - 16 || y >= oy - 16 + spriteHeight) { + continue; + } + // TODO: Sort + video->objThisLine[o] = &video->oam.obj[i]; + ++o; + if (o == 10) { + break; + } + } + video->objMax = o; } void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {@@ -219,9 +260,12 @@ UNUSED(oam);
// Nothing to do } -static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y) { +static void GBVideoDummyRendererDrawDot(struct GBVideoRenderer* renderer, int x, int y, struct GBObj** obj, size_t oamMax) { UNUSED(renderer); + UNUSED(x); UNUSED(y); + UNUSED(obj); + UNUSED(oamMax); // Nothing to do }
@@ -16,11 +16,12 @@ GB_VIDEO_VERTICAL_PIXELS = 144,
GB_VIDEO_VBLANK_PIXELS = 10, GB_VIDEO_VERTICAL_TOTAL_PIXELS = GB_VIDEO_VERTICAL_PIXELS + GB_VIDEO_VBLANK_PIXELS, - GB_VIDEO_MODE_0_LENGTH = 203, // Estimates, figure out with more precision - GB_VIDEO_MODE_2_LENGTH = 81, - GB_VIDEO_MODE_3_LENGTH = 172, + // TODO: Figure out exact lengths + GB_VIDEO_MODE_2_LENGTH = 84, + GB_VIDEO_MODE_3_LENGTH_BASE = 168, + GB_VIDEO_MODE_0_LENGTH_BASE = 204, - GB_VIDEO_HORIZONTAL_LENGTH = GB_VIDEO_MODE_0_LENGTH + GB_VIDEO_MODE_2_LENGTH + GB_VIDEO_MODE_3_LENGTH, + GB_VIDEO_HORIZONTAL_LENGTH = GB_VIDEO_MODE_0_LENGTH_BASE + GB_VIDEO_MODE_2_LENGTH + GB_VIDEO_MODE_3_LENGTH_BASE, GB_VIDEO_MODE_1_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VBLANK_PIXELS, GB_VIDEO_TOTAL_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VERTICAL_TOTAL_PIXELS,@@ -53,9 +54,7 @@ void (*reset)(struct GBVideoRenderer* renderer);
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 (*drawDot)(struct GBVideoRenderer* renderer, int x, int y, struct GBObj** objOnLine, size_t nObj); void (*finishFrame)(struct GBVideoRenderer* renderer); void (*getPixels)(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);@@ -87,6 +86,7 @@ struct GBVideo {
struct GB* p; struct GBVideoRenderer* renderer; + int x; int ly; GBRegisterSTAT stat;@@ -96,11 +96,14 @@ int32_t nextEvent;
int32_t eventDiff; int32_t nextMode; + int32_t nextDot; uint8_t* vram; uint8_t* vramBank; union GBOAM oam; + struct GBObj* objThisLine[10]; + int objMax; int32_t frameCounter; int frameskip;