all repos — mgba @ 6b84383d1ad29d9dd39a62a80c1c8c5878979f45

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) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
 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) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
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) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
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) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
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 &&
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			flags |= FLAG_REBLEND;
267			variant = 0;
268		} else {
269			flags &= ~FLAG_TARGET_1;
270		}
271	}
272
273	color_t* palette = &renderer->normalPalette[0x100];
274	if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
275		palette = &renderer->highlightPalette[0x100];
276	}
277	color_t* objwinPalette = palette;
278
279	if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
280		if (!variant) {
281			palette = renderer->objExtPalette;
282			objwinPalette = palette;
283		} else {
284			palette = renderer->objExtVariantPalette;
285			if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
286				objwinPalette = palette;
287			}
288		}
289	} else if (variant) {
290		palette = &renderer->variantPalette[0x100];
291		if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
292			palette = &renderer->highlightVariantPalette[0x100];
293		}
294		if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
295			objwinPalette = palette;
296		}
297	}
298
299	int inY = y - ((int) GBAObjAttributesAGetY(sprite->a) + renderer->objOffsetY);
300	int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> !GBAObjAttributesAIs256Color(sprite->a)) : 0x80;
301	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
302		stride = renderer->bitmapStride << 3;
303	}
304
305	uint32_t current;
306	if (GBAObjAttributesAIsTransformed(sprite->a)) {
307		int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
308		int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
309		struct GBAOAMMatrix mat;
310		LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
311		LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
312		LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
313		LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
314
315		if (inY < 0) {
316			inY += 256;
317		}
318		int outX = x >= start ? x : start;
319		int condition = x + totalWidth;
320		int inX = outX - x;
321		if (end < condition) {
322			condition = end;
323		}
324		int mosaicH = 1;
325		if (GBAObjAttributesAIsMosaic(sprite->a)) {
326			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
327			if (condition % mosaicH) {
328				condition += mosaicH - (condition % mosaicH);
329			}
330		}
331
332		int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7);
333		int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7);
334
335		// Clip off early pixels
336		// TODO: Transform end coordinates too
337		if (mat.a) {
338			if ((xAccum >> 8) < 0) {
339				int32_t diffX = -xAccum - 1;
340				int32_t x = mat.a ? diffX / mat.a : 0;
341				xAccum += mat.a * x;
342				yAccum += mat.c * x;
343				outX += x;
344				inX += x;
345			} else if ((xAccum >> 8) >= width) {
346				int32_t diffX = (width << 8) - xAccum;
347				int32_t x = mat.a ? diffX / mat.a : 0;
348				xAccum += mat.a * x;
349				yAccum += mat.c * x;
350				outX += x;
351				inX += x;
352			}
353		}
354		if (mat.c) {
355			if ((yAccum >> 8) < 0) {
356				int32_t diffY = - yAccum - 1;
357				int32_t y = mat.c ? diffY / mat.c : 0;
358				xAccum += mat.a * y;
359				yAccum += mat.c * y;
360				outX += y;
361				inX += y;
362			} else if ((yAccum >> 8) >= height) {
363				int32_t diffY = (height << 8) - yAccum;
364				int32_t y = mat.c ? diffY / mat.c : 0;
365				xAccum += mat.a * y;
366				yAccum += mat.c * y;
367				outX += y;
368				inX += y;
369			}
370		}
371
372		if (outX < start || outX >= condition) {
373			return 0;
374		}
375		renderer->spriteCyclesRemaining -= 10;
376
377		if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
378			int alpha = GBAObjAttributesCGetPalette(sprite->c);
379			if (flags & FLAG_OBJWIN) {
380				SPRITE_TRANSFORMED_LOOP(BITMAP, OBJWIN);
381			} else if (objwinSlowPath) {
382				SPRITE_TRANSFORMED_LOOP(BITMAP, NORMAL_OBJWIN);
383			} else {
384				SPRITE_TRANSFORMED_LOOP(BITMAP, NORMAL);
385			}
386		} else if (!GBAObjAttributesAIs256Color(sprite->a)) {
387			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
388			if (flags & FLAG_OBJWIN) {
389				SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
390			} else if (mosaicH > 1) {
391				if (objwinSlowPath) {
392					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
393					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN);
394				} else {
395					SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL);
396				}
397			} else if (objwinSlowPath) {
398				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
399				SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN);
400			} else {
401				SPRITE_TRANSFORMED_LOOP(16, NORMAL);
402			}
403		} else if (!renderer->objExtPalette) {
404			if (flags & FLAG_OBJWIN) {
405				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
406			} else if (objwinSlowPath) {
407				SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);
408			} else {
409				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
410			}
411		} else {
412			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8];
413			if (flags & FLAG_OBJWIN) {
414				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
415			} else if (mosaicH > 1) {
416				if (objwinSlowPath) {
417					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN);
418				} else {
419					SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL);
420				}
421			} else if (objwinSlowPath) {
422				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8];
423				SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);
424			} else {
425				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
426			}
427		}
428		if (end == renderer->masterEnd && x + totalWidth > renderer->masterEnd) {
429			renderer->spriteCyclesRemaining -= (x + totalWidth - renderer->masterEnd) * 2;
430		}
431	} else {
432		int outX = x >= start ? x : start;
433		int condition = x + width;
434		int mosaicH = 1;
435		if (GBAObjAttributesAIsMosaic(sprite->a)) {
436			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
437			if (condition % mosaicH) {
438				condition += mosaicH - (condition % mosaicH);
439			}
440		}
441		if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) {
442			inY += 256;
443		}
444		if (GBAObjAttributesBIsVFlip(sprite->b)) {
445			inY = height - inY - 1;
446		}
447		if (end < condition) {
448			condition = end;
449		}
450		int inX = outX - x;
451		int xOffset = 1;
452		if (GBAObjAttributesBIsHFlip(sprite->b)) {
453			inX = width - inX - 1;
454			xOffset = -1;
455		}
456		if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
457			int alpha = GBAObjAttributesCGetPalette(sprite->c);
458			if (flags & FLAG_OBJWIN) {
459				SPRITE_NORMAL_LOOP(BITMAP, OBJWIN);
460			} else if (mosaicH > 1) {
461				if (objwinSlowPath) {
462					SPRITE_MOSAIC_LOOP(BITMAP, NORMAL_OBJWIN);
463				} else {
464					SPRITE_MOSAIC_LOOP(BITMAP, NORMAL);
465				}
466			} else if (objwinSlowPath) {
467				SPRITE_NORMAL_LOOP(BITMAP, NORMAL_OBJWIN);
468			} else {
469				SPRITE_NORMAL_LOOP(BITMAP, NORMAL);
470			}
471		} else if (!GBAObjAttributesAIs256Color(sprite->a)) {
472			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
473			if (flags & FLAG_OBJWIN) {
474				SPRITE_NORMAL_LOOP(16, OBJWIN);
475			} else if (mosaicH > 1) {
476				if (objwinSlowPath) {
477					objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
478					SPRITE_MOSAIC_LOOP(16, NORMAL_OBJWIN);
479				} else {
480					SPRITE_MOSAIC_LOOP(16, NORMAL);
481				}
482			} else if (objwinSlowPath) {
483				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
484				SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN);
485			} else {
486				SPRITE_NORMAL_LOOP(16, NORMAL);
487			}
488		} else if (!renderer->objExtPalette) {
489			if (flags & FLAG_OBJWIN) {
490				SPRITE_NORMAL_LOOP(256, OBJWIN);
491			} else if (mosaicH > 1) {
492				if (objwinSlowPath) {
493					SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN);
494				} else {
495					SPRITE_MOSAIC_LOOP(256, NORMAL);
496				}
497			} else if (objwinSlowPath) {
498				SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN);
499			} else {
500				SPRITE_NORMAL_LOOP(256, NORMAL);
501			}
502		} else {
503			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8];
504			if (flags & FLAG_OBJWIN) {
505				SPRITE_NORMAL_LOOP(256, OBJWIN);
506			} else if (mosaicH > 1) {
507				if (objwinSlowPath) {
508					SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN);
509				} else {
510					SPRITE_MOSAIC_LOOP(256, NORMAL);
511				}
512			} else if (objwinSlowPath) {
513				objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8];
514				SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN);
515			} else {
516				SPRITE_NORMAL_LOOP(256, NORMAL);
517			}
518
519		}
520		if (end = renderer->masterEnd && x + width > renderer->masterEnd) {
521			renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd;
522		}
523	}
524	return 1;
525}
526
527void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) {
528	int x;
529	uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj;
530
531	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt);
532	bool objwinDisable = false;
533	bool objwinOnly = false;
534	if (objwinSlowPath) {
535		objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
536		objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
537		if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
538			return;
539		}
540
541		if (objwinDisable) {
542			for (x = renderer->start; x < renderer->end; ++x) {
543				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
544				uint32_t current = renderer->row[x];
545				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
546					_compositeBlendObjwin(renderer, x, color | flags, current);
547				}
548			}
549			return;
550		} else if (objwinOnly) {
551			for (x = renderer->start; x < renderer->end; ++x) {
552				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
553				uint32_t current = renderer->row[x];
554				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
555					_compositeBlendObjwin(renderer, x, color | flags, current);
556				}
557			}
558			return;
559		} else {
560			for (x = renderer->start; x < renderer->end; ++x) {
561				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
562				uint32_t current = renderer->row[x];
563				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
564					_compositeBlendObjwin(renderer, x, color | flags, current);
565				}
566			}
567			return;
568		}
569	} else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
570		return;
571	}
572	for (x = renderer->start; x < renderer->end; ++x) {
573		uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
574		uint32_t current = renderer->row[x];
575		if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
576			_compositeBlendNoObjwin(renderer, x, color | flags, current);
577		}
578	}
579}