all repos — mgba @ 7619d8aa67e1ee25d2cc0cf5f1a22ef7e6e98a3e

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