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#ifdef M_CORE_GBA
10#include <mgba/internal/gba/gba.h>
11#include <mgba/internal/gba/input.h>
12#include <mgba/internal/gba/video.h>
13#endif
14#ifdef M_CORE_GB
15#include <mgba/internal/gb/gb.h>
16#endif
17#include "feature/gui/gui-runner.h"
18#include <mgba-util/gui.h>
19#include <mgba-util/gui/file-select.h>
20#include <mgba-util/gui/font.h>
21#include <mgba-util/gui/menu.h>
22#include <mgba-util/memory.h>
23
24#include <mgba-util/platform/3ds/3ds-vfs.h>
25#include "ctr-gpu.h"
26
27#include <3ds.h>
28#include <3ds/gpu/gx.h>
29
30static enum ScreenMode {
31 SM_PA_BOTTOM,
32 SM_AF_BOTTOM,
33 SM_SF_BOTTOM,
34 SM_PA_TOP,
35 SM_AF_TOP,
36 SM_SF_TOP,
37 SM_MAX
38} screenMode = SM_PA_TOP;
39
40static enum FilterMode {
41 FM_NEAREST,
42 FM_LINEAR_1x,
43 FM_LINEAR_2x,
44 FM_MAX
45} filterMode = FM_LINEAR_2x;
46
47static enum DarkenMode {
48 DM_NATIVE,
49 DM_MULT,
50 DM_MULT_SCALE,
51 DM_MULT_SCALE_BIAS,
52 DM_MAX
53} darkenMode = DM_NATIVE;
54
55#define _3DS_INPUT 0x3344534B
56
57#define AUDIO_SAMPLES 384
58#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
59#define DSP_BUFFERS 4
60
61static struct GBA3DSRotationSource {
62 struct mRotationSource d;
63 accelVector accel;
64 angularRate gyro;
65} rotation;
66
67static enum {
68 NO_SOUND,
69 DSP_SUPPORTED,
70 CSND_SUPPORTED
71} hasSound;
72
73// TODO: Move into context
74static void* outputBuffer;
75static struct mAVStream stream;
76static int16_t* audioLeft = 0;
77static int16_t* audioRight = 0;
78static size_t audioPos = 0;
79static C3D_Tex outputTexture;
80static ndspWaveBuf dspBuffer[DSP_BUFFERS];
81static int bufferId = 0;
82static bool frameLimiter = true;
83static unsigned frameCounter;
84
85static C3D_RenderTarget* topScreen[2];
86static C3D_RenderTarget* bottomScreen[2];
87static int doubleBuffer = 0;
88
89static C3D_RenderTarget* upscaleBuffer;
90static C3D_Tex upscaleBufferTex;
91
92static aptHookCookie cookie;
93
94extern bool allocateRomBuffer(void);
95
96static bool _initGpu(void) {
97 if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
98 return false;
99 }
100
101 topScreen[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
102 topScreen[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
103 bottomScreen[0] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
104 bottomScreen[1] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
105 if (!topScreen[0] || !topScreen[1] || !bottomScreen[0] || !bottomScreen[1]) {
106 return false;
107 }
108
109 if (!C3D_TexInitVRAM(&upscaleBufferTex, 512, 512, GPU_RB_RGB8)) {
110 return false;
111 }
112 upscaleBuffer = C3D_RenderTargetCreateFromTex(&upscaleBufferTex, GPU_TEXFACE_2D, 0, 0);
113 if (!upscaleBuffer) {
114 return false;
115 }
116
117 C3D_RenderTargetSetClear(upscaleBuffer, C3D_CLEAR_COLOR, 0, 0);
118
119 return ctrInitGpu();
120}
121
122static void _cleanup(void) {
123 ctrDeinitGpu();
124
125 if (outputBuffer) {
126 linearFree(outputBuffer);
127 }
128
129 C3D_RenderTargetDelete(topScreen[0]);
130 C3D_RenderTargetDelete(topScreen[1]);
131 C3D_RenderTargetDelete(bottomScreen[0]);
132 C3D_RenderTargetDelete(bottomScreen[1]);
133 C3D_RenderTargetDelete(upscaleBuffer);
134 C3D_TexDelete(&upscaleBufferTex);
135 C3D_TexDelete(&outputTexture);
136 C3D_Fini();
137
138 gfxExit();
139
140 if (hasSound != NO_SOUND) {
141 linearFree(audioLeft);
142 }
143
144 if (hasSound == CSND_SUPPORTED) {
145 linearFree(audioRight);
146 csndExit();
147 }
148
149 if (hasSound == DSP_SUPPORTED) {
150 ndspExit();
151 }
152
153 csndExit();
154 ptmuExit();
155}
156
157static void _aptHook(APT_HookType hook, void* user) {
158 UNUSED(user);
159 switch (hook) {
160 case APTHOOK_ONSUSPEND:
161 case APTHOOK_ONSLEEP:
162 if (hasSound == CSND_SUPPORTED) {
163 CSND_SetPlayState(8, 0);
164 CSND_SetPlayState(9, 0);
165 csndExecCmds(false);
166 }
167 break;
168 case APTHOOK_ONEXIT:
169 if (hasSound == CSND_SUPPORTED) {
170 CSND_SetPlayState(8, 0);
171 CSND_SetPlayState(9, 0);
172 csndExecCmds(false);
173 }
174 _cleanup();
175 exit(0);
176 break;
177 default:
178 break;
179 }
180}
181
182static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
183 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
184}
185
186static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
187 u32 pleft = 0, pright = 0;
188
189 int loopMode = (flags >> 10) & 3;
190 if (!loopMode) {
191 flags |= SOUND_ONE_SHOT;
192 }
193
194 pleft = osConvertVirtToPhys(left);
195 pright = osConvertVirtToPhys(right);
196
197 u32 timer = CSND_TIMER(sampleRate);
198 if (timer < 0x0042) {
199 timer = 0x0042;
200 }
201 else if (timer > 0xFFFF) {
202 timer = 0xFFFF;
203 }
204 flags &= ~0xFFFF001F;
205 flags |= SOUND_ENABLE | (timer << 16);
206
207 u32 volumes = CSND_VOL(vol, -1.0);
208 CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
209 volumes = CSND_VOL(vol, 1.0);
210 CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
211}
212
213static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
214
215static void _drawStart(void) {
216 C3D_FrameBegin(frameLimiter ? 0 : C3D_FRAME_NONBLOCK);
217 // Mark both buffers used to make sure they get cleared
218 C3D_FrameDrawOn(topScreen[doubleBuffer]);
219 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
220}
221
222static void _drawEnd(void) {
223 ctrFinalize();
224 C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
225 C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
226 C3D_FrameEnd(GX_CMDLIST_FLUSH);
227 if (frameLimiter) {
228 while (frameCounter == C3D_FrameCounter(0)) {
229 gspWaitForAnyEvent();
230 }
231 }
232 doubleBuffer ^= 1;
233 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
234 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
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 _setup(struct mGUIRunner* runner) {
262 uint8_t mask;
263 if (R_SUCCEEDED(svcGetProcessAffinityMask(&mask, CUR_PROCESS_HANDLE, 4)) && mask >= 4) {
264 mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
265 mCoreLoadForeignConfig(runner->core, &runner->config);
266 }
267
268 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
269 if (hasSound != NO_SOUND) {
270 runner->core->setAVStream(runner->core, &stream);
271 }
272
273 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
274 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
275 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
276 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
277 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
278 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
279 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
280 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
281 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
282 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
283
284 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
285 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
286
287 unsigned mode;
288 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
289 screenMode = mode;
290 }
291 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
292 filterMode = mode;
293 if (filterMode == FM_NEAREST) {
294 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
295 } else {
296 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
297 }
298 }
299 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
300 darkenMode = mode;
301 }
302 frameLimiter = true;
303
304 runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
305}
306
307static void _gameLoaded(struct mGUIRunner* runner) {
308 switch (runner->core->platform(runner->core)) {
309#ifdef M_CORE_GBA
310 case PLATFORM_GBA:
311 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
312 HIDUSER_EnableAccelerometer();
313 }
314 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
315 HIDUSER_EnableGyroscope();
316 }
317 break;
318#endif
319#ifdef M_CORE_GB
320 case PLATFORM_GB:
321 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
322 HIDUSER_EnableAccelerometer();
323 }
324 break;
325#endif
326 default:
327 break;
328 }
329 osSetSpeedupEnable(true);
330
331 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
332 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
333 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
334 if (hasSound != NO_SOUND) {
335 audioPos = 0;
336 }
337 if (hasSound == CSND_SUPPORTED) {
338 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
339 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
340 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
341 csndExecCmds(false);
342 } else if (hasSound == DSP_SUPPORTED) {
343 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
344 }
345 unsigned mode;
346 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
347 screenMode = mode;
348 }
349 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
350 filterMode = mode;
351 if (filterMode == FM_NEAREST) {
352 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
353 } else {
354 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
355 }
356 }
357 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
358 darkenMode = mode;
359 }
360}
361
362static void _gameUnloaded(struct mGUIRunner* runner) {
363 if (hasSound == CSND_SUPPORTED) {
364 CSND_SetPlayState(8, 0);
365 CSND_SetPlayState(9, 0);
366 csndExecCmds(false);
367 }
368 osSetSpeedupEnable(false);
369 frameLimiter = true;
370
371 switch (runner->core->platform(runner->core)) {
372#ifdef M_CORE_GBA
373 case PLATFORM_GBA:
374 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
375 HIDUSER_DisableAccelerometer();
376 }
377 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
378 HIDUSER_DisableGyroscope();
379 }
380 break;
381#endif
382#ifdef M_CORE_GB
383 case PLATFORM_GB:
384 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
385 HIDUSER_DisableAccelerometer();
386 }
387 break;
388#endif
389 default:
390 break;
391 }
392}
393
394static void _storeCounter(struct mGUIRunner* runner) {
395 UNUSED(runner);
396 frameCounter = C3D_FrameCounter(0);
397}
398
399static void _drawTex(struct mCore* core, bool faded) {
400 unsigned screen_w, screen_h;
401 switch (screenMode) {
402 case SM_PA_BOTTOM:
403 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
404 screen_w = 320;
405 screen_h = 240;
406 break;
407 case SM_PA_TOP:
408 C3D_FrameDrawOn(topScreen[doubleBuffer]);
409 screen_w = 400;
410 screen_h = 240;
411 break;
412 default:
413 C3D_FrameDrawOn(upscaleBuffer);
414 screen_w = 512;
415 screen_h = 512;
416 break;
417 }
418
419 unsigned corew, coreh;
420 core->desiredVideoDimensions(core, &corew, &coreh);
421
422 int w = corew;
423 int h = coreh;
424 // Get greatest common divisor
425 while (w != 0) {
426 int temp = h % w;
427 h = w;
428 w = temp;
429 }
430 int gcd = h;
431 unsigned aspectw = corew / gcd;
432 unsigned aspecth = coreh / gcd;
433 int x = 0;
434 int y = 0;
435
436 switch (screenMode) {
437 case SM_PA_TOP:
438 case SM_PA_BOTTOM:
439 w = corew;
440 h = coreh;
441 x = (screen_w - w) / 2;
442 y = (screen_h - h) / 2;
443 ctrSetViewportSize(screen_w, screen_h, true);
444 break;
445 case SM_AF_TOP:
446 case SM_AF_BOTTOM:
447 case SM_SF_TOP:
448 case SM_SF_BOTTOM:
449 default:
450 if (filterMode == FM_LINEAR_1x) {
451 w = corew;
452 h = coreh;
453 } else {
454 w = corew * 2;
455 h = coreh * 2;
456 }
457 ctrSetViewportSize(screen_w, screen_h, false);
458 break;
459 }
460
461 ctrActivateTexture(&outputTexture);
462 u32 color;
463 if (!faded) {
464 color = 0xFFFFFFFF;
465 switch (darkenMode) {
466 case DM_NATIVE:
467 case DM_MAX:
468 break;
469 case DM_MULT_SCALE_BIAS:
470 ctrTextureBias(0x070707);
471 // Fall through
472 case DM_MULT_SCALE:
473 color = 0xFF707070;
474 // Fall through
475 case DM_MULT:
476 ctrTextureMultiply();
477 break;
478 }
479 } else {
480 color = 0xFF484848;
481 switch (darkenMode) {
482 case DM_NATIVE:
483 case DM_MAX:
484 break;
485 case DM_MULT_SCALE_BIAS:
486 ctrTextureBias(0x030303);
487 // Fall through
488 case DM_MULT_SCALE:
489 color = 0xFF303030;
490 // Fall through
491 case DM_MULT:
492 ctrTextureMultiply();
493 break;
494 }
495
496 }
497 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
498 ctrFlushBatch();
499
500 corew = w;
501 coreh = h;
502 screen_h = 240;
503 if (screenMode < SM_PA_TOP) {
504 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
505 screen_w = 320;
506 } else {
507 C3D_FrameDrawOn(topScreen[doubleBuffer]);
508 screen_w = 400;
509 }
510 ctrSetViewportSize(screen_w, screen_h, true);
511
512 switch (screenMode) {
513 default:
514 return;
515 case SM_AF_TOP:
516 case SM_AF_BOTTOM:
517 w = screen_w / aspectw;
518 h = screen_h / aspecth;
519 if (w * aspecth > screen_h) {
520 w = aspectw * h;
521 h = aspecth * h;
522 } else {
523 h = aspecth * w;
524 w = aspectw * w;
525 }
526 break;
527 case SM_SF_TOP:
528 case SM_SF_BOTTOM:
529 w = screen_w;
530 h = screen_h;
531 break;
532 }
533
534 x = (screen_w - w) / 2;
535 y = (screen_h - h) / 2;
536 ctrActivateTexture(&upscaleBufferTex);
537 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
538 ctrFlushBatch();
539}
540
541static void _drawFrame(struct mGUIRunner* runner, bool faded) {
542 UNUSED(runner);
543
544 C3D_Tex* tex = &outputTexture;
545
546 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
547 C3D_SafeDisplayTransfer(
548 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
549 tex->data, GX_BUFFER_DIM(256, 256),
550 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
551 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
552 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
553
554 if (hasSound == NO_SOUND) {
555 blip_clear(runner->core->getAudioChannel(runner->core, 0));
556 blip_clear(runner->core->getAudioChannel(runner->core, 1));
557 }
558
559 gspWaitForPPF();
560 _drawTex(runner->core, faded);
561}
562
563static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
564
565 C3D_Tex* tex = &outputTexture;
566
567 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
568
569 unsigned y;
570 for (y = 0; y < height; ++y) {
571 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
572 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
573 }
574
575 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
576 C3D_SafeDisplayTransfer(
577 (u32*) newPixels, GX_BUFFER_DIM(256, height),
578 tex->data, GX_BUFFER_DIM(256, 256),
579 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
580 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
581 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
582 gspWaitForPPF();
583 linearFree(newPixels);
584
585 _drawTex(runner->core, faded);
586}
587
588static uint16_t _pollGameInput(struct mGUIRunner* runner) {
589 UNUSED(runner);
590
591 hidScanInput();
592 uint32_t activeKeys = hidKeysHeld();
593 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
594 keys |= (activeKeys >> 24) & 0xF0;
595 return keys;
596}
597
598static void _incrementScreenMode(struct mGUIRunner* runner) {
599 UNUSED(runner);
600 screenMode = (screenMode + 1) % SM_MAX;
601 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
602
603 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
604 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
605}
606
607static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
608 UNUSED(runner);
609 frameLimiter = limit;
610}
611
612static uint32_t _pollInput(const struct mInputMap* map) {
613 hidScanInput();
614 int activeKeys = hidKeysHeld();
615 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
616}
617
618static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
619 hidScanInput();
620 if (!(hidKeysHeld() & KEY_TOUCH)) {
621 return GUI_CURSOR_NOT_PRESENT;
622 }
623 touchPosition pos;
624 hidTouchRead(&pos);
625 *x = pos.px;
626 *y = pos.py;
627 return GUI_CURSOR_DOWN;
628}
629
630static void _sampleRotation(struct mRotationSource* source) {
631 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
632 // Work around ctrulib getting the entries wrong
633 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
634 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
635}
636
637static int32_t _readTiltX(struct mRotationSource* source) {
638 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
639 return rotation->accel.x << 18L;
640}
641
642static int32_t _readTiltY(struct mRotationSource* source) {
643 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
644 return rotation->accel.y << 18L;
645}
646
647static int32_t _readGyroZ(struct mRotationSource* source) {
648 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
649 return rotation->gyro.y << 18L; // Yes, y
650}
651
652static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
653 UNUSED(stream);
654 if (hasSound == CSND_SUPPORTED) {
655 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
656 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
657 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
658 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
659 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
660 if (audioPos == AUDIO_SAMPLES * 3) {
661 u8 playing = 0;
662 csndIsPlaying(0x8, &playing);
663 if (!playing) {
664 CSND_SetPlayState(0x8, 1);
665 CSND_SetPlayState(0x9, 1);
666 csndExecCmds(false);
667 }
668 }
669 } else if (hasSound == DSP_SUPPORTED) {
670 int startId = bufferId;
671 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
672 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
673 if (bufferId == startId) {
674 blip_clear(left);
675 blip_clear(right);
676 return;
677 }
678 }
679 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
680 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
681 dspBuffer[bufferId].data_pcm16 = tmpBuf;
682 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
683 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
684 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
685 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
686 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
687 }
688}
689
690int main() {
691 rotation.d.sample = _sampleRotation;
692 rotation.d.readTiltX = _readTiltX;
693 rotation.d.readTiltY = _readTiltY;
694 rotation.d.readGyroZ = _readGyroZ;
695
696 stream.videoDimensionsChanged = 0;
697 stream.postVideoFrame = 0;
698 stream.postAudioFrame = 0;
699 stream.postAudioBuffer = _postAudioBuffer;
700
701 if (!allocateRomBuffer()) {
702 return 1;
703 }
704
705 aptHook(&cookie, _aptHook, 0);
706
707 ptmuInit();
708 hasSound = NO_SOUND;
709 if (!ndspInit()) {
710 hasSound = DSP_SUPPORTED;
711 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
712 ndspSetOutputCount(1);
713 ndspChnReset(0);
714 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
715 ndspChnSetInterp(0, NDSP_INTERP_NONE);
716 ndspChnSetRate(0, 0x8000);
717 ndspChnWaveBufClear(0);
718 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
719 memset(dspBuffer, 0, sizeof(dspBuffer));
720 int i;
721 for (i = 0; i < DSP_BUFFERS; ++i) {
722 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
723 dspBuffer[i].nsamples = AUDIO_SAMPLES;
724 }
725 }
726
727 if (hasSound == NO_SOUND && !csndInit()) {
728 hasSound = CSND_SUPPORTED;
729 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
730 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
731 }
732
733 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
734
735 if (!_initGpu()) {
736 outputTexture.data = 0;
737 _cleanup();
738 return 1;
739 }
740
741 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
742 _cleanup();
743 return 1;
744 }
745 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
746 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
747 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
748 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
749
750 // Zero texture data to make sure no garbage around the border interferes with filtering
751 GX_MemoryFill(
752 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
753 NULL, 0, NULL, 0);
754 gspWaitForPSC0();
755
756 struct GUIFont* font = GUIFontCreate();
757
758 if (!font) {
759 _cleanup();
760 return 1;
761 }
762
763 struct mGUIRunner runner = {
764 .params = {
765 320, 240,
766 font, "/",
767 _drawStart, _drawEnd,
768 _pollInput, _pollCursor,
769 _batteryState,
770 _guiPrepare, _guiFinish,
771 },
772 .keySources = (struct GUIInputKeys[]) {
773 {
774 .name = "3DS Input",
775 .id = _3DS_INPUT,
776 .keyNames = (const char*[]) {
777 "A",
778 "B",
779 "Select",
780 "Start",
781 "D-Pad Right",
782 "D-Pad Left",
783 "D-Pad Up",
784 "D-Pad Down",
785 "R",
786 "L",
787 "X",
788 "Y",
789 0,
790 0,
791 "ZL",
792 "ZR",
793 0,
794 0,
795 0,
796 0,
797 0,
798 0,
799 0,
800 0,
801 "C-Stick Right",
802 "C-Stick Left",
803 "C-Stick Up",
804 "C-Stick Down",
805 },
806 .nKeys = 28
807 },
808 { .id = 0 }
809 },
810 .configExtra = (struct GUIMenuItem[]) {
811 {
812 .title = "Screen mode",
813 .data = "screenMode",
814 .submenu = 0,
815 .state = SM_PA_TOP,
816 .validStates = (const char*[]) {
817 "Pixel-Accurate/Bottom",
818 "Aspect-Ratio Fit/Bottom",
819 "Stretched/Bottom",
820 "Pixel-Accurate/Top",
821 "Aspect-Ratio Fit/Top",
822 "Stretched/Top",
823 },
824 .nStates = 6
825 },
826 {
827 .title = "Filtering",
828 .data = "filterMode",
829 .submenu = 0,
830 .state = FM_LINEAR_2x,
831 .validStates = (const char*[]) {
832 NULL, // Disable choosing nearest neighbor; it always looks bad
833 "Bilinear (smoother)",
834 "Bilinear (pixelated)",
835 },
836 .nStates = 3
837 },
838 {
839 .title = "Screen darkening",
840 .data = "darkenMode",
841 .submenu = 0,
842 .state = DM_NATIVE,
843 .validStates = (const char*[]) {
844 "None",
845 "Dark",
846 "Very dark",
847 "Grayed",
848 },
849 .nStates = 4
850 }
851 },
852 .nConfigExtra = 3,
853 .setup = _setup,
854 .teardown = 0,
855 .gameLoaded = _gameLoaded,
856 .gameUnloaded = _gameUnloaded,
857 .prepareForFrame = _storeCounter,
858 .drawFrame = _drawFrame,
859 .drawScreenshot = _drawScreenshot,
860 .paused = _gameUnloaded,
861 .unpaused = _gameLoaded,
862 .incrementScreenMode = _incrementScreenMode,
863 .setFrameLimiter = _setFrameLimiter,
864 .pollGameInput = _pollGameInput
865 };
866
867 mGUIInit(&runner, "3ds");
868
869 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
870 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
871 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
872 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
873 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
874 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
875 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
876 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
877 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
878 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
879
880 mGUIRunloop(&runner);
881 mGUIDeinit(&runner);
882
883 _cleanup();
884 return 0;
885}