src/platform/wii/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#define asm __asm__
7
8#include <fat.h>
9#include <gccore.h>
10#include <malloc.h>
11#include <wiiuse/wpad.h>
12
13#include "util/common.h"
14
15#include "gba/renderers/video-software.h"
16#include "gba/context/context.h"
17#include "util/gui.h"
18#include "util/gui/file-select.h"
19#include "util/gui/font.h"
20#include "util/vfs.h"
21
22#define SAMPLES 1024
23
24static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
25static void GBAWiiFrame(void);
26static bool GBAWiiLoadGame(const char* path);
27
28static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
29static void _audioDMA(void);
30static void _setRumble(struct GBARumble* rumble, int enable);
31static void _sampleRotation(struct GBARotationSource* source);
32static int32_t _readTiltX(struct GBARotationSource* source);
33static int32_t _readTiltY(struct GBARotationSource* source);
34static int32_t _readGyroZ(struct GBARotationSource* source);
35
36static void _drawStart(void);
37static void _drawEnd(void);
38static int _pollInput(void);
39
40static struct GBAContext context;
41static struct GBAVideoSoftwareRenderer renderer;
42static struct GBAAVStream stream;
43static struct GBARumble rumble;
44static struct GBARotationSource rotation;
45static FILE* logfile;
46static GXRModeObj* mode;
47static Mtx model, view, modelview;
48static uint16_t* texmem;
49static GXTexObj tex;
50static int32_t tiltX;
51static int32_t tiltY;
52static int32_t gyroZ;
53
54static void* framebuffer[2];
55static int whichFb = 0;
56
57static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
58static volatile size_t audioBufferSize = 0;
59static volatile int currentAudioBuffer = 0;
60
61static struct GUIFont* font;
62
63int main() {
64 VIDEO_Init();
65 PAD_Init();
66 WPAD_Init();
67 AUDIO_Init(0);
68 AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
69 AUDIO_RegisterDMACallback(_audioDMA);
70
71 memset(audioBuffer, 0, sizeof(audioBuffer));
72
73#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
74#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
75#endif
76
77 mode = VIDEO_GetPreferredMode(0);
78 framebuffer[0] = SYS_AllocateFramebuffer(mode);
79 framebuffer[1] = SYS_AllocateFramebuffer(mode);
80
81 VIDEO_Configure(mode);
82 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
83 VIDEO_SetBlack(FALSE);
84 VIDEO_Flush();
85 VIDEO_WaitVSync();
86 if (mode->viTVMode & VI_NON_INTERLACE) {
87 VIDEO_WaitVSync();
88 }
89
90 GXColor bg = { 0, 0, 0, 0xFF };
91 void* fifo = memalign(32, 0x40000);
92 memset(fifo, 0, 0x40000);
93 GX_Init(fifo, 0x40000);
94 GX_SetCopyClear(bg, 0x00FFFFFF);
95 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
96
97 f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight);
98 u32 xfbHeight = GX_SetDispCopyYScale(yscale);
99 GX_SetScissor(0, 0, mode->viWidth, mode->viWidth);
100 GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
101 GX_SetDispCopyDst(mode->fbWidth, xfbHeight);
102 GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter);
103 GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
104
105 GX_SetCullMode(GX_CULL_NONE);
106 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
107 GX_SetDispCopyGamma(GX_GM_1_0);
108
109 GX_ClearVtxDesc();
110 GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
111 GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
112
113 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
114 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
115
116 GX_SetNumChans(1);
117 GX_SetNumTexGens(1);
118 GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
119 GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
120
121 GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
122 GX_InvVtxCache();
123 GX_InvalidateTexAll();
124
125 Mtx44 proj;
126
127 guVector cam = { 0.0f, 0.0f, 0.0f };
128 guVector up = { 0.0f, 1.0f, 0.0f };
129 guVector look = { 0.0f, 0.0f, -1.0f };
130 guLookAt(view, &cam, &up, &look);
131
132 guMtxIdentity(model);
133 guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
134 guMtxConcat(view, model, modelview);
135 GX_LoadPosMtxImm(modelview, GX_PNMTX0);
136
137 texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
138 memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
139 GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
140
141 font = GUIFontCreate();
142
143 fatInitDefault();
144
145 logfile = fopen("/mgba.log", "w");
146
147 stream.postAudioFrame = 0;
148 stream.postAudioBuffer = 0;
149 stream.postVideoFrame = _postVideoFrame;
150
151 rumble.setRumble = _setRumble;
152
153 rotation.sample = _sampleRotation;
154 rotation.readTiltX = _readTiltX;
155 rotation.readTiltY = _readTiltY;
156 rotation.readGyroZ = _readGyroZ;
157
158 GBAContextInit(&context, 0);
159 struct GBAOptions opts = {
160 .useBios = true,
161 .logLevel = 0,
162 .idleOptimization = IDLE_LOOP_DETECT
163 };
164 GBAConfigLoadDefaults(&context.config, &opts);
165 context.gba->logHandler = GBAWiiLog;
166 context.gba->stream = &stream;
167 context.gba->rumble = &rumble;
168 context.gba->rotationSource = &rotation;
169
170 GBAVideoSoftwareRendererCreate(&renderer);
171 renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
172 renderer.outputBufferStride = 256;
173 context.renderer = &renderer.d;
174
175 GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
176
177#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
178 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 48000);
179 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
180#endif
181
182 struct GUIParams params = {
183 352, 230,
184 font, "/", _drawStart, _drawEnd, _pollInput,
185
186 GUI_PARAMS_TRAIL
187 };
188 GUIInit(¶ms);
189
190 while (true) {
191 char path[256];
192 guOrtho(proj, -20, 240, 0, 352, 0, 300);
193 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
194 GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
195
196 if (!GUISelectFile(¶ms, path, sizeof(path), GBAIsROM) || !GBAWiiLoadGame(path)) {
197 break;
198 }
199 WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC);
200 GBAContextStart(&context);
201 if (context.gba->memory.hw.devices & HW_GYRO) {
202 int i;
203 for (i = 0; i < 6; ++i) {
204 u32 result = WPAD_SetMotionPlus(0, 1);
205 if (result == WPAD_ERR_NONE) {
206 break;
207 }
208 sleep(1);
209 }
210 }
211
212 guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
213 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
214 GX_SetVtxDesc(GX_VA_CLR0, GX_NONE);
215
216 while (true) {
217 #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
218 int available = blip_samples_avail(context.gba->audio.left);
219 if (available + audioBufferSize > SAMPLES) {
220 available = SAMPLES - audioBufferSize;
221 }
222 available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
223 if (available > 0) {
224 blip_read_samples(context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
225 blip_read_samples(context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
226 audioBufferSize += available;
227 }
228 if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
229 _audioDMA();
230 AUDIO_StartDMA();
231 }
232 #endif
233 PAD_ScanPads();
234 u16 padkeys = PAD_ButtonsHeld(0);
235 WPAD_ScanPads();
236 u32 wiiPad = WPAD_ButtonsHeld(0);
237 u32 ext = 0;
238 uint16_t keys = 0;
239 WPAD_Probe(0, &ext);
240
241 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
242 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
243 keys |= 1 << GBA_KEY_A;
244 }
245 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
246 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
247 keys |= 1 << GBA_KEY_B;
248 }
249 if ((padkeys & PAD_TRIGGER_L) || (wiiPad & WPAD_BUTTON_B) ||
250 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_L))) {
251 keys |= 1 << GBA_KEY_L;
252 }
253 if ((padkeys & PAD_TRIGGER_R) || (wiiPad & WPAD_BUTTON_A) ||
254 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_R))) {
255 keys |= 1 << GBA_KEY_R;
256 }
257 if ((padkeys & PAD_BUTTON_START) || (wiiPad & WPAD_BUTTON_PLUS) ||
258 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_PLUS))) {
259 keys |= 1 << GBA_KEY_START;
260 }
261 if ((padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y)) || (wiiPad & WPAD_BUTTON_MINUS) ||
262 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_MINUS))) {
263 keys |= 1 << GBA_KEY_SELECT;
264 }
265 if ((padkeys & PAD_BUTTON_LEFT) || (wiiPad & WPAD_BUTTON_UP) ||
266 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
267 keys |= 1 << GBA_KEY_LEFT;
268 }
269 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
270 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
271 keys |= 1 << GBA_KEY_RIGHT;
272 }
273 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
274 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
275 keys |= 1 << GBA_KEY_UP;
276 }
277 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
278 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
279 keys |= 1 << GBA_KEY_DOWN;
280 }
281 int x = PAD_StickX(0);
282 int y = PAD_StickY(0);
283 if (x < -0x40) {
284 keys |= 1 << GBA_KEY_LEFT;
285 }
286 if (x > 0x40) {
287 keys |= 1 << GBA_KEY_RIGHT;
288 }
289 if (y < -0x40) {
290 keys |= 1 << GBA_KEY_DOWN;
291 }
292 if (y > 0x40) {
293 keys |= 1 << GBA_KEY_UP;
294 }
295 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) || (wiiPad & WPAD_CLASSIC_BUTTON_HOME)) {
296 break;
297 }
298 GBAContextFrame(&context, keys);
299 }
300 AUDIO_StopDMA();
301 GBAContextStop(&context);
302 }
303
304 fclose(logfile);
305 free(fifo);
306
307 GBAContextDeinit(&context);
308
309 free(renderer.outputBuffer);
310 GUIFontDestroy(font);
311
312 return 0;
313}
314
315static void GBAWiiFrame(void) {
316 size_t x, y;
317 uint64_t* texdest = (uint64_t*) texmem;
318 uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
319 for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
320 for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
321 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
322 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
323 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
324 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
325 }
326 }
327 DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
328
329 _drawStart();
330
331 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
332 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
333 GX_InvalidateTexAll();
334 GX_LoadTexObj(&tex, GX_TEXMAP0);
335
336 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
337 GX_Position2s16(0, 256);
338 GX_TexCoord2s16(0, 1);
339
340 GX_Position2s16(256, 256);
341 GX_TexCoord2s16(1, 1);
342
343 GX_Position2s16(256, 0);
344 GX_TexCoord2s16(1, 0);
345
346 GX_Position2s16(0, 0);
347 GX_TexCoord2s16(0, 0);
348 GX_End();
349
350 _drawEnd();
351}
352
353bool GBAWiiLoadGame(const char* path) {
354 _drawStart();
355 GUIFontPrintf(font, 176, 120, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
356 _drawEnd();
357 _drawStart();
358 GUIFontPrintf(font, 176, 120, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
359 _drawEnd();
360
361 return GBAContextLoadROM(&context, path, true);
362}
363
364void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
365 UNUSED(thread);
366 UNUSED(level);
367 if (!logfile) {
368 return;
369 }
370 vfprintf(logfile, format, args);
371 fprintf(logfile, "\n");
372 fflush(logfile);
373}
374
375static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
376 UNUSED(stream);
377 UNUSED(renderer);
378 GBAWiiFrame();
379}
380
381static void _audioDMA(void) {
382 if (!audioBufferSize) {
383 return;
384 }
385 DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
386 AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
387 currentAudioBuffer = (currentAudioBuffer + 1) % 3;
388 audioBufferSize = 0;
389}
390
391static void _drawStart(void) {
392 VIDEO_WaitVSync();
393 GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
394 GX_SetColorUpdate(GX_TRUE);
395
396 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
397}
398
399static void _drawEnd(void) {
400 GX_DrawDone();
401
402 whichFb = !whichFb;
403
404 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
405 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
406 VIDEO_Flush();
407}
408
409static int _pollInput(void) {
410 PAD_ScanPads();
411 u16 padkeys = PAD_ButtonsHeld(0);
412
413 WPAD_ScanPads();
414 u32 wiiPad = WPAD_ButtonsHeld(0);
415 u32 ext = 0;
416 WPAD_Probe(0, &ext);
417
418 int keys = 0;
419 int x = PAD_StickX(0);
420 int y = PAD_StickY(0);
421 if (x < -0x40) {
422 keys |= 1 << GUI_INPUT_LEFT;
423 }
424 if (x > 0x40) {
425 keys |= 1 << GUI_INPUT_RIGHT;
426 }
427 if (y < -0x40) {
428 keys |= 1 << GUI_INPUT_DOWN;
429 }
430 if (y > 0x40) {
431 keys |= 1 << GUI_INPUT_UP;
432 }
433 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
434 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
435 keys |= 1 << GUI_INPUT_SELECT;
436 }
437 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
438 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
439 keys |= 1 << GUI_INPUT_BACK;
440 }
441 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
442 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
443 keys |= 1 << GUI_INPUT_CANCEL;
444 }
445 if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
446 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
447 keys |= 1 << GUI_INPUT_LEFT;
448 }
449 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
450 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
451 keys |= 1 << GUI_INPUT_RIGHT;
452 }
453 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
454 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
455 keys |= 1 << GUI_INPUT_UP;
456 }
457 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
458 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
459 keys |= 1 << GUI_INPUT_DOWN;
460 }
461 return keys;
462}
463
464void _setRumble(struct GBARumble* rumble, int enable) {
465 UNUSED(rumble);
466 WPAD_Rumble(0, enable);
467 if (enable) {
468 PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
469 } else {
470 PAD_ControlMotor(0, PAD_MOTOR_STOP);
471 }
472}
473
474void _sampleRotation(struct GBARotationSource* source) {
475 UNUSED(source);
476 vec3w_t accel;
477 WPAD_Accel(0, &accel);
478 // These are swapped
479 tiltX = (accel.y - 0x1EA) << 22;
480 tiltY = (accel.x - 0x1EA) << 22;
481
482 // This doesn't seem to work at all with -TR remotes
483 struct expansion_t exp;
484 WPAD_Expansion(0, &exp);
485 if (exp.type != EXP_MOTION_PLUS) {
486 return;
487 }
488 gyroZ = exp.mp.rz - 0x1FA0;
489 gyroZ <<= 18;
490}
491
492int32_t _readTiltX(struct GBARotationSource* source) {
493 UNUSED(source);
494 return tiltX;
495}
496
497int32_t _readTiltY(struct GBARotationSource* source) {
498 UNUSED(source);
499 return tiltY;
500}
501
502int32_t _readGyroZ(struct GBARotationSource* source) {
503 UNUSED(source);
504 return gyroZ;
505}