File:  [Generator SEGA Genesis emulator] / generator / src / vdp.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Mar 4 04:46:32 2020 UTC (6 years, 4 months ago) by root
Branches: MAIN, JamesPonder
CVS tags: v0_31, HEAD
0.31

/* Generator is (c) James Ponder, 1997-2001 http://www.squish.net/generator/ */

/* video display processor emulation */

/* for dma 'bytes per line' stuff we use 'memory to vram' figures as a baseline
   and double the number of real bytes for 'vram copy' in order to adjust to
   our model; also there is a subtraction of one for vram fill to compensate
   there too - probably being far too picky and it isn't exactly perfect
   anyway */

/* to do: fix F flag in status register */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "generator.h"
#include "vdp.h"
#include "cpu68k.h"
#include "ui.h"
#include "event.h"

#undef DEBUG_VDP
#undef DEBUG_VDPDMA
#undef DEBUG_VDPDATA
#undef DEBUG_VDPCRAM

/*** variables externed ***/

unsigned int vdp_event;
unsigned int vdp_vislines;
unsigned int vdp_visstartline;
unsigned int vdp_visendline;
unsigned int vdp_totlines;
unsigned int vdp_framerate;
unsigned int vdp_clock;
unsigned int vdp_68kclock;
unsigned int vdp_clksperline_68k;
unsigned int vdp_oddframe = 0;   /* odd/even frame */
unsigned int vdp_vblank = 0;     /* set during vertical blanking */
unsigned int vdp_hblank = 0;     /* set during horizontal blanking */
unsigned int vdp_line = 0;       /* current line number */
unsigned int vdp_vsync = 0;      /* a vsync just happened */
unsigned int vdp_dmabusy = 0;    /* dma busy flag */
unsigned int vdp_pal = 0;        /* set for pal mode */
unsigned int vdp_overseas = 1;   /* set for overseas model */
unsigned int vdp_layerB  = 1;    /* flag */
unsigned int vdp_layerBp = 1;    /* flag */
unsigned int vdp_layerA  = 1;    /* flag */
unsigned int vdp_layerAp = 1;    /* flag */
unsigned int vdp_layerW  = 1;    /* flag */
unsigned int vdp_layerWp = 1;    /* flag */
unsigned int vdp_layerH  = 1;    /* flag */
unsigned int vdp_layerS  = 1;    /* flag */
unsigned int vdp_layerSp = 1;    /* flag */
uint8 vdp_cram[LEN_CRAM];
uint8 vdp_vsram[LEN_VSRAM];
uint8 vdp_vram[LEN_VRAM];
uint8 vdp_cramf[LEN_CRAM/2];
unsigned int vdp_event_start;
unsigned int vdp_event_vint;
unsigned int vdp_event_hint;
unsigned int vdp_event_hdisplay;
unsigned int vdp_event_end;
signed int vdp_nextevent = 0;
sint32 vdp_dmabytes = 0;            /* bytes left in DMA - must be fixed size */
signed int vdp_hskip_countdown = 0; /* actual countdown */
uint16 vdp_address; /* address for data/dma transfers */
t_code vdp_code;    /* code number for data/dma transfers CD3-CD0 */
unsigned int vdp_ctrlflag; /* set inbetween ctrl writes */
uint16 vdp_first;   /* first word of address set */
uint16 vdp_second;  /* second word of address set */

/*** global variables ***/

uint8 vdp_reg[25];
static int vdp_collision;  /* set during a sprite collision */
static int vdp_overflow;   /* set when too many sprites in one line */
static int vdp_fifofull;   /* set when write fifo full */
static int vdp_fifoempty;  /* set when write fifo empty */
static int vdp_complex;    /* set when simple routines can't cope */

/*** forward references ***/

void vdp_ramcopy_vram(int type);
void vdp_dma_vramcopy(void);
void vdp_dma_fill(uint8 data);
void vdp_showregs(void);
void vdp_describe(void);
void vdp_eventinit(void);
void vdp_layer_simple(unsigned int layer, unsigned int priority,
        	      uint8 *fielddata, unsigned int lineoffset);
inline void vdp_plotcell(uint8 *patloc, uint8 palette, uint8 flags,
        		 uint8 *cellloc, unsigned int lineoffset);
void vdp_sprites(unsigned int line, uint8 *pridata, uint8 *outdata);
inline void vdp_spritelayer(unsigned int line, uint8 *spritedata,
        		    uint8 *pridata, uint8 *linedata,
        		    unsigned int priority);
int vdp_sprite_simple(unsigned int priority, uint8 *framedata,
        	      unsigned int lineoffset, unsigned int number,
        	      uint8 *spritelist, uint8 *sprite);
void vdp_sprites_simple(unsigned int priority, uint8 *framedata,
                        unsigned int lineoffset);
void vdp_shadow_simple(uint8 *framedata, unsigned int lineoffset);
void vdp_window(unsigned int line, unsigned int priority, uint8 *linedata);
void vdp_newlayer(unsigned int line, uint8 *pridata, uint8 *outdata,
                  unsigned int layer);
void vdp_newwindow(unsigned int line, uint8 *pridata, uint8 *outdata);

#define PRIBIT_LAYERB 0
#define PRIBIT_LAYERA 1
#define PRIBIT_SPRITE 2

/*** vdp_init - initialise this sub-unit ***/

int vdp_init(void)
{
  vdp_reset();
  return 0;
}

/*** vdp_setupvideo - setup parameters dependant on vdp_pal ***/

void vdp_setupvideo(void)
{
  int v30 = (vdp_reg[1] & 1<<3) ? 1 : 0;

  if (!vdp_pal && v30)
    ui_err("Impossible VDP mode - vertical 30 cell NTSC");

  vdp_clock = vdp_pal ? 53200000 : 53693100; /* What speed is the PAL clock? */
  vdp_68kclock = vdp_clock / 7;
  vdp_vislines = v30 ? 240 : 224;
  vdp_visstartline = v30 ? 46 : (vdp_pal ? 54 : 19);
  vdp_visendline = vdp_visstartline + vdp_vislines;
  vdp_totlines = vdp_pal ? 312 : 262;
  vdp_framerate = vdp_pal ? 50 : 60;
  vdp_clksperline_68k = (vdp_68kclock/vdp_framerate/vdp_totlines);
}

/*** vdp_softreset - soft reset ***/

void vdp_softreset(void)
{
  /* a soft reset involves resetting the cpu so we need to reset the
     vdp event timers */
  vdp_eventinit();
}

/*** vdp_reset - reset vdp sub-unit ***/

void vdp_reset(void)
{
  int i;

  /* clear registers */

  for (i = 0; i < 25; i++)
    vdp_reg[i] = 0;

  /* set PAL/NTSC variables */

  vdp_setupvideo();
  vdp_vblank = 0;
  vdp_hblank = 0;
  vdp_oddframe = 0;
  vdp_collision = 0;
  vdp_overflow = 0;
  vdp_vsync = 0;
  vdp_fifofull = 0;
  vdp_fifoempty = 0;
  vdp_ctrlflag = 0;

  memset(vdp_cram, 0, LEN_CRAM);
  memset(vdp_vsram, 0, LEN_VSRAM);
  memset(vdp_vram, 0, LEN_VRAM);

  /* clear CRAM */

  for (i = 0; i < 64; i++) {
    (vdp_cram+i*2)[0] = (i & 7)<<1;
    (vdp_cram+i*2)[1] = (i & 7)<<5 | (i & 7)<<1;
    vdp_cramf[i] = 1;
  }
  vdp_eventinit();
  LOG_VERBOSE(("VDP: totlines = %d (%s)", vdp_totlines, vdp_pal ?
               "PAL" : "NTSC"));
}

uint16 vdp_status(void)
{
  uint16 ret;

  /* bit      meaning (when set)
   * 0        0:ntsc 1:pal
   * 1        dma busy
   * 2        during h blanking
   * 3        during v blanking
   * 4        frame in interlace mode - 0:even 1:odd
   * 5        collision happened between non-zero pixels in two sprites
   * 6        too many sprites in one line
   * 7        v interrupt has happened
   * 8        write fifo full
   * 9        write fifo empty
   * 10-15 are next word on bus, i.e. next word in ROM - CM
   */
  ret = vdp_pal | vdp_dmabusy<<1 | vdp_hblank<<2 | vdp_vblank<<3 |
    vdp_oddframe<<4 | vdp_collision<<5 | vdp_overflow<<6 | vdp_vsync<<7 |
    vdp_fifofull<<8 | vdp_fifoempty<<9;

#ifdef DEBUG_VDP
  if (vdp_collision)
    LOG_VERBOSE(("%08X Collision read", vdp_collision));
#endif

  vdp_vsync = vdp_collision = vdp_overflow = 0;
  vdp_ctrlflag = 0; /* Charles MacDonald - so he claims ;) */

  LOG_DEBUG1(("%08X STATUS READ %02X", regs.pc, ret));

  return (ret);
}

void vdp_storectrl(uint16 data)
{
  uint8 reg;
  uint8 regdata;

  if (!vdp_ctrlflag) {
    if ((data & 0xE000) == 0x8000) {
      /* register set */
      reg = (data>>8) & 31;
      regdata = data & 255;
      if (reg > 24) {
        LOG_NORMAL(("%08X [VDP] Invalid register (%d)", regs.pc,
        	      ((data>>8) & 31)));
        return;
      }
      vdp_reg[reg] = regdata;
      vdp_code = 0;
#ifdef DEBUG_VDP
      LOG_VERBOSE(("%08X [VDP] Register %d set to %04X", regs.pc,
        	   reg, regdata));
#endif
      return;
    } else {
      vdp_ctrlflag = 1;
      vdp_first = data;
      return;
    }
  } else {
    vdp_second = data;
    vdp_ctrlflag = 0;
    vdp_code = ((vdp_first>>14) & 3) | ((data>>2) & (3<<2));
    vdp_address = (vdp_first & 0x3FFF) | (data<<14 & 0xC000);

    if ((data & 1<<7) && (vdp_reg[1] & 1<<4)) { /* CD5 - DMA ? */
      if (vdp_dmabusy) {
        vdp_dmabusy = 1; /* null statement to avoid gcc warnings */
        LOG_DEBUG1(("DMA initiation during DMA!"));
      }
      /* CD4 - not read - need to verify */
      vdp_dmabusy = 1;
      switch((vdp_reg[23] >> 6) & 3) {
      case 0:
      case 1: /* ram to vram */
        switch (vdp_code) {
        case 1: /* ram copy to vram */
          vdp_ramcopy_vram(0);
          break;
        case 3: /* ram copy to cram */
          vdp_ramcopy_vram(1);
          break;
        case 5: /* ram copy to vsram */
          vdp_ramcopy_vram(2);
          break;
        default: /* undefined */
          LOG_NORMAL(("%08X [VDP] start of type %d to address %X",
        	      regs.pc, vdp_code, vdp_address));
          break;
        }
        vdp_dmabusy = 0; /* 68k was frozen */
        break;
      case 2: /* VRAM fill */
        /* later folks */
        break;
      case 3: /* VRAM copy */
        vdp_dma_vramcopy();
        /* vdp_dmabusy is cleared when vdp_dmabytes is empty */
        break;
      }
    }
  }
}

