all repos — mgba @ 20e0c8f303285bd3ce03f30cda62bda9996b6e9d

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