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