Browse Source

Merge branch 'master' into fixes/toggleswitch-empty-header-margin

pull/4816/head
Max Katz 6 years ago
committed by GitHub
parent
commit
b4f12dcab6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  2. 4
      samples/RenderDemo/App.xaml.cs
  3. 17
      src/Avalonia.Base/AvaloniaLocator.cs
  4. 15
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  5. 11
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  6. 13
      src/Avalonia.OpenGL/GlInterface.cs
  7. 2
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  8. 8
      src/Avalonia.Themes.Fluent/DataValidationErrors.xaml
  9. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  10. 17
      src/Avalonia.Visuals/Media/GlyphRun.cs
  11. 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  12. 67
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  13. 44
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  14. 16
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  15. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  16. 2
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  17. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  18. 14
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  19. 14
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  20. 134
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  21. 32
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  22. 7
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  23. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  24. 72
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  25. 2
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  26. 2
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  27. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  28. 11
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  29. 5
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  30. 2
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  31. 2
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  32. 92
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  33. 62
      src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs
  34. 85
      src/Windows/Avalonia.Win32/OpenGl/WglContext.cs
  35. 183
      src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
  36. 85
      src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs
  37. 39
      src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs
  38. 39
      src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs
  39. 18
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  40. 10
      src/Windows/Avalonia.Win32/Win32Platform.cs
  41. 13
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  42. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  43. 28
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  44. 18
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  45. 2
      tests/Avalonia.UnitTests/TestRoot.cs

4
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -18,8 +18,8 @@
</StackPanel.Styles>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Text="Left aligned text" TextAlignment="Left" />
<TextBlock Text="Center aligned text" TextAlignment="Center" />
<TextBlock Text="Right aligned text" TextAlignment="Right" />

4
samples/RenderDemo/App.xaml.cs

@ -18,6 +18,10 @@ namespace RenderDemo
// App configuration, used by the entry point and previewer
static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.With(new Win32PlatformOptions
{
OverlayPopups = true,
})
.UsePlatformDetect()
.UseReactiveUI()
.LogToDebug();

17
src/Avalonia.Base/AvaloniaLocator.cs

@ -54,6 +54,23 @@ namespace Avalonia
return _locator;
}
public AvaloniaLocator ToLazy<TImlp>(Func<TImlp> func) where TImlp : TService
{
var constructed = false;
TImlp instance = default;
_locator._registry[typeof (TService)] = () =>
{
if (!constructed)
{
instance = func();
constructed = true;
}
return instance;
};
return _locator;
}
public AvaloniaLocator ToSingleton<TImpl>() where TImpl : class, TService, new()
{
TImpl instance = null;

15
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -848,21 +848,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Sets an item container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
/// </summary>
/// <param name="index">The index of the item.</param>
/// <param name="selected">Whether the item should be selected or deselected.</param>
private void MarkItemSelected(int index, bool selected)
{
var container = ItemContainerGenerator?.ContainerFromIndex(index);
if (container != null)
{
MarkContainerSelected(container, selected);
}
}
private void UpdateContainerSelection()
{
if (Presenter?.Panel is IPanel panel)

11
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -238,7 +238,7 @@ namespace Avalonia.Headless
}
}
class HeadlessBitmapStub : IBitmapImpl, IRenderTargetBitmapImpl, IWriteableBitmapImpl
class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
{
public Size Size { get; }
@ -267,6 +267,13 @@ namespace Avalonia.Headless
return new HeadlessDrawingContextStub();
}
public void Blit(IDrawingContextImpl context)
{
}
public bool CanBlit => false;
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
public int Version { get; set; }
@ -307,7 +314,7 @@ namespace Avalonia.Headless
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return new HeadlessBitmapStub(size, new Vector(96, 96));
}

13
src/Avalonia.OpenGL/GlInterface.cs

@ -117,6 +117,19 @@ namespace Avalonia.OpenGL
public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")]
public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
public delegate void GlBlitFramebuffer(int srcX0,
int srcY0,
int srcX1,
int srcY1,
int dstX0,
int dstY0,
int dstX1,
int dstY1,
int mask,
int filter);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)]
public GlBlitFramebuffer BlitFramebuffer { get; }
public delegate void GlGenRenderbuffers(int count, int[] res);
[GlEntryPoint("glGenRenderbuffers")]

2
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -439,7 +439,7 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!--Resources for NotificationCard.xaml -->
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" />
<SolidColorBrush x:Key="NotificationCardProgressBackgroundBrush" Color="#9A9A9A" />
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>

8
src/Avalonia.Themes.Fluent/DataValidationErrors.xaml

@ -31,12 +31,16 @@
</Setter>
<Setter Property="ErrorTemplate">
<DataTemplate>
<Canvas Width="14" Height="14" Margin="4 0 1 0"
<Canvas Name="PART_ErrorTemplateCanvas"
Width="14" Height="14" Margin="4 0 1 0"
Background="Transparent">
<Canvas.Styles>
<Style Selector="ToolTip">
<Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
</Style>
<Style Selector="Canvas#PART_ErrorTemplateCanvas ToolTip TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Canvas.Styles>
<ToolTip.Tip>
<ItemsControl Items="{Binding}"/>

6
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -24,10 +24,14 @@ CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.Tex
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
Total Issues: 31
MembersMustExist : Member 'public Avalonia.Utilities.IRef<Avalonia.Platform.IRenderTargetBitmapImpl> Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract.
Total Issues: 35

