gpulib: allow commands to span list entries
[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 <string.h>
13 #include "gpu.h"
14
15 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
16 #ifdef __GNUC__
17 #define unlikely(x) __builtin_expect((x), 0)
18 #define preload __builtin_prefetch
19 #define noinline __attribute__((noinline))
20 #else
21 #define unlikely(x)
22 #define preload(...)
23 #define noinline
24 #endif
25
26 #define gpu_log(fmt, ...) \
27   printf("%d:%03d: " fmt, *gpu.state.frame_count, *gpu.state.hcnt, ##__VA_ARGS__)
28
29 //#define log_io gpu_log
30 #define log_io(...)
31 //#define log_anomaly gpu_log
32 #define log_anomaly(...)
33
34 struct psx_gpu gpu;
35
36 static noinline int do_cmd_buffer(uint32_t *data, int count);
37 static void finish_vram_transfer(int is_read);
38
39 static noinline void do_cmd_reset(void)
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 }
65
66 static noinline void update_width(void)
67 {
68   int sw = gpu.screen.x2 - gpu.screen.x1;
69   if (sw <= 0 || sw >= 2560)
70     // full width
71     gpu.screen.w = gpu.screen.hres;
72   else
73     gpu.screen.w = sw * gpu.screen.hres / 2560;
74 }
75
76 static noinline void update_height(void)
77 {
78   // TODO: emulate this properly..
79   int sh = gpu.screen.y2 - gpu.screen.y1;
80   if (gpu.status & PSX_GPU_STATUS_DHEIGHT)
81     sh *= 2;
82   if (sh <= 0 || sh > gpu.screen.vres)
83     sh = gpu.screen.vres;
84
85   gpu.screen.h = sh;
86 }
87
88 static noinline void decide_frameskip(void)
89 {
90   if (gpu.frameskip.active)
91     gpu.frameskip.cnt++;
92   else {
93     gpu.frameskip.cnt = 0;
94     gpu.frameskip.frame_ready = 1;
95   }
96
97   if (!gpu.frameskip.active && *gpu.frameskip.advice)
98     gpu.frameskip.active = 1;
99   else if (gpu.frameskip.set > 0 && gpu.frameskip.cnt < gpu.frameskip.set)
100     gpu.frameskip.active = 1;
101   else
102     gpu.frameskip.active = 0;
103
104   if (!gpu.frameskip.active && gpu.frameskip.pending_fill[0] != 0) {
105     int dummy;
106     do_cmd_list(gpu.frameskip.pending_fill, 3, &dummy);
107     gpu.frameskip.pending_fill[0] = 0;
108   }
109 }
110
111 static noinline int decide_frameskip_allow(uint32_t cmd_e3)
112 {
113   // no frameskip if it decides to draw to display area,
114   // but not for interlace since it'll most likely always do that
115   uint32_t x = cmd_e3 & 0x3ff;
116   uint32_t y = (cmd_e3 >> 10) & 0x3ff;
117   gpu.frameskip.allow = (gpu.status & PSX_GPU_STATUS_INTERLACE) ||
118     (uint32_t)(x - gpu.screen.x) >= (uint32_t)gpu.screen.w ||
119     (uint32_t)(y - gpu.screen.y) >= (uint32_t)gpu.screen.h;
120   return gpu.frameskip.allow;
121 }
122
123 static noinline void get_gpu_info(uint32_t data)
124 {
125   switch (data & 0x0f) {
126     case 0x02:
127     case 0x03:
128     case 0x04:
129       gpu.gp0 = gpu.ex_regs[data & 7] & 0xfffff;
130       break;
131     case 0x05:
132       gpu.gp0 = gpu.ex_regs[5] & 0x3fffff;
133       break;
134     case 0x07:
135       gpu.gp0 = 2;
136       break;
137     default:
138       // gpu.gp0 unchanged
139       break;
140   }
141 }
142
143 // double, for overdraw guard
144 #define VRAM_SIZE (1024 * 512 * 2 * 2)
145
146 static int map_vram(void)
147 {
148   gpu.vram = gpu.mmap(VRAM_SIZE);
149   if (gpu.vram != NULL) {
150     gpu.vram += 4096 / 2;
151     return 0;
152   }
153   else {
154     fprintf(stderr, "could not map vram, expect crashes\n");
155     return -1;
156   }
157 }
158
159 long GPUinit(void)
160 {
161   int ret;
162   ret  = vout_init();
163   ret |= renderer_init();
164
165   gpu.state.frame_count = &gpu.zero;
166   gpu.state.hcnt = &gpu.zero;
167   gpu.frameskip.active = 0;
168   gpu.cmd_len = 0;
169   do_reset();
170
171   if (gpu.mmap != NULL) {
172     if (map_vram() != 0)
173       ret = -1;
174   }
175   return ret;
176 }
177
178 long GPUshutdown(void)
179 {
180   long ret;
181
182   renderer_finish();
183   ret = vout_finish();
184   if (gpu.vram != NULL) {
185     gpu.vram -= 4096 / 2;
186     gpu.munmap(gpu.vram, VRAM_SIZE);
187   }
188   gpu.vram = NULL;
189
190   return ret;
191 }
192
193 void GPUwriteStatus(uint32_t data)
194 {
195   static const short hres[8] = { 256, 368, 320, 384, 512, 512, 640, 640 };
196   static const short vres[4] = { 240, 480, 256, 480 };
197   uint32_t cmd = data >> 24;
198
199   if (cmd < ARRAY_SIZE(gpu.regs)) {
200     if (cmd > 1 && cmd != 5 && gpu.regs[cmd] == data)
201       return;
202     gpu.regs[cmd] = data;
203   }
204
205   gpu.state.fb_dirty = 1;
206
207   switch (cmd) {
208     case 0x00:
209       do_reset();
210       break;
211     case 0x01:
212       do_cmd_reset();
213       break;
214     case 0x03:
215       if (data & 1)
216         gpu.status |= PSX_GPU_STATUS_BLANKING;
217       else
218         gpu.status &= ~PSX_GPU_STATUS_BLANKING;
219       break;
220     case 0x04:
221       gpu.status &= ~PSX_GPU_STATUS_DMA_MASK;
222       gpu.status |= PSX_GPU_STATUS_DMA(data & 3);
223       break;
224     case 0x05:
225       gpu.screen.x = data & 0x3ff;
226       gpu.screen.y = (data >> 10) & 0x1ff;
227       if (gpu.frameskip.set) {
228         decide_frameskip_allow(gpu.ex_regs[3]);
229         if (gpu.frameskip.last_flip_frame != *gpu.state.frame_count) {
230           decide_frameskip();
231           gpu.frameskip.last_flip_frame = *gpu.state.frame_count;
232         }
233       }
234       break;
235     case 0x06:
236       gpu.screen.x1 = data & 0xfff;
237       gpu.screen.x2 = (data >> 12) & 0xfff;
238       update_width();
239       break;
240     case 0x07:
241       gpu.screen.y1 = data & 0x3ff;
242       gpu.screen.y2 = (data >> 10) & 0x3ff;
243       update_height();
244       break;
245     case 0x08:
246       gpu.status = (gpu.status & ~0x7f0000) | ((data & 0x3F) << 17) | ((data & 0x40) << 10);
247       gpu.screen.hres = hres[(gpu.status >> 16) & 7];
248       gpu.screen.vres = vres[(gpu.status >> 19) & 3];
249       update_width();
250       update_height();
251       renderer_notify_res_change();
252       break;
253     default:
254       if ((cmd & 0xf0) == 0x10)
255         get_gpu_info(data);
256       break;
257   }
258
259 #ifdef GPUwriteStatus_ext
260   GPUwriteStatus_ext(data);
261 #endif
262 }
263
264 const unsigned char cmd_lengths[256] =
265 {
266         0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
267         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
268         3, 3, 3, 3, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8, 8, 8, // 20
269         5, 5, 5, 5, 8, 8, 8, 8, 7, 7, 7, 7, 11, 11, 11, 11,
270         2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // 40
271         3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
272         2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 0, 0, 0, 0, // 60
273         1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
274         3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80
275         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
276         2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a0
277         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
278         2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c0
279         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
280         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e0
281         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
282 };
283
284 #define VRAM_MEM_XY(x, y) &gpu.vram[(y) * 1024 + (x)]
285
286 static inline void do_vram_line(int x, int y, uint16_t *mem, int l, int is_read)
287 {
288   uint16_t *vram = VRAM_MEM_XY(x, y);
289   if (is_read)
290     memcpy(mem, vram, l * 2);
291   else
292     memcpy(vram, mem, l * 2);
293 }
294
295 static int do_vram_io(uint32_t *data, int count, int is_read)
296 {
297   int count_initial = count;
298   uint16_t *sdata = (uint16_t *)data;
299   int x = gpu.dma.x, y = gpu.dma.y;
300   int w = gpu.dma.w, h = gpu.dma.h;
301   int o = gpu.dma.offset;
302   int l;
303   count *= 2; // operate in 16bpp pixels
304
305   if (gpu.dma.offset) {
306     l = w - gpu.dma.offset;
307     if (count < l)
308       l = count;
309
310     do_vram_line(x + o, y, sdata, l, is_read);
311
312     if (o + l < w)
313       o += l;
314     else {
315       o = 0;
316       y++;
317       h--;
318     }
319     sdata += l;
320     count -= l;
321   }
322
323   for (; h > 0 && count >= w; sdata += w, count -= w, y++, h--) {
324     y &= 511;
325     do_vram_line(x, y, sdata, w, is_read);
326   }
327
328   if (h > 0) {
329     if (count > 0) {
330       y &= 511;
331       do_vram_line(x, y, sdata, count, is_read);
332       o = count;
333       count = 0;
334     }
335   }
336   else
337     finish_vram_transfer(is_read);
338   gpu.dma.y = y;
339   gpu.dma.h = h;
340   gpu.dma.offset = o;
341
342   return count_initial - count / 2;
343 }
344
345 static void start_vram_transfer(uint32_t pos_word, uint32_t size_word, int is_read)
346 {
347   if (gpu.dma.h)
348     log_anomaly("start_vram_transfer while old unfinished\n");
349
350   gpu.dma.x = pos_word & 0x3ff;
351   gpu.dma.y = (pos_word >> 16) & 0x1ff;
352   gpu.dma.w = ((size_word - 1) & 0x3ff) + 1;
353   gpu.dma.h = (((size_word >> 16) - 1) & 0x1ff) + 1;
354   gpu.dma.offset = 0;
355   gpu.dma.is_read = is_read;
356   gpu.dma_start = gpu.dma;
357
358   renderer_flush_queues();
359   if (is_read) {
360     gpu.status |= PSX_GPU_STATUS_IMG;
361     // XXX: wrong for width 1
362     gpu.gp0 = LE32TOH(*(uint32_t *) VRAM_MEM_XY(gpu.dma.x, gpu.dma.y));
363     gpu.state.last_vram_read_frame = *gpu.state.frame_count;
364   }
365
366   log_io("start_vram_transfer %c (%d, %d) %dx%d\n", is_read ? 'r' : 'w',
367     gpu.dma.x, gpu.dma.y, gpu.dma.w, gpu.dma.h);
368 }
369
370 static void finish_vram_transfer(int is_read)
371 {
372   if (is_read)
373     gpu.status &= ~PSX_GPU_STATUS_IMG;
374   else
375     renderer_update_caches(gpu.dma_start.x, gpu.dma_start.y,
376                            gpu.dma_start.w, gpu.dma_start.h);
377 }
378
379 static noinline int do_cmd_list_skip(uint32_t *data, int count, int *last_cmd)
380 {
381   int cmd = 0, pos = 0, len, dummy, v;
382   int skip = 1;
383
384   gpu.frameskip.pending_fill[0] = 0;
385
386   while (pos < count && skip) {
387     uint32_t *list = data + pos;
388     cmd = LE32TOH(list[0]) >> 24;
389     len = 1 + cmd_lengths[cmd];
390
391     switch (cmd) {
392       case 0x02:
393         if ((LE32TOH(list[2]) & 0x3ff) > gpu.screen.w || ((LE32TOH(list[2]) >> 16) & 0x1ff) > gpu.screen.h)
394           // clearing something large, don't skip
395           do_cmd_list(list, 3, &dummy);
396         else
397           memcpy(gpu.frameskip.pending_fill, list, 3 * 4);
398         break;
399       case 0x24 ... 0x27:
400       case 0x2c ... 0x2f:
401       case 0x34 ... 0x37:
402       case 0x3c ... 0x3f:
403         gpu.ex_regs[1] &= ~0x1ff;
404         gpu.ex_regs[1] |= LE32TOH(list[4 + ((cmd >> 4) & 1)]) & 0x1ff;
405         break;
406       case 0x48 ... 0x4F:
407         for (v = 3; pos + v < count; v++)
408         {
409           if ((list[v] & HTOLE32(0xf000f000)) == HTOLE32(0x50005000))
410             break;
411         }
412         len += v - 3;
413         break;
414       case 0x58 ... 0x5F:
415         for (v = 4; pos + v < count; v += 2)
416         {
417           if ((list[v] & HTOLE32(0xf000f000)) == HTOLE32(0x50005000))
418             break;
419         }
420         len += v - 4;
421         break;
422       default:
423         if (cmd == 0xe3)
424           skip = decide_frameskip_allow(LE32TOH(list[0]));
425         if ((cmd & 0xf8) == 0xe0)
426           gpu.ex_regs[cmd & 7] = LE32TOH(list[0]);
427         break;
428     }
429
430     if (pos + len > count) {
431       cmd = -1;
432       break; // incomplete cmd
433     }
434     if (0xa0 <= cmd && cmd <= 0xdf)
435       break; // image i/o
436
437     pos += len;
438   }
439
440   renderer_sync_ecmds(gpu.ex_regs);
441   *last_cmd = cmd;
442   return pos;
443 }
444
445 static noinline int do_cmd_buffer(uint32_t *data, int count)
446 {
447   int cmd, pos;
448   uint32_t old_e3 = gpu.ex_regs[3];
449   int vram_dirty = 0;
450
451   // process buffer
452   for (pos = 0; pos < count; )
453   {
454     if (gpu.dma.h && !gpu.dma_start.is_read) { // XXX: need to verify
455       vram_dirty = 1;
456       pos += do_vram_io(data + pos, count - pos, 0);
457       if (pos == count)
458         break;
459     }
460
461     cmd = LE32TOH(data[pos]) >> 24;
462     if (0xa0 <= cmd && cmd <= 0xdf) {
463       if (unlikely((pos+2) >= count)) {
464         // incomplete vram write/read cmd, can't consume yet
465         cmd = -1;
466         break;
467       }
468
469       // consume vram write/read cmd
470       start_vram_transfer(LE32TOH(data[pos + 1]), LE32TOH(data[pos + 2]), (cmd & 0xe0) == 0xc0);
471       pos += 3;
472       continue;
473     }
474
475     // 0xex cmds might affect frameskip.allow, so pass to do_cmd_list_skip
476     if (gpu.frameskip.active && (gpu.frameskip.allow || ((LE32TOH(data[pos]) >> 24) & 0xf0) == 0xe0))
477       pos += do_cmd_list_skip(data + pos, count - pos, &cmd);
478     else {
479       pos += do_cmd_list(data + pos, count - pos, &cmd);
480       vram_dirty = 1;
481     }
482
483     if (cmd == -1)
484       // incomplete cmd
485       break;
486   }
487
488   gpu.status &= ~0x1fff;
489   gpu.status |= gpu.ex_regs[1] & 0x7ff;
490   gpu.status |= (gpu.ex_regs[6] & 3) << 11;
491
492   gpu.state.fb_dirty |= vram_dirty;
493
494   if (old_e3 != gpu.ex_regs[3])
495     decide_frameskip_allow(gpu.ex_regs[3]);
496
497   return count - pos;
498 }
499
500 static void flush_cmd_buffer(void)
501 {
502   int left = do_cmd_buffer(gpu.cmd_buffer, gpu.cmd_len);
503   if (left > 0)
504     memmove(gpu.cmd_buffer, gpu.cmd_buffer + gpu.cmd_len - left, left * 4);
505   gpu.cmd_len = left;
506 }
507
508 void GPUwriteDataMem(uint32_t *mem, int count)
509 {
510   int left;
511
512   log_io("gpu_dma_write %p %d\n", mem, count);
513
514   if (unlikely(gpu.cmd_len > 0))
515     flush_cmd_buffer();
516
517   left = do_cmd_buffer(mem, count);
518   if (left)
519     log_anomaly("GPUwriteDataMem: discarded %d/%d words\n", left, count);
520 }
521
522 void GPUwriteData(uint32_t data)
523 {
524   log_io("gpu_write %08x\n", data);
525   gpu.cmd_buffer[gpu.cmd_len++] = HTOLE32(data);
526   if (gpu.cmd_len >= CMD_BUFFER_LEN)
527     flush_cmd_buffer();
528 }
529
530 long GPUdmaChain(uint32_t *rambase, uint32_t start_addr, uint32_t *progress_addr)
531 {
532   uint32_t addr, *list, ld_addr = 0;
533   int len, left, count;
534   long cpu_cycles = 0;
535
536   preload(rambase + (start_addr & 0x1fffff) / 4);
537
538   if (unlikely(gpu.cmd_len > 0))
539     flush_cmd_buffer();
540
541   log_io("gpu_dma_chain\n");
542   addr = start_addr & 0xffffff;
543   for (count = 0; (addr & 0x800000) == 0; count++)
544   {
545     list = rambase + (addr & 0x1fffff) / 4;
546     len = LE32TOH(list[0]) >> 24;
547     addr = LE32TOH(list[0]) & 0xffffff;
548     preload(rambase + (addr & 0x1fffff) / 4);
549
550     cpu_cycles += 10;
551     if (len > 0)
552       cpu_cycles += 5 + len;
553
554     log_io(".chain %08lx #%d+%d\n",
555       (long)(list - rambase) * 4, len, gpu.cmd_len);
556     if (unlikely(gpu.cmd_len > 0)) {
557       memcpy(gpu.cmd_buffer + gpu.cmd_len, list + 1, len * 4);
558       gpu.cmd_len += len;
559       flush_cmd_buffer();
560       continue;
561     }
562
563     if (len) {
564       left = do_cmd_buffer(list + 1, len);
565       if (left) {
566         memcpy(gpu.cmd_buffer, list + 1 + len - left, left * 4);
567         gpu.cmd_len = left;
568         log_anomaly("GPUdmaChain: %d/%d words left\n", left, len);
569       }
570     }
571
572     if (progress_addr) {
573       *progress_addr = addr;
574       break;
575     }
576     #define LD_THRESHOLD (8*1024)
577     if (count >= LD_THRESHOLD) {
578       if (count == LD_THRESHOLD) {
579         ld_addr = addr;
580         continue;
581       }
582
583       // loop detection marker
584       // (bit23 set causes DMA error on real machine, so
585       //  unlikely to be ever set by the game)
586       list[0] |= HTOLE32(0x800000);
587     }
588   }
589
590   if (ld_addr != 0) {
591     // remove loop detection markers
592     count -= LD_THRESHOLD + 2;
593     addr = ld_addr & 0x1fffff;
594     while (count-- > 0) {
595       list = rambase + addr / 4;
596       addr = LE32TOH(list[0]) & 0x1fffff;
597       list[0] &= HTOLE32(~0x800000);
598     }
599   }
600
601   gpu.state.last_list.frame = *gpu.state.frame_count;
602   gpu.state.last_list.hcnt = *gpu.state.hcnt;
603   gpu.state.last_list.cycles = cpu_cycles;
604   gpu.state.last_list.addr = start_addr;
605
606   return cpu_cycles;
607 }
608
609 void GPUreadDataMem(uint32_t *mem, int count)
610 {
611   log_io("gpu_dma_read  %p %d\n", mem, count);
612
613   if (unlikely(gpu.cmd_len > 0))
614     flush_cmd_buffer();
615
616   if (gpu.dma.h)
617     do_vram_io(mem, count, 1);
618 }
619
620 uint32_t GPUreadData(void)
621 {
622   uint32_t ret;
623
624   if (unlikely(gpu.cmd_len > 0))
625     flush_cmd_buffer();
626
627   ret = gpu.gp0;
628   if (gpu.dma.h) {
629     ret = HTOLE32(ret);
630     do_vram_io(&ret, 1, 1);
631     ret = LE32TOH(ret);
632   }
633
634   log_io("gpu_read %08x\n", ret);
635   return ret;
636 }
637
638 uint32_t GPUreadStatus(void)
639 {
640   uint32_t ret;
641
642   if (unlikely(gpu.cmd_len > 0))
643     flush_cmd_buffer();
644
645   ret = gpu.status;
646   log_io("gpu_read_status %08x\n", ret);
647   return ret;
648 }
649
650 struct GPUFreeze
651 {
652   uint32_t ulFreezeVersion;      // should be always 1 for now (set by main emu)
653   uint32_t ulStatus;             // current gpu status
654   uint32_t ulControl[256];       // latest control register values
655   unsigned char psxVRam[1024*1024*2]; // current VRam image (full 2 MB for ZN)
656 };
657
658 long GPUfreeze(uint32_t type, struct GPUFreeze *freeze)
659 {
660   int i;
661
662   switch (type) {
663     case 1: // save
664       if (gpu.cmd_len > 0)
665         flush_cmd_buffer();
666       memcpy(freeze->psxVRam, gpu.vram, 1024 * 512 * 2);
667       memcpy(freeze->ulControl, gpu.regs, sizeof(gpu.regs));
668       memcpy(freeze->ulControl + 0xe0, gpu.ex_regs, sizeof(gpu.ex_regs));
669       freeze->ulStatus = gpu.status;
670       break;
671     case 0: // load
672       memcpy(gpu.vram, freeze->psxVRam, 1024 * 512 * 2);
673       memcpy(gpu.regs, freeze->ulControl, sizeof(gpu.regs));
674       memcpy(gpu.ex_regs, freeze->ulControl + 0xe0, sizeof(gpu.ex_regs));
675       gpu.status = freeze->ulStatus;
676       gpu.cmd_len = 0;
677       for (i = 8; i > 0; i--) {
678         gpu.regs[i] ^= 1; // avoid reg change detection
679         GPUwriteStatus((i << 24) | (gpu.regs[i] ^ 1));
680       }
681       renderer_sync_ecmds(gpu.ex_regs);
682       renderer_update_caches(0, 0, 1024, 512);
683       break;
684   }
685
686   return 1;
687 }
688
689 void GPUupdateLace(void)
690 {
691   if (gpu.cmd_len > 0)
692     flush_cmd_buffer();
693   renderer_flush_queues();
694
695   if (gpu.status & PSX_GPU_STATUS_BLANKING) {
696     if (!gpu.state.blanked) {
697       vout_blank();
698       gpu.state.blanked = 1;
699       gpu.state.fb_dirty = 1;
700     }
701     return;
702   }
703
704   if (!gpu.state.fb_dirty)
705     return;
706
707   if (gpu.frameskip.set) {
708     if (!gpu.frameskip.frame_ready) {
709       if (*gpu.state.frame_count - gpu.frameskip.last_flip_frame < 9)
710         return;
711       gpu.frameskip.active = 0;
712     }
713     gpu.frameskip.frame_ready = 0;
714   }
715
716   vout_update();
717   gpu.state.fb_dirty = 0;
718   gpu.state.blanked = 0;
719 }
720
721 void GPUvBlank(int is_vblank, int lcf)
722 {
723   int interlace = gpu.state.allow_interlace
724     && (gpu.status & PSX_GPU_STATUS_INTERLACE)
725     && (gpu.status & PSX_GPU_STATUS_DHEIGHT);
726   // interlace doesn't look nice on progressive displays,
727   // so we have this "auto" mode here for games that don't read vram
728   if (gpu.state.allow_interlace == 2
729       && *gpu.state.frame_count - gpu.state.last_vram_read_frame > 1)
730   {
731     interlace = 0;
732   }
733   if (interlace || interlace != gpu.state.old_interlace) {
734     gpu.state.old_interlace = interlace;
735
736     if (gpu.cmd_len > 0)
737       flush_cmd_buffer();
738     renderer_flush_queues();
739     renderer_set_interlace(interlace, !lcf);
740   }
741 }
742
743 #include "../../frontend/plugin_lib.h"
744
745 void GPUrearmedCallbacks(const struct rearmed_cbs *cbs)
746 {
747   gpu.frameskip.set = cbs->frameskip;
748   gpu.frameskip.advice = &cbs->fskip_advice;
749   gpu.frameskip.active = 0;
750   gpu.frameskip.frame_ready = 1;
751   gpu.state.hcnt = cbs->gpu_hcnt;
752   gpu.state.frame_count = cbs->gpu_frame_count;
753   gpu.state.allow_interlace = cbs->gpu_neon.allow_interlace;
754   gpu.state.enhancement_enable = cbs->gpu_neon.enhancement_enable;
755
756   gpu.mmap = cbs->mmap;
757   gpu.munmap = cbs->munmap;
758
759   // delayed vram mmap
760   if (gpu.vram == NULL)
761     map_vram();
762
763   if (cbs->pl_vout_set_raw_vram)
764     cbs->pl_vout_set_raw_vram(gpu.vram);
765   renderer_set_config(cbs);
766   vout_set_config(cbs);
767 }
768
769 // vim:shiftwidth=2:expandtab