339 changed files with 11263 additions and 3780 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -1,8 +1,9 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SharpDX" Version="3.1.1" /> |
|||
<PackageReference Include="SharpDX.Direct2D1" Version="3.1.1" /> |
|||
<PackageReference Include="SharpDX.Direct3D11" Version="3.1.1" /> |
|||
<PackageReference Include="SharpDX.DXGI" Version="3.1.1" /> |
|||
<PackageReference Include="SharpDX" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.DXGI" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the ambient class/property
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = true)] |
|||
public class AmbientAttribute : Attribute |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the property that contains the object's content in markup.
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class TemplateContentAttribute : Attribute |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// Holds resources for a <see cref="Style"/>.
|
|||
/// </summary>
|
|||
public class StyleResources : IDictionary<string, object>, IDictionary |
|||
{ |
|||
private Dictionary<string, object> _inner = new Dictionary<string, object>(); |
|||
|
|||
public object this[string key] |
|||
{ |
|||
get { return _inner[key]; } |
|||
set { _inner[key] = value; } |
|||
} |
|||
|
|||
public int Count => _inner.Count; |
|||
|
|||
ICollection<string> IDictionary<string, object>.Keys => _inner.Keys; |
|||
|
|||
ICollection<object> IDictionary<string, object>.Values => _inner.Values; |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false; |
|||
|
|||
object IDictionary.this[object key] |
|||
{ |
|||
get { return ((IDictionary)_inner)[key]; } |
|||
set { ((IDictionary)_inner)[key] = value; } |
|||
} |
|||
|
|||
ICollection IDictionary.Keys => _inner.Keys; |
|||
|
|||
ICollection IDictionary.Values => _inner.Values; |
|||
|
|||
bool ICollection.IsSynchronized => false; |
|||
|
|||
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot; |
|||
|
|||
bool IDictionary.IsFixedSize => false; |
|||
|
|||
bool IDictionary.IsReadOnly => false; |
|||
|
|||
public void Add(string key, object value) => _inner.Add(key, value); |
|||
|
|||
public void Clear() => _inner.Clear(); |
|||
|
|||
public bool ContainsKey(string key) => _inner.ContainsKey(key); |
|||
|
|||
public bool Remove(string key) => _inner.Remove(key); |
|||
|
|||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _inner.GetEnumerator(); |
|||
|
|||
public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) |
|||
{ |
|||
return ((IDictionary<string, object>)_inner).Contains(item); |
|||
} |
|||
|
|||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) |
|||
{ |
|||
((IDictionary<string, object>)_inner).Add(item); |
|||
} |
|||
|
|||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) |
|||
{ |
|||
((IDictionary<string, object>)_inner).CopyTo(array, arrayIndex); |
|||
} |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) |
|||
{ |
|||
return ((IDictionary<string, object>)_inner).Remove(item); |
|||
} |
|||
|
|||
void ICollection.CopyTo(Array array, int index) => ((IDictionary)_inner).CopyTo(array, index); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); |
|||
|
|||
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator(); |
|||
|
|||
void IDictionary.Add(object key, object value) => ((IDictionary)_inner).Add(key, value); |
|||
|
|||
bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key); |
|||
|
|||
void IDictionary.Remove(object key) => ((IDictionary)_inner).Remove(key); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for brush classes.
|
|||
/// </summary>
|
|||
public static class BrushExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a brush to an immutable brush.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <returns>
|
|||
/// The result of calling <see cref="IMutableBrush.ToImmutable"/> if the brush is mutable,
|
|||
/// otherwise <paramref name="brush"/>.
|
|||
/// </returns>
|
|||
public static IBrush ToImmutable(this IBrush brush) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(brush != null); |
|||
|
|||
return (brush as IMutableBrush)?.ToImmutable() ?? brush; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a pen to a pen with an immutable brush
|
|||
/// </summary>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <returns>
|
|||
/// A copy of the pen with an immutable brush, or <paramref name="pen"/> if the pen's brush
|
|||
/// is already immutable or null.
|
|||
/// </returns>
|
|||
public static Pen ToImmutable(this Pen pen) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(pen != null); |
|||
|
|||
var brush = pen?.Brush?.ToImmutable(); |
|||
return pen == null || ReferenceEquals(pen?.Brush, brush) ? |
|||
pen : |
|||
new Pen( |
|||
brush, |
|||
thickness: pen.Thickness, |
|||
dashStyle: pen.DashStyle, |
|||
dashCap: pen.DashCap, |
|||
startLineCap: pen.StartLineCap, |
|||
endLineCap: pen.EndLineCap, |
|||
lineJoin: pen.LineJoin, |
|||
miterLimit: pen.MiterLimit); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public class DefaultRenderLayerFactory : IRenderLayerFactory |
|||
{ |
|||
private IPlatformRenderInterface _renderInterface; |
|||
|
|||
public DefaultRenderLayerFactory() |
|||
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()) |
|||
{ |
|||
} |
|||
|
|||
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface) |
|||
{ |
|||
_renderInterface = renderInterface; |
|||
} |
|||
|
|||
public IRenderTargetBitmapImpl CreateLayer( |
|||
IVisual layerRoot, |
|||
Size size, |
|||
double dpiX, |
|||
double dpiY) |
|||
{ |
|||
return _renderInterface.CreateRenderTargetBitmap( |
|||
(int)Math.Ceiling(size.Width), |
|||
(int)Math.Ceiling(size.Height), |
|||
dpiX, |
|||
dpiY); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,430 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering.SceneGraph; |
|||
using Avalonia.Threading; |
|||
using Avalonia.VisualTree; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using Avalonia.Media.Immutable; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// A renderer which renders the state of the visual tree to an intermediate scene graph
|
|||
/// representation which is then rendered on a rendering thread.
|
|||
/// </summary>
|
|||
public class DeferredRenderer : RendererBase, IRenderer, IVisualBrushRenderer |
|||
{ |
|||
private readonly IDispatcher _dispatcher; |
|||
private readonly IRenderLoop _renderLoop; |
|||
private readonly IVisual _root; |
|||
private readonly ISceneBuilder _sceneBuilder; |
|||
private readonly RenderLayers _layers; |
|||
private readonly IRenderLayerFactory _layerFactory; |
|||
|
|||
private bool _running; |
|||
private Scene _scene; |
|||
private IRenderTarget _renderTarget; |
|||
private DirtyVisuals _dirty; |
|||
private IRenderTargetBitmapImpl _overlay; |
|||
private bool _updateQueued; |
|||
private object _rendering = new object(); |
|||
private int _lastSceneId = -1; |
|||
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); |
|||
private IDrawOperation _currentDraw; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="root">The control to render.</param>
|
|||
/// <param name="renderLoop">The render loop.</param>
|
|||
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
|
|||
/// <param name="layerFactory">The layer factory to use. Optional.</param>
|
|||
/// <param name="dispatcher">The dispatcher to use. Optional.</param>
|
|||
public DeferredRenderer( |
|||
IRenderRoot root, |
|||
IRenderLoop renderLoop, |
|||
ISceneBuilder sceneBuilder = null, |
|||
IRenderLayerFactory layerFactory = null, |
|||
IDispatcher dispatcher = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(root != null); |
|||
|
|||
_dispatcher = dispatcher ?? Dispatcher.UIThread; |
|||
_root = root; |
|||
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); |
|||
_scene = new Scene(root); |
|||
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); |
|||
_layers = new RenderLayers(_layerFactory); |
|||
_renderLoop = renderLoop; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="root">The control to render.</param>
|
|||
/// <param name="renderTarget">The render target.</param>
|
|||
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
|
|||
/// <param name="layerFactory">The layer factory to use. Optional.</param>
|
|||
/// <remarks>
|
|||
/// This constructor is intended to be used for unit testing.
|
|||
/// </remarks>
|
|||
public DeferredRenderer( |
|||
IVisual root, |
|||
IRenderTarget renderTarget, |
|||
ISceneBuilder sceneBuilder = null, |
|||
IRenderLayerFactory layerFactory = null) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(root != null); |
|||
Contract.Requires<ArgumentNullException>(renderTarget != null); |
|||
|
|||
_root = root; |
|||
_renderTarget = renderTarget; |
|||
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); |
|||
_scene = new Scene(root); |
|||
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); |
|||
_layers = new RenderLayers(_layerFactory); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool DrawFps { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool DrawDirtyRects { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a path to which rendered frame should be rendered for debugging.
|
|||
/// </summary>
|
|||
public string DebugFramesPath { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void AddDirty(IVisual visual) |
|||
{ |
|||
_dirty?.Add(visual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes of the renderer and detaches from the render loop.
|
|||
/// </summary>
|
|||
public void Dispose() => Stop(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter) |
|||
{ |
|||
if (_renderLoop == null && (_dirty == null || _dirty.Count > 0)) |
|||
{ |
|||
// When unit testing the renderLoop may be null, so update the scene manually.
|
|||
UpdateScene(); |
|||
} |
|||
|
|||
return _scene.HitTest(p, filter); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Paint(Rect rect) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Resized(Size size) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Start() |
|||
{ |
|||
if (!_running && _renderLoop != null) |
|||
{ |
|||
_renderLoop.Tick += OnRenderLoopTick; |
|||
_running = true; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Stop() |
|||
{ |
|||
if (_running && _renderLoop != null) |
|||
{ |
|||
_renderLoop.Tick -= OnRenderLoopTick; |
|||
_running = false; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) |
|||
{ |
|||
return (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) |
|||
{ |
|||
var childScene = (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]; |
|||
|
|||
if (childScene != null) |
|||
{ |
|||
Render(context, (VisualNode)childScene.Root, null, new Rect(childScene.Size)); |
|||
} |
|||
} |
|||
|
|||
internal void UnitTestUpdateScene() => UpdateScene(); |
|||
|
|||
internal void UnitTestRender() => Render(_scene); |
|||
|
|||
private void Render(Scene scene) |
|||
{ |
|||
_dirtyRectsDisplay.Tick(); |
|||
|
|||
if (scene.Size != Size.Empty) |
|||
{ |
|||
if (scene.Generation != _lastSceneId) |
|||
{ |
|||
_layers.Update(scene); |
|||
RenderToLayers(scene); |
|||
|
|||
if (DebugFramesPath != null) |
|||
{ |
|||
SaveDebugFrames(scene.Generation); |
|||
} |
|||
|
|||
_lastSceneId = scene.Generation; |
|||
} |
|||
|
|||
RenderOverlay(scene); |
|||
RenderComposite(scene); |
|||
} |
|||
} |
|||
|
|||
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) |
|||
{ |
|||
if (layer == null || node.LayerRoot == layer) |
|||
{ |
|||
clipBounds = node.ClipBounds.Intersect(clipBounds); |
|||
|
|||
if (!clipBounds.IsEmpty) |
|||
{ |
|||
node.BeginRender(context); |
|||
|
|||
foreach (var operation in node.DrawOperations) |
|||
{ |
|||
_currentDraw = operation; |
|||
operation.Render(context); |
|||
_currentDraw = null; |
|||
} |
|||
|
|||
foreach (var child in node.Children) |
|||
{ |
|||
Render(context, (VisualNode)child, layer, clipBounds); |
|||
} |
|||
|
|||
node.EndRender(context); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void RenderToLayers(Scene scene) |
|||
{ |
|||
if (scene.Layers.HasDirty) |
|||
{ |
|||
foreach (var layer in scene.Layers) |
|||
{ |
|||
var renderTarget = _layers[layer.LayerRoot].Bitmap; |
|||
var node = (VisualNode)scene.FindNode(layer.LayerRoot); |
|||
|
|||
if (node != null) |
|||
{ |
|||
using (var context = renderTarget.CreateDrawingContext(this)) |
|||
{ |
|||
foreach (var rect in layer.Dirty) |
|||
{ |
|||
context.Transform = Matrix.Identity; |
|||
context.PushClip(rect); |
|||
context.Clear(Colors.Transparent); |
|||
Render(context, node, layer.LayerRoot, rect); |
|||
context.PopClip(); |
|||
|
|||
if (DrawDirtyRects) |
|||
{ |
|||
_dirtyRectsDisplay.Add(rect); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void RenderOverlay(Scene scene) |
|||
{ |
|||
if (DrawDirtyRects) |
|||
{ |
|||
var overlay = GetOverlay(scene.Size, scene.Scaling); |
|||
|
|||
using (var context = overlay.CreateDrawingContext(this)) |
|||
{ |
|||
context.Clear(Colors.Transparent); |
|||
RenderDirtyRects(context); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
_overlay?.Dispose(); |
|||
_overlay = null; |
|||
} |
|||
} |
|||
|
|||
private void RenderDirtyRects(IDrawingContextImpl context) |
|||
{ |
|||
foreach (var r in _dirtyRectsDisplay) |
|||
{ |
|||
var brush = new ImmutableSolidColorBrush(Colors.Magenta, r.Opacity); |
|||
context.FillRectangle(brush, r.Rect); |
|||
} |
|||
} |
|||
|
|||
private void RenderComposite(Scene scene) |
|||
{ |
|||
try |
|||
{ |
|||
if (_renderTarget == null) |
|||
{ |
|||
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); |
|||
} |
|||
|
|||
using (var context = _renderTarget.CreateDrawingContext(this)) |
|||
{ |
|||
var clientRect = new Rect(scene.Size); |
|||
|
|||
foreach (var layer in scene.Layers) |
|||
{ |
|||
var bitmap = _layers[layer.LayerRoot].Bitmap; |
|||
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); |
|||
|
|||
if (layer.GeometryClip != null) |
|||
{ |
|||
context.PushGeometryClip(layer.GeometryClip); |
|||
} |
|||
|
|||
if (layer.OpacityMask == null) |
|||
{ |
|||
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect); |
|||
} |
|||
else |
|||
{ |
|||
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); |
|||
} |
|||
|
|||
if (layer.GeometryClip != null) |
|||
{ |
|||
context.PopGeometryClip(); |
|||
} |
|||
} |
|||
|
|||
if (_overlay != null) |
|||
{ |
|||
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); |
|||
context.DrawImage(_overlay, 0.5, sourceRect, clientRect); |
|||
} |
|||
|
|||
if (DrawFps) |
|||
{ |
|||
RenderFps(context, clientRect, true); |
|||
} |
|||
} |
|||
} |
|||
catch (RenderTargetCorruptedException ex) |
|||
{ |
|||
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); |
|||
_renderTarget?.Dispose(); |
|||
_renderTarget = null; |
|||
} |
|||
} |
|||
|
|||
private void UpdateScene() |
|||
{ |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
try |
|||
{ |
|||
var scene = _scene.Clone(); |
|||
|
|||
if (_dirty == null) |
|||
{ |
|||
_dirty = new DirtyVisuals(); |
|||
_sceneBuilder.UpdateAll(scene); |
|||
} |
|||
else if (_dirty.Count > 0) |
|||
{ |
|||
foreach (var visual in _dirty) |
|||
{ |
|||
_sceneBuilder.Update(scene, visual); |
|||
} |
|||
} |
|||
|
|||
Interlocked.Exchange(ref _scene, scene); |
|||
|
|||
_dirty.Clear(); |
|||
(_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); |
|||
} |
|||
finally |
|||
{ |
|||
_updateQueued = false; |
|||
} |
|||
} |
|||
|
|||
private void OnRenderLoopTick(object sender, EventArgs e) |
|||
{ |
|||
if (Monitor.TryEnter(_rendering)) |
|||
{ |
|||
try |
|||
{ |
|||
if (!_updateQueued && (_dirty == null || _dirty.Count > 0)) |
|||
{ |
|||
_updateQueued = true; |
|||
_dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render); |
|||
} |
|||
|
|||
Scene scene = null; |
|||
Interlocked.Exchange(ref scene, _scene); |
|||
Render(scene); |
|||
} |
|||
catch { } |
|||
finally |
|||
{ |
|||
Monitor.Exit(_rendering); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling) |
|||
{ |
|||
size = new Size(size.Width * scaling, size.Height * scaling); |
|||
|
|||
if (_overlay == null || |
|||
_overlay.PixelWidth != size.Width || |
|||
_overlay.PixelHeight != size.Height) |
|||
{ |
|||
_overlay?.Dispose(); |
|||
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling); |
|||
} |
|||
|
|||
return _overlay; |
|||
} |
|||
|
|||
private void SaveDebugFrames(int id) |
|||
{ |
|||
var index = 0; |
|||
|
|||
foreach (var layer in _layers) |
|||
{ |
|||
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png"); |
|||
layer.Bitmap.Save(fileName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Tracks dirty rectangles.
|
|||
/// </summary>
|
|||
internal class DirtyRects : IEnumerable<Rect> |
|||
{ |
|||
private List<Rect> _rects = new List<Rect>(); |
|||
|
|||
public bool IsEmpty => _rects.Count == 0; |
|||
|
|||
/// <summary>
|
|||
/// Adds a dirty rectangle, extending an existing dirty rectangle if it intersects.
|
|||
/// </summary>
|
|||
/// <param name="rect">The dirt rectangle.</param>
|
|||
/// <remarks>
|
|||
/// We probably want to do this more intellegently because:
|
|||
/// - Adding e.g. the top left quarter of a scene and the bottom left quarter of a scene
|
|||
/// will cause the whole scene to be invalidated if they overlap by a single pixel
|
|||
/// - Adding two adjacent rectangles that don't overlap will not cause them to be
|
|||
/// coalesced
|
|||
/// - It only coaleces the first intersecting rectangle found - one needs to
|
|||
/// call <see cref="Coalesce"/> at the end of the draw cycle to coalesce the rest.
|
|||
/// </remarks>
|
|||
public void Add(Rect rect) |
|||
{ |
|||
if (!rect.IsEmpty) |
|||
{ |
|||
for (var i = 0; i < _rects.Count; ++i) |
|||
{ |
|||
var r = _rects[i]; |
|||
|
|||
if (r.Intersects(rect)) |
|||
{ |
|||
_rects[i] = r.Union(rect); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_rects.Add(rect); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Works around our flimsy dirt-rect coalescing algorithm.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// See the comments in <see cref="Add(Rect)"/>.
|
|||
/// </remarks>
|
|||
public void Coalesce() |
|||
{ |
|||
for (var i = _rects.Count - 1; i >= 0; --i) |
|||
{ |
|||
var a = _rects[i]; |
|||
|
|||
for (var j = 0; j < i; ++j) |
|||
{ |
|||
var b = _rects[j]; |
|||
|
|||
if (i < _rects.Count && a.Intersects(b)) |
|||
{ |
|||
_rects[i] = _rects[i].Union(b); |
|||
_rects.RemoveAt(i); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty rectangles.
|
|||
/// </summary>
|
|||
/// <returns>A collection of dirty rectangles</returns>
|
|||
public IEnumerator<Rect> GetEnumerator() => _rects.GetEnumerator(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty rectangles.
|
|||
/// </summary>
|
|||
/// <returns>A collection of dirty rectangles</returns>
|
|||
IEnumerator IEnumerable.GetEnumerator() => _rects.GetEnumerator(); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Stores a list of dirty visuals for an <see cref="IRenderer"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This class stores the dirty visuals for a scene, ordered by their distance to the root
|
|||
/// visual. TODO: We probably want to put an upper limit on the number of visuals that can be
|
|||
/// stored and if we reach that limit, assume all visuals are dirty.
|
|||
/// </remarks>
|
|||
internal class DirtyVisuals : IEnumerable<IVisual> |
|||
{ |
|||
private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>(); |
|||
private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of dirty visuals.
|
|||
/// </summary>
|
|||
public int Count => _index.Count; |
|||
|
|||
/// <summary>
|
|||
/// Adds a visual to the dirty list.
|
|||
/// </summary>
|
|||
/// <param name="visual">The dirty visual.</param>
|
|||
public void Add(IVisual visual) |
|||
{ |
|||
var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot); |
|||
int existingDistance; |
|||
|
|||
if (_index.TryGetValue(visual, out existingDistance)) |
|||
{ |
|||
if (distance == existingDistance) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_inner[existingDistance].Remove(visual); |
|||
_index.Remove(visual); |
|||
} |
|||
|
|||
List<IVisual> list; |
|||
|
|||
if (!_inner.TryGetValue(distance, out list)) |
|||
{ |
|||
list = new List<IVisual>(); |
|||
_inner.Add(distance, list); |
|||
} |
|||
|
|||
list.Add(visual); |
|||
_index.Add(visual, distance); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears the list.
|
|||
/// </summary>
|
|||
public void Clear() |
|||
{ |
|||
_inner.Clear(); |
|||
_index.Clear(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes a visual from the dirty list.
|
|||
/// </summary>
|
|||
/// <param name="visual">The visual.</param>
|
|||
/// <returns>True if the visual was present in the list; otherwise false.</returns>
|
|||
public bool Remove(IVisual visual) |
|||
{ |
|||
int distance; |
|||
|
|||
if (_index.TryGetValue(visual, out distance)) |
|||
{ |
|||
_inner[distance].Remove(visual); |
|||
_index.Remove(visual); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty visuals, in ascending order of distance to their root.
|
|||
/// </summary>
|
|||
/// <returns>A collection of visuals.</returns>
|
|||
public IEnumerator<IVisual> GetEnumerator() |
|||
{ |
|||
foreach (var i in _inner) |
|||
{ |
|||
foreach (var j in i.Value) |
|||
{ |
|||
yield return j; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty visuals, in ascending order of distance to their root.
|
|||
/// </summary>
|
|||
/// <returns>A collection of visuals.</returns>
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the state for a dirty rect rendered when <see cref="IRenderer.DrawDirtyRects"/> is set.
|
|||
/// </summary>
|
|||
internal class DisplayDirtyRect |
|||
{ |
|||
public static readonly TimeSpan TimeToLive = TimeSpan.FromMilliseconds(250); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DisplayDirtyRect"/> class.
|
|||
/// </summary>
|
|||
/// <param name="rect">The dirt rect.</param>
|
|||
public DisplayDirtyRect(Rect rect) |
|||
{ |
|||
Rect = rect; |
|||
ResetLifetime(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the bounds of the dirty rectangle.
|
|||
/// </summary>
|
|||
public Rect Rect { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the time at which the rectangle was made dirty.
|
|||
/// </summary>
|
|||
public DateTimeOffset Born { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the time at which the rectagle should no longer be displayed.
|
|||
/// </summary>
|
|||
public DateTimeOffset Dies { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the opacity at which to display the dirty rectangle.
|
|||
/// </summary>
|
|||
public double Opacity => (Dies - DateTimeOffset.UtcNow).TotalMilliseconds / TimeToLive.TotalMilliseconds; |
|||
|
|||
/// <summary>
|
|||
/// Resets the rectangle's lifetime.
|
|||
/// </summary>
|
|||
public void ResetLifetime() |
|||
{ |
|||
Born = DateTimeOffset.UtcNow; |
|||
Dies = Born + TimeToLive; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Holds a collection of <see cref="DisplayDirtyRect"/> objects and manages their aging.
|
|||
/// </summary>
|
|||
internal class DisplayDirtyRects : IEnumerable<DisplayDirtyRect> |
|||
{ |
|||
private List<DisplayDirtyRect> _inner = new List<DisplayDirtyRect>(); |
|||
|
|||
/// <summary>
|
|||
/// Adds new new dirty rect to the collection.
|
|||
/// </summary>
|
|||
/// <param name="rect"></param>
|
|||
public void Add(Rect rect) |
|||
{ |
|||
foreach (var r in _inner) |
|||
{ |
|||
if (r.Rect == rect) |
|||
{ |
|||
r.ResetLifetime(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_inner.Add(new DisplayDirtyRect(rect)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes dirty rects one they are no longer active.
|
|||
/// </summary>
|
|||
public void Tick() |
|||
{ |
|||
var now = DateTimeOffset.UtcNow; |
|||
|
|||
for (var i = _inner.Count - 1; i >= 0; --i) |
|||
{ |
|||
var r = _inner[i]; |
|||
|
|||
if (now > r.Dies) |
|||
{ |
|||
_inner.RemoveAt(i); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty rects.
|
|||
/// </summary>
|
|||
/// <returns>A collection of <see cref="DisplayDirtyRect"/> objects.</returns>
|
|||
public IEnumerator<DisplayDirtyRect> GetEnumerator() => _inner.GetEnumerator(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the dirty rects.
|
|||
/// </summary>
|
|||
/// <returns>A collection of <see cref="DisplayDirtyRect"/> objects.</returns>
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public interface IRenderLayerFactory |
|||
{ |
|||
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a factory for creating <see cref="IRenderer"/> instances.
|
|||
/// </summary>
|
|||
public interface IRendererFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new renderer for the specified render root.
|
|||
/// </summary>
|
|||
/// <param name="root">The render root.</param>
|
|||
/// <param name="renderLoop">The render loop.</param>
|
|||
/// <returns>An instance of an <see cref="IRenderer"/>.</returns>
|
|||
IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public class RenderLayer |
|||
{ |
|||
private readonly IRenderLayerFactory _factory; |
|||
|
|||
public RenderLayer( |
|||
IRenderLayerFactory factory, |
|||
Size size, |
|||
double scaling, |
|||
IVisual layerRoot) |
|||
{ |
|||
_factory = factory; |
|||
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling); |
|||
Size = size; |
|||
Scaling = scaling; |
|||
LayerRoot = layerRoot; |
|||
} |
|||
|
|||
public IRenderTargetBitmapImpl Bitmap { get; private set; } |
|||
public double Scaling { get; private set; } |
|||
public Size Size { get; private set; } |
|||
public IVisual LayerRoot { get; } |
|||
|
|||
public void ResizeBitmap(Size size, double scaling) |
|||
{ |
|||
if (Size != size || Scaling != scaling) |
|||
{ |
|||
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling); |
|||
|
|||
using (var context = resized.CreateDrawingContext(null)) |
|||
{ |
|||
context.Clear(Colors.Transparent); |
|||
context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size)); |
|||
Bitmap.Dispose(); |
|||
Bitmap = resized; |
|||
Size = size; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Rendering.SceneGraph; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public class RenderLayers : IEnumerable<RenderLayer> |
|||
{ |
|||
private readonly IRenderLayerFactory _factory; |
|||
private List<RenderLayer> _inner = new List<RenderLayer>(); |
|||
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>(); |
|||
|
|||
public RenderLayers(IRenderLayerFactory factory) |
|||
{ |
|||
_factory = factory; |
|||
} |
|||
|
|||
public int Count => _inner.Count; |
|||
public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; |
|||
|
|||
public void Update(Scene scene) |
|||
{ |
|||
for (var i = scene.Layers.Count - 1; i >= 0; --i) |
|||
{ |
|||
var src = scene.Layers[i]; |
|||
RenderLayer layer; |
|||
|
|||
if (!_index.TryGetValue(src.LayerRoot, out layer)) |
|||
{ |
|||
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot); |
|||
_inner.Add(layer); |
|||
_index.Add(src.LayerRoot, layer); |
|||
} |
|||
else |
|||
{ |
|||
layer.ResizeBitmap(scene.Size, scene.Scaling); |
|||
} |
|||
} |
|||
|
|||
for (var i = _inner.Count - 1; i >= 0; --i) |
|||
{ |
|||
var layer = _inner[i]; |
|||
|
|||
if (!scene.Layers.Exists(layer.LayerRoot)) |
|||
{ |
|||
layer.Bitmap.Dispose(); |
|||
_inner.RemoveAt(i); |
|||
_index.Remove(layer.LayerRoot); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool TryGetValue(IVisual layerRoot, out RenderLayer value) |
|||
{ |
|||
return _index.TryGetValue(layerRoot, out value); |
|||
} |
|||
|
|||
public IEnumerator<RenderLayer> GetEnumerator() => _inner.GetEnumerator(); |
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for draw operations that can use a brush.
|
|||
/// </summary>
|
|||
internal abstract class BrushDrawOperation : IDrawOperation |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public abstract Rect Bounds { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public abstract bool HitTest(Point p); |
|||
|
|||
/// <summary>
|
|||
/// Gets a collection of child scenes that are needed to draw visual brushes.
|
|||
/// </summary>
|
|||
public abstract IDictionary<IVisual, Scene> ChildScenes { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public abstract void Render(IDrawingContextImpl context); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// A node in the scene graph which represents a clip push or pop.
|
|||
/// </summary>
|
|||
internal class ClipNode : IDrawOperation |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
|
|||
/// clip push.
|
|||
/// </summary>
|
|||
/// <param name="clip">The clip to push.</param>
|
|||
public ClipNode(Rect clip) |
|||
{ |
|||
Clip = clip; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
|
|||
/// clip pop.
|
|||
/// </summary>
|
|||
public ClipNode() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rect Bounds => Rect.Empty; |
|||
|
|||
/// <summary>
|
|||
/// Gets the clip to be pushed or null if the operation represents a pop.
|
|||
/// </summary>
|
|||
public Rect? Clip { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool HitTest(Point p) => false; |
|||
|
|||
/// <summary>
|
|||
/// Determines if this draw operation equals another.
|
|||
/// </summary>
|
|||
/// <param name="clip">The clip of the other draw operation.</param>
|
|||
/// <returns>True if the draw operations are the same, otherwise false.</returns>
|
|||
/// <remarks>
|
|||
/// The properties of the other draw operation are passed in as arguments to prevent
|
|||
/// allocation of a not-yet-constructed draw operation object.
|
|||
/// </remarks>
|
|||
public bool Equals(Rect? clip) => Clip == clip; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Render(IDrawingContextImpl context) |
|||
{ |
|||
if (Clip.HasValue) |
|||
{ |
|||
context.PushClip(Clip.Value); |
|||
} |
|||
else |
|||
{ |
|||
context.PopClip(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,392 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// A drawing context which builds a scene graph.
|
|||
/// </summary>
|
|||
internal class DeferredDrawingContextImpl : IDrawingContextImpl |
|||
{ |
|||
private readonly ISceneBuilder _sceneBuilder; |
|||
private VisualNode _node; |
|||
private int _childIndex; |
|||
private int _drawOperationindex; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DeferredDrawingContextImpl"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sceneBuilder">
|
|||
/// A scene builder used for constructing child scenes for visual brushes.
|
|||
/// </param>
|
|||
/// <param name="layers">The scene layers.</param>
|
|||
public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers) |
|||
{ |
|||
_sceneBuilder = sceneBuilder; |
|||
Layers = layers; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Matrix Transform { get; set; } = Matrix.Identity; |
|||
|
|||
/// <summary>
|
|||
/// Gets the layers in the scene being built.
|
|||
/// </summary>
|
|||
public SceneLayers Layers { get; } |
|||
|
|||
/// <summary>
|
|||
/// Informs the drawing context of the visual node that is about to be rendered.
|
|||
/// </summary>
|
|||
/// <param name="node">The visual node.</param>
|
|||
/// <returns>
|
|||
/// An object which when disposed will commit the changes to visual node.
|
|||
/// </returns>
|
|||
public UpdateState BeginUpdate(VisualNode node) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(node != null); |
|||
|
|||
if (_node != null) |
|||
{ |
|||
if (_childIndex < _node.Children.Count) |
|||
{ |
|||
_node.ReplaceChild(_childIndex, node); |
|||
} |
|||
else |
|||
{ |
|||
_node.AddChild(node); |
|||
} |
|||
|
|||
++_childIndex; |
|||
} |
|||
|
|||
var state = new UpdateState(this, _node, _childIndex, _drawOperationindex); |
|||
_node = node; |
|||
_childIndex = _drawOperationindex = 0; |
|||
return state; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Clear(Color color) |
|||
{ |
|||
// Cannot clear a deferred scene.
|
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
// Nothing to do here as we allocate no unmanaged resources.
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes any remaining drawing operations from the visual node.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Drawing operations are updated in place, overwriting existing drawing operations if
|
|||
/// they are different. Once drawing has completed for the current visual node, it is
|
|||
/// possible that there are stale drawing operations at the end of the list. This method
|
|||
/// trims these stale drawing operations.
|
|||
/// </remarks>
|
|||
public void TrimChildren() |
|||
{ |
|||
_node.TrimChildren(_childIndex); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry) |
|||
{ |
|||
var next = NextDrawAs<GeometryNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, brush, pen, geometry)) |
|||
{ |
|||
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) |
|||
{ |
|||
var next = NextDrawAs<ImageNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, source, opacity, sourceRect, destRect)) |
|||
{ |
|||
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect)); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect) |
|||
{ |
|||
// This method is currently only used to composite layers so shouldn't be called here.
|
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawLine(Pen pen, Point p1, Point p2) |
|||
{ |
|||
var next = NextDrawAs<LineNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, pen, p1, p2)) |
|||
{ |
|||
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0) |
|||
{ |
|||
var next = NextDrawAs<RectangleNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, null, pen, rect, cornerRadius)) |
|||
{ |
|||
Add(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) |
|||
{ |
|||
var next = NextDrawAs<TextNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, foreground, origin, text)) |
|||
{ |
|||
Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0) |
|||
{ |
|||
var next = NextDrawAs<RectangleNode>(); |
|||
|
|||
if (next == null || !next.Equals(Transform, brush, null, rect, cornerRadius)) |
|||
{ |
|||
Add(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PopClip() |
|||
{ |
|||
var next = NextDrawAs<ClipNode>(); |
|||
|
|||
if (next == null || !next.Equals(null)) |
|||
{ |
|||
Add(new ClipNode()); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PopGeometryClip() |
|||
{ |
|||
var next = NextDrawAs<GeometryClipNode>(); |
|||
|
|||
if (next == null || !next.Equals(null)) |
|||
{ |
|||
Add(new GeometryClipNode()); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PopOpacity() |
|||
{ |
|||
var next = NextDrawAs<OpacityNode>(); |
|||
|
|||
if (next == null || !next.Equals(null)) |
|||
{ |
|||
Add(new OpacityNode()); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PopOpacityMask() |
|||
{ |
|||
var next = NextDrawAs<OpacityMaskNode>(); |
|||
|
|||
if (next == null || !next.Equals(null, null)) |
|||
{ |
|||
Add(new OpacityMaskNode()); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PushClip(Rect clip) |
|||
{ |
|||
var next = NextDrawAs<ClipNode>(); |
|||
|
|||
if (next == null || !next.Equals(clip)) |
|||
{ |
|||
Add(new ClipNode(clip)); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PushGeometryClip(IGeometryImpl clip) |
|||
{ |
|||
var next = NextDrawAs<GeometryClipNode>(); |
|||
|
|||
if (next == null || !next.Equals(clip)) |
|||
{ |
|||
Add(new GeometryClipNode(clip)); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PushOpacity(double opacity) |
|||
{ |
|||
var next = NextDrawAs<OpacityNode>(); |
|||
|
|||
if (next == null || !next.Equals(opacity)) |
|||
{ |
|||
Add(new OpacityNode(opacity)); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void PushOpacityMask(IBrush mask, Rect bounds) |
|||
{ |
|||
var next = NextDrawAs<OpacityMaskNode>(); |
|||
|
|||
if (next == null || !next.Equals(mask, bounds)) |
|||
{ |
|||
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask))); |
|||
} |
|||
else |
|||
{ |
|||
++_drawOperationindex; |
|||
} |
|||
} |
|||
|
|||
public struct UpdateState : IDisposable |
|||
{ |
|||
public UpdateState( |
|||
DeferredDrawingContextImpl owner, |
|||
VisualNode node, |
|||
int childIndex, |
|||
int drawOperationIndex) |
|||
{ |
|||
Owner = owner; |
|||
Node = node; |
|||
ChildIndex = childIndex; |
|||
DrawOperationIndex = drawOperationIndex; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Owner._node.TrimDrawOperations(Owner._drawOperationindex); |
|||
|
|||
var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot).Dirty; |
|||
|
|||
foreach (var operation in Owner._node.DrawOperations) |
|||
{ |
|||
dirty.Add(operation.Bounds); |
|||
} |
|||
|
|||
Owner._node = Node; |
|||
Owner._childIndex = ChildIndex; |
|||
Owner._drawOperationindex = DrawOperationIndex; |
|||
} |
|||
|
|||
public DeferredDrawingContextImpl Owner { get; } |
|||
public VisualNode Node { get; } |
|||
public int ChildIndex { get; } |
|||
public int DrawOperationIndex { get; } |
|||
} |
|||
|
|||
private void Add(IDrawOperation node) |
|||
{ |
|||
if (_drawOperationindex < _node.DrawOperations.Count) |
|||
{ |
|||
_node.ReplaceDrawOperation(_drawOperationindex, node); |
|||
} |
|||
else |
|||
{ |
|||
_node.AddDrawOperation(node); |
|||
} |
|||
|
|||
++_drawOperationindex; |
|||
} |
|||
|
|||
private T NextDrawAs<T>() where T : class, IDrawOperation |
|||
{ |
|||
return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as T : null; |
|||
} |
|||
|
|||
private IDictionary<IVisual, Scene> CreateChildScene(IBrush brush) |
|||
{ |
|||
var visualBrush = brush as VisualBrush; |
|||
|
|||
if (visualBrush != null) |
|||
{ |
|||
var visual = visualBrush.Visual; |
|||
|
|||
if (visual != null) |
|||
{ |
|||
(visual as IVisualBrushInitialize)?.EnsureInitialized(); |
|||
var scene = new Scene(visual); |
|||
_sceneBuilder.UpdateAll(scene); |
|||
return new Dictionary<IVisual, Scene> { { visualBrush.Visual, scene } }; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// A node in the scene graph which represents a geometry clip push or pop.
|
|||
/// </summary>
|
|||
internal class GeometryClipNode : IDrawOperation |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
|
|||
/// geometry clip push.
|
|||
/// </summary>
|
|||
/// <param name="clip">The clip to push.</param>
|
|||
public GeometryClipNode(IGeometryImpl clip) |
|||
{ |
|||
Clip = clip; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
|
|||
/// geometry clip pop.
|
|||
/// </summary>
|
|||
public GeometryClipNode() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rect Bounds => Rect.Empty; |
|||
|
|||
/// <summary>
|
|||
/// Gets the clip to be pushed or null if the operation represents a pop.
|
|||
/// </summary>
|
|||
public IGeometryImpl Clip { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool HitTest(Point p) => false; |
|||
|
|||
/// <summary>
|
|||
/// Determines if this draw operation equals another.
|
|||
/// </summary>
|
|||
/// <param name="clip">The clip of the other draw operation.</param>
|
|||
/// <returns>True if the draw operations are the same, otherwise false.</returns>
|
|||
/// <remarks>
|
|||
/// The properties of the other draw operation are passed in as arguments to prevent
|
|||
/// allocation of a not-yet-constructed draw operation object.
|
|||
/// </remarks>
|
|||
public bool Equals(IGeometryImpl clip) => Clip == clip; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Render(IDrawingContextImpl context) |
|||
{ |
|||
if (Clip != null) |
|||
{ |
|||
context.PushGeometryClip(Clip); |
|||
} |
|||
else |
|||
{ |
|||
context.PopGeometryClip(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// A node in the scene graph which represents a geometry draw.
|
|||
/// </summary>
|
|||
internal class GeometryNode : BrushDrawOperation |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GeometryNode"/> class.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform.</param>
|
|||
/// <param name="brush">The fill brush.</param>
|
|||
/// <param name="pen">The stroke pen.</param>
|
|||
/// <param name="geometry">The geometry.</param>
|
|||
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
|
|||
public GeometryNode( |
|||
Matrix transform, |
|||
IBrush brush, |
|||
Pen pen, |
|||
IGeometryImpl geometry, |
|||
IDictionary<IVisual, Scene> childScenes = null) |
|||
{ |
|||
Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform); |
|||
Transform = transform; |
|||
Brush = brush?.ToImmutable(); |
|||
Pen = pen?.ToImmutable(); |
|||
Geometry = geometry; |
|||
ChildScenes = childScenes; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Rect Bounds { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform with which the node will be drawn.
|
|||
/// </summary>
|
|||
public Matrix Transform { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fill brush.
|
|||
/// </summary>
|
|||
public IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the stroke pen.
|
|||
/// </summary>
|
|||
public Pen Pen { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the geometry to draw.
|
|||
/// </summary>
|
|||
public IGeometryImpl Geometry { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override IDictionary<IVisual, Scene> ChildScenes { get; } |
|||
|
|||
/// <summary>
|
|||
/// Determines if this draw operation equals another.
|
|||
/// </summary>
|
|||
/// <param name="transform">The transform of the other draw operation.</param>
|
|||
/// <param name="brush">The fill of the other draw operation.</param>
|
|||
/// <param name="pen">The stroke of the other draw operation.</param>
|
|||
/// <param name="geometry">The geometry of the other draw operation.</param>
|
|||
/// <returns>True if the draw operations are the same, otherwise false.</returns>
|
|||
/// <remarks>
|
|||
/// The properties of the other draw operation are passed in as arguments to prevent
|
|||
/// allocation of a not-yet-constructed draw operation object.
|
|||
/// </remarks>
|
|||
public bool Equals(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry) |
|||
{ |
|||
return transform == Transform && |
|||
Equals(brush, Brush) && |
|||
pen == Pen && |
|||
Equals(geometry, Geometry); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Render(IDrawingContextImpl context) |
|||
{ |
|||
context.Transform = Transform; |
|||
context.DrawGeometry(Brush, Pen, Geometry); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool HitTest(Point p) |
|||
{ |
|||
p *= Transform.Invert(); |
|||
return (Brush != null && Geometry.FillContains(p)) || |
|||
(Pen != null && Geometry.StrokeContains(Pen, p)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a node in the low-level scene graph that represents geometry.
|
|||
/// </summary>
|
|||
public interface IDrawOperation |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the bounds of the visible content in the node.
|
|||
/// </summary>
|
|||
Rect Bounds { get; } |
|||
|
|||
/// <summary>
|
|||
/// Hit test the geometry in this node.
|
|||
/// </summary>
|
|||
/// <param name="p">The point in global coordinates.</param>
|
|||
/// <returns>True if the point hits the node's geometry; otherwise false.</returns>
|
|||
/// <remarks>
|
|||
/// This method does not recurse to child <see cref="IVisualNode"/>s, if you want
|
|||
/// to hit test children they must be hit tested manually.
|
|||
/// </remarks>
|
|||
bool HitTest(Point p); |
|||
|
|||
/// <summary>
|
|||
/// Renders the node to a drawing context.
|
|||
/// </summary>
|
|||
/// <param name="context">The drawing context.</param>
|
|||
void Render(IDrawingContextImpl context); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue