DistributedDisplayImpl.mesa
Last Edited by: Crow, May 24, 1986 1:38:12 pm PDT
DIRECTORY
BasicTime   USING [PulsesToSeconds, GetClockPulses],
Atom     USING [PutPropOnList, GetPropFromList, GetPName],
RuntimeError  USING [UNCAUGHT],  
CedarProcess   USING [Fork, ForkableProc, Join, Process,
        Status],
Process    USING [Pause, SecondsToTicks],
ComputeServerClient USING [StartService, RemoteSuccess],
IO      USING [STREAM, PutRope],
Rope     USING [ROPE, Cat, Equal],
Convert    USING [RopeFromReal, RopeFromCard],
Real     USING [FixC, RoundC, Float],
Vector2    USING [ VEC ],
Imager    USING [ Rectangle ],
ImagerColor   USING [ RGB ],
Pixels     USING [ PixelBuffer, Extent, Copy ],
Vector3d    USING [ Pair, Triple, Quad ],
Matrix3d    USING [ Matrix ],
ThreeDScenes  USING [ Box, ClipState, Context, Create, Error, FillInBackGround,
         FillViewPort, GetShading, SetWindow, SetViewPort,
         ShadingProcs, ShapeInstance, ShapeSequence, SixSides, Vertex,
         VtxToRealSeqProc, XfmToDisplay, XfmToEyeSpace ],
ScanConvert   USING [ GetColorProc ],
TextureMaps   USING [ TextureMap ],
SolidTextures   USING [ ProcToRope ],
AISAnimation  USING [ GetAIS ],
ThreeDMisc   USING [ StartLog, PrependWorkingDirectory,
         FlushLog];
