-- PDInterpBitmapImpl.mesa
-- Michael Plass,  4-Mar-84 11:17:27
DIRECTORY BitBlt, Environment, Inline, PDInterpBitmap, PDBitBltTiming, Mopcodes, System;
PDInterpBitmapImpl: PROGRAM
	IMPORTS BitBlt, Inline, System
	EXPORTS PDInterpBitmap, PDBitBltTiming
	= BEGIN
BitmapDesc: TYPE = PDInterpBitmap.BitmapDesc;
Tile: TYPE = PDInterpBitmap.Tile;
Rectangle: TYPE = PDInterpBitmap.Rectangle;
Function: TYPE = PDInterpBitmap.Function;
bitsPerWord: NAT = Environment.bitsPerWord;
lgBitsPerWord: NAT = Environment.logBitsPerWord;
wordsPerPage: NAT = Environment.wordsPerPage;
lgBitsPerPixel: NAT = 0;
pixelsPerWord: NAT = bitsPerWord;
-- Some inlines for basic bit-hacking operations.
	BITAND: PROC [a, b: CARDINAL] RETURNS[CARDINAL]
		= INLINE {RETURN[Inline.BITAND[a, b]]};
	Shift: PROC [a: CARDINAL, b: INTEGER] RETURNS [CARDINAL]
		= INLINE {RETURN[Inline.BITSHIFT[a, b]]};
	

timingBitBlts: BOOLEAN ← FALSE;

bitBltPulses: System.Pulses ← [pulses: 0];

ResetBitBltMicroseconds: PUBLIC PROC = {bitBltPulses ← [pulses: 0]; timingBitBlts ← TRUE};

GetBitBltMicroseconds: PUBLIC PROC RETURNS [LONG CARDINAL] = {RETURN[System.PulsesToMicroseconds[bitBltPulses]]};

BITBLT: PROC [ptr: BitBlt.BBptr] = INLINE {
	IF timingBitBlts THEN {
		bitBltPulses.pulses ← bitBltPulses.pulses - System.GetClockPulses[];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		BitBlt.BITBLT[ptr];
		bitBltPulses.pulses ← bitBltPulses.pulses + System.GetClockPulses[];
		}
	ELSE BitBlt.BITBLT[ptr];
	};
	
Intersect: PUBLIC PROC [a, b: Rectangle] RETURNS [Rectangle] = {
	sMin: INTEGER ← MAX[a.sMin, b.sMin];
	fMin: INTEGER ← MAX[a.fMin, b.fMin];
	sMax: INTEGER ← MIN[INT[a.sMin]+a.sSize, INT[b.sMin]+b.sSize];
	fMax: INTEGER ← MIN[INT[a.fMin]+a.fSize, INT[b.fMin]+b.fSize];
	IF sMax <= sMin OR fMax <= fMin THEN RETURN [[0, 0, 0, 0]]
	ELSE RETURN [[sMin, fMin, sMax-sMin, fMax-fMin]]
	};
	
BlockTransfer: PROC [source: LONG POINTER, count: CARDINAL, dest: LONG POINTER]
	= MACHINE CODE {Mopcodes.zBLTL};
	
LongZero: PROC [address: LONG POINTER TO CARDINAL, count: CARDINAL] = {
	IF count > 0 THEN {
		address↑ ← 0;
		BlockTransfer[source: address, count: count-1, dest: address+1];
		};
	};
	
Clear: PUBLIC PROC [bitmapDesc: BitmapDesc] = {
	pointer: LONG POINTER ← bitmapDesc.pointer;
	words: LONG CARDINAL ← Inline.LongMult[bitmapDesc.rast, bitmapDesc.lines];
	WHILE words > 32768 DO
		LongZero[pointer, 32768];
		pointer ← pointer + 32768;
		words ← words - 32768;
		ENDLOOP;
	LongZero[pointer, words];
	};
	
