 {************************************************************************}
 {*                     4-bit planar VGA mode routines                   *}
 {************************************************************************}


const

  VideoOfs = 0;


var

  VidMem: PByteArray;
  ScrWidth: SmallInt;


procedure bytemove(var source, dest; count: SmallInt);
var
  s, d: PByte;
begin
  s := PByte(@source);
  d := PByte(@dest);
  while count > 0 do begin
    d^ := s^;
    Inc(d);
    Inc(s);
    Dec(count);
  end;
end;



procedure PutPixel16(X,Y : SmallInt; Pixel: Word);
var
  offset: word;
  dummy: byte;
begin
  Inc(x, StartXViewPort);
  Inc(y, StartYViewPort);
  { convert to absolute coordinates and then verify clipping...}
  if ClipPixels then
  begin
    if (X < StartXViewPort) or (X > (StartXViewPort + ViewWidth)) then
      exit;
    if (Y < StartYViewPort) or (Y > (StartYViewPort + ViewHeight)) then
      exit;
  end;
  offset := y * 80 + (x shr 3) + VideoOfs;
  WritePortW($3ce, $0f01);       { Index 01 : Enable ops on all 4 planes }
  WritePortW($3ce, (Pixel and $ff) shl 8); { Index 00 : Enable correct plane and write color }

  WritePortW($3ce, 8 or ($8000 shr (x and $7)));{ Select correct bits to modify }
  dummy := VidMem^[offset];     { Read data byte into VGA latch register }
  VidMem^[offset] := dummy;     { Write the data into video memory }
end;


function GetPixel16(X,Y: SmallInt):word;
var
  dummy, offset: Word;
  shift: byte;
begin
  Inc(x, StartXViewPort);
  Inc(y, StartYViewPort);
  offset := Y * 80 + (x shr 3) + VideoOfs;
  WritePortW($3ce, 4);
  shift := 7 - (X and 7);
  dummy := (VidMem^[offset] shr shift) and 1;
  WritePortB($3cf, 1);
  dummy := dummy or (((VidMem^[offset] shr shift) and 1) shl 1);
  WritePortB($3cf, 2);
  dummy := dummy or (((VidMem^[offset] shr shift) and 1) shl 2);
  WritePortB($3cf, 3);
  dummy := dummy or (((VidMem^[offset] shr shift) and 1) shl 3);
  GetPixel16 := dummy;
end;


procedure GetScanLine16(x1, x2, y: SmallInt; var data);
var
  dummylong: longint;
  Offset, count, count2, amount, index: word;
  plane: byte;
