all repos — mgba @ 68800da019f468d64cfbeb1b81b6982e62b5f318

mGBA Game Boy Advance Emulator

src/platform/3ds/main.c (view raw)

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