void vdp_ramcopy_vram_obsolete(int type)
{
  uint16 length = vdp_reg[19] | vdp_reg[20]<<8;
  uint8 srcbank = vdp_reg[23];
  uint16 srcoffset = vdp_reg[21] | vdp_reg[22]<<8;
  uint8 increment = vdp_reg[15];
  uint16 *srcmemory;
  uint16 data;
  uint8 *memory;
  uint16 length_t;
  uint16 vdp_address_t;
  uint32 vdp_address_mask;

  /* VRAM (type 0): reads are word-wide, writes are byte-wide */
  /* CRAM (type 1) WSRAM (type 2): reads are word-wide, writes are byte-wide */

  if (srcbank & 1<<6) {
    /* this is a ram source - note altered beast uses E00000 upwards */
    srcmemory = (uint16 *)cpu68k_ram;
    srcoffset&= 0x7fff;
#ifdef DEBUG_VDP
    LOG_VERBOSE(("%08X [VDP] ramcopy from RAM offset %08X type=%d "
        	 "vdpaddr=%08X length=%d",
        	 regs.pc, srcoffset*2, type, vdp_address, length));
#endif
  } else {
    /* this is a rom source - each bank is 128k, srcoffset is in words */
    srcmemory = (uint16 *)(cpu68k_rom + srcbank*0x20000);
#ifdef DEBUG_VDP
    LOG_VERBOSE(("%08X [VDP] ramcopy from ROM offset %08X type=%d "
        	 "vdpaddr=%08X length=%d",
        	 regs.pc, srcbank*0x20000+srcoffset*2, type, vdp_address,
        	 length));
#endif
  }

  switch(type) {
  case 0:
    memory = vdp_vram;
    vdp_address_mask = LEN_VRAM-1;
    break;
  case 1:
    length&= 0xff; /* maybe this or maybe we just stop at the end */
    memory = vdp_cram;
    vdp_address_mask = LEN_CRAM-1;
    length_t = length;
    vdp_address_t = vdp_address & vdp_address_mask;
    for(; length_t; length_t--) {
      vdp_cramf[vdp_address_t>>1] = 1;
      vdp_address_t+= increment;
      vdp_address_t&= vdp_address_mask;
    }
    break;
  case 2:
    length&= 0xff; /* maybe this or maybe we just stop at the end */
    memory = vdp_vsram;
    vdp_address_mask = 255;
    if (vdp_address + (length * increment) > LEN_VSRAM) {
      LOG_CRITICAL(("%08X [VDP] write to out of bounds vsram addr=$%X "
        	    "len=$%X inc=$%X end=$%X", regs.pc, vdp_address,
        	    length, increment, LEN_VSRAM));
    }      
    break;
  default:
    LOG_NORMAL(("%08X [VDP] Bad type %d", regs.pc, type));
    return;
  }
  vdp_address_t = vdp_address & vdp_address_mask;
  for(; length; length--) {
    data = LOCENDIAN16(srcmemory[srcoffset]);
#ifdef DEBUG_VDPDMA
    /*
      LOG_VERBOSE(("%08X [VDP] data %04X (ram/rom+2*%04X) -> %04X",
      regs.pc, data, srcoffset, vdp_address_t));
    */
#endif
    memory[vdp_address_t] = data>>8;
    memory[vdp_address_t+1] = data & 0xff;
    srcoffset+= 1;
    vdp_address_t+= increment;
    vdp_address_t&= vdp_address_mask;
  }
  /* don't think about writing vdp_address_t into registers - T2 is a good
     example of something that doesn't like that */
}

void vdp_ramcopy_vram(int type)
{
  uint16 length = vdp_reg[19] | vdp_reg[20]<<8;
  uint8 srcbank = vdp_reg[23];
  uint16 srcoffset = vdp_reg[21] | vdp_reg[22]<<8;
  uint8 increment = vdp_reg[15];
  uint16 *srcmemory;
  uint16 srcmask;
  uint16 data;
  unsigned int i;

#ifdef DEBUG_VDP
  LOG_VERBOSE(("%08X [VDP] VRAM copy from source %08X "
               "vdpaddr=%08X length=%d",
               regs.pc, (srcbank*0x10000+srcoffset)*2, vdp_address, length));
#endif

  if (srcbank & 1<<6) {
    srcmemory = (uint16 *)cpu68k_ram;
    srcmask = 0x7fff; /* 32k words = 64k */
  } else {
    srcmemory = (uint16 *)(cpu68k_rom + srcbank*0x20000);
    srcmask = 0xffff; /* 64k words = 128k */
  }
  for (i = 0; i < length; i++) {
    data = LOCENDIAN16(srcmemory[srcoffset & srcmask]);
    switch (type) {
    case 0: /* VRAM */
      vdp_vram[vdp_address] = data>>8;
      vdp_vram[vdp_address ^ 1] = data & 0xff;
      break;
    case 1: /* CRAM */
      vdp_cram[vdp_address & 0x7e] = data>>8;
      vdp_cram[(vdp_address & 0x7e) | 1] = data & 0xff;
      vdp_cramf[vdp_address>>1] = 1;
      break;
    case 2: /* VSRAM */
      if ((vdp_address & 0x7e) < LEN_VSRAM) {
        vdp_vsram[vdp_address & 0x7e] = data>>8;
        vdp_vsram[(vdp_address & 0x7e) | 1] = data & 0xff;
      }
      break;
    }
    srcoffset+=1;
    vdp_address+= increment;
  }
  vdp_reg[19] = 0;
  vdp_reg[20] = 0;
  vdp_reg[22] = (srcoffset >> 8) & 0xff;
  vdp_reg[21] = srcoffset & 0xff;
  /* vram sends bytes, cram/vsram send words so are twice as efficient */
  event_freeze(type == 0 ? length*2 : length);
}

void vdp_dma_vramcopy()
{
  uint32 length = vdp_reg[19] | vdp_reg[20]<<8;
  uint8 increment = vdp_reg[15];
  uint16 srcaddr = vdp_reg[21] | vdp_reg[22]<<8;
  unsigned int i;

  if (length == 0) {
    LOG_NORMAL(("%08X [VDP] Warning - length of 0 used in vram copy"));
    length = 0x10000; /* could be 0xffff */
  }

#ifdef DEBUG_VDPDMA
  LOG_VERBOSE(("%08X [VDP] COPY length %04X dstaddr %08X inc %02X "
               "srcaddr %04X", regs.pc, length, vdp_address, increment,
               srcaddr));
#endif

  for (i = 0; i < length; i++) {
    vdp_vram[vdp_address] = vdp_vram[srcaddr++];
    vdp_address+= increment;
  }

  vdp_reg[19] = 0;
  vdp_reg[20] = 0;
  vdp_reg[22] = (srcaddr >> 8) & 0xff;
  vdp_reg[21] = srcaddr & 0xff;
  vdp_dmabytes = length*2; /* factor of 2 vram copy to vram fill (p36) */
}

void vdp_dma_fill(uint8 data)
{
  uint16 length = vdp_reg[19] | vdp_reg[20]<<8;
  uint8 increment = vdp_reg[15];
  unsigned int screencells = (vdp_reg[12] & 1) ? 40 : 32;
  unsigned int i;

  if (increment != 1 && increment != 2)
    LOG_NORMAL(("VDP fill used with strange increment %d", increment));

  for (i = 0; i < length; i++) {
    vdp_vram[vdp_address] = data;
    vdp_address+= increment; /* 16 bit wrap */
  }
  vdp_reg[19] = 0;
  vdp_reg[20] = 0;
  vdp_dmabytes = length+1; /* extra byte used (see p36) */
}

uint16 vdp_fetchdata_obsolete(void)
{
  uint8 *memory;
  uint32 size;
  uint16 data;

  switch(vdp_code) {
  case cd_vram_fetch:
#ifdef DEBUG_VDPDATA
    LOG_VERBOSE(("%08X [VDP] VRAM fetch from address %X", regs.pc,
        	 vdp_address));
#endif
    memory = vdp_vram;
    size = LEN_VRAM;
    break;
  case cd_vsram_fetch:
#ifdef DEBUG_VDPDATA
    LOG_VERBOSE(("%08X [VDP] VSRAM fetch from address %X",
        	 regs.pc, vdp_address));
#endif
    memory = vdp_vsram;
    size = LEN_VSRAM;
    break;
  case cd_cram_fetch:
#ifdef DEBUG_VDPDATA
    LOG_VERBOSE(("%08X [VDP] CRAM fetch from address %X", regs.pc,
        	 vdp_address));
#endif
    memory = vdp_cram;
    size = LEN_CRAM;
    break;
  default:
    LOG_NORMAL(("%08X [VDP] Fetch in invalid mode %d", regs.pc,
        	  vdp_code));
    return 0;
  }
  if (vdp_address>=size) {
    LOG_VERBOSE(("%08X [VDP] Address (%08X) out of bounds (%08X)",
        	 regs.pc, vdp_address, size));
  }
  data = LOCENDIAN16(*(uint16 *)(memory+(vdp_address & ~1)));
#ifdef DEBUG_VDPDATA
  LOG_VERBOSE(("%08X [VDP] Fetch %04X from %08X", regs.pc, data,
               vdp_address & ~1));
#endif
  vdp_address+= vdp_reg[15];
  vdp_ctrlflag = 0;
  return data;
}

void vdp_storedata(uint16 data)
{
  uint16 address;
  uint16 sdata;

  if (vdp_ctrlflag) {
    LOG_NORMAL(("%08X [VDP] Unterminated ctrl setting %04X/%04X", regs.pc,
        	vdp_first, vdp_second));
    vdp_storectrl(vdp_second);
  }

  switch(vdp_code) {
  case cd_vram_store:
    sdata = (vdp_address & 1) ? SWAP16(data) : data; /* only for VRAM */
    *(uint16 *)(vdp_vram+(vdp_address & 0xfffe)) = LOCENDIAN16(sdata);
    break;
  case cd_cram_store:
    address = vdp_address & 0x7e; /* address lines used */
    *(uint16 *)(vdp_cram+address) = LOCENDIAN16(data);
    vdp_cramf[address>>1] = 1;
    break;
  case cd_vsram_store:
    address = vdp_address & 0x7e; /* address lines used */
    if (address < LEN_VSRAM)
      *(uint16 *)(vdp_vsram+address) = LOCENDIAN16(data);
    break;
  default: /* undefined */
    LOG_NORMAL(("%08X [VDP] Bad word store to %08X of type %d data = %04X",
        	regs.pc, vdp_address, vdp_code, data));
    break;
  }
  vdp_address+= vdp_reg[15]; /* 16 bit wrap */

  if (vdp_dmabusy) {
    if (vdp_code == 1)
      /* other vdp_codes consume time but don't result in fills */
      /* vdp_dmabusy is cleared when vdp_dmabytes is empty */
      vdp_dma_fill((data >> 8) & 0xff);
  }
}

