Copyright (C) 1992, Digital Equipment Corporation
All rights reserved.
See the file COPYRIGHT for a full description.
by Steve Freeman
Last modified on Wed Mar 22 18:21:09 PST 1995 by msm
modified on Mon Oct 25 13:52:17 PDT 1993 by sfreeman
UNSAFE MODULE VideoVBT EXPORTS VideoVBT, VideoVBTRep;
IMPORT Atom, AtomList, Axis, BasicCtypes, IO, JVBuffer, JVConverter,
JVFromDecomp, JVDecomp, JVDecompPool, Jvs, JvsBuffer, JVSink,
OSError, Picture, Point, RdUtils, Rect, Region, ScreenType, Stdio,
Thread, Tick, Time, Trestle, TrestleClass, TrestleComm, TrestleOnX,
VBT, VBTClass, Word, XScreenType, XScrnCmap(*, Fmt *);
<*PRAGMA LL*>
{{{ -- Buffer --
REVEAL
Buffer = BufferPublic BRANDED OBJECT
st : VBT.ScreenType := NIL;
width, height: CARDINAL := 0;
pic : Picture.T := NIL;
shmInfo : Picture.SharedMemInfo := NIL;
next : Buffer := NIL;
fac : Factory := NIL;
lastUse : CARDINAL := 0;
OVERRIDES
init := BInit;
picture := BPicture;
END;
PROCEDURE BInit (b: Buffer; shmid: BasicCtypes.int; address: ADDRESS):
JVBuffer.T RAISES {OSError.E} =
BEGIN
TRY
EVAL JVFromDecomp.T.init(b, shmid, address);
IF b.st # NIL THEN
<* ASSERT b.addr # NIL *>
LOCK b DO
b.shmInfo :=
NEW(Picture.SharedMemInfo, id := b.shmid, address := address);
b.pic := Picture.New(b.st, b.width, b.height);
Picture.AttachData(b.pic, address, b.shmInfo);
END;
END;
EXCEPT
| Picture.TrestleFail =>
RAISE
OSError.E(AtomList.List2(Atom.FromText("VideoVBT.Init"),
Atom.FromText("Picture.TrestleFail")));
| Picture.ScreenTypeNotSupported =>
RAISE OSError.E(
AtomList.List2(Atom.FromText("VideoVBT.Init"),
Atom.FromText("Picture.BadScreenType")));
END;
RETURN b;
END BInit;
PROCEDURE BPicture (b: Buffer): Picture.T =
BEGIN
RETURN b.pic;
END BPicture;
}}}
{{{ -- factory --
REVEAL
Factory = PublicFactory BRANDED OBJECT
st : VBT.ScreenType := NIL;
width, height: CARDINAL;
OVERRIDES
preInit := FPreInit;
reset := FReset;
newBuf := FNewBuf;
destroy := FDontDestroy;
make := FMake;
END;
VAR
fmu := NEW(MUTEX);
thread : Thread.T := NIL;
bufList: Buffer := NIL;
PROCEDURE FDontDestroy (fac: JVBuffer.Factory; buf: JVBuffer.T) =
VAR b: Buffer := buf;
BEGIN
LOCK fmu DO
IF thread = NIL THEN
thread := Thread.Fork(NEW(Thread.Closure, apply := Cleaner))
END;
INC(fladds);
b.next := bufList;
bufList := buf;
b.lastUse := 0;
b.fac := fac
END
END FDontDestroy;
VAR fladds, flsubs, flcleans := 0;
PROCEDURE Cleaner (<* UNUSED *> cl: Thread.Closure): REFANY =
VAR
useless := 0;
cleanse, cprev, prev, next: Buffer := NIL;
BEGIN
LOOP
cprev := NIL;
prev := NIL;
next := NIL;
cleanse := NIL;
Thread.Pause(3.0D0);
IO.Put(
Fmt.F(VideoVBT: adds: %s, subs: %s, cleans: %s\n
,
Fmt.Int(fladds), Fmt.Int(flsubs), Fmt.Int(flcleans)),
Stdio.stderr);
LOCK fmu DO
IF bufList = NIL THEN
INC(useless);
IF useless > 15 THEN thread := NIL; RETURN NIL END
ELSE
useless := 0;
next := bufList;
WHILE next # NIL DO
INC(next.lastUse);
IF next.lastUse <= 2 THEN
cleanse := NIL
ELSIF cleanse = NIL THEN
cleanse := next;
cprev := prev
END;
prev := next;
next := next.next
END;
IF cleanse # NIL THEN
IF cprev = NIL THEN bufList := NIL ELSE cprev.next := NIL END
END
END
END;
WHILE cleanse # NIL DO
next := cleanse.next;
TRY
INC(flcleans);
LOCK cleanse DO FDestroy(cleanse.fac, cleanse) END
EXCEPT
OSError.E, Thread.Alerted =>
END;
cleanse := next
END
END
END Cleaner;
PROCEDURE FMake (f: Factory; wait := TRUE; subtype: CARDINAL): JVBuffer.T
RAISES {Thread.Alerted, OSError.E} =
VAR prev, cur: Buffer := NIL;
BEGIN
LOCK fmu DO
cur := bufList;
WHILE cur # NIL AND (cur.st # f.st OR cur.width # f.width
OR cur.height # f.height
OR f.subtype # cur.subtype) DO
prev := cur;
cur := cur.next
END;
IF cur # NIL THEN
INC(flsubs);
IF prev = NIL THEN
bufList := cur.next
ELSE
prev.next := cur.next
END;
cur.next := NIL;
cur.fac := NIL;
RETURN cur
END;
END;
RETURN PublicFactory.make(f, wait, subtype)
END FMake;
PROCEDURE FPreInit (f: Factory; st: VBT.ScreenType; width, height: CARDINAL)
RAISES {Picture.ScreenTypeNotSupported, OSError.E} =
VAR params: Jvs.DcmpParams;
BEGIN
IF NOT Picture.Supported(st) THEN
RAISE Picture.ScreenTypeNotSupported;
END;
(* get nearest output width and height *)
params.inX := width;
params.inY := height;
params.reqX := width;
params.reqY := height;
TRY
WITH jvs = NEW(Jvs.T).init() DO
EVAL jvs.setDecompress(params);
jvs.close();
END;
EXCEPT
| Thread.Alerted => (* skip *)
END;
f.st := st;
f.width := params.outX;
f.height := params.outY;
END FPreInit;
PROCEDURE FNewBuf (f: Factory): JvsBuffer.T =
BEGIN
RETURN NEW(Buffer, st := f.st, width := f.width, height := f.height);
END FNewBuf;
PROCEDURE FReset (f: JVBuffer.Factory; b: JVBuffer.T) =
BEGIN
(* reset the dimensions of the XImage structure. The shared memory
buffers all have a fixed size. *)
WITH fac = NARROW(f, Factory),
buf = NARROW(b, Buffer),
image = Picture.Image(buf.pic) DO
buf.width := fac.width;
buf.height := fac.height;
image.width := fac.width;
image.height := fac.height;
END;
END FReset;
PROCEDURE FDestroy (fac: JVBuffer.Factory; buf: JVBuffer.T)
RAISES {Thread.Alerted, OSError.E} =
BEGIN
(* drop the Picture data structures, but keep the shared memory
segment *)
TYPECASE buf OF
| Buffer (b) =>
TRY
Picture.DetachData(b.pic);
EXCEPT
| Picture.TrestleFail =>
IO.Put(
"VideoVBT.Picture.DetachData.TrestleFail\n", Stdio.stderr);
END;
ELSE
END;
JVFromDecomp.Factory.destroy(fac, buf);
END FDestroy;
}}}
{{{ -- the T --
CONST DefaultMaxBuffers = 2;
REVEAL
T = Public BRANDED OBJECT
offset := Point.Origin;
sourceHost: TEXT;
quality : JVSink.Quality;
dparams := Jvs.DefaultDecompress;
cmap : Jvs.ColormapInfo;
sinkBuffers, decompBuffers: CARDINAL := DefaultMaxBuffers;
synchronous := FALSE;
fixedSize := FALSE;
minFrameMSecs : CARDINAL := 0;
minFrameSecs : LONGREAL := 0.0d0;
(* start a thread when nothing running and reshape from empty to
not empty and suitable screen type. a thread when reshape to
empty. stop a thread when rescreen. Thread cleans up fields in
v when it is alerted *)
starting := FALSE;
input : JVBuffer.Pool := NIL;
decomp : JVDecomp.T := NIL;
thread : Thread.T := NIL;
pauseEvent : Thread.Condition;
paused := FALSE;
statistics : JVDecomp.Statistics := NIL;
stopEvent : Thread.Condition;
startThread: Thread.T := NIL;
(* used to avoid locking the VBT tree when starting up a new
connection *)
vis := TrestleOnX.VisibilityUnobscured;
OVERRIDES
init := Init;
repaint := Repaint;
reshape := Reshape;
rescreen := Rescreen;
shape := Shape;
discard := Discard;
misc := Misc;
setQuality := SetQuality;
setSize := SetSize;
setMinFrameMSecs := SetMinFrameMSecs;
setSynchronous := SetSynchronous;
setFixedSize := SetFixedSize;
getDecomp := GetDecomp;
getSize := GetSize;
setPaused := SetPaused;
startStats := StartStats;
stopStats := StopStats;
getStats := GetStats;
END;
}}}
{{{ -- methods --
PROCEDURE Init (v : T;
sourceHost : TEXT;
quality : JVSink.Quality;
ncolours : CARDINAL := 0;
width : CARDINAL := 320;
height : CARDINAL := 240;
synchronous := FALSE;
fixedSize := FALSE;
minFrameMSecs: CARDINAL := 0 ): T =
BEGIN
v.sourceHost := sourceHost;
v.quality := quality;
v.cmap.nColors := ncolours;
v.dparams.reqX := width;
v.dparams.outX := width;
v.dparams.reqY := height;
v.dparams.outY := height;
v.synchronous := synchronous;
v.fixedSize := fixedSize;
v.minFrameMSecs := minFrameMSecs;
IF minFrameMSecs > 0 THEN
v.minFrameSecs := FLOAT(minFrameMSecs, LONGREAL) / 1000.0d0;
END;
v.pauseEvent := NEW(Thread.Condition);
v.stopEvent := NEW(Thread.Condition);
RETURN v;
END Init;
PROCEDURE Repaint (v: T; READONLY rgn: Region.T) =
VAR buff: Buffer := NIL;
BEGIN
IF v.st # NIL AND v.input # NIL THEN
LOCK v DO
buff := NARROW(v.input.getCurrentBuffer(), Buffer);
IF buff = NIL THEN RETURN; END;
END;
TRY
IF buff.pic # NIL THEN
Picture.Paint(v, buff.pic, rgn.r, delta := v.offset);
END;
EXCEPT
| Thread.Alerted => (*skip *)
END;
<* ASSERT buff # NIL *>
buff.free();
END;
END Repaint;
PROCEDURE Reshape (v: T; READONLY cd: VBT.ReshapeRec) =
BEGIN
LOCK v DO v.offset := Rect.NorthWest(v.domain); END;
IF Rect.Congruent(cd.prev, cd.new) OR v.st = NIL THEN
(* there's no video work to do *)
VBT.Leaf.reshape(v, cd);
RETURN;
END;
IF cd.new = Rect.Empty THEN
(* think about closing down the thread *)
Stop(v);
ELSE
IF cd.prev = Rect.Empty AND v.thread = NIL THEN
(* try to start a new connection *)
TYPECASE v.st OF
| XScreenType.T (xst) =>
WITH trsl = Trestle.ScreenOf(v, Point.Origin).trsl,
id = XScrnCmap.ColorMapID(xst.cmap.standard()) DO
IF trsl # NIL AND id # Jvs.IdNone THEN
LOCK v DO
IF NOT v.fixedSize THEN
v.dparams.reqX := Rect.HorSize(cd.new);
v.dparams.outX := v.dparams.reqX;
v.dparams.reqY := Rect.VerSize(cd.new);
v.dparams.outY := v.dparams.reqY;
END;
v.cmap.id := id;
v.cmap.displayName := trsl.trestleId();
v.starting := FALSE;
END;
Start(v);
END;
END;
ELSE (* we don't understand this type *)
END;
ELSE
IF NOT v.fixedSize THEN
(* change the size of the image *)
Stop(v);
LOCK v DO
v.dparams.reqX := Rect.HorSize(cd.new);
v.dparams.outX := v.dparams.reqX;
v.dparams.reqY := Rect.VerSize(cd.new);
v.dparams.outY := v.dparams.reqY;
END;
Start(v);
END;
IF cd.prev = Rect.Empty THEN Thread.Signal(v.pauseEvent); END;
END;
END;
VBT.Leaf.reshape(v, cd);
END Reshape;
<* FATAL TrestleComm.Failure *>
PROCEDURE Rescreen (v: T; READONLY cd: VBT.RescreenRec) =
BEGIN
Stop(v);
(* if we need to restart it will be done in the reshape *)
IF cd.st # NIL THEN LOCK v DO v.starting := TRUE; END; END;
VBT.Leaf.rescreen(v, cd);
END Rescreen;
PROCEDURE Shape (v: T; ax: Axis.T; <*UNUSED*> n: CARDINAL): VBT.SizeRange =
VAR res := VBT.DefaultShape;
BEGIN
IF v.st # NIL THEN
LOCK v DO
IF v.fixedSize THEN
CASE ax OF
| Axis.T.Hor =>
(* the JVboard wastes 12 pixels on the end of each line *)
res.pref := MAX(v.dparams.outX - Jvs.linePadding, 0);
| Axis.T.Ver => res.pref := v.dparams.outY;
END;
END;
END;
END;
RETURN res;
END Shape;
PROCEDURE Discard (v: T) =
BEGIN
Stop(v);
VBT.Leaf.discard(v);
END Discard;
PROCEDURE Misc (v: T; READONLY cd: VBT.MiscRec) =
BEGIN
IF cd.type = VBT.Deleted OR cd.type = VBT.Disconnected THEN
Discard(v);
END;
IF cd.type = TrestleOnX.Visibility THEN
v.vis := cd.detail[0];
Thread.Signal(v.pauseEvent);
END;
VBT.Leaf.misc(v, cd);
END Misc;
PROCEDURE SetQuality (v: T; quality: JVSink.Quality) =
BEGIN
SetPictureParams(v, v.sourceHost, quality, v.dparams, v.cmap);
END SetQuality;
PROCEDURE SetSize (v: T; width, height: CARDINAL) =
VAR dparams := v.dparams;
BEGIN
dparams.reqX := width;
dparams.reqY := height;
SetPictureParams(v, v.sourceHost, v.quality, dparams, v.cmap);
END SetSize;
PROCEDURE SetMinFrameMSecs (v: T; msecs: CARDINAL) =
BEGIN
Stop(v);
LOCK v DO
v.minFrameMSecs := msecs;
v.minFrameSecs := FLOAT(msecs, LONGREAL) / 1000.0d0;
END;
IF v.st # NIL THEN
TYPECASE v.st OF XScreenType.T => Start(v) ELSE END
END
END SetMinFrameMSecs;
PROCEDURE SetSynchronous (v: T; synchronous: BOOLEAN) =
BEGIN
LOCK v DO v.synchronous := synchronous; END;
END SetSynchronous;
PROCEDURE SetFixedSize (v: T; fixedSize: BOOLEAN) =
BEGIN
LOCK v DO v.fixedSize := fixedSize; END;
VBT.NewShape(v);
END SetFixedSize;
PROCEDURE GetDecomp (v: T): JVDecomp.T =
BEGIN
RETURN v.decomp;
END GetDecomp;
PROCEDURE GetSize (v: T; VAR width, height: CARDINAL) =
BEGIN
LOCK v DO width := v.dparams.outX; height := v.dparams.outY; END;
END GetSize;
PROCEDURE SetPaused (v: T; paused := FALSE) =
BEGIN
LOCK v DO v.paused := paused; END;
Thread.Signal(v.pauseEvent);
END SetPaused;
}}}
{{{ -- statistics --
PROCEDURE StartStats (t: T) =
BEGIN
LOCK t DO
IF t.statistics = NIL THEN
t.statistics := NEW(JVDecomp.Statistics);
END;
WITH s = t.statistics DO
s.framesStarted := 0;
s.framesProcessed := 0;
s.timesBlocked := 0;
s.cumLatency := 0;
END;
END;
END StartStats;
PROCEDURE StopStats (t: T) =
BEGIN
LOCK t DO t.statistics := NIL; END;
END StopStats;
PROCEDURE GetStats (t: T): JVDecomp.Statistics =
BEGIN
RETURN t.statistics;
END GetStats;
}}}
{{{ -- exported in VideoVBTRep.i3 --
PROCEDURE SetPictureParams ( v : T;
sourceHost: TEXT;
quality : JVSink.Quality;
READONLY dparams : Jvs.DcmpParams;
READONLY cmap : Jvs.ColormapInfo) =
VAR
name: TEXT;
id : Jvs.Id;
BEGIN
Stop(v);
LOCK v DO
v.sourceHost := sourceHost;
v.quality := quality;
v.dparams := dparams;
v.cmap := cmap;
END;
IF v.st # NIL THEN
TYPECASE v.st OF
| XScreenType.T (xst) =>
WITH trsl = Trestle.ScreenOf(v, Point.Origin).trsl DO
IF trsl # NIL THEN
name := trsl.trestleId();
id := XScrnCmap.ColorMapID(xst.cmap.standard());
IF id # Jvs.IdNone THEN
LOCK v DO v.cmap.id := id; v.cmap.displayName := name; END;
IF NOT v.fixedSize THEN
VBT.NewShape(v);
ELSE
Start(v);
END;
END;
END;
END;
ELSE (* skip *)
END;
END;
END SetPictureParams;
PROCEDURE GetPictureParams ( v : T;
VAR sourceHost: TEXT;
VAR quality : JVSink.Quality;
VAR dparams : Jvs.DcmpParams;
VAR cmap : Jvs.ColormapInfo) =
BEGIN
LOCK v DO
sourceHost := v.sourceHost;
quality := v.quality;
dparams := v.dparams;
cmap := v.cmap;
END;
END GetPictureParams;
}}}
{{{ -- local procedures --
PROCEDURE Start (v: T) =
VAR oldt := v.startThread;
BEGIN
IF oldt # NIL THEN Thread.Alert(oldt); END;
LOCK v DO v.startThread := Thread.Fork(NEW(StartClosure, v := v)); END;
END Start;
PROCEDURE Stop (v: T) =
VAR oldstart := v.startThread;
BEGIN
IF oldstart # NIL THEN Thread.Alert(oldstart); END;
(* v.thread is set to NIL at the end of Apply() *)
IF v.thread # NIL THEN
LOCK v DO
WHILE v.thread # NIL DO
Thread.Alert(v.thread);
Thread.Wait(v, v.stopEvent);
END;
END;
END;
END Stop;
}}}
{{{ -- Picture.Paint callback --
PROCEDURE FreeProc (param: REFANY) =
BEGIN
NARROW(param, Buffer).free();
END FreeProc;
}}}
{{{ -- paint thread --
TYPE Closure = Thread.Closure OBJECT v: T; OVERRIDES apply := Apply; END;
PROCEDURE Apply (cl: Closure): REFANY =
VAR
buff : Buffer := NIL;
v := cl.v;
input := v.input;
inDecomp, paused := FALSE;
lastFrameTime := Time.Now();
BEGIN
TRY
v.decomp.join();
inDecomp := TRUE;
input.join();
TRY
LOOP
IF v.paused OR v.domain = Rect.Empty
OR v.vis = TrestleOnX.VisibilityFullyObscured THEN
v.decomp.setPaused(TRUE);
paused := TRUE;
LOCK v DO
IF v.statistics # NIL THEN
INC(v.statistics.timesBlocked);
END;
WHILE v.paused OR v.domain = Rect.Empty
OR v.vis = TrestleOnX.VisibilityFullyObscured DO
Thread.AlertWait(v, v.pauseEvent);
END;
END;
v.decomp.setPaused(FALSE);
paused := FALSE;
END;
IF v.minFrameMSecs > 0 THEN
WITH waitTime = v.minFrameSecs + lastFrameTime - Time.Now() DO
IF waitTime > 0.0d0 THEN Thread.AlertPause(waitTime); END;
END;
END;
IF Thread.TestAlert() THEN RAISE Thread.Alerted; END;
buff := NARROW(input.waitForChange(), Buffer);
lastFrameTime := Time.Now();
IF buff.pic # NIL THEN
LOCK v DO
IF v.statistics # NIL THEN
INC(v.statistics.framesStarted);
v.statistics.cumLatency :=
Word.Plus(v.statistics.cumLatency,
Word.Minus(Tick.Now(), buff.localTime));
END;
END;
IF buff.ready # NIL THEN
buff.ready.endToEnd := TRUE;
END;
IF v.synchronous THEN
Picture.Paint(v, buff.pic, delta := v.offset);
buff.free();
ELSE
Picture.Paint(v, buff.pic, freeProc := FreeProc,
freeParam := buff, delta := v.offset);
END;
END;
buff := NIL;
END;
EXCEPT
| Thread.Alerted, JVBuffer.Closed => (* skip *)
END;
IF buff # NIL AND v.synchronous THEN buff.free(); END;
IF input # NIL THEN input.leave(); END;
IF paused THEN v.decomp.setPaused(FALSE); END;
LOCK v DO
(* clean up vbt fields *)
v.thread := NIL;
v.input := NIL;
IF inDecomp AND v.decomp # NIL THEN v.decomp.leave(); END;
END;
EXCEPT
| JVConverter.Error (e) =>
VAR etext := "";
BEGIN
IF e # NIL AND e.head # NIL THEN
etext := RdUtils.FailureText(e);
END;
JVConverter.ReportError("VideoVBT.Apply " & etext);
END;
| Thread.Alerted => (*skip *)
END;
LOCK v DO v.decomp := NIL; v.input := NIL; v.thread := NIL; END;
Thread.Signal(v.stopEvent);
RETURN NIL;
END Apply;
}}}
{{{ -- Start thread --
TYPE
StartClosure =
Thread.Closure OBJECT v: T; OVERRIDES apply := StartApply; END;
PROCEDURE StartApply (cl: StartClosure): REFANY =
VAR
v := cl.v;
decomp : JVDecomp.T;
jvs : Jvs.T;
factory: Factory;
failed := TRUE;
subtype := JvsBuffer.Subtype(v.dparams.outX, v.dparams.outY);
BEGIN
TRY
decomp :=
JVDecompPool.GetDecomp(
v.sourceHost, v.quality, v.dparams, v.cmap, FALSE, v.sinkBuffers,
v.decompBuffers, delay := v.minFrameMSecs, subtype := subtype);
IF decomp = NIL THEN
jvs := NEW(Jvs.T).init();
factory := NEW(Factory);
factory.preInit(v.st, v.dparams.reqX, v.dparams.reqY);
EVAL factory.init(jvs);
decomp :=
JVDecompPool.GetDecomp(
v.sourceHost, v.quality, v.dparams, v.cmap, TRUE,
v.sinkBuffers, v.decompBuffers, factory, jvs, v.minFrameMSecs,
subtype := subtype);
factory.subtype := subtype
END;
LOCK v DO
v.decomp := decomp;
v.input := decomp.getOutput();
<* ASSERT v.input # NIL *>
v.thread := Thread.Fork(NEW(Closure, v := v));
END;
failed := FALSE;
EXCEPT
| OSError.E (e) =>
VAR etext := "";
BEGIN
IF e # NIL AND e.head # NIL THEN
etext := RdUtils.FailureText(e);
END;
JVConverter.ReportError("VideoVBT.Start " & etext);
END;
| Picture.ScreenTypeNotSupported =>
JVConverter.ReportError("VideoVBT.Start.BadScreenType");
| Thread.Alerted => (* skip *)
END;
LOCK v DO v.startThread := NIL; END;
IF failed THEN LOCK v DO v.decomp := NIL; v.input := NIL; END; END;
RETURN NIL;
END StartApply;
}}}
BEGIN
EVAL JVConverter.RegisterErrorReporter(JVConverter.toStderr);
END VideoVBT.