/* * PicoDrive * (C) notaz, 2007,2008 * (C) irixxxx, 2022-2024 * * This work is licensed under the terms of MAME license. * See COPYING file in the top-level directory. */ #include #include #include // PATH_MAX #include #include #include #include #include #include #include "psp.h" #include "emu.h" #include "mp3.h" #include "in_psp.h" #include "asm_utils.h" #include "../common/emu.h" #include "../common/input_pico.h" #include "platform/libpicofe/input.h" #include "platform/libpicofe/menu.h" #include "platform/libpicofe/plat.h" #include #include #include #define OSD_FPS_X 432 int engineStateSuspend; struct Vertex { short u,v; short x,y,z; }; static struct Vertex __attribute__((aligned(4))) g_vertices[2]; static u16 __attribute__((aligned(16))) localPal[0x100]; static int need_pal_upload = 0; u16 __attribute__((aligned(16))) osd_buf[512*8]; // buffer for osd text int osd_buf_x[4], osd_buf_l[4]; // store for x and length for blitting int osd_buf_cnt, osd_cdleds; static int out_x, out_y; static int out_w, out_h; static float hscale, vscale; static struct in_default_bind in_psp_defbinds[] = { { PSP_CTRL_UP, IN_BINDTYPE_PLAYER12, GBTN_UP }, { PSP_CTRL_DOWN, IN_BINDTYPE_PLAYER12, GBTN_DOWN }, { PSP_CTRL_LEFT, IN_BINDTYPE_PLAYER12, GBTN_LEFT }, { PSP_CTRL_RIGHT, IN_BINDTYPE_PLAYER12, GBTN_RIGHT }, { PSP_CTRL_SQUARE, IN_BINDTYPE_PLAYER12, GBTN_A }, { PSP_CTRL_CROSS, IN_BINDTYPE_PLAYER12, GBTN_B }, { PSP_CTRL_CIRCLE, IN_BINDTYPE_PLAYER12, GBTN_C }, { PSP_CTRL_START, IN_BINDTYPE_PLAYER12, GBTN_START }, { PSP_CTRL_TRIANGLE, IN_BINDTYPE_EMU, PEVB_SWITCH_RND }, { PSP_CTRL_LTRIGGER, IN_BINDTYPE_EMU, PEVB_STATE_SAVE }, { PSP_CTRL_RTRIGGER, IN_BINDTYPE_EMU, PEVB_STATE_LOAD }, { PSP_CTRL_SELECT, IN_BINDTYPE_EMU, PEVB_MENU }, { 0, 0, 0 } }; const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast", NULL }; const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL }; enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT }; #define is_16bit_mode() \ (currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X)) static int get_renderer(void) { if (PicoIn.AHW & PAHW_32X) return currentConfig.renderer32x; else return currentConfig.renderer; } static void change_renderer(int diff) { int *r; if (PicoIn.AHW & PAHW_32X) r = ¤tConfig.renderer32x; else r = ¤tConfig.renderer; *r += diff; if (*r >= RT_COUNT) *r = 0; else if (*r < 0) *r = RT_COUNT - 1; } static void apply_renderer(void) { PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE); PicoIn.opt |= POPT_DIS_32C_BORDER; switch (get_renderer()) { case RT_16BIT: PicoDrawSetOutFormat(PDF_RGB555, 0); break; case RT_8BIT_ACC: PicoDrawSetOutFormat(PDF_8BIT, 0); break; case RT_8BIT_FAST: PicoIn.opt |= POPT_ALT_RENDERER; PicoDrawSetOutFormat(PDF_NONE, 0); break; } } static void set_scaling_params(void) { int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0; g_vertices[0].z = g_vertices[1].z = 0; fbimg_height = (int)(out_h * vscale + 0.5); fbimg_width = (int)(out_w * hscale + 0.5); if (fbimg_width & 1) fbimg_width++; // make even if (fbimg_height & 1) fbimg_height++; if (fbimg_width >= 480) { g_vertices[0].u = out_x + (fbimg_width-480)/2; g_vertices[1].u = out_x + out_w - (fbimg_width-480)/2 - 1; fbimg_width = 480; fbimg_xoffs = 0; } else { g_vertices[0].u = out_x; g_vertices[1].u = out_x + out_w; fbimg_xoffs = 240 - fbimg_width/2; } if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1; if (fbimg_height >= 272) { g_vertices[0].v = out_y + (fbimg_height-272)/2; g_vertices[1].v = out_y + out_h - (fbimg_height-272)/2; fbimg_height = 272; fbimg_yoffs = 0; } else { g_vertices[0].v = out_y; g_vertices[1].v = out_y + out_h; fbimg_yoffs = 136 - fbimg_height/2; } if (fbimg_xoffs < 0) fbimg_xoffs = 0; if (fbimg_yoffs < 0) fbimg_yoffs = 0; g_vertices[0].x = fbimg_xoffs; g_vertices[0].y = fbimg_yoffs; g_vertices[1].x = fbimg_xoffs + fbimg_width; g_vertices[1].y = fbimg_yoffs + fbimg_height; if (!is_16bit_mode()) { // 8-bit modes have an 8 px overlap area on the left g_vertices[0].u += 8; g_vertices[1].u += 8; } if (border_hack) { g_vertices[0].u++; g_vertices[1].u--; g_vertices[0].x++; g_vertices[1].x--; } /* lprintf("set_scaling_params:\n"); lprintf("offs: %i, %i\n", fbimg_xoffs, fbimg_yoffs); lprintf("xy0, xy1: %i, %i; %i, %i\n", g_vertices[0].x, g_vertices[0].y, g_vertices[1].x, g_vertices[1].y); lprintf("uv0, uv1: %i, %i; %i, %i\n", g_vertices[0].u, g_vertices[0].v, g_vertices[1].u, g_vertices[1].v); */ } static void do_pal_update_sms(void) { static u16 tmspal[32] = { // SMS palette 0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0, 0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff, // TMS palette 0x0000, 0x0000, 0x04c2, 0x07d6, 0x0e55, 0x0f77, 0x055c, 0x0ee4, 0x055f, 0x077f, 0x05bc, 0x08ce, 0x03a2, 0x0b5c, 0x0ccc, 0x0fff, }; int i; if (!(Pico.video.reg[0] & 0x4)) { int sg = !!(PicoIn.AHW & (PAHW_SG|PAHW_SC)); for (i = Pico.est.SonicPalCount; i >= 0; i--) do_pal_convert(localPal+i*0x40, tmspal+sg*0x10, currentConfig.gamma, currentConfig.gamma2); } else { for (i = Pico.est.SonicPalCount; i >= 0; i--) do_pal_convert(localPal+i*0x40, Pico.est.SonicPal+i*0x40, currentConfig.gamma, currentConfig.gamma2); } if (Pico.m.dirtyPal == 2) Pico.m.dirtyPal = 0; need_pal_upload = 1; } static void do_pal_update(void) { u32 *dpal=(void *)localPal; int i; if (PicoIn.opt & POPT_ALT_RENDERER) { do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2); Pico.m.dirtyPal = 0; } else if (Pico.est.rendstatus & PDRAW_SONIC_MODE) { switch (Pico.est.SonicPalCount) { case 3: do_pal_convert(localPal+0xc0, Pico.est.SonicPal+0xc0, currentConfig.gamma, currentConfig.gamma2); case 2: do_pal_convert(localPal+0x80, Pico.est.SonicPal+0x80, currentConfig.gamma, currentConfig.gamma2); case 1: do_pal_convert(localPal+0x40, Pico.est.SonicPal+0x40, currentConfig.gamma, currentConfig.gamma2); default:do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2); } } else if (Pico.video.reg[0xC]&8) // shadow/hilight? { do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2); // shadowed pixels for (i = 0; i < 0x40 / 2; i++) { dpal[0xc0/2 + i] = dpal[i]; dpal[0x80/2 + i] = (dpal[i] >> 1) & 0x738e738e; } // hilighted pixels for (i = 0; i < 0x40 / 2; i++) { u32 t = ((dpal[i] >> 1) & 0x738e738e) + 0x738e738e; t |= (t >> 4) & 0x08610861; dpal[0x40/2 + i] = t; } } else { do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2); memcpy((int *)dpal+0x40/2, (void *)localPal, 0x40*2); memcpy((int *)dpal+0x80/2, (void *)localPal, 0x80*2); } localPal[0xe0] = 0; localPal[0xf0] = 0x001f; if (Pico.m.dirtyPal == 2) Pico.m.dirtyPal = 0; need_pal_upload = 1; } static void osd_text(int x, const char *text) { int len = strlen(text) * 8; int *p, h; void *tmp = g_screen_ptr; g_screen_ptr = osd_buf; for (h = 0; h < 8; h++) { p = (int *) (osd_buf+x+512*h); p = (int *) ((int)p & ~3); // align memset32_uncached(p, 0, len/2); } emu_text_out16(x, 0, text); g_screen_ptr = tmp; osd_buf_x[osd_buf_cnt] = x; osd_buf_l[osd_buf_cnt] = len; osd_buf_cnt ++; } static void blit_osd(void) { struct Vertex* vx; int x, len; while (osd_buf_cnt > 0) { osd_buf_cnt --; x = osd_buf_x[osd_buf_cnt]; len = osd_buf_l[osd_buf_cnt]; vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex)); vx[0].u = x, vx[0].v = 0; vx[1].u = x + len, vx[1].v = 8; vx[0].x = x, vx[0].y = 264; vx[1].x = x + len, vx[1].y = 272; sceGuTexMode(GU_PSM_5650,0,0,0); sceGuTexImage(0,512,8,512,osd_buf); sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx); } } static void cd_leds(void) { unsigned int reg, col_g, col_r, *p; reg = Pico_mcd->s68k_regs[0]; p = (unsigned int *)((short *)osd_buf + 512*2+498); col_g = (reg & 2) ? 0x06000600 : 0; col_r = (reg & 1) ? 0x00180018 : 0; *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2; *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2; *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; osd_cdleds = 1; } static void blit_cdleds(void) { struct Vertex* vx; if (!osd_cdleds) return; vx = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex)); vx[0].u = 497, vx[0].v = 1; vx[1].u = 497+14, vx[1].v = 6; vx[0].x = 4, vx[0].y = 1; vx[1].x = 4+14, vx[1].y = 6; sceGuTexMode(GU_PSM_5650,0,0,0); sceGuTexImage(0,512,8,512,osd_buf); sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vx); } void blitscreen_clut(void) { sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0); sceGuTexImage(0,512,512,512,g_screen_ptr); if (!is_16bit_mode() && Pico.m.dirtyPal) { if (PicoIn.AHW & PAHW_SMS) do_pal_update_sms(); else do_pal_update(); } if (need_pal_upload) { need_pal_upload = 0; sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256) } #if 1 if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x) { struct Vertex* vertices; int x; #define SLICE_WIDTH 32 for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH) { // render sprite vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex)); memcpy(vertices, g_vertices, 2 * sizeof(struct Vertex)); vertices[0].u = vertices[0].x = x; vertices[1].u = vertices[1].x = x + SLICE_WIDTH; sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices); } // lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only } else #endif sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices); blit_osd(); blit_cdleds(); } static void draw_pico_ptr(void) { int up = (PicoPicohw.pen_pos[0]|PicoPicohw.pen_pos[1]) & 0x8000; int x = pico_pen_x, y = pico_pen_y, offs; int pitch = g_screen_ppitch; x = (x * out_w * ((1ULL<<32) / 320 + 1)) >> 32; y = (y * out_h * ((1ULL<<32) / 224 + 1)) >> 32; offs = 512 * (out_y+y) + (out_x+x); if (is_16bit_mode()) { unsigned short *p = (unsigned short *)g_screen_ptr + offs; int o = (up ? 0x0000 : 0xffff), _ = (up ? 0xffff : 0x0000); p[-pitch-1] ^= _; p[-pitch] ^= o; p[-pitch+1] ^= _; p[1] ^= o; p[0] ^= o; p[1] ^= o; p[pitch-1] ^= _; p[pitch] ^= o; p[pitch+1] ^= _; } else { unsigned char *p = (unsigned char *)g_screen_ptr + offs + 8; int o = (up ? 0xe0 : 0xf0), _ = (up ? 0xf0 : 0xe0); p[-pitch-1] = _; p[-pitch] = o; p[-pitch+1] = _; p[-1] = o; p[0] = o; p[1] = o; p[pitch-1] = _; p[pitch] = o; p[pitch+1] = _; } } static void vidResetMode(void) { // setup GU sceGuSync(0,0); // sync with prev sceGuStart(GU_DIRECT, guCmdList); sceGuClutMode(GU_PSM_5650,0,0xff,0); sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB); if (currentConfig.filter) sceGuTexFilter(GU_LINEAR, GU_LINEAR); else sceGuTexFilter(GU_NEAREST, GU_NEAREST); sceGuTexScale(1.0f,1.0f); sceGuTexOffset(0.0f,0.0f); Pico.m.dirtyPal = 1; sceGuFinish(); set_scaling_params(); sceGuSync(0,0); } /* sound stuff */ #define SOUND_BLOCK_COUNT 7 #define SOUND_BUFFER_CHUNK (2*44100/50) // max.rate/min.frames in stereo static short sndBuffer_emu[SOUND_BUFFER_CHUNK+4]; // 4 for sample rounding overhang static short __attribute__((aligned(4))) sndBuffer[SOUND_BUFFER_CHUNK*SOUND_BLOCK_COUNT]; static short *sndBuffer_endptr; static int samples_block; static short *snd_playptr, *sndBuffer_ptr; static int samples_made, samples_done; static int sound_thread_exit = 0; static SceUID sound_sem = -1; // There are problems if the sample rate used with the PSP isn't 44100 Hz stereo. // Hence, use only 11025,22050,44100 here and handle duplication and upsampling. // Upsample by nearest neighbour, which is the fastest but may create artifacts. static void writeSound(int len) { // make sure there is (samples_block+2) free space in the buffer after // this frame, else the next frame may overwrite old stored samples. if (samples_made - samples_done < samples_block * (SOUND_BLOCK_COUNT-2) - 4) { sndBuffer_ptr += len / 2; if (sndBuffer_ptr - sndBuffer > sizeof(sndBuffer)/2) lprintf("snd ovrn %d %d\n", len, PicoIn.sndOut - sndBuffer); if (sndBuffer_ptr >= sndBuffer_endptr) { int wrap = sndBuffer_ptr - sndBuffer_endptr; if (wrap > 0) memcpy(sndBuffer, sndBuffer_endptr, 2*wrap); sndBuffer_ptr -= sndBuffer_endptr - sndBuffer; } samples_made += len / 2; } else lprintf("snd oflow %i!\n", samples_made - samples_done); // signal the snd thread sceKernelSignalSema(sound_sem, 1); } static void writeSound_44100_stereo(int len) { writeSound(len); PicoIn.sndOut = sndBuffer_ptr; } static void writeSound_44100_mono(int len) { short *p = sndBuffer_ptr; int i; for (i = 0; i < len / 2; i++, p+=2) p[0] = p[1] = PicoIn.sndOut[i]; writeSound(2*len); } static void writeSound_22050_stereo(int len) { short *p = sndBuffer_ptr; int i; for (i = 0; i < len / 2; i+=2, p+=4) { p[0] = p[2] = PicoIn.sndOut[i]; p[1] = p[3] = PicoIn.sndOut[i+1]; } writeSound(2*len); } static void writeSound_22050_mono(int len) { short *p = sndBuffer_ptr; int i; for (i = 0; i < len / 2; i++, p+=4) { p[0] = p[2] = PicoIn.sndOut[i]; p[1] = p[3] = PicoIn.sndOut[i]; } writeSound(4*len); } static void writeSound_11025_stereo(int len) { short *p = sndBuffer_ptr; int i; for (i = 0; i < len / 2; i+=2, p+=8) { p[0] = p[2] = p[4] = p[6] = PicoIn.sndOut[i]; p[1] = p[3] = p[5] = p[7] = PicoIn.sndOut[i+1]; } writeSound(4*len); } static void writeSound_11025_mono(int len) { short *p = sndBuffer_ptr; int i; for (i = 0; i < len / 2; i++, p+=8) { p[0] = p[2] = p[4] = p[6] = PicoIn.sndOut[i]; p[1] = p[3] = p[5] = p[7] = PicoIn.sndOut[i]; } writeSound(8*len); } static int sound_thread(SceSize args, void *argp) { lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority()); while (!sound_thread_exit) { int ret; if (samples_made - samples_done < samples_block) { // wait for data (use at least 2 blocks) //lprintf("sthr: wait... (%i)\n", samples_made - samples_done); while (samples_made - samples_done < samples_block*2 && !sound_thread_exit) { ret = sceKernelWaitSema(sound_sem, 1, 0); if (ret < 0) lprintf("sthr: sceKernelWaitSema: %i\n", ret); } } // if the sample buffer runs low, push some extra if (sceAudioOutput2GetRestSample()*2 < samples_block/4) ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr); ret = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, snd_playptr); // 1.5 kernel returns 0, newer ones return # of samples queued if (ret < 0) lprintf("sthr: play: ret %08x; pos %i/%i\n", ret, samples_done, samples_made); samples_done += samples_block; snd_playptr += samples_block; if (snd_playptr >= sndBuffer_endptr) snd_playptr -= sndBuffer_endptr - sndBuffer; } lprintf("sthr: exit\n"); sceKernelExitDeleteThread(0); return 0; } static void sound_init(void) { SceUID thid; int ret; sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL); if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem); samples_made = samples_done = 0; samples_block = 2*22050/60; // make sure it goes to sema sound_thread_exit = 0; thid = sceKernelCreateThread("sndthread", sound_thread, 0x12, 0x1000, 0, NULL); if (thid >= 0) { ret = sceKernelStartThread(thid, 0, 0); if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret); } else lprintf("sceKernelCreateThread failed: %i\n", thid); } #define PSP_RATE 44100 // PicoIn.sndRate void pemu_sound_start(void) { static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0; static int mp3_init_done; int ret, stereo, factor; samples_made = samples_done = 0; if (!(currentConfig.EmuOpt & EOPT_EN_SOUND)) return; if (PicoIn.AHW & PAHW_MCD) { // mp3... if (!mp3_init_done) { ret = mp3_init(); mp3_init_done = 1; if (ret) emu_status_msg("mp3 init failed (%i)", ret); } } ret = POPT_EN_FM|POPT_EN_PSG|POPT_EN_STEREO; if (PicoIn.sndRate != PsndRate_old || (PicoIn.opt&ret) != (PicoOpt_old&ret) || Pico.m.pal != pal_old) { PsndRerate(Pico.m.frame_count ? 1 : 0); } stereo = (PicoIn.opt&8)>>3; // PSP doesn't support mono in SRC, always use stereo and convert factor = PSP_RATE / PicoIn.sndRate; samples_block = (PSP_RATE / (Pico.m.pal ? 50 : 60)) * 2; lprintf("starting audio: %i, len: %i, stereo: %i, pal: %i, block samples: %i\n", PicoIn.sndRate, Pico.snd.len, stereo, Pico.m.pal, samples_block); ret = sceAudioSRCChReserve(samples_block/2, PSP_RATE, 2); // seems to not need that stupid 64byte alignment if (ret < 0) { lprintf("sceAudioSRCChReserve() failed: %i\n", ret); emu_status_msg("sound init failed (%i), snd disabled", ret); currentConfig.EmuOpt &= ~EOPT_EN_SOUND; } else { switch (factor) { case 1: PicoIn.writeSound = stereo ? writeSound_44100_stereo:writeSound_44100_mono; break; case 2: PicoIn.writeSound = stereo ? writeSound_22050_stereo:writeSound_22050_mono; break; case 4: PicoIn.writeSound = stereo ? writeSound_11025_stereo:writeSound_11025_mono; break; } sndBuffer_endptr = sndBuffer + (SOUND_BLOCK_COUNT-1)*samples_block; snd_playptr = sndBuffer_ptr = sndBuffer; PicoIn.sndOut = (factor == 1 && stereo ? sndBuffer_ptr : sndBuffer_emu); // push one audio block to cover time to first frame audio // memset32(PicoIn.sndOut, 0, samples_block/2); // writeSound(samples_block*2); PsndRate_old = PicoIn.sndRate; PicoOpt_old = PicoIn.opt; pal_old = Pico.m.pal; } } void pemu_sound_stop(void) { int i; if (samples_done == 0) { // if no data is written between sceAudioSRCChReserve and sceAudioSRCChRelease calls, // we get a deadlock on next sceAudioSRCChReserve call // so this is yet another workaround: memset32((int *)(void *)sndBuffer, 0, samples_block*4/4); samples_made = samples_block * 3; sceKernelSignalSema(sound_sem, 1); } sceKernelDelayThread(100*1000); samples_made = samples_done = 0; for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++) psp_msleep(100); sceAudioSRCChRelease(); } static void sound_deinit(void) { sound_thread_exit = 1; sceKernelSignalSema(sound_sem, 1); sceKernelDeleteSema(sound_sem); sound_sem = -1; } /* set default configuration values */ void pemu_prep_defconfig(void) { defaultConfig.s_PsndRate = 22050; defaultConfig.s_PicoCDBuffers = 64; defaultConfig.CPUclock = 333; defaultConfig.filter = EOPT_FILTER_BILINEAR; // bilinear filtering defaultConfig.scaling = EOPT_SCALE_43; defaultConfig.vscaling = EOPT_VSCALE_FULL; defaultConfig.renderer = RT_8BIT_ACC; defaultConfig.renderer32x = RT_8BIT_FAST; defaultConfig.EmuOpt |= EOPT_SHOW_RTC; } /* check configuration for inconsistencies */ void pemu_validate_config(void) { if (currentConfig.CPUclock < 33 || currentConfig.CPUclock > 333) currentConfig.CPUclock = 333; if (currentConfig.gamma < -4 || currentConfig.gamma > 16) currentConfig.gamma = 0; if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2) currentConfig.gamma2 = 0; } /* finalize rendering a frame */ void pemu_finalize_frame(const char *fps, const char *notice) { int emu_opt = currentConfig.EmuOpt; if ((PicoIn.AHW & PAHW_PICO) && (currentConfig.EmuOpt & EOPT_PICO_PEN)) if (pico_inp_mode) draw_pico_ptr(); osd_buf_cnt = 0; if (notice) osd_text(4, notice); if (emu_opt & EOPT_SHOW_FPS) osd_text(OSD_FPS_X, fps); osd_cdleds = 0; if ((emu_opt & EOPT_EN_CD_LEDS) && (PicoIn.AHW & PAHW_MCD)) cd_leds(); sceKernelDcacheWritebackAll(); } /* FIXME: move plat_* to plat? */ void plat_debug_cat(char *str) { } /* platform dependend emulator initialization */ void plat_init(void) { flip_after_sync = 1; psp_menu_init(); in_psp_init(in_psp_defbinds); in_probe(); sound_init(); plat_get_data_dir(rom_fname_loaded, sizeof(rom_fname_loaded)); } /* platform dependend emulator deinitialization */ void plat_finish(void) { sound_deinit(); } /* display emulator status messages before holding emulation */ void plat_status_msg_busy_first(const char *msg) { plat_status_msg_busy_next(msg); } void plat_status_msg_busy_next(const char *msg) { plat_status_msg_clear(); pemu_finalize_frame("", msg); plat_video_flip(); emu_status_msg(""); reset_timing = 1; } /* clear status message area */ void plat_status_msg_clear(void) { // not needed since the screen buf is cleared through the GU } /* change the audio volume setting */ void plat_update_volume(int has_changed, int is_up) { } /* prepare for MD screen mode change */ void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count) { int h43 = (col_count >= 192 ? 320 : col_count); // ugh, mind GG... int v43 = (line_count >= 192 ? Pico.m.pal ? 240 : 224 : line_count); out_y = start_line; out_x = start_col; out_h = line_count; out_w = col_count; if (col_count == 248) // mind aspect ratio when blanking 1st column col_count = 256; switch (currentConfig.vscaling) { case EOPT_VSCALE_FULL: line_count = v43; vscale = (float)270/line_count; break; case EOPT_VSCALE_NOBORDER: vscale = (float)270/line_count; break; default: vscale = 1; break; } switch (currentConfig.scaling) { case EOPT_SCALE_43: hscale = (vscale*h43)/col_count; break; case EOPT_SCALE_STRETCH: hscale = (vscale*h43/2 + 480/2)/col_count; break; case EOPT_SCALE_WIDE: hscale = (float)480/col_count; break; default: hscale = vscale; break; } vidResetMode(); } /* render one frame in RGB */ void pemu_forced_frame(int no_scale, int do_emu) { Pico.m.dirtyPal = 1; if (!no_scale) no_scale = currentConfig.scaling == EOPT_SCALE_NONE; emu_cmn_forced_frame(no_scale, do_emu, g_screen_ptr); } /* change the platform output rendering */ void plat_video_toggle_renderer(int change, int is_menu_call) { change_renderer(change); if (is_menu_call) return; apply_renderer(); vidResetMode(); rendstatus_old = -1; if (PicoIn.AHW & PAHW_32X) emu_status_msg(renderer_names32x[get_renderer()]); else emu_status_msg(renderer_names[get_renderer()]); } /* set the buffer for emulator output rendering */ void plat_video_set_buffer(void *buf) { if (is_16bit_mode()) PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2); else PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch); } /* prepare for emulator output rendering */ void plat_video_loop_prepare(void) { apply_renderer(); vidResetMode(); } /* prepare for entering the emulator loop */ void pemu_loop_prep(void) { } /* terminate the emulator loop */ void pemu_loop_end(void) { pemu_sound_stop(); } /* resume from suspend: change to main menu if emu was running */ void emu_handle_resume(void) { if (engineState == PGS_Running) engineState = PGS_Menu; }