/**************************************************************************
 *
 * Copyright (C) 1999 Mats Eirik Hansen (mats.hansen@triumph.no)
 *
 * $Id: video.c,v 1.1 1999/04/20 18:52:45 meh Exp meh $
 *
 * $Log: video.c,v $
 * Revision 1.1  1999/04/20 18:52:45  meh
 * Initial revision
 *
 *************************************************************************/

#include <stdio.h>

#include <clib/alib_protos.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <graphics/rastport.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <libraries/iffparse.h>
#include <libraries/gadtools.h>
#include <libraries/asl.h>
#include <cybergraphx/cybergraphics.h>
#include <cgxhooks.h>

#include <inline/exec.h>
#include <inline/dos.h>
#include <inline/graphics.h>
#include <inline/cybergraphics.h>
#include <inline/intuition.h>
#include <inline/gadtools.h>
#include <inline/asl.h>
#include <inline/utility.h>

#define TIMER_BASE_NAME video->TimerBase
#include <inline/timer.h>

#ifdef POWERUP
#include <exec/execbase.h>
#include <powerup/ppclib/memory.h>
#include <inline/ppc.h>
#endif

#include <macros.h>
#include "video.h"

#define ABS(a) ((a>=0)?(a):-(a))

struct BackFillMsg
{
  struct Layer    *Layer;
  struct Rectangle  Bounds;
  LONG        OffsetX;
  LONG        OffsetY;
};

struct BMHD
{
  UWORD Width;
  UWORD Height;
  WORD  Left;
  WORD  Top;
  UBYTE Planes;
  UBYTE Masking;
  UBYTE Compression;
  UBYTE pad;
  UWORD TransparentColor;
  UBYTE XAspect;
  UBYTE YAspect;
  WORD  PageWidth;
  WORD  PageHeight;
};

extern struct ExecBase  *SysBase;
extern struct Library   *DOSBase;
extern struct GfxBase   *GfxBase;
extern struct Library   *CyberGfxBase;
extern struct IntuitionBase *IntuitionBase;
extern struct Library   *AslBase;
extern struct Library   *GadToolsBase;
extern struct Library   *UtilityBase;

#ifdef POWERUP
extern struct Library   *PPCLibBase;
#endif

verror_t VError;

void ASM RemapPixels(UBYTE * REG(a0), UBYTE * REG(a1), UBYTE *REG(a2), LONG REG(d0), LONG REG(d1), LONG REG(d2), LONG REG(d3));
void ASM c2p(UBYTE * REG(a0), struct BitMap * REG(a1), WORD REG(d0), WORD REG(d1), WORD REG(d2), WORD REG(d3), LONG REG(d4));

#ifndef POWERUP
static inline APTR memAlloc(ULONG size)
{
  return(AllocVec(size, MEMF_PUBLIC|MEMF_CLEAR));
}

static inline void memFree(APTR mem)
{
  FreeVec(mem);
}
#else
static inline APTR memAlloc(ULONG size)
{
  return(PPCAllocVec(size, MEMF_PUBLIC|MEMF_CLEAR));
}

static inline void memFree(APTR mem)
{
  PPCFreeVec(mem);
}
#endif

ULONG ASM VBackFill(struct Hook *hook REG(a0), struct RastPort *rp REG(a2), struct BackFillMsg *bfm REG(a1))
{
  struct Layer  *l;
  LONG          old_pen, new_pen;

  l         = rp->Layer;
  rp->Layer = NULL;
  old_pen   = GetAPen(rp);
  new_pen   = ((struct Video *) hook->h_Data)->BackFillPen;

  if(new_pen != -1)
    SetAPen(rp, new_pen);
  else
    SetAPen(rp, 0);

  RectFill(rp, bfm->Bounds.MinX, bfm->Bounds.MinY, bfm->Bounds.MaxX, bfm->Bounds.MaxY);

  SetAPen(rp, old_pen);

  rp->Layer = l;

  return(0);
}

ULONG VGetBestMode(LONG width, LONG height, LONG depth)
{
  struct List          *mode_list;
  struct CyberModeNode *mode_node;
  ULONG                mode_id;
  ULONG                fallback_id;
  LONG                 mode_error;
  LONG                 fallback_error;
  LONG                 error;
  
  mode_id = INVALID_ID;

  if(CyberGfxBase)
  {
    mode_list = AllocCModeListTagList(NULL);
    
    if(mode_list)
    {
      mode_node = (struct CyberModeNode *) mode_list->lh_Head;
      
      mode_error     = 0xfffffff;
      fallback_id    = INVALID_ID;
      fallback_error = 0xfffffff;
      
      while(mode_node->Node.ln_Succ)
      {
        if(((depth <= 8) && (mode_node->Depth == 8))
        || ((depth > 8) && (mode_node->Depth == depth)))
        {
          error = ABS(mode_node->Width - width) + ABS(mode_node->Height - height);

          if((mode_node->Width < width) || (mode_node->Height < height))
          {
            if(error < fallback_error)
            {
              fallback_id   = mode_node->DisplayID;
              fallback_error  = error;
            }
          }
          else
          {
            if(error < mode_error)
            {
              mode_id   = mode_node->DisplayID;
              mode_error  = error;
            }
          }
        }
      
        mode_node = (struct CyberModeNode *) mode_node->Node.ln_Succ;
      }
      
      if(mode_id == INVALID_ID)
        mode_id = fallback_id;

      FreeCModeList(mode_list);
    }
  }

  if((mode_id == INVALID_ID) && (depth <= 8))
    mode_id = BestModeID(BIDTAG_Depth,         depth,
                         BIDTAG_NominalWidth,  width,
                         BIDTAG_NominalHeight, height,
                         TAG_END);
  
  return(mode_id);
}

struct Video *AllocVideo(Tag tags,...)
{
  struct Video               *video;
  struct TagItem             *tag, *taglist;
  struct ScreenModeRequester *screen_req;
  struct Rectangle           rect;
  struct Window              *win;

  STRPTR title;
  LONG   i, w, h, depth, rb, gb, bb;
  ULONG  max_colors;
  ULONG  mode_id;
  BOOL   use_screen, use_screen_req, no_refresh;

