src/platform/psp2/psp2-context.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#include "psp2-context.h"
7
8#include "gba/gba.h"
9#include "gba/input.h"
10#include "gba/audio.h"
11#include "gba/context/context.h"
12
13#include "gba/renderers/video-software.h"
14#include "util/circle-buffer.h"
15#include "util/memory.h"
16#include "util/threading.h"
17#include "util/vfs.h"
18#include "platform/psp2/sce-vfs.h"
19#include "third-party/blip_buf/blip_buf.h"
20
21#include <psp2/audioout.h>
22#include <psp2/ctrl.h>
23#include <psp2/display.h>
24#include <psp2/gxm.h>
25#include <psp2/kernel/sysmem.h>
26#include <psp2/motion.h>
27#include <psp2/power.h>
28
29#include <vita2d.h>
30
31enum ScreenMode {
32 SM_BACKDROP,
33 SM_PLAIN,
34 SM_FULL,
35 SM_MAX
36};
37
38static struct GBAContext context;
39static struct GBAVideoSoftwareRenderer renderer;
40static vita2d_texture* tex;
41static Thread audioThread;
42static struct GBASceRotationSource {
43 struct GBARotationSource d;
44 struct SceMotionSensorState state;
45} rotation;
46
47static int screenMode = 0;
48
49extern const uint8_t _binary_backdrop_png_start[];
50static vita2d_texture* backdrop = 0;
51
52#define PSP2_INPUT 0x50535032
53#define PSP2_SAMPLES 64
54#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 19)
55
56static struct GBAPSP2AudioContext {
57 struct CircleBuffer buffer;
58 Mutex mutex;
59 Condition cond;
60 bool running;
61} audioContext;
62
63static void _mapVitaKey(struct GBAInputMap* map, int pspKey, enum GBAKey key) {
64 GBAInputBindKey(map, PSP2_INPUT, __builtin_ctz(pspKey), key);
65}
66
67static THREAD_ENTRY _audioThread(void* context) {
68 struct GBAPSP2AudioContext* audio = (struct GBAPSP2AudioContext*) context;
69 struct GBAStereoSample buffer[PSP2_SAMPLES];
70 int audioPort = sceAudioOutOpenPort(PSP2_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, PSP2_AUDIO_OUT_MODE_STEREO);
71 while (audio->running) {
72 memset(buffer, 0, sizeof(buffer));
73 MutexLock(&audio->mutex);
74 int len = CircleBufferSize(&audio->buffer);
75 len /= sizeof(buffer[0]);
76 if (len > PSP2_SAMPLES) {
77 len = PSP2_SAMPLES;
78 }
79 if (len > 0) {
80 len &= ~(PSP2_AUDIO_MIN_LEN - 1);
81 CircleBufferRead(&audio->buffer, buffer, len * sizeof(buffer[0]));
82 MutexUnlock(&audio->mutex);
83 sceAudioOutOutput(audioPort, buffer);
84 MutexLock(&audio->mutex);
85 }
86
87 if (CircleBufferSize(&audio->buffer) < PSP2_SAMPLES) {
88 ConditionWait(&audio->cond, &audio->mutex);
89 }
90 MutexUnlock(&audio->mutex);
91 }
92 sceAudioOutReleasePort(audioPort);
93 return 0;
94}
95
96static void _sampleRotation(struct GBARotationSource* source) {
97 struct GBASceRotationSource* rotation = (struct GBASceRotationSource*) source;
98 sceMotionGetSensorState(&rotation->state, 1);
99}
100
101static int32_t _readTiltX(struct GBARotationSource* source) {
102 struct GBASceRotationSource* rotation = (struct GBASceRotationSource*) source;
103 return rotation->state.accelerometer.x * 0x60000000;
104}
105
106static int32_t _readTiltY(struct GBARotationSource* source) {
107 struct GBASceRotationSource* rotation = (struct GBASceRotationSource*) source;
108 return rotation->state.accelerometer.y * 0x60000000;
109}
110
111static int32_t _readGyroZ(struct GBARotationSource* source) {
112 struct GBASceRotationSource* rotation = (struct GBASceRotationSource*) source;
113 return rotation->state.gyro.z * 0x10000000;
114}
115
116void GBAPSP2Setup() {
117 scePowerSetArmClockFrequency(80);
118 GBAContextInit(&context, 0);
119 struct GBAOptions opts = {
120 .useBios = true,
121 .logLevel = 0,
122 .idleOptimization = IDLE_LOOP_DETECT
123 };
124 GBAConfigLoadDefaults(&context.config, &opts);
125 _mapVitaKey(&context.inputMap, PSP2_CTRL_CROSS, GBA_KEY_A);
126 _mapVitaKey(&context.inputMap, PSP2_CTRL_CIRCLE, GBA_KEY_B);
127 _mapVitaKey(&context.inputMap, PSP2_CTRL_START, GBA_KEY_START);
128 _mapVitaKey(&context.inputMap, PSP2_CTRL_SELECT, GBA_KEY_SELECT);
129 _mapVitaKey(&context.inputMap, PSP2_CTRL_UP, GBA_KEY_UP);
130 _mapVitaKey(&context.inputMap, PSP2_CTRL_DOWN, GBA_KEY_DOWN);
131 _mapVitaKey(&context.inputMap, PSP2_CTRL_LEFT, GBA_KEY_LEFT);
132 _mapVitaKey(&context.inputMap, PSP2_CTRL_RIGHT, GBA_KEY_RIGHT);
133 _mapVitaKey(&context.inputMap, PSP2_CTRL_LTRIGGER, GBA_KEY_L);
134 _mapVitaKey(&context.inputMap, PSP2_CTRL_RTRIGGER, GBA_KEY_R);
135
136 struct GBAAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
137 GBAInputBindAxis(&context.inputMap, PSP2_INPUT, 0, &desc);
138 desc = (struct GBAAxis) { GBA_KEY_RIGHT, GBA_KEY_LEFT, 192, 64 };
139 GBAInputBindAxis(&context.inputMap, PSP2_INPUT, 1, &desc);
140
141 tex = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
142
143 GBAVideoSoftwareRendererCreate(&renderer);
144 renderer.outputBuffer = vita2d_texture_get_datap(tex);
145 renderer.outputBufferStride = 256;
146 context.renderer = &renderer.d;
147
148 rotation.d.sample = _sampleRotation;
149 rotation.d.readTiltX = _readTiltX;
150 rotation.d.readTiltY = _readTiltY;
151 rotation.d.readGyroZ = _readGyroZ;
152 context.gba->rotationSource = &rotation.d;
153
154 backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start);
155
156 printf("%s starting", projectName);
157}
158
159bool GBAPSP2LoadROM(const char* path) {
160 scePowerSetArmClockFrequency(444);
161 if (!GBAContextLoadROM(&context, path, true)) {
162 printf("%s failed to load!", path);
163 return false;
164 }
165 printf("%s loaded, starting...", path);
166 GBAContextStart(&context);
167 char gameTitle[13];
168 GBAGetGameTitle(context.gba, gameTitle);
169 printf("%s started!", gameTitle);
170 double ratio = GBAAudioCalculateRatio(1, 60, 1);
171 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
172 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
173
174 if (context.gba->memory.hw.devices & (HW_TILT | HW_GYRO)) {
175 sceMotionStartSampling();
176 }
177
178 CircleBufferInit(&audioContext.buffer, PSP2_AUDIO_BUFFER_SIZE * sizeof(struct GBAStereoSample));
179 MutexInit(&audioContext.mutex);
180 ConditionInit(&audioContext.cond);
181 audioContext.running = true;
182 ThreadCreate(&audioThread, _audioThread, &audioContext);
183 return true;
184}
185
186void GBAPSP2Runloop(void) {
187 int activeKeys = 0;
188
189 bool fsToggle = false;
190 while (true) {
191 SceCtrlData pad;
192 sceCtrlPeekBufferPositive(0, &pad, 1);
193 if (pad.buttons & PSP2_CTRL_TRIANGLE) {
194 break;
195 }
196 if (pad.buttons & PSP2_CTRL_SQUARE) {
197 if (!fsToggle) {
198 ++screenMode;
199 screenMode %= SM_MAX;
200 }
201 fsToggle = true;
202 } else {
203 fsToggle = false;
204 }
205
206 activeKeys = GBAInputMapKeyBits(&context.inputMap, PSP2_INPUT, pad.buttons, 0);
207 enum GBAKey angles = GBAInputMapAxis(&context.inputMap, PSP2_INPUT, 0, pad.ly);
208 if (angles != GBA_KEY_NONE) {
209 activeKeys |= 1 << angles;
210 }
211 angles = GBAInputMapAxis(&context.inputMap, PSP2_INPUT, 1, pad.lx);
212 if (angles != GBA_KEY_NONE) {
213 activeKeys |= 1 << angles;
214 }
215 angles = GBAInputMapAxis(&context.inputMap, PSP2_INPUT, 2, pad.ry);
216 if (angles != GBA_KEY_NONE) {
217 activeKeys |= 1 << angles;
218 }
219 angles = GBAInputMapAxis(&context.inputMap, PSP2_INPUT, 3, pad.rx);
220 if (angles != GBA_KEY_NONE) {
221 activeKeys |= 1 << angles;
222 }
223
224 GBAContextFrame(&context, activeKeys);
225
226 MutexLock(&audioContext.mutex);
227 while (blip_samples_avail(context.gba->audio.left) >= PSP2_SAMPLES) {
228 if (CircleBufferSize(&audioContext.buffer) + PSP2_SAMPLES * sizeof(struct GBAStereoSample) > CircleBufferCapacity(&audioContext.buffer)) {
229 break;
230 }
231 struct GBAStereoSample samples[PSP2_SAMPLES];
232 blip_read_samples(context.gba->audio.left, &samples[0].left, PSP2_SAMPLES, true);
233 blip_read_samples(context.gba->audio.right, &samples[0].right, PSP2_SAMPLES, true);
234 int i;
235 for (i = 0; i < PSP2_SAMPLES; ++i) {
236 CircleBufferWrite16(&audioContext.buffer, samples[i].left);
237 CircleBufferWrite16(&audioContext.buffer, samples[i].right);
238 }
239 }
240 ConditionWake(&audioContext.cond);
241 MutexUnlock(&audioContext.mutex);
242
243 vita2d_start_drawing();
244 vita2d_clear_screen();
245 GBAPSP2Draw(0xFF);
246 vita2d_end_drawing();
247 vita2d_swap_buffers();
248 }
249}
250
251void GBAPSP2UnloadROM(void) {
252 if (context.gba->memory.hw.devices & (HW_TILT | HW_GYRO)) {
253 sceMotionStopSampling();
254 }
255
256 GBAContextStop(&context);
257 scePowerSetArmClockFrequency(80);
258}
259
260void GBAPSP2Teardown(void) {
261 GBAContextDeinit(&context);
262 vita2d_free_texture(tex);
263 vita2d_free_texture(backdrop);
264}
265
266void GBAPSP2Draw(uint8_t alpha) {
267 switch (screenMode) {
268 case SM_BACKDROP:
269 vita2d_draw_texture_tint(backdrop, 0, 0, (alpha << 24) | 0xFFFFFF);
270 // Fall through
271 case SM_PLAIN:
272 vita2d_draw_texture_tint_part_scale(tex, 120, 32, 0, 0, 240, 160, 3.0f, 3.0f, (alpha << 24) | 0xFFFFFF);
273 break;
274 case SM_FULL:
275 vita2d_draw_texture_tint_scale(tex, 0, 0, 960.0f / 240.0f, 544.0f / 160.0f, (alpha << 24) | 0xFFFFFF);
276 break;
277 }
278}
279
280__attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) {
281 printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line);
282 exit(1);
283}