all repos — mgba @ 62e39558485d896d794e912663915349f25ae390

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