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