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) * 2);
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->palette = video->palette;
162 renderer->vram = video->vram;
163 renderer->oam = &video->oam;
164 video->renderer->init(video->renderer);
165}
166
167void DSVideoDeinit(struct DSVideo* video) {
168 DSVideoAssociateRenderer(video, &dummyRenderer);
169 mappedMemoryFree(video->vram, DS_SIZE_VRAM);
170}
171
172void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
173 struct DSVideo* video = context;
174 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
175 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
176 video->event7.callback = _startHblank7;
177 mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
178
179 video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
180
181 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
182 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
183 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
184 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER);
185 }
186 } else {
187 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
188 }
189 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
190
191 switch (video->vcount) {
192 case DS_VIDEO_VERTICAL_PIXELS:
193 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
194 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
195 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK);
196 }
197 break;
198 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
199 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
200 break;
201 }
202}
203
204void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
205 struct DSVideo* video = context;
206 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
207 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
208 video->event7.callback = _startHdraw7;
209 mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
210
211 // Begin Hblank
212 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
213
214 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
215 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK);
216 }
217 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
218}
219
220void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
221 struct DSVideo* video = context;
222 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
223 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
224 video->event9.callback = _startHblank9;
225 mTimingSchedule(timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2 - cyclesLate);
226
227 ++video->vcount;
228 if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
229 video->vcount = 0;
230 }
231 video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
232
233 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
234 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
235 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
236 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER);
237 }
238 } else {
239 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
240 }
241 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
242
243 // Note: state may be recorded during callbacks, so ensure it is consistent!
244 switch (video->vcount) {
245 case 0:
246 DSFrameStarted(video->p);
247 break;
248 case DS_VIDEO_VERTICAL_PIXELS:
249 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
250 if (video->frameskipCounter <= 0) {
251 video->renderer->finishFrame(video->renderer);
252 }
253 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
254 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);
255 }
256 DSFrameEnded(video->p);
257 --video->frameskipCounter;
258 if (video->frameskipCounter < 0) {
259 mCoreSyncPostFrame(video->p->sync);
260 video->frameskipCounter = video->frameskip;
261 }
262 ++video->frameCounter;
263 break;
264 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
265 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
266 break;
267 }
268}
269
270void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
271 struct DSVideo* video = context;
272 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
273 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
274 video->event9.callback = _startHdraw9;
275 mTimingSchedule(timing, &video->event9, (DS9_VIDEO_HBLANK_LENGTH * 2) - cyclesLate);
276
277 // Begin Hblank
278 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
279 if (video->vcount < DS_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
280 video->renderer->drawScanline(video->renderer, video->vcount);
281 }
282
283 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
284 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK);
285 }
286 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
287}
288
289void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) {
290 dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7;
291 dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value;
292 // TODO: Does a VCounter IRQ trigger on write?
293}
294
295void DSVideoConfigureVRAM(struct DSMemory* memory, int index, uint8_t value) {
296 struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7];
297 memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index]));
298 memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index]));
299 if (!(value & 0x80) || !info.mirrorSize) {
300 return;
301 }
302 uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET;
303 memory->vramMode[index][info.mode] = 0xFFFF;
304 uint32_t offset = info.base + info.offset[(value >> 3) & 3];
305 uint32_t i, j;
306 for (j = offset; j < 0x40; j += info.mirrorSize) {
307 for (i = 0; i < size; ++i) {
308 memory->vramMirror[index][i + j] = 1 << index;
309 }
310 }
311}
312
313static void DSVideoDummyRendererInit(struct DSVideoRenderer* renderer) {
314 UNUSED(renderer);
315 // Nothing to do
316}
317
318static void DSVideoDummyRendererReset(struct DSVideoRenderer* renderer) {
319 UNUSED(renderer);
320 // Nothing to do
321}
322
323static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer) {
324 UNUSED(renderer);
325 // Nothing to do
326}
327
328static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
329 UNUSED(renderer);
330 return value;
331}
332
333static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
334 UNUSED(renderer);
335 UNUSED(y);
336 // Nothing to do
337}
338
339static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer) {
340 UNUSED(renderer);
341 // Nothing to do
342}
343
344static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
345 UNUSED(renderer);
346 UNUSED(stride);
347 UNUSED(pixels);
348 // Nothing to do
349}
350
351static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
352 UNUSED(renderer);
353 UNUSED(stride);
354 UNUSED(pixels);
355 // Nothing to do
356}