all repos — mgba @ 5122a236e0cb6ed73a274584b94d0639bf0157f3

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);
 17static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
 18
 19static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags);
 20static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
 21
 22static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
 23static inline uint16_t _brighten(uint16_t color, int y);
 24static inline uint16_t _darken(uint16_t color, int y);
 25static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB);
 26
 27static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
 28static int _backgroundComparator(const void* a, const void* b);
 29
 30void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
 31	renderer->d.init = GBAVideoSoftwareRendererInit;
 32	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
 33	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
 34	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
 35	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
 36
 37	renderer->d.turbo = 0;
 38	renderer->d.framesPending = 0;
 39
 40	renderer->sortedBg[0] = &renderer->bg[0];
 41	renderer->sortedBg[1] = &renderer->bg[1];
 42	renderer->sortedBg[2] = &renderer->bg[2];
 43	renderer->sortedBg[3] = &renderer->bg[3];
 44
 45	{
 46		pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 47		renderer->mutex = mutex;
 48		pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 49		renderer->cond = cond;
 50	}
 51}
 52
 53static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
 54	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 55	int i;
 56
 57	softwareRenderer->dispcnt.packed = 0x0080;
 58
 59	softwareRenderer->target1Obj = 0;
 60	softwareRenderer->target1Bd = 0;
 61	softwareRenderer->target2Obj = 0;
 62	softwareRenderer->target2Bd = 0;
 63	softwareRenderer->blendEffect = BLEND_NONE;
 64	memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
 65
 66	softwareRenderer->blda = 0;
 67	softwareRenderer->bldb = 0;
 68	softwareRenderer->bldy = 0;
 69
 70	for (i = 0; i < 4; ++i) {
 71		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
 72		bg->index = i;
 73		bg->enabled = 0;
 74		bg->priority = 0;
 75		bg->charBase = 0;
 76		bg->mosaic = 0;
 77		bg->multipalette = 0;
 78		bg->screenBase = 0;
 79		bg->overflow = 0;
 80		bg->size = 0;
 81		bg->target1 = 0;
 82		bg->target2 = 0;
 83		bg->x = 0;
 84		bg->y = 0;
 85		bg->refx = 0;
 86		bg->refy = 0;
 87		bg->dx = 256;
 88		bg->dmx = 0;
 89		bg->dy = 0;
 90		bg->dmy = 256;
 91		bg->sx = 0;
 92		bg->sy = 0;
 93	}
 94
 95	pthread_mutex_init(&softwareRenderer->mutex, 0);
 96	pthread_cond_init(&softwareRenderer->cond, 0);
 97}
 98
 99static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
