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 u32 _setupTex(int out, bool faded) {
421 ctrActivateTexture(&outputTexture[out]);
422 u32 color;
423 if (!faded) {
424 color = 0xFFFFFFFF;
425 switch (darkenMode) {
426 case DM_NATIVE:
427 case DM_MAX:
428 break;
429 case DM_MULT_SCALE_BIAS:
430 ctrTextureBias(0x070707);
431 // Fall through
432 case DM_MULT_SCALE:
433 color = 0xFF707070;
434 // Fall through
435 case DM_MULT:
436 ctrTextureMultiply();
437 break;
438 }
439 } else {
440 color = 0xFF484848;
441 switch (darkenMode) {
442 case DM_NATIVE:
443 case DM_MAX:
444 break;
445 case DM_MULT_SCALE_BIAS:
446 ctrTextureBias(0x030303);
447 // Fall through
448 case DM_MULT_SCALE:
449 color = 0xFF303030;
450 // Fall through
451 case DM_MULT:
452 ctrTextureMultiply();
453 break;
454 }
455
456 }
457 return color;
458}
459
460static void _drawTex(struct mCore* core, bool faded, bool both) {
461 unsigned screen_w, screen_h;
462 switch (screenMode) {
463 case SM_PA_BOTTOM:
464 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
465 screen_w = 320;
466 screen_h = 240;
467 break;
468 case SM_PA_TOP:
469 C3D_FrameDrawOn(topScreen[doubleBuffer]);
470 screen_w = 400;
471 screen_h = 240;
472 break;
473 default:
474 C3D_FrameDrawOn(upscaleBuffer);
475 screen_w = 512;
476 screen_h = 512;
477 break;
478 }
479
480 unsigned corew, coreh;
481 core->desiredVideoDimensions(core, &corew, &coreh);
482
483 int w = corew;
484 int h = coreh;
485 if (sgbCrop && w == 256 && h == 224) {
486 w = GB_VIDEO_HORIZONTAL_PIXELS;
487 h = GB_VIDEO_VERTICAL_PIXELS;
488 }
489 int innerw = w;
490 int innerh = h;
491 // Get greatest common divisor
492 while (w != 0) {
493 int temp = h % w;
494 h = w;
495 w = temp;
496 }
497 int gcd = h;
498 unsigned aspectw = innerw / gcd;
499 unsigned aspecth = innerh / gcd;
500 int x = 0;
501 int y = 0;
502
503 switch (screenMode) {
504 case SM_PA_TOP:
505 case SM_PA_BOTTOM:
506 w = corew;
507 h = coreh;
508 x = (screen_w - w) / 2;
509 y = (screen_h - h) / 2;
510 ctrSetViewportSize(screen_w, screen_h, true);
511 break;
512 case SM_AF_TOP:
513 case SM_AF_BOTTOM:
514 case SM_SF_TOP:
515 case SM_SF_BOTTOM:
516 default:
517 if (filterMode == FM_LINEAR_1x) {
518 w = corew;
519 h = coreh;
520 } else {
521 w = corew * 2;
522 h = coreh * 2;
523 }
524 ctrSetViewportSize(screen_w, screen_h, false);
525 break;
526 }
527
528 uint32_t color = _setupTex(activeOutputTexture, faded);
529 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
530 if (both) {
531 color = _setupTex(activeOutputTexture ^ 1, faded);
532 ctrAddRectEx(color & 0x7FFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
533 }
534 ctrFlushBatch();
535
536 innerw = corew;
537 innerh = coreh;
538 corew = w;
539 coreh = h;
540 screen_h = 240;
541 if (screenMode < SM_PA_TOP) {
542 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
543 screen_w = 320;
544 } else {
545 C3D_FrameDrawOn(topScreen[doubleBuffer]);
546 screen_w = 400;
547 }
548 ctrSetViewportSize(screen_w, screen_h, true);
549
550 float afw, afh;
551 switch (screenMode) {
552 default:
553 return;
554 case SM_AF_TOP:
555 case SM_AF_BOTTOM:
556 afw = screen_w / (float) aspectw;
557 afh = screen_h / (float) aspecth;
558 if (afw * aspecth > screen_h) {
559 w = innerw * afh / gcd;
560 h = innerh * afh / gcd;
561 } else {
562 h = innerh * afw / gcd;
563 w = innerw * afw / gcd;
564 }
565 break;
566 case SM_SF_TOP:
567 case SM_SF_BOTTOM:
568 w = screen_w;
569 h = screen_h;
570 break;
571 }
572
573 x = (screen_w - w) / 2;
574 y = (screen_h - h) / 2;
575 ctrActivateTexture(&upscaleBufferTex);
576 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
577 ctrFlushBatch();
578}
579
580static void _prepareForFrame(struct mGUIRunner* runner) {
581 UNUSED(runner);
582 activeOutputTexture ^= 1;
583}
584
585static void _drawFrame(struct mGUIRunner* runner, bool faded) {
586 UNUSED(runner);
587 C3D_Tex* tex = &outputTexture[activeOutputTexture];
588
589 GSPGPU_FlushDataCache(outputBuffer, 256 * GBA_VIDEO_VERTICAL_PIXELS * 2);
590 C3D_SyncDisplayTransfer(
591 (u32*) outputBuffer, GX_BUFFER_DIM(256, GBA_VIDEO_VERTICAL_PIXELS),
592 tex->data, GX_BUFFER_DIM(256, 256),
593 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
594 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
595 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
596
597 if (hasSound == NO_SOUND) {
598 blip_clear(runner->core->getAudioChannel(runner->core, 0));
599 blip_clear(runner->core->getAudioChannel(runner->core, 1));
600 }
601
602 _drawTex(runner->core, faded, interframeBlending);
603}
604
605static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
606 C3D_Tex* tex = &outputTexture[activeOutputTexture];
607
608 if (!screenshotBuffer) {
609 screenshotBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
610 }
611 unsigned y;
612 for (y = 0; y < height; ++y) {
613 memcpy(&screenshotBuffer[y * 256], &pixels[y * width], width * sizeof(color_t));
614 memset(&screenshotBuffer[y * 256 + width], 0, (256 - width) * sizeof(color_t));
615 }
616
617 GSPGPU_FlushDataCache(screenshotBuffer, 256 * height * sizeof(color_t));
618 C3D_SyncDisplayTransfer(
619 (u32*) screenshotBuffer, GX_BUFFER_DIM(256, height),
620 tex->data, GX_BUFFER_DIM(256, 256),
621 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
622 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
623 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
624
625 _drawTex(runner->core, faded, false);
626}
627
628static uint16_t _pollGameInput(struct mGUIRunner* runner) {
629 UNUSED(runner);
630
631 hidScanInput();
632 uint32_t activeKeys = hidKeysHeld();
633 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
634 keys |= (activeKeys >> 24) & 0xF0;
635 return keys;
636}
637
638static void _incrementScreenMode(struct mGUIRunner* runner) {
639 UNUSED(runner);
640 screenMode = (screenMode + 1) % SM_MAX;
641 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
642}
643
644static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
645 UNUSED(runner);
646 if (frameLimiter == limit) {
647 return;
648 }
649 frameLimiter = limit;
650 tickCounter = svcGetSystemTick();
651}
652
653static bool _running(struct mGUIRunner* runner) {
654 UNUSED(runner);
655 return aptMainLoop();
656}
657
658static uint32_t _pollInput(const struct mInputMap* map) {
659 hidScanInput();
660 int activeKeys = hidKeysHeld();
661 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
662}
663
664static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
665 hidScanInput();
666 if (!(hidKeysHeld() & KEY_TOUCH)) {
667 return GUI_CURSOR_NOT_PRESENT;
668 }
669 touchPosition pos;
670 hidTouchRead(&pos);
671 *x = pos.px;
672 *y = pos.py;
673 return GUI_CURSOR_DOWN;
674}
675
676static void _sampleRotation(struct mRotationSource* source) {
677 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
678 // Work around ctrulib getting the entries wrong
679 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
680 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
681}
682
683static int32_t _readTiltX(struct mRotationSource* source) {
684 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
685 return rotation->accel.x << 18L;
686}
687
688static int32_t _readTiltY(struct mRotationSource* source) {
689 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
690 return rotation->accel.y << 18L;
691}
692
693static int32_t _readGyroZ(struct mRotationSource* source) {
694 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
695 return rotation->gyro.y << 18L; // Yes, y
696}
697
698static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
699 UNUSED(colorFormats);
700 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
701
702 _resetCamera(imageSource);
703
704 CAMU_SetTrimming(PORT_CAM1, true);
705 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
706 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
707
708 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
709 free(imageSource->buffer);
710 imageSource->buffer = NULL;
711 }
712 imageSource->bufferSize = w * h * 2;
713 if (!imageSource->buffer) {
714 imageSource->buffer = malloc(imageSource->bufferSize);
715 }
716 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
717 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
718 CAMU_Activate(imageSource->cam);
719 CAMU_ClearBuffer(PORT_CAM1);
720 CAMU_StartCapture(PORT_CAM1);
721
722 if (imageSource->cam) {
723 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
724 }
725}
726
727static void _stopRequestImage(struct mImageSource* source) {
728 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
729
730 free(imageSource->buffer);
731 imageSource->buffer = NULL;
732 svcCloseHandle(imageSource->handles[0]);
733 svcCloseHandle(imageSource->handles[1]);
734
735 CAMU_StopCapture(PORT_CAM1);
736 CAMU_Activate(SELECT_NONE);
737}
738
739
740static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
741 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
742
743 if (!imageSource->cam) {
744 memset(imageSource->buffer, 0, imageSource->bufferSize);
745 *buffer = imageSource->buffer;
746 *stride = 128;
747 *colorFormat = mCOLOR_RGB565;
748 return;
749 }
750
751 s32 i;
752 svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
753
754 if (i == 0) {
755 *buffer = imageSource->buffer;
756 *stride = 128;
757 *colorFormat = mCOLOR_RGB565;
758 } else {
759 CAMU_ClearBuffer(PORT_CAM1);
760 CAMU_StartCapture(PORT_CAM1);
761 }
762
763 svcCloseHandle(imageSource->handles[0]);
764 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
765}
766
767static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
768 UNUSED(stream);
769 if (hasSound == DSP_SUPPORTED) {
770 int startId = bufferId;
771 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
772 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
773 if (bufferId == startId) {
774 blip_clear(left);
775 blip_clear(right);
776 return;
777 }
778 }
779 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
780 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
781 dspBuffer[bufferId].data_pcm16 = tmpBuf;
782 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
783 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
784 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
785 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
786 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
787 }
788}
789
790THREAD_ENTRY _core2Test(void* context) {
791 UNUSED(context);
792}
793
794int main() {
795 rotation.d.sample = _sampleRotation;
796 rotation.d.readTiltX = _readTiltX;
797 rotation.d.readTiltY = _readTiltY;
798 rotation.d.readGyroZ = _readGyroZ;
799
800 stream.videoDimensionsChanged = 0;
801 stream.postVideoFrame = 0;
802 stream.postAudioFrame = 0;
803 stream.postAudioBuffer = _postAudioBuffer;
804
805 camera.d.startRequestImage = _startRequestImage;
806 camera.d.stopRequestImage = _stopRequestImage;
807 camera.d.requestImage = _requestImage;
808 camera.buffer = NULL;
809 camera.bufferSize = 0;
810 camera.cam = SELECT_IN1;
811
812 if (!allocateRomBuffer()) {
813 return 1;
814 }
815
816 aptHook(&cookie, _aptHook, 0);
817
818 ptmuInit();
819 camInit();
820
821 hasSound = NO_SOUND;
822 if (!ndspInit()) {
823 hasSound = DSP_SUPPORTED;
824 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
825 ndspSetOutputCount(1);
826 ndspChnReset(0);
827 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
828 ndspChnSetInterp(0, NDSP_INTERP_NONE);
829 ndspChnSetRate(0, 0x8000);
830 ndspChnWaveBufClear(0);
831 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
832 memset(dspBuffer, 0, sizeof(dspBuffer));
833 int i;
834 for (i = 0; i < DSP_BUFFERS; ++i) {
835 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
836 dspBuffer[i].nsamples = AUDIO_SAMPLES;
837 }
838 }
839
840 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
841
842 if (!_initGpu()) {
843 outputTexture[0].data = 0;
844 _cleanup();
845 return 1;
846 }
847
848 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
849
850 int i;
851 for (i = 0; i < 2; ++i) {
852 if (!C3D_TexInitVRAM(&outputTexture[i], 256, 256, GPU_RGB565)) {
853 _cleanup();
854 return 1;
855 }
856 C3D_TexSetWrap(&outputTexture[i], GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
857 C3D_TexSetFilter(&outputTexture[i], GPU_NEAREST, GPU_NEAREST);
858 void* outputTextureEnd = (u8*)outputTexture[i].data + 256 * 256 * 2;
859
860 // Zero texture data to make sure no garbage around the border interferes with filtering
861 GX_MemoryFill(
862 outputTexture[i].data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
863 NULL, 0, NULL, 0);
864 gspWaitForPSC0();
865 }
866
867 struct GUIFont* font = GUIFontCreate();
868
869 if (!font) {
870 _cleanup();
871 return 1;
872 }
873
874 struct mGUIRunner runner = {
875 .params = {
876 320, 240,
877 font, "/",
878 _drawStart, _drawEnd,
879 _pollInput, _pollCursor,
880 _batteryState,
881 _guiPrepare, _guiFinish,
882 },
883 .keySources = (struct GUIInputKeys[]) {
884 {
885 .name = "3DS Input",
886 .id = _3DS_INPUT,
887 .keyNames = (const char*[]) {
888 "A",
889 "B",
890 "Select",
891 "Start",
892 "D-Pad Right",
893 "D-Pad Left",
894 "D-Pad Up",
895 "D-Pad Down",
896 "R",
897 "L",
898 "X",
899 "Y",
900 0,
901 0,
902 "ZL",
903 "ZR",
904 0,
905 0,
906 0,
907 0,
908 0,
909 0,
910 0,
911 0,
912 "C-Stick Right",
913 "C-Stick Left",
914 "C-Stick Up",
915 "C-Stick Down",
916 },
917 .nKeys = 28
918 },
919 { .id = 0 }
920 },
921 .configExtra = (struct GUIMenuItem[]) {
922 {
923 .title = "Screen mode",
924 .data = "screenMode",
925 .submenu = 0,
926 .state = SM_PA_TOP,
927 .validStates = (const char*[]) {
928 "Pixel-Accurate/Bottom",
929 "Aspect-Ratio Fit/Bottom",
930 "Stretched/Bottom",
931 "Pixel-Accurate/Top",
932 "Aspect-Ratio Fit/Top",
933 "Stretched/Top",
934 },
935 .nStates = 6
936 },
937 {
938 .title = "Filtering",
939 .data = "filterMode",
940 .submenu = 0,
941 .state = FM_LINEAR_2x,
942 .validStates = (const char*[]) {
943 NULL, // Disable choosing nearest neighbor; it always looks bad
944 "Bilinear (smoother)",
945 "Bilinear (pixelated)",
946 },
947 .nStates = 3
948 },
949 {
950 .title = "Screen darkening",
951 .data = "darkenMode",
952 .submenu = 0,
953 .state = DM_NATIVE,
954 .validStates = (const char*[]) {
955 "None",
956 "Dark",
957 "Very dark",
958 "Grayed",
959 },
960 .nStates = 4
961 },
962 {
963 .title = "Camera",
964 .data = "camera",
965 .submenu = 0,
966 .state = 1,
967 .validStates = (const char*[]) {
968 "None",
969 "Inner",
970 "Outer",
971 },
972 .nStates = 3
973 }
974 },
975 .nConfigExtra = 4,
976 .setup = _setup,
977 .teardown = 0,
978 .gameLoaded = _gameLoaded,
979 .gameUnloaded = _gameUnloaded,
980 .prepareForFrame = _prepareForFrame,
981 .drawFrame = _drawFrame,
982 .drawScreenshot = _drawScreenshot,
983 .paused = _gameUnloaded,
984 .unpaused = _gameLoaded,
985 .incrementScreenMode = _incrementScreenMode,
986 .setFrameLimiter = _setFrameLimiter,
987 .pollGameInput = _pollGameInput,
988 .running = _running
989 };
990
991 runner.autosave.running = true;
992 MutexInit(&runner.autosave.mutex);
993 ConditionInit(&runner.autosave.cond);
994
995 APT_SetAppCpuTimeLimit(20);
996 runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true);
997
998 Thread thread2;
999 if (ThreadCreate(&thread2, _core2Test, NULL) == 0) {
1000 core2 = true;
1001 ThreadJoin(&thread2);
1002 }
1003
1004 mGUIInit(&runner, "3ds");
1005
1006 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
1007 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
1008 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
1009 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
1010 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
1011 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
1012 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
1013 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
1014 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
1015 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
1016
1017 mGUIRunloop(&runner);
1018 mGUIDeinit(&runner);
1019
1020 _cleanup();
1021 return 0;
1022}