all repos — mgba @ 1d58985d2508fcfe91c6b485e8fe2c3f1ee64ad4

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