ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/raster.c

This is raster.c in view mode; [Download] [Up]

/*
 * raster.c - Common routines for video chip emulation.
 *
 * Written by
 *  Ettore Perazzoli (ettore@comm2000.it)
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

/* This file is ugly, and needs better organization.  In particular, it would
   be nice to remove all the machine-specific hacks.  And it would be nice to
   put it into a standalone module, although this would probably affect
   performance.  */

#define _RASTER_C

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>

#include "vice.h"
#include "vmachine.h"
#include "resources.h"

#ifndef _RASTER_H
#include "raster.h"
#endif

#include "vsync.h"

#undef MIN
#undef MAX
#define MIN(a, b)	((a) < (b) ? (a) : (b))
#define MAX(a, b)	((a) > (b) ? (a) : (b))

/* XShmPutImage is a bit more picky about its parameters than XPutImage.
   This is useful to debug XLib failures caused by wrong calls.  */
/* #define RASTER_DEBUG_PUTIMAGE_CALLS */

inline static void refresh_all(void);

/* ------------------------------------------------------------------------- */

inline static void apply_change(struct changes *c, int idx)
{
    switch (c->actions[idx].type) {
      case CHANGE_BYTE:
	*(c->actions[idx].value.integer.oldp) = c->actions[idx].value.integer.new;
	break;
      case CHANGE_PTR:
      default:
	*(c->actions[idx].value.ptr.oldp) = c->actions[idx].value.ptr.new;
    }
}

inline static void apply_all_changes(struct changes *c)
{
    int i;

    for (i = 0; i < c->count; i++)
        apply_change(c, i);
    c->count = 0;
}

inline static void add_int_change(struct changes *c, int where,
				  int *ptr, int new_value)
{
    int count = c->count++;

    c->actions[count].where = where;
    c->actions[count].type = CHANGE_BYTE;
    c->actions[count].value.integer.oldp = ptr;
    c->actions[count].value.integer.new = new_value;
}

inline static void add_ptr_change(struct changes *c, int where,
				  void **ptr, void *new_value)
{
    int count = c->count++;

    c->actions[count].where = where;
    c->actions[count].type = CHANGE_PTR;
    c->actions[count].value.ptr.oldp = ptr;
    c->actions[count].value.ptr.new = new_value;
}

inline static void add_int_change_next_line(int *ptr, int new_value)
{
    if (skip_next_frame)
	*ptr = new_value;
    else
	add_int_change(&next_line_changes, 0, ptr, new_value);
}

inline static void add_ptr_change_next_line(void **ptr, void *new_value)
{
    if (skip_next_frame)
	*ptr = new_value;
    else
	add_ptr_change(&next_line_changes, 0, ptr, new_value);
}

inline static void add_int_change_foreground(int char_x, int *ptr,
                                             int new_value)
{
    if (skip_next_frame || char_x <= 0) {
	*ptr = new_value;
    } else if (char_x < SCREEN_TEXTCOLS) {
	add_int_change(&foreground_changes, char_x, ptr, new_value);
	have_changes_on_this_line = 1;
    } else {
	add_int_change_next_line(ptr, new_value);
    }
}

inline static void add_ptr_change_foreground(int char_x, void **ptr,
					     void *new_value)
{
    if (skip_next_frame || char_x <= 0) {
	*ptr = new_value;
    } else if (char_x < SCREEN_TEXTCOLS) {
	add_ptr_change(&foreground_changes, char_x, ptr, new_value);
	have_changes_on_this_line = 1;
    } else {
	add_ptr_change_next_line(ptr, new_value);
    }
}

inline static void add_int_change_background(int raster_x, int *ptr,
					     int new_value)
{
    if (skip_next_frame || raster_x <= 0) {
	*ptr = new_value;
    } else if (raster_x < SCREEN_WIDTH) {
	add_int_change(&background_changes, raster_x, ptr, new_value);
	have_changes_on_this_line = 1;
    } else {
	add_int_change_next_line(ptr, new_value);
    }
}

inline static void add_ptr_change_background(int raster_x, void **ptr,
					     void *new_value)
{
    if (skip_next_frame || raster_x <= 0) {
	*ptr = new_value;
    } else if (raster_x < SCREEN_WIDTH) {
	add_ptr_change(&background_changes, raster_x, ptr, new_value);
	have_changes_on_this_line = 1;
    } else {
	add_ptr_change_next_line(ptr, new_value);
    }
}

inline static void add_int_change_border(int raster_x, int *ptr,
					 int new_value)
{
    if (skip_next_frame || raster_x <= 0) {
	*ptr = new_value;
    } else if (raster_x < SCREEN_WIDTH) {
	add_int_change(&border_changes, raster_x, ptr, new_value);
	have_changes_on_this_line = 1;
    } else {
	add_int_change_next_line(ptr, new_value);
    }
}

/* ------------------------------------------------------------------------- */

#define vid_memcpy(dst, src, cnt) memcpy(dst, src, cnt * sizeof(PIXEL))

#if X_DISPLAY_DEPTH > 8
inline static void vid_memset(PIXEL *dst, PIXEL c, int cnt)
{
    int i;

    for (i = 0; i < cnt; i++)
	dst[i] = c;
}

/* This is used to paint borders and blank lines.  */
#define DRAW_BLANK(p, start, end, pixel_width)			\
    do {							\
	PIXEL *dst;						\
	int i;							\
    								\
	dst = (PIXEL *)p + start * pixel_width;			\
	for(i = 0;i < ((end) - (start) + 1) * pixel_width; i++)	\
	       *(dst++) = PIXEL((int)border_color);		\
    } while(0)

