restore defaults function + some refactoring
[libpicofe.git] / gp2x / emu.c
1 // (c) Copyright 2006-2009 notaz, All rights reserved.\r
2 // Free for non-commercial use.\r
3 \r
4 // For commercial use, separate licencing terms must be obtained.\r
5 \r
6 #include <stdio.h>\r
7 #include <stdlib.h>\r
8 #include <sys/time.h>\r
9 #include <stdarg.h>\r
10 \r
11 #include "plat_gp2x.h"\r
12 #include "soc.h"\r
13 #include "../common/plat.h"\r
14 #include "../common/menu.h"\r
15 #include "../common/arm_utils.h"\r
16 #include "../common/fonts.h"\r
17 #include "../common/emu.h"\r
18 #include "../common/config.h"\r
19 #include "../linux/sndout_oss.h"\r
20 #include "version.h"\r
21 \r
22 #include <pico/pico_int.h>\r
23 #include <pico/patch.h>\r
24 #include <pico/sound/mix.h>\r
25 #include <zlib/zlib.h>\r
26 \r
27 //#define PFRAMES\r
28 \r
29 #ifdef BENCHMARK\r
30 #define OSD_FPS_X 220\r
31 #else\r
32 #define OSD_FPS_X 260\r
33 #endif\r
34 \r
35 \r
36 extern int crashed_940;\r
37 \r
38 static short __attribute__((aligned(4))) sndBuffer[2*44100/50];\r
39 static struct timeval noticeMsgTime = { 0, 0 }; // when started showing\r
40 static int osd_fps_x;\r
41 static int gp2x_old_gamma = 100;\r
42 static char noticeMsg[40];\r
43 static unsigned char PicoDraw2FB_[(8+320) * (8+240+8)];\r
44 unsigned char *PicoDraw2FB = PicoDraw2FB_;\r
45 \r
46 \r
47 void plat_status_msg(const char *format, ...)\r
48 {\r
49         va_list vl;\r
50 \r
51         va_start(vl, format);\r
52         vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);\r
53         va_end(vl);\r
54 \r
55         gettimeofday(&noticeMsgTime, 0);\r
56 }\r
57 \r
58 int plat_get_root_dir(char *dst, int len)\r
59 {\r
60         extern char **g_argv;\r
61         int j;\r
62 \r
63         strncpy(dst, g_argv[0], len);\r
64         len -= 32; // reserve\r
65         if (len < 0) len = 0;\r
66         dst[len] = 0;\r
67         for (j = strlen(dst); j > 0; j--)\r
68                 if (dst[j] == '/') { dst[j+1] = 0; break; }\r
69 \r
70         return j + 1;\r
71 }\r
72 \r
73 \r
74 static void scaling_update(void)\r
75 {\r
76         PicoOpt &= ~(POPT_DIS_32C_BORDER|POPT_EN_SOFTSCALE);\r
77         switch (currentConfig.scaling) {\r
78                 default:break;\r
79                 case EOPT_SCALE_HW_H:\r
80                 case EOPT_SCALE_HW_HV:\r
81                         PicoOpt |= POPT_DIS_32C_BORDER;\r
82                         break;\r
83                 case EOPT_SCALE_SW_H:\r
84                         PicoOpt |= POPT_EN_SOFTSCALE;\r
85                         break;\r
86         }\r
87 }\r
88 \r
89 \r
90 void pemu_prep_defconfig(void)\r
91 {\r
92         gp2x_soc_t soc;\r
93 \r
94         memset(&defaultConfig, 0, sizeof(defaultConfig));\r
95         defaultConfig.EmuOpt    = 0x9d | EOPT_RAM_TIMINGS | 0x600; // | <- confirm_save, cd_leds\r
96         defaultConfig.s_PicoOpt = 0x0f | POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_SVP_DRC|POPT_ACC_SPRITES;\r
97         defaultConfig.s_PsndRate = 44100;\r
98         defaultConfig.s_PicoRegion = 0; // auto\r
99         defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP\r
100         defaultConfig.s_PicoCDBuffers = 0;\r
101         defaultConfig.Frameskip = -1; // auto\r
102         defaultConfig.CPUclock = default_cpu_clock;\r
103         defaultConfig.volume = 50;\r
104         defaultConfig.gamma = 100;\r
105         defaultConfig.scaling = 0;\r
106         defaultConfig.turbo_rate = 15;\r
107 \r
108         soc = soc_detect();\r
109         if (soc == SOCID_MMSP2)\r
110                 defaultConfig.s_PicoOpt |= POPT_EXT_FM;\r
111 }\r
112 \r
113 static void osd_text(int x, int y, const char *text)\r
114 {\r
115         int len = strlen(text)*8;\r
116         int *p, i, h, offs;\r
117 \r
118         if ((PicoOpt&0x10)||!(currentConfig.EmuOpt&0x80)) {\r
119                 len = (len+3) >> 2;\r
120                 for (h = 0; h < 8; h++) {\r
121                         offs = (x + g_screen_width * (y+h)) & ~3;\r
122                         p = (int *) ((char *)g_screen_ptr + offs);\r
123                         for (i = len; i; i--, p++)\r
124                                 *p = 0xe0e0e0e0;\r
125                 }\r
126                 emu_textOut8(x, y, text);\r
127         } else {\r
128                 len = (len+1) >> 1;\r
129                 for (h = 0; h < 8; h++) {\r
130                         offs = (x + g_screen_width * (y+h)) & ~1;\r
131                         p = (int *) ((short *)g_screen_ptr + offs);\r
132                         for (i = len; i; i--, p++)\r
133                                 *p = (*p >> 2) & 0x39e7;\r
134                 }\r
135                 emu_textOut16(x, y, text);\r
136         }\r
137 }\r
138 \r
139 static void draw_cd_leds(void)\r
140 {\r
141         int old_reg;\r
142         old_reg = Pico_mcd->s68k_regs[0];\r
143 \r
144         if ((PicoOpt & POPT_ALT_RENDERER) || !(currentConfig.EmuOpt & EOPT_16BPP)) {\r
145                 // 8-bit modes\r
146                 unsigned int col_g = (old_reg & 2) ? 0xc0c0c0c0 : 0xe0e0e0e0;\r
147                 unsigned int col_r = (old_reg & 1) ? 0xd0d0d0d0 : 0xe0e0e0e0;\r
148                 *(unsigned int *)((char *)g_screen_ptr + 320*2+ 4) =\r
149                 *(unsigned int *)((char *)g_screen_ptr + 320*3+ 4) =\r
150                 *(unsigned int *)((char *)g_screen_ptr + 320*4+ 4) = col_g;\r
151                 *(unsigned int *)((char *)g_screen_ptr + 320*2+12) =\r
152                 *(unsigned int *)((char *)g_screen_ptr + 320*3+12) =\r
153                 *(unsigned int *)((char *)g_screen_ptr + 320*4+12) = col_r;\r
154         } else {\r
155                 // 16-bit modes\r
156                 unsigned int *p = (unsigned int *)((short *)g_screen_ptr + 320*2+4);\r
157                 unsigned int col_g = (old_reg & 2) ? 0x06000600 : 0;\r
158                 unsigned int col_r = (old_reg & 1) ? 0xc000c000 : 0;\r
159                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 320/2 - 12/2;\r
160                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 320/2 - 12/2;\r
161                 *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r;\r
162         }\r
163 }\r
164 \r
165 static void draw_pico_ptr(void)\r
166 {\r
167         unsigned short *p = (unsigned short *)g_screen_ptr;\r
168 \r
169         // only if pen enabled and for 16bit modes\r
170         if (pico_inp_mode == 0 || (PicoOpt&0x10) || !(currentConfig.EmuOpt&0x80)) return;\r
171 \r
172         if (!(Pico.video.reg[12]&1) && !(PicoOpt&POPT_DIS_32C_BORDER))\r
173                 p += 32;\r
174 \r
175         p += 320 * (pico_pen_y + PICO_PEN_ADJUST_Y);\r
176         p += pico_pen_x + PICO_PEN_ADJUST_X;\r
177         p[0]   ^= 0xffff;\r
178         p[319] ^= 0xffff;\r
179         p[320] ^= 0xffff;\r
180         p[321] ^= 0xffff;\r
181         p[640] ^= 0xffff;\r
182 }\r
183 \r
184 static int EmuScanBegin16(unsigned int num)\r
185 {\r
186         if (!(Pico.video.reg[1]&8)) num += 8;\r
187         DrawLineDest = (unsigned short *) g_screen_ptr + g_screen_width * num;\r
188 \r
189         return 0;\r
190 }\r
191 \r
192 static int EmuScanBegin8(unsigned int num)\r
193 {\r
194         if (!(Pico.video.reg[1]&8)) num += 8;\r
195         DrawLineDest = (unsigned char *)  g_screen_ptr + g_screen_width * num;\r
196 \r
197         return 0;\r
198 }\r
199 \r
200 int localPal[0x100];\r
201 static void (*vidCpyM2)(void *dest, void *src) = NULL;\r
202 \r
203 static void blit(const char *fps, const char *notice)\r
204 {\r
205         int emu_opt = currentConfig.EmuOpt;\r
206 \r
207         if (PicoOpt & POPT_ALT_RENDERER)\r
208         {\r
209                 // 8bit fast renderer\r
210                 if (Pico.m.dirtyPal) {\r
211                         Pico.m.dirtyPal = 0;\r
212                         vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
213                         // feed new palette to our device\r
214                         gp2x_video_setpalette(localPal, 0x40);\r
215                 }\r
216                 // a hack for VR\r
217                 if (PicoRead16Hook == PicoSVPRead16)\r
218                         memset32((int *)(PicoDraw2FB+328*8+328*223), 0xe0e0e0e0, 328);\r
219                 // do actual copy\r
220                 vidCpyM2((unsigned char *)g_screen_ptr+320*8, PicoDraw2FB+328*8);\r
221         }\r
222         else if (!(emu_opt & EOPT_16BPP))\r
223         {\r
224                 // 8bit accurate renderer\r
225                 if (Pico.m.dirtyPal)\r
226                 {\r
227                         int pallen = 0xc0;\r
228                         Pico.m.dirtyPal = 0;\r
229                         if (Pico.video.reg[0xC]&8) // shadow/hilight mode\r
230                         {\r
231                                 vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
232                                 vidConvCpyRGB32sh(localPal+0x40, Pico.cram, 0x40);\r
233                                 vidConvCpyRGB32hi(localPal+0x80, Pico.cram, 0x40);\r
234                                 memcpy32(localPal+0xc0, localPal+0x40, 0x40);\r
235                                 pallen = 0x100;\r
236                         }\r
237                         else if (rendstatus & PDRAW_SONIC_MODE) { // mid-frame palette changes\r
238                                 vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
239                                 vidConvCpyRGB32(localPal+0x40, HighPal, 0x40);\r
240                                 vidConvCpyRGB32(localPal+0x80, HighPal+0x40, 0x40);\r
241                         }\r
242                         else {\r
243                                 vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
244                                 memcpy32(localPal+0x80, localPal, 0x40); // for spr prio mess\r
245                         }\r
246                         if (pallen > 0xc0) {\r
247                                 localPal[0xc0] = 0x0000c000;\r
248                                 localPal[0xd0] = 0x00c00000;\r
249                                 localPal[0xe0] = 0x00000000; // reserved pixels for OSD\r
250                                 localPal[0xf0] = 0x00ffffff;\r
251                         }\r
252                         gp2x_video_setpalette(localPal, pallen);\r
253                 }\r
254         }\r
255 \r
256         if (notice || (emu_opt & 2)) {\r
257                 int h = 232;\r
258                 if (currentConfig.scaling == EOPT_SCALE_HW_HV && !(Pico.video.reg[1]&8))\r
259                         h -= 8;\r
260                 if (notice)\r
261                         osd_text(4, h, notice);\r
262                 if (emu_opt & 2)\r
263                         osd_text(osd_fps_x, h, fps);\r
264         }\r
265         if ((emu_opt & 0x400) && (PicoAHW & PAHW_MCD))\r
266                 draw_cd_leds();\r
267         if (PicoAHW & PAHW_PICO)\r
268                 draw_pico_ptr();\r
269 \r
270         gp2x_video_flip();\r
271 \r
272         if (!(PicoOpt & POPT_ALT_RENDERER)) {\r
273                 if (!(Pico.video.reg[1]&8)) {\r
274                         if (currentConfig.EmuOpt & EOPT_16BPP)\r
275                                 DrawLineDest = (unsigned short *) g_screen_ptr + 320*8;\r
276                         else\r
277                                 DrawLineDest = (unsigned char  *) g_screen_ptr + 320*8;\r
278                 } else {\r
279                         DrawLineDest = g_screen_ptr;\r
280                 }\r
281         }\r
282 }\r
283 \r
284 // clears whole screen or just the notice area (in all buffers)\r
285 static void clearArea(int full)\r
286 {\r
287         if ((PicoOpt&0x10)||!(currentConfig.EmuOpt&0x80)) {\r
288                 // 8-bit renderers\r
289                 if (full) gp2x_memset_all_buffers(0, 0xe0, 320*240);\r
290                 else      gp2x_memset_all_buffers(320*232, 0xe0, 320*8);\r
291         } else {\r
292                 // 16bit accurate renderer\r
293                 if (full) gp2x_memset_all_buffers(0, 0, 320*240*2);\r
294                 else      gp2x_memset_all_buffers(320*232*2, 0, 320*8*2);\r
295         }\r
296 }\r
297 \r
298 void plat_status_msg_busy_next(const char *msg)\r
299 {\r
300         clearArea(0);\r
301         blit("", msg);\r
302 \r
303         /* assumption: msg_busy_next gets called only when\r
304          * something slow is about to happen */\r
305         reset_timing = 1;\r
306 }\r
307 \r
308 void plat_status_msg_busy_first(const char *msg)\r
309 {\r
310         gp2x_memcpy_all_buffers(g_screen_ptr, 0, 320*240*2);\r
311         plat_status_msg_busy_next(msg);\r
312 }\r
313 \r
314 static void vidResetMode(void)\r
315 {\r
316         if (PicoOpt & POPT_ALT_RENDERER) {\r
317                 gp2x_video_changemode(8);\r
318         } else if (currentConfig.EmuOpt & EOPT_16BPP) {\r
319                 gp2x_video_changemode(16);\r
320                 PicoDrawSetColorFormat(1);\r
321                 PicoScanBegin = EmuScanBegin16;\r
322         } else {\r
323                 gp2x_video_changemode(8);\r
324                 PicoDrawSetColorFormat(2);\r
325                 PicoScanBegin = EmuScanBegin8;\r
326         }\r
327         if ((PicoOpt & POPT_ALT_RENDERER) || !(currentConfig.EmuOpt & EOPT_16BPP)) {\r
328                 // setup pal for 8-bit modes\r
329                 localPal[0xc0] = 0x0000c000; // MCD LEDs\r
330                 localPal[0xd0] = 0x00c00000;\r
331                 localPal[0xe0] = 0x00000000; // reserved pixels for OSD\r
332                 localPal[0xf0] = 0x00ffffff;\r
333                 gp2x_video_setpalette(localPal, 0x100);\r
334                 gp2x_memset_all_buffers(0, 0xe0, 320*240);\r
335                 gp2x_video_flip();\r
336         }\r
337         Pico.m.dirtyPal = 1;\r
338         // reset scaling\r
339         if (currentConfig.scaling == EOPT_SCALE_HW_HV && !(Pico.video.reg[1]&8))\r
340              gp2x_video_RGB_setscaling(8, (PicoOpt&0x100)&&!(Pico.video.reg[12]&1) ? 256 : 320, 224);\r
341         else gp2x_video_RGB_setscaling(0, (PicoOpt&0x100)&&!(Pico.video.reg[12]&1) ? 256 : 320, 240);\r
342 }\r
343 \r
344 void plat_video_toggle_renderer(int is_next, int is_menu)\r
345 {\r
346         /* alt, 16bpp, 8bpp */\r
347         if (PicoOpt & POPT_ALT_RENDERER) {\r
348                 PicoOpt &= ~POPT_ALT_RENDERER;\r
349                 if (is_next)\r
350                         currentConfig.EmuOpt |= EOPT_16BPP;\r
351         } else if (!(currentConfig.EmuOpt & EOPT_16BPP)) {\r
352                 if (is_next)\r
353                         PicoOpt |= POPT_ALT_RENDERER;\r
354                 else\r
355                         currentConfig.EmuOpt |= EOPT_16BPP;\r
356         } else {\r
357                 currentConfig.EmuOpt &= ~EOPT_16BPP;\r
358                 if (!is_next)\r
359                         PicoOpt |= POPT_ALT_RENDERER;\r
360         }\r
361 \r
362         if (is_menu)\r
363                 return;\r
364 \r
365         vidResetMode();\r
366 \r
367         if (PicoOpt & POPT_ALT_RENDERER) {\r
368                 plat_status_msg(" 8bit fast renderer");\r
369         } else if (currentConfig.EmuOpt & EOPT_16BPP) {\r
370                 plat_status_msg("16bit accurate renderer");\r
371         } else {\r
372                 plat_status_msg(" 8bit accurate renderer");\r
373         }\r
374 }\r
375 \r
376 #if 0 // TODO\r
377 static void RunEventsPico(unsigned int events)\r
378 {\r
379         int ret, px, py, lim_x;\r
380         static int pdown_frames = 0;\r
381 \r
382         // for F200\r
383         ret = gp2x_touchpad_read(&px, &py);\r
384         if (ret >= 0)\r
385         {\r
386                 if (ret > 35000)\r
387                 {\r
388                         if (pdown_frames++ > 5)\r
389                                 PicoPad[0] |= 0x20;\r
390 \r
391                         pico_pen_x = px;\r
392                         pico_pen_y = py;\r
393                         if (!(Pico.video.reg[12]&1)) {\r
394                                 pico_pen_x -= 32;\r
395                                 if (pico_pen_x <   0) pico_pen_x = 0;\r
396                                 if (pico_pen_x > 248) pico_pen_x = 248;\r
397                         }\r
398                         if (pico_pen_y > 224) pico_pen_y = 224;\r
399                 }\r
400                 else\r
401                         pdown_frames = 0;\r
402 \r
403                 //if (ret == 0)\r
404                 //      PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;\r
405         }\r
406 }\r
407 #endif\r
408 \r
409 void plat_update_volume(int has_changed, int is_up)\r
410 {\r
411         static int prev_frame = 0, wait_frames = 0;\r
412         int vol = currentConfig.volume;\r
413         int need_low_volume = 0;\r
414         gp2x_soc_t soc;\r
415 \r
416         soc = soc_detect();\r
417         if ((PicoOpt & POPT_EN_STEREO) && soc == SOCID_MMSP2)\r
418                 need_low_volume = 1;\r
419 \r
420         if (has_changed)\r
421         {\r
422                 if (need_low_volume && vol < 5 && prev_frame == Pico.m.frame_count - 1 && wait_frames < 12)\r
423                         wait_frames++;\r
424                 else {\r
425                         if (is_up) {\r
426                                 if (vol < 99) vol++;\r
427                         } else {\r
428                                 if (vol >  0) vol--;\r
429                         }\r
430                         wait_frames = 0;\r
431                         sndout_oss_setvol(vol, vol);\r
432                         currentConfig.volume = vol;\r
433                 }\r
434                 plat_status_msg("VOL: %02i", vol);\r
435                 prev_frame = Pico.m.frame_count;\r
436         }\r
437 \r
438         if (need_low_volume)\r
439                 return;\r
440 \r
441         /* set the right mixer func */\r
442         if (vol >= 5)\r
443                 PsndMix_32_to_16l = mix_32_to_16l_stereo;\r
444         else {\r
445                 mix_32_to_16l_level = 5 - vol;\r
446                 PsndMix_32_to_16l = mix_32_to_16l_stereo_lvl;\r
447         }\r
448 }\r
449 \r
450 \r
451 static void updateSound(int len)\r
452 {\r
453         if (PicoOpt&8) len<<=1;\r
454 \r
455         /* avoid writing audio when lagging behind to prevent audio lag */\r
456         if (PicoSkipFrame != 2)\r
457                 sndout_oss_write(PsndOut, len<<1);\r
458 }\r
459 \r
460 void pemu_sound_start(void)\r
461 {\r
462         static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;\r
463         int target_fps = Pico.m.pal ? 50 : 60;\r
464 \r
465         PsndOut = NULL;\r
466 \r
467         // prepare sound stuff\r
468         if (currentConfig.EmuOpt & 4)\r
469         {\r
470                 int snd_excess_add;\r
471                 if (PsndRate != PsndRate_old || (PicoOpt&0x20b) != (PicoOpt_old&0x20b) || Pico.m.pal != pal_old ||\r
472                                 ((PicoOpt&0x200) && crashed_940)) {\r
473                         PsndRerate(Pico.m.frame_count ? 1 : 0);\r
474                 }\r
475                 snd_excess_add = ((PsndRate - PsndLen*target_fps)<<16) / target_fps;\r
476                 printf("starting audio: %i len: %i (ex: %04x) stereo: %i, pal: %i\n",\r
477                         PsndRate, PsndLen, snd_excess_add, (PicoOpt&8)>>3, Pico.m.pal);\r
478                 sndout_oss_start(PsndRate, 16, (PicoOpt&8)>>3);\r
479                 sndout_oss_setvol(currentConfig.volume, currentConfig.volume);\r
480                 PicoWriteSound = updateSound;\r
481                 plat_update_volume(0, 0);\r
482                 memset(sndBuffer, 0, sizeof(sndBuffer));\r
483                 PsndOut = sndBuffer;\r
484                 PsndRate_old = PsndRate;\r
485                 PicoOpt_old  = PicoOpt;\r
486                 pal_old = Pico.m.pal;\r
487         }\r
488 }\r
489 \r
490 void pemu_sound_stop(void)\r
491 {\r
492 }\r
493 \r
494 void pemu_sound_wait(void)\r
495 {\r
496         // don't need to do anything, writes will block by themselves\r
497 }\r
498 \r
499 \r
500 static void SkipFrame(int do_audio)\r
501 {\r
502         PicoSkipFrame=do_audio ? 1 : 2;\r
503         PicoFrame();\r
504         PicoSkipFrame=0;\r
505 }\r
506 \r
507 \r
508 void pemu_forced_frame(int opts)\r
509 {\r
510         int po_old = PicoOpt;\r
511         int eo_old = currentConfig.EmuOpt;\r
512 \r
513         PicoOpt &= ~POPT_ALT_RENDERER;\r
514         PicoOpt |= opts|POPT_ACC_SPRITES;\r
515         currentConfig.EmuOpt |= EOPT_16BPP;\r
516 \r
517         PicoDrawSetColorFormat(1);\r
518         PicoScanBegin = EmuScanBegin16;\r
519         Pico.m.dirtyPal = 1;\r
520         PicoFrameDrawOnly();\r
521 \r
522 /*\r
523         if (!(Pico.video.reg[12]&1)) {\r
524                 vidCpyM2 = vidCpyM2_32col;\r
525                 clearArea(1);\r
526         } else  vidCpyM2 = vidCpyM2_40col;\r
527 \r
528         vidCpyM2((unsigned char *)g_screen_ptr+320*8, PicoDraw2FB+328*8);\r
529         vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
530         gp2x_video_setpalette(localPal, 0x40);\r
531 */\r
532         PicoOpt = po_old;\r
533         currentConfig.EmuOpt = eo_old;\r
534 }\r
535 \r
536 void plat_debug_cat(char *str)\r
537 {\r
538 }\r
539 \r
540 static void simpleWait(int thissec, int lim_time)\r
541 {\r
542         struct timeval tval;\r
543 \r
544         spend_cycles(1024);\r
545         gettimeofday(&tval, 0);\r
546         if (thissec != tval.tv_sec) tval.tv_usec+=1000000;\r
547 \r
548         while (tval.tv_usec < lim_time)\r
549         {\r
550                 spend_cycles(1024);\r
551                 gettimeofday(&tval, 0);\r
552                 if (thissec != tval.tv_sec) tval.tv_usec+=1000000;\r
553         }\r
554 }\r
555 \r
556 \r
557 #if 0\r
558 static void tga_dump(void)\r
559 {\r
560 #define BYTE unsigned char\r
561 #define WORD unsigned short\r
562         struct\r
563         {\r
564                 BYTE IDLength;        /* 00h  Size of Image ID field */\r
565                 BYTE ColorMapType;    /* 01h  Color map type */\r
566                 BYTE ImageType;       /* 02h  Image type code */\r
567                 WORD CMapStart;       /* 03h  Color map origin */\r
568                 WORD CMapLength;      /* 05h  Color map length */\r
569                 BYTE CMapDepth;       /* 07h  Depth of color map entries */\r
570                 WORD XOffset;         /* 08h  X origin of image */\r
571                 WORD YOffset;         /* 0Ah  Y origin of image */\r
572                 WORD Width;           /* 0Ch  Width of image */\r
573                 WORD Height;          /* 0Eh  Height of image */\r
574                 BYTE PixelDepth;      /* 10h  Image pixel size */\r
575                 BYTE ImageDescriptor; /* 11h  Image descriptor byte */\r
576         } __attribute__((packed)) TGAHEAD;\r
577         static unsigned short oldscr[320*240];\r
578         FILE *f; char name[128]; int i;\r
579 \r
580         memset(&TGAHEAD, 0, sizeof(TGAHEAD));\r
581         TGAHEAD.ImageType = 2;\r
582         TGAHEAD.Width = 320;\r
583         TGAHEAD.Height = 240;\r
584         TGAHEAD.PixelDepth = 16;\r
585         TGAHEAD.ImageDescriptor = 2<<4; // image starts at top-left\r
586 \r
587 #define CONV(X) (((X>>1)&0x7fe0)|(X&0x1f)) // 555?\r
588 \r
589         for (i = 0; i < 320*240; i++)\r
590                 if(oldscr[i] != CONV(((unsigned short *)g_screen_ptr)[i])) break;\r
591         if (i < 320*240)\r
592         {\r
593                 for (i = 0; i < 320*240; i++)\r
594                         oldscr[i] = CONV(((unsigned short *)g_screen_ptr)[i]);\r
595                 sprintf(name, "%05i.tga", Pico.m.frame_count);\r
596                 f = fopen(name, "wb");\r
597                 if (!f) { printf("!f\n"); exit(1); }\r
598                 fwrite(&TGAHEAD, 1, sizeof(TGAHEAD), f);\r
599                 fwrite(oldscr, 1, 320*240*2, f);\r
600                 fclose(f);\r
601         }\r
602 }\r
603 #endif\r
604 \r
605 \r
606 void pemu_loop(void)\r
607 {\r
608         static int gp2x_old_clock = -1, EmuOpt_old = 0;\r
609         char fpsbuff[24]; // fps count c string\r
610         struct timeval tval; // timing\r
611         int pframes_done, pframes_shown, pthissec; // "period" frames, used for sync\r
612         int  frames_done,  frames_shown,  thissec; // actual frames\r
613         int oldmodes = 0, target_fps, target_frametime, lim_time, vsync_offset, i;\r
614         char *notice = 0;\r
615 \r
616         printf("entered emu_Loop()\n");\r
617 \r
618         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_RAM_TIMINGS) {\r
619                 if (currentConfig.EmuOpt & EOPT_RAM_TIMINGS)\r
620                         set_ram_timings();\r
621                 else\r
622                         unset_ram_timings();\r
623         }\r
624 \r
625         if (gp2x_old_clock < 0)\r
626                 gp2x_old_clock = default_cpu_clock;\r
627         if (gp2x_old_clock != currentConfig.CPUclock) {\r
628                 printf("changing clock to %i...", currentConfig.CPUclock); fflush(stdout);\r
629                 gp2x_set_cpuclk(currentConfig.CPUclock);\r
630                 gp2x_old_clock = currentConfig.CPUclock;\r
631                 printf(" done\n");\r
632         }\r
633 \r
634         if (gp2x_old_gamma != currentConfig.gamma || (EmuOpt_old&0x1000) != (currentConfig.EmuOpt&0x1000)) {\r
635                 set_lcd_gamma(currentConfig.gamma, !!(currentConfig.EmuOpt&0x1000));\r
636                 gp2x_old_gamma = currentConfig.gamma;\r
637                 printf("updated gamma to %i, A_SN's curve: %i\n", currentConfig.gamma, !!(currentConfig.EmuOpt&0x1000));\r
638         }\r
639 \r
640         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_PSYNC) {\r
641                 if (currentConfig.EmuOpt & EOPT_PSYNC)\r
642                         set_lcd_custom_rate(Pico.m.pal);\r
643                 else\r
644                         unset_lcd_custom_rate();\r
645         }\r
646 \r
647         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_MMUHACK)\r
648                 gp2x_make_fb_bufferable(currentConfig.EmuOpt & EOPT_MMUHACK);\r
649 \r
650         EmuOpt_old = currentConfig.EmuOpt;\r
651         fpsbuff[0] = 0;\r
652 \r
653         // make sure we are in correct mode\r
654         vidResetMode();\r
655         scaling_update();\r
656         Pico.m.dirtyPal = 1;\r
657         oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;\r
658 \r
659         // pal/ntsc might have changed, reset related stuff\r
660         target_fps = Pico.m.pal ? 50 : 60;\r
661         target_frametime = 1000000/target_fps;\r
662         reset_timing = 1;\r
663 \r
664         pemu_sound_start();\r
665 \r
666         // prepare CD buffer\r
667         if (PicoAHW & PAHW_MCD) PicoCDBufferInit();\r
668 \r
669         // calc vsync offset to sync timing code with vsync\r
670         if (currentConfig.EmuOpt&0x2000) {\r
671                 gettimeofday(&tval, 0);\r
672                 gp2x_video_wait_vsync();\r
673                 gettimeofday(&tval, 0);\r
674                 vsync_offset = tval.tv_usec;\r
675                 while (vsync_offset >= target_frametime)\r
676                         vsync_offset -= target_frametime;\r
677                 if (!vsync_offset) vsync_offset++;\r
678                 printf("vsync_offset: %i\n", vsync_offset);\r
679         } else\r
680                 vsync_offset = 0;\r
681 \r
682         frames_done = frames_shown = thissec =\r
683         pframes_done = pframes_shown = pthissec = 0;\r
684 \r
685         // loop\r
686         while (engineState == PGS_Running)\r
687         {\r
688                 int modes;\r
689 \r
690                 gettimeofday(&tval, 0);\r
691                 if (reset_timing) {\r
692                         reset_timing = 0;\r
693                         pthissec = tval.tv_sec;\r
694                         pframes_shown = pframes_done = tval.tv_usec/target_frametime;\r
695                 }\r
696 \r
697                 // show notice message?\r
698                 if (noticeMsgTime.tv_sec)\r
699                 {\r
700                         static int noticeMsgSum;\r
701                         if((tval.tv_sec*1000000+tval.tv_usec) - (noticeMsgTime.tv_sec*1000000+noticeMsgTime.tv_usec) > 2000000) { // > 2.0 sec\r
702                                 noticeMsgTime.tv_sec = noticeMsgTime.tv_usec = 0;\r
703                                 clearArea(0);\r
704                                 notice = 0;\r
705                         } else {\r
706                                 int sum = noticeMsg[0]+noticeMsg[1]+noticeMsg[2];\r
707                                 if (sum != noticeMsgSum) { clearArea(0); noticeMsgSum = sum; }\r
708                                 notice = noticeMsg;\r
709                         }\r
710                 }\r
711 \r
712                 // check for mode changes\r
713                 modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8);\r
714                 if (modes != oldmodes)\r
715                 {\r
716                         int scalex = 320;\r
717                         osd_fps_x = OSD_FPS_X;\r
718                         if (modes & 4) {\r
719                                 vidCpyM2 = vidCpyM2_40col;\r
720                         } else {\r
721                                 if (PicoOpt & 0x100) {\r
722                                         vidCpyM2 = vidCpyM2_32col_nobord;\r
723                                         scalex = 256;\r
724                                         osd_fps_x = OSD_FPS_X - 64;\r
725                                 } else {\r
726                                         vidCpyM2 = vidCpyM2_32col;\r
727                                 }\r
728                         }\r
729                         /* want vertical scaling and game is not in 240 line mode */\r
730                         if (currentConfig.scaling == EOPT_SCALE_HW_HV && !(modes&8))\r
731                              gp2x_video_RGB_setscaling(8, scalex, 224);\r
732                         else gp2x_video_RGB_setscaling(0, scalex, 240);\r
733                         oldmodes = modes;\r
734                         clearArea(1);\r
735                 }\r
736 \r
737                 // second changed?\r
738                 if (thissec != tval.tv_sec)\r
739                 {\r
740 #ifdef BENCHMARK\r
741                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];\r
742                         if (++bench == 10) {\r
743                                 bench = 0;\r
744                                 bench_fps_s = bench_fps;\r
745                                 bf[bfp++ & 3] = bench_fps;\r
746                                 bench_fps = 0;\r
747                         }\r
748                         bench_fps += frames_shown;\r
749                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);\r
750 #else\r
751                         if (currentConfig.EmuOpt & 2) {\r
752                                 sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);\r
753                                 if (fpsbuff[5] == 0) { fpsbuff[5] = fpsbuff[6] = ' '; fpsbuff[7] = 0; }\r
754                         }\r
755 #endif\r
756                         frames_shown = frames_done = 0;\r
757                         thissec = tval.tv_sec;\r
758                 }\r
759 #ifdef PFRAMES\r
760                 sprintf(fpsbuff, "%i", Pico.m.frame_count);\r
761 #endif\r
762 \r
763                 if (pthissec != tval.tv_sec)\r
764                 {\r
765                         if (PsndOut == 0 && currentConfig.Frameskip >= 0) {\r
766                                 pframes_done = pframes_shown = 0;\r
767                         } else {\r
768                                 // it is quite common for this implementation to leave 1 fame unfinished\r
769                                 // when second changes, but we don't want buffer to starve.\r
770                                 if (PsndOut && pframes_done < target_fps && pframes_done > target_fps-5) {\r
771                                         emu_update_input();\r
772                                         SkipFrame(1); pframes_done++;\r
773                                 }\r
774 \r
775                                 pframes_done  -= target_fps; if (pframes_done  < 0) pframes_done  = 0;\r
776                                 pframes_shown -= target_fps; if (pframes_shown < 0) pframes_shown = 0;\r
777                                 if (pframes_shown > pframes_done) pframes_shown = pframes_done;\r
778                         }\r
779                         pthissec = tval.tv_sec;\r
780                 }\r
781 \r
782                 lim_time = (pframes_done+1) * target_frametime + vsync_offset;\r
783                 if (currentConfig.Frameskip >= 0) // frameskip enabled\r
784                 {\r
785                         for(i = 0; i < currentConfig.Frameskip; i++) {\r
786                                 emu_update_input();\r
787                                 SkipFrame(1); pframes_done++; frames_done++;\r
788                                 if (PsndOut && !reset_timing) { // do framelimitting if sound is enabled\r
789                                         gettimeofday(&tval, 0);\r
790                                         if (pthissec != tval.tv_sec) tval.tv_usec+=1000000;\r
791                                         if (tval.tv_usec < lim_time) { // we are too fast\r
792                                                 simpleWait(pthissec, lim_time);\r
793                                         }\r
794                                 }\r
795                                 lim_time += target_frametime;\r
796                         }\r
797                 }\r
798                 else if (tval.tv_usec > lim_time) // auto frameskip\r
799                 {\r
800                         // no time left for this frame - skip\r
801                         if (tval.tv_usec - lim_time >= 300000) {\r
802                                 /* something caused a slowdown for us (disk access? cache flush?)\r
803                                  * try to recover by resetting timing... */\r
804                                 reset_timing = 1;\r
805                                 continue;\r
806                         }\r
807                         emu_update_input();\r
808                         SkipFrame(tval.tv_usec < lim_time+target_frametime*2); pframes_done++; frames_done++;\r
809                         continue;\r
810                 }\r
811 \r
812                 emu_update_input();\r
813                 PicoFrame();\r
814 \r
815                 // check time\r
816                 gettimeofday(&tval, 0);\r
817                 if (pthissec != tval.tv_sec) tval.tv_usec+=1000000;\r
818 \r
819                 if (currentConfig.Frameskip < 0 && tval.tv_usec - lim_time >= 300000) // slowdown detection\r
820                         reset_timing = 1;\r
821                 else if (PsndOut != NULL || currentConfig.Frameskip < 0)\r
822                 {\r
823                         // sleep or vsync if we are still too fast\r
824                         // usleep sleeps for ~20ms minimum, so it is not a solution here\r
825                         if (!reset_timing && tval.tv_usec < lim_time)\r
826                         {\r
827                                 // we are too fast\r
828                                 if (vsync_offset) {\r
829                                         if (lim_time - tval.tv_usec > target_frametime/2)\r
830                                                 simpleWait(pthissec, lim_time - target_frametime/4);\r
831                                         gp2x_video_wait_vsync();\r
832                                 } else {\r
833                                         simpleWait(pthissec, lim_time);\r
834                                 }\r
835                         }\r
836                 }\r
837 \r
838                 blit(fpsbuff, notice);\r
839 \r
840                 pframes_done++; pframes_shown++;\r
841                  frames_done++;  frames_shown++;\r
842         }\r
843 \r
844         emu_set_fastforward(0);\r
845 \r
846         if (PicoAHW & PAHW_MCD)\r
847                 PicoCDBufferFree();\r
848 \r
849         // save SRAM\r
850         if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {\r
851                 plat_status_msg_busy_first("Writing SRAM/BRAM...");\r
852                 emu_save_load_game(0, 1);\r
853                 SRam.changed = 0;\r
854         }\r
855 \r
856         // if in 8bit mode, generate 16bit image for menu background\r
857         if ((PicoOpt & POPT_ALT_RENDERER) || !(currentConfig.EmuOpt & EOPT_16BPP))\r
858                 pemu_forced_frame(POPT_EN_SOFTSCALE);\r
859 }\r
860 \r
861 const char *plat_get_credits(void)\r
862 {\r
863         return "PicoDrive v" VERSION " (c) notaz, 2006-2009\n\n\n"\r
864                 "Credits:\n"\r
865                 "fDave: Cyclone 68000 core,\n"\r
866                 "      base code of PicoDrive\n"\r
867                 "Reesy & FluBBa: DrZ80 core\n"\r
868                 "MAME devs: YM2612 and SN76496 cores\n"\r
869                 "rlyeh and others: minimal SDK\n"\r
870                 "Squidge: mmuhack\n"\r
871                 "Dzz: ARM940 sample\n"\r
872                 "GnoStiC / Puck2099: USB joy code\n"\r
873                 "craigix: GP2X hardware\n"\r
874                 "ketchupgun: skin design\n"\r
875                 "\n"\r
876                 "special thanks (for docs, ideas):\n"\r
877                 " Charles MacDonald, Haze,\n"\r
878                 " Stephane Dallongeville,\n"\r
879                 " Lordus, Exophase, Rokas,\n"\r
880                 " Nemesis, Tasco Deluxe";\r
881 }\r