/** EMULib Emulation Library *********************************/
/**                                                         **/
/**                        DRV9918.c                        **/
/**                                                         **/
/** This file contains screen refresh routines for the      **/
/** Texas Instruments TMS9918 video chip emulation. See     **/
/** TMS9918.h for declarations and TMS9918.c for the main   **/
/** code.                                                   **/
/**                                                         **/
/** Copyright (C) Marat Fayzullin 1996-2007                 **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/
#include <stdio.h>
#include "TMS9918.h"

/** Static Functions *****************************************/
/** Functions used internally by the TMS9918 drivers.       **/
/*************************************************************/
static void RefreshSprites(TMS9918 *VDP,register byte Y);
static void RefreshBorder(TMS9918 *VDP,register byte Y);

/** RefreshBorder() ******************************************/
/** This function is called from RefreshLine#() to refresh  **/
/** the screen border.                                      **/
/*************************************************************/
void RefreshBorder(register TMS9918 *VDP,register byte Y)
{
  register pixel *P,BC;
  register int J,N;

  /* Border color */
  BC=VDP->BGColor;

  /* Screen buffer */
  P=(pixel *)(VDP->XBuf);
  J=VDP->Width*(Y+(VDP->Height-192)/2);

  /* For the first line, refresh top border */
  if(Y) P+=J;
  else for(;J;J--) *P++=BC;

  /* Calculate number of pixels */
  N=(VDP->Width-(VDP->Mode? 256:240))/2; 

  /* Refresh left border */
  for(J=N;J;J--) *P++=BC;

  /* Refresh right border */
  P+=VDP->Width-(N<<1);
  for(J=N;J;J--) *P++=BC;

  /* For the last line, refresh bottom border */
  if(Y==191)
    for(J=VDP->Width*(VDP->Height-192)/2;J;J--) *P++=BC;
}

/** RefreshSprites() *****************************************/
/** This function is called from RefreshLine#() to refresh  **/
/** sprites.                                                **/
/*************************************************************/
void RefreshSprites(register TMS9918 *VDP,register byte Y)
{
  static const byte SprHeights[4] = { 8,16,16,32 };
  register byte OH,IH,*PT,*AT;
  register pixel *P,*T,C;
  register int L,K;
  register unsigned int M;

  T  = (pixel *)(VDP->XBuf)
     + VDP->Width*(Y+(VDP->Height-192)/2)
     + VDP->Width/2-128;
  OH = SprHeights[VDP->R[1]&0x03];
  IH = SprHeights[VDP->R[1]&0x02];
  AT = VDP->SprTab-4;
  C  = VDP->MaxSprites;
  M  = 0;
  L  = 0;

  do
  {
    M<<=1;AT+=4;L++;     /* Iterate through SprTab */
    K=AT[0];             /* K = sprite Y coordinate */
    if(K==208) break;    /* Iteration terminates if Y=208 */
    if(K>256-IH) K-=256; /* Y coordinate may be negative */

    /* Mark all valid sprites with 1s, break at MaxSprites */
    if((Y>K)&&(Y<=K+OH)) { M|=1;if(!--C) break; }
  }
  while(L<32);

  for(;M;M>>=1,AT-=4)
    if(M&1)
    {
      C=AT[3];                  /* C = sprite attributes */
      L=C&0x80? AT[1]-32:AT[1]; /* Sprite may be shifted left by 32 */
      C&=0x0F;                  /* C = sprite color */

      if((L<256)&&(L>-OH)&&C)
      {
        K=AT[0];                /* K = sprite Y coordinate */
        if(K>256-IH) K-=256;    /* Y coordinate may be negative */

        C  = VDP->XPal[C];
        P  = T+L;
        K  = Y-K-1;
        PT = VDP->SprGen
           + ((int)(IH>8? (AT[2]&0xFC):AT[2])<<3)
           + (OH>IH? (K>>1):K);

        /* Mask 1: clip left sprite boundary */
        K=L>=0? 0xFFFF:(0x10000>>(OH>IH? (-L>>1):-L))-1;

        /* Mask 2: clip right sprite boundary */
        L+=OH-257;
        if(L>=0)
        {
          L=(IH>8? 0x0002:0x0200)<<(OH>IH? (L>>1):L);
          K&=~(L-1);
        }

        /* Get and clip the sprite data */
        K&=((int)PT[0]<<8)|(IH>8? PT[16]:0x00);

        if(OH>IH)
        {
          /* Big (zoomed) sprite */

          /* Draw left 16 pixels of the sprite */
          if(K&0xFF00)
          {
            if(K&0x8000) P[1]=P[0]=C;
            if(K&0x4000) P[3]=P[2]=C;
            if(K&0x2000) P[5]=P[4]=C;
            if(K&0x1000) P[7]=P[6]=C;
            if(K&0x0800) P[9]=P[8]=C;
            if(K&0x0400) P[11]=P[10]=C;
            if(K&0x0200) P[13]=P[12]=C;
            if(K&0x0100) P[15]=P[14]=C;
          }

          /* Draw right 16 pixels of the sprite */
          if(K&0x00FF)
          {
            if(K&0x0080) P[17]=P[16]=C;
            if(K&0x0040) P[19]=P[18]=C;
            if(K&0x0020) P[21]=P[20]=C;
            if(K&0x0010) P[23]=P[22]=C;
            if(K&0x0008) P[25]=P[24]=C;
            if(K&0x0004) P[27]=P[26]=C;
            if(K&0x0002) P[29]=P[28]=C;
            if(K&0x0001) P[31]=P[30]=C;
          }
        }
        else
        {
          /* Normal (unzoomed) sprite */

          /* Draw left 8 pixels of the sprite */
          if(K&0xFF00)
          {
            if(K&0x8000) P[0]=C;
            if(K&0x4000) P[1]=C;
            if(K&0x2000) P[2]=C;
            if(K&0x1000) P[3]=C;
            if(K&0x0800) P[4]=C;
            if(K&0x0400) P[5]=C;
            if(K&0x0200) P[6]=C;
            if(K&0x0100) P[7]=C;
          }

          /* Draw right 8 pixels of the sprite */
          if(K&0x00FF)
          {
            if(K&0x0080) P[8]=C;
            if(K&0x0040) P[9]=C;
            if(K&0x0020) P[10]=C;
            if(K&0x0010) P[11]=C;
            if(K&0x0008) P[12]=C;
            if(K&0x0004) P[13]=C;
            if(K&0x0002) P[14]=C;
            if(K&0x0001) P[15]=C;
          }
        }
      }
    }
}

/** RefreshLine0() *******************************************/
/** Refresh line Y (0..191) of SCREEN0, including sprites   **/
/** in this line.                                           **/
/*************************************************************/
void RefreshLine0(register TMS9918 *VDP,register byte Y)
{
  register byte *T,X,K,Offset;
  register pixel *P,FC,BC;

  P  = (pixel *)(VDP->XBuf)
     + VDP->Width*(Y+(VDP->Height-192)/2)
     + VDP->Width/2-128+8;
  BC = VDP->BGColor;
  FC = VDP->FGColor;

  if(!TMS9918_ScreenON(VDP))
    for(X=0;X<240;X++) *P++=BC;
  else
  {
    T=VDP->ChrTab+(Y>>3)*40;
    Offset=Y&0x07;

    for(X=0;X<40;X++)
    {
      K=VDP->ChrGen[((int)*T<<3)+Offset];
      P[0]=K&0x80? FC:BC;
      P[1]=K&0x40? FC:BC;
      P[2]=K&0x20? FC:BC;
      P[3]=K&0x10? FC:BC;
      P[4]=K&0x08? FC:BC;
      P[5]=K&0x04? FC:BC;
      P+=6;T++;
    }
  }

  /* Refresh screen border */
  RefreshBorder(VDP,Y);
}

/** RefreshLine1() *******************************************/
/** Refresh line Y (0..191) of SCREEN1, including sprites   **/
/** in this line.                                           **/
/*************************************************************/
void RefreshLine1(register TMS9918 *VDP,register byte Y)
{
  register byte *T,X,K,Offset;
  register pixel *P,FC,BC;

  P  = (pixel *)(VDP->XBuf)
     + VDP->Width*(Y+(VDP->Height-192)/2)
     + VDP->Width/2-128;

  if(!TMS9918_ScreenON(VDP))
  {
    register int J;
    BC=VDP->BGColor;
    for(J=0;J<256;J++) *P++=BC;
  }
  else
  {
    T=VDP->ChrTab+((int)(Y&0xF8)<<2);
    Offset=Y&0x07;

    for(X=0;X<32;X++)
    {
      K=*T;
      BC=VDP->ColTab[K>>3];
      FC=VDP->XPal[BC>>4];
      BC=VDP->XPal[BC&0x0F];
      K=VDP->ChrGen[((int)K<<3)+Offset];
      P[0]=K&0x80? FC:BC;
      P[1]=K&0x40? FC:BC;
      P[2]=K&0x20? FC:BC;
      P[3]=K&0x10? FC:BC;
      P[4]=K&0x08? FC:BC;
      P[5]=K&0x04? FC:BC; 
      P[6]=K&0x02? FC:BC;
      P[7]=K&0x01? FC:BC;
      P+=8;T++;
    }

    /* Refresh sprites */
    RefreshSprites(VDP,Y);
  }

  /* Refresh screen border */
  RefreshBorder(VDP,Y);
}

/** RefreshLine2() *******************************************/
/** Refresh line Y (0..191) of SCREEN2, including sprites   **/
/** in this line.                                           **/
/*************************************************************/
void RefreshLine2(register TMS9918 *VDP,register byte Y)
{
  register byte X,K,Offset;
  register byte *T,*PGT,*CLT;
  register pixel *P,FC,BC;
  register int J;

  P  = (pixel *)(VDP->XBuf)
     + VDP->Width*(Y+(VDP->Height-192)/2)
     + VDP->Width/2-128;

  if(!TMS9918_ScreenON(VDP))
  {
    BC=VDP->BGColor;
    for(J=0;J<256;J++) *P++=BC;
  }
  else
  {
    J      = (int)(Y&0xC0)<<5;
    PGT    = VDP->ChrGen+J;
    CLT    = VDP->ColTab+J;
    T      = VDP->ChrTab+((int)(Y&0xF8)<<2);
    Offset = Y&0x07;

    for(X=0;X<32;X++)
    {
      J    = ((int)*T<<3)+Offset;
      K    = CLT[J];
      FC   = VDP->XPal[K>>4];
      BC   = VDP->XPal[K&0x0F];
      K    = PGT[J];
      P[0] = K&0x80? FC:BC;
      P[1] = K&0x40? FC:BC;
      P[2] = K&0x20? FC:BC;
      P[3] = K&0x10? FC:BC;
      P[4] = K&0x08? FC:BC;
      P[5] = K&0x04? FC:BC;
      P[6] = K&0x02? FC:BC;
      P[7] = K&0x01? FC:BC;
      P+=8;
      T++;
    }

    /* Refresh sprites */
    RefreshSprites(VDP,Y);
  }

  /* Refresh screen border */
  RefreshBorder(VDP,Y);
}

/** RefreshLine3() *******************************************/
/** Refresh line Y (0..191) of SCREEN3, including sprites   **/
/** in this line.                                           **/
/*************************************************************/
void RefreshLine3(register TMS9918 *VDP,register byte Y)
{
  register byte *T,X,K,Offset;
  register pixel *P;

  P  = (pixel *)(VDP->XBuf)
     + VDP->Width*(Y+(VDP->Height-192)/2)
     + VDP->Width/2-128;

  if(!TMS9918_ScreenON(VDP))
  {
    register pixel BC;
    register int J;
    BC=VDP->BGColor;
    for(J=0;J<256;J++) *P++=BC;
  }
  else
  {
    T=VDP->ChrTab+((int)(Y&0xF8)<<2);
    Offset=(Y&0x1C)>>2;

    for(X=0;X<32;X++)
    {
      K=VDP->ChrGen[((int)*T<<3)+Offset];
      P[0]=P[1]=P[2]=P[3]=VDP->XPal[K>>4];
      P[4]=P[5]=P[6]=P[7]=VDP->XPal[K&0x0F];
      P+=8;T++;
    }

    /* Refresh sprites */
    RefreshSprites(VDP,Y);
  }

  /* Refresh screen border */
  RefreshBorder(VDP,Y);
}