17
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -18,7 +18,7 @@ namespace Avalonia.Media
private double _fontRenderingEmSize;
private Size? _size;
private int _biDiLevel;
private Point? _baselineOrigin;
private Point _baselineOrigin;
private ReadOnlySlice<ushort> _glyphIndices;
private ReadOnlySlice<double> _glyphAdvances;
@ -97,9 +97,7 @@ namespace Avalonia.Media
{
get
{
_baselineOrigin ??= CalculateBaselineOrigin();
return _baselineOrigin.Value;
return _baselineOrigin;
}
set => Set(ref _baselineOrigin, value);
}
@ -540,15 +538,6 @@ namespace Avalonia.Media
return GlyphAdvances[index];
}
/// <summary>
/// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
/// </summary>
/// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin()
{
return new Point(0, -GlyphTypeface.Ascent * Scale);
}
/// <summary>
/// Calculates the size of the <see cref="GlyphRun"/>.
/// </summary>
@ -611,8 +600,6 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
_baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale);
var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);

2
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
return;
}
if (Properties.Typeface == null)
if (Properties.Typeface == default)
{
return;
}

67
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting
}
/// <summary>
/// Measures the number of characters that fits into available width.
/// Measures the number of characters that fit into available width.
/// </summary>
/// <param name="textCharacters">The text run.</param>
/// <param name="availableWidth">The available width.</param>
/// <returns></returns>
internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
/// <param name="count">The count of fitting characters.</param>
/// <returns>
/// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
/// </returns>
internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count)
{
var glyphRun = textCharacters.GlyphRun;
if (glyphRun.Size.Width < availableWidth)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
var glyphCount = 0;
@ -96,21 +101,34 @@ namespace Avalonia.Media.TextFormatting
}
}
if (glyphCount == 0)
{
count = 0;
return false;
}
if (glyphCount == glyphRun.GlyphIndices.Length)
{
return glyphRun.Characters.Length;
count = glyphRun.Characters.Length;
return true;
}
if (glyphRun.GlyphClusters.IsEmpty)
{
return glyphCount;
count = glyphCount;
return true;
}
var firstCluster = glyphRun.GlyphClusters[0];
var lastCluster = glyphRun.GlyphClusters[glyphCount];
return lastCluster - firstCluster;
count = lastCluster - firstCluster;
return count > 0;
}
/// <summary>
@ -350,29 +368,38 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.Size.Width > availableWidth)
{
var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
var breakFound = false;
var currentBreakPosition = 0;
if (measuredLength < currentRun.Text.Length)
if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (measuredLength < currentRun.Text.Length)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
}
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
{
break;
}
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
breakFound = lineBreaker.Current.Required ||
lineBreaker.Current.PositionWrap != currentRun.Text.Length;
currentBreakPosition = nextBreakPosition;
currentBreakPosition = nextBreakPosition;
}
}
}
else
{
// Make sure we wrap at least one character.
if (currentLength == 0)
{
measuredLength = 1;
}
}

44
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -39,7 +39,9 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0)))
var offsetY = LineMetrics.TextBaseline;
using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{
textRun.Draw(drawingContext);
}
@ -75,37 +77,35 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth > availableWidth)
{
var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
var currentBreakPosition = 0;
if (measuredLength < textRange.End)
if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength))
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End)
{
var nextBreakPosition = lineBreaker.Current.PositionWrap;
var currentBreakPosition = 0;
if (nextBreakPosition == 0)
{
break;
}
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
if (nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
break;
var nextBreakPosition = lineBreaker.Current.PositionWrap;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition > measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
currentBreakPosition = nextBreakPosition;
measuredLength = currentBreakPosition;
}
}
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
{
measuredLength = currentBreakPosition;
}
collapsedLength += measuredLength;
var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);

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

@ -98,7 +98,7 @@ namespace Avalonia.Platform
/// 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);
IDrawingContextLayerImpl CreateLayer(Size size);
/// <summary>
/// Pushes a clip rectangle.
@ -155,4 +155,18 @@ namespace Avalonia.Platform
/// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom);
}
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl
{
/// <summary>
/// Does optimized blit with Src blend mode
/// </summary>
/// <param name="context"></param>
void Blit(IDrawingContextImpl context);
/// <summary>
/// Returns true if layer supports optimized blit
/// </summary>
bool CanBlit { get; }
}
}

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

@ -489,6 +489,7 @@ namespace Avalonia.Rendering
var clientRect = new Rect(scene.Size);
var firstLayer = true;
foreach (var layer in scene.Layers)
{
var bitmap = Layers[layer.LayerRoot].Bitmap;
@ -501,7 +502,10 @@ namespace Avalonia.Rendering
if (layer.OpacityMask == null)
{
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
if (firstLayer && bitmap.Item.CanBlit)
bitmap.Item.Blit(context);
else
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
@ -512,6 +516,8 @@ namespace Avalonia.Rendering
{
context.PopGeometryClip();
}
firstLayer = false;
}
if (_overlay != null)

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

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
IsEmpty = true;
}
public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; }
public IRef<IDrawingContextLayerImpl> Bitmap { get; private set; }
public bool IsEmpty { get; set; }
public double Scaling { get; private set; }
public Size Size { get; private set; }

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

