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