all repos — mgba @ 39c6bb2de633139b0b35a741fec9de07f44f8810

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