all repos — mgba @ 38613b577024ee4941a6827dfbf0474659f3b4fc

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