pandora: fix readme and pxml version
[picodrive.git] / platform / psp / emu.c
... / ...
CommitLineData
1/*
2 * PicoDrive PSP frontend
3 *
4 * (C) notaz, 2007,2008
5 * (C) irixxxx, 2022-2024
6 *
7 * This work is licensed under the terms of MAME license.
8 * See COPYING file in the top-level directory.
9 */
10
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <sys/syslimits.h> // PATH_MAX
14
15#include <pspthreadman.h>
16#include <pspdisplay.h>
17#include <psputils.h>
18#include <pspgu.h>
19#include <pspaudio.h>
20#include <pspsysmem.h>
21
22#include "psp.h"
23#include "emu.h"
24#include "mp3.h"
25#include "in_psp.h"
26#include "asm_utils.h"
27#include "../common/emu.h"
28#include "../common/input_pico.h"
29#include "../common/keyboard.h"
30#include "platform/libpicofe/input.h"
31#include "platform/libpicofe/menu.h"
32#include "platform/libpicofe/plat.h"
33
34#include <pico/pico_int.h>
35#include <pico/cd/genplus_macros.h>
36#include <pico/cd/cdd.h>
37
38#define OSD_FPS_X 432
39
40int engineStateSuspend;
41
42struct Vertex
43{
44 short u,v;
45 short x,y,z;
46};
47
48static struct Vertex __attribute__((aligned(4))) g_vertices[2];
49static u16 __attribute__((aligned(16))) localPal[0x100];
50static int need_pal_upload = 0;
51
52u16 __attribute__((aligned(16))) osd_buf[512*8]; // buffer for osd text
53int osd_buf_x[4], osd_buf_l[4]; // store for x and length for blitting
54int osd_buf_cnt, osd_cdleds;
55
56static int out_x, out_y;
57static int out_w, out_h;
58static float hscale, vscale;
59
60static struct in_default_bind in_psp_defbinds[] =
61{
62 { PSP_CTRL_UP, IN_BINDTYPE_PLAYER12, GBTN_UP },
63 { PSP_CTRL_DOWN, IN_BINDTYPE_PLAYER12, GBTN_DOWN },
64 { PSP_CTRL_LEFT, IN_BINDTYPE_PLAYER12, GBTN_LEFT },
65 { PSP_CTRL_RIGHT, IN_BINDTYPE_PLAYER12, GBTN_RIGHT },
66 { PSP_CTRL_SQUARE, IN_BINDTYPE_PLAYER12, GBTN_A },
67 { PSP_CTRL_CROSS, IN_BINDTYPE_PLAYER12, GBTN_B },
68 { PSP_CTRL_CIRCLE, IN_BINDTYPE_PLAYER12, GBTN_C },
69 { PSP_CTRL_START, IN_BINDTYPE_PLAYER12, GBTN_START },
70 { PSP_CTRL_TRIANGLE, IN_BINDTYPE_EMU, PEVB_SWITCH_RND },
71 { PSP_CTRL_LTRIGGER, IN_BINDTYPE_EMU, PEVB_STATE_SAVE },
72 { PSP_CTRL_RTRIGGER, IN_BINDTYPE_EMU, PEVB_STATE_LOAD },
73 { PSP_CTRL_SELECT, IN_BINDTYPE_EMU, PEVB_MENU },
74 { 0, 0, 0 }
75};
76
77
78const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast", NULL };
79const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };
80enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };
81
82#define is_16bit_mode() \
83 (currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))
84
85static int get_renderer(void)
86{
87 if (PicoIn.AHW & PAHW_32X)
88 return currentConfig.renderer32x;
89 else
90 return currentConfig.renderer;
91}
92
93static void change_renderer(int diff)
94{
95 int *r;
96 if (PicoIn.AHW & PAHW_32X)
97 r = &currentConfig.renderer32x;
98 else
99 r = &currentConfig.renderer;
100 *r += diff;
101
102 if (*r >= RT_COUNT)
103 *r = 0;
104 else if (*r < 0)
105 *r = RT_COUNT - 1;
106}
107
108static void apply_renderer(void)
109{
110 PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
111 PicoIn.opt |= POPT_DIS_32C_BORDER;
112
113 switch (get_renderer()) {
114 case RT_16BIT:
115 PicoDrawSetOutFormat(PDF_RGB555, 0);
116 break;
117 case RT_8BIT_ACC:
118 PicoDrawSetOutFormat(PDF_8BIT, 0);
119 break;
120 case RT_8BIT_FAST:
121 PicoIn.opt |= POPT_ALT_RENDERER;
122 PicoDrawSetOutFormat(PDF_NONE, 0);
123 break;
124 }
125}
126
127
128static void set_scaling_params(void)
129{
130 int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0;
131 g_vertices[0].z = g_vertices[1].z = 0;
132
133 fbimg_height = (int)(out_h * vscale + 0.5);
134 fbimg_width = (int)(out_w * hscale + 0.5);
135
136 if (fbimg_width & 1) fbimg_width++; // make even
137 if (fbimg_height & 1) fbimg_height++;
138
139 if (fbimg_width >= 480) {
140 g_vertices[0].u = out_x + (fbimg_width-480)/2;
141 g_vertices[1].u = out_x + out_w - (fbimg_width-480)/2 - 1;
142 fbimg_width = 480;
143 fbimg_xoffs = 0;
144 } else {
145 g_vertices[0].u = out_x;
146 g_vertices[1].u = out_x + out_w;
147 fbimg_xoffs = 240 - fbimg_width/2;
148 }
149 if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1;
150
151 if (fbimg_height >= 272) {
152 g_vertices[0].v = out_y + (fbimg_height-272)/2;
153 g_vertices[1].v = out_y + out_h - (fbimg_height-272)/2;
154 fbimg_height = 272;
155 fbimg_yoffs = 0;
156 } else {
157 g_vertices[0].v = out_y;
158 g_vertices[1].v = out_y + out_h;
159 fbimg_yoffs = 136 - fbimg_height/2;
160 }
161
162 if (fbimg_xoffs < 0) fbimg_xoffs = 0;
163 if (fbimg_yoffs < 0) fbimg_yoffs = 0;
164 g_vertices[0].x = fbimg_xoffs;
165 g_vertices[0].y = fbimg_yoffs;
166 g_vertices[1].x = fbimg_xoffs + fbimg_width;
167 g_vertices[1].y = fbimg_yoffs + fbimg_height;
168 if (!is_16bit_mode()) {
169 // 8-bit modes have an 8 px overlap area on the left
170 g_vertices[0].u += 8; g_vertices[1].u += 8;
171 }
172 if (border_hack) {
173 g_vertices[0].u++; g_vertices[1].u--;
174 g_vertices[0].x++; g_vertices[1].x--;
175 }
176
177 /*
178 lprintf("set_scaling_params:\n");
179 lprintf("offs: %i, %i\n", fbimg_xoffs, fbimg_yoffs);
180 lprintf("xy0, xy1: %i, %i; %i, %i\n", g_vertices[0].x, g_vertices[0].y, g_vertices[1].x, g_vertices[1].y);
181 lprintf("uv0, uv1: %i, %i; %i, %i\n", g_vertices[0].u, g_vertices[0].v, g_vertices[1].u, g_vertices[1].v);
182 */
183}
184
185static void do_pal_update_sms(void)
186{
187 static u16 tmspal[32] = {
188 // SMS palette
189 0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
190 0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
191 // TMS palette
192 0x0000, 0x0000, 0x04c2, 0x07d6, 0x0e55, 0x0f77, 0x055c, 0x0ee4,
193 0x055f, 0x077f, 0x05bc, 0x08ce, 0x03a2, 0x0b5c, 0x0ccc, 0x0fff,
194 };
195 int i;
196
197 if (!(Pico.video.reg[0] & 0x4)) {
198 int sg = !!(PicoIn.AHW & (PAHW_SG|PAHW_SC));
199 for (i = Pico.est.SonicPalCount; i >= 0; i--)
200 do_pal_convert(localPal+i*0x40, tmspal+sg*0x10, currentConfig.gamma, currentConfig.gamma2);
201 } else {
202 for (i = Pico.est.SonicPalCount; i >= 0; i--)
203 do_pal_convert(localPal+i*0x40, Pico.est.SonicPal+i*0x40, currentConfig.gamma, currentConfig.gamma2);
204 }
205 if (Pico.m.dirtyPal == 2)
206 Pico.m.dirtyPal = 0;
207 need_pal_upload = 1;
208}
209
210static void do_pal_update(void)
211{
212 u32 *dpal=(void *)localPal;
213 int i;
214
215 if (PicoIn.opt & POPT_ALT_RENDERER) {
216 do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2);
217 Pico.m.dirtyPal = 0;
218 }
219 else if (Pico.est.rendstatus & PDRAW_SONIC_MODE)
220 {
221 switch (Pico.est.SonicPalCount) {
222 case 3: do_pal_convert(localPal+0xc0, Pico.est.SonicPal+0xc0, currentConfig.gamma, currentConfig.gamma2);
223 case 2: do_pal_convert(localPal+0x80, Pico.est.SonicPal+0x80, currentConfig.gamma, currentConfig.gamma2);
224 case 1: do_pal_convert(localPal+0x40, Pico.est.SonicPal+0x40, currentConfig.gamma, currentConfig.gamma2);
225 default:do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
226 }
227 }
228 else if (Pico.video.reg[0xC]&8) // shadow/hilight?
229 {
230 do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
231 // shadowed pixels
232 for (i = 0; i < 0x40 / 2; i++) {
233 dpal[0xc0/2 + i] = dpal[i];
234 dpal[0x80/2 + i] = (dpal[i] >> 1) & 0x738e738e;
235 }
236 // hilighted pixels
237 for (i = 0; i < 0x40 / 2; i++) {
238 u32 t = ((dpal[i] >> 1) & 0x738e738e) + 0x738e738e;
239 t |= (t >> 4) & 0x08610861;
240 dpal[0x40/2 + i] = t;
241 }
242 }
243 else
244 {
245 do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
246 memcpy((int *)dpal+0x40/2, (void *)localPal, 0x40*2);
247 memcpy((int *)dpal+0x80/2, (void *)localPal, 0x80*2);
248 }
249 localPal[0xe0] = 0;
250 localPal[0xf0] = 0x001f;
251
252 if (Pico.m.dirtyPal == 2)
253 Pico.m.dirtyPal = 0;
254 need_pal_upload = 1;
255}
256
257static void osd_text(int x, const char *text)
258{
259 int len = strlen(text) * 8;
260 int *p, h;
261 void *tmp = g_screen_ptr;
262
263 g_screen_ptr = osd_buf;
264 for (h = 0; h < 8; h++) {
265 p = (int *) (osd_buf+x+512*h);
266 p = (int *) ((int)p & ~3); // align
267 memset32_uncached(p, 0, len/2);
268 }
269 emu_text_out16(x, 0, text);
270 g_screen_ptr = tmp;
271
272 osd_buf_x[osd_buf_cnt] = x;
273 osd_buf_l[osd_buf_cnt] = len;
274 osd_buf_cnt ++;
275}
276
277static void blit_osd(void)
278{
279 struct Vertex* vx;
280 int x, len;
281
282 while (osd_buf_cnt > 0) {
283 osd_buf_cnt --;
284 x = osd_buf_x[osd_buf_cnt];
285 len = osd_buf_l[osd_buf_cnt];
286 vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
287 vx[0].u = x, vx[0].v = 0;
288 vx[1].u = x + len, vx[1].v = 8;
289 vx[0].x = x, vx[0].y = 264;
290 vx[1].x = x + len, vx[1].y = 272;
291 sceGuTexMode(GU_PSM_5650,0,0,0);
292 sceGuTexImage(0,512,8,512,osd_buf);
293 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx);
294 }
295}
296
297static void cd_leds(void)
298{
299 unsigned int reg, col_g, col_r, *p;
300
301 reg = Pico_mcd->s68k_regs[0];
302
303 p = (unsigned int *)((short *)osd_buf + 512*2+498);
304 col_g = (reg & 2) ? 0x06000600 : 0;
305 col_r = (reg & 1) ? 0x00180018 : 0;
306 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
307 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
308 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;
309
310 osd_cdleds = 1;
311}
312
313static void blit_cdleds(void)
314{
315 struct Vertex* vx;
316
317 if (!osd_cdleds) return;
318
319 vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
320 vx[0].u = 497, vx[0].v = 1;
321 vx[1].u = 497+14, vx[1].v = 6;
322 vx[0].x = 4, vx[0].y = 1;
323 vx[1].x = 4+14, vx[1].y = 6;
324 sceGuTexMode(GU_PSM_5650,0,0,0);
325 sceGuTexImage(0,512,8,512,osd_buf);
326 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx);
327}
328
329void blitscreen_clut(void)
330{
331 sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0);
332 sceGuTexImage(0,512,512,512,g_screen_ptr);
333
334 if (!is_16bit_mode() && Pico.m.dirtyPal) {
335 if (PicoIn.AHW & PAHW_SMS)
336 do_pal_update_sms();
337 else
338 do_pal_update();
339 }
340
341 if (need_pal_upload) {
342 need_pal_upload = 0;
343 sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
344 }
345
346#if 1
347 if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x)
348 {
349 struct Vertex* vertices;
350 int x;
351
352 #define SLICE_WIDTH 32
353 for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH)
354 {
355 // render sprite
356 vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
357 memcpy(vertices, g_vertices, 2 * sizeof(struct Vertex));
358 vertices[0].u = vertices[0].x = x;
359 vertices[1].u = vertices[1].x = x + SLICE_WIDTH;
360 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices);
361 }
362 // lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only
363 }
364 else
365#endif
366 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices);
367
368 blit_osd();
369 blit_cdleds();
370}
371
372
373static void draw_pico_ptr(void)
374{
375 int up = (PicoPicohw.pen_pos[0]|PicoPicohw.pen_pos[1]) & 0x8000;
376 int x = pico_pen_x, y = pico_pen_y, offs;
377 int pitch = g_screen_ppitch;
378 // storyware pages are actually squished, 2:1
379 int h = (pico_inp_mode == 1 ? 160 : out_h);
380 if (h < 224) y++;
381
382 x = (x * out_w * ((1ULL<<32) / 320 + 1)) >> 32;
383 y = (y * h * ((1ULL<<32) / 224 + 1)) >> 32;
384
385 offs = pitch * (out_y+y) + (out_x+x);
386
387 if (is_16bit_mode()) {
388 unsigned short *p = (unsigned short *)g_screen_ptr + offs;
389 int o = (up ? 0x0000 : 0xffff), _ = (up ? 0xffff : 0x0000);
390
391 p[-pitch-1] ^= o; p[-pitch] ^= _; p[-pitch+1] ^= _; p[-pitch+2] ^= o;
392 p[-1] ^= _; p[0] ^= o; p[1] ^= o; p[2] ^= _;
393 p[pitch-1] ^= _; p[pitch] ^= o; p[pitch+1] ^= o; p[pitch+2] ^= _;
394 p[2*pitch-1]^= o; p[2*pitch]^= _; p[2*pitch+1]^= _; p[2*pitch+2]^= o;
395 } else {
396 unsigned char *p = (unsigned char *)g_screen_ptr + offs + 8;
397 int o = (up ? 0xe0 : 0xf0), _ = (up ? 0xf0 : 0xe0);
398
399 p[-pitch-1] = o; p[-pitch] = _; p[-pitch+1] = _; p[-pitch+2] = o;
400 p[-1] = _; p[0] = o; p[1] = o; p[2] = _;
401 p[pitch-1] = _; p[pitch] = o; p[pitch+1] = o; p[pitch+2] = _;
402 p[2*pitch-1]= o; p[2*pitch]= _; p[2*pitch+1]= _; p[2*pitch+2]= o;
403 }
404}
405
406
407static void vidResetMode(void)
408{
409 // setup GU
410 sceGuSync(0,0); // sync with prev
411 sceGuStart(GU_DIRECT, guCmdList);
412
413 sceGuClutMode(GU_PSM_5650,0,0xff,0);
414 sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
415 if (currentConfig.filter)
416 sceGuTexFilter(GU_LINEAR, GU_LINEAR);
417 else sceGuTexFilter(GU_NEAREST, GU_NEAREST);
418 sceGuTexScale(1.0f,1.0f);
419 sceGuTexOffset(0.0f,0.0f);
420
421 Pico.m.dirtyPal = 1;
422
423 sceGuFinish();
424 set_scaling_params();
425 sceGuSync(0,0);
426}
427
428/* sound stuff */
429#define SOUND_BLOCK_COUNT 7
430#define SOUND_BUFFER_CHUNK (2*44100/50) // max.rate/min.frames in stereo
431
432static short sndBuffer_emu[SOUND_BUFFER_CHUNK+4]; // 4 for sample rounding overhang
433static short __attribute__((aligned(4))) sndBuffer[SOUND_BUFFER_CHUNK*SOUND_BLOCK_COUNT];
434static short *sndBuffer_endptr;
435static int samples_block;
436
437static short *snd_playptr, *sndBuffer_ptr;
438static int samples_made, samples_done;
439
440static int sound_thread_exit = 0;
441static SceUID sound_sem = -1;
442
443// There are problems if the sample rate used with the PSP isn't 44100 Hz stereo.
444// Hence, use only 11025,22050,44100 here and handle duplication and upsampling.
445// Upsample by nearest neighbour, which is the fastest but may create artifacts.
446
447static void writeSound(int len)
448{
449 // make sure there is enough free space in the buffer after
450 // this frame, else the next frame may overwrite old stored samples.
451 if (samples_made - samples_done < samples_block * (SOUND_BLOCK_COUNT-2) - 8) {
452 sndBuffer_ptr += len / 2;
453 if (sndBuffer_ptr - sndBuffer > sizeof(sndBuffer)/2)
454 lprintf("snd ovrn %d %d\n", len, sndBuffer_ptr - sndBuffer);
455 if (sndBuffer_ptr >= sndBuffer_endptr) {
456 int wrap = sndBuffer_ptr - sndBuffer_endptr;
457 if (wrap > 0)
458 memcpy(sndBuffer, sndBuffer_endptr, 2*wrap);
459 sndBuffer_ptr -= sndBuffer_endptr - sndBuffer;
460 }
461
462 samples_made += len / 2;
463 } else
464 lprintf("snd oflow %i!\n", samples_made - samples_done);
465
466 // signal the snd thread
467 sceKernelSignalSema(sound_sem, 1);
468}
469
470static void writeSound_44100_stereo(int len)
471{
472 writeSound(len);
473 PicoIn.sndOut = sndBuffer_ptr;
474}
475
476static void writeSound_44100_mono(int len)
477{
478 short *p = sndBuffer_ptr;
479 int i;
480
481 for (i = 0; i < len / 2; i++, p+=2)
482 p[0] = p[1] = PicoIn.sndOut[i];
483 writeSound(2*len);
484}
485
486static void writeSound_22050_stereo(int len)
487{
488 short *p = sndBuffer_ptr;
489 int i;
490
491 for (i = 0; i < len / 2; i+=2, p+=4) {
492 p[0] = p[2] = PicoIn.sndOut[i];
493 p[1] = p[3] = PicoIn.sndOut[i+1];
494 }
495 writeSound(2*len);
496}
497
498static void writeSound_22050_mono(int len)
499{
500 short *p = sndBuffer_ptr;
501 int i;
502
503 for (i = 0; i < len / 2; i++, p+=4) {
504 p[0] = p[2] = PicoIn.sndOut[i];
505 p[1] = p[3] = PicoIn.sndOut[i];
506 }
507 writeSound(4*len);
508}
509
510static void writeSound_11025_stereo(int len)
511{
512 short *p = sndBuffer_ptr;
513 int i;
514
515 for (i = 0; i < len / 2; i+=2, p+=8) {
516 p[0] = p[2] = p[4] = p[6] = PicoIn.sndOut[i];
517 p[1] = p[3] = p[5] = p[7] = PicoIn.sndOut[i+1];
518 }
519 writeSound(4*len);
520}
521
522static void writeSound_11025_mono(int len)
523{
524 short *p = sndBuffer_ptr;
525 int i;
526
527 for (i = 0; i < len / 2; i++, p+=8) {
528 p[0] = p[2] = p[4] = p[6] = PicoIn.sndOut[i];
529 p[1] = p[3] = p[5] = p[7] = PicoIn.sndOut[i];
530 }
531 writeSound(8*len);
532}
533
534static int sound_thread(SceSize args, void *argp)
535{
536 lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority());
537
538 while (!sound_thread_exit)
539 {
540 int ret;
541
542 if (samples_made - samples_done < samples_block) {
543 // wait for data (use at least 2 blocks)
544 //lprintf("sthr: wait... (%i)\n", samples_made - samples_done);
545 while (samples_made - samples_done < samples_block*2 && !sound_thread_exit) {
546 ret = sceKernelWaitSema(sound_sem, 1, 0);
547 if (ret < 0) lprintf("sthr: sceKernelWaitSema: %i\n", ret);
548 }
549 }
550
551 // if the sample buffer runs low, push some extra
552 if (sceAudioOutput2GetRestSample()*2 < samples_block/4)
553 ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr);
554 ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr);
555 // 1.5 kernel returns 0, newer ones return # of samples queued
556 if (ret < 0) lprintf("sthr: play: ret %08x; pos %i/%i\n", ret, samples_done, samples_made);
557
558 samples_done += samples_block;
559 snd_playptr += samples_block;
560 if (snd_playptr >= sndBuffer_endptr)
561 snd_playptr -= sndBuffer_endptr - sndBuffer;
562 }
563
564 lprintf("sthr: exit\n");
565 sceKernelExitDeleteThread(0);
566 return 0;
567}
568
569static void sound_init(void)
570{
571 SceUID thid;
572 int ret;
573
574 sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL);
575 if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem);
576
577 samples_made = samples_done = 0;
578 samples_block = 2*22050/60; // make sure it goes to sema
579 sound_thread_exit = 0;
580 thid = sceKernelCreateThread("sndthread", sound_thread, 0x12, 0x1000, 0, NULL);
581 if (thid >= 0)
582 {
583 ret = sceKernelStartThread(thid, 0, 0);
584 if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret);
585 }
586 else
587 lprintf("sceKernelCreateThread failed: %i\n", thid);
588}
589
590#define PSP_RATE 44100 // PicoIn.sndRate
591
592void pemu_sound_start(void)
593{
594 static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
595 static int mp3_init_done;
596 int ret, stereo, factor;
597
598 samples_made = samples_done = 0;
599
600 if (!(currentConfig.EmuOpt & EOPT_EN_SOUND))
601 return;
602
603 if (PicoIn.AHW & PAHW_MCD) {
604 // mp3...
605 if (!mp3_init_done) {
606 ret = mp3_init();
607 mp3_init_done = 1;
608 if (ret) emu_status_msg("mp3 init failed (%i)", ret);
609 }
610 }
611
612 ret = POPT_EN_FM|POPT_EN_PSG|POPT_EN_STEREO;
613 if (PicoIn.sndRate != PsndRate_old || (PicoIn.opt&ret) != (PicoOpt_old&ret) || Pico.m.pal != pal_old) {
614 PsndRerate(Pico.m.frame_count ? 1 : 0);
615 }
616 stereo = (PicoIn.opt&8)>>3;
617
618 // PSP doesn't support mono in SRC, always use stereo and convert
619 factor = PSP_RATE / PicoIn.sndRate;
620 samples_block = (PSP_RATE / (Pico.m.pal ? 50 : 60)) * 2;
621
622 lprintf("starting audio: %i, len: %i, stereo: %i, pal: %i, block samples: %i\n",
623 PicoIn.sndRate, Pico.snd.len, stereo, Pico.m.pal, samples_block);
624
625 ret = sceAudioSRCChReserve(samples_block/2, PSP_RATE, 2); // seems to not need that stupid 64byte alignment
626 if (ret < 0) {
627 lprintf("sceAudioSRCChReserve() failed: %i\n", ret);
628 emu_status_msg("sound init failed (%i), snd disabled", ret);
629 currentConfig.EmuOpt &= ~EOPT_EN_SOUND;
630 } else {
631 switch (factor) {
632 case 1: PicoIn.writeSound = stereo ? writeSound_44100_stereo:writeSound_44100_mono; break;
633 case 2: PicoIn.writeSound = stereo ? writeSound_22050_stereo:writeSound_22050_mono; break;
634 case 4: PicoIn.writeSound = stereo ? writeSound_11025_stereo:writeSound_11025_mono; break;
635 }
636 sndBuffer_endptr = sndBuffer + (SOUND_BLOCK_COUNT-1)*samples_block;
637 snd_playptr = sndBuffer_ptr = sndBuffer;
638 PicoIn.sndOut = (factor == 1 && stereo ? sndBuffer_ptr : sndBuffer_emu);
639
640 // push one audio block to cover time to first frame audio
641// memset32(PicoIn.sndOut, 0, samples_block/2);
642// writeSound(samples_block*2);
643
644 PsndRate_old = PicoIn.sndRate;
645 PicoOpt_old = PicoIn.opt;
646 pal_old = Pico.m.pal;
647 }
648}
649
650void pemu_sound_stop(void)
651{
652 int i;
653 if (samples_done == 0)
654 {
655 // if no data is written between sceAudioSRCChReserve and sceAudioSRCChRelease calls,
656 // we get a deadlock on next sceAudioSRCChReserve call
657 // so this is yet another workaround:
658 memset32((int *)(void *)sndBuffer, 0, samples_block*4/4);
659 samples_made = samples_block * 3;
660 sceKernelSignalSema(sound_sem, 1);
661 }
662 sceKernelDelayThread(100*1000);
663 samples_made = samples_done = 0;
664 for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++)
665 psp_msleep(100);
666 sceAudioSRCChRelease();
667}
668
669static void sound_deinit(void)
670{
671 sound_thread_exit = 1;
672 sceKernelSignalSema(sound_sem, 1);
673 sceKernelDeleteSema(sound_sem);
674 sound_sem = -1;
675}
676
677/* set default configuration values */
678void pemu_prep_defconfig(void)
679{
680 defaultConfig.s_PsndRate = 22050;
681 defaultConfig.s_PicoCDBuffers = 64;
682 defaultConfig.CPUclock = 333;
683 defaultConfig.filter = EOPT_FILTER_BILINEAR; // bilinear filtering
684 defaultConfig.scaling = EOPT_SCALE_43;
685 defaultConfig.vscaling = EOPT_VSCALE_FULL;
686 defaultConfig.renderer = RT_8BIT_ACC;
687 defaultConfig.renderer32x = RT_8BIT_FAST;
688 defaultConfig.EmuOpt |= EOPT_SHOW_RTC;
689}
690
691/* check configuration for inconsistencies */
692void pemu_validate_config(void)
693{
694 if (currentConfig.CPUclock < 33 || currentConfig.CPUclock > 333)
695 currentConfig.CPUclock = 333;
696 if (currentConfig.gamma < -4 || currentConfig.gamma > 16)
697 currentConfig.gamma = 0;
698 if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2)
699 currentConfig.gamma2 = 0;
700}
701
702/* finalize rendering a frame */
703void pemu_finalize_frame(const char *fps, const char *notice)
704{
705 int emu_opt = currentConfig.EmuOpt;
706
707 if (PicoIn.AHW & PAHW_PICO) {
708 int h = out_h, w = out_w;
709 u16 *pd = g_screen_ptr + out_y*g_screen_ppitch + out_x;
710
711 if (pico_inp_mode && is_16bit_mode())
712 emu_pico_overlay(pd, w, h, g_screen_ppitch);
713 if (pico_inp_mode /*== 2 || overlay*/)
714 draw_pico_ptr();
715 }
716
717 // draw virtual keyboard on display
718 if (kbd_mode && currentConfig.keyboard == 1 && vkbd)
719 vkbd_draw(vkbd);
720
721 osd_buf_cnt = 0;
722 if (notice)
723 osd_text(4, notice);
724 if (emu_opt & EOPT_SHOW_FPS)
725 osd_text(OSD_FPS_X, fps);
726
727 osd_cdleds = 0;
728 if ((emu_opt & EOPT_EN_CD_LEDS) && (PicoIn.AHW & PAHW_MCD))
729 cd_leds();
730
731 sceKernelDcacheWritebackAll();
732}
733
734/* FIXME: move plat_* to plat? */
735
736void plat_debug_cat(char *str)
737{
738}
739
740/* platform dependend emulator initialization */
741void plat_init(void)
742{
743 flip_after_sync = 1;
744 psp_menu_init();
745 in_psp_init(in_psp_defbinds);
746 in_probe();
747 sound_init();
748 plat_get_data_dir(rom_fname_loaded, sizeof(rom_fname_loaded));
749}
750
751/* platform dependend emulator deinitialization */
752void plat_finish(void)
753{
754 sound_deinit();
755}
756
757/* display emulator status messages before holding emulation */
758void plat_status_msg_busy_first(const char *msg)
759{
760 plat_status_msg_busy_next(msg);
761}
762
763void plat_status_msg_busy_next(const char *msg)
764{
765 plat_status_msg_clear();
766 pemu_finalize_frame("", msg);
767 // flip twice since our GU pipeline has one frame delay
768 plat_video_flip();
769 plat_video_flip();
770 emu_status_msg("");
771 reset_timing = 1;
772}
773
774void plat_status_msg_busy_done(void)
775{
776}
777
778/* clear status message area */
779void plat_status_msg_clear(void)
780{
781 // not needed since the screen buf is cleared through the GU
782}
783
784/* change the audio volume setting */
785void plat_update_volume(int has_changed, int is_up)
786{
787}
788
789/* prepare for MD screen mode change */
790void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
791{
792 int h43 = (col_count >= 192 ? 320 : col_count); // ugh, mind GG...
793 int v43 = (line_count >= 192 ? Pico.m.pal ? 240 : 224 : line_count);
794
795 out_y = start_line; out_x = start_col;
796 out_h = line_count; out_w = col_count;
797
798 if (col_count == 248) // mind aspect ratio when blanking 1st column
799 col_count = 256;
800
801 switch (currentConfig.vscaling) {
802 case EOPT_VSCALE_FULL:
803 line_count = v43;
804 vscale = (float)270/line_count;
805 break;
806 case EOPT_VSCALE_NOBORDER:
807 vscale = (float)270/line_count;
808 break;
809 default:
810 vscale = 1;
811 break;
812 }
813
814 switch (currentConfig.scaling) {
815 case EOPT_SCALE_43:
816 hscale = (vscale*h43)/col_count;
817 break;
818 case EOPT_SCALE_STRETCH:
819 hscale = (vscale*h43/2 + 480/2)/col_count;
820 break;
821 case EOPT_SCALE_WIDE:
822 hscale = (float)480/col_count;
823 break;
824 default:
825 hscale = vscale;
826 break;
827 }
828
829 vidResetMode();
830}
831
832/* render one frame in RGB */
833void pemu_forced_frame(int no_scale, int do_emu)
834{
835 Pico.m.dirtyPal = 1;
836
837 if (!no_scale)
838 no_scale = currentConfig.scaling == EOPT_SCALE_NONE;
839 emu_cmn_forced_frame(no_scale, do_emu, g_screen_ptr);
840}
841
842/* clear all video buffers to remove remnants in borders */
843void plat_video_clear_buffers(void)
844{
845 // not needed since output buffer is cleared in flip anyway
846}
847
848/* change the platform output rendering */
849void plat_video_toggle_renderer(int change, int is_menu_call)
850{
851 change_renderer(change);
852
853 if (is_menu_call)
854 return;
855
856 apply_renderer();
857 vidResetMode();
858 rendstatus_old = -1;
859
860 if (PicoIn.AHW & PAHW_32X)
861 emu_status_msg(renderer_names32x[get_renderer()]);
862 else
863 emu_status_msg(renderer_names[get_renderer()]);
864}
865
866/* set the buffer for emulator output rendering */
867void plat_video_set_buffer(void *buf)
868{
869 if (is_16bit_mode())
870 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
871 else
872 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch);
873}
874
875/* prepare for emulator output rendering */
876void plat_video_loop_prepare(void)
877{
878 apply_renderer();
879 vidResetMode();
880}
881
882/* prepare for entering the emulator loop */
883void pemu_loop_prep(void)
884{
885}
886
887/* terminate the emulator loop */
888void pemu_loop_end(void)
889{
890 pemu_sound_stop();
891}
892
893/* resume from suspend: change to main menu if emu was running */
894void emu_handle_resume(void)
895{
896 if (engineState == PGS_Running)
897 engineState = PGS_Menu;
898}
899