DIRECTORY BasicTime USING [ PulsesToSeconds, GetClockPulses, Now, GMT, Period ], Atom USING [ PropList, PutPropOnList, GetPropFromList, RemPropFromList, DottedPair, DottedPairNode ], Process USING [ Pause, SecondsToTicks ], CedarProcess USING [ Abort, CheckAbort, Fork, ForkableProc, GetStatus, Join, Process, Status ], RPC USING [ CallFailed ], ComputeServerClient USING [ RemoteSuccess, StartService ], ComputeClientExtras USING [ RemoteProcessSite ], ComputeClientInternal USING [ ControllerInterface ], ComputeServerControllerRpcControl USING [ InterfaceRecord ], FS USING [ StreamOpen ], IO USING [ Close, PutRope, STREAM, SetIndex, UnsafeGetBlock, UnsafePutBlock ], Rope USING [ ROPE, Cat, Substr, Index, Length, Equal ], Convert USING [ RopeFromCard, RopeFromInt, RopeFromReal, RealFromRope, RopeFromTime, TimeFromRope ], Real USING [ Fix, RoundC, Float, RoundI ], Imager USING [ Rectangle ], Pixels USING [ PixelBuffer, Extent, GetScanSeg, PutScanSeg, SampleSetSequence, SampleSet, GetSampleSet, PixelOp ], SampleMapOps USING [ GetPointer ], ThreeDBasics USING [ Box, ClipState, Context, NatSequence, Pair, Quad, RealSequence, Rectangle, RGB, ShapeInstance, ShapeSequence, SixSides, Triple, Vertex ], ThreeDScenes USING [ AddAlphaBuffer, Create, DisplayFromVM, Error, FillInBackGround, FillViewPort, GetShading, ReadScene, SetEyeSpace, SetWindow, SetViewPort, WriteScene, XfmToDisplay, XfmToEyeSpace ], ThreeDMisc USING [ CombineBoxes, CopyContextData, CopyDisplayData, StartLog, FlushLog, PrependWorkingDirectory, MakeFrame ]; DistributedDisplayImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, CedarProcess, ComputeServerClient, ComputeClientExtras, ComputeClientInternal, Convert, FS, IO, Pixels, Process, Real, Rope, RPC, SampleMapOps, ThreeDMisc, ThreeDScenes EXPORTS ThreeDMisc ~ BEGIN Context: TYPE ~ ThreeDBasics.Context; ShapeSequence: TYPE ~ ThreeDBasics.ShapeSequence; ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance; BoolSequence: TYPE ~ RECORD [ length: NAT _ 0, data: SEQUENCE maxLength: NAT OF BOOLEAN]; Pair: TYPE ~ ThreeDBasics.Pair; -- RECORD [ x, y: REAL]; Triple: TYPE ~ ThreeDBasics.Triple; RealSequence: TYPE ~ ThreeDBasics.RealSequence; RGB: TYPE ~ ThreeDBasics.RGB; Rectangle: TYPE ~ ThreeDBasics.Rectangle; NatSequence: TYPE ~ ThreeDBasics.NatSequence; IntSequence: TYPE ~ RECORD [ length: NAT _ 0, data: SEQUENCE maxLength: NAT OF INT ]; StreamPair: TYPE ~ RECORD [ in, out: IO.STREAM ]; CountedCondition: TYPE ~ RECORD [ count: NAT, condition: CONDITION ]; stopMe: BOOLEAN _ FALSE; -- external stop signal masterTimeOut: REAL _ 1800; -- 1/2 hr, max time for a multiprocess picture remoteProcessTimeOut: INT _ 300; -- 5 min., max time for remote process maxProcClonesAllowed: NAT ~ 3; -- times process may be replicated to get better service procClonesAllowed: NAT _ 3; timesKept: NAT ~ 5; -- number of timings recorded and summed costPerSecond: INT _ 200; -- cost measure for 1 second compute time imageSpaceDivision: BOOLEAN _ FALSE; -- for forcing image subdivision method computingRemote: BOOLEAN _ TRUE; -- flag for forcing local execution computingSerially: BOOLEAN _ FALSE; -- flag for forcing serialization noCostAdjustments: BOOLEAN _ FALSE; -- flag for skipping optimization with cost estimation serverStats: Rope.ROPE _ NIL; -- Parameters for Server statistics serverStatsTime: REAL _ 0.0; serverStatsWanted: BOOLEAN _ FALSE; serverStatsOut: IO.STREAM _ NIL; minimumUsefulServerPortion: REAL _ .5; showBoxes, showGaps: BOOLEAN _ FALSE; -- pedagogical aids showBoxCoverage: REAL _ 0.2; Ceiling: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.RoundI[in]; IF Real.Float[out] < in THEN out _ out + 1; }; Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.RoundI[in]; IF Real.Float[out] > in THEN out _ out - 1; }; RopeFromSeconds: PROC[seconds: REAL] RETURNS[Rope.ROPE] ~ { RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[100.0 * seconds] / 100.0 ] ] ]; }; ElapsedTime: PROC[startTime: REAL] RETURNS[Rope.ROPE] ~ { timeX100: REAL _ 100.0 *(BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] - startTime); RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[timeX100] / 100.0 ], " s. " ] ]; }; CurrentTime: PROC[] RETURNS[REAL] ~ { RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ]; }; AddTimes: PROC[msg: Rope.ROPE, times: REF RealSequence] RETURNS[REF RealSequence] ~ { newTimes: REF RealSequence _ NEW[RealSequence[timesKept]]; pos, pos2: NAT _ 0; pos _ MIN[ Rope.Index[msg, pos, "set-up: "] + 8, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; IF pos2 <= pos THEN RETURN [times] ELSE newTimes[0] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[0] _ times[0] + newTimes[0]; pos _ MIN[ Rope.Index[msg, pos, "rendering: "] + 11, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[1] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[1] _ times[1] + newTimes[1]; pos _ MIN[ Rope.Index[msg, pos, "output: "] + 8, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[2] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[2] _ times[2] + newTimes[2]; pos _ MIN[ Rope.Index[msg, pos, "total: "] + 7, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; IF pos2 <= pos THEN newTimes[3] _ newTimes[0] + newTimes[1] + newTimes[2] -- no total ELSE newTimes[3] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[3] _ times[3] + newTimes[3]; pos _ MIN[ Rope.Index[msg, pos, "matted in: "] + 11, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[4] _ 0.0; IF pos2 > pos THEN newTimes[4] _ Convert.RealFromRope[Rope.Substr[msg, pos, pos2 - pos]]; times[4] _ times[4] + newTimes[4]; RETURN [times]; }; RemoteStop: PUBLIC ENTRY PROC[] ~ { stopMe _ TRUE; }; GetProcess: ENTRY PROC[processCount: REF CountedCondition] ~ { ENABLE UNWIND => NULL; WHILE processCount.count <= 0 DO WAIT processCount.condition; ENDLOOP; processCount.count _ processCount.count - 1; }; ReleaseProcess: ENTRY PROC[processCount: REF CountedCondition] ~ { processCount.count _ processCount.count + 1; NOTIFY processCount.condition; }; SortShapes: PROC[context: REF Context, numForksHint: NAT] RETURNS [REF NatSequence] ~ { shapeOrder: REF NatSequence; { shape: REF ShapeSequence _ context.shapes; shapeOrder _ NEW[ NatSequence[ 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 { FOR j: NAT DECREASING IN [0..shapeOrder.length) 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 shapeOrder.length = 0 THEN shapeOrder[0] _ i; shapeOrder.length _ shapeOrder.length + 1; }; ENDLOOP; }; { occlusionList: LIST OF REF ShapeInstance; Overlap: PROC[box1, box2: ThreeDBasics.Box] RETURNS[ BOOLEAN] ~ { IF box1.left > box2.right OR box2.left > box1.right OR box1.bottom > box2.top OR box2.bottom > box1.top THEN RETURN[FALSE] ELSE RETURN[TRUE]; }; FOR i: NAT IN [0..shapeOrder.length) DO occlusionList _ NIL; FOR j: NAT IN [0..i) DO IF Overlap[context.shapes[shapeOrder[i]].screenExtent, context.shapes[shapeOrder[j]].screenExtent] THEN occlusionList _ CONS[context.shapes[shapeOrder[j]], occlusionList]; ENDLOOP; context.shapes[shapeOrder[i]].props _ Atom.PutPropOnList[ -- list of occluding shapes context.shapes[shapeOrder[i]].props, $Occlusions, occlusionList ]; ENDLOOP; }; RETURN[ shapeOrder ]; }; CostHeuristic: PROC[shape: REF ShapeInstance] RETURNS[seconds: INT] ~ { cost: INT _ INT[(shape.screenExtent.right - shape.screenExtent.left)] * (shape.screenExtent.top - shape.screenExtent.bottom); IF shape.type # $ConvexPolygon THEN cost _ cost * 3; IF ThreeDScenes.GetShading[shape, $Transmittance] # NIL AND shape.insideVisible THEN cost _ cost * 2; IF ThreeDScenes.GetShading[ shape, $TextureMap ] # NIL THEN cost _ cost * 2; IF ThreeDScenes.GetShading[ shape, $ShadingProcs ] # NIL THEN cost _ cost * 4; seconds _ cost / costPerSecond; }; ServerUsage: ENTRY PROC[process: CedarProcess.Process] RETURNS[server: Rope.ROPE, pctAvailable: REAL ] ~ { ENABLE UNWIND => NULL; IF computingRemote = FALSE THEN RETURN[ NIL, 1.0 ]; -- not using compute server server _ ComputeClientExtras.RemoteProcessSite[process.process]; IF CurrentTime[] - serverStatsTime > 5.0 THEN { newserverStats: Rope.ROPE; success: ATOM; controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord _ ComputeClientInternal.ControllerInterface; IF controllerInterface # NIL THEN [success, newserverStats] _ controllerInterface.clientStubGenericToController[ interface: controllerInterface, requestCode: $ServerLoads, requestString: NIL ! RPC.CallFailed => CONTINUE ]; IF success = $success THEN { serverStats _ newserverStats; serverStatsTime _ CurrentTime[]; IF serverStatsOut # NIL THEN serverStatsOut.PutRope[Rope.Cat["\n\n", newserverStats]]; }; }; pctAvailable _ 101.0; -- indicates no answer IF server # NIL THEN { aveBackgroundLoad, nonBackgroundCPULoad: REAL _ 0.0; pos: INT _ Rope.Index[serverStats, 0, server] + Rope.Length[server] + 1; pos2: INT _ Rope.Index[serverStats, pos, ","]; IF pos2 > pos THEN { -- if not at end of string aveBackgroundLoad _ Convert.RealFromRope[ Rope.Substr[serverStats, pos, pos2 - pos] ]; pos _ pos2 + 1; pos2 _ Rope.Index[serverStats, pos, ","]; nonBackgroundCPULoad _ Convert.RealFromRope[ Rope.Substr[serverStats, pos, pos2 - pos] ]; IF nonBackgroundCPULoad > 1.0 OR nonBackgroundCPULoad < 0.0 OR aveBackgroundLoad < -1.0 OR aveBackgroundLoad > 100.0 THEN SIGNAL ThreeDScenes.Error[[ $MisMatch, Rope.Cat[server, " - ", serverStats] ]]; nonBackgroundCPULoad _ MAX[0.0, MIN[1.0, nonBackgroundCPULoad]]; aveBackgroundLoad _ MAX[0.0, MIN[5.0, aveBackgroundLoad]]; IF serverStatsOut # NIL AND nonBackgroundCPULoad > 0.0 THEN { serverStatsOut.PutRope[ Rope.Cat[ "\n", server] ]; serverStatsOut.PutRope[ Rope.Cat[ " - load: ", Convert.RopeFromReal[nonBackgroundCPULoad], " pctAvailable: ", Convert.RopeFromReal[ (1.0 - nonBackgroundCPULoad) / MAX[1.0, aveBackgroundLoad] ] ] ]; }; }; pctAvailable _ (1.0 - nonBackgroundCPULoad) / MAX[1.0, aveBackgroundLoad]; }; }; KillProcs: PROC[ context: REF Context, procList: LIST OF CedarProcess.Process ] ~ { FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO CedarProcess.Abort[procs.first]; ENDLOOP; }; JoinProcs: PROC[ context: REF Context, procList: LIST OF CedarProcess.Process, log: IO.STREAM, startTime: REAL ] ~ { procTimes: REF RealSequence _ NEW[ RealSequence[5] ]; FOR i: NAT IN [0..5) DO procTimes[i] _ 0.0; ENDLOOP; log.PutRope[Rope.Cat[" Processes all forked at: ", ElapsedTime[startTime], "\n" ]]; FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO status: CedarProcess.Status _ busy; result: REF; returnMsg: Rope.ROPE; WHILE status = busy DO -- wait loop for process completion IF context.stopMe OR CurrentTime[] - startTime > masterTimeOut -- bailouts THEN { KillProcs[context, procs]; RETURN[]; }; status _ CedarProcess.GetStatus[procs.first]; IF status # busy THEN LOOP; Process.Pause[ Process.SecondsToTicks[1] ]; ENDLOOP; [status, result] _ CedarProcess.Join[procs.first]; -- wait for process completion procs.first _ NIL; -- drop REF returnMsg _ NARROW[result]; IF status # done THEN returnMsg _ Rope.Cat[" process failed - ", returnMsg] ELSE [procTimes] _ AddTimes[returnMsg, procTimes]; -- get timings log.PutRope[Rope.Cat[returnMsg, "\n" ] ]; ENDLOOP; procList _ NIL; -- drop REF log.PutRope[Rope.Cat["Totals - set-up: ", Convert.RopeFromReal[procTimes[0]] ] ]; log.PutRope[Rope.Cat[" rendering: ", Convert.RopeFromReal[procTimes[1]] ] ]; log.PutRope[Rope.Cat[" output: ", Convert.RopeFromReal[procTimes[2]] ] ]; log.PutRope[Rope.Cat[" Grand Total: ", Convert.RopeFromReal[procTimes[3]] ] ]; log.PutRope[Rope.Cat[" Matting: ", Convert.RopeFromReal[procTimes[4]], "\n"] ]; }; GetTempContext: PROC[context: REF Context, name: Rope.ROPE, killBackground: BOOLEAN, shapes: REF ShapeSequence, cost, processCount, startTime: REF] RETURNS[tmpCtx: REF Context] ~ { tmpCtx _ NEW[ Context ]; ThreeDMisc.CopyContextData[dstCtx: tmpCtx, srcCtx: context]; ThreeDMisc.CopyDisplayData[dstCtx: tmpCtx, srcCtx: context]; IF killBackground THEN tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $BackGround, NIL] -- no bckgrd ELSE tmpCtx.props _ Atom.PutPropOnList[ -- copy background from context tmpCtx.props, $BackGround, Atom.GetPropFromList[context.props, $BackGround] ]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $ComputeCost, cost ]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $ProcessCount, processCount]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $StartTime, startTime]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $SubImageName, name]; tmpCtx.display.props _ Atom.PutPropOnList[tmpCtx.display.props, $ImagerContext, NIL]; tmpCtx.shapes _ NEW[ ShapeSequence[ context.lights.length + shapes.length] ]; FOR i: NAT IN [0..context.lights.length) DO -- retain all light sources tmpCtx.shapes[i] _ NEW[ ShapeInstance _ context.lights[i]^ ]; ENDLOOP; FOR i: NAT IN [0 .. shapes.length) DO j: NAT _ i + context.lights.length; tmpCtx.shapes[j] _ NEW[ ShapeInstance _ shapes[i]^ ]; -- add each shape tmpCtx.shapes[j].props _ tmpCtx.shapes[j].shadingProps _ NIL; FOR list: Atom.PropList _ shapes[i].props, list.rest UNTIL list = NIL DO element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; tmpCtx.shapes[j].props _ CONS[element, tmpCtx.shapes[j].props]; ENDLOOP; FOR list: Atom.PropList _ shapes[i].shadingProps, list.rest UNTIL list = NIL DO element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; tmpCtx.shapes[j].shadingProps _ CONS[element, tmpCtx.shapes[j].shadingProps]; ENDLOOP; ENDLOOP; }; AddShape: PROC[ shapes: REF ShapeSequence, newShape: REF ShapeInstance ] RETURNS [REF ShapeSequence] ~ { size: NAT _ IF shapes # NIL THEN shapes.length + 1 ELSE 1; tmpShapes: REF ShapeSequence _ NEW[ ShapeSequence[size] ]; IF shapes # NIL THEN FOR i: NAT IN [0..shapes.length) DO tmpShapes[i] _ shapes[i]; ENDLOOP; -- copy list tmpShapes[tmpShapes.length-1] _ newShape; RETURN[tmpShapes]; }; DeleteShape: PROC[ shapes: REF ShapeSequence, oldShape: REF ShapeInstance ] RETURNS [REF ShapeSequence] ~ { tmpShapes: REF ShapeSequence _ NEW[ ShapeSequence[shapes.length - 1] ]; FOR i: NAT IN [0..shapes.length) DO IF Rope.Equal[shapes[i].name, oldShape.name] THEN { -- find matching shape FOR j: NAT IN [0..i) DO tmpShapes[j] _ shapes[j]; ENDLOOP; -- copy up to oldshape FOR j: NAT IN [i..shapes.length-1) DO tmpShapes[j] _ shapes[j+1]; ENDLOOP; -- remove EXIT; }; ENDLOOP; RETURN[tmpShapes]; }; RemoteMakeFrame: PUBLIC PROC[context: REF Context, numForksHint: NAT _ 0] ~ { log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; shape: REF ShapeSequence _ context.shapes; shapeOrder: REF NatSequence; startTime: REAL _ CurrentTime[]; context.stopMe _ FALSE; -- release remote stop flag IF serverStatsWanted THEN serverStatsOut _ FS.StreamOpen[ fileName: ThreeDMisc.PrependWorkingDirectory[context, Rope.Cat["Temp/", "ServerStats"]], accessOptions: $create ]; IF serverStatsTime = 0.0 THEN serverStatsTime _ startTime; -- initialize time IF context.renderMode # $Dorado24 AND context.renderMode # $FullColor THEN SIGNAL ThreeDScenes.Error[[$MisMatch, "Full color display expected"]]; IF log = NIL THEN log _ ThreeDMisc.StartLog[context]; -- open log file if not yet done 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; IF NOT imageSpaceDivision THEN shapeOrder _ SortShapes[context, numForksHint]; IF numForksHint > 0 THEN { log.PutRope[Rope.Cat["\n", Convert.RopeFromCard[numForksHint], " processors"] ]; IF imageSpaceDivision THEN log.PutRope[" - slices "]; IF noCostAdjustments THEN log.PutRope[" - No Costing "]; }; log.PutRope[Rope.Cat["\nObject transform and sort: ", ElapsedTime[startTime] ]]; ThreeDScenes.FillViewPort[context, [0.0, 0.0, 0.0] ]; -- clear all pixel bits IF imageSpaceDivision THEN ForkSubImageProcs[context, startTime, log, numForksHint] ELSE ForkSubSceneProcs[context, shapeOrder, startTime, log, numForksHint]; IF NOT imageSpaceDivision THEN { ThreeDMisc.CombineBoxes[context]; -- get combined bounding box on scene ThreeDScenes.FillInBackGround[context]; -- load background }; log.PutRope[Rope.Cat["All done at ", ElapsedTime[startTime], "\n\n" ] ]; ThreeDMisc.FlushLog[context]; IF serverStatsOut # NIL THEN IO.Close[serverStatsOut]; }; ForkSubImageProcs: PROC[context: REF Context, startTime: REAL, log: IO.STREAM, numForksHint: NAT] ~ { Edge: TYPE ~ RECORD [shape, position: NAT, entering: BOOLEAN]; EdgeSequence: TYPE ~ RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF Edge]; shapeEdgeOrder: REF EdgeSequence _ NEW[ EdgeSequence[ 2 * (context.shapes.length - context.lights.length) ] ]; processCount: REF CountedCondition _ NEW[CountedCondition]; procList: LIST OF CedarProcess.Process _ NIL; shape: REF ShapeSequence _ context.shapes; cost: REF RealSequence _ NEW[ RealSequence[shape.length] ]; totalCost, averageCost: REAL _ 0; IF NOT noCostAdjustments THEN { FOR i: NAT IN [0..shape.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 { cost[i] _ CostHeuristic[shape[i]]; totalCost _ totalCost + cost[i] }; ENDLOOP; IF numForksHint # 0 THEN { averageCost _ totalCost / numForksHint; -- base division on no. of processors processCount.count _ numForksHint -- limit processors (2 processes per processor) } ELSE { SIGNAL ThreeDScenes.Error[[$MisMatch, "No processors?"]]; }; FOR i: NAT IN [0..shape.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 { cost[i] _ cost[i] / ( shape[i].screenExtent.right + 1 - shape[i].screenExtent.left ); }; ENDLOOP; FOR s: NAT IN [0..shape.length) DO IF shape[s] # NIL AND Atom.GetPropFromList[shape[s].props, $Hidden] = NIL THEN IF shape[s].clipState # out AND shape[s].surface # NIL THEN { FOR i: NAT IN [0..2) DO position: NAT _ IF i = 0 THEN shape[s].screenExtent.left ELSE shape[s].screenExtent.right; left: BOOLEAN _ i = 0; EnterEdge: PROC[place, shape: NAT] ~ { shapeEdgeOrder[place].shape _ s; shapeEdgeOrder[place].position _ position; shapeEdgeOrder[place].entering _ left; }; FOR p: NAT DECREASING IN [0..shapeEdgeOrder.length) DO IF position < shapeEdgeOrder[p].position THEN { shapeEdgeOrder[p+1] _ shapeEdgeOrder[p]; EnterEdge[p, s]; } ELSE { EnterEdge[p+1, s]; EXIT; }; ENDLOOP; IF shapeEdgeOrder.length = 0 THEN EnterEdge[0, s]; shapeEdgeOrder.length _ shapeEdgeOrder.length + 1; ENDLOOP; }; ENDLOOP; }; { currentPlace: NAT _ 0; currentCost: REAL _ 0.0; activeShapes: REF ShapeSequence _ NIL; lastPos: REAL _ 0.0; thisPos: REAL _ shapeEdgeOrder[currentPlace].position; FOR i: NAT IN [0..numForksHint) DO -- do side to side sliceCost: REAL _ 0.0; activeShapesInSlice: REF ShapeSequence _ NIL; viewPortSize: REAL _ MAX[context.viewPort.w, context.viewPort.h]; vuPtWindow: Imager.Rectangle _ [ x: MAX[ 0.0, context.viewPort.h - context.viewPort.w ] / viewPortSize, y: MAX[ 0.0, context.viewPort.w - context.viewPort.h ] / viewPortSize, w: 1.0, h: 1.0 ]; left: REAL _ lastPos; right: REAL _ context.viewPort.w; top: REAL _ context.viewPort.h; bottom: REAL _ 0; forkedProcess: CedarProcess.Process; tmpCtx: REF Context; IF context.stopMe THEN { KillProcs[ context, procList ]; RETURN[]; }; -- escape hatch IF noCostAdjustments THEN { left _ i * right / numForksHint; right _ (i+1) * right / numForksHint; activeShapesInSlice _ NEW[ ShapeSequence[ context.shapes.length - context.lights.length ] ]; FOR i: NAT IN [0..activeShapesInSlice.length) DO j: NAT _ i + context.lights.length; activeShapesInSlice[i] _ context.shapes[j]; ENDLOOP; IF numForksHint # 0 THEN processCount.count _ numForksHint ELSE processCount.count _ 200/2; sliceCost _ 1.0; -- to avoid divide errors, etc. } ELSE { IF activeShapes # NIL THEN { -- grab all currently active shapes activeShapesInSlice _ NEW[ ShapeSequence[activeShapes.length] ]; FOR i: NAT IN [0..activeShapes.length) DO activeShapesInSlice[i] _ activeShapes[i]; ENDLOOP; }; WHILE TRUE DO costLeft: REAL _ averageCost - sliceCost; -- ideal slice cost - accumulated cost IF costLeft > currentCost * (thisPos - lastPos) THEN { -- finishing shape, get the next one sliceCost _ sliceCost + currentCost * (thisPos - lastPos); -- add this shape IF shapeEdgeOrder[currentPlace].entering THEN { -- moving into shape currentCost _ currentCost + cost[shapeEdgeOrder[currentPlace].shape]; activeShapes _ AddShape[activeShapes, shape[ shapeEdgeOrder[currentPlace].shape] ]; activeShapesInSlice _ AddShape[activeShapesInSlice, shape[ shapeEdgeOrder[currentPlace].shape] ]; } ELSE { -- moving out of shape currentCost _ currentCost - cost[shapeEdgeOrder[currentPlace].shape]; activeShapes _ DeleteShape[activeShapes, shape[ shapeEdgeOrder[currentPlace].shape] ]; }; lastPos _ thisPos; currentPlace _ currentPlace + 1; IF currentPlace < shapeEdgeOrder.length THEN thisPos _ shapeEdgeOrder[currentPlace].position ELSE EXIT; -- off end of sorted shape array, must be last slice } ELSE { -- slice ends in middle of shape oldPos: REAL _ lastPos; lastPos _ right _ lastPos + costLeft / currentCost; -- no new shapes sliceCost _ sliceCost + currentCost * (lastPos - oldPos); EXIT; }; ENDLOOP; }; tmpCtx _ GetTempContext[ context: context, name: Rope.Cat["Slice", Convert.RopeFromCard[i] ], -- get a unique name killBackground: FALSE, shapes: activeShapesInSlice, cost: NEW[ INT _ Real.RoundC[sliceCost] ], processCount: processCount, startTime: NEW[ REAL _ startTime ] ]; IF i = numForksHint-1 THEN tmpCtx.props _ Atom.PutPropOnList[ tmpCtx.props, $RightMostSlice, NEW[BOOLEAN _ TRUE] ]; left _ MAX[0, left]; -- limit window width right _ MIN[context.viewPort.w-1, right]; -- kludge to keep in bounds ThreeDScenes.SetViewPort[ tmpCtx, [ left + context.viewPort.x, bottom + context.viewPort.y, right - left, top - bottom ] ]; ThreeDScenes.SetWindow[ tmpCtx, [ 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 ] ]; forkedProcess _ CedarProcess.Fork[RenderRemote, tmpCtx ]; -- fork process procList _ CONS[ forkedProcess, procList]; -- save on list for later IF computingSerially THEN [] _ CedarProcess.Join[forkedProcess]; -- wait for process if serializing ENDLOOP; }; JoinProcs[context: context, procList: procList, log: log, startTime: startTime]; }; ForkSubSceneProcs: PROC[context: REF Context, shapeOrder: REF NatSequence, startTime: REAL, log: IO.STREAM, numForksHint: NAT] ~ { processCount: REF CountedCondition _ NEW[CountedCondition]; allProcsList: LIST OF CedarProcess.Process _ NIL; cost: REF IntSequence _ NEW[ IntSequence[ shapeOrder.maxLength] ]; totalCost, averageCost: INT _ 0; IF noCostAdjustments THEN { averageCost _ 1; FOR i: NAT IN [0..shapeOrder.maxLength) DO cost[i] _ 1; ENDLOOP; IF numForksHint # 0 THEN processCount.count _ numForksHint ELSE processCount.count _ 200/2; } ELSE { shape: REF ShapeSequence _ context.shapes; FOR j: NAT IN [0..shapeOrder.length) DO i: NAT _ shapeOrder[j]; cost[j] _ CostHeuristic[shape[i]]; totalCost _ totalCost + cost[j] ENDLOOP; IF numForksHint # 0 THEN { averageCost _ totalCost / numForksHint; -- base division on no. of processors processCount.count _ numForksHint -- limit processors (2 processes per processor) } ELSE { averageCost _ totalCost / shapeOrder.length; -- base division on no. of objects processCount.count _ 200/2; -- no suggestion, keep safely under cedar limit of 300 }; }; FOR i: NAT IN [0..shapeOrder.length) DO -- do front to back screenExtent: ThreeDBasics.Box _ context.shapes[shapeOrder[i]].screenExtent; procList: LIST OF CedarProcess.Process _ NIL; baseName: Rope.ROPE _ context.shapes[shapeOrder[i]].name; imageCount: NAT _ Real.RoundC[ Real.Float[ cost[i] ] / averageCost ]; IF imageCount < 1 THEN imageCount _ 1; IF context.stopMe THEN { KillProcs[ context, allProcsList ]; RETURN[]; }; -- escape hatch FOR j: NAT IN [0..imageCount) DO -- do for each slice of object viewPortSize: REAL _ MAX[context.viewPort.w, context.viewPort.h]; vuPtWindow: Imager.Rectangle _ [ -- size of viewPort within unit square x: -1.0 + MAX[ 0.0, context.viewPort.h - context.viewPort.w ] / viewPortSize, y: -1.0 + MAX[ 0.0, context.viewPort.w - context.viewPort.h ] / viewPortSize, w: 2.0, h: 2.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; forkedProcess: CedarProcess.Process; tmpCtx: REF Context; shapes: REF ShapeSequence _ NEW[ ShapeSequence[1] ]; shapes[0] _ context.shapes[shapeOrder[i]]; tmpCtx _ GetTempContext[ context: context, name: Rope.Cat[ baseName, Convert.RopeFromCard[j] ], -- get a unique name killBackground: TRUE, shapes: shapes, cost: NEW[ INT _ cost[i] / imageCount ], processCount: processCount, startTime: NEW[ REAL _ startTime ] ]; IF j = imageCount-1 THEN tmpCtx.props _ Atom.PutPropOnList[ tmpCtx.props, $RightMostSlice, NEW[BOOLEAN _ TRUE] ]; left _ MAX[0.0, left - 1.0]; -- spread window by a pixel on each side right _ MIN[context.viewPort.w-1.0, right + 1.0]; ThreeDScenes.SetViewPort[ tmpCtx, [ left + context.viewPort.x, bottom + context.viewPort.y, right - left, top - bottom ] ]; ThreeDScenes.SetWindow[ tmpCtx, [ x: (2.0 * left / viewPortSize) + vuPtWindow.x, -- window range is -1.0  +1.0 y: (2.0 * bottom / viewPortSize) + vuPtWindow.y, w: 2.0 * (right - left) / viewPortSize, h: 2.0 * (top - bottom) / viewPortSize ] ]; forkedProcess _ CedarProcess.Fork[RenderRemote, tmpCtx ]; -- fork process procList _ CONS[ forkedProcess, procList]; -- save on list for later allProcsList _ CONS[ forkedProcess, allProcsList]; IF computingSerially THEN [] _ CedarProcess.Join[forkedProcess]; -- wait for process if serializing 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 ]; IF showBoxes AND computingSerially THEN { -- bounding boxes for pedagogical purposes area: Pixels.Extent _ [ x: screenExtent.left, y: screenExtent.bottom, w: screenExtent.right - screenExtent.left, h: screenExtent.top - screenExtent.bottom ]; pixelValues: Pixels.SampleSet _ Pixels.GetSampleSet[4]; color: REF RGB _ NARROW[ThreeDScenes.GetShading[ context.shapes[shapeOrder[i]], $Color ] ]; pixelValues[0] _ Real.Fix[color.R * 255.0]; pixelValues[1] _ Real.Fix[color.G * 255.0]; pixelValues[2] _ Real.Fix[color.B * 255.0]; pixelValues[3] _ Real.Fix[showBoxCoverage * 255.0]; -- coverage Pixels.PixelOp[ context.display, area, pixelValues, $WriteUnder ]; }; ENDLOOP; JoinProcs[ context, allProcsList, log, startTime ]; FOR i: NAT IN [0..shapeOrder.length) DO -- clear out process REFs context.shapes[shapeOrder[i]].props _ Atom.RemPropFromList[ context.shapes[shapeOrder[i]].props, $RemoteProc ]; ENDLOOP; }; RenderRemote: CedarProcess.ForkableProc ~ { context: REF Context _ NARROW[data]; startTime: REAL _ NARROW[ Atom.GetPropFromList[context.props, $StartTime], REF REAL ]^; lastTime: REAL _ CurrentTime[]; fileName: Rope.ROPE _ ThreeDMisc.PrependWorkingDirectory[ context, Rope.Cat[ "Temp/", NARROW[ Atom.GetPropFromList[context.props, $SubImageName] ] ] ]; input: IO.STREAM _ NIL; ref: REF _ Atom.GetPropFromList[context.props, $RightMostSlice]; rightMostSlice: BOOLEAN _ IF ref # NIL THEN NARROW[ ref, REF BOOLEAN ]^ ELSE FALSE; forkedProc: ARRAY[0..maxProcClonesAllowed) OF CedarProcess.Process; procIO: ARRAY[0..maxProcClonesAllowed) OF REF StreamPair; serverName: ARRAY[0..maxProcClonesAllowed) OF Rope.ROPE; serverPctAvail: ARRAY[0..maxProcClonesAllowed) OF REAL; returnMsg: Rope.ROPE _ Rope.Cat[ NARROW[ Atom.GetPropFromList[context.props, $SubImageName] ], " - " ]; estComputeTime: INT _ NARROW[ Atom.GetPropFromList[context.props, $ComputeCost], REF INT ]^; times: REF RealSequence _ NEW[ RealSequence[5] ]; FOR i: NAT IN [0..5) DO times[i] _ 0.0; ENDLOOP; returnMsg _ Rope.Cat[returnMsg, "est. ", Convert.RopeFromInt[estComputeTime], " s., "]; results _ returnMsg; FOR i:NAT IN [0..procClonesAllowed) DO IF i > 0 THEN { returnMsg _ Rope.Cat[returnMsg, "\n "]; FOR k: NAT IN [0..i) DO returnMsg _ Rope.Cat[ returnMsg, serverName[k], " " ]; IF CurrentTime[] - lastTime >= (i) * 2 * estComputeTime THEN returnMsg _ Rope.Cat[returnMsg, "over time limit, "] ELSE returnMsg _ Rope.Cat[returnMsg, Convert.RopeFromReal[ serverPctAvail[k] ], " too busy, "]; ENDLOOP; }; procIO[i] _ NEW[ StreamPair ]; procIO[i].in _ FS.StreamOpen[ fileName: Rope.Cat[ fileName, "-in", Convert.RopeFromCard[i] ], accessOptions: $create ]; procIO[i].out _ FS.StreamOpen[ fileName: Rope.Cat[ fileName, "-out", Convert.RopeFromCard[i] ], accessOptions: $create ]; ThreeDScenes.WriteScene[context, procIO[i].out]; -- get scene to remote proc IO.SetIndex[procIO[i].out, 0]; -- reset index for downstream read returnMsg _ Rope.Cat[returnMsg, "Started proc. at: ", ElapsedTime[startTime], " "]; IF i = 0 THEN GetProcess[NARROW[ Atom.GetPropFromList[context.props, $ProcessCount] ]]; forkedProc[i] _ CedarProcess.Fork[CallComputeServer, procIO[i] ]; -- fork process WHILE CurrentTime[] - lastTime < (i+1) * 2 * estComputeTime -- allow 2*estimated time OR NOT computingRemote OR procClonesAllowed = 1 DO -- these stop timeout KillChildren: PROC[ i: NAT ] ~ { FOR k: NAT IN [0..i] DO CedarProcess.Abort[ forkedProc[k] ]; ENDLOOP; ERROR ABORTED; }; serversAvailable: BOOLEAN _ FALSE; CedarProcess.CheckAbort[ ! ABORTED => KillChildren[i] ]; -- abort on request Process.Pause[ Process.SecondsToTicks[1] ]; -- pause for a second FOR j: NAT IN [0..i] DO -- check on each forked process result: REF; status: CedarProcess.Status _ CedarProcess.GetStatus[ forkedProc[j] ]; IF status = done OR procClonesAllowed = 1 -- don't timeout if no clones allowed THEN { -- finished! FOR k: NAT IN [0..i] DO -- abort other procs IF k # j THEN { returnMsg _ Rope.Cat[ returnMsg, "\n aborted proc on ", ComputeClientExtras.RemoteProcessSite[forkedProc[k].process], " " ]; CedarProcess.Abort[ forkedProc[k] ]; }; ENDLOOP; [status, result] _ CedarProcess.Join[ forkedProc[j] ]; -- get proc results ReleaseProcess[NARROW[ Atom.GetPropFromList[context.props, $ProcessCount] ]]; returnMsg _ Rope.Cat[ returnMsg, NARROW[result, Rope.ROPE] ]; input _ procIO[j].in; EXIT; -- some process finished, stop checking } ELSE IF status = aborted THEN EXIT -- go try again if aborted ELSE { -- check on remote processor utilization [ serverName[j], serverPctAvail[j] ] _ ServerUsage[ forkedProc[j] ]; IF serverPctAvail[j] > minimumUsefulServerPortion THEN serversAvailable _ TRUE -- based on 5 second samples ELSE { server: Rope.ROPE _ serverName[j]; }; -- place for debug }; IF NOT serversAvailable AND i < procClonesAllowed-1 THEN EXIT; -- lousy service, get another server ENDLOOP; IF input # NIL THEN EXIT; -- some process finished, stop waiting ENDLOOP; IF input # NIL THEN EXIT; -- some process finished, stop cloning new ones ENDLOOP; IF input = NIL THEN { -- clean up processes on timeout returnMsg _ Rope.Cat[returnMsg, " process forker timed-out!! "]; FOR i: NAT IN [0..procClonesAllowed) DO CedarProcess.Abort[ forkedProc[i] ]; IF i = 0 THEN ReleaseProcess[ NARROW[ Atom.GetPropFromList[ context.props, $ProcessCount] ] ]; ENDLOOP; }; times _ AddTimes[returnMsg, times]; returnMsg _ Rope.Cat[returnMsg, "\n cost factor: ", RopeFromSeconds[ times[3] / estComputeTime]]; returnMsg _ Rope.Cat[returnMsg, " remote ovrhd: ", -- compute server overhead RopeFromSeconds[ CurrentTime[] - startTime - times[3] ], " s. " ]; IF input # NIL THEN IO.SetIndex[input, 0]; -- reset index for returning pixel stream IF NOT imageSpaceDivision THEN { lastTime _ CurrentTime[]; FOR i: NAT IN [0..context.shapes.length) DO IF Atom.GetPropFromList[context.shapes[i].props, $Hidden] = NIL THEN { -- no lights occlusionList: LIST OF REF ShapeInstance _ NARROW[ Atom.GetPropFromList[ context.shapes[i].props, $Occlusions ] ]; FOR shapes: LIST OF REF ShapeInstance _ occlusionList, shapes.rest UNTIL shapes = NIL DO procList: LIST OF CedarProcess.Process _ NARROW[ Atom.GetPropFromList[ shapes.first.props, $RemoteProc ] ]; FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO [] _ CedarProcess.Join[procs.first]; -- wait for process completion ENDLOOP; ENDLOOP; }; ENDLOOP; returnMsg _ Rope.Cat[returnMsg, " waited for: ", ElapsedTime[lastTime] ]; }; lastTime _ CurrentTime[]; IF input # NIL THEN { xPos: NAT _ Floor[context.viewPort.x]; yPos: NAT _ Floor[context.viewPort.y]; width: NAT _ Ceiling[context.viewPort.w]; outWidth: NAT _ IF rightMostSlice OR imageSpaceDivision THEN width ELSE width-2; height: NAT _ Ceiling[context.viewPort.h]; writeOp: ATOM _ IF imageSpaceDivision THEN $Write ELSE $WriteUnder; scanSeg: REF Pixels.SampleSetSequence _ Pixels.GetScanSeg[ context.display, 0, 0, width ]; IF showGaps AND NOT rightMostSlice THEN outWidth _ outWidth - 2; FOR y: NAT IN [ 0 .. height ) DO FOR i: NAT IN [0.. context.display.samplesPerPixel) DO TRUSTED { IF IO.UnsafeGetBlock[ -- uses 16 bits per byte (wasting net bandwidth) self: input, block: [ base: LOOPHOLE[SampleMapOps.GetPointer[scanSeg[i], 0, width]], count: 2*width ] ] = 2*width THEN NULL ELSE { returnMsg _ Rope.Cat[returnMsg, "Unexpected end of stream"]; -- too short GOTO GiveUp; }; }; ENDLOOP; Pixels.PutScanSeg[context.display, 0, y, outWidth, scanSeg, writeOp]; ENDLOOP; EXITS GiveUp => NULL; }; returnMsg _ Rope.Cat[returnMsg, " matted in: ", ElapsedTime[lastTime] ]; returnMsg _ Rope.Cat[returnMsg, "\n done at: ", ElapsedTime[startTime] ]; RETURN[returnMsg]; }; CallComputeServer: CedarProcess.ForkableProc ~ { procIO: REF StreamPair _ NARROW[data]; input: IO.STREAM _ procIO.in; output: IO.STREAM _ procIO.out; found: BOOLEAN; success: ComputeServerClient.RemoteSuccess _ false; successMsg, procMsg, server: Rope.ROPE _ NIL; IF NOT computingRemote THEN [success, procMsg, server] _ RenderFromStream[ -- test code, no remote calls Convert.RopeFromTime[from: BasicTime.Now[], end: seconds], output, input ] ELSE [ found: found, success: success, remoteMsg: procMsg, serverInstance: server ] _ ComputeServerClient.StartService[ service: "Render3dFromStream", cmdLine: Convert.RopeFromTime[from: BasicTime.Now[], end: seconds], in: output, out: input, -- in/out as seen from server queueService: TRUE, timeToWait: remoteProcessTimeOut, -- estimate of time to wait before giving up retries: 3 -- default number of retries ! ANY => ERROR ABORTED ]; SELECT success FROM true => {}; false => { IF ~found THEN successMsg _ "compute server command not found" ELSE successMsg _ "compute server success was false (Compute Server bug?)"; }; timeOut => successMsg _ "timeOut"; commandNotFound => successMsg _ "commandNotFound"; aborted => successMsg _ "command aborted"; communicationFailure => successMsg _ "communicationFailure"; cantImportController => successMsg _ "cantImportController"; cantImportServer => successMsg _ "cantImportServer"; serverTooBusy => successMsg _ "serverTooBusy"; clientNotRunning => successMsg _ "clientNotRunning"; ENDCASE => successMsg _ "unknown Compute Server error code"; successMsg _ Rope.Cat["Server: ", server, " - ", successMsg, "\n"]; IF procMsg # NIL THEN successMsg _ Rope.Cat[successMsg, " Remote: ", procMsg]; RETURN[successMsg]; }; RenderFromStream: PROC[cmd: Rope.ROPE, input, output: IO.STREAM] RETURNS[success: ComputeServerClient.RemoteSuccess, msg, server: Rope.ROPE] ~ { startTime, lastTime: REAL _ CurrentTime[]; procTime: BasicTime.GMT _ BasicTime.Now[]; scanSeg: REF Pixels.SampleSetSequence; displaycontext: REF Context; context: REF Context _ ThreeDScenes.Create[]; serverDelay: INT _ BasicTime.Period[ Convert.TimeFromRope[cmd], procTime ]; msg _ Rope.Cat[" delay: ", Convert.RopeFromInt[ serverDelay], " s. " ]; ThreeDScenes.ReadScene[context, input]; ThreeDScenes.DisplayFromVM[ displaycontext, -- get display bits of proper size Ceiling[context.viewPort.w], Ceiling[context.viewPort.h], $FullColor ]; ThreeDScenes.AddAlphaBuffer[displaycontext]; context.display _ displaycontext.display; -- give display bits to context ThreeDScenes.SetEyeSpace[context]; -- update for actual display size msg _ Rope.Cat[" set-up: ", ElapsedTime[startTime] ]; lastTime _ CurrentTime[]; ThreeDMisc.MakeFrame[context]; -- render image msg _ Rope.Cat[msg, " rendering: ", ElapsedTime[lastTime] ]; lastTime _ CurrentTime[]; FOR y: NAT IN [0 .. Ceiling[context.viewPort.h] ) DO scanSeg _ Pixels.GetScanSeg[context.display, 0, y, Ceiling[context.viewPort.w], scanSeg]; FOR i: NAT IN [0.. context.display.samplesPerPixel) DO TRUSTED { IO.UnsafePutBlock[ -- uses 16 bits per byte (wasting net bandwidth) self: output, block: [ base: LOOPHOLE[SampleMapOps.GetPointer[scanSeg[i], 0, scanSeg[i].length]], count: 2*scanSeg[i].length ] ]; }; ENDLOOP; ENDLOOP; msg _ Rope.Cat[msg, " output: ", ElapsedTime[lastTime], " total: ", ElapsedTime[startTime] ]; context _ displaycontext _ NIL; -- kill off everything, no state saved success _ true; server _ "local"; }; END. DistributedDisplayImpl.mesa Last Edited by: Crow, September 23, 1986 3:00:59 pm PDT Bloomenthal, February 26, 1987 11:21:03 pm PST Types Global Constant/Variables Utility Procedures Support Procedures Sort Objects Bubble sort shapes, numShapes assumed quite small Make Lists of Occluding Objects Start with area of bounding box times 3 for Non-linear surface times 2 for Transparency times 2 for Mapped Texture times 4 for Solid Texture - cost could be higher Estimated maximum seconds for completion Returns percent of remote machine process is getting (in theory). If anything goes wrong, return indicates process is getting all of machine. Analyze Stats for current behavior Stats format: eg. Hornet( 0.11, 0.40, 0.40, 0.40, 0), Wasp( 0.83, 0.40, 0.40, 0.70, 1) - aveBackgroundLoad: the average number of ready background processes. - nonBackgroundCPULoad: fraction of CPU used that was not idle or background - CPULoad: CPU load returned by Watch expressed as a fraction. - FOM: Figure of Merit - same as SummonerInfo - 0.0 is idle - numberOfCurrentRequests: count of Summoner requests on the server Await completion of processes and log messages Build new context to send to forked process Kill imager context to avoid side effects Copy proplists to avoid side effects Multiprocessor control Uses compute server to parcel out rendering to other processors Get Everything (including light centroids) into eyespace Sort Shapes Prepare for display Distribute subscenes to servers Finish Frame Find average scan conversion cost Estimate relative cost of each shape (untamed heuristics here) should consult Summoner to find number of idle machines Adjust costs to unit width Sort Object Screen Extents left to right Bubble sort left and right shape edges, numShapes assumed quite small Parcel out image slices to servers Set up window to clip image apart Calculate slice width and active shapes Build new context to send to forked process Set window in context Fork new Process to Render each slice of the image Await completion of processes and log messages Find average scan conversion cost Estimate relative cost of each shape (untamed heuristics here) Parcel out shapes to servers Calculate Window to clip object apart Build new context to send to forked process Set window in context Fork new Process to Render each piece of the Shape Await completion of processes and log messages PROC [data: REF] RETURNS [results: REF _ NIL] Robust remote processes. Start a process, check status regularly, clone process on evidence of poor remote service, LIMIT CLONES!! Start Remote Process and Send Scene Data Limit remote calls to control number of processors used Monitor process for completion and remote machine utilization Process Return message Check that all occluding processes are complete list of occluding shapes Display Pixels from Remote Process Get scanline from remote proc Write to context with appropriate Op PROC [data: REF] RETURNS [results: REF _ NIL] Renders scene described on input stream, passes pixels back on output stream Write pixels back on output stream Κ(ΐ˜headšœ™Jšœ7™7Icode™.defaultšΟk ˜ Jšœ œ)œ ˜HJšœ œd˜rJšœ œ˜+JšœœV˜jJšœœ˜Jšœ œ!Οc˜@Jšœ œž˜6Jšœœ˜8Jšœ"œ˜Jšœœœ˜Jšœœœœ˜FJšœ,˜,J˜—š‘œœœœ˜BJšœ,˜,Jšœ˜J˜—š ‘ œœ œœœœ˜WJšœ œ ˜Jš  ™ ašœ œ ˜-Jšœ œ@˜Pšœœœœ˜, š œ œœ1œ˜N šœœœœ˜=JšΠbc1™1š œœ œœ˜2šœ9˜;Jšœ=˜AJšœœ˜(—Jšœ˜—Jšœœ˜0Jšœ*˜*J˜——Jšœ˜—J˜—Jš ™šœœœœ˜,š‘œœœœ˜Ašœœœœ˜lJšœœœ˜Jšœœœ˜—J˜—šœœœœ˜(Jšœ ˜šœœœ˜šœc˜eJšœœ/˜H—Jšœ˜—šœ9ž˜UJšœ%˜%Jšœ ˜ Jšœ ˜ Jšœ˜—Jšœ˜—J˜—Jšœ˜J˜—š ‘ œœœœ œ˜GJš  ™ šœœœu˜…Jš ™—šœœ˜4Jš  ’ ™—šœ2œœ˜PJšœ˜Jš  ’™—šœ1œœ˜MJš 0™0—šœ3œœ˜OJš’(™(—Jšœ˜Jšœ˜—š ‘ œœœ œœœ˜qJšœA™AJšœK™KJšœœœ˜Jš œœœœœ ž˜RJšœA˜Ašœ'œ˜/Jšœ˜Jšœ œ˜Jšœ˜šœœ˜šœO˜SJšœ ˜ Jšœ˜Jšœ˜Jšœœ˜JšΟt˜——šœ £œœ˜Jšœ˜Jšœ ˜ Jšœœœ:˜VJ˜—J˜—J˜J™"Jšœ ™ šœP™PJšœF™FJšœL™LJšœ>™>Jšœ6™;JšœC™C—Jšœž˜5šœ œœ˜Jšœ)œ˜4Jšœœ@˜HJšœœ%˜.šœ œ ž˜6JšœV˜VJšœ<˜œž ˜Sšœ'ž˜JJšœ˜Jšœ0˜0Jšœ˜——Jšœ>œ˜GJšœ>œ˜NJšœ;œ ˜Hšœ>œ˜FJšž)™)—JšœPœ˜UJšœœD˜Wšœœœ ž˜MJšœœ'˜=Jšœ˜—šœœœ˜%Jšœœ˜#šœœ ž˜GJšœ$™$—Jšœ9œ˜=šœ2œœ˜HJšœœ$˜BJšœœ"˜?Jšœ˜—šœ9œœ˜OJšœœ$˜BJšœ œ)˜MJšœ˜—Jšœ˜—J˜—š ‘œœ œœœœ˜lJš œœœ œœœ˜JšœG˜KJ™ —šœœœ˜ Jšœ%ž%˜JQšœ)ž˜;Q˜—JšœI˜IJšœ˜Jšœœœœ˜6Jšœ˜—š‘œœ œ œœœœ˜pJš œœœœ œ˜>Jš œœœ œ œ œœ˜Ršœœœ˜(JšœD˜DJšœ˜—Jšœœœ˜;Jšœ œœœ˜-Jšœœ ˜*Jšœœœ˜;Jšœœ˜!šœœœ˜Jš !™!šœœœ˜# š œ œœ1œ˜N šœœœœ˜=Jš >™>Jšœ"˜"Jšœ˜J˜——Jšœ˜—šœ˜šœ˜Jšœ(ž%˜MJšœ#ž/˜RJ˜—šœ˜J™7Jšœ3˜9J˜——Jš ™šœœœ˜# š œ œœ1œ˜N šœœœœ˜=JšœU˜UJšœ˜——Jšœ˜—J™Jš (™(šœœœ˜# š œ œœ1œ˜N šœœœœ˜=Jš’E™Ešœœœ˜Jš œ œœœœ˜eJšœœ ˜š‘ œœœ˜&Jšœ ˜ Jšœ*˜*Jšœ&˜&J˜—š œœ œœ˜6šœ'˜)JšœC˜GJšœœ˜(—Jšœ˜—Jšœœ˜2Jšœ2˜2—Jšœ˜J˜——Jšœ˜—J˜—J™Jš "™"šœœ˜Jšœ œ˜Jšœœœ˜&Jšœ œ˜Jšœ œ)˜6š œœœœž˜8Jšœ œ˜Jšœœœ˜-Jš !™!Jšœœœ)˜Ašœ ˜ Jšœœ@˜FJšœœ@˜FJ˜J˜J˜—Jšœœ ˜Jšœœ˜!Jšœœ˜Jšœœ˜Jšœ$˜$Jšœœ ˜šœ˜Jšœ'œ ž˜J—šœ˜š˜Jšœ ˜ Jšœ%˜%šœœ˜JšœB˜B—šœœœ!˜0Jšœœ˜#Jšœ+˜+Jšœ˜—šœ˜Jšœ#˜'Jšœž˜!—Jšœž˜4Jšœ˜—šœ˜Jš '™'šœœœž#˜CJšœœ'˜@šœœœœ˜*Jšœ+˜+Jšœ˜—J˜—šœœ˜ Jšœ œž'˜Qšœ-˜/šœ ž$˜4Jšœ;ž˜Lšœ&˜(šœ ž˜%JšœE˜EJšœ]˜]Jšœk˜kJšœ˜—šœ ž˜'JšœE˜EJšœ_˜_J˜——Jšœ˜Jšœ ˜ šœ&˜(Jšœ0˜4Jšœœž4˜B—J˜—šœ ž ˜0Jšœœ ˜Jšœ4ž˜DJšœ9˜9Jšœ˜J˜——Jšœ˜—J˜——Jš +™+šœ˜Jšœ˜Jšœ1ž˜HJšœœ˜Jšœ˜Jšœœœ˜+Jšœ˜Jšœ œœ˜"J˜—šœœ#˜=Jšœ ˜ Jšœ˜Jšœœœ˜Jšœ˜—J˜Jš ™Jšœœ ž˜4Jšœœ ž˜Fšœ#˜#Jšœ˜Jšœ˜Jšœ˜Jšœ ˜ Jšœ˜—šœ!˜!Jšœ6ž˜TJšœ7˜7Jšœ(˜(Jšœ'˜'J˜—Jš 3™3Jšœ;ž˜JJšœ œ"ž˜Jšœ˜Jšœ*ž#˜Q—Jšœ˜—J˜—Jš .™.JšœP˜PJ˜—š‘œœ œœ$œœœœ˜Jšœœœ˜;Jšœœœœ˜1Jšœœœ'˜BJšœœ˜ Jš !™!šœ˜šœ˜Jšœ˜Jš œœœœœ˜@šœ˜Jšœ#˜'Jšœž˜!—J˜—šœ˜Jšœœ ˜*šœœœ˜'Jš >™>Jšœœ˜Jšœ"˜"Jšœ˜Jšœ˜—šœ˜šœ˜Jšœ(ž%˜MJšœ#ž/˜RJ˜—šœ˜Jšœ-ž"˜OJšœž7˜RJ˜——Jšœ˜——Jš ™š œœœœž˜>JšœL˜LJšœ œœœ˜-Jšœœ&˜9Jšœ œ6˜EJšœœ˜'šœ˜Jšœ+œž˜M—š œœœœž˜BJš %™%Jšœœœ)˜Ašœ$ž&˜JJšœ œ@˜MJšœ œ@˜MJ˜J˜J˜—JšœœžœM˜lJšœœd˜oJšœœ˜Jšœœ˜$Jš +™+Jšœ$˜$Jšœœ ˜Jšœœœ˜5Jšœ*˜*šœ˜Jšœ˜Jšœ6žœ˜ZJšœœ˜Jšœ˜Jšœœœ˜)Jšœ˜Jšœ œœ˜"J˜—šœœ#˜;Jšœ ˜ Jšœ˜Jšœœœ˜Jšœ˜—Jš ™Jšœœž(˜LJšœœ&˜1šœ#˜#Jšœ˜Jšœ˜Jšœ˜Jšœ ˜ Jšœ˜—šœ!˜!Jšœ0ž˜NJšœ1˜1Jšœ(˜(Jšœ'˜'J˜—Jš 3™3Jšœ;ž˜JJšœ œ"ž˜JJšœœ˜2šœ˜Jšœ*ž"˜P—Jšœ˜—šœ9ž˜WJšœ%˜%Jšœ ˜ Jšœž ˜1Jšœ˜—šœ œœž*˜Ušœ˜Jšœ˜Jšœ˜Jšœ*˜*Jšœ)˜)Jšœ˜—Jšœ7˜7šœœœœ˜0Jšœ˜Jšœ˜Jšœ˜—Jšœ+˜+Jšœ+˜+Jšœ+˜+Jšœ7ž ˜BJšœB˜BJšœ˜—Jšœ˜ —Jš .™.Jšœ5˜5š œœœœž˜Fšœ;˜;Jšœ%˜%Jšœ ˜ Jšœ˜—Jšœ˜—J˜—š  œ˜+Icode2š œœœ œœ™-Jšœ œ œ˜$Rš œ œœ3œœ˜WRšœ œ˜ šœœ'˜:Ršœ ˜ Ršœœ8˜QRšœ˜—Ršœœœœ˜Ršœœ8˜@Ršœœœœœœœœœœ˜TRšœ œœ˜CRšœœœœ ˜9Mšœ œœœ˜8Mšœœœœ˜7šœœ ˜ Mšœ7˜=Mšœ˜Mšœ˜—šœœœ˜Mšœ3˜3Mšœ˜Mšœ˜—Ršœœœ˜1Jš œœœœœ˜0MšœW˜WMšœ˜Jš …™…šœœœ˜&šœœ˜Mšœ,˜,šœœœ˜šœ˜Mšœ ˜ Mšœ˜Mšœ˜—šœ6˜8Mšœ5˜9Mšœc˜g—Mšœ˜—M˜—Mšœ œ˜šœœ ˜Mšœ@˜@Mšœ˜Mšœ˜—šœœ ˜Mšœ@˜@Mšœ˜Mšœ˜—Jš (™(Ršœ1ž˜LRšœž"˜BšœU˜UM™7—Mšœœ œ8˜WMšœCž˜RMš =™=š œ8žœœœœœž˜₯š‘ œœœ˜ Mš œœœœ(œ˜GMšœœ˜Mšœ˜—Mšœœœ˜"Mšœœž˜MMšœ-ž˜Bš œœœœž˜>Mšœœ˜ MšœF˜Fšœœž%˜Pšœž ˜ š œœœœž˜3šœœ˜šœ˜Mšœ%˜%Mšœ=˜=M˜Mšœ˜—Mšœ$˜$M˜—Mšœ˜—Mšœ8ž˜KMšœœ8˜MMšœ!œœ˜=Mšœ˜Mšœ ž'˜5Mšœ˜—šœœ˜Mšœœž˜*šœ ž(˜7MšœD˜Dšœ0˜2Mšœœž˜:Mšœœž˜?—M˜———šœœœ˜4Mšœœž-˜7—Mšœ˜—Mš œ œœœž'˜CMšœ˜—Mš œ œœœž/˜LMšœ˜ —šœ œœž ˜;Mšœ@˜@šœœœ˜'Mšœ$˜$šœœ˜Mšœœ:˜P—Mšœ˜—M˜—Mš ™Ršœ#˜#Ršœj˜jJšœ7žœKž˜Rš œ œœœž*˜UJš’/™/šœœœ˜ Jšœ˜šœœœ˜+šœ:œœž˜TQšž™Jš œœœœœG˜yš œ œœœ,œ œ˜kJšœ œœœ>˜mš œœœ-œ œ˜SJšœ'ž˜EJšœ˜—Jšœ˜—J˜—Jšœ˜—JšœI˜IJ˜—Jš‘"™"Jšœ˜šœ œœ˜Jšœœ˜&Jšœœ˜&Jšœœ˜)Jš œ œœœœœ ˜QJšœœ˜*Jš œ œœœœ ˜CJšœ œN˜ZJšœ œœœ˜@šœœœ˜ J™šœœœ'˜6šœ˜ šœœž0˜FLšœ ˜ MšœœH˜_Lšœ ˜ Lšœ˜ šœ˜Lšœ=ž ˜ILšœ˜ L˜——L˜—Jšœ˜—J™$JšœE˜E—Jšœ˜Jšœ ˜J˜—JšœH˜HJšœK˜KJšœ ˜J˜—š‘œ˜0Rš œœœ œœ™-Ršœœœ˜&Ršœœœ ˜Ršœœœ˜Ršœœ˜Ršœ3˜3Ršœ"œœ˜- šœœ˜šœ1ž˜RRšœ:˜:Ršœ ˜ Ršœ˜— šœQ˜U šœ!˜!Ršœ˜RšœC˜CRšœž˜5Ršœœ˜Ršœ"ž,˜NRšœž˜*Ršœœœ˜Ršœ˜——— šœ ˜Ršœ ˜ šœ ˜ šœ˜ Ršœ0˜4RšœG˜K—Ršœ˜—Ršœ$˜$Ršœ3˜3Ršœ,˜,Ršœ<˜˜B—Jšœ˜R˜—šΠbn‘ œœ œœœ œ?œ˜—J™LJšœœ˜*Jšœœ˜*Jšœ œ˜&Jšœœ ˜Jšœ œ#˜/Jšœ œ<˜LJšœH˜HRšœ'˜'J˜ šœ.ž"˜PQšœ:˜:Qšœ ˜ —Jšœ,˜,Qšœ,ž˜KJšœ&ž!˜GJšœS˜SJšœ&ž˜5šœZ˜ZJ™"—šœœœ&˜5JšœY˜Yšœœœ'˜6šœ˜ šœž0˜CLšœ˜Mšœœ\˜sLšœ˜—L˜—Jšœ˜—Jšœ˜ —Jšœ]˜]Jšœœž(˜LJ˜J˜Jšœ˜—Jš˜——…——dΜB