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