@ -218,7 +218,7 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}

14
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -31,6 +31,7 @@ namespace Avalonia.Skia
private bool _disposed;
private GRContext _grContext;
public GRContext GrContext => _grContext;
private ISkiaGpu _gpu;
private readonly SKPaint _strokePaint = new SKPaint();
private readonly SKPaint _fillPaint = new SKPaint();
private readonly SKPaint _boxShadowPaint = new SKPaint();
@ -65,6 +66,11 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional)
/// </summary>
public GRContext GrContext;
/// <summary>
/// Skia GPU provider context (optional)
/// </summary>
public ISkiaGpu Gpu;
}
/// <summary>
@ -79,6 +85,7 @@ namespace Avalonia.Skia
_disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext;
_gpu = createInfo.Gpu;
if (_grContext != null)
Monitor.Enter(_grContext);
Surface = createInfo.Surface;
@ -415,9 +422,9 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return CreateRenderTarget(size);
return CreateRenderTarget( size);
}
/// <inheritdoc />
@ -956,7 +963,8 @@ namespace Avalonia.Skia
Dpi = _dpi,
Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering,
GrContext = _grContext
GrContext = _grContext,
Gpu = _gpu
};
return new SurfaceRenderTarget(createInfo);

14
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
@ -15,6 +16,19 @@ namespace Avalonia.Skia
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
/// <summary>
/// Creates an offscreen render target surface
/// </summary>
/// <param name="size">size in pixels</param>
ISkiaSurface TryCreateSurface(PixelSize size);
}
public interface ISkiaSurface : IDisposable
{
SKSurface Surface { get; }
bool CanBlit { get; }
void Blit(SKCanvas canvas);
}
public interface IOpenGlAwareSkiaGpu : ISkiaGpu

134
src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs

@ -0,0 +1,134 @@
using System;
using Avalonia.OpenGL;
using SkiaSharp;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
public class FboSkiaSurface : ISkiaSurface
{
private readonly GRContext _grContext;
private readonly IGlContext _glContext;
private readonly PixelSize _pixelSize;
private int _fbo;
private int _depthStencil;
private int _texture;
private static readonly bool[] TrueFalse = new[] { true, false };
public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize)
{
_grContext = grContext;
_glContext = glContext;
_pixelSize = pixelSize;
var InternalFormat = glContext.Version.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8;
var gl = glContext.GlInterface;
// Save old bindings
gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo);
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer);
gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
var arr = new int[2];
// Generate FBO
gl.GenFramebuffers(1, arr);
_fbo = arr[0];
gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
// Create a texture to render into
gl.GenTextures(1, arr);
_texture = arr[0];
gl.BindTexture(GL_TEXTURE_2D, _texture);
gl.TexImage2D(GL_TEXTURE_2D, 0,
InternalFormat, pixelSize.Width, pixelSize.Height,
0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
var success = false;
foreach (var useStencil8 in TrueFalse)
{
gl.GenRenderbuffers(1, arr);
_depthStencil = arr[0];
gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
if (useStencil8)
{
gl.RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, pixelSize.Width, pixelSize.Height);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
}
else
{
gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, pixelSize.Width, pixelSize.Height);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
}
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE)
{
success = true;
break;
}
else
{
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
gl.DeleteRenderbuffers(1, arr);
}
}
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
gl.BindTexture(GL_TEXTURE_2D, oldTexture);
gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo);
if (!success)
{
arr[0] = _fbo;
gl.DeleteFramebuffers(1, arr);
arr[0] = _texture;
gl.DeleteTextures(1, arr);
throw new OpenGlException("Unable to create FBO with stencil");
}
var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 8,
new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat()));
Surface = SKSurface.Create(_grContext, target,
GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888);
CanBlit = gl.BlitFramebuffer != null;
}
public void Dispose()
{
using (_glContext.EnsureCurrent())
{
Surface?.Dispose();
Surface = null;
var gl = _glContext.GlInterface;
if (_fbo != 0)
{
gl.DeleteFramebuffers(1, new[] { _fbo });
gl.DeleteTextures(1, new[] { _texture });
gl.DeleteRenderbuffers(1, new[] { _depthStencil });
_fbo = _texture = _depthStencil = 0;
}
}
}
public SKSurface Surface { get; private set; }
public bool CanBlit { get; }
public void Blit(SKCanvas canvas)
{
// This should set the render target as the current FBO
// which is definitely not the best method, but it works
canvas.Clear();
canvas.Flush();
var gl = _glContext.GlInterface;
gl.GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, out var oldRead);
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
gl.BlitFramebuffer(0, 0, _pixelSize.Width, _pixelSize.Height, 0, 0, _pixelSize.Width, _pixelSize.Height,
GL_COLOR_BUFFER_BIT, GL_LINEAR);
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, oldRead);
}
}
}

32
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces;
@ -10,6 +13,7 @@ namespace Avalonia.Skia
{
private GRContext _grContext;
private IGlContext _glContext;
private bool? _canCreateSurfaces;
public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes)
{
@ -43,6 +47,34 @@ namespace Avalonia.Skia
return null;
}
public ISkiaSurface TryCreateSurface(PixelSize size)
{
// Only windows platform needs our FBO trickery
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return null;
// Blit feature requires glBlitFramebuffer
if (_glContext.GlInterface.BlitFramebuffer == null)
return null;
size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1));
if (_canCreateSurfaces == false)
return null;
try
{
var surface = new FboSkiaSurface(_grContext, _glContext, size);
_canCreateSurfaces = true;
return surface;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")
?.Log(this, "Unable to create a Skia-compatible FBO manually");
_canCreateSurfaces ??= false;
return null;
}
}
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
}
}