  if((video = AllocVec(sizeof(struct Video), MEMF_PUBLIC|MEMF_CLEAR)))
  {
    /* Setup defaults. */

    VError = None;

    mode_id        = INVALID_ID;
    depth          = 0;
    win            = NULL;
    title          = NULL;
    use_screen     = FALSE;
    use_screen_req = FALSE;
    no_refresh     = FALSE;
    max_colors     = 256;

    video->Buffers = 1;

    video->VectorPen   = -1;
    video->BackFillPen = -1;

    video->BackFillHook.h_Entry = (HOOKFUNC) VBackFill;
    video->BackFillHook.h_Data  = (APTR) video;

    video->MaxFrameSkip = 4;

    taglist = (struct TagItem *) &tags;

    while((tag = NextTagItem(&taglist)))
    {
      switch(tag->ti_Tag)
      {
        case VA_UseScreen:
          use_screen = tag->ti_Data;
          break;

        case VA_UseScreenReq:
          use_screen_req = tag->ti_Data;
          break;

        case VA_Width:
          video->Width = tag->ti_Data;
          break;

        case VA_Height:
          video->Height = tag->ti_Data;
          break;

        case VA_Depth:
          depth = tag->ti_Data;
          break;

        case VA_ModeID:
          mode_id = tag->ti_Data;
          break;

        case VA_Buffers:
          video->Buffers  = tag->ti_Data;

          if(video->Buffers > 3)
            video->Buffers  = 3;
          else if(!video->Buffers)
            video->Buffers  = 1;
          break;

        case VA_LessFlicker:
          video->LessFlicker  = tag->ti_Data;
          break;

        case VA_Title:
          title = (STRPTR) tag->ti_Data;
          break;

        case VA_Menu:
          video->Menu = (struct Menu *) tag->ti_Data;
          break;

        case VA_NoRefresh:
          no_refresh  = tag->ti_Data;
          break;

        case VA_FPS:
          video->FPS = tag->ti_Data;
          break;

        case VA_MaxColors:
          max_colors = tag->ti_Data;
          break;

        case VA_AutoFrameSkip:
          video->AutoFrameSkip = tag->ti_Data;
          break;

        case VA_MaxFrameSkip:
          video->MaxFrameSkip = tag->ti_Data;
          break;
      }
    }

    if(use_screen || use_screen_req)
    {
      if(ModeNotAvailable(mode_id))
      {
        mode_id = INVALID_ID;

        if(depth == 0)
          for(; (depth < 32) && (1 << depth < max_colors); depth++);

        if(!use_screen_req)
          mode_id = VGetBestMode(video->Width, video->Height, depth);

        if(mode_id == INVALID_ID)
        {
          screen_req = (struct ScreenModeRequester *) AllocAslRequest(ASL_ScreenModeRequest, NULL);

          if(screen_req)
          {
            if(AslRequestTags(screen_req, ASLSM_MinWidth,            video->Width,
                                          ASLSM_MinHeight,           video->Height,
                                          ASLSM_InitialDisplayDepth, depth,
                                          ASLSM_DoDepth,             TRUE,
                                          TAG_END))

            mode_id = screen_req->sm_DisplayID;
            depth   = screen_req->sm_DisplayDepth;

            FreeAslRequest(screen_req);
          }
        }

      }

      if(mode_id != INVALID_ID)
      {
        if(QueryOverscan(mode_id, &rect, OSCAN_TEXT))
        {
          if(CyberGfxBase)
            video->CyberMode = IsCyberModeID(mode_id);
          else
            video->CyberMode = FALSE;

          if(video->CyberMode && (depth < 8))
            depth = 8;

          if(GfxBase->LibNode.lib_Version >= 40)
            video->PixelMode = WriteChunkyPixels;
          else
            video->PixelMode = WritePixelArray;

          if(!video->CyberMode && (depth <= 8))
            video->PixelMode = CustomC2P;

          if(video->Width < rect.MaxX - rect.MinX + 1)
            w = rect.MaxX - rect.MinX + 1;
          else
            w = video->Width;

          if(video->Height < rect.MaxY - rect.MinY + 1)
            h = rect.MaxY - rect.MinY + 1;
          else
            h = video->Height;

          if(video->PixelMode == CustomC2P)
            w = ((w + 31) >> 5) << 5;

          if((1 << depth) < max_colors)
          {
            bb = depth / 3;
            rb = (depth - bb) / 2;
            gb = depth - rb - bb;

            video->Palette[0] = (1 << depth) << 16;

            for(i = 0; i < (1 << depth); i++)
            {
              video->Palette[3*i+1] = ((i / (1 << (gb + bb))) % (1 << rb)) << (32 - rb);
              video->Palette[3*i+2] = ((i / (1 << bb)) % (1 << gb)) << (32 - gb);
              video->Palette[3*i+3] = (i % (1 << bb)) << (32 - bb);
            }
            
            video->Palette[3*i+1] = 0;
          }

          video->Screen = OpenScreenTags(NULL, SA_DisplayID, mode_id,
                                               SA_Width,     w,
                                               SA_Height,    (video->CyberMode)
                                                             ? h * video->Buffers
                                                             : h,
                                               SA_Depth,     depth,
                                               SA_Colors32,  ((depth <= 8) && ((1 << depth) < max_colors))
                                                             ? (ULONG) video->Palette
                                                             : NULL,
                                               SA_ShowTitle, FALSE,
                                               SA_Overscan,  OSCAN_TEXT,
                                               SA_Type,      CUSTOMSCREEN,
                                               SA_Title,     (ULONG) title,
                                               TAG_END);

          if(video->Screen)
          {
            if(!video->CyberMode && (video->Buffers > 1))
            {
              video->ScreenBuffers[0]   = AllocScreenBuffer(video->Screen, NULL, SB_SCREEN_BITMAP);
              if(video->ScreenBuffers[0])
              {
                video->ScreenBuffers[1]   = AllocScreenBuffer(video->Screen, NULL, 0);
                if(video->ScreenBuffers[1])
                {
                  InitRastPort(&video->ScreenBufferRP);
                  video->ScreenBufferRP.BitMap  = video->ScreenBuffers[1]->sb_BitMap;
                  video->RastPort         = &video->ScreenBufferRP;

                  if(video->Buffers > 2)
                  {
                    video->ScreenBuffers[2]   = AllocScreenBuffer(video->Screen, NULL, 0);
                      
                    if(!video->ScreenBuffers[2])
                      video->Buffers = 2; /* fall back to double buffering. */
                  }

                  if(video->Buffers == 2)
                  {
                    video->ScreenBuffers[0]->sb_DBufInfo->dbi_UserData1                = (APTR) 1;
                    video->ScreenBuffers[0]->sb_DBufInfo->dbi_SafeMessage.mn_ReplyPort = &video->ScreenBufferPort;
                    video->ScreenBuffers[1]->sb_DBufInfo->dbi_UserData1                = (APTR) 0;
                    video->ScreenBuffers[1]->sb_DBufInfo->dbi_SafeMessage.mn_ReplyPort = &video->ScreenBufferPort;
                  }
                }
                else
                  video->Buffers  = 1;
              }
              else
                video->Buffers  = 1;
            }

            win = OpenWindowTags(NULL, WA_Width,         video->Screen->Width,
                                       WA_Height,        video->Screen->Height,
                                       WA_Left,          0,
                                       WA_Top,           0,
                                       WA_NoCareRefresh, no_refresh,
                                       WA_SimpleRefresh, TRUE,
                                       WA_Activate,      TRUE,
                                       WA_Borderless,    TRUE,
                                       WA_NewLookMenus,  TRUE,
                                       WA_AutoAdjust,    FALSE,
                                       WA_BackFill,      (ULONG) &video->BackFillHook,
                                       WA_CustomScreen,  (ULONG) video->Screen,
                                       WA_IDCMP,         video->Menu
                                                         ? IDCMP_REFRESHWINDOW|IDCMP_MENUPICK
                                                         : IDCMP_REFRESHWINDOW,
                                       TAG_END);
            
            if(win)
            {
              video->ScreenBufferPort.mp_Node.ln_Type = NT_MSGPORT;
              video->ScreenBufferPort.mp_Flags        = PA_SIGNAL;
              video->ScreenBufferPort.mp_SigBit       = win->UserPort->mp_SigBit;           
              video->ScreenBufferPort.mp_SigTask      = win->UserPort->mp_SigTask;            

              NewList(&video->ScreenBufferPort.mp_MsgList);

              video->Pointer  = AllocVec(8, MEMF_CHIP|MEMF_CLEAR);
              
              if(video->Pointer)
                SetPointer(win, video->Pointer, 0, 0, 0, 0);
            }
            else
              VError = OpenWindowFailed;
          }
          else
            VError = OpenScreenFailed;
        }
      }
    }
    else
    {
      win = OpenWindowTags(NULL, WA_InnerWidth,    video->Width,
                                 WA_InnerHeight,   video->Height,
                                 WA_DragBar,       TRUE,
                                 WA_CloseGadget,   TRUE,
                                 WA_DepthGadget,   TRUE,
                                 WA_NoCareRefresh, no_refresh,
                                 WA_SimpleRefresh, TRUE,
                                 WA_Activate,      TRUE,
                                 WA_NewLookMenus,  TRUE,
                                 WA_AutoAdjust,    FALSE,
                                 WA_BackFill,      (ULONG) &video->BackFillHook,
                                 WA_Title,         (ULONG) title,
                                 WA_IDCMP,         video->Menu
                                                   ? IDCMP_REFRESHWINDOW|IDCMP_MENUPICK|IDCMP_CLOSEWINDOW
                                                   : IDCMP_REFRESHWINDOW|IDCMP_CLOSEWINDOW,
                                 TAG_END);

      if(win)
      {
        if(GfxBase->LibNode.lib_Version >= 40)
          video->PixelMode = WriteChunkyPixels;
        else
          video->PixelMode = WritePixelArray;

        if(CyberGfxBase && win)
          video->CyberMode = GetCyberMapAttr(win->RPort->BitMap, CYBRMATTR_ISCYBERGFX);
        else
          video->CyberMode = FALSE;
      }
      else
        VError = OpenWindowFailed;
    }

    if((video->Window = win))
    {
      w = video->Width;
      h = video->Height;

      if((video->Buffers > 1) && video->Screen)
        video->CurrentScreenBuffer    = 1;
      else
        video->CurrentScreenBuffer    = 0;

      video->Left   = (win->Width  + win->BorderLeft - win->BorderRight - w) >> 1;

      if((video->Buffers > 1) && video->Screen && video->CyberMode)
      {
        video->Top     = ((win->Height / video->Buffers) + win->BorderTop - win->BorderBottom - h) >> 1;
        video->OffsetY = (win->Height / video->Buffers) * video->CurrentScreenBuffer;
      }
      else
      {
        video->Top     = (win->Height  + win->BorderTop - win->BorderBottom - h) >> 1;
        video->OffsetY = 0;
      }

      video->FrameBox[0].MinX = video->Left;
      video->FrameBox[0].MinY = video->Top;
      video->FrameBox[0].MaxX = video->Left + video->Width - 1;
      video->FrameBox[0].MaxY = video->Top + video->Height - 1;

      video->FrameBox[1] = video->FrameBox[0];
      video->FrameBox[2] = video->FrameBox[0];

      if(video->CyberMode)
      {
        video->PixelFormat = GetCyberMapAttr(win->RPort->BitMap, CYBRMATTR_PIXFMT);

        if(video->PixelFormat && (max_colors <= 256))
        {
          video->CGXHook = AllocCLUT8RemapHook(win->WScreen, NULL);

          if(video->CGXHook)
          {
            video->PixelMode = CGXHook;
            video->VectorPen = ObtainPen(win->WScreen->ViewPort.ColorMap, -1, 0, 0, 0, PEN_EXCLUSIVE);

            CustomRemapCLUT8RemapHook(video->CGXHook, video->PackedPalette);
          }
        }
        else
          video->PixelMode = BltBitMapRastPort;
      }

      if(video->Screen && ((1 << video->Screen->RastPort.BitMap->Depth) < max_colors))
      {
        video->RemapBuffer = AllocVec((((video->Width + 31) >> 5) << 5) * video->Height,
                                      MEMF_PUBLIC|MEMF_CLEAR);
      }

      if(video->PixelFormat)
        video->BackFillPen = ObtainBestPenA(video->Window->WScreen->ViewPort.ColorMap, 0, 0, 0, NULL);
      else
        video->BackFillPen = -1;

      if(video->RastPort)
      {
        if(video->BackFillPen != -1)
          SetAPen(video->RastPort, video->BackFillPen);
        else
          SetAPen(video->RastPort, 0);

        RectFill(video->RastPort,
                 win->BorderLeft,                    win->BorderTop,
                 win->Width - win->BorderRight - 1,  win->Height - win->BorderBottom - 1);
      }
      else
        video->RastPort = win->RPort;

      EraseRect(win->RPort,
                win->BorderLeft,                   win->BorderTop,
                win->Width - win->BorderRight - 1, win->Height - win->BorderBottom - 1);

      if(GadToolsBase && video->Menu)
      {
        video->VisualInfo = GetVisualInfoA(win->WScreen, NULL);

        if(video->VisualInfo)
        {
          if(LayoutMenus(video->Menu, video->VisualInfo, GTMN_NewLookMenus, TRUE, TAG_END))
            SetMenuStrip(win, video->Menu);
        }
      }

      video->TimerMsgPort.mp_Node.ln_Type = NT_MSGPORT;
      video->TimerMsgPort.mp_Flags        = PA_SIGNAL;
      video->TimerMsgPort.mp_SigBit       = video->Window->UserPort->mp_SigBit;
      video->TimerMsgPort.mp_SigTask      = video->Window->UserPort->mp_SigTask;
      NewList(&video->TimerMsgPort.mp_MsgList);
      
      video->TimerRequest = CreateIORequest(&video->TimerMsgPort, sizeof(struct timerequest));
        
      if(video->TimerRequest)
      {
        if(OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *) video->TimerRequest, 0))
        {
          DeleteIORequest((struct IORequest *) video->TimerRequest);
          video->TimerRequest = NULL;
        }
        else
          video->TimerBase = (struct Library *) video->TimerRequest->tr_node.io_Device;
      }

