psp gfx scaling/etc stuff
[picodrive.git] / platform / psp / emu.c
1 #include <sys/stat.h>
2 #include <sys/types.h>
3 #include <sys/syslimits.h> // PATH_MAX
4
5 #include <pspthreadman.h>
6 #include <pspdisplay.h>
7 #include <psputils.h>
8 #include <pspgu.h>
9
10 #include "psp.h"
11 #include "menu.h"
12 #include "emu.h"
13 #include "../common/emu.h"
14 #include "../common/lprintf.h"
15 #include "../../Pico/PicoInt.h"
16
17 #ifdef BENCHMARK
18 #define OSD_FPS_X 380
19 #else
20 #define OSD_FPS_X 420
21 #endif
22
23 char romFileName[PATH_MAX];
24 unsigned char *PicoDraw2FB = (unsigned char *)VRAM_CACHED_STUFF + 8; // +8 to be able to skip border with 1 quadword..
25 int engineState;
26
27 static int combo_keys = 0, combo_acts = 0; // keys and actions which need button combos
28 static unsigned int noticeMsgTime = 0;
29 int reset_timing = 0; // do we need this?
30
31
32 static void blit2(const char *fps, const char *notice);
33 static void clearArea(int full);
34
35 void emu_noticeMsgUpdated(void)
36 {
37         noticeMsgTime = sceKernelGetSystemTimeLow();
38 }
39
40 void emu_getMainDir(char *dst, int len)
41 {
42         if (len > 0) *dst = 0;
43 }
44
45 static void osd_text(int x, const char *text, int is_active)
46 {
47         unsigned short *screen = is_active ? psp_video_get_active_fb() : psp_screen;
48         int len = strlen(text) * 8 / 2;
49         int *p, h;
50         void *tmp;
51         for (h = 0; h < 8; h++) {
52                 p = (int *) (screen+x+512*(264+h));
53                 p = (int *) ((int)p & ~3); // align
54                 memset32(p, 0, len);
55         }
56         if (is_active) { tmp = psp_screen; psp_screen = screen; } // nasty pointer tricks
57         emu_textOut16(x, 264, text);
58         if (is_active) psp_screen = tmp;
59 }
60
61 void emu_msg_cb(const char *msg)
62 {
63         osd_text(4, msg, 1);
64         noticeMsgTime = sceKernelGetSystemTimeLow() - 2000000;
65
66         /* assumption: emu_msg_cb gets called only when something slow is about to happen */
67         reset_timing = 1;
68 }
69
70 static void emu_msg_tray_open(void)
71 {
72         strcpy(noticeMsg, "CD tray opened");
73         noticeMsgTime = sceKernelGetSystemTimeLow();
74 }
75
76
77 void emu_Init(void)
78 {
79         // make dirs for saves, cfgs, etc.
80         mkdir("mds", 0777);
81         mkdir("srm", 0777);
82         mkdir("brm", 0777);
83         mkdir("cfg", 0777);
84
85         PicoInit();
86         PicoMessage = emu_msg_cb;
87         PicoMCDopenTray = emu_msg_tray_open;
88         PicoMCDcloseTray = menu_loop_tray;
89 }
90
91 void emu_Deinit(void)
92 {
93         // save SRAM
94         if ((currentConfig.EmuOpt & 1) && SRam.changed) {
95                 emu_SaveLoadGame(0, 1);
96                 SRam.changed = 0;
97         }
98
99         if (!(currentConfig.EmuOpt & 0x20)) {
100                 FILE *f = fopen(PicoConfigFile, "r+b");
101                 if (!f) emu_WriteConfig(0);
102                 else {
103                         // if we already have config, reload it, except last ROM
104                         fseek(f, sizeof(currentConfig.lastRomFile), SEEK_SET);
105                         fread(&currentConfig.EmuOpt, 1, sizeof(currentConfig) - sizeof(currentConfig.lastRomFile), f);
106                         fseek(f, 0, SEEK_SET);
107                         fwrite(&currentConfig, 1, sizeof(currentConfig), f);
108                         fflush(f);
109                         fclose(f);
110                 }
111         }
112
113         PicoExit();
114 }
115
116 void emu_setDefaultConfig(void)
117 {
118         memset(&currentConfig, 0, sizeof(currentConfig));
119         currentConfig.lastRomFile[0] = 0;
120         currentConfig.EmuOpt  = 0x1f | 0x680; // | confirm_save, cd_leds, 16bit rend
121         currentConfig.PicoOpt = 0x07 | 0xc00; // | cd_pcm, cd_cdda
122         currentConfig.PsndRate = 22050;
123         currentConfig.PicoRegion = 0; // auto
124         currentConfig.PicoAutoRgnOrder = 0x184; // US, EU, JP
125         currentConfig.Frameskip = -1; // auto
126         currentConfig.volume = 50;
127         currentConfig.CPUclock = 222;
128         currentConfig.KeyBinds[ 4] = 1<<0; // SACB RLDU
129         currentConfig.KeyBinds[ 6] = 1<<1;
130         currentConfig.KeyBinds[ 7] = 1<<2;
131         currentConfig.KeyBinds[ 5] = 1<<3;
132         currentConfig.KeyBinds[14] = 1<<4;
133         currentConfig.KeyBinds[13] = 1<<5;
134         currentConfig.KeyBinds[15] = 1<<6;
135         currentConfig.KeyBinds[ 3] = 1<<7;
136         currentConfig.KeyBinds[ 8] = 1<<27; // save state
137         currentConfig.KeyBinds[ 9] = 1<<28; // load state
138         currentConfig.PicoCDBuffers = 0;
139         currentConfig.scaling = 1; // bilinear filtering for psp
140         currentConfig.scale = currentConfig.hscale32 = currentConfig.hscale40 = 1.0;
141 }
142
143
144 extern void amips_clut(unsigned short *dst, unsigned char *src, unsigned short *pal, int count);
145
146 struct Vertex
147 {
148         short u,v;
149         short x,y,z;
150 };
151
152 static struct Vertex __attribute__((aligned(4))) g_vertices[2];
153 static unsigned short __attribute__((aligned(16))) localPal[0x100];
154 static int dynamic_palette = 0, need_pal_upload = 0, blit_16bit_mode = 0;
155 static int fbimg_offs = 0;
156
157 static void set_scaling_params(void)
158 {
159         int src_width, fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs;
160         g_vertices[0].x = g_vertices[0].y =
161         g_vertices[0].z = g_vertices[1].z = 0;
162
163         fbimg_height = (int)(240.0 * currentConfig.scale + 0.5);
164         if (Pico.video.reg[12] & 1) {
165                 fbimg_width = (int)(320.0 * currentConfig.scale * currentConfig.hscale40 + 0.5);
166                 src_width = 320;
167         } else {
168                 fbimg_width = (int)(256.0 * currentConfig.scale * currentConfig.hscale32 + 0.5);
169                 src_width = 256;
170         }
171
172         if (fbimg_width >= 480) {
173                 g_vertices[0].u = (fbimg_width-480)/2;
174                 g_vertices[1].u = src_width - (fbimg_width-480)/2;
175                 fbimg_width = 480;
176                 fbimg_xoffs = 0;
177         } else {
178                 g_vertices[0].u = 0;
179                 g_vertices[1].u = src_width;
180                 fbimg_xoffs = 240 - fbimg_width/2;
181         }
182
183         if (fbimg_height >= 272) {
184                 g_vertices[0].v = (fbimg_height-272)/2;
185                 g_vertices[1].v = 240 - (fbimg_height-272)/2;
186                 fbimg_height = 272;
187                 fbimg_yoffs = 0;
188         } else {
189                 g_vertices[0].v = 0;
190                 g_vertices[1].v = 240;
191                 fbimg_yoffs = 136 - fbimg_height/2;
192         }
193
194         g_vertices[1].x = fbimg_width;
195         g_vertices[1].y = fbimg_height;
196         if (fbimg_xoffs < 0) fbimg_xoffs = 0;
197         if (fbimg_yoffs < 0) fbimg_yoffs = 0;
198         fbimg_offs = (fbimg_yoffs*512 + fbimg_xoffs) * 2; // dst is always 16bit
199
200         lprintf("set_scaling_params:\n");
201         lprintf("offs: %i, %i\n", fbimg_xoffs, fbimg_yoffs);
202         lprintf("xy0, xy1: %i, %i; %i, %i\n", g_vertices[0].x, g_vertices[0].y, g_vertices[1].x, g_vertices[1].y);
203         lprintf("uv0, uv1: %i, %i; %i, %i\n", g_vertices[0].u, g_vertices[0].v, g_vertices[1].u, g_vertices[1].v);
204 }
205
206 static void do_slowmode_pal(void)
207 {
208         unsigned int *spal=(void *)Pico.cram;
209         unsigned int *dpal=(void *)localPal;
210         int i;
211
212         for (i = 0x3f/2; i >= 0; i--)
213                 dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
214
215         if (Pico.video.reg[0xC]&8) // shadow/hilight?
216         {
217                 // shadowed pixels
218                 for (i = 0x3f/2; i >= 0; i--)
219                         dpal[0x20|i] = dpal[0x60|i] = (spal[i]>>1)&0x738e738e;
220                 // hilighted pixels
221                 for (i = 0x3f; i >= 0; i--) {
222                         int t=localPal[i]&0xe71c;t+=0x4208;
223                         if (t&0x20) t|=0x1c;
224                         if (t&0x800) t|=0x700;
225                         if (t&0x10000) t|=0xe000;
226                         t&=0xe71c;
227                         localPal[0x80|i]=(unsigned short)t;
228                 }
229                 localPal[0xe0] = 0;
230         }
231         Pico.m.dirtyPal = 0;
232         need_pal_upload = 1;
233 }
234
235 static void do_slowmode_lines(int line_to)
236 {
237         int line = 0, line_len = (Pico.video.reg[12]&1) ? 320 : 256;
238         unsigned short *dst = (unsigned short *)VRAM_STUFF + 512*240/2;
239         unsigned char  *src = (unsigned char  *)VRAM_CACHED_STUFF + 16;
240         if (!(Pico.video.reg[1]&8)) { line = 8; dst += 512*8; src += 512*8; }
241
242         for (; line < line_to; line++, dst+=512, src+=512)
243                 amips_clut(dst, src, localPal, line_len);
244 }
245
246 static void EmuScanPrepare(void)
247 {
248         HighCol = (unsigned char *)VRAM_CACHED_STUFF + 8;
249         if (!(Pico.video.reg[1]&8)) HighCol += 8*512;
250
251         dynamic_palette = 0;
252         if (Pico.m.dirtyPal)
253                 do_slowmode_pal();
254 }
255
256 static int EmuScanSlow(unsigned int num, void *sdata)
257 {
258         if (!(Pico.video.reg[1]&8)) num += 8;
259
260         if (Pico.m.dirtyPal) {
261                 if (!dynamic_palette) {
262                         do_slowmode_lines(num);
263                         dynamic_palette = 1;
264                 }
265                 do_slowmode_pal();
266         }
267
268         if (dynamic_palette) {
269                 int line_len = (Pico.video.reg[12]&1) ? 320 : 256;
270                 void *dst = (char *)VRAM_STUFF + 512*240 + 512*2*num;
271                 amips_clut(dst, HighCol + 8, localPal, line_len);
272         } else
273                 HighCol = (unsigned char *)VRAM_CACHED_STUFF + (num+1)*512 + 8;
274
275         return 0;
276 }
277
278 static void blitscreen_clut(void)
279 {
280         int offs = fbimg_offs;
281         offs += (psp_screen == VRAM_FB0) ? VRAMOFFS_FB0 : VRAMOFFS_FB1;
282
283         sceKernelDcacheWritebackAll();
284
285         sceGuSync(0,0); // sync with prev
286         sceGuStart(GU_DIRECT, guCmdList);
287         sceGuDrawBuffer(GU_PSM_5650, (void *)offs, 512); // point to back buffer
288
289         if (dynamic_palette)
290         {
291                 if (!blit_16bit_mode) {
292                         sceGuTexMode(GU_PSM_5650, 0, 0, 0);
293                         sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 512*240);
294
295                         blit_16bit_mode = 1;
296                 }
297         }
298         else
299         {
300                 if (blit_16bit_mode) {
301                         sceGuClutMode(GU_PSM_5650,0,0xff,0);
302                         sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image
303                         sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 16);
304                         blit_16bit_mode = 0;
305                 }
306
307                 if ((PicoOpt&0x10) && Pico.m.dirtyPal)
308                 {
309                         int i, *dpal = (void *)localPal, *spal = (int *)Pico.cram;
310                         for (i = 0x3f/2; i >= 0; i--)
311                                 dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
312                         localPal[0xe0] = 0;
313                         Pico.m.dirtyPal = 0;
314                         need_pal_upload = 1;
315                 }
316
317                 if (need_pal_upload) {
318                         need_pal_upload = 0;
319                         sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256)
320                 }
321         }
322
323 #if 1
324         if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x)
325         {
326                 struct Vertex* vertices;
327                 int x;
328
329                 #define SLICE_WIDTH 32
330                 for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH)
331                 {
332                         // render sprite
333                         vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex));
334                         memcpy(vertices, g_vertices, 2 * sizeof(struct Vertex));
335                         vertices[0].u = vertices[0].x = x;
336                         vertices[1].u = vertices[1].x = x + SLICE_WIDTH;
337                         sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices);
338                 }
339                 // lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only
340         }
341         else
342 #endif
343                 sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices);
344
345         sceGuFinish();
346 }
347
348
349 static void cd_leds(void)
350 {
351         static int old_reg = 0;
352         unsigned int col_g, col_r, *p;
353
354         if (!((Pico_mcd->s68k_regs[0] ^ old_reg) & 3)) return; // no change
355         old_reg = Pico_mcd->s68k_regs[0];
356
357         p = (unsigned int *)((short *)psp_screen + 512*2+4+2);
358         col_g = (old_reg & 2) ? 0x06000600 : 0;
359         col_r = (old_reg & 1) ? 0xc000c000 : 0;
360         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
361         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2;
362         *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;
363 }
364
365
366 static void dbg_text(void)
367 {
368         int *p, h, len;
369         char text[128];
370
371         sprintf(text, "sl: %i, 16b: %i", g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x, blit_16bit_mode);
372         len = strlen(text) * 8 / 2;
373         for (h = 0; h < 8; h++) {
374                 p = (int *) ((unsigned short *) psp_screen+2+512*(256+h));
375                 p = (int *) ((int)p & ~3); // align
376                 memset32(p, 0, len);
377         }
378         emu_textOut16(2, 256, text);
379 }
380
381
382 /* called after rendering is done, but frame emulation is not finished */
383 void blit1(void)
384 {
385         if (PicoOpt&0x10)
386         {
387                 int i;
388                 unsigned char *pd;
389                 // clear top and bottom trash
390                 for (pd = PicoDraw2FB+8, i = 8; i > 0; i--, pd += 512)
391                         memset32((int *)pd, 0xe0e0e0e0, 320/4);
392                 for (pd = PicoDraw2FB+512*232+8, i = 8; i > 0; i--, pd += 512)
393                         memset32((int *)pd, 0xe0e0e0e0, 320/4);
394         }
395
396         blitscreen_clut();
397 }
398
399
400 static void blit2(const char *fps, const char *notice)
401 {
402         int emu_opt = currentConfig.EmuOpt;
403
404         sceGuSync(0,0);
405
406         if (notice || (emu_opt & 2)) {
407                 if (notice)      osd_text(4, notice, 0);
408                 if (emu_opt & 2) osd_text(OSD_FPS_X, fps, 0);
409         }
410
411         dbg_text();
412
413         if ((emu_opt & 0x400) && (PicoMCD & 1))
414                 cd_leds();
415
416         psp_video_flip(0);
417 }
418
419 // clears whole screen or just the notice area (in all buffers)
420 static void clearArea(int full)
421 {
422         if (full) {
423                 memset32(psp_screen, 0, 512*272*2/4);
424                 psp_video_flip(0);
425                 memset32(psp_screen, 0, 512*272*2/4);
426                 memset32(VRAM_CACHED_STUFF, 0xe0e0e0e0, 512*240/4);
427                 memset32((int *)VRAM_CACHED_STUFF+512*240/4, 0, 512*240*2/4);
428         } else {
429                 void *fb = psp_video_get_active_fb();
430                 memset32((int *)((char *)psp_screen + 512*264*2), 0, 512*8*2/4);
431                 memset32((int *)((char *)fb         + 512*264*2), 0, 512*8*2/4);
432         }
433 }
434
435 static void vidResetMode(void)
436 {
437         // setup GU
438         sceGuSync(0,0); // sync with prev
439         sceGuStart(GU_DIRECT, guCmdList);
440
441         sceGuClutMode(GU_PSM_5650,0,0xff,0);
442         sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image
443         sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
444         if (currentConfig.scaling)
445              sceGuTexFilter(GU_LINEAR, GU_LINEAR);
446         else sceGuTexFilter(GU_NEAREST, GU_NEAREST);
447         sceGuTexScale(1.0f,1.0f);
448         sceGuTexOffset(0.0f,0.0f);
449
450         sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 16);
451
452         // slow rend.
453         PicoDrawSetColorFormat(-1);
454         PicoScan = EmuScanSlow;
455
456         localPal[0xe0] = 0;
457         Pico.m.dirtyPal = 1;
458         blit_16bit_mode = dynamic_palette = 0;
459
460         sceGuFinish();
461         set_scaling_params();
462         sceGuSync(0,0);
463 }
464
465 /*
466 static void updateSound(int len)
467 {
468         if (PicoOpt&8) len<<=1;
469
470         // TODO..
471 }
472 */
473
474 static void SkipFrame(void)
475 {
476         PicoSkipFrame=1;
477         PicoFrame();
478         PicoSkipFrame=0;
479 }
480
481 void emu_forcedFrame(void)
482 {
483         int po_old = PicoOpt;
484         int eo_old = currentConfig.EmuOpt;
485
486         PicoOpt &= ~0x0010;
487         PicoOpt |=  0x4080; // soft_scale | acc_sprites
488         currentConfig.EmuOpt |= 0x80;
489
490         vidResetMode();
491         memset32(VRAM_CACHED_STUFF, 0xe0e0e0e0, 512*8/4); // borders
492         memset32((int *)VRAM_CACHED_STUFF + 512*232/4, 0xe0e0e0e0, 512*8/4);
493
494         PicoDrawSetColorFormat(-1);
495         PicoScan = EmuScanSlow;
496         EmuScanPrepare();
497         PicoFrameDrawOnly();
498         blit1();
499         sceGuSync(0,0);
500
501         PicoOpt = po_old;
502         currentConfig.EmuOpt = eo_old;
503 }
504
505
506 static void RunEvents(unsigned int which)
507 {
508         if (which & 0x1800) // save or load (but not both)
509         {
510                 int do_it = 1;
511
512                 if ( emu_checkSaveFile(state_slot) &&
513                                 (( (which & 0x1000) && (currentConfig.EmuOpt & 0x800)) || // load
514                                  (!(which & 0x1000) && (currentConfig.EmuOpt & 0x200))) ) // save
515                 {
516                         int keys;
517                         blit2("", (which & 0x1000) ? "LOAD STATE? (X=yes, O=no)" : "OVERWRITE SAVE? (X=yes, O=no)");
518                         while( !((keys = psp_pad_read(1)) & (BTN_X|BTN_CIRCLE)) )
519                                 psp_msleep(50);
520                         if (keys & BTN_CIRCLE) do_it = 0;
521                         while(  ((keys = psp_pad_read(1)) & (BTN_X|BTN_CIRCLE)) ) // wait for release
522                                 psp_msleep(50);
523                         clearArea(0);
524                 }
525
526                 if (do_it)
527                 {
528                         osd_text(4, (which & 0x1000) ? "LOADING GAME" : "SAVING GAME", 1);
529                         PicoStateProgressCB = emu_msg_cb;
530                         emu_SaveLoadGame((which & 0x1000) >> 12, 0);
531                         PicoStateProgressCB = NULL;
532                         psp_msleep(0);
533                 }
534
535                 reset_timing = 1;
536         }
537         if (which & 0x0400) // switch renderer
538         {
539                 if (PicoOpt&0x10) { PicoOpt&=~0x10; currentConfig.EmuOpt |=  0x80; }
540                 else              { PicoOpt|= 0x10; currentConfig.EmuOpt &= ~0x80; }
541
542                 vidResetMode();
543
544                 if (PicoOpt&0x10) {
545                         strcpy(noticeMsg, " 8bit fast renderer");
546                 } else if (currentConfig.EmuOpt&0x80) {
547                         strcpy(noticeMsg, "16bit accurate renderer");
548                 } else {
549                         strcpy(noticeMsg, " 8bit accurate renderer");
550                 }
551
552                 noticeMsgTime = sceKernelGetSystemTimeLow();
553         }
554         if (which & 0x0300)
555         {
556                 if(which&0x0200) {
557                         state_slot -= 1;
558                         if(state_slot < 0) state_slot = 9;
559                 } else {
560                         state_slot += 1;
561                         if(state_slot > 9) state_slot = 0;
562                 }
563                 sprintf(noticeMsg, "SAVE SLOT %i [%s]", state_slot, emu_checkSaveFile(state_slot) ? "USED" : "FREE");
564                 noticeMsgTime = sceKernelGetSystemTimeLow();
565         }
566 }
567
568 static void updateKeys(void)
569 {
570         unsigned int keys, allActions[2] = { 0, 0 }, events;
571         static unsigned int prevEvents = 0;
572         int i;
573
574         keys = psp_pad_read(0);
575         if (keys & PSP_CTRL_HOME)
576                 sceDisplayWaitVblankStart();
577
578         if (keys & BTN_SELECT)
579                 engineState = PGS_Menu;
580
581         keys &= CONFIGURABLE_KEYS;
582
583         for (i = 0; i < 32; i++)
584         {
585                 if (keys & (1 << i))
586                 {
587                         int pl, acts = currentConfig.KeyBinds[i];
588                         if (!acts) continue;
589                         pl = (acts >> 16) & 1;
590                         if (combo_keys & (1 << i))
591                         {
592                                 int u = i+1, acts_c = acts & combo_acts;
593                                 // let's try to find the other one
594                                 if (acts_c)
595                                         for (; u < 32; u++)
596                                                 if ( (currentConfig.KeyBinds[u] & acts_c) && (keys & (1 << u)) ) {
597                                                         allActions[pl] |= acts_c;
598                                                         keys &= ~((1 << i) | (1 << u));
599                                                         break;
600                                                 }
601                                 // add non-combo actions if combo ones were not found
602                                 if (!acts_c || u == 32)
603                                         allActions[pl] |= acts & ~combo_acts;
604                         } else {
605                                 allActions[pl] |= acts;
606                         }
607                 }
608         }
609
610         PicoPad[0] = (unsigned short) allActions[0];
611         PicoPad[1] = (unsigned short) allActions[1];
612
613         events = (allActions[0] | allActions[1]) >> 16;
614
615         // volume is treated in special way and triggered every frame
616         if ((events & 0x6000) && PsndOut != NULL)
617         {
618                 int vol = currentConfig.volume;
619                 if (events & 0x2000) {
620                         if (vol < 100) vol++;
621                 } else {
622                         if (vol >   0) vol--;
623                 }
624                 // FrameworkAudio_SetVolume(vol, vol); // TODO
625                 sprintf(noticeMsg, "VOL: %02i ", vol);
626                 noticeMsgTime = sceKernelGetSystemTimeLow();
627                 currentConfig.volume = vol;
628         }
629
630         events &= ~prevEvents;
631         if (events) RunEvents(events);
632         if (movie_data) emu_updateMovie();
633
634         prevEvents = (allActions[0] | allActions[1]) >> 16;
635 }
636
637 static void find_combos(void)
638 {
639         int act, u;
640
641         // find out which keys and actions are combos
642         combo_keys = combo_acts = 0;
643         for (act = 0; act < 32; act++)
644         {
645                 int keyc = 0;
646                 if (act == 16 || act == 17) continue; // player2 flag
647                 for (u = 0; u < 32; u++)
648                 {
649                         if (currentConfig.KeyBinds[u] & (1 << act)) keyc++;
650                 }
651                 if (keyc > 1)
652                 {
653                         // loop again and mark those keys and actions as combo
654                         for (u = 0; u < 32; u++)
655                         {
656                                 if (currentConfig.KeyBinds[u] & (1 << act)) {
657                                         combo_keys |= 1 << u;
658                                         combo_acts |= 1 << act;
659                                 }
660                         }
661                 }
662         }
663 }
664
665
666 static void simpleWait(unsigned int until)
667 {
668         unsigned int tval;
669         int diff;
670
671         tval = sceKernelGetSystemTimeLow();
672         diff = (int)until - (int)tval;
673         if (diff >= 512 && diff < 100*1024)
674                 sceKernelDelayThread(diff);
675 }
676
677 void emu_Loop(void)
678 {
679         //static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;
680         char fpsbuff[24]; // fps count c string
681         unsigned int tval, tval_prev = 0, tval_thissec = 0; // timing
682         int frames_done = 0, frames_shown = 0, oldmodes = 0;
683         int target_fps, target_frametime, lim_time, tval_diff, i;
684         char *notice = NULL;
685
686         lprintf("entered emu_Loop()\n");
687
688         fpsbuff[0] = 0;
689
690         if (currentConfig.CPUclock != psp_get_cpu_clock()) {
691                 lprintf("setting cpu clock to %iMHz... ", currentConfig.CPUclock);
692                 i = psp_set_cpu_clock(currentConfig.CPUclock);
693                 lprintf(i ? "failed\n" : "done\n");
694                 currentConfig.CPUclock = psp_get_cpu_clock();
695         }
696
697         // make sure we are in correct mode
698         vidResetMode();
699         clearArea(1);
700         Pico.m.dirtyPal = 1;
701         oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;
702         find_combos();
703
704         // pal/ntsc might have changed, reset related stuff
705         target_fps = Pico.m.pal ? 50 : 60;
706         target_frametime = Pico.m.pal ? (1000000<<8)/50 : (1000000<<8)/60+1;
707         reset_timing = 1;
708
709         // prepare CD buffer
710         if (PicoMCD & 1) PicoCDBufferInit();
711
712         // prepare sound stuff
713         PsndOut = NULL;
714 #if 0 // TODO
715         if (currentConfig.EmuOpt & 4)
716         {
717                 int ret, snd_excess_add, stereo;
718                 if (PsndRate != PsndRate_old || (PicoOpt&0x0b) != (PicoOpt_old&0x0b) || Pico.m.pal != pal_old) {
719                         sound_rerate(Pico.m.frame_count ? 1 : 0);
720                 }
721                 stereo=(PicoOpt&8)>>3;
722                 snd_excess_add = ((PsndRate - PsndLen*target_fps)<<16) / target_fps;
723                 snd_cbuf_samples = (PsndRate<<stereo) * 16 / target_fps;
724                 lprintf("starting audio: %i len: %i (ex: %04x) stereo: %i, pal: %i\n",
725                         PsndRate, PsndLen, snd_excess_add, stereo, Pico.m.pal);
726                 ret = FrameworkAudio_Init(PsndRate, snd_cbuf_samples, stereo);
727                 if (ret != 0) {
728                         lprintf("FrameworkAudio_Init() failed: %i\n", ret);
729                         sprintf(noticeMsg, "sound init failed (%i), snd disabled", ret);
730                         noticeMsgTime = sceKernelGetSystemTimeLow();
731                         currentConfig.EmuOpt &= ~4;
732                 } else {
733                         FrameworkAudio_SetVolume(currentConfig.volume, currentConfig.volume);
734                         PicoWriteSound = updateSound;
735                         snd_cbuff = FrameworkAudio_56448Buffer();
736                         PsndOut = snd_cbuff + snd_cbuf_samples / 2; // start writing at the middle
737                         snd_all_samples = 0;
738                         PsndRate_old = PsndRate;
739                         PicoOpt_old  = PicoOpt;
740                         pal_old = Pico.m.pal;
741                 }
742         }
743 #endif
744
745         // loop?
746         while (engineState == PGS_Running)
747         {
748                 int modes;
749
750                 tval = sceKernelGetSystemTimeLow();
751                 if (reset_timing || tval < tval_prev) {
752                         //stdbg("timing reset");
753                         reset_timing = 0;
754                         tval_thissec = tval;
755                         frames_shown = frames_done = 0;
756                 }
757
758                 // show notice message?
759                 if (noticeMsgTime) {
760                         static int noticeMsgSum;
761                         if (tval - noticeMsgTime > 2000000) { // > 2.0 sec
762                                 noticeMsgTime = 0;
763                                 clearArea(0);
764                                 notice = 0;
765                         } else {
766                                 int sum = noticeMsg[0]+noticeMsg[1]+noticeMsg[2];
767                                 if (sum != noticeMsgSum) { clearArea(0); noticeMsgSum = sum; }
768                                 notice = noticeMsg;
769                         }
770                 }
771
772                 // check for mode changes
773                 modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8);
774                 if (modes != oldmodes) {
775                         oldmodes = modes;
776                         clearArea(1);
777                         set_scaling_params();
778                 }
779
780                 // second passed?
781                 if (tval - tval_thissec >= 1000000)
782                 {
783 #ifdef BENCHMARK
784                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];
785                         if(++bench == 10) {
786                                 bench = 0;
787                                 bench_fps_s = bench_fps;
788                                 bf[bfp++ & 3] = bench_fps;
789                                 bench_fps = 0;
790                         }
791                         bench_fps += frames_shown;
792                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);
793 #else
794                         if(currentConfig.EmuOpt & 2)
795                                 sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);
796 #endif
797                         tval_thissec += 1000000;
798
799                         if (currentConfig.Frameskip < 0) {
800                                 frames_done  -= target_fps; if (frames_done  < 0) frames_done  = 0;
801                                 frames_shown -= target_fps; if (frames_shown < 0) frames_shown = 0;
802                                 if (frames_shown > frames_done) frames_shown = frames_done;
803                         } else {
804                                 frames_done = frames_shown = 0;
805                         }
806                 }
807 #ifdef PFRAMES
808                 sprintf(fpsbuff, "%i", Pico.m.frame_count);
809 #endif
810
811                 tval_prev = tval;
812                 lim_time = (frames_done+1) * target_frametime;
813                 if (currentConfig.Frameskip >= 0) // frameskip enabled
814                 {
815                         for (i = 0; i < currentConfig.Frameskip; i++) {
816                                 updateKeys();
817                                 SkipFrame(); frames_done++;
818                                 if (PsndOut) { // do framelimitting if sound is enabled
819                                         int tval_diff;
820                                         tval = sceKernelGetSystemTimeLow();
821                                         tval_diff = (int)(tval - tval_thissec) << 8;
822                                         if (tval_diff < lim_time) // we are too fast
823                                                 simpleWait(tval + ((lim_time - tval_diff)>>8));
824                                 }
825                                 lim_time += target_frametime;
826                         }
827                 }
828                 else // auto frameskip
829                 {
830                         int tval_diff;
831                         tval = sceKernelGetSystemTimeLow();
832                         tval_diff = (int)(tval - tval_thissec) << 8;
833                         if (tval_diff > lim_time)
834                         {
835                                 // no time left for this frame - skip
836                                 if (tval_diff - lim_time >= (300000<<8)) {
837                                         /* something caused a slowdown for us (disk access? cache flush?)
838                                          * try to recover by resetting timing... */
839                                         reset_timing = 1;
840                                         continue;
841                                 }
842                                 updateKeys();
843                                 SkipFrame(); frames_done++;
844                                 continue;
845                         }
846                 }
847
848                 updateKeys();
849
850                 if (!(PicoOpt&0x10))
851                         EmuScanPrepare();
852
853                 PicoFrame();
854
855                 blit2(fpsbuff, notice);
856
857                 // check time
858                 tval = sceKernelGetSystemTimeLow();
859                 tval_diff = (int)(tval - tval_thissec) << 8;
860
861                 if (currentConfig.Frameskip < 0 && tval_diff - lim_time >= (300000<<8)) // slowdown detection
862                         reset_timing = 1;
863                 else if (PsndOut != NULL || currentConfig.Frameskip < 0)
864                 {
865                         // sleep if we are still too fast
866                         if (tval_diff < lim_time)
867                         {
868                                 // we are too fast
869                                 simpleWait(tval + ((lim_time - tval_diff) >> 8));
870                         }
871                 }
872
873                 frames_done++; frames_shown++;
874         }
875
876
877         if (PicoMCD & 1) PicoCDBufferFree();
878 /*
879         if (PsndOut != NULL) {
880                 PsndOut = snd_cbuff = NULL;
881                 FrameworkAudio_Close();
882         }
883 */
884         // save SRAM
885         if ((currentConfig.EmuOpt & 1) && SRam.changed) {
886                 emu_msg_cb("Writing SRAM/BRAM..");
887                 emu_SaveLoadGame(0, 1);
888                 SRam.changed = 0;
889         }
890 }
891
892
893 void emu_ResetGame(void)
894 {
895         PicoReset(0);
896         reset_timing = 1;
897 }
898