begin
  inc(x1,StartXViewPort);
  inc(x2,StartXViewPort);
{$ifdef logging}
  LogLn('GetScanLine16 start, length to get: '+strf(x2-x1+1)+' at y = '+strf(y));
{$Endif logging}
  offset := (Y + StartYViewPort) * 80 + (x1 shr 3) + VideoOfs;
{$ifdef logging}
  LogLn('Offset: '+HexStr(offset,4)+' - ' + strf(offset));
{$Endif logging}
  { first get enough pixels so offset is 32bit aligned }
  amount := 0;
  index := 0;
  If ((x1 and 31) <> 0) Or
     ((x2-x1+1) < 32) Then
    Begin
      If ((x2-x1+1) >= 32+32-(x1 and 31)) Then
        amount := 32-(x1 and 31)
      Else amount := x2-x1+1;
{$ifdef logging}
      LogLn('amount to align to 32bits or to get all: ' + strf(amount));
{$Endif logging}
      For count := 0 to amount-1 do
        WordArray(Data)[Count] := getpixel16(x1-StartXViewPort+Count,y);
      index := amount;
      Inc(Offset,(amount+7) shr 3);
{$ifdef logging}
      LogLn('offset now: '+HexStr(offset,4)+' - ' + strf(offset));
      LogLn('index now: '+strf(index));
{$Endif logging}
    End;
  amount := x2-x1+1 - amount;
{$ifdef logging}
  LogLn('amount left: ' + strf(amount));
{$Endif logging}
  If amount = 0 Then Exit;
  WritePortB($3ce, 4);
  { first get everything from plane 3 (4th plane) }
  WritePortB($3cf, 3);
  Count := 0;
  For Count := 1 to (amount shr 5) Do
    Begin
      dummylong := PLongInt(@VidMem^[offset+(Count-1)*4])^;
      dummylong :=
        ((dummylong and $ff) shl 24) or
        ((dummylong and $ff00) shl 8) or
        ((dummylong and $ff0000) shr 8) or
        ((dummylong and $ff000000) shr 24);
      For Count2 := 31 downto 0 Do
        Begin
          WordArray(Data)[index+Count2] := DummyLong and 1;
          DummyLong := DummyLong shr 1;
        End;
      Inc(Index, 32);
    End;
{ Now get the data from the 3 other planes }
  plane := 3;
  Repeat
    Dec(Index,Count*32);
    Dec(plane);
    WritePortB($3cf, plane);
    Count := 0;
    For Count := 1 to (amount shr 5) Do
      Begin
        dummylong := PLongInt(@VidMem^[offset+(Count-1)*4])^;
        dummylong :=
          ((dummylong and $ff) shl 24) or
          ((dummylong and $ff00) shl 8) or
          ((dummylong and $ff0000) shr 8) or
          ((dummylong and $ff000000) shr 24);
        For Count2 := 31 downto 0 Do
          Begin
            WordArray(Data)[index+Count2] :=
              (WordArray(Data)[index+Count2] shl 1) or (DummyLong and 1);
            DummyLong := DummyLong shr 1;
          End;
        Inc(Index, 32);
      End;
  Until plane = 0;
  amount := amount and 31;
  Dec(index);
{$ifdef Logging}
  LogLn('Last array index written to: '+strf(index));
  LogLn('amount left: '+strf(amount)+' starting at x = '+strf(index+1));
{$Endif logging}
  For Count := 1 to amount Do
    WordArray(Data)[index+Count] := getpixel16(index+Count,y);
{$ifdef logging}
  LogLn('First 32 bytes gotten with getscanline16: ');
  If x2-x1+1 >= 32 Then
    Count2 := 32
  Else Count2 := x2-x1+1;
  For Count := 0 to Count2-1 Do
    Log(strf(WordArray(Data)[Count])+' ');
  LogLn('');
  If x2-x1+1 >= 32 Then
    Begin
      LogLn('Last 32 bytes gotten with getscanline16: ');
      For Count := 31 downto 0 Do
      Log(strf(WordArray(Data)[x2-x1-Count])+' ');
    End;
  LogLn('');
  GetScanLineDefault(x1-StartXViewPort,x2-StartXViewPort,y,Data);
  LogLn('First 32 bytes gotten with getscanlinedef: ');
  If x2-x1+1 >= 32 Then
    Count2 := 32
  Else Count2 := x2-x1+1;
  For Count := 0 to Count2-1 Do
    Log(strf(WordArray(Data)[Count])+' ');
  LogLn('');
  If x2-x1+1 >= 32 Then
    Begin
      LogLn('Last 32 bytes gotten with getscanlinedef: ');
      For Count := 31 downto 0 Do
      Log(strf(WordArray(Data)[x2-x1-Count])+' ');
    End;
  LogLn('');
  LogLn('GetScanLine16 end');
{$Endif logging}
end;


procedure DirectPutPixel16(X,Y : SmallInt);
{ x,y -> must be in global coordinates. No clipping. }
var
  color: word;
  offset: word;
  dummy: byte;
begin
  case CurrentWriteMode of
    XORPut:
      begin
        { getpixel wants local/relative coordinates }
        Color := GetPixel(x - StartXViewPort, y - StartYViewPort);
        Color := CurrentColor xor Color;
      end;
    OrPut:
      begin
        { getpixel wants local/relative coordinates }
        Color := GetPixel(x - StartXViewPort, y - StartYViewPort);
        Color := CurrentColor or Color;
      end;
    AndPut:
      begin
        { getpixel wants local/relative coordinates }
        Color := GetPixel(x - StartXViewPort, y - StartYViewPort);
        Color := CurrentColor and Color;
      end;
    NotPut:
      Color := Not Color;
    else
      Color := CurrentColor;
  end;
  offset := Y * 80 + (X shr 3) + VideoOfs;
  WritePortW($3ce, $f01);
  WritePortW($3ce, Color shl 8);
  WritePortW($3ce, 8 or $8000 shr (X and 7));
  dummy := VidMem^[offset];
  VidMem^[offset] := dummy;
