all repos — mgba @ 0ff5f43eb0f14409614282fb27d540eec1fb516f

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