pandora: fix readme and pxml version
[picodrive.git] / platform / psp / emu.c
CommitLineData
cff531af 1/*
7bf552b5 2 * PicoDrive PSP frontend
3 *
cff531af 4 * (C) notaz, 2007,2008
089f516d 5 * (C) irixxxx, 2022-2024
cff531af 6 *
7 * This work is licensed under the terms of MAME license.
8 * See COPYING file in the top-level directory.
9 */
8b99ab90 10
1820b5a7 11#include <sys/stat.h>
12#include <sys/types.h>
13#include <sys/syslimits.h> // PATH_MAX
14
7d4906bf 15#include <pspthreadman.h>
70357ce5 16#include <pspdisplay.h>
9112b6ce 17#include <psputils.h>
18#include <pspgu.h>
a4221917 19#include <pspaudio.h>
48fc9762 20#include <pspsysmem.h>
7d4906bf 21
22#include "psp.h"
7d4906bf 23#include "emu.h"
b542be46 24#include "mp3.h"
cdc6aac4 25#include "in_psp.h"
2445b7cb 26#include "asm_utils.h"
7d4906bf 27#include "../common/emu.h"
cdc6aac4 28#include "../common/input_pico.h"
bac44b18 29#include "../common/keyboard.h"
cdc6aac4 30#include "platform/libpicofe/input.h"
31#include "platform/libpicofe/menu.h"
1cc77481 32#include "platform/libpicofe/plat.h"
cdc6aac4 33
efcba75f 34#include <pico/pico_int.h>
cdc6aac4 35#include <pico/cd/genplus_macros.h>
36#include <pico/cd/cdd.h>
1820b5a7 37
9dc09829 38#define OSD_FPS_X 432
7d4906bf 39
713c9224 40int engineStateSuspend;
1820b5a7 41
cdc6aac4 42struct Vertex
43{
44 short u,v;
45 short x,y,z;
46};
47
48static struct Vertex __attribute__((aligned(4))) g_vertices[2];
b1b5f9c0 49static u16 __attribute__((aligned(16))) localPal[0x100];
cdc6aac4 50static int need_pal_upload = 0;
1cc77481 51
48fc9762 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;
b1b5f9c0 55
1cc77481 56static int out_x, out_y;
57static int out_w, out_h;
0924243a 58static float hscale, vscale;
cdc6aac4 59
bfd66623 60static struct in_default_bind in_psp_defbinds[] =
cdc6aac4 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
1820b5a7 77
cdc6aac4 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() \
1cc77481 83 (currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))
cdc6aac4 84
85static int get_renderer(void)
1820b5a7 86{
cdc6aac4 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{
44e4bf2b 110 PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
48fc9762 111 PicoIn.opt |= POPT_DIS_32C_BORDER;
cdc6aac4 112
1cc77481 113 switch (get_renderer()) {
cdc6aac4 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 }
1820b5a7 125}
126
7d4906bf 127
8ab3e3c1 128static void set_scaling_params(void)
9112b6ce 129{
1cc77481 130 int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0;
8ab3e3c1 131 g_vertices[0].z = g_vertices[1].z = 0;
132
0924243a 133 fbimg_height = (int)(out_h * vscale + 0.5);
134 fbimg_width = (int)(out_w * hscale + 0.5);
9112b6ce 135
b542be46 136 if (fbimg_width & 1) fbimg_width++; // make even
137 if (fbimg_height & 1) fbimg_height++;
138
8ab3e3c1 139 if (fbimg_width >= 480) {
1cc77481 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;
8ab3e3c1 142 fbimg_width = 480;
143 fbimg_xoffs = 0;
144 } else {
1cc77481 145 g_vertices[0].u = out_x;
146 g_vertices[1].u = out_x + out_w;
8ab3e3c1 147 fbimg_xoffs = 240 - fbimg_width/2;
148 }
c6196c0f 149 if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1;
8ab3e3c1 150
151 if (fbimg_height >= 272) {
1cc77481 152 g_vertices[0].v = out_y + (fbimg_height-272)/2;
153 g_vertices[1].v = out_y + out_h - (fbimg_height-272)/2;
8ab3e3c1 154 fbimg_height = 272;
155 fbimg_yoffs = 0;
156 } else {
1cc77481 157 g_vertices[0].v = out_y;
158 g_vertices[1].v = out_y + out_h;
8ab3e3c1 159 fbimg_yoffs = 136 - fbimg_height/2;
160 }
161
8ab3e3c1 162 if (fbimg_xoffs < 0) fbimg_xoffs = 0;
163 if (fbimg_yoffs < 0) fbimg_yoffs = 0;
1cc77481 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;
cdc6aac4 168 if (!is_16bit_mode()) {
169 // 8-bit modes have an 8 px overlap area on the left
6370b1d4 170 g_vertices[0].u += 8; g_vertices[1].u += 8;
1cc77481 171 }
172 if (border_hack) {
173 g_vertices[0].u++; g_vertices[1].u--;
174 g_vertices[0].x++; g_vertices[1].x--;
cdc6aac4 175 }
8ab3e3c1 176
80db4442 177 /*
8ab3e3c1 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);
80db4442 182 */
9112b6ce 183}
184
ace18401 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,
96948bdf 191 // TMS palette
192 0x0000, 0x0000, 0x04c2, 0x07d6, 0x0e55, 0x0f77, 0x055c, 0x0ee4,
193 0x055f, 0x077f, 0x05bc, 0x08ce, 0x03a2, 0x0b5c, 0x0ccc, 0x0fff,
ace18401 194 };
195 int i;
196
197 if (!(Pico.video.reg[0] & 0x4)) {
0aa63fce 198 int sg = !!(PicoIn.AHW & (PAHW_SG|PAHW_SC));
ace18401 199 for (i = Pico.est.SonicPalCount; i >= 0; i--)
96948bdf 200 do_pal_convert(localPal+i*0x40, tmspal+sg*0x10, currentConfig.gamma, currentConfig.gamma2);
ace18401 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
cdc6aac4 210static void do_pal_update(void)
7d4906bf 211{
1cc77481 212 u32 *dpal=(void *)localPal;
8ab3e3c1 213 int i;
9112b6ce 214
ace18401 215 if (PicoIn.opt & POPT_ALT_RENDERER) {
cdc6aac4 216 do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2);
b1a047c9 217 Pico.m.dirtyPal = 0;
218 }
cdc6aac4 219 else if (Pico.est.rendstatus & PDRAW_SONIC_MODE)
b1a047c9 220 {
221 switch (Pico.est.SonicPalCount) {
ace18401 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);
b1a047c9 225 default:do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
226 }
227 }
cdc6aac4 228 else if (Pico.video.reg[0xC]&8) // shadow/hilight?
8ab3e3c1 229 {
b1a047c9 230 do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
8ab3e3c1 231 // shadowed pixels
cdc6aac4 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 }
8ab3e3c1 236 // hilighted pixels
cdc6aac4 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;
8ab3e3c1 241 }
9112b6ce 242 }
cdc6aac4 243 else
b1a047c9 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 }
cdc6aac4 249 localPal[0xe0] = 0;
250 localPal[0xf0] = 0x001f;
b1a047c9 251
252 if (Pico.m.dirtyPal == 2)
253 Pico.m.dirtyPal = 0;
254 need_pal_upload = 1;
8ab3e3c1 255}
9112b6ce 256
48fc9762 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)
9112b6ce 330{
cdc6aac4 331 sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0);
332 sceGuTexImage(0,512,512,512,g_screen_ptr);
9112b6ce 333
ace18401 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 }
9112b6ce 340
cdc6aac4 341 if (need_pal_upload) {
342 need_pal_upload = 0;
343 sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
8ab3e3c1 344 }
7d4906bf 345
8ab3e3c1 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;
7d4906bf 351
8ab3e3c1 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
7d4906bf 363 }
8ab3e3c1 364 else
365#endif
366 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices);
b1b5f9c0 367
48fc9762 368 blit_osd();
369 blit_cdleds();
7d4906bf 370}
371
48fc9762 372
c060a9ab 373static void draw_pico_ptr(void)
374{
c87e36d7 375 int up = (PicoPicohw.pen_pos[0]|PicoPicohw.pen_pos[1]) & 0x8000;
dca20eff 376 int x = pico_pen_x, y = pico_pen_y, offs;
c87e36d7 377 int pitch = g_screen_ppitch;
15cc45c0 378 // storyware pages are actually squished, 2:1
379 int h = (pico_inp_mode == 1 ? 160 : out_h);
380 if (h < 224) y++;
dca20eff 381
c87e36d7 382 x = (x * out_w * ((1ULL<<32) / 320 + 1)) >> 32;
15cc45c0 383 y = (y * h * ((1ULL<<32) / 224 + 1)) >> 32;
c060a9ab 384
15cc45c0 385 offs = pitch * (out_y+y) + (out_x+x);
c060a9ab 386
dca20eff 387 if (is_16bit_mode()) {
388 unsigned short *p = (unsigned short *)g_screen_ptr + offs;
c87e36d7 389 int o = (up ? 0x0000 : 0xffff), _ = (up ? 0xffff : 0x0000);
1cc77481 390
15cc45c0 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;
dca20eff 395 } else {
396 unsigned char *p = (unsigned char *)g_screen_ptr + offs + 8;
c87e36d7 397 int o = (up ? 0xe0 : 0xf0), _ = (up ? 0xf0 : 0xe0);
dca20eff 398
15cc45c0 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;
dca20eff 403 }
c060a9ab 404}
405
406
7d4906bf 407static void vidResetMode(void)
408{
9112b6ce 409 // setup GU
410 sceGuSync(0,0); // sync with prev
411 sceGuStart(GU_DIRECT, guCmdList);
412
413 sceGuClutMode(GU_PSM_5650,0,0xff,0);
9112b6ce 414 sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
0924243a 415 if (currentConfig.filter)
8ab3e3c1 416 sceGuTexFilter(GU_LINEAR, GU_LINEAR);
417 else sceGuTexFilter(GU_NEAREST, GU_NEAREST);
9112b6ce 418 sceGuTexScale(1.0f,1.0f);
419 sceGuTexOffset(0.0f,0.0f);
9112b6ce 420
7d4906bf 421 Pico.m.dirtyPal = 1;
422
9112b6ce 423 sceGuFinish();
8ab3e3c1 424 set_scaling_params();
9112b6ce 425 sceGuSync(0,0);
7d4906bf 426}
8ab3e3c1 427
a4221917 428/* sound stuff */
e28fd20f 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;
a4221917 439
a4221917 440static int sound_thread_exit = 0;
441static SceUID sound_sem = -1;
442
e28fd20f 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.
a4221917 446
e28fd20f 447static void writeSound(int len)
448{
4be2e8c9 449 // make sure there is enough free space in the buffer after
e28fd20f 450 // this frame, else the next frame may overwrite old stored samples.
4be2e8c9 451 if (samples_made - samples_done < samples_block * (SOUND_BLOCK_COUNT-2) - 8) {
e28fd20f 452 sndBuffer_ptr += len / 2;
453 if (sndBuffer_ptr - sndBuffer > sizeof(sndBuffer)/2)
75db61b7 454 lprintf("snd ovrn %d %d\n", len, sndBuffer_ptr - sndBuffer);
e28fd20f 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)
7d4906bf 523{
e28fd20f 524 short *p = sndBuffer_ptr;
525 int i;
a4221917 526
e28fd20f 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{
c5b61ac2 536 lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority());
a4221917 537
538 while (!sound_thread_exit)
539 {
e28fd20f 540 int ret;
541
a4221917 542 if (samples_made - samples_done < samples_block) {
88b3d7c1 543 // wait for data (use at least 2 blocks)
110df09c 544 //lprintf("sthr: wait... (%i)\n", samples_made - samples_done);
e28fd20f 545 while (samples_made - samples_done < samples_block*2 && !sound_thread_exit) {
88b3d7c1 546 ret = sceKernelWaitSema(sound_sem, 1, 0);
e28fd20f 547 if (ret < 0) lprintf("sthr: sceKernelWaitSema: %i\n", ret);
548 }
a4221917 549 }
550
e28fd20f 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);
cdc6aac4 554 ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr);
e28fd20f 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);
a4221917 557
558 samples_done += samples_block;
559 snd_playptr += samples_block;
e41da5ce 560 if (snd_playptr >= sndBuffer_endptr)
e28fd20f 561 snd_playptr -= sndBuffer_endptr - sndBuffer;
a4221917 562 }
563
564 lprintf("sthr: exit\n");
565 sceKernelExitDeleteThread(0);
566 return 0;
567}
568
569static void sound_init(void)
570{
571 SceUID thid;
110df09c 572 int ret;
a4221917 573
574 sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL);
575 if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem);
576
c5b61ac2 577 samples_made = samples_done = 0;
e28fd20f 578 samples_block = 2*22050/60; // make sure it goes to sema
a4221917 579 sound_thread_exit = 0;
56ec20d2 580 thid = sceKernelCreateThread("sndthread", sound_thread, 0x12, 0x1000, 0, NULL);
a4221917 581 if (thid >= 0)
582 {
110df09c 583 ret = sceKernelStartThread(thid, 0, 0);
584 if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret);
a4221917 585 }
586 else
587 lprintf("sceKernelCreateThread failed: %i\n", thid);
588}
589
e28fd20f 590#define PSP_RATE 44100 // PicoIn.sndRate
591
f2cf8472 592void pemu_sound_start(void)
a4221917 593{
594 static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
cdc6aac4 595 static int mp3_init_done;
e28fd20f 596 int ret, stereo, factor;
a4221917 597
598 samples_made = samples_done = 0;
599
575d2247 600 if (!(currentConfig.EmuOpt & EOPT_EN_SOUND))
601 return;
602
cdc6aac4 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
37631374 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) {
9d917eea 614 PsndRerate(Pico.m.frame_count ? 1 : 0);
a4221917 615 }
e28fd20f 616 stereo = (PicoIn.opt&8)>>3;
e41da5ce 617
e28fd20f 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;
a4221917 621
622 lprintf("starting audio: %i, len: %i, stereo: %i, pal: %i, block samples: %i\n",
6311a3ba 623 PicoIn.sndRate, Pico.snd.len, stereo, Pico.m.pal, samples_block);
a4221917 624
e28fd20f 625 ret = sceAudioSRCChReserve(samples_block/2, PSP_RATE, 2); // seems to not need that stupid 64byte alignment
a4221917 626 if (ret < 0) {
cdc6aac4 627 lprintf("sceAudioSRCChReserve() failed: %i\n", ret);
b24e0f6c 628 emu_status_msg("sound init failed (%i), snd disabled", ret);
d34a42f9 629 currentConfig.EmuOpt &= ~EOPT_EN_SOUND;
a4221917 630 } else {
e28fd20f 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
6311a3ba 644 PsndRate_old = PicoIn.sndRate;
93f9619e 645 PicoOpt_old = PicoIn.opt;
a4221917 646 pal_old = Pico.m.pal;
647 }
648}
649
f2cf8472 650void pemu_sound_stop(void)
a4221917 651{
8022f53d 652 int i;
653 if (samples_done == 0)
654 {
cdc6aac4 655 // if no data is written between sceAudioSRCChReserve and sceAudioSRCChRelease calls,
656 // we get a deadlock on next sceAudioSRCChReserve call
8022f53d 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);
80db4442 663 samples_made = samples_done = 0;
8022f53d 664 for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++)
e41da5ce 665 psp_msleep(100);
cdc6aac4 666 sceAudioSRCChRelease();
a4221917 667}
668
669static void sound_deinit(void)
670{
671 sound_thread_exit = 1;
672 sceKernelSignalSema(sound_sem, 1);
80db4442 673 sceKernelDeleteSema(sound_sem);
674 sound_sem = -1;
a4221917 675}
676
cdc6aac4 677/* set default configuration values */
678void pemu_prep_defconfig(void)
7d4906bf 679{
cdc6aac4 680 defaultConfig.s_PsndRate = 22050;
681 defaultConfig.s_PicoCDBuffers = 64;
682 defaultConfig.CPUclock = 333;
0924243a 683 defaultConfig.filter = EOPT_FILTER_BILINEAR; // bilinear filtering
684 defaultConfig.scaling = EOPT_SCALE_43;
fc07fe2b 685 defaultConfig.vscaling = EOPT_VSCALE_FULL;
6370b1d4 686 defaultConfig.renderer = RT_8BIT_ACC;
e28fd20f 687 defaultConfig.renderer32x = RT_8BIT_FAST;
37631374 688 defaultConfig.EmuOpt |= EOPT_SHOW_RTC;
7d4906bf 689}
690
cdc6aac4 691/* check configuration for inconsistencies */
692void pemu_validate_config(void)
7d4906bf 693{
cdc6aac4 694 if (currentConfig.CPUclock < 33 || currentConfig.CPUclock > 333)
695 currentConfig.CPUclock = 333;
c28911fd 696 if (currentConfig.gamma < -4 || currentConfig.gamma > 16)
697 currentConfig.gamma = 0;
698 if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2)
699 currentConfig.gamma2 = 0;
cdc6aac4 700}
7d4906bf 701
cdc6aac4 702/* finalize rendering a frame */
703void pemu_finalize_frame(const char *fps, const char *notice)
704{
705 int emu_opt = currentConfig.EmuOpt;
7d4906bf 706
15cc45c0 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 }
cdc6aac4 716
bac44b18 717 // draw virtual keyboard on display
4814d19e 718 if (kbd_mode && currentConfig.keyboard == 1 && vkbd)
bac44b18 719 vkbd_draw(vkbd);
720
48fc9762 721 osd_buf_cnt = 0;
dca20eff 722 if (notice)
723 osd_text(4, notice);
724 if (emu_opt & EOPT_SHOW_FPS)
725 osd_text(OSD_FPS_X, fps);
cdc6aac4 726
48fc9762 727 osd_cdleds = 0;
dca20eff 728 if ((emu_opt & EOPT_EN_CD_LEDS) && (PicoIn.AHW & PAHW_MCD))
cdc6aac4 729 cd_leds();
b1b5f9c0 730
731 sceKernelDcacheWritebackAll();
7d4906bf 732}
733
cdc6aac4 734/* FIXME: move plat_* to plat? */
7d4906bf 735
cdc6aac4 736void plat_debug_cat(char *str)
c060a9ab 737{
c060a9ab 738}
739
cdc6aac4 740/* platform dependend emulator initialization */
741void plat_init(void)
7d4906bf 742{
cdc6aac4 743 flip_after_sync = 1;
1cc77481 744 psp_menu_init();
cdc6aac4 745 in_psp_init(in_psp_defbinds);
746 in_probe();
747 sound_init();
80bc2254 748 plat_get_data_dir(rom_fname_loaded, sizeof(rom_fname_loaded));
cdc6aac4 749}
7d4906bf 750
cdc6aac4 751/* platform dependend emulator deinitialization */
752void plat_finish(void)
753{
754 sound_deinit();
7d4906bf 755}
756
cdc6aac4 757/* display emulator status messages before holding emulation */
758void plat_status_msg_busy_first(const char *msg)
7d4906bf 759{
cdc6aac4 760 plat_status_msg_busy_next(msg);
761}
7d4906bf 762
cdc6aac4 763void plat_status_msg_busy_next(const char *msg)
764{
765 plat_status_msg_clear();
766 pemu_finalize_frame("", msg);
95b08943 767 // flip twice since our GU pipeline has one frame delay
768 plat_video_flip();
cdc6aac4 769 plat_video_flip();
770 emu_status_msg("");
771 reset_timing = 1;
772}
70357ce5 773
95b08943 774void plat_status_msg_busy_done(void)
775{
776}
777
cdc6aac4 778/* clear status message area */
779void plat_status_msg_clear(void)
780{
48fc9762 781 // not needed since the screen buf is cleared through the GU
cdc6aac4 782}
7d4906bf 783
cdc6aac4 784/* change the audio volume setting */
785void plat_update_volume(int has_changed, int is_up)
786{
787}
7d4906bf 788
cdc6aac4 789/* prepare for MD screen mode change */
d5d17782 790void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
cdc6aac4 791{
fc07fe2b 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
0924243a 795 out_y = start_line; out_x = start_col;
796 out_h = line_count; out_w = col_count;
797
48fc9762 798 if (col_count == 248) // mind aspect ratio when blanking 1st column
6370b1d4 799 col_count = 256;
800
0924243a 801 switch (currentConfig.vscaling) {
fc07fe2b 802 case EOPT_VSCALE_FULL:
803 line_count = v43;
6370b1d4 804 vscale = (float)270/line_count;
0924243a 805 break;
fc07fe2b 806 case EOPT_VSCALE_NOBORDER:
0924243a 807 vscale = (float)270/line_count;
808 break;
809 default:
810 vscale = 1;
811 break;
812 }
fc07fe2b 813
0924243a 814 switch (currentConfig.scaling) {
815 case EOPT_SCALE_43:
fc07fe2b 816 hscale = (vscale*h43)/col_count;
0924243a 817 break;
fc07fe2b 818 case EOPT_SCALE_STRETCH:
819 hscale = (vscale*h43/2 + 480/2)/col_count;
0924243a 820 break;
fc07fe2b 821 case EOPT_SCALE_WIDE:
0924243a 822 hscale = (float)480/col_count;
823 break;
824 default:
fc07fe2b 825 hscale = vscale;
0924243a 826 break;
827 }
1cc77481 828
cdc6aac4 829 vidResetMode();
cdc6aac4 830}
f0f0d2df 831
cdc6aac4 832/* render one frame in RGB */
833void pemu_forced_frame(int no_scale, int do_emu)
834{
cdc6aac4 835 Pico.m.dirtyPal = 1;
7d4906bf 836
cdc6aac4 837 if (!no_scale)
838 no_scale = currentConfig.scaling == EOPT_SCALE_NONE;
832faed3 839 emu_cmn_forced_frame(no_scale, do_emu, g_screen_ptr);
cdc6aac4 840}
7d4906bf 841
bac44b18 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
cdc6aac4 848/* change the platform output rendering */
849void plat_video_toggle_renderer(int change, int is_menu_call)
850{
851 change_renderer(change);
c060a9ab 852
cdc6aac4 853 if (is_menu_call)
854 return;
c060a9ab 855
cdc6aac4 856 apply_renderer();
857 vidResetMode();
858 rendstatus_old = -1;
7d4906bf 859
cdc6aac4 860 if (PicoIn.AHW & PAHW_32X)
861 emu_status_msg(renderer_names32x[get_renderer()]);
862 else
863 emu_status_msg(renderer_names[get_renderer()]);
7d4906bf 864}
865
cdc6aac4 866/* set the buffer for emulator output rendering */
867void plat_video_set_buffer(void *buf)
7d4906bf 868{
1cc77481 869 if (is_16bit_mode())
cdc6aac4 870 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
871 else
872 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch);
7d4906bf 873}
874
cdc6aac4 875/* prepare for emulator output rendering */
876void plat_video_loop_prepare(void)
7d4906bf 877{
cdc6aac4 878 apply_renderer();
7d4906bf 879 vidResetMode();
cdc6aac4 880}
a4221917 881
cdc6aac4 882/* prepare for entering the emulator loop */
883void pemu_loop_prep(void)
884{
885}
a4221917 886
cdc6aac4 887/* terminate the emulator loop */
888void pemu_loop_end(void)
889{
890 pemu_sound_stop();
7d4906bf 891}
892
f0e6d1e3 893/* resume from suspend: change to main menu if emu was running */
cdc6aac4 894void emu_handle_resume(void)
ea08c296 895{
f0e6d1e3 896 if (engineState == PGS_Running)
897 engineState = PGS_Menu;
ea08c296 898}
899