#else  /* 8 bit depth */
#define vid_memset(dst, c, cnt) memset(dst,c,cnt)

/* This is used to paint borders and blank lines.  */
#define DRAW_BLANK(p, start, end, pixel_width)		\
    vid_memset(((BYTE *)(p) + (start) * pixel_width),	\
	       PIXEL(border_color),			\
	       ((end) - (start) + 1) * pixel_width)

#endif

/* ------------------------------------------------------------------------- */

/* Initialization.  */
static void reset_raster(void)
{
    int i;

    frame_buffer_ptr = (FRAME_BUFFER_START(frame_buffer)
			+ 2 * SCREEN_MAX_SPRITE_WIDTH);

    rasterline = 0;
    oldclk = 0;
    xsmooth = ysmooth = 0;
    blank = 0;
    mem_counter = memptr = 0;

#ifdef __VIC_II__
    mem_counter_inc = SCREEN_TEXTCOLS;
#endif

    ycounter = 0;
    asleep = 0;

    changed_area.is_null = 1;
    for (i = 0; i < SCREEN_HEIGHT; i++) {
#if SCREEN_NUM_SPRITES > 0
	int j;
	for (j = 0; j < SCREEN_NUM_SPRITES; j++)
	    memset(&(cache[i].sprites[j]), 0, sizeof(struct sprite_line_cache));
#endif
	cache[i].is_dirty = 1;
    }

#if SCREEN_NUM_SPRITES > 0
#if 0
    for (i = 0; i < SCREEN_NUM_SPRITES; i++)
	memset(&(sprites[i]), 0, sizeof(struct sprite));
#endif
    sprite_data = sprite_data_1;
    new_sprite_data = sprite_data_2;
#endif
}

static int init_raster(int active, int max_pixel_width, int max_pixel_height)
{
    /* Keep enough space on the left and the right for one sprite of the
       maximum size.  This way we can clip sprites more easily.  */
    if (frame_buffer_alloc(&frame_buffer,
			   ((SCREEN_WIDTH + 2 * 2 * SCREEN_MAX_SPRITE_WIDTH)
			    * max_pixel_width),
			   (SCREEN_HEIGHT + 1) * max_pixel_height))
	return -1;

    reset_raster();

    return 0;
}

/* Resize the canvas with the specified values and center the screen image on
   it.  The actual size can be different if the parameters are not suitable.  */
static void resize(unsigned int width, unsigned int height)
{
    if (width >= SCREEN_WIDTH * pixel_width) {
	window_x_offset = (width - SCREEN_WIDTH * pixel_width) / 2;
	window_first_x = 0;
    } else {
	window_x_offset = 0;
#ifndef SCREEN_BORDERWIDTH_VARIES
	if (width > SCREEN_XPIX * pixel_width)
	    window_first_x = (SCREEN_BORDERWIDTH
			      - (width / pixel_width - SCREEN_XPIX) / 2);
	else
	    window_first_x = SCREEN_BORDERWIDTH;
#else
	window_first_x = (SCREEN_WIDTH - width / pixel_width) / 2;
#endif
    }

    if (height >= SCREEN_HEIGHT * pixel_height) {
	window_y_offset = (height - SCREEN_HEIGHT * pixel_height) / 2;
	window_first_line = 0;
	window_last_line = window_first_line + SCREEN_HEIGHT - 1;
    } else {
	window_y_offset = 0;
#ifndef SCREEN_BORDERHEIGHT_VARIES
	if (height > SCREEN_YPIX * pixel_height)
	    window_first_line = (SCREEN_BORDERHEIGHT
				 - (height / pixel_height - SCREEN_YPIX) / 2);
	else
	    window_first_line = SCREEN_BORDERHEIGHT;
#else
	window_first_line = (SCREEN_HEIGHT - height / pixel_height) / 2;
#endif
	window_last_line = window_first_line + height / pixel_height;
    }

    canvas_resize(canvas, width, height);

    /* Make sure we don't waste space showing unused lines.  */
    if ((window_first_line < SCREEN_FIRST_DISPLAYED_LINE
	 && window_last_line < SCREEN_LAST_DISPLAYED_LINE)
	|| (window_first_line > SCREEN_FIRST_DISPLAYED_LINE
	    && window_last_line > SCREEN_LAST_DISPLAYED_LINE)) {
	window_first_line = SCREEN_FIRST_DISPLAYED_LINE;
	window_last_line = window_first_line + height / pixel_height;
    }

    window_width = width;
    window_height = height;
}

/* Open the emulation window.  */
static int open_output_window(char *win_name, unsigned int width,
			      unsigned int height, palette_t *palette,
			      canvas_redraw_t exposure_handler)
{
    int i;

    window_width = width * pixel_width;
    window_height = height * pixel_height;

    canvas = canvas_create(win_name, &window_width, &window_height, !asleep,
			   exposure_handler, palette, pixel_table);

    /* Prepare the double and quad pixel tables.  */
    for (i = 0; i < 0x100; i++)
	*((PIXEL *)(double_pixel_table + i))
	    = *((PIXEL *)(double_pixel_table + i) + 1) = pixel_table[i];
    for (i = 0; i < 0x100; i++)
	*((PIXEL2 *)(quad_pixel_table + i))
	    = *((PIXEL2 *)(quad_pixel_table + i) + 1) = double_pixel_table[i];

    if (!canvas)
	return 1;

    resize(window_width, window_height);

    frame_buffer_clear(&frame_buffer, PIXEL(0));

    refresh_all();
    return 0;
}