InsufficientSpace: PUBLIC ERROR = CODE;
Reshape: PUBLIC PROC [pointer: LONG POINTER, words: INT, bounds: Rectangle] RETURNS [bitmapDesc: BitmapDesc] = {
	bitsPerPixel: CARDINAL = Shift[1, lgBitsPerPixel];
	bitsPerLine: NAT = Inline.LongMult[bounds.fSize, bitsPerPixel];
		-- Will raise a bounds fault if too many bits per line.
	wordsPerLine: CARDINAL = (bitsPerLine+(bitsPerWord-1)) / bitsPerWord;
	wordsNeeded: INT = Inline.LongMult[wordsPerLine, bounds.sSize];
	IF wordsNeeded > words THEN ERROR InsufficientSpace;
	RETURN [[bounds.sMin, bounds.fMin, 0, 0, bounds.sSize, bounds.fSize, pointer, wordsPerLine, bounds.sSize]];
	};
	
ShiftMap: PUBLIC PROC [p: BitmapDesc, s, f: INTEGER] RETURNS [BitmapDesc] = {
	p.sOrigin ← p.sOrigin + s;
	p.fOrigin ← p.fOrigin + f;
	RETURN [p]
	};
	
ShiftWindow: PUBLIC PROC [p: BitmapDesc, s, f: INTEGER] RETURNS [BitmapDesc] = {
	p.sMin ← p.sMin + s;
	p.fMin ← p.fMin + f;
	RETURN [p]
	};
	
Clip: PUBLIC PROC [p: BitmapDesc, bounds: Rectangle] RETURNS [BitmapDesc] = {
	bounds ← Intersect[Window[p], bounds]; 
	p.sMin ← bounds.sMin-p.sOrigin;
	p.fMin ← bounds.fMin-p.fOrigin;
	p.sSize ← bounds.sSize;
	p.fSize ← bounds.fSize;
	RETURN [p]
	};
	
SetWindow: PUBLIC PROC [p: BitmapDesc, bounds: Rectangle] RETURNS [BitmapDesc] = {
	RETURN [p]
	};
	
Window: PUBLIC PROC [p: BitmapDesc] RETURNS [Rectangle] = {
	RETURN [[p.sOrigin + p.sMin, p.fOrigin + p.fMin, p.sSize, p.fSize]]
	};
	
BufferBounds: PUBLIC PROC [p: BitmapDesc] RETURNS [Rectangle] = {
	RETURN [[p.sOrigin, p.fOrigin, p.lines, Shift[p.rast, lgBitsPerWord-lgBitsPerPixel]]]
	};
	
BoundedWindow: PUBLIC PROC [p: BitmapDesc] RETURNS [Rectangle] = {
	sMin: INTEGER ← MAX[p.sOrigin + p.sMin, p.sOrigin];
	fMin: INTEGER ← MAX[p.fOrigin + p.fMin, p.fOrigin];
	sMax: INTEGER ← MIN[p.sOrigin + p.sMin + p.sSize, p.sOrigin + p.lines];
	fMax: INTEGER ← MIN[p.fOrigin + p.fMin + p.fSize, p.fOrigin + Shift[p.rast, lgBitsPerWord-lgBitsPerPixel]];
	RETURN [[sMin, fMin, MAX[sMax-sMin, 0], MAX[fMax-fMin, 0]]]
	};
	
replicator: ARRAY [0..4] OF CARDINAL = [0FFFFH, 05555H, 01111H, 00101H, 00001H];
Fill: PUBLIC PROC [dest: BitmapDesc, area: Rectangle, value: CARDINAL, function: Function] = {
	lgPixelsPerWord: INTEGER = lgBitsPerWord-lgBitsPerPixel;
	-- The following bounding box is calculated in terms of the destination buffer.
		sMin: INTEGER = MAX[dest.sMin, 0, area.sMin-dest.sOrigin];
		sMax: INTEGER = MIN[dest.sMin+dest.sSize, dest.lines, area.sMin+area.sSize-dest.sOrigin];
		fMin: INTEGER = MAX[dest.fMin, 0, area.fMin-dest.fOrigin];
		fMax: INTEGER = MIN[dest.fMin+dest.fSize, Shift[dest.rast, lgBitsPerWord-lgBitsPerPixel], area.fMin+area.fSize-dest.fOrigin];
	replicatedPixel: CARDINAL ← Inline.BITAND[value, Shift[1, Shift[1, lgBitsPerPixel]]-1] * replicator[lgBitsPerPixel];
	bbTableSpace: BitBlt.BBTableSpace;
	bb: BitBlt.BBptr ← BitBlt.AlignedBBTable[@bbTableSpace];
	IF sMin<sMax AND fMin<fMax THEN {
		bb↑ ← [
			dst: [word: dest.pointer + Inline.LongMult[sMin, dest.rast] + Shift[fMin, -lgPixelsPerWord], bit: Inline.BITAND[fMin, Shift[1, lgPixelsPerWord]-1]],
			dstBpl: dest.rast*bitsPerWord,
			src: [word: @replicatedPixel, bit: 0],
			srcDesc: [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]],
			height: sMax-sMin,
			width: Shift[fMax-fMin, lgBitsPerPixel],
			flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: TRUE, srcFunc: function.srcFunc, dstFunc: function.dstFunc]
			];
		BITBLT[bb];
		};
	};
	
