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 bool _running(struct mGUIRunner* runner) {
682 return aptMainLoop();
683}
684
685static uint32_t _pollInput(const struct mInputMap* map) {
686 hidScanInput();
687 int activeKeys = hidKeysHeld();
688 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
689}
690
691static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
692 hidScanInput();
693 if (!(hidKeysHeld() & KEY_TOUCH)) {
694 return GUI_CURSOR_NOT_PRESENT;
695 }
696 touchPosition pos;
697 hidTouchRead(&pos);
698 *x = pos.px;
699 *y = pos.py;
700 return GUI_CURSOR_DOWN;
701}
702
703static void _sampleRotation(struct mRotationSource* source) {
704 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
705 // Work around ctrulib getting the entries wrong
706 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
707 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
708}
709
710static int32_t _readTiltX(struct mRotationSource* source) {
711 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
712 return rotation->accel.x << 18L;
713}
714
715static int32_t _readTiltY(struct mRotationSource* source) {
716 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
717 return rotation->accel.y << 18L;
718}
719
720static int32_t _readGyroZ(struct mRotationSource* source) {
721 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
722 return rotation->gyro.y << 18L; // Yes, y
723}
724
725static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
726 UNUSED(colorFormats);
727 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
728
729 _resetCamera(imageSource);
730
731 CAMU_SetTrimming(PORT_CAM1, true);
732 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
733 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
734
735 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
736 free(imageSource->buffer);
737 imageSource->buffer = NULL;
738 }
739 imageSource->bufferSize = w * h * 2;
740 if (!imageSource->buffer) {
741 imageSource->buffer = malloc(imageSource->bufferSize);
742 }
743 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
744 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
745 CAMU_Activate(imageSource->cam);
746 CAMU_ClearBuffer(PORT_CAM1);
747 CAMU_StartCapture(PORT_CAM1);
748
749 if (imageSource->cam) {
750 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
751 }
752}
753
754static void _stopRequestImage(struct mImageSource* source) {
755 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
756
757 free(imageSource->buffer);
758 imageSource->buffer = NULL;
759 svcCloseHandle(imageSource->handles[0]);
760 svcCloseHandle(imageSource->handles[1]);
761
762 CAMU_StopCapture(PORT_CAM1);
763 CAMU_Activate(SELECT_NONE);
764}
765
766
767static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
768 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
769
770 if (!imageSource->cam) {
771 memset(imageSource->buffer, 0, imageSource->bufferSize);
772 *buffer = imageSource->buffer;
773 *stride = 128;
774 *colorFormat = mCOLOR_RGB565;
775 return;
776 }
777
778 s32 i;
779 svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
780
781 if (i == 0) {
782 *buffer = imageSource->buffer;
783 *stride = 128;
784 *colorFormat = mCOLOR_RGB565;
785 } else {
786 CAMU_ClearBuffer(PORT_CAM1);
787 CAMU_StartCapture(PORT_CAM1);
788 }
789
790 svcCloseHandle(imageSource->handles[0]);
791 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
792}
793
794static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
795 UNUSED(stream);
796 if (hasSound == CSND_SUPPORTED) {
797 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
798 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
799 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
800 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
801 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
802 if (audioPos == AUDIO_SAMPLES * 3) {
803 u8 playing = 0;
804 csndIsPlaying(0x8, &playing);
805 if (!playing) {
806 CSND_SetPlayState(0x8, 1);
807 CSND_SetPlayState(0x9, 1);
808 csndExecCmds(false);
809 }
810 }
811 } else if (hasSound == DSP_SUPPORTED) {
812 int startId = bufferId;
813 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
814 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
815 if (bufferId == startId) {
816 blip_clear(left);
817 blip_clear(right);
818 return;
819 }
820 }
821 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
822 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
823 dspBuffer[bufferId].data_pcm16 = tmpBuf;
824 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
825 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
826 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
827 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
828 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
829 }
830}
831
832int main() {
833 rotation.d.sample = _sampleRotation;
834 rotation.d.readTiltX = _readTiltX;
835 rotation.d.readTiltY = _readTiltY;
836 rotation.d.readGyroZ = _readGyroZ;
837
838 stream.videoDimensionsChanged = 0;
839 stream.postVideoFrame = 0;
840 stream.postAudioFrame = 0;
841 stream.postAudioBuffer = _postAudioBuffer;
842
843 camera.d.startRequestImage = _startRequestImage;
844 camera.d.stopRequestImage = _stopRequestImage;
845 camera.d.requestImage = _requestImage;
846 camera.buffer = NULL;
847 camera.bufferSize = 0;
848 camera.cam = SELECT_IN1;
849
850 if (!allocateRomBuffer()) {
851 return 1;
852 }
853
854 aptHook(&cookie, _aptHook, 0);
855
856 ptmuInit();
857 camInit();
858
859 hasSound = NO_SOUND;
860 if (!ndspInit()) {
861 hasSound = DSP_SUPPORTED;
862 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
863 ndspSetOutputCount(1);
864 ndspChnReset(0);
865 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
866 ndspChnSetInterp(0, NDSP_INTERP_NONE);
867 ndspChnSetRate(0, 0x8000);
868 ndspChnWaveBufClear(0);
869 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
870 memset(dspBuffer, 0, sizeof(dspBuffer));
871 int i;
872 for (i = 0; i < DSP_BUFFERS; ++i) {
873 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
874 dspBuffer[i].nsamples = AUDIO_SAMPLES;
875 }
876 }
877
878 if (hasSound == NO_SOUND && !csndInit()) {
879 hasSound = CSND_SUPPORTED;
880 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
881 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
882 }
883
884 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
885
886 if (!_initGpu()) {
887 outputTexture.data = 0;
888 _cleanup();
889 return 1;
890 }
891
892 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
893 _cleanup();
894 return 1;
895 }
896 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
897 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
898 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
899 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
900
901 // Zero texture data to make sure no garbage around the border interferes with filtering
902 GX_MemoryFill(
903 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
904 NULL, 0, NULL, 0);
905 gspWaitForPSC0();
906
907 struct GUIFont* font = GUIFontCreate();
908
909 if (!font) {
910 _cleanup();
911 return 1;
912 }
913
914 struct mGUIRunner runner = {
915 .params = {
916 320, 240,
917 font, "/",
918 _drawStart, _drawEnd,
919 _pollInput, _pollCursor,
920 _batteryState,
921 _guiPrepare, _guiFinish,
922 },
923 .keySources = (struct GUIInputKeys[]) {
924 {
925 .name = "3DS Input",
926 .id = _3DS_INPUT,
927 .keyNames = (const char*[]) {
928 "A",
929 "B",
930 "Select",
931 "Start",
932 "D-Pad Right",
933 "D-Pad Left",
934 "D-Pad Up",
935 "D-Pad Down",
936 "R",
937 "L",
938 "X",
939 "Y",
940 0,
941 0,
942 "ZL",
943 "ZR",
944 0,
945 0,
946 0,
947 0,
948 0,
949 0,
950 0,
951 0,
952 "C-Stick Right",
953 "C-Stick Left",
954 "C-Stick Up",
955 "C-Stick Down",
956 },
957 .nKeys = 28
958 },
959 { .id = 0 }
960 },
961 .configExtra = (struct GUIMenuItem[]) {
962 {
963 .title = "Screen mode",
964 .data = "screenMode",
965 .submenu = 0,
966 .state = SM_PA_TOP,
967 .validStates = (const char*[]) {
968 "Pixel-Accurate/Bottom",
969 "Aspect-Ratio Fit/Bottom",
970 "Stretched/Bottom",
971 "Pixel-Accurate/Top",
972 "Aspect-Ratio Fit/Top",
973 "Stretched/Top",
974 },
975 .nStates = 6
976 },
977 {
978 .title = "Filtering",
979 .data = "filterMode",
980 .submenu = 0,
981 .state = FM_LINEAR_2x,
982 .validStates = (const char*[]) {
983 NULL, // Disable choosing nearest neighbor; it always looks bad
984 "Bilinear (smoother)",
985 "Bilinear (pixelated)",
986 },
987 .nStates = 3
988 },
989 {
990 .title = "Screen darkening",
991 .data = "darkenMode",
992 .submenu = 0,
993 .state = DM_NATIVE,
994 .validStates = (const char*[]) {
995 "None",
996 "Dark",
997 "Very dark",
998 "Grayed",
999 },
1000 .nStates = 4
1001 },
1002 {
1003 .title = "Camera",
1004 .data = "camera",
1005 .submenu = 0,
1006 .state = 1,
1007 .validStates = (const char*[]) {
1008 "None",
1009 "Inner",
1010 "Outer",
1011 },
1012 .nStates = 3
1013 }
1014 },
1015 .nConfigExtra = 4,
1016 .setup = _setup,
1017 .teardown = 0,
1018 .gameLoaded = _gameLoaded,
1019 .gameUnloaded = _gameUnloaded,
1020 .prepareForFrame = 0,
1021 .drawFrame = _drawFrame,
1022 .drawScreenshot = _drawScreenshot,
1023 .paused = _gameUnloaded,
1024 .unpaused = _gameLoaded,
1025 .incrementScreenMode = _incrementScreenMode,
1026 .setFrameLimiter = _setFrameLimiter,
1027 .pollGameInput = _pollGameInput,
1028 .running = _running
1029 };
1030
1031 mGUIInit(&runner, "3ds");
1032
1033 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
1034 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
1035 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
1036 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
1037 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
1038 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
1039 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
1040 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
1041 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
1042 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
1043
1044 mGUIRunloop(&runner);
1045 mGUIDeinit(&runner);
1046
1047 _cleanup();
1048 return 0;
1049}