/* ------------------------------------------------------------------------- */

/* set a new palette */

static int set_palette_file_name(resource_value_t v)
{
    int i;
    /* If called before initialization, just set the resource value.  The
       palette file will be loaded afterwards.  */
    if (palette == NULL) {
        string_set(&palette_file_name, (char *) v);
        return 0;
    }

    if (palette_load((char *) v, palette) < 0) {
        fprintf(stderr, "Couldn't load palette `%s'\n", (char *) v);
        return -1;
    }
    canvas_set_palette(canvas, palette, pixel_table);

    /* Prepare the double and quad pixel tables.  */
    for (i = 0; i < 0x100; i++)
	*((PIXEL *)(double_pixel_table + i))
	    = *((PIXEL *)(double_pixel_table + i) + 1) = pixel_table[i];
    for (i = 0; i < 0x100; i++)
	*((PIXEL2 *)(quad_pixel_table + i))
	    = *((PIXEL2 *)(quad_pixel_table + i) + 1) = double_pixel_table[i];

    init_drawing_tables();

    /* Make sure the pixel tables are recalculated properly.  */
    video_resize();

    string_set(&palette_file_name, (char *) v);
    return 0;
}

/* ------------------------------------------------------------------------- */

/* Read length bytes from src and store them in dest, checking for differences
   between the two arrays.  The smallest interval that contains different bytes
   is returned as [*xs; *xe].  */
inline static int _fill_cache(BYTE *dest, BYTE *src, int length, int srcstep,
			      int *xs, int *xe, int no_check)
{
    if (no_check) {
	int i;

	*xs = 0;
	*xe = length - 1;
	if (srcstep == 1)
	    memcpy(dest, src, length);
	else
	    for (i = 0; i < length; i++, src += srcstep)
		dest[i] = src[0];
	return 1;
    } else {
	int x = 0, i;

	for (i = 0; dest[i] == src[0] && i < length; i++, src += srcstep)
	    /* do nothing */ ;

	if (i < length) {
	    if (*xs > i)
		*xs = i;
	    for (; i < length; i++, src += srcstep)
		if (dest[i] != src[0]) {
		    dest[i] = src[0];
		    x = i;
		}
	    if (*xe < x)
		*xe = x;
	    return 1;
	} else
	    return 0;
    }
}

/* Do as _fill_cache(), but split each byte into low and high nibble.  These
   are stored into different destinations.  */
inline static int _fill_cache_nibbles(BYTE * desthi, BYTE * destlo, BYTE * src,
				      int length, int srcstep, int *xs, int *xe,
				      int no_check)
{
    if (no_check) {
	int i;

	*xs = 0;
	*xe = length - 1;
	for (i = 0; i < length; i++, src += srcstep) {
	    desthi[i] = HI_NIBBLE(src[0]);
	    destlo[i] = LO_NIBBLE(src[0]);
	}
	return 1;
    } else {
	int i, x = 0;
	BYTE b;

	for (i = 0;
	     desthi[i] == HI_NIBBLE(src[0])
	     && destlo[i] == LO_NIBBLE(src[0]) && i < length;
	     i++, src += srcstep)
	    /* do nothing */ ;

	if (i < length) {
	    if (*xs > i)
		*xs = i;
	    for (; i < length; i++, src += srcstep)
		if (desthi[i] != (b = HI_NIBBLE(src[0]))) {
		    desthi[i] = b;
		    x = i;
		} else if (destlo[i] != (b = LO_NIBBLE(src[0]))) {
		    destlo[i] = b;
		    x = i;
		}
	    if (*xe < x)
		*xe = x;
	    return 1;
	} else
	    return 0;
    }
}


/* This function is used for text modes.  It checks for differences in the
   character memory too.  */
inline static int _fill_cache_text(BYTE * dest, BYTE * src, BYTE * charmem,
				   int length, int l, int *xs, int *xe,
				   int no_check)
{
    if (no_check) {
	int i;

	*xs = 0;
	*xe = length - 1;
	for (i = 0; i < length; i++, src++)
	    dest[i] = GET_CHAR_DATA(charmem, src[0], l);
	return 1;
    } else {
	BYTE b;
	int i;

	for (i = 0;
	     dest[i] == GET_CHAR_DATA(charmem, src[0], l) && i < length;
	     i++, src++)
	    /* do nothing */ ;

	if (i < length) {
	    *xs = *xe = i;
	    for (; i < length; i++, src++)
		if (dest[i] != (b = GET_CHAR_DATA(charmem, src[0], l))) {
		    dest[i] = b;
		    *xe = i;
		}
	    return 1;
	} else
	    return 0;
    }
}


#if SCREEN_NUM_SPRITES > 0
/* Fill the sprite cache with the new sprite data. [*xs; *xe] is the changed
   interval (in pixels).  */
