all repos — mgba @ a05d97a85cc95b935f1b0cf22cf1287239b9b9f6

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