      if(video->PixelMode == WritePixelArray)
      {
        CopyMem(win->RPort, &video->TempRastPort, sizeof(struct RastPort));

        video->TempRastPort.Layer = NULL;

        video->TempRastPort.BitMap = AllocBitMap((((video->Width + 15) >> 4) << 4), 1, 8, 0, NULL);

        if(video->TempRastPort.BitMap)
          return(video);
          
        VError = OutOfMemory;
      }
      else
        return(video);
    }
  }
  else
    VError = OutOfMemory;

  FreeVideo(video);

  return(NULL);
}

void FreeVideo(struct Video *video)
{
  if(video)
  {
    if(video->Window)
    {
      if(video->VisualInfo && video->Menu)
        ClearMenuStrip(video->Window);

      if(video->VectorPen != -1)
        ReleasePen(video->Window->WScreen->ViewPort.ColorMap, video->VectorPen);

      if(video->BackFillPen != -1)
        ReleasePen(video->Window->WScreen->ViewPort.ColorMap, video->BackFillPen);

      CloseWindow(video->Window);

      if(video->PixelMode == WritePixelArray)
      {
        if(video->TempRastPort.BitMap)
          FreeBitMap(video->TempRastPort.BitMap);
      }

      if(video->CGXHook)
        FreeCGXHook(video->CGXHook);

      if(video->RemapBuffer)
        FreeVec(video->RemapBuffer);

      if(video->VectorBitMap)
        FreeBitMap(video->VectorBitMap);

      if(video->VisualInfo)
        FreeVisualInfo(video->VisualInfo);
    }

    if(video->Screen)
    {
      if(video->BitMapLock)
        UnLockBitMap(video->BitMapLock);
    
      if(video->ScreenBufferChange && (video->Buffers == 2))
      {
        WaitPort(&video->ScreenBufferPort);
        GetMsg(&video->ScreenBufferPort);
      }

      WaitBlit();

      if(video->ScreenBuffers[0])
        FreeScreenBuffer(video->Screen, video->ScreenBuffers[0]);

      if(video->ScreenBuffers[1])
        FreeScreenBuffer(video->Screen, video->ScreenBuffers[1]);

      if(video->ScreenBuffers[2])
        FreeScreenBuffer(video->Screen, video->ScreenBuffers[2]);

      if(video->Pointer)
        FreeVec(video->Pointer);

      CloseScreen(video->Screen);
    }
    
    if(video->TimerRequest)
    {
      if(!CheckIO((struct IORequest *) video->TimerRequest))
      {
        AbortIO((struct IORequest *) video->TimerRequest);
        WaitIO((struct IORequest *) video->TimerRequest);
      }

      CloseDevice((struct IORequest *) video->TimerRequest);
      DeleteIORequest((struct IORequest *) video->TimerRequest);
    }

    FreeVec(video);
  }
}

