all repos — mgba @ a6367030db01638c1106058ef4fe5cd53fdc1fb7

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