all repos — mgba @ c62d913e233e7ea3bb23a3f52fcb7b481f2faed5

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	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
 78	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
 79	if (UNLIKELY(!vramBase)) { \
 80		return 0; \
 81	} \
 82	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
 83	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
 84	current = renderer->spriteLayer[outX]; \
 85	if ((current & FLAG_ORDER_MASK) > flags) { \
 86		if (tileData) { \
 87			renderer->spriteLayer[outX] = palette[tileData] | flags; \
 88		} else if (current != FLAG_UNWRITTEN) { \
 89			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
 90		} \
 91	}
 92
 93#define SPRITE_DRAW_PIXEL_16_NORMAL_OBJWIN(localX) \
 94	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
 95	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
 96	if (UNLIKELY(!vramBase)) { \
 97		return 0; \
 98	} \
 99	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
100	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
101	current = renderer->spriteLayer[outX]; \
102	if ((current & FLAG_ORDER_MASK) > flags) { \
103		if (tileData) { \
104			unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \
105			renderer->spriteLayer[outX] = color | flags; \
106		} else if (current != FLAG_UNWRITTEN) { \
107			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
108		} \
109	}
110
111#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
112	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
113	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
114	if (UNLIKELY(!vramBase)) { \
115		return 0; \
116	} \
117	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
118	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
119	if (tileData) { \
120		renderer->row[outX] |= FLAG_OBJWIN; \
121	}
122
123#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
124#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8;
125
126#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
127	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
128	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
129	if (UNLIKELY(!vramBase)) { \
130		return 0; \
131	} \
132	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
133	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
134	current = renderer->spriteLayer[outX]; \
135	if ((current & FLAG_ORDER_MASK) > flags) { \
136		if (tileData) { \
137			renderer->spriteLayer[outX] = palette[tileData] | flags; \
138		} else if (current != FLAG_UNWRITTEN) { \
139			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
140		} \
141	}
142
143#define SPRITE_DRAW_PIXEL_256_NORMAL_OBJWIN(localX) \
144	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
145	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
146	if (UNLIKELY(!vramBase)) { \
147		return 0; \
148	} \
149	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
150	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
151	current = renderer->spriteLayer[outX]; \
152	if ((current & FLAG_ORDER_MASK) > flags) { \
153		if (tileData) { \
154			unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \
155			renderer->spriteLayer[outX] = color | flags; \
156		} else if (current != FLAG_UNWRITTEN) { \
157			renderer->spriteLayer[outX] = (current & ~(FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)) | (flags & (FLAG_ORDER_MASK | FLAG_REBLEND | FLAG_TARGET_1)); \
158		} \
159	}
160
161#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
162	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
163	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
164	if (UNLIKELY(!vramBase)) { \
165		return 0; \
166	} \
167	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
168	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
169	if (tileData) { \
170		renderer->row[outX] |= FLAG_OBJWIN; \
171	}
172
173#ifndef COLOR_16_BIT
174#define TILE_TO_COLOR(tileData) \
175	unsigned color32; \
176	color32 = 0; \
177	color32 |= (tileData << 3) & 0xF8; \
178	color32 |= (tileData << 6) & 0xF800; \
179	color32 |= (tileData << 9) & 0xF80000; \
180	color32 |= (color32 >> 5) & 0x070707; \
181	color = color32;
182#elif COLOR_5_6_5
183#define TILE_TO_COLOR(tileData) \
184	uint16_t color16 = 0; \
185	color16 |= (tileData & 0x001F) << 11; \
186	color16 |= (tileData & 0x03E0) << 1; \
187	color16 |= (tileData & 0x7C00) >> 10; \
188	color = color16;
189#else
190#define TILE_TO_COLOR(tileData) \
191	color = tileData;
192#endif
193
194#define SPRITE_XBASE_BITMAP(localX) unsigned xBase = (localX & (stride - 1)) << 1;
195#define SPRITE_YBASE_BITMAP(localY) unsigned yBase = localY * (stride << 1);
196
197#define SPRITE_DRAW_PIXEL_BITMAP_NORMAL(localX) \
198	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
199	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
200	if (UNLIKELY(!vramBase)) { \
201		return 0; \
202	} \
203	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
204	current = renderer->spriteLayer[outX]; \
205	if ((current & FLAG_ORDER_MASK) > flags) { \
206		if (tileData & 0x8000) { \
207			uint32_t color; \
208			TILE_TO_COLOR(tileData); \
209			renderer->spriteLayer[outX] = color | flags; \
210		} else if (current != FLAG_UNWRITTEN) { \
211			renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
212		} \
213	}
214
215#define SPRITE_DRAW_PIXEL_BITMAP_NORMAL_OBJWIN(localX) SPRITE_DRAW_PIXEL_BITMAP_NORMAL(localX)
216
217#define SPRITE_DRAW_PIXEL_BITMAP_OBJWIN(localX) \
218	uint32_t spriteBase = ((yBase + charBase + xBase) & 0x3FFFE); \
219	uint16_t* vramBase = renderer->d.vramOBJ[spriteBase >> VRAM_BLOCK_OFFSET]; \
220	if (UNLIKELY(!vramBase)) { \
221		return 0; \
222	} \
223	LOAD_16(tileData, spriteBase & VRAM_BLOCK_MASK, vramBase); \
224	if (tileData & 0x8000) { \
225		renderer->row[outX] |= FLAG_OBJWIN; \
226	}
227
228int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) {
229	int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
230	int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
231	int start = renderer->start;
232	int end = renderer->end;
233	uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY;
234	flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
235	flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN);
236	if ((flags & FLAG_OBJWIN) && renderer->currentWindow.priority < renderer->objwin.priority) {
237		return 0;
238	}
239	int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23;
240	x >>= 23;
241	x += renderer->objOffsetX;
242	unsigned align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt);
243	unsigned charBase = GBAObjAttributesCGetTile(sprite->c) & ~align;
244	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
245		charBase = (charBase & (renderer->bitmapStride - 1)) * 0x10 + (charBase & ~(renderer->bitmapStride - 1)) * 0x80;
246	} else {
247		charBase *= renderer->tileStride;
248	}
249	if (!renderer->d.vramOBJ[charBase >> VRAM_BLOCK_OFFSET]) {
250		return 0;
251	}
252
253	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed);
254	int variant = (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) &&
255	              GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) &&
256	              (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
257	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
258		int target2 = renderer->target2Bd;
259		target2 |= renderer->bg[0].target2;
260		target2 |= renderer->bg[1].target2;
261		target2 |= renderer->bg[2].target2;
262		target2 |= renderer->bg[3].target2;
263		if (target2) {
264			renderer->forceTarget1 = true;
265			flags |= FLAG_REBLEND;
266			variant = 0;
267		} else {
268			flags &= ~FLAG_TARGET_1;
269		}
270	}
271
272	color_t* palette = &renderer->normalPalette[0x100];
273	if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
274		palette = &renderer->highlightPalette[0x100];
275	}
276	color_t* objwinPalette = palette;
277
278	if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
279		if (!variant) {
280			palette = renderer->objExtPalette;
281			objwinPalette = palette;
282		} else {
283			palette = renderer->objExtVariantPalette;
284			if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
285				objwinPalette = palette;
286			}
287		}
288	} else if (variant) {
289		palette = &renderer->variantPalette[0x100];
290		if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
291			palette = &renderer->highlightVariantPalette[0x100];
292		}
293		if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
294			objwinPalette = palette;
295		}
296	}
297
298	int inY = y - ((int) GBAObjAttributesAGetY(sprite->a) + renderer->objOffsetY);
299	int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> !GBAObjAttributesAIs256Color(sprite->a)) : 0x80;
300	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
301		stride = renderer->bitmapStride << 3;
302	}
303
304	uint32_t current;
305	if (GBAObjAttributesAIsTransformed(sprite->a)) {
306		int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
307		int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
308		struct GBAOAMMatrix mat;
309		LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
310		LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
311		LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
312		LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
313
314		if (inY < 0) {
315			inY += 256;
316		}
317		int outX = x >= start ? x : start;
318		int condition = x + totalWidth;
319		int inX = outX - x;
320		if (end < condition) {
321			condition = end;
322		}
323		int mosaicH = 1;
324		if (GBAObjAttributesAIsMosaic(sprite->a)) {
325			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
326			if (condition != end && condition % mosaicH) {
327				condition += mosaicH - (condition % mosaicH);
328			}
329		}
330
331		int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7);
332		int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7);
333
334		// Clip off early pixels
335		// TODO: Transform end coordinates too
336		if (mat.a) {
337			if ((xAccum >> 8) < 0) {
338				int32_t diffX = -xAccum - 1;
339				int32_t x = mat.a ? diffX / mat.a : 0;
340				xAccum += mat.a * x;
341				yAccum += mat.c * x;
342				outX += x;
343				inX += x;
344			} else if ((xAccum >> 8) >= width) {
345				int32_t diffX = (width << 8) - xAccum;
346				int32_t x = mat.a ? diffX / mat.a : 0;
347				xAccum += mat.a * x;
348				yAccum += mat.c * x;
349				outX += x;
350				inX += x;
351			}
352		}
353		if (mat.c) {
354			if ((yAccum >> 8) < 0) {
355				int32_t diffY = - yAccum - 1;
356				int32_t y = mat.c ? diffY / mat.c : 0;
357				xAccum += mat.a * y;
358				yAccum += mat.c * y;
359				outX += y;
360				inX += y;
361			} else if ((yAccum >> 8) >= height) {
362				int32_t diffY = (height << 8) - yAccum;
363				int32_t y = mat.c ? diffY / mat.c : 0;
364				xAccum += mat.a * y;
365				yAccum += mat.c * y;
366				outX += y;
367				inX += y;
368			}
369		}
370
371		if (outX < start || outX >= condition) {
372			return 0;
373		}
374
375		if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
376			int alpha = GBAObjAttributesCGetPalette(sprite->c);
377			if (flags & FLAG_OBJWIN) {
378				SPRITE_TRANSFORMED_LOOP(BITMAP, OBJWIN);
379			} else if (objwinSlowPath) {
380				SPRITE_TRANSFORMED_LOOP(BITMAP, NORMAL_OBJWIN);
381			} else {
382				SPRITE_TRANSFORMED_LOOP(BITMAP, NORMAL);
383			}
384		} else if (!GBAObjAttributesAIs256Color(sprite->a)) {
385			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
386			if (flags & FLAG_OBJWIN) {
387				SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
388			} else if (mosaicH > 1) {
389				if (objwinSlowPath) {
390					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
391					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN);
392				} else {
393					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL);
394				}
395			} else if (objwinSlowPath) {
396				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
397				SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN);
398			} else {
399				SPRITE_TRANSFORMED_LOOP(16, NORMAL);
400			}
401		} else if (!renderer->objExtPalette) {
402			if (flags & FLAG_OBJWIN) {
403				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
404			} else if (objwinSlowPath) {
405				SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);
406			} else {
407				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
408			}
409		} else {
410			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8];
411			if (flags & FLAG_OBJWIN) {
412				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
413			} else if (mosaicH > 1) {
414				if (objwinSlowPath) {
415					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN);
416				} else {
417					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL);
418				}
419			} else if (objwinSlowPath) {
420				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8];
421				SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);
422			} else {
423				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
424			}
425		}
426	} else {
427		int outX = x >= start ? x : start;
428		int condition = x + width;
429		int mosaicH = 1;
430		if (GBAObjAttributesAIsMosaic(sprite->a)) {
431			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
432			if (condition % mosaicH) {
433				condition += mosaicH - (condition % mosaicH);
434			}
435		}
436		if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) {
437			inY += 256;
438		}
439		if (GBAObjAttributesBIsVFlip(sprite->b)) {
440			inY = height - inY - 1;
441		}
442		if (end < condition) {
443			condition = end;
444		}
445		int inX = outX - x;
446		int xOffset = 1;
447		if (GBAObjAttributesBIsHFlip(sprite->b)) {
448			inX = width - inX - 1;
449			xOffset = -1;
450		}
451		if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
452			int alpha = GBAObjAttributesCGetPalette(sprite->c);
453			if (flags & FLAG_OBJWIN) {
454				SPRITE_NORMAL_LOOP(BITMAP, OBJWIN);
455			} else if (mosaicH > 1) {
456				if (objwinSlowPath) {
457					SPRITE_MOSAIC_LOOP(BITMAP, NORMAL_OBJWIN);
458				} else {
459					SPRITE_MOSAIC_LOOP(BITMAP, NORMAL);
460				}
461			} else if (objwinSlowPath) {
462				SPRITE_NORMAL_LOOP(BITMAP, NORMAL_OBJWIN);
463			} else {
464				SPRITE_NORMAL_LOOP(BITMAP, NORMAL);
465			}
466		} else if (!GBAObjAttributesAIs256Color(sprite->a)) {
467			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
468			if (flags & FLAG_OBJWIN) {
469				SPRITE_NORMAL_LOOP(16, OBJWIN);
470			} else if (mosaicH > 1) {
471				if (objwinSlowPath) {
472					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
473					SPRITE_MOSAIC_LOOP(16, NORMAL_OBJWIN);
474				} else {
475					SPRITE_MOSAIC_LOOP(16, NORMAL);
476				}
477			} else if (objwinSlowPath) {
478				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
479				SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN);
480			} else {
481				SPRITE_NORMAL_LOOP(16, NORMAL);
482			}
483		} else if (!renderer->objExtPalette) {
484			if (flags & FLAG_OBJWIN) {
485				SPRITE_NORMAL_LOOP(256, OBJWIN);
486			} else if (mosaicH > 1) {
487				if (objwinSlowPath) {
488					SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN);
489				} else {
490					SPRITE_MOSAIC_LOOP(256, NORMAL);
491				}
492			} else if (objwinSlowPath) {
493				SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN);
494			} else {
495				SPRITE_NORMAL_LOOP(256, NORMAL);
496			}
497		} else {
498			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8];
499			if (flags & FLAG_OBJWIN) {
500				SPRITE_NORMAL_LOOP(256, OBJWIN);
501			} else if (mosaicH > 1) {
502				if (objwinSlowPath) {
503					SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN);
504				} else {
505					SPRITE_MOSAIC_LOOP(256, NORMAL);
506				}
507			} else if (objwinSlowPath) {
508				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8];
509				SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN);
510			} else {
511				SPRITE_NORMAL_LOOP(256, NORMAL);
512			}
513
514		}
515	}
516	return 1;
517}
518
519void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) {
520	int x;
521	uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj;
522
523	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt);
524	bool objwinDisable = false;
525	bool objwinOnly = false;
526	if (objwinSlowPath) {
527		objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
528		objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
529		if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
530			return;
531		}
532
533		if (objwinDisable) {
534			for (x = renderer->start; x < renderer->end; ++x) {
535				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
536				uint32_t current = renderer->row[x];
537				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
538					_compositeBlendObjwin(renderer, x, color | flags, current);
539				}
540			}
541			return;
542		} else if (objwinOnly) {
543			for (x = renderer->start; x < renderer->end; ++x) {
544				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
545				uint32_t current = renderer->row[x];
546				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
547					_compositeBlendObjwin(renderer, x, color | flags, current);
548				}
549			}
550			return;
551		} else {
552			for (x = renderer->start; x < renderer->end; ++x) {
553				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
554				uint32_t current = renderer->row[x];
555				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
556					_compositeBlendObjwin(renderer, x, color | flags, current);
557				}
558			}
559			return;
560		}
561	} else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
562		return;
563	}
564	for (x = renderer->start; x < renderer->end; ++x) {
565		uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
566		uint32_t current = renderer->row[x];
567		if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
568			_compositeBlendNoObjwin(renderer, x, color | flags, current);
569		}
570	}
571}