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