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