void VBeginFrame(struct Video *video, LONG bgpen, UBYTE *palette)
{
  struct timeval time;

  ULONG *pal;
  ULONG *cmap;
  ULONG t;
  LONG  i, j, k, l, depth, rb, gb, bb;
  LONG  fps;

  if(video->TimerBase)
  {
    video->Frames[video->CurrentFrame].WaitTime.tv_secs  = 0;
    video->Frames[video->CurrentFrame].WaitTime.tv_micro = 0;
  
    if(video->FPS && video->LimitSpeed && video->TimerRequest)
    {
      if(!CheckIO((struct IORequest *) video->TimerRequest))
      {
        GetSysTime(&time);
        WaitIO((struct IORequest *) video->TimerRequest);
        GetSysTime(&video->Frames[video->CurrentFrame].WaitTime);
        SubTime(&video->Frames[video->CurrentFrame].WaitTime, &time);
        AddTime(&video->TotalWaitTime, &video->Frames[video->CurrentFrame].WaitTime);
      }
    
      video->TimerRequest->tr_node.io_Command = TR_ADDREQUEST;
      video->TimerRequest->tr_time.tv_secs    = 0;
      video->TimerRequest->tr_time.tv_micro   = (1 + video->FrameSkip) * 1000000 / video->FPS;
      SendIO((struct IORequest *) video->TimerRequest);
    }
  }

  video->BackgroundPen  = bgpen;

  if((video->Buffers > 1) && video->Screen)
  {
    if(video->CyberMode)
      video->OffsetY  = (video->Window->Height / video->Buffers) * video->CurrentScreenBuffer;
                        
    else if(video->ScreenBufferChange)
    {
      if(video->Buffers == 2)
      {
        WaitPort(&video->ScreenBufferPort);
        video->CurrentScreenBuffer = *((ULONG *) (GetMsg(&video->ScreenBufferPort) + 1));
      }
      else
        video->CurrentScreenBuffer = (video->CurrentScreenBuffer + 1) % video->Buffers;
      
      video->ScreenBufferRP.BitMap  = video->ScreenBuffers[video->CurrentScreenBuffer]->sb_BitMap;
      video->ScreenBufferChange   = FALSE;
    }
  }

  cmap = video->CMAP;

  if(video->Screen && (!video->PixelFormat))
  {
    depth = video->Screen->RastPort.BitMap->Depth;

    if(!video->RemapBuffer)
    {
      pal = video->Palette;

      for(i = 0, k = 0; i < (1 << depth); i++)
      {
        if(palette[4*i])
        {
          palette[4*i]  = 0;

          j = i;
          l = k;
          k++;
          do
          {
            cmap[i] = (palette[4*i+1] << 16) | (palette[4*i+2] << 8) | palette[4*i+3];

            pal[k++]  = palette[4*i+1] << 24;
            pal[k++]  = palette[4*i+2] << 24;
            pal[k++]  = palette[4*i+3] << 24;
            i++;
          } while((i < 256) && (palette[4*i]));

          pal[l]  = ((i - j) << 16) | j;
        }
      }

      if(k)
      {
        pal[k]  = 0;

        LoadRGB32(&video->Screen->ViewPort, pal);
      }
    }
    else
    {
      pal = video->PackedPalette;

      for(i = 0; i < 256; i++)
      {
        if(palette[4*i])
        {
          palette[4*i]  = 0;

          cmap[i] = (palette[4*i+1] << 16) | (palette[4*i+2] << 8) | palette[4*i+3];

          bb = depth / 3;
          rb = (depth - bb) / 2;
          gb = depth - rb - bb;
          
          ((UBYTE *) pal)[i]  = ((palette[4*i+1] >> (8 - rb)) << (gb + bb))
                    | ((palette[4*i+2] >> (8 - gb)) << (bb))
                    | (palette[4*i+3] >> (8 - bb));
        }
      }
    }
  }
  else if(video->CGXHook)
  {
    pal = video->PackedPalette;

    for(i = 0; i < 256; i++)
    {
      if(palette[4*i])
      {
        palette[4*i]  = 0;

        cmap[i] = (palette[4*i+1] << 16) | (palette[4*i+2] << 8) | palette[4*i+3];

        switch(video->PixelFormat)
        {
          case PIXFMT_RGB15:
            ((UWORD *) pal)[i] = ((palette[4*i+1]&0xf8)<<7)|((palette[4*i+2]&0xf8)<<2)|(palette[4*i+3]>>3);
            break;

          case PIXFMT_BGR15:
            ((UWORD *) pal)[i] = ((palette[4*i+3]&0xf8)<<7)|((palette[4*i+2]&0xf8)<<2)|(palette[4*i+1]>>3);
            break;

          case PIXFMT_RGB15PC:
            ((UWORD *) pal)[i] = ((palette[4*i+1]&0xf8)>>1)|(palette[4*i+2]>>6)|((palette[4*i+2]&0x38)<<10)|((palette[4*i+3]&0xf8)<<5);
            break;

          case PIXFMT_BGR15PC:
            ((UWORD *) pal)[i] = ((palette[4*i+3]&0xf8)>>1)|(palette[4*i+2]>>6)|((palette[4*i+2]&0x38)<<10)|((palette[4*i+1]&0xf8)<<5);
            break;

          case PIXFMT_RGB16:
            ((UWORD *) pal)[i] = ((palette[4*i+1]&0xf8)<<8)|((palette[4*i+2]&0xfc)<<3)|(palette[4*i+3]>>3);
            break;

          case PIXFMT_BGR16:
            ((UWORD *) pal)[i] = ((palette[4*i+3]&0xf8)<<8)|((palette[4*i+2]&0xfc)<<3)|(palette[4*i+1]>>3);
            break;

          case PIXFMT_RGB16PC:
            ((UWORD *) pal)[i] = (palette[4*i+1]&0xf8)|(palette[4*i+2]>>5)|((palette[4*i+2]&0x1c)<<11)|((palette[4*i+3]&0xf8)<<5);
            break;

          case PIXFMT_BGR16PC:
            ((UWORD *) pal)[i] = (palette[4*i+3]&0xf8)|(palette[4*i+2]>>5)|((palette[4*i+2]&0x1c)<<11)|((palette[4*i+1]&0xf8)<<5);
            break;

          case PIXFMT_BGR24:
            ((ULONG *) pal)[i]  = (palette[4*i+3]<<16)|(palette[4*i+2]<<8)|palette[4*i+1];
            break;

          case PIXFMT_RGB24:
          case PIXFMT_ARGB32:
            ((ULONG *) pal)[i]  = (palette[4*i+1]<<16)|(palette[4*i+2]<<8)|palette[4*i+3];
            break;

          case PIXFMT_BGRA32:
            ((ULONG *) pal)[i]  = (palette[4*i+3]<<24)|(palette[4*i+2]<<16)|(palette[4*i+1]<<8);
            break;

          case PIXFMT_RGBA32:
            ((ULONG *) pal)[i]  = (palette[4*i+1]<<24)|(palette[4*i+2]<<16)|(palette[4*i+3]<<8);
            break;
        }
        
        video->Palette[3*i+1] = palette[4*i+1];
        video->Palette[3*i+2] = palette[4*i+2];
        video->Palette[3*i+3] = palette[4*i+3];
      }
    }
  }

  if(video->TimerBase)
  {
    /* Calculate fps. */

    GetSysTime(&time);
    video->Frames[video->CurrentFrame].Time   = time;
    video->Frames[video->CurrentFrame].Skipped  = video->FrameSkip + 1;
    video->TotalFrames += video->Frames[video->CurrentFrame].Skipped;
    video->CurrentFrame = (video->CurrentFrame + 1) % V_FrameHistory;
    video->TotalFrames -= video->Frames[video->CurrentFrame].Skipped;
    SubTime(&video->TotalWaitTime, &video->Frames[video->CurrentFrame].WaitTime);

    SubTime(&time, &video->Frames[video->CurrentFrame].Time);
    t = ((time.tv_secs * 1000) + (time.tv_micro / 1000));
    if(t)
      video->CurrentFPS = (video->TotalFrames * 1000) / t;
    else
      video->CurrentFPS = 0;

    /* Auto frameskip. */

    if(video->AutoFrameSkip && video->FPS)
    {
      /* Calculate what the fps would be without speed limiting. */
    
      SubTime(&time, &video->TotalWaitTime);
      t = ((time.tv_secs * 1000) + (time.tv_micro / 1000));
      if(t)
        fps = (video->TotalFrames * 1000) / t;
      else
        fps = 0;

      if((fps < (video->FPS - 5)) && (video->FrameSkip < video->MaxFrameSkip))
        video->FrameSkip++;
      else if((fps > (video->FPS + 5)) && (video->FrameSkip > 0))
        video->FrameSkip--;
    }
  }
}

