GB Video: Change to dot-based renderer

This commit is contained in:
Jeffrey Pfau 2016-01-28 21:29:13 -08:00
parent 32f0bb9f1f
commit 2290dd6781
5 changed files with 113 additions and 132 deletions

View File

@ -203,7 +203,6 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) {
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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t 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;

View File

@ -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 @@ void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) {
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 @@ 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) {
@ -79,17 +71,6 @@ static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* 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 @@ static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer*
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 @@ 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);
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 @@ static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* rende
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);
}
}

View File

@ -34,10 +34,6 @@ struct GBVideoSoftwareRenderer {
uint8_t wx;
GBRegisterLCDC lcdc;
struct GBObj* obj[40];
int oamMax;
bool oamDirty;
};
void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*);

View File

@ -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 @@ void GBVideoReset(struct GBVideo* video) {
video->eventDiff = 0;
video->nextMode = INT_MAX;
video->nextDot = INT_MAX;
video->frameCounter = 0;
video->frameskipCounter = 0;
@ -83,6 +82,17 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
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 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
++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)) {
@ -111,6 +117,9 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
}
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;
case 1:
@ -121,7 +130,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
}
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 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
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,13 +161,42 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
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) {
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) {
// TODO: Does enabling the LCD start in vblank?
@ -219,9 +260,12 @@ static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8
// 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
}

View File

@ -16,11 +16,12 @@ enum {
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 @@ struct GBVideoRenderer {
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 @@ struct GBVideo {
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;