all repos — mgba @ 9dbd925d90f33f64b6370b135be02c01d4d4eaae

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 inX = sprite->x;
404	int inY = y - sprite->y;
405	if (sprite->y + height - 256 >= 0) {
406		inY += 256;
407	}
408	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
409	unsigned yBase = (inY & ~0x7) * 0x80 + (inY & 0x7) * 4;
410	for (int outX = inX >= 0 ? inX : 0; outX < inX + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
411		int x = outX - inX;
412		if (renderer->flags[outX].isSprite) {
413			continue;
414		}
415		unsigned xBase = (x & ~0x7) * 4 + ((x >> 1) & 2);
416		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
417		tileData = (tileData >> ((x & 3) << 2)) & 0xF;
418		if (tileData) {
419			renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
420			renderer->flags[outX] = flags;
421		}
422	}
423}
424
425static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
426	if (renderer->blendEffect == BLEND_BRIGHTEN) {
427		for (int i = 0; i < 512; ++i) {
428			renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
429		}
430	} else if (renderer->blendEffect == BLEND_DARKEN) {
431		for (int i = 0; i < 512; ++i) {
432			renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
433		}
434	}
435}
436
437static inline uint16_t _brighten(uint16_t c, int y) {
438	union GBAColor color = { .packed = c };
439	color.r = color.r + ((31 - color.r) * y) / 16;
440	color.g = color.g + ((31 - color.g) * y) / 16;
441	color.b = color.b + ((31 - color.b) * y) / 16;
442	return color.packed;
443}
444
445static inline uint16_t _darken(uint16_t c, int y) {
446	union GBAColor color = { .packed = c };
447	color.r = color.r - (color.r * y) / 16;
448	color.g = color.g - (color.g * y) / 16;
449	color.b = color.b - (color.b * y) / 16;
450	return color.packed;
451}
452
453static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
454	union GBAColor ca = { .packed = colorA };
455	union GBAColor cb = { .packed = colorB };
456
457	int r = (ca.r * weightA + cb.r * weightB) / 16;
458	ca.r = r > 0x1F ? 0x1F : r;
459
460	int g = (ca.g * weightA + cb.g * weightB) / 16;
461	ca.g = g > 0x1F ? 0x1F : g;
462
463	int b = (ca.b * weightA + cb.b * weightB) / 16;
464	ca.b = b > 0x1F ? 0x1F : b;
465
466	return ca.packed;
467}
468
469static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
470	qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
471}
472
473static int _backgroundComparator(const void* a, const void* b) {
474	const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
475	const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
476	if (bgA->priority != bgB->priority) {
477		return bgA->priority - bgB->priority;
478	} else {
479		return bgA->index - bgB->index;
480	}
481}