all repos — mgba @ 9b86abec09dc3f979b1e463fd42a76c9ce8f5273

mGBA Game Boy Advance Emulator

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}