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