all repos — mgba @ 754725e1244bbf6c4aa8d54c0e4f7c0370f921ad

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