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