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