Browse Source

Merge branch 'master' into MethodsAsCommands

pull/1179/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
f6208c100a
  1. 3
      appveyor.yml
  2. 2
      build/NetCore.props
  3. 6
      docs/guidelines/build.md
  4. 4
      readme.md
  5. BIN
      samples/ControlCatalog/Assets/test_icon.ico
  6. 6
      samples/ControlCatalog/ControlCatalog.csproj
  7. 1
      samples/ControlCatalog/MainView.xaml
  8. 24
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  9. 18
      samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs
  10. 4
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  11. 5
      samples/interop/Direct3DInteropSample/MainWindow.cs
  12. 2
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  13. 3
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  14. 9
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  15. 13
      src/Avalonia.Base/Threading/Dispatcher.cs
  16. 18
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  17. 15
      src/Avalonia.Controls/MenuItem.cs
  18. 21
      src/Avalonia.Controls/Orientation.cs
  19. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  20. 139
      src/Avalonia.Controls/ProgressBar.cs
  21. 27
      src/Avalonia.Controls/StackPanel.cs
  22. 4
      src/Avalonia.Controls/TreeView.cs
  23. 3
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  24. 42
      src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs
  25. 5
      src/Avalonia.Themes.Default/ProgressBar.xaml
  26. 14
      src/Avalonia.Themes.Default/RepeatButton.xaml
  27. 29
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  28. 34
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  29. 161
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  30. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  31. 10
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  32. 9
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  33. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  34. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  35. 26
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  36. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  37. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  38. 11
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  39. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  40. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  41. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  42. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  43. 34
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  44. 11
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  45. 2
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  46. 31
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  47. 6
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  48. 38
      src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs
  49. 34
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  50. 47
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  51. 9
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  52. 54
      src/Gtk/Avalonia.Gtk3/X11.cs
  53. 55
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  54. 4
      src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs
  55. 2
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  56. 142
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  57. 23
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  58. 15
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  59. 6
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  60. 28
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  61. 84
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  62. 10
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  63. 39
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  64. 7
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  65. 32
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  66. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  67. 68
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  68. 57
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  69. 17
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  70. 22
      src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
  71. 34
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  72. 40
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  73. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  74. 2
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs
  75. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  76. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs
  77. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  78. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  79. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  80. 8
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  81. 2
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  82. 4
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  83. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  84. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  85. 11
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  86. 2
      tests/Avalonia.RenderTests/TestBase.cs
  87. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  88. 70
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  89. 55
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  90. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  91. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  92. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

3
appveyor.yml

@ -16,9 +16,7 @@ environment:
init: init:
- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")} - ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
install: install:
- if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
- if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe" - if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe"
- ps: Start-Process -FilePath "msiexec" -ArgumentList "/i gtk-sharp-2.12.26.msi /quiet /qn /norestart" -Wait
- ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait - ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait
- cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH% - cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH%
before_build: before_build:
@ -36,5 +34,4 @@ artifacts:
- path: artifacts\zip\*.zip - path: artifacts\zip\*.zip
- path: artifacts\inspectcode.xml - path: artifacts\inspectcode.xml
cache: cache:
- gtk-sharp-2.12.26.msi
- dotnet-2.0.0.exe - dotnet-2.0.0.exe

2
build/NetCore.props

@ -1,6 +1,4 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

6
docs/guidelines/build.md

@ -4,12 +4,6 @@
Avalonia requires at least Visual Studio 2017 and .NET Core SDK 2.0 to build on Windows. Avalonia requires at least Visual Studio 2017 and .NET Core SDK 2.0 to build on Windows.
### Install GTK Sharp
For the moment under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win)
installed. Note that after installing the package your machine may require a restart before GTK# is
added to your path. We hope to remove or make this dependency optional at some point in the future.
### Clone the Avalonia repository ### Clone the Avalonia repository
``` ```

4
readme.md

@ -7,7 +7,7 @@
A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
![](docs/images/screen.png) [![](docs/images/screen.png)](https://youtu.be/wHcB3sGLVYg)
Desktop platforms: Desktop platforms:
@ -36,7 +36,7 @@ Try out the ControlCatalog to give it a quick demo.
Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi- Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi-
platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows
using Direct2D and other operating systems using Gtk & Cairo. using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc).
## Current Status ## Current Status

BIN
samples/ControlCatalog/Assets/test_icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 172 KiB

6
samples/ControlCatalog/ControlCatalog.csproj

@ -69,6 +69,9 @@
<EmbeddedResource Include="Pages\MenuPage.xaml"> <EmbeddedResource Include="Pages\MenuPage.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Pages\ProgressBarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\RadioButtonPage.xaml"> <EmbeddedResource Include="Pages\RadioButtonPage.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </EmbeddedResource>
@ -131,6 +134,9 @@
<Compile Include="Pages\MenuPage.xaml.cs"> <Compile Include="Pages\MenuPage.xaml.cs">
<DependentUpon>MenuPage.xaml</DependentUpon> <DependentUpon>MenuPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Pages\ProgressBarPage.xaml.cs">
<DependentUpon>ProgressBarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RadioButtonPage.xaml.cs"> <Compile Include="Pages\RadioButtonPage.xaml.cs">
<DependentUpon>RadioButtonPage.xaml</DependentUpon> <DependentUpon>RadioButtonPage.xaml</DependentUpon>
</Compile> </Compile>

1
samples/ControlCatalog/MainView.xaml

@ -16,6 +16,7 @@
<TabItem Header="Image"><pages:ImagePage/></TabItem> <TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem> <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem> <TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem> <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem> <TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem> <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>

24
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -0,0 +1,24 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">ProgressBar</TextBlock>
<TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Gap="16">
<ProgressBar Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="True"/>
</StackPanel>
<ProgressBar Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar Orientation="Vertical" IsIndeterminate="True" />
</StackPanel>
<StackPanel Margin="16">
<Slider Name="hprogress" Maximum="100" Value="40"/>
<Slider Name="vprogress" Maximum="100" Value="60"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ProgressBarPage : UserControl
{
public ProgressBarPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

4
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Border Grid.Column="0" <Border Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}" Background="{DynamicResource ThemeAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Tip="This is a ToolTip"> ToolTip.Tip="This is a ToolTip">
@ -24,7 +24,7 @@
<Border Name="Border" <Border Name="Border"
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}" Background="{DynamicResource ThemeAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Placement="Bottom"> ToolTip.Placement="Bottom">

5
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -253,8 +253,9 @@ namespace Direct3DInteropSample
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>()); AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
} }
} }

2
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -30,7 +30,7 @@ namespace Avalonia.Android
return; return;
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
if (interval.TotalMilliseconds < 10) if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10); interval = TimeSpan.FromMilliseconds(10);

3
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@ -17,10 +17,11 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Starts a timer. /// Starts a timer.
/// </summary> /// </summary>
/// <param name="priority"></param>
/// <param name="interval">The interval.</param> /// <param name="interval">The interval.</param>
/// <param name="tick">The action to call on each tick.</param> /// <param name="tick">The action to call on each tick.</param>
/// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns> /// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns>
IDisposable StartTimer(TimeSpan interval, Action tick); IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick);
void Signal(DispatcherPriority priority); void Signal(DispatcherPriority priority);

9
src/Avalonia.Base/Platform/IRuntimePlatform.cs

@ -14,6 +14,15 @@ namespace Avalonia.Platform
IDisposable StartSystemTimer(TimeSpan interval, Action tick); IDisposable StartSystemTimer(TimeSpan interval, Action tick);
string GetStackTrace(); string GetStackTrace();
RuntimePlatformInfo GetRuntimeInfo(); RuntimePlatformInfo GetRuntimeInfo();
IUnmanagedBlob AllocBlob(int size);
}
public interface IUnmanagedBlob : IDisposable
{
IntPtr Address { get; }
int Size { get; }
bool IsDisposed { get; }
} }
public struct RuntimePlatformInfo public struct RuntimePlatformInfo

13
src/Avalonia.Base/Threading/Dispatcher.cs

@ -84,6 +84,19 @@ namespace Avalonia.Threading
_jobRunner?.Post(action, priority); _jobRunner?.Post(action, priority);
} }
/// <summary>
/// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority
/// </summary>
/// <param name="currentPriority"></param>
internal void EnsurePriority(DispatcherPriority currentPriority)
{
if (currentPriority == DispatcherPriority.MaxValue)
return;
currentPriority += 1;
_jobRunner.RunJobs(currentPriority);
}
/// <summary> /// <summary>
/// Allows unit tests to change the platform threading interface. /// Allows unit tests to change the platform threading interface.
/// </summary> /// </summary>

18
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -17,13 +17,11 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority; private readonly DispatcherPriority _priority;
private TimeSpan _interval; private TimeSpan _interval;
private readonly Action _raiseTickAction;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class. /// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary> /// </summary>
public DispatcherTimer() : this(DispatcherPriority.Normal) public DispatcherTimer() : this(DispatcherPriority.Background)
{ {
} }
@ -34,7 +32,6 @@ namespace Avalonia.Threading
public DispatcherTimer(DispatcherPriority priority) public DispatcherTimer(DispatcherPriority priority)
{ {
_priority = priority; _priority = priority;
_raiseTickAction = RaiseTick;
} }
/// <summary> /// <summary>
@ -187,7 +184,7 @@ namespace Avalonia.Threading
throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered.");
} }
_timer = threading.StartTimer(Interval, InternalTick); _timer = threading.StartTimer(_priority, Interval, InternalTick);
} }
} }
@ -210,14 +207,7 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
private void InternalTick() private void InternalTick()
{ {
Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority); Dispatcher.UIThread.EnsurePriority(_priority);
}
/// <summary>
/// Raises the <see cref="Tick"/> event.
/// </summary>
private void RaiseTick()
{
Tick?.Invoke(this, EventArgs.Empty); Tick?.Invoke(this, EventArgs.Empty);
} }
} }

15
src/Avalonia.Controls/MenuItem.cs

@ -307,6 +307,21 @@ namespace Avalonia.Controls
() => IsSubMenuOpen = true, () => IsSubMenuOpen = true,
TimeSpan.FromMilliseconds(400)); TimeSpan.FromMilliseconds(400));
} }
else
{
var parentItem = Parent as MenuItem;
if (parentItem != null)
{
foreach (var sibling in parentItem.Items
.OfType<MenuItem>()
.Where(x => x != this && x.IsSubMenuOpen))
{
sibling.CloseSubmenus();
sibling.IsSubMenuOpen = false;
sibling.IsSelected = false;
}
}
}
} }
/// <summary> /// <summary>

