all repos — mgba @ ea4c16042422a2b0a3848fd990fe5f4e26e44530

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