all repos — mgba @ 55679df8fc49fca3d5f8584d16b90e9bb734a922

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
258	CAMU_SetNoiseFilter(imageSource->cam, true);
259	CAMU_SetAutoExposure(imageSource->cam, false);
260	CAMU_SetAutoWhiteBalance(imageSource->cam, false);
261}
262
263static void _setup(struct mGUIRunner* runner) {
264	bool isNew3DS = false;
265	APT_CheckNew3DS(&isNew3DS);
266	if (isNew3DS && !envIsHomebrew()) {
267		mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
268		mCoreLoadForeignConfig(runner->core, &runner->config);
269	}
270
271	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
272	runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d);
273	if (hasSound != NO_SOUND) {
274		runner->core->setAVStream(runner->core, &stream);
275	}
276
277	_map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
278	_map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
279	_map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
280	_map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
281	_map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
282	_map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
283	_map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
284	_map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
285	_map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
286	_map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
287
288	outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
289	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
290
291	unsigned mode;
292	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
293		screenMode = mode;
294	}
295	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
296		filterMode = mode;
297		if (filterMode == FM_NEAREST) {
298			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
299		} else {
300			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
301		}
302	}
303	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
304		darkenMode = mode;
305	}
306	frameLimiter = true;
307
308	runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
309}
310
311static void _gameLoaded(struct mGUIRunner* runner) {
312	switch (runner->core->platform(runner->core)) {
313#ifdef M_CORE_GBA
314		// TODO: Move these to callbacks
315	case PLATFORM_GBA:
316		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
317			HIDUSER_EnableAccelerometer();
318		}
319		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
320			HIDUSER_EnableGyroscope();
321		}
322		break;
323#endif
324#ifdef M_CORE_GB
325	case PLATFORM_GB:
326		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
327			HIDUSER_EnableAccelerometer();
328		}
329		break;
330#endif
331	default:
332		break;
333	}
334	osSetSpeedupEnable(true);
335
336	double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
337	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
338	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
339	if (hasSound != NO_SOUND) {
340		audioPos = 0;
341	}
342	if (hasSound == CSND_SUPPORTED) {
343		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
344		memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
345		_csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
346		csndExecCmds(false);
347	} else if (hasSound == DSP_SUPPORTED) {
348		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
349	}
350	unsigned mode;
351	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
352		screenMode = mode;
353	}
354	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
355		filterMode = mode;
356		if (filterMode == FM_NEAREST) {
357			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
358		} else {
359			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
360		}
361	}
362	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
363		darkenMode = mode;
364	}
365	if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
366		switch (mode) {
367		case 0:
368		default:
369			mode = SELECT_NONE;
370			break;
371		case 1:
372			mode = SELECT_IN1;
373			break;
374		case 2:
375			mode = SELECT_OUT1;
376			break;
377		}
378		if (mode != camera.cam) {
379			camera.cam = mode;
380			if (camera.buffer) {
381				_resetCamera(&camera);
382				CAMU_Activate(camera.cam);
383			}
384		}
385	}
386}
387
388static void _gameUnloaded(struct mGUIRunner* runner) {
389	if (hasSound == CSND_SUPPORTED) {
390		CSND_SetPlayState(8, 0);
391		CSND_SetPlayState(9, 0);
392		csndExecCmds(false);
393	}
394	osSetSpeedupEnable(false);
395	frameLimiter = true;
396
397	switch (runner->core->platform(runner->core)) {
398#ifdef M_CORE_GBA
399		// TODO: Move these to callbacks
400	case PLATFORM_GBA:
401		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
402			HIDUSER_DisableAccelerometer();
403		}
404		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
405			HIDUSER_DisableGyroscope();
406		}
407		break;
408#endif
409#ifdef M_CORE_GB
410	case PLATFORM_GB:
411		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
412			HIDUSER_DisableAccelerometer();
413		}
414		break;
415#endif
416	default:
417		break;
418	}
419}
420
421static void _drawTex(struct mCore* core, bool faded) {
422	unsigned screen_w, screen_h;
423	switch (screenMode) {
424	case SM_PA_BOTTOM:
425		C3D_RenderBufBind(&bottomScreen);
426		screen_w = 320;
427		screen_h = 240;
428		break;
429	case SM_PA_TOP:
430		C3D_RenderBufBind(&topScreen);
431		screen_w = 400;
432		screen_h = 240;
433		break;
434	default:
435		C3D_RenderBufBind(&upscaleBuffer);
436		screen_w = upscaleBuffer.colorBuf.width;
437		screen_h = upscaleBuffer.colorBuf.height;
438		break;
439	}
440
441	unsigned corew, coreh;
442	core->desiredVideoDimensions(core, &corew, &coreh);
443
444	int w = corew;
445	int h = coreh;
446	// Get greatest common divisor
447	while (w != 0) {
448		int temp = h % w;
449		h = w;
450		w = temp;
451	}
452	int gcd = h;
453	unsigned aspectw = corew / gcd;
454	unsigned aspecth = coreh / gcd;
455	int x = 0;
456	int y = 0;
457
458	switch (screenMode) {
459	case SM_PA_TOP:
460	case SM_PA_BOTTOM:
461		w = corew;
462		h = coreh;
463		x = (screen_w - w) / 2;
464		y = (screen_h - h) / 2;
465		ctrSetViewportSize(screen_w, screen_h, true);
466		break;
467	case SM_AF_TOP:
468	case SM_AF_BOTTOM:
469	case SM_SF_TOP:
470	case SM_SF_BOTTOM:
471	default:
472		if (filterMode == FM_LINEAR_1x) {
473			w = corew;
474			h = coreh;
475		} else {
476			w = corew * 2;
477			h = coreh * 2;
478		}
479		ctrSetViewportSize(screen_w, screen_h, false);
480		break;
481	}
482
483	ctrActivateTexture(&outputTexture);
484	u32 color;
485	if (!faded) {
486		color = 0xFFFFFFFF;
487		switch (darkenMode) {
488		case DM_NATIVE:
489		case DM_MAX:
490			break;
491		case DM_MULT_SCALE_BIAS:
492			ctrTextureBias(0x070707);
493			// Fall through
494		case DM_MULT_SCALE:
495			color = 0xFF707070;
496			// Fall through
497		case DM_MULT:
498			ctrTextureMultiply();
499			break;
500		}
501	} else {
502		color = 0xFF484848;
503		switch (darkenMode) {
504		case DM_NATIVE:
505		case DM_MAX:
506			break;
507		case DM_MULT_SCALE_BIAS:
508			ctrTextureBias(0x030303);
509			// Fall through
510		case DM_MULT_SCALE:
511			color = 0xFF303030;
512			// Fall through
513		case DM_MULT:
514			ctrTextureMultiply();
515			break;
516		}
517
518	}
519	ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
520	ctrFlushBatch();
521
522	corew = w;
523	coreh = h;
524	screen_h = 240;
525	if (screenMode < SM_PA_TOP) {
526		C3D_RenderBufBind(&bottomScreen);
527		screen_w = 320;
528	} else {
529		C3D_RenderBufBind(&topScreen);
530		screen_w = 400;
531	}
532	ctrSetViewportSize(screen_w, screen_h, true);
533
534	switch (screenMode) {
535	default:
536		return;
537	case SM_AF_TOP:
538	case SM_AF_BOTTOM:
539		w = screen_w / aspectw;
540		h = screen_h / aspecth;
541		if (w * aspecth > screen_h) {
542			w = aspectw * h;
543			h = aspecth * h;
544		} else {
545			h = aspecth * w;
546			w = aspectw * w;
547		}
548		break;
549	case SM_SF_TOP:
550	case SM_SF_BOTTOM:
551		w = screen_w;
552		h = screen_h;
553		break;
554	}
555
556	x = (screen_w - w) / 2;
557	y = (screen_h - h) / 2;
558	ctrActivateTexture(&upscaleBuffer.colorBuf);
559	ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
560	ctrFlushBatch();
561}
562
563static void _drawFrame(struct mGUIRunner* runner, bool faded) {
564	UNUSED(runner);
565
566	C3D_Tex* tex = &outputTexture;
567
568	GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
569	C3D_SafeDisplayTransfer(
570			outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
571			tex->data, GX_BUFFER_DIM(256, 256),
572			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
573				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
574				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
575
576	if (hasSound == NO_SOUND) {
577		blip_clear(runner->core->getAudioChannel(runner->core, 0));
578		blip_clear(runner->core->getAudioChannel(runner->core, 1));
579	}
580
581	gspWaitForPPF();
582	_drawTex(runner->core, faded);
583}
584
585static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
586
587	C3D_Tex* tex = &outputTexture;
588
589	color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
590
591	unsigned y;
592	for (y = 0; y < height; ++y) {
593		memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
594		memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
595	}
596
597	GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
598	C3D_SafeDisplayTransfer(
599			(u32*) newPixels, GX_BUFFER_DIM(256, height),
600			tex->data, GX_BUFFER_DIM(256, 256),
601			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
602				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
603				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
604	gspWaitForPPF();
605	linearFree(newPixels);
606
607	_drawTex(runner->core, faded);
608}
609
610static uint16_t _pollGameInput(struct mGUIRunner* runner) {
611	UNUSED(runner);
612
613	hidScanInput();
614	uint32_t activeKeys = hidKeysHeld();
615	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
616	keys |= (activeKeys >> 24) & 0xF0;
617	return keys;
618}
619
620static void _incrementScreenMode(struct mGUIRunner* runner) {
621	UNUSED(runner);
622	screenMode = (screenMode + 1) % SM_MAX;
623	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
624
625	C3D_RenderBufClear(&bottomScreen);
626	C3D_RenderBufClear(&topScreen);
627}
628
629static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
630	UNUSED(runner);
631	frameLimiter = limit;
632}
633
634static uint32_t _pollInput(const struct mInputMap* map) {
635	hidScanInput();
636	int activeKeys = hidKeysHeld();
637	return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
638}
639
640static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
641	hidScanInput();
642	if (!(hidKeysHeld() & KEY_TOUCH)) {
643		return GUI_CURSOR_NOT_PRESENT;
644	}
645	touchPosition pos;
646	hidTouchRead(&pos);
647	*x = pos.px;
648	*y = pos.py;
649	return GUI_CURSOR_DOWN;
650}
651
652static void _sampleRotation(struct mRotationSource* source) {
653	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
654	// Work around ctrulib getting the entries wrong
655	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
656	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
657}
658
659static int32_t _readTiltX(struct mRotationSource* source) {
660	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
661	return rotation->accel.x << 18L;
662}
663
664static int32_t _readTiltY(struct mRotationSource* source) {
665	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
666	return rotation->accel.y << 18L;
667}
668
669static int32_t _readGyroZ(struct mRotationSource* source) {
670	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
671	return rotation->gyro.y << 18L; // Yes, y
672}
673
674static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
675	UNUSED(colorFormats);
676	struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
677
678	_resetCamera(imageSource);
679
680	CAMU_SetTrimming(PORT_CAM1, true);
681	CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
682	CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
683
684	if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
685		free(imageSource->buffer);
686		imageSource->buffer = NULL;
687	}
688	imageSource->bufferSize = w * h * 2;
689	if (!imageSource->buffer) {
690		imageSource->buffer = malloc(imageSource->bufferSize);
691	}
692	CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
693	CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
694	CAMU_Activate(imageSource->cam);
695	CAMU_ClearBuffer(PORT_CAM1);
696	CAMU_StartCapture(PORT_CAM1);
697	CAMU_PlayShutterSound(SHUTTER_SOUND_TYPE_MOVIE);
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}