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