Browse Source

Refactored overlays, so most of them are usable without an intermediate layer (#15028)

* [SKIA] Don't skip setting SKCanvas.TotalMatrix when we aren't sure what the current transform is due to Save/Restore calls

* Refactored overlays, so most of them are usable without an intermediate layer

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
pull/15037/head
Nikita Tsukanov 3 years ago
committed by GitHub
parent
commit
34d4d89fbf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 143
      src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs
  2. 8
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  3. 14
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  4. 29
      src/Avalonia.Base/Rendering/Composition/Server/FrameTimeGraph.cs
  5. 118
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

143
src/Avalonia.Base/Rendering/Composition/Server/CompositionTargetOverlays.cs

@ -0,0 +1,143 @@
using System;
using System.Diagnostics;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server;
internal class CompositionTargetOverlays
{
private FpsCounter? _fpsCounter;
private FrameTimeGraph? _renderTimeGraph;
private FrameTimeGraph? _layoutTimeGraph;
private Rect? _oldFpsCounterRect;
private long _updateStarted;
private readonly ServerCompositionTarget _target;
private readonly DiagnosticTextRenderer? _diagnosticTextRenderer;
public CompositionTargetOverlays(
ServerCompositionTarget target,
DiagnosticTextRenderer? diagnosticTextRenderer)
{
_target = target;
_diagnosticTextRenderer = diagnosticTextRenderer;
}
private RendererDebugOverlays DebugOverlays { get; set; }
private FpsCounter? FpsCounter
=> _fpsCounter ??= _diagnosticTextRenderer != null ? new FpsCounter(_diagnosticTextRenderer) : null;
private FrameTimeGraph? LayoutTimeGraph
=> _layoutTimeGraph ??= CreateTimeGraph("Layout");
private FrameTimeGraph? RenderTimeGraph
=> _renderTimeGraph ??= CreateTimeGraph("Render");
public bool RequireLayer => DebugOverlays.HasAnyFlag(RendererDebugOverlays.DirtyRects);
private FrameTimeGraph? CreateTimeGraph(string title)
{
if (_diagnosticTextRenderer == null)
return null;
return new FrameTimeGraph(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, _diagnosticTextRenderer);
}
public void OnChanged(RendererDebugOverlays debugOverlays)
{
DebugOverlays = debugOverlays;
_oldFpsCounterRect = null;
if ((DebugOverlays & RendererDebugOverlays.Fps) == 0)
{
_fpsCounter?.Reset();
}
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) == 0)
{
_layoutTimeGraph?.Reset();
}
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
{
_renderTimeGraph?.Reset();
}
}
private bool CaptureTiming => (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
public void Draw(IDrawingContextImpl targetContext, bool hasLayer)
{
if (DebugOverlays != RendererDebugOverlays.None)
{
if (CaptureTiming)
{
var elapsed = StopwatchHelper.GetElapsedTime(_updateStarted);
RenderTimeGraph?.AddFrameValue(elapsed.TotalMilliseconds);
}
if (DebugOverlays.HasFlag(RendererDebugOverlays.DirtyRects))
_target.DirtyRects.Visualize(targetContext);
targetContext.Transform = Matrix.CreateScale(_target.Scaling, _target.Scaling);
using (var immediate = new ImmediateDrawingContext(targetContext, false))
DrawOverlays(immediate, hasLayer, _target.PixelSize.ToSize(_target.Scaling));
}
}
public void MarkUpdateCallStart() => _updateStarted = CaptureTiming ? Stopwatch.GetTimestamp() : 0L;
private void DrawOverlays(ImmediateDrawingContext targetContext, bool hasLayer, Size logicalSize)
{
if (DebugOverlays.HasFlag(RendererDebugOverlays.Fps))
{
var nativeMem = ByteSizeHelper.ToString((ulong)(
(_target.Compositor.BatchMemoryPool.CurrentUsage + _target.Compositor.BatchMemoryPool.CurrentPool) *
_target.Compositor.BatchMemoryPool.BufferSize), false);
var managedMem = ByteSizeHelper.ToString((ulong)(
(_target.Compositor.BatchObjectPool.CurrentUsage + _target.Compositor.BatchObjectPool.CurrentPool) *
_target.Compositor.BatchObjectPool.ArraySize *
IntPtr.Size), false);
_oldFpsCounterRect = FpsCounter?.RenderFps(targetContext,
FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{_target.RenderedVisuals:0000}"),
hasLayer, _oldFpsCounterRect);
}
var top = 0.0;
void DrawTimeGraph(FrameTimeGraph? graph)
{
if (graph == null)
return;
var left = logicalSize.Width - graph.Size.Width - 8.0;
top += 8.0;
if (!hasLayer)
targetContext.FillRectangle(Brushes.White, new Rect(left, top, graph.Size.Width, graph.Size.Height));
using (targetContext.PushSetTransform(Matrix.CreateTranslation(left, top)))
graph.Render(targetContext);
top += graph.Size.Height;
}
if (DebugOverlays.HasFlag(RendererDebugOverlays.LayoutTimeGraph))
DrawTimeGraph(LayoutTimeGraph);
if (DebugOverlays.HasFlag(RendererDebugOverlays.RenderTimeGraph))
DrawTimeGraph(RenderTimeGraph);
}
public void OnLastLayoutPassTimingChanged(LayoutPassTiming lastLayoutPassTiming)
{
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
{
LayoutTimeGraph?.AddFrameValue(lastLayoutPassTiming.Elapsed.TotalMilliseconds);
}
}
}

