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