all repos — mgba @ 43e2a6ab5d32d1a1165bc37699bcf9dfc6e1992e

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