inline static int _fill_sprite_cache(struct line_cache *ll, int *xs, int *xe)
{
    int rr = 0, i, r, sxe, sxs, sxe1, sxs1, n = 0, msk;
    struct sprite spr;
    struct sprite_line_cache *l = ll->sprites;
    struct sprite_line_cache *sprl;
    int xs_return = SCREEN_WIDTH, xe_return = 0;

    ll->numsprites = SCREEN_NUM_SPRITES;
    ll->sprmask = 0;
    for (msk = 1, i = 0; i < SCREEN_NUM_SPRITES; i++, msk <<= 1) {
	spr = sprites[i];
	sprl = l + i;
	r = 0;
	if (dma_msk & msk) {
	    DWORD data = sprite_data[i];

	    ll->sprmask |= msk;
	    sxe = spr.x + (spr.x_expanded ? 48 : 24);
	    sxs = spr.x;
	    if (spr.x != sprl->x) {
		if (sprl->visible) {
		    sxe1 = sprl->x + (sprl->x_expanded ? 48 : 24);
		    sxs1 = sprl->x;
		    n++;
		    if (sxs1 < sxs)
			sxs = sxs1;
		    if (sxe1 > sxe)
			sxe = sxe1;
		}
		sprl->x = spr.x;
		r = 1;
	    }
	    if (!sprl->visible) {
		sprl->visible = 1;
		r = 1;
	    }
	    if (spr.x_expanded != sprl->x_expanded) {
		sprl->x_expanded = spr.x_expanded;
		r = 1;
	    }
	    if (spr.multicolor != sprl->multicolor) {
		sprl->multicolor = spr.multicolor;
		r = 1;
	    }
	    if (mc_sprite_color_1 != sprl->c1) {
		sprl->c1 = mc_sprite_color_1;
		r = 1;
	    }
	    if (mc_sprite_color_2 != sprl->c2) {
		sprl->c2 = mc_sprite_color_2;
		r = 1;
	    }
	    if (spr.color != sprl->c3) {
		sprl->c3 = spr.color;
		r = 1;
	    }
	    if (spr.in_background != sprl->in_background) {
		sprl->in_background = spr.in_background;
		r = 1;
	    }

	    if (sprl->data != data) {
		sprl->data = data;
		r = 1;
	    }

	    if (r) {
		xs_return = MIN(xs_return, sxs);
		xe_return = MAX(xe_return, sxe);
		rr = 1;
	    }
	} else if (sprl->visible) {
	    sprl->visible = 0;
	    sxe = sprl->x + (sprl->x_expanded ? 24 : 48);
	    xs_return = MIN(xs_return, sprl->x);
	    xe_return = MAX(xe_return, sxe);
	    rr = 1;
	}
    }

    if (xe_return >= SCREEN_WIDTH)
	*xe = SCREEN_WIDTH - 1;
    else
	*xe = xe_return;
    *xs = xs_return;

    return rr;
}
#endif				/* SCREEN_NUM_SPRITES > 0 */

/* ------------------------------------------------------------------------- */

/* Increase the size of the rectangle to be refreshed so that it also includes
   the interval [xs; xe] of line y.  The lines must be added in order: from
   top to bottom.  */
inline static void add_line(int y, int xs, int xe)
{
#ifdef RASTER_DEBUG_PUTIMAGE_CALLS
    printf("add_line(): y = %d, xs = %d, xe = %d\n", y, xs, xe);
#endif

    if (changed_area.is_null) {
	changed_area.ys = changed_area.ye = y;
	changed_area.xs = xs;
	changed_area.xe = xe;
	changed_area.is_null = 0;
    } else {
	changed_area.xs = MIN(xs, changed_area.xs);
	changed_area.xe = MAX(xe, changed_area.xe);
	changed_area.ye = y;
    }

#ifdef RASTER_DEBUG_PUTIMAGE_CALLS
    printf("add_line(): current changed_area has "
	   "ys = %d, ye = %d, xs = %d, xe = %d\n",
     changed_area.ys, changed_area.ye, changed_area.xs, changed_area.xe);
#endif
}

/* Refresh the changed rectangle.  */
inline static void refresh_changed(void)
{
    int x, y, xx, yy;
    int w, h;

    if (changed_area.is_null)
	return;

    x = changed_area.xs;
    y = changed_area.ys;
    xx = changed_area.xs - window_first_x;
    yy = changed_area.ys - window_first_line;
    w = changed_area.xe - changed_area.xs + 1;
    h = changed_area.ye - changed_area.ys + 1;

    if (xx < 0) {
	x -= xx;
	w += xx;
	xx = 0;
    }
    if (yy < 0) {
	y -= yy;
	h += yy;
	yy = 0;
    }

    x *= pixel_width;
    xx *= pixel_width;
    w *= pixel_width;

    y *= pixel_height;
    yy *= pixel_height;
    h *= pixel_height;

#if defined (RASTER_DEBUG_PUTIMAGE_CALLS)
    printf("Refresh %d %d %d %d %d %d \n", x, y, xx, yy, w, h);
#endif

    x += 2 * SCREEN_MAX_SPRITE_WIDTH;

    canvas_refresh(canvas, frame_buffer, x, y,
		   xx + window_x_offset, yy + window_y_offset,
		   w, h);

    changed_area.is_null = 1;
}

/* Unconditionally refresh the whole screen.  */
inline static void refresh_all(void)
{
#if defined (RASTER_DEBUG_PUTIMAGE_CALLS)
    printf("Global refresh %d %d %d %d %d %d\n",
	   window_first_x * pixel_width + 2 * SCREEN_MAX_SPRITE_WIDTH,
	   window_first_line * pixel_height,
	   window_x_offset, window_y_offset,
	   MIN(window_width, SCREEN_WIDTH * pixel_width),
	   MIN(window_height, SCREEN_HEIGHT * pixel_height));
#endif
    canvas_refresh(canvas, frame_buffer,
		   window_first_x * pixel_width + 2 * SCREEN_MAX_SPRITE_WIDTH,
		   window_first_line * pixel_height,
		   window_x_offset, window_y_offset,
		   MIN(window_width, SCREEN_WIDTH * pixel_width),
		   MIN(window_height, SCREEN_HEIGHT * pixel_height));
}

