|
@@ -1,6 +1,6 @@
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Numerics;
|
|
|
+using System.Diagnostics;
|
|
|
using System.Threading;
|
|
|
using Avalonia.Media;
|
|
|
using Avalonia.Media.Imaging;
|
|
@@ -22,10 +22,11 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
private readonly ServerCompositor _compositor;
|
|
|
private readonly Func<IEnumerable<object>> _surfaces;
|
|
|
private static long s_nextId = 1;
|
|
|
- public long Id { get; }
|
|
|
- public ulong Revision { get; private set; }
|
|
|
private IRenderTarget? _renderTarget;
|
|
|
- private FpsCounter _fpsCounter = new FpsCounter(Typeface.Default.GlyphTypeface);
|
|
|
+ private DiagnosticTextRenderer? _diagnosticTextRenderer;
|
|
|
+ private FpsCounter? _fpsCounter;
|
|
|
+ private FrameTimeGraph? _renderTimeGraph;
|
|
|
+ private FrameTimeGraph? _layoutTimeGraph;
|
|
|
private Rect _dirtyRect;
|
|
|
private Random _random = new();
|
|
|
private Size _layerSize;
|
|
@@ -35,10 +36,24 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
private HashSet<ServerCompositionVisual> _attachedVisuals = new();
|
|
|
private Queue<ServerCompositionVisual> _adornerUpdateQueue = new();
|
|
|
|
|
|
+ public long Id { get; }
|
|
|
+ public ulong Revision { get; private set; }
|
|
|
public ICompositionTargetDebugEvents? DebugEvents { get; set; }
|
|
|
public ReadbackIndices Readback { get; } = new();
|
|
|
public int RenderedVisuals { get; set; }
|
|
|
|
|
|
+ private DiagnosticTextRenderer DiagnosticTextRenderer
|
|
|
+ => _diagnosticTextRenderer ??= new DiagnosticTextRenderer(Typeface.Default.GlyphTypeface, 12.0);
|
|
|
+
|
|
|
+ private FpsCounter FpsCounter
|
|
|
+ => _fpsCounter ??= new FpsCounter(DiagnosticTextRenderer);
|
|
|
+
|
|
|
+ private FrameTimeGraph LayoutTimeGraph
|
|
|
+ => _layoutTimeGraph ??= CreateTimeGraph("Layout");
|
|
|
+
|
|
|
+ private FrameTimeGraph RenderTimeGraph
|
|
|
+ => _renderTimeGraph ??= CreateTimeGraph("Render");
|
|
|
+
|
|
|
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces) :
|
|
|
base(compositor)
|
|
|
{
|
|
@@ -47,6 +62,9 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
Id = Interlocked.Increment(ref s_nextId);
|
|
|
}
|
|
|
|
|
|
+ private FrameTimeGraph CreateTimeGraph(string title)
|
|
|
+ => new(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, DiagnosticTextRenderer);
|
|
|
+
|
|
|
partial void OnIsEnabledChanged()
|
|
|
{
|
|
|
if (IsEnabled)
|
|
@@ -62,7 +80,33 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
v.Deactivate();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ partial void OnDebugOverlaysChanged()
|
|
|
+ {
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.Fps) == 0)
|
|
|
+ {
|
|
|
+ _fpsCounter?.Reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) == 0)
|
|
|
+ {
|
|
|
+ _layoutTimeGraph?.Reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
|
|
|
+ {
|
|
|
+ _renderTimeGraph?.Reset();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ partial void OnLastLayoutPassTimingChanged()
|
|
|
+ {
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
|
|
|
+ {
|
|
|
+ LayoutTimeGraph.AddFrameValue(LastLayoutPassTiming.Elapsed.TotalMilliseconds);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
partial void DeserializeChangesExtra(BatchStreamReader c)
|
|
|
{
|
|
|
_redrawRequested = true;
|
|
@@ -92,7 +136,10 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
return;
|
|
|
|
|
|
Revision++;
|
|
|
-
|
|
|
+
|
|
|
+ var captureTiming = (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
|
|
|
+ var startingTimestamp = captureTiming ? Stopwatch.GetTimestamp() : 0L;
|
|
|
+
|
|
|
// Update happens in a separate phase to extend dirty rect if needed
|
|
|
Root.Update(this);
|
|
|
|
|
@@ -137,33 +184,69 @@ namespace Avalonia.Rendering.Composition.Server
|
|
|
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
|
|
|
new Rect(_layerSize),
|
|
|
new Rect(Size), BitmapInterpolationMode.LowQuality);
|
|
|
-
|
|
|
-
|
|
|
- if (DrawDirtyRects)
|
|
|
- {
|
|
|
- targetContext.DrawRectangle(new ImmutableSolidColorBrush(
|
|
|
- new Color(30, (byte)_random.Next(255), (byte)_random.Next(255),
|
|
|
- (byte)_random.Next(255)))
|
|
|
- , null, _dirtyRect);
|
|
|
- }
|
|
|
|
|
|
- if (DrawFps)
|
|
|
+ if (DebugOverlays != RendererDebugOverlays.None)
|
|
|
{
|
|
|
- var nativeMem = ByteSizeHelper.ToString((ulong)(
|
|
|
- (Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
|
|
|
- Compositor.BatchMemoryPool.BufferSize), false);
|
|
|
- var managedMem = ByteSizeHelper.ToString((ulong)(
|
|
|
- (Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
|
|
|
- Compositor.BatchObjectPool.ArraySize *
|
|
|
- IntPtr.Size), false);
|
|
|
- _fpsCounter.RenderFps(targetContext, FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
|
|
|
+ if (captureTiming)
|
|
|
+ {
|
|
|
+ var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
|
|
|
+ RenderTimeGraph.AddFrameValue(elapsed.TotalMilliseconds);
|
|
|
+ }
|
|
|
+
|
|
|
+ DrawOverlays(targetContext, layerSize);
|
|
|
}
|
|
|
+
|
|
|
RenderedVisuals = 0;
|
|
|
|
|
|
_dirtyRect = default;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void DrawOverlays(IDrawingContextImpl targetContext, Size layerSize)
|
|
|
+ {
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
|
|
|
+ {
|
|
|
+ targetContext.DrawRectangle(
|
|
|
+ new ImmutableSolidColorBrush(
|
|
|
+ new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
|
|
|
+ null,
|
|
|
+ _dirtyRect);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.Fps) != 0)
|
|
|
+ {
|
|
|
+ var nativeMem = ByteSizeHelper.ToString((ulong) (
|
|
|
+ (Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
|
|
|
+ Compositor.BatchMemoryPool.BufferSize), false);
|
|
|
+ var managedMem = ByteSizeHelper.ToString((ulong) (
|
|
|
+ (Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
|
|
|
+ Compositor.BatchObjectPool.ArraySize *
|
|
|
+ IntPtr.Size), false);
|
|
|
+ FpsCounter.RenderFps(targetContext,
|
|
|
+ FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
|
|
|
+ }
|
|
|
+
|
|
|
+ var top = 0.0;
|
|
|
+
|
|
|
+ void DrawTimeGraph(FrameTimeGraph graph)
|
|
|
+ {
|
|
|
+ top += 8.0;
|
|
|
+ targetContext.Transform = Matrix.CreateTranslation(layerSize.Width - graph.Size.Width - 8.0, top);
|
|
|
+ graph.Render(targetContext);
|
|
|
+ top += graph.Size.Height;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
|
|
|
+ {
|
|
|
+ DrawTimeGraph(LayoutTimeGraph);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0)
|
|
|
+ {
|
|
|
+ DrawTimeGraph(RenderTimeGraph);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling);
|
|
|
|
|
|
private static Rect SnapToDevicePixels(Rect rect, double scale)
|