uint16 vdp_fetchdata(void)
{
  uint16 address;
  uint16 data;

  if (vdp_ctrlflag) {
    LOG_NORMAL(("%08X [VDP] Unterminated ctrl setting %04X/%04X", regs.pc,
        	vdp_first, vdp_second));
    vdp_storectrl(vdp_second);
  }

  switch(vdp_code) {
  case cd_vram_fetch:
    data = LOCENDIAN16(*(uint16 *)(vdp_vram+(vdp_address & 0xfffe)));
    break;
  case cd_cram_fetch:
    address = vdp_address & 0x7e; /* address lines used */
    data = LOCENDIAN16(*(uint16 *)(vdp_cram+address));
    break;
  case cd_vsram_fetch:
    address = vdp_address & 0x7e; /* address lines used */
    if (address < LEN_VSRAM)
      data = LOCENDIAN16(*(uint16 *)(vdp_vsram+address));
    else
      data = 0; /* tests show this range appears random (although I'm sure it
        	   isn't) */
    break;
  default: /* undefined */
    /* reading in write mode suspends 68000 on a real machine */
    LOG_NORMAL(("%08X [VDP] Bad word fetch of %08X of type %d",
        	regs.pc, vdp_address, vdp_code));
    data = 0;
    break;
  }
  vdp_address+= vdp_reg[15]; /* 16 bit wrap */
  return data;
}

#define LINEDATA(offset, value, palette, priority) \
if (value) { \
  linedata[offset] = (palette)*16 + value; \
} else if (priority) { \
  linedata[offset] = (linedata[offset] & 63); \
}

#define LINEDATASPR(offset, value, palette, priority) \
if (value) { \
  if (outdata[offset] < 62) \
    vdp_collision = 1; \
  if (priority) \
    pridata[offset]|= 1<<PRIBIT_SPRITE; \
  else \
    pridata[offset]&= ~(1<<PRIBIT_SPRITE); \
  outdata[offset] = (palette)*16 + value; \
}

#define LINEDATALAYER(offset, value, palette, priority) \
  if (priority) \
    pridata[offset]|= layer ? 1<<PRIBIT_LAYERB : 1<<PRIBIT_LAYERA; \
  if (value) \
    outdata[offset] = (palette)*16 + value; \
  else \
    outdata[offset] = 0;

void vdp_sprites(unsigned int line, uint8 *pridata, uint8 *outdata)
{
  uint8 interlace = (vdp_reg[12]>>1) & 3;
  uint8 *spritelist = vdp_vram + ((vdp_reg[5] & 0x7F)<<9);
  t_spriteinfo si[128]; /* 128 - max sprites supported per line */
  unsigned int sprites;
  unsigned int idx;
  int i;
  uint8 link;
  uint8 *sprite = spritelist;
  int plotter; /* flag */
  unsigned int screencells = (vdp_reg[12] & 1) ? 40 : 32;
  unsigned int maxspl = (vdp_reg[12] & 1) ? 20 : 16; /* max sprs/line */
  unsigned int cells;
  uint8 loops = 0;

  for (idx = 0; loops < 255 && idx < maxspl; loops++) {
    link = sprite[3] & 0x7F;
    si[idx].sprite = sprite;
    if (interlace == 3)
      si[idx].vpos = (LOCENDIAN16(*(uint16 *)(sprite)) & 0x3FF) - 0x100;
    else
      si[idx].vpos = (LOCENDIAN16(*(uint16 *)(sprite)) & 0x1FF) - 0x080;
    if ((signed int)line < si[idx].vpos)
      goto next;
    si[idx].vsize = 1+(sprite[2] & 3);
    if (interlace == 3)
      si[idx].vsize<<= 1;
    si[idx].vmax = si[idx].vpos + si[idx].vsize*8;
    if ((signed int)line >= si[idx].vmax)
      goto next;
    si[idx].hpos = (LOCENDIAN16(*(uint16 *)(sprite+6)) & 0x1FF) - 0x80;
    si[idx].hsize = 1+((sprite[2]>>2) & 3);
    si[idx].hplot = si[idx].hsize;
    si[idx].hmax = si[idx].hpos + si[idx].hsize*8;
    idx++;
  next:
    if (!link)
      break;
    sprite = spritelist + (link<<3);

  }
  if (idx < 1)
    return;
  sprites = idx;
  plotter = 1;
  cells = (vdp_reg[12] & 1) ? 40 : 32; /* 320 or 256 pixels */
  /* loop masking */
  for (i = 0; i < (signed int)sprites; i++) {
    if (plotter == 0) {
      sprites = i;
      break;
    }
    if (si[i].hpos == -128) {
      /* mask sprite - but does it? */
      if (i > 0) {
        /* is there a higher priority sprite? */
        if (si[i-1].vpos <= (signed int)line &&
            si[i-1].vmax > (signed int)line) {
          /* match, mask time */
          plotter = 0;
        }
      } else {
        /* higest priority sprite, so mask */
        plotter = 0;
      }
      si[i].hplot = 0;
    }
    if (si[i].hpos == -127) {
      LOG_VERBOSE(("Warning: Use of hpos = 1 in plotter"));
      plotter = 1;
    }
    if (cells >= si[i].hplot) {
      cells-= si[i].hplot;
    } else {
      si[i].hplot = cells; /* only room for this many */
      cells = 0;
    }
  }    

  {
    sint16 hpos, vpos;
    uint16 hsize, vsize, hplot;
    sint16 hmax, vmax;
    uint16 cellinfo;
    uint16 pattern;
    uint8 palette;
    uint8 *cellline;
    uint32 data;
    uint8 pixel;
    uint8 priority;
    int j, k;

    /* loop around sprites until end of list marker or no more */
    for (i = sprites-1; i >= 0; i--) {
      hpos = si[i].hpos;
      vpos = si[i].vpos;
      vsize = si[i].vsize; /* doubled when in interlace mode */
      hsize = si[i].hsize;
      hmax = si[i].hmax;
      vmax = si[i].vmax;
      hplot = si[i].hplot; /* number of cells to plot for this sprite */
      cellinfo = LOCENDIAN16(*(uint16 *)(si[i].sprite+4));
      pattern = cellinfo & 0x7FF;
      palette = (cellinfo >> 13) & 3;
      priority = (cellinfo >> 15) & 1;

      cellline = vdp_vram + ((interlace == 3) ? (pattern<<6) : (pattern<<5));
      if (cellinfo & 1<<12) /* vertical flip */
        cellline+= (vmax-line-1)*4;
      else
        cellline+= (line-vpos)*4;
      for (k = 0; k < hsize && hplot--; k++) {
        if (hpos > -8 && hpos < 0) {
          if (cellinfo & 1<<11) {
            /* horizontal flip */
            data = LOCENDIAN32(*(uint32 *)(cellline+
        				   (hsize-k*2-1)*(vsize<<5)));
            data>>= (-hpos)*4; /* get first pixel in bottom 4 bits */
            for (j = 0; j < 8+hpos; j++) {
              pixel = data & 15;
              LINEDATASPR(j, pixel, palette, priority);
              data>>= 4;
            }
          } else {
            data = LOCENDIAN32(*(uint32 *)cellline);
            data<<= (-hpos)*4; /* get first pixel in top 4 bits */
            for (j = 0; j < 8+hpos; j++) {
              pixel = (data>>28) & 15;
              LINEDATASPR(j, pixel, palette, priority);
              data<<= 4;
            }
          }
        } else if (hpos >= 0 && hpos <= (signed int)(screencells-1)*8) {
          if (cellinfo & 1<<11) {
            /* horizontal flip */
            data = LOCENDIAN32(*(uint32 *)(cellline+
        				   (hsize-k*2-1)*(vsize<<5)));
            for (j = 0; j < 8; j++) {
              pixel = data & 15;
              LINEDATASPR(hpos+j, pixel, palette, priority);
              data>>= 4;
            }
          } else {
            data = LOCENDIAN32(*(uint32 *)cellline);
            for (j = 0; j < 8; j++) {
              pixel = (data>>28) & 15;
              LINEDATASPR(hpos+j, pixel, palette, priority);
              data<<= 4;
            }
          }
        } else if (hpos > (signed int)(screencells-1)*8 &&
        	   hpos < (signed int)screencells*8) {
          if (cellinfo & 1<<11) {
            /* horizontal flip */
            data = LOCENDIAN32(*(uint32 *)(cellline+
        				   (hsize-k*2-1)*(vsize<<5)));
            for (j = 0; j < (signed int)(screencells*8 - hpos); j++) {
              pixel = data & 15;
              LINEDATASPR(hpos+j, pixel, palette, priority);
              data>>= 4;
            }
          } else {
            data = LOCENDIAN32(*(uint32 *)cellline);
            for (j = 0; j < (signed int)(screencells*8 - hpos); j++) {
              pixel = (data>>28) & 15;
              LINEDATASPR(hpos+j, pixel, palette, priority);
              data<<= 4;
            }
          }
        }
        cellline+= vsize<<5; /* 32 bytes per cell (note vsize is doubled
        			when interlaced) */
        hpos+= 8;
      }
    }
  }
}

inline void vdp_spritelayer(unsigned int line, uint8 *spritedata,
        		    uint8 *pridata, uint8 *linedata,
        		    unsigned int priority)
{
  int i;
  
  if (vdp_reg[12] & 1<<3) { /* s/ten = 1 */
    for (i = 0; i < 320; i++) {
      if (pridata[i] == priority) {
        if (spritedata[i]) {
          if (spritedata[i] == 62) { /* pal=3, val=14 */
            linedata[i] = (linedata[i] & 63) + 64;
          } else if (spritedata[i] == 63) { /* pal=3, val=15 */
            linedata[i] = (linedata[i] & 63) + 128;
          } else {
            linedata[i] = spritedata[i];
          }
        }
      }
    }
  } else {
    for (i = 0; i < 320; i++) {
      if (pridata[i] == priority) {
        if (spritedata[i]) {
          linedata[i] = spritedata[i];
        }
      }
    }
  }
}

/* layer 0 = A (top), layer 1 = B (bottom) */

