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