all repos — mgba @ 3ef59bd2c401d49654b0d003ed83e86e55e245a3

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