all repos — mgba @ 140d89b3ba90ebf8cb44ea3f2a462d430d637527

mGBA Game Boy Advance Emulator

src/platform/3ds/main.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
  7#include <mgba/core/blip_buf.h>
  8#include <mgba/core/core.h>
  9#ifdef M_CORE_GBA
 10#include <mgba/internal/gba/gba.h>
 11#include <mgba/internal/gba/input.h>
 12#include <mgba/internal/gba/video.h>
 13#endif
 14#ifdef M_CORE_GB
 15#include <mgba/internal/gb/gb.h>
 16#endif
 17#include "feature/gui/gui-runner.h"
 18#include <mgba-util/gui.h>
 19#include <mgba-util/gui/file-select.h>
 20#include <mgba-util/gui/font.h>
 21#include <mgba-util/gui/menu.h>
 22#include <mgba-util/memory.h>
 23
 24#include <mgba-util/platform/3ds/3ds-vfs.h>
 25#include "ctr-gpu.h"
 26
 27#include <3ds.h>
 28#include <3ds/gpu/gx.h>
 29
 30static enum ScreenMode {
 31	SM_PA_BOTTOM,
 32	SM_AF_BOTTOM,
 33	SM_SF_BOTTOM,
 34	SM_PA_TOP,
 35	SM_AF_TOP,
 36	SM_SF_TOP,
 37	SM_MAX
 38} screenMode = SM_PA_TOP;
 39
 40static enum FilterMode {
 41	FM_NEAREST,
 42	FM_LINEAR_1x,
 43	FM_LINEAR_2x,
 44	FM_MAX
 45} filterMode = FM_LINEAR_2x;
 46
 47static enum DarkenMode {
 48	DM_NATIVE,
 49	DM_MULT,
 50	DM_MULT_SCALE,
 51	DM_MULT_SCALE_BIAS,
 52	DM_MAX
 53} darkenMode = DM_NATIVE;
 54
 55#define _3DS_INPUT 0x3344534B
 56
 57#define AUDIO_SAMPLES 384
 58#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
 59#define DSP_BUFFERS 4
 60
 61static struct GBA3DSRotationSource {
 62	struct mRotationSource d;
 63	accelVector accel;
 64	angularRate gyro;
 65} rotation;
 66
 67static enum {
 68	NO_SOUND,
 69	DSP_SUPPORTED,
 70	CSND_SUPPORTED
 71} hasSound;
 72
 73// TODO: Move into context
 74static void* outputBuffer;
 75static struct mAVStream stream;
 76static int16_t* audioLeft = 0;
 77static int16_t* audioRight = 0;
 78static size_t audioPos = 0;
 79static C3D_Tex outputTexture;
 80static ndspWaveBuf dspBuffer[DSP_BUFFERS];
 81static int bufferId = 0;
 82static bool frameLimiter = true;
 83static u64 tickCounter;
 84
 85static C3D_RenderTarget* topScreen[2];
 86static C3D_RenderTarget* bottomScreen[2];
 87static int doubleBuffer = 0;
 88static bool frameStarted = false;
 89
 90static C3D_RenderTarget* upscaleBuffer;
 91static C3D_Tex upscaleBufferTex;
 92
 93static aptHookCookie cookie;
 94
 95extern bool allocateRomBuffer(void);
 96
 97static bool _initGpu(void) {
 98	if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
 99		return false;
100	}
101
102	topScreen[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
103	topScreen[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
104	bottomScreen[0] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
105	bottomScreen[1] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
106	if (!topScreen[0] || !topScreen[1] || !bottomScreen[0] || !bottomScreen[1]) {
107		return false;
108	}
109
110	if (!C3D_TexInitVRAM(&upscaleBufferTex, 512, 512, GPU_RB_RGB8)) {
111		return false;
112	}
113	upscaleBuffer = C3D_RenderTargetCreateFromTex(&upscaleBufferTex, GPU_TEXFACE_2D, 0, 0);
114	if (!upscaleBuffer) {
115		return false;
116	}
117
118	C3D_RenderTargetSetClear(upscaleBuffer, C3D_CLEAR_COLOR, 0, 0);
119
120	return ctrInitGpu();
121}
122
123static void _cleanup(void) {
124	ctrDeinitGpu();
125
126	if (outputBuffer) {
127		linearFree(outputBuffer);
128	}
129
130	C3D_RenderTargetDelete(topScreen[0]);
131	C3D_RenderTargetDelete(topScreen[1]);
132	C3D_RenderTargetDelete(bottomScreen[0]);
133	C3D_RenderTargetDelete(bottomScreen[1]);
134	C3D_RenderTargetDelete(upscaleBuffer);
135	C3D_TexDelete(&upscaleBufferTex);
136	C3D_TexDelete(&outputTexture);
137	C3D_Fini();
138
139	gfxExit();
140
141	if (hasSound != NO_SOUND) {
142		linearFree(audioLeft);
143	}
144
145	if (hasSound == CSND_SUPPORTED) {
146		linearFree(audioRight);
147		csndExit();
148	}
149
150	if (hasSound == DSP_SUPPORTED) {
151		ndspExit();
152	}
153
154	csndExit();
155	ptmuExit();
156}
157
158static void _aptHook(APT_HookType hook, void* user) {
159	UNUSED(user);
160	switch (hook) {
161	case APTHOOK_ONSUSPEND:
162	case APTHOOK_ONSLEEP:
163		if (hasSound == CSND_SUPPORTED) {
164			CSND_SetPlayState(8, 0);
165			CSND_SetPlayState(9, 0);
166			csndExecCmds(false);
167		}
168		break;
169	case APTHOOK_ONEXIT:
170		if (hasSound == CSND_SUPPORTED) {
171			CSND_SetPlayState(8, 0);
172			CSND_SetPlayState(9, 0);
173			csndExecCmds(false);
174		}
175		_cleanup();
176		exit(0);
177		break;
178	default:
179		break;
180	}
181}
182
183static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
184	mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
185}
186
187static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
188	u32 pleft = 0, pright = 0;
189
190	int loopMode = (flags >> 10) & 3;
191	if (!loopMode) {
192		flags |= SOUND_ONE_SHOT;
193	}
194
195	pleft = osConvertVirtToPhys(left);
196	pright = osConvertVirtToPhys(right);
197
198	u32 timer = CSND_TIMER(sampleRate);
199	if (timer < 0x0042) {
200		timer = 0x0042;
201	}
202	else if (timer > 0xFFFF) {
203		timer = 0xFFFF;
204	}
205	flags &= ~0xFFFF001F;
206	flags |= SOUND_ENABLE | (timer << 16);
207
208	u32 volumes = CSND_VOL(vol, -1.0);
209	CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
210	volumes = CSND_VOL(vol, 1.0);
211	CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
212}
213
214static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
215
216static void _drawStart(void) {
217}
218
219static void _frameStart(void) {
220	if (frameStarted) {
221		return;
222	}
223	frameStarted = true;
224	u8 flags = 0;
225	if (!frameLimiter) {
226		if (tickCounter + 4481000 > svcGetSystemTick()) {
227			flags = C3D_FRAME_NONBLOCK;
228		} else {
229			tickCounter = svcGetSystemTick();
230		}
231	}
232	C3D_FrameBegin(flags);
233	// Mark both buffers used to make sure they get cleared
234	C3D_FrameDrawOn(topScreen[doubleBuffer]);
235	C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
236	ctrStartFrame();
237}
238
239static void _drawEnd(void) {
240	if (!frameStarted) {
241		return;
242	}
243	ctrEndFrame();
244	C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
245	C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
246	C3D_FrameEnd(GX_CMDLIST_FLUSH);
247	frameStarted = false;
248
249	doubleBuffer ^= 1;
250	C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
251	C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
252}
253
254static int _batteryState(void) {
255	u8 charge;
256	u8 adapter;
257	PTMU_GetBatteryLevel(&charge);
258	PTMU_GetBatteryChargeState(&adapter);
259	int state = 0;
260	if (adapter) {
261		state |= BATTERY_CHARGING;
262	}
263	if (charge > 0) {
264		--charge;
265	}
266	return state | charge;
267}
268
269static void _guiPrepare(void) {
270	_frameStart();
271	C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
272	ctrSetViewportSize(320, 240, true);
273}
274
275static void _guiFinish(void) {
276	ctrFlushBatch();
277}
278
279static void _setup(struct mGUIRunner* runner) {
280	uint8_t mask;
281	if (R_SUCCEEDED(svcGetProcessAffinityMask(&mask, CUR_PROCESS_HANDLE, 4)) && mask >= 4) {
282		mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
283		mCoreLoadForeignConfig(runner->core, &runner->config);
284	}
285
286	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
287	if (hasSound != NO_SOUND) {
288		runner->core->setAVStream(runner->core, &stream);
289	}
290
291	_map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
292	_map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
293	_map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
294	_map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
295	_map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
296	_map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
297	_map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
298	_map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
299	_map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
300	_map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
301
302	outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
303	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
304
305	unsigned mode;
306	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
307		screenMode = mode;
308	}
309	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
310		filterMode = mode;
311		if (filterMode == FM_NEAREST) {
312			C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
313		} else {
314			C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
315		}
316	}
317	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
318		darkenMode = mode;
319	}
320	frameLimiter = true;
321
322	runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
323}
324
325static void _gameLoaded(struct mGUIRunner* runner) {
326	switch (runner->core->platform(runner->core)) {
327#ifdef M_CORE_GBA
328	case PLATFORM_GBA:
329		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
330			HIDUSER_EnableAccelerometer();
331		}
332		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
333			HIDUSER_EnableGyroscope();
334		}
335		break;
336#endif
337#ifdef M_CORE_GB
338	case PLATFORM_GB:
339		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
340			HIDUSER_EnableAccelerometer();
341		}
342		break;
343#endif
344	default:
345		break;
346	}
347	osSetSpeedupEnable(true);
348
349	double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
350	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
351	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
352	if (hasSound != NO_SOUND) {
353		audioPos = 0;
354	}
355	if (hasSound == CSND_SUPPORTED) {
356		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
357		memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
358		_csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
359		csndExecCmds(false);
360	} else if (hasSound == DSP_SUPPORTED) {
361		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
362	}
363	unsigned mode;
364	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
365		screenMode = mode;
366	}
367	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
368		filterMode = mode;
369		if (filterMode == FM_NEAREST) {
370			C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
371		} else {
372			C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
373		}
374	}
375	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
376		darkenMode = mode;
377	}
378}
379
380static void _gameUnloaded(struct mGUIRunner* runner) {
381	if (hasSound == CSND_SUPPORTED) {
382		CSND_SetPlayState(8, 0);
383		CSND_SetPlayState(9, 0);
384		csndExecCmds(false);
385	}
386	osSetSpeedupEnable(false);
387	frameLimiter = true;
388
389	switch (runner->core->platform(runner->core)) {
390#ifdef M_CORE_GBA
391	case PLATFORM_GBA:
392		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
393			HIDUSER_DisableAccelerometer();
394		}
395		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
396			HIDUSER_DisableGyroscope();
397		}
398		break;
399#endif
400#ifdef M_CORE_GB
401	case PLATFORM_GB:
402		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
403			HIDUSER_DisableAccelerometer();
404		}
405		break;
406#endif
407	default:
408		break;
409	}
410}
411
412static void _drawTex(struct mCore* core, bool faded) {
413	_frameStart();
414	unsigned screen_w, screen_h;
415	switch (screenMode) {
416	case SM_PA_BOTTOM:
417		C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
418		screen_w = 320;
419		screen_h = 240;
420		break;
421	case SM_PA_TOP:
422		C3D_FrameDrawOn(topScreen[doubleBuffer]);
423		screen_w = 400;
424		screen_h = 240;
425		break;
426	default:
427		C3D_FrameDrawOn(upscaleBuffer);
428		screen_w = 512;
429		screen_h = 512;
430		break;
431	}
432
433	unsigned corew, coreh;
434	core->desiredVideoDimensions(core, &corew, &coreh);
435
436	int w = corew;
437	int h = coreh;
438	// Get greatest common divisor
439	while (w != 0) {
440		int temp = h % w;
441		h = w;
442		w = temp;
443	}
444	int gcd = h;
445	unsigned aspectw = corew / gcd;
446	unsigned aspecth = coreh / gcd;
447	int x = 0;
448	int y = 0;
449
450	switch (screenMode) {
451	case SM_PA_TOP:
452	case SM_PA_BOTTOM:
453		w = corew;
454		h = coreh;
455		x = (screen_w - w) / 2;
456		y = (screen_h - h) / 2;
457		ctrSetViewportSize(screen_w, screen_h, true);
458		break;
459	case SM_AF_TOP:
460	case SM_AF_BOTTOM:
461	case SM_SF_TOP:
462	case SM_SF_BOTTOM:
463	default:
464		if (filterMode == FM_LINEAR_1x) {
465			w = corew;
466			h = coreh;
467		} else {
468			w = corew * 2;
469			h = coreh * 2;
470		}
471		ctrSetViewportSize(screen_w, screen_h, false);
472		break;
473	}
474
475	ctrActivateTexture(&outputTexture);
476	u32 color;
477	if (!faded) {
478		color = 0xFFFFFFFF;
479		switch (darkenMode) {
480		case DM_NATIVE:
481		case DM_MAX:
482			break;
483		case DM_MULT_SCALE_BIAS:
484			ctrTextureBias(0x070707);
485			// Fall through
486		case DM_MULT_SCALE:
487			color = 0xFF707070;
488			// Fall through
489		case DM_MULT:
490			ctrTextureMultiply();
491			break;
492		}
493	} else {
494		color = 0xFF484848;
495		switch (darkenMode) {
496		case DM_NATIVE:
497		case DM_MAX:
498			break;
499		case DM_MULT_SCALE_BIAS:
500			ctrTextureBias(0x030303);
501			// Fall through
502		case DM_MULT_SCALE:
503			color = 0xFF303030;
504			// Fall through
505		case DM_MULT:
506			ctrTextureMultiply();
507			break;
508		}
509
510	}
511	ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
512	ctrFlushBatch();
513
514	corew = w;
515	coreh = h;
516	screen_h = 240;
517	if (screenMode < SM_PA_TOP) {
518		C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
519		screen_w = 320;
520	} else {
521		C3D_FrameDrawOn(topScreen[doubleBuffer]);
522		screen_w = 400;
523	}
524	ctrSetViewportSize(screen_w, screen_h, true);
525
526	switch (screenMode) {
527	default:
528		return;
529	case SM_AF_TOP:
530	case SM_AF_BOTTOM:
531		w = screen_w / aspectw;
532		h = screen_h / aspecth;
533		if (w * aspecth > screen_h) {
534			w = aspectw * h;
535			h = aspecth * h;
536		} else {
537			h = aspecth * w;
538			w = aspectw * w;
539		}
540		break;
541	case SM_SF_TOP:
542	case SM_SF_BOTTOM:
543		w = screen_w;
544		h = screen_h;
545		break;
546	}
547
548	x = (screen_w - w) / 2;
549	y = (screen_h - h) / 2;
550	ctrActivateTexture(&upscaleBufferTex);
551	ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
552	ctrFlushBatch();
553}
554
555static void _drawFrame(struct mGUIRunner* runner, bool faded) {
556	UNUSED(runner);
557
558	C3D_Tex* tex = &outputTexture;
559
560	GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
561	C3D_SafeDisplayTransfer(
562			outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
563			tex->data, GX_BUFFER_DIM(256, 256),
564			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
565				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
566				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
567
568	if (hasSound == NO_SOUND) {
569		blip_clear(runner->core->getAudioChannel(runner->core, 0));
570		blip_clear(runner->core->getAudioChannel(runner->core, 1));
571	}
572
573	gspWaitForPPF();
574	_drawTex(runner->core, faded);
575}
576
577static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
578
579	C3D_Tex* tex = &outputTexture;
580
581	color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
582
583	unsigned y;
584	for (y = 0; y < height; ++y) {
585		memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
586		memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
587	}
588
589	GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
590	C3D_SafeDisplayTransfer(
591			(u32*) newPixels, GX_BUFFER_DIM(256, height),
592			tex->data, GX_BUFFER_DIM(256, 256),
593			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
594				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
595				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
596	gspWaitForPPF();
597	linearFree(newPixels);
598
599	_drawTex(runner->core, faded);
600}
601
602static uint16_t _pollGameInput(struct mGUIRunner* runner) {
603	UNUSED(runner);
604
605	hidScanInput();
606	uint32_t activeKeys = hidKeysHeld();
607	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
608	keys |= (activeKeys >> 24) & 0xF0;
609	return keys;
610}
611
612static void _incrementScreenMode(struct mGUIRunner* runner) {
613	UNUSED(runner);
614	screenMode = (screenMode + 1) % SM_MAX;
615	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
616
617	C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
618	C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
619}
620
621static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
622	UNUSED(runner);
623	if (frameLimiter == limit) {
624		return;
625	}
626	frameLimiter = limit;
627	tickCounter = svcGetSystemTick();
628}
629
630static uint32_t _pollInput(const struct mInputMap* map) {
631	hidScanInput();
632	int activeKeys = hidKeysHeld();
633	return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
634}
635
636static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
637	hidScanInput();
638	if (!(hidKeysHeld() & KEY_TOUCH)) {
639		return GUI_CURSOR_NOT_PRESENT;
640	}
641	touchPosition pos;
642	hidTouchRead(&pos);
643	*x = pos.px;
644	*y = pos.py;
645	return GUI_CURSOR_DOWN;
646}
647
648static void _sampleRotation(struct mRotationSource* source) {
649	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
650	// Work around ctrulib getting the entries wrong
651	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
652	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
653}
654
655static int32_t _readTiltX(struct mRotationSource* source) {
656	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
657	return rotation->accel.x << 18L;
658}
659
660static int32_t _readTiltY(struct mRotationSource* source) {
661	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
662	return rotation->accel.y << 18L;
663}
664
665static int32_t _readGyroZ(struct mRotationSource* source) {
666	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
667	return rotation->gyro.y << 18L; // Yes, y
668}
669
670static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
671	UNUSED(stream);
672	if (hasSound == CSND_SUPPORTED) {
673		blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
674		blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
675		GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
676		GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
677		audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
678		if (audioPos == AUDIO_SAMPLES * 3) {
679			u8 playing = 0;
680			csndIsPlaying(0x8, &playing);
681			if (!playing) {
682				CSND_SetPlayState(0x8, 1);
683				CSND_SetPlayState(0x9, 1);
684				csndExecCmds(false);
685			}
686		}
687	} else if (hasSound == DSP_SUPPORTED) {
688		int startId = bufferId;
689		while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
690			bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
691			if (bufferId == startId) {
692				blip_clear(left);
693				blip_clear(right);
694				return;
695			}
696		}
697		void* tmpBuf = dspBuffer[bufferId].data_pcm16;
698		memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
699		dspBuffer[bufferId].data_pcm16 = tmpBuf;
700		dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
701		blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
702		blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
703		DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
704		ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
705	}
706}
707
708int main() {
709	rotation.d.sample = _sampleRotation;
710	rotation.d.readTiltX = _readTiltX;
711	rotation.d.readTiltY = _readTiltY;
712	rotation.d.readGyroZ = _readGyroZ;
713
714	stream.videoDimensionsChanged = 0;
715	stream.postVideoFrame = 0;
716	stream.postAudioFrame = 0;
717	stream.postAudioBuffer = _postAudioBuffer;
718
719	if (!allocateRomBuffer()) {
720		return 1;
721	}
722
723	aptHook(&cookie, _aptHook, 0);
724
725	ptmuInit();
726	hasSound = NO_SOUND;
727	if (!ndspInit()) {
728		hasSound = DSP_SUPPORTED;
729		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
730		ndspSetOutputCount(1);
731		ndspChnReset(0);
732		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
733		ndspChnSetInterp(0, NDSP_INTERP_NONE);
734		ndspChnSetRate(0, 0x8000);
735		ndspChnWaveBufClear(0);
736		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
737		memset(dspBuffer, 0, sizeof(dspBuffer));
738		int i;
739		for (i = 0; i < DSP_BUFFERS; ++i) {
740			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
741			dspBuffer[i].nsamples = AUDIO_SAMPLES;
742		}
743	}
744
745	if (hasSound == NO_SOUND && !csndInit()) {
746		hasSound = CSND_SUPPORTED;
747		audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
748		audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
749	}
750
751	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
752
753	if (!_initGpu()) {
754		outputTexture.data = 0;
755		_cleanup();
756		return 1;
757	}
758
759	if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
760		_cleanup();
761		return 1;
762	}
763	C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
764	C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
765	C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
766	void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
767
768	// Zero texture data to make sure no garbage around the border interferes with filtering
769	GX_MemoryFill(
770			outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
771			NULL, 0, NULL, 0);
772	gspWaitForPSC0();
773
774	struct GUIFont* font = GUIFontCreate();
775
776	if (!font) {
777		_cleanup();
778		return 1;
779	}
780
781	struct mGUIRunner runner = {
782		.params = {
783			320, 240,
784			font, "/",
785			_drawStart, _drawEnd,
786			_pollInput, _pollCursor,
787			_batteryState,
788			_guiPrepare, _guiFinish,
789		},
790		.keySources = (struct GUIInputKeys[]) {
791			{
792				.name = "3DS Input",
793				.id = _3DS_INPUT,
794				.keyNames = (const char*[]) {
795					"A",
796					"B",
797					"Select",
798					"Start",
799					"D-Pad Right",
800					"D-Pad Left",
801					"D-Pad Up",
802					"D-Pad Down",
803					"R",
804					"L",
805					"X",
806					"Y",
807					0,
808					0,
809					"ZL",
810					"ZR",
811					0,
812					0,
813					0,
814					0,
815					0,
816					0,
817					0,
818					0,
819					"C-Stick Right",
820					"C-Stick Left",
821					"C-Stick Up",
822					"C-Stick Down",
823				},
824				.nKeys = 28
825			},
826			{ .id = 0 }
827		},
828		.configExtra = (struct GUIMenuItem[]) {
829			{
830				.title = "Screen mode",
831				.data = "screenMode",
832				.submenu = 0,
833				.state = SM_PA_TOP,
834				.validStates = (const char*[]) {
835					"Pixel-Accurate/Bottom",
836					"Aspect-Ratio Fit/Bottom",
837					"Stretched/Bottom",
838					"Pixel-Accurate/Top",
839					"Aspect-Ratio Fit/Top",
840					"Stretched/Top",
841				},
842				.nStates = 6
843			},
844			{
845				.title = "Filtering",
846				.data = "filterMode",
847				.submenu = 0,
848				.state = FM_LINEAR_2x,
849				.validStates = (const char*[]) {
850					NULL, // Disable choosing nearest neighbor; it always looks bad
851					"Bilinear (smoother)",
852					"Bilinear (pixelated)",
853				},
854				.nStates = 3
855			},
856			{
857				.title = "Screen darkening",
858				.data = "darkenMode",
859				.submenu = 0,
860				.state = DM_NATIVE,
861				.validStates = (const char*[]) {
862					"None",
863					"Dark",
864					"Very dark",
865					"Grayed",
866				},
867				.nStates = 4
868			}
869		},
870		.nConfigExtra = 3,
871		.setup = _setup,
872		.teardown = 0,
873		.gameLoaded = _gameLoaded,
874		.gameUnloaded = _gameUnloaded,
875		.prepareForFrame = 0,
876		.drawFrame = _drawFrame,
877		.drawScreenshot = _drawScreenshot,
878		.paused = _gameUnloaded,
879		.unpaused = _gameLoaded,
880		.incrementScreenMode = _incrementScreenMode,
881		.setFrameLimiter = _setFrameLimiter,
882		.pollGameInput = _pollGameInput
883	};
884
885	mGUIInit(&runner, "3ds");
886
887	_map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
888	_map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
889	_map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
890	_map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
891	_map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
892	_map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
893	_map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
894	_map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
895	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
896	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
897
898	mGUIRunloop(&runner);
899	mGUIDeinit(&runner);
900
901	_cleanup();
902	return 0;
903}