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/internal/gba/gba.h>
12#include <mgba/internal/gba/input.h>
13#include <mgba/internal/gba/video.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 void* outputBuffer;
88static struct mAVStream stream;
89static int16_t* audioLeft = 0;
90static int16_t* audioRight = 0;
91static size_t audioPos = 0;
92static C3D_Tex outputTexture;
93static ndspWaveBuf dspBuffer[DSP_BUFFERS];
94static int bufferId = 0;
95static bool frameLimiter = true;
96static u64 tickCounter;
97
98static C3D_RenderTarget* topScreen[2];
99static C3D_RenderTarget* bottomScreen[2];
100static int doubleBuffer = 0;
101static bool frameStarted = false;
102
103static C3D_RenderTarget* upscaleBuffer;
104static C3D_Tex upscaleBufferTex;
105
106static aptHookCookie cookie;
107
108extern bool allocateRomBuffer(void);
109
110static bool _initGpu(void) {
111 if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
112 return false;
113 }
114
115 topScreen[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
116 topScreen[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
117 bottomScreen[0] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
118 bottomScreen[1] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
119 if (!topScreen[0] || !topScreen[1] || !bottomScreen[0] || !bottomScreen[1]) {
120 return false;
121 }
122
123 if (!C3D_TexInitVRAM(&upscaleBufferTex, 512, 512, GPU_RB_RGB8)) {
124 return false;
125 }
126 upscaleBuffer = C3D_RenderTargetCreateFromTex(&upscaleBufferTex, GPU_TEXFACE_2D, 0, 0);
127 if (!upscaleBuffer) {
128 return false;
129 }
130
131 C3D_RenderTargetSetClear(upscaleBuffer, C3D_CLEAR_COLOR, 0, 0);
132
133 return ctrInitGpu();
134}
135
136static void _cleanup(void) {
137 ctrDeinitGpu();
138
139 if (outputBuffer) {
140 linearFree(outputBuffer);
141 }
142
143 C3D_RenderTargetDelete(topScreen[0]);
144 C3D_RenderTargetDelete(topScreen[1]);
145 C3D_RenderTargetDelete(bottomScreen[0]);
146 C3D_RenderTargetDelete(bottomScreen[1]);
147 C3D_RenderTargetDelete(upscaleBuffer);
148 C3D_TexDelete(&upscaleBufferTex);
149 C3D_TexDelete(&outputTexture);
150 C3D_Fini();
151
152 gfxExit();
153
154 if (hasSound != NO_SOUND) {
155 linearFree(audioLeft);
156 }
157
158 if (hasSound == DSP_SUPPORTED) {
159 ndspExit();
160 }
161
162 camExit();
163 ndspExit();
164 ptmuExit();
165}
166
167static void _aptHook(APT_HookType hook, void* user) {
168 UNUSED(user);
169 switch (hook) {
170 case APTHOOK_ONEXIT:
171 _cleanup();
172 exit(0);
173 break;
174 default:
175 break;
176 }
177}
178
179static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
180 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
181}
182
183static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
184
185static void _drawStart(void) {
186}
187
188static void _frameStart(void) {
189 if (frameStarted) {
190 return;
191 }
192 frameStarted = true;
193 u8 flags = 0;
194 if (!frameLimiter) {
195 if (tickCounter + 4481000 > svcGetSystemTick()) {
196 flags = C3D_FRAME_NONBLOCK;
197 } else {
198 tickCounter = svcGetSystemTick();
199 }
200 }
201 C3D_FrameBegin(flags);
202 // Mark both buffers used to make sure they get cleared
203 C3D_FrameDrawOn(topScreen[doubleBuffer]);
204 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
205 ctrStartFrame();
206}
207
208static void _drawEnd(void) {
209 if (!frameStarted) {
210 return;
211 }
212 ctrEndFrame();
213 C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
214 C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
215 C3D_FrameEnd(GX_CMDLIST_FLUSH);
216 frameStarted = false;
217
218 doubleBuffer ^= 1;
219 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
220 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
221}
222
223static int _batteryState(void) {
224 u8 charge;
225 u8 adapter;
226 PTMU_GetBatteryLevel(&charge);
227 PTMU_GetBatteryChargeState(&adapter);
228 int state = 0;
229 if (adapter) {
230 state |= BATTERY_CHARGING;
231 }
232 if (charge > 0) {
233 --charge;
234 }
235 return state | charge;
236}
237
238static void _guiPrepare(void) {
239 _frameStart();
240 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
241 ctrSetViewportSize(320, 240, true);
242}
243
244static void _guiFinish(void) {
245 ctrFlushBatch();
246}
247
248static void _resetCamera(struct m3DSImageSource* imageSource) {
249 if (!imageSource->cam) {
250 return;
251 }
252 CAMU_SetSize(imageSource->cam, SIZE_QCIF, CONTEXT_A);
253 CAMU_SetOutputFormat(imageSource->cam, OUTPUT_RGB_565, CONTEXT_A);
254 CAMU_SetFrameRate(imageSource->cam, FRAME_RATE_30);
255 CAMU_FlipImage(imageSource->cam, FLIP_NONE, CONTEXT_A);
256
257 CAMU_SetNoiseFilter(imageSource->cam, true);
258 CAMU_SetAutoExposure(imageSource->cam, false);
259 CAMU_SetAutoWhiteBalance(imageSource->cam, false);
260}
261
262static void _setup(struct mGUIRunner* runner) {
263 uint8_t mask;
264 if (R_SUCCEEDED(svcGetProcessAffinityMask(&mask, CUR_PROCESS_HANDLE, 4)) && mask >= 4) {
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 * 2, 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
381static void _gameUnloaded(struct mGUIRunner* runner) {
382 osSetSpeedupEnable(false);
383 frameLimiter = true;
384
385 switch (runner->core->platform(runner->core)) {
386#ifdef M_CORE_GBA
387 // TODO: Move these to callbacks
388 case PLATFORM_GBA:
389 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
390 HIDUSER_DisableAccelerometer();
391 }
392 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
393 HIDUSER_DisableGyroscope();
394 }
395 break;
396#endif
397#ifdef M_CORE_GB
398 case PLATFORM_GB:
399 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
400 HIDUSER_DisableAccelerometer();
401 }
402 break;
403#endif
404 default:
405 break;
406 }
407}
408
409static void _drawTex(struct mCore* core, bool faded) {
410 _frameStart();
411 unsigned screen_w, screen_h;
412 switch (screenMode) {
413 case SM_PA_BOTTOM:
414 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
415 screen_w = 320;
416 screen_h = 240;
417 break;
418 case SM_PA_TOP:
419 C3D_FrameDrawOn(topScreen[doubleBuffer]);
420 screen_w = 400;
421 screen_h = 240;
422 break;
423 default:
424 C3D_FrameDrawOn(upscaleBuffer);
425 screen_w = 512;
426 screen_h = 512;
427 break;
428 }
429
430 unsigned corew, coreh;
431 core->desiredVideoDimensions(core, &corew, &coreh);
432
433 int w = corew;
434 int h = coreh;
435 // Get greatest common divisor
436 while (w != 0) {
437 int temp = h % w;
438 h = w;
439 w = temp;
440 }
441 int gcd = h;
442 unsigned aspectw = corew / gcd;
443 unsigned aspecth = coreh / gcd;
444 int x = 0;
445 int y = 0;
446
447 switch (screenMode) {
448 case SM_PA_TOP:
449 case SM_PA_BOTTOM:
450 w = corew;
451 h = coreh;
452 x = (screen_w - w) / 2;
453 y = (screen_h - h) / 2;
454 ctrSetViewportSize(screen_w, screen_h, true);
455 break;
456 case SM_AF_TOP:
457 case SM_AF_BOTTOM:
458 case SM_SF_TOP:
459 case SM_SF_BOTTOM:
460 default:
461 if (filterMode == FM_LINEAR_1x) {
462 w = corew;
463 h = coreh;
464 } else {
465 w = corew * 2;
466 h = coreh * 2;
467 }
468 ctrSetViewportSize(screen_w, screen_h, false);
469 break;
470 }
471
472 ctrActivateTexture(&outputTexture);
473 u32 color;
474 if (!faded) {
475 color = 0xFFFFFFFF;
476 switch (darkenMode) {
477 case DM_NATIVE:
478 case DM_MAX:
479 break;
480 case DM_MULT_SCALE_BIAS:
481 ctrTextureBias(0x070707);
482 // Fall through
483 case DM_MULT_SCALE:
484 color = 0xFF707070;
485 // Fall through
486 case DM_MULT:
487 ctrTextureMultiply();
488 break;
489 }
490 } else {
491 color = 0xFF484848;
492 switch (darkenMode) {
493 case DM_NATIVE:
494 case DM_MAX:
495 break;
496 case DM_MULT_SCALE_BIAS:
497 ctrTextureBias(0x030303);
498 // Fall through
499 case DM_MULT_SCALE:
500 color = 0xFF303030;
501 // Fall through
502 case DM_MULT:
503 ctrTextureMultiply();
504 break;
505 }
506
507 }
508 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
509 ctrFlushBatch();
510
511 corew = w;
512 coreh = h;
513 screen_h = 240;
514 if (screenMode < SM_PA_TOP) {
515 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
516 screen_w = 320;
517 } else {
518 C3D_FrameDrawOn(topScreen[doubleBuffer]);
519 screen_w = 400;
520 }
521 ctrSetViewportSize(screen_w, screen_h, true);
522
523 switch (screenMode) {
524 default:
525 return;
526 case SM_AF_TOP:
527 case SM_AF_BOTTOM:
528 w = screen_w / aspectw;
529 h = screen_h / aspecth;
530 if (w * aspecth > screen_h) {
531 w = aspectw * h;
532 h = aspecth * h;
533 } else {
534 h = aspecth * w;
535 w = aspectw * w;
536 }
537 break;
538 case SM_SF_TOP:
539 case SM_SF_BOTTOM:
540 w = screen_w;
541 h = screen_h;
542 break;
543 }
544
545 x = (screen_w - w) / 2;
546 y = (screen_h - h) / 2;
547 ctrActivateTexture(&upscaleBufferTex);
548 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
549 ctrFlushBatch();
550}
551
552static void _drawFrame(struct mGUIRunner* runner, bool faded) {
553 UNUSED(runner);
554
555 C3D_Tex* tex = &outputTexture;
556
557 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
558 C3D_SafeDisplayTransfer(
559 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
560 tex->data, GX_BUFFER_DIM(256, 256),
561 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
562 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
563 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
564
565 if (hasSound == NO_SOUND) {
566 blip_clear(runner->core->getAudioChannel(runner->core, 0));
567 blip_clear(runner->core->getAudioChannel(runner->core, 1));
568 }
569
570 gspWaitForPPF();
571 _drawTex(runner->core, faded);
572}
573
574static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
575
576 C3D_Tex* tex = &outputTexture;
577
578 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
579
580 unsigned y;
581 for (y = 0; y < height; ++y) {
582 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
583 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
584 }
585
586 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
587 C3D_SafeDisplayTransfer(
588 (u32*) newPixels, GX_BUFFER_DIM(256, height),
589 tex->data, GX_BUFFER_DIM(256, 256),
590 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
591 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
592 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
593 gspWaitForPPF();
594 linearFree(newPixels);
595
596 _drawTex(runner->core, faded);
597}
598
599static uint16_t _pollGameInput(struct mGUIRunner* runner) {
600 UNUSED(runner);
601
602 hidScanInput();
603 uint32_t activeKeys = hidKeysHeld();
604 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
605 keys |= (activeKeys >> 24) & 0xF0;
606 return keys;
607}
608
609static void _incrementScreenMode(struct mGUIRunner* runner) {
610 UNUSED(runner);
611 screenMode = (screenMode + 1) % SM_MAX;
612 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
613
614 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
615 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
616}
617
618static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
619 UNUSED(runner);
620 if (frameLimiter == limit) {
621 return;
622 }
623 frameLimiter = limit;
624 tickCounter = svcGetSystemTick();
625}
626
627static bool _running(struct mGUIRunner* runner) {
628 UNUSED(runner);
629 return aptMainLoop();
630}
631
632static uint32_t _pollInput(const struct mInputMap* map) {
633 hidScanInput();
634 int activeKeys = hidKeysHeld();
635 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
636}
637
638static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
639 hidScanInput();
640 if (!(hidKeysHeld() & KEY_TOUCH)) {
641 return GUI_CURSOR_NOT_PRESENT;
642 }
643 touchPosition pos;
644 hidTouchRead(&pos);
645 *x = pos.px;
646 *y = pos.py;
647 return GUI_CURSOR_DOWN;
648}
649
650static void _sampleRotation(struct mRotationSource* source) {
651 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
652 // Work around ctrulib getting the entries wrong
653 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
654 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
655}
656
657static int32_t _readTiltX(struct mRotationSource* source) {
658 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
659 return rotation->accel.x << 18L;
660}
661
662static int32_t _readTiltY(struct mRotationSource* source) {
663 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
664 return rotation->accel.y << 18L;
665}
666
667static int32_t _readGyroZ(struct mRotationSource* source) {
668 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
669 return rotation->gyro.y << 18L; // Yes, y
670}
671
672static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
673 UNUSED(colorFormats);
674 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
675
676 _resetCamera(imageSource);
677
678 CAMU_SetTrimming(PORT_CAM1, true);
679 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
680 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
681
682 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
683 free(imageSource->buffer);
684 imageSource->buffer = NULL;
685 }
686 imageSource->bufferSize = w * h * 2;
687 if (!imageSource->buffer) {
688 imageSource->buffer = malloc(imageSource->bufferSize);
689 }
690 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
691 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
692 CAMU_Activate(imageSource->cam);
693 CAMU_ClearBuffer(PORT_CAM1);
694 CAMU_StartCapture(PORT_CAM1);
695
696 if (imageSource->cam) {
697 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
698 }
699}
700
701static void _stopRequestImage(struct mImageSource* source) {
702 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
703
704 free(imageSource->buffer);
705 imageSource->buffer = NULL;
706 svcCloseHandle(imageSource->handles[0]);
707 svcCloseHandle(imageSource->handles[1]);
708
709 CAMU_StopCapture(PORT_CAM1);
710 CAMU_Activate(SELECT_NONE);
711}
712
713
714static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
715 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
716
717 if (!imageSource->cam) {
718 memset(imageSource->buffer, 0, imageSource->bufferSize);
719 *buffer = imageSource->buffer;
720 *stride = 128;
721 *colorFormat = mCOLOR_RGB565;
722 return;
723 }
724
725 s32 i;
726 svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
727
728 if (i == 0) {
729 *buffer = imageSource->buffer;
730 *stride = 128;
731 *colorFormat = mCOLOR_RGB565;
732 } else {
733 CAMU_ClearBuffer(PORT_CAM1);
734 CAMU_StartCapture(PORT_CAM1);
735 }
736
737 svcCloseHandle(imageSource->handles[0]);
738 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
739}
740
741static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
742 UNUSED(stream);
743 if (hasSound == DSP_SUPPORTED) {
744 int startId = bufferId;
745 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
746 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
747 if (bufferId == startId) {
748 blip_clear(left);
749 blip_clear(right);
750 return;
751 }
752 }
753 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
754 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
755 dspBuffer[bufferId].data_pcm16 = tmpBuf;
756 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
757 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
758 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
759 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
760 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
761 }
762}
763
764int main() {
765 rotation.d.sample = _sampleRotation;
766 rotation.d.readTiltX = _readTiltX;
767 rotation.d.readTiltY = _readTiltY;
768 rotation.d.readGyroZ = _readGyroZ;
769
770 stream.videoDimensionsChanged = 0;
771 stream.postVideoFrame = 0;
772 stream.postAudioFrame = 0;
773 stream.postAudioBuffer = _postAudioBuffer;
774
775 camera.d.startRequestImage = _startRequestImage;
776 camera.d.stopRequestImage = _stopRequestImage;
777 camera.d.requestImage = _requestImage;
778 camera.buffer = NULL;
779 camera.bufferSize = 0;
780 camera.cam = SELECT_IN1;
781
782 if (!allocateRomBuffer()) {
783 return 1;
784 }
785
786 aptHook(&cookie, _aptHook, 0);
787
788 ptmuInit();
789 camInit();
790
791 hasSound = NO_SOUND;
792 if (!ndspInit()) {
793 hasSound = DSP_SUPPORTED;
794 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
795 ndspSetOutputCount(1);
796 ndspChnReset(0);
797 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
798 ndspChnSetInterp(0, NDSP_INTERP_NONE);
799 ndspChnSetRate(0, 0x8000);
800 ndspChnWaveBufClear(0);
801 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
802 memset(dspBuffer, 0, sizeof(dspBuffer));
803 int i;
804 for (i = 0; i < DSP_BUFFERS; ++i) {
805 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
806 dspBuffer[i].nsamples = AUDIO_SAMPLES;
807 }
808 }
809
810 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
811
812 if (!_initGpu()) {
813 outputTexture.data = 0;
814 _cleanup();
815 return 1;
816 }
817
818 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
819 _cleanup();
820 return 1;
821 }
822 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
823 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
824 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
825 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
826
827 // Zero texture data to make sure no garbage around the border interferes with filtering
828 GX_MemoryFill(
829 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
830 NULL, 0, NULL, 0);
831 gspWaitForPSC0();
832
833 struct GUIFont* font = GUIFontCreate();
834
835 if (!font) {
836 _cleanup();
837 return 1;
838 }
839
840 struct mGUIRunner runner = {
841 .params = {
842 320, 240,
843 font, "/",
844 _drawStart, _drawEnd,
845 _pollInput, _pollCursor,
846 _batteryState,
847 _guiPrepare, _guiFinish,
848 },
849 .keySources = (struct GUIInputKeys[]) {
850 {
851 .name = "3DS Input",
852 .id = _3DS_INPUT,
853 .keyNames = (const char*[]) {
854 "A",
855 "B",
856 "Select",
857 "Start",
858 "D-Pad Right",
859 "D-Pad Left",
860 "D-Pad Up",
861 "D-Pad Down",
862 "R",
863 "L",
864 "X",
865 "Y",
866 0,
867 0,
868 "ZL",
869 "ZR",
870 0,
871 0,
872 0,
873 0,
874 0,
875 0,
876 0,
877 0,
878 "C-Stick Right",
879 "C-Stick Left",
880 "C-Stick Up",
881 "C-Stick Down",
882 },
883 .nKeys = 28
884 },
885 { .id = 0 }
886 },
887 .configExtra = (struct GUIMenuItem[]) {
888 {
889 .title = "Screen mode",
890 .data = "screenMode",
891 .submenu = 0,
892 .state = SM_PA_TOP,
893 .validStates = (const char*[]) {
894 "Pixel-Accurate/Bottom",
895 "Aspect-Ratio Fit/Bottom",
896 "Stretched/Bottom",
897 "Pixel-Accurate/Top",
898 "Aspect-Ratio Fit/Top",
899 "Stretched/Top",
900 },
901 .nStates = 6
902 },
903 {
904 .title = "Filtering",
905 .data = "filterMode",
906 .submenu = 0,
907 .state = FM_LINEAR_2x,
908 .validStates = (const char*[]) {
909 NULL, // Disable choosing nearest neighbor; it always looks bad
910 "Bilinear (smoother)",
911 "Bilinear (pixelated)",
912 },
913 .nStates = 3
914 },
915 {
916 .title = "Screen darkening",
917 .data = "darkenMode",
918 .submenu = 0,
919 .state = DM_NATIVE,
920 .validStates = (const char*[]) {
921 "None",
922 "Dark",
923 "Very dark",
924 "Grayed",
925 },
926 .nStates = 4
927 },
928 {
929 .title = "Camera",
930 .data = "camera",
931 .submenu = 0,
932 .state = 1,
933 .validStates = (const char*[]) {
934 "None",
935 "Inner",
936 "Outer",
937 },
938 .nStates = 3
939 }
940 },
941 .nConfigExtra = 4,
942 .setup = _setup,
943 .teardown = 0,
944 .gameLoaded = _gameLoaded,
945 .gameUnloaded = _gameUnloaded,
946 .prepareForFrame = 0,
947 .drawFrame = _drawFrame,
948 .drawScreenshot = _drawScreenshot,
949 .paused = _gameUnloaded,
950 .unpaused = _gameLoaded,
951 .incrementScreenMode = _incrementScreenMode,
952 .setFrameLimiter = _setFrameLimiter,
953 .pollGameInput = _pollGameInput,
954 .running = _running
955 };
956
957 runner.autosave.running = true;
958 MutexInit(&runner.autosave.mutex);
959 ConditionInit(&runner.autosave.cond);
960
961 APT_SetAppCpuTimeLimit(20);
962 runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true);
963
964 mGUIInit(&runner, "3ds");
965
966 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
967 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
968 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
969 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
970 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
971 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
972 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
973 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
974 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
975 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
976
977 mGUIRunloop(&runner);
978 mGUIDeinit(&runner);
979
980 _cleanup();
981 return 0;
982}