1 /*
2  * vdc.c - MOS 8563 (VDC) emulation.
3  *
4  * Written by
5  *  Markus Brenner <markus@brenner.de>
6  *  Ettore Perazzoli <ettore@comm2000.it>
7  *  Andreas Boose <viceteam@t-online.de>
8  *
9  * This file is part of VICE, the Versatile Commodore Emulator.
10  * See README for copyright notice.
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25  *  02111-1307  USA.
26  *
27  */
28 
29 #include "vice.h"
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include "alarm.h"
36 #include "lib.h"
37 #include "log.h"
38 #include "machine.h"
39 #include "maincpu.h"
40 #include "raster.h"
41 #include "raster-line.h"
42 #include "raster-modes.h"
43 #include "resources.h"
44 #include "screenshot.h"
45 #include "snapshot.h"
46 #include "types.h"
47 #include "vdc-cmdline-options.h"
48 #include "vdc-color.h"
49 #include "vdc-draw.h"
50 #include "vdc-resources.h"
51 #include "vdc-snapshot.h"
52 #include "vdc.h"
53 #include "vdctypes.h"
54 #include "video.h"
55 #include "viewport.h"
56 
57 vdc_t vdc;
58 
59 static void vdc_raster_draw_alarm_handler(CLOCK offset, void *data);
60 
61 /* return pixel aspect ratio for current video mode */
62 /* FIXME: calculate proper values.
63    look at http://www.codebase64.org/doku.php?id=base:pixel_aspect_ratio&s[]=aspect
64    for an example calculation
65 */
vdc_get_pixel_aspect(void)66 static float vdc_get_pixel_aspect(void)
67 {
68 /*
69     int video;
70     resources_get_int("MachineVideoStandard", &video);
71     switch (video) {
72         case MACHINE_SYNC_PAL:
73         case MACHINE_SYNC_PALN:
74             return 0.936f;
75         default:
76             return 0.75f;
77     }
78 */
79     return 1.0f; /* assume 1:1 for CGA */
80 }
81 
82 /* return type of monitor used for current video mode */
vdc_get_crt_type(void)83 static int vdc_get_crt_type(void)
84 {
85     return 2; /* RGB */
86 }
87 
vdc_set_geometry(void)88 static void vdc_set_geometry(void)
89 {
90     raster_t *raster;
91     unsigned int screen_width, screen_height;
92     unsigned int first_displayed_line, last_displayed_line;
93     unsigned int screen_xpix, screen_ypix;
94     unsigned int border_height, border_width;
95     unsigned int vdc_25row_start_line, vdc_25row_stop_line;
96     unsigned int displayed_width, displayed_height;
97     unsigned int vdc_80col_start_pixel, vdc_80col_stop_pixel;
98     unsigned int charwidth;
99 
100     raster = &vdc.raster;
101 
102     screen_width = VDC_SCREEN_WIDTH;
103     screen_height = VDC_SCREEN_HEIGHT;
104 
105     first_displayed_line = vdc.first_displayed_line;
106     last_displayed_line = vdc.last_displayed_line;
107 
108     screen_xpix = vdc.screen_xpix;
109     screen_ypix = vdc.screen_ypix;
110 
111     border_width = vdc.border_width;
112     border_height = vdc.border_height;
113 
114     vdc_25row_start_line = border_height;
115     vdc_25row_stop_line = vdc_25row_start_line + screen_ypix;
116 
117     if(vdc.regs[25] & 0x10) { /* double pixel a.k.a 40column mode */
118         charwidth = 2 * (vdc.regs[22] >> 4);
119     } else { /* 80 column mode */
120         charwidth = 1 + (vdc.regs[22] >> 4);
121     }
122     vdc_80col_start_pixel = border_width;
123     vdc_80col_stop_pixel = vdc_80col_start_pixel + charwidth * vdc.screen_text_cols;
124 
125     displayed_width = VDC_SCREEN_WIDTH;
126     displayed_height = last_displayed_line - first_displayed_line + 1;
127 
128 /*
129 printf("SH: %03i SW: %03i\n", screen_height, screen_width);
130 printf("YP: %03i XP: %03i\n", screen_ypix, screen_xpix);
131 printf("DH: %03i DW: %03i\n", displayed_height, displayed_width);
132 printf("BH: %03i BW: %03i\n", border_height, border_width);
133 printf("SA: %03i SO: %03i\n", vdc_25row_start_line, vdc_25row_stop_line);
134 printf("LD: %03i FD: %03i\n", last_displayed_line, first_displayed_line);
135 */
136 
137     raster->display_ystart = vdc_25row_start_line;
138     raster->display_ystop = vdc_25row_stop_line;
139     raster->display_xstart = vdc_80col_start_pixel;
140     raster->display_xstop = vdc_80col_stop_pixel;
141 
142     raster_set_geometry(raster,
143                         displayed_width, displayed_height,  /* canvas - physically displayed width/height, ie size of visible window */
144                         screen_width, screen_height,        /* width/height of virtual screen */
145                         screen_xpix, screen_ypix,   /* size of the foreground area (pixels) */
146                         VDC_SCREEN_MAX_TEXTCOLS, vdc.screen_textlines,  /* size of the foreground area (characters) */
147                         border_width, vdc_25row_start_line, /* gfx_pos_x/y - position of visible screen in virtual coords */
148                         0,  /* gfx_area_moves */
149                         first_displayed_line,   /* 1st line of virtual screen physically visible */
150                         last_displayed_line,    /* last line physically visible */
151                         0, 0); /* extra off screen border left / right */
152 
153     raster->geometry->pixel_aspect_ratio = vdc_get_pixel_aspect();
154     raster->geometry->char_pixel_width = charwidth;
155     raster->viewport->crt_type = vdc_get_crt_type();
156 }
157 
vdc_invalidate_cache(raster_t * raster,unsigned int screen_height)158 static void vdc_invalidate_cache(raster_t *raster, unsigned int screen_height)
159 {
160     raster_new_cache(raster, screen_height);
161 }
162 
init_raster(void)163 static int init_raster(void)
164 {
165     raster_t *raster;
166 
167     raster = &vdc.raster;
168 
169     raster->sprite_status = NULL;
170     raster_line_changes_init(raster);
171 
172     if (raster_init(raster, VDC_NUM_VMODES) < 0) {
173         return -1;
174     }
175 
176     raster_modes_set_idle_mode(raster->modes, VDC_IDLE_MODE);
177     resources_touch("VDCVideoCache");
178 
179     vdc_set_geometry();
180 
181     if (vdc_color_update_palette(vdc.raster.canvas) < 0) {
182         log_error(vdc.log, "Cannot load palette.");
183         return -1;
184     }
185 
186     raster_set_title(raster, machine_name);
187 
188     if (raster_realize(raster) < 0) {
189         return -1;
190     }
191 
192     raster->border_color = 0;
193 
194     /* FIXME: this seems to be the only way to disable cache on VDC.
195        The GUI (at least on win32) doesn't let you do it */
196     /* raster->cache_enabled = 0; */  /* Force disable cache for testing non-cache mode */
197 
198     return 0;
199 }
200 
201 /* Initialize the VDC emulation. */
vdc_init(void)202 raster_t *vdc_init(void)
203 {
204     vdc.initialized = 0;
205 
206     vdc.log = log_open("VDC");
207 
208     vdc.raster_draw_alarm = alarm_new(maincpu_alarm_context, "VdcRasterDraw",
209                                       vdc_raster_draw_alarm_handler, NULL);
210 
211     vdc_powerup();
212 
213     if (init_raster() < 0) {
214         return NULL;
215     }
216 
217     vdc.force_resize = 0;
218     vdc.force_repaint = 0;
219 
220     vdc_draw_init();
221 
222     vdc.initialized = 1;
223 
224     /*vdc_set_geometry();*/
225     resources_touch("VDCDoubleSize");
226 
227     return &vdc.raster;
228 }
229 
vdc_get_canvas(void)230 struct video_canvas_s *vdc_get_canvas(void)
231 {
232     return vdc.raster.canvas;
233 }
234 
235 
vdc_set_next_alarm(CLOCK offset)236 static void vdc_set_next_alarm(CLOCK offset)
237 {
238     unsigned int next_alarm;
239     static unsigned int next_line_accu = 0;
240 
241     next_line_accu += vdc.xsync_increment;
242     next_alarm = next_line_accu >> 16;
243     next_line_accu -= (next_alarm << 16);
244 
245     /* Set the next draw event. */
246     alarm_set(vdc.raster_draw_alarm, maincpu_clk + (CLOCK)next_alarm - offset);
247 }
248 
vdc_update_geometry(void)249 static void vdc_update_geometry(void)
250 {   /* This sets things based on registers:  2, 6, 9
251     it sets vdc.screenheight
252                 last_displayed_line
253                 screen_textlines
254                 screen_ypix
255                 screen_text_cols
256                 hsync_shift
257                 border_width    */
258 
259     int charwidth, hsync;
260 
261     /* Leave this fixed so the window isn't getting constantly resized */
262     vdc.screen_height = VDC_SCREEN_HEIGHT;
263 
264     vdc.last_displayed_line = MIN(VDC_LAST_DISPLAYED_LINE, vdc.screen_height - 1);
265 
266     /* TODO get rid of this if/when it we don't need it anymore..  */
267 /*     printf("BH:%1i 0:%02X 1:%02X 2:%02X 3:%02X 4:%02X 5:%02X 6:%02X 7:%02X 9:%02X 22:%02X 24:%02X 25:%02X 26:%02X\n",
268         vdc.border_height, vdc.regs[0], vdc.regs[1], vdc.regs[2], vdc.regs[3], vdc.regs[4], (vdc.regs[5] & 0x1f), vdc.regs[6], vdc.regs[7], vdc.regs[9] & 0x1f, vdc.regs[22], vdc.regs[24], vdc.regs[25], vdc.regs[26] );
269 */
270 
271     vdc.screen_textlines = vdc.regs[6];
272 
273     vdc.screen_ypix = vdc.regs[6] * ((vdc.regs[9] & 0x1f) + 1);
274 
275     if (vdc.regs[1] >= 6 && vdc.regs[1] <= VDC_SCREEN_MAX_TEXTCOLS) {
276         vdc.screen_text_cols = vdc.regs[1];
277     } else if (vdc.regs[1] < 6) {
278         vdc.screen_text_cols = 6;
279     } else {
280         vdc.screen_text_cols = VDC_SCREEN_MAX_TEXTCOLS;
281     }
282 
283     /* FIXME: this seems to have semi-randomly selected constants... */
284     if(vdc.regs[25] & 0x10) { /* double pixel a.k.a 40column mode */
285         charwidth = 2 * (vdc.regs[22] >> 4);
286         hsync = 62 * 16            /* 992 */
287             - vdc.regs[2] * charwidth;       /* default (55) - 880 = 112 */
288     } else { /* 80 column mode */
289         charwidth = 1 + (vdc.regs[22] >> 4);
290         hsync = 116 * 8            /* 928 */
291             - vdc.regs[2] * charwidth;       /* default (102) - 816 = 112 */
292     }
293     /* FIXME: It's potentially valid for the active display area to start off the left (or right) of the display,
294         because the VDC can put the horizontal sync anywhere. But we can't really emulate that (yet) without things crashing. */
295     if (hsync < 0) {
296             hsync = 0;
297     }
298     vdc.hsync_shift = hsync;
299 
300     /* clamp the display within the right edge of the screen */
301     if ((vdc.screen_text_cols * charwidth) > VDC_SCREEN_WIDTH ) {
302         /* bounds check so we don't wind up with a "negative unsigned" hsync_shift (i.e. a massive value) which then causes a segfault in vdc-draw.. */
303         vdc.hsync_shift = 0;
304     } else if (vdc.hsync_shift + (vdc.screen_text_cols * charwidth) > VDC_SCREEN_WIDTH ) {
305         vdc.hsync_shift = VDC_SCREEN_WIDTH - (vdc.screen_text_cols * charwidth);
306     }
307     vdc.border_width = vdc.hsync_shift;
308 
309     vdc.update_geometry = 0;
310 }
311 
312 
313 /* Reset the VDC chip */
vdc_reset(void)314 void vdc_reset(void)
315 {
316     if (vdc.initialized) {
317         raster_reset(&vdc.raster);
318     }
319 
320     vdc.frame_counter = 0;
321     vdc.screen_text_cols = VDC_SCREEN_MAX_TEXTCOLS;
322     vdc.xsmooth = 7;
323     vdc.regs[0] = 126;
324     vdc.regs[1] = 102;
325     vdc.xchars_total = vdc.regs[0] + 1;
326     vdc_calculate_xsync();
327     vdc.regs[4] = 39;
328     vdc.regs[5] = 0;
329     vdc.regs[6] = 25;
330     vdc.regs[9] = vdc.raster_ycounter_max = 7;
331     vdc.attribute_offset = 0;
332     vdc.border_height = 59;
333     vdc.bytes_per_char = 16;
334     vdc_update_geometry();
335     vdc_set_next_alarm((CLOCK)0);
336     vdc.light_pen.x = vdc.light_pen.y = vdc.light_pen.triggered = 0;
337 }
338 
339 /* This _should_ put the VDC in the same state as powerup */
vdc_powerup(void)340 void vdc_powerup(void)
341 {
342     /* Setup the VDC's ram with a 0xff00ff00.. pattern */
343     unsigned int i;
344     uint8_t v = 0xff;
345     for (i = 0; i < sizeof(vdc.ram); i++) {
346         vdc.ram[i] = v;
347         v ^= 0xff;
348     }
349     memset(vdc.regs, 0, sizeof(vdc.regs));
350     vdc.mem_counter = 0;
351     vdc.mem_counter_inc = 0;
352 
353     vdc.screen_xpix = VDC_SCREEN_XPIX;
354     vdc.first_displayed_line = VDC_FIRST_DISPLAYED_LINE;
355     vdc.last_displayed_line = VDC_LAST_DISPLAYED_LINE;
356 
357     vdc_reset();
358 }
359 
360 
361 
362 /* ---------------------------------------------------------------------*/
363 
364 /* Trigger the light pen.  */
vdc_trigger_light_pen(CLOCK mclk)365 void vdc_trigger_light_pen(CLOCK mclk)
366 {
367     vdc.light_pen.triggered = 1;
368     vdc.regs[16] = vdc.light_pen.y;
369     vdc.regs[17] = vdc.light_pen.x;
370 }
371 
372 /* Calculate lightpen pulse time based on x/y */
vdc_lightpen_timing(int x,int y)373 CLOCK vdc_lightpen_timing(int x, int y)
374 {
375     CLOCK pulse_time;
376 
377     double vdc_cycles_per_line, host_cycles_per_second;
378     host_cycles_per_second = (double)machine_get_cycles_per_second();
379     vdc_cycles_per_line = (double)(vdc.xchars_total) * 8.0
380                           * host_cycles_per_second / VDC_DOT_CLOCK;
381 
382     /* FIXME - this doesn't work properly.. */
383     pulse_time = maincpu_clk;
384     pulse_time += (CLOCK)((x / 8) + (y * vdc_cycles_per_line));
385 
386     /* Figure out what values should go into the registers when triggered */
387     vdc.light_pen.y = (y - (int)vdc.first_displayed_line - 1) / ((int)(vdc.regs[9] & 0x1f) + 1);
388     if (vdc.light_pen.y < 0) {
389         vdc.light_pen.y += vdc.regs[4] + 1;
390     }
391     vdc.light_pen.x = (x - vdc.border_width) / ((vdc.regs[22] >> 4) + 1) + 22;
392     return pulse_time;
393 }
394 
395 /* ---------------------------------------------------------------------*/
396 
397 /* Set the memory pointers according to the values in the registers. */
vdc_update_memory_ptrs(unsigned int cycle)398 void vdc_update_memory_ptrs(unsigned int cycle)
399 {
400     /* FIXME: use it or lose it */
401 }
402 
vdc_increment_memory_pointer(void)403 static void vdc_increment_memory_pointer(void)
404 {
405     vdc.mem_counter_inc = vdc.screen_text_cols;
406     if (vdc.raster.ycounter >= vdc.raster_ycounter_max) {
407         vdc.mem_counter += vdc.mem_counter_inc + vdc.regs[27];
408     }
409 
410     vdc.raster.ycounter = (vdc.raster.ycounter + 1)
411                           % (vdc.raster_ycounter_max + 1);
412 
413     vdc.bitmap_counter += vdc.mem_counter_inc + vdc.regs[27];
414 }
415 
vdc_increment_memory_pointer_interlace_bitmap(void)416 static void vdc_increment_memory_pointer_interlace_bitmap(void)
417 {   /* This is identical to above (and should remain so), we just don't increment the bitmap pointer */
418     vdc.mem_counter_inc = vdc.screen_text_cols;
419     if (vdc.raster.ycounter >= vdc.raster_ycounter_max) {
420         vdc.mem_counter += vdc.mem_counter_inc + vdc.regs[27];
421     }
422     vdc.raster.ycounter = (vdc.raster.ycounter + 1)
423                           % (vdc.raster_ycounter_max + 1);
424 }
425 
vdc_set_video_mode(void)426 static void vdc_set_video_mode(void)
427 {
428     vdc.raster.video_mode = (vdc.regs[25] & 0x80)
429                             ? VDC_BITMAP_MODE : VDC_TEXT_MODE;
430 
431     if (vdc.raster.ycounter > (unsigned int)(vdc.regs[9] & 0x1f)) {
432         vdc.raster.video_mode = VDC_IDLE_MODE;
433     }
434 }
435 
436 
437 /* Redraw the current raster line. */
vdc_raster_draw_alarm_handler(CLOCK offset,void * data)438 static void vdc_raster_draw_alarm_handler(CLOCK offset, void *data)
439 {
440     int in_idle_state, calculated_border_height, i;
441     static unsigned int old_screen_adr, old_attribute_adr, screen_ystart, need_increment_memory_pointer;
442 
443     /* Update the memory pointers just before we draw the next line (vs after last line),
444        in case relevant registers changed since last call. */
445     if (need_increment_memory_pointer) {
446         vdc_increment_memory_pointer();
447         /* If interlace we need to skip a line, so we increment again */
448         if ((vdc.regs[8] & 0x03) == 3) {
449             if (vdc.regs[25] & 0x80) {    /* in interlace bitmap mode we want to increment the attribute memory pointer again, but not the bitmap pointer */
450                 vdc_increment_memory_pointer_interlace_bitmap();
451             } else {    /* text mode */
452                 vdc_increment_memory_pointer();
453             }
454         }
455         need_increment_memory_pointer = 0;
456     }
457 
458     /* VDC locks in the screen/attr start addresses after the last raster line of foreground */
459     if (vdc.raster.current_line == vdc.border_height + vdc.screen_ypix + 1) {
460         vdc.screen_adr = ((vdc.regs[12] << 8) | vdc.regs[13])
461                          & vdc.vdc_address_mask;
462         vdc.attribute_adr = ((vdc.regs[20] << 8) | vdc.regs[21])
463                             & vdc.vdc_address_mask;
464         if (old_screen_adr != vdc.screen_adr || old_attribute_adr != vdc.attribute_adr) {
465             /* the cache can't cleanly handle these changing */
466             vdc.force_repaint = 1;
467             old_screen_adr = vdc.screen_adr;
468             old_attribute_adr = vdc.attribute_adr;
469         }
470     }
471 
472     if (vdc.raster.current_line == 0) { /* We are on the first raster line, so go reset and/or handle everything for a new frame */
473         /* The top border position is based on the position of the vertical
474            sync pulse [7] in relation to the total height of the screen [4]
475            and the width of the sync pulse [3] */
476         calculated_border_height = (vdc.regs[4] + 1 - vdc.regs[7])  /* # of rows from sync pulse */
477                                    * ((vdc.regs[9] & 0x1f) + 1)     /* height of each row (R9) */
478                                    - (vdc.regs[3] >> 4)             /* vertical sync pulse width */
479                                    + (vdc.regs[5] & 0x1f);          /* vertical total adjust */
480 
481         if (calculated_border_height >= 0 && calculated_border_height <= VDC_SCREEN_HEIGHT) {
482             vdc.border_height = calculated_border_height;
483         } else {
484             vdc.border_height = 0;
485         }
486         vdc.screen_ypix = vdc.regs[6] * ((vdc.regs[9] & 0x1f) + 1);
487         /* screen_ystart is the raster line the foreground data actually starts on, which may be above or below the border */
488         screen_ystart = vdc.border_height + (((vdc.regs[9] & 0x1f) - (vdc.regs[24] & 0x1f)) & 0x1f);  /* - R24 is vertical smooth scroll, which interacts with the screen & R9 like this based on experimentation. */
489         vdc.border_height = vdc.border_height + (vdc.regs[9] & 0x1f);
490 
491         /* If interlace we need to adjust the borders and screen height because they are twice as big otherwise */
492         if ((vdc.regs[8] & 0x03) == 3)  {
493             vdc.border_height >>= 1;
494             vdc.screen_ypix >>= 1;
495             screen_ystart >>= 1;
496         }
497 
498         /* fix to catch the end of the display for the bitmap/character memory pointers */
499         if ((vdc.border_height + vdc.screen_ypix + 1) > vdc.last_displayed_line) {
500             vdc.screen_ypix = vdc.last_displayed_line - vdc.border_height - 1;
501         }
502         vdc.raster.display_ystart = vdc.border_height;
503         vdc.raster.display_ystop = vdc.border_height + vdc.screen_ypix;
504         vdc.row_counter = 0;
505         vdc.row_counter_y = vdc.raster_ycounter_max;
506         vdc.raster.video_mode = VDC_IDLE_MODE;
507         vdc.frame_counter++;    /* Note that as far as the frame counter is concerned, we are now on a new frame */
508         if (vdc.regs[24] & 0x20) {
509             vdc.attribute_blink = vdc.frame_counter & 16;
510         } else {
511             vdc.attribute_blink = vdc.frame_counter & 8;
512         }
513 
514         /* Normally we would reset all the memory pointers etc. on a new frame.
515         BUT, in one particular circumstance we don't - interlace bitmap mode on an even frame -
516         because interlace bitmap is interleaved, we want it to keep reading into the 2nd bank on the even frame
517         (it starts on an odd frame) */
518         if (((vdc.regs[8] & 0x03) == 3)  /* interlace */
519             && (vdc.regs[25] & 0x80)    /* bitmap mode */
520             && !(vdc.frame_counter & 1)) { /* even frame */
521             /* FIXME: hideous hacks to fix VDCModeMania
522                Need to figure out WTF the VDC is doing with its memory pointers that we need this much hackery.. */
523             switch (vdc.regs[4]) {
524                 case 0x84:  /* Option 1 - NTSC  Interlace */
525                     for (i = 0; i < 20; i++) { /* decrement the memory pointer a few times based on [WTF?] */
526                         vdc.mem_counter -= vdc.mem_counter_inc + vdc.regs[27];
527                     }
528                     for (i = 0; i < 40; i++) { /* decrement the bitmap memory pointer a few times based on how many columns are in a row */
529                         vdc.bitmap_counter -= (vdc.mem_counter_inc + vdc.regs[27]);
530                     }
531                     break;
532                 case 0x68:  /* Option 2 - PAL Interlace */
533                     for (i = 0; i < 47; i++) { /* increment the memory pointer a few times based on [WTF?] */
534                         vdc_increment_memory_pointer_interlace_bitmap();    /* increment everything except the bitmap pointer */
535                     }
536                     for (i = 0; i < 23; i++) { /* increment the bitmap memory pointer a few times based on how many columns are in a row */
537                         vdc.bitmap_counter += (vdc.mem_counter_inc + vdc.regs[27]);
538                     }
539                     break;
540                 case 0x6a:  /* Option 5 - "VDC-IMONO" 720x700 Mono Interlace */
541                     for (i = 0; i < 83; i++) { /* increment the bitmap memory pointer a few times based on how many columns are in a row */
542                         vdc.bitmap_counter += (vdc.mem_counter_inc + vdc.regs[27]);
543                     }
544                     break;
545                 case 0x5c:  /* Option 6 - "VDC-IM800" 800x600 Mono Interlace */
546                     for (i = 0; i < 34; i++) { /* increment the bitmap memory pointer a few times based on how many columns are in a row */
547                         vdc.bitmap_counter += (vdc.mem_counter_inc + vdc.regs[27]);
548                     }
549                     break;
550                 case 0x4c:  /* Platoterm - 640x532 Mono Interlace */
551                     for (i = 0; i < 20; i++) { /* decrement the bitmap memory pointer a few times based on how many columns are in a row */
552                         vdc.bitmap_counter -= (vdc.mem_counter_inc + vdc.regs[27]);
553                     }
554                     break;
555             }
556         } else {    /* Reset all the internal VDC memory pointers and counters to 0 */
557             vdc.mem_counter = 0;
558             need_increment_memory_pointer = 0;
559             vdc.bitmap_counter = 0;
560             vdc.raster.ycounter = 0;
561         }
562 
563         /* If interlace mode we need to skip to the 2nd line on an odd frame so that we render the odd field vs the even */
564         if (((vdc.regs[8] & 0x03) == 3)     /* interlace and */
565         && (vdc.frame_counter & 1)) {       /* odd frame */
566             if (!(vdc.regs[25] & 0x80)) {   /* text mode */
567                 vdc_increment_memory_pointer();
568             } else {                        /* bitmap mode */
569                 /* FIXME: this hack fixes VDCModeMania, but I'm not completely sure why, or if it's correct in general.. */
570                 /* Add half the number of raster lines in a char * number of characters in a row, to the bitmap pointer only
571                     effectively skips the bitmap pointer over one full (interlace) attribute cell row worth of bitmap data
572                     but why, because we don't increment the attribute pointer?? */
573                 vdc.bitmap_counter += (((vdc.regs[9] & 0x1f) + 1) >> 1) * (vdc.mem_counter_inc + vdc.regs[27]);
574             }
575         }
576 
577         if (vdc.update_geometry) {
578             vdc_update_geometry();
579             vdc.force_resize = 1;
580             vdc.force_repaint = 1;
581             /* Screen height has changed, so do not invalidate cache with
582                the new value.  It will be recreated by resize anyway.  */
583             vdc.force_cache_flush = 0;
584         } else {
585             if (vdc.force_cache_flush) {
586                 vdc_invalidate_cache(&vdc.raster, vdc.screen_height);
587                 vdc.force_cache_flush = 0;
588             }
589         }
590 
591         if (vdc.force_resize) {
592             if (vdc.initialized) {
593                 vdc_set_geometry();
594                 raster_mode_change();
595             }
596             vdc.force_resize = 0;
597         }
598 
599         if (vdc.force_repaint) {
600             vdc.force_repaint = 0;
601             raster_force_repaint(&vdc.raster);
602         }
603     }
604 
605     /* If in_idle_state then we are not drawing anything on the current raster line */
606     in_idle_state = (vdc.raster.current_line < vdc.border_height)
607                     || vdc.raster.current_line < screen_ystart
608                     || (vdc.raster.current_line >= (vdc.border_height + vdc.screen_ypix));
609 
610     if (!in_idle_state) {
611         vdc_set_video_mode();
612     } else {
613         vdc.raster.video_mode = VDC_IDLE_MODE;
614     }
615 
616     /* actually draw the current raster line */
617     raster_line_emulate(&vdc.raster);
618 
619     /* see if we still should be drawing things - if we haven't drawn more than regs[6] rows since the top border */
620     if (!in_idle_state) {
621         vdc.row_counter_y--;
622         if (vdc.row_counter_y < 0) {
623             vdc.row_counter_y = vdc.raster_ycounter_max;
624             /* update the row counter if we are starting a new line */
625             vdc.row_counter++;
626             /* check if we are at the end of the display */
627             if (vdc.row_counter == vdc.regs[6]) {
628                 /* vdc.last_displayed_line = vdc.raster.current_line; */
629                 /* FIXME - this is really a hack to lock in the screen/attr addresses at the next raster alarm handler */
630                 vdc.screen_ypix = vdc.raster.current_line - vdc.border_height;
631             }
632         }
633     }
634 
635     /* update the memory pointers if we are past screen_ystart, which may be above or below the top border */
636     need_increment_memory_pointer = (vdc.raster.current_line > screen_ystart);
637 
638     vdc_set_next_alarm(offset);
639 }
640 
641 
vdc_calculate_xsync(void)642 void vdc_calculate_xsync(void)
643 {
644     double vdc_cycles_per_line, host_cycles_per_second;
645 
646     host_cycles_per_second = (double)machine_get_cycles_per_second();
647 
648     vdc_cycles_per_line = (double)(vdc.xchars_total) * 8.0
649                           * host_cycles_per_second / VDC_DOT_CLOCK;
650 
651     vdc.xsync_increment = (unsigned int)(vdc_cycles_per_line * 65536);
652 }
653 
vdc_set_canvas_refresh(int enable)654 void vdc_set_canvas_refresh(int enable)
655 {
656     raster_set_canvas_refresh(&vdc.raster, enable);
657 }
658 
vdc_write_snapshot_module(snapshot_t * s)659 int vdc_write_snapshot_module(snapshot_t *s)
660 {
661     return vdc_snapshot_write_module(s);
662 }
663 
vdc_read_snapshot_module(snapshot_t * s)664 int vdc_read_snapshot_module(snapshot_t *s)
665 {
666     return vdc_snapshot_read_module(s);
667 }
668 
vdc_screenshot(screenshot_t * screenshot)669 void vdc_screenshot(screenshot_t *screenshot)
670 {
671     raster_screenshot(&vdc.raster, screenshot);
672     screenshot->chipid = "VDC";
673     screenshot->video_regs = vdc.regs;
674     screenshot->screen_ptr = vdc.ram + vdc.screen_adr;
675     screenshot->chargen_ptr = vdc.ram + vdc.chargen_adr;
676     screenshot->bitmap_ptr = NULL; /* todo */
677     screenshot->bitmap_low_ptr = NULL;
678     screenshot->bitmap_high_ptr = NULL;
679     screenshot->color_ram_ptr = vdc.ram + vdc.attribute_adr;
680 }
681 
vdc_async_refresh(struct canvas_refresh_s * refresh)682 void vdc_async_refresh(struct canvas_refresh_s *refresh)
683 {
684     raster_async_refresh(&vdc.raster, refresh);
685 }
686 
vdc_shutdown(void)687 void vdc_shutdown(void)
688 {
689     raster_shutdown(&vdc.raster);
690 }
691