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