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