all repos — mgba @ cea8109c06aa6ccf20915e65d85de0e2c4445362

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