all repos — mgba @ 9a5546867b1d602a1d22bcfacfa64720e0691cc6

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