100	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
101
102	pthread_mutex_destroy(&softwareRenderer->mutex);
103	pthread_cond_destroy(&softwareRenderer->cond);
104}
105
106static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
107	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
108	switch (address) {
109	case REG_DISPCNT:
110		value &= 0xFFFB;
111		softwareRenderer->dispcnt.packed = value;
112		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
113		break;
114	case REG_BG0CNT:
115		value &= 0xFFCF;
116		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
117		break;
118	case REG_BG1CNT:
119		value &= 0xFFCF;
120		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
121		break;
122	case REG_BG2CNT:
123		value &= 0xFFCF;
124		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
125		break;
126	case REG_BG3CNT:
127		value &= 0xFFCF;
128		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
129		break;
130	case REG_BG0HOFS:
131		value &= 0x01FF;
132		softwareRenderer->bg[0].x = value;
133		break;
134	case REG_BG0VOFS:
135		value &= 0x01FF;
136		softwareRenderer->bg[0].y = value;
137		break;
138	case REG_BG1HOFS:
139		value &= 0x01FF;
140		softwareRenderer->bg[1].x = value;
141		break;
142	case REG_BG1VOFS:
143		value &= 0x01FF;
144		softwareRenderer->bg[1].y = value;
145		break;
146	case REG_BG2HOFS:
147		value &= 0x01FF;
148		softwareRenderer->bg[2].x = value;
149		break;
150	case REG_BG2VOFS:
151		value &= 0x01FF;
152		softwareRenderer->bg[2].y = value;
153		break;
154	case REG_BG3HOFS:
155		value &= 0x01FF;
156		softwareRenderer->bg[3].x = value;
157		break;
158	case REG_BG3VOFS:
159		value &= 0x01FF;
160		softwareRenderer->bg[3].y = value;
161		break;
162	case REG_BLDCNT:
163		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
164		break;
165	case REG_BLDALPHA:
166		softwareRenderer->blda = value & 0x1F;
167		if (softwareRenderer->blda > 0x10) {
168			softwareRenderer->blda = 0x10;
169		}
170		softwareRenderer->bldb = (value >> 8) & 0x1F;
171		if (softwareRenderer->bldb > 0x10) {
172			softwareRenderer->bldb = 0x10;
173		}
174		break;
175	case REG_BLDY:
176		softwareRenderer->bldy = value & 0x1F;
177		if (softwareRenderer->bldy > 0x10) {
178			softwareRenderer->bldy = 0x10;
179		}
180		_updatePalettes(softwareRenderer);
181		break;
182	default:
183		GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
184	}
185	return value;
186}
187
188static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
189	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
190	uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
191	if (softwareRenderer->dispcnt.forcedBlank) {
192		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
193			row[x] = 0x7FFF;
194		}
195		return;
196	}
197
198	memset(softwareRenderer->flags, 0, sizeof(softwareRenderer->flags));
199	softwareRenderer->row = row;
200
201	for (int i = 0; i < 4; ++i) {
202		if (softwareRenderer->sortedBg[i]->enabled) {
203			_drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
204		}
205	}
206}
207
208static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
209	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
210
211	pthread_mutex_lock(&softwareRenderer->mutex);
212	renderer->framesPending++;
213	if (!renderer->turbo) {
214		pthread_cond_wait(&softwareRenderer->cond, &softwareRenderer->mutex);
215	}
216	pthread_mutex_unlock(&softwareRenderer->mutex);
217}
218
219static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
220	renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
221	renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
222	renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
223	renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
224}
225
226static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
227	union GBARegisterBGCNT reg = { .packed = value };
228	bg->priority = reg.priority;
229	bg->charBase = reg.charBase << 14;
230	bg->mosaic = reg.mosaic;
231	bg->multipalette = reg.multipalette;
232	bg->screenBase = reg.screenBase << 11;
233	bg->overflow = reg.overflow;
234	bg->size = reg.size;
235
236	_sortBackgrounds(renderer);
237}
238
239static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
240	union {
241		struct {
242			unsigned target1Bg0 : 1;
243			unsigned target1Bg1 : 1;
244			unsigned target1Bg2 : 1;
245			unsigned target1Bg3 : 1;
246			unsigned target1Obj : 1;
247			unsigned target1Bd : 1;
248			enum BlendEffect effect : 2;
249			unsigned target2Bg0 : 1;
250			unsigned target2Bg1 : 1;
251			unsigned target2Bg2 : 1;
252			unsigned target2Bg3 : 1;
253			unsigned target2Obj : 1;
254			unsigned target2Bd : 1;
255		};
256		uint16_t packed;
257	} bldcnt = { .packed = value };
258
259	enum BlendEffect oldEffect = renderer->blendEffect;
260
261	renderer->bg[0].target1 = bldcnt.target1Bg0;
262	renderer->bg[1].target1 = bldcnt.target1Bg1;
263	renderer->bg[2].target1 = bldcnt.target1Bg2;
264	renderer->bg[3].target1 = bldcnt.target1Bg3;
265	renderer->bg[0].target2 = bldcnt.target2Bg0;
266	renderer->bg[1].target2 = bldcnt.target2Bg1;
267	renderer->bg[2].target2 = bldcnt.target2Bg2;
268	renderer->bg[3].target2 = bldcnt.target2Bg3;
269
270	renderer->blendEffect = bldcnt.effect;
271	renderer->target1Obj = bldcnt.target1Obj;
272	renderer->target1Bd = bldcnt.target1Bd;
273	renderer->target2Obj = bldcnt.target2Obj;
274	renderer->target2Bd = bldcnt.target2Bd;
275
276	if (oldEffect != renderer->blendEffect) {
277		_updatePalettes(renderer);
278	}
279}
280
281static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags) {
282	if (renderer->blendEffect == BLEND_NONE || (!flags.target1 && !flags.target2)) {
283		renderer->row[offset] = renderer->d.palette[entry];
284		renderer->flags[offset].finalized = 1;
285	} else if (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN) {
286		renderer->row[offset] = renderer->variantPalette[entry];
287		renderer->flags[offset].finalized = 1;
288	} else if (renderer->blendEffect == BLEND_ALPHA) {
289		if (renderer->flags[offset].written) {
290			if (renderer->flags[offset].target1 && flags.target2) {
291				renderer->row[offset] = _mix(renderer->bldb, renderer->d.palette[entry], renderer->blda, renderer->row[offset]);
292				renderer->flags[offset].finalized = 1;
293			} else {
294				renderer->flags[offset].finalized = 1;
295			}
296		} else {
297			renderer->row[offset] = renderer->d.palette[entry];
298			renderer->flags[offset].target1 = flags.target1;
299		}
300	}
301	renderer->flags[offset].written = 1;
302}
303
304static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
305	int start = 0;
306	int end = VIDEO_HORIZONTAL_PIXELS;
307	int inX = start + background->x;
308	int inY = y + background->y;
309	union GBATextMapData mapData;
310
311	unsigned yBase = inY & 0xF8;
312	if (background->size & 2) {
313		yBase += inY & 0x100;
314	} else if (background->size == 3) {
315		yBase += (inY & 0x100) << 1;
316	}
317
318	unsigned xBase;
319
320	uint32_t screenBase;
321	uint32_t charBase;
322
323	for (int outX = start; outX < end; ++outX) {
324		if (renderer->flags[outX].finalized) {
325			continue;
326		}
327		xBase = (outX + inX) & 0xF8;
328		if (background->size & 1) {
329			xBase += ((outX + inX) & 0x100) << 5;
330		}
331		screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
332		mapData.packed = renderer->d.vram[screenBase];
333		charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
334		uint16_t tileData = renderer->d.vram[charBase];
335		tileData >>= ((outX + inX) & 0x3) << 2;
336		if (tileData & 0xF) {
337			struct PixelFlags flags = {
338				.finalized = 1,
339				.target1 = background->target1,
340				.target2 = background->target2
341			};
342			_composite(renderer, outX, (tileData & 0xF) | (mapData.palette << 4), flags);
343		}
344	}
345}
346
347static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
348	if (renderer->blendEffect == BLEND_BRIGHTEN) {
349		for (int i = 0; i < 512; ++i) {
350			renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
351		}
352	} else if (renderer->blendEffect == BLEND_DARKEN) {
353		for (int i = 0; i < 512; ++i) {
354			renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
355		}
356	}
357}
358
359static inline uint16_t _brighten(uint16_t c, int y) {
360	union GBAColor color = { .packed = c };
361	color.r = color.r + ((31 - color.r) * y) / 16;
362	color.g = color.g + ((31 - color.g) * y) / 16;
363	color.b = color.b + ((31 - color.b) * y) / 16;
364	return color.packed;
365}
366
367static inline uint16_t _darken(uint16_t c, int y) {
368	union GBAColor color = { .packed = c };
369	color.r = color.r - (color.r * y) / 16;
370	color.g = color.g - (color.g * y) / 16;
371	color.b = color.b - (color.b * y) / 16;
372	return color.packed;
373}
374
375static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
376	union GBAColor ca = { .packed = colorA };
377	union GBAColor cb = { .packed = colorB };
378
379	int r = (ca.r * weightA + cb.r * weightB) / 16;
380	ca.r = r > 0x1F ? 0x1F : r;
381
382	int g = (ca.g * weightA + cb.g * weightB) / 16;
383	ca.g = g > 0x1F ? 0x1F : g;
384
385	int b = (ca.b * weightA + cb.b * weightB) / 16;
386	ca.b = b > 0x1F ? 0x1F : b;
387
388	return ca.packed;
389}
390
391static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
392	qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
393}
394
395static int _backgroundComparator(const void* a, const void* b) {
396	const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
397	const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
398	if (bgA->priority != bgB->priority) {
399		return bgA->priority - bgB->priority;
400	} else {
401		return bgA->index - bgB->index;
402	}
403}