void VEndFrame(struct Video *video)
{
  if((video->Buffers > 1) && video->Screen)
  {
    if(video->CyberMode)
    {
      video->Screen->ViewPort.RasInfo->RyOffset = (video->Window->Height / video->Buffers) * video->CurrentScreenBuffer;
      ScrollVPort(&video->Screen->ViewPort);
      video->CurrentScreenBuffer  = (video->CurrentScreenBuffer + 1) % video->Buffers;
    }
    else
    {
      video->ScreenBufferChange = ChangeScreenBuffer( video->Screen, 
                                video->ScreenBuffers[video->CurrentScreenBuffer]);
    }
  }
}

struct VPixelArray *VAllocPixelArray(struct Video *video, LONG width, LONG height,
                                     LONG dirty, ULONG *pixel_formats)
{
  struct VPixelArray *pa;

  ULONG pixel_format;
  ULONG depth;
  LONG  size;
  LONG  w, i;

  if(width && height)
  {
    switch(video->PixelMode)
    {
      case CustomC2P:
        w = ((width + 31) >> 5) << 5;
        break;

      case CGXHook:
      case BltBitMapRastPort:
      case WriteChunkyPixels:
        w = ((width + 3) >> 2) << 2;
        break;

      default:
        if(video->RemapBuffer)
          w = ((width + 3) >> 2) << 2;
        else
          w = ((width + 15) >> 4) << 4;
    }
  }
  else
    w = 0;

  size = sizeof(struct VPixelArray);

  if(video->PixelMode != BltBitMapRastPort)
    size += (w * height);
  
  if(dirty)
    size += height;

  pa  = memAlloc(size);

  if(pa)
  {
    pa->Video  = video;
    pa->Size   = size;
    pa->Width  = width;
    pa->Height = height;
    pa->StartX = 0;
    pa->StartY = 0;
    pa->EndX   = width - 1;
    pa->EndY   = height - 1;

    if(video->PixelMode == BltBitMapRastPort)
    {
      if(pixel_formats)
      {
        pixel_format = GetCyberMapAttr(video->Window->RPort->BitMap, CYBRMATTR_PIXFMT);

        /* Try to find the pixel format of the video window in the list
         * of possible pixel formats for this pixel array. */
      
        for(i = 0; pixel_formats[i] != ~0; i++)
        {
          if(pixel_formats[i] == pixel_format)
            break;
        }
        
        if(pixel_formats[i] == ~0)
        {
          /* Didn't find the exact pixel format so try to find one that will map
           * to the pixel format of the window. */
        
          if((pixel_format == PIXFMT_LUT8) || (pixel_formats[0] == ~0))
          {
            /* If the possible pixel formats list is empty or the pixel format
             * of the video window is palette based then fail. Any true color
             * pixel format can't be mapped to a palette based one. */
          
            memFree(pa);
            
            return(NULL);
          }
          
          pixel_format = pixel_formats[0];
        }
      }
      else
      {
        /* An empty possible pixel formats list defaults to only
         * a palette based pixel format. */
      
        pixel_format = PIXFMT_LUT8;
      }
    
      switch(pixel_format)
      {
        case PIXFMT_RGB15:
        case PIXFMT_BGR15:
        case PIXFMT_RGB15PC:
        case PIXFMT_BGR15PC:
          depth = 15;
          break;


        case PIXFMT_RGB16:
        case PIXFMT_BGR16:
        case PIXFMT_RGB16PC:
        case PIXFMT_BGR16PC:
          depth = 16;
          break;

        case PIXFMT_RGB24:
        case PIXFMT_BGR24:
          depth = 24;
          break;

        case PIXFMT_ARGB32:
        case PIXFMT_BGRA32:
        case PIXFMT_RGBA32:
          depth = 32;
          break;

        case PIXFMT_LUT8:
        default:
          depth = 8;
          break;
      }
      
      pa->BitMap = AllocBitMap(width, height, depth,
                               BMF_MINPLANES|BMF_CLEAR|BMF_SPECIALFMT|SHIFT_PIXFMT(pixel_format),
                               video->Window->RPort->BitMap);

      if(!pa->BitMap)
      {
        memFree(pa);
        
        return(NULL);
      }

      pa->BytesPerRow   = GetCyberMapAttr(pa->BitMap, CYBRMATTR_XMOD);
      pa->BytesPerPixel = GetCyberMapAttr(pa->BitMap, CYBRMATTR_BPPIX);
      pa->PixelFormat   = GetCyberMapAttr(pa->BitMap, CYBRMATTR_PIXFMT);
      pa->Pixels        = (UBYTE *) GetCyberMapAttr(pa->BitMap, CYBRMATTR_DISPADR);
      pa->PixelsSize    = pa->BytesPerRow * height;

      if(dirty)
        pa->DirtyLines = (UBYTE *) &pa[1];
    }
    else
    {
      pa->BitMap        = NULL;
      pa->BytesPerRow   = w;
      pa->BytesPerPixel = 1;
      pa->PixelFormat   = PIXFMT_LUT8;
      pa->Pixels        = (UBYTE *) &pa[1];
      pa->PixelsSize    = 0;

      if(dirty)
        pa->DirtyLines  = &pa->Pixels[w * height];
    }

    for(i = 0; i < 256; i++)
    {
      pa->Palette[i][0] = 1;
      pa->Palette[i][1] = 0;
      pa->Palette[i][2] = 0;
      pa->Palette[i][3] = 0;
    }

#ifdef POWERUP
    PPCCacheClearE(pa, pa->Size, CACRF_ClearD);
#endif
  }

  return(pa);
}

