all repos — mgba @ 2b64d45906d5b687d4cf240a140dee7a48cf1aad

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 DSVideoDummyRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot);
 24static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y);
 25static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer);
 26static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels);
 27static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels);
 28
 29static void _startHblank7(struct mTiming*, void* context, uint32_t cyclesLate);
 30static void _startHdraw7(struct mTiming*, void* context, uint32_t cyclesLate);
 31static void _startHblank9(struct mTiming*, void* context, uint32_t cyclesLate);
 32static void _startHdraw9(struct mTiming*, void* context, uint32_t cyclesLate);
 33
 34static const uint32_t _vramSize[9] = {
 35	0x20000,
 36	0x20000,
 37	0x20000,
 38	0x20000,
 39	0x10000,
 40	0x04000,
 41	0x04000,
 42	0x08000,
 43	0x04000
 44};
 45
 46enum DSVRAMBankMode {
 47	MODE_A_BG = 0,
 48	MODE_B_BG = 1,
 49	MODE_A_OBJ = 2,
 50	MODE_B_OBJ = 3,
 51	MODE_LCDC,
 52	MODE_7_VRAM,
 53	MODE_A_BG_EXT_PAL,
 54	MODE_B_BG_EXT_PAL,
 55	MODE_A_OBJ_EXT_PAL,
 56	MODE_B_OBJ_EXT_PAL,
 57	MODE_3D_TEX,
 58	MODE_3D_TEX_PAL,
 59};
 60
 61const struct DSVRAMBankInfo {
 62	int base;
 63	uint32_t mirrorSize;
 64	enum DSVRAMBankMode mode;
 65	int offset[4];
 66} _vramInfo[9][8] = {
 67	{ // A
 68		{ 0x000, 0x40, MODE_LCDC },
 69		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } },
 70		{ 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x08, 0x80, 0x80 } },
 71		{ 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } },
 72	},
 73	{ // B
 74		{ 0x008, 0x40, MODE_LCDC },
 75		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } },
 76		{ 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x08, 0x80, 0x80 } },
 77		{ 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } },
 78	},
 79	{ // C
 80		{ 0x010, 0x40, MODE_LCDC },
 81		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } },
 82		{ 0x000, 0x40, MODE_7_VRAM, { 0x00, 0x08, 0x80, 0x80 } },
 83		{ 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } },
 84		{ 0x000, 0x08, MODE_B_BG },
 85	},
 86	{ // D
 87		{ 0x018, 0x40, MODE_LCDC },
 88		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } },
 89		{ 0x000, 0x40, MODE_7_VRAM, { 0x00, 0x08, 0x80, 0x80 } },
 90		{ 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } },
 91		{ 0x000, 0x08, MODE_B_OBJ },
 92	},
 93	{ // E
 94		{ 0x020, 0x40, MODE_LCDC },
 95		{ 0x000, 0x20, MODE_A_BG },
 96		{ 0x000, 0x10, MODE_A_OBJ },
 97		{ 0x000, 0x04, MODE_3D_TEX_PAL },
 98		{ 0x000, 0x04, MODE_A_BG_EXT_PAL },
 99	},
