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 _startHblank7(struct mTiming*, void* context, uint32_t cyclesLate);
18static void _startHdraw7(struct mTiming*, void* context, uint32_t cyclesLate);
19static void _startHblank9(struct mTiming*, void* context, uint32_t cyclesLate);
20static void _startHdraw9(struct mTiming*, void* context, uint32_t cyclesLate);
21
22static const uint32_t _vramSize[9] = {
23 0x20000,
24 0x20000,
25 0x20000,
26 0x20000,
27 0x10000,
28 0x04000,
29 0x04000,
30 0x08000,
31 0x04000
32};
33
34const struct DSVRAMBankInfo {
35 int base;
36 uint32_t mirrorSize;
37 int mode;
38 int offset[4];
39} _vramInfo[9][8] = {
40 { // A
41 { 0x000, 0x40, 4 }, // LCDC
42 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
43 { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ
44 },
45 { // B
46 { 0x008, 0x40, 4 }, // LCDC
47 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
48 { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ
49 },
50 { // C
51 { 0x010, 0x40, 4 }, // LCDC
52 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
53 {},
54 {},
55 { 0x000, 0x08, 1 }, // B-BG
56 },
57 { // D
58 { 0x018, 0x40, 4 }, // LCDC
59 { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG
60 {},
61 {},
62 { 0x000, 0x08, 3 }, // B-OBJ
63 },
64 { // E
65 { 0x020, 0x40, 4 }, // LCDC
66 { 0x000, 0x20, 0 }, // A-BG
67 { 0x000, 0x10, 2 }, // A-OBJ
68 },
69 { // F
70 { 0x024, 0x40, 4 }, // LCDC
71 { 0x000, 0x20, 0, { 0x00, 0x01, 0x04, 0x05 } }, // A-BG
72 { 0x000, 0x10, 2, { 0x00, 0x01, 0x04, 0x05 } }, // A-OBJ
73 },
74 { // G
75 { 0x025, 0x40, 4 }, // LCDC
76 { 0x000, 0x20, 0 }, // A-BG
77 { 0x000, 0x10, 2 }, // A-OBJ
78 },
79 { // H
80 { 0x026, 0x40, 4 }, // LCDC
81 { 0x000, 0x04, 1 }, // B-BG
82 { 0x000, 0x10, 2 }, // A-OBJ
83 },
84 { // I
85 { 0x028, 0x40, 4 }, // LCDC
86 { 0x002, 0x04, 1 }, // B-BG
87 { 0x000, 0x01, 3 }, // B-OBJ
88 },
89};
90
91void DSVideoInit(struct DSVideo* video) {
92 video->vram = NULL;
93 video->frameskip = 0;
94 video->event7.name = "DS7 Video";
95 video->event7.callback = NULL;
96 video->event7.context = video;
97 video->event7.priority = 8;
98 video->event9.name = "DS9 Video";
99 video->event9.callback = NULL;
100 video->event9.context = video;
101 video->event9.priority = 8;
102}
103
104void DSVideoReset(struct DSVideo* video) {
105 video->vcount = 0;
106 video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
107 video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
108
109 video->event7.callback = _startHblank7;
110 video->event9.callback = _startHblank9;
111 mTimingSchedule(&video->p->ds7.timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH);
112 mTimingSchedule(&video->p->ds9.timing, &video->event9, DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH);
113
114 video->frameCounter = 0;
115 video->frameskipCounter = 0;
116
117 if (video->vram) {
118 mappedMemoryFree(video->vram, DS_SIZE_VRAM);
119 }
120 video->vram = anonymousMemoryMap(DS_SIZE_VRAM);
121
122 video->p->memory.vramBank[0] = &video->vram[0x00000];
123 video->p->memory.vramBank[1] = &video->vram[0x10000];
124 video->p->memory.vramBank[2] = &video->vram[0x20000];
125 video->p->memory.vramBank[3] = &video->vram[0x30000];
126 video->p->memory.vramBank[4] = &video->vram[0x40000];
127 video->p->memory.vramBank[5] = &video->vram[0x48000];
128 video->p->memory.vramBank[6] = &video->vram[0x4A000];
129 video->p->memory.vramBank[7] = &video->vram[0x4C000];
130 video->p->memory.vramBank[8] = &video->vram[0x50000];
131}
132
133void DSVideoDeinit(struct DSVideo* video) {
134 mappedMemoryFree(video->vram, DS_SIZE_VRAM);
135}
136
137void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
138 struct DSVideo* video = context;
139 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
140 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
141 video->event7.callback = _startHblank7;
142 mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
143
144 ++video->vcount;
145 if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
146 video->vcount = 0;
147 }
148 video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
149
150 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
151 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
152 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
153 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER);
154 }
155 } else {
156 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
157 }
158 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
159
160 switch (video->vcount) {
161 case DS_VIDEO_VERTICAL_PIXELS:
162 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
163 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
164 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK);
165 }
166 break;
167 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
168 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
169 break;
170 }
171}
172
173void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
174 struct DSVideo* video = context;
175 GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
176 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
177 video->event7.callback = _startHdraw7;
178 mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
179
180 // Begin Hblank
181 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
182
183 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
184 DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK);
185 }
186 video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
187}
188
189void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
190 struct DSVideo* video = context;
191 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
192 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
193 video->event9.callback = _startHblank9;
194 mTimingSchedule(timing, &video->event9, DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH - cyclesLate);
195
196 ++video->vcount;
197 if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
198 video->vcount = 0;
199 }
200 video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
201
202 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
203 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
204 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
205 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER);
206 }
207 } else {
208 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
209 }
210 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
211
212 // Note: state may be recorded during callbacks, so ensure it is consistent!
213 switch (video->vcount) {
214 case 0:
215 DSFrameStarted(video->p);
216 break;
217 case DS_VIDEO_VERTICAL_PIXELS:
218 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
219 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
220 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);
221 }
222 DSFrameEnded(video->p);
223 --video->frameskipCounter;
224 if (video->frameskipCounter < 0) {
225 mCoreSyncPostFrame(video->p->sync);
226 video->frameskipCounter = video->frameskip;
227 }
228 ++video->frameCounter;
229 break;
230 case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
231 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
232 break;
233 }
234}
235
236void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
237 struct DSVideo* video = context;
238 GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
239 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
240 video->event9.callback = _startHdraw9;
241 mTimingSchedule(timing, &video->event9, DS9_VIDEO_HBLANK_LENGTH - cyclesLate);
242
243 // Begin Hblank
244 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
245
246 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
247 DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK);
248 }
249 video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
250}
251
252void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) {
253 dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7;
254 dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value;
255 // TODO: Does a VCounter IRQ trigger on write?
256}
257
258void DSVideoConfigureVRAM(struct DSMemory* memory, int index, uint8_t value) {
259 struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7];
260 memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index]));
261 memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index]));
262 if (!(value & 0x80)) {
263 return;
264 }
265 uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET;
266 memory->vramMode[index][info.mode] = 0xFFFF;
267 uint32_t offset = info.base + info.offset[(value >> 3) & 3];
268 uint32_t i, j;
269 for (j = offset; j < 0x40; j += info.mirrorSize) {
270 for (i = 0; i < size; ++i) {
271 memory->vramMirror[index][i + j] = 1 << index;
272 }
273 }
274}