all repos — mgba @ 510a539a504ea914d7ea6f30ece1e15d11c02b39

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