100	{ // F
101		{ 0x024, 0x40, MODE_LCDC },
102		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x01, 0x04, 0x05 } },
103		{ 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x01, 0x04, 0x05 } },
104		{ 0x000, 0x01, MODE_3D_TEX_PAL, { 0x00, 0x01, 0x04, 0x05 } },
105		{ 0x000, 0x02, MODE_A_BG_EXT_PAL, { 0x00, 0x02, 0x00, 0x02 } },
106		{ 0x000, 0x01, MODE_A_OBJ_EXT_PAL},
107	},
108	{ // G
109		{ 0x025, 0x40, MODE_LCDC },
110		{ 0x000, 0x20, MODE_A_BG, { 0x00, 0x01, 0x04, 0x05 } },
111		{ 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x01, 0x04, 0x05 } },
112		{ 0x000, 0x01, MODE_3D_TEX_PAL, { 0x00, 0x01, 0x04, 0x05 } },
113		{ 0x000, 0x02, MODE_A_BG_EXT_PAL, { 0x00, 0x02, 0x00, 0x02 } },
114		{ 0x000, 0x01, MODE_A_OBJ_EXT_PAL},
115	},
116	{ // H
117		{ 0x026, 0x40, MODE_LCDC },
118		{ 0x000, 0x04, MODE_B_BG },
119		{ 0x000, 0x04, MODE_B_BG_EXT_PAL },
120	},
121	{ // I
122		{ 0x028, 0x40, MODE_LCDC },
123		{ 0x002, 0x04, MODE_B_BG },
124		{ 0x000, 0x01, MODE_B_OBJ },
125		{ 0x000, 0x01, MODE_B_OBJ_EXT_PAL },
126	},
127};
128
129static struct DSVideoRenderer dummyRenderer = {
130	.init = DSVideoDummyRendererInit,
131	.reset = DSVideoDummyRendererReset,
132	.deinit = DSVideoDummyRendererDeinit,
133	.writeVideoRegister = DSVideoDummyRendererWriteVideoRegister,
134	.writePalette = DSVideoDummyRendererWritePalette,
135	.writeOAM = DSVideoDummyRendererWriteOAM,
136	.invalidateExtPal = DSVideoDummyRendererInvalidateExtPal,
137	.drawScanline = DSVideoDummyRendererDrawScanline,
138	.finishFrame = DSVideoDummyRendererFinishFrame,
139	.getPixels = DSVideoDummyRendererGetPixels,
140	.putPixels = DSVideoDummyRendererPutPixels,
141};
142
143void DSVideoInit(struct DSVideo* video) {
144	video->renderer = &dummyRenderer;
145	video->vram = NULL;
146	video->frameskip = 0;
147	video->event7.name = "DS7 Video";
148	video->event7.callback = NULL;
149	video->event7.context = video;
150	video->event7.priority = 8;
151	video->event9.name = "DS9 Video";
152	video->event9.callback = NULL;
153	video->event9.context = video;
154	video->event9.priority = 8;
155}
156
157void DSVideoReset(struct DSVideo* video) {
158	video->vcount = 0;
159	video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
160	video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
161
162	video->event7.callback = _startHblank7;
163	video->event9.callback = _startHblank9;
164	mTimingSchedule(&video->p->ds7.timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH);
165	mTimingSchedule(&video->p->ds9.timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2);
166
167	video->frameCounter = 0;
168	video->frameskipCounter = 0;
169
170	if (video->vram) {
171		mappedMemoryFree(video->vram, DS_SIZE_VRAM);
172	}
173	video->vram = anonymousMemoryMap(DS_SIZE_VRAM);
174	video->renderer->vram = video->vram;
175
176	video->p->memory.vramBank[0] = &video->vram[0x00000];
177	video->p->memory.vramBank[1] = &video->vram[0x10000];
178	video->p->memory.vramBank[2] = &video->vram[0x20000];
179	video->p->memory.vramBank[3] = &video->vram[0x30000];
180	video->p->memory.vramBank[4] = &video->vram[0x40000];
181	video->p->memory.vramBank[5] = &video->vram[0x48000];
182	video->p->memory.vramBank[6] = &video->vram[0x4A000];
183	video->p->memory.vramBank[7] = &video->vram[0x4C000];
184	video->p->memory.vramBank[8] = &video->vram[0x50000];
185
186	video->renderer->deinit(video->renderer);
187	video->renderer->init(video->renderer);
188}
189
190void DSVideoAssociateRenderer(struct DSVideo* video, struct DSVideoRenderer* renderer) {
191	video->renderer->deinit(video->renderer);
192	video->renderer = renderer;
193	renderer->palette = video->palette;
194	renderer->vram = video->vram;
195	memcpy(renderer->vramABG, video->vramABG, sizeof(renderer->vramABG));
196	memcpy(renderer->vramAOBJ, video->vramAOBJ, sizeof(renderer->vramAOBJ));
197	memcpy(renderer->vramABGExtPal, video->vramABGExtPal, sizeof(renderer->vramABGExtPal));
198	renderer->vramAOBJExtPal = video->vramAOBJExtPal;
199	memcpy(renderer->vramBBG, video->vramBBG, sizeof(renderer->vramBBG));
200	memcpy(renderer->vramBOBJ, video->vramBOBJ, sizeof(renderer->vramBOBJ));
201	memcpy(renderer->vramBBGExtPal, video->vramBBGExtPal, sizeof(renderer->vramBBGExtPal));
202	renderer->vramBOBJExtPal = video->vramBOBJExtPal;
203	renderer->oam = &video->oam;
204	renderer->gx = &video->p->gx;
205	video->renderer->init(video->renderer);
206}
207
208void DSVideoDeinit(struct DSVideo* video) {
209	DSVideoAssociateRenderer(video, &dummyRenderer);
210	mappedMemoryFree(video->vram, DS_SIZE_VRAM);
211}
212
213void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
214	struct DSVideo* video = context;
215	GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
216	dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
217	video->event7.callback = _startHblank7;
218	mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
219
220	video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
221
222	if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
223		dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
224		if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
225			DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER);
226		}
227	} else {
228		dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
229	}
230	video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
231
232	switch (video->vcount) {
233	case DS_VIDEO_VERTICAL_PIXELS:
234		video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
235		if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
236			DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK);
237		}
238		break;
239	case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
240		video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
241		break;
242	}
243}
244
245void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
246	struct DSVideo* video = context;
247	GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
248	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
249	video->event7.callback = _startHdraw7;
250	mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
251
252	// Begin Hblank
253	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
254
255	if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
256		DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK);
257	}
258	video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
259}
260
261void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
262	struct DSVideo* video = context;
263	GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
264	dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
265	video->event9.callback = _startHblank9;
266	mTimingSchedule(timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2 - cyclesLate);
267
268	++video->vcount;
269	if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
270		video->vcount = 0;
271	}
272	video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
273
274	if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
275		dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
276		if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
277			DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER);
278		}
279	} else {
280		dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
281	}
282	video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
283
284	// Note: state may be recorded during callbacks, so ensure it is consistent!
285	switch (video->vcount) {
286	case 0:
287		DSFrameStarted(video->p);
288		break;
289	case DS_VIDEO_VERTICAL_PIXELS:
290		video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
291		if (video->frameskipCounter <= 0) {
292			video->renderer->finishFrame(video->renderer);
293			if (video->p->gx.swapBuffers) {
294				DSGXSwapBuffers(&video->p->gx);
295			}
296		}
297		if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
298			DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);
299		}
300		DSFrameEnded(video->p);
301		--video->frameskipCounter;
302		if (video->frameskipCounter < 0) {
303			mCoreSyncPostFrame(video->p->sync);
304			video->frameskipCounter = video->frameskip;
305		}
306		++video->frameCounter;
307		break;
308	case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
309		video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
310		break;
311	}
312}
313
314void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
315	struct DSVideo* video = context;
316	GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
317	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
318	video->event9.callback = _startHdraw9;
319	mTimingSchedule(timing, &video->event9, (DS9_VIDEO_HBLANK_LENGTH * 2) - cyclesLate);
320
321	// Begin Hblank
322	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
323	if (video->frameskipCounter <= 0) {
324		if (video->vcount < DS_VIDEO_VERTICAL_PIXELS) {
325			video->renderer->drawScanline(video->renderer, video->vcount);
326		}
327		if (video->vcount < DS_VIDEO_VERTICAL_PIXELS - 48) {
328			video->p->gx.renderer->drawScanline(video->p->gx.renderer, video->vcount + 48);
329		}
330		if (video->vcount >= DS_VIDEO_VERTICAL_TOTAL_PIXELS - 48) {
331			video->p->gx.renderer->drawScanline(video->p->gx.renderer, video->vcount + 48 - DS_VIDEO_VERTICAL_TOTAL_PIXELS);
332		}
333	}
334
335	if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
336		DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK);
337	}
338	video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
339}
340
341void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) {
342	dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7;
343	dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value;
344	// TODO: Does a VCounter IRQ trigger on write?
345}
346
347void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value, uint8_t oldValue) {
348	struct DSMemory* memory = &ds->memory;
349	if (value == oldValue) {
350		return;
351	}
352	uint32_t i, j;
353	uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET;
354	struct DSVRAMBankInfo oldInfo = _vramInfo[index][oldValue & 0x7];
355	uint32_t offset = oldInfo.base + oldInfo.offset[(oldValue >> 3) & 3];
356	switch (oldInfo.mode) {
357	case MODE_A_BG:
358		for (i = 0; i < size; ++i) {
359			if (ds->video.vramABG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
360				ds->video.vramABG[offset + i] = NULL;
361				ds->video.renderer->vramABG[offset + i] = NULL;
362			}
363		}
364		break;
365	case MODE_B_BG:
366		for (i = 0; i < size; ++i) {
367			if (ds->video.vramBBG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
368				ds->video.vramBBG[offset + i] = NULL;
369				ds->video.renderer->vramBBG[offset + i] = NULL;
370			}
371		}
372		break;
373	case MODE_A_OBJ:
374		for (i = 0; i < size; ++i) {
375			if (ds->video.vramAOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
376				ds->video.vramAOBJ[offset + i] = NULL;
377				ds->video.renderer->vramAOBJ[offset + i] = NULL;
378			}
379		}
380		break;
381	case MODE_B_OBJ:
382		for (i = 0; i < size; ++i) {
383			if (ds->video.vramBOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
384				ds->video.vramBOBJ[offset + i] = NULL;
385				ds->video.renderer->vramBOBJ[offset + i] = NULL;
386			}
387		}
388		break;
389	case MODE_A_BG_EXT_PAL:
390		for (i = 0; i < oldInfo.mirrorSize; ++i) {
391			if (ds->video.vramABGExtPal[offset + i] == &memory->vramBank[index][i << 12]) {
392				ds->video.vramABGExtPal[offset + i] = NULL;
393				ds->video.renderer->vramABGExtPal[offset + i] = NULL;
394				ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i);
395			}
396		}
397		break;
398	case MODE_B_BG_EXT_PAL:
399		for (i = 0; i < oldInfo.mirrorSize; ++i) {
400			if (ds->video.vramBBGExtPal[offset + i] == &memory->vramBank[index][i << 12]) {
401				ds->video.vramBBGExtPal[offset + i] = NULL;
402				ds->video.renderer->vramBBGExtPal[offset + i] = NULL;
403				ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i);
404			}
405		}
406		break;
407	case MODE_A_OBJ_EXT_PAL:
408		if (ds->video.vramAOBJExtPal == memory->vramBank[index]) {
409			ds->video.vramAOBJExtPal = NULL;
410			ds->video.renderer->vramAOBJExtPal = NULL;
411			ds->video.renderer->invalidateExtPal(ds->video.renderer, true, false, 0);
412		}
413		break;
414	case MODE_B_OBJ_EXT_PAL:
415		if (ds->video.vramBOBJExtPal == memory->vramBank[index]) {
416			ds->video.vramBOBJExtPal = NULL;
417			ds->video.renderer->vramBOBJExtPal = NULL;
418			ds->video.renderer->invalidateExtPal(ds->video.renderer, true, true, 0);
419		}
420		break;
421	case MODE_7_VRAM:
422		for (i = 0; i < size; i += 16) {
423			ds->memory.vram7[(offset + i) >> 4] = NULL;
424		}
425		break;
426	case MODE_LCDC:
427		break;
428	}
429
430	struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7];
431	memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index]));
432	memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index]));
433	if (!(value & 0x80) || !info.mirrorSize) {
434		return;
435	}
436	offset = info.base + info.offset[(value >> 3) & 3];
437	if (info.mode <= MODE_LCDC) {
438		memory->vramMode[index][info.mode] = 0xFFFF;
439		for (j = offset; j < 0x40; j += info.mirrorSize) {
440			for (i = 0; i < size; ++i) {
441				memory->vramMirror[index][i + j] = 1 << index;
442			}
443		}
444	}
445	switch (info.mode) {
446	case MODE_A_BG:
447		for (i = 0; i < size; ++i) {
448			ds->video.vramABG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
449			ds->video.renderer->vramABG[offset + i] = ds->video.vramABG[offset + i];
450		}
451		break;
452	case MODE_B_BG:
453		for (i = 0; i < size; ++i) {
454			ds->video.vramBBG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
455			ds->video.renderer->vramBBG[offset + i] = ds->video.vramBBG[offset + i];
456		}
457		break;
458	case MODE_A_OBJ:
459		for (i = 0; i < size; ++i) {
460			ds->video.vramAOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
461			ds->video.renderer->vramAOBJ[offset + i] = ds->video.vramAOBJ[offset + i];
462		}
463		break;
464	case MODE_B_OBJ:
465		for (i = 0; i < size; ++i) {
466			ds->video.vramBOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
467			ds->video.renderer->vramBOBJ[offset + i] = ds->video.vramBOBJ[offset + i];
468		}
469		break;
470	case MODE_A_BG_EXT_PAL:
471		for (i = 0; i < info.mirrorSize; ++i) {
472			ds->video.vramABGExtPal[offset + i] = &memory->vramBank[index][i << 12];
473			ds->video.renderer->vramABGExtPal[offset + i] = ds->video.vramABGExtPal[offset + i];
474			ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i);
475		}
476		break;
477	case MODE_B_BG_EXT_PAL:
478		for (i = 0; i < info.mirrorSize; ++i) {
479			ds->video.vramBBGExtPal[offset + i] = &memory->vramBank[index][i << 12];
480			ds->video.renderer->vramBBGExtPal[offset + i] = ds->video.vramBBGExtPal[offset + i];
481			ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i);
482		}
483		break;
484	case MODE_A_OBJ_EXT_PAL:
485		ds->video.vramAOBJExtPal = memory->vramBank[index];
486		ds->video.renderer->vramAOBJExtPal = ds->video.vramAOBJExtPal;
487		ds->video.renderer->invalidateExtPal(ds->video.renderer, true, false, 0);
488		break;
489	case MODE_B_OBJ_EXT_PAL:
490		ds->video.vramBOBJExtPal = memory->vramBank[index];
491		ds->video.renderer->vramBOBJExtPal = ds->video.vramBOBJExtPal;
492		ds->video.renderer->invalidateExtPal(ds->video.renderer, true, true, 0);
493		break;
494	case MODE_7_VRAM:
495		for (i = 0; i < size; i += 16) {
496			ds->memory.vram7[(offset + i) >> 4] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 5)];
497		}
498		break;
499	case MODE_LCDC:
500		break;
501	}
502}
503
504static void DSVideoDummyRendererInit(struct DSVideoRenderer* renderer) {
505	UNUSED(renderer);
506	// Nothing to do
507}
508
509static void DSVideoDummyRendererReset(struct DSVideoRenderer* renderer) {
510	UNUSED(renderer);
511	// Nothing to do
512}
513
514static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer) {
515	UNUSED(renderer);
516	// Nothing to do
517}
518
519static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
520	UNUSED(renderer);
521	return value;
522}
523
524static void DSVideoDummyRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
525	UNUSED(renderer);
526	UNUSED(address);
527	UNUSED(value);
528	// Nothing to do
529}
530
531static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam) {
532	UNUSED(renderer);
533	UNUSED(oam);
534	// Nothing to do
535}
536
537static void DSVideoDummyRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) {
538	UNUSED(renderer);
539	UNUSED(obj);
540	UNUSED(engB);
541	// Nothing to do
542}
543
544static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
545	UNUSED(renderer);
546	UNUSED(y);
547	// Nothing to do
548}
549
550static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer) {
551	UNUSED(renderer);
552	// Nothing to do
553}
554
555static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
556	UNUSED(renderer);
557	UNUSED(stride);
558	UNUSED(pixels);
559	// Nothing to do
560}
561
562static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
563	UNUSED(renderer);
564	UNUSED(stride);
565	UNUSED(pixels);
566	// Nothing to do
567}