7
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -8,10 +8,12 @@ namespace Avalonia.Skia
/// </summary>
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
{
private readonly ISkiaGpu _skiaGpu;
private readonly ISkiaGpuRenderTarget _renderTarget;
public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget)
public SkiaGpuRenderTarget(ISkiaGpu skiaGpu, ISkiaGpuRenderTarget renderTarget)
{
_skiaGpu = skiaGpu;
_renderTarget = renderTarget;
}
@ -30,7 +32,8 @@ namespace Avalonia.Skia
Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
DisableTextLcdRendering = true,
Gpu = _skiaGpu
};
return new DrawingContextImpl(nfo, session);

2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -138,7 +138,7 @@ namespace Avalonia.Skia
var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null)
{
return new SkiaGpuRenderTarget(gpuRenderTarget);
return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget);
}
foreach (var surface in surfaces)

72
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Reactive.Disposables;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Skia.Helpers;
@ -11,12 +12,31 @@ namespace Avalonia.Skia
/// <summary>
/// Skia render target that writes to a surface.
/// </summary>
internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
internal class SurfaceRenderTarget : IDrawingContextLayerImpl, IDrawableBitmapImpl
{
private readonly SKSurface _surface;
private readonly ISkiaSurface _surface;
private readonly SKCanvas _canvas;
private readonly bool _disableLcdRendering;
private readonly GRContext _grContext;
private readonly ISkiaGpu _gpu;
class SkiaSurfaceWrapper : ISkiaSurface
{
public SKSurface Surface { get; private set; }
public bool CanBlit => false;
public void Blit(SKCanvas canvas) => throw new NotSupportedException();
public SkiaSurfaceWrapper(SKSurface surface)
{
Surface = surface;
}
public void Dispose()
{
Surface?.Dispose();
Surface = null;
}
}
/// <summary>
/// Create new surface render target.
@ -29,9 +49,14 @@ namespace Avalonia.Skia
_disableLcdRendering = createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext;
_surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format);
_gpu = createInfo.Gpu;
_canvas = _surface?.Canvas;
_surface = _gpu?.TryCreateSurface(PixelSize);
if (_surface == null)
_surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height,
createInfo.Format));
_canvas = _surface?.Surface.Canvas;
if (_surface == null || _canvas == null)
{
@ -70,11 +95,12 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo
{
Surface = _surface,
Surface = _surface.Surface,
Dpi = Dpi,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = _disableLcdRendering,
GrContext = _grContext
GrContext = _grContext,
Gpu = _gpu
};
return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++));
@ -106,19 +132,31 @@ namespace Avalonia.Skia
}
}
/// <inheritdoc />
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
public void Blit(IDrawingContextImpl contextImpl)
{
if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size)
var context = (DrawingContextImpl)contextImpl;
if (_surface.CanBlit)
{
_surface.Canvas.Flush();
_surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint);
_surface.Surface.Canvas.Flush();
_surface.Blit(context.Canvas);
}
else
using (var image = SnapshotImage())
{
context.Canvas.DrawImage(image, sourceRect, destRect, paint);
}
{
var oldMatrix = context.Canvas.TotalMatrix;
context.Canvas.ResetMatrix();
_surface.Surface.Draw(context.Canvas, 0, 0, null);
context.Canvas.SetMatrix(oldMatrix);
}
}
public bool CanBlit => true;
/// <inheritdoc />
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{
using var image = SnapshotImage();
context.Canvas.DrawImage(image, sourceRect, destRect, paint);
}
/// <summary>
@ -127,7 +165,7 @@ namespace Avalonia.Skia
/// <returns>Image snapshot.</returns>
public SKImage SnapshotImage()
{
return _surface.Snapshot();
return _surface.Surface.Snapshot();
}
/// <summary>
@ -178,6 +216,8 @@ namespace Avalonia.Skia
/// GPU-accelerated context (optional)
/// </summary>
public GRContext GrContext;
public ISkiaGpu Gpu;
}
}
}

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

@ -38,7 +38,7 @@ namespace Avalonia.Direct2D1
});
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size);

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

@ -4,6 +4,6 @@ namespace Avalonia.Direct2D1
{
public interface ILayerFactory
{
IRenderTargetBitmapImpl CreateLayer(Size size);
IDrawingContextLayerImpl CreateLayer(Size size);
}
}

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

@ -335,7 +335,7 @@ namespace Avalonia.Direct2D1.Media
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
if (_layerFactory != null)
{
@ -346,7 +346,7 @@ namespace Avalonia.Direct2D1.Media
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = PixelSize.FromSizeWithDpi(size, dpi);
return platform.CreateRenderTargetBitmap(pixelSize, dpi);
return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi);
}
}

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

@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Utilities;
@ -8,7 +9,7 @@ using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IDrawingContextLayerImpl, ILayerFactory
{
private readonly BitmapRenderTarget _renderTarget;
@ -34,7 +35,11 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return CreateCompatible(_renderTarget, size);
}

