all repos — mgba @ 17ca8f524a165cb15856b76925a17d68b2cdb09d

mGBA Game Boy Advance Emulator

src/gba/renderers/software-obj.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "gba/renderers/software-private.h"
  7
  8#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
  9	SPRITE_YBASE_ ## DEPTH(inY); \
 10	unsigned tileData; \
 11	for (; outX < condition; ++outX, inX += xOffset) { \
 12		SPRITE_XBASE_ ## DEPTH(inX); \
 13		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
 14	}
 15
 16#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \
 17	SPRITE_YBASE_ ## DEPTH(inY); \
 18	unsigned tileData; \
 19	for (; outX < condition; ++outX, inX += xOffset) { \
 20		int localX = inX - xOffset * (outX % mosaicH); \
 21		if (localX < 0) { \
 22			localX = 0; \
 23		} else if (localX > width - 1) {\
 24			localX = width - 1; \
 25		} \
 26		SPRITE_XBASE_ ## DEPTH(localX); \
 27		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
 28	}
 29
 30#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
 31	unsigned tileData; \
 32	unsigned widthMask = ~(width - 1); \
 33	unsigned heightMask = ~(height - 1); \
 34	for (; outX < condition; ++outX, ++inX) { \
 35		xAccum += mat.a; \
 36		yAccum += mat.c; \
 37		int localX = xAccum >> 8; \
 38		int localY = yAccum >> 8; \
 39		\
 40		if (localX & widthMask || localY & heightMask) { \
 41			break; \
 42		} \
 43		\
 44		SPRITE_YBASE_ ## DEPTH(localY); \
 45		SPRITE_XBASE_ ## DEPTH(localX); \
 46		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
 47	}
 48
 49#define SPRITE_TRANSFORMED_MOSAIC_LOOP(DEPTH, TYPE) \
 50	unsigned tileData; \
 51	unsigned widthMask = ~(width - 1); \
 52	unsigned heightMask = ~(height - 1); \
 53	int localX = xAccum >> 8; \
 54	int localY = yAccum >> 8; \
 55	for (; outX < condition; ++outX, ++inX) { \
 56		xAccum += mat.a; \
 57		yAccum += mat.c; \
 58		\
 59		if (outX % mosaicH == 0) { \
 60			localX = xAccum >> 8; \
 61			localY = yAccum >> 8; \
 62		} \
 63		\
 64		if (localX & widthMask || localY & heightMask) { \
 65			continue; \
 66		} \
 67		\
 68		SPRITE_YBASE_ ## DEPTH(localY); \
 69		SPRITE_XBASE_ ## DEPTH(localX); \
 70		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
 71	}
 72
 73#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
 74#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4;
 75
 76#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
 77	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
 78	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
 79	current = renderer->spriteLayer[outX]; \
 80	if ((current & FLAG_ORDER_MASK) > flags) { \
 81		if (tileData) { \
 82			renderer->spriteLayer[outX] = palette[tileData] | flags; \
 83		} else if (current != FLAG_UNWRITTEN) { \
 84			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
 85		} \
 86	}
 87
 88#define SPRITE_DRAW_PIXEL_16_NORMAL_OBJWIN(localX) \
 89	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
 90	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
 91	current = renderer->spriteLayer[outX]; \
 92	if ((current & FLAG_ORDER_MASK) > flags) { \
 93		if (tileData) { \
 94			unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \
 95			renderer->spriteLayer[outX] = color | flags; \
 96		} else if (current != FLAG_UNWRITTEN) { \
 97			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
 98		} \
 99	}