end;


procedure HLine16(x, x2, y: SmallInt);
var
  xtmp: SmallInt;
  ScrOfs, HLength: Word;
  LMask, RMask: Byte;
begin
  { must we swap the values? }
  if x > x2 then
  begin
    xtmp := x2;
    x2 := x;
    x:= xtmp;
  end;
  { First convert to global coordinates }
  Inc(x, StartXViewPort);
  Inc(x2, StartXViewPort);
  Inc(y, StartYViewPort);
  if ClipPixels and LineClipped(x,y,x2,y,StartXViewPort,StartYViewPort,
    StartXViewPort+ViewWidth, StartYViewPort+ViewHeight) then
    exit;

  ScrOfs := y * ScrWidth + x div 8;
  HLength := x2 div 8 - x div 8;
  LMask := $ff shr (x and 7);
{$push}
{$r-}
{$q-}
  RMask:=$ff shl (7 - (x2 and 7));
{$pop}
  if HLength=0 then
    LMask:=LMask and RMask;
  WritePortB($3ce, 0);
  if CurrentWriteMode <> NotPut Then
    WritePortB($3cf, CurrentColor)
  else
    WritePortB($3cf, not CurrentColor);
  WritePortW($3ce, $0f01);
  WritePortB($3ce, 3);
  case CurrentWriteMode of
    XORPut:
      WritePortB($3cf, 3 shl 3);
    ANDPut:
      WritePortB($3cf, 1 shl 3);
    ORPut:
      WritePortB($3cf, 2 shl 3);
    NormalPut, NotPut:
      WritePortB($3cf, 0)
    else
      WritePortB($3cf, 0)
  end;

  WritePortB($3ce, 8);
  WritePortB($3cf, LMask);
{$push}
{$r-}
{$q-}
  VidMem^[ScrOfs] := VidMem^[ScrOfs] + 1;
{$pop}
  if HLength>0 then
  begin
    Dec(HLength);
    Inc(ScrOfs);
    if HLength>0 then
    begin
      WritePortW($3ce, $ff08);
      bytemove(VidMem^[ScrOfs], VidMem^[ScrOfs], HLength);
      Inc(ScrOfs, HLength);
    end else
      WritePortB($3ce, 8);
    WritePortB($3cf, RMask);
{$push}
{$r-}
{$q-}
    VidMem^[ScrOfs] := VidMem^[ScrOfs] + 1;
{$pop}
  end;
end;



procedure VLine16(x,y,y2: SmallInt);
var
  ytmp: SmallInt;
  ScrOfs,i: longint;
  BitMask: byte;

begin
  { must we swap the values? }
  if y > y2 then
  begin
    ytmp := y2;
    y2 := y;
    y:= ytmp;
  end;
  { First convert to global coordinates }
  Inc(x, StartXViewPort);
  Inc(y, StartYViewPort);
  Inc(y2, StartYViewPort);
  if ClipPixels and LineClipped(x,y,x,y2,StartXViewPort,StartYViewPort,
    StartXViewPort+ViewWidth, StartYViewPort+ViewHeight) then
    exit;
  ScrOfs:=y*ScrWidth+x div 8;
  BitMask:=$80 shr (x and 7);
  WritePortB($3ce, 0);
  if CurrentWriteMode <> NotPut then
    WritePortB($3cf, CurrentColor)
  else
    WritePortB($3cf, not CurrentColor);
  WritePortW($3ce, $0f01);
  WritePortB($3ce, 8);
  WritePortB($3cf, BitMask);
  WritePortB($3ce, 3);
  case CurrentWriteMode of
    XORPut:
      WritePortB($3cf, 3 shl 3);
    ANDPut:
      WritePortB($3cf, 1 shl 3);
    ORPut:
      WritePortB($3cf, 2 shl 3);
    NormalPut, NotPut:
      WritePortB($3cf, 0)
    else
      WritePortB($3cf, 0)
  end;
  for i:=y to y2 do
  begin
{$push}
{$r-}
{$q-}
    VidMem^[ScrOfs]:=VidMem^[ScrOfs]+1;
{$pop}
    Inc(ScrOfs, ScrWidth);
  end;
end;