21
src/Avalonia.Controls/Orientation.cs

@ -0,0 +1,21 @@
// 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.
namespace Avalonia.Controls
{
/// <summary>
/// Defines vertical or horizontal orientation.
/// </summary>
public enum Orientation
{
/// <summary>
/// Horizontal orientation.
/// </summary>
Horizontal,
/// <summary>
/// Vertical orientation.
/// </summary>
Vertical,
}
}

2
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="Orientation"/> property. /// Defines the <see cref="Orientation"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation)); AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
private Button _lineUpButton; private Button _lineUpButton;
private Button _lineDownButton; private Button _lineDownButton;

139
src/Avalonia.Controls/ProgressBar.cs

@ -1,8 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Layout;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -11,11 +15,38 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class ProgressBar : RangeBase public class ProgressBar : RangeBase
{ {
public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
private Border _indicator; private Border _indicator;
private IndeterminateAnimation _indeterminateAnimation;
static ProgressBar() static ProgressBar()
{ {
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged); ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
}
public bool IsIndeterminate
{
get => GetValue(IsIndeterminateProperty);
set => SetValue(IsIndeterminateProperty, value);
}
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -29,21 +60,123 @@ namespace Avalonia.Controls
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
_indicator = e.NameScope.Get<Border>("PART_Indicator"); _indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size); UpdateIndicator(Bounds.Size);
UpdateOrientation(Orientation);
UpdateIsIndeterminate(IsIndeterminate);
} }
private void UpdateIndicator(Size bounds) private void UpdateIndicator(Size bounds)
{ {
if (_indicator != null) if (_indicator != null)
{ {
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); if (IsIndeterminate)
_indicator.Width = bounds.Width * percent; {
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width / 5.0;
else
_indicator.Height = bounds.Height / 5.0;
}
else
{
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width * percent;
else
_indicator.Height = bounds.Height * percent;
}
}
}
private void UpdateOrientation(Orientation orientation)
{
if (orientation == Orientation.Horizontal)
{
MinHeight = 14;
MinWidth = 200;
_indicator.HorizontalAlignment = HorizontalAlignment.Left;
_indicator.VerticalAlignment = VerticalAlignment.Stretch;
}
else
{
MinHeight = 200;
MinWidth = 14;
_indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
_indicator.VerticalAlignment = VerticalAlignment.Bottom;
} }
} }
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)
if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
_indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
else
_indeterminateAnimation?.Dispose();
}
private void ValueChanged(AvaloniaPropertyChangedEventArgs e) private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
{ {
UpdateIndicator(Bounds.Size); UpdateIndicator(Bounds.Size);
} }
private class IndeterminateAnimation : IDisposable
{
private WeakReference<ProgressBar> _progressBar;
private IDisposable _indeterminateBindSubscription;
private TimeSpan _startTime;
private bool _disposed;
public bool Disposed => _disposed;
private IndeterminateAnimation(ProgressBar progressBar)
{
_progressBar = new WeakReference<ProgressBar>(progressBar);
_startTime = Animate.Stopwatch.Elapsed;
_indeterminateBindSubscription = Animate.Timer.TakeWhile(x => (x - _startTime).TotalSeconds <= 4.0)
.Select(GetAnimationRect)
.Finally(() => _startTime = Animate.Stopwatch.Elapsed)
.Repeat()
.Subscribe(AnimationTick);
}
public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
{
return new IndeterminateAnimation(progressBar);
}
private Rect GetAnimationRect(TimeSpan time)
{
if (_progressBar.TryGetTarget(out var progressBar))
{
if (progressBar.Orientation == Orientation.Horizontal)
return new Rect(-progressBar._indicator.Width - 5 + (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Width + progressBar._indicator.Width + 10), 0, progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
else
return new Rect(0, progressBar.Bounds.Height + 5 - (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Height + progressBar._indicator.Height + 10), progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
}
else
{
_indeterminateBindSubscription.Dispose();
return Rect.Empty;
}
}
private void AnimationTick(Rect rect)
{
if (_progressBar.TryGetTarget(out var progressBar))
progressBar._indicator.Arrange(rect);
else
_indeterminateBindSubscription.Dispose();
}
public void Dispose()
{
_indeterminateBindSubscription?.Dispose();
_disposed = true;
}
}
} }
} }

27
src/Avalonia.Controls/StackPanel.cs

@ -6,22 +6,6 @@ using Avalonia.Input;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary>
/// Defines vertical or horizontal orientation.
/// </summary>
public enum Orientation
{
/// <summary>
/// Vertical orientation.
/// </summary>
Vertical,
/// <summary>
/// Horizontal orientation.
/// </summary>
Horizontal,
}
/// <summary> /// <summary>
/// A panel which lays out its children horizontally or vertically. /// A panel which lays out its children horizontally or vertically.
/// </summary> /// </summary>
@ -37,7 +21,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="Orientation"/> property. /// Defines the <see cref="Orientation"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation)); AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation), Orientation.Vertical);
/// <summary> /// <summary>
/// Initializes static members of the <see cref="StackPanel"/> class. /// Initializes static members of the <see cref="StackPanel"/> class.
@ -186,6 +170,15 @@ namespace Avalonia.Controls
} }
} }
if (Orientation == Orientation.Vertical)
{
measuredHeight -= gap;
}
else
{
measuredWidth -= gap;
}
return new Size(measuredWidth, measuredHeight); return new Size(measuredWidth, measuredHeight);
} }

4
src/Avalonia.Controls/TreeView.cs

@ -253,9 +253,7 @@ namespace Avalonia.Controls
if (AutoScrollToSelectedItem) if (AutoScrollToSelectedItem)
{ {
DispatcherTimer.RunOnce( Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView);
container.ContainerControl.BringIntoView,
TimeSpan.Zero);
} }
break; break;

3
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile> <DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
@ -21,5 +22,5 @@
<ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" /> <ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\NetCore.props" /> <Import Project="..\..\build\NetCore.props" />
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" /> <Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" />
</Project> </Project>

42
src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;
namespace Avalonia.Shared.PlatformSupport
{
internal partial class StandardRuntimePlatform
{
private static readonly Lazy<Assembly[]> Assemblies = new Lazy<Assembly[]>(LoadAssemblies);
public Assembly[] GetLoadedAssemblies() => Assemblies.Value;
static Assembly[] LoadAssemblies()
{
var assemblies = new List<Assembly>();
// Mostly copy-pasted from (MIT):
// https://github.com/StefH/System.AppDomain.Core/blob/0b35e676c2721aa367b96e62eb52c97ee0b43a70/src/System.AppDomain.NetCoreApp/AppDomain.cs
foreach (var assemblyName in
DependencyContext.Default.GetRuntimeAssemblyNames(RuntimeEnvironment.GetRuntimeIdentifier()))
{
try
{
var assembly = Assembly.Load(assemblyName);
// just load all types and skip this assembly if one or more types cannot be resolved
assembly.DefinedTypes.ToArray();
assemblies.Add(assembly);
}
catch (Exception ex)
{
Debug.Write(ex.Message);
}
}
return assemblies.ToArray();
}
}
}

5
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -1,8 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ProgressBar"> <Style xmlns="https://github.com/avaloniaui" Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="MinHeight" Value="14"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
@ -14,8 +12,7 @@
BorderBrush="{TemplateBinding Background}"/> BorderBrush="{TemplateBinding Background}"/>
<Border Name="PART_Indicator" <Border Name="PART_Indicator"
BorderThickness="1" BorderThickness="1"
Background="{TemplateBinding Foreground}" Background="{TemplateBinding Foreground}"/>
HorizontalAlignment="Left"/>
</Grid> </Grid>
</Border> </Border>
</ControlTemplate> </ControlTemplate>

14
src/Avalonia.Themes.Default/RepeatButton.xaml

@ -1,13 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="RepeatButton"> <Style Selector="RepeatButton">
<Setter Property="Background" <Setter Property="Background"
Value="{StyleResource ThemeControlMidBrush}" /> Value="{DynamicResource ThemeControlMidBrush}" />
<Setter Property="BorderBrush" <Setter Property="BorderBrush"
Value="{StyleResource ThemeBorderLightBrush}" /> Value="{DynamicResource ThemeBorderLightBrush}" />
<Setter Property="BorderThickness" <Setter Property="BorderThickness"
Value="{StyleResource ThemeBorderThickness}" /> Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="Foreground" <Setter Property="Foreground"
Value="{StyleResource ThemeForegroundBrush}" /> Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="HorizontalContentAlignment" <Setter Property="HorizontalContentAlignment"
Value="Center" /> Value="Center" />
<Setter Property="VerticalContentAlignment" <Setter Property="VerticalContentAlignment"
@ -31,14 +31,14 @@
</Style> </Style>
<Style Selector="RepeatButton:pointerover /template/ ContentPresenter"> <Style Selector="RepeatButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush" <Setter Property="BorderBrush"
Value="{StyleResource ThemeBorderMidBrush}" /> Value="{DynamicResource ThemeBorderMidBrush}" />
</Style> </Style>
<Style Selector="RepeatButton:pressed /template/ ContentPresenter"> <Style Selector="RepeatButton:pressed /template/ ContentPresenter">
<Setter Property="Background" <Setter Property="Background"
Value="{StyleResource ThemeControlDarkBrush}" /> Value="{DynamicResource ThemeControlDarkBrush}" />
</Style> </Style>
<Style Selector="RepeatButton:disabled"> <Style Selector="RepeatButton:disabled">
<Setter Property="Opacity" <Setter Property="Opacity"
Value="{StyleResource ThemeDisabledOpacity}" /> Value="{DynamicResource ThemeDisabledOpacity}" />
</Style> </Style>
</Styles> </Styles>