5
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -5,7 +5,7 @@ using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IDrawingContextLayerImpl
{
private readonly WicRenderTarget _renderTarget;
@ -45,5 +45,8 @@ namespace Avalonia.Direct2D1.Media
finishedCallback?.Invoke();
});
}
public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;
}
}

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

@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size);
}

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

@ -35,7 +35,7 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{

92
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1011,6 +1011,10 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect);
[DllImport("user32.dll")]
public static extern bool IsWindowEnabled(IntPtr hWnd);
@ -1292,6 +1296,41 @@ namespace Avalonia.Win32.Interop
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int ChoosePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int DescribePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")]
public static extern bool SwapBuffers(IntPtr hdc);
[DllImport("opengl32.dll")]
public static extern IntPtr wglCreateContext(IntPtr hdc);
[DllImport("opengl32.dll")]
public static extern bool wglDeleteContext(IntPtr context);
[DllImport("opengl32.dll")]
public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context);
[DllImport("opengl32.dll")]
public static extern IntPtr wglGetCurrentContext();
[DllImport("opengl32.dll")]
public static extern IntPtr wglGetCurrentDC();
[DllImport("opengl32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr wglGetProcAddress(string name);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
@ -2121,4 +2160,57 @@ namespace Avalonia.Win32.Interop
public bool fNC;
public bool fWide;
}
[Flags]
internal enum PixelFormatDescriptorFlags : uint
{
PFD_DOUBLEBUFFER = 0x00000001,
PFD_STEREO = 0x00000002,
PFD_DRAW_TO_WINDOW = 0x00000004,
PFD_DRAW_TO_BITMAP = 0x00000008,
PFD_SUPPORT_GDI = 0x00000010,
PFD_SUPPORT_OPENGL = 0x00000020,
PFD_GENERIC_FORMAT = 0x00000040,
PFD_NEED_PALETTE = 0x00000080,
PFD_NEED_SYSTEM_PALETTE = 0x00000100,
PFD_SWAP_EXCHANGE = 0x00000200,
PFD_SWAP_COPY = 0x00000400,
PFD_SWAP_LAYER_BUFFERS = 0x00000800,
PFD_GENERIC_ACCELERATED = 0x00001000,
PFD_SUPPORT_DIRECTDRAW = 0x00002000,
PFD_DEPTH_DONTCARE = 0x20000000,
PFD_DOUBLEBUFFER_DONTCARE = 0x40000000,
PFD_STEREO_DONTCARE = 0x80000000,
}
[StructLayout(LayoutKind.Sequential)]
internal struct PixelFormatDescriptor
{
public ushort Size;
public ushort Version;
public PixelFormatDescriptorFlags Flags;
public byte PixelType;
public byte ColorBits;
public byte RedBits;
public byte RedShift;
public byte GreenBits;
public byte GreenShift;
public byte BlueBits;
public byte BlueShift;
public byte AlphaBits;
public byte AlphaShift;
public byte AccumBits;
public byte AccumRedBits;
public byte AccumGreenBits;
public byte AccumBlueBits;
public byte AccumAlphaBits;
public byte DepthBits;
public byte StencilBits;
public byte AuxBuffers;
public byte LayerType;
private byte Reserved;
public uint LayerMask;
public uint VisibleMask;
public uint DamageMask;
}
}

62
src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs

@ -0,0 +1,62 @@
namespace Avalonia.Win32.OpenGl
{
internal class WglConsts
{
public const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
public const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
public const int WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093;
public const int WGL_CONTEXT_FLAGS_ARB = 0x2094;
public const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
public const int WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000;
public const int WGL_DRAW_TO_WINDOW_ARB = 0x2001;
public const int WGL_DRAW_TO_BITMAP_ARB = 0x2002;
public const int WGL_ACCELERATION_ARB = 0x2003;
public const int WGL_NEED_PALETTE_ARB = 0x2004;
public const int WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005;
public const int WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006;
public const int WGL_SWAP_METHOD_ARB = 0x2007;
public const int WGL_NUMBER_OVERLAYS_ARB = 0x2008;
public const int WGL_NUMBER_UNDERLAYS_ARB = 0x2009;
public const int WGL_TRANSPARENT_ARB = 0x200A;
public const int WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037;
public const int WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038;
public const int WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039;
public const int WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A;
public const int WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B;
public const int WGL_SHARE_DEPTH_ARB = 0x200C;
public const int WGL_SHARE_STENCIL_ARB = 0x200D;
public const int WGL_SHARE_ACCUM_ARB = 0x200E;
public const int WGL_SUPPORT_GDI_ARB = 0x200F;
public const int WGL_SUPPORT_OPENGL_ARB = 0x2010;
public const int WGL_DOUBLE_BUFFER_ARB = 0x2011;
public const int WGL_STEREO_ARB = 0x2012;
public const int WGL_PIXEL_TYPE_ARB = 0x2013;
public const int WGL_COLOR_BITS_ARB = 0x2014;
public const int WGL_RED_BITS_ARB = 0x2015;
public const int WGL_RED_SHIFT_ARB = 0x2016;
public const int WGL_GREEN_BITS_ARB = 0x2017;
public const int WGL_GREEN_SHIFT_ARB = 0x2018;
public const int WGL_BLUE_BITS_ARB = 0x2019;
public const int WGL_BLUE_SHIFT_ARB = 0x201A;
public const int WGL_ALPHA_BITS_ARB = 0x201B;
public const int WGL_ALPHA_SHIFT_ARB = 0x201C;
public const int WGL_ACCUM_BITS_ARB = 0x201D;
public const int WGL_ACCUM_RED_BITS_ARB = 0x201E;
public const int WGL_ACCUM_GREEN_BITS_ARB = 0x201F;
public const int WGL_ACCUM_BLUE_BITS_ARB = 0x2020;
public const int WGL_ACCUM_ALPHA_BITS_ARB = 0x2021;
public const int WGL_DEPTH_BITS_ARB = 0x2022;
public const int WGL_STENCIL_BITS_ARB = 0x2023;
public const int WGL_AUX_BUFFERS_ARB = 0x2024;
public const int WGL_NO_ACCELERATION_ARB = 0x2025;
public const int WGL_GENERIC_ACCELERATION_ARB = 0x2026;
public const int WGL_FULL_ACCELERATION_ARB = 0x2027;
public const int WGL_SWAP_EXCHANGE_ARB = 0x2028;
public const int WGL_SWAP_COPY_ARB = 0x2029;
public const int WGL_SWAP_UNDEFINED_ARB = 0x202A;
public const int WGL_TYPE_RGBA_ARB = 0x202B;
public const int WGL_TYPE_COLORINDEX_ARB = 0x202C;
}
}

