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 mcuHwcExit();
191}
192
193static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
194 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
195}
196
197static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
198
199static void _drawStart(void) {
200 if (frameStarted) {
201 return;
202 }
203 frameStarted = true;
204
205 int screen = screenMode >= SM_PA_TOP ? GSP_SCREEN_TOP : GSP_SCREEN_BOTTOM;
206 if (frameLimiter) {
207 u32 oldFrame = frameCounter;
208 frameCounter = C3D_FrameCounter(screen);
209 while (oldFrame == frameCounter) {
210 gspWaitForAnyEvent();
211 frameCounter = C3D_FrameCounter(screen);
212 }
213 } else {
214 frameCounter = C3D_FrameCounter(screen);
215 }
216 C3D_FrameBegin(0);
217 ctrStartFrame();
218
219 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
220 C3D_RenderTargetClear(bottomScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0);
221 C3D_FrameDrawOn(topScreen[doubleBuffer]);
222 C3D_RenderTargetClear(topScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0);
223}
224
225static void _drawEnd(void) {
226 if (!frameStarted) {
227 return;
228 }
229 ctrEndFrame();
230 C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
231 C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
232 C3D_FrameEnd(0);
233 frameStarted = false;
234
235 doubleBuffer ^= 1;
236}
237
238static int _batteryState(void) {
239 u8 charge;
240 u8 adapter;
241 PTMU_GetBatteryChargeState(&adapter);
242
243 int state = 0;
244 if (R_SUCCEEDED(MCUHWC_GetBatteryLevel(&charge))) {
245 charge |= BATTERY_PERCENTAGE_VALID;
246 } else {
247 PTMU_GetBatteryLevel(&charge);
248 if (charge > 0) {
249 --charge;
250 }
251 }
252 if (adapter) {
253 state |= BATTERY_CHARGING;
254 }
255 return state | charge;
256}
257
258static void _guiPrepare(void) {
259 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
260 ctrSetViewportSize(320, 240, true);
261}
262
263static void _guiFinish(void) {
264 ctrFlushBatch();
265}
266
267static void _resetCamera(struct m3DSImageSource* imageSource) {
268 if (!imageSource->cam) {
269 return;
270 }
271 CAMU_SetSize(imageSource->cam, SIZE_QCIF, CONTEXT_A);
272 CAMU_SetOutputFormat(imageSource->cam, OUTPUT_RGB_565, CONTEXT_A);
273 CAMU_SetFrameRate(imageSource->cam, FRAME_RATE_30);
274 CAMU_FlipImage(imageSource->cam, FLIP_NONE, CONTEXT_A);
275
276 CAMU_SetNoiseFilter(imageSource->cam, true);
277 CAMU_SetAutoExposure(imageSource->cam, false);
278 CAMU_SetAutoWhiteBalance(imageSource->cam, false);
279}
280
281static void _setup(struct mGUIRunner* runner) {
282 if (core2) {
283 mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
284 mCoreLoadForeignConfig(runner->core, &runner->config);
285 }
286
287 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
288 runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d);
289 if (hasSound != NO_SOUND) {
290 runner->core->setAVStream(runner->core, &stream);
291 }
292
293 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
294 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
295 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
296 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
297 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
298 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
299 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
300 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
301 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
302 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
303
304 memset(outputBuffer, 0, 256 * 224 * sizeof(color_t));
305 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
306
307 unsigned mode;
308 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
309 screenMode = mode;
310 }
311 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
312 filterMode = mode;
313 if (filterMode == FM_NEAREST) {
314 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
315 } else {
316 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
317 }
318 }
319 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
320 darkenMode = mode;
321 }
322 frameLimiter = true;
323
324 runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
325}
326
327static void _gameLoaded(struct mGUIRunner* runner) {
328 switch (runner->core->platform(runner->core)) {
329#ifdef M_CORE_GBA
330 // TODO: Move these to callbacks
331 case mPLATFORM_GBA:
332 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
333 HIDUSER_EnableAccelerometer();
334 }
335 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
336 HIDUSER_EnableGyroscope();
337 }
338 break;
339#endif
340#ifdef M_CORE_GB
341 case mPLATFORM_GB:
342 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
343 HIDUSER_EnableAccelerometer();
344 }
345 break;
346#endif
347 default:
348 break;
349 }
350 osSetSpeedupEnable(true);
351
352 double ratio = GBAAudioCalculateRatio(1, 268111856.f / 4481136.f, 1);
353 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
354 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
355 if (hasSound != NO_SOUND) {
356 audioPos = 0;
357 }
358 if (hasSound == DSP_SUPPORTED) {
359 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
360 }
361 unsigned mode;
362 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
363 screenMode = mode;
364 }
365 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
366 filterMode = mode;
367 if (filterMode == FM_NEAREST) {
368 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
369 } else {
370 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
371 }
372 }
373 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
374 darkenMode = mode;
375 }
376 if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
377 switch (mode) {
378 case 0:
379 default:
380 mode = SELECT_NONE;
381 break;
382 case 1:
383 mode = SELECT_IN1;
384 break;
385 case 2:
386 mode = SELECT_OUT1;
387 break;
388 }
389 if (mode != camera.cam) {
390 camera.cam = mode;
391 if (camera.buffer) {
392 _resetCamera(&camera);
393 CAMU_Activate(camera.cam);
394 }
395 }
396 }
397
398 int fakeBool;
399 if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
400 interframeBlending = fakeBool;
401 }
402
403 if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
404 sgbCrop = fakeBool;
405 }
406}
407
408static void _gameUnloaded(struct mGUIRunner* runner) {
409 osSetSpeedupEnable(false);
410 frameLimiter = true;
411
412 switch (runner->core->platform(runner->core)) {
413#ifdef M_CORE_GBA
414 // TODO: Move these to callbacks
415 case mPLATFORM_GBA:
416 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
417 HIDUSER_DisableAccelerometer();
418 }
419 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
420 HIDUSER_DisableGyroscope();
421 }
422 break;
423#endif
424#ifdef M_CORE_GB
425 case mPLATFORM_GB:
426 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
427 HIDUSER_DisableAccelerometer();
428 }
429 break;
430#endif
431 default:
432 break;
433 }
434}
435
436static u32 _setupTex(int out, bool faded) {
437 ctrActivateTexture(&outputTexture[out]);
438 u32 color;
439 if (!faded) {
440 color = 0xFFFFFFFF;
441 switch (darkenMode) {
442 case DM_NATIVE:
443 case DM_MAX:
444 break;
445 case DM_MULT_SCALE_BIAS:
446 ctrTextureBias(0x070707);
447 // Fall through
448 case DM_MULT_SCALE:
449 color = 0xFF707070;
450 // Fall through
451 case DM_MULT:
452 ctrTextureMultiply();
453 break;
454 }
455 } else {
456 color = 0xFF484848;
457 switch (darkenMode) {
458 case DM_NATIVE:
459 case DM_MAX:
460 break;
461 case DM_MULT_SCALE_BIAS:
462 ctrTextureBias(0x030303);
463 // Fall through
464 case DM_MULT_SCALE:
465 color = 0xFF303030;
466 // Fall through
467 case DM_MULT:
468 ctrTextureMultiply();
469 break;
470 }
471
472 }
473 return color;
474}
475
476static void _drawTex(struct mCore* core, bool faded, bool both) {
477 unsigned screen_w, screen_h;
478 bool isWide = screenMode >= SM_PA_TOP && gfxIsWide();
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 = isWide ? 800 : 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 int wide = isWide ? 2 : 1;
497
498 unsigned corew, coreh;
499 core->desiredVideoDimensions(core, &corew, &coreh);
500
501 int w = corew;
502 int h = coreh;
503 if (sgbCrop && w == 256 && h == 224) {
504 w = GB_VIDEO_HORIZONTAL_PIXELS;
505 h = GB_VIDEO_VERTICAL_PIXELS;
506 }
507 int aspectw = w;
508 int aspecth = h;
509 int gcd = reduceFraction(&aspecth, &aspectw);
510 int x = 0;
511 int y = 0;
512
513 switch (screenMode) {
514 case SM_PA_TOP:
515 case SM_PA_BOTTOM:
516 w = corew * wide;
517 h = coreh;
518 x = (screen_w - w) / 2;
519 y = (screen_h - h) / 2;
520 ctrSetViewportSize(screen_w, screen_h, true);
521 break;
522 case SM_AF_TOP:
523 case SM_AF_BOTTOM:
524 case SM_SF_TOP:
525 case SM_SF_BOTTOM:
526 default:
527 if (filterMode == FM_LINEAR_1x) {
528 w = corew;
529 h = coreh;
530 } else {
531 w = corew * 2;
532 h = coreh * 2;
533 }
534 ctrSetViewportSize(screen_w, screen_h, false);
535 break;
536 }
537
538 uint32_t color = _setupTex(activeOutputTexture, faded);
539 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
540 if (both) {
541 color = _setupTex(activeOutputTexture ^ 1, faded);
542 ctrAddRectEx(color & 0x7FFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
543 }
544 ctrFlushBatch();
545
546 int innerw = corew;
547 int innerh = coreh;
548 corew = w;
549 coreh = h;
550 screen_h = 240;
551 if (screenMode < SM_PA_TOP) {
552 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
553 screen_w = 320;
554 } else {
555 C3D_FrameDrawOn(topScreen[doubleBuffer]);
556 screen_w = isWide ? 800 : 400;
557 }
558 ctrSetViewportSize(screen_w, screen_h, true);
559
560 float afw, afh;
561 switch (screenMode) {
562 default:
563 return;
564 case SM_AF_TOP:
565 case SM_AF_BOTTOM:
566 afw = screen_w / (float) aspectw;
567 afh = screen_h / (float) aspecth;
568 innerw *= wide;
569 if (afw * aspecth > screen_h) {
570 w = innerw * afh / gcd;
571 h = innerh * afh / gcd;
572 } else {
573 h = innerh * afw / gcd;
574 w = innerw * afw / gcd;
575 }
576 break;
577 case SM_SF_TOP:
578 case SM_SF_BOTTOM:
579 w = screen_w;
580 h = screen_h;
581 break;
582 }
583
584 x = (screen_w - w) / 2;
585 y = (screen_h - h) / 2;
586 ctrActivateTexture(&upscaleBufferTex);
587 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
588 ctrFlushBatch();
589}
590
591static void _prepareForFrame(struct mGUIRunner* runner) {
592 UNUSED(runner);
593 activeOutputTexture ^= 1;
594}
595
596static void _drawFrame(struct mGUIRunner* runner, bool faded) {
597 UNUSED(runner);
598 C3D_Tex* tex = &outputTexture[activeOutputTexture];
599
600 GSPGPU_FlushDataCache(outputBuffer, 256 * GBA_VIDEO_VERTICAL_PIXELS * 2);
601 C3D_SyncDisplayTransfer(
602 (u32*) outputBuffer, GX_BUFFER_DIM(256, GBA_VIDEO_VERTICAL_PIXELS),
603 tex->data, GX_BUFFER_DIM(256, 256),
604 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
605 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
606 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
607
608 if (hasSound == NO_SOUND) {
609 blip_clear(runner->core->getAudioChannel(runner->core, 0));
610 blip_clear(runner->core->getAudioChannel(runner->core, 1));
611 }
612
613 _drawTex(runner->core, faded, interframeBlending);
614}
615
616static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
617 C3D_Tex* tex = &outputTexture[activeOutputTexture];
618
619 if (!screenshotBuffer) {
620 screenshotBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
621 }
622 unsigned y;
623 for (y = 0; y < height; ++y) {
624 memcpy(&screenshotBuffer[y * 256], &pixels[y * width], width * sizeof(color_t));
625 memset(&screenshotBuffer[y * 256 + width], 0, (256 - width) * sizeof(color_t));
626 }
627
628 GSPGPU_FlushDataCache(screenshotBuffer, 256 * height * sizeof(color_t));
629 C3D_SyncDisplayTransfer(
630 (u32*) screenshotBuffer, GX_BUFFER_DIM(256, height),
631 tex->data, GX_BUFFER_DIM(256, 256),
632 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
633 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
634 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
635
636 _drawTex(runner->core, faded, false);
637}
638
639static uint16_t _pollGameInput(struct mGUIRunner* runner) {
640 UNUSED(runner);
641
642 hidScanInput();
643 uint32_t activeKeys = hidKeysHeld();
644 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
645 keys |= (activeKeys >> 24) & 0xF0;
646 return keys;
647}
648
649static void _incrementScreenMode(struct mGUIRunner* runner) {
650 UNUSED(runner);
651 screenMode = (screenMode + 1) % SM_MAX;
652 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
653}
654
655static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
656 UNUSED(runner);
657 if (frameLimiter == limit) {
658 return;
659 }
660 frameLimiter = limit;
661}
662
663static bool _running(struct mGUIRunner* runner) {
664 UNUSED(runner);
665 return aptMainLoop();
666}
667
668static uint32_t _pollInput(const struct mInputMap* map) {
669 hidScanInput();
670 int activeKeys = hidKeysHeld();
671 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
672}
673
674static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
675 hidScanInput();
676 if (!(hidKeysHeld() & KEY_TOUCH)) {
677 return GUI_CURSOR_NOT_PRESENT;
678 }
679 touchPosition pos;
680 hidTouchRead(&pos);
681 *x = pos.px;
682 *y = pos.py;
683 return GUI_CURSOR_DOWN;
684}
685
686static void _sampleRotation(struct mRotationSource* source) {
687 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
688 // Work around ctrulib getting the entries wrong
689 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
690 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
691}
692
693static int32_t _readTiltX(struct mRotationSource* source) {
694 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
695 return rotation->accel.x << 18L;
696}
697
698static int32_t _readTiltY(struct mRotationSource* source) {
699 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
700 return rotation->accel.y << 18L;
701}
702
703static int32_t _readGyroZ(struct mRotationSource* source) {
704 struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
705 return rotation->gyro.y << 18L; // Yes, y
706}
707
708static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
709 UNUSED(colorFormats);
710 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
711
712 _resetCamera(imageSource);
713
714 CAMU_SetTrimming(PORT_CAM1, true);
715 CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
716 CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
717
718 if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
719 free(imageSource->buffer);
720 imageSource->buffer = NULL;
721 }
722 imageSource->bufferSize = w * h * 2;
723 if (!imageSource->buffer) {
724 imageSource->buffer = malloc(imageSource->bufferSize);
725 }
726 CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
727 CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
728 CAMU_Activate(imageSource->cam);
729 CAMU_ClearBuffer(PORT_CAM1);
730 CAMU_StartCapture(PORT_CAM1);
731
732 if (imageSource->cam) {
733 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
734 }
735}
736
737static void _stopRequestImage(struct mImageSource* source) {
738 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
739
740 free(imageSource->buffer);
741 imageSource->buffer = NULL;
742 svcCloseHandle(imageSource->handles[0]);
743 svcCloseHandle(imageSource->handles[1]);
744
745 CAMU_StopCapture(PORT_CAM1);
746 CAMU_Activate(SELECT_NONE);
747}
748
749
750static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
751 struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
752
753 if (!imageSource->cam) {
754 memset(imageSource->buffer, 0, imageSource->bufferSize);
755 *buffer = imageSource->buffer;
756 *stride = 128;
757 *colorFormat = mCOLOR_RGB565;
758 return;
759 }
760
761 s32 i;
762 svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
763
764 if (i == 0) {
765 *buffer = imageSource->buffer;
766 *stride = 128;
767 *colorFormat = mCOLOR_RGB565;
768 } else {
769 CAMU_ClearBuffer(PORT_CAM1);
770 CAMU_StartCapture(PORT_CAM1);
771 }
772
773 svcCloseHandle(imageSource->handles[0]);
774 CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
775}
776
777static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
778 UNUSED(stream);
779 if (hasSound == DSP_SUPPORTED) {
780 int startId = bufferId;
781 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
782 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
783 if (bufferId == startId) {
784 blip_clear(left);
785 blip_clear(right);
786 return;
787 }
788 }
789 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
790 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
791 dspBuffer[bufferId].data_pcm16 = tmpBuf;
792 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
793 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
794 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
795 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
796 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
797 }
798}
799
800THREAD_ENTRY _core2Test(void* context) {
801 UNUSED(context);
802}
803
804int main() {
805 rotation.d.sample = _sampleRotation;
806 rotation.d.readTiltX = _readTiltX;
807 rotation.d.readTiltY = _readTiltY;
808 rotation.d.readGyroZ = _readGyroZ;
809
810 stream.videoDimensionsChanged = 0;
811 stream.postVideoFrame = 0;
812 stream.postAudioFrame = 0;
813 stream.postAudioBuffer = _postAudioBuffer;
814
815 camera.d.startRequestImage = _startRequestImage;
816 camera.d.stopRequestImage = _stopRequestImage;
817 camera.d.requestImage = _requestImage;
818 camera.buffer = NULL;
819 camera.bufferSize = 0;
820 camera.cam = SELECT_IN1;
821
822 ptmuInit();
823 mcuHwcInit();
824 camInit();
825
826 hasSound = NO_SOUND;
827 if (!ndspInit()) {
828 hasSound = DSP_SUPPORTED;
829 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
830 ndspSetOutputCount(1);
831 ndspChnReset(0);
832 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
833 ndspChnSetInterp(0, NDSP_INTERP_NONE);
834 ndspChnSetRate(0, 0x8000);
835 ndspChnWaveBufClear(0);
836 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
837 memset(dspBuffer, 0, sizeof(dspBuffer));
838 int i;
839 for (i = 0; i < DSP_BUFFERS; ++i) {
840 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
841 dspBuffer[i].nsamples = AUDIO_SAMPLES;
842 }
843 }
844
845 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
846
847 u8 model = 0;
848 CFGU_GetSystemModel(&model);
849 if (model != 3 /* o2DS */) {
850 gfxSetWide(true);
851 }
852
853 if (!_initGpu()) {
854 outputTexture[0].data = 0;
855 _cleanup();
856 return 1;
857 }
858
859 C3D_TexSetWrap(&upscaleBufferTex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
860 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
861
862 int i;
863 for (i = 0; i < 2; ++i) {
864 if (!C3D_TexInitVRAM(&outputTexture[i], 256, 256, GPU_RGB565)) {
865 _cleanup();
866 return 1;
867 }
868 C3D_TexSetWrap(&outputTexture[i], GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
869 C3D_TexSetFilter(&outputTexture[i], GPU_NEAREST, GPU_NEAREST);
870 void* outputTextureEnd = (u8*)outputTexture[i].data + 256 * 256 * 2;
871
872 // Zero texture data to make sure no garbage around the border interferes with filtering
873 GX_MemoryFill(
874 outputTexture[i].data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
875 NULL, 0, NULL, 0);
876 gspWaitForPSC0();
877 }
878
879 struct GUIFont* font = GUIFontCreate();
880
881 if (!font) {
882 _cleanup();
883 return 1;
884 }
885 outputBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
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}