src/gba/video.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 <mgba/internal/gba/video.h>
7
8#include <mgba/core/sync.h>
9#include <mgba/core/cache-set.h>
10#include <mgba/internal/arm/macros.h>
11#include <mgba/internal/gba/dma.h>
12#include <mgba/internal/gba/gba.h>
13#include <mgba/internal/gba/io.h>
14#include <mgba/internal/gba/renderers/cache-set.h>
15#include <mgba/internal/gba/serialize.h>
16
17#include <mgba-util/memory.h>
18
19mLOG_DEFINE_CATEGORY(GBA_VIDEO, "GBA Video", "gba.video");
20
21static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
22static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
23static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
24static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
25static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
26static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
27static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
28static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
29static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer);
30static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
31static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
32
33static void _startHblank(struct mTiming*, void* context, uint32_t cyclesLate);
34static void _midHblank(struct mTiming*, void* context, uint32_t cyclesLate);
35static void _startHdraw(struct mTiming*, void* context, uint32_t cyclesLate);
36
37static uint16_t _zeroes[0x2000] = {0};
38
39MGBA_EXPORT const int GBAVideoObjSizes[16][2] = {
40 { 8, 8 },
41 { 16, 16 },
42 { 32, 32 },
43 { 64, 64 },
44 { 16, 8 },
45 { 32, 8 },
46 { 32, 16 },
47 { 64, 32 },
48 { 8, 16 },
49 { 8, 32 },
50 { 16, 32 },
51 { 32, 64 },
52 { 0, 0 },
53 { 0, 0 },
54 { 0, 0 },
55 { 0, 0 },
56};
57
58void GBAVideoInit(struct GBAVideo* video) {
59 video->renderer = NULL;
60 video->vram = anonymousMemoryMap(SIZE_VRAM);
61 video->frameskip = 0;
62 video->event.name = "GBA Video";
63 video->event.callback = NULL;
64 video->event.context = video;
65 video->event.priority = 8;
66}
67
68void GBAVideoReset(struct GBAVideo* video) {
69 int32_t nextEvent = VIDEO_HDRAW_LENGTH;
70 if (video->p->memory.fullBios) {
71 video->vcount = 0;
72 } else {
73 // TODO: Verify exact scanline on hardware
74 video->vcount = 0x7E;
75 nextEvent = 170;
76 }
77 video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
78
79 video->event.callback = _startHblank;
80 mTimingSchedule(&video->p->timing, &video->event, nextEvent);
81
82 video->frameCounter = 0;
83 video->frameskipCounter = 0;
84
85 video->shouldStall = 0;
86
87 memset(video->palette, 0, sizeof(video->palette));
88 memset(video->oam.raw, 0, sizeof(video->oam.raw));
89
90 if (!video->renderer) {
91 mLOG(GBA_VIDEO, FATAL, "No renderer associated");
92 return;
93 }
94
95 memset(video->renderer->vramBG, 0, sizeof(video->renderer->vramBG));
96 video->renderer->vramBG[0] = &video->vram[0x0000];
97 video->renderer->vramBG[1] = &video->vram[0x2000];
98 video->renderer->vramBG[2] = &video->vram[0x4000];
99 video->renderer->vramBG[3] = &video->vram[0x6000];
100 memset(video->renderer->vramOBJ, 0, sizeof(video->renderer->vramOBJ));
101 video->renderer->vramOBJ[0] = &video->vram[0x8000];
102 video->renderer->vramOBJ[1] = &video->vram[0xA000];
103 video->renderer->vramOBJ[2] = _zeroes;
104 video->renderer->vramOBJ[3] = _zeroes;
105
106 video->renderer->reset(video->renderer);
107}
108
109void GBAVideoDeinit(struct GBAVideo* video) {
110 video->renderer->deinit(video->renderer);
111 mappedMemoryFree(video->vram, SIZE_VRAM);
112}
113
114void GBAVideoDummyRendererCreate(struct GBAVideoRenderer* renderer) {
115 static const struct GBAVideoRenderer dummyRenderer = {
116 .init = GBAVideoDummyRendererInit,
117 .reset = GBAVideoDummyRendererReset,
118 .deinit = GBAVideoDummyRendererDeinit,
119 .writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
120 .writeVRAM = GBAVideoDummyRendererWriteVRAM,
121 .writePalette = GBAVideoDummyRendererWritePalette,
122 .writeOAM = GBAVideoDummyRendererWriteOAM,
123 .drawScanline = GBAVideoDummyRendererDrawScanline,
124 .finishFrame = GBAVideoDummyRendererFinishFrame,
125 .getPixels = GBAVideoDummyRendererGetPixels,
126 .putPixels = GBAVideoDummyRendererPutPixels,
127 };
128 memcpy(renderer, &dummyRenderer, sizeof(*renderer));
129}
130
131void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer* renderer) {
132 if (video->renderer) {
133 video->renderer->deinit(video->renderer);
134 renderer->cache = video->renderer->cache;
135 } else {
136 renderer->cache = NULL;
137 }
138 video->renderer = renderer;
139 renderer->palette = video->palette;
140 memset(renderer->vramBG, 0, sizeof(renderer->vramBG));
141 renderer->vramBG[0] = &video->vram[0x0000];
142 renderer->vramBG[1] = &video->vram[0x2000];
143 renderer->vramBG[2] = &video->vram[0x4000];
144 renderer->vramBG[3] = &video->vram[0x6000];
145 memset(renderer->vramOBJ, 0, sizeof(renderer->vramOBJ));
146 renderer->vramOBJ[0] = &video->vram[0x8000];
147 renderer->vramOBJ[1] = &video->vram[0xA000];
148 renderer->vramOBJ[2] = _zeroes;
149 renderer->vramOBJ[3] = _zeroes;
150 renderer->oam = &video->oam;
151 video->renderer->init(video->renderer);
152}
153
154void _midHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
155 struct GBAVideo* video = context;
156 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
157 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
158 video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
159 video->event.callback = _startHdraw;
160 mTimingSchedule(timing, &video->event, VIDEO_HBLANK_FLIP - cyclesLate);
161}
162
163void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
164 struct GBAVideo* video = context;
165 video->event.callback = _startHblank;
166 mTimingSchedule(timing, &video->event, VIDEO_HDRAW_LENGTH - cyclesLate);
167
168 ++video->vcount;
169 if (video->vcount == VIDEO_VERTICAL_TOTAL_PIXELS) {
170 video->vcount = 0;
171 }
172 video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
173
174 if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS) {
175 if (video->frameskipCounter <= 0) {
176 video->renderer->drawScanline(video->renderer, video->vcount);
177 }
178 video->shouldStall = 1;
179 }
180
181 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
182 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
183 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
184 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
185 GBARaiseIRQ(video->p, IRQ_VCOUNTER, cyclesLate);
186 }
187 } else {
188 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
189 }
190 video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
191
192 // Note: state may be recorded during callbacks, so ensure it is consistent!
193 switch (video->vcount) {
194 case 0:
195 GBAFrameStarted(video->p);
196 break;
197 case GBA_VIDEO_VERTICAL_PIXELS:
198 video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
199 if (video->frameskipCounter <= 0) {
200 video->renderer->finishFrame(video->renderer);
201 }
202 GBADMARunVblank(video->p, -cyclesLate);
203 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
204 GBARaiseIRQ(video->p, IRQ_VBLANK, cyclesLate);
205 }
206 GBAFrameEnded(video->p);
207 mCoreSyncPostFrame(video->p->sync);
208 --video->frameskipCounter;
209 if (video->frameskipCounter < 0) {
210 video->frameskipCounter = video->frameskip;
211 }
212 ++video->frameCounter;
213 video->p->earlyExit = true;
214 break;
215 case VIDEO_VERTICAL_TOTAL_PIXELS - 1:
216 video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
217 break;
218 }
219}
220
221void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
222 struct GBAVideo* video = context;
223 video->event.callback = _midHblank;
224 mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - VIDEO_HBLANK_FLIP - cyclesLate);
225
226 // Begin Hblank
227 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
228 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
229
230 if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS) {
231 GBADMARunHblank(video->p, -cyclesLate);
232 }
233 if (video->vcount >= 2 && video->vcount < GBA_VIDEO_VERTICAL_PIXELS + 2) {
234 GBADMARunDisplayStart(video->p, -cyclesLate);
235 }
236 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
237 GBARaiseIRQ(video->p, IRQ_HBLANK, cyclesLate);
238 }
239 video->shouldStall = 0;
240 video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
241}
242
243void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value) {
244 video->p->memory.io[REG_DISPSTAT >> 1] &= 0x7;
245 video->p->memory.io[REG_DISPSTAT >> 1] |= value;
246 // TODO: Does a VCounter IRQ trigger on write?
247}
248
249static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer) {
250 UNUSED(renderer);
251 // Nothing to do
252}
253
254static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer) {
255 UNUSED(renderer);
256 // Nothing to do
257}
258
259static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer) {
260 UNUSED(renderer);
261 // Nothing to do
262}
263
264static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
265 if (renderer->cache) {
266 GBAVideoCacheWriteVideoRegister(renderer->cache, address, value);
267 }
268 switch (address) {
269 case REG_DISPCNT:
270 value &= 0xFFF7;
271 break;
272 case REG_BG0CNT:
273 case REG_BG1CNT:
274 value &= 0xDFFF;
275 break;
276 case REG_BG2CNT:
277 case REG_BG3CNT:
278 value &= 0xFFFF;
279 break;
280 case REG_BG0HOFS:
281 case REG_BG0VOFS:
282 case REG_BG1HOFS:
283 case REG_BG1VOFS:
284 case REG_BG2HOFS:
285 case REG_BG2VOFS:
286 case REG_BG3HOFS:
287 case REG_BG3VOFS:
288 value &= 0x01FF;
289 break;
290 case REG_BLDCNT:
291 value &= 0x3FFF;
292 break;
293 case REG_BLDALPHA:
294 value &= 0x1F1F;
295 break;
296 case REG_WININ:
297 case REG_WINOUT:
298 value &= 0x3F3F;
299 break;
300 default:
301 break;
302 }
303 return value;
304}
305
306static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
307 if (renderer->cache) {
308 mCacheSetWriteVRAM(renderer->cache, address);
309 }
310}
311
312static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
313 if (renderer->cache) {
314 mCacheSetWritePalette(renderer->cache, address >> 1, mColorFrom555(value));
315 }
316}
317
318static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
319 UNUSED(renderer);
320 UNUSED(oam);
321 // Nothing to do
322}
323
324static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
325 UNUSED(renderer);
326 UNUSED(y);
327 // Nothing to do
328}
329
330static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
331 UNUSED(renderer);
332 // Nothing to do
333}
334
335static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
336 UNUSED(renderer);
337 UNUSED(stride);
338 UNUSED(pixels);
339 // Nothing to do
340}
341
342static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
343 UNUSED(renderer);
344 UNUSED(stride);
345 UNUSED(pixels);
346 // Nothing to do
347}
348
349void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) {
350 memcpy(state->vram, video->vram, SIZE_VRAM);
351 memcpy(state->oam, video->oam.raw, SIZE_OAM);
352 memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
353 STORE_32(video->event.when - mTimingCurrentTime(&video->p->timing), 0, &state->video.nextEvent);
354 int32_t flags = 0;
355 if (video->event.callback == _startHdraw) {
356 flags = GBASerializedVideoFlagsSetMode(flags, 1);
357 } else if (video->event.callback == _startHblank) {
358 flags = GBASerializedVideoFlagsSetMode(flags, 2);
359 } else if (video->event.callback == _midHblank) {
360 flags = GBASerializedVideoFlagsSetMode(flags, 3);
361 }
362 STORE_32(flags, 0, &state->video.flags);
363 STORE_32(video->frameCounter, 0, &state->video.frameCounter);
364}
365
366void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) {
367 memcpy(video->vram, state->vram, SIZE_VRAM);
368 uint16_t value;
369 int i;
370 for (i = 0; i < SIZE_OAM; i += 2) {
371 LOAD_16(value, i, state->oam);
372 GBAStore16(video->p->cpu, BASE_OAM | i, value, 0);
373 }
374 for (i = 0; i < SIZE_PALETTE_RAM; i += 2) {
375 LOAD_16(value, i, state->pram);
376 GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0);
377 }
378 LOAD_32(video->frameCounter, 0, &state->video.frameCounter);
379
380 video->shouldStall = 0;
381 int32_t flags;
382 LOAD_32(flags, 0, &state->video.flags);
383 GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1];
384 switch (GBASerializedVideoFlagsGetMode(flags)) {
385 case 0:
386 if (GBARegisterDISPSTATIsInHblank(dispstat)) {
387 video->event.callback = _startHdraw;
388 } else {
389 video->event.callback = _startHblank;
390 }
391 break;
392 case 1:
393 video->event.callback = _startHdraw;
394 break;
395 case 2:
396 video->event.callback = _startHblank;
397 video->shouldStall = 1;
398 break;
399 case 3:
400 video->event.callback = _midHblank;
401 break;
402 }
403 uint32_t when;
404 LOAD_32(when, 0, &state->video.nextEvent);
405 mTimingSchedule(&video->p->timing, &video->event, when);
406
407 LOAD_16(video->vcount, REG_VCOUNT, state->io);
408 video->renderer->reset(video->renderer);
409}