all repos — mgba @ 80593c96115b7f958ca5716a884e00236662f1b9

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