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