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