void VFreePixelArray(struct VPixelArray *pa)
{
  if(pa->BitMap)
    FreeBitMap(pa->BitMap);

  memFree(pa);
}

LONG _WritePixelArray(APTR a, UWORD b, UWORD c, UWORD d, struct RastPort *e, UWORD f, UWORD g, UWORD h, UWORD i, UBYTE j)
{
  LONG r;
  
  r = WritePixelArray(a,b,c,d,e,f,g,h,i,j);

  return(r);  
}

void VDrawPixelArray(struct VPixelArray *pa)
{
  struct Video  *video;

  UBYTE *pixels;
  UBYTE *remap_pixels;
  UBYTE *dirty_lines;
  ULONG bpr;
  ULONG remap_bpr;
  LONG  x, y, w, h;
  LONG  sy, iy;

  if(!pa->Width || !pa->Height)
    return;

  video = pa->Video;
  w   = pa->EndX - pa->StartX + 1;
  h   = pa->EndY - pa->StartY + 1;
  x   = video->Left + ((video->Width - w) >> 1);
  y   = video->Top + video->OffsetY + ((video->Height - h) >> 1);

  if(video->Screen)
    x &= 0xfffffffc;

  if(pa->DirtyLines)
  {
    dirty_lines = pa->DirtyLines + pa->StartY;

    switch(video->PixelMode)
    {
      case CustomC2P:
        x = x & 0xffffffe0;
        w = ((w + 31) >> 5) << 5;
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);

        pixels  = VGetPixelArrayAddr(pa);
        bpr   = pa->BytesPerRow;

        if(video->RemapBuffer)
        {
          remap_pixels  = video->RemapBuffer;
          remap_bpr   = ((video->Width + 31) >> 5) << 5;
          
          for(iy = 0; iy < h; iy++)
          {
            if(dirty_lines[iy])
            {
              dirty_lines[iy] = 0;

              sy = iy;
              iy++;
            
              while(dirty_lines[iy] && (iy < h))
                dirty_lines[iy++] = 0;

              RemapPixels(pixels + (bpr * sy), remap_pixels, (UBYTE *) video->PackedPalette,
                    w, remap_bpr, iy - sy, bpr);

              c2p(remap_pixels, video->RastPort->BitMap,
                  w,            iy - sy,
                  x,            y + sy,
                  bpr);
            }
          }
        }
        else
        {
          for(iy = 0; iy < h; iy++)
          {
            if(dirty_lines[iy])
            {
              dirty_lines[iy] = 0;

              sy = iy;
              iy++;
            
              while(dirty_lines[iy] && (iy < h))
                dirty_lines[iy++] = 0;

              c2p(pixels + (bpr * sy), video->RastPort->BitMap,
                  w,                   iy - sy,
                  x,                    y + sy,
                  bpr);
            }
          }
        }
        break;

      case BltBitMapRastPort:    
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        pixels = VGetPixelArrayAddr(pa);
        for(iy = 0; iy < h; iy++)
        {
          if(dirty_lines[iy])
          {
            dirty_lines[iy] = 0;

            sy = iy;
            iy++;
            
            while(dirty_lines[iy] && (iy < h))
              dirty_lines[iy++] = 0;
            
            BltBitMapRastPort(pa->BitMap, pa->StartX, pa->StartY + sy, video->RastPort,
                              x, y + sy, w, iy - sy, 0xc0);
          }
        }
        break;

      case WriteChunkyPixels:   
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        pixels = VGetPixelArrayAddr(pa);
        for(iy = 0; iy < h; iy++)
        {
          if(dirty_lines[iy])
          {
            dirty_lines[iy] = 0;

            sy = iy;
            iy++;
            
            while(dirty_lines[iy] && (iy < h))
              dirty_lines[iy] = 0;
            
            WriteChunkyPixels(  video->RastPort, x, y + sy, x + w - 1, y + iy - 1,
                      pixels + (pa->BytesPerRow * sy), pa->BytesPerRow);
          }
        }
        break;

      case CGXHook:
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        pixels = VGetPixelArrayAddr(pa);
        for(iy = 0; iy < h; iy++)
        {
          if(dirty_lines[iy])
          {
            dirty_lines[iy] = 0;

            sy = iy;
            iy++;
            
            while(dirty_lines[iy] && (iy < h))
              dirty_lines[iy] = 0;
            
            DoCLUT8RemapHook(video->CGXHook,
                             pixels + (pa->BytesPerRow * sy), video->RastPort,
                             0, 0, x, y + sy, w, iy - sy, pa->BytesPerRow);
          }
        }
        break;    
    }
  }
  else
  {
    switch(video->PixelMode)
    {
      case CustomC2P:
        x   = x & 0xffffffe0;
        w   = ((w + 31) >> 5) << 5;
        bpr   = pa->BytesPerRow;
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        pixels  = VGetPixelArrayAddr(pa);
        if(video->RemapBuffer)
        {
          bpr = ((video->Width + 31) >> 5) << 5;
          RemapPixels(pixels, video->RemapBuffer,  (UBYTE *) video->PackedPalette,
                w, bpr, h, pa->BytesPerRow);
          pixels = video->RemapBuffer;
        }

        c2p( pixels, video->RastPort->BitMap,
            w,    h,
            x,    y,
            bpr);
        break;

      case BltBitMapRastPort:    
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        BltBitMapRastPort(pa->BitMap, pa->StartX, pa->StartY, video->RastPort,
                          x, y, w, h, 0xc0);
        break;
      case WriteChunkyPixels:   
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        WriteChunkyPixels(  video->RastPort, x, y, x + w - 1, y + h - 1,
                  VGetPixelArrayAddr(pa), pa->BytesPerRow);
        break;
      case CGXHook:
        VSetFrameBox(video, x, y, x + w - 1, y + h - 1);
        DoCLUT8RemapHook( video->CGXHook,
                  VGetPixelArrayAddr(pa), video->RastPort,
                  0,            0,
                  x,            y,
                  w,            h,
                  pa->BytesPerRow);
        break;    
    }
  }
}

void VSetPixelFrame(struct VPixelArray *pa)
{
  struct Video *video;

  video = pa->Video;

  VBeginFrame(video, pa->BackgroundPen, &pa->Palette[0][0]);

  video->CurrentPixelArray  = pa;
  video->CurrentVectorArray = NULL;

  VDrawPixelArray(pa);

  VEndFrame(video);
}

