all repos — mgba @ a1910206c0b620bec09e525c5fe763b563ed9220

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 if (renderer->flags[offset].isSprite && renderer->flags[offset].target2 && flags.target1) {
321			renderer->row[offset] = _mix(renderer->blda, renderer->d.palette[entry], renderer->bldb, renderer->row[offset]);
322			renderer->flags[offset].finalized = 1;
323		} else {
324			renderer->row[offset] = renderer->d.palette[entry];
325			renderer->flags[offset].target1 = flags.target1;
326		}
327	}
328	renderer->flags[offset].written = 1;
329}
330
331static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
332	int start = 0;
333	int end = VIDEO_HORIZONTAL_PIXELS;
334	int inX = start + background->x;
335	int inY = y + background->y;
336	union GBATextMapData mapData;
337
338	unsigned yBase = inY & 0xF8;
339	if (background->size & 2) {
340		yBase += inY & 0x100;
341	} else if (background->size == 3) {
342		yBase += (inY & 0x100) << 1;
343	}
344
345	unsigned xBase;
346
347	uint32_t screenBase;
348	uint32_t charBase;
349
350	for (int outX = start; outX < end; ++outX) {
351		if (renderer->flags[outX].finalized) {
352			continue;
353		}
354		xBase = (outX + inX) & 0xF8;
355		if (background->size & 1) {
356			xBase += ((outX + inX) & 0x100) << 5;
357		}
358		screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
359		mapData.packed = renderer->d.vram[screenBase];
360		charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
361		uint16_t tileData = renderer->d.vram[charBase];
362		tileData >>= ((outX + inX) & 0x3) << 2;
363		if (tileData & 0xF) {
364			struct PixelFlags flags = {
365				.target1 = background->target1,
366				.target2 = background->target2,
367				.priority = background->priority
368			};
369			_compositeBackground(renderer, outX, (tileData & 0xF) | (mapData.palette << 4), flags);
370		}
371	}
372}
373
374static const int _objSizes[32] = {
375	8, 8,
376	16, 16,
377	32, 32,
378	64, 64,
379	16, 8,
380	32, 8,
381	32, 16,
382	64, 32,
383	8, 16,
384	8, 32,
385	16, 32,
386	32, 64,
387	0, 0,
388	0, 0,
389	0, 0,
390	0, 0
391};
392
393static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
394	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
395	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
396	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
397		return;
398	}
399	(void)(renderer);
400	struct PixelFlags flags = {
401		.priority = sprite->priority,
402		.isSprite = 1,
403		.target1 = renderer->target1Obj || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
404		.target2 = renderer->target2Obj
405	};
406	int x = sprite->x;
407	int inY = y - sprite->y;
408	if (sprite->y + height - 256 >= 0) {
409		inY += 256;
410	}
411	if (sprite->vflip) {
412		inY = height - inY - 1;
413	}
414	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
415	unsigned yBase = (inY & ~0x7) * 0x80 + (inY & 0x7) * 4;
416	for (int outX = x >= 0 ? x : 0; outX < x + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
417		int inX = outX - x;
418		if (sprite->hflip) {
419			inX = width - inX - 1;
420		}
421		if (renderer->flags[outX].isSprite) {
422			continue;
423		}
424		unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
425		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
426		tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
427		if (tileData) {
428			if (!renderer->target1Obj) {
429				renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
430			} else {
431				renderer->row[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
432			}
433			renderer->flags[outX] = flags;
434		}
435	}
436}
437
438static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
439	if (renderer->blendEffect == BLEND_BRIGHTEN) {
440		for (int i = 0; i < 512; ++i) {
441			renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
442		}
443	} else if (renderer->blendEffect == BLEND_DARKEN) {
444		for (int i = 0; i < 512; ++i) {
445			renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
446		}
447	} else {
448		for (int i = 0; i < 512; ++i) {
449			renderer->variantPalette[i] = renderer->d.palette[i];
450		}
451	}
452}
453
454static inline uint16_t _brighten(uint16_t c, int y) {
455	union GBAColor color = { .packed = c };
456	color.r = color.r + ((31 - color.r) * y) / 16;
457	color.g = color.g + ((31 - color.g) * y) / 16;
458	color.b = color.b + ((31 - color.b) * y) / 16;
459	return color.packed;
460}
461
462static inline uint16_t _darken(uint16_t c, int y) {
463	union GBAColor color = { .packed = c };
464	color.r = color.r - (color.r * y) / 16;
465	color.g = color.g - (color.g * y) / 16;
466	color.b = color.b - (color.b * y) / 16;
467	return color.packed;
468}
469
470static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
471	union GBAColor ca = { .packed = colorA };
472	union GBAColor cb = { .packed = colorB };
473
474	int r = (ca.r * weightA + cb.r * weightB) / 16;
475	ca.r = r > 0x1F ? 0x1F : r;
476
477	int g = (ca.g * weightA + cb.g * weightB) / 16;
478	ca.g = g > 0x1F ? 0x1F : g;
479
480	int b = (ca.b * weightA + cb.b * weightB) / 16;
481	ca.b = b > 0x1F ? 0x1F : b;
482
483	return ca.packed;
484}
485
486static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
487	qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
488}
489
490static int _backgroundComparator(const void* a, const void* b) {
491	const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
492	const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
493	if (bgA->priority != bgB->priority) {
494		return bgA->priority - bgB->priority;
495	} else {
496		return bgA->index - bgB->index;
497	}
498}