all repos — mgba @ 448bc200c36c68a1250a2eb0394f5b90a2f75bed

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