100
101#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
102	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
103	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
104	if (tileData) { \
105		renderer->row[outX] |= FLAG_OBJWIN; \
106	}
107
108#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
109#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8;
110
111#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
112	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
113	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
114	current = renderer->spriteLayer[outX]; \
115	if ((current & FLAG_ORDER_MASK) > flags) { \
116		if (tileData) { \
117			renderer->spriteLayer[outX] = palette[tileData] | flags; \
118		} else if (current != FLAG_UNWRITTEN) { \
119			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
120		} \
121	}
122
123#define SPRITE_DRAW_PIXEL_256_NORMAL_OBJWIN(localX) \
124	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase);  \
125	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
126	current = renderer->spriteLayer[outX]; \
127	if ((current & FLAG_ORDER_MASK) > flags) { \
128		if (tileData) { \
129			unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \
130			renderer->spriteLayer[outX] = color | flags; \
131		} else if (current != FLAG_UNWRITTEN) { \
132			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
133		} \
134	}
135
136#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
137	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
138	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
139	if (tileData) { \
140		renderer->row[outX] |= FLAG_OBJWIN; \
141	}
142
143int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) {
144	int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
145	int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
146	int start = renderer->start;
147	int end = renderer->end;
148	uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY;
149	flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
150	flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN);
151	if ((flags & FLAG_OBJWIN) && renderer->currentWindow.priority < renderer->objwin.priority) {
152		return 0;
153	}
154	int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23;
155	x >>= 23;
156	x += renderer->objOffsetX;
157	uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
158	unsigned align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt);
159	unsigned charBase = (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x20;
160	if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) {
161		return 0;
162	}
163
164	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed);
165	int variant = renderer->target1Obj &&
166	              GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) &&
167	              (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
168	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
169		int target2 = renderer->target2Bd;
170		target2 |= renderer->bg[0].target2;
171		target2 |= renderer->bg[1].target2;
172		target2 |= renderer->bg[2].target2;
173		target2 |= renderer->bg[3].target2;
174		if (target2) {
175			renderer->forceTarget1 = true;
176			flags |= FLAG_REBLEND;
177			variant = 0;
178		} else {
179			flags &= ~FLAG_TARGET_1;
180		}
181	}
182
183	color_t* palette = &renderer->normalPalette[0x100];
184	if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
185		palette = &renderer->highlightPalette[0x100];
186	}
187	color_t* objwinPalette = palette;
188
189	if (variant) {
190		palette = &renderer->variantPalette[0x100];
191		if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
192			palette = &renderer->highlightVariantPalette[0x100];
193		}
194		if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
195			objwinPalette = palette;
196		}
197	}
198
199	int inY = y - ((int) GBAObjAttributesAGetY(sprite->a) + renderer->objOffsetY);
200	int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> !GBAObjAttributesAIs256Color(sprite->a)) : 0x80;
201
202	uint32_t current;
203	if (GBAObjAttributesAIsTransformed(sprite->a)) {
204		int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
205		int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
206		struct GBAOAMMatrix mat;
207		LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
208		LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
209		LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
210		LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
211
212		if (inY < 0) {
213			inY += 256;
214		}
215		int outX = x >= start ? x : start;
216		int condition = x + totalWidth;
217		int inX = outX - x;
218		if (end < condition) {
219			condition = end;
220		}
221		int mosaicH = 1;
222		if (GBAObjAttributesAIsMosaic(sprite->a)) {
223			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
224			if (condition != end && condition % mosaicH) {
225				condition += mosaicH - (condition % mosaicH);
226			}
227		}
228
229		int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7);
230		int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7);
231
232		// Clip off early pixels
233		// TODO: Transform end coordinates too
234		if (mat.a) {
235			if ((xAccum >> 8) < 0) {
236				int32_t diffX = -xAccum - 1;
237				int32_t x = mat.a ? diffX / mat.a : 0;
238				xAccum += mat.a * x;
239				yAccum += mat.c * x;
240				outX += x;
241				inX += x;
242			} else if ((xAccum >> 8) >= width) {
243				int32_t diffX = (width << 8) - xAccum;
244				int32_t x = mat.a ? diffX / mat.a : 0;
245				xAccum += mat.a * x;
246				yAccum += mat.c * x;
247				outX += x;
248				inX += x;
249			}
250		}
251		if (mat.c) {
252			if ((yAccum >> 8) < 0) {
253				int32_t diffY = - yAccum - 1;
254				int32_t y = mat.c ? diffY / mat.c : 0;
255				xAccum += mat.a * y;
256				yAccum += mat.c * y;
257				outX += y;
258				inX += y;
259			} else if ((yAccum >> 8) >= height) {
260				int32_t diffY = (height << 8) - yAccum;
261				int32_t y = mat.c ? diffY / mat.c : 0;
262				xAccum += mat.a * y;
263				yAccum += mat.c * y;
264				outX += y;
265				inX += y;
266			}
267		}
268
269		if (outX < start || outX >= condition) {
270			return 0;
271		}
272
273		if (!GBAObjAttributesAIs256Color(sprite->a)) {
274			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
275			if (flags & FLAG_OBJWIN) {
276				SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
277			} else if (mosaicH > 1) {
278				if (objwinSlowPath) {
279					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
280					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN);
281				} else {
282					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL);
283				}
284			} else if (objwinSlowPath) {
285				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
286				SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN);
287			} else {
288				SPRITE_TRANSFORMED_LOOP(16, NORMAL);
289			}
290		} else {
291			if (flags & FLAG_OBJWIN) {
292				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
293			} else if (mosaicH > 1) {
294				if (objwinSlowPath) {
295					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN);
296				} else {
297					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL);
298				}
299			} else if (objwinSlowPath) {
300				SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);
301			} else {
302				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
303			}
304		}
305	} else {
306		int outX = x >= start ? x : start;
307		int condition = x + width;
308		int mosaicH = 1;
309		if (GBAObjAttributesAIsMosaic(sprite->a)) {
310			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
311			if (condition % mosaicH) {
312				condition += mosaicH - (condition % mosaicH);
313			}
314		}
315		if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) {
316			inY += 256;
317		}
318		if (GBAObjAttributesBIsVFlip(sprite->b)) {
319			inY = height - inY - 1;
320		}
321		if (end < condition) {
322			condition = end;
323		}
324		int inX = outX - x;
325		int xOffset = 1;
326		if (GBAObjAttributesBIsHFlip(sprite->b)) {
327			inX = width - inX - 1;
328			xOffset = -1;
329		}
330		if (!GBAObjAttributesAIs256Color(sprite->a)) {
331			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
332			if (flags & FLAG_OBJWIN) {
333				SPRITE_NORMAL_LOOP(16, OBJWIN);
334			} else if (mosaicH > 1) {
335				if (objwinSlowPath) {
336					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
337					SPRITE_MOSAIC_LOOP(16, NORMAL_OBJWIN);
338				} else {
339					SPRITE_MOSAIC_LOOP(16, NORMAL);
340				}
341			} else if (objwinSlowPath) {
342				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
343				SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN);
344			} else {
345				SPRITE_NORMAL_LOOP(16, NORMAL);
346			}
347		} else {
348			if (flags & FLAG_OBJWIN) {
349				SPRITE_NORMAL_LOOP(256, OBJWIN);
350			} else if (mosaicH > 1) {
351				if (objwinSlowPath) {
352					SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN);
353				} else {
354					SPRITE_MOSAIC_LOOP(256, NORMAL);
355				}
356			} else if (objwinSlowPath) {
357				SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN);
358			} else {
359				SPRITE_NORMAL_LOOP(256, NORMAL);
360			}
361		}
362	}
363	return 1;
364}
365
366void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) {
367	int x;
368	uint32_t* pixel = &renderer->row[renderer->start];
369	uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj;
370
371	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt);
372	bool objwinDisable = false;
373	bool objwinOnly = false;
374	if (objwinSlowPath) {
375		objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
376		objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
377		if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
378			return;
379		}
380
381		if (objwinDisable) {
382			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
383				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
384				uint32_t current = *pixel;
385				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
386					_compositeBlendObjwin(renderer, pixel, color | flags, current);
387				}
388			}
389			return;
390		} else if (objwinOnly) {
391			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
392				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
393				uint32_t current = *pixel;
394				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
395					_compositeBlendObjwin(renderer, pixel, color | flags, current);
396				}
397			}
398			return;
399		} else {
400			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
401				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
402				uint32_t current = *pixel;
403				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
404					_compositeBlendObjwin(renderer, pixel, color | flags, current);
405				}
406			}
407			return;
408		}
409	} else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
410		return;
411	}
412	for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
413		uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
414		uint32_t current = *pixel;
415		if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
416			_compositeBlendNoObjwin(renderer, pixel, color | flags, current);
417		}
418	}
419}