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 frameCounter = C3D_FrameCounter(0);
218}
219
220static void _drawEnd(void) {
221 ctrFinalize();
222 C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
223 C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
224 C3D_FrameEnd(GX_CMDLIST_FLUSH);
225 if (frameLimiter) {
226 while (frameCounter == C3D_FrameCounter(0)) {
227 gspWaitForAnyEvent();
228 }
229 }
230 doubleBuffer ^= 1;
231 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
232 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
233}
234
235static int _batteryState(void) {
236 u8 charge;
237 u8 adapter;
238 PTMU_GetBatteryLevel(&charge);
239 PTMU_GetBatteryChargeState(&adapter);
240 int state = 0;
241 if (adapter) {
242 state |= BATTERY_CHARGING;
243 }
244 if (charge > 0) {
245 --charge;
246 }
247 return state | charge;
248}
249
250static void _guiPrepare(void) {
251 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
252 ctrSetViewportSize(320, 240, true);
253}
254
255static void _guiFinish(void) {
256 ctrFlushBatch();
257}
258
259static void _setup(struct mGUIRunner* runner) {
260 bool isNew3DS = false;
261 APT_CheckNew3DS(&isNew3DS);
262 if (isNew3DS && !envIsHomebrew()) {
263 mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
264 mCoreLoadForeignConfig(runner->core, &runner->config);
265 }
266
267 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
268 if (hasSound != NO_SOUND) {
269 runner->core->setAVStream(runner->core, &stream);
270 }
271
272 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
273 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
274 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
275 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
276 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
277 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
278 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
279 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
280 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
281 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
282
283 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
284 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
285
286 unsigned mode;
287 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
288 screenMode = mode;
289 }
290 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
291 filterMode = mode;
292 if (filterMode == FM_NEAREST) {
293 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
294 } else {
295 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
296 }
297 }
298 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
299 darkenMode = mode;
300 }
301 frameLimiter = true;
302
303 runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
304}
305
306static void _gameLoaded(struct mGUIRunner* runner) {
307 switch (runner->core->platform(runner->core)) {
308#ifdef M_CORE_GBA
309 case PLATFORM_GBA:
310 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
311 HIDUSER_EnableAccelerometer();
312 }
313 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
314 HIDUSER_EnableGyroscope();
315 }
316 break;
317#endif
318#ifdef M_CORE_GB
319 case PLATFORM_GB:
320 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
321 HIDUSER_EnableAccelerometer();
322 }
323 break;
324#endif
325 default:
326 break;
327 }
328 osSetSpeedupEnable(true);
329
330 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
331 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
332 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
333 if (hasSound != NO_SOUND) {
334 audioPos = 0;
335 }
336 if (hasSound == CSND_SUPPORTED) {
337 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
338 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
339 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
340 csndExecCmds(false);
341 } else if (hasSound == DSP_SUPPORTED) {
342 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
343 }
344 unsigned mode;
345 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
346 screenMode = mode;
347 }
348 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
349 filterMode = mode;
350 if (filterMode == FM_NEAREST) {
351 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
352 } else {
353 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
354 }
355 }
356 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
357 darkenMode = mode;
358 }
359}
360
361static void _gameUnloaded(struct mGUIRunner* runner) {
362 if (hasSound == CSND_SUPPORTED) {
363 CSND_SetPlayState(8, 0);
364 CSND_SetPlayState(9, 0);
365 csndExecCmds(false);
366 }
367 osSetSpeedupEnable(false);
368 frameLimiter = true;
369
370 switch (runner->core->platform(runner->core)) {
371#ifdef M_CORE_GBA
372 case PLATFORM_GBA:
373 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
374 HIDUSER_DisableAccelerometer();
375 }
376 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
377 HIDUSER_DisableGyroscope();
378 }
379 break;
380#endif
381#ifdef M_CORE_GB
382 case PLATFORM_GB:
383 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
384 HIDUSER_DisableAccelerometer();
385 }
386 break;
387#endif
388 default:
389 break;
390 }
391}
392
393static void _drawTex(struct mCore* core, bool faded) {
394 unsigned screen_w, screen_h;
395 switch (screenMode) {
396 case SM_PA_BOTTOM:
397 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
398 screen_w = 320;
399 screen_h = 240;
400 break;
401 case SM_PA_TOP:
402 C3D_FrameDrawOn(topScreen[doubleBuffer]);
403 screen_w = 400;
404 screen_h = 240;
405 break;
406 default:
407 C3D_FrameDrawOn(upscaleBuffer);
408 screen_w = 512;
409 screen_h = 512;
410 break;
411 }
412
413 unsigned corew, coreh;
414 core->desiredVideoDimensions(core, &corew, &coreh);
415
416 int w = corew;
417 int h = coreh;
418 // Get greatest common divisor
419 while (w != 0) {
420 int temp = h % w;
421 h = w;
422 w = temp;
423 }
424 int gcd = h;
425 unsigned aspectw = corew / gcd;
426 unsigned aspecth = coreh / gcd;
427 int x = 0;
428 int y = 0;
429
430 switch (screenMode) {
431 case SM_PA_TOP:
432 case SM_PA_BOTTOM:
433 w = corew;
434 h = coreh;
435 x = (screen_w - w) / 2;
436 y = (screen_h - h) / 2;
437 ctrSetViewportSize(screen_w, screen_h, true);
438 break;
439 case SM_AF_TOP:
440 case SM_AF_BOTTOM:
441 case SM_SF_TOP:
442 case SM_SF_BOTTOM:
443 default:
444 if (filterMode == FM_LINEAR_1x) {
445 w = corew;
446 h = coreh;
447 } else {
448 w = corew * 2;
449 h = coreh * 2;
450 }
451 ctrSetViewportSize(screen_w, screen_h, false);
452 break;
453 }
454
455 ctrActivateTexture(&outputTexture);
456 u32 color;
457 if (!faded) {
458 color = 0xFFFFFFFF;
459 switch (darkenMode) {
460 case DM_NATIVE:
461 case DM_MAX:
462 break;
463 case DM_MULT_SCALE_BIAS:
464 ctrTextureBias(0x070707);
465 // Fall through
466 case DM_MULT_SCALE:
467 color = 0xFF707070;
468 // Fall through
469 case DM_MULT:
470 ctrTextureMultiply();
471 break;
472 }
473 } else {
474 color = 0xFF484848;
475 switch (darkenMode) {
476 case DM_NATIVE:
477 case DM_MAX:
478 break;
479 case DM_MULT_SCALE_BIAS:
480 ctrTextureBias(0x030303);
481 // Fall through
482 case DM_MULT_SCALE:
483 color = 0xFF303030;
484 // Fall through
485 case DM_MULT:
486 ctrTextureMultiply();
487 break;
488 }
489
490 }
491 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
492 ctrFlushBatch();
493
494 corew = w;
495 coreh = h;
496 screen_h = 240;
497 if (screenMode < SM_PA_TOP) {
498 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
499 screen_w = 320;
500 } else {
501 C3D_FrameDrawOn(topScreen[doubleBuffer]);
502 screen_w = 400;
503 }
504 ctrSetViewportSize(screen_w, screen_h, true);
505
506 switch (screenMode) {
507 default:
508 return;
509 case SM_AF_TOP:
510 case SM_AF_BOTTOM:
511 w = screen_w / aspectw;
512 h = screen_h / aspecth;
513 if (w * aspecth > screen_h) {
514 w = aspectw * h;
515 h = aspecth * h;
516 } else {
517 h = aspecth * w;
518 w = aspectw * w;
519 }
520 break;
521 case SM_SF_TOP:
522 case SM_SF_BOTTOM:
523 w = screen_w;
524 h = screen_h;
525 break;
526 }
527
528 x = (screen_w - w) / 2;
529 y = (screen_h - h) / 2;
530 ctrActivateTexture(&upscaleBufferTex);
531 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
532 ctrFlushBatch();
533}
534
535static void _drawFrame(struct mGUIRunner* runner, bool faded) {
536 UNUSED(runner);
537
538 C3D_Tex* tex = &outputTexture;
539
540 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
541 C3D_SafeDisplayTransfer(
542 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
543 tex->data, GX_BUFFER_DIM(256, 256),
544 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
545 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
546 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
547
548 if (hasSound == NO_SOUND) {
549 blip_clear(runner->core->getAudioChannel(runner->core, 0));
550 blip_clear(runner->core->getAudioChannel(runner->core, 1));
551 }
552
553 gspWaitForPPF();
554 _drawTex(runner->core, faded);
555}
556
557static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
558
559 C3D_Tex* tex = &outputTexture;
560
561 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
562
563 unsigned y;
564 for (y = 0; y < height; ++y) {
565 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
566 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
567 }
568
569 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
570 C3D_SafeDisplayTransfer(
571 (u32*) newPixels, GX_BUFFER_DIM(256, height),
572 tex->data, GX_BUFFER_DIM(256, 256),
573 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
574 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
575 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
576 gspWaitForPPF();
577 linearFree(newPixels);
578
579 _drawTex(runner->core, faded);
580}
581
582static uint16_t _pollGameInput(struct mGUIRunner* runner) {
583 UNUSED(runner);
584
585 hidScanInput();
586 uint32_t activeKeys = hidKeysHeld();
587 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
588 keys |= (activeKeys >> 24) & 0xF0;
589 return keys;
590}
591
592static void _incrementScreenMode(struct mGUIRunner* runner) {
593 UNUSED(runner);
594 screenMode = (screenMode + 1) % SM_MAX;
595 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
596
597 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
598 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
599}
600
601static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
602 UNUSED(runner);
603 frameLimiter = limit;
604}
605
606static uint32_t _pollInput(const struct mInputMap* map) {
607 hidScanInput();
608 int activeKeys = hidKeysHeld();
609 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
610}
611
612static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
613 hidScanInput();
614 if (!(hidKeysHeld() & KEY_TOUCH)) {
615 return GUI_CURSOR_NOT_PRESENT;
616 }
617 touchPosition pos;
618 hidTouchRead(&pos);
619 *x = pos.px;
620 *y = pos.py;
621 return GUI_CURSOR_DOWN;
622}
623
624static void _sampleRotation(struct mRotationSource* source) {
625 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
626 // Work around ctrulib getting the entries wrong
627 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
628 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
629}
630
631static int32_t _readTiltX(struct mRotationSource* source) {
632 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
633 return rotation->accel.x << 18L;
634}
635
636static int32_t _readTiltY(struct mRotationSource* source) {
637 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
638 return rotation->accel.y << 18L;
639}
640
641static int32_t _readGyroZ(struct mRotationSource* source) {
642 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
643 return rotation->gyro.y << 18L; // Yes, y
644}
645
646static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
647 UNUSED(stream);
648 if (hasSound == CSND_SUPPORTED) {
649 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
650 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
651 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
652 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
653 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
654 if (audioPos == AUDIO_SAMPLES * 3) {
655 u8 playing = 0;
656 csndIsPlaying(0x8, &playing);
657 if (!playing) {
658 CSND_SetPlayState(0x8, 1);
659 CSND_SetPlayState(0x9, 1);
660 csndExecCmds(false);
661 }
662 }
663 } else if (hasSound == DSP_SUPPORTED) {
664 int startId = bufferId;
665 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
666 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
667 if (bufferId == startId) {
668 blip_clear(left);
669 blip_clear(right);
670 return;
671 }
672 }
673 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
674 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
675 dspBuffer[bufferId].data_pcm16 = tmpBuf;
676 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
677 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
678 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
679 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
680 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
681 }
682}
683
684int main() {
685 rotation.d.sample = _sampleRotation;
686 rotation.d.readTiltX = _readTiltX;
687 rotation.d.readTiltY = _readTiltY;
688 rotation.d.readGyroZ = _readGyroZ;
689
690 stream.videoDimensionsChanged = 0;
691 stream.postVideoFrame = 0;
692 stream.postAudioFrame = 0;
693 stream.postAudioBuffer = _postAudioBuffer;
694
695 if (!allocateRomBuffer()) {
696 return 1;
697 }
698
699 aptHook(&cookie, _aptHook, 0);
700
701 ptmuInit();
702 hasSound = NO_SOUND;
703 if (!ndspInit()) {
704 hasSound = DSP_SUPPORTED;
705 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
706 ndspSetOutputCount(1);
707 ndspChnReset(0);
708 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
709 ndspChnSetInterp(0, NDSP_INTERP_NONE);
710 ndspChnSetRate(0, 0x8000);
711 ndspChnWaveBufClear(0);
712 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
713 memset(dspBuffer, 0, sizeof(dspBuffer));
714 int i;
715 for (i = 0; i < DSP_BUFFERS; ++i) {
716 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
717 dspBuffer[i].nsamples = AUDIO_SAMPLES;
718 }
719 }
720
721 if (hasSound == NO_SOUND && !csndInit()) {
722 hasSound = CSND_SUPPORTED;
723 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
724 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
725 }
726
727 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
728
729 if (!_initGpu()) {
730 outputTexture.data = 0;
731 _cleanup();
732 return 1;
733 }
734
735 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
736 _cleanup();
737 return 1;
738 }
739 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
740 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
741 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
742 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
743
744 // Zero texture data to make sure no garbage around the border interferes with filtering
745 GX_MemoryFill(
746 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
747 NULL, 0, NULL, 0);
748 gspWaitForPSC0();
749
750 struct GUIFont* font = GUIFontCreate();
751
752 if (!font) {
753 _cleanup();
754 return 1;
755 }
756
757 struct mGUIRunner runner = {
758 .params = {
759 320, 240,
760 font, "/",
761 _drawStart, _drawEnd,
762 _pollInput, _pollCursor,
763 _batteryState,
764 _guiPrepare, _guiFinish,
765 },
766 .keySources = (struct GUIInputKeys[]) {
767 {
768 .name = "3DS Input",
769 .id = _3DS_INPUT,
770 .keyNames = (const char*[]) {
771 "A",
772 "B",
773 "Select",
774 "Start",
775 "D-Pad Right",
776 "D-Pad Left",
777 "D-Pad Up",
778 "D-Pad Down",
779 "R",
780 "L",
781 "X",
782 "Y",
783 0,
784 0,
785 "ZL",
786 "ZR",
787 0,
788 0,
789 0,
790 0,
791 0,
792 0,
793 0,
794 0,
795 "C-Stick Right",
796 "C-Stick Left",
797 "C-Stick Up",
798 "C-Stick Down",
799 },
800 .nKeys = 28
801 },
802 { .id = 0 }
803 },
804 .configExtra = (struct GUIMenuItem[]) {
805 {
806 .title = "Screen mode",
807 .data = "screenMode",
808 .submenu = 0,
809 .state = SM_PA_TOP,
810 .validStates = (const char*[]) {
811 "Pixel-Accurate/Bottom",
812 "Aspect-Ratio Fit/Bottom",
813 "Stretched/Bottom",
814 "Pixel-Accurate/Top",
815 "Aspect-Ratio Fit/Top",
816 "Stretched/Top",
817 },
818 .nStates = 6
819 },
820 {
821 .title = "Filtering",
822 .data = "filterMode",
823 .submenu = 0,
824 .state = FM_LINEAR_2x,
825 .validStates = (const char*[]) {
826 NULL, // Disable choosing nearest neighbor; it always looks bad
827 "Bilinear (smoother)",
828 "Bilinear (pixelated)",
829 },
830 .nStates = 3
831 },
832 {
833 .title = "Screen darkening",
834 .data = "darkenMode",
835 .submenu = 0,
836 .state = DM_NATIVE,
837 .validStates = (const char*[]) {
838 "None",
839 "Dark",
840 "Very dark",
841 "Grayed",
842 },
843 .nStates = 4
844 }
845 },
846 .nConfigExtra = 3,
847 .setup = _setup,
848 .teardown = 0,
849 .gameLoaded = _gameLoaded,
850 .gameUnloaded = _gameUnloaded,
851 .prepareForFrame = 0,
852 .drawFrame = _drawFrame,
853 .drawScreenshot = _drawScreenshot,
854 .paused = _gameUnloaded,
855 .unpaused = _gameLoaded,
856 .incrementScreenMode = _incrementScreenMode,
857 .setFrameLimiter = _setFrameLimiter,
858 .pollGameInput = _pollGameInput
859 };
860
861 mGUIInit(&runner, "3ds");
862
863 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
864 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
865 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
866 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
867 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
868 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
869 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
870 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
871 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
872 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
873
874 mGUIRunloop(&runner);
875 mGUIDeinit(&runner);
876
877 _cleanup();
878 return 0;
879}