all repos — mgba @ bcf446737c90d95f4914b96e9085befd063721df

mGBA Game Boy Advance Emulator

src/gba/renderers/video-software.c (view raw)

  1#include "video-software.h"
  2
  3#include "gba.h"
  4#include "gba-io.h"
  5
  6#include <stdlib.h>
  7
  8static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
  9static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
 10static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
 11static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
 12static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
 13
 14static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
 15static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
 16
 17static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
 18
 19static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
 20static int _backgroundComparator(const void* a, const void* b);
 21
 22void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
 23	renderer->d.init = GBAVideoSoftwareRendererInit;
 24	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
 25	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
 26	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
 27	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
 28
 29	renderer->sortedBg[0] = &renderer->bg[0];
 30	renderer->sortedBg[1] = &renderer->bg[1];
 31	renderer->sortedBg[2] = &renderer->bg[2];
 32	renderer->sortedBg[3] = &renderer->bg[3];
 33
 34	{
 35		pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 36		renderer->mutex = mutex;
 37		pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 38		renderer->cond = cond;
 39	}
 40}
 41
 42static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
 43	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 44	int i;
 45
 46	softwareRenderer->dispcnt.packed = 0x0080;
 47
 48	for (i = 0; i < 4; ++i) {
 49		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
 50		bg->index = i;
 51		bg->enabled = 0;
 52		bg->priority = 0;
 53		bg->charBase = 0;
 54		bg->mosaic = 0;
 55		bg->multipalette = 0;
 56		bg->screenBase = 0;
 57		bg->overflow = 0;
 58		bg->size = 0;
 59		bg->x = 0;
 60		bg->y = 0;
 61		bg->refx = 0;
 62		bg->refy = 0;
 63		bg->dx = 256;
 64		bg->dmx = 0;
 65		bg->dy = 0;
 66		bg->dmy = 256;
 67		bg->sx = 0;
 68		bg->sy = 0;
 69	}
 70
 71	pthread_mutex_init(&softwareRenderer->mutex, 0);
 72	pthread_cond_init(&softwareRenderer->cond, 0);
 73}
 74
 75static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
 76	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 77
 78	pthread_mutex_destroy(&softwareRenderer->mutex);
 79	pthread_cond_destroy(&softwareRenderer->cond);
 80}
 81
 82static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 83	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 84	switch (address) {
 85	case REG_DISPCNT:
 86		value &= 0xFFFB;
 87		softwareRenderer->dispcnt.packed = value;
 88		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 89		break;
 90	case REG_BG0CNT:
 91		value &= 0xFFCF;
 92		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
 93		break;
 94	case REG_BG1CNT:
 95		value &= 0xFFCF;
 96		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
 97		break;
 98	case REG_BG2CNT:
 99		value &= 0xFFCF;
100		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
101		break;
102	case REG_BG3CNT:
103		value &= 0xFFCF;
104		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
105		break;
106	case REG_BG0HOFS:
107		value &= 0x01FF;
108		softwareRenderer->bg[0].x = value;
109		break;
110	case REG_BG0VOFS:
111		value &= 0x01FF;
112		softwareRenderer->bg[0].y = value;
113		break;
114	case REG_BG1HOFS:
115		value &= 0x01FF;
116		softwareRenderer->bg[1].x = value;
117		break;
118	case REG_BG1VOFS:
119		value &= 0x01FF;
120		softwareRenderer->bg[1].y = value;
121		break;
122	case REG_BG2HOFS:
123		value &= 0x01FF;
124		softwareRenderer->bg[2].x = value;
125		break;
126	case REG_BG2VOFS:
127		value &= 0x01FF;
128		softwareRenderer->bg[2].y = value;
129		break;
130	case REG_BG3HOFS:
131		value &= 0x01FF;
132		softwareRenderer->bg[3].x = value;
133		break;
134	case REG_BG3VOFS:
135		value &= 0x01FF;
136		softwareRenderer->bg[3].y = value;
137		break;
138	default:
139		GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
140	}
141	return value;
142}
143
144static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
145	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
146	uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
147	if (softwareRenderer->dispcnt.forcedBlank) {
148		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
149			row[x] = 0x7FFF;
150		}
151		return;
152	}
153
154	for (int i = 0; i < 4; ++i) {
155		if (softwareRenderer->sortedBg[i]->enabled) {
156			_drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
157		}
158	}
159	for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
160		for (int i = 0; i < 4; ++i) {
161			if (softwareRenderer->sortedBg[i]->enabled && softwareRenderer->sortedBg[i]->internalBuffer[x] != 0x8000) {
162				row[x] = softwareRenderer->sortedBg[i]->internalBuffer[x];
163				break;
164			}
165		}
166	}
167}
168
169static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
170	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
171
172	pthread_mutex_lock(&softwareRenderer->mutex);
173	pthread_cond_wait(&softwareRenderer->cond, &softwareRenderer->mutex);
174	pthread_mutex_unlock(&softwareRenderer->mutex);
175}
176
177static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
178	renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
179	renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
180	renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
181	renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
182}
183
184static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
185	union GBARegisterBGCNT reg = { .packed = value };
186	bg->priority = reg.priority;
187	bg->charBase = reg.charBase << 14;
188	bg->mosaic = reg.mosaic;
189	bg->multipalette = reg.multipalette;
190	bg->screenBase = reg.screenBase << 11;
191	bg->overflow = reg.overflow;
192	bg->size = reg.size;
193
194	_sortBackgrounds(renderer);
195}
196
197static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
198	int start = 0;
199	int end = VIDEO_HORIZONTAL_PIXELS;
200	int inX = start + background->x;
201	int inY = y + background->y;
202	union GBATextMapData mapData;
203
204	unsigned yBase = inY & 0xF8;
205	if (background->size & 2) {
206		yBase += inY & 0x100;
207	} else if (background->size == 3) {
208		yBase += (inY & 0x100) << 1;
209	}
210
211	unsigned xBase;
212
213	uint32_t screenBase;
214	uint32_t charBase;
215
216	for (int outX = start; outX < end; ++outX) {
217		xBase = (outX + inX) & 0xF8;
218		if (background->size & 1) {
219			xBase += ((outX + inX) & 0x100) << 5;
220		}
221		screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
222		mapData.packed = renderer->d.vram[screenBase];
223		charBase = ((background->charBase + mapData.tile << 5) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
224		uint16_t tileData = renderer->d.vram[charBase];
225		tileData >>= ((outX + inX) & 0x3) << 2;
226		if (tileData & 0xF) {
227			background->internalBuffer[outX] = renderer->d.palette[(tileData & 0xF) | (mapData.palette << 4)];
228		} else {
229			background->internalBuffer[outX] = 0x8000;
230		}
231	}
232}
233
234static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
235	qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
236}
237
238static int _backgroundComparator(const void* a, const void* b) {
239	const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
240	const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
241	if (bgA->priority != bgB->priority) {
242		return bgA->priority - bgB->priority;
243	} else {
244		return bgA->index - bgB->index;
245	}
246}