src/ds/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/ds/video.h>
7
8#include <mgba/core/sync.h>
9#include <mgba/internal/ds/ds.h>
10#include <mgba/internal/ds/memory.h>
11#include <mgba/internal/gba/video.h>
12
13#include <mgba-util/memory.h>
14
15mLOG_DEFINE_CATEGORY(DS_VIDEO, "DS Video");
16
17static void DSVideoDummyRendererInit(struct DSVideoRenderer* renderer);
18static void DSVideoDummyRendererReset(struct DSVideoRenderer* renderer);
19static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer);
20static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
21static void DSVideoDummyRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
22static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam);
23static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y);
24static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer);
25static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels);
26static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels);
27
28static void _startHblank7(struct mTiming*, void* context, uint32_t cyclesLate);
29static void _startHdraw7(struct mTiming*, void* context, uint32_t cyclesLate);
30static void _startHblank9(struct mTiming*, void* context, uint32_t cyclesLate);
31static void _startHdraw9(struct mTiming*, void* context, uint32_t cyclesLate);
32
33static const uint32_t _vramSize[9] = {
34 0x20000,
35 0x20000,
36 0x20000,
37 0x20000,
38 0x10000,
39 0x04000,
40 0x04000,
41 0x08000,
42 0x04000
43};
44
45const struct DSVRAMBankInfo {
46 int base;
47 uint32_t mirrorSize;
48 int mode;
49 int offset[4];
50} _vramInfo[9][8] = {
51 { // A
52 { 0x000, 0x40, 4 }, // LCDC
53 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
54 { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ
55 },
56 { // B
57 { 0x008, 0x40, 4 }, // LCDC
58 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
59 { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ
60 },
61 { // C
62 { 0x010, 0x40, 4 }, // LCDC
63 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
64 { 0x000, 0x40, 5, { 0x00, 0x08, 0x80, 0x80 } }, // 7-VRAM
65 {},
66 { 0x000, 0x08, 1 }, // B-BG
67 },
68 { // D
69 { 0x018, 0x40, 4 }, // LCDC
70 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
71 { 0x000, 0x40, 8, { 0x00, 0x08, 0x80, 0x80 } }, // 7-VRAM
72 {},
73 { 0x000, 0x08, 3 }, // B-OBJ
74 },
75 { // E
76 { 0x020, 0x40, 4 }, // LCDC
77 { 0x000, 0x20, 0 }, // A-BG
78 { 0x000, 0x10, 2 }, // A-OBJ
79 },
80 { // F
81 { 0x024, 0x40, 4 }, // LCDC
82 { 0x000, 0x20, 0, { 0x00, 0x01, 0x04, 0x05 } }, // A-BG
83 { 0x000, 0x10, 2, { 0x00, 0x01, 0x04, 0x05 } }, // A-OBJ
84 },
85 { // G
86 { 0x025, 0x40, 4 }, // LCDC
87 { 0x000, 0x20, 0 }, // A-BG
88 { 0x000, 0x10, 2 }, // A-OBJ
89 },
90 { // H
91 { 0x026, 0x40, 4 }, // LCDC
92 { 0x000, 0x04, 1 }, // B-BG
93 { 0x000, 0x10, 2 }, // A-OBJ
94 },
95 { // I
96 { 0x028, 0x40, 4 }, // LCDC
97 { 0x002, 0x04, 1 }, // B-BG
98 { 0x000, 0x01, 3 }, // B-OBJ
99 },
100};
101
102static struct DSVideoRenderer dummyRenderer = {
103 .init = DSVideoDummyRendererInit,
104 .reset = DSVideoDummyRendererReset,
105 .deinit = DSVideoDummyRendererDeinit,
106 .writeVideoRegister = DSVideoDummyRendererWriteVideoRegister,
107 .writePalette = DSVideoDummyRendererWritePalette,
108 .writeOAM = DSVideoDummyRendererWriteOAM,
109 .drawScanline = DSVideoDummyRendererDrawScanline,
110 .finishFrame = DSVideoDummyRendererFinishFrame,
111 .getPixels = DSVideoDummyRendererGetPixels,
112 .putPixels = DSVideoDummyRendererPutPixels,
113};
114
115void DSVideoInit(struct DSVideo* video) {
116 video->renderer = &dummyRenderer;
117 video->vram = NULL;
118 video->frameskip = 0;
119 video->event7.name = "DS7 Video";
120 video->event7.callback = NULL;
121 video->event7.context = video;
122 video->event7.priority = 8;
123 video->event9.name = "DS9 Video";
124 video->event9.callback = NULL;
125 video->event9.context = video;
126 video->event9.priority = 8;
127}
128
129void DSVideoReset(struct DSVideo* video) {
130 video->vcount = 0;
131 video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
132 video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
133
134 video->event7.callback = _startHblank7;
135 video->event9.callback = _startHblank9;
136 mTimingSchedule(&video->p->ds7.timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH);
137 mTimingSchedule(&video->p->ds9.timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2);
138
139 video->frameCounter = 0;
140 video->frameskipCounter = 0;
141
142 if (video->vram) {
143 mappedMemoryFree(video->vram, DS_SIZE_VRAM);
144 }
145 video->vram = anonymousMemoryMap(DS_SIZE_VRAM);
146 video->renderer->vram = video->vram;
147
148 video->p->memory.vramBank[0] = &video->vram[0x00000];
149 video->p->memory.vramBank[1] = &video->vram[0x10000];
150 video->p->memory.vramBank[2] = &video->vram[0x20000];
151 video->p->memory.vramBank[3] = &video->vram[0x30000];
152 video->p->memory.vramBank[4] = &video->vram[0x40000];
153 video->p->memory.vramBank[5] = &video->vram[0x48000];
154 video->p->memory.vramBank[6] = &video->vram[0x4A000];
155 video->p->memory.vramBank[7] = &video->vram[0x4C000];
156 video->p->memory.vramBank[8] = &video->vram[0x50000];
157
158 video->renderer->deinit(video->renderer);
159 video->renderer->init(video->renderer);
160}
161
162void DSVideoAssociateRenderer(struct DSVideo* video, struct DSVideoRenderer* renderer) {
163 video->renderer->deinit(video->renderer);
164 video->renderer = renderer;
165 renderer->palette = video->palette;
166 renderer->vram = video->vram;
167 memcpy(renderer->vramABG, video->vramABG, sizeof(renderer->vramABG));
168 memcpy(renderer->vramAOBJ, video->vramAOBJ, sizeof(renderer->vramAOBJ));
169 memcpy(renderer->vramBBG, video->vramBBG, sizeof(renderer->vramBBG));
170 memcpy(renderer->vramBOBJ, video->vramBOBJ, sizeof(renderer->vramBOBJ));
171 renderer->oam = &video->oam;
172 video->renderer->init(video->renderer);
173}
174
175void DSVideoDeinit(struct DSVideo* video) {
176 DSVideoAssociateRenderer(video, &dummyRenderer);
177 mappedMemoryFree(video->vram, DS_SIZE_VRAM);
178}
179
180void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
181 struct DSVideo* video = context;
182 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
183 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
184 video->event7.callback = _startHblank7;
185 mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
186
187 video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
188
189 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
190 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
191 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
192 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER);
193 }
194 } else {
195 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
196 }
197 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
198
199 switch (video->vcount) {
200 case DS_VIDEO_VERTICAL_PIXELS:
201 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
202 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
203 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK);
204 }
205 break;
206 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
207 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
208 break;
209 }
210}
211
212void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
213 struct DSVideo* video = context;
214 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
215 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
216 video->event7.callback = _startHdraw7;
217 mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
218
219 // Begin Hblank
220 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
221
222 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
223 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK);
224 }
225 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
226}
227
228void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
229 struct DSVideo* video = context;
230 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
231 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
232 video->event9.callback = _startHblank9;
233 mTimingSchedule(timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2 - cyclesLate);
234
235 ++video->vcount;
236 if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
237 video->vcount = 0;
238 }
239 video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
240
241 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
242 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
243 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
244 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER);
245 }
246 } else {
247 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
248 }
249 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
250
251 // Note: state may be recorded during callbacks, so ensure it is consistent!
252 switch (video->vcount) {
253 case 0:
254 DSFrameStarted(video->p);
255 break;
256 case DS_VIDEO_VERTICAL_PIXELS:
257 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
258 if (video->frameskipCounter <= 0) {
259 video->renderer->finishFrame(video->renderer);
260 }
261 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
262 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);
263 }
264 DSFrameEnded(video->p);
265 --video->frameskipCounter;
266 if (video->frameskipCounter < 0) {
267 mCoreSyncPostFrame(video->p->sync);
268 video->frameskipCounter = video->frameskip;
269 }
270 ++video->frameCounter;
271 break;
272 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
273 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
274 break;
275 }
276}
277
278void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
279 struct DSVideo* video = context;
280 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
281 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
282 video->event9.callback = _startHdraw9;
283 mTimingSchedule(timing, &video->event9, (DS9_VIDEO_HBLANK_LENGTH * 2) - cyclesLate);
284
285 // Begin Hblank
286 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
287 if (video->vcount < DS_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
288 video->renderer->drawScanline(video->renderer, video->vcount);
289 }
290
291 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
292 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK);
293 }
294 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
295}
296
297void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) {
298 dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7;
299 dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value;
300 // TODO: Does a VCounter IRQ trigger on write?
301}
302
303void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value) {
304 struct DSMemory* memory = &ds->memory;
305 struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7];
306 memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index]));
307 memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index]));
308 if (!(value & 0x80) || !info.mirrorSize) {
309 return;
310 }
311 uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET;
312 uint32_t i, j;
313 uint32_t offset = info.base + info.offset[(value >> 3) & 3];
314 if (info.mode < 4) {
315 memory->vramMode[index][info.mode] = 0xFFFF;
316 for (j = offset; j < 0x40; j += info.mirrorSize) {
317 for (i = 0; i < size; ++i) {
318 memory->vramMirror[index][i + j] = 1 << index;
319 }
320 }
321 }
322 switch (info.mode) {
323 case 0:
324 for (i = 0; i < size; ++i) {
325 ds->video.vramABG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
326 ds->video.renderer->vramABG[offset + i] = ds->video.vramABG[offset + i];
327 }
328 break;
329 case 1:
330 for (i = 0; i < size; ++i) {
331 ds->video.vramBBG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
332 ds->video.renderer->vramBBG[offset + i] = ds->video.vramBBG[offset + i];
333 }
334 break;
335 case 2:
336 for (i = 0; i < size; ++i) {
337 ds->video.vramAOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
338 ds->video.renderer->vramAOBJ[offset + i] = ds->video.vramAOBJ[offset + i];
339 }
340 break;
341 case 3:
342 for (i = 0; i < size; ++i) {
343 ds->video.vramBOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
344 ds->video.renderer->vramBOBJ[offset + i] = ds->video.vramBOBJ[offset + i];
345 }
346 break;
347 case 5:
348 for (i = 0; i < size; i += 16) {
349 ds->memory.vram7[(offset + i) >> 4] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 5)];
350 }
351 break;
352 }
353}
354
355static void DSVideoDummyRendererInit(struct DSVideoRenderer* renderer) {
356 UNUSED(renderer);
357 // Nothing to do
358}
359
360static void DSVideoDummyRendererReset(struct DSVideoRenderer* renderer) {
361 UNUSED(renderer);
362 // Nothing to do
363}
364
365static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer) {
366 UNUSED(renderer);
367 // Nothing to do
368}
369
370static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
371 UNUSED(renderer);
372 return value;
373}
374
375static void DSVideoDummyRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
376 UNUSED(renderer);
377 UNUSED(address);
378 UNUSED(value);
379 // Nothing to do
380}
381
382static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam) {
383 UNUSED(renderer);
384 UNUSED(oam);
385 // Nothing to do
386}
387
388static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
389 UNUSED(renderer);
390 UNUSED(y);
391 // Nothing to do
392}
393
394static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer) {
395 UNUSED(renderer);
396 // Nothing to do
397}
398
399static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
400 UNUSED(renderer);
401 UNUSED(stride);
402 UNUSED(pixels);
403 // Nothing to do
404}
405
406static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
407 UNUSED(renderer);
408 UNUSED(stride);
409 UNUSED(pixels);
410 // Nothing to do
411}