33 private TBackbuffer _backbuffer;
34 private bool _resolutionEstablished;
35 private int _logicalWidth, _logicalHeight;
36 private int _presentationInvalidated = 1;
37 private float _deferredRenderScale =
float.NaN;
39 private RecordingBackbuffer? _recordingBackbuffer;
40 private long _snapshotSequence;
54 public event EventHandler<RenderSurfaceHostBindEventArgs>?
BindToScene;
124 _renderSurfaceAdapter = renderSurfaceAdapter ??
throw new ArgumentNullException(nameof(renderSurfaceAdapter));
127 RenderSurfaceAdapter.Resized += OnRenderSurfaceAdapterResized;
132 if (w <= 0 || h <= 0)
133 throw new InvalidOperationException(
"RenderSurfaceAdapter has non-positive dimensions.");
139 _backbuffer = CreateBackbuffer(_logicalWidth, _logicalHeight);
144 Backbuffer.SizeChanged += OnBackbufferSizeChanged;
147 private static TBackbuffer CreateBackbuffer(
int width,
int height)
149 var typeName = typeof(TBackbuffer).FullName;
150 Console.WriteLine($
"CreateBackbuffer called for type: {typeName}, dimensions: {width}x{height}");
155 Engine.
Logger.LogInformation(
"Using explicit BitmapBackbuffer constructor for type {TypeName}", typeName);
161 Engine.
Logger.LogInformation(
"Using explicit GpuBackbuffer constructor for type {TypeName}", typeName);
166 Engine.
Logger.LogInformation(
"Falling back to Activator.CreateInstance for type {TypeName}", typeName);
167 return (TBackbuffer)Activator.CreateInstance(typeof(TBackbuffer), width, height)!;
259 public void Bind(
Scene newScene,
bool limitCameraToWorldBoundPx =
true)
261 ArgumentNullException.ThrowIfNull(newScene,
"Cannot bind to null Scene.");
263 if (ReferenceEquals(newScene, _scene))
268 newScene.BindRenderSurfaceHost(
this);
270 var oldScene = _scene;
275 if (!ReferenceEquals(oldScene,
Scene.Empty))
277 oldScene.SceneDisposing -= OnSourceDisposing;
278 oldScene.UnbindRenderSurfaceHost(
this);
282 ViewManager.BindToScene(_scene, limitCameraToWorldBoundPx);
284 _scene.SceneDisposing += OnSourceDisposing;
285 _scene.FullRefreshNeeded =
true;
290 newScene.UnbindRenderSurfaceHost(
this);
293 if (!ReferenceEquals(oldScene,
Scene.Empty))
295 oldScene.BindRenderSurfaceHost(
this);
296 oldScene.SceneDisposing += OnSourceDisposing;
310 internal override void RenderToBackbuffer(
long tick)
312 Backbuffer.BeginFrame();
313 RenderBackbufferBegin?.Invoke();
320 if (Backbuffer.IsGlThreadRendered)
321 RenderToBackbufferGpuFull(tick);
323 RenderToBackbufferBitmap(tick);
325 RenderBackbufferEnd?.Invoke();
330 internal override void ProduceRenderFrameSnapshot(
long tick)
332 if (!UsesRenderFrameSnapshots || _disposed)
return;
333 var slot = FrameMailbox.TryBeginBuild();
334 if (slot is
null)
return;
335 var recorder = _recordingBackbuffer ??=
new RecordingBackbuffer();
336 bool transferred =
false;
337 long telemetryGeneration = Telemetry?.BeginSample() ?? 0;
338 long started = GpuRenderFrameDiagnosticsCalculated is
null && telemetryGeneration == 0 ? 0 : HighResTimer.GetCurrentTick();
339 _recordingDiagnostics =
null;
342 recorder.Start(Backbuffer);
343 RenderBackbufferBegin?.Invoke();
344 RenderToBackbufferGpuFull(tick, recorder);
345 RenderBackbufferEnd?.Invoke();
346 var frame =
new RenderFrameSnapshot(recorder.Complete(), ++_snapshotSequence,
347 HighResTimer.GetCurrentTick(), recorder.Width, recorder.Height);
349 FrameMailbox.Publish(slot, frame);
350 if (telemetryGeneration != 0)
352 Telemetry!.Record(telemetryGeneration,
"build.cpu.ms", HighResTimer.GetDuration(started, HighResTimer.GetCurrentTick()) * 1000d);
353 Telemetry.Record(telemetryGeneration,
"atlas.batches", recorder.AtlasBatchCount);
354 Telemetry.Record(telemetryGeneration,
"atlas.tiles", recorder.AtlasBatchedTileCount);
356 if (_recordingDiagnostics is { } diagnostics)
357 GpuRenderFrameDiagnosticsCalculated?.Invoke(diagnostics with
359 TotalRenderMilliseconds =
360 HighResTimer.GetDuration(started, HighResTimer.GetCurrentTick()) * 1000d,
361 AtlasBatchCount = recorder.AtlasBatchCount,
362 AtlasBatchedTileCount = recorder.AtlasBatchedTileCount
368 if (!transferred) FrameMailbox.AbortBuild(slot);
405 private void RenderToBackbufferGpuFull(
long tick, BackbufferBase? destination =
null)
408 long telemetryGeneration = Telemetry?.BeginSample() ?? 0;
409 bool legacyDiagnostics = GpuRenderFrameDiagnosticsCalculated is not
null;
410 bool collectDiagnostics = legacyDiagnostics || telemetryGeneration != 0;
411 long diagnosticsStartTick = collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
412 long queryAndSortTicks = 0;
416 long overlayTicks = 0;
417 int drawableCount = 0;
419 List<GpuLayerRenderDiagnostics>? layerDiagnostics = legacyDiagnostics
420 ?
new List<GpuLayerRenderDiagnostics>(
Scene.VisibleSceneLayers.Count)
426 if (ViewManager.
Views.Count == 0)
428 target.ClearRect(
new Rectangle(0, 0, target.Width, target.Height));
429 Scene.FullRefreshNeeded =
false;
431 if (telemetryGeneration != 0 && destination is not RecordingBackbuffer)
433 long diagnosticsEndTick = HighResTimer.GetCurrentTick();
437 HighResTimer.GetDuration(diagnosticsStartTick, diagnosticsEndTick) * 1000d);
446 target.ClearRect(
new Rectangle(0, 0, target.Width, target.Height));
448 foreach (var view
in ViewManager.
Views)
450 RenderContext.Push(view, tick);
456 var overlays = DirectDrawingManager.Instance.GetDrawingsForView(view);
458 var vp = view.GetRenderViewportTargetRectPx();
461 target.Canvas.Save();
462 target.Canvas.ResetMatrix();
463 target.Canvas.ClipRect(vp.ToSKRect(), SKClipOperation.Intersect, antialias:
false);
467 if (!blocker.BlocksViewsBelow)
470 var overlap =
Rectangle.Intersect(vp, blocker.GetRenderViewportTargetRectPx());
471 if (!overlap.IsEmpty)
472 target.Canvas.ClipRect(overlap.ToSKRect(), SKClipOperation.Difference, antialias:
false);
475 int viewPresentation = BeginPresentation(
476 view.GetPresentationBoundsPx(),
479 view.EffectRevealDirection, target);
481 if (viewPresentation > 0)
485 target.ClearRect(view.GetPresentationBoundsPx().ToPixelAlignedRect());
489 var sceneLayers =
Scene.VisibleSceneLayers;
491 for (
int i = 0; i < sceneLayers.Count; i++)
493 var layer = sceneLayers[i];
495 int layerPresentation = BeginPresentation(
496 GetLayerPresentationBounds(view, layer),
499 layer.EffectRevealDirection, target);
501 if (layerPresentation == 0)
506 var layerWorldRectF = view.ScreenRectToWorldRect(layer, vp);
507 layerWorldRectF.Inflate(layer.TileWidth, layer.TileHeight);
508 var layerWorldRect = layerWorldRectF.ToPixelAlignedRect();
510 long queryStartTick =
511 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
512 long layerSortTicks = 0;
513 int layerDrawableCount;
516 var recording = target as RecordingBackbuffer;
517 RecordingBackbuffer.FixedGridRenderPlan fixedGridPlan =
default;
518 bool streamed = recording is not
null &&
519 recording.TryPrepareFixedGridLayer(
531 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
534 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
535 layerTileCount = recording!.DrawFixedGridLayer(
539 layerDrawableCount = layerTileCount;
541 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
545 List<IDrawable> drawables;
546 if (collectDiagnostics)
548 drawables = layer.GetDrawablesInWorldRectWithDiagnostics(
554 drawables = layer.GetDrawablesInWorldRect(layerWorldRect);
558 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
560 layerDrawableCount = drawables.Count;
562 if (collectDiagnostics)
564 for (
int d = 0; d < drawables.Count; d++)
566 IDrawable drawable = drawables[d] is WrappedDrawable wrapped
570 if (drawable is Tile)
576 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
577 target.DrawDrawables(view, drawables, vp);
579 collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
582 if (collectDiagnostics)
584 long layerQueryAndSortTicks = queryEndTick - queryStartTick;
585 long layerQueryTicks = Math.Max(
587 layerQueryAndSortTicks - layerSortTicks);
588 long layerDrawTicks = drawEndTick - drawStartTick;
589 queryAndSortTicks += layerQueryAndSortTicks;
590 queryTicks += layerQueryTicks;
591 sortTicks += layerSortTicks;
592 drawTicks += layerDrawTicks;
593 drawableCount += layerDrawableCount;
594 tileCount += layerTileCount;
596 if (telemetryGeneration != 0 && i < Diagnostics.RenderTelemetry.Layers.Length)
598 var keys = Diagnostics.RenderTelemetry.Layers[i];
599 Telemetry!.Record(telemetryGeneration, keys[0], HighResTimer.GetDuration(0, layerQueryTicks) * 1000d);
600 Telemetry.Record(telemetryGeneration, keys[1], HighResTimer.GetDuration(0, layerDrawTicks) * 1000d);
601 Telemetry.Record(telemetryGeneration, keys[2], layerDrawableCount);
602 Telemetry.Record(telemetryGeneration, keys[3], layerTileCount);
603 Telemetry.Record(telemetryGeneration, keys[4], layer.TransformedTiles.Count);
604 Telemetry.Record(telemetryGeneration, keys[5], layer.TileWidth);
605 Telemetry.Record(telemetryGeneration, keys[6], layer.TileHeight);
606 Telemetry.Record(telemetryGeneration, keys[7], layer.ZOrder);
615 layer.TransformedTiles.Count,
618 HighResTimer.GetDuration(0, layerQueryAndSortTicks) * 1000d,
619 HighResTimer.GetDuration(0, layerDrawTicks) * 1000d));
622 EndPresentation(layerPresentation, target);
626 long overlayStartTick = collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
627 for (
int i = 0; i < overlays.Count; i++)
628 overlays[i].Draw(target, overlays[i].GetDrawLocationScreen(view));
629 long overlayEndTick = collectDiagnostics ? HighResTimer.GetCurrentTick() : 0;
631 if (collectDiagnostics)
632 overlayTicks += overlayEndTick - overlayStartTick;
634 EndPresentation(viewPresentation, target);
637 target.Canvas.Restore();
645 Scene.FullRefreshNeeded =
false;
649 InvokePostSceneCanvasHooks(target);
651 if (collectDiagnostics)
653 long diagnosticsEndTick = HighResTimer.GetCurrentTick();
654 if (telemetryGeneration != 0)
656 if (destination is not RecordingBackbuffer)
657 Telemetry!.Record(telemetryGeneration,
"build.cpu.ms", HighResTimer.GetDuration(diagnosticsStartTick, diagnosticsEndTick) * 1000d);
658 Telemetry!.Record(telemetryGeneration,
"query.cpu.ms", HighResTimer.GetDuration(0, queryTicks) * 1000d);
659 Telemetry.Record(telemetryGeneration,
"sort.cpu.ms", HighResTimer.GetDuration(0, sortTicks) * 1000d);
660 Telemetry.Record(telemetryGeneration,
"record.cpu.ms", HighResTimer.GetDuration(0, drawTicks) * 1000d);
661 Telemetry.Record(telemetryGeneration,
"overlay.cpu.ms", HighResTimer.GetDuration(0, overlayTicks) * 1000d);
662 Telemetry.Record(telemetryGeneration,
"visible.drawables", drawableCount);
663 Telemetry.Record(telemetryGeneration,
"visible.tiles", tileCount);
664 Telemetry.Record(telemetryGeneration,
"layers.omitted", Math.Max(0,
Scene.VisibleSceneLayers.Count - Diagnostics.RenderTelemetry.Layers.Length));
665 if (
Scene.VisibleSceneLayers.Count > Diagnostics.RenderTelemetry.Layers.Length)
666 Telemetry.MarkTruncated(telemetryGeneration);
668 if (!legacyDiagnostics)
return;
670 HighResTimer.GetDuration(diagnosticsStartTick, diagnosticsEndTick) * 1000d,
671 HighResTimer.GetDuration(0, queryAndSortTicks) * 1000d,
672 HighResTimer.GetDuration(0, drawTicks) * 1000d,
673 HighResTimer.GetDuration(0, overlayTicks) * 1000d,
678 QueryMilliseconds = HighResTimer.GetDuration(0, queryTicks) * 1000d,
679 SortMilliseconds = HighResTimer.GetDuration(0, sortTicks) * 1000d
681 if (destination is RecordingBackbuffer) _recordingDiagnostics = diagnostics;
682 else GpuRenderFrameDiagnosticsCalculated?.Invoke(diagnostics);
721 private void RenderToBackbufferBitmap(
long tick)
723 if (ViewManager.
Views.Count == 0)
726 Scene.FullRefreshNeeded =
false;
730 bool fullViewComposition = ViewManager.
Views.Any(view => view.HasPresentationEffect)
731 ||
Scene.VisibleSceneLayers.Any(layer => layer.WrapHorizontally || layer.WrapVertically);
734 if (
Scene.CountOfVisibleLayers == 0)
737 Scene.FullRefreshNeeded =
false;
743 if (
Scene.FullRefreshNeeded || (fullViewComposition &&
Scene.IsDirty))
746 EnqueueFullSceneRefresh();
753 RenderBackbufferNoOp?.Invoke();
759 if (fullViewComposition)
763 foreach (var view
in ViewManager.
Views)
765 RenderContext.Push(view, tick);
770 var overlays = DirectDrawingManager.Instance.GetDrawingsForView(view);
771 foreach (var overlay
in overlays)
773 overlay.ForceRefresh();
777 var dirtyScreenRects = CollectDirtyScreenArea(view);
780 var vp = view.GetRenderViewportTargetRectPx();
785 Backbuffer.Canvas.ClipRect(vp.ToSKRect(), SKClipOperation.Intersect, antialias:
false);
790 if (!blocker.BlocksViewsBelow)
793 var overlap =
Rectangle.Intersect(vp, blocker.GetRenderViewportTargetRectPx());
794 if (!overlap.IsEmpty)
795 Backbuffer.Canvas.ClipRect(overlap.ToSKRect(), SKClipOperation.Difference, antialias:
false);
798 int viewPresentation = BeginPresentation(
799 view.GetPresentationBoundsPx(),
802 view.EffectRevealDirection);
804 if (viewPresentation > 0)
806 if (fullViewComposition)
808 Backbuffer.ClearRect(view.GetPresentationBoundsPx().ToPixelAlignedRect());
813 PreclearScreenAreas(view, dirtyScreenRects);
818 var sceneLayers =
Scene.VisibleSceneLayers;
820 for (
int i = 0; i < sceneLayers.Count; i++)
822 var layer = sceneLayers[i];
823 int layerPresentation = BeginPresentation(
824 GetLayerPresentationBounds(view, layer),
827 layer.EffectRevealDirection);
829 if (layerPresentation == 0)
832 RenderLayerDirtyRegions(view, layer);
833 EndPresentation(layerPresentation);
837 for (
int i = 0; i < overlays.Count; i++)
838 overlays[i].Draw(Backbuffer, overlays[i].GetDrawLocationScreen(view));
840 EndPresentation(viewPresentation);
853 for (
int i = 0; i <
Scene.CountOfVisibleLayers; i++)
854 Scene.VisibleSceneLayers[i].RefreshQueue.ClearRefreshQueue();
856 Scene.FullRefreshNeeded =
false;
861 InvokePostSceneCanvasHooks();
864 #region DrawRefreshQueueToBackbuffer helpers
866 private void EnqueueFullSceneRefresh()
868 foreach (var view
in ViewManager.
Views)
870 var viewport = view.Viewport;
871 var screenRect = viewport.TargetRectPx;
873 for (
int i = 0; i <
Scene.CountOfVisibleLayers; i++)
875 var layer =
Scene.VisibleSceneLayers[i];
878 var layerWorldRect = view.ScreenRectToWorldRect(layer, screenRect);
883 int expandX = layer.TileWidth;
884 int expandY = layer.TileHeight;
886 layerWorldRect.Inflate(expandX, expandY);
889 var worldRectInt = layerWorldRect.ToPixelAlignedRect();
891 layer.RefreshQueue.AddWorldRect(worldRectInt);
896 private List<Rectangle> CollectDirtyScreenArea(
View view)
898 var dirty =
new List<Rectangle>(64);
899 var viewportRect = view.GetRenderViewportTargetRectPx();
901 foreach (var sceneLayer
in Scene.VisibleSceneLayers)
903 var refreshQueue = sceneLayer.RefreshQueue;
904 if (!refreshQueue.IsDirty)
907 foreach (var worldRect
in refreshQueue.SnapshotWorldRects())
911 screenRectF.ToPixelAlignedRect(),
917 dirty.AddDeduped(rect);
924 private void PreclearScreenAreas(
View view, List<Rectangle> screenRects)
926 if (Backbuffer is
null || screenRects is
null || screenRects.Count == 0)
929 foreach (var screenRect
in screenRects)
931 var screenRectViewport =
Rectangle.Intersect(screenRect, view.GetRenderViewportTargetRectPx());
933 if (screenRectViewport.IsEmpty || screenRectViewport.Width <= 0 || screenRectViewport.Height <= 0)
939 EnqueueForOverlappingSceneLayers(view, screenRectViewport);
943 private void EnqueueForOverlappingSceneLayers(
View sourceView, Rectangle clearedScreenRect)
945 var overlap =
Rectangle.Intersect(clearedScreenRect, sourceView.GetRenderViewportTargetRectPx());
950 foreach (var sceneLayer
in Scene.VisibleSceneLayers)
953 var worldRect = worldRectF.ToPixelAlignedRect();
955 if (!worldRect.IsEmpty)
956 sceneLayer.RefreshQueue.AddWorldRect(worldRect);
962 var refreshQueue = layer.RefreshQueue;
965 if (!refreshQueue.IsDirty)
968 foreach (var worldRect
in refreshQueue.SnapshotWorldRects())
971 var drawables = layer.GetDrawablesInWorldRect(worldRect);
976 Backbuffer.DrawDrawables(view, drawables, screenRect);
980 private int BeginPresentation(
985 BackbufferBase? destination =
null)
988 opacity = Math.Clamp(opacity, 0f, 1f);
989 reveal = Math.Clamp(reveal, 0f, 1f);
991 if (opacity <= 0.0001f || reveal <= 0.0001f || bounds.IsEmpty)
995 target.Canvas.Save();
997 if (reveal < 0.9999f)
999 RectangleF revealRect = EffectGeometry.GetRevealRect(bounds, direction, reveal);
1000 target.Canvas.ClipRect(
1001 revealRect.ToSKRect(),
1002 SKClipOperation.Intersect,
1006 if (opacity < 0.9999f)
1008 using var paint =
new SKPaint
1010 Color =
new SKColor(255, 255, 255, (
byte)Math.Round(opacity * 255f))
1013 target.Canvas.SaveLayer(bounds.ToSKRect(), paint);
1020 private void EndPresentation(
int saveCount, BackbufferBase? destination =
null)
1023 while (saveCount-- > 0)
1024 target.Canvas.Restore();
1027 private static RectangleF GetLayerPresentationBounds(
View view,
SceneLayer layer)
1029 RectangleF viewport = view.GetRenderViewportTargetRectPx();
1030 PointF offset = view.GetEffectOffsetPx(layer);
1032 return new RectangleF(
1033 viewport.Left + offset.X,
1034 viewport.Top + offset.Y,
1039 #endregion DrawRefreshQueueToBackbuffer helpers
1049 internal override void PresentBackbufferToAdapter()
1051 if (RenderSurfaceAdapter is
null)
1056 if (RedrawDirtyRectangleOnly && Interlocked.Exchange(ref _presentationInvalidated, 0) == 0)
1057 PresentBackbufferRect();
1059 PresentBackbufferAll();
1067 private bool _disposed;
1079 _scene.SceneDisposing -= OnSourceDisposing;
1080 _scene.UnbindRenderSurfaceHost(
this);
1081 _scene =
Scene.Empty;
1084 RenderSurfaceAdapter.Resized -= OnRenderSurfaceAdapterResized;
1085 Backbuffer.SizeChanged -= OnBackbufferSizeChanged;
1086 _recordingBackbuffer?.Dispose();
1093 base.Dispose(disposing);
1096 #endregion IDisposable
1098 #region private methods
1100 private void OnSourceDisposing(
Scene scene)
1102 scene.SceneDisposing -= OnSourceDisposing;
1103 scene.UnbindRenderSurfaceHost(
this);
1109 Interlocked.Exchange(ref _presentationInvalidated, 1);
1110 if (
float.IsFinite(_deferredRenderScale) && args.
NewWidth > 0 && args.
NewHeight > 0)
1111 RequestRenderScale(_deferredRenderScale);
1114 _resolutionEstablished =
true;
1119 internal override void InvalidatePresentation() => Interlocked.Exchange(ref _presentationInvalidated, 1);
1121 internal override void RequestRenderScale(
float scale)
1123 if (!_resolutionEstablished || _disposed)
return;
1124 var adapter = RenderSurfaceAdapter;
1126 if (adapter.Width <= 0 || adapter.Height <= 0)
1128 _deferredRenderScale = scale;
1131 _deferredRenderScale =
float.NaN;
1136 private void OnBackbufferSizeChanged(
int width,
int height)
1138 float scaleX = (float)width / _logicalWidth;
1139 float scaleY = (float)height / _logicalHeight;
1140 _logicalWidth = width;
1141 _logicalHeight = height;
1142 RenderSurfaceAdapter.SetBackbufferSize(width, height);
1143 foreach (var view
in ViewManager.
Views)
1145 var old = view.Viewport.TargetRectPx;
1146 view.Viewport.TargetRectPx =
Rectangle.FromLTRB(
1147 (
int)Math.Round(old.Left * scaleX), (
int)Math.Round(old.Top * scaleY),
1148 (
int)Math.Round(old.Right * scaleX), (
int)Math.Round(old.Bottom * scaleY));
1150 Scene.FullRefreshNeeded =
true;
1151 Interlocked.Exchange(ref _presentationInvalidated, 1);
1154 private void PresentBackbufferAll()
1156 if (Backbuffer ==
null)
1160 var src =
new SKRectI(0, 0, img.Width, img.Height);
1161 var dst = RenderSurfaceAdapter.
Presentation.DestinationRect;
1164 Engine.Instance.UiDispatcher!.Post(() => RenderSurfaceAdapter.
Present(img, src, dst));
1167 private void PresentBackbufferRect()
1169 if (Backbuffer ==
null)
1177 var clamped =
Rectangle.Intersect(dirty, bounds);
1178 if (clamped.IsEmpty)
1184 Engine.Instance.UiDispatcher!.Post(() =>
1185 RenderSurfaceAdapter!.Present(img, clamped.ToSKRectI(),
1187 .ScreenRectToAdapterRect(clamped).ToSKRect()));
1190 private void InvokePostSceneCanvasHooks(BackbufferBase? destination =
null)
1193 var hasSurfaceHandlers = RenderBackbufferPostScene is not
null;
1194 var hasPlugins = EnginePluginRegistry.All.Count > 0;
1196 if (!hasSurfaceHandlers && !hasPlugins)
1199 if (!target.IsGlThreadRendered)
1200 target.AddToBackbufferDirtyRectangle(
new Rectangle(0, 0, target.Width, target.Height));
1202 if (hasSurfaceHandlers)
1203 RenderBackbufferPostScene?.Invoke(target.Canvas);
1206 EnginePluginRegistry.InvokePostRenderCanvas(Engine.Instance,
this, target.Canvas);
1209 #endregion private methods