2 * PicoDrive PSP frontend
5 * (C) irixxxx, 2022-2024
7 * This work is licensed under the terms of MAME license.
8 * See COPYING file in the top-level directory.
12 #include <sys/types.h>
13 #include <sys/syslimits.h> // PATH_MAX
15 #include <pspthreadman.h>
16 #include <pspdisplay.h>
20 #include <pspsysmem.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"
34 #include <pico/pico_int.h>
35 #include <pico/cd/genplus_macros.h>
36 #include <pico/cd/cdd.h>
40 int engineStateSuspend;
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;
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;
56 static int out_x, out_y;
57 static int out_w, out_h;
58 static float hscale, vscale;
60 static struct in_default_bind in_psp_defbinds[] =
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 },
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 };
82 #define is_16bit_mode() \
83 (currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))
85 static int get_renderer(void)
87 if (PicoIn.AHW & PAHW_32X)
88 return currentConfig.renderer32x;
90 return currentConfig.renderer;
93 static void change_renderer(int diff)
96 if (PicoIn.AHW & PAHW_32X)
97 r = ¤tConfig.renderer32x;
99 r = ¤tConfig.renderer;
108 static void apply_renderer(void)
110 PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
111 PicoIn.opt |= POPT_DIS_32C_BORDER;
113 switch (get_renderer()) {
115 PicoDrawSetOutFormat(PDF_RGB555, 0);
118 PicoDrawSetOutFormat(PDF_8BIT, 0);
121 PicoIn.opt |= POPT_ALT_RENDERER;
122 PicoDrawSetOutFormat(PDF_NONE, 0);
128 static void set_scaling_params(void)
130 int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0;
131 g_vertices[0].z = g_vertices[1].z = 0;
133 fbimg_height = (int)(out_h * vscale + 0.5);
134 fbimg_width = (int)(out_w * hscale + 0.5);
136 if (fbimg_width & 1) fbimg_width++; // make even
137 if (fbimg_height & 1) fbimg_height++;
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;
145 g_vertices[0].u = out_x;
146 g_vertices[1].u = out_x + out_w;
147 fbimg_xoffs = 240 - fbimg_width/2;
149 if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1;
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;
157 g_vertices[0].v = out_y;
158 g_vertices[1].v = out_y + out_h;
159 fbimg_yoffs = 136 - fbimg_height/2;
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;
173 g_vertices[0].u++; g_vertices[1].u--;
174 g_vertices[0].x++; g_vertices[1].x--;
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);
185 static void do_pal_update_sms(void)
187 static u16 tmspal[32] = {
189 0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
190 0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
192 0x0000, 0x0000, 0x04c2, 0x07d6, 0x0e55, 0x0f77, 0x055c, 0x0ee4,
193 0x055f, 0x077f, 0x05bc, 0x08ce, 0x03a2, 0x0b5c, 0x0ccc, 0x0fff,
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);
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);
205 if (Pico.m.dirtyPal == 2)
210 static void do_pal_update(void)
212 u32 *dpal=(void *)localPal;
215 if (PicoIn.opt & POPT_ALT_RENDERER) {
216 do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2);
219 else if (Pico.est.rendstatus & PDRAW_SONIC_MODE)
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);
228 else if (Pico.video.reg[0xC]&8) // shadow/hilight?
230 do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
232 for (i = 0; i < 0x40 / 2; i++) {
233 dpal[0xc0/2 + i] = dpal[i];
234 dpal[0x80/2 + i] = (dpal[i] >> 1) & 0x738e738e;
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;
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);
250 localPal[0xf0] = 0x001f;
252 if (Pico.m.dirtyPal == 2)
257 static void osd_text(int x, const char *text)
259 int len = strlen(text) * 8;
261 void *tmp = g_screen_ptr;
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);
269 emu_text_out16(x, 0, text);
272 osd_buf_x[osd_buf_cnt] = x;
273 osd_buf_l[osd_buf_cnt] = len;
277 static void blit_osd(void)
282 while (osd_buf_cnt > 0) {
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);
297 static void cd_leds(void)
299 unsigned int reg, col_g, col_r, *p;
301 reg = Pico_mcd->s68k_regs[0];
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;
313 static void blit_cdleds(void)
317 if (!osd_cdleds) return;
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);
329 void blitscreen_clut(void)
331 sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0);
332 sceGuTexImage(0,512,512,512,g_screen_ptr);
334 if (!is_16bit_mode() && Pico.m.dirtyPal) {
335 if (PicoIn.AHW & PAHW_SMS)
341 if (need_pal_upload) {
343 sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
347 if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x)
349 struct Vertex* vertices;
352 #define SLICE_WIDTH 32
353 for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH)
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);
362 // lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only
366 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices);
373 static void draw_pico_ptr(void)
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);
382 x = (x * out_w * ((1ULL<<32) / 320 + 1)) >> 32;
383 y = (y * h * ((1ULL<<32) / 224 + 1)) >> 32;
385 offs = pitch * (out_y+y) + (out_x+x);
387 if (is_16bit_mode()) {
388 unsigned short *p = (unsigned short *)g_screen_ptr + offs;
389 int o = (up ? 0x0000 : 0xffff), _ = (up ? 0xffff : 0x0000);
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;
396 unsigned char *p = (unsigned char *)g_screen_ptr + offs + 8;
397 int o = (up ? 0xe0 : 0xf0), _ = (up ? 0xf0 : 0xe0);
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;
407 static void vidResetMode(void)
410 sceGuSync(0,0); // sync with prev
411 sceGuStart(GU_DIRECT, guCmdList);
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);
424 set_scaling_params();
429 #define SOUND_BLOCK_COUNT 7
430 #define SOUND_BUFFER_CHUNK (2*44100/50) // max.rate/min.frames in stereo
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;
437 static short *snd_playptr, *sndBuffer_ptr;
438 static int samples_made, samples_done;
440 static int sound_thread_exit = 0;
441 static SceUID sound_sem = -1;
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.
447 static void writeSound(int len)
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;
458 memcpy(sndBuffer, sndBuffer_endptr, 2*wrap);
459 sndBuffer_ptr -= sndBuffer_endptr - sndBuffer;
462 samples_made += len / 2;
464 lprintf("snd oflow %i!\n", samples_made - samples_done);
466 // signal the snd thread
467 sceKernelSignalSema(sound_sem, 1);
470 static void writeSound_44100_stereo(int len)
473 PicoIn.sndOut = sndBuffer_ptr;
476 static void writeSound_44100_mono(int len)
478 short *p = sndBuffer_ptr;
481 for (i = 0; i < len / 2; i++, p+=2)
482 p[0] = p[1] = PicoIn.sndOut[i];
486 static void writeSound_22050_stereo(int len)
488 short *p = sndBuffer_ptr;
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];
498 static void writeSound_22050_mono(int len)
500 short *p = sndBuffer_ptr;
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];
510 static void writeSound_11025_stereo(int len)
512 short *p = sndBuffer_ptr;
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];
522 static void writeSound_11025_mono(int len)
524 short *p = sndBuffer_ptr;
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];
534 static int sound_thread(SceSize args, void *argp)
536 lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority());
538 while (!sound_thread_exit)
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);
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);
558 samples_done += samples_block;
559 snd_playptr += samples_block;
560 if (snd_playptr >= sndBuffer_endptr)
561 snd_playptr -= sndBuffer_endptr - sndBuffer;
564 lprintf("sthr: exit\n");
565 sceKernelExitDeleteThread(0);
569 static void sound_init(void)
574 sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL);
575 if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem);
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);
583 ret = sceKernelStartThread(thid, 0, 0);
584 if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret);
587 lprintf("sceKernelCreateThread failed: %i\n", thid);
590 #define PSP_RATE 44100 // PicoIn.sndRate
592 void pemu_sound_start(void)
594 static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
595 static int mp3_init_done;
596 int ret, stereo, factor;
598 samples_made = samples_done = 0;
600 if (!(currentConfig.EmuOpt & EOPT_EN_SOUND))
603 if (PicoIn.AHW & PAHW_MCD) {
605 if (!mp3_init_done) {
608 if (ret) emu_status_msg("mp3 init failed (%i)", ret);
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);
616 stereo = (PicoIn.opt&8)>>3;
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;
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);
625 ret = sceAudioSRCChReserve(samples_block/2, PSP_RATE, 2); // seems to not need that stupid 64byte alignment
627 lprintf("sceAudioSRCChReserve() failed: %i\n", ret);
628 emu_status_msg("sound init failed (%i), snd disabled", ret);
629 currentConfig.EmuOpt &= ~EOPT_EN_SOUND;
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;
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);
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);
644 PsndRate_old = PicoIn.sndRate;
645 PicoOpt_old = PicoIn.opt;
646 pal_old = Pico.m.pal;
650 void pemu_sound_stop(void)
653 if (samples_done == 0)
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);
662 sceKernelDelayThread(100*1000);
663 samples_made = samples_done = 0;
664 for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++)
666 sceAudioSRCChRelease();
669 static void sound_deinit(void)
671 sound_thread_exit = 1;
672 sceKernelSignalSema(sound_sem, 1);
673 sceKernelDeleteSema(sound_sem);
677 /* set default configuration values */
678 void pemu_prep_defconfig(void)
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;
691 /* check configuration for inconsistencies */
692 void pemu_validate_config(void)
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;
702 /* finalize rendering a frame */
703 void pemu_finalize_frame(const char *fps, const char *notice)
705 int emu_opt = currentConfig.EmuOpt;
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;
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*/)
717 // draw virtual keyboard on display
718 if (kbd_mode && currentConfig.keyboard == 1 && vkbd)
724 if (emu_opt & EOPT_SHOW_FPS)
725 osd_text(OSD_FPS_X, fps);
728 if ((emu_opt & EOPT_EN_CD_LEDS) && (PicoIn.AHW & PAHW_MCD))
731 sceKernelDcacheWritebackAll();
734 /* FIXME: move plat_* to plat? */
736 void plat_debug_cat(char *str)
740 /* platform dependend emulator initialization */
745 in_psp_init(in_psp_defbinds);
748 plat_get_data_dir(rom_fname_loaded, sizeof(rom_fname_loaded));
751 /* platform dependend emulator deinitialization */
752 void plat_finish(void)
757 /* display emulator status messages before holding emulation */
758 void plat_status_msg_busy_first(const char *msg)
760 plat_status_msg_busy_next(msg);
763 void plat_status_msg_busy_next(const char *msg)
765 plat_status_msg_clear();
766 pemu_finalize_frame("", msg);
767 // flip twice since our GU pipeline has one frame delay
774 void plat_status_msg_busy_done(void)
778 /* clear status message area */
779 void plat_status_msg_clear(void)
781 // not needed since the screen buf is cleared through the GU
784 /* change the audio volume setting */
785 void plat_update_volume(int has_changed, int is_up)
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)
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);
795 out_y = start_line; out_x = start_col;
796 out_h = line_count; out_w = col_count;
798 if (col_count == 248) // mind aspect ratio when blanking 1st column
801 switch (currentConfig.vscaling) {
802 case EOPT_VSCALE_FULL:
804 vscale = (float)270/line_count;
806 case EOPT_VSCALE_NOBORDER:
807 vscale = (float)270/line_count;
814 switch (currentConfig.scaling) {
816 hscale = (vscale*h43)/col_count;
818 case EOPT_SCALE_STRETCH:
819 hscale = (vscale*h43/2 + 480/2)/col_count;
821 case EOPT_SCALE_WIDE:
822 hscale = (float)480/col_count;
832 /* render one frame in RGB */
833 void pemu_forced_frame(int no_scale, int do_emu)
838 no_scale = currentConfig.scaling == EOPT_SCALE_NONE;
839 emu_cmn_forced_frame(no_scale, do_emu, g_screen_ptr);
842 /* clear all video buffers to remove remnants in borders */
843 void plat_video_clear_buffers(void)
845 // not needed since output buffer is cleared in flip anyway
848 /* change the platform output rendering */
849 void plat_video_toggle_renderer(int change, int is_menu_call)
851 change_renderer(change);
860 if (PicoIn.AHW & PAHW_32X)
861 emu_status_msg(renderer_names32x[get_renderer()]);
863 emu_status_msg(renderer_names[get_renderer()]);
866 /* set the buffer for emulator output rendering */
867 void plat_video_set_buffer(void *buf)
870 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
872 PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch);
875 /* prepare for emulator output rendering */
876 void plat_video_loop_prepare(void)
882 /* prepare for entering the emulator loop */
883 void pemu_loop_prep(void)
887 /* terminate the emulator loop */
888 void pemu_loop_end(void)
893 /* resume from suspend: change to main menu if emu was running */
894 void emu_handle_resume(void)
896 if (engineState == PGS_Running)
897 engineState = PGS_Menu;