29
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -80,12 +80,29 @@ namespace Avalonia.Platform
/// <param name="cornerRadius">The corner radius.</param> /// <param name="cornerRadius">The corner radius.</param>
void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f); void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f);
/// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer
/// for the current render target.
/// </summary>
/// <param name="size">The size of the layer in DIPs.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/></returns>
/// <remarks>
/// Depending on the rendering backend used, a layer created via this method may be more
/// performant than a standard render target bitmap. In particular the Direct2D backend
/// has to do a format conversion each time a standard render target bitmap is rendered,
/// but a layer created via this method has no such overhead.
/// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size);
/// <summary> /// <summary>
/// Pushes a clip rectange. /// Pushes a clip rectange.
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip); void PushClip(Rect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip(); void PopClip();
/// <summary> /// <summary>
@ -94,10 +111,19 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
void PushOpacity(double opacity); void PushOpacity(double opacity);
/// <summary>
/// Pops the latest pushed opacity value.
/// </summary>
void PopOpacity(); void PopOpacity();
/// <summary>
/// Pushes an opacity mask
/// </summary>
void PushOpacityMask(IBrush mask, Rect bounds); void PushOpacityMask(IBrush mask, Rect bounds);
/// <summary>
/// Pops the latest pushed opacity mask.
/// </summary>
void PopOpacityMask(); void PopOpacityMask();
/// <summary> /// <summary>
@ -106,6 +132,9 @@ namespace Avalonia.Platform
/// <param name="clip">The clip geometry.</param> /// <param name="clip">The clip geometry.</param>
void PushGeometryClip(IGeometryImpl clip); void PushGeometryClip(IGeometryImpl clip);
/// <summary>
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip(); void PopGeometryClip();
} }
} }

34
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -1,34 +0,0 @@
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);
}
}
}

161
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -26,7 +26,6 @@ namespace Avalonia.Rendering
private readonly IVisual _root; private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder; private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers; private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running; private bool _running;
private Scene _scene; private Scene _scene;
@ -45,13 +44,11 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param> /// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</param> /// <param name="renderLoop">The render loop.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</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> /// <param name="dispatcher">The dispatcher to use. Optional.</param>
public DeferredRenderer( public DeferredRenderer(
IRenderRoot root, IRenderRoot root,
IRenderLoop renderLoop, IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null, ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null) IDispatcher dispatcher = null)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
@ -59,8 +56,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread; _dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root; _root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers();
_layers = new RenderLayers(_layerFactory);
_renderLoop = renderLoop; _renderLoop = renderLoop;
} }
@ -70,15 +66,13 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param> /// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param> /// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param> /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks> /// <remarks>
/// This constructor is intended to be used for unit testing. /// This constructor is intended to be used for unit testing.
/// </remarks> /// </remarks>
public DeferredRenderer( public DeferredRenderer(
IVisual root, IVisual root,
IRenderTarget renderTarget, IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null, ISceneBuilder sceneBuilder = null)
IRenderLayerFactory layerFactory = null)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null); Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -86,8 +80,7 @@ namespace Avalonia.Rendering
_root = root; _root = root;
_renderTarget = renderTarget; _renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers();
_layers = new RenderLayers(_layerFactory);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -180,38 +173,60 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps; bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false; bool composite = false;
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay) if (renderOverlay)
{ {
_dirtyRectsDisplay.Tick(); _dirtyRectsDisplay.Tick();
} }
if (scene != null && scene.Size != Size.Empty) try
{ {
if (scene.Generation != _lastSceneId) if (scene != null && scene.Size != Size.Empty)
{ {
_layers.Update(scene); IDrawingContextImpl context = null;
RenderToLayers(scene);
if (DebugFramesPath != null) if (scene.Generation != _lastSceneId)
{ {
SaveDebugFrames(scene.Generation); context = _renderTarget.CreateDrawingContext(this);
} _layers.Update(scene, context);
_lastSceneId = scene.Generation; RenderToLayers(scene);
composite = true; if (DebugFramesPath != null)
} {
SaveDebugFrames(scene.Generation);
}
if (renderOverlay) _lastSceneId = scene.Generation;
{
RenderOverlay(scene); composite = true;
RenderComposite(scene); }
}
else if(composite) if (renderOverlay)
{ {
RenderComposite(scene); context = context ?? _renderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
context?.Dispose();
} }
} }
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
}
} }
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
@ -273,11 +288,11 @@ namespace Avalonia.Rendering
} }
} }
private void RenderOverlay(Scene scene) private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
{ {
if (DrawDirtyRects) if (DrawDirtyRects)
{ {
var overlay = GetOverlay(scene.Size, scene.Scaling); var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this)) using (var context = overlay.CreateDrawingContext(this))
{ {
@ -301,61 +316,44 @@ namespace Avalonia.Rendering
} }
} }
private void RenderComposite(Scene scene) private void RenderComposite(Scene scene, IDrawingContextImpl context)
{ {
try var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{ {
if (_renderTarget == null) var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{ {
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); context.PushGeometryClip(layer.GeometryClip);
} }
using (var context = _renderTarget.CreateDrawingContext(this)) if (layer.OpacityMask == null)
{ {
var clientRect = new Rect(scene.Size); context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
foreach (var layer in scene.Layers) else
{ {
var bitmap = _layers[layer.LayerRoot].Bitmap; context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
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) if (layer.GeometryClip != null)
{ {
RenderFps(context, clientRect, true); context.PopGeometryClip();
}
} }
} }
catch (RenderTargetCorruptedException ex)
if (_overlay != null)
{ {
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
_renderTarget?.Dispose(); context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
_renderTarget = null; }
if (DrawFps)
{
RenderFps(context, clientRect, true);
} }
} }
@ -422,16 +420,19 @@ namespace Avalonia.Rendering
} }
} }
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling) private IRenderTargetBitmapImpl GetOverlay(
IDrawingContextImpl parentContext,
Size size,
double scaling)
{ {
size = new Size(size.Width * scaling, size.Height * scaling); var pixelSize = size * scaling;
if (_overlay == null || if (_overlay == null ||
_overlay.PixelWidth != size.Width || _overlay.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != size.Height) _overlay.PixelHeight != pixelSize.Height)
{ {
_overlay?.Dispose(); _overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling); _overlay = parentContext.CreateLayer(size);
} }
return _overlay; return _overlay;

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -1,11 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY);
}
}

10
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -7,16 +7,16 @@ namespace Avalonia.Rendering
{ {
public class RenderLayer public class RenderLayer
{ {
private readonly IRenderLayerFactory _factory; private readonly IDrawingContextImpl _drawingContext;
public RenderLayer( public RenderLayer(
IRenderLayerFactory factory, IDrawingContextImpl drawingContext,
Size size, Size size,
double scaling, double scaling,
IVisual layerRoot) IVisual layerRoot)
{ {
_factory = factory; _drawingContext = drawingContext;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling); Bitmap = drawingContext.CreateLayer(size);
Size = size; Size = size;
Scaling = scaling; Scaling = scaling;
LayerRoot = layerRoot; LayerRoot = layerRoot;
@ -31,7 +31,7 @@ namespace Avalonia.Rendering
{ {
if (Size != size || Scaling != scaling) if (Size != size || Scaling != scaling)
{ {
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling); var resized = _drawingContext.CreateLayer(size);
using (var context = resized.CreateDrawingContext(null)) using (var context = resized.CreateDrawingContext(null))
{ {

9
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -8,19 +9,17 @@ namespace Avalonia.Rendering
{ {
public class RenderLayers : IEnumerable<RenderLayer> public class RenderLayers : IEnumerable<RenderLayer>
{ {
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>(); private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>(); private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory) public RenderLayers()
{ {
_factory = factory;
} }
public int Count => _inner.Count; public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
public void Update(Scene scene) public void Update(Scene scene, IDrawingContextImpl context)
{ {
for (var i = scene.Layers.Count - 1; i >= 0; --i) for (var i = scene.Layers.Count - 1; i >= 0; --i)
{ {
@ -29,7 +28,7 @@ namespace Avalonia.Rendering
if (!_index.TryGetValue(src.LayerRoot, out layer)) if (!_index.TryGetValue(src.LayerRoot, out layer))
{ {
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot); layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot);
_inner.Add(layer); _inner.Add(layer);
_index.Add(src.LayerRoot, layer); _index.Add(src.LayerRoot, layer);
} }

15
src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
@ -12,20 +11,16 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Base class for draw operations that can use a brush. /// Base class for draw operations that can use a brush.
/// </summary> /// </summary>
internal abstract class BrushDrawOperation : IDrawOperation internal abstract class BrushDrawOperation : DrawOperation
{ {
/// <inheritdoc/> public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen)
public abstract Rect Bounds { get; } : base(bounds, transform, pen)
{
/// <inheritdoc/> }
public abstract bool HitTest(Point p);
/// <summary> /// <summary>
/// Gets a collection of child scenes that are needed to draw visual brushes. /// Gets a collection of child scenes that are needed to draw visual brushes.
/// </summary> /// </summary>
public abstract IDictionary<IVisual, Scene> ChildScenes { get; } public abstract IDictionary<IVisual, Scene> ChildScenes { get; }
/// <inheritdoc/>
public abstract void Render(IDrawingContextImpl context);
} }
} }

5
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
/// <inheritdoc/> /// <inheritdoc/>
public void PopClip() public void PopClip()
{ {

26
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Base class for draw operations that have bounds.
/// </summary>
internal abstract class DrawOperation : IDrawOperation
{
public DrawOperation(Rect bounds, Matrix transform, Pen pen)
{
bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform);
Bounds = new Rect(
new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)),
new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom)));
}
public Rect Bounds { get; }
public abstract bool HitTest(Point p);
public abstract void Render(IDrawingContextImpl context);
}
}

5
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Pen pen, Pen pen,
IGeometryImpl geometry, IGeometryImpl geometry,
IDictionary<IVisual, Scene> childScenes = null) IDictionary<IVisual, Scene> childScenes = null)
: base(geometry.GetRenderBounds(pen?.Thickness ?? 0), transform, null)
{ {
Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform);
Transform = transform; Transform = transform;
Brush = brush?.ToImmutable(); Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable(); Pen = pen?.ToImmutable();
@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
ChildScenes = childScenes; ChildScenes = childScenes;
} }
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the transform with which the node will be drawn. /// Gets the transform with which the node will be drawn.
/// </summary> /// </summary>

2
src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs

@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph
public interface IDrawOperation public interface IDrawOperation
{ {
/// <summary> /// <summary>
/// Gets the bounds of the visible content in the node. /// Gets the bounds of the visible content in the node in global coordinates.
/// </summary> /// </summary>
Rect Bounds { get; } Rect Bounds { get; }

11
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// A node in the scene graph which represents an image draw. /// A node in the scene graph which represents an image draw.
/// </summary> /// </summary>
internal class ImageNode : IDrawOperation internal class ImageNode : DrawOperation
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageNode"/> class. /// Initializes a new instance of the <see cref="ImageNode"/> class.
@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="sourceRect">The source rect.</param> /// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param> /// <param name="destRect">The destination rect.</param>
public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
: base(destRect, transform, null)
{ {
Bounds = destRect.TransformToAABB(transform);
Transform = transform; Transform = transform;
Source = source; Source = source;
Opacity = opacity; Opacity = opacity;
@ -29,9 +29,6 @@ namespace Avalonia.Rendering.SceneGraph
DestRect = destRect; DestRect = destRect;
} }
/// <inheritdoc/>
public Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the transform with which the node will be drawn. /// Gets the transform with which the node will be drawn.
/// </summary> /// </summary>
@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Render(IDrawingContextImpl context) public override void Render(IDrawingContextImpl context)
{ {
// TODO: Probably need to introduce some kind of locking mechanism in the case of // TODO: Probably need to introduce some kind of locking mechanism in the case of
// WriteableBitmap. // WriteableBitmap.
@ -89,6 +86,6 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool HitTest(Point p) => Bounds.Contains(p); public override bool HitTest(Point p) => Bounds.Contains(p);
} }
} }

5
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Point p1, Point p1,
Point p2, Point p2,
IDictionary<IVisual, Scene> childScenes = null) IDictionary<IVisual, Scene> childScenes = null)
: base(new Rect(p1, p2), transform, pen)
{ {
Bounds = new Rect(p1, p2).TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
Transform = transform; Transform = transform;
Pen = pen?.ToImmutable(); Pen = pen?.ToImmutable();
P1 = p1; P1 = p1;
@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
ChildScenes = childScenes; ChildScenes = childScenes;
} }
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the transform with which the node will be drawn. /// Gets the transform with which the node will be drawn.
/// </summary> /// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs

@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="bounds">The bounds of the mask.</param> /// <param name="bounds">The bounds of the mask.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param> /// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null) public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null)
: base(Rect.Empty, Matrix.Identity, null)
{ {
Mask = mask?.ToImmutable(); Mask = mask?.ToImmutable();
MaskBounds = bounds; MaskBounds = bounds;
@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph
/// opacity mask pop. /// opacity mask pop.
/// </summary> /// </summary>
public OpacityMaskNode() public OpacityMaskNode()
: base(Rect.Empty, Matrix.Identity, null)
{ {
} }
/// <inheritdoc/>
public override Rect Bounds => Rect.Empty;
/// <summary> /// <summary>
/// Gets the mask to be pushed or null if the operation represents a pop. /// Gets the mask to be pushed or null if the operation represents a pop.
/// </summary> /// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph
Rect rect, Rect rect,
float cornerRadius, float cornerRadius,
IDictionary<IVisual, Scene> childScenes = null) IDictionary<IVisual, Scene> childScenes = null)
: base(rect, transform, pen)
{ {
Bounds = rect.TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
Transform = transform; Transform = transform;
Brush = brush?.ToImmutable(); Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable(); Pen = pen?.ToImmutable();
@ -40,9 +40,6 @@ namespace Avalonia.Rendering.SceneGraph
ChildScenes = childScenes; ChildScenes = childScenes;
} }
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the transform with which the node will be drawn. /// Gets the transform with which the node will be drawn.
/// </summary> /// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Point origin, Point origin,
IFormattedTextImpl text, IFormattedTextImpl text,
IDictionary<IVisual, Scene> childScenes = null) IDictionary<IVisual, Scene> childScenes = null)
: base(new Rect(origin, text.Size), transform, null)
{ {
Bounds = new Rect(origin, text.Size).TransformToAABB(transform);
Transform = transform; Transform = transform;
Foreground = foreground?.ToImmutable(); Foreground = foreground?.ToImmutable();
Origin = origin; Origin = origin;
@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph
ChildScenes = childScenes; ChildScenes = childScenes;
} }
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the transform with which the node will be drawn. /// Gets the transform with which the node will be drawn.
/// </summary> /// </summary>

34
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Gtk3 namespace Avalonia.Gtk3
{ {
@ -27,7 +28,38 @@ namespace Avalonia.Gtk3
var s = _window.ClientSize; var s = _window.ClientSize;
var width = (int) s.Width; var width = (int) s.Width;
var height = (int) s.Height; var height = (int) s.Height;
return new ImageSurfaceFramebuffer(_window, width, height);
if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11"))
{
var x11 = LockX11Framebuffer(width, height);
if (x11 != null)
return x11;
}
return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor);
}
private static int X11ErrorHandler(IntPtr d, IntPtr e)
{
return 0;
}
private static X11.XErrorHandler X11ErrorHandlerDelegate = X11ErrorHandler;
private static IntPtr X11Display;
private ILockedFramebuffer LockX11Framebuffer(int width, int height)
{
if (!_window.GdkWindowHandle.HasValue)
return null;
if (X11Display == IntPtr.Zero)
{
X11Display = X11.XOpenDisplay(IntPtr.Zero);
if (X11Display == IntPtr.Zero)
return null;
X11.XSetErrorHandler(X11ErrorHandlerDelegate);
}
return new X11Framebuffer(X11Display, _window.GdkWindowHandle.Value, width, height, _window.LastKnownScaleFactor);
} }
} }
} }

