-- File: ByteBltAlto.mesa,  Last Edit: HGM  July 31, 1980  10:02 PM

DIRECTORY
  Environment USING [Block],
  BitBltDefs USING [BBptr, BBTableSpace, AlignedBBTable, BITBLT],
  Inline USING [COPY],
  ByteBlt USING [];

ByteBltAlto: PROGRAM IMPORTS BitBltDefs, Inline EXPORTS ByteBlt =
  BEGIN

  StartIndexGreaterThanStopIndexPlusOne: PUBLIC ERROR = CODE;

  ByteBlt: PUBLIC PROCEDURE [to, from: Environment.Block]
    RETURNS [nBytes: CARDINAL] =
    BEGIN
    -- NB: to+from are RECORDs, not POINTERs to RECORDs, so we can update them
    toBytes, fromBytes: POINTER TO PACKED ARRAY [0..0) OF [0..377B);
    moved: CARDINAL ← 0;
    -- This check is necessary since subtracting CARDINALs gives big numbers
    IF to.startIndex > to.stopIndexPlusOne OR from.startIndex >
      from.stopIndexPlusOne THEN ERROR StartIndexGreaterThanStopIndexPlusOne;
    nBytes ← MIN[
      to.stopIndexPlusOne - to.startIndex,
	from.stopIndexPlusOne - from.startIndex];
    IF nBytes = 0 THEN RETURN;
    toBytes ← ShortenPointer[to.blockPointer];
    fromBytes ← ShortenPointer[from.blockPointer];
    -- move the first odd byte (if any) to be sure that to is word aligned
    IF (to.startIndex MOD 2) # 0 THEN
      BEGIN
      toBytes[to.startIndex] ← fromBytes[from.startIndex];
      moved ← 1;
      to.startIndex ← to.startIndex + 1;
      from.startIndex ← from.startIndex + 1;
      END;
    IF (from.startIndex MOD 2) = 0 THEN
      -- fast case: both are word aligned
      BEGIN
      words: CARDINAL = (nBytes - moved)/2;
      Inline.COPY[
	to: toBytes + to.startIndex/2, from: fromBytes + from.startIndex/2,
	nwords: words];
      IF (moved + 2*words) # nBytes THEN
	-- move the one and only remaining byte
	BEGIN
	toBytes[to.startIndex + 2*words] ← fromBytes[from.startIndex + 2*words];
	END;
      END
    ELSE
      -- slow case: have to ripple things
      BEGIN
      lineWidth: CARDINAL = 16; -- words per scan line: controls interrupt latency
      bbTable: BitBltDefs.BBTableSpace;
      bbt: BitBltDefs.BBptr ← BitBltDefs.AlignedBBTable[@bbTable];
      lines, bytes, tail: CARDINAL;
      bbt↑ ←
	[sourcealt: FALSE, destalt: FALSE, sourcetype: block, function: replace,
	  dbca: toBytes + to.startIndex/2, dbmr: lineWidth, dlx: 0, dty:,
	  dw: 16*lineWidth, dh:, sbca: fromBytes + from.startIndex/2,
	  sbmr: lineWidth, slx: 8, sty:, gray0:, gray1:, gray2:, gray3:];
      -- BITBLT is not interruptable except at the end of each scan line, so we break things up into chunks in order to maintain reasonable interrupt latency for the IO devices.  It takes about 200microsec to move 50 bytes with the display off.
      bytes ← nBytes - moved;
      tail ← bytes MOD (2*lineWidth); -- bytes left to move with second BitBlt
      lines ← bytes/(2*lineWidth);
      bytes ← lines*(2*lineWidth); -- bytes we move with main BitBlt
      -- This "moves" a rectangle that is lineWidth words wide by as many lines high as will fit.  NB: It cheats and actually reads a byte from beyond the edge of the rectangle.  This is not really legal, but works out OK for any reasonable implementation of BitBlt.
      bbt.dty ← bbt.sty ← 0;
      bbt.dw ← 16*lineWidth;
      bbt.dh ← lines;
      BitBltDefs.BITBLT[bbt];
      -- This BitBlt will move one line that is less than lineWidth words wide.
      bbt.dty ← bbt.sty ← lines;
      bbt.dw ← 8*tail;
      bbt.dh ← 1;
      BitBltDefs.BITBLT[bbt];
      END;
    END;

  HyperSpaceNotSupported: PUBLIC ERROR = CODE;
  NilRejected: PUBLIC ERROR = CODE;

  ShortenPointer: PUBLIC PROCEDURE [lp: LONG POINTER] RETURNS [sp: POINTER] =
    BEGIN
    LongPointer: TYPE = RECORD [p: POINTER, other: WORD];
    myCopy: LongPointer ← LOOPHOLE[lp];
    IF myCopy.other # 0 THEN ERROR HyperSpaceNotSupported;
    IF lp = NIL THEN ERROR NilRejected;
    sp ← myCopy.p;
    END;


  -- initialization

  END.  -- ByteBltAlto