339 changed files with 10918 additions and 3645 deletions
@ -1,13 +1,7 @@ |
|||
[submodule "src/Avalonia.ReactiveUI/src"] |
|||
path = src/Avalonia.ReactiveUI/src |
|||
url = https://github.com/reactiveui/ReactiveUI.git |
|||
[submodule "src/Avalonia.HtmlRenderer/external"] |
|||
path = src/Avalonia.HtmlRenderer/external |
|||
url = https://github.com/AvaloniaUI/HTML-Renderer.git |
|||
branch = perspex-pcl |
|||
[submodule "src/Markup/Avalonia.Markup.Xaml/OmniXAML"] |
|||
path = src/Markup/Avalonia.Markup.Xaml/OmniXAML |
|||
url = https://github.com/AvaloniaUI/OmniXAML.git |
|||
[submodule "src/Markup/Avalonia.Markup.Xaml/glass"] |
|||
path = src/Markup/Avalonia.Markup.Xaml/glass |
|||
url = https://github.com/SuperJMN/glass |
|||
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] |
|||
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github |
|||
url = https://github.com/AvaloniaUI/Portable.Xaml.git |
|||
|
|||
@ -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> |
|||
@ -0,0 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="reactiveui" Version="8.0.0-alpha0034" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,11 +1,10 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.Reactive" Version="3.0.0" /> |
|||
<PackageReference Include="System.Reactive.Core" Version="3.0.0" /> |
|||
<PackageReference Include="System.Reactive.Interfaces" Version="3.0.0" /> |
|||
<PackageReference Include="System.Reactive.Linq" Version="3.0.0" /> |
|||
<PackageReference Include="System.Reactive.PlatformServices" Version="3.0.0" /> |
|||
<PackageReference Condition="'$(TargetFramework)' == 'net45'" Include="System.Reactive.Windows.Threading" Version="3.0.0" /> |
|||
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="System.Reactive.Windows.Threading" Version="3.0.0" /> |
|||
<PackageReference Include="System.Reactive" Version="3.1.0" /> |
|||
<PackageReference Include="System.Reactive.Core" Version="3.1.0" /> |
|||
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.0" /> |
|||
<PackageReference Include="System.Reactive.Linq" Version="3.1.0" /> |
|||
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.0" /> |
|||
<PackageReference Condition="$(TargetFramework.StartsWith('net4'))" Include="System.Reactive.Windows.Threading" Version="3.1.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,9 +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.Direct3D9" Version="3.1.1" Condition="'$(UseDirect3D9)' == 'true'" /> |
|||
<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,24 @@ |
|||
// 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 Avalonia.Controls; |
|||
using Avalonia.Threading; |
|||
using ReactiveUI; |
|||
using System; |
|||
using System.Reactive.Concurrency; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class AppBuilderExtensions |
|||
{ |
|||
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder) |
|||
where TAppBuilder : AppBuilderBase<TAppBuilder>, new() |
|||
{ |
|||
return builder.AfterSetup(_ => |
|||
{ |
|||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// 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.Reactive.Concurrency; |
|||
using System.Threading; |
|||
|
|||
|
|||
namespace ReactiveUI |
|||
{ |
|||
/// <summary>
|
|||
/// Ignore me. This class is a secret handshake between RxUI and RxUI.Xaml
|
|||
/// in order to register certain classes on startup that would be difficult
|
|||
/// to register otherwise.
|
|||
/// </summary>
|
|||
public class PlatformRegistrations : IWantsToRegisterStuff |
|||
{ |
|||
public void Register(Action<Func<object>, Type> registerFunction) |
|||
{ |
|||
RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current); |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace System.Runtime.Serialization |
|||
{ |
|||
class IgnoreDataMemberAttribute : Attribute |
|||
{ |
|||
} |
|||
|
|||
class DataMemberAttribute : Attribute |
|||
{ |
|||
} |
|||
class OnDeserializedAttribute : Attribute |
|||
{ |
|||
} |
|||
|
|||
class DataContractAttribute : Attribute |
|||
{ |
|||
} |
|||
|
|||
class StreamingContext { } |
|||
} |
|||
|
|||
namespace System.Diagnostics.Contracts |
|||
{ |
|||
static class Contract |
|||
{ |
|||
public static void Requires(bool condition) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -1 +0,0 @@ |
|||
Subproject commit 3f725c808b1d4c8457f0d3204e0a071aa462cd75 |
|||
@ -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); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue