gpulib: add some missed sync and flush
[pcsx_rearmed.git] / plugins / gpulib / gpu.c
1 /*
2  * (C) GraÅžvydas "notaz" Ignotas, 2011-2012
3  *
4  * This work is licensed under the terms of any of these licenses
5  * (at your option):
6  *  - GNU GPL, version 2 or later.
7  *  - GNU LGPL, version 2.1 or later.
8  * See the COPYING file in the top-level directory.
9  */
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <stdlib.h> /* for calloc */
15
16 #include "gpu.h"
17
18 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
19 #ifdef __GNUC__
20 #define unlikely(x) __builtin_expect((x), 0)
21 #define preload __builtin_prefetch
22 #define noinline __attribute__((noinline))
23 #else
24 #define unlikely(x)
25 #define preload(...)
26 #define noinline
27 #endif
28
29 //#define log_io gpu_log
30 #define log_io(...)
31
32 struct psx_gpu gpu;
33
34 static noinline int do_cmd_buffer(uint32_t *data, int count);
35 static void finish_vram_transfer(int is_read);
36
37 static noinline void do_cmd_reset(void)
38 {
39   renderer_sync();
40
41   if (unlikely(gpu.cmd_len > 0))
42     do_cmd_buffer(gpu.cmd_buffer, gpu.cmd_len);
43   gpu.cmd_len = 0;
44
45   if (unlikely(gpu.dma.h > 0))
46     finish_vram_transfer(gpu.dma_start.is_read);
47   gpu.dma.h = 0;
48 }
49
50 static noinline void do_reset(void)
51 {
52   unsigned int i;
53
54   do_cmd_reset();
55
56   memset(gpu.regs, 0, sizeof(gpu.regs));
57   for (i = 0; i < sizeof(gpu.ex_regs) / sizeof(gpu.ex_regs[0]); i++)
58     gpu.ex_regs[i] = (0xe0 + i) << 24;
59   gpu.status = 0x14802000;
60   gpu.gp0 = 0;
61   gpu.regs[3] = 1;
62   gpu.screen.hres = gpu.screen.w = 256;
63   gpu.screen.vres = gpu.screen.h = 240;
64   gpu.screen.x = gpu.screen.y = 0;
65   renderer_sync_ecmds(gpu.ex_regs);
66   renderer_notify_res_change();
67 }
68
69 static noinline void update_width(void)
70 {
71   static const short hres_all[8] = { 256, 368, 320, 368, 512, 368, 640, 368 };
72   static const uint8_t hdivs[8] = { 10, 7, 8, 7, 5, 7, 4, 7 };
73   uint8_t hdiv = hdivs[(gpu.status >> 16) & 7];
74   int hres = hres_all[(gpu.status >> 16) & 7];
75   int pal = gpu.status & PSX_GPU_STATUS_PAL;
76   int sw = gpu.screen.x2 - gpu.screen.x1;
77   int x = 0, x_auto;
78   if (sw <= 0)
79     /* nothing displayed? */;
80   else {
81     int s = pal ? 656 : 608; // or 600? pal is just a guess
82     x = (gpu.screen.x1 - s) / hdiv;
83     x = (x + 1) & ~1;   // blitter limitation
84     sw /= hdiv;
85     sw = (sw + 2) & ~3; // according to nocash
86     switch (gpu.state.screen_centering_type) {
87     case 1:
88       break;
89     case 2:
90       x = gpu.state.screen_centering_x;
91       break;
92     default:
93       // correct if slightly miscentered
94       x_auto = (hres - sw) / 2 & ~3;
95       if ((uint32_t)x_auto <= 8u && abs(x) < 24)
96         x = x_auto;
97     }
98     if (x + sw > hres)
99       sw = hres - x;
100     // .x range check is done in vout_update()
101   }
102   // reduce the unpleasant right border that a few games have
103   if (gpu.state.screen_centering_type == 0
104       && x <= 4 && hres - (x + sw) >= 4)
105     hres -= 4;
106   gpu.screen.x = x;
107   gpu.screen.w = sw;
108   gpu.screen.hres = hres;
109   gpu.state.dims_changed = 1;
110   //printf("xx %d %d -> %2d, %d / %d\n",
111   //  gpu.screen.x1, gpu.screen.x2, x, sw, hres);
112 }
113
114 static noinline void update_height(void)
115 {
116   int pal = gpu.status & PSX_GPU_STATUS_PAL;
117   int dheight = gpu.status & PSX_GPU_STATUS_DHEIGHT;
118   int y = gpu.screen.y1 - (pal ? 39 : 16); // 39 for spyro
119   int sh = gpu.screen.y2 - gpu.screen.y1;
120   int center_tol = 16;
121   int vres = 240;
122
123   if (pal && (sh > 240 || gpu.screen.vres == 256))
124     vres = 256;
125   if (dheight)
126     y *= 2, sh *= 2, vres *= 2, center_tol *= 2;
127   if (sh <= 0)
128     /* nothing displayed? */;
129   else {
130     switch (gpu.state.screen_centering_type) {
131     case 1:
132       break;
133     case 2:
134       y = gpu.state.screen_centering_y;
135       break;
136     default:
137       // correct if slightly miscentered
138       if ((uint32_t)(vres - sh) <= 1 && abs(y) <= center_tol)
139         y = 0;
140     }
141     if (y + sh > vres)
142       sh = vres - y;
143   }
144   gpu.screen.y = y;
145   gpu.screen.h = sh;
146   gpu.screen.vres = vres;
147   gpu.state.dims_changed = 1;
148   //printf("yy %d %d -> %d, %d / %d\n",
149   //  gpu.screen.y1, gpu.screen.y2, y, sh, vres);
150 }
151
152 static noinline void decide_frameskip(void)
153 {
154   *gpu.frameskip.dirty = 1;
155
156   if (gpu.frameskip.active)
157     gpu.frameskip.cnt++;
158   else {
159     gpu.frameskip.cnt = 0;
160     gpu.frameskip.frame_ready = 1;
161   }
162
163   if (*gpu.frameskip.force)
164     gpu.frameskip.active = 1;
165   else if (!gpu.frameskip.active && *gpu.frameskip.advice)
166     gpu.frameskip.active = 1;
167   else if (gpu.frameskip.set > 0 && gpu.frameskip.cnt < gpu.frameskip.set)
168     gpu.frameskip.active = 1;
169   else
170     gpu.frameskip.active = 0;
171
172   if (!gpu.frameskip.active && gpu.frameskip.pending_fill[0] != 0) {
173     int dummy;
174     do_cmd_list(gpu.frameskip.pending_fill, 3, &dummy);
175     gpu.frameskip.pending_fill[0] = 0;
176   }
177 }
178
179 static noinline int decide_frameskip_allow(uint32_t cmd_e3)
180 {
181   // no frameskip if it decides to draw to display area,
182   // but not for interlace since it'll most likely always do that
183   uint32_t x = cmd_e3 & 0x3ff;
184   uint32_t y = (cmd_e3 >> 10) & 0x3ff;
185   gpu.frameskip.allow = (gpu.status & PSX_GPU_STATUS_INTERLACE) ||
186     (uint32_t)(x - gpu.screen.src_x) >= (uint32_t)gpu.screen.w ||
187     (uint32_t)(y - gpu.screen.src_y) >= (uint32_t)gpu.screen.h;
188   return gpu.frameskip.allow;
189 }
190
191 static void flush_cmd_buffer(void);
192
193 static noinline void get_gpu_info(uint32_t data)
194 {
195   if (unlikely(gpu.cmd_len > 0))
196     flush_cmd_buffer();
197   switch (data & 0x0f) {
198     case 0x02:
199     case 0x03:
200     case 0x04:
201       gpu.gp0 = gpu.ex_regs[data & 7] & 0xfffff;
202       break;
203     case 0x05:
204       gpu.gp0 = gpu.ex_regs[5] & 0x3fffff;
205       break;
206     case 0x07:
207       gpu.gp0 = 2;
208       break;
209     default:
210       // gpu.gp0 unchanged
211       break;
212   }
213 }
214
215 // double, for overdraw guard
216 #define VRAM_SIZE ((1024 * 512 * 2 * 2) + 4096)
217
218 //  Minimum 16-byte VRAM alignment needed by gpu_unai's pixel-skipping
219 //  renderer/downscaler it uses in high res modes:
220 #ifdef GCW_ZERO
221         // On GCW platform (MIPS), align to 8192 bytes (1 TLB entry) to reduce # of
222         // fills. (Will change this value if it ever gets large page support)
223         #define VRAM_ALIGN 8192
224 #else
225         #define VRAM_ALIGN 16
226 #endif
227
228 // vram ptr received from mmap/malloc/alloc (will deallocate using this)
229 static uint16_t *vram_ptr_orig = NULL;
230
231 #ifdef GPULIB_USE_MMAP
232 static int map_vram(void)
233 {
234   gpu.vram = vram_ptr_orig = gpu.mmap(VRAM_SIZE + (VRAM_ALIGN-1));
235   if (gpu.vram != NULL) {
236         // 4kb guard in front
237     gpu.vram += (4096 / 2);
238         // Align
239         gpu.vram = (uint16_t*)(((uintptr_t)gpu.vram + (VRAM_ALIGN-1)) & ~(VRAM_ALIGN-1));
240     return 0;
241   }
242   else {
243     fprintf(stderr, "could not map vram, expect crashes\n");
244     return -1;
245   }
246 }
247 #else
248 static int map_vram(void)
249 {
250   gpu.vram = vram_ptr_orig = (uint16_t*)calloc(VRAM_SIZE + (VRAM_ALIGN-1), 1);
251   if (gpu.vram != NULL) {
252         // 4kb guard in front
253     gpu.vram += (4096 / 2);
254         // Align
255         gpu.vram = (uint16_t*)(((uintptr_t)gpu.vram + (VRAM_ALIGN-1)) & ~(VRAM_ALIGN-1));
256     return 0;
257   } else {
258     fprintf(stderr, "could not allocate vram, expect crashes\n");
259     return -1;
260   }
261 }
262
263 static int allocate_vram(void)
264 {
265   gpu.vram = vram_ptr_orig = (uint16_t*)calloc(VRAM_SIZE + (VRAM_ALIGN-1), 1);
266   if (gpu.vram != NULL) {
267         // 4kb guard in front
268     gpu.vram += (4096 / 2);
269         // Align
270         gpu.vram = (uint16_t*)(((uintptr_t)gpu.vram + (VRAM_ALIGN-1)) & ~(VRAM_ALIGN-1));
271     return 0;
272   } else {
273     fprintf(stderr, "could not allocate vram, expect crashes\n");
274     return -1;
275   }
276 }
277 #endif
278
279 long GPUinit(void)
280 {
281 #ifndef GPULIB_USE_MMAP
282   if (gpu.vram == NULL) {
283     if (allocate_vram() != 0) {
284       printf("ERROR: could not allocate VRAM, exiting..\n");
285           exit(1);
286         }
287   }
288 #endif
289
290   //extern uint32_t hSyncCount;         // in psxcounters.cpp
291   //extern uint32_t frame_counter;      // in psxcounters.cpp
292   //gpu.state.hcnt = &hSyncCount;
293   //gpu.state.frame_count = &frame_counter;
294
295   int ret;
296   ret  = vout_init();
297   ret |= renderer_init();
298
299   memset(&gpu.state, 0, sizeof(gpu.state));
300   memset(&gpu.frameskip, 0, sizeof(gpu.frameskip));
301   gpu.zero = 0;
302   gpu.state.frame_count = &gpu.zero;
303   gpu.state.hcnt = &gpu.zero;
304   gpu.cmd_len = 0;
305   do_reset();
306
307   /*if (gpu.mmap != NULL) {
308     if (map_vram() != 0)
309       ret = -1;
310   }*/
311   return ret;
312 }
313
314 long GPUshutdown(void)
315 {
316   long ret;
317
318   renderer_finish();
319   ret = vout_finish();
320
321   if (vram_ptr_orig != NULL) {
322 #ifdef GPULIB_USE_MMAP
323     gpu.munmap(vram_ptr_orig, VRAM_SIZE);
324 #else
325     free(vram_ptr_orig);
326 #endif
327   }
328   vram_ptr_orig = gpu.vram = NULL;
329
330   return ret;
331 }
332
333 void GPUwriteStatus(uint32_t data)
334 {
335   uint32_t cmd = data >> 24;
336
337   if (cmd < ARRAY_SIZE(gpu.regs)) {
338     if (cmd > 1 && cmd != 5 && gpu.regs[cmd] == data)
339       return;
340     gpu.regs[cmd] = data;
341   }
342
343   gpu.state.fb_dirty = 1;
344
345   switch (cmd) {
346     case 0x00:
347       do_reset();
348       break;
349     case 0x01:
350       do_cmd_reset();
351       break;
352     case 0x03:
353       if (data & 1) {
354         gpu.status |= PSX_GPU_STATUS_BLANKING;
355         gpu.state.dims_changed = 1; // for hud clearing
356       }
357       else
358         gpu.status &= ~PSX_GPU_STATUS_BLANKING;
359       break;
360     case 0x04:
361       gpu.status &= ~PSX_GPU_STATUS_DMA_MASK;
362       gpu.status |= PSX_GPU_STATUS_DMA(data & 3);
363       break;
364     case 0x05:
365       gpu.screen.src_x = data & 0x3ff;
366       gpu.screen.src_y = (data >> 10) & 0x1ff;
367       renderer_notify_scanout_x_change(gpu.screen.src_x, gpu.screen.hres);
368       if (gpu.frameskip.set) {
369         decide_frameskip_allow(gpu.ex_regs[3]);
370         if (gpu.frameskip.last_flip_frame != *gpu.state.frame_count) {
371           decide_frameskip();
372           gpu.frameskip.last_flip_frame = *gpu.state.frame_count;
373         }
374       }
375       break;
376     case 0x06:
377       gpu.screen.x1 = data & 0xfff;
378       gpu.screen.x2 = (data >> 12) & 0xfff;
379       update_width();
380       break;
381     case 0x07:
382       gpu.screen.y1 = data & 0x3ff;
383       gpu.screen.y2 = (data >> 10) & 0x3ff;
384       update_height();
385       break;
386     case 0x08:
387       gpu.status = (gpu.status & ~0x7f0000) | ((data & 0x3F) << 17) | ((data & 0x40) << 10);
388       update_width();
389       update_height();
390       renderer_notify_res_change();
391       break;
392     default:
393       if ((cmd & 0xf0) == 0x10)
394         get_gpu_info(data);
395       break;
396   }
397
398 #ifdef GPUwriteStatus_ext
399   GPUwriteStatus_ext(data);
400 #endif
401 }
402
403 const unsigned char cmd_lengths[256] =
404 {
405         0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
406         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
407         3, 3, 3, 3, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8, 8, 8, // 20
408         5, 5, 5, 5, 8, 8, 8, 8, 7, 7, 7, 7, 11, 11, 11, 11,
409         2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // 40
410         3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
411         2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 0, 0, 0, 0, // 60
412         1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
413         3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80
414         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
415         2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a0
416         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
417         2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c0
418         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
419         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e0
420         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
421 };
422
423 #define VRAM_MEM_XY(x, y) &gpu.vram[(y) * 1024 + (x)]
424
425 static inline void do_vram_line(int x, int y, uint16_t *mem, int l, int is_read)
426 {
427   uint16_t *vram = VRAM_MEM_XY(x, y);
428   if (is_read)
429     memcpy(mem, vram, l * 2);
430   else
431     memcpy(vram, mem, l * 2);
432 }
433
434 static int do_vram_io(uint32_t *data, int count, int is_read)
435 {
436   int count_initial = count;
437   uint16_t *sdata = (uint16_t *)data;
438   int x = gpu.dma.x, y = gpu.dma.y;
439   int w = gpu.dma.w, h = gpu.dma.h;
440   int o = gpu.dma.offset;
441   int l;
442   count *= 2; // operate in 16bpp pixels
443
444   renderer_sync();
445
446   if (gpu.dma.offset) {
447     l = w - gpu.dma.offset;
448     if (count < l)
449       l = count;
450
451     do_vram_line(x + o, y, sdata, l, is_read);
452
453     if (o + l < w)
454       o += l;
455     else {
456       o = 0;
457       y++;
458       h--;
459     }
460     sdata += l;
461     count -= l;
462   }
463
464   for (; h > 0 && count >= w; sdata += w, count -= w, y++, h--) {
465     y &= 511;
466     do_vram_line(x, y, sdata, w, is_read);
467   }
468
469   if (h > 0) {
470     if (count > 0) {
471       y &= 511;
472       do_vram_line(x, y, sdata, count, is_read);
473       o = count;
474       count = 0;
475     }
476   }
477   else
478     finish_vram_transfer(is_read);
479   gpu.dma.y = y;
480   gpu.dma.h = h;
481   gpu.dma.offset = o;
482
483   return count_initial - count / 2;
484 }
485
486 static void start_vram_transfer(uint32_t pos_word, uint32_t size_word, int is_read)
487 {
488   if (gpu.dma.h)
489     log_anomaly("start_vram_transfer while old unfinished\n");
490
491   gpu.dma.x = pos_word & 0x3ff;
492   gpu.dma.y = (pos_word >> 16) & 0x1ff;
493   gpu.dma.w = ((size_word - 1) & 0x3ff) + 1;
494   gpu.dma.h = (((size_word >> 16) - 1) & 0x1ff) + 1;
495   gpu.dma.offset = 0;
496   gpu.dma.is_read = is_read;
497   gpu.dma_start = gpu.dma;
498
499   renderer_flush_queues();
500   if (is_read) {
501     gpu.status |= PSX_GPU_STATUS_IMG;
502     // XXX: wrong for width 1
503     gpu.gp0 = LE32TOH(*(uint32_t *) VRAM_MEM_XY(gpu.dma.x, gpu.dma.y));
504     gpu.state.last_vram_read_frame = *gpu.state.frame_count;
505   }
506
507   log_io("start_vram_transfer %c (%d, %d) %dx%d\n", is_read ? 'r' : 'w',
508     gpu.dma.x, gpu.dma.y, gpu.dma.w, gpu.dma.h);
509 }
510
511 static void finish_vram_transfer(int is_read)
512 {
513   if (is_read)
514     gpu.status &= ~PSX_GPU_STATUS_IMG;
515   else
516     renderer_update_caches(gpu.dma_start.x, gpu.dma_start.y,
517                            gpu.dma_start.w, gpu.dma_start.h, 0);
518 }
519
520 static noinline int do_cmd_list_skip(uint32_t *data, int count, int *last_cmd)
521 {
522   int cmd = 0, pos = 0, len, dummy, v;
523   int skip = 1;
524
525   gpu.frameskip.pending_fill[0] = 0;
526
527   while (pos < count && skip) {
528     uint32_t *list = data + pos;
529     cmd = LE32TOH(list[0]) >> 24;
530     len = 1 + cmd_lengths[cmd];
531
532     switch (cmd) {
533       case 0x02:
534         if ((LE32TOH(list[2]) & 0x3ff) > gpu.screen.w || ((LE32TOH(list[2]) >> 16) & 0x1ff) > gpu.screen.h)
535           // clearing something large, don't skip
536           do_cmd_list(list, 3, &dummy);
537         else
538           memcpy(gpu.frameskip.pending_fill, list, 3 * 4);
539         break;
540       case 0x24 ... 0x27:
541       case 0x2c ... 0x2f:
542       case 0x34 ... 0x37:
543       case 0x3c ... 0x3f:
544         gpu.ex_regs[1] &= ~0x1ff;
545         gpu.ex_regs[1] |= LE32TOH(list[4 + ((cmd >> 4) & 1)]) & 0x1ff;
546         break;
547       case 0x48 ... 0x4F:
548         for (v = 3; pos + v < count; v++)
549         {
550           if ((list[v] & HTOLE32(0xf000f000)) == HTOLE32(0x50005000))
551             break;
552         }
553         len += v - 3;
554         break;
555       case 0x58 ... 0x5F:
556         for (v = 4; pos + v < count; v += 2)
557         {
558           if ((list[v] & HTOLE32(0xf000f000)) == HTOLE32(0x50005000))
559             break;
560         }
561         len += v - 4;
562         break;
563       default:
564         if (cmd == 0xe3)
565           skip = decide_frameskip_allow(LE32TOH(list[0]));
566         if ((cmd & 0xf8) == 0xe0)
567           gpu.ex_regs[cmd & 7] = LE32TOH(list[0]);
568         break;
569     }
570
571     if (pos + len > count) {
572       cmd = -1;
573       break; // incomplete cmd
574     }
575     if (0xa0 <= cmd && cmd <= 0xdf)
576       break; // image i/o
577
578     pos += len;
579   }
580
581   renderer_sync_ecmds(gpu.ex_regs);
582   *last_cmd = cmd;
583   return pos;
584 }
585
586 static noinline int do_cmd_buffer(uint32_t *data, int count)
587 {
588   int cmd, pos;
589   uint32_t old_e3 = gpu.ex_regs[3];
590   int vram_dirty = 0;
591
592   // process buffer
593   for (pos = 0; pos < count; )
594   {
595     if (gpu.dma.h && !gpu.dma_start.is_read) { // XXX: need to verify
596       vram_dirty = 1;
597       pos += do_vram_io(data + pos, count - pos, 0);
598       if (pos == count)
599         break;
600     }
601
602     cmd = LE32TOH(data[pos]) >> 24;
603     if (0xa0 <= cmd && cmd <= 0xdf) {
604       if (unlikely((pos+2) >= count)) {
605         // incomplete vram write/read cmd, can't consume yet
606         cmd = -1;
607         break;
608       }
609
610       // consume vram write/read cmd
611       start_vram_transfer(LE32TOH(data[pos + 1]), LE32TOH(data[pos + 2]), (cmd & 0xe0) == 0xc0);
612       pos += 3;
613       continue;
614     }
615
616     // 0xex cmds might affect frameskip.allow, so pass to do_cmd_list_skip
617     if (gpu.frameskip.active && (gpu.frameskip.allow || ((LE32TOH(data[pos]) >> 24) & 0xf0) == 0xe0))
618       pos += do_cmd_list_skip(data + pos, count - pos, &cmd);
619     else {
620       pos += do_cmd_list(data + pos, count - pos, &cmd);
621       vram_dirty = 1;
622     }
623
624     if (cmd == -1)
625       // incomplete cmd
626       break;
627   }
628
629   gpu.status &= ~0x1fff;
630   gpu.status |= gpu.ex_regs[1] & 0x7ff;
631   gpu.status |= (gpu.ex_regs[6] & 3) << 11;
632
633   gpu.state.fb_dirty |= vram_dirty;
634
635   if (old_e3 != gpu.ex_regs[3])
636     decide_frameskip_allow(gpu.ex_regs[3]);
637
638   return count - pos;
639 }
640
641 static void flush_cmd_buffer(void)
642 {
643   int left = do_cmd_buffer(gpu.cmd_buffer, gpu.cmd_len);
644   if (left > 0)
645     memmove(gpu.cmd_buffer, gpu.cmd_buffer + gpu.cmd_len - left, left * 4);
646   gpu.cmd_len = left;
647 }
648
649 void GPUwriteDataMem(uint32_t *mem, int count)
650 {
651   int left;
652
653   log_io("gpu_dma_write %p %d\n", mem, count);
654
655   if (unlikely(gpu.cmd_len > 0))
656     flush_cmd_buffer();
657
658   left = do_cmd_buffer(mem, count);
659   if (left)
660     log_anomaly("GPUwriteDataMem: discarded %d/%d words\n", left, count);
661 }
662
663 void GPUwriteData(uint32_t data)
664 {
665   log_io("gpu_write %08x\n", data);
666   gpu.cmd_buffer[gpu.cmd_len++] = HTOLE32(data);
667   if (gpu.cmd_len >= CMD_BUFFER_LEN)
668     flush_cmd_buffer();
669 }
670
671 long GPUdmaChain(uint32_t *rambase, uint32_t start_addr, uint32_t *progress_addr)
672 {
673   uint32_t addr, *list, ld_addr = 0;
674   int len, left, count;
675   long cpu_cycles = 0;
676
677   preload(rambase + (start_addr & 0x1fffff) / 4);
678
679   if (unlikely(gpu.cmd_len > 0))
680     flush_cmd_buffer();
681
682   log_io("gpu_dma_chain\n");
683   addr = start_addr & 0xffffff;
684   for (count = 0; (addr & 0x800000) == 0; count++)
685   {
686     list = rambase + (addr & 0x1fffff) / 4;
687     len = LE32TOH(list[0]) >> 24;
688     addr = LE32TOH(list[0]) & 0xffffff;
689     preload(rambase + (addr & 0x1fffff) / 4);
690
691     cpu_cycles += 10;
692     if (len > 0)
693       cpu_cycles += 5 + len;
694
695     log_io(".chain %08lx #%d+%d\n",
696       (long)(list - rambase) * 4, len, gpu.cmd_len);
697     if (unlikely(gpu.cmd_len > 0)) {
698       if (gpu.cmd_len + len > ARRAY_SIZE(gpu.cmd_buffer)) {
699         log_anomaly("cmd_buffer overflow, likely garbage commands\n");
700         gpu.cmd_len = 0;
701       }
702       memcpy(gpu.cmd_buffer + gpu.cmd_len, list + 1, len * 4);
703       gpu.cmd_len += len;
704       flush_cmd_buffer();
705       continue;
706     }
707
708     if (len) {
709       left = do_cmd_buffer(list + 1, len);
710       if (left) {
711         memcpy(gpu.cmd_buffer, list + 1 + len - left, left * 4);
712         gpu.cmd_len = left;
713         log_anomaly("GPUdmaChain: %d/%d words left\n", left, len);
714       }
715     }
716
717     if (progress_addr) {
718       *progress_addr = addr;
719       break;
720     }
721     #define LD_THRESHOLD (8*1024)
722     if (count >= LD_THRESHOLD) {
723       if (count == LD_THRESHOLD) {
724         ld_addr = addr;
725         continue;
726       }
727
728       // loop detection marker
729       // (bit23 set causes DMA error on real machine, so
730       //  unlikely to be ever set by the game)
731       list[0] |= HTOLE32(0x800000);
732     }
733   }
734
735   if (ld_addr != 0) {
736     // remove loop detection markers
737     count -= LD_THRESHOLD + 2;
738     addr = ld_addr & 0x1fffff;
739     while (count-- > 0) {
740       list = rambase + addr / 4;
741       addr = LE32TOH(list[0]) & 0x1fffff;
742       list[0] &= HTOLE32(~0x800000);
743     }
744   }
745
746   gpu.state.last_list.frame = *gpu.state.frame_count;
747   gpu.state.last_list.hcnt = *gpu.state.hcnt;
748   gpu.state.last_list.cycles = cpu_cycles;
749   gpu.state.last_list.addr = start_addr;
750
751   return cpu_cycles;
752 }
753
754 void GPUreadDataMem(uint32_t *mem, int count)
755 {
756   log_io("gpu_dma_read  %p %d\n", mem, count);
757
758   if (unlikely(gpu.cmd_len > 0))
759     flush_cmd_buffer();
760
761   if (gpu.dma.h)
762     do_vram_io(mem, count, 1);
763 }
764
765 uint32_t GPUreadData(void)
766 {
767   uint32_t ret;
768
769   if (unlikely(gpu.cmd_len > 0))
770     flush_cmd_buffer();
771
772   ret = gpu.gp0;
773   if (gpu.dma.h) {
774     ret = HTOLE32(ret);
775     do_vram_io(&ret, 1, 1);
776     ret = LE32TOH(ret);
777   }
778
779   log_io("gpu_read %08x\n", ret);
780   return ret;
781 }
782
783 uint32_t GPUreadStatus(void)
784 {
785   uint32_t ret;
786
787   if (unlikely(gpu.cmd_len > 0))
788     flush_cmd_buffer();
789
790   ret = gpu.status;
791   log_io("gpu_read_status %08x\n", ret);
792   return ret;
793 }
794
795 struct GPUFreeze
796 {
797   uint32_t ulFreezeVersion;      // should be always 1 for now (set by main emu)
798   uint32_t ulStatus;             // current gpu status
799   uint32_t ulControl[256];       // latest control register values
800   unsigned char psxVRam[1024*1024*2]; // current VRam image (full 2 MB for ZN)
801 };
802
803 long GPUfreeze(uint32_t type, struct GPUFreeze *freeze)
804 {
805   int i;
806
807   switch (type) {
808     case 1: // save
809       if (gpu.cmd_len > 0)
810         flush_cmd_buffer();
811
812       renderer_sync();
813       memcpy(freeze->psxVRam, gpu.vram, 1024 * 512 * 2);
814       memcpy(freeze->ulControl, gpu.regs, sizeof(gpu.regs));
815       memcpy(freeze->ulControl + 0xe0, gpu.ex_regs, sizeof(gpu.ex_regs));
816       freeze->ulStatus = gpu.status;
817       break;
818     case 0: // load
819       renderer_sync();
820       memcpy(gpu.vram, freeze->psxVRam, 1024 * 512 * 2);
821       memcpy(gpu.regs, freeze->ulControl, sizeof(gpu.regs));
822       memcpy(gpu.ex_regs, freeze->ulControl + 0xe0, sizeof(gpu.ex_regs));
823       gpu.status = freeze->ulStatus;
824       gpu.cmd_len = 0;
825       for (i = 8; i > 0; i--) {
826         gpu.regs[i] ^= 1; // avoid reg change detection
827         GPUwriteStatus((i << 24) | (gpu.regs[i] ^ 1));
828       }
829       renderer_sync_ecmds(gpu.ex_regs);
830       renderer_update_caches(0, 0, 1024, 512, 1);
831       break;
832   }
833
834   return 1;
835 }
836
837 void GPUupdateLace(void)
838 {
839   if (gpu.cmd_len > 0)
840     flush_cmd_buffer();
841   renderer_flush_queues();
842
843 #ifndef RAW_FB_DISPLAY
844   if (gpu.status & PSX_GPU_STATUS_BLANKING) {
845     if (!gpu.state.blanked) {
846       vout_blank();
847       gpu.state.blanked = 1;
848       gpu.state.fb_dirty = 1;
849     }
850     return;
851   }
852
853   renderer_notify_update_lace(0);
854
855   if (!gpu.state.fb_dirty)
856     return;
857 #endif
858
859   if (gpu.frameskip.set) {
860     if (!gpu.frameskip.frame_ready) {
861       if (*gpu.state.frame_count - gpu.frameskip.last_flip_frame < 9)
862         return;
863       gpu.frameskip.active = 0;
864     }
865     gpu.frameskip.frame_ready = 0;
866   }
867
868   vout_update();
869   if (gpu.state.enhancement_active && !gpu.state.enhancement_was_active)
870     renderer_update_caches(0, 0, 1024, 512, 1);
871   gpu.state.enhancement_was_active = gpu.state.enhancement_active;
872   gpu.state.fb_dirty = 0;
873   gpu.state.blanked = 0;
874   renderer_notify_update_lace(1);
875 }
876
877 void GPUvBlank(int is_vblank, int lcf)
878 {
879   int interlace = gpu.state.allow_interlace
880     && (gpu.status & PSX_GPU_STATUS_INTERLACE)
881     && (gpu.status & PSX_GPU_STATUS_DHEIGHT);
882   // interlace doesn't look nice on progressive displays,
883   // so we have this "auto" mode here for games that don't read vram
884   if (gpu.state.allow_interlace == 2
885       && *gpu.state.frame_count - gpu.state.last_vram_read_frame > 1)
886   {
887     interlace = 0;
888   }
889   if (interlace || interlace != gpu.state.old_interlace) {
890     gpu.state.old_interlace = interlace;
891
892     if (gpu.cmd_len > 0)
893       flush_cmd_buffer();
894     renderer_flush_queues();
895     renderer_set_interlace(interlace, !lcf);
896   }
897 }
898
899 #include "../../frontend/plugin_lib.h"
900
901 void GPUrearmedCallbacks(const struct rearmed_cbs *cbs)
902 {
903   gpu.frameskip.set = cbs->frameskip;
904   gpu.frameskip.advice = &cbs->fskip_advice;
905   gpu.frameskip.force = &cbs->fskip_force;
906   gpu.frameskip.dirty = (void *)&cbs->fskip_dirty;
907   gpu.frameskip.active = 0;
908   gpu.frameskip.frame_ready = 1;
909   gpu.state.hcnt = cbs->gpu_hcnt;
910   gpu.state.frame_count = cbs->gpu_frame_count;
911   gpu.state.allow_interlace = cbs->gpu_neon.allow_interlace;
912   gpu.state.enhancement_enable = cbs->gpu_neon.enhancement_enable;
913   if (gpu.state.screen_centering_type != cbs->screen_centering_type
914       || gpu.state.screen_centering_x != cbs->screen_centering_x
915       || gpu.state.screen_centering_y != cbs->screen_centering_y) {
916     gpu.state.screen_centering_type = cbs->screen_centering_type;
917     gpu.state.screen_centering_x = cbs->screen_centering_x;
918     gpu.state.screen_centering_y = cbs->screen_centering_y;
919     update_width();
920     update_height();
921   }
922
923   gpu.mmap = cbs->mmap;
924   gpu.munmap = cbs->munmap;
925
926   // delayed vram mmap
927   if (gpu.vram == NULL)
928     map_vram();
929
930   if (cbs->pl_vout_set_raw_vram)
931     cbs->pl_vout_set_raw_vram(gpu.vram);
932   renderer_set_config(cbs);
933   vout_set_config(cbs);
934 }
935
936 // vim:shiftwidth=2:expandtab