/* ------------------------------------------------------------------------- */

inline static void handle_blank_line(void)
{
    /* Changes... Should/could be handled better.  */
    if (have_changes_on_this_line) {
        int i, xs;

    	apply_all_changes(&background_changes);
    	apply_all_changes(&foreground_changes);
    	for (xs = i = 0; i < border_changes.count; i++) {
    	    int xe = border_changes.actions[i].where;

    	    if (xs < xe) {
    	        DRAW_BLANK(frame_buffer_ptr, xs, xe, pixel_width);
    	        xs = xe;
    	    }
    	    apply_change(&border_changes, i);
    	}
    	if (xs <= SCREEN_WIDTH - 1)
    	    DRAW_BLANK(frame_buffer_ptr, xs, SCREEN_WIDTH - 1, pixel_width);
    	border_changes.count = 0;
    	cache[rasterline].border_color = -1;
	cache[rasterline].blank = 1;
    	have_changes_on_this_line = 0;
	add_line(rasterline, 0, SCREEN_WIDTH - 1);
	if (pixel_height == 2 && double_scan_enabled)
	    vid_memcpy((FRAME_BUFFER_LINE_START(frame_buffer, 2*rasterline + 1)
			+ 2 * SCREEN_MAX_SPRITE_WIDTH),
	               frame_buffer_ptr, pixel_width * SCREEN_WIDTH);
    } else if (CANVAS_USES_TRIPLE_BUFFERING(canvas)
               || dont_cache
	       || cache[rasterline].is_dirty
	       || border_color != cache[rasterline].border_color
	       || !cache[rasterline].blank) {

        /* Even when the actual caching is disabled, redraw blank lines only if
           it is really necessary to do so.  */
	cache[rasterline].border_color = border_color;
	cache[rasterline].blank = 1;
	cache[rasterline].is_dirty = 0;

	DRAW_BLANK(frame_buffer_ptr, 0, SCREEN_WIDTH - 1, pixel_width);
	if (pixel_height == 2 && double_scan_enabled)
	    DRAW_BLANK((FRAME_BUFFER_LINE_START(frame_buffer, 2*rasterline + 1)
			+ 2 * SCREEN_MAX_SPRITE_WIDTH),
		       0, SCREEN_WIDTH - 1, pixel_width);
	add_line(rasterline, 0, SCREEN_WIDTH - 1);
    }

#ifdef __VIC_II__
    update_sprite_collisions();
#endif
}

/* Draw the borders.  */
inline static void draw_borders(void)
{
    if (!open_left_border)
	DRAW_BLANK(frame_buffer_ptr, 0, display_xstart - 1, pixel_width);
    if (!open_right_border)
	DRAW_BLANK(frame_buffer_ptr, display_xstop, SCREEN_WIDTH - 1,
		   pixel_width);
}

