fca8cd722dc390f50f8c9306199f422f5bf595e6
[picodrive.git] / platform / psp / emu.c
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
40 int engineStateSuspend;
41
42 struct Vertex
43 {
44         short u,v;
45         short x,y,z;
46 };
47
48 static struct Vertex __attribute__((aligned(4))) g_vertices[2];
49 static u16 __attribute__((aligned(16))) localPal[0x100];
50 static int need_pal_upload = 0;
51
52 u16 __attribute__((aligned(16))) osd_buf[512*8]; // buffer for osd text
53 int osd_buf_x[4], osd_buf_l[4]; // store for x and length for blitting
54 int osd_buf_cnt, osd_cdleds;
55
56 static int out_x, out_y;
57 static int out_w, out_h;
58 static float hscale, vscale;
59
60 static 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
78 const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast", NULL };
79 const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };
80 enum 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
85 static int get_renderer(void)
86 {
87         if (PicoIn.AHW & PAHW_32X)
88                 return currentConfig.renderer32x;
89         else
90                 return currentConfig.renderer;
91 }
92
93 static 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
108 static 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
128 static 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
185 static 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
210 static 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
257 static 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
277 static 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
297 static 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
313 static 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
329 void 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
373 static 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
407 static 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
432 static short sndBuffer_emu[SOUND_BUFFER_CHUNK+4]; // 4 for sample rounding overhang
433 static short __attribute__((aligned(4))) sndBuffer[SOUND_BUFFER_CHUNK*SOUND_BLOCK_COUNT];
434 static short *sndBuffer_endptr;
435 static int samples_block;
436
437 static short *snd_playptr, *sndBuffer_ptr;
438 static int samples_made, samples_done;
439
440 static int sound_thread_exit = 0;
441 static 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
447 static 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
470 static void writeSound_44100_stereo(int len)
471 {
472         writeSound(len);
473         PicoIn.sndOut = sndBuffer_ptr;
474 }
475
476 static 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
486 static 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
498 static 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
510 static 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
522 static 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
534 static 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
569 static 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
592 void 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
650 void 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
669 static 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 */
678 void 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 */
692 void 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 */
703 void 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
736 void plat_debug_cat(char *str)
737 {
738 }
739
740 /* platform dependend emulator initialization */
741 void 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 */
752 void plat_finish(void)
753 {
754         sound_deinit();
755 }
756
757 /* display emulator status messages before holding emulation */
758 void plat_status_msg_busy_first(const char *msg)
759 {
760         plat_status_msg_busy_next(msg);
761 }
762
763 void 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
774 void plat_status_msg_busy_done(void)
775 {
776 }
777
778 /* clear status message area */
779 void 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 */
785 void plat_update_volume(int has_changed, int is_up)
786 {
787 }
788
789 /* prepare for MD screen mode change */
790 void 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 */
833 void 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 */
843 void 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 */
849 void 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 */
867 void 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 */
876 void plat_video_loop_prepare(void) 
877 {
878         apply_renderer();
879         vidResetMode();
880 }
881
882 /* prepare for entering the emulator loop */
883 void pemu_loop_prep(void)
884 {
885 }
886
887 /* terminate the emulator loop */
888 void pemu_loop_end(void)
889 {
890         pemu_sound_stop();
891 }
892
893 /* resume from suspend: change to main menu if emu was running */
894 void emu_handle_resume(void)
895 {
896         if (engineState == PGS_Running)
897                 engineState = PGS_Menu;
898 }
899