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