all repos — mgba @ 4085b9cdc5cce9397c349573bd6fcf53a3bae8ec

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#ifdef M_CORE_GBA
  8#include "gba/gba.h"
  9#include "gba/input.h"
 10#include "gba/video.h"
 11#endif
 12#ifdef M_CORE_GB
 13#include "gb/gb.h"
 14#endif
 15#include "feature/gui/gui-runner.h"
 16#include "util/gui.h"
 17#include "util/gui/file-select.h"
 18#include "util/gui/font.h"
 19#include "util/gui/menu.h"
 20#include "util/memory.h"
 21
 22#include "3ds-vfs.h"
 23#include "ctr-gpu.h"
 24
 25#include <3ds.h>
 26#include <3ds/gpu/gx.h>
 27
 28static enum ScreenMode {
 29	SM_PA_BOTTOM,
 30	SM_AF_BOTTOM,
 31	SM_SF_BOTTOM,
 32	SM_PA_TOP,
 33	SM_AF_TOP,
 34	SM_SF_TOP,
 35	SM_MAX
 36} screenMode = SM_PA_TOP;
 37
 38static enum FilterMode {
 39	FM_NEAREST,
 40	FM_LINEAR_1x,
 41	FM_LINEAR_2x,
 42	FM_MAX
 43} filterMode = FM_LINEAR_2x;
 44
 45static enum DarkenMode {
 46	DM_NATIVE,
 47	DM_MULT,
 48	DM_MULT_SCALE,
 49	DM_MULT_SCALE_BIAS,
 50	DM_MAX
 51} darkenMode = DM_NATIVE;
 52
 53#define _3DS_INPUT 0x3344534B
 54
 55#define AUDIO_SAMPLES 384
 56#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
 57#define DSP_BUFFERS 4
 58
 59static struct GBA3DSRotationSource {
 60	struct mRotationSource d;
 61	accelVector accel;
 62	angularRate gyro;
 63} rotation;
 64
 65static enum {
 66	NO_SOUND,
 67	DSP_SUPPORTED,
 68	CSND_SUPPORTED
 69} hasSound;
 70
 71// TODO: Move into context
 72static void* outputBuffer;
 73static struct mAVStream stream;
 74static int16_t* audioLeft = 0;
 75static int16_t* audioRight = 0;
 76static size_t audioPos = 0;
 77static C3D_Tex outputTexture;
 78static ndspWaveBuf dspBuffer[DSP_BUFFERS];
 79static int bufferId = 0;
 80static bool frameLimiter = true;
 81
 82static C3D_RenderBuf bottomScreen;
 83static C3D_RenderBuf topScreen;
 84static C3D_RenderBuf upscaleBuffer;
 85
 86static aptHookCookie cookie;
 87
 88extern bool allocateRomBuffer(void);
 89
 90static bool _initGpu(void) {
 91	if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
 92		return false;
 93	}
 94
 95	if (!C3D_RenderBufInit(&topScreen, 240, 400, GPU_RB_RGB8, 0) || !C3D_RenderBufInit(&bottomScreen, 240, 320, GPU_RB_RGB8, 0) || !C3D_RenderBufInit(&upscaleBuffer, 512, 512, GPU_RB_RGB8, 0)) {
 96		return false;
 97	}
 98
 99	return ctrInitGpu();
