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