void vdp_newlayer(unsigned int line, uint8 *pridata, uint8 *outdata,
        	  unsigned int layer)
{
  int i, j;
  uint8 hsize = vdp_reg[16] & 3;
  uint8 vsize = (vdp_reg[16] >> 4) & 3;
  uint8 hmode = vdp_reg[11] & 3;
  uint8 vmode = (vdp_reg[11] >> 2) & 1;
  uint16 vramoffset = (layer ? ((vdp_reg[4] & 7) << 13) :
        	       ((vdp_reg[2] & (7<<3)) << 10));
  uint16 *patterndata = (uint16 *)(vdp_vram + vramoffset);
  uint16 *hscrolldata = (uint16 *)(((vdp_reg[13] & 63) << 10)
        			   + vdp_vram + layer*2);
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint16 hwidth, vwidth, vmask, hoffset, voffset;
  uint16 cellinfo;
  uint8 *pattern;
  uint32 data;
  uint8 palette;
  uint8 pixel;
  uint8 topbottom, leftright, winhpos, winvpos;
  uint8 priority;
  int interlace = (vdp_reg[12] >> 1) & 3;
  int realline = interlace ? line >> 1 : line;

  /* do window vertical check */

  if (layer == 0) {
    /* turn off A when window is on */
    topbottom = vdp_reg[18] & 0x80;
    leftright = vdp_reg[17] & 0x80;
    winhpos = vdp_reg[17] & 0x1f;
    winvpos = vdp_reg[18] & 0x1f;
    /* Battle Tank sets winvpos to 1F and expects layer A to be off */
    if (topbottom && winvpos != 0x1F) {
      if ((realline >> 3) >= winvpos)
        return;
    } else {
      if ((realline >> 3) < winvpos)
        return;
    }
  }

  /* isn't this bug fixed now? */
  /* BUG: layer A is still displayed left/right when window is on,
     only apparent with high-priority layer A and low-priority window */

  /* select horizontal scrolling offset based on mode */

  switch(hmode) {
  case 0: /* full screen */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[0])) & 0x3FF;
    break;
  case 2: /* cell scroll */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*(realline & ~7)])) & 0x3FF;
    break;
  case 3: /* line scroll */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*realline])) & 0x3FF;
    break;
  default:
    LOG_NORMAL(("prohibited HSCR/LSCR"));
    hoffset = 0;
    break;
  }
  hwidth = (hsize+1) << 5;
  vwidth = ((vsize+1) << 5) * (interlace ? 2 : 1);
  vmask = (vwidth << 3) - 1; /* to put offsets into range */
  hoffset&= (hwidth << 3)-1; /* put offset in range */
  voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & vmask;
  cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+
                         hwidth*((voffset>>3)>>(interlace ? 1 : 0))]);
  /* 32 bytes per pattern or 64 in interlace mode */
  pattern = vdp_vram + (((cellinfo & 2047)<<5) << (interlace ? 1 : 0));
  /* now get correct line from pattern data */
  switch (interlace) {
  case 0: /* no interlace */
  case 1: /* ?? */
  case 2: /* ?? */
    if (cellinfo & 1<<12) {
      /* vertical flip */
      pattern+= 4*(7-(voffset & 7));
    } else {
      /* no vertical flip */
      pattern+= 4*(voffset & 7);
    }
    break;
  case 3: /* interlace with double height cells */
    if (cellinfo & 1<<12) {
      /* vertical flip */
      pattern+= 4*(15-(voffset & 15));
    } else {
      /* no vertical flip */
      pattern+= 4*(voffset & 15);
    }
    break;
  }
  priority = (cellinfo >> 15) & 1;
  palette = (cellinfo >> 13) & 3;
  data = LOCENDIAN32(*(uint32 *)pattern);
  if (cellinfo & 1<<11) {
    /* horizontal flip */
    data>>= (hoffset & 7)*4; /* get first pixel in bottom 4 bits */
    for (i = 0; i < 8-(hoffset & 7); i++) {
      pixel = data & 15;
      LINEDATALAYER(i, pixel, palette, priority);
      data>>= 4;
    }
  } else {
    data<<= (hoffset & 7)*4; /* get first pixel in top 4 bits */
    for (i = 0; i < 8-(hoffset & 7); i++) {
      pixel = (data>>28) & 15;
      LINEDATALAYER(i, pixel, palette, priority);
      data<<= 4;
    }
  }
  outdata+= 8-(hoffset & 7);
  pridata+= 8-(hoffset & 7);
  hoffset+= 8;
  hoffset&= (hwidth*8)-1; /* put offset in range */
  for (j = 1; j < screencells; j++) {
    if (vmode) {
      /* 2-cell scroll */
      /* DEBUG DEBUG DEBUG awooga awooga */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)
          [(j&~1)+layer] )) & vmask;
    } else {
      /* full screen */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & vmask;
    }
    cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+
                           hwidth*((voffset>>3)>>(interlace ? 1 : 0))]);
    priority = (cellinfo >> 15) & 1;
    /* printf("hoff: %04X voff: %04X hwid: %04X cell: %08X info: %04X\n",
           hoffset, voffset, hwidth, (hoffset>>3)+hwidth*(voffset>>3),
           cellinfo); */
    /* printf("loc %08X cellinfo %08X\n",
           (hoffset>>3)+hwidth*(voffset>>3),
           cellinfo); */
    palette = (cellinfo >> 13) & 3;
    /* 32 bytes per pattern or 64 in interlace mode */
    pattern = vdp_vram + (((cellinfo & 2047)<<5) << (interlace ? 1 : 0));
    /* now get correct line from pattern data */
    switch (interlace) {
    case 0: /* no interlace */
    case 1: /* ?? */
    case 2: /* ?? */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(7-(voffset & 7));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 7);
      }
      break;
    case 3: /* interlace with double height cells */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(15-(voffset & 15));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 15);
      }
      break;
    }
    data = LOCENDIAN32(*(uint32 *)pattern);
    if (cellinfo & 1<<11) {
      /* horizontal flip */
      for (i = 0; i < 8; i++) {
        pixel = data & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data>>= 4;
      }
    } else {
      for (i = 0; i < 8; i++) {
        pixel = (data>>28) & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data<<= 4;
      }
    }
    outdata+= 8;
    pridata+= 8;
    hoffset+= 8;
    hoffset&= (hwidth*8)-1; /* put offset in range */
  }
  if (hoffset & 7) {
    if (vmode) {
      /* 2-cell scroll - note use last offset, not next */
      /* DEBUG DEBUG DEBUG awooga awooga */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)
          [(screencells-2)+layer] )) & vmask;
    } else {
      /* full screen */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & vmask;
    }
    cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+
                           hwidth*((voffset>>3)>>(interlace ? 1 : 0))]);
    priority = (cellinfo >> 15) & 1;
    palette = (cellinfo >> 13) & 3;
    /* 32 bytes per pattern or 64 in interlace mode */
    pattern = vdp_vram + (((cellinfo & 2047)<<5) << (interlace ? 1 : 0));
    /* now get correct line from pattern data */
    switch (interlace) {
    case 0: /* no interlace */
    case 1: /* ?? */
    case 2: /* ?? */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(7-(voffset & 7));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 7);
      }
      break;
    case 3: /* interlace with double height cells */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(15-(voffset & 15));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 15);
      }
      break;
    }
    data = LOCENDIAN32(*(uint32 *)pattern);
    if (cellinfo & 1<<11) {
      /* horizontal flip */
      for (i = 0; i < (hoffset & 7); i++) {
        pixel = data & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data>>= 4;
      }
    } else {
      for (i = 0; i < (hoffset & 7); i++) {
        pixel = (data>>28) & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data<<= 4;
      }
    }
  }
}

/* this function updates outdata/pridata which came from the layerA generation
   routines */

void vdp_newwindow(unsigned int line, uint8 *pridata, uint8 *outdata)
{
  uint16 *patterndata = (uint16 *)(vdp_vram + ((vdp_reg[3] & 0x3e) << 10));
  uint8 winhpos = vdp_reg[17] & 0x1f;
  uint8 winvpos = vdp_reg[18] & 0x1f;
  uint8 vcell = line / 8;
  uint8 hcell;
  uint8 voffset = line & 7;
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint8 topbottom = vdp_reg[18] & 0x80;
  uint8 leftright = vdp_reg[17] & 0x80;
  uint8 patternshift = (vdp_reg[12] & 1) ? 6 : 5;
  uint16 cellinfo;
  uint8 *pattern;
  uint32 data;
  uint8 palette;
  uint8 pixel;
  unsigned int i;
  unsigned int wholeline = 0;
  uint8 priority;
  int layer = 0; /* for LINEDATALAYER macro */

  if (vdp_reg[17] == 0 && vdp_reg[18] == 0)
    return; /* window is off */

  /* Battle Tank sets winvpos to 0x80+0x1F and expects layer A to be off */

  /* this section below is repeated in the main renderline code, so that
     layerA is not unnecessarily calculated */
  if (topbottom ? (vcell >= winvpos) : (vcell < winvpos))
    wholeline = 1;
  if (topbottom && winvpos == 0x1f)
    wholeline = 1;

  if (!wholeline) {
    if (winhpos >= 20)
      winhpos = 20; /* 0x1F might be special? */
    if (leftright) {
      /* clear out priority bits on right of line (winhpos units of 16 pix */
      for (i = winhpos*4; i < 320/4; i++) /* winhpos units of 16 pix */
        ((uint32 *)pridata)[i]&= (0xffffffff - (0x01010101<<PRIBIT_LAYERA));
      memset(outdata+winhpos*16, 0, 320-(winhpos*16));
    } else {
      /* clear out priority bits on left of line (winhpos units of 16 pix) */
      for (i = 0; i < (unsigned int)(winhpos/4); i++)
        ((uint32 *)pridata)[i]&= (0xffffffff - (0x01010101<<PRIBIT_LAYERA));
      memset(outdata, 0, winhpos*16);
    }
  }
  for (hcell = 0; hcell < screencells; hcell++, outdata+=8, pridata+=8) {
    if (!wholeline) {
      if (leftright) {
        if ((hcell>>1) < winhpos)
          continue;
      } else {
        if ((hcell>>1) >= winhpos)
          continue;
      }
    }
    cellinfo = LOCENDIAN16(patterndata[vcell<<patternshift | hcell]);
    priority = (cellinfo >> 15) & 1;
    palette = (cellinfo >> 13) & 3;
    /* 32 bytes per pattern */
    pattern = vdp_vram + ((cellinfo & 2047)<<5); /* fix for interlace */
    if (cellinfo & 1<<12) {
      /* vertical flip */
      pattern+= 4*(7-(voffset & 7));
    } else {
      /* no vertical flip */
      pattern+= 4*(voffset & 7);
    }
    data = LOCENDIAN32(*(uint32 *)pattern);
    if (cellinfo & 1<<11) {
      /* horizontal flip */
      for (i = 0; i < 8; i++) {
        pixel = data & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data>>= 4;
      }
    } else {
      for (i = 0; i < 8; i++) {
        pixel = (data>>28) & 15;
        LINEDATALAYER(i, pixel, palette, priority);
        data<<= 4;
      }
    }
  } /* hcell */
}

inline void vdp_shadow(unsigned int line, uint8 *linedata)
{
  int i;
  
  /* this could be done 4 bytes at a time */
  for (i = 0; i < 320; i++)
    linedata[i] = (linedata[i] & 63) + 128;
}