85
src/Windows/Avalonia.Win32/OpenGl/WglContext.cs

@ -0,0 +1,85 @@
using System;
using System.Reactive.Disposables;
using Avalonia.OpenGL;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
namespace Avalonia.Win32.OpenGl
{
class WglContext : IGlContext
{
private object _lock = new object();
private readonly WglContext _sharedWith;
private readonly IntPtr _context;
private readonly IntPtr _hWnd;
private readonly IntPtr _dc;
private readonly int _pixelFormat;
private readonly PixelFormatDescriptor _formatDescriptor;
public IntPtr Handle => _context;
public WglContext(WglContext sharedWith, GlVersion version, IntPtr context, IntPtr hWnd, IntPtr dc, int pixelFormat,
PixelFormatDescriptor formatDescriptor)
{
Version = version;
_sharedWith = sharedWith;
_context = context;
_hWnd = hWnd;
_dc = dc;
_pixelFormat = pixelFormat;
_formatDescriptor = formatDescriptor;
StencilSize = formatDescriptor.StencilBits;
using (MakeCurrent())
GlInterface = new GlInterface(version, proc =>
{
var ext = wglGetProcAddress(proc);
if (ext != IntPtr.Zero)
return ext;
return GetProcAddress(WglDisplay.OpenGl32Handle, proc);
});
}
public void Dispose()
{
wglDeleteContext(_context);
ReleaseDC(_hWnd, _dc);
DestroyWindow(_hWnd);
}
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc;
public IDisposable MakeCurrent()
{
if(IsCurrent)
return Disposable.Empty;
return new WglRestoreContext(_dc, _context, _lock);
}
public IDisposable EnsureCurrent() => MakeCurrent();
public IntPtr CreateConfiguredDeviceContext(IntPtr hWnd)
{
var dc = GetDC(hWnd);
var fmt = _formatDescriptor;
SetPixelFormat(dc, _pixelFormat, ref fmt);
return dc;
}
public IDisposable MakeCurrent(IntPtr hdc) => new WglRestoreContext(hdc, _context, _lock);
public bool IsSharedWith(IGlContext context)
{
var c = (WglContext)context;
return c == this
|| c._sharedWith == this
|| _sharedWith == context
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
}
}

183
src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs

@ -0,0 +1,183 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.OpenGl.WglConsts;
namespace Avalonia.Win32.OpenGl
{
internal class WglDisplay
{
private static bool? _initialized;
private static ushort _windowClass;
private static readonly WndProc _wndProcDelegate = WndProc;
private static readonly DebugCallbackDelegate _debugCallback = DebugCallback;
private static IntPtr _bootstrapContext;
private static IntPtr _bootstrapWindow;
private static IntPtr _bootstrapDc;
private static PixelFormatDescriptor _defaultPfd;
private static int _defaultPixelFormat;
public static IntPtr OpenGl32Handle = LoadLibrary("opengl32");
private delegate bool WglChoosePixelFormatARBDelegate(IntPtr hdc, int[] piAttribIList, float[] pfAttribFList,
int nMaxFormats, int[] piFormats, out int nNumFormats);
private static WglChoosePixelFormatARBDelegate WglChoosePixelFormatArb;
private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList);
private static WglCreateContextAttribsARBDelegate WglCreateContextAttribsArb;
private delegate void GlDebugMessageCallbackDelegate(IntPtr callback, IntPtr userParam);
private static GlDebugMessageCallbackDelegate GlDebugMessageCallback;
private delegate void DebugCallbackDelegate(int source, int type, int id, int severity, int len, IntPtr message,
IntPtr userParam);
static bool Initialize()
{
if (!_initialized.HasValue)
_initialized = InitializeCore();
return _initialized.Value;
}
static bool InitializeCore()
{
var wndClassEx = new WNDCLASSEX
{
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
hInstance = GetModuleHandle(null),
lpfnWndProc = _wndProcDelegate,
lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(),
style = (int)ClassStyles.CS_OWNDC
};
_windowClass = RegisterClassEx(ref wndClassEx);
_bootstrapWindow = CreateOffscreenWindow();
_bootstrapDc = GetDC(_bootstrapWindow);
_defaultPfd = new PixelFormatDescriptor
{
Size = (ushort)Marshal.SizeOf<PixelFormatDescriptor>(),
Version = 1,
Flags = PixelFormatDescriptorFlags.PFD_DRAW_TO_WINDOW |
PixelFormatDescriptorFlags.PFD_SUPPORT_OPENGL | PixelFormatDescriptorFlags.PFD_DOUBLEBUFFER,
DepthBits = 24,
StencilBits = 8,
ColorBits = 32
};
_defaultPixelFormat = ChoosePixelFormat(_bootstrapDc, ref _defaultPfd);
SetPixelFormat(_bootstrapDc, _defaultPixelFormat, ref _defaultPfd);
_bootstrapContext = wglCreateContext(_bootstrapDc);
if (_bootstrapContext == IntPtr.Zero)
return false;
wglMakeCurrent(_bootstrapDc, _bootstrapContext);
WglCreateContextAttribsArb = Marshal.GetDelegateForFunctionPointer<WglCreateContextAttribsARBDelegate>(
wglGetProcAddress("wglCreateContextAttribsARB"));
WglChoosePixelFormatArb =
Marshal.GetDelegateForFunctionPointer<WglChoosePixelFormatARBDelegate>(
wglGetProcAddress("wglChoosePixelFormatARB"));
GlDebugMessageCallback =
Marshal.GetDelegateForFunctionPointer<GlDebugMessageCallbackDelegate>(
wglGetProcAddress("glDebugMessageCallback"));
var formats = new int[1];
WglChoosePixelFormatArb(_bootstrapDc, new int[]
{
WGL_DRAW_TO_WINDOW_ARB, 1,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_SUPPORT_OPENGL_ARB, 1,
WGL_DOUBLE_BUFFER_ARB, 1,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 0,
WGL_STENCIL_BITS_ARB, 0,
0, // End
}, null, 1, formats, out int numFormats);
if (numFormats != 0)
{
DescribePixelFormat(_bootstrapDc, formats[0], Marshal.SizeOf<PixelFormatDescriptor>(), ref _defaultPfd);
_defaultPixelFormat = formats[0];
}
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
return true;
}
static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private static void DebugCallback(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userparam)
{
var err = Marshal.PtrToStringAnsi(message, len);
Console.Error.WriteLine(err);
}
public static WglContext CreateContext(GlVersion[] versions, IGlContext share)
{
if (!Initialize())
return null;
var shareContext = (WglContext)share;
using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null))
{
var window = CreateOffscreenWindow();
var dc = GetDC(window);
SetPixelFormat(dc, _defaultPixelFormat, ref _defaultPfd);
foreach (var version in versions)
{
if(version.Type != GlProfileType.OpenGL)
continue;
var context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero,
new[]
{
// major
WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major,
// minor
WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor,
// core profile
WGL_CONTEXT_PROFILE_MASK_ARB, 1,
// debug
// WGL_CONTEXT_FLAGS_ARB, 1,
// end
0, 0
});
using(new WglRestoreContext(dc, context, null))
GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero);
if (context != IntPtr.Zero)
return new WglContext(shareContext, version, context, window, dc,
_defaultPixelFormat, _defaultPfd);
}
ReleaseDC(window, dc);
DestroyWindow(window);
return null;
}
}
static IntPtr CreateOffscreenWindow() =>
CreateWindowEx(
0,
_windowClass,
null,
(int)WindowStyles.WS_OVERLAPPEDWINDOW,
0,
0,
640,
480,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
}

