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