src/gba/renderers/video-software.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#include <mgba/core/cache-set.h>
9#include <mgba/internal/arm/macros.h>
10#include <mgba/internal/gba/io.h>
11#include <mgba/internal/gba/renderers/cache-set.h>
12
13#include <mgba-util/arm-algo.h>
14#include <mgba-util/memory.h>
15
16#define DIRTY_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] |= (1 << (Y & 0x1F))
17#define CLEAN_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] &= ~(1 << (Y & 0x1F))
18
19static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
20static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
21static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
22static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
23static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
24static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
25static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
26static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
27static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
28static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
29static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
30
31static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
32static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
33static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
34static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
35static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
36static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
37static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
38
39static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
40
41static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
42
43static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y);
44static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
45
46void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
47 renderer->d.init = GBAVideoSoftwareRendererInit;
48 renderer->d.reset = GBAVideoSoftwareRendererReset;
49 renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
50 renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
51 renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
52 renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
53 renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
54 renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
55 renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
56 renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
57 renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
58
59 renderer->d.disableBG[0] = false;
60 renderer->d.disableBG[1] = false;
61 renderer->d.disableBG[2] = false;
62 renderer->d.disableBG[3] = false;
63 renderer->d.disableOBJ = false;
64
65 renderer->d.highlightBG[0] = false;
66 renderer->d.highlightBG[1] = false;
67 renderer->d.highlightBG[2] = false;
68 renderer->d.highlightBG[3] = false;
69 int i;
70 for (i = 0; i < 128; ++i) {
71 renderer->d.highlightOBJ[i] = false;
72 }
73 renderer->d.highlightColor = GBA_COLOR_WHITE;
74 renderer->d.highlightAmount = 0;
75
76 renderer->temporaryBuffer = 0;
77}
78
79static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
80 GBAVideoSoftwareRendererReset(renderer);
81
82 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
83
84 int y;
85 for (y = 0; y < GBA_VIDEO_VERTICAL_PIXELS; ++y) {
86 color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
87 int x;
88 for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; ++x) {
89 row[x] = GBA_COLOR_WHITE;
90 }
91 }
92}
93
94static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
95 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
96 int i;
97
98 softwareRenderer->dispcnt = 0x0080;
99
100 softwareRenderer->target1Obj = 0;
101 softwareRenderer->target1Bd = 0;
102 softwareRenderer->target2Obj = 0;
103 softwareRenderer->target2Bd = 0;
104 softwareRenderer->blendEffect = BLEND_NONE;
105 for (i = 0; i < 1024; i += 2) {
106 uint16_t entry;
107 LOAD_16(entry, i, softwareRenderer->d.palette);
108 GBAVideoSoftwareRendererWritePalette(renderer, i, entry);
109 }
110 softwareRenderer->blendDirty = false;
111 _updatePalettes(softwareRenderer);
112
113 softwareRenderer->blda = 0;
114 softwareRenderer->bldb = 0;
115 softwareRenderer->bldy = 0;
116
117 softwareRenderer->winN[0] = (struct WindowN) { .control = { .priority = 0 } };
118 softwareRenderer->winN[1] = (struct WindowN) { .control = { .priority = 1 } };
119 softwareRenderer->objwin = (struct WindowControl) { .priority = 2 };
120 softwareRenderer->winout = (struct WindowControl) { .priority = 3 };
121 softwareRenderer->oamDirty = 1;
122 softwareRenderer->oamMax = 0;
123
124 softwareRenderer->mosaic = 0;
125 softwareRenderer->nextY = 0;
126
127 softwareRenderer->objOffsetX = 0;
128 softwareRenderer->objOffsetY = 0;
129
130 memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
131 memset(softwareRenderer->cache, 0, sizeof(softwareRenderer->cache));
132 memset(softwareRenderer->nextIo, 0, sizeof(softwareRenderer->nextIo));
133
134 for (i = 0; i < 4; ++i) {
135 struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
136 bg->index = i;
137 bg->enabled = 0;
138 bg->priority = 0;
139 bg->charBase = 0;
140 bg->mosaic = 0;
141 bg->multipalette = 0;
142 bg->screenBase = 0;
143 bg->overflow = 0;
144 bg->size = 0;
145 bg->target1 = 0;
146 bg->target2 = 0;
147 bg->x = 0;
148 bg->y = 0;
149 bg->refx = 0;
150 bg->refy = 0;
151 bg->dx = 256;
152 bg->dmx = 0;
153 bg->dy = 0;
154 bg->dmy = 256;
155 bg->sx = 0;
156 bg->sy = 0;
157 bg->yCache = -1;
158 bg->offsetX = 0;
159 bg->offsetY = 0;
160 }
161}
162
163static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
164 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
165 UNUSED(softwareRenderer);
166}
167
168static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
169 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
170 if (renderer->cache) {
171 GBAVideoCacheWriteVideoRegister(renderer->cache, address, value);
172 }
173
174 switch (address) {
175 case REG_DISPCNT:
176 value &= 0xFFF7;
177 softwareRenderer->dispcnt = value;
178 GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
179 break;
180 case REG_BG0CNT:
181 value &= 0xDFFF;
182 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
183 break;
184 case REG_BG1CNT:
185 value &= 0xDFFF;
186 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
187 break;
188 case REG_BG2CNT:
189 value &= 0xFFFF;
190 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
191 break;
192 case REG_BG3CNT:
193 value &= 0xFFFF;
194 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
195 break;
196 case REG_BG0HOFS:
197 value &= 0x01FF;
198 softwareRenderer->bg[0].x = value;
199 break;
200 case REG_BG0VOFS:
201 value &= 0x01FF;
202 softwareRenderer->bg[0].y = value;
203 break;
204 case REG_BG1HOFS:
205 value &= 0x01FF;
206 softwareRenderer->bg[1].x = value;
207 break;
208 case REG_BG1VOFS:
209 value &= 0x01FF;
210 softwareRenderer->bg[1].y = value;
211 break;
212 case REG_BG2HOFS:
213 value &= 0x01FF;
214 softwareRenderer->bg[2].x = value;
215 break;
216 case REG_BG2VOFS:
217 value &= 0x01FF;
218 softwareRenderer->bg[2].y = value;
219 break;
220 case REG_BG3HOFS:
221 value &= 0x01FF;
222 softwareRenderer->bg[3].x = value;
223 break;
224 case REG_BG3VOFS:
225 value &= 0x01FF;
226 softwareRenderer->bg[3].y = value;
227 break;
228 case REG_BG2PA:
229 softwareRenderer->bg[2].dx = value;
230 break;
231 case REG_BG2PB:
232 softwareRenderer->bg[2].dmx = value;
233 break;
234 case REG_BG2PC:
235 softwareRenderer->bg[2].dy = value;
236 break;
237 case REG_BG2PD:
238 softwareRenderer->bg[2].dmy = value;
239 break;
240 case REG_BG2X_LO:
241 GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[2], value);
242 if (softwareRenderer->bg[2].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[0][0]) {
243 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
244 }
245 break;
246 case REG_BG2X_HI:
247 GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[2], value);
248 if (softwareRenderer->bg[2].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[0][0]) {
249 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
250 }
251 break;
252 case REG_BG2Y_LO:
253 GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[2], value);
254 if (softwareRenderer->bg[2].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[0][1]) {
255 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
256 }
257 break;
258 case REG_BG2Y_HI:
259 GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[2], value);
260 if (softwareRenderer->bg[2].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[0][1]) {
261 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
262 }
263 break;
264 case REG_BG3PA:
265 softwareRenderer->bg[3].dx = value;
266 break;
267 case REG_BG3PB:
268 softwareRenderer->bg[3].dmx = value;
269 break;
270 case REG_BG3PC:
271 softwareRenderer->bg[3].dy = value;
272 break;
273 case REG_BG3PD:
274 softwareRenderer->bg[3].dmy = value;
275 break;
276 case REG_BG3X_LO:
277 GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[3], value);
278 if (softwareRenderer->bg[3].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[1][0]) {
279 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
280 }
281 break;
282 case REG_BG3X_HI:
283 GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[3], value);
284 if (softwareRenderer->bg[3].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[1][0]) {
285 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
286 }
287 break;
288 case REG_BG3Y_LO:
289 GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[3], value);
290 if (softwareRenderer->bg[3].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[1][1]) {
291 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
292 }
293 break;
294 case REG_BG3Y_HI:
295 GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[3], value);
296 if (softwareRenderer->bg[3].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[1][1]) {
297 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
298 }
299 break;
300 case REG_BLDCNT:
301 GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
302 value &= 0x3FFF;
303 break;
304 case REG_BLDALPHA:
305 softwareRenderer->blda = value & 0x1F;
306 if (softwareRenderer->blda > 0x10) {
307 softwareRenderer->blda = 0x10;
308 }
309 softwareRenderer->bldb = (value >> 8) & 0x1F;
310 if (softwareRenderer->bldb > 0x10) {
311 softwareRenderer->bldb = 0x10;
312 }
313 value &= 0x1F1F;
314 break;
315 case REG_BLDY:
316 value &= 0x1F;
317 if (value > 0x10) {
318 value = 0x10;
319 }
320 if (softwareRenderer->bldy != value) {
321 softwareRenderer->bldy = value;
322 softwareRenderer->blendDirty = true;
323 }
324 break;
325 case REG_WIN0H:
326 softwareRenderer->winN[0].h.end = value;
327 softwareRenderer->winN[0].h.start = value >> 8;
328 if (softwareRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[0].h.start > softwareRenderer->winN[0].h.end) {
329 softwareRenderer->winN[0].h.start = 0;
330 }
331 if (softwareRenderer->winN[0].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) {
332 softwareRenderer->winN[0].h.end = GBA_VIDEO_HORIZONTAL_PIXELS;
333 if (softwareRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) {
334 softwareRenderer->winN[0].h.start = GBA_VIDEO_HORIZONTAL_PIXELS;
335 }
336 }
337 break;
338 case REG_WIN1H:
339 softwareRenderer->winN[1].h.end = value;
340 softwareRenderer->winN[1].h.start = value >> 8;
341 if (softwareRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[1].h.start > softwareRenderer->winN[1].h.end) {
342 softwareRenderer->winN[1].h.start = 0;
343 }
344 if (softwareRenderer->winN[1].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) {
345 softwareRenderer->winN[1].h.end = GBA_VIDEO_HORIZONTAL_PIXELS;
346 if (softwareRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) {
347 softwareRenderer->winN[1].h.start = GBA_VIDEO_HORIZONTAL_PIXELS;
348 }
349 }
350 break;
351 case REG_WIN0V:
352 softwareRenderer->winN[0].v.end = value;
353 softwareRenderer->winN[0].v.start = value >> 8;
354 if (softwareRenderer->winN[0].v.start > GBA_VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[0].v.start > softwareRenderer->winN[0].v.end) {
355 softwareRenderer->winN[0].v.start = 0;
356 }
357 if (softwareRenderer->winN[0].v.end > GBA_VIDEO_VERTICAL_PIXELS) {
358 softwareRenderer->winN[0].v.end = GBA_VIDEO_VERTICAL_PIXELS;
359 if (softwareRenderer->winN[0].v.start > GBA_VIDEO_VERTICAL_PIXELS) {
360 softwareRenderer->winN[0].v.start = GBA_VIDEO_VERTICAL_PIXELS;
361 }
362 }
363 break;
364 case REG_WIN1V:
365 softwareRenderer->winN[1].v.end = value;
366 softwareRenderer->winN[1].v.start = value >> 8;
367 if (softwareRenderer->winN[1].v.start > GBA_VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[1].v.start > softwareRenderer->winN[1].v.end) {
368 softwareRenderer->winN[1].v.start = 0;
369 }
370 if (softwareRenderer->winN[1].v.end > GBA_VIDEO_VERTICAL_PIXELS) {
371 softwareRenderer->winN[1].v.end = GBA_VIDEO_VERTICAL_PIXELS;
372 if (softwareRenderer->winN[1].v.start > GBA_VIDEO_VERTICAL_PIXELS) {
373 softwareRenderer->winN[1].v.start = GBA_VIDEO_VERTICAL_PIXELS;
374 }
375 }
376 break;
377 case REG_WININ:
378 value &= 0x3F3F;
379 softwareRenderer->winN[0].control.packed = value;
380 softwareRenderer->winN[1].control.packed = value >> 8;
381 break;
382 case REG_WINOUT:
383 value &= 0x3F3F;
384 softwareRenderer->winout.packed = value;
385 softwareRenderer->objwin.packed = value >> 8;
386 break;
387 case REG_MOSAIC:
388 softwareRenderer->mosaic = value;
389 break;
390 case REG_GREENSWP:
391 mLOG(GBA_VIDEO, STUB, "Stub video register write: 0x%03X", address);
392 break;
393 default:
394 mLOG(GBA_VIDEO, GAME_ERROR, "Invalid video register: 0x%03X", address);
395 }
396 softwareRenderer->nextIo[address >> 1] = value;
397 if (softwareRenderer->cache[softwareRenderer->nextY].io[address >> 1] != value) {
398 softwareRenderer->cache[softwareRenderer->nextY].io[address >> 1] = value;
399 DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
400 }
401 return value;
402}
403
404static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
405 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
406 if (renderer->cache) {
407 mCacheSetWriteVRAM(renderer->cache, address);
408 }
409 memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
410 softwareRenderer->bg[0].yCache = -1;
411 softwareRenderer->bg[1].yCache = -1;
412 softwareRenderer->bg[2].yCache = -1;
413 softwareRenderer->bg[3].yCache = -1;
414}
415
416static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
417 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
418 UNUSED(oam);
419 softwareRenderer->oamDirty = 1;
420 memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
421}
422
423static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
424 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
425 color_t color = mColorFrom555(value);
426 softwareRenderer->normalPalette[address >> 1] = color;
427 if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
428 softwareRenderer->variantPalette[address >> 1] = _brighten(color, softwareRenderer->bldy);
429 } else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
430 softwareRenderer->variantPalette[address >> 1] = _darken(color, softwareRenderer->bldy);
431 }
432 if (renderer->cache) {
433 mCacheSetWritePalette(renderer->cache, address >> 1, color);
434 }
435 memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
436}
437
438static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y) {
439 if (win->v.end >= win->v.start) {
440 if (y >= win->v.end) {
441 return;
442 }
443 if (y < win->v.start) {
444 return;
445 }
446 } else if (y >= win->v.end && y < win->v.start) {
447 return;
448 }
449 if (win->h.end > GBA_VIDEO_HORIZONTAL_PIXELS || win->h.end < win->h.start) {
450 struct WindowN splits[2] = { *win, *win };
451 splits[0].h.start = 0;
452 splits[1].h.end = GBA_VIDEO_HORIZONTAL_PIXELS;
453 _breakWindowInner(softwareRenderer, &splits[0]);
454 _breakWindowInner(softwareRenderer, &splits[1]);
455 } else {
456 _breakWindowInner(softwareRenderer, win);
457 }
458}
459
460static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win) {
461 int activeWindow;
462 int startX = 0;
463 if (win->h.end > 0) {
464 for (activeWindow = 0; activeWindow < softwareRenderer->nWindows; ++activeWindow) {
465 if (win->h.start < softwareRenderer->windows[activeWindow].endX) {
466 // Insert a window before the end of the active window
467 struct Window oldWindow = softwareRenderer->windows[activeWindow];
468 if (win->h.start > startX) {
469 // And after the start of the active window
470 int nextWindow = softwareRenderer->nWindows;
471 ++softwareRenderer->nWindows;
472 for (; nextWindow > activeWindow; --nextWindow) {
473 softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
474 }
475 softwareRenderer->windows[activeWindow].endX = win->h.start;
476 ++activeWindow;
477 }
478 softwareRenderer->windows[activeWindow].control = win->control;
479 softwareRenderer->windows[activeWindow].endX = win->h.end;
480 if (win->h.end >= oldWindow.endX) {
481 // Trim off extra windows we've overwritten
482 for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
483 if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) {
484 mLOG(GBA_VIDEO, FATAL, "Out of bounds window write will occur");
485 return;
486 }
487 softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
488 --softwareRenderer->nWindows;
489 }
490 } else {
491 ++activeWindow;
492 int nextWindow = softwareRenderer->nWindows;
493 ++softwareRenderer->nWindows;
494 for (; nextWindow > activeWindow; --nextWindow) {
495 softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
496 }
497 softwareRenderer->windows[activeWindow] = oldWindow;
498 }
499 break;
500 }
501 startX = softwareRenderer->windows[activeWindow].endX;
502 }
503 }
504#ifdef DEBUG
505 if (softwareRenderer->nWindows > MAX_WINDOW) {
506 mLOG(GBA_VIDEO, FATAL, "Out of bounds window write occurred!");
507 }
508#endif
509}
510
511static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
512 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
513
514 if (y == GBA_VIDEO_VERTICAL_PIXELS - 1) {
515 softwareRenderer->nextY = 0;
516 } else {
517 softwareRenderer->nextY = y + 1;
518 }
519
520 bool dirty = softwareRenderer->scanlineDirty[y >> 5] & (1 << (y & 0x1F));
521 if (memcmp(softwareRenderer->nextIo, softwareRenderer->cache[y].io, sizeof(softwareRenderer->nextIo))) {
522 memcpy(softwareRenderer->cache[y].io, softwareRenderer->nextIo, sizeof(softwareRenderer->nextIo));
523 dirty = true;
524 }
525
526 if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
527 if (softwareRenderer->cache[y].scale[0][0] != softwareRenderer->bg[2].sx ||
528 softwareRenderer->cache[y].scale[0][1] != softwareRenderer->bg[2].sy ||
529 softwareRenderer->cache[y].scale[1][0] != softwareRenderer->bg[3].sx ||
530 softwareRenderer->cache[y].scale[1][1] != softwareRenderer->bg[3].sy) {
531 dirty = true;
532 }
533 }
534 softwareRenderer->cache[y].scale[0][0] = softwareRenderer->bg[2].sx;
535 softwareRenderer->cache[y].scale[0][1] = softwareRenderer->bg[2].sy;
536 softwareRenderer->cache[y].scale[1][0] = softwareRenderer->bg[3].sx;
537 softwareRenderer->cache[y].scale[1][1] = softwareRenderer->bg[3].sy;
538
539 if (!dirty) {
540 if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
541 softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
542 softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
543 softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
544 softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
545 }
546 return;
547 }
548
549 CLEAN_SCANLINE(softwareRenderer, y);
550
551 color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
552 if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
553 int x;
554 for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; ++x) {
555 row[x] = GBA_COLOR_WHITE;
556 }
557 return;
558 }
559
560 int x;
561 for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) {
562 softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
563 softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
564 softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
565 softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
566 }
567
568 softwareRenderer->windows[0].endX = GBA_VIDEO_HORIZONTAL_PIXELS;
569 softwareRenderer->nWindows = 1;
570 if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
571 softwareRenderer->windows[0].control = softwareRenderer->winout;
572 if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
573 _breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
574 }
575 if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
576 _breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
577 }
578 } else {
579 softwareRenderer->windows[0].control.packed = 0xFF;
580 }
581
582 if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) {
583 softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount;
584 if (softwareRenderer->lastHighlightAmount) {
585 softwareRenderer->blendDirty = true;
586 }
587 }
588
589 if (softwareRenderer->blendDirty) {
590 _updatePalettes(softwareRenderer);
591 softwareRenderer->blendDirty = false;
592 }
593
594 int w;
595 x = 0;
596 for (w = 0; w < softwareRenderer->nWindows; ++w) {
597 // TOOD: handle objwin on backdrop
598 uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
599 if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
600 backdrop |= softwareRenderer->normalPalette[0];
601 } else {
602 backdrop |= softwareRenderer->variantPalette[0];
603 }
604 int end = softwareRenderer->windows[w].endX;
605 for (; x < end - 3; x += 4) {
606 softwareRenderer->row[x] = backdrop;
607 softwareRenderer->row[x + 1] = backdrop;
608 softwareRenderer->row[x + 2] = backdrop;
609 softwareRenderer->row[x + 3] = backdrop;
610 }
611 for (; x < end; ++x) {
612 softwareRenderer->row[x] = backdrop;
613 }
614 }
615
616 softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
617 softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
618 softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
619 softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
620
621 _drawScanline(softwareRenderer, y);
622
623 if (softwareRenderer->target2Bd) {
624 x = 0;
625 for (w = 0; w < softwareRenderer->nWindows; ++w) {
626 uint32_t backdrop = 0;
627 if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
628 backdrop |= softwareRenderer->normalPalette[0];
629 } else {
630 backdrop |= softwareRenderer->variantPalette[0];
631 }
632 int end = softwareRenderer->windows[w].endX;
633 for (; x < end; ++x) {
634 uint32_t color = softwareRenderer->row[x];
635 if (color & FLAG_TARGET_1) {
636 softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color);
637 }
638 }
639 }
640 }
641 if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
642 x = 0;
643 uint32_t mask = FLAG_REBLEND | FLAG_TARGET_1 | FLAG_IS_BACKGROUND;
644 uint32_t match = FLAG_REBLEND;
645 if (GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
646 mask |= FLAG_OBJWIN;
647 if (GBAWindowControlIsBlendEnable(softwareRenderer->objwin.packed)) {
648 match |= FLAG_OBJWIN;
649 }
650 }
651 for (w = 0; w < softwareRenderer->nWindows; ++w) {
652 if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
653 continue;
654 }
655 int end = softwareRenderer->windows[w].endX;
656 if (softwareRenderer->blendEffect == BLEND_DARKEN) {
657 for (; x < end; ++x) {
658 uint32_t color = softwareRenderer->row[x];
659 if ((color & mask) == match) {
660 softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
661 }
662 }
663 } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
664 for (; x < end; ++x) {
665 uint32_t color = softwareRenderer->row[x];
666 if ((color & mask) == match) {
667 softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
668 }
669 }
670 }
671 }
672 }
673
674#ifdef COLOR_16_BIT
675 for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) {
676 row[x] = softwareRenderer->row[x];
677 row[x + 1] = softwareRenderer->row[x + 1];
678 row[x + 2] = softwareRenderer->row[x + 2];
679 row[x + 3] = softwareRenderer->row[x + 3];
680 }
681#else
682 memcpy(row, softwareRenderer->row, GBA_VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
683#endif
684}
685
686static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
687 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
688
689 softwareRenderer->nextY = 0;
690 if (softwareRenderer->temporaryBuffer) {
691 mappedMemoryFree(softwareRenderer->temporaryBuffer, GBA_VIDEO_HORIZONTAL_PIXELS * GBA_VIDEO_VERTICAL_PIXELS * 4);
692 softwareRenderer->temporaryBuffer = 0;
693 }
694 softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
695 softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
696 softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
697 softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
698
699 if (softwareRenderer->bg[0].enabled > 0) {
700 softwareRenderer->bg[0].enabled = 4;
701 }
702 if (softwareRenderer->bg[1].enabled > 0) {
703 softwareRenderer->bg[1].enabled = 4;
704 }
705 if (softwareRenderer->bg[2].enabled > 0) {
706 softwareRenderer->bg[2].enabled = 4;
707 }
708 if (softwareRenderer->bg[3].enabled > 0) {
709 softwareRenderer->bg[3].enabled = 4;
710 }
711}
712
713static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
714 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
715 *stride = softwareRenderer->outputBufferStride;
716 *pixels = softwareRenderer->outputBuffer;
717}
718
719static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
720 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
721
722 const color_t* colorPixels = pixels;
723 unsigned i;
724 for (i = 0; i < GBA_VIDEO_VERTICAL_PIXELS; ++i) {
725 memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GBA_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
726 }
727}
728
729static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) {
730 int wasActive = renderer->bg[bg].enabled;
731 if (!active) {
732 renderer->bg[bg].enabled = 0;
733 } else if (!wasActive && active) {
734 if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
735 // TODO: Investigate in more depth how switching background works in different modes
736 renderer->bg[bg].enabled = 4;
737 } else {
738 renderer->bg[bg].enabled = 1;
739 }
740 }
741}
742
743static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
744 _enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt));
745 _enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt));
746 _enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt));
747 _enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt));
748}
749
750static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
751 UNUSED(renderer);
752 bg->priority = GBARegisterBGCNTGetPriority(value);
753 bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
754 bg->mosaic = GBARegisterBGCNTGetMosaic(value);
755 bg->multipalette = GBARegisterBGCNTGet256Color(value);
756 bg->screenBase = GBARegisterBGCNTGetScreenBase(value) << 11;
757 bg->overflow = GBARegisterBGCNTGetOverflow(value);
758 bg->size = GBARegisterBGCNTGetSize(value);
759}
760
761static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
762 bg->refx = (bg->refx & 0xFFFF0000) | value;
763 bg->sx = bg->refx;
764}
765
766static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
767 bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
768 bg->refx <<= 4;
769 bg->refx >>= 4;
770 bg->sx = bg->refx;
771}
772
773static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
774 bg->refy = (bg->refy & 0xFFFF0000) | value;
775 bg->sy = bg->refy;
776}
777
778static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
779 bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
780 bg->refy <<= 4;
781 bg->refy >>= 4;
782 bg->sy = bg->refy;
783}
784
785static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
786 enum GBAVideoBlendEffect oldEffect = renderer->blendEffect;
787
788 renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
789 renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
790 renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
791 renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
792 renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
793 renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
794 renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
795 renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
796
797 renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
798 renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
799 renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
800 renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
801 renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
802
803 if (oldEffect != renderer->blendEffect) {
804 renderer->blendDirty = true;
805 }
806}
807
808#define TEST_LAYER_ENABLED(X) \
809 !renderer->d.disableBG[X] && \
810 (renderer->bg[X].enabled == 4 && \
811 (GBAWindowControlIsBg ## X ## Enable(renderer->currentWindow.packed) || \
812 (GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (renderer->objwin.packed))) && \
813 renderer->bg[X].priority == priority)
814
815static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
816 int w;
817 int spriteLayers = 0;
818 if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
819 if (renderer->oamDirty) {
820 renderer->oamMax = GBAVideoRendererCleanOAM(renderer->d.oam->obj, renderer->sprites, renderer->objOffsetY);
821 renderer->oamDirty = false;
822 }
823 renderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(renderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
824 int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
825 int mosaicY = y - (y % mosaicV);
826 int i;
827 for (i = 0; i < renderer->oamMax; ++i) {
828 struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
829 int localY = y;
830 renderer->end = 0;
831 if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
832 continue;
833 }
834 if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
835 localY = mosaicY;
836 if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
837 localY = sprite->y;
838 }
839 if (localY >= (sprite->endY & 0xFF)) {
840 localY = sprite->endY - 1;
841 }
842 }
843 for (w = 0; w < renderer->nWindows; ++w) {
844 if (renderer->spriteCyclesRemaining <= 0) {
845 break;
846 }
847 renderer->currentWindow = renderer->windows[w].control;
848 renderer->start = renderer->end;
849 renderer->end = renderer->windows[w].endX;
850 if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
851 continue;
852 }
853
854 int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
855 spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
856 }
857 if (renderer->spriteCyclesRemaining <= 0) {
858 break;
859 }
860 }
861 }
862
863 unsigned priority;
864 for (priority = 0; priority < 4; ++priority) {
865 renderer->end = 0;
866 for (w = 0; w < renderer->nWindows; ++w) {
867 renderer->start = renderer->end;
868 renderer->end = renderer->windows[w].endX;
869 renderer->currentWindow = renderer->windows[w].control;
870 if (spriteLayers & (1 << priority)) {
871 GBAVideoSoftwareRendererPostprocessSprite(renderer, priority);
872 }
873 if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
874 GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[0], y);
875 }
876 if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
877 GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[1], y);
878 }
879 if (TEST_LAYER_ENABLED(2)) {
880 switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
881 case 0:
882 GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[2], y);
883 break;
884 case 1:
885 case 2:
886 GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[2], y);
887 break;
888 case 3:
889 GBAVideoSoftwareRendererDrawBackgroundMode3(renderer, &renderer->bg[2], y);
890 break;
891 case 4:
892 GBAVideoSoftwareRendererDrawBackgroundMode4(renderer, &renderer->bg[2], y);
893 break;
894 case 5:
895 GBAVideoSoftwareRendererDrawBackgroundMode5(renderer, &renderer->bg[2], y);
896 break;
897 }
898 }
899 if (TEST_LAYER_ENABLED(3)) {
900 switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
901 case 0:
902 GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[3], y);
903 break;
904 case 2:
905 GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[3], y);
906 break;
907 }
908 }
909 }
910 }
911 if (GBARegisterDISPCNTGetMode(renderer->dispcnt) != 0) {
912 renderer->bg[2].sx += renderer->bg[2].dmx;
913 renderer->bg[2].sy += renderer->bg[2].dmy;
914 renderer->bg[3].sx += renderer->bg[3].dmx;
915 renderer->bg[3].sy += renderer->bg[3].dmy;
916 }
917
918 if (renderer->bg[0].enabled > 0 && renderer->bg[0].enabled < 4) {
919 ++renderer->bg[0].enabled;
920 DIRTY_SCANLINE(renderer, y);
921 }
922 if (renderer->bg[1].enabled > 0 && renderer->bg[1].enabled < 4) {
923 ++renderer->bg[1].enabled;
924 DIRTY_SCANLINE(renderer, y);
925 }
926 if (renderer->bg[2].enabled > 0 && renderer->bg[2].enabled < 4) {
927 ++renderer->bg[2].enabled;
928 DIRTY_SCANLINE(renderer, y);
929 }
930 if (renderer->bg[3].enabled > 0 && renderer->bg[3].enabled < 4) {
931 ++renderer->bg[3].enabled;
932 DIRTY_SCANLINE(renderer, y);
933 }
934}
935
936static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
937 int i;
938 if (renderer->blendEffect == BLEND_BRIGHTEN) {
939 for (i = 0; i < 512; ++i) {
940 renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
941 }
942 } else if (renderer->blendEffect == BLEND_DARKEN) {
943 for (i = 0; i < 512; ++i) {
944 renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
945 }
946 } else {
947 for (i = 0; i < 512; ++i) {
948 renderer->variantPalette[i] = renderer->normalPalette[i];
949 }
950 }
951 unsigned highlightAmount = renderer->d.highlightAmount >> 4;
952
953 if (highlightAmount) {
954 for (i = 0; i < 512; ++i) {
955 renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
956 renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
957 }
958 }
959}