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