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/supervisor/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);
30
31static void _drawStart(void);
32static void _drawEnd(void);
33static int _pollInput(void);
34
35static struct GBAContext context;
36static struct GBAVideoSoftwareRenderer renderer;
37static struct GBAAVStream stream;
38static FILE* logfile;
39static GXRModeObj* mode;
40static Mtx model, view, modelview;
41static uint16_t* texmem;
42static GXTexObj tex;
43
44static void* framebuffer[2];
45static int whichFb = 0;
46
47static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
48static volatile size_t audioBufferSize = 0;
49static volatile int currentAudioBuffer = 0;
50
51static struct GUIFont* font;
52
53int main() {
54 VIDEO_Init();
55 PAD_Init();
56 WPAD_Init();
57 AUDIO_Init(0);
58 AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
59 AUDIO_RegisterDMACallback(_audioDMA);
60
61 memset(audioBuffer, 0, sizeof(audioBuffer));
62
63#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
64#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
65#endif
66
67 mode = VIDEO_GetPreferredMode(0);
68 framebuffer[0] = SYS_AllocateFramebuffer(mode);
69 framebuffer[1] = SYS_AllocateFramebuffer(mode);
70
71 VIDEO_Configure(mode);
72 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
73 VIDEO_SetBlack(FALSE);
74 VIDEO_Flush();
75 VIDEO_WaitVSync();
76 if (mode->viTVMode & VI_NON_INTERLACE) {
77 VIDEO_WaitVSync();
78 }
79
80 GXColor bg = { 0, 0, 0, 0xFF };
81 void* fifo = memalign(32, 0x40000);
82 memset(fifo, 0, 0x40000);
83 GX_Init(fifo, 0x40000);
84 GX_SetCopyClear(bg, 0x00FFFFFF);
85 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
86
87 f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight);
88 u32 xfbHeight = GX_SetDispCopyYScale(yscale);
89 GX_SetScissor(0, 0, mode->viWidth, mode->viWidth);
90 GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
91 GX_SetDispCopyDst(mode->fbWidth, xfbHeight);
92 GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter);
93 GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
94
95 GX_SetCullMode(GX_CULL_NONE);
96 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
97 GX_SetDispCopyGamma(GX_GM_1_0);
98
99 GX_ClearVtxDesc();
100 GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
101 GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
102
103 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
104 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
105
106 GX_SetNumChans(1);
107 GX_SetNumTexGens(1);
108 GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
109 GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);
110
111 GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
112 GX_InvVtxCache();
113 GX_InvalidateTexAll();
114
115 Mtx44 proj;
116
117 guVector cam = { 0.0f, 0.0f, 0.0f };
118 guVector up = { 0.0f, 1.0f, 0.0f };
119 guVector look = { 0.0f, 0.0f, -1.0f };
120 guLookAt(view, &cam, &up, &look);
121
122 guMtxIdentity(model);
123 guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
124 guMtxConcat(view, model, modelview);
125 GX_LoadPosMtxImm(modelview, GX_PNMTX0);
126
127 texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
128 memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
129 GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
130
131 font = GUIFontCreate();
132
133 fatInitDefault();
134
135 logfile = fopen("/mgba.log", "w");
136
137 stream.postAudioFrame = 0;
138 stream.postAudioBuffer = 0;
139 stream.postVideoFrame = _postVideoFrame;
140
141 GBAContextInit(&context, 0);
142 struct GBAOptions opts = {
143 .useBios = true,
144 .logLevel = 0,
145 .idleOptimization = IDLE_LOOP_REMOVE
146 };
147 GBAConfigLoadDefaults(&context.config, &opts);
148 context.gba->logHandler = GBAWiiLog;
149 context.gba->stream = &stream;
150
151 GBAVideoSoftwareRendererCreate(&renderer);
152 renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
153 renderer.outputBufferStride = 256;
154 GBAVideoAssociateRenderer(&context.gba->video, &renderer.d);
155
156 GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
157
158#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
159 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 48000);
160 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
161#endif
162
163 char path[256];
164 guOrtho(proj, -20, 220, 0, 400, 0, 300);
165 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
166
167 struct GUIParams params = {
168 400, 230,
169 font, _drawStart, _drawEnd, _pollInput
170 };
171 if (!selectFile(¶ms, "/", path, sizeof(path), "gba") || !GBAWiiLoadGame(path)) {
172 free(renderer.outputBuffer);
173 GUIFontDestroy(font);
174 return 1;
175 }
176 GBAContextStart(&context);
177
178 guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
179 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
180
181 while (true) {
182#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
183 int available = blip_samples_avail(context.gba->audio.left);
184 if (available + audioBufferSize > SAMPLES) {
185 available = SAMPLES - audioBufferSize;
186 }
187 available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
188 if (available > 0) {
189 blip_read_samples(context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
190 blip_read_samples(context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
191 audioBufferSize += available;
192 }
193 if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
194 _audioDMA();
195 AUDIO_StartDMA();
196 }
197#endif
198 PAD_ScanPads();
199 u16 padkeys = PAD_ButtonsHeld(0);
200 WPAD_ScanPads();
201 u32 wiiPad = WPAD_ButtonsHeld(0);
202 u32 ext = 0;
203 uint16_t keys = 0;
204 WPAD_Probe(0, &ext);
205
206 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
207 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
208 keys |= 1 << GBA_KEY_A;
209 }
210 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
211 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
212 keys |= 1 << GBA_KEY_B;
213 }
214 if ((padkeys & PAD_TRIGGER_L) || (wiiPad & WPAD_BUTTON_B) ||
215 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_L))) {
216 keys |= 1 << GBA_KEY_L;
217 }
218 if ((padkeys & PAD_TRIGGER_R) || (wiiPad & WPAD_BUTTON_A) ||
219 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_R))) {
220 keys |= 1 << GBA_KEY_R;
221 }
222 if ((padkeys & PAD_BUTTON_START) || (wiiPad & WPAD_BUTTON_PLUS) ||
223 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_PLUS))) {
224 keys |= 1 << GBA_KEY_START;
225 }
226 if ((padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y))|| (wiiPad & WPAD_BUTTON_MINUS) ||
227 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_MINUS))) {
228 keys |= 1 << GBA_KEY_SELECT;
229 }
230 if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
231 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
232 keys |= 1 << GBA_KEY_LEFT;
233 }
234 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
235 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
236 keys |= 1 << GBA_KEY_RIGHT;
237 }
238 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
239 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
240 keys |= 1 << GBA_KEY_UP;
241 }
242 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
243 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
244 keys |= 1 << GBA_KEY_DOWN;
245 }
246 int x = PAD_StickX(0);
247 int y = PAD_StickY(0);
248 if (x < -0x40) {
249 keys |= 1 << GBA_KEY_LEFT;
250 }
251 if (x > 0x40) {
252 keys |= 1 << GBA_KEY_RIGHT;
253 }
254 if (y < -0x40) {
255 keys |= 1 << GBA_KEY_DOWN;
256 }
257 if (y > 0x40) {
258 keys |= 1 << GBA_KEY_UP;
259 }
260 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) || (wiiPad & WPAD_CLASSIC_BUTTON_HOME)) {
261 break;
262 }
263 GBAContextFrame(&context, keys);
264 }
265
266 fclose(logfile);
267 free(fifo);
268
269 GBAContextStop(&context);
270 GBAContextDeinit(&context);
271
272 free(renderer.outputBuffer);
273 GUIFontDestroy(font);
274
275 return 0;
276}
277
278static void GBAWiiFrame(void) {
279 VIDEO_WaitVSync();
280
281 size_t x, y;
282 uint64_t* texdest = (uint64_t*) texmem;
283 uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
284 for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
285 for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
286 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
287 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
288 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
289 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
290 }
291 }
292 DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
293
294 _drawStart();
295
296 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
297 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
298 GX_InvalidateTexAll();
299 GX_LoadTexObj(&tex, GX_TEXMAP0);
300
301 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
302 GX_Position2s16(0, 256);
303 GX_TexCoord2s16(0, 1);
304
305 GX_Position2s16(256, 256);
306 GX_TexCoord2s16(1, 1);
307
308 GX_Position2s16(256, 0);
309 GX_TexCoord2s16(1, 0);
310
311 GX_Position2s16(0, 0);
312 GX_TexCoord2s16(0, 0);
313 GX_End();
314
315 _drawEnd();
316}
317
318bool GBAWiiLoadGame(const char* path) {
319 _drawStart();
320 GUIFontPrintf(font, 0, 30, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
321 _drawEnd();
322
323 return GBAContextLoadROM(&context, path, true);
324}
325
326void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
327 UNUSED(thread);
328 UNUSED(level);
329 vfprintf(logfile, format, args);
330 fprintf(logfile, "\n");
331 fflush(logfile);
332}
333
334static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
335 UNUSED(stream);
336 UNUSED(renderer);
337 GBAWiiFrame();
338}
339
340static void _audioDMA(void) {
341 if (!audioBufferSize) {
342 return;
343 }
344 DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
345 AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
346 currentAudioBuffer = (currentAudioBuffer + 1) % 3;
347 audioBufferSize = 0;
348}
349
350static void _drawStart(void) {
351 GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
352 GX_SetColorUpdate(GX_TRUE);
353
354 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
355}
356
357static void _drawEnd(void) {
358 GX_DrawDone();
359
360 whichFb = !whichFb;
361
362 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
363 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
364 VIDEO_Flush();
365}
366
367static int _pollInput(void) {
368 PAD_ScanPads();
369 u16 padkeys = PAD_ButtonsHeld(0);
370
371 WPAD_ScanPads();
372 u32 wiiPad = WPAD_ButtonsHeld(0);
373 u32 ext = 0;
374
375 int keys = 0;
376
377 WPAD_Probe(0, &ext);
378 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
379 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
380 keys |= 1 << GUI_INPUT_SELECT;
381 }
382 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
383 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
384 keys |= 1 << GUI_INPUT_BACK;
385 }
386 if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
387 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
388 keys |= 1 << GUI_INPUT_LEFT;
389 }
390 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
391 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
392 keys |= 1 << GUI_INPUT_RIGHT;
393 }
394 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
395 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
396 keys |= 1 << GUI_INPUT_UP;
397 }
398 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
399 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
400 keys |= 1 << GUI_INPUT_DOWN;
401 }
402 return keys;
403}