8
src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs

@ -58,21 +58,19 @@ namespace Avalonia.Rendering.Composition.Server
return new Size(width, height);
}
public void DrawAsciiText(IDrawingContextImpl context, ReadOnlySpan<char> text, IBrush foreground)
public void DrawAsciiText(ImmediateDrawingContext context, ReadOnlySpan<char> text, IImmutableBrush foreground)
{
var offset = 0.0;
var originalTransform = context.Transform;
foreach (var c in text)
{
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
var run = _runs[effectiveChar - FirstChar];
context.Transform = Matrix.CreateTranslation(offset, 0.0) * originalTransform;
context.DrawGlyphRun(foreground, run.PlatformImpl.Item);
using (context.PushPreTransform(Matrix.CreateTranslation(offset, 0.0)))
context.PlatformImpl.DrawGlyphRun(foreground, run.PlatformImpl.Item);
offset += run.Bounds.Width;
}
context.Transform = originalTransform;
}
}
}

14
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@ -2,6 +2,7 @@ using System;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
@ -26,7 +27,7 @@ internal class FpsCounter
public void FpsTick()
=> _framesThisSecond++;
public void RenderFps(IDrawingContextImpl context, string aux)
public Rect? RenderFps(ImmediateDrawingContext context, string aux, bool hasLayer, Rect? oldRect)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
@ -48,11 +49,18 @@ internal class FpsCounter
#endif
var size = _textRenderer.MeasureAsciiText(fpsLine.AsSpan());
var rect = new Rect(0.0, 0.0, size.Width + 3.0, size.Height + 3.0);
context.DrawRectangle(Brushes.Black, null, rect);
var rect = new Rect(0.0, 0.0, size.Width + 3.0, size.Height + 3.0);
if (hasLayer && oldRect.HasValue)
rect = rect.Union(oldRect.Value);
var layerBr = new ImmutableSolidColorBrush(Colors.Black, 0.5);
context.DrawRectangle(hasLayer ? layerBr : Brushes.Black, null, rect);
_textRenderer.DrawAsciiText(context, fpsLine.AsSpan(), Brushes.White);
return rect;
}
public void Reset()

29
src/Avalonia.Base/Rendering/Composition/Server/FrameTimeGraph.cs

