all repos — mgba @ 9639d7ad49ee24ca045c6db170bee825d615538b

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