void vdp_window(unsigned int line, unsigned int priority, uint8 *linedata)
{
  uint16 *patterndata = (uint16 *)(vdp_vram + ((vdp_reg[3] & 0x3e) << 10));
  uint8 winhpos = vdp_reg[17] & 0x1f;
  uint8 winvpos = vdp_reg[18] & 0x1f;
  uint8 vcell = line / 8;
  uint8 hcell;
  uint8 voffset = line & 7;
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint8 topbottom = vdp_reg[18] & 0x80;
  uint8 leftright = vdp_reg[17] & 0x80;
  uint8 patternshift = (vdp_reg[12] & 1) ? 6 : 5;
  uint16 cellinfo;
  uint8 *pattern;
  uint32 data;
  uint8 palette;
  uint8 pixel;
  unsigned int i;
  unsigned int wholeline = 0;

  if (topbottom) {
    /* bottom part */
    /* Battle Tank sets winvpos to 0x1F and expects layer A to be off */
    if (winvpos == 0x1F || vcell >= winvpos)
      wholeline = 1;
  } else {
    /* top part */
    if (vcell < winvpos)
      wholeline = 1;
  }
  for (hcell = 0; hcell < screencells; hcell++, linedata+=8) {
    if (!wholeline) {
      if (leftright) {
        if ((hcell>>1) < winhpos)
          continue;
      } else {
        if ((hcell>>1) >= winhpos)
          continue;
      }
    }
    cellinfo = LOCENDIAN16(patterndata[vcell<<patternshift | hcell]);
    if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) == priority) {
      palette = (cellinfo >> 13) & 3;
      pattern = vdp_vram + ((cellinfo & 2047)<<5); /* 32 bytes per pattern */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(7-(voffset & 7));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 7);
      }
      data = LOCENDIAN32(*(uint32 *)pattern);
      if (cellinfo & 1<<11) {
        /* horizontal flip */
        for (i = 0; i < 8; i++) {
          pixel = data & 15;
          LINEDATA(i, pixel, palette, priority);
          data>>= 4;
        }
      } else {
        for (i = 0; i < 8; i++) {
          pixel = (data>>28) & 15;
          LINEDATA(i, pixel, palette, priority);
          data<<= 4;
        }
      }
    }
  } /* hcell */
}

void vdp_layer_interlace(unsigned int line, unsigned int layer,
        		 unsigned int priority, uint8 *linedata)
{
  int i, j;
  uint8 hsize = vdp_reg[16] & 3;
  uint8 vsize = (vdp_reg[16] >> 4) & 3;
  uint8 hmode = vdp_reg[11] & 3;
  uint8 vmode = (vdp_reg[11] >> 2) & 1;
  uint16 vramoffset = (layer ? ((vdp_reg[4] & 7) << 13) :
        	       ((vdp_reg[2] & (7<<3)) << 10));
  uint16 *patterndata = (uint16 *)(vdp_vram + vramoffset);
  uint16 *hscrolldata = (uint16 *)(((vdp_reg[13] & 63) << 10)
        			   + vdp_vram + layer*2);
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint16 hwidth, vwidth, hoffset, voffset;
  uint16 cellinfo;
  uint8 *pattern;
  uint32 data;
  uint8 palette;
  uint8 pixel;
  uint8 topbottom, leftright, winhpos, winvpos;

  if (layer == 0) {
    /* turn off A when window is on */
    topbottom = vdp_reg[18] & 0x80;
    leftright = vdp_reg[17] & 0x80;
    winhpos = vdp_reg[17] & 0x1f;
    winvpos = vdp_reg[18] & 0x1f;
    if (topbottom) {
      if ((line>>4) >= winvpos)
        return;
    } else {
      if ((line>>4) < winvpos)
        return;
    }
  }

  /* BUG: layer A is still displayed left/right when window is on,
     only apparent with high-priority layer A and low-priority window */

  switch(hmode) {
  case 0: /* full screen */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[0])) & 0x3FF;
    break;
  case 2: /* cell scroll */
    /* interlace we use the actual line for the hscroll */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*((line>>1) & ~7)])) & 0x3FF;
    break;
  case 3: /* line scroll */
    /* interlace we use the actual line for the hscroll */
    hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*(line>>1)])) & 0x3FF;
    break;
  default:
    LOG_NORMAL(("prohibited HSCR/LSCR"));
    hoffset = 0;
    break;
  }
  hwidth = 32+hsize*32;
  vwidth = 32+vsize*32;
  hoffset&= (hwidth*8)-1; /* put offset in range */
  voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & 0x7FF;
  voffset&= (vwidth*16)-1; /* put offset in range */

  cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+hwidth*(voffset>>4)]);
  if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) == priority) {
    palette = (cellinfo >> 13) & 3;
    pattern = vdp_vram + ((cellinfo & 2047)<<6); /* 64 bytes per pattern */
    if (cellinfo & 1<<12) {
      /* vertical flip */
      pattern+= 4*(15-(voffset & 15));
    } else {
      /* no vertical flip */
      pattern+= 4*(voffset & 15);
    }
    data = LOCENDIAN32(*(uint32 *)pattern);
    if (cellinfo & 1<<11) {
      /* horizontal flip */
      data>>= (hoffset & 7)*4; /* get first pixel in bottom 4 bits */
      for (i = 0; i < 8-(hoffset & 7); i++) {
        pixel = data & 15;
        LINEDATA(i, pixel, palette, priority);
        data>>= 4;
      }
    } else {
      data<<= (hoffset & 7)*4; /* get first pixel in top 4 bits */
      for (i = 0; i < 8-(hoffset & 7); i++) {
        pixel = (data>>28) & 15;
        LINEDATA(i, pixel, palette, priority);
        data<<= 4;
      }
    }
  }
  linedata+= 8-(hoffset & 7);
  hoffset+= 8;
  hoffset&= (hwidth*8)-1; /* put offset in range */
  for (j = 0; j < screencells-1; j++) {
    if (vmode) {
      /* 2-cell scroll */
      /* DEBUG DEBUG DEBUG awooga awooga */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)
        			     [2*(j>>1)+layer] )) & 0x7FF;
    } else {
      /* full screen */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & 0x7FF;
    }
    voffset&= (vwidth*16)-1; /* put offset in range */
    cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+hwidth*(voffset>>4)]);
    /* printf("hoff: %04X voff: %04X hwid: %04X cell: %08X info: %04X\n",
           hoffset, voffset, hwidth, (hoffset>>3)+hwidth*(voffset>>4),
           cellinfo); */
    /* printf("loc %08X cellinfo %08X\n",
           (hoffset>>3)+hwidth*(voffset>>4),
           cellinfo); */
    if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) == priority) {
      palette = (cellinfo >> 13) & 3;
      pattern = vdp_vram + ((cellinfo & 2047)<<6); /* 64 bytes per pattern */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(15-(voffset & 15));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 15);
      }
      data = LOCENDIAN32(*(uint32 *)pattern);
      if (cellinfo & 1<<11) {
        /* horizontal flip */
        for (i = 0; i < 8; i++) {
          pixel = data & 15;
          LINEDATA(i, pixel, palette, priority);
          data>>= 4;
        }
      } else {
        for (i = 0; i < 8; i++) {
          pixel = (data>>28) & 15;
          LINEDATA(i, pixel, palette, priority);
          data<<= 4;
        }
      }
    }
    linedata+= 8;
    hoffset+= 8;
    hoffset&= (hwidth*8)-1; /* put offset in range */
  }
  if (hoffset & 7) {
    if (vmode) {
      /* 2-cell scroll - note use last offset, not next */
      /* DEBUG DEBUG DEBUG awooga awooga */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)
        			     [2*(j>>1)+layer] )) & 0x7FF;
    } else {
      /* full screen */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & 0x7FF;
    }
    voffset&= (vwidth*16)-1; /* put offset in range */
    cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+hwidth*(voffset>>4)]);
    if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) == priority) {
      palette = (cellinfo >> 13) & 3;
      pattern = vdp_vram + ((cellinfo & 2047)<<6); /* 64 bytes per pattern */
      if (cellinfo & 1<<12) {
        /* vertical flip */
        pattern+= 4*(15-(voffset & 15));
      } else {
        /* no vertical flip */
        pattern+= 4*(voffset & 15);
      }
      data = LOCENDIAN32(*(uint32 *)pattern);
      if (cellinfo & 1<<11) {
        /* horizontal flip */
        for (i = 0; i < (hoffset & 7); i++) {
          pixel = data & 15;
          LINEDATA(i, pixel, palette, priority);
          data>>= 4;
        }
      } else {
        for (i = 0; i < (hoffset & 7); i++) {
          pixel = (data>>28) & 15;
          LINEDATA(i, pixel, palette, priority);
          data<<= 4;
        }
      }
    }
    linedata+= 8;
  }
}