11
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -22,11 +23,15 @@ namespace Avalonia.Gtk3
internal static readonly MouseDevice Mouse = new MouseDevice(); internal static readonly MouseDevice Mouse = new MouseDevice();
internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); internal static readonly KeyboardDevice Keyboard = new KeyboardDevice();
internal static IntPtr App { get; set; } internal static IntPtr App { get; set; }
internal static string DisplayClassName;
public static bool UseDeferredRendering = true; public static bool UseDeferredRendering = true;
public static void Initialize() public static void Initialize()
{ {
Resolver.Resolve(); Resolver.Resolve();
Native.GtkInit(0, IntPtr.Zero); Native.GtkInit(0, IntPtr.Zero);
var disp = Native.GdkGetDefaultDisplay();
DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp))));
using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid()))
App = Native.GtkApplicationNew(utf, 0); App = Native.GtkApplicationNew(utf, 0);
//Mark current thread as UI thread //Mark current thread as UI thread
@ -65,15 +70,13 @@ namespace Avalonia.Gtk3
Native.GtkMainIteration(); Native.GtkMainIteration();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
var msec = interval.TotalMilliseconds; var msec = interval.TotalMilliseconds;
if (msec <= 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var imsec = (uint) msec; var imsec = (uint) msec;
if (imsec == 0) if (imsec == 0)
imsec = 1; imsec = 1;
return GlibTimeout.StarTimer(imsec, tick); return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick);
} }
private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1];

2
src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs

@ -4,6 +4,6 @@ namespace Avalonia.Gtk3
{ {
public interface IDeferredRenderOperation : IDisposable public interface IDeferredRenderOperation : IDisposable
{ {
void RenderNow(); void RenderNow(IntPtr? ctx);
} }
} }

31
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -16,23 +16,23 @@ namespace Avalonia.Gtk3
{ {
private readonly WindowBaseImpl _impl; private readonly WindowBaseImpl _impl;
private readonly GtkWidget _widget; private readonly GtkWidget _widget;
private CairoSurface _surface; private ManagedCairoSurface _surface;
private int _factor; private int _factor;
private object _lock = new object(); private object _lock = new object();
public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height) public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor)
{ {
_impl = impl; _impl = impl;
_widget = impl.GtkWidget; _widget = impl.GtkWidget;
_factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u); _factor = factor;
width *= _factor; width *= _factor;
height *= _factor; height *= _factor;
_surface = Native.CairoImageSurfaceCreate(1, width, height); _surface = new ManagedCairoSurface(width, height);
Width = width; Width = width;
Height = height; Height = height;
Address = Native.CairoImageSurfaceGetData(_surface); Address = _surface.Buffer;
RowBytes = Native.CairoImageSurfaceGetStride(_surface); RowBytes = _surface.Stride;
Native.CairoSurfaceFlush(_surface); Native.CairoSurfaceFlush(_surface.Surface);
} }
static void Draw(IntPtr context, CairoSurface surface, double factor) static void Draw(IntPtr context, CairoSurface surface, double factor)
@ -83,15 +83,15 @@ namespace Avalonia.Gtk3
class RenderOp : IDeferredRenderOperation class RenderOp : IDeferredRenderOperation
{ {
private readonly GtkWidget _widget; private readonly GtkWidget _widget;
private CairoSurface _surface; private ManagedCairoSurface _surface;
private readonly double _factor; private readonly double _factor;
private readonly int _width; private readonly int _width;
private readonly int _height; private readonly int _height;
public RenderOp(GtkWidget widget, CairoSurface _surface, double factor, int width, int height) public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height)
{ {
_widget = widget; _widget = widget;
this._surface = _surface; _surface = surface ?? throw new ArgumentNullException();
_factor = factor; _factor = factor;
_width = width; _width = width;
_height = height; _height = height;
@ -103,9 +103,12 @@ namespace Avalonia.Gtk3
_surface = null; _surface = null;
} }
public void RenderNow() public void RenderNow(IntPtr? ctx)
{ {
DrawToWidget(_widget, _surface, _width, _height, _factor); if(ctx.HasValue)
Draw(ctx.Value, _surface.Surface, _factor);
else
DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
} }
} }
@ -116,9 +119,9 @@ namespace Avalonia.Gtk3
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
if (_impl.CurrentCairoContext != IntPtr.Zero) if (_impl.CurrentCairoContext != IntPtr.Zero)
Draw(_impl.CurrentCairoContext, _surface, _factor); Draw(_impl.CurrentCairoContext, _surface.Surface, _factor);
else else
DrawToWidget(_widget, _surface, Width, Height, _factor); DrawToWidget(_widget, _surface.Surface, Width, Height, _factor);
_surface.Dispose(); _surface.Dispose();
} }
else else

6
src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs

@ -48,12 +48,10 @@ namespace Avalonia.Gtk3.Interop
} }
} }
public static IDisposable StarTimer(uint interval, Action tick) public static IDisposable StartTimer(int priority, uint interval, Action tick)
{ {
if (interval == 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var timer = new Timer (); var timer = new Timer ();
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval, GlibTimeout.Add(priority, interval,
() => () =>
{ {
if (timer.Stopped) if (timer.Stopped)

38
src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs

@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Avalonia.Gtk3.Interop
{
class ManagedCairoSurface : IDisposable
{
public IntPtr Buffer { get; private set; }
public CairoSurface Surface { get; private set; }
public int Stride { get; private set; }
private int _size;
private IRuntimePlatform _plat;
private IUnmanagedBlob _blob;
public ManagedCairoSurface(int width, int height)
{
_plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
Stride = width * 4;
_size = height * Stride;
_blob = _plat.AllocBlob(_size * 2);
Buffer = _blob.Address;
Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride);
}
public void Dispose()
{
if (Buffer != IntPtr.Zero)
{
Surface.Dispose();
_blob.Dispose();
Buffer = IntPtr.Zero;
}
}
}
}

34
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -160,6 +160,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate CairoSurface cairo_image_surface_create(int format, int width, int height); public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface); public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface);
@ -178,7 +181,7 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_surface_destroy(IntPtr surface); public delegate void cairo_surface_destroy(IntPtr surface);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y);
@ -236,17 +239,17 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate GdkWindowState gdk_window_get_state(IntPtr window); public delegate GdkWindowState gdk_window_get_state(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gdk_window_iconify(IntPtr window); public delegate void gtk_window_iconify(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gdk_window_deiconify(IntPtr window); public delegate void gtk_window_deiconify(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gdk_window_maximize(IntPtr window); public delegate void gtk_window_maximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gdk_window_unmaximize(IntPtr window); public delegate void gtk_window_unmaximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask); public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@ -315,6 +318,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate void g_object_ref(GObject instance); public delegate void g_object_ref(GObject instance);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate IntPtr g_type_name(IntPtr instance);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags);
@ -407,6 +413,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_dialog_add_button GtkDialogAddButton; public static D.gtk_dialog_add_button GtkDialogAddButton;
public static D.g_object_unref GObjectUnref; public static D.g_object_unref GObjectUnref;
public static D.g_object_ref GObjectRef; public static D.g_object_ref GObjectRef;
public static D.g_type_name GTypeName;
public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_connect_object GSignalConnectObject;
public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect;
public static D.g_timeout_add GTimeoutAdd; public static D.g_timeout_add GTimeoutAdd;
@ -437,10 +444,10 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_get_origin GdkWindowGetOrigin; public static D.gdk_window_get_origin GdkWindowGetOrigin;
public static D.gdk_window_get_pointer GdkWindowGetPointer; public static D.gdk_window_get_pointer GdkWindowGetPointer;
public static D.gdk_window_get_state GdkWindowGetState; public static D.gdk_window_get_state GdkWindowGetState;
public static D.gdk_window_iconify GdkWindowIconify; public static D.gtk_window_iconify GtkWindowIconify;
public static D.gdk_window_deiconify GdkWindowDeiconify; public static D.gtk_window_deiconify GtkWindowDeiconify;
public static D.gdk_window_maximize GdkWindowMaximize; public static D.gtk_window_maximize GtkWindowMaximize;
public static D.gdk_window_unmaximize GdkWindowUnmaximize; public static D.gtk_window_unmaximize GtkWindowUnmaximize;
public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag; public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions; public static D.gdk_event_request_motions GdkEventRequestMotions;
@ -459,6 +466,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_cairo_create GdkCairoCreate; public static D.gdk_cairo_create GdkCairoCreate;
public static D.cairo_image_surface_create CairoImageSurfaceCreate; public static D.cairo_image_surface_create CairoImageSurfaceCreate;
public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData;
public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; public static D.cairo_image_surface_get_data CairoImageSurfaceGetData;
public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride;
public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty;

47
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Gtk3.Interop; using Avalonia.Gtk3.Interop;
using Avalonia.Input; using Avalonia.Input;
@ -29,7 +30,8 @@ namespace Avalonia.Gtk3
private GCHandle _gcHandle; private GCHandle _gcHandle;
private object _lock = new object(); private object _lock = new object();
private IDeferredRenderOperation _nextRenderOperation; private IDeferredRenderOperation _nextRenderOperation;
private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
internal IntPtr? GdkWindowHandle;
public WindowBaseImpl(GtkWindow gtkWidget) public WindowBaseImpl(GtkWindow gtkWidget)
{ {
@ -53,12 +55,8 @@ namespace Avalonia.Gtk3
ConnectEvent("leave-notify-event", OnLeaveNotifyEvent); ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
Connect<Native.D.signal_generic>("destroy", OnDestroy); Connect<Native.D.signal_generic>("destroy", OnDestroy);
Native.GtkWidgetRealize(gtkWidget); Native.GtkWidgetRealize(gtkWidget);
GdkWindowHandle = this.Handle.Handle;
_lastSize = ClientSize; _lastSize = ClientSize;
GlibTimeout.Add(0, 16, () =>
{
Invalidate(default(Rect));
return true;
});
if (Gtk3Platform.UseDeferredRendering) if (Gtk3Platform.UseDeferredRendering)
{ {
Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
@ -138,7 +136,7 @@ namespace Avalonia.Gtk3
? RawMouseEventType.LeftButtonDown ? RawMouseEventType.LeftButtonDown
: evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state)); new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state));
Input?.Invoke(e); OnInput(e);
return true; return true;
} }
@ -166,7 +164,7 @@ namespace Avalonia.Gtk3
_inputRoot, _inputRoot,
RawMouseEventType.Move, RawMouseEventType.Move,
position, GetModifierKeys(evnt->state)); position, GetModifierKeys(evnt->state));
Input(e); OnInput(e);
return true; return true;
} }
@ -195,7 +193,7 @@ namespace Avalonia.Gtk3
} }
var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot, var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot,
new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state)); new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state));
Input(e); OnInput(e);
return true; return true;
} }
@ -210,7 +208,7 @@ namespace Avalonia.Gtk3
evnt->time, evnt->time,
evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state)); Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state));
Input(e); OnInput(e);
return true; return true;
} }
@ -218,7 +216,7 @@ namespace Avalonia.Gtk3
{ {
var evnt = (GdkEventCrossing*) pev; var evnt = (GdkEventCrossing*) pev;
var position = new Point(evnt->x, evnt->y); var position = new Point(evnt->x, evnt->y);
Input(new RawMouseEventArgs(Gtk3Platform.Mouse, OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse,
evnt->time, evnt->time,
_inputRoot, _inputRoot,
RawMouseEventType.Move, RawMouseEventType.Move,
@ -228,7 +226,7 @@ namespace Avalonia.Gtk3
private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata) private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata)
{ {
Input(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string))); OnInput(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
return true; return true;
} }
@ -260,11 +258,19 @@ namespace Avalonia.Gtk3
public void SetNextRenderOperation(IDeferredRenderOperation op) public void SetNextRenderOperation(IDeferredRenderOperation op)
{ {
lock (_lock) while (true)
{ {
_nextRenderOperation?.Dispose(); lock (_lock)
_nextRenderOperation = op; {
if (_nextRenderOperation == null)
{
_nextRenderOperation = op;
return;
}
}
_canSetNextOperation.WaitOne();
} }
} }
private void OnRenderTick() private void OnRenderTick()
@ -277,10 +283,11 @@ namespace Avalonia.Gtk3
op = _nextRenderOperation; op = _nextRenderOperation;
_nextRenderOperation = null; _nextRenderOperation = null;
} }
_canSetNextOperation.Set();
} }
if (op != null) if (op != null)
{ {
op?.RenderNow(); op?.RenderNow(null);
op?.Dispose(); op?.Dispose();
} }
} }
@ -311,7 +318,7 @@ namespace Avalonia.Gtk3
public IMouseDevice MouseDevice => Gtk3Platform.Mouse; public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
public IPlatformHandle Handle => this; public IPlatformHandle Handle => this;
@ -338,6 +345,11 @@ namespace Avalonia.Gtk3
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
void OnInput(RawInputEventArgs args)
{
Dispatcher.UIThread.InvokeAsync(() => Input?.Invoke(args), DispatcherPriority.Input);
}
public Point PointToClient(Point point) public Point PointToClient(Point point)
{ {
int x, y; int x, y;
@ -387,6 +399,7 @@ namespace Avalonia.Gtk3
public Size ClientSize { get; private set; } public Size ClientSize { get; private set; }
public int LastKnownScaleFactor { get; private set; }
public void Resize(Size value) public void Resize(Size value)
{ {

9
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -32,15 +32,14 @@ namespace Avalonia.Gtk3
} }
set set
{ {
var w = Native.GtkWidgetGetWindow(GtkWidget);
if (value == WindowState.Minimized) if (value == WindowState.Minimized)
Native.GdkWindowIconify(w); Native.GtkWindowIconify(GtkWidget);
else if (value == WindowState.Maximized) else if (value == WindowState.Maximized)
Native.GdkWindowMaximize(w); Native.GtkWindowMaximize(GtkWidget);
else else
{ {
Native.GdkWindowUnmaximize(w); Native.GtkWindowUnmaximize(GtkWidget);
Native.GdkWindowDeiconify(w); Native.GtkWindowDeiconify(GtkWidget);
} }
} }
} }

54
src/Gtk/Avalonia.Gtk3/X11.cs

@ -0,0 +1,54 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Gtk3
{
class X11
{
[DllImport("libX11.so.6")]
public static extern IntPtr XOpenDisplay(IntPtr name);
[DllImport("libX11.so.6")]
public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc);
[DllImport("libX11.so.6")]
public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values);
[DllImport("libX11.so.6")]
public static extern int XInitImage(ref XImage image);
[DllImport("libX11.so.6")]
public static extern int XDestroyImage(ref XImage image);
[DllImport("libX11.so.6")]
public static extern IntPtr XSetErrorHandler(XErrorHandler handler);
public delegate int XErrorHandler(IntPtr display, IntPtr error);
[DllImport("libX11.so.6")]
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image,
int srcx, int srcy, int destx, int desty, uint width, uint height);
public unsafe struct XImage
{
public int width, height; /* size of image */
public int xoffset; /* number of pixels offset in X direction */
public int format; /* XYBitmap, XYPixmap, ZPixmap */
public IntPtr data; /* pointer to image data */
public int byte_order; /* data byte order, LSBFirst, MSBFirst */
public int bitmap_unit; /* quant. of scanline 8, 16, 32 */
public int bitmap_bit_order; /* LSBFirst, MSBFirst */
public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
public int depth; /* depth of image */
public int bytes_per_line; /* accelerator to next scanline */
public int bits_per_pixel; /* bits per pixel (ZPixmap) */
public ulong red_mask; /* bits in z arrangement */
public ulong green_mask;
public ulong blue_mask;
private fixed byte funcs[128];
}
}
}

55
src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs

@ -0,0 +1,55 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Avalonia.Gtk3
{
class X11Framebuffer : ILockedFramebuffer
{
private readonly IntPtr _display;
private readonly IntPtr _xid;
private IUnmanagedBlob _blob;
public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor)
{
_display = display;
_xid = xid;
Width = width*factor;
Height = height*factor;
RowBytes = Width * 4;
Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Bgra8888;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * Height);
Address = _blob.Address;
}
public void Dispose()
{
var image = new X11.XImage();
int bitsPerPixel = 32;
image.width = Width;
image.height = Height;
image.format = 2; //ZPixmap;
image.data = Address;
image.byte_order = 0;// LSBFirst;
image.bitmap_unit = bitsPerPixel;
image.bitmap_bit_order = 0;// LSBFirst;
image.bitmap_pad = bitsPerPixel;
image.depth = 24;
image.bytes_per_line = RowBytes - Width * 4;
image.bits_per_pixel = bitsPerPixel;
X11.XInitImage(ref image);
var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero);
X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height);
X11.XFreeGC(_display, gc);
_blob.Dispose();
}
public IntPtr Address { get; }
public int Width { get; }
public int Height { get; }
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
}

4
src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs

@ -17,7 +17,7 @@ namespace Avalonia.LinuxFramebuffer
public PlatformThreadingInterface() public PlatformThreadingInterface()
{ {
TlsCurrentThreadIsLoopThread = true; TlsCurrentThreadIsLoopThread = true;
StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
} }
private readonly AutoResetEvent _signaled = new AutoResetEvent(false); private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
@ -74,7 +74,7 @@ namespace Avalonia.LinuxFramebuffer
} }
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
return new WatTimer(new System.Threading.Timer(delegate return new WatTimer(new System.Threading.Timer(delegate
{ {

2
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -16,7 +16,7 @@ namespace Avalonia.MonoMac
public event Action<DispatcherPriority?> Signaled; public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)

142
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -2,8 +2,11 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using Avalonia.Platform; using Avalonia.Platform;
@ -11,20 +14,145 @@ namespace Avalonia.Shared.PlatformSupport
{ {
internal partial class StandardRuntimePlatform : IRuntimePlatform internal partial class StandardRuntimePlatform : IRuntimePlatform
{ {
#if NETCOREAPP2_0
public void PostThreadPoolItem(Action cb) => ThreadPool.QueueUserWorkItem(_ => cb(), null);
#else
public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null); public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null);
#endif public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
public IDisposable StartSystemTimer(TimeSpan interval, Action tick) public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
{ {
return new Timer(_ => tick(), null, interval, interval); return new Timer(_ => tick(), null, interval, interval);
} }
public string GetStackTrace() => Environment.StackTrace;
public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
class UnmanagedBlob : IUnmanagedBlob
{
private readonly StandardRuntimePlatform _plat;
#if DEBUG
private static readonly List<string> Backtraces = new List<string>();
private static Thread GCThread;
private readonly string _backtrace;
public string GetStackTrace() => Environment.StackTrace; class GCThreadDetector
{
~GCThreadDetector()
{
GCThread = Thread.CurrentThread;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Spawn() => new GCThreadDetector();
static UnmanagedBlob()
{
Spawn();
GC.WaitForPendingFinalizers();
}
#endif
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
{
_plat = plat;
Address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
#if DEBUG
_backtrace = Environment.StackTrace;
Backtraces.Add(_backtrace);
#endif
}
void DoDispose()
{
if (!IsDisposed)
{
#if DEBUG
Backtraces.Remove(_backtrace);
#endif
_plat.Free(Address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
Address = IntPtr.Zero;
Size = 0;
}
}
public void Dispose()
{
#if DEBUG
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
{
Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+ Environment.StackTrace
+ "\n\nBlob created by " + _backtrace);
}
#endif
DoDispose();
GC.SuppressFinalize(this);
}
~UnmanagedBlob()
{
#if DEBUG
Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
#endif
DoDispose();
}
public IntPtr Address { get; private set; }
public int Size { get; private set; }
public bool IsDisposed { get; private set; }
}
#if FULLDOTNET || DOTNETCORE
[DllImport("libc", SetLastError = true)]
private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
[DllImport("libc", SetLastError = true)]
private static extern int munmap(IntPtr addr, IntPtr length);
[DllImport("libc", SetLastError = true)]
private static extern long sysconf(int name);
private bool? _useMmap;
private bool UseMmap
=> _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
IntPtr Alloc(int size)
{
if (UseMmap)
{
var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to allocate memory: " + errno);
}
return rv;
}
else
return Marshal.AllocHGlobal(size);
}
void Free(IntPtr ptr, int len)
{
if (UseMmap)
{
if (munmap(ptr, new IntPtr(len)) == -1)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to free memory: " + errno);
}
}
else
Marshal.FreeHGlobal(ptr);
}
#else
IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
#endif
} }
} }

23
src/Skia/Avalonia.Skia/BitmapImpl.cs

@ -20,16 +20,35 @@ namespace Avalonia.Skia
_dpi = new Vector(96, 96); _dpi = new Vector(96, 96);
} }
static void ReleaseProc(IntPtr address, object ctx)
{
((IUnmanagedBlob) ctx).Dispose();
}
private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc;
public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null) public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null)
{ {
PixelHeight = height; PixelHeight = height;
PixelWidth = width; PixelWidth = width;
_dpi = dpi; _dpi = dpi;
var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType;
var runtime = AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo(); var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>();
var runtime = runtimePlatform?.GetRuntimeInfo();
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
colorType = SKColorType.Bgra8888; colorType = SKColorType.Bgra8888;
Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
if (runtimePlatform != null)
{
Bitmap = new SKBitmap();
var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul);
var plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
var blob = plat.AllocBlob(nfo.BytesSize);
Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob);
}
else
Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
Bitmap.Erase(SKColor.Empty); Bitmap.Erase(SKColor.Empty);
} }

15
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -45,7 +45,7 @@ namespace Avalonia.Skia
var s = sourceRect.ToSKRect(); var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect(); var d = destRect.ToSKRect();
using (var paint = new SKPaint() using (var paint = new SKPaint()
{ Color = new SKColor(255, 255, 255, (byte)(255 * opacity)) }) { Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) })
{ {
Canvas.DrawBitmap(impl.Bitmap, s, d, paint); Canvas.DrawBitmap(impl.Bitmap, s, d, paint);
} }
@ -112,6 +112,7 @@ namespace Avalonia.Skia
public readonly SKPaint Paint; public readonly SKPaint Paint;
private IDisposable _disposable1; private IDisposable _disposable1;
private IDisposable _disposable2;
public IDisposable ApplyTo(SKPaint paint) public IDisposable ApplyTo(SKPaint paint)
{ {
@ -127,6 +128,8 @@ namespace Avalonia.Skia
{ {
if (_disposable1 == null) if (_disposable1 == null)
_disposable1 = disposable; _disposable1 = disposable;
else if (_disposable2 == null)
_disposable2 = disposable;
else else
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
@ -135,12 +138,14 @@ namespace Avalonia.Skia
{ {
Paint = paint; Paint = paint;
_disposable1 = null; _disposable1 = null;
_disposable2 = null;
} }
public void Dispose() public void Dispose()
{ {
Paint?.Dispose(); Paint?.Dispose();
_disposable1?.Dispose(); _disposable1?.Dispose();
_disposable2?.Dispose();
} }
} }
@ -221,8 +226,8 @@ namespace Avalonia.Skia
_visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
} }
rv.AddDisposable(tileBrushImage);
tileBrushImage = intermediate; tileBrushImage = intermediate;
rv.AddDisposable(tileBrushImage);
} }
} }
else else
@ -349,6 +354,12 @@ namespace Avalonia.Skia
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var pixelSize = size * (_dpi / 96);
return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi);
}
public void PushClip(Rect clip) public void PushClip(Rect clip)
{ {
Canvas.Save(); Canvas.Save();

6
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -145,7 +145,9 @@ namespace Avalonia.Direct2D1
return new HwndRenderTarget(nativeWindow); return new HwndRenderTarget(nativeWindow);
} }
if (s is IExternalDirect2DRenderTargetSurface external) if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory); return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory);
if (s is IFramebufferPlatformSurface fb)
return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory);
} }
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
} }
@ -156,7 +158,7 @@ namespace Avalonia.Direct2D1
double dpiX, double dpiX,
double dpiY) double dpiY)
{ {
return new RenderTargetBitmapImpl( return new WicRenderTargetBitmapImpl(
s_imagingFactory, s_imagingFactory,
s_d2D1Factory, s_d2D1Factory,
s_dwfactory, s_dwfactory,

28
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX; using SharpDX;
@ -11,15 +8,20 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
class ExternalRenderTarget : IRenderTarget class ExternalRenderTarget : IRenderTarget, ILayerFactory
{ {
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory; private readonly DirectWriteFactory _dwFactory;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, private readonly SharpDX.WIC.ImagingFactory _wicFactory;
DirectWriteFactory dwFactory)
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
{ {
_externalRenderTargetProvider = externalRenderTargetProvider; _externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory; _dwFactory = dwFactory;
_wicFactory = wicFactory;
} }
public void Dispose() public void Dispose()
@ -31,7 +33,7 @@ namespace Avalonia.Direct2D1
{ {
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing(); _externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () => return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
{ {
try try
{ {
@ -43,5 +45,15 @@ namespace Avalonia.Direct2D1
} }
}); });
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(
_wicFactory,
_dwFactory,
target,
size);
}
} }
} }

84
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Win32.Interop;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Direct2D1
{
class FramebufferShimRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _surface;
private readonly ImagingFactory _imagingFactory;
private readonly Factory _d2DFactory;
private readonly SharpDX.DirectWrite.Factory _dwriteFactory;
public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface,
ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory)
{
_surface = surface;
_imagingFactory = imagingFactory;
_d2DFactory = d2dFactory;
_dwriteFactory = dwriteFactory;
}
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
var locked = _surface.Lock();
if (locked.Format == PixelFormat.Rgb565)
{
locked.Dispose();
throw new ArgumentException("Unsupported pixel format: " + locked.Format);
}
return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory)
.CreateDrawingContext(visualBrushRenderer);
}
class FramebufferShim : WicRenderTargetBitmapImpl
{
private readonly ILockedFramebuffer _target;
public FramebufferShim(ILockedFramebuffer target,
ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory
) : base(imagingFactory, d2dFactory, dwriteFactory,
target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format)
{
_target = target;
}
public override IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return base.CreateDrawingContext(visualBrushRenderer, () =>
{
using (var l = WicImpl.Lock(BitmapLockFlags.Read))
{
for (var y = 0; y < _target.Height; y++)
{
UnmanagedMethods.CopyMemory(
_target.Address + _target.RowBytes * y,
l.Data.DataPointer + l.Stride * y,
(uint) Math.Min(l.Stride, _target.RowBytes));
}
}
Dispose();
_target.Dispose();
});
}
}
}
}

10
src/Windows/Avalonia.Direct2D1/ILayerFactory.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Direct2D1
{
public interface ILayerFactory
{
IRenderTargetBitmapImpl CreateLayer(Size size);
}
}

39
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -2,16 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.RenderHelpers;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX; using SharpDX;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
using IBitmap = Avalonia.Media.Imaging.IBitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -21,9 +18,11 @@ namespace Avalonia.Direct2D1.Media
public class DrawingContextImpl : IDrawingContextImpl, IDisposable public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{ {
private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback; private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory; private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary> /// <summary>
@ -31,21 +30,30 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param> /// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param> /// <param name="renderTarget">The render target to draw to.</param>
/// <param name="layerFactory">
/// An object to use to create layers. May be null, in which case a
/// <see cref="WicRenderTargetBitmapImpl"/> will created when a new layer is requested.
/// </param>
/// <param name="directWriteFactory">The DirectWrite factory.</param> /// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="imagingFactory">The WIC imaging factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param> /// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param> /// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl( public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer, IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory, SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null, SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null) Action finishedCallback = null)
{ {
_visualBrushRenderer = visualBrushRenderer; _visualBrushRenderer = visualBrushRenderer;
_layerFactory = layerFactory;
_renderTarget = renderTarget; _renderTarget = renderTarget;
_swapChain = swapChain; _swapChain = swapChain;
_finishedCallback = finishedCallback; _finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory; _directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain; _swapChain = swapChain;
_renderTarget.BeginDraw(); _renderTarget.BeginDraw();
} }
@ -97,7 +105,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{ {
_renderTarget.DrawBitmap( _renderTarget.DrawBitmap(
d2d, d2d.Value,
destRect.ToSharpDX(), destRect.ToSharpDX(),
(float)opacity, (float)opacity,
BitmapInterpolationMode.Linear, BitmapInterpolationMode.Linear,
@ -115,7 +123,7 @@ namespace Avalonia.Direct2D1.Media
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{ {
using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource)) using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{ {
@ -284,6 +292,25 @@ namespace Avalonia.Direct2D1.Media
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_layerFactory != null)
{
return _layerFactory.CreateLayer(size);
}
else
{
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height);
var pixelSize = size * (dpi / 96);
return platform.CreateRenderTargetBitmap(
(int)pixelSize.Width,
(int)pixelSize.Height,
dpi.X,
dpi.Y);
}
}
/// <summary> /// <summary>
/// Pushes a clip rectange. /// Pushes a clip rectange.
/// </summary> /// </summary>
@ -397,7 +424,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl( return new ImageBrushImpl(
visualBrush, visualBrush,
_renderTarget, _renderTarget,
new D2DBitmapImpl(intermediate.Bitmap), new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
destinationSize); destinationSize);
} }
} }

7
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media
{ {
public sealed class ImageBrushImpl : BrushImpl public sealed class ImageBrushImpl : BrushImpl
{ {
OptionalDispose<Bitmap> _bitmap;
public ImageBrushImpl( public ImageBrushImpl(
ITileBrush brush, ITileBrush brush,
SharpDX.Direct2D1.RenderTarget target, SharpDX.Direct2D1.RenderTarget target,
@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media
if (!calc.NeedsIntermediate) if (!calc.NeedsIntermediate)
{ {
_bitmap = bitmap.GetDirect2DBitmap(target);
PlatformBrush = new BitmapBrush( PlatformBrush = new BitmapBrush(
target, target,
bitmap.GetDirect2DBitmap(target), _bitmap.Value,
GetBitmapBrushProperties(brush), GetBitmapBrushProperties(brush),
GetBrushProperties(brush, calc.DestinationRect)); GetBrushProperties(brush, calc.DestinationRect));
} }
@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media
public override void Dispose() public override void Dispose()
{ {
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); _bitmap.Dispose();
base.Dispose(); base.Dispose();
} }

32
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,20 +1,38 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
public abstract class BitmapImpl : IBitmapImpl, IDisposable public abstract class BitmapImpl : IBitmapImpl, IDisposable
{ {
public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); public BitmapImpl(ImagingFactory imagingFactory)
{
WicImagingFactory = imagingFactory;
}
public ImagingFactory WicImagingFactory { get; }
public abstract int PixelWidth { get; } public abstract int PixelWidth { get; }
public abstract int PixelHeight { get; } public abstract int PixelHeight { get; }
public abstract void Save(string fileName);
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public void Save(string fileName)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
Save(s);
}
}
public abstract void Save(Stream stream); public abstract void Save(Stream stream);
public virtual void Dispose() public virtual void Dispose()

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using WICFactory = SharpDX.WIC.ImagingFactory;
using ImagingFactory2 = SharpDX.WIC.ImagingFactory2;
using ImageParameters = SharpDX.WIC.ImageParameters;
using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>, /// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable. /// the device associated with this context, to be renderable.
/// </remarks> /// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap) public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
{ {
if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap)); _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
_direct2D = d2DBitmap;
} }
public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
public override int PixelWidth => _direct2D.PixelSize.Width; public override int PixelWidth => _direct2D.PixelSize.Width;
public override int PixelHeight => _direct2D.PixelSize.Height; public override int PixelHeight => _direct2D.PixelSize.Height;
public override void Save(string fileName) public override void Dispose()
{ {
throw new NotImplementedException(); base.Dispose();
_direct2D.Dispose();
} }
public override void Save(Stream stream) public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{ {
throw new NotImplementedException(); return new OptionalDispose<Bitmap>(_direct2D, false);
} }
public override void Dispose() public override void Save(Stream stream)
{ {
base.Dispose(); using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
_direct2D.Dispose(); using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder))
using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null))
{
var parameters = new ImageParameters(
new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
_direct2D.DotsPerInch.Width,
_direct2D.DotsPerInch.Height,
0, 0, PixelWidth, PixelHeight);
imageEncoder.WriteFrame(_direct2D, frameEncode, parameters);
frameEncode.Commit();
encoder.Commit();
}
} }
} }
} }

68
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -0,0 +1,68 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly BitmapRenderTarget _target;
public D2DRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
BitmapRenderTarget target)
: base(imagingFactory, target.Bitmap)
{
_dwriteFactory = dwriteFactory;
_target = target;
}
public override int PixelWidth => _target.PixelSize.Width;
public override int PixelHeight => _target.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
Size size)
{
var bitmapRenderTarget = new BitmapRenderTarget(
renderTarget,
CompatibleRenderTargetOptions.None,
new Size2F((float)size.Width, (float)size.Height));
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(
visualBrushRenderer,
this,
_target,
_dwriteFactory,
WicImagingFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size);
}
public override void Dispose()
{
_target.Dispose();
}
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false);
}
}
}

57
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -3,11 +3,10 @@
using System; using System;
using System.IO; using System.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC; using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
public class WicBitmapImpl : BitmapImpl public class WicBitmapImpl : BitmapImpl
{ {
private readonly ImagingFactory _factory;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class. /// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary> /// </summary>
/// <param name="factory">The WIC imaging factory to use.</param> /// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param> /// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName) public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
{ {
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
{ {
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media
/// <param name="factory">The WIC imaging factory to use.</param> /// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param> /// <param name="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream) public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
{ {
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
{ {
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media
/// <param name="height">The height of the bitmap.</param> /// <param name="height">The height of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param> /// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
{ {
if (!pixelFormat.HasValue) if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888; pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat; PixelFormat = pixelFormat;
WicImpl = new Bitmap( WicImpl = new Bitmap(
factory, factory,
@ -71,7 +65,8 @@ namespace Avalonia.Direct2D1.Media
BitmapCreateCacheOption.CacheOnLoad); BitmapCreateCacheOption.CacheOnLoad);
} }
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride) public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride)
: base(factory)
{ {
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
PixelFormat = format; PixelFormat = format;
@ -112,41 +107,23 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="renderTarget">The render target.</param> /// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns> /// <returns>The Direct2D bitmap.</returns>
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{ {
FormatConverter converter = new FormatConverter(_factory); FormatConverter converter = new FormatConverter(WicImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
} }
/// <summary> public override void Save(Stream stream)
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public override void Save(string fileName)
{ {
if (Path.GetExtension(fileName) != ".png") using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
{ using (var frame = new BitmapFrameEncode(encoder))
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{ {
Save(s); frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
} }
} }
public override void Save(Stream stream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
encoder.Initialize(stream);
BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
} }
} }

17
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs → src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -10,20 +10,21 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{ {
private readonly DirectWriteFactory _dwriteFactory; private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target; private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl( public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory, ImagingFactory imagingFactory,
Factory d2dFactory, Factory d2dFactory,
DirectWriteFactory dwriteFactory, DirectWriteFactory dwriteFactory,
int width, int width,
int height, int height,
double dpiX, double dpiX,
double dpiY) double dpiY,
: base(imagingFactory, width, height) Platform.PixelFormat? pixelFormat = null)
: base(imagingFactory, width, height, pixelFormat)
{ {
var props = new RenderTargetProperties var props = new RenderTargetProperties
{ {
@ -45,9 +46,13 @@ namespace Avalonia.Direct2D1.Media
base.Dispose(); base.Dispose();
} }
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
=> CreateDrawingContext(visualBrushRenderer, null);
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{ {
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory); return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
finishedCallback: finishedCallback);
} }
} }
} }

22
src/Windows/Avalonia.Direct2D1/OptionalDispose.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Direct2D1
{
public struct OptionalDispose<T> : IDisposable where T : IDisposable
{
private readonly bool _dispose;
public OptionalDispose(T value, bool dispose)
{
Value = value;
_dispose = dispose;
}
public T Value { get; }
public void Dispose()
{
if (_dispose) Value?.Dispose();
}
}
}

34
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -3,14 +3,16 @@
using System; using System;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory; using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
public class RenderTarget : IRenderTarget public class RenderTarget : IRenderTarget, ILayerFactory
{ {
/// <summary> /// <summary>
/// The render target. /// The render target.
@ -25,24 +27,13 @@ namespace Avalonia.Direct2D1
{ {
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>(); Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>(); DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget; _renderTarget = renderTarget;
} }
/// <summary> public Factory Direct2DFactory { get; }
/// Gets the Direct2D factory. public DwFactory DirectWriteFactory { get; }
/// </summary> public WicFactory WicFactory { get; }
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public DwFactory DirectWriteFactory
{
get;
}
/// <summary> /// <summary>
/// Creates a drawing context for a rendering session. /// Creates a drawing context for a rendering session.
@ -50,7 +41,16 @@ namespace Avalonia.Direct2D1
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns> /// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory); return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicFactory,
DirectWriteFactory,
_renderTarget,
size);
} }
public void Dispose() public void Dispose()

40
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2; using Factory2 = SharpDX.DXGI.Factory2;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
public abstract class SwapChainRenderTarget : IRenderTarget public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory
{ {
private Size2 _savedSize; private Size2 _savedSize;
private Size2F _savedDpi; private Size2F _savedDpi;
@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1
D2DDevice = AvaloniaLocator.Current.GetService<Device>(); D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>(); Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(); DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
WicImagingFactory = AvaloniaLocator.Current.GetService<SharpDX.WIC.ImagingFactory>();
} }
public Factory Direct2DFactory { get; }
/// <summary> public SharpDX.DirectWrite.Factory DirectWriteFactory { get; }
/// Gets the Direct2D factory. public SharpDX.WIC.ImagingFactory WicImagingFactory { get; }
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
protected SharpDX.DXGI.Device DxgiDevice { get; } protected SharpDX.DXGI.Device DxgiDevice { get; }
@ -67,11 +56,27 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl( return new DrawingContextImpl(
visualBrushRenderer, visualBrushRenderer,
this,
_deviceContext, _deviceContext,
DirectWriteFactory, DirectWriteFactory,
WicImagingFactory,
_swapChain); _swapChain);
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{
CreateSwapChain();
}
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicImagingFactory,
DirectWriteFactory,
_deviceContext,
size);
}
public void Dispose() public void Dispose()
{ {
_deviceContext?.Dispose(); _deviceContext?.Dispose();
@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1
_deviceContext?.Dispose(); _deviceContext?.Dispose();
_deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
var swapChainDesc = new SwapChainDescription1 var swapChainDesc = new SwapChainDescription1
{ {
Width = _savedSize.Width, Width = _savedSize.Width,

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -114,7 +114,7 @@ namespace Avalonia.Win32
} }
} }
public IDisposable StartTimer(TimeSpan interval, Action callback) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback)
{ {
UnmanagedMethods.TimerProc timerDelegate = UnmanagedMethods.TimerProc timerDelegate =
(hWnd, uMsg, nIDEvent, dwTime) => callback(); (hWnd, uMsg, nIDEvent, dwTime) => callback();

2
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -51,7 +51,7 @@ namespace Avalonia.iOS
} }
}*/ }*/
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); => NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick());
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests
source.OnNext(45); source.OnNext(45);
Assert.Equal(null, target.Foo); Assert.Null(target.Foo);
} }
[Fact] [Fact]

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs

@ -160,7 +160,7 @@ namespace Avalonia.Base.UnitTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

2
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators
target.Dematerialize(1, 1); target.Dematerialize(1, 1);
Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0));
Assert.Equal(null, target.ContainerFromIndex(1)); Assert.Null(target.ContainerFromIndex(1));
Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2));
} }

4
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child; target.Content = child;
target.Content = null; target.Content = null;
Assert.Equal(null, child.GetLogicalParent()); Assert.Null(child.GetLogicalParent());
Assert.Empty(target.GetLogicalChildren()); Assert.Empty(target.GetLogicalChildren());
} }
@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child; target.Content = child;
target.Content = null; target.Content = null;
Assert.Equal(null, child.GetVisualParent()); Assert.Null(child.GetVisualParent());
Assert.Empty(target.GetVisualChildren()); Assert.Empty(target.GetVisualChildren());
} }

2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = "bar"; target.Content = "bar";
target.UpdateChild(); target.UpdateChild();
Assert.Equal(null, foo.Parent); Assert.Null(foo.Parent);
logicalChildren = target.GetLogicalChildren(); logicalChildren = target.GetLogicalChildren();

8
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItem = new Item(); target.SelectedItem = new Item();
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Items = null; target.Items = null;
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.RemoveAt(1); items.RemoveAt(1);
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.Clear(); items.Clear();
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }

2
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItems = new AvaloniaList<object>(); target.SelectedItems = new AvaloniaList<object>();
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
} }
[Fact] [Fact]

4
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(120, 130), target.Bounds.Size); Assert.Equal(new Size(120, 120), target.Bounds.Size);
Assert.Equal(new Rect(0, 0, 120, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 0, 120, 20), target.Children[0].Bounds);
Assert.Equal(new Rect(0, 30, 120, 30), target.Children[1].Bounds); Assert.Equal(new Rect(0, 30, 120, 30), target.Children[1].Bounds);
Assert.Equal(new Rect(0, 70, 120, 50), target.Children[2].Bounds); Assert.Equal(new Rect(0, 70, 120, 50), target.Children[2].Bounds);
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize)); target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(130, 120), target.Bounds.Size); Assert.Equal(new Size(120, 120), target.Bounds.Size);
Assert.Equal(new Rect(0, 0, 20, 120), target.Children[0].Bounds); Assert.Equal(new Rect(0, 0, 20, 120), target.Children[0].Bounds);
Assert.Equal(new Rect(30, 0, 30, 120), target.Children[1].Bounds); Assert.Equal(new Rect(30, 0, 30, 120), target.Children[1].Bounds);
Assert.Equal(new Rect(70, 0, 50, 120), target.Children[2].Bounds); Assert.Equal(new Rect(70, 0, 50, 120), target.Children[2].Bounds);

2
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs

@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
}; };
target.Bind(TextBox.TextProperty, binding); target.Bind(TextBox.TextProperty, binding);
Assert.Equal(null, target.Text); Assert.Null(target.Text);
} }
[Fact] [Fact]

4
tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs

@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
{ {
var selector = new MemberSelector() { MemberName = "StringValue" }; var selector = new MemberSelector() { MemberName = "StringValue" };
Assert.Equal(null, selector.Select(null)); Assert.Null(selector.Select(null));
} }
[Fact] [Fact]
@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
var data = new Item() { StringValue = "Value1" }; var data = new Item() { StringValue = "Value1" };
Assert.Same(null, selector.Select(data)); Assert.Null(selector.Select(data));
} }
[Fact] [Fact]

11
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -64,13 +64,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public void Deallocate() => Marshal.FreeHGlobal(Address); public void Deallocate() => Marshal.FreeHGlobal(Address);
} }
#if AVALONIA_SKIA
[Theory] [Theory]
#else [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888),
[Theory(Skip = "Framebuffer not supported")] #if SKIA
InlineData(PixelFormat.Rgb565)
#endif #endif
[InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)] ]
public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
{ {
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
@ -84,6 +84,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100)); ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100));
ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100)); ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100));
ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100)); ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100));
ctx.PopOpacity();
} }
var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes);

2
tests/Avalonia.RenderTests/TestBase.cs

@ -161,7 +161,7 @@ namespace Avalonia.Direct2D1.RenderTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -49,8 +49,6 @@ namespace Avalonia.UnitTests
public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>(); public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>();
public IRenderTarget RenderTarget => null;
public IRenderer Renderer { get; set; } public IRenderer Renderer { get; set; }
public IAccessKeyHandler AccessKeyHandler => null; public IAccessKeyHandler AccessKeyHandler => null;

70
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: MockSceneBuilder(root).Object, sceneBuilder: MockSceneBuilder(root).Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher.Object); dispatcher: dispatcher.Object);
target.Start(); target.Start();
@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -146,22 +142,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize));
}); });
var layers = new Mock<IRenderLayerFactory>();
layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
var renderInterface = new Mock<IPlatformRenderInterface>(); var renderInterface = new Mock<IPlatformRenderInterface>();
var target = new DeferredRenderer( var target = new DeferredRenderer(
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object, //layerFactory: layers.Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
RunFrame(loop); RunFrame(loop);
layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
context.Verify(x => x.CreateLayer(root.ClientSize));
} }
[Fact] [Fact]
@ -185,25 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity); root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize)); root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>(); var rootLayer = CreateLayer();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl> var borderLayer = CreateLayer();
{ var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
{ root, CreateLayer() }, renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
{ border, CreateLayer() }, .Returns(rootLayer)
}); .Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer( var target = new DeferredRenderer(
root, root,
loop.Object, loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher()); dispatcher: new ImmediateDispatcher());
root.Renderer = target; root.Renderer = target;
target.Start(); target.Start();
RunFrame(loop); RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root); var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
var borderContext = layerFactory.GetMockDrawingContext(border); var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -223,7 +217,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
border.Opacity = 1; border.Opacity = 1;
RunFrame(loop); RunFrame(loop);
layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); Mock.Get(borderLayer).Verify(x => x.Dispose());
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never); borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
@ -246,13 +240,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>()); x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
} }
private Mock<IRenderLayerFactory> MockLayerFactory(IRenderRoot root)
{
var result = new Mock<IRenderLayerFactory>();
result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
return result;
}
private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root) private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root)
{ {
var result = new Mock<ISceneBuilder>(); var result = new Mock<ISceneBuilder>();
@ -260,34 +247,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering
.Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); .Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)));
return result; return result;
} }
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;
public MockRenderLayerFactory(IDictionary<IVisual, IRenderTargetBitmapImpl> layers)
{
_layers = layers;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _layers[layerRoot];
}
public Mock<IRenderTargetBitmapImpl> GetMockBitmap(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot]);
}
public Mock<IDrawingContextImpl> GetMockDrawingContext(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot].CreateDrawingContext(null));
}
}
} }
} }

55
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
public class DrawOperationTests
{
[Fact]
public void Empty_Bounds_Remain_Empty()
{
var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null);
Assert.Equal(Rect.Empty, target.Bounds);
}
[Theory]
[InlineData(10, 10, 10, 10, 1, 1, 1, 9, 9, 12, 12)]
[InlineData(10, 10, 10, 10, 1, 1, 2, 9, 9, 12, 12)]
[InlineData(10, 10, 10, 10, 1.5, 1.5, 1, 14, 14, 17, 17)]
public void Rectangle_Bounds_Are_Snapped_To_Pixels(
double x,
double y,
double width,
double height,
double scaleX,
double scaleY,
double? penThickness,
double expectedX,
double expectedY,
double expectedWidth,
double expectedHeight)
{
var target = new TestDrawOperation(
new Rect(x, y, width, height),
Matrix.CreateScale(scaleX, scaleY),
penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null);
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds);
}
private class TestDrawOperation : DrawOperation
{
public TestDrawOperation(Rect bounds, Matrix transform, Pen pen)
:base(bounds, transform, pen)
{
}
public override bool HitTest(Point p) => false;
public override void Render(IDrawingContextImpl context) { }
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Loading…
Cancel
Save