DistributedDisplayImpl: CEDAR PROGRAM
IMPORTS Pixels, ThreeDScenes, Real, IO, AISAnimation, RuntimeError, CedarProcess, Process, ComputeServerClient, Atom, Rope, Convert, BasicTime, ThreeDMisc, SolidTextures
EXPORTS ThreeDMisc
~ BEGIN
Types
Context: TYPE ~ ThreeDScenes.Context;
Pair: TYPE ~ Vector3d.Pair;           -- RECORD [ x, y: REAL];
Triple: TYPE ~ Vector3d.Triple;
RGB: TYPE ~ ImagerColor.RGB;
Rectangle: TYPE ~ Pixels.Extent;
NatSequence: TYPE ~ RECORD [ SEQUENCE length: NAT OF NAT ];
IntSequence: TYPE ~ RECORD [ SEQUENCE length: NAT OF INT ];
tooManyTries: NAT ← 16;     -- number of retries before process gives up
pauseTime: NAT ← 10;     -- time to wait before retry when no processors available
Utility Procedures
ElapsedTime: PROC[startTime: REAL] RETURNS[Rope.ROPE] ~ {
RETURN[ Rope.Cat[
Convert.RopeFromCard[
Real.FixC[BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] - startTime]
],
" secs. "
] ];
};
CurrentTime: PROC[] RETURNS[REAL] ~ {
RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ];
};
Multiprocessor Frame Generation
RemoteMakeFrame: PUBLIC PROC[context: REF ThreeDScenes.Context, numForksHint: NAT ← 0] ~ {
Uses compute server to parcel out rendering to other processors
bufferContext: REF ThreeDScenes.Context;
log: IO.STREAMNARROW[ Atom.GetPropFromList[context.props, $Log] ];
shape: REF ThreeDScenes.ShapeSequence ← context.shapes;
numShapes: NAT ← shape.length - context.lights.length;
shapeOrder: REF NatSequence ← NEW[NatSequence[numShapes]];
startTime: REAL ← CurrentTime[];
IF log = NIL THEN log ← ThreeDMisc.StartLog[context];  -- open log file if not yet done
Get Everything (including light centroids) into eyespace
FOR i: NAT IN [0.. shape.length) DO
IF Atom.GetPropFromList[shape[i].props, $Hidden] = NIL OR shape[i].type = $Light
THEN IF shape[i].vtcesInValid THEN { 
shape[i].clipState ← ThreeDScenes.XfmToEyeSpace[context, shape[i]];
IF shape[i].clipState # out
THEN ThreeDScenes.XfmToDisplay[context, shape[i] ];
};
ENDLOOP;
Sort Objects and Parcel out to servers
numShapes ← SortAndForkProcs[context, shapeOrder, startTime, log, numForksHint];
Prepare for display
ThreeDScenes.FillViewPort[context, [0.0, 0.0, 0.0] ];  -- clear all pixel bits
ThreeDScenes.FillInBackGround[context];   -- load background
bufferContext ← ThreeDScenes.Create[     -- get buffer
width: Real.FixC[context.viewPort.w],
height: Real.FixC[context.viewPort.h],
renderMode: $FullClr,
alpha: TRUE
];
Display shapes back to front as soon as they complete
FOR i: NAT DECREASING IN [0..numShapes) DO
procList: LIST OF CedarProcess.Process ← NARROW[ Atom.GetPropFromList[
context.shapes[shapeOrder[i]].props,
$RemoteProc
] ];
baseName: Rope.ROPE ← context.shapes[shapeOrder[i]].name;
baseScreenExtent: ThreeDScenes.Box ← context.shapes[shapeOrder[i]].screenExtent;
j: NAT ← 0;
FOR procs: LIST OF CedarProcess.Process ← procList, procs.rest UNTIL procs = NIL DO
shapeName: Rope.ROPE ← Rope.Cat[baseName, Convert.RopeFromCard[j]];
results: REF;
msg: Rope.ROPE;
status: CedarProcess.Status;
[status, results] ← CedarProcess.Join[procs.first];    -- await termination
msg ← NARROW[results, Rope.ROPE];
SELECT status FROM
done => {
doneTime: REAL ← CurrentTime[];
context.shapes[shapeOrder[i]].name ← shapeName;
DisplayRemoteResult[ context, bufferContext, context.shapes[shapeOrder[i]], log ];
msg ← Rope.Cat[msg, shapeName, " matted in ", ElapsedTime[doneTime] ];
log.PutRope[Rope.Cat[msg, " done at ", ElapsedTime[startTime], "\n" ] ];
};
ENDCASE => SIGNAL ThreeDScenes.Error[[$Condition, "Forked process failed"]];
j ← j + 1;
ENDLOOP;
context.shapes[shapeOrder[i]].name ← baseName;
context.shapes[shapeOrder[i]].screenExtent ← baseScreenExtent
ENDLOOP;
log.PutRope[Rope.Cat[" All done at ", ElapsedTime[startTime], "\n\n" ] ];
ThreeDMisc.FlushLog[context];
};
SortAndForkProcs: PROC[context: REF ThreeDScenes.Context, shapeOrder: REF NatSequence,
        startTime: REAL, log: IO.STREAM, numForksHint: NAT]
      RETURNS [NAT] ~ {
Sort Objects
costFactor: REF IntSequence ← NEW[IntSequence[context.shapes.length]];
totalCost, averageCost: INT ← 0;
shape: REF ThreeDScenes.ShapeSequence ← context.shapes;
shapesFound: NAT ← 0;
numShapes: NAT ← context.shapes.length - context.lights.length;
FOR i: NAT IN [0..context.shapes.length) DO
IF shape[i] # NIL AND Atom.GetPropFromList[shape[i].props, $Hidden] = NIL THEN
IF shape[i].clipState # out AND shape[i].surface # NIL THEN {
Estimate relative cost of scan conversion (untamed heuristics here)
costFactor[i] ←  INT[(shape[i].screenExtent.right - shape[i].screenExtent.left)]
    * (shape[i].screenExtent.top - shape[i].screenExtent.bottom);
IF ThreeDScenes.GetShading[shape[i], $Transmittance] # NIL
              AND
shape[i].insideVisible
THEN costFactor[i] ← costFactor[i] * 2;
IF ThreeDScenes.GetShading[ shape[i], $TextureMap ] # NIL
THEN costFactor[i] ← costFactor[i] * 2;
IF ThreeDScenes.GetShading[ shape[i], $ShadingProcs ] # NIL
THEN costFactor[i] ← costFactor[i] * 2;   -- may be much higher
Bubble sort shapes, numShapes assumed quite small
FOR j: NAT DECREASING IN [0..shapesFound) DO
IF shape[i].centroid.ez < shape[shapeOrder[j]].centroid.ez
THEN { shapeOrder[j+1] ← shapeOrder[j]; shapeOrder[j] ← i; }
ELSE { shapeOrder[j+1] ← i; EXIT; };
ENDLOOP;
IF shapesFound = 0 THEN shapeOrder[0] ← i;
shapesFound ← shapesFound + 1;
};
ENDLOOP;
log.PutRope[Rope.Cat["\nObject transform and sort: ", ElapsedTime[startTime], "\n" ]];
Find average scan conversion cost
FOR i: NAT IN [0..shapesFound) DO totalCost ← totalCost + costFactor[shapeOrder[i]]; ENDLOOP;
averageCost ← totalCost / shapesFound;
IF shapesFound < numForksHint      -- allows division of single-object scenes
THEN averageCost ← averageCost / (numForksHint / shapesFound); -- note DIV fn
Parcel out shapes to servers
FOR i: NAT DECREASING IN [0..shapesFound) DO   -- do back to front
wholeWindow: Imager.Rectangle ← context.window;
wholeViewPort: Imager.Rectangle ← context.viewPort;
baseName: Rope.ROPE ← context.shapes[shapeOrder[i]].name;
screenExtent: ThreeDScenes.Box ← context.shapes[shapeOrder[i]].screenExtent;
procList: LIST OF CedarProcess.Process ← NIL;
imageCount: NAT ← Real.RoundC[ Real.Float[ costFactor[shapeOrder[i]] ] / averageCost ];
IF imageCount < 1 THEN imageCount ← 1;
FOR j: NAT IN [0..imageCount) DO
cmd: Rope.ROPE;
Set Window to clip object apart
viewPortSize: REALMAX[wholeViewPort.w, wholeViewPort.h];
vuPtWindow: Imager.Rectangle ← [
x: MAX[ 0.0, wholeViewPort.h - wholeViewPort.w ] / viewPortSize,
y: MAX[ 0.0, wholeViewPort.w - wholeViewPort.h ] / viewPortSize,
w: 1.0,
h: 1.0
];
left: REAL ← screenExtent.left
   + (Real.Float[j] / imageCount) * (screenExtent.right - screenExtent.left);