inline static void handle_visible_line_with_cache(void)
{
    int needs_update = 0;
    int line;
    int vm = draw_idle_state ? SCREEN_IDLE_MODE : video_mode;
    int changed_start = SCREEN_TEXTCOLS, changed_end = -1;
    struct line_cache *l = &cache[rasterline];

    line = rasterline - SCREEN_BORDERHEIGHT - ysmooth - 1;

    /* Check for "major" changes first.  If there is any, just write straight
       to the cache without any comparisons and redraw the whole line.  */
    if (l->is_dirty
	|| dont_cache
	|| l->n != line
	|| l->xsmooth != xsmooth
	|| l->video_mode != vm
	|| l->blank
	|| l->ycounter != ycounter
	|| l->border_color != border_color
	|| l->display_xstart != display_xstart
	|| l->display_xstop != display_xstop
	|| (l->open_right_border && !open_right_border)
        || (l->open_left_border && !open_left_border)) {

	needs_update = 1;
	l->n = line;
	l->video_mode = vm;
	l->blank = 0;
	l->xsmooth = xsmooth;
	l->ycounter = ycounter;
	l->border_color = border_color;
	l->display_xstart = display_xstart;
	l->display_xstop = display_xstop;
	l->open_right_border = open_right_border;
	l->open_left_border = open_left_border;

#if defined(__VIC_II__)
	l->overscan_background_color = overscan_background_color;
#endif /* defined(__VIC_II__) */

	/* Fill the space between the border and the graphics with the
	   background color (necessary if `xsmooth' is != 0).  */
	vid_memset(frame_buffer_ptr + SCREEN_BORDERWIDTH * pixel_width,
		   PIXEL(overscan_background_color), xsmooth * pixel_width);

	if (open_left_border)
	    vid_memset(frame_buffer_ptr, PIXEL(overscan_background_color),
		       (SCREEN_BORDERWIDTH + xsmooth) * pixel_width);
	if (open_right_border)
	    vid_memset((frame_buffer_ptr +
			((SCREEN_BORDERWIDTH + SCREEN_XPIX + xsmooth)
			 * pixel_width)), PIXEL(overscan_background_color),
		       (SCREEN_WIDTH - SCREEN_BORDERWIDTH - SCREEN_XPIX
			- xsmooth) * pixel_width);

#if SCREEN_NUM_SPRITES > 0
	_fill_sprite_cache(l, &changed_start, &changed_end);
#endif

	video_modes[vm].fill_cache(l, &changed_start, &changed_end, 1);

	/* [ `changed_start' ; `changed_end' ] now covers the whole line, as
	   we have called fill_cache() with `1' as the last parameter (no
	   check).  */
	video_modes[vm].draw_line_cached(l, changed_start, changed_end);

	changed_start = 0;
	changed_end = SCREEN_WIDTH - 1;

	draw_borders();

    } else {

	/* There are no `major' changes: try to do some optimization.  */

#if SCREEN_NUM_SPRITES > 0

	int sprites_need_update;
	int sprite_changed_start, sprite_changed_end;

	sprites_need_update = _fill_sprite_cache(l, &sprite_changed_start,
						 &sprite_changed_end);
	/* If sprites have changed, do not bother trying to reduce the amount
	   of recalculated data, but simply redraw everything.  */
	needs_update = video_modes[vm].fill_cache(l, &changed_start,
						  &changed_end,
						  sprites_need_update);

#if defined(__VIC_II__)
	/* If the background color changes, we might get the wrong color in
           the left part of the screen, between the border and the start of
           the graphics.  */
	if (ysmooth > 0
	    && l->overscan_background_color != overscan_background_color)
	    needs_update = 1;
#endif /* defined(__VIC_II__) */

	if (needs_update) {
	    video_modes[vm].draw_line_cached(l, changed_start, changed_end);

	    /* Fill the space between the border and the graphics with the
	       background color (necessary if xsmooth is > 0).  */
	    vid_memset(frame_buffer_ptr + SCREEN_BORDERWIDTH * pixel_width,
		       PIXEL(overscan_background_color), xsmooth * pixel_width);

	    /* If xsmooth > 0, drawing the graphics might have corrupted
	       part of the border... fix it here.  */
	    if (!open_right_border)
		DRAW_BLANK(frame_buffer_ptr, SCREEN_BORDERWIDTH + SCREEN_XPIX,
			   SCREEN_BORDERWIDTH + SCREEN_XPIX + 8, pixel_width);

	    /* Calculate the interval in pixel coordinates.  */

#if defined(__VIC_II__)
	    if (l->overscan_background_color != overscan_background_color) {
		if (ysmooth > 0)
		    changed_start = SCREEN_BORDERWIDTH;
		else
		    changed_start = (SCREEN_BORDERWIDTH + xsmooth
				     + 8 * changed_start);
		l->overscan_background_color = overscan_background_color;
	    } else {
		changed_start = (SCREEN_BORDERWIDTH + xsmooth
				 + 8 * changed_start);
	    }
#else  /* defined(__VIC_II__) */
	    changed_start = SCREEN_BORDERWIDTH + xsmooth + 8 * changed_start;
#endif /* defined(__VIC_II__) */

	    changed_end = (SCREEN_BORDERWIDTH + xsmooth + 8 * (changed_end + 1)
			   - 1);

	    if (sprites_need_update) {
		/* FIXME: wrong.  */
		if (open_left_border)
		    changed_start = 0;
		if (open_right_border)
		    changed_end = SCREEN_WIDTH - 1;

		/* Even if we have recalculated the whole line, we will refresh
		   only the part that has actually changed when writing to the
		   window.  */
		changed_start = MIN(changed_start, sprite_changed_start);
		changed_end = MAX(changed_end, sprite_changed_end);

		/* The borders have not changed, so do not repaint them even
		   if there are sprites under them.  */
		changed_start = MAX(changed_start, display_xstart);
		changed_end = MIN(changed_end, display_xstop);
	    }
	}
#if SCREEN_NUM_SPRITES > 0
        else {                /* if (needs_update) */
            cl_ss_collmask = l->ss_collmask;
            cl_sb_collmask = l->sb_collmask;
        }
#endif

#else  /* no sprites */

	needs_update = video_modes[vm].fill_cache(l, &changed_start,
						  &changed_end, 0);

	if (needs_update) {
	    video_modes[vm].draw_line_cached(l, changed_start, changed_end);

	    /* Convert from character to pixel coordinates.  */
	    changed_start = SCREEN_BORDERWIDTH + xsmooth + 8 * changed_start;
	    changed_end = (SCREEN_BORDERWIDTH + xsmooth + 8 * (changed_end + 1)
			   - 1);
	}
#endif				/* no sprites */

	draw_borders();
    }

    if (needs_update) {
	add_line(rasterline, changed_start, changed_end);

	if (pixel_height == 2 && double_scan_enabled) {
	    vid_memcpy((FRAME_BUFFER_LINE_START(frame_buffer, 2*rasterline + 1)
		        + 2*SCREEN_MAX_SPRITE_WIDTH
			+ changed_start * pixel_width),
		       frame_buffer_ptr + changed_start * pixel_width,
		       (changed_end - changed_start + 1) * pixel_width);
	}
    }

    l->is_dirty = 0;
}

