cc68a136 |
1 | // This is part of Pico Library\r |
2 | \r |
3 | // (c) Copyright 2004 Dave, All rights reserved.\r |
4 | // (c) Copyright 2006 notaz, All rights reserved.\r |
5 | // Free for non-commercial use.\r |
6 | \r |
7 | // For commercial use, separate licencing terms must be obtained.\r |
8 | \r |
9 | \r |
10 | #include "PicoInt.h"\r |
11 | \r |
12 | extern const unsigned char hcounts_32[];\r |
13 | extern const unsigned char hcounts_40[];\r |
14 | extern const unsigned short vcounts[];\r |
15 | extern int rendstatus;\r |
16 | \r |
17 | typedef unsigned short u16;\r |
18 | \r |
19 | \r |
20 | static __inline void AutoIncrement()\r |
21 | {\r |
22 | Pico.video.addr=(unsigned short)(Pico.video.addr+Pico.video.reg[0xf]);\r |
23 | }\r |
24 | \r |
25 | static void VideoWrite(u16 d)\r |
26 | {\r |
27 | unsigned int a=Pico.video.addr;\r |
28 | \r |
29 | switch (Pico.video.type)\r |
30 | {\r |
31 | case 1: if(a&1) d=(u16)((d<<8)|(d>>8)); // If address is odd, bytes are swapped (which game needs this?)\r |
32 | Pico.vram [(a>>1)&0x7fff]=d;\r |
33 | rendstatus|=0x10; break;\r |
34 | case 3: Pico.m.dirtyPal = 1;\r |
35 | //dprintf("w[%i] @ %04x, inc=%i [%i|%i]", Pico.video.type, a, Pico.video.reg[0xf], Pico.m.scanline, SekCyclesDone());\r |
36 | Pico.cram [(a>>1)&0x003f]=d; break; // wraps (Desert Strike)\r |
37 | case 5: Pico.vsram[(a>>1)&0x003f]=d; break;\r |
38 | }\r |
39 | \r |
40 | //dprintf("w[%i] @ %04x, inc=%i [%i|%i]", Pico.video.type, a, Pico.video.reg[0xf], Pico.m.scanline, SekCyclesDone());\r |
41 | AutoIncrement();\r |
42 | }\r |
43 | \r |
44 | static unsigned int VideoRead()\r |
45 | {\r |
46 | unsigned int a=0,d=0;\r |
47 | \r |
48 | a=Pico.video.addr; a>>=1;\r |
49 | \r |
50 | switch (Pico.video.type)\r |
51 | {\r |
52 | case 0: d=Pico.vram [a&0x7fff]; break;\r |
53 | case 8: d=Pico.cram [a&0x003f]; break;\r |
54 | case 4: d=Pico.vsram[a&0x003f]; break;\r |
55 | }\r |
4f672280 |
56 | \r |
cc68a136 |
57 | AutoIncrement();\r |
58 | return d;\r |
59 | }\r |
60 | \r |
61 | // calculate the number of cycles 68k->VDP dma operation would take\r |
62 | static int DmaSlowBurn(int len)\r |
63 | {\r |
64 | // test: Legend of Galahad, Time Killers\r |
65 | int burn,maxlen,line=Pico.m.scanline;\r |
66 | \r |
67 | if(line == -1) line=vcounts[SekCyclesDone()>>8];\r |
68 | maxlen=(224-line)*18;\r |
69 | if(len <= maxlen)\r |
70 | burn = len*(((488<<8)/18))>>8;\r |
71 | else {\r |
72 | burn = maxlen*(((488<<8)/18))>>8;\r |
73 | burn += (len-maxlen)*(((488<<8)/180))>>8;\r |
74 | }\r |
75 | \r |
76 | return burn;\r |
77 | }\r |
78 | \r |
79 | static int GetDmaLength()\r |
80 | {\r |
81 | struct PicoVideo *pvid=&Pico.video;\r |
82 | int len=0;\r |
83 | // 16-bit words to transfer:\r |
84 | len =pvid->reg[0x13];\r |
85 | len|=pvid->reg[0x14]<<8;\r |
86 | // Charles MacDonald:\r |
87 | if(!len) len = 0xffff;\r |
88 | return len;\r |
89 | }\r |
90 | \r |
91 | static void DmaSlow(int len)\r |
92 | {\r |
93 | u16 *pd=0, *pdend, *r;\r |
94 | unsigned int a=Pico.video.addr, a2, d;\r |
95 | unsigned char inc=Pico.video.reg[0xf];\r |
96 | unsigned int source, burn;\r |
97 | \r |
98 | source =Pico.video.reg[0x15]<<1;\r |
99 | source|=Pico.video.reg[0x16]<<9;\r |
100 | source|=Pico.video.reg[0x17]<<17;\r |
101 | \r |
4f672280 |
102 | dprintf("DmaSlow[%i] %06x->%04x len %i inc=%i blank %i [%i|%i]", Pico.video.type, source, a, len, inc,\r |
103 | (Pico.video.status&8)||!(Pico.video.reg[1]&0x40), Pico.m.scanline, SekCyclesDone());\r |
cc68a136 |
104 | \r |
105 | if ((source&0xe00000)==0xe00000) { pd=(u16 *)(Pico.ram+(source&0xfffe)); pdend=(u16 *)(Pico.ram+0x10000); } // Ram\r |
106 | else if(source<Pico.romsize) { pd=(u16 *)(Pico.rom+(source&~1)); pdend=(u16 *)(Pico.rom+Pico.romsize); } // Rom\r |
107 | else return; // Invalid source address\r |
108 | \r |
4f672280 |
109 | #if 0\r |
cc68a136 |
110 | // CPU is stopped during DMA, so we burn some cycles to compensate that\r |
111 | if((Pico.video.status&8)||!(Pico.video.reg[1]&0x40)) { // vblank?\r |
112 | burn = (len*(((488<<8)/167))>>8); // very approximate\r |
113 | if(!(Pico.video.status&8)) burn+=burn>>1; // a hack for Legend of Galahad\r |
114 | } else burn = DmaSlowBurn(len);\r |
115 | SekCyclesBurn(burn);\r |
4f672280 |
116 | #else\r |
117 | Pico.m.dma_bytes += len;\r |
118 | #endif\r |
cc68a136 |
119 | if(!(Pico.video.status&8))\r |
120 | SekEndRun(0);\r |
4f672280 |
121 | // dprintf("DmaSlow burn: %i @ %06x", burn, SekPc);\r |
cc68a136 |
122 | \r |
123 | switch (Pico.video.type)\r |
124 | {\r |
125 | case 1: // vram\r |
126 | r = Pico.vram;\r |
127 | for(; len; len--)\r |
128 | {\r |
129 | d=*pd++;\r |
130 | if(a&1) d=(d<<8)|(d>>8);\r |
131 | r[a>>1] = (u16)d; // will drop the upper bits\r |
132 | // AutoIncrement\r |
133 | a=(u16)(a+inc);\r |
134 | // didn't src overlap?\r |
135 | if(pd >= pdend) pd-=0x8000; // should be good for RAM, bad for ROM\r |
136 | }\r |
137 | rendstatus|=0x10;\r |
138 | break;\r |
4f672280 |
139 | \r |
cc68a136 |
140 | case 3: // cram\r |
4f672280 |
141 | dprintf("DmaSlow[%i] %06x->%04x len %i inc=%i blank %i [%i|%i]", Pico.video.type, source, a, len, inc,\r |
142 | (Pico.video.status&8)||!(Pico.video.reg[1]&0x40), Pico.m.scanline, SekCyclesDone());\r |
cc68a136 |
143 | Pico.m.dirtyPal = 1;\r |
144 | r = Pico.cram;\r |
145 | for(a2=a&0x7f; len; len--)\r |
146 | {\r |
147 | r[a2>>1] = (u16)*pd++;; // bit 0 is ignored\r |
148 | // AutoIncrement\r |
149 | a2+=inc;\r |
150 | // didn't src overlap?\r |
151 | if(pd >= pdend) pd-=0x8000;\r |
152 | // good dest?\r |
153 | if(a2 >= 0x80) break; // Todds Adventures in Slime World / Andre Agassi tennis\r |
154 | }\r |
155 | a=(a&0xff00)|a2;\r |
156 | break;\r |
157 | \r |
158 | case 5: // vsram[a&0x003f]=d;\r |
159 | r = Pico.vsram;\r |
160 | for(a2=a&0x7f; len; len--)\r |
161 | {\r |
162 | r[a2>>1] = (u16)*pd++;\r |
163 | // AutoIncrement\r |
164 | a2+=inc;\r |
165 | // didn't src overlap?\r |
166 | if(pd >= pdend) pd-=0x8000;\r |
167 | // good dest?\r |
168 | if(a2 >= 0x80) break;\r |
169 | }\r |
170 | a=(a&0xff00)|a2;\r |
171 | break;\r |
172 | }\r |
173 | // remember addr\r |
174 | Pico.video.addr=(u16)a;\r |
175 | }\r |
176 | \r |
177 | static void DmaCopy(int len)\r |
178 | {\r |
179 | u16 a=Pico.video.addr;\r |
180 | unsigned char *vr = (unsigned char *) Pico.vram;\r |
181 | unsigned char *vrs;\r |
182 | unsigned char inc=Pico.video.reg[0xf];\r |
183 | int source;\r |
4f672280 |
184 | dprintf("DmaCopy len %i [%i|%i]", len, Pico.m.scanline, SekCyclesDone());\r |
185 | \r |
186 | Pico.m.dma_bytes += len;\r |
187 | Pico.video.status|=2; // dma busy\r |
cc68a136 |
188 | \r |
189 | source =Pico.video.reg[0x15];\r |
190 | source|=Pico.video.reg[0x16]<<8;\r |
191 | vrs=vr+source;\r |
192 | \r |
193 | if(source+len > 0x10000) len=0x10000-source; // clip??\r |
194 | \r |
195 | for(;len;len--)\r |
196 | {\r |
197 | vr[a] = *vrs++;\r |
198 | // AutoIncrement\r |
199 | a=(u16)(a+inc);\r |
200 | }\r |
201 | // remember addr\r |
202 | Pico.video.addr=a;\r |
203 | rendstatus|=0x10;\r |
204 | }\r |
205 | \r |
206 | // check: Contra, Megaman\r |
207 | // note: this is still inaccurate\r |
208 | static void DmaFill(int data)\r |
209 | {\r |
210 | int len;\r |
211 | unsigned short a=Pico.video.addr;\r |
212 | unsigned char *vr=(unsigned char *) Pico.vram;\r |
213 | unsigned char high = (unsigned char) (data >> 8);\r |
214 | unsigned char inc=Pico.video.reg[0xf];\r |
4f672280 |
215 | \r |
cc68a136 |
216 | len=GetDmaLength();\r |
4f672280 |
217 | dprintf("DmaFill len %i inc %i [%i|%i]", len, inc, Pico.m.scanline, SekCyclesDone());\r |
218 | \r |
219 | Pico.m.dma_bytes += len;\r |
220 | Pico.video.status|=2; // dma busy\r |
cc68a136 |
221 | \r |
222 | // from Charles MacDonald's genvdp.txt:\r |
223 | // Write lower byte to address specified\r |
224 | vr[a] = (unsigned char) data;\r |
225 | a=(u16)(a+inc);\r |
226 | \r |
227 | if(!inc) len=1;\r |
228 | \r |
229 | for(;len;len--) {\r |
230 | // Write upper byte to adjacent address\r |
231 | // (here we are byteswapped, so address is already 'adjacent')\r |
232 | vr[a] = high;\r |
233 | \r |
234 | // Increment address register\r |
235 | a=(u16)(a+inc);\r |
236 | }\r |
237 | // remember addr\r |
238 | Pico.video.addr=a;\r |
239 | // update length\r |
240 | Pico.video.reg[0x13] = Pico.video.reg[0x14] = 0; // Dino Dini's Soccer (E) (by Haze)\r |
241 | \r |
242 | rendstatus|=0x10;\r |
243 | }\r |
244 | \r |
245 | static void CommandDma()\r |
246 | {\r |
247 | struct PicoVideo *pvid=&Pico.video;\r |
248 | int len=0,method=0;\r |
249 | \r |
250 | if ((pvid->reg[1]&0x10)==0) return; // DMA not enabled\r |
251 | \r |
252 | len=GetDmaLength();\r |
253 | \r |
254 | method=pvid->reg[0x17]>>6;\r |
255 | if (method< 2) DmaSlow(len); // 68000 to VDP\r |
256 | if (method==3) DmaCopy(len); // VRAM Copy\r |
257 | }\r |
258 | \r |
259 | static void CommandChange()\r |
260 | {\r |
261 | struct PicoVideo *pvid=&Pico.video;\r |
262 | unsigned int cmd=0,addr=0;\r |
263 | \r |
264 | cmd=pvid->command;\r |
265 | \r |
266 | // Get type of transfer 0xc0000030 (v/c/vsram read/write)\r |
267 | pvid->type=(unsigned char)(((cmd>>2)&0xc)|(cmd>>30));\r |
268 | \r |
269 | // Get address 0x3fff0003\r |
270 | addr =(cmd>>16)&0x3fff;\r |
271 | addr|=(cmd<<14)&0xc000;\r |
272 | pvid->addr=(unsigned short)addr;\r |
273 | //dprintf("addr set: %04x", addr);\r |
274 | \r |
275 | // Check for dma:\r |
276 | if (cmd&0x80) CommandDma();\r |
277 | }\r |
278 | \r |
279 | void PicoVideoWrite(unsigned int a,unsigned short d)\r |
280 | {\r |
281 | struct PicoVideo *pvid=&Pico.video;\r |
282 | \r |
283 | a&=0x1c;\r |
284 | \r |
285 | if (a==0x00) // Data port 0 or 2\r |
4f672280 |
286 | {\r |
cc68a136 |
287 | if (pvid->pending) CommandChange();\r |
288 | pvid->pending=0;\r |
289 | \r |
290 | // If a DMA fill has been set up, do it\r |
291 | if ((pvid->command&0x80) && (pvid->reg[1]&0x10) && (pvid->reg[0x17]>>6)==2)\r |
292 | {\r |
293 | DmaFill(d);\r |
294 | }\r |
295 | else\r |
296 | {\r |
297 | VideoWrite(d);\r |
298 | }\r |
299 | return;\r |
300 | }\r |
301 | \r |
302 | if (a==0x04) // Control (command) port 4 or 6\r |
303 | {\r |
304 | //dprintf("vdp cw(%04x), p=%i @ %06x [%i]", d, pvid->pending, SekPc, SekCyclesDone());\r |
305 | if(pvid->pending)\r |
306 | {\r |
307 | // Low word of command:\r |
308 | pvid->command&=0xffff0000;\r |
309 | pvid->command|=d;\r |
310 | pvid->pending=0;\r |
311 | CommandChange();\r |
312 | } else {\r |
313 | if((d&0xc000)==0x8000)\r |
314 | {\r |
315 | // Register write:\r |
316 | int num=(d>>8)&0x1f;\r |
317 | //if(num==00) dprintf("hint_onoff: %i->%i [%i|%i] pend=%i @ %06x", (pvid->reg[0]&0x10)>>4, (d&0x10)>>4, Pico.m.scanline, SekCyclesDone(), (pvid->pending_ints&0x10)>>4, SekPc);\r |
318 | //if(num==01) dprintf("vint_onoff: %i->%i [%i|%i] pend=%i @ %06x", (pvid->reg[1]&0x20)>>5, (d&0x20)>>5, Pico.m.scanline, SekCyclesDone(), (pvid->pending_ints&0x20)>>5, SekPc);\r |
319 | //if(num==01) dprintf("set_blank: %i @ %06x [%i|%i]", !((d&0x40)>>6), SekPc, Pico.m.scanline, SekCyclesDone());\r |
320 | //if(num==05) dprintf("spr_set: %i @ %06x [%i|%i]", (unsigned char)d, SekPc, Pico.m.scanline, SekCyclesDone());\r |
321 | //if(num==10) dprintf("hint_set: %i @ %06x [%i|%i]", (unsigned char)d, SekPc, Pico.m.scanline, SekCyclesDone());\r |
322 | pvid->reg[num]=(unsigned char)d;\r |
323 | #if !(defined(EMU_C68K) && defined(EMU_M68K)) // not debugging Cyclone\r |
324 | // update IRQ level (Lemmings, Wiz 'n' Liz intro, ... )\r |
325 | // may break if done improperly:\r |
326 | // International Superstar Soccer Deluxe (crash), Street Racer (logos), Burning Force (gfx), Fatal Rewind (hang), Sesame Street Counting Cafe\r |
327 | if(num < 2) {\r |
328 | #ifdef EMU_C68K\r |
329 | // hack: make sure we do not touch the irq line if Cyclone is just about to take the IRQ\r |
330 | if (PicoCpu.irq <= (PicoCpu.srh&7)) {\r |
331 | #endif\r |
332 | int lines, pints;\r |
333 | lines = (pvid->reg[1] & 0x20) | (pvid->reg[0] & 0x10);\r |
334 | pints = (pvid->pending_ints&lines);\r |
335 | if(pints & 0x20) SekInterrupt(6);\r |
336 | else if(pints & 0x10) SekInterrupt(4);\r |
337 | else SekInterrupt(0);\r |
338 | #ifdef EMU_C68K\r |
339 | // adjust cycles for Cyclone so it would take the int "in time"\r |
340 | if(PicoCpu.irq) {\r |
341 | SekEndRun(24);\r |
342 | }\r |
343 | }\r |
344 | #endif\r |
345 | }\r |
346 | else\r |
347 | #endif\r |
348 | if(num == 5) rendstatus|=1;\r |
349 | else if(num == 0xc) Pico.m.dirtyPal = 2; // renderers should update their palettes if sh/hi mode is changed\r |
350 | pvid->type=0; // register writes clear command (else no Sega logo in Golden Axe II)\r |
351 | } else {\r |
352 | // High word of command:\r |
353 | pvid->command&=0x0000ffff;\r |
354 | pvid->command|=d<<16;\r |
355 | pvid->pending=1;\r |
356 | }\r |
357 | }\r |
358 | }\r |
359 | }\r |
360 | \r |
361 | unsigned int PicoVideoRead(unsigned int a)\r |
362 | {\r |
363 | unsigned int d=0;\r |
4f672280 |
364 | \r |
cc68a136 |
365 | a&=0x1c;\r |
366 | \r |
367 | if (a==0x00) // data port\r |
368 | {\r |
369 | d=VideoRead();\r |
370 | goto end;\r |
371 | }\r |
372 | \r |
373 | if (a==0x04) // control port\r |
374 | {\r |
cc68a136 |
375 | d=Pico.video.status;\r |
376 | if(PicoOpt&0x10) d|=0x0020; // sprite collision (Shadow of the Beast)\r |
377 | if(Pico.m.rotate++&8) d|=0x0100; else d|=0x0200; // Toggle fifo full empty (who uses that stuff?)\r |
378 | if(!(Pico.video.reg[1]&0x40)) d|=0x0008; // set V-Blank if display is disabled\r |
379 | if(SekCyclesLeft < 84+4) d|=0x0004; // H-Blank (Sonic3 vs)\r |
4f672280 |
380 | dprintf("sr_read %04x @ %06x [%i|%i]", d, SekPc, Pico.m.scanline, SekCyclesDone());\r |
cc68a136 |
381 | \r |
382 | Pico.video.pending=0; // ctrl port reads clear write-pending flag (Charles MacDonald)\r |
383 | \r |
384 | goto end;\r |
385 | }\r |
386 | \r |
387 | // H-counter info (based on Generator):\r |
388 | // frame:\r |
389 | // | <- hblank? -> |\r |
390 | // start <416> hint <36> hdisplay <38> end // CPU cycles\r |
391 | // |---------...---------|------------|-------------|\r |
392 | // 0 B6 E4 FF // 40 cells\r |
393 | // 0 93 E8 FF // 32 cells\r |
394 | \r |
395 | // Gens (?) v-render\r |
396 | // start <hblank=84> hint hdisplay <404> |\r |
397 | // |---------------------|--------------------------|\r |
398 | // E4 (hc[0x43]==0) 07 B1 // 40\r |
399 | // E8 (hc[0x45]==0) 05 91 // 32\r |
4f672280 |
400 | \r |
cc68a136 |
401 | // check: Sonic 3D Blast bonus, Cannon Fodder, Chase HQ II, 3 Ninjas kick back, Road Rash 3, Skitchin', Wheel of Fortune\r |
402 | if ((a&0x1c)==0x08)\r |
403 | {\r |
404 | unsigned int hc;\r |
405 | \r |
406 | if(Pico.m.scanline != -1) {\r |
407 | int lineCycles=(488-SekCyclesLeft)&0x1ff;\r |
408 | d=Pico.m.scanline; // V-Counter\r |
409 | \r |
410 | if(Pico.video.reg[12]&1)\r |
411 | hc=hcounts_40[lineCycles];\r |
412 | else hc=hcounts_32[lineCycles];\r |
413 | \r |
414 | if(lineCycles > 488-12) d++; // Wheel of Fortune\r |
415 | } else {\r |
416 | // get approximate V-Counter\r |
417 | d=vcounts[SekCyclesDone()>>8];\r |
418 | hc = Pico.m.rotate&0xff;\r |
419 | }\r |
420 | \r |
421 | if(Pico.m.pal) {\r |
422 | if(d >= 0x103) d-=56; // based on Gens\r |
423 | } else {\r |
424 | if(d >= 0xEB) d-=6;\r |
425 | }\r |
426 | \r |
427 | if((Pico.video.reg[12]&6) == 6) {\r |
428 | // interlace mode 2 (Combat Cars (UE) [!])\r |
429 | d <<= 1;\r |
430 | if (d&0xf00) d|= 1;\r |
431 | }\r |
432 | \r |
4f672280 |
433 | dprintf("hv: %02x %02x (%i) @ %06x", hc, d, SekCyclesDone(), SekPc);\r |
cc68a136 |
434 | d&=0xff; d<<=8;\r |
435 | d|=hc;\r |
436 | goto end;\r |
437 | }\r |
438 | \r |
439 | end:\r |
440 | \r |
441 | return d;\r |
442 | }\r |