@ -75,37 +75,32 @@ internal sealed class FrameTimeGraph
_frameCount = 0;
}
public void Render(IDrawingContextImpl context)
public void Render(ImmediateDrawingContext context)
{
var originalTransform = context.Transform;
context.PushClip(new Rect(_size));
using var _ = context.PushClip(new Rect(_size));
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_size)));
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_headerSize)));
context.DrawRectangle(_borderBrush, null, new Rect(_size));
context.DrawRectangle(_borderBrush, null, new Rect(_headerSize));
context.Transform = originalTransform * Matrix.CreateTranslation(HeaderPadding, HeaderPadding);
using var __ = context.PushPreTransform(Matrix.CreateTranslation(HeaderPadding, HeaderPadding));
_textRenderer.DrawAsciiText(context, _title.AsSpan(), Brushes.Black);
if (_frameCount > 0)
{
var (min, avg, max) = GetYValues();
DrawLabelledValue(context, "Min", min, originalTransform, _headerSize.Width * 0.19);
DrawLabelledValue(context, "Avg", avg, originalTransform, _headerSize.Width * 0.46);
DrawLabelledValue(context, "Max", max, originalTransform, _headerSize.Width * 0.73);
DrawLabelledValue(context, "Min", min, _headerSize.Width * 0.19);
DrawLabelledValue(context, "Avg", avg, _headerSize.Width * 0.46);
DrawLabelledValue(context, "Max", max, _headerSize.Width * 0.73);
context.Transform = originalTransform * Matrix.CreateTranslation(0.0, _headerSize.Height);
context.DrawGeometry(null, _graphPen, BuildGraphGeometry(Math.Max(max, _defaultMaxY)));
using (context.PushPreTransform(Matrix.CreateTranslation(0.0, _headerSize.Height)))
context.PlatformImpl.DrawGeometry(null, _graphPen, BuildGraphGeometry(Math.Max(max, _defaultMaxY)));
}
context.Transform = originalTransform;
context.PopClip();
}
private void DrawLabelledValue(IDrawingContextImpl context, string label, double value, in Matrix originalTransform,
double left)
private void DrawLabelledValue(ImmediateDrawingContext context, string label, double value, double left)
{
context.Transform = originalTransform * Matrix.CreateTranslation(left + HeaderPadding, HeaderPadding);
using var _ = context.PushPreTransform(Matrix.CreateTranslation(left + HeaderPadding, HeaderPadding));
var brush = value <= _defaultMaxY ? Brushes.Black : Brushes.Red;

118
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -20,12 +20,9 @@ namespace Avalonia.Rendering.Composition.Server
{
private readonly ServerCompositor _compositor;
private readonly Func<IEnumerable<object>> _surfaces;
private readonly DiagnosticTextRenderer? _diagnosticTextRenderer;
private CompositionTargetOverlays _overlays;
private static long s_nextId = 1;
private IRenderTarget? _renderTarget;
private FpsCounter? _fpsCounter;
private FrameTimeGraph? _renderTimeGraph;
private FrameTimeGraph? _layoutTimeGraph;
private PixelSize _layerSize;
private IDrawingContextLayerImpl? _layer;
private bool _updateRequested;
@ -41,22 +38,13 @@ namespace Avalonia.Rendering.Composition.Server
public ReadbackIndices Readback { get; } = new();
public int RenderedVisuals { get; set; }
private FpsCounter? FpsCounter
=> _fpsCounter ??= _diagnosticTextRenderer != null ? new FpsCounter(_diagnosticTextRenderer) : null;
private FrameTimeGraph? LayoutTimeGraph
=> _layoutTimeGraph ??= CreateTimeGraph("Layout");
private FrameTimeGraph? RenderTimeGraph
=> _renderTimeGraph ??= CreateTimeGraph("Render");
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces,
DiagnosticTextRenderer? diagnosticTextRenderer)
: base(compositor)
{
_compositor = compositor;
_surfaces = surfaces;
_diagnosticTextRenderer = diagnosticTextRenderer;
_overlays = new CompositionTargetOverlays(this, diagnosticTextRenderer);
var platformRender = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
DirtyRects = compositor.Options.UseRegionDirtyRectClipping == true &&
platformRender?.SupportsRegions == true
@ -64,14 +52,7 @@ namespace Avalonia.Rendering.Composition.Server
: new DirtyRectTracker();
Id = Interlocked.Increment(ref s_nextId);
}
private FrameTimeGraph? CreateTimeGraph(string title)
{
if (_diagnosticTextRenderer == null)
return null;
return new FrameTimeGraph(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, _diagnosticTextRenderer);
}
partial void OnIsEnabledChanged()
{
if (IsEnabled)
@ -90,29 +71,11 @@ namespace Avalonia.Rendering.Composition.Server
partial void OnDebugOverlaysChanged()
{
if ((DebugOverlays & RendererDebugOverlays.Fps) == 0)
{
_fpsCounter?.Reset();
}
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) == 0)
{
_layoutTimeGraph?.Reset();
}
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
{
_renderTimeGraph?.Reset();
}
_fullRedrawRequested = true;
_overlays.OnChanged(DebugOverlays);
}
partial void OnLastLayoutPassTimingChanged()
{
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
{
LayoutTimeGraph?.AddFrameValue(LastLayoutPassTiming.Elapsed.TotalMilliseconds);
}
}
partial void OnLastLayoutPassTimingChanged() => _overlays.OnLastLayoutPassTimingChanged(LastLayoutPassTiming);
partial void DeserializeChangesExtra(BatchStreamReader c)
{
@ -158,8 +121,7 @@ namespace Avalonia.Rendering.Composition.Server
Revision++;
var captureTiming = (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
var startingTimestamp = captureTiming ? Stopwatch.GetTimestamp() : 0L;
_overlays.MarkUpdateCallStart();
var transform = Matrix.CreateScale(Scaling, Scaling);
// Update happens in a separate phase to extend dirty rect if needed
@ -181,7 +143,7 @@ namespace Avalonia.Rendering.Composition.Server
var renderTargetWithProperties = _renderTarget as IRenderTargetWithProperties;
var needLayer = DebugOverlays != RendererDebugOverlays.None // Check if we don't need overlays
var needLayer = _overlays.RequireLayer // Check if we don't need overlays
// Check if render target can be rendered to directly and preserves the previous frame
|| !(renderTargetWithProperties?.Properties.RetainsPreviousFrameContents == true
&& renderTargetWithProperties?.Properties.IsSuitableForDirectRendering == true);
@ -224,20 +186,13 @@ namespace Avalonia.Rendering.Composition.Server
var rect = new PixelRect(default, PixelSize).ToRect(1);
renderTargetContext.DrawBitmap(_layer, 1, rect, rect);
}
_overlays.Draw(renderTargetContext, true);
}
else
RenderRootToContextWithClip(renderTargetContext, Root);
}
if (DebugOverlays != RendererDebugOverlays.None)
{
if (captureTiming)
{
var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
RenderTimeGraph?.AddFrameValue(elapsed.TotalMilliseconds);
RenderRootToContextWithClip(renderTargetContext, Root);
_overlays.Draw(renderTargetContext, false);
}
DrawOverlays(renderTargetContext, PixelSize.ToSize(Scaling));
}
RenderedVisuals = 0;
@ -265,57 +220,6 @@ namespace Avalonia.Rendering.Composition.Server
}
}
private void DrawOverlays(IDrawingContextImpl targetContext, Size logicalSize)
{
if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
DirtyRects.Visualize(targetContext);
targetContext.Transform = Matrix.CreateScale(Scaling, Scaling);
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)
{
if (graph == null)
return;
top += 8.0;
var oldTransform = targetContext.Transform;
targetContext.Transform = Matrix.CreateTranslation(logicalSize.Width - graph.Size.Width - 8.0, top) *
oldTransform;
graph.Render(targetContext);
top += graph.Size.Height;
targetContext.Transform = oldTransform;
}
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
{
DrawTimeGraph(LayoutTimeGraph);
}
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0)
{
DrawTimeGraph(RenderTimeGraph);
}
targetContext.Transform = Matrix.Identity;
}
public void RequestUpdate() => _updateRequested = true;
public void Dispose()

Loading…
Cancel
Save