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 u32 frameCounter;
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
211 int screen = screenMode >= SM_PA_TOP ? GSP_SCREEN_TOP : GSP_SCREEN_BOTTOM;
212 if (frameLimiter) {
213 u32 oldFrame = frameCounter;
214 frameCounter = C3D_FrameCounter(screen);
215 while (oldFrame == frameCounter) {
216 gspWaitForAnyEvent();
217 frameCounter = C3D_FrameCounter(screen);
218 }
219 } else {
220 frameCounter = C3D_FrameCounter(screen);
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, 268111856.f / 4481136.f, 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 switch (screenMode) {
480 case SM_PA_BOTTOM:
481 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
482 screen_w = 320;
483 screen_h = 240;
484 break;
485 case SM_PA_TOP:
486 C3D_FrameDrawOn(topScreen[doubleBuffer]);
487 screen_w = 400;
488 screen_h = 240;
489 break;
490 default:
491 C3D_FrameDrawOn(upscaleBuffer);
492 screen_w = 512;
493 screen_h = 512;
494 break;
495 }
496
497 unsigned corew, coreh;
498 core->desiredVideoDimensions(core, &corew, &coreh);
499
500 int w = corew;
501 int h = coreh;
502 if (sgbCrop && w == 256 && h == 224) {
503 w = GB_VIDEO_HORIZONTAL_PIXELS;
504 h = GB_VIDEO_VERTICAL_PIXELS;
505 }
506 int innerw = w;
507 int innerh = h;
508 // Get greatest common divisor
509 while (w != 0) {
510 int temp = h % w;
511 h = w;
512 w = temp;
513 }
514 int gcd = h;
515 unsigned aspectw = innerw / gcd;
516 unsigned aspecth = innerh / gcd;
517 int x = 0;
518 int y = 0;
519
520 switch (screenMode) {
521 case SM_PA_TOP:
522 case SM_PA_BOTTOM:
523 w = corew;
524 h = coreh;
525 x = (screen_w - w) / 2;
526 y = (screen_h - h) / 2;
527 ctrSetViewportSize(screen_w, screen_h, true);
528 break;
529 case SM_AF_TOP:
530 case SM_AF_BOTTOM:
531 case SM_SF_TOP:
532 case SM_SF_BOTTOM:
533 default:
534 if (filterMode == FM_LINEAR_1x) {
535 w = corew;
536 h = coreh;
537 } else {
538 w = corew * 2;
539 h = coreh * 2;
540 }
541 ctrSetViewportSize(screen_w, screen_h, false);
542 break;
543 }
544
545 uint32_t color = _setupTex(activeOutputTexture, faded);
546 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
547 if (both) {
548 color = _setupTex(activeOutputTexture ^ 1, faded);
549 ctrAddRectEx(color & 0x7FFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
550 }
551 ctrFlushBatch();
552
553 innerw = corew;
554 innerh = coreh;
555 corew = w;
556 coreh = h;
557 screen_h = 240;
558 if (screenMode < SM_PA_TOP) {
559 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
560 screen_w = 320;
561 } else {
562 C3D_FrameDrawOn(topScreen[doubleBuffer]);
563 screen_w = 400;
564 }
565 ctrSetViewportSize(screen_w, screen_h, true);
566
567 float afw, afh;
568 switch (screenMode) {
569 default:
570 return;
571 case SM_AF_TOP:
572 case SM_AF_BOTTOM:
573 afw = screen_w / (float) aspectw;
574 afh = screen_h / (float) aspecth;
575 if (afw * aspecth > screen_h) {
576 w = innerw * afh / gcd;
577 h = innerh * afh / gcd;
578 } else {
579 h = innerh * afw / gcd;
580 w = innerw * afw / gcd;
581 }
582 break;
583 case SM_SF_TOP:
584 case SM_SF_BOTTOM:
585 w = screen_w;
586 h = screen_h;
587 break;
588 }
589
590 x = (screen_w - w) / 2;
591 y = (screen_h - h) / 2;
592 ctrActivateTexture(&upscaleBufferTex);
593 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
594 ctrFlushBatch();
595}
596
597static void _prepareForFrame(struct mGUIRunner* runner) {
598 UNUSED(runner);
599 activeOutputTexture ^= 1;
600}
601
602static void _drawFrame(struct mGUIRunner* runner, bool faded) {
603 UNUSED(runner);
604 C3D_Tex* tex = &outputTexture[activeOutputTexture];
605
606 GSPGPU_FlushDataCache(outputBuffer, 256 * GBA_VIDEO_VERTICAL_PIXELS * 2);
607 C3D_SyncDisplayTransfer(
608 (u32*) outputBuffer, GX_BUFFER_DIM(256, GBA_VIDEO_VERTICAL_PIXELS),
609 tex->data, GX_BUFFER_DIM(256, 256),
610 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
611 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
612 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
613
614 if (hasSound == NO_SOUND) {
615 blip_clear(runner->core->getAudioChannel(runner->core, 0));
616 blip_clear(runner->core->getAudioChannel(runner->core, 1));
617 }
618
619 _drawTex(runner->core, faded, interframeBlending);
620}
621
622static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
623 C3D_Tex* tex = &outputTexture[activeOutputTexture];
624
625 if (!screenshotBuffer) {
626 screenshotBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
627 }
628 unsigned y;
629 for (y = 0; y < height; ++y) {
630 memcpy(&screenshotBuffer[y * 256], &pixels[y * width], width * sizeof(color_t));
631 memset(&screenshotBuffer[y * 256 + width], 0, (256 - width) * sizeof(color_t));
632 }
633
634 GSPGPU_FlushDataCache(screenshotBuffer, 256 * height * sizeof(color_t));
635 C3D_SyncDisplayTransfer(
636 (u32*) screenshotBuffer, GX_BUFFER_DIM(256, height),
637 tex->data, GX_BUFFER_DIM(256, 256),
638 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
639 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
640 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
641
642 _drawTex(runner->core, faded, false);
643}
644
645static uint16_t _pollGameInput(struct mGUIRunner* runner) {
646 UNUSED(runner);
647
648 hidScanInput();
649 uint32_t activeKeys = hidKeysHeld();
650 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
651 keys |= (activeKeys >> 24) & 0xF0;
652 return keys;
653}
654
655static void _incrementScreenMode(struct mGUIRunner* runner) {
656 UNUSED(runner);
657 screenMode = (screenMode + 1) % SM_MAX;
658 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
659}
660
661static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
662 UNUSED(runner);
663 if (frameLimiter == limit) {
664 return;
665 }
666 frameLimiter = limit;
667}
668
669static bool _running(struct mGUIRunner* runner) {
670 UNUSED(runner);
671 return aptMainLoop();
672}
673
674static uint32_t _pollInput(const struct mInputMap* map) {
675 hidScanInput();
676 int activeKeys = hidKeysHeld();
677 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
678}
679
680static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
681 hidScanInput();
682 if (!(hidKeysHeld() & KEY_TOUCH)) {
683 return GUI_CURSOR_NOT_PRESENT;
684 }
685 touchPosition pos;
686 hidTouchRead(&pos);
687 *x = pos.px;
688 *y = pos.py;
689 return GUI_CURSOR_DOWN;
690}
691
692static void _sampleRotation(struct mRotationSource* source) {
693 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
694 // Work around ctrulib getting the entries wrong
695 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
696 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
697}
698
699static int32_t _readTiltX(struct mRotationSource* source) {
700 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
701 return rotation->accel.x << 18L;
702}
703
704static int32_t _readTiltY(struct mRotationSource* source) {
705 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
706 return rotation->accel.y << 18L;
707}
708
709static int32_t _readGyroZ(struct mRotationSource* source) {
710 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
711 return rotation->gyro.y << 18L; // Yes, y
712}
713
714static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
715 UNUSED(colorFormats);
716 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
717
718 _resetCamera(imageSource);
719
720 CAMU_SetTrimming(PORT_CAM1, true);
721 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
722 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
723
724 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
725 free(imageSource->buffer);
726 imageSource->buffer = NULL;
727 }
728 imageSource->bufferSize = w * h * 2;
729 if (!imageSource->buffer) {
730 imageSource->buffer = malloc(imageSource->bufferSize);
731 }
732 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
733 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
734 CAMU_Activate(imageSource->cam);
735 CAMU_ClearBuffer(PORT_CAM1);
736 CAMU_StartCapture(PORT_CAM1);
737
738 if (imageSource->cam) {
739 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
740 }
741}
742
743static void _stopRequestImage(struct mImageSource* source) {
744 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
745
746 free(imageSource->buffer);
747 imageSource->buffer = NULL;
748 svcCloseHandle(imageSource->handles[0]);
749 svcCloseHandle(imageSource->handles[1]);
750
751 CAMU_StopCapture(PORT_CAM1);
752 CAMU_Activate(SELECT_NONE);
753}
754
755
756static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
757 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
758
759 if (!imageSource->cam) {
760 memset(imageSource->buffer, 0, imageSource->bufferSize);
761 *buffer = imageSource->buffer;
762 *stride = 128;
763 *colorFormat = mCOLOR_RGB565;
764 return;
765 }
766
767 s32 i;
768 svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
769
770 if (i == 0) {
771 *buffer = imageSource->buffer;
772 *stride = 128;
773 *colorFormat = mCOLOR_RGB565;
774 } else {
775 CAMU_ClearBuffer(PORT_CAM1);
776 CAMU_StartCapture(PORT_CAM1);
777 }
778
779 svcCloseHandle(imageSource->handles[0]);
780 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
781}
782
783static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
784 UNUSED(stream);
785 if (hasSound == DSP_SUPPORTED) {
786 int startId = bufferId;
787 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
788 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
789 if (bufferId == startId) {
790 blip_clear(left);
791 blip_clear(right);
792 return;
793 }
794 }
795 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
796 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
797 dspBuffer[bufferId].data_pcm16 = tmpBuf;
798 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
799 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
800 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
801 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
802 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
803 }
804}
805
806THREAD_ENTRY _core2Test(void* context) {
807 UNUSED(context);
808}
809
810int main() {
811 rotation.d.sample = _sampleRotation;
812 rotation.d.readTiltX = _readTiltX;
813 rotation.d.readTiltY = _readTiltY;
814 rotation.d.readGyroZ = _readGyroZ;
815
816 stream.videoDimensionsChanged = 0;
817 stream.postVideoFrame = 0;
818 stream.postAudioFrame = 0;
819 stream.postAudioBuffer = _postAudioBuffer;
820
821 camera.d.startRequestImage = _startRequestImage;
822 camera.d.stopRequestImage = _stopRequestImage;
823 camera.d.requestImage = _requestImage;
824 camera.buffer = NULL;
825 camera.bufferSize = 0;
826 camera.cam = SELECT_IN1;
827
828 aptHook(&cookie, _aptHook, 0);
829
830 ptmuInit();
831 camInit();
832
833 hasSound = NO_SOUND;
834 if (!ndspInit()) {
835 hasSound = DSP_SUPPORTED;
836 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
837 ndspSetOutputCount(1);
838 ndspChnReset(0);
839 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
840 ndspChnSetInterp(0, NDSP_INTERP_NONE);
841 ndspChnSetRate(0, 0x8000);
842 ndspChnWaveBufClear(0);
843 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
844 memset(dspBuffer, 0, sizeof(dspBuffer));
845 int i;
846 for (i = 0; i < DSP_BUFFERS; ++i) {
847 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
848 dspBuffer[i].nsamples = AUDIO_SAMPLES;
849 }
850 }
851
852 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
853
854 if (!_initGpu()) {
855 outputTexture[0].data = 0;
856 _cleanup();
857 return 1;
858 }
859
860 C3D_TexSetWrap(&upscaleBufferTex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
861 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
862
863 int i;
864 for (i = 0; i < 2; ++i) {
865 if (!C3D_TexInitVRAM(&outputTexture[i], 256, 256, GPU_RGB565)) {
866 _cleanup();
867 return 1;
868 }
869 C3D_TexSetWrap(&outputTexture[i], GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
870 C3D_TexSetFilter(&outputTexture[i], GPU_NEAREST, GPU_NEAREST);
871 void* outputTextureEnd = (u8*)outputTexture[i].data + 256 * 256 * 2;
872
873 // Zero texture data to make sure no garbage around the border interferes with filtering
874 GX_MemoryFill(
875 outputTexture[i].data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
876 NULL, 0, NULL, 0);
877 gspWaitForPSC0();
878 }
879
880 struct GUIFont* font = GUIFontCreate();
881
882 if (!font) {
883 _cleanup();
884 return 1;
885 }
886
887 struct mGUIRunner runner = {
888 .params = {
889 320, 240,
890 font, "/",
891 _drawStart, _drawEnd,
892 _pollInput, _pollCursor,
893 _batteryState,
894 _guiPrepare, _guiFinish,
895 },
896 .keySources = (struct GUIInputKeys[]) {
897 {
898 .name = "3DS Input",
899 .id = _3DS_INPUT,
900 .keyNames = (const char*[]) {
901 "A",
902 "B",
903 "Select",
904 "Start",
905 "D-Pad Right",
906 "D-Pad Left",
907 "D-Pad Up",
908 "D-Pad Down",
909 "R",
910 "L",
911 "X",
912 "Y",
913 0,
914 0,
915 "ZL",
916 "ZR",
917 0,
918 0,
919 0,
920 0,
921 0,
922 0,
923 0,
924 0,
925 "C-Stick Right",
926 "C-Stick Left",
927 "C-Stick Up",
928 "C-Stick Down",
929 },
930 .nKeys = 28
931 },
932 { .id = 0 }
933 },
934 .configExtra = (struct GUIMenuItem[]) {
935 {
936 .title = "Screen mode",
937 .data = "screenMode",
938 .submenu = 0,
939 .state = SM_PA_TOP,
940 .validStates = (const char*[]) {
941 "Pixel-Accurate/Bottom",
942 "Aspect-Ratio Fit/Bottom",
943 "Stretched/Bottom",
944 "Pixel-Accurate/Top",
945 "Aspect-Ratio Fit/Top",
946 "Stretched/Top",
947 },
948 .nStates = 6
949 },
950 {
951 .title = "Filtering",
952 .data = "filterMode",
953 .submenu = 0,
954 .state = FM_LINEAR_2x,
955 .validStates = (const char*[]) {
956 NULL, // Disable choosing nearest neighbor; it always looks bad
957 "Bilinear (smoother)",
958 "Bilinear (pixelated)",
959 },
960 .nStates = 3
961 },
962 {
963 .title = "Screen darkening",
964 .data = "darkenMode",
965 .submenu = 0,
966 .state = DM_NATIVE,
967 .validStates = (const char*[]) {
968 "None",
969 "Dark",
970 "Very dark",
971 "Grayed",
972 },
973 .nStates = 4
974 },
975 {
976 .title = "Camera",
977 .data = "camera",
978 .submenu = 0,
979 .state = 1,
980 .validStates = (const char*[]) {
981 "None",
982 "Inner",
983 "Outer",
984 },
985 .nStates = 3
986 }
987 },
988 .nConfigExtra = 4,
989 .setup = _setup,
990 .teardown = 0,
991 .gameLoaded = _gameLoaded,
992 .gameUnloaded = _gameUnloaded,
993 .prepareForFrame = _prepareForFrame,
994 .drawFrame = _drawFrame,
995 .drawScreenshot = _drawScreenshot,
996 .paused = _gameUnloaded,
997 .unpaused = _gameLoaded,
998 .incrementScreenMode = _incrementScreenMode,
999 .setFrameLimiter = _setFrameLimiter,
1000 .pollGameInput = _pollGameInput,
1001 .running = _running
1002 };
1003
1004 runner.autosave.running = true;
1005 MutexInit(&runner.autosave.mutex);
1006 ConditionInit(&runner.autosave.cond);
1007
1008 APT_SetAppCpuTimeLimit(20);
1009 runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true);
1010
1011 Thread thread2;
1012 if (ThreadCreate(&thread2, _core2Test, NULL) == 0) {
1013 core2 = true;
1014 ThreadJoin(&thread2);
1015 }
1016
1017 mGUIInit(&runner, "3ds");
1018
1019 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
1020 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
1021 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
1022 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
1023 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
1024 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
1025 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
1026 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
1027 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
1028 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
1029
1030 mGUIRunloop(&runner);
1031 mGUIDeinit(&runner);
1032
1033 _cleanup();
1034 return 0;
1035}