struct VVectorArray *VAllocVectorArray( struct Video *video, LONG width, LONG height, LONG length)
{
  struct VVectorArray *va;

  va  = memAlloc(sizeof(struct VVectorArray) + length * sizeof(struct VVector));

  if(va)
  {
    va->Video   = video;
    va->Size    = sizeof(struct VVectorArray) + length * sizeof(struct VVector);
    va->Width   = width;
    va->Height    = height;
    va->MaxLength = length;
    va->Vectors   = (struct VVector *) &va[1];

#ifdef POWERUP
    PPCCacheClearE(va, va->Size, CACRF_ClearD);
#endif
  }

  return(va);
}

void VFreeVectorArray(struct VVectorArray *va)
{
  memFree(va);
}

void VDrawVectorArray(struct VVectorArray *va)
{
  struct Video  *video;
  struct RastPort *rp;
  LONG      i, t, x1, y1, x2, y2, dx, dy, w, h, left, top, width, height;

  video = va->Video;
  rp    = NULL;
  left  = video->Left;
  top   = video->Top + video->OffsetY;
  width = video->Width;
  height  = video->Height;
  w   = va->Width;
  h   = va->Height;

  VSetFrameBox(video, left, top, left + w - 1, top + h - 1);

  if((video->Buffers > 1) && !video->Screen)
  {
    if(!video->VectorBitMap)
    {
      video->VectorBitMap = AllocBitMap(  width,  height, 
                        GetBitMapAttr(video->Window->RPort->BitMap, BMA_DEPTH),
                        BMF_MINPLANES,  video->Window->RPort->BitMap);
      if(video->VectorBitMap)
      {
        InitRastPort(&video->VectorRastPort);
        video->VectorRastPort.Layer   = NULL;
        video->VectorRastPort.BitMap  = video->VectorBitMap;
      }
    }

    if(video->VectorBitMap)
    {
      rp   = &video->VectorRastPort;
      left = 0;
      top  = 0;
    }
  }
  else
    rp    = video->RastPort;

  if(rp)
  {
    if(video->LessFlicker && !(video->Buffers > 1) && !video->VectorBitMap)
    {
      video->VectorBitMap = AllocBitMap(width, height, 1, 0, 0);
      if(video->VectorBitMap)
      {
        InitRastPort(&video->VectorRastPort);
        video->VectorRastPort.Layer   = NULL;
        video->VectorRastPort.BitMap  = video->VectorBitMap;
      }
    }

    if(!video->LessFlicker || !video->VectorBitMap)
    {
      if(video->CGXHook)
      {
        FillPixelArray( rp,
                left,   top,
                width,    height,
                video->BackgroundColor);
      }
      else
      {
        SetAPen(rp, video->BackgroundPen);

        RectFill( rp,
              left,       top,
              left + width - 1, top + height - 1);
      }
    }
    else
    {
      SetAPen(&video->VectorRastPort, 1);
      RectFill(&video->VectorRastPort, 0, 0, width - 1, height - 1);
      SetAPen(&video->VectorRastPort, 0);
    }

    if(video->CGXHook)
      SetAPen(rp, video->VectorPen);

    x1  = video->VectorX;
    y1  = video->VectorY;

    for(i = 0; i < va->Length; i++)
    {
      if(va->Vectors[i].Pen >= 0)
      {
        x2  = va->Vectors[i].X;
        y2  = va->Vectors[i].Y;

        if(x1 > x2)
        {
          t = x2;
          x2  = x1;
          x1  = t;
          t = y2;
          y2  = y1;
          y1  = t;
        }

        if((x1 < w) && (x2 >= 0))
        {
          if(x1 != x2)
          {
            dy  = ((y2 - y1) << 16) / (x2 - x1);

            if(x1 < 0)
            {
              x1  = 0;
              y1  = y1 - ((x1 * dy) >> 16);
            }

            if(x2 >= w)
            {
              y2  = y2 - (((x2 - w - 1) * dy) >> 16);
              x2  = w - 1;
            }
          }

          if(y1 > y2)
          {
            t = x2;
            x2  = x1;
            x1  = t;
            t = y2;
            y2  = y1;
            y1  = t;
          }
          
          if((y1 < h) && (y2 >= 0))
          {
            if(y1 != y2)
            {
              dx  = ((x2 - x1) << 16) / (y2 - y1);

              if(y1 < 0)
              {
                x1  = x1 - ((y1 * dx) >> 16);
                y1  = 0;
              }

              if(y2 >= h)
              {
                x2  = x2 - (((y2 - h - 1) * dx) >> 16);
                y2  = h - 1;
              }
            }

            if(video->CGXHook)
            {
              SetRGB32( &video->Window->WScreen->ViewPort, video->VectorPen,
                    video->Palette[3*va->Vectors[i].Pen+1] << 24,
                    video->Palette[3*va->Vectors[i].Pen+2] << 24,
                    video->Palette[3*va->Vectors[i].Pen+3] << 24);
            }
            else
              SetAPen(rp, va->Vectors[i].Pen);

            Move(rp, left + x1, top + y1);
            Draw(rp, left + x2, top + y2);

            if(video->LessFlicker && video->VectorBitMap)
            {
              Move(&video->VectorRastPort, x1, y1);
              Draw(&video->VectorRastPort, x2, y2);
            }
          }
        }
      }

      x1 = va->Vectors[i].X;
      y1 = va->Vectors[i].Y;
    }
    
    video->VectorX  = x1;
    video->VectorY  = y1;

    if(video->VectorBitMap)
    {
      if(video->Buffers > 1)
        BltBitMapRastPort(  video->VectorBitMap,  0,        0,
                  video->RastPort,    video->Left,  video->Top + video->OffsetY,
                  width,          height,     0xc0);
      else
      {
        if(video->CGXHook)
        {
          SetRGB32( &video->Window->WScreen->ViewPort, video->VectorPen,
                video->Palette[3*va->BackgroundPen+1] << 24,
                video->Palette[3*va->BackgroundPen+2] << 24,
                video->Palette[3*va->BackgroundPen+3] << 24);
        }
        else
          SetAPen(rp, va->BackgroundPen);

        SetDrMd(rp, JAM1);

        BltTemplate(video->VectorBitMap->Planes[0],   0, 
              video->VectorBitMap->BytesPerRow, rp, 
              video->Left,            video->Top + video->OffsetY,
              width,                height);
      }
    }
  }
}

void VSetVectorFrame(struct VVectorArray *va)
{
  struct Video  *video;

  video = va->Video;

  VBeginFrame(video, va->BackgroundPen, &va->Palette[0][0]);

  video->CurrentPixelArray  = NULL;
  video->CurrentVectorArray = va;

  VDrawVectorArray(va);

  VEndFrame(video);
}

struct VDirectArray *VAllocDirectArray(struct Video *video, LONG width, LONG height)
{
  struct VDirectArray *da;
  LONG        i;

#ifdef POWERUP  
  da = PPCAllocVec(sizeof(struct VDirectArray), MEMF_PUBLIC|MEMF_CLEAR|MEMF_NOCACHESYNCPPC|MEMF_NOCACHESYNCM68K);
#else
  da = AllocVec(sizeof(struct VDirectArray), MEMF_PUBLIC|MEMF_CLEAR);
#endif

