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