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 "gba/renderers/video-software.h"
8#include "gba/context/context.h"
9#include "gba/gui/gui-runner.h"
10#include "gba/video.h"
11#include "util/gui.h"
12#include "util/gui/file-select.h"
13#include "util/gui/font.h"
14#include "util/memory.h"
15
16#include "3ds-vfs.h"
17#include "ctr-gpu.h"
18
19#include <3ds.h>
20
21static enum ScreenMode {
22 SM_PA_BOTTOM,
23 SM_AF_BOTTOM,
24 SM_SF_BOTTOM,
25 SM_PA_TOP,
26 SM_AF_TOP,
27 SM_SF_TOP,
28 SM_MAX
29} screenMode = SM_PA_BOTTOM;
30
31#define AUDIO_SAMPLES 0x80
32#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 24)
33
34FS_archive sdmcArchive;
35
36static struct GBA3DSRotationSource {
37 struct GBARotationSource d;
38 accelVector accel;
39 angularRate gyro;
40} rotation;
41
42static bool hasSound;
43// TODO: Move into context
44static struct GBAVideoSoftwareRenderer renderer;
45static struct GBAAVStream stream;
46static int16_t* audioLeft = 0;
47static int16_t* audioRight = 0;
48static size_t audioPos = 0;
49static struct ctrTexture gbaOutputTexture;
50
51extern bool allocateRomBuffer(void);
52
53static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio);
54
55static void _drawStart(void) {
56 ctrGpuBeginFrame();
57 if (screenMode < SM_PA_TOP) {
58 ctrSetViewportSize(320, 240);
59 } else {
60 ctrSetViewportSize(400, 240);
61 }
62}
63
64static void _drawEnd(void) {
65 int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
66 u16 width = 0, height = 0;
67
68 void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width);
69 ctrGpuEndFrame(outputFramebuffer, width, height);
70 gfxSwapBuffersGpu();
71 gspWaitForEvent(GSPEVENT_VBlank0, false);
72}
73
74static void _setup(struct GBAGUIRunner* runner) {
75 runner->context.gba->rotationSource = &rotation.d;
76 if (hasSound) {
77 runner->context.gba->stream = &stream;
78 }
79
80 GBAVideoSoftwareRendererCreate(&renderer);
81 renderer.outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
82 renderer.outputBufferStride = 256;
83 runner->context.renderer = &renderer.d;
84
85 GBAAudioResizeBuffer(&runner->context.gba->audio, AUDIO_SAMPLES);
86}
87
88static void _gameLoaded(struct GBAGUIRunner* runner) {
89 if (runner->context.gba->memory.hw.devices & HW_TILT) {
90 HIDUSER_EnableAccelerometer();
91 }
92 if (runner->context.gba->memory.hw.devices & HW_GYRO) {
93 HIDUSER_EnableGyroscope();
94 }
95
96#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
97 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
98 blip_set_rates(runner->context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768 * ratio);
99 blip_set_rates(runner->context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768 * ratio);
100#endif
101 if (hasSound) {
102 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
103 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
104 audioPos = 0;
105 csndPlaySound(0x8, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, -1.0, audioLeft, audioLeft, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
106 csndPlaySound(0x9, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, 1.0, audioRight, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
107 }
108}
109
110static void _gameUnloaded(struct GBAGUIRunner* runner) {
111 if (hasSound) {
112 CSND_SetPlayState(8, 0);
113 CSND_SetPlayState(9, 0);
114 csndExecCmds(false);
115 }
116
117 if (runner->context.gba->memory.hw.devices & HW_TILT) {
118 HIDUSER_DisableAccelerometer();
119 }
120 if (runner->context.gba->memory.hw.devices & HW_GYRO) {
121 HIDUSER_DisableGyroscope();
122 }
123}
124
125static void _drawTex(bool faded) {
126 u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF;
127
128 int screen_w = screenMode < SM_PA_TOP ? 320 : 400;
129 int screen_h = 240;
130
131 int w, h;
132
133 switch (screenMode) {
134 case SM_PA_TOP:
135 case SM_PA_BOTTOM:
136 default:
137 w = VIDEO_HORIZONTAL_PIXELS;
138 h = VIDEO_VERTICAL_PIXELS;
139 break;
140 case SM_AF_TOP:
141 w = 360;
142 h = 240;
143 break;
144 case SM_AF_BOTTOM:
145 // Largest possible size with 3:2 aspect ratio and integer dimensions
146 w = 318;
147 h = 212;
148 break;
149 case SM_SF_TOP:
150 case SM_SF_BOTTOM:
151 w = screen_w;
152 h = screen_h;
153 break;
154 }
155
156 int x = (screen_w - w) / 2;
157 int y = (screen_h - h) / 2;
158
159 ctrAddRectScaled(color, x, y, w, h, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
160}
161
162static void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
163 UNUSED(runner);
164
165 void* outputBuffer = renderer.outputBuffer;
166 struct ctrTexture* tex = &gbaOutputTexture;
167
168 GSPGPU_FlushDataCache(NULL, outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
169 GX_SetDisplayTransfer(NULL,
170 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
171 tex->data, GX_BUFFER_DIM(256, 256),
172 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
173 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
174 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
175
176#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
177 if (!hasSound) {
178 blip_clear(runner->context.gba->audio.left);
179 blip_clear(runner->context.gba->audio.right);
180 }
181#endif
182
183 gspWaitForPPF();
184 ctrActivateTexture(tex);
185 _drawTex(faded);
186}
187
188static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) {
189 UNUSED(runner);
190
191 struct ctrTexture* tex = &gbaOutputTexture;
192
193 u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100);
194
195 // Convert image from RGBX8 to BGR565
196 for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
197 for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
198 // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
199 u32 p = *pixels++;
200 newPixels[y * 256 + x] =
201 (p << 24 >> (24 + 3) << 11) | // R
202 (p << 16 >> (24 + 2) << 5) | // G
203 (p << 8 >> (24 + 3) << 0); // B
204 }
205 memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
206 }
207
208 GSPGPU_FlushDataCache(NULL, (void*)newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
209 GX_SetDisplayTransfer(NULL,
210 (void*)newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
211 tex->data, GX_BUFFER_DIM(256, 256),
212 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
213 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
214 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
215 gspWaitForPPF();
216 linearFree(newPixels);
217
218 ctrActivateTexture(tex);
219 _drawTex(faded);
220}
221
222static uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
223 UNUSED(runner);
224
225 hidScanInput();
226 uint32_t activeKeys = hidKeysHeld() & 0xF00003FF;
227 activeKeys |= activeKeys >> 24;
228 return activeKeys;
229}
230
231static void _incrementScreenMode(struct GBAGUIRunner* runner) {
232 UNUSED(runner);
233 // Clear the buffer
234 _drawStart();
235 _drawEnd();
236 _drawStart();
237 _drawEnd();
238 screenMode = (screenMode + 1) % SM_MAX;
239}
240
241static uint32_t _pollInput(void) {
242 hidScanInput();
243 uint32_t keys = 0;
244 int activeKeys = hidKeysHeld();
245 if (activeKeys & KEY_X) {
246 keys |= 1 << GUI_INPUT_CANCEL;
247 }
248 if (activeKeys & KEY_Y) {
249 keys |= 1 << GBA_GUI_INPUT_SCREEN_MODE;
250 }
251 if (activeKeys & KEY_B) {
252 keys |= 1 << GUI_INPUT_BACK;
253 }
254 if (activeKeys & KEY_A) {
255 keys |= 1 << GUI_INPUT_SELECT;
256 }
257 if (activeKeys & KEY_LEFT) {
258 keys |= 1 << GUI_INPUT_LEFT;
259 }
260 if (activeKeys & KEY_RIGHT) {
261 keys |= 1 << GUI_INPUT_RIGHT;
262 }
263 if (activeKeys & KEY_UP) {
264 keys |= 1 << GUI_INPUT_UP;
265 }
266 if (activeKeys & KEY_DOWN) {
267 keys |= 1 << GUI_INPUT_DOWN;
268 }
269 if (activeKeys & KEY_CSTICK_UP) {
270 keys |= 1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS;
271 }
272 if (activeKeys & KEY_CSTICK_DOWN) {
273 keys |= 1 << GBA_GUI_INPUT_DECREASE_BRIGHTNESS;
274 }
275 return keys;
276}
277
278static enum GUICursorState _pollCursor(int* x, int* y) {
279 hidScanInput();
280 if (!(hidKeysHeld() & KEY_TOUCH)) {
281 return GUI_CURSOR_NOT_PRESENT;
282 }
283 touchPosition pos;
284 hidTouchRead(&pos);
285 *x = pos.px;
286 *y = pos.py;
287 return GUI_CURSOR_DOWN;
288}
289
290static void _sampleRotation(struct GBARotationSource* source) {
291 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
292 // Work around ctrulib getting the entries wrong
293 rotation->accel = *(accelVector*)& hidSharedMem[0x48];
294 rotation->gyro = *(angularRate*)& hidSharedMem[0x5C];
295}
296
297static int32_t _readTiltX(struct GBARotationSource* source) {
298 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
299 return rotation->accel.x << 18L;
300}
301
302static int32_t _readTiltY(struct GBARotationSource* source) {
303 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
304 return rotation->accel.y << 18L;
305}
306
307static int32_t _readGyroZ(struct GBARotationSource* source) {
308 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
309 return rotation->gyro.y << 18L; // Yes, y
310}
311
312static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) {
313 UNUSED(stream);
314#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
315 blip_read_samples(audio->left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
316 blip_read_samples(audio->right, &audioRight[audioPos], AUDIO_SAMPLES, false);
317#elif RESAMPLE_LIBRARY == RESAMPLE_NN
318 GBAAudioCopy(audio, &audioLeft[audioPos], &audioRight[audioPos], AUDIO_SAMPLES);
319#endif
320 GSPGPU_FlushDataCache(0, (void*) &audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
321 GSPGPU_FlushDataCache(0, (void*) &audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
322 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
323 if (audioPos == AUDIO_SAMPLES * 3) {
324 u8 playing = 0;
325 csndIsPlaying(0x8, &playing);
326 if (!playing) {
327 CSND_SetPlayState(0x8, 1);
328 CSND_SetPlayState(0x9, 1);
329 csndExecCmds(false);
330 }
331 }
332}
333
334int main() {
335 hasSound = !csndInit();
336
337 rotation.d.sample = _sampleRotation;
338 rotation.d.readTiltX = _readTiltX;
339 rotation.d.readTiltY = _readTiltY;
340 rotation.d.readGyroZ = _readGyroZ;
341
342 stream.postVideoFrame = 0;
343 stream.postAudioFrame = 0;
344 stream.postAudioBuffer = _postAudioBuffer;
345
346 if (!allocateRomBuffer()) {
347 return 1;
348 }
349
350 if (hasSound) {
351 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
352 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
353 }
354
355 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
356
357 if (ctrInitGpu() < 0) {
358 goto cleanup;
359 }
360
361 ctrTexture_Init(&gbaOutputTexture);
362 gbaOutputTexture.format = GPU_RGB565;
363 gbaOutputTexture.filter = GPU_LINEAR;
364 gbaOutputTexture.width = 256;
365 gbaOutputTexture.height = 256;
366 gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
367 void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
368
369 // Zero texture data to make sure no garbage around the border interferes with filtering
370 GX_SetMemoryFill(NULL,
371 gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
372 NULL, 0, NULL, 0);
373 gspWaitForPSC0();
374
375 sdmcArchive = (FS_archive) {
376 ARCH_SDMC,
377 (FS_path) { PATH_EMPTY, 1, (const u8*)"" },
378 0, 0
379 };
380 FSUSER_OpenArchive(0, &sdmcArchive);
381
382 struct GUIFont* font = GUIFontCreate();
383
384 if (!font) {
385 goto cleanup;
386 }
387
388 struct GBAGUIRunner runner = {
389 .params = {
390 320, 240,
391 font, "/",
392 _drawStart, _drawEnd,
393 _pollInput, _pollCursor,
394 0, 0,
395
396 GUI_PARAMS_TRAIL
397 },
398 .setup = _setup,
399 .teardown = 0,
400 .gameLoaded = _gameLoaded,
401 .gameUnloaded = _gameUnloaded,
402 .prepareForFrame = 0,
403 .drawFrame = _drawFrame,
404 .drawScreenshot = _drawScreenshot,
405 .paused = _gameUnloaded,
406 .unpaused = _gameLoaded,
407 .incrementScreenMode = _incrementScreenMode,
408 .pollGameInput = _pollGameInput
409 };
410
411 GBAGUIInit(&runner, "3ds");
412 GBAGUIRunloop(&runner);
413 GBAGUIDeinit(&runner);
414
415cleanup:
416 linearFree(renderer.outputBuffer);
417
418 ctrDeinitGpu();
419 vramFree(gbaOutputTexture.data);
420
421 gfxExit();
422
423 if (hasSound) {
424 linearFree(audioLeft);
425 linearFree(audioRight);
426 }
427 csndExit();
428 return 0;
429}