void vdp_renderline(unsigned int line, uint8 *linedata, unsigned int odd)
{
  int i;
  uint8 datablock[320*4];
  uint8 *data_sprite = datablock;
  uint8 *data_layerA = datablock + 320;
  uint8 *data_layerB = datablock + 320*2;
  uint8 *priorities = datablock + 320*3;
  uint8 bg = vdp_reg[7] & 63;
  unsigned int winvpos = vdp_reg[18] & 0x1f;
  int windowall;
  unsigned int interlace = (vdp_reg[12] >> 1) & 3;

  windowall = ((vdp_reg[18] & 0x80) ? (winvpos == 0x1f || (line/8) >= winvpos)
               : (line/8 < winvpos));

  memset(datablock, 0, sizeof(datablock));
  
  if ((vdp_reg[1] & 1<<6) == 0) {
    /* screen is disabled */
    for (i = 0; i < 320; i++)
      linedata[i] = bg;
    return;
  }

  if (vdp_layerS || vdp_layerSp)
    vdp_sprites(interlace ? (line*2+odd) : line, priorities, data_sprite);
  if (vdp_layerA || vdp_layerAp) {
    if (!windowall)
      vdp_newlayer(interlace ? (line*2+odd) : line, priorities, data_layerA, 0);
    if (vdp_layerW || vdp_layerWp)
      vdp_newwindow(line, priorities, data_layerA);
  }
  if (vdp_layerB || vdp_layerBp)
    vdp_newlayer(interlace ? (line*2+odd) : line, priorities, data_layerB, 1);

  for (i = 0; i < 320; i++) {
    switch(priorities[i] | (vdp_reg[12] & 1<<3)) {
    case 0:                                        /* s/ten=0, B=0, A=0, S=0 */
      if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else
        linedata[i] = bg;
      break;
    case 1:                                        /* s/ten=0, B=1, A=0, S=0 */
      if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else
        linedata[i] = bg;
      break;
    case 2:                                        /* s/ten=0, B=0, A=1, S=0 */
      if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else
        linedata[i] = bg;
      break;
    case 3:                                        /* s/ten=0, B=1, A=1, S=0 */
      if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else
        linedata[i] = bg;
      break;
    case 4:                                        /* s/ten=0, B=0, A=0, S=1 */
      if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else
        linedata[i] = bg;
      break;
    case 5:                                        /* s/ten=0, B=1, A=0, S=1 */
      if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else
        linedata[i] = bg;
      break;
    case 6:                                        /* s/ten=0, B=0, A=1, S=1 */
      if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else
        linedata[i] = bg;
      break;
    case 7:                                        /* s/ten=0, B=1, A=1, S=1 */
      if (data_sprite[i])
        linedata[i] = data_sprite[i];
      else if (data_layerA[i])
        linedata[i] = data_layerA[i];
      else if (data_layerB[i])
        linedata[i] = data_layerB[i];
      else
        linedata[i] = bg;
      break;
    case 8:                                        /* s/ten=1, B=0, A=0, S=0 */
      if (data_sprite[i]) {
        if (data_sprite[i] == 63) {                /* shadow operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 128; /* shadow */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 128; /* shadow */
          else
            linedata[i] = bg | 128; /* shadow */
        } else if (data_sprite[i] == 62) {         /* highlight operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i]; /* normal */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i]; /* normal */
          else
            linedata[i] = bg; /* normal */
        } else {
          linedata[i] = data_sprite[i] | 128; /* shadow */
        }
      } else {
        if (data_layerA[i])
          linedata[i] = data_layerA[i] | 128; /* shadow */
        else if (data_layerB[i])
          linedata[i] = data_layerB[i] | 128; /* shadow */
        else
          linedata[i] = bg | 128; /* shadow */
      }
      break;
    case 9:                                        /* s/ten=1, B=1, A=0, S=0 */
      if (data_layerB[i]) {
        linedata[i] = data_layerB[i]; /* normal */
      } else {
        if (data_sprite[i]) {
          if (data_sprite[i] == 63) {              /* shadow operator */
            if (data_layerA[i])
              linedata[i] = data_layerA[i] | 128; /* shadow */
            else
              linedata[i] = bg | 128; /* shadow */
          } else if (data_sprite[i] == 62) {       /* highlight operator */
            if (data_layerA[i])
              linedata[i] = data_layerA[i] | 64; /* highlight */
            else
              linedata[i] = bg | 64; /* highlight */
          } else {
            linedata[i] = data_sprite[i]; /* normal */
          }
        } else {
          if (data_layerA[i])
            linedata[i] = data_layerA[i]; /* normal */
          else
            linedata[i] = bg; /* normal */
        }
      }
      break;
    case 10:                                       /* s/ten=1, B=0, A=1, S=0 */
      if (data_layerA[i]) {
        linedata[i] = data_layerA[i]; /* normal */
      } else {
        if (data_sprite[i]) {
          if (data_sprite[i] == 63) {              /* shadow operator */
            if (data_layerB[i])
              linedata[i] = data_layerB[i] | 128; /* shadow */
            else
              linedata[i] = bg | 128; /* shadow */
          } else if (data_sprite[i] == 62) {       /* highlight operator */
            if (data_layerB[i])
              linedata[i] = data_layerB[i] | 64; /* highlight */
            else
              linedata[i] = bg | 64; /* highlight */
          } else {
            linedata[i] = data_sprite[i]; /* normal */
          }
        } else {
          if (data_layerB[i])
            linedata[i] = data_layerB[i]; /* normal */
          else
            linedata[i] = bg; /* normal */
        }
      }
      break;
    case 11:                                       /* s/ten=1, B=1, A=1, S=0 */
      if (data_layerA[i]) {
        linedata[i] = data_layerA[i]; /* normal */
      } else if (data_layerB[i]) {
        linedata[i] = data_layerB[i]; /* normal */
      } else if (data_sprite[i]) {
        if (data_sprite[i] == 63)                  /* shadow operator */
          linedata[i] = bg | 128; /* shadow */
        else if (data_sprite[i] == 62)             /* highlight operator */
          linedata[i] = bg | 64; /* highlight */
        else
          linedata[i] = data_sprite[i]; /* normal */
      } else {
        linedata[i] = bg; /* normal */
      }
      break;
    case 12:                                       /* s/ten=0, B=0, A=0, S=1 */
      if (data_sprite[i]) {
        if (data_sprite[i] == 63) {                /* shadow operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 128; /* shadow */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 128; /* shadow */
          else
            linedata[i] = bg | 128; /* shadow */
        } else if (data_sprite[i] == 62) {         /* highlight operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i]; /* normal */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i]; /* normal */
          else
            linedata[i] = bg; /* normal */
        } else {
          linedata[i] = data_sprite[i]; /* normal */
        }
      } else if (data_layerA[i]) {
        linedata[i] = data_layerA[i] | 128; /* shadow */
      } else if (data_layerB[i]) {
        linedata[i] = data_layerB[i] | 128; /* shadow */
      } else {
        linedata[i] = bg | 128; /* shadow */
      }
      break;
    case 13:                                       /* s/ten=1, B=1, A=0, S=1 */
      if (data_sprite[i]) {
        if (data_sprite[i] == 63) {                /* shadow operator */
          if (data_layerB[i])
            linedata[i] = data_layerB[i] | 128; /* shadow */
          else if (data_layerA[i])
            linedata[i] = data_layerA[i] | 128; /* shadow */
          else
            linedata[i] = bg | 128; /* shadow */
        } else if (data_sprite[i] == 62) {         /* highlight operator */
          if (data_layerB[i])
            linedata[i] = data_layerB[i] | 64; /* highlight */
          else if (data_layerA[i])
            linedata[i] = data_layerA[i] | 64; /* highlight */
          else
            linedata[i] = bg | 64; /* highlight */
        } else {
          linedata[i] = data_sprite[i]; /* normal */
        }
      } else if (data_layerB[i]) {
        linedata[i] = data_layerB[i]; /* normal */
      } else if (data_layerA[i]) {
        linedata[i] = data_layerA[i]; /* normal */
      }
      break;
    case 14:                                       /* s/ten=1, B=0, A=1, S=1 */
      if (data_sprite[i]) {
        if (data_sprite[i] == 63) {                /* shadow operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 128; /* shadow */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 128; /* shadow */
          else
            linedata[i] = bg | 128; /* shadow */
        } else if (data_sprite[i] == 62) {         /* highlight operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 64; /* highlight */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 64; /* highlight */
          else
            linedata[i] = bg | 64; /* highlight */
        } else {
          linedata[i] = data_sprite[i]; /* normal */
        }
      } else if (data_layerA[i]) {
        linedata[i] = data_layerA[i]; /* normal */
      } else if (data_layerB[i]) {
        linedata[i] = data_layerB[i]; /* normal */
      }
      break;
    case 15:                                       /* s/ten=1, B=1, A=1, S=1 */
      if (data_sprite[i]) {
        if (data_sprite[i] == 63) {                /* shadow operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 128; /* shadow */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 128; /* shadow */
          else
            linedata[i] = bg | 128; /* shadow */
        } else if (data_sprite[i] == 62) {         /* highlight operator */
          if (data_layerA[i])
            linedata[i] = data_layerA[i] | 64; /* highlight */
          else if (data_layerB[i])
            linedata[i] = data_layerB[i] | 64; /* highlight */
          else
            linedata[i] = bg | 64; /* highlight */
        } else {
          linedata[i] = data_sprite[i]; /* normal */
        }
      } else if (data_layerA[i]) {
        linedata[i] = data_layerA[i]; /* normal */
      } else if (data_layerB[i]) {
        linedata[i] = data_layerB[i]; /* normal */
      }
      break;
    }
  }
}

void vdp_renderline_interlace2(unsigned int line, uint8 *linedata)
{
  int i;
  uint8 pridata[640];
  uint8 *spritedata = pridata + 320;

  memset(pridata, 0, 640);
  memset(linedata, vdp_reg[7] & 63, 320);

  if (vdp_reg[1] & 1<<6) {
    vdp_sprites(line, pridata, spritedata);
    ;
    if (vdp_layerB)
      vdp_layer_interlace(line, 1, 0, linedata);
    if (vdp_layerA)
      vdp_layer_interlace(line, 0, 0, linedata);
    /*
      if (vdp_layerW)
      vdp_window(line, 0, linedata);
    */
    if (vdp_layerH && (vdp_reg[12] & 1<<3))
      vdp_shadow(line, linedata);
    if (vdp_layerS)
      vdp_spritelayer(line, spritedata, pridata, linedata, 0);
    if (vdp_layerBp)
      vdp_layer_interlace(line, 1, 1, linedata);
    if (vdp_layerAp)
      vdp_layer_interlace(line, 0, 1, linedata);
    /*
      if (vdp_layerWp)
      vdp_window(line, 1, linedata);
    */
    if (vdp_layerSp)
      vdp_spritelayer(line, spritedata, pridata, linedata, 1);
  }
}

void vdp_renderframe(uint8 *framedata, unsigned int lineoffset)
{
  unsigned int i, line;
  uint32 background;
  unsigned int vertcells = vdp_reg[1] & 1<<3 ? 30 : 28;
  uint8 *linedata;

  /* fill in background */

  background = vdp_reg[7] & 63;
  background|= background<<8;
  background|= background<<16;

  for (line = 0; line < vertcells*8; line++) {
    linedata = framedata + line*lineoffset;
    for (i = 0; i < (320 / 4); i++) {
      ((uint32 *)linedata)[i] = background;
    }
  }

  if (vdp_reg[1] & 1<<6) {
    if (vdp_layerB)
      vdp_layer_simple(1, 0, framedata, lineoffset);
    if (vdp_layerA)
      vdp_layer_simple(0, 0, framedata, lineoffset);
    if (vdp_layerH && (vdp_reg[12] & 1<<3))
      vdp_shadow_simple(framedata, lineoffset);
    if (vdp_layerS)
      vdp_sprites_simple(0, framedata, lineoffset);
    if (vdp_layerBp)
      vdp_layer_simple(1, 1, framedata, lineoffset);
    if (vdp_layerAp)
      vdp_layer_simple(0, 1, framedata, lineoffset);
    if (vdp_layerSp)
      vdp_sprites_simple(1, framedata, lineoffset);
  }
}

void vdp_showregs(void)
{
  int i;

  for (i = 0; i < 25; i++) {
    printf("[%02d] %02X: ", i, vdp_reg[i]);
    switch(i) {
    case 0:
      printf("%s ", vdp_reg[0] & 1<<1 ? "HV-stop" : "HV-enable");
      printf("%s ", vdp_reg[0] & 1<<4 ? "HInt-enable" : "HInt-disable");
      break;
    case 1:
      printf("%s ", vdp_reg[1] & 1<<3 ? "30-cell" : "28-cell");
      printf("%s ", vdp_reg[1] & 1<<4 ? "DMA-enable" : "DMA-disable");
      printf("%s ", vdp_reg[1] & 1<<5 ? "VInt-enable" : "VInt-disable");
      printf("%s ", vdp_reg[1] & 1<<6 ? "Disp-enable" : "Disp-disable");
      break;
    case 2:
      printf("Scroll A @ %04X", (vdp_reg[2] & 0x38)<<10);
      break;
    case 3:
      printf("Window @ %04X", (vdp_reg[3] & 0x3E)<<10);
      break;
    case 4:
      printf("Scroll B @ %04X", (vdp_reg[4] & 7)<<13);
      break;
    case 5:
      printf("Sprites @ %04X", (vdp_reg[5] & 0x7F)<<9);
      break;
    case 7:
      printf("bgpal %d col %d", (vdp_reg[7]>>4 & 3), (vdp_reg[7] & 15));
      break;
    case 10:
      printf("hintreg %04X", vdp_reg[10]);
      break;
    case 11:
      printf("V-mode %d H-mode %d ", (vdp_reg[11]>>2) & 1, (vdp_reg[11] & 3));
      printf("%s", (vdp_reg[11] & 1<<3) ? "ExtInt-enable" : "ExtInt-disable");
      break;
    case 12:
      printf("Interlace %d ", (vdp_reg[12]>>1) & 3);
      printf("%s ", (vdp_reg[12] & 1<<0) ? "40-cell" : "32-cell");
      printf("%s ", (vdp_reg[12] & 1<<3) ? "Shadow-enable" : "Shadow-disable");
      break;
    case 13:
      printf("Scroll A @ %04X", (vdp_reg[13] & 0x3F)<<10);
      break;
    case 15:
      printf("Autoinc %d", vdp_reg[15]);
      break;
    case 16:
      printf("Vsize %d Hsize %d", (vdp_reg[16]>>4) & 3, (vdp_reg[16] & 3));
      break;
    case 17:
      printf("Window H %s ", (vdp_reg[17] & 1<<7) ? "right" : "left");
      printf("%d", vdp_reg[17] & 0x1F);
      break;
    case 18:
      printf("Window V %s ", (vdp_reg[18] & 1<<7) ? "lower" : "upper");
      printf("%d", vdp_reg[18] & 0x1F);
      break;
    case 19:
      printf("DMA-length-low %02X", vdp_reg[19]);
      break;
    case 20:
      printf("DMA-length-high %02X", vdp_reg[20]);
      break;
    case 21:
      printf("DMA-source-low %02X", vdp_reg[21]);
      break;
    case 22:
      printf("DMA-source-mid %02X", vdp_reg[22]);
      break;
    case 23:
      printf("DMA-source-high %02X", vdp_reg[23]);
      break;
    }
    printf("\n");
  }
}

