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