right: REAL ← screenExtent.left
   + (Real.Float[j+1] / imageCount) * (screenExtent.right - screenExtent.left);
top: REAL ← screenExtent.top;
bottom: REAL ← screenExtent.bottom;
left ← left - 1;            -- add one pixel border on left
right ← right + 1;           -- add one pixel border on right
ThreeDScenes.SetWindow[ context, [
x: (2.0 * left / viewPortSize) - 1.0 + vuPtWindow.x, -- window range is -1.0 — +1.0
y: (2.0 * bottom / viewPortSize) - 1.0 + vuPtWindow.y,
w: 2.0 * (right - left) / viewPortSize,
h: 2.0 * (top - bottom) / viewPortSize
] ];
ThreeDScenes.SetViewPort[ context, [left, bottom, right - left, top - bottom]];
context.shapes[shapeOrder[i]].name ← Rope.Cat[ baseName, Convert.RopeFromCard[j] ];
Build Command rope
cmd ← RopeFromShapeInstance[ context, context.shapes[shapeOrder[i]] ];
Fork new Process
procList ← CONS[ CedarProcess.Fork[StartRemote, cmd ], procList]; -- fork process
ENDLOOP;
context.shapes[shapeOrder[i]].props ← Atom.PutPropOnList[ -- store proc refs with shape
context.shapes[shapeOrder[i]].props,
$RemoteProc,
procList          -- leave list of proc refs here
];
context.shapes[shapeOrder[i]].name ← baseName;
ThreeDScenes.SetWindow[ context, wholeWindow];
ThreeDScenes.SetViewPort[ context, wholeViewPort];
ENDLOOP;
RETURN[shapesFound];
};
DisplayRemoteResult: PROC[context, bufferContext: REF ThreeDScenes.Context,
         shape: REF ThreeDScenes.ShapeInstance, log: IO.STREAM] ~ {
width, height: NAT;
xOffset: NAT ← shape.screenExtent.left -1;
yOffset: NAT ← shape.screenExtent.bottom;
[width, height] ← AISAnimation.GetAIS[       -- read image into buffer
context: bufferContext,
fileRoot: ThreeDMisc.PrependWorkingDirectory[context, Rope.Cat[shape.name, ".ais"] ],
xOffset: xOffset,
yOffset: yOffset,
center: FALSE
! RuntimeError.UNCAUGHT => {
log.PutRope[Rope.Cat[" Failed to load - ", shape.name, "\n"] ];
GO TO GiveUp;
}
];
shape.screenExtent.left ← shape.screenExtent.left + width -2; -- for next slice (minus border)
Pixels.Copy[              -- write over previous objects, etc.
destination: context.display,
source: bufferContext.display,
destArea: [xOffset+1, yOffset, width-2, height],
srcArea: [xOffset+1, yOffset, width-2, height],
op: $WriteOver
];
EXITS GiveUp => NULL
};
StartRemote: CedarProcess.ForkableProc ~ {
PROC [data: REF] RETURNS [results: REFNIL]
retries: NAT ← 0;
startTime: REAL ← CurrentTime[];
found: BOOLEAN;
success: ComputeServerClient.RemoteSuccess ← false;
returnMsg, successMsg, procMsg, server: Rope.ROPENIL;
WHILE success # true AND success # aborted AND retries < tooManyTries DO
serversTried: LIST OF Rope.ROPENIL;
WaitOnRetry: PROC [server: Rope.ROPE] ~ {
FOR servers: LIST OF Rope.ROPE ← serversTried, servers.rest UNTIL servers = NIL DO
IF Rope.Equal[ servers.first, server, FALSE ] THEN { -- has server rejected us before?
Process.Pause[ Process.SecondsToTicks[pauseTime] ];  -- wait decent interval
serversTried ← NIL;
RETURN[];
};
ENDLOOP;
serversTried ← CONS[ server, serversTried ];
};
[ found: found, success: success, remoteMsg: procMsg, serverInstance: server ] ←
ComputeServerClient.StartService[
service: "RenderOneShape",
cmdLine: NARROW[ data, Rope.ROPE ],
in: NIL, out: NIL
];
SELECT success FROM
true => {};
false => {
IF ~found
THEN successMsg ← "compute server command not found"
ELSE successMsg ← "compute server success was false (Computer Server bug?)";
};
aborted   => successMsg ← "compute server command aborted";
communicationFailure => successMsg ← "communicationFailure";
cantImportController => successMsg ← "cantImportController";
cantImportServer => successMsg ← "cantImportServer";
serverTooBusy  => {
successMsg ← "serverTooBusy";
WaitOnRetry[server];
}
ENDCASE  => successMsg ← "unknown error code";
successMsg ← Rope.Cat["Server: ", server, " - ", successMsg];
IF procMsg # NIL
THEN successMsg ← Rope.Cat[successMsg, "\nProcess Message: ", procMsg, "\n"];
returnMsg ← Rope.Cat[returnMsg, successMsg];
retries ← retries + 1;
IF retries = tooManyTries AND success # true
THEN successMsg ← Rope.Cat[successMsg, "\t\tMaximum number of tries"];
ENDLOOP;
returnMsg ← Rope.Cat[returnMsg, " took ", ElapsedTime[startTime] ];
RETURN[returnMsg];
};
RopeFromShapeInstance: PROC[ context: REF ThreeDScenes.Context,
          shape: REF ThreeDScenes.ShapeInstance ]
        RETURNS[ Rope.ROPE ] ~ {
Vec3toRope: PROC[ r1, r2, r3: REAL] RETURNS[Rope.ROPE] ~ {
rope: Rope.ROPE;
rope ← Rope.Cat[ " ", Convert.RopeFromReal[r1], " ", Convert.RopeFromReal[r2] ];
rope ← Rope.Cat[ rope, " ", Convert.RopeFromReal[r3], " " ];
RETURN[ rope ];
};
Vec4toRope: PROC[ r1, r2, r3, r4: REAL] RETURNS[Rope.ROPE] ~ {
rope: Rope.ROPE;
rope ← Rope.Cat[ " ", Convert.RopeFromReal[r1], " ", Convert.RopeFromReal[r2], " " ];
rope ← Rope.Cat[ rope, Convert.RopeFromReal[r3], " ", Convert.RopeFromReal[r4], " " ];
RETURN[ rope ];
};
ref: REF ANYNIL;
shadingType: ATOMNIL;
color: REF RGBNIL;
shininess, transmittance: REF REALNIL;
texture: REF TextureMaps.TextureMap ← NIL;
getVtxProc: ThreeDScenes.VtxToRealSeqProc ← NIL;
getColorProc: ScanConvert.GetColorProc ← NIL;
xfm: Matrix3d.Matrix ← shape.position;
clip: ARRAY ThreeDScenes.SixSides OF Vector3d.Quad ← context.clippingPlanes;
cmd: Rope.ROPE" ";
File Name and Surface Type
cmd ← Rope.Cat[cmd, " Shape: ", shape.fileName, " ", Atom.GetPName[shape.type]];
IF shape.insideVisible
THEN cmd ← Rope.Cat[cmd, " TRUE "]
ELSE cmd ← Rope.Cat[cmd, " FALSE "];
Color
color ← NARROW[ThreeDScenes.GetShading[ shape, $Color ], REF RGB];
shadingType ← NARROW[ThreeDScenes.GetShading[ shape, $Type ], ATOM]; 
IF shadingType = $Smooth AND color # NIL
THEN cmd ← Rope.Cat[cmd, " SmoothColor: ", Vec3toRope[color.R, color.G, color.B] ];
IF shadingType = $Faceted AND color # NIL
THEN cmd ← Rope.Cat[cmd, " FacetedColor: ", Vec3toRope[color.R, color.G, color.B] ];
Transmittance
transmittance ← NARROW[ThreeDScenes.GetShading[ shape, $Transmittance ], REF REAL ];
IF transmittance # NIL THEN cmd ← Rope.Cat[cmd, " Transmittance: ",            Convert.RopeFromReal[transmittance^] ];
Shininess
shininess ← NARROW[ThreeDScenes.GetShading[ shape, $Shininess ], REF REAL ];
IF shininess # NIL THEN cmd ← Rope.Cat[cmd, " Shininess: ",
            Convert.RopeFromReal[shininess^] ];
