all repos — mgba @ 5a08abacf126476d81215c87ab499d6d25cc6c8e

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