  if(da)
  {
    da->Video = video;
    da->Width = width;
    da->Height  = height;

    for(i = 0; i < 256; i++)
    {
      da->Palette[i][0] = 1;
      da->Palette[i][1] = 0;
      da->Palette[i][2] = 0;
      da->Palette[i][3] = 0;
    }
  }
  
  return(da);
}

void VFreeDirectArray(struct VDirectArray *da)
{
#ifdef POWERUP
  PPCFreeVec(da);
#else
  FreeVec(da);
#endif
}

void VSetDirectFrame(struct VDirectArray *da)
{
  struct Video  *video;
  LONG      x;
  LONG      y;

  video = da->Video;

  if(video->CyberMode)
  {
    if(video->BitMapLock)
      UnLockBitMap(video->BitMapLock);

    VBeginFrame(video, da->BackgroundPen, &da->Palette[0][0]);
    VEndFrame(video);

    video->OffsetY = (video->Window->Height / video->Buffers) * video->CurrentScreenBuffer;

    x = (video->Left + ((video->Width - da->Width) >> 1)) & 0xfffffffc;;
    y = video->Top + video->OffsetY + ((video->Height - da->Height) >> 1);

    VSetFrameBox(video, x, y, x + da->Width - 1, y + da->Height - 1);

    video->CurrentPixelArray  = NULL;
    video->CurrentVectorArray = NULL;

    video->BitMapLock = LockBitMapTags(video->Screen->RastPort.BitMap,  LBMI_BASEADDRESS, (ULONG) &da->Pixels,
                                      LBMI_BYTESPERROW, (ULONG) &da->BytesPerRow,
                                      TAG_END);
    if(video->BitMapLock)
      da->Pixels += x + (da->BytesPerRow * y);
    else
    {
      da->Pixels    = NULL;
      da->BytesPerRow = 0;
    }
  }
}

void VSetFrameBox(struct Video *video, LONG sx, LONG sy, LONG ex, LONG ey)
{
  struct Rect32 *frame_box;

  frame_box = &video->FrameBox[video->CurrentScreenBuffer];

  if((sx > frame_box->MinX) || (sy > frame_box->MinY)
  || (ex < frame_box->MaxX) || (ey < frame_box->MaxY))
  {
    if(video->CGXHook)
    {
      FillPixelArray( video->RastPort,
              frame_box->MinX,
              frame_box->MinY,
              frame_box->MaxX - frame_box->MinX + 1,
              frame_box->MaxY - frame_box->MinY + 1,
              video->BackgroundColor);
    }
    else
    {
      SetAPen(video->RastPort, video->BackgroundPen);

      RectFill( video->RastPort,
            frame_box->MinX,  frame_box->MinY,
            frame_box->MaxX,  frame_box->MaxY);
    }
  }

  frame_box->MinX = sx;
  frame_box->MinY = sy;
  frame_box->MaxX = ex;
  frame_box->MaxY = ey;
}

void VRefresh(struct Video *video)
{
  if(video->CurrentPixelArray)
    VDrawPixelArray(video->CurrentPixelArray);
  else if(video->CurrentVectorArray)
    VDrawVectorArray(video->CurrentVectorArray);
}

void VSetFrameSkip(struct Video *video, LONG frameskip)
{
  video->FrameSkip = frameskip;
}

void VSetLimitSpeed(struct Video *video, LONG limitspeed)
{
  video->LimitSpeed = limitspeed;
}

LONG VSaveILBM(struct Video *video, STRPTR filename)
{
  BPTR  file;
  APTR  buffer;
  APTR  buf;
  UBYTE *pix;
  UBYTE *line;
  LONG  w;
  LONG  h;
  LONG  i;
  LONG  j;
  LONG  k;
  LONG  l;
  UWORD t;

  if(video->CurrentPixelArray)
  {
    w = video->CurrentPixelArray->EndX - video->CurrentPixelArray->StartX + 1;
    h = video->CurrentPixelArray->EndY - video->CurrentPixelArray->StartY + 1;
    
    buffer = AllocVec(9*sizeof(ULONG)+sizeof(struct BMHD)+(256*3)+(((w+15)&0xfffffff0)*h), MEMF_PUBLIC);
    
    buf = buffer;
    
    if(buf)
    {
      file = Open(filename, MODE_NEWFILE);
      
      if(file)
      {
        *((ULONG *) buf)++  = ID_FORM;
        *((ULONG *) buf)++  = 7*sizeof(ULONG)+sizeof(struct BMHD)+(256*3)+(((w+15)&0xfffffff0)*h);
        *((ULONG *) buf)++  = MAKE_ID('I','L','B','M');
        *((ULONG *) buf)++  = MAKE_ID('B','M','H','D');
        *((ULONG *) buf)++  = sizeof(struct BMHD);
        ((struct BMHD *) buf)->Width      = w;
        ((struct BMHD *) buf)->Height     = h;
        ((struct BMHD *) buf)->Left       = 0;
        ((struct BMHD *) buf)->Top        = 0;
        ((struct BMHD *) buf)->Planes     = 8;
        ((struct BMHD *) buf)->Masking      = 0;
        ((struct BMHD *) buf)->Compression    = 0;
        ((struct BMHD *) buf)->pad        = 0;
        ((struct BMHD *) buf)->TransparentColor = 0;
        ((struct BMHD *) buf)->XAspect      = 1;
        ((struct BMHD *) buf)->YAspect      = 1;
        ((struct BMHD *) buf)->PageWidth    = w;
        ((struct BMHD *) buf)->PageHeight   = h;
        ((struct BMHD *) buf)++;
        *((ULONG *) buf)++  = MAKE_ID('C','M','A','P');
        *((ULONG *) buf)++  = 256*3;
        for(i = 0; i < 256; i++)
        {
          *((UBYTE *) buf)++  = video->CMAP[i] >> 16;
          *((UBYTE *) buf)++  = (video->CMAP[i] >> 8) & 0xff;
          *((UBYTE *) buf)++  = video->CMAP[i] & 0xff;
        }
        *((ULONG *) buf)++  = MAKE_ID('B','O','D','Y');
        *((ULONG *) buf)++  = (((w+15)&0xfffffff0)*h);
        line = VGetPixelArrayAddr(video->CurrentPixelArray);
        for(i = 0; i < h; i++)
        {
          for(j = 0; j < 8; j++)
          {
            t = 0;
            l = 15;
            pix = line;

            for(k = 0; k < w; k++)
            {
              if(l > j)
                t |= (*pix++ << (l - j)) & (1 << l);
              else
                t |= (*pix++ >> (j - l)) & (1 << l);
              
              if(l)
                l--;
              else
              {
                *((UWORD *) buf)++ = t;
                
                t = 0;
                l = 15;
              }
            }
            
            if(l < 15)
              *((UWORD *) buf)++ = t;
          }
          
          line += video->CurrentPixelArray->BytesPerRow;
        }


        Write(file, buffer, 9*sizeof(ULONG)+sizeof(struct BMHD)+(256*3)+(((w+15)&0xfffffff0)*h));
      
        Close(file);
      }
      
      FreeVec(buffer);
    }
  }
  
  return(0);
}

LONG VGetFrameSkip(struct Video *video)
{
  return(video->FrameSkip);
}

LONG VGetFPS(struct Video *video)
{
  return(video->CurrentFPS);
}
