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