85
src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs

@ -0,0 +1,85 @@
using System;
using System.Diagnostics;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Win32.Interop;
using static Avalonia.OpenGL.GlConsts;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.OpenGl
{
class WglGlPlatformSurface: IGlPlatformSurface
{
private readonly WglContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public WglGlPlatformSurface(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
return new RenderTarget(_context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly WglContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IntPtr _hdc;
public RenderTarget(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
_hdc = context.CreateConfiguredDeviceContext(info.Handle);
}
public void Dispose()
{
UnmanagedMethods.ReleaseDC(_hdc, _info.Handle);
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var oldContext = _context.MakeCurrent(_hdc);
// Reset to default FBO first
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, 0);
return new Session(_context, _hdc, _info, oldContext);
}
class Session : IGlPlatformSurfaceRenderingSession
{
private readonly WglContext _context;
private readonly IntPtr _hdc;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private readonly IDisposable _clearContext;
public IGlContext Context => _context;
public Session(WglContext context, IntPtr hdc, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable clearContext)
{
_context = context;
_hdc = hdc;
_info = info;
_clearContext = clearContext;
}
public void Dispose()
{
_context.GlInterface.Flush();
UnmanagedMethods.SwapBuffers(_hdc);
_clearContext.Dispose();
}
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }
}
}
}
}

39
src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs

@ -0,0 +1,39 @@
using System;
using System.Linq;
using Avalonia.Logging;
using Avalonia.OpenGL;
namespace Avalonia.Win32.OpenGl
{
class WglPlatformOpenGlInterface : IPlatformOpenGlInterface
{
public WglContext PrimaryContext { get; }
IGlContext IPlatformOpenGlInterface.PrimaryContext => PrimaryContext;
public IGlContext CreateSharedContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, PrimaryContext);
public bool CanShareContexts => true;
public bool CanCreateContexts => true;
public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null);
private WglPlatformOpenGlInterface(WglContext primary)
{
PrimaryContext = primary;
}
public static WglPlatformOpenGlInterface TryCreate()
{
try
{
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>() ?? new Win32PlatformOptions();
var primary = WglDisplay.CreateContext(opts.WglProfiles.ToArray(), null);
return new WglPlatformOpenGlInterface(primary);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("WGL", "Unable to initialize WGL: " + e);
}
return null;
}
}
}

39
src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs

@ -0,0 +1,39 @@
using System;
using System.Threading;
using Avalonia.OpenGL;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.OpenGl
{
internal class WglRestoreContext : IDisposable
{
private readonly object _monitor;
private readonly IntPtr _oldDc;
private readonly IntPtr _oldContext;
public WglRestoreContext(IntPtr gc, IntPtr context, object monitor, bool takeMonitor = true)
{
_monitor = monitor;
_oldDc = wglGetCurrentDC();
_oldContext = wglGetCurrentContext();
if (monitor != null && takeMonitor)
Monitor.Enter(monitor);
if (!wglMakeCurrent(gc, context))
{
if(monitor != null && takeMonitor)
Monitor.Exit(monitor);
throw new OpenGlException("Unable to make the context current");
}
}
public void Dispose()
{
if (!wglMakeCurrent(_oldDc, _oldContext))
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
if (_monitor != null)
Monitor.Exit(_monitor);
}
}
}

18
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -1,27 +1,29 @@
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.Win32.OpenGl;
namespace Avalonia.Win32
{
static class Win32GlManager
{
/// <summary>This property is initialized if drawing platform requests OpenGL support</summary>
public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; }
private static bool s_attemptedToInitialize;
public static void Initialize()
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToFunc(() =>
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToLazy<IPlatformOpenGlInterface>(() =>
{
if (!s_attemptedToInitialize)
var opts = AvaloniaLocator.Current.GetService<Win32PlatformOptions>();
if (opts?.UseWgl == true)
{
EglPlatformInterface = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
s_attemptedToInitialize = true;
var wgl = WglPlatformOpenGlInterface.TryCreate();
return wgl;
}
if (opts?.AllowEglInitialization == true)
return EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
return EglPlatformInterface;
return null;
});
}
}

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

@ -11,6 +11,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
@ -39,6 +40,12 @@ namespace Avalonia
public bool AllowEglInitialization { get; set; } = true;
public bool? EnableMultitouch { get; set; }
public bool OverlayPopups { get; set; }
public bool UseWgl { get; set; }
public IList<GlVersion> WglProfiles { get; set; } = new List<GlVersion>
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
new GlVersion(GlProfileType.OpenGL, 3, 2),
};
}
}
@ -96,8 +103,7 @@ namespace Avalonia.Win32
.Bind<AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider>().ToConstant(new NonPumpingWaitProvider())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider());
if (options.AllowEglInitialization)
Win32GlManager.Initialize();
Win32GlManager.Initialize();
_uiThread = Thread.CurrentThread;

13
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -342,19 +342,14 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_PAINT:
{
{
using (_rendererLock.Lock())
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
var f = RenderScaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
EndPaint(_hwnd, ref ps);
}
Paint?.Invoke(default);
}
ValidateRect(hWnd, IntPtr.Zero);
return IntPtr.Zero;
}

9
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -13,6 +13,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Win32.OpenGl;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
@ -105,8 +106,12 @@ namespace Avalonia.Win32
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
if (Win32GlManager.EglPlatformInterface != null)
_gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this);
var glPlatform = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if(glPlatform is EglPlatformOpenGlInterface egl)
_gl = new EglGlPlatformSurface(egl, this);
else if (glPlatform is WglPlatformOpenGlInterface wgl)
_gl = new WglGlPlatformSurface(wgl.PrimaryContext, this);
Screen = new ScreenImpl();

28
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -293,6 +293,34 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Wrap_Should_Not_Produce_Empty_Lines()
{
using (Start())
{
const string text = "012345";
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textSourceIndex = 0;
while (textSourceIndex < text.Length)
{
var textLine =
formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties);
Assert.NotEqual(0, textLine.TextRange.Length);
textSourceIndex += textLine.TextRange.Length;
}
Assert.Equal(text.Length, textSourceIndex);
}
}
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

18
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -575,6 +575,24 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Wrap_Min_OneCharacter_EveryLine()
{
using (Start())
{
var layout = new TextLayout(
s_singleLineText,
Typeface.Default,
12,
Brushes.Black,
textWrapping: TextWrapping.Wrap,
maxWidth: 3);
//every character should be new line as there not enough space for even one character
Assert.Equal(s_singleLineText.Length, layout.TextLines.Count);
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -72,7 +72,7 @@ namespace Avalonia.UnitTests
dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() =>
{
var layerDc = new Mock<IDrawingContextImpl>();
var layer = new Mock<IRenderTargetBitmapImpl>();
var layer = new Mock<IDrawingContextLayerImpl>();
layer.Setup(x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(layerDc.Object);
return layer.Object;
});

Loading…
Cancel
Save