100}
101
102static void _cleanup(void) {
103	ctrDeinitGpu();
104
105	if (outputBuffer) {
106		linearFree(outputBuffer);
107	}
108
109	C3D_RenderBufDelete(&topScreen);
110	C3D_RenderBufDelete(&bottomScreen);
111	C3D_RenderBufDelete(&upscaleBuffer);
112	C3D_Fini();
113
114	gfxExit();
115
116	if (hasSound != NO_SOUND) {
117		linearFree(audioLeft);
118	}
119
120	if (hasSound == CSND_SUPPORTED) {
121		linearFree(audioRight);
122		csndExit();
123	}
124
125	if (hasSound == DSP_SUPPORTED) {
126		ndspExit();
127	}
128
129	csndExit();
130	ptmuExit();
131}
132
133static void _aptHook(APT_HookType hook, void* user) {
134	UNUSED(user);
135	switch (hook) {
136	case APTHOOK_ONSUSPEND:
137	case APTHOOK_ONSLEEP:
138		if (hasSound == CSND_SUPPORTED) {
139			CSND_SetPlayState(8, 0);
140			CSND_SetPlayState(9, 0);
141			csndExecCmds(false);
142		}
143		break;
144	case APTHOOK_ONEXIT:
145		if (hasSound == CSND_SUPPORTED) {
146			CSND_SetPlayState(8, 0);
147			CSND_SetPlayState(9, 0);
148			csndExecCmds(false);
149		}
150		_cleanup();
151		exit(0);
152		break;
153	default:
154		break;
155	}
156}
157
158static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
159	mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
160}
161
162static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
163	u32 pleft = 0, pright = 0;
164
165	int loopMode = (flags >> 10) & 3;
166	if (!loopMode) {
167		flags |= SOUND_ONE_SHOT;
168	}
169
170	pleft = osConvertVirtToPhys(left);
171	pright = osConvertVirtToPhys(right);
172
173	u32 timer = CSND_TIMER(sampleRate);
174	if (timer < 0x0042) {
175		timer = 0x0042;
176	}
177	else if (timer > 0xFFFF) {
178		timer = 0xFFFF;
179	}
180	flags &= ~0xFFFF001F;
181	flags |= SOUND_ENABLE | (timer << 16);
182
183	u32 volumes = CSND_VOL(vol, -1.0);
184	CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
185	volumes = CSND_VOL(vol, 1.0);
186	CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
187}
188
189static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
190
191static void _drawStart(void) {
192	C3D_RenderBufClear(&bottomScreen);
193	C3D_RenderBufClear(&topScreen);
194}
195
196static void _drawEnd(void) {
197	ctrFinalize();
198	C3D_RenderBufTransfer(&topScreen, (u32*) gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
199	C3D_RenderBufTransfer(&bottomScreen, (u32*) gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
200	gfxSwapBuffersGpu();
201	if (frameLimiter) {
202		gspWaitForEvent(GSPGPU_EVENT_VBlank0, false);
203	}
204}
205
206static int _batteryState(void) {
207	u8 charge;
208	u8 adapter;
209	PTMU_GetBatteryLevel(&charge);
210	PTMU_GetBatteryChargeState(&adapter);
211	int state = 0;
212	if (adapter) {
213		state |= BATTERY_CHARGING;
214	}
215	if (charge > 0) {
216		--charge;
217	}
218	return state | charge;
219}
220
221static void _guiPrepare(void) {
222	int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
223	if (screen == GFX_BOTTOM) {
224		return;
225	}
226
227	C3D_RenderBufBind(&bottomScreen);
228	ctrSetViewportSize(320, 240, true);
229}
230
231static void _guiFinish(void) {
232	ctrFlushBatch();
233}
234
235static void _setup(struct mGUIRunner* runner) {
236	bool isNew3DS = false;
237	APT_CheckNew3DS(&isNew3DS);
238	if (isNew3DS && !envIsHomebrew()) {
239		mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
240		mCoreLoadForeignConfig(runner->core, &runner->config);
241	}
242
243	runner->core->setRotation(runner->core, &rotation.d);
244	if (hasSound != NO_SOUND) {
245		runner->core->setAVStream(runner->core, &stream);
246	}
247
248	_map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
249	_map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
250	_map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
251	_map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
252	_map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
253	_map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
254	_map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
255	_map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
256	_map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
257	_map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
258
259	outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
260	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
261
262	unsigned mode;
263	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
264		screenMode = mode;
265	}
266	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
267		filterMode = mode;
268		if (filterMode == FM_NEAREST) {
269			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
270		} else {
271			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
272		}
273	}
274	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
275		darkenMode = mode;
276	}
277	frameLimiter = true;
278
279	runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
280}
281
282static void _gameLoaded(struct mGUIRunner* runner) {
283	switch (runner->core->platform(runner->core)) {
284#ifdef M_CORE_GBA
285	case PLATFORM_GBA:
286		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
287			HIDUSER_EnableAccelerometer();
288		}
289		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
290			HIDUSER_EnableGyroscope();
291		}
292		break;
293#endif
294#ifdef M_CORE_GB
295	case PLATFORM_GB:
296		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
297			HIDUSER_EnableAccelerometer();
298		}
299		break;
300#endif
301	default:
302		break;
303	}
304	osSetSpeedupEnable(true);
305
306	double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
307	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
308	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
309	if (hasSound != NO_SOUND) {
310		audioPos = 0;
311	}
312	if (hasSound == CSND_SUPPORTED) {
313		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
314		memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
315		_csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
316		csndExecCmds(false);
317	} else if (hasSound == DSP_SUPPORTED) {
318		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
319	}
320	unsigned mode;
321	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
322		screenMode = mode;
323	}
324	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
325		filterMode = mode;
326		if (filterMode == FM_NEAREST) {
327			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
328		} else {
329			C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
330		}
331	}
332	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
333		darkenMode = mode;
334	}
335}
336
337static void _gameUnloaded(struct mGUIRunner* runner) {
338	if (hasSound == CSND_SUPPORTED) {
339		CSND_SetPlayState(8, 0);
340		CSND_SetPlayState(9, 0);
341		csndExecCmds(false);
342	}
343	osSetSpeedupEnable(false);
344	frameLimiter = true;
345
346	switch (runner->core->platform(runner->core)) {
347#ifdef M_CORE_GBA
348	case PLATFORM_GBA:
349		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
350			HIDUSER_DisableAccelerometer();
351		}
352		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
353			HIDUSER_DisableGyroscope();
354		}
355		break;
356#endif
357#ifdef M_CORE_GB
358	case PLATFORM_GB:
359		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
360			HIDUSER_DisableAccelerometer();
361		}
362		break;
363#endif
364	default:
365		break;
366	}
367}
368
369static void _drawTex(struct mCore* core, bool faded) {
370	unsigned screen_w, screen_h;
371	switch (screenMode) {
372	case SM_PA_BOTTOM:
373		C3D_RenderBufBind(&bottomScreen);
374		screen_w = 320;
375		screen_h = 240;
376		break;
377	case SM_PA_TOP:
378		C3D_RenderBufBind(&topScreen);
379		screen_w = 400;
380		screen_h = 240;
381		break;
382	default:
383		C3D_RenderBufBind(&upscaleBuffer);
384		screen_w = upscaleBuffer.colorBuf.width;
385		screen_h = upscaleBuffer.colorBuf.height;
386		break;
387	}
388
389	unsigned corew, coreh;
390	core->desiredVideoDimensions(core, &corew, &coreh);
391
392	int w = corew;
393	int h = coreh;
394	// Get greatest common divisor
395	while (w != 0) {
396		int temp = h % w;
397		h = w;
398		w = temp;
399	}
400	int gcd = h;
401	unsigned aspectw = corew / gcd;
402	unsigned aspecth = coreh / gcd;
403	int x = 0;
404	int y = 0;
405
406	switch (screenMode) {
407	case SM_PA_TOP:
408	case SM_PA_BOTTOM:
409		w = corew;
410		h = coreh;
411		x = (screen_w - w) / 2;
412		y = (screen_h - h) / 2;
413		ctrSetViewportSize(screen_w, screen_h, true);
414		break;
415	case SM_AF_TOP:
416	case SM_AF_BOTTOM:
417	case SM_SF_TOP:
418	case SM_SF_BOTTOM:
419	default:
420		if (filterMode == FM_LINEAR_1x) {
421			w = corew;
422			h = coreh;
423		} else {
424			w = corew * 2;
425			h = coreh * 2;
426		}
427		ctrSetViewportSize(screen_w, screen_h, false);
428		break;
429	}
430
431	ctrActivateTexture(&outputTexture);
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	ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
468	ctrFlushBatch();
469
470	corew = w;
471	coreh = h;
472	screen_h = 240;
473	if (screenMode < SM_PA_TOP) {
474		C3D_RenderBufBind(&bottomScreen);
475		screen_w = 320;
476	} else {
477		C3D_RenderBufBind(&topScreen);
478		screen_w = 400;
479	}
480	ctrSetViewportSize(screen_w, screen_h, true);
481
482	switch (screenMode) {
483	default:
484		return;
485	case SM_AF_TOP:
486	case SM_AF_BOTTOM:
487		w = screen_w / aspectw;
488		h = screen_h / aspecth;
489		if (w * aspecth > screen_h) {
490			w = aspectw * h;
491			h = aspecth * h;
492		} else {
493			h = aspecth * w;
494			w = aspectw * w;
495		}
496		break;
497	case SM_SF_TOP:
498	case SM_SF_BOTTOM:
499		w = screen_w;
500		h = screen_h;
501		break;
502	}
503
504	x = (screen_w - w) / 2;
505	y = (screen_h - h) / 2;
506	ctrActivateTexture(&upscaleBuffer.colorBuf);
507	ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
508	ctrFlushBatch();
509}
510
511static void _drawFrame(struct mGUIRunner* runner, bool faded) {
512	UNUSED(runner);
513
514	C3D_Tex* tex = &outputTexture;
515
516	GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
517	C3D_SafeDisplayTransfer(
518			outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
519			tex->data, GX_BUFFER_DIM(256, 256),
520			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
521				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
522				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
523
524	if (hasSound == NO_SOUND) {
525		blip_clear(runner->core->getAudioChannel(runner->core, 0));
526		blip_clear(runner->core->getAudioChannel(runner->core, 1));
527	}
528
529	gspWaitForPPF();
530	_drawTex(runner->core, faded);
531}
532
533static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
534
535	C3D_Tex* tex = &outputTexture;
536
537	color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
538
539	unsigned y;
540	for (y = 0; y < height; ++y) {
541		memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
542		memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
543	}
544
545	GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
546	C3D_SafeDisplayTransfer(
547			(u32*) newPixels, GX_BUFFER_DIM(256, height),
548			tex->data, GX_BUFFER_DIM(256, 256),
549			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
550				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
551				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
552	gspWaitForPPF();
553	linearFree(newPixels);
554
555	_drawTex(runner->core, faded);
556}
557
558static uint16_t _pollGameInput(struct mGUIRunner* runner) {
559	UNUSED(runner);
560
561	hidScanInput();
562	uint32_t activeKeys = hidKeysHeld();
563	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
564	keys |= (activeKeys >> 24) & 0xF0;
565	return keys;
566}
567
568static void _incrementScreenMode(struct mGUIRunner* runner) {
569	UNUSED(runner);
570	screenMode = (screenMode + 1) % SM_MAX;
571	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
572
573	C3D_RenderBufClear(&bottomScreen);
574	C3D_RenderBufClear(&topScreen);
575}
576
577static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
578	UNUSED(runner);
579	frameLimiter = limit;
580}
581
582static uint32_t _pollInput(const struct mInputMap* map) {
583	hidScanInput();
584	int activeKeys = hidKeysHeld();
585	return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
586}
587
588static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
589	hidScanInput();
590	if (!(hidKeysHeld() & KEY_TOUCH)) {
591		return GUI_CURSOR_NOT_PRESENT;
592	}
593	touchPosition pos;
594	hidTouchRead(&pos);
595	*x = pos.px;
596	*y = pos.py;
597	return GUI_CURSOR_DOWN;
598}
599
600static void _sampleRotation(struct mRotationSource* source) {
601	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
602	// Work around ctrulib getting the entries wrong
603	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
604	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
605}
606
607static int32_t _readTiltX(struct mRotationSource* source) {
608	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
609	return rotation->accel.x << 18L;
610}
611
612static int32_t _readTiltY(struct mRotationSource* source) {
613	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
614	return rotation->accel.y << 18L;
615}
616
617static int32_t _readGyroZ(struct mRotationSource* source) {
618	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
619	return rotation->gyro.y << 18L; // Yes, y
620}
621
622static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
623	UNUSED(stream);
624	if (hasSound == CSND_SUPPORTED) {
625		blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
626		blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
627		GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
628		GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
629		audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
630		if (audioPos == AUDIO_SAMPLES * 3) {
631			u8 playing = 0;
632			csndIsPlaying(0x8, &playing);
633			if (!playing) {
634				CSND_SetPlayState(0x8, 1);
635				CSND_SetPlayState(0x9, 1);
636				csndExecCmds(false);
637			}
638		}
639	} else if (hasSound == DSP_SUPPORTED) {
640		int startId = bufferId;
641		while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
642			bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
643			if (bufferId == startId) {
644				blip_clear(left);
645				blip_clear(right);
646				return;
647			}
648		}
649		void* tmpBuf = dspBuffer[bufferId].data_pcm16;
650		memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
651		dspBuffer[bufferId].data_pcm16 = tmpBuf;
652		dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
653		blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
654		blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
655		DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
656		ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
657	}
658}
659
660int main() {
661	rotation.d.sample = _sampleRotation;
662	rotation.d.readTiltX = _readTiltX;
663	rotation.d.readTiltY = _readTiltY;
664	rotation.d.readGyroZ = _readGyroZ;
665
666	stream.videoDimensionsChanged = 0;
667	stream.postVideoFrame = 0;
668	stream.postAudioFrame = 0;
669	stream.postAudioBuffer = _postAudioBuffer;
670
671	if (!allocateRomBuffer()) {
672		return 1;
673	}
674
675	aptHook(&cookie, _aptHook, 0);
676
677	ptmuInit();
678	hasSound = NO_SOUND;
679	if (!ndspInit()) {
680		hasSound = DSP_SUPPORTED;
681		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
682		ndspSetOutputCount(1);
683		ndspChnReset(0);
684		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
685		ndspChnSetInterp(0, NDSP_INTERP_NONE);
686		ndspChnSetRate(0, 0x8000);
687		ndspChnWaveBufClear(0);
688		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
689		memset(dspBuffer, 0, sizeof(dspBuffer));
690		int i;
691		for (i = 0; i < DSP_BUFFERS; ++i) {
692			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
693			dspBuffer[i].nsamples = AUDIO_SAMPLES;
694		}
695	}
696
697	if (hasSound == NO_SOUND && !csndInit()) {
698		hasSound = CSND_SUPPORTED;
699		audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
700		audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
701	}
702
703	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
704
705	if (!_initGpu()) {
706		outputTexture.data = 0;
707		_cleanup();
708		return 1;
709	}
710
711	if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
712		_cleanup();
713		return 1;
714	}
715	C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
716	C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
717	C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
718	void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
719
720	// Zero texture data to make sure no garbage around the border interferes with filtering
721	GX_MemoryFill(
722			outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
723			NULL, 0, NULL, 0);
724	gspWaitForPSC0();
725
726	struct GUIFont* font = GUIFontCreate();
727
728	if (!font) {
729		_cleanup();
730		return 1;
731	}
732
733	struct mGUIRunner runner = {
734		.params = {
735			320, 240,
736			font, "/",
737			_drawStart, _drawEnd,
738			_pollInput, _pollCursor,
739			_batteryState,
740			_guiPrepare, _guiFinish,
741		},
742		.keySources = (struct GUIInputKeys[]) {
743			{
744				.name = "3DS Input",
745				.id = _3DS_INPUT,
746				.keyNames = (const char*[]) {
747					"A",
748					"B",
749					"Select",
750					"Start",
751					"D-Pad Right",
752					"D-Pad Left",
753					"D-Pad Up",
754					"D-Pad Down",
755					"R",
756					"L",
757					"X",
758					"Y",
759					0,
760					0,
761					"ZL",
762					"ZR",
763					0,
764					0,
765					0,
766					0,
767					0,
768					0,
769					0,
770					0,
771					"C-Stick Right",
772					"C-Stick Left",
773					"C-Stick Up",
774					"C-Stick Down",
775				},
776				.nKeys = 28
777			},
778			{ .id = 0 }
779		},
780		.configExtra = (struct GUIMenuItem[]) {
781			{
782				.title = "Screen mode",
783				.data = "screenMode",
784				.submenu = 0,
785				.state = SM_PA_TOP,
786				.validStates = (const char*[]) {
787					"Pixel-Accurate/Bottom",
788					"Aspect-Ratio Fit/Bottom",
789					"Stretched/Bottom",
790					"Pixel-Accurate/Top",
791					"Aspect-Ratio Fit/Top",
792					"Stretched/Top",
793				},
794				.nStates = 6
795			},
796			{
797				.title = "Filtering",
798				.data = "filterMode",
799				.submenu = 0,
800				.state = FM_LINEAR_2x,
801				.validStates = (const char*[]) {
802					NULL, // Disable choosing nearest neighbor; it always looks bad
803					"Bilinear (smoother)",
804					"Bilinear (pixelated)",
805				},
806				.nStates = 3
807			},
808			{
809				.title = "Screen darkening",
810				.data = "darkenMode",
811				.submenu = 0,
812				.state = DM_NATIVE,
813				.validStates = (const char*[]) {
814					"None",
815					"Dark",
816					"Very dark",
817					"Grayed",
818				},
819				.nStates = 4
820			}
821		},
822		.nConfigExtra = 3,
823		.setup = _setup,
824		.teardown = 0,
825		.gameLoaded = _gameLoaded,
826		.gameUnloaded = _gameUnloaded,
827		.prepareForFrame = 0,
828		.drawFrame = _drawFrame,
829		.drawScreenshot = _drawScreenshot,
830		.paused = _gameUnloaded,
831		.unpaused = _gameLoaded,
832		.incrementScreenMode = _incrementScreenMode,
833		.setFrameLimiter = _setFrameLimiter,
834		.pollGameInput = _pollGameInput
835	};
836
837	mGUIInit(&runner, "3ds");
838
839	_map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
840	_map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
841	_map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
842	_map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
843	_map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
844	_map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
845	_map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
846	_map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
847	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
848	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
849
850	mGUIRunloop(&runner);
851	mGUIDeinit(&runner);
852
853	_cleanup();
854	return 0;
855}