Transfer: PUBLIC PROC [dest, source: BitmapDesc, function: Function] = {
	lgPixelsPerWord: INTEGER = lgBitsPerWord-lgBitsPerPixel;
	sMin: INTEGER = MAX[dest.sOrigin+MAX[dest.sMin, 0], source.sOrigin+MAX[source.sMin, 0]];
	sMax: INTEGER = MIN[dest.sOrigin+MIN[dest.sMin+dest.sSize, dest.lines], source.sOrigin+MIN[source.sMin+source.sSize, source.lines]];
	fMin: INTEGER = MAX[dest.fOrigin+MAX[dest.fMin, 0], source.fOrigin+MAX[source.fMin, 0]];
	fMax: INTEGER = MIN[dest.fOrigin+MIN[dest.fMin+dest.fSize, Shift[dest.rast, lgBitsPerWord-lgBitsPerPixel]], source.fOrigin+MIN[source.fMin+source.fSize, Shift[source.rast, lgBitsPerWord-lgBitsPerPixel]]];
	bbTableSpace: BitBlt.BBTableSpace;
	bb: BitBlt.BBptr = BitBlt.AlignedBBTable[@bbTableSpace];
	fMinDest: NAT = fMin - dest.fOrigin;
	fMinSource: NAT = fMin - source.fOrigin;
	sStartDest: NAT ← sMin-dest.sOrigin;
	sStartSource: NAT ← sMin-source.sOrigin;
	IF sMin<sMax AND fMin<fMax THEN {
		bb↑ ← [
			dstBpl: dest.rast*bitsPerWord,
			srcDesc: [srcBpl[source.rast*bitsPerWord]],
			height: sMax-sMin,
			width: Shift[fMax-fMin, lgBitsPerPixel],
			flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: FALSE, srcFunc: function.srcFunc, dstFunc: function.dstFunc]
			];
		IF source.pointer = dest.pointer THEN {
			sSize: NAT = sMax-sMin;
			fSize: NAT = fMax-fMin;
			IF (fMinSource+fSize)>fMinDest AND (fMinDest+fSize)>fMinSource AND (sStartSource+sSize)>sStartDest AND (sStartDest+sSize)>sStartSource THEN {
				bb.flags.disjoint ← FALSE; -- the rectangles overlap
				IF sStartDest=sStartSource THEN bb.flags.disjointItems ← FALSE; -- so do the items
				IF sStartDest>sStartSource OR (sStartDest=sStartSource AND fMinDest>fMinSource) THEN { -- reverse direction
					bb.flags.direction ← backward; bb.srcDesc.srcBpl ← bb.dstBpl ← -bb.dstBpl;
					sStartSource ← sStartSource + (sSize-1); sStartDest ← sStartDest + (sSize-1);
					};
				};
			};
		bb.dst ← [word: dest.pointer + Inline.LongMult[sStartDest, dest.rast] + Shift[fMinDest, -lgPixelsPerWord], bit: Inline.BITAND[fMinDest, Shift[1, lgPixelsPerWord]-1]];
		bb.src ← [word: source.pointer + Inline.LongMult[sStartSource, source.rast] + Shift[fMinSource, -lgPixelsPerWord], bit: Inline.BITAND[fMinSource, Shift[1, lgPixelsPerWord]-1]];
		BITBLT[bb];
		};
	};
	
