further unification and 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(void)\r
345 {\r
346         if (PicoOpt & POPT_ALT_RENDERER) {\r
347                 PicoOpt &= ~POPT_ALT_RENDERER;\r
348                 currentConfig.EmuOpt |= EOPT_16BPP;\r
349         } else if (!(currentConfig.EmuOpt & EOPT_16BPP))\r
350                 PicoOpt |= POPT_ALT_RENDERER;\r
351         else\r
352                 currentConfig.EmuOpt &= ~EOPT_16BPP;\r
353 \r
354         vidResetMode();\r
355 \r
356         if (PicoOpt & POPT_ALT_RENDERER) {\r
357                 plat_status_msg(" 8bit fast renderer");\r
358         } else if (currentConfig.EmuOpt & EOPT_16BPP) {\r
359                 plat_status_msg("16bit accurate renderer");\r
360         } else {\r
361                 plat_status_msg(" 8bit accurate renderer");\r
362         }\r
363 }\r
364 \r
365 #if 0 // TODO\r
366 static void RunEventsPico(unsigned int events)\r
367 {\r
368         int ret, px, py, lim_x;\r
369         static int pdown_frames = 0;\r
370 \r
371         // for F200\r
372         ret = gp2x_touchpad_read(&px, &py);\r
373         if (ret >= 0)\r
374         {\r
375                 if (ret > 35000)\r
376                 {\r
377                         if (pdown_frames++ > 5)\r
378                                 PicoPad[0] |= 0x20;\r
379 \r
380                         pico_pen_x = px;\r
381                         pico_pen_y = py;\r
382                         if (!(Pico.video.reg[12]&1)) {\r
383                                 pico_pen_x -= 32;\r
384                                 if (pico_pen_x <   0) pico_pen_x = 0;\r
385                                 if (pico_pen_x > 248) pico_pen_x = 248;\r
386                         }\r
387                         if (pico_pen_y > 224) pico_pen_y = 224;\r
388                 }\r
389                 else\r
390                         pdown_frames = 0;\r
391 \r
392                 //if (ret == 0)\r
393                 //      PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;\r
394         }\r
395 }\r
396 #endif\r
397 \r
398 void plat_update_volume(int has_changed, int is_up)\r
399 {\r
400         static int prev_frame = 0, wait_frames = 0;\r
401         int vol = currentConfig.volume;\r
402         int need_low_volume = 0;\r
403         gp2x_soc_t soc;\r
404 \r
405         soc = soc_detect();\r
406         if ((PicoOpt & POPT_EN_STEREO) && soc == SOCID_MMSP2)\r
407                 need_low_volume = 1;\r
408 \r
409         if (has_changed)\r
410         {\r
411                 if (need_low_volume && vol < 5 && prev_frame == Pico.m.frame_count - 1 && wait_frames < 12)\r
412                         wait_frames++;\r
413                 else {\r
414                         if (is_up) {\r
415                                 if (vol < 99) vol++;\r
416                         } else {\r
417                                 if (vol >  0) vol--;\r
418                         }\r
419                         wait_frames = 0;\r
420                         sndout_oss_setvol(vol, vol);\r
421                         currentConfig.volume = vol;\r
422                 }\r
423                 plat_status_msg("VOL: %02i", vol);\r
424                 prev_frame = Pico.m.frame_count;\r
425         }\r
426 \r
427         if (need_low_volume)\r
428                 return;\r
429 \r
430         /* set the right mixer func */\r
431         if (vol >= 5)\r
432                 PsndMix_32_to_16l = mix_32_to_16l_stereo;\r
433         else {\r
434                 mix_32_to_16l_level = 5 - vol;\r
435                 PsndMix_32_to_16l = mix_32_to_16l_stereo_lvl;\r
436         }\r
437 }\r
438 \r
439 \r
440 static void updateSound(int len)\r
441 {\r
442         if (PicoOpt&8) len<<=1;\r
443 \r
444         /* avoid writing audio when lagging behind to prevent audio lag */\r
445         if (PicoSkipFrame != 2)\r
446                 sndout_oss_write(PsndOut, len<<1);\r
447 }\r
448 \r
449 void pemu_sound_start(void)\r
450 {\r
451         static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0;\r
452         int target_fps = Pico.m.pal ? 50 : 60;\r
453 \r
454         PsndOut = NULL;\r
455 \r
456         // prepare sound stuff\r
457         if (currentConfig.EmuOpt & 4)\r
458         {\r
459                 int snd_excess_add;\r
460                 if (PsndRate != PsndRate_old || (PicoOpt&0x20b) != (PicoOpt_old&0x20b) || Pico.m.pal != pal_old ||\r
461                                 ((PicoOpt&0x200) && crashed_940)) {\r
462                         PsndRerate(Pico.m.frame_count ? 1 : 0);\r
463                 }\r
464                 snd_excess_add = ((PsndRate - PsndLen*target_fps)<<16) / target_fps;\r
465                 printf("starting audio: %i len: %i (ex: %04x) stereo: %i, pal: %i\n",\r
466                         PsndRate, PsndLen, snd_excess_add, (PicoOpt&8)>>3, Pico.m.pal);\r
467                 sndout_oss_start(PsndRate, 16, (PicoOpt&8)>>3);\r
468                 sndout_oss_setvol(currentConfig.volume, currentConfig.volume);\r
469                 PicoWriteSound = updateSound;\r
470                 plat_update_volume(0, 0);\r
471                 memset(sndBuffer, 0, sizeof(sndBuffer));\r
472                 PsndOut = sndBuffer;\r
473                 PsndRate_old = PsndRate;\r
474                 PicoOpt_old  = PicoOpt;\r
475                 pal_old = Pico.m.pal;\r
476         }\r
477 }\r
478 \r
479 void pemu_sound_stop(void)\r
480 {\r
481 }\r
482 \r
483 void pemu_sound_wait(void)\r
484 {\r
485         // don't need to do anything, writes will block by themselves\r
486 }\r
487 \r
488 \r
489 static void SkipFrame(int do_audio)\r
490 {\r
491         PicoSkipFrame=do_audio ? 1 : 2;\r
492         PicoFrame();\r
493         PicoSkipFrame=0;\r
494 }\r
495 \r
496 \r
497 void pemu_forced_frame(int opts)\r
498 {\r
499         int po_old = PicoOpt;\r
500         int eo_old = currentConfig.EmuOpt;\r
501 \r
502         PicoOpt &= ~POPT_ALT_RENDERER;\r
503         PicoOpt |= opts|POPT_ACC_SPRITES;\r
504         currentConfig.EmuOpt |= EOPT_16BPP;\r
505 \r
506         PicoDrawSetColorFormat(1);\r
507         PicoScanBegin = EmuScanBegin16;\r
508         Pico.m.dirtyPal = 1;\r
509         PicoFrameDrawOnly();\r
510 \r
511 /*\r
512         if (!(Pico.video.reg[12]&1)) {\r
513                 vidCpyM2 = vidCpyM2_32col;\r
514                 clearArea(1);\r
515         } else  vidCpyM2 = vidCpyM2_40col;\r
516 \r
517         vidCpyM2((unsigned char *)g_screen_ptr+320*8, PicoDraw2FB+328*8);\r
518         vidConvCpyRGB32(localPal, Pico.cram, 0x40);\r
519         gp2x_video_setpalette(localPal, 0x40);\r
520 */\r
521         PicoOpt = po_old;\r
522         currentConfig.EmuOpt = eo_old;\r
523 }\r
524 \r
525 void plat_debug_cat(char *str)\r
526 {\r
527 }\r
528 \r
529 static void simpleWait(int thissec, int lim_time)\r
530 {\r
531         struct timeval tval;\r
532 \r
533         spend_cycles(1024);\r
534         gettimeofday(&tval, 0);\r
535         if (thissec != tval.tv_sec) tval.tv_usec+=1000000;\r
536 \r
537         while (tval.tv_usec < lim_time)\r
538         {\r
539                 spend_cycles(1024);\r
540                 gettimeofday(&tval, 0);\r
541                 if (thissec != tval.tv_sec) tval.tv_usec+=1000000;\r
542         }\r
543 }\r
544 \r
545 \r
546 #if 0\r
547 static void tga_dump(void)\r
548 {\r
549 #define BYTE unsigned char\r
550 #define WORD unsigned short\r
551         struct\r
552         {\r
553                 BYTE IDLength;        /* 00h  Size of Image ID field */\r
554                 BYTE ColorMapType;    /* 01h  Color map type */\r
555                 BYTE ImageType;       /* 02h  Image type code */\r
556                 WORD CMapStart;       /* 03h  Color map origin */\r
557                 WORD CMapLength;      /* 05h  Color map length */\r
558                 BYTE CMapDepth;       /* 07h  Depth of color map entries */\r
559                 WORD XOffset;         /* 08h  X origin of image */\r
560                 WORD YOffset;         /* 0Ah  Y origin of image */\r
561                 WORD Width;           /* 0Ch  Width of image */\r
562                 WORD Height;          /* 0Eh  Height of image */\r
563                 BYTE PixelDepth;      /* 10h  Image pixel size */\r
564                 BYTE ImageDescriptor; /* 11h  Image descriptor byte */\r
565         } __attribute__((packed)) TGAHEAD;\r
566         static unsigned short oldscr[320*240];\r
567         FILE *f; char name[128]; int i;\r
568 \r
569         memset(&TGAHEAD, 0, sizeof(TGAHEAD));\r
570         TGAHEAD.ImageType = 2;\r
571         TGAHEAD.Width = 320;\r
572         TGAHEAD.Height = 240;\r
573         TGAHEAD.PixelDepth = 16;\r
574         TGAHEAD.ImageDescriptor = 2<<4; // image starts at top-left\r
575 \r
576 #define CONV(X) (((X>>1)&0x7fe0)|(X&0x1f)) // 555?\r
577 \r
578         for (i = 0; i < 320*240; i++)\r
579                 if(oldscr[i] != CONV(((unsigned short *)g_screen_ptr)[i])) break;\r
580         if (i < 320*240)\r
581         {\r
582                 for (i = 0; i < 320*240; i++)\r
583                         oldscr[i] = CONV(((unsigned short *)g_screen_ptr)[i]);\r
584                 sprintf(name, "%05i.tga", Pico.m.frame_count);\r
585                 f = fopen(name, "wb");\r
586                 if (!f) { printf("!f\n"); exit(1); }\r
587                 fwrite(&TGAHEAD, 1, sizeof(TGAHEAD), f);\r
588                 fwrite(oldscr, 1, 320*240*2, f);\r
589                 fclose(f);\r
590         }\r
591 }\r
592 #endif\r
593 \r
594 \r
595 void pemu_loop(void)\r
596 {\r
597         static int gp2x_old_clock = -1, EmuOpt_old = 0;\r
598         char fpsbuff[24]; // fps count c string\r
599         struct timeval tval; // timing\r
600         int pframes_done, pframes_shown, pthissec; // "period" frames, used for sync\r
601         int  frames_done,  frames_shown,  thissec; // actual frames\r
602         int oldmodes = 0, target_fps, target_frametime, lim_time, vsync_offset, i;\r
603         char *notice = 0;\r
604 \r
605         printf("entered emu_Loop()\n");\r
606 \r
607         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_RAM_TIMINGS) {\r
608                 if (currentConfig.EmuOpt & EOPT_RAM_TIMINGS)\r
609                         set_ram_timings();\r
610                 else\r
611                         unset_ram_timings();\r
612         }\r
613 \r
614         if (gp2x_old_clock < 0)\r
615                 gp2x_old_clock = default_cpu_clock;\r
616         if (gp2x_old_clock != currentConfig.CPUclock) {\r
617                 printf("changing clock to %i...", currentConfig.CPUclock); fflush(stdout);\r
618                 gp2x_set_cpuclk(currentConfig.CPUclock);\r
619                 gp2x_old_clock = currentConfig.CPUclock;\r
620                 printf(" done\n");\r
621         }\r
622 \r
623         if (gp2x_old_gamma != currentConfig.gamma || (EmuOpt_old&0x1000) != (currentConfig.EmuOpt&0x1000)) {\r
624                 set_lcd_gamma(currentConfig.gamma, !!(currentConfig.EmuOpt&0x1000));\r
625                 gp2x_old_gamma = currentConfig.gamma;\r
626                 printf("updated gamma to %i, A_SN's curve: %i\n", currentConfig.gamma, !!(currentConfig.EmuOpt&0x1000));\r
627         }\r
628 \r
629         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_PSYNC) {\r
630                 if (currentConfig.EmuOpt & EOPT_PSYNC)\r
631                         set_lcd_custom_rate(Pico.m.pal);\r
632                 else\r
633                         unset_lcd_custom_rate();\r
634         }\r
635 \r
636         if ((EmuOpt_old ^ currentConfig.EmuOpt) & EOPT_MMUHACK)\r
637                 gp2x_make_fb_bufferable(currentConfig.EmuOpt & EOPT_MMUHACK);\r
638 \r
639         EmuOpt_old = currentConfig.EmuOpt;\r
640         fpsbuff[0] = 0;\r
641 \r
642         // make sure we are in correct mode\r
643         vidResetMode();\r
644         scaling_update();\r
645         Pico.m.dirtyPal = 1;\r
646         oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;\r
647 \r
648         // pal/ntsc might have changed, reset related stuff\r
649         target_fps = Pico.m.pal ? 50 : 60;\r
650         target_frametime = 1000000/target_fps;\r
651         reset_timing = 1;\r
652 \r
653         pemu_sound_start();\r
654 \r
655         // prepare CD buffer\r
656         if (PicoAHW & PAHW_MCD) PicoCDBufferInit();\r
657 \r
658         // calc vsync offset to sync timing code with vsync\r
659         if (currentConfig.EmuOpt&0x2000) {\r
660                 gettimeofday(&tval, 0);\r
661                 gp2x_video_wait_vsync();\r
662                 gettimeofday(&tval, 0);\r
663                 vsync_offset = tval.tv_usec;\r
664                 while (vsync_offset >= target_frametime)\r
665                         vsync_offset -= target_frametime;\r
666                 if (!vsync_offset) vsync_offset++;\r
667                 printf("vsync_offset: %i\n", vsync_offset);\r
668         } else\r
669                 vsync_offset = 0;\r
670 \r
671         frames_done = frames_shown = thissec =\r
672         pframes_done = pframes_shown = pthissec = 0;\r
673 \r
674         // loop\r
675         while (engineState == PGS_Running)\r
676         {\r
677                 int modes;\r
678 \r
679                 gettimeofday(&tval, 0);\r
680                 if (reset_timing) {\r
681                         reset_timing = 0;\r
682                         pthissec = tval.tv_sec;\r
683                         pframes_shown = pframes_done = tval.tv_usec/target_frametime;\r
684                 }\r
685 \r
686                 // show notice message?\r
687                 if (noticeMsgTime.tv_sec)\r
688                 {\r
689                         static int noticeMsgSum;\r
690                         if((tval.tv_sec*1000000+tval.tv_usec) - (noticeMsgTime.tv_sec*1000000+noticeMsgTime.tv_usec) > 2000000) { // > 2.0 sec\r
691                                 noticeMsgTime.tv_sec = noticeMsgTime.tv_usec = 0;\r
692                                 clearArea(0);\r
693                                 notice = 0;\r
694                         } else {\r
695                                 int sum = noticeMsg[0]+noticeMsg[1]+noticeMsg[2];\r
696                                 if (sum != noticeMsgSum) { clearArea(0); noticeMsgSum = sum; }\r
697                                 notice = noticeMsg;\r
698                         }\r
699                 }\r
700 \r
701                 // check for mode changes\r
702                 modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8);\r
703                 if (modes != oldmodes)\r
704                 {\r
705                         int scalex = 320;\r
706                         osd_fps_x = OSD_FPS_X;\r
707                         if (modes & 4) {\r
708                                 vidCpyM2 = vidCpyM2_40col;\r
709                         } else {\r
710                                 if (PicoOpt & 0x100) {\r
711                                         vidCpyM2 = vidCpyM2_32col_nobord;\r
712                                         scalex = 256;\r
713                                         osd_fps_x = OSD_FPS_X - 64;\r
714                                 } else {\r
715                                         vidCpyM2 = vidCpyM2_32col;\r
716                                 }\r
717                         }\r
718                         /* want vertical scaling and game is not in 240 line mode */\r
719                         if (currentConfig.scaling == EOPT_SCALE_HW_HV && !(modes&8))\r
720                              gp2x_video_RGB_setscaling(8, scalex, 224);\r
721                         else gp2x_video_RGB_setscaling(0, scalex, 240);\r
722                         oldmodes = modes;\r
723                         clearArea(1);\r
724                 }\r
725 \r
726                 // second changed?\r
727                 if (thissec != tval.tv_sec)\r
728                 {\r
729 #ifdef BENCHMARK\r
730                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];\r
731                         if (++bench == 10) {\r
732                                 bench = 0;\r
733                                 bench_fps_s = bench_fps;\r
734                                 bf[bfp++ & 3] = bench_fps;\r
735                                 bench_fps = 0;\r
736                         }\r
737                         bench_fps += frames_shown;\r
738                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);\r
739 #else\r
740                         if (currentConfig.EmuOpt & 2) {\r
741                                 sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);\r
742                                 if (fpsbuff[5] == 0) { fpsbuff[5] = fpsbuff[6] = ' '; fpsbuff[7] = 0; }\r
743                         }\r
744 #endif\r
745                         frames_shown = frames_done = 0;\r
746                         thissec = tval.tv_sec;\r
747                 }\r
748 #ifdef PFRAMES\r
749                 sprintf(fpsbuff, "%i", Pico.m.frame_count);\r
750 #endif\r
751 \r
752                 if (pthissec != tval.tv_sec)\r
753                 {\r
754                         if (PsndOut == 0 && currentConfig.Frameskip >= 0) {\r
755                                 pframes_done = pframes_shown = 0;\r
756                         } else {\r
757                                 // it is quite common for this implementation to leave 1 fame unfinished\r
758                                 // when second changes, but we don't want buffer to starve.\r
759                                 if (PsndOut && pframes_done < target_fps && pframes_done > target_fps-5) {\r
760                                         emu_update_input();\r
761                                         SkipFrame(1); pframes_done++;\r
762                                 }\r
763 \r
764                                 pframes_done  -= target_fps; if (pframes_done  < 0) pframes_done  = 0;\r
765                                 pframes_shown -= target_fps; if (pframes_shown < 0) pframes_shown = 0;\r
766                                 if (pframes_shown > pframes_done) pframes_shown = pframes_done;\r
767                         }\r
768                         pthissec = tval.tv_sec;\r
769                 }\r
770 \r
771                 lim_time = (pframes_done+1) * target_frametime + vsync_offset;\r
772                 if (currentConfig.Frameskip >= 0) // frameskip enabled\r
773                 {\r
774                         for(i = 0; i < currentConfig.Frameskip; i++) {\r
775                                 emu_update_input();\r
776                                 SkipFrame(1); pframes_done++; frames_done++;\r
777                                 if (PsndOut && !reset_timing) { // do framelimitting if sound is enabled\r
778                                         gettimeofday(&tval, 0);\r
779                                         if (pthissec != tval.tv_sec) tval.tv_usec+=1000000;\r
780                                         if (tval.tv_usec < lim_time) { // we are too fast\r
781                                                 simpleWait(pthissec, lim_time);\r
782                                         }\r
783                                 }\r
784                                 lim_time += target_frametime;\r
785                         }\r
786                 }\r
787                 else if (tval.tv_usec > lim_time) // auto frameskip\r
788                 {\r
789                         // no time left for this frame - skip\r
790                         if (tval.tv_usec - lim_time >= 300000) {\r
791                                 /* something caused a slowdown for us (disk access? cache flush?)\r
792                                  * try to recover by resetting timing... */\r
793                                 reset_timing = 1;\r
794                                 continue;\r
795                         }\r
796                         emu_update_input();\r
797                         SkipFrame(tval.tv_usec < lim_time+target_frametime*2); pframes_done++; frames_done++;\r
798                         continue;\r
799                 }\r
800 \r
801                 emu_update_input();\r
802                 PicoFrame();\r
803 \r
804                 // check time\r
805                 gettimeofday(&tval, 0);\r
806                 if (pthissec != tval.tv_sec) tval.tv_usec+=1000000;\r
807 \r
808                 if (currentConfig.Frameskip < 0 && tval.tv_usec - lim_time >= 300000) // slowdown detection\r
809                         reset_timing = 1;\r
810                 else if (PsndOut != NULL || currentConfig.Frameskip < 0)\r
811                 {\r
812                         // sleep or vsync if we are still too fast\r
813                         // usleep sleeps for ~20ms minimum, so it is not a solution here\r
814                         if (!reset_timing && tval.tv_usec < lim_time)\r
815                         {\r
816                                 // we are too fast\r
817                                 if (vsync_offset) {\r
818                                         if (lim_time - tval.tv_usec > target_frametime/2)\r
819                                                 simpleWait(pthissec, lim_time - target_frametime/4);\r
820                                         gp2x_video_wait_vsync();\r
821                                 } else {\r
822                                         simpleWait(pthissec, lim_time);\r
823                                 }\r
824                         }\r
825                 }\r
826 \r
827                 blit(fpsbuff, notice);\r
828 \r
829                 pframes_done++; pframes_shown++;\r
830                  frames_done++;  frames_shown++;\r
831         }\r
832 \r
833         emu_changeFastForward(0);\r
834 \r
835         if (PicoAHW & PAHW_MCD)\r
836                 PicoCDBufferFree();\r
837 \r
838         // save SRAM\r
839         if ((currentConfig.EmuOpt & EOPT_USE_SRAM) && SRam.changed) {\r
840                 plat_status_msg_busy_first("Writing SRAM/BRAM...");\r
841                 emu_SaveLoadGame(0, 1);\r
842                 SRam.changed = 0;\r
843         }\r
844 \r
845         // if in 8bit mode, generate 16bit image for menu background\r
846         if ((PicoOpt & POPT_ALT_RENDERER) || !(currentConfig.EmuOpt & EOPT_16BPP))\r
847                 pemu_forced_frame(POPT_EN_SOFTSCALE);\r
848 }\r
849 \r
850 const char *plat_get_credits(void)\r
851 {\r
852         return "PicoDrive v" VERSION " (c) notaz, 2006-2009\n\n\n"\r
853                 "Credits:\n"\r
854                 "fDave: Cyclone 68000 core,\n"\r
855                 "      base code of PicoDrive\n"\r
856                 "Reesy & FluBBa: DrZ80 core\n"\r
857                 "MAME devs: YM2612 and SN76496 cores\n"\r
858                 "rlyeh and others: minimal SDK\n"\r
859                 "Squidge: mmuhack\n"\r
860                 "Dzz: ARM940 sample\n"\r
861                 "GnoStiC / Puck2099: USB joy code\n"\r
862                 "craigix: GP2X hardware\n"\r
863                 "ketchupgun: skin design\n"\r
864                 "\n"\r
865                 "special thanks (for docs, ideas):\n"\r
866                 " Charles MacDonald, Haze,\n"\r
867                 " Stephane Dallongeville,\n"\r
868                 " Lordus, Exophase, Rokas,\n"\r
869                 " Nemesis, Tasco Deluxe";\r
870 }\r