Mapped Texture
texture ← NARROW[ ThreeDScenes.GetShading[ shape, $TextureMap ],
      REF
TextureMaps.TextureMap ];
IF texture # NIL THEN {
fileName: Rope.ROPENARROW[ Atom.GetPropFromList[texture.props, $FileName] ];
coordType: ATOMNARROW[ Atom.GetPropFromList[texture.props, $CoordType] ];
coords: REF ← Atom.GetPropFromList[texture.props, $Coords];
IF coordType = NIL THEN coordType ← $NoCoords;
cmd ← Rope.Cat[cmd, " MapTexture: ", fileName, " ", Atom.GetPName[texture.type] ];
cmd ← Rope.Cat[cmd, " ", Atom.GetPName[coordType], " " ];
SELECT coordType FROM
$FromVtxNos, $FromNormals => {
argList: LIST OF REALNARROW[coords];
WHILE argList # NIL DO
cmd ← Rope.Cat[cmd, Convert.RopeFromReal[argList.first], " " ];
argList ← argList.rest;
ENDLOOP;
};
$File => cmd ← Rope.Cat[cmd, " ", NARROW[coords, Rope.ROPE] ];
ENDCASE => SIGNAL ThreeDScenes.Error[[$MisMatch, "Bad texture coordinate type"]];
};
Solid Texture
ref ← ThreeDScenes.GetShading[ shape, $ShadingProcs];
IF ref # NIL THEN {
procName: Rope.ROPE;
[getVtxProc, getColorProc] ← NARROW[ ref, REF ThreeDScenes.ShadingProcs ]^;
IF getColorProc # NIL THEN {
procName ← SolidTextures.ProcToRope[getColorProc];
cmd ← Rope.Cat[cmd, " SolidTexture: ", procName, " " ];
};
};
Light Source
FOR i: NAT IN [0..context.lights.length) DO
color ← NARROW[ThreeDScenes.GetShading[ context.lights[i], $Color ], REF RGB];
cmd ← Rope.Cat[cmd, " Light: ", context.lights[i].name,
Vec3toRope[
context.lights[i].location.x, context.lights[i].location.y, context.lights[i].location.z ],
Vec3toRope[color.R, color.G, color.B]
];
ENDLOOP;
Shape Transform
cmd ← Rope.Cat[cmd, " ShapeXfm: ",
     Vec4toRope[xfm[0][0], xfm[0][1], xfm[0][2], xfm[0][3]],
     Vec4toRope[xfm[1][0], xfm[1][1], xfm[1][2], xfm[1][3]] ];