IsPowerOfTwo: PROC [c: CARDINAL] RETURNS [BOOLEAN] = INLINE {
	RETURN [BITAND[c, c-1] = 0]
	};
	
largeBitSize: INT ← 5000;
fSizeHint: NAT ← 100;
CreateTile: PUBLIC PROC [rectangle: Rectangle, phase: INTEGER ← 0, rasterPointer: LONG POINTER, scratchPointer: LONG POINTER ← NIL, scratchWords: INT ← 0] RETURNS [tile: Tile] = {
	ENABLE InsufficientSpace => GO TO InsufficientScratch;
	bitWidth: CARDINAL = Shift[rectangle.fSize, lgBitsPerPixel];
	bitmapDesc: BitmapDesc = [rectangle.sMin, rectangle.fMin, 0, 0, rectangle.sSize, rectangle.fSize, rasterPointer, (rectangle.fSize+(pixelsPerWord-1))/pixelsPerWord, rectangle.sSize];
	IF bitWidth <= 16 AND IsPowerOfTwo[bitWidth] AND bitmapDesc.sSize < 16 AND phase = 0 THEN {
		small: BitmapDesc ← Reshape[scratchPointer, scratchWords, [rectangle.sMin, rectangle.fMin, rectangle.sSize, Shift[16, -lgBitsPerPixel]]];
		FOR fShift: NAT ← 0, fShift+bitmapDesc.fSize UNTIL fShift>=small.fSize DO
			Transfer[small, ShiftMap[bitmapDesc, 0, fShift], [null, null]];
			ENDLOOP;
		tile ← [small.sOrigin, small.fOrigin, small.sSize, small.fSize, 0, small.pointer, small.rast, small.lines];
		}
	ELSE {
		fSize: NAT ← (fSizeHint+bitmapDesc.fSize-1)/bitmapDesc.fSize*bitmapDesc.fSize;
		bitsPerLine: NAT ← Shift[fSize, lgBitsPerPixel];
		sSize: NAT ← MAX[(((largeBitSize+bitsPerLine-1)/bitsPerLine)/bitmapDesc.sSize)*bitmapDesc.sSize, bitmapDesc.sSize];
		large: BitmapDesc ← Reshape[scratchPointer, scratchWords, [bitmapDesc.sOrigin+bitmapDesc.sMin, bitmapDesc.fOrigin+bitmapDesc.fMin, sSize, fSize]];
		finalPhase: CARDINAL;
		WHILE phase<0 DO phase ← phase + bitmapDesc.fSize ENDLOOP;
		phase ← phase MOD bitmapDesc.fSize;
		finalPhase ← (phase * (sSize/bitmapDesc.sSize)) MOD bitmapDesc.fSize;
		large.sSize ← bitmapDesc.sSize;
		large.fSize ← bitmapDesc.fSize;
		Transfer[large, bitmapDesc, [null, null]];
		WHILE large.fSize<fSize DO
			fSizePrev: NAT = large.fSize;
			large.fSize ← MIN[fSize, 2*fSizePrev];
			Transfer[large, ShiftMap[large, 0, fSizePrev], [null, null]];
			ENDLOOP;
		WHILE large.sSize<sSize DO
			sSizePrev: NAT = large.sSize;
			large.sSize ← MIN[sSize, 2*sSizePrev];
			Transfer[large, ShiftMap[large, sSizePrev, phase], [null, null]];
			{firstPart: BitmapDesc ← ShiftMap[large, sSizePrev, phase-bitmapDesc.fSize];
				firstPart.fSize ← bitmapDesc.fSize;
				Transfer[large, firstPart, [null, null]];
				};
			phase ← 2*phase;
			WHILE phase > bitmapDesc.fSize DO phase ← phase - bitmapDesc.fSize ENDLOOP;
			ENDLOOP;
		tile ← [large.sOrigin, large.fOrigin, sSize, fSize, finalPhase, large.pointer, large.rast, large.lines];
		};
	EXITS InsufficientScratch => {
		tile ← [rectangle.sMin, rectangle.fMin, rectangle.sSize, rectangle.fSize, phase, rasterPointer, (rectangle.fSize+(pixelsPerWord-1))/pixelsPerWord, rectangle.sSize];
		};
	};
	