inline static void handle_visible_line_without_cache()
{
    /* If screen is scrolled to the right, we need to fill with the background
       color the blank part on the left.  */
    vid_memset(frame_buffer_ptr + SCREEN_BORDERWIDTH * pixel_width,
	       PIXEL(overscan_background_color), xsmooth * pixel_width);

    if (open_left_border)
	vid_memset(frame_buffer_ptr, PIXEL(overscan_background_color),
		   (SCREEN_BORDERWIDTH + xsmooth) * pixel_width);
    if (open_right_border)
	vid_memset((frame_buffer_ptr +
		    ((SCREEN_BORDERWIDTH + SCREEN_XPIX + xsmooth)
		     * pixel_width)), PIXEL(overscan_background_color),
		    (SCREEN_WIDTH - SCREEN_BORDERWIDTH - SCREEN_XPIX
		     - xsmooth) * pixel_width);

    /* Draw the graphics and sprites.  */
    if (draw_idle_state)
	video_modes[SCREEN_IDLE_MODE].draw_line();
    else
	video_modes[video_mode].draw_line();

    draw_borders();

    if (CANVAS_USES_TRIPLE_BUFFERING(canvas)
        || dont_cache
	|| dma_msk
	|| cache[rasterline].is_dirty
	|| cache[rasterline].blank
	|| cache[rasterline].border_color != border_color
	|| (cache[rasterline].open_right_border != open_right_border)
	|| (cache[rasterline].open_left_border != open_left_border)
#if defined(__VIC_II__)
	|| (cache[rasterline].overscan_background_color
	    != overscan_background_color)
#endif
	) {
	cache[rasterline].blank = 0;
	cache[rasterline].is_dirty = 0;
	cache[rasterline].border_color = border_color;
	cache[rasterline].open_right_border = open_right_border;
	cache[rasterline].open_left_border = open_left_border;
#if defined(__VIC_II__)
	cache[rasterline].overscan_background_color = overscan_background_color;
#endif
	add_line(rasterline, 0, SCREEN_WIDTH - 1);

    } else {

        /* Still do some minimal caching anyway.  */
	/* Only update the part between the borders.  */
	add_line(rasterline,
		 SCREEN_BORDERWIDTH,
		 SCREEN_BORDERWIDTH + SCREEN_XPIX - 1);

    }

    if (pixel_height == 2 && double_scan_enabled)
	vid_memcpy((FRAME_BUFFER_LINE_START(frame_buffer, rasterline*2 + 1)
		    + 2 * SCREEN_MAX_SPRITE_WIDTH),
		   frame_buffer_ptr,
		   SCREEN_WIDTH * pixel_width);
}

inline static void handle_visible_line_with_changes(void)
{
    int xs, xstop, i;

    for (xs = i = 0; i < background_changes.count; i++) {
	int xe = background_changes.actions[i].where;

        if (xs < xe) {
            int vm = draw_idle_state ? SCREEN_IDLE_MODE : video_mode;

	    video_modes[vm].draw_background(xs, xe - 1);
  	    xs = xe;
        }
	apply_change(&background_changes, i);
    }
    if (xs <= SCREEN_WIDTH - 1) {
	int vm = draw_idle_state ? SCREEN_IDLE_MODE : video_mode;

        video_modes[vm].draw_background(xs, SCREEN_WIDTH - 1);
    }

    for (xs = i = 0; i < foreground_changes.count; i++) {
	int xe = foreground_changes.actions[i].where;

        if (xs < xe) {
	    int vm = draw_idle_state ? SCREEN_IDLE_MODE : video_mode;

	    video_modes[vm].draw_foreground(xs, xe - 1);
	    xs = xe;
	}
	apply_change(&foreground_changes, i);
    }
    if (xs <= SCREEN_TEXTCOLS - 1) {
	int vm = draw_idle_state ? SCREEN_IDLE_MODE : video_mode;

        video_modes[vm].draw_foreground(xs, SCREEN_TEXTCOLS - 1);
    }

#if SCREEN_NUM_SPRITES > 0
    draw_sprites();
#endif

    /* Draw left border.  */
    xstop = display_xstart - 1;
    if (!open_left_border) {
	for (xs = i = 0;
	     (i < border_changes.count
	      && border_changes.actions[i].where <= xstop);
	     i++) {
	    int xe = border_changes.actions[i].where;

	    if (xs < xe) {
	        DRAW_BLANK(frame_buffer_ptr, xs, xe - 1, pixel_width);
	        xs = xe;
	    }
	    apply_change(&border_changes, i);
	}
	if (xs <= xstop)
	    DRAW_BLANK(frame_buffer_ptr, xs, xstop, pixel_width);
    } else {
	for (i = 0;
	     (i < border_changes.count
	      && border_changes.actions[i].where <= xstop);
	     i++)
	    apply_change(&border_changes, i);
    }

    /* Draw right border.  */
    if (!open_right_border) {
	for (;
	     (i < border_changes.count
	      && border_changes.actions[i].where <= display_xstop);
	     i++)
	    apply_change(&border_changes, i);
	for (xs = display_xstop; i < border_changes.count; i++) {
	    int xe = border_changes.actions[i].where;

            if (xs < xe) {
	        DRAW_BLANK(frame_buffer_ptr, xs, xe - 1, pixel_width);
	        xs = xe;
	    }
	    apply_change(&border_changes, i);
	}
	if (xs <= SCREEN_WIDTH - 1)
	    DRAW_BLANK(frame_buffer_ptr, xs, SCREEN_WIDTH - 1, pixel_width);
    } else {
	for (i = 0; i < border_changes.count; i++)
	    apply_change(&border_changes, i);
    }

    foreground_changes.count =  0;
    background_changes.count =  0;
    border_changes.count = 0;

    /* Do not cache this line.  */
    cache[rasterline].is_dirty = 1;

    add_line(rasterline, 0, SCREEN_WIDTH - 1);
    if (pixel_height == 2 && double_scan_enabled)
        vid_memcpy(FRAME_BUFFER_LINE_START(frame_buffer, 2*rasterline + 1)
		   + 2 * SCREEN_MAX_SPRITE_WIDTH,
                   frame_buffer_ptr, pixel_width * SCREEN_WIDTH);

    have_changes_on_this_line = 0;
}

