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