TransferTile: PUBLIC PROC [dest: BitmapDesc, tile: Tile, function: Function ← [null, null]] = {
	bitWidth: CARDINAL = Shift[tile.fSize, lgBitsPerPixel];
	IF bitWidth = bitsPerWord AND tile.rast = 1 AND tile.sSize <= 16 AND tile.phase = 0 THEN {
		startLine: NAT ← MAX[dest.sMin, 0];
		endLine: INTEGER ← MIN[dest.sMin+dest.sSize, dest.lines];
		startPixel: NAT ← MAX[dest.fMin, 0];
		endPixel: INTEGER ← MIN[dest.fMin+dest.fSize, Shift[dest.rast, lgBitsPerWord-lgBitsPerPixel]];
		IF endLine>startLine AND endPixel>startPixel THEN {
			startBit: NAT ← Shift[startPixel, lgBitsPerPixel];
			bbTableSpace: BitBlt.BBTableSpace;
			bb: BitBlt.BBptr = BitBlt.AlignedBBTable[@bbTableSpace];
			sOffset: INTEGER ← (dest.sOrigin+dest.sMin-tile.sOrigin) MOD tile.sSize;
			fOffset: INTEGER ← (dest.fOrigin+dest.fMin-tile.fOrigin) MOD tile.fSize;
			WHILE sOffset < 0 DO sOffset ← sOffset + tile.sSize ENDLOOP;
			WHILE fOffset < 0 DO fOffset ← fOffset + tile.fSize ENDLOOP;
			bb↑ ← [
				dst: [word: dest.pointer + Inline.LongMult[startLine, dest.rast] + startBit/bitsPerWord, bit: startBit MOD bitsPerWord],
				dstBpl: dest.rast*bitsPerWord,
				src: [word: tile.pointer+sOffset, bit: Shift[fOffset, lgBitsPerPixel]],
				srcDesc: [gray[[yOffset: sOffset, widthMinusOne: 0, heightMinusOne: tile.sSize-1]]],
				height: endLine-startLine,
				width: Shift[endPixel-startPixel, lgBitsPerPixel],
				flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: TRUE, srcFunc: function.srcFunc, dstFunc: function.dstFunc]
				];
			BITBLT[bb];
			};
		}
	ELSE {
		sMin: INT ← dest.sOrigin+dest.sMin;
		fMin: INT ← dest.fOrigin+dest.fMin;
		source: BitmapDesc;
		sOrigin: INT ← tile.sOrigin;
		fOrigin: INT ← tile.fOrigin;
		WHILE sOrigin < sMin DO
			sOrigin ← sOrigin + tile.sSize;
			fOrigin ← fOrigin + tile.phase;
			ENDLOOP;
		WHILE sOrigin > sMin DO
			sOrigin ← sOrigin - tile.sSize;
			fOrigin ← fOrigin - tile.phase;
			ENDLOOP;
		WHILE fOrigin < fMin DO fOrigin ← fOrigin + tile.fSize ENDLOOP;
		WHILE fOrigin > fMin DO fOrigin ← fOrigin - tile.fSize ENDLOOP;
		source ← [sOrigin, fOrigin, 0, 0, tile.sSize, tile.fSize, tile.pointer, tile.rast, tile.lines];
		WHILE source.sOrigin < sMin+dest.sSize DO
			source.fOrigin ← fOrigin;
			WHILE source.fOrigin < fMin+dest.fSize DO
				Transfer[dest, source, function];
				source.fOrigin ← source.fOrigin + source.fSize;
				ENDLOOP;
			source.sOrigin ← source.sOrigin + source.sSize;
			fOrigin ← fOrigin + tile.phase;
			WHILE fOrigin > fMin DO fOrigin ← fOrigin - source.fSize ENDLOOP;
			ENDLOOP;
		};
	};
	
END.