all repos — mgba @ 0fb69126a5cda995f723882e4f79a4d653c78f79

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