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