inline static void handle_visible_line(void)
{
    if (have_changes_on_this_line)
	handle_visible_line_with_changes();
    else if (!CANVAS_USES_TRIPLE_BUFFERING(canvas)
             && video_cache_enabled
	     && !open_left_border
	     && !open_right_border) /* FIXME: shortcut! */
	handle_visible_line_with_cache();
    else
	handle_visible_line_without_cache();

#if !defined (__VIC_II__) && !defined (__VDC__)
    if (++ycounter >= SCREEN_CHARHEIGHT) {
	ycounter = 0;
	memptr += memptr_inc;
#ifdef __CRTC__
	memptr &= addr_mask;
#endif
    }
#endif
}

inline static void handle_end_of_frame(void)
{
    frame_buffer_ptr = (FRAME_BUFFER_START(frame_buffer)
			+ 2 * SCREEN_MAX_SPRITE_WIDTH);
    mem_counter = memptr = rasterline = 0;

#ifndef __VIC_II__
    ycounter = 0;
#endif
#ifdef __CRTC__
    memptr = scraddr & addr_mask;
#endif

    if (!skip_next_frame) {
	if (dont_cache)
	    refresh_all();
	else
	    refresh_changed();
    }
    skip_next_frame = do_vsync(skip_next_frame);

#if defined(__MSDOS__) && !defined(VIC20)
    if (window_width == SCREEN_XPIX && window_height == SCREEN_YPIX)
	canvas_set_border_color(canvas, border_color);
#endif
}

/* Emulate one raster line.  */
inline static void emulate_line(void)
{
    oldclk += CYCLES_PER_LINE;

    /* Emulate the vertical blank flip-flops.  (Well, sort of.)  */
    if (rasterline == display_ystart && !blank)
	blank_enabled = 0;
    else if (rasterline == display_ystop)
	blank_enabled = 1;

    if (rasterline >= SCREEN_FIRST_DISPLAYED_LINE
	&& rasterline <= SCREEN_LAST_DISPLAYED_LINE) {
	if (!skip_next_frame && !asleep
            && (rasterline >= window_first_line
                && rasterline <= window_last_line)) {
	    if ((blank_this_line || blank_enabled) && !open_left_border)
		handle_blank_line();
	    else
		handle_visible_line();
	    if (++num_cached_lines == (window_last_line - window_first_line)) {
		dont_cache = 0;
		num_cached_lines = 0;
	    }
	} else {
#ifdef __VIC_II__
	    if (!skip_next_frame)
		update_sprite_collisions();
#endif
	    if (have_changes_on_this_line) {
	        apply_all_changes(&background_changes);
	        apply_all_changes(&foreground_changes);
	        apply_all_changes(&border_changes);
	        have_changes_on_this_line = 0;
	    }
	}

#ifdef __VIC_II__
	if (!idle_state)
	    mem_counter = (mem_counter + mem_counter_inc) & 0x3ff;
	mem_counter_inc = SCREEN_TEXTCOLS;
	/* `ycounter' makes the chip go to idle state when it reaches the
	   maximum value.  */
	if (ycounter == 7) {
	    idle_state = 1;
	    memptr = mem_counter;
	}
	if (!idle_state || bad_line) {
	    ycounter = (ycounter + 1) & 0x7;
	    idle_state = 0;
	}
	if (force_display_state) {
	    idle_state = 0;
	    force_display_state = 0;
	}
	draw_idle_state = idle_state;
	bad_line = 0;
#endif

	rasterline++;
	frame_buffer_ptr = FRAME_BUFFER_LINE_START(frame_buffer,
						   rasterline * pixel_height);
	frame_buffer_ptr += 2 * SCREEN_MAX_SPRITE_WIDTH;

#ifdef __VIC_II__
 	if (rasterline == 0x30)
	    allow_bad_lines = !blank;
#endif

    } else {

#ifdef __VIC_II__
	if (!skip_next_frame)
	    update_sprite_collisions();
#endif

        if (have_changes_on_this_line) {
            apply_all_changes(&background_changes);
            apply_all_changes(&foreground_changes);
            apply_all_changes(&border_changes);
            have_changes_on_this_line = 0;
        }
	rasterline++;
	if (rasterline == SCREEN_HEIGHT) {
	    handle_end_of_frame();
	} else {
	    frame_buffer_ptr = FRAME_BUFFER_LINE_START(frame_buffer,
						    rasterline * pixel_height);
	    frame_buffer_ptr += 2 * SCREEN_MAX_SPRITE_WIDTH;
	}

    }

    apply_all_changes(&next_line_changes);

#ifdef __VIC_II__
    /* Handle open borders.  */
    open_left_border = open_right_border;
    open_right_border = 0;
#endif

#if SCREEN_NUM_SPRITES > 0
    dma_msk = new_dma_msk;
#endif

    memory_fetch_done = 0;

    blank_this_line = 0;
}

/* Disable all the caching for the next frame.  */
inline static void force_repaint(void)
{
    dont_cache = 1;
    num_cached_lines = 0;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.