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
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 bool isNew3DS = false;
265 APT_CheckNew3DS(&isNew3DS);
266 if (isNew3DS && !envIsHomebrew()) {
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 * VIDEO_VERTICAL_PIXELS * 2, 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(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
299 } else {
300 C3D_TexSetFilter(&upscaleBuffer.colorBuf, 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 == CSND_SUPPORTED) {
343 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
344 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
345 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
346 csndExecCmds(false);
347 } else if (hasSound == DSP_SUPPORTED) {
348 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
349 }
350 unsigned mode;
351 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
352 screenMode = mode;
353 }
354 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
355 filterMode = mode;
356 if (filterMode == FM_NEAREST) {
357 C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_NEAREST, GPU_NEAREST);
358 } else {
359 C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
360 }
361 }
362 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
363 darkenMode = mode;
364 }
365 if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
366 switch (mode) {
367 case 0:
368 default:
369 mode = SELECT_NONE;
370 break;
371 case 1:
372 mode = SELECT_IN1;
373 break;
374 case 2:
375 mode = SELECT_OUT1;
376 break;
377 }
378 if (mode != camera.cam) {
379 camera.cam = mode;
380 if (camera.buffer) {
381 _resetCamera(&camera);
382 CAMU_Activate(camera.cam);
383 }
384 }
385 }
386}
387
388static void _gameUnloaded(struct mGUIRunner* runner) {
389 if (hasSound == CSND_SUPPORTED) {
390 CSND_SetPlayState(8, 0);
391 CSND_SetPlayState(9, 0);
392 csndExecCmds(false);
393 }
394 osSetSpeedupEnable(false);
395 frameLimiter = true;
396
397 switch (runner->core->platform(runner->core)) {
398#ifdef M_CORE_GBA
399 // TODO: Move these to callbacks
400 case PLATFORM_GBA:
401 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
402 HIDUSER_DisableAccelerometer();
403 }
404 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
405 HIDUSER_DisableGyroscope();
406 }
407 break;
408#endif
409#ifdef M_CORE_GB
410 case PLATFORM_GB:
411 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
412 HIDUSER_DisableAccelerometer();
413 }
414 break;
415#endif
416 default:
417 break;
418 }
419}
420
421static void _drawTex(struct mCore* core, bool faded) {
422 unsigned screen_w, screen_h;
423 switch (screenMode) {
424 case SM_PA_BOTTOM:
425 C3D_RenderBufBind(&bottomScreen);
426 screen_w = 320;
427 screen_h = 240;
428 break;
429 case SM_PA_TOP:
430 C3D_RenderBufBind(&topScreen);
431 screen_w = 400;
432 screen_h = 240;
433 break;
434 default:
435 C3D_RenderBufBind(&upscaleBuffer);
436 screen_w = upscaleBuffer.colorBuf.width;
437 screen_h = upscaleBuffer.colorBuf.height;
438 break;
439 }
440
441 unsigned corew, coreh;
442 core->desiredVideoDimensions(core, &corew, &coreh);
443
444 int w = corew;
445 int h = coreh;
446 // Get greatest common divisor
447 while (w != 0) {
448 int temp = h % w;
449 h = w;
450 w = temp;
451 }
452 int gcd = h;
453 unsigned aspectw = corew / gcd;
454 unsigned aspecth = coreh / gcd;
455 int x = 0;
456 int y = 0;
457
458 switch (screenMode) {
459 case SM_PA_TOP:
460 case SM_PA_BOTTOM:
461 w = corew;
462 h = coreh;
463 x = (screen_w - w) / 2;
464 y = (screen_h - h) / 2;
465 ctrSetViewportSize(screen_w, screen_h, true);
466 break;
467 case SM_AF_TOP:
468 case SM_AF_BOTTOM:
469 case SM_SF_TOP:
470 case SM_SF_BOTTOM:
471 default:
472 if (filterMode == FM_LINEAR_1x) {
473 w = corew;
474 h = coreh;
475 } else {
476 w = corew * 2;
477 h = coreh * 2;
478 }
479 ctrSetViewportSize(screen_w, screen_h, false);
480 break;
481 }
482
483 ctrActivateTexture(&outputTexture);
484 u32 color;
485 if (!faded) {
486 color = 0xFFFFFFFF;
487 switch (darkenMode) {
488 case DM_NATIVE:
489 case DM_MAX:
490 break;
491 case DM_MULT_SCALE_BIAS:
492 ctrTextureBias(0x070707);
493 // Fall through
494 case DM_MULT_SCALE:
495 color = 0xFF707070;
496 // Fall through
497 case DM_MULT:
498 ctrTextureMultiply();
499 break;
500 }
501 } else {
502 color = 0xFF484848;
503 switch (darkenMode) {
504 case DM_NATIVE:
505 case DM_MAX:
506 break;
507 case DM_MULT_SCALE_BIAS:
508 ctrTextureBias(0x030303);
509 // Fall through
510 case DM_MULT_SCALE:
511 color = 0xFF303030;
512 // Fall through
513 case DM_MULT:
514 ctrTextureMultiply();
515 break;
516 }
517
518 }
519 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
520 ctrFlushBatch();
521
522 corew = w;
523 coreh = h;
524 screen_h = 240;
525 if (screenMode < SM_PA_TOP) {
526 C3D_RenderBufBind(&bottomScreen);
527 screen_w = 320;
528 } else {
529 C3D_RenderBufBind(&topScreen);
530 screen_w = 400;
531 }
532 ctrSetViewportSize(screen_w, screen_h, true);
533
534 switch (screenMode) {
535 default:
536 return;
537 case SM_AF_TOP:
538 case SM_AF_BOTTOM:
539 w = screen_w / aspectw;
540 h = screen_h / aspecth;
541 if (w * aspecth > screen_h) {
542 w = aspectw * h;
543 h = aspecth * h;
544 } else {
545 h = aspecth * w;
546 w = aspectw * w;
547 }
548 break;
549 case SM_SF_TOP:
550 case SM_SF_BOTTOM:
551 w = screen_w;
552 h = screen_h;
553 break;
554 }
555
556 x = (screen_w - w) / 2;
557 y = (screen_h - h) / 2;
558 ctrActivateTexture(&upscaleBuffer.colorBuf);
559 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
560 ctrFlushBatch();
561}
562
563static void _drawFrame(struct mGUIRunner* runner, bool faded) {
564 UNUSED(runner);
565
566 C3D_Tex* tex = &outputTexture;
567
568 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
569 C3D_SafeDisplayTransfer(
570 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
571 tex->data, GX_BUFFER_DIM(256, 256),
572 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
573 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
574 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
575
576 if (hasSound == NO_SOUND) {
577 blip_clear(runner->core->getAudioChannel(runner->core, 0));
578 blip_clear(runner->core->getAudioChannel(runner->core, 1));
579 }
580
581 gspWaitForPPF();
582 _drawTex(runner->core, faded);
583}
584
585static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
586
587 C3D_Tex* tex = &outputTexture;
588
589 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
590
591 unsigned y;
592 for (y = 0; y < height; ++y) {
593 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
594 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
595 }
596
597 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
598 C3D_SafeDisplayTransfer(
599 (u32*) newPixels, GX_BUFFER_DIM(256, height),
600 tex->data, GX_BUFFER_DIM(256, 256),
601 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
602 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
603 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
604 gspWaitForPPF();
605 linearFree(newPixels);
606
607 _drawTex(runner->core, faded);
608}
609
610static uint16_t _pollGameInput(struct mGUIRunner* runner) {
611 UNUSED(runner);
612
613 hidScanInput();
614 uint32_t activeKeys = hidKeysHeld();
615 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
616 keys |= (activeKeys >> 24) & 0xF0;
617 return keys;
618}
619
620static void _incrementScreenMode(struct mGUIRunner* runner) {
621 UNUSED(runner);
622 screenMode = (screenMode + 1) % SM_MAX;
623 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
624
625 C3D_RenderBufClear(&bottomScreen);
626 C3D_RenderBufClear(&topScreen);
627}
628
629static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
630 UNUSED(runner);
631 frameLimiter = limit;
632}
633
634static uint32_t _pollInput(const struct mInputMap* map) {
635 hidScanInput();
636 int activeKeys = hidKeysHeld();
637 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
638}
639
640static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
641 hidScanInput();
642 if (!(hidKeysHeld() & KEY_TOUCH)) {
643 return GUI_CURSOR_NOT_PRESENT;
644 }
645 touchPosition pos;
646 hidTouchRead(&pos);
647 *x = pos.px;
648 *y = pos.py;
649 return GUI_CURSOR_DOWN;
650}
651
652static void _sampleRotation(struct mRotationSource* source) {
653 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
654 // Work around ctrulib getting the entries wrong
655 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
656 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
657}
658
659static int32_t _readTiltX(struct mRotationSource* source) {
660 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
661 return rotation->accel.x << 18L;
662}
663
664static int32_t _readTiltY(struct mRotationSource* source) {
665 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
666 return rotation->accel.y << 18L;
667}
668
669static int32_t _readGyroZ(struct mRotationSource* source) {
670 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
671 return rotation->gyro.y << 18L; // Yes, y
672}
673
674static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
675 UNUSED(colorFormats);
676 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
677
678 _resetCamera(imageSource);
679
680 CAMU_SetTrimming(PORT_CAM1, true);
681 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
682 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
683
684 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
685 free(imageSource->buffer);
686 imageSource->buffer = NULL;
687 }
688 imageSource->bufferSize = w * h * 2;
689 if (!imageSource->buffer) {
690 imageSource->buffer = malloc(imageSource->bufferSize);
691 }
692 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
693 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
694 CAMU_Activate(imageSource->cam);
695 CAMU_ClearBuffer(PORT_CAM1);
696 CAMU_StartCapture(PORT_CAM1);
697 CAMU_PlayShutterSound(SHUTTER_SOUND_TYPE_MOVIE);
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}