cmd ← Rope.Cat[cmd,
     Vec4toRope[xfm[2][0], xfm[2][1], xfm[2][2], xfm[2][3]],
     Vec4toRope[xfm[3][0], xfm[3][1], xfm[3][2], xfm[3][3]] ];
View Parameters
cmd ← Rope.Cat[cmd, " View: ",
Vec3toRope[context.eyePoint.x, context.eyePoint.y, context.eyePoint.z],
Vec3toRope[context.ptOfInterest.x, context.ptOfInterest.y, context.ptOfInterest.z],
Vec3toRope[context.upDirection.x, context.upDirection.y, context.upDirection.z]
];
cmd ← Rope.Cat[cmd,
Vec4toRope[ context.rollAngle, context.fieldOfView, context.hitherLimit, context.yonLimit ]
];
Window
cmd ← Rope.Cat[cmd, " Window: ",
Vec4toRope[context.window.x, context.window.y, context.window.w, context.window.h]
];
ViewPort
cmd ← Rope.Cat[cmd, " ViewPort: ",             -- ViewPort
Vec4toRope[context.viewPort.x, context.viewPort.y, context.viewPort.w, context.viewPort.h] ];
AIS result file name root
cmd ← Rope.Cat[cmd, " ResultFile: ",
    ThreeDMisc.PrependWorkingDirectory[context, Rope.Cat[shape.name, ".ais"] ] ];
RETURN[cmd];
};
END.