all repos — mgba @ 6d2b0aa78708f2f70c0d924d9be7f4903a0aa16a

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	.drawScanline = DSVideoDummyRendererDrawScanline,
137	.finishFrame = DSVideoDummyRendererFinishFrame,
138	.getPixels = DSVideoDummyRendererGetPixels,
139	.putPixels = DSVideoDummyRendererPutPixels,
140};
141
142void DSVideoInit(struct DSVideo* video) {
143	video->renderer = &dummyRenderer;
144	video->vram = NULL;
145	video->frameskip = 0;
146	video->event7.name = "DS7 Video";
147	video->event7.callback = NULL;
148	video->event7.context = video;
149	video->event7.priority = 8;
150	video->event9.name = "DS9 Video";
151	video->event9.callback = NULL;
152	video->event9.context = video;
153	video->event9.priority = 8;
154}
155
156void DSVideoReset(struct DSVideo* video) {
157	video->vcount = 0;
158	video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
159	video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
160
161	video->event7.callback = _startHblank7;
162	video->event9.callback = _startHblank9;
163	mTimingSchedule(&video->p->ds7.timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH);
164	mTimingSchedule(&video->p->ds9.timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2);
165
166	video->frameCounter = 0;
167	video->frameskipCounter = 0;
168
169	if (video->vram) {
170		mappedMemoryFree(video->vram, DS_SIZE_VRAM);
171	}
172	video->vram = anonymousMemoryMap(DS_SIZE_VRAM);
173	video->renderer->vram = video->vram;
174
175	video->p->memory.vramBank[0] = &video->vram[0x00000];
176	video->p->memory.vramBank[1] = &video->vram[0x10000];
177	video->p->memory.vramBank[2] = &video->vram[0x20000];
178	video->p->memory.vramBank[3] = &video->vram[0x30000];
179	video->p->memory.vramBank[4] = &video->vram[0x40000];
180	video->p->memory.vramBank[5] = &video->vram[0x48000];
181	video->p->memory.vramBank[6] = &video->vram[0x4A000];
182	video->p->memory.vramBank[7] = &video->vram[0x4C000];
183	video->p->memory.vramBank[8] = &video->vram[0x50000];
184
185	video->renderer->deinit(video->renderer);
186	video->renderer->init(video->renderer);
187}
188
189void DSVideoAssociateRenderer(struct DSVideo* video, struct DSVideoRenderer* renderer) {
190	video->renderer->deinit(video->renderer);
191	video->renderer = renderer;
192	renderer->palette = video->palette;
193	renderer->vram = video->vram;
194	memcpy(renderer->vramABG, video->vramABG, sizeof(renderer->vramABG));
195	memcpy(renderer->vramAOBJ, video->vramAOBJ, sizeof(renderer->vramAOBJ));
196	memcpy(renderer->vramABGExtPal, video->vramABGExtPal, sizeof(renderer->vramABGExtPal));
197	memcpy(renderer->vramAOBJExtPal, video->vramAOBJExtPal, sizeof(renderer->vramAOBJExtPal));
198	memcpy(renderer->vramBBG, video->vramBBG, sizeof(renderer->vramBBG));
199	memcpy(renderer->vramBOBJ, video->vramBOBJ, sizeof(renderer->vramBOBJ));
200	memcpy(renderer->vramBBGExtPal, video->vramBBGExtPal, sizeof(renderer->vramBBGExtPal));
201	memcpy(renderer->vramBOBJExtPal, video->vramBOBJExtPal, sizeof(renderer->vramBOBJExtPal));
202	renderer->oam = &video->oam;
203	video->renderer->init(video->renderer);
204}
205
206void DSVideoDeinit(struct DSVideo* video) {
207	DSVideoAssociateRenderer(video, &dummyRenderer);
208	mappedMemoryFree(video->vram, DS_SIZE_VRAM);
209}
210
211void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
212	struct DSVideo* video = context;
213	GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
214	dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
215	video->event7.callback = _startHblank7;
216	mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
217
218	video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
219
220	if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
221		dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
222		if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
223			DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER);
224		}
225	} else {
226		dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
227	}
228	video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
229
230	switch (video->vcount) {
231	case DS_VIDEO_VERTICAL_PIXELS:
232		video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
233		if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
234			DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK);
235		}
236		break;
237	case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
238		video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
239		break;
240	}
241}
242
243void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) {
244	struct DSVideo* video = context;
245	GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1];
246	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
247	video->event7.callback = _startHdraw7;
248	mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate);
249
250	// Begin Hblank
251	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
252
253	if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
254		DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK);
255	}
256	video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
257}
258
259void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
260	struct DSVideo* video = context;
261	GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
262	dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
263	video->event9.callback = _startHblank9;
264	mTimingSchedule(timing, &video->event9, (DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH) * 2 - cyclesLate);
265
266	++video->vcount;
267	if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) {
268		video->vcount = 0;
269	}
270	video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount;
271
272	if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
273		dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
274		if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
275			DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER);
276		}
277	} else {
278		dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
279	}
280	video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
281
282	// Note: state may be recorded during callbacks, so ensure it is consistent!
283	switch (video->vcount) {
284	case 0:
285		DSFrameStarted(video->p);
286		break;
287	case DS_VIDEO_VERTICAL_PIXELS:
288		video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
289		if (video->frameskipCounter <= 0) {
290			video->renderer->finishFrame(video->renderer);
291		}
292		if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
293			DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);
294		}
295		DSFrameEnded(video->p);
296		--video->frameskipCounter;
297		if (video->frameskipCounter < 0) {
298			mCoreSyncPostFrame(video->p->sync);
299			video->frameskipCounter = video->frameskip;
300		}
301		++video->frameCounter;
302		break;
303	case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1:
304		video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
305		break;
306	}
307}
308
309void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) {
310	struct DSVideo* video = context;
311	GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1];
312	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
313	video->event9.callback = _startHdraw9;
314	mTimingSchedule(timing, &video->event9, (DS9_VIDEO_HBLANK_LENGTH * 2) - cyclesLate);
315
316	// Begin Hblank
317	dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
318	if (video->vcount < DS_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
319		video->renderer->drawScanline(video->renderer, video->vcount);
320	}
321
322	if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
323		DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK);
324	}
325	video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat;
326}
327
328void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) {
329	dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7;
330	dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value;
331	// TODO: Does a VCounter IRQ trigger on write?
332}
333
334void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value, uint8_t oldValue) {
335	struct DSMemory* memory = &ds->memory;
336	if (value == oldValue) {
337		return;
338	}
339	uint32_t i, j;
340	uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET;
341	struct DSVRAMBankInfo oldInfo = _vramInfo[index][oldValue & 0x7];
342	uint32_t offset = oldInfo.base + oldInfo.offset[(oldValue >> 3) & 3];
343	switch (oldInfo.mode) {
344	case MODE_A_BG:
345		for (i = 0; i < size; ++i) {
346			if (ds->video.vramABG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
347				ds->video.vramABG[offset + i] = NULL;
348				ds->video.renderer->vramABG[offset + i] = NULL;
349			}
350		}
351		break;
352	case MODE_B_BG:
353		for (i = 0; i < size; ++i) {
354			if (ds->video.vramBBG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
355				ds->video.vramBBG[offset + i] = NULL;
356				ds->video.renderer->vramBBG[offset + i] = NULL;
357			}
358		}
359		break;
360	case MODE_A_OBJ:
361		for (i = 0; i < size; ++i) {
362			if (ds->video.vramAOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
363				ds->video.vramAOBJ[offset + i] = NULL;
364				ds->video.renderer->vramAOBJ[offset + i] = NULL;
365			}
366		}
367		break;
368	case MODE_B_OBJ:
369		for (i = 0; i < size; ++i) {
370			if (ds->video.vramBOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) {
371				ds->video.vramBOBJ[offset + i] = NULL;
372				ds->video.renderer->vramBOBJ[offset + i] = NULL;
373			}
374		}
375		break;
376	case MODE_A_BG_EXT_PAL:
377		for (i = 0; i < oldInfo.mirrorSize; ++i) {
378			if (ds->video.vramABGExtPal[offset + i] == &memory->vramBank[index][i << 12]) {
379				ds->video.vramABGExtPal[offset + i] = NULL;
380				ds->video.renderer->vramABGExtPal[offset + i] = NULL;
381				ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i);
382			}
383		}
384		break;
385	case MODE_B_BG_EXT_PAL:
386		for (i = 0; i < oldInfo.mirrorSize; ++i) {
387			if (ds->video.vramBBGExtPal[offset + i] == &memory->vramBank[index][i << 12]) {
388				ds->video.vramBBGExtPal[offset + i] = NULL;
389				ds->video.renderer->vramBBGExtPal[offset + i] = NULL;
390				ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i);
391			}
392		}
393		break;
394	case MODE_7_VRAM:
395		for (i = 0; i < size; i += 16) {
396			ds->memory.vram7[(offset + i) >> 4] = NULL;
397		}
398		break;
399	case MODE_LCDC:
400		break;
401	}
402
403	struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7];
404	memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index]));
405	memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index]));
406	if (!(value & 0x80) || !info.mirrorSize) {
407		return;
408	}
409	offset = info.base + info.offset[(value >> 3) & 3];
410	if (info.mode <= MODE_LCDC) {
411		memory->vramMode[index][info.mode] = 0xFFFF;
412		for (j = offset; j < 0x40; j += info.mirrorSize) {
413			for (i = 0; i < size; ++i) {
414				memory->vramMirror[index][i + j] = 1 << index;
415			}
416		}
417	}
418	switch (info.mode) {
419	case MODE_A_BG:
420		for (i = 0; i < size; ++i) {
421			ds->video.vramABG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
422			ds->video.renderer->vramABG[offset + i] = ds->video.vramABG[offset + i];
423		}
424		break;
425	case MODE_B_BG:
426		for (i = 0; i < size; ++i) {
427			ds->video.vramBBG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
428			ds->video.renderer->vramBBG[offset + i] = ds->video.vramBBG[offset + i];
429		}
430		break;
431	case MODE_A_OBJ:
432		for (i = 0; i < size; ++i) {
433			ds->video.vramAOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
434			ds->video.renderer->vramAOBJ[offset + i] = ds->video.vramAOBJ[offset + i];
435		}
436		break;
437	case MODE_B_OBJ:
438		for (i = 0; i < size; ++i) {
439			ds->video.vramBOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)];
440			ds->video.renderer->vramBOBJ[offset + i] = ds->video.vramBOBJ[offset + i];
441		}
442		break;
443	case MODE_A_BG_EXT_PAL:
444		for (i = 0; i < info.mirrorSize; ++i) {
445			ds->video.vramABGExtPal[offset + i] = &memory->vramBank[index][i << 12];
446			ds->video.renderer->vramABGExtPal[offset + i] = ds->video.vramABGExtPal[offset + i];
447			ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i);
448		}
449		break;
450	case MODE_B_BG_EXT_PAL:
451		for (i = 0; i < info.mirrorSize; ++i) {
452			ds->video.vramBBGExtPal[offset + i] = &memory->vramBank[index][i << 12];
453			ds->video.renderer->vramBBGExtPal[offset + i] = ds->video.vramBBGExtPal[offset + i];
454			ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i);
455		}
456		break;
457	case MODE_7_VRAM:
458		for (i = 0; i < size; i += 16) {
459			ds->memory.vram7[(offset + i) >> 4] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 5)];
460		}
461		break;
462	case MODE_LCDC:
463		break;
464	}
465}
466
467static void DSVideoDummyRendererInit(struct DSVideoRenderer* renderer) {
468	UNUSED(renderer);
469	// Nothing to do
470}
471
472static void DSVideoDummyRendererReset(struct DSVideoRenderer* renderer) {
473	UNUSED(renderer);
474	// Nothing to do
475}
476
477static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer) {
478	UNUSED(renderer);
479	// Nothing to do
480}
481
482static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
483	UNUSED(renderer);
484	return value;
485}
486
487static void DSVideoDummyRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
488	UNUSED(renderer);
489	UNUSED(address);
490	UNUSED(value);
491	// Nothing to do
492}
493
494static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam) {
495	UNUSED(renderer);
496	UNUSED(oam);
497	// Nothing to do
498}
499
500static void DSVideoDummyRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) {
501	UNUSED(renderer);
502	UNUSED(obj);
503	UNUSED(engB);
504	// Nothing to do
505}
506
507static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
508	UNUSED(renderer);
509	UNUSED(y);
510	// Nothing to do
511}
512
513static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer) {
514	UNUSED(renderer);
515	// Nothing to do
516}
517
518static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
519	UNUSED(renderer);
520	UNUSED(stride);
521	UNUSED(pixels);
522	// Nothing to do
523}
524
525static void DSVideoDummyRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
526	UNUSED(renderer);
527	UNUSED(stride);
528	UNUSED(pixels);
529	// Nothing to do
530}