void vdp_spritelist(void)
{
  uint8 *spritelist = vdp_vram + ((vdp_reg[5] & 0x7F)<<9);
  uint8 *sprite;
  uint8 link = 0;
  uint16 pattern;
  uint8 palette;
  uint16 cellinfo;
  sint16 vpos, hpos, vmax;
  uint8 vsize, hsize;

  LOG_REQUEST(("SPRITE DUMP: (base=vram+%X)",
               (vdp_reg[5] & 0x7f)<<9));
  do {
    sprite = spritelist + (link<<3);
    hpos = (LOCENDIAN16(*(uint16 *)(sprite+6)) & 0x1FF) - 0x80;
    vpos = (LOCENDIAN16(*(uint16 *)(sprite)) & 0x3FF) - 0x80;
    vsize = 1+(sprite[2] & 3);
    hsize = 1+((sprite[2]>>2) & 3);
    cellinfo = LOCENDIAN16(*(uint16 *)(sprite+4));
    pattern = cellinfo & 0x7FF;
    palette = (cellinfo >> 13) & 3;
    vmax = vpos + vsize*8;

    LOG_REQUEST(("Sprite %d @ %X", link,
        	 (link<<3) | (vdp_reg[5] & 0x7f)<<9));
    LOG_REQUEST(("  Pos:  %d,%d", hpos, vpos));
    LOG_REQUEST(("  Size: %d,%d", hsize, vsize));
    LOG_REQUEST(("  Pri: %d, Pal: %d, Vflip: %d, Hflip: %d",
        	 (cellinfo>>15 & 1), (cellinfo>>13 & 3), (cellinfo>>12 & 1),
        	 (cellinfo>>11 & 1)));
    LOG_REQUEST(("  Pattern: %d (%x) @ vram+%X (%X if interlaced)",
        	 (cellinfo & 0x7FF), (cellinfo & 0x7FF),
        	(cellinfo & 0x7FF)*32, (cellinfo & 0x7FF)*32));
    link = sprite[3] & 0x7F;
  } while (link);
}

void vdp_describe(void)
{
  int layer;
  unsigned int line;
  uint32 o_patterndata, o_hscrolldata;
  uint16 *patterndata, *hscrolldata;
  uint8 hsize = vdp_reg[16] & 3;
  uint8 vsize = (vdp_reg[16] >> 4) & 3;
  uint8 hmode = vdp_reg[11] & 3;
  uint8 vmode = (vdp_reg[11] >> 2) & 1;
  uint16 hwidth, vwidth, hoffset, voffset, raw_hoffset;

  hwidth = 32+hsize*32;
  vwidth = 32+vsize*32;
  LOG_REQUEST(("VDP description:"));
  LOG_REQUEST(("  hsize = %d (ie. width=%d)", hsize, hwidth));
  LOG_REQUEST(("  vsize = %d (ie. width=%d)", vsize, vwidth));
  LOG_REQUEST(("  hmode = %d (0=full, 2=cell, 3=line)", hmode));
  LOG_REQUEST(("  vmode = %d (0=full, 1=2cell", vmode));

  for (layer = 0; layer < 2; layer++) {
    LOG_REQUEST(("  Layer %s:", layer == 0 ? "A" : "B"));
    o_patterndata = (layer == 0 ? ((vdp_reg[2] & 0x38)<<10) :
        	     ((vdp_reg[4] & 7)<<13));
    o_hscrolldata = layer*2 + ((vdp_reg[13] & 63)<<10);
    LOG_REQUEST(("    Pattern data @ vram+%08X", o_patterndata));
    LOG_REQUEST(("    Hscroll data @ vram+%08X", o_hscrolldata));
    patterndata = (uint16 *)(vdp_vram + o_patterndata);
    hscrolldata = (uint16 *)(vdp_vram + o_hscrolldata);
    for (line = 0; line < vdp_vislines; line++) {
      switch(hmode) {
      case 0: /* full screen */
        hoffset = (0x400 - LOCENDIAN16(hscrolldata[0])) & 0x3FF;
        break;
      case 2: /* cell scroll */
        hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*(line & ~7)])) & 0x3FF;
        break;
      case 3: /* line scroll */
        hoffset = (0x400 - LOCENDIAN16(hscrolldata[2*line])) & 0x3FF;
        break;
      default:
        LOG_REQUEST(("prohibited HSCR/LSCR on line %d", line));
        hoffset = 0;
        break;
      }
      raw_hoffset = hoffset;
      hoffset&= (hwidth*8)-1; /* put offset in range */
      voffset = (line + LOCENDIAN16( ((uint16 *)vdp_vsram)[layer] )) & 0x3FF;
      voffset&= (vwidth*8)-1; /* put offset in range */
      LOG_REQUEST(("     line %d: hoffset=%d=%d, voffset=%d, "
        	   "firstcell=vram+%08X", line, raw_hoffset, hoffset, voffset,
        	   o_patterndata+2*((hoffset>>3)+hwidth*(voffset>>3))));
    }
  }
}

void vdp_eventinit(void)
{
  /* Facts from documentation:
       H-Blank is 73.7 clock cycles long.
       The VDP settings are aquired 36 clocks after start of H-Blank.
       The display period is 413.3 clocks in duration.
       V-Int occurs 14.7us after H-Int (which is 112 clock cycles)
     Facts from clock data:
       One line takes 488 clocks (vdp_clksperline_68k)
     Assumptions: (not sure if these are true anymore)
       We 'approximate' and make H-Int occur at the same time as H-Blank.
       V-Blank starts at V-Int and ends at the start of line 0.

     vdp_event_start    = start of line, end of v-blank
     vdp_event_vint     = v-int time on line 224 (or 240)
                                (112 clocks after h-int)
     vdp_event_hint     = h-int time at end of each line
     vdp_event_hdisplay = settings are aquired and current line displayed
     vdp_event_end      = end of line, end of h-blank

     Note that if the program stays in H-Int 224 longer than 112 clocks, V-Int
     is not supposed to occur due to the processor acknowledging the wrong
     interrupt from the VDP, thus programs disable H-Ints on 223 to prevent
     this problem.  We don't worry about this.
  */
  vdp_event = 0;
  vdp_event_start = 0;
  vdp_event_vint = 112-74;
  vdp_event_hint = vdp_clksperline_68k-74;
  vdp_event_hdisplay = vdp_event_hint+36;
  vdp_event_end = vdp_clksperline_68k;
  vdp_nextevent = 0;
}

void vdp_endfield(void)
{
  vdp_line = 0;
  vdp_eventinit();
  vdp_oddframe^= 1; /* toggle */
  /* printf("(%d,%d,%d,%d,%d)\n", vdp_event_type,
     vdp_event_startline, vdp_event_hint, vdp_event_vdpplot,
     vdp_event_endline); */
}

inline void vdp_plotcell(uint8 *patloc, uint8 palette, uint8 flags,
        		 uint8 *cellloc, unsigned int lineoffset)
{
  int y, x;
  uint8 value;
  uint32 data;

  switch(flags) {
  case 0:
    /* normal tile - no s/ten */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[y]);
      for (x = 0; x < 8; x++, data<<= 4) {
        value = data>>28;
        if (value)
          cellloc[x] = palette*16 + value;
      }
    }
    break;
  case 1:
    /* h flipped tile - no s/ten */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[y]);
      for (x = 0; x < 8; x++, data>>= 4) {
        value = data & 15;
        if (value)
          cellloc[x] = palette*16 + value;
      }
    }
    break;
  case 2:
    /* v flipped tile - no s/ten */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[7-y]);
      for (x = 0; x < 8; x++, data<<= 4) {
        value = data>>28;
        if (value)
          cellloc[x] = palette*16 + value;
      }
    }
    break;
  case 3:
    /* h and v flipped tile - no s/ten */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[7-y]);
      for (x = 0; x < 8; x++, data>>= 4) {
        value = data & 15;
        if (value)
          cellloc[x] = palette*16 + value;
      }
    }
    break;
  case 4:
    /* normal tile - s/ten enabled */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[y]);
      for (x = 0; x < 8; x++, data<<= 4) {
        value = data>>28;
        if (value) {
          if (palette == 3 && value == 14) {
            cellloc[x] = (cellloc[x] & 63) + 64;
          } else if (palette == 3 && value == 15) {
            cellloc[x] = (cellloc[x] & 63) + 128;
          } else {
            cellloc[x] = palette*16 + value;
          }
        }
      }
    }
    break;
  case 5:
    /* h flipped tile - s/ten */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[y]);
      for (x = 0; x < 8; x++, data>>= 4) {
        value = data & 15;
        if (value) {
          if (palette == 3 && value == 14) {
            cellloc[x] = (cellloc[x] & 63) + 64;
          } else if (palette == 3 && value == 15) {
            cellloc[x] = (cellloc[x] & 63) + 128;
          } else {
            cellloc[x] = palette*16 + value;
          }
        }
      }
    }
    break;
  case 6:
    /* v flipped tile - s/ten enabled */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[7-y]);
      for (x = 0; x < 8; x++, data<<= 4) {
        value = data>>28;
        if (value) {
          if (palette == 3 && value == 14) {
            cellloc[x] = (cellloc[x] & 63) + 64;
          } else if (palette == 3 && value == 15) {
            cellloc[x] = (cellloc[x] & 63) + 128;
          } else {
            cellloc[x] = palette*16 + value;
          }
        }
      }
    }
    break;
  case 7:
    /* h and v flipped tile - s/ten enabled */
    for (y = 0; y < 8; y++, cellloc+= lineoffset) {
      data = LOCENDIAN32(((uint32 *)patloc)[7-y]);
      for (x = 0; x < 8; x++, data>>= 4) {
        value = data & 15;
        if (value) {
          if (palette == 3 && value == 14) {
            cellloc[x] = (cellloc[x] & 63) + 64;
          } else if (palette == 3 && value == 15) {
            cellloc[x] = (cellloc[x] & 63) + 128;
          } else {
            cellloc[x] = palette*16 + value;
          }
        }
      }
    }
    break;
  default:
    ui_err("Unknown plotcell flags");
  }
}

/* must be 8*lineoffset bytes scrap before and after fielddata and also
   8 bytes before each line and 8 bytes after each line */

void vdp_layer_simple(unsigned int layer, unsigned int priority,
        	      uint8 *framedata, unsigned int lineoffset)
{
  int i, j;
  uint8 hsize = vdp_reg[16] & 3;
  uint8 vsize = (vdp_reg[16] >> 4) & 3;
  uint8 hmode = vdp_reg[11] & 3;
  uint8 vmode = (vdp_reg[11] >> 2) & 1;
  uint16 vramoffset = (layer ? ((vdp_reg[4] & 7) << 13) :
        	       ((vdp_reg[2] & (7<<3)) << 10));
  uint16 *patterndata = (uint16 *)(vdp_vram + vramoffset);
  uint16 *hscrolldata = (uint16 *)(((vdp_reg[13] & 63) << 10)
        			   + vdp_vram + layer*2);
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint16 hwidth = 32+hsize*32;
  uint16 vwidth = 32+vsize*32;
  uint16 hoffset, voffset;
  uint16 cellinfo;
  uint8 *pattern;
  uint32 data;
  uint8 palette;
  uint8 value;
  unsigned int xcell, ycell;
  unsigned int pos;
  uint8 *toploc, *cellloc;
  uint8 flags;
  uint32 hscroll, vscroll;
  unsigned int x, y;

  for (xcell = 0; xcell <= screencells; xcell++) {
    if (vmode) {
      /* 2-cell scroll */
      vscroll = ((xcell >= screencells ? xcell-2 : xcell) & ~1) + layer;
    } else {
      /* full screen */
      vscroll = layer;
    }
    voffset = LOCENDIAN16( ((uint16 *)vdp_vsram)[vscroll] ) & 0x3FF;
    toploc = framedata - lineoffset*(voffset & 7);
    for (ycell = 0; ycell <= 28; ycell++, voffset+=8, toploc+=lineoffset*8) {
      switch(hmode) {
      case 0: /* full screen */
        hscroll = 0;
        break;
      case 2: /* cell scroll */
        hscroll = 2 * (ycell >= 28 ? ycell-2 : ycell) * 8;
        break;
      case 3: /* line scroll - approximation */
        hscroll = 2 * (ycell >= 28 ? ycell-2 : ycell) * 8;
        vdp_complex = 1;
        break;
      default:
        LOG_NORMAL(("prohibited HSCR/LSCR"));
        hscroll = 0;
        break;
      }
      voffset &= (vwidth*8)-1;
      hoffset = (0x400 - LOCENDIAN16(hscrolldata[hscroll])) & 0x3FF;
      hoffset = (hoffset+xcell*8) & ((hwidth*8)-1);
      cellinfo = LOCENDIAN16(patterndata[(hoffset>>3)+hwidth*(voffset>>3)]);
      if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) == priority) {
        /* plot cell */
        palette = (cellinfo >> 13) & 3;
        pattern = vdp_vram + ((cellinfo & 2047)<<5);
        flags = (cellinfo>>11) & 3; /* bit0=H flip, bit1=V flip */
        cellloc = toploc - (hoffset&7) + xcell*8;
        vdp_plotcell(pattern, palette, flags, cellloc, lineoffset);
      }
    } /* ycell */
  } /* xcell */
}

void vdp_shadow_simple(uint8 *framedata, unsigned int lineoffset)
{
  unsigned int vertcells = vdp_reg[1] & 1<<3 ? 30 : 28;
  uint8 *linedata;
  unsigned int line;
  int i;

  for (line = 0; line < vertcells*8; line++) {
    linedata = framedata + line*lineoffset;
    /* this could be done 4 bytes at a time */
    for (i = 0; i < 320; i++)
      linedata[i] = (linedata[i] & 63) + 128;
  }
}

void vdp_sprites_simple(unsigned int priority, uint8 *framedata,
        		unsigned int lineoffset)
{
  uint8 *spritelist = vdp_vram + ((vdp_reg[5] & 0x7F)<<9);

  vdp_sprite_simple(priority, framedata, lineoffset, 1,
        	    spritelist, spritelist);
}

int vdp_sprite_simple(unsigned int priority, uint8 *framedata,
        	      unsigned int lineoffset, unsigned int number,
        	      uint8 *spritelist, uint8 *sprite)
{
  int i, j;
  int plotted = 1;
  uint8 screencells = (vdp_reg[12] & 1) ? 40 : 32;
  uint8 link;
  uint16 pattern;
  uint32 data;
  uint8 palette;
  uint16 cellinfo;
  sint16 vpos, hpos, vmax;
  uint16 xcell, ycell;
  uint8 vsize, hsize;
  uint8 *cellloc;
  uint8 flags;
  uint8 *patloc;

  if (number > 80) {
    LOG_VERBOSE(("%08X [VDP] Maximum of 80 sprites exceeded", regs.pc));
    return 0;
  }

  link = sprite[3] & 0x7F;
  hpos = (LOCENDIAN16(*(uint16 *)(sprite+6)) & 0x1FF) - 0x80;
  vpos = (LOCENDIAN16(*(uint16 *)(sprite)) & 0x3FF) - 0x80;
  vsize = 1+(sprite[2] & 3);
  hsize = 1+((sprite[2]>>2) & 3);
  cellinfo = LOCENDIAN16(*(uint16 *)(sprite+4));
  pattern = cellinfo & 0x7FF;
  palette = (cellinfo >> 13) & 3;
  vmax = vpos + vsize*8;
  
  if (link) {
    if (hpos == -128)
      /* we do not support 'masking' in simple mode */
      vdp_complex = 1;
    plotted = vdp_sprite_simple(priority, framedata, lineoffset, number+1,
        			spritelist, spritelist + (link<<3));
    plotted++;
  }

  if (((uint8)((cellinfo & 1<<15) ? 1 : 0)) != priority)
    return plotted;
  if (vpos >= 240 || hpos >= 320 || vpos+vsize*8 <= 0 || hpos+hsize*8 <= 0)
    /* sprite is not on screen */
    return plotted;
  flags = (cellinfo>>11) & 3; /* bit0=H flip, bit1=V flip */
  if (vdp_reg[12] & 1<<3) /* s/ten enabled? */
    flags|= 1<<2;
  switch(flags) {
  case 0:
  case 4:
    /* normal orientation */
    for (ycell = 0; ycell < vsize; ycell++) {
      if (ycell*8+vpos < -7 || ycell*8+vpos >= 240)
        /* cell out of plotting area (remember scrap area) */
        continue;
      patloc = vdp_vram + (pattern<<5) + ycell*32;
      for (xcell = 0; xcell < hsize; xcell++, patloc+= vsize*32) {
        if (xcell*8+hpos < -7 || xcell*8+hpos >= 320)
          /* cell out of plotting area */
          continue;
        cellloc = framedata + ((vpos+ycell*8)*lineoffset +
        		       (hpos+xcell*8));
        vdp_plotcell(patloc, palette, flags, cellloc, lineoffset);
      }
    }
    break;
  case 1:
  case 5:
    /* H flip */
    for (ycell = 0; ycell < vsize; ycell++) {
      if (ycell*8+vpos < -7 || ycell*8+vpos >= 240)
        /* cell out of plotting area (remember scrap area) */
        continue;
      patloc = vdp_vram + (pattern<<5) + ycell*32 + vsize*32*(hsize-1);
      for (xcell = 0; xcell < hsize; xcell++, patloc-= vsize*32) {
        if (xcell*8+hpos < -7 || xcell*8+hpos >= 320)
          /* cell out of plotting area */
          continue;
        cellloc = framedata + ((vpos+ycell*8)*lineoffset +
        		       (hpos+xcell*8));
        vdp_plotcell(patloc, palette, flags, cellloc, lineoffset);
      }
    }
    break;
  case 2:
  case 6:
    /* V flip */
    for (ycell = 0; ycell < vsize; ycell++) {
      if (ycell*8+vpos < -7 || ycell*8+vpos >= 240)
        /* cell out of plotting area (remember scrap area) */
        continue;
      patloc = vdp_vram + (pattern<<5) + (vsize-ycell-1)*32;
      for (xcell = 0; xcell < hsize; xcell++, patloc+= vsize*32) {
        if (xcell*8+hpos < -7 || xcell*8+hpos >= 320)
          /* cell out of plotting area */
          continue;
        cellloc = framedata + ((vpos+ycell*8)*lineoffset +
        		       (hpos+xcell*8));
        vdp_plotcell(patloc, palette, flags, cellloc, lineoffset);
      }
    }
    break;
  case 3:
  case 7:
    /* H and V flip */
    for (ycell = 0; ycell < vsize; ycell++) {
      if (ycell*8+vpos < -7 || ycell*8+vpos >= 240)
        /* cell out of plotting area (remember scrap area) */
        continue;
      patloc = vdp_vram + (pattern<<5) + ((vsize-ycell-1)*32 +
        				  vsize*32*(hsize-1));
      for (xcell = 0; xcell < hsize; xcell++, patloc-= vsize*32) {
        if (xcell*8+hpos < -7 || xcell*8+hpos >= 320)
          /* cell out of plotting area */
          continue;
        cellloc = framedata + ((vpos+ycell*8)*lineoffset +
        		       (hpos+xcell*8));
        vdp_plotcell(patloc, palette, flags, cellloc, lineoffset);
      }
    }
    break;
  }
  return plotted;
}

uint8 vdp_gethpos(void) {
  float percent;

  /* vdp_event = 0/1/2 -> beginning of line until 74 clocks before end
                 3     -> between hint and hdisplay (36 clocks)
                 4     -> between hdisplay and end  (38 clocks)
     This routine goes from 0 to the maximum number allowed not within
     H-blank, and then goes slightly beyond up to hdisplay.  Then
     between hdisplay and end we go negative.  I'm not sure how negative
     it is supposed to be, this goes from -38 to 0.

     40 horizontal cells, H goes from $00 to $B6, $E4 to $FF
     32 horizontal cells, H goes from $00 to $93, $E8 to $FF

     this is such a bodge - any changes, check '3 Ninjas kick back'
  */
  LOG_DEBUG1(("gethpos %X: clocks=%X : startofline=%X : hint=%X : "
              "end=%X", vdp_event, cpu68k_clocks,
              vdp_event_start,
              vdp_event_hint, vdp_event_end));
  if (vdp_event < 3) {
    percent = ((float)(cpu68k_clocks-vdp_event_start)/
               (float)(vdp_event_hint-vdp_event_start));
    if (vdp_reg[12] & 1)
      return (cpu68k_clocks > vdp_event_hint) ? 0xE4 : percent*0xB6;
    else
      return (cpu68k_clocks > vdp_event_hint) ? 0xE8 : percent*0x93;
  } else {
    percent = ((float)(cpu68k_clocks-vdp_event_hint)/
               (float)(vdp_event_end-vdp_event_hint));
    if (vdp_reg[12] & 1)
      return ((cpu68k_clocks > vdp_event_end) ? 0 :
              ((0xE4+(uint8)percent*28) & 0xff));
    else
      return ((cpu68k_clocks > vdp_event_end) ? 0 :
              ((0xE8+(uint8)percent*24) & 0xff));
  }
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.