diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 4a1c196917..d4f72f161a 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -18,8 +18,8 @@ - - + + diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 233160b025..340ccdae19 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/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() + .With(new Win32PlatformOptions + { + OverlayPopups = true, + }) .UsePlatformDetect() .UseReactiveUI() .LogToDebug(); diff --git a/src/Avalonia.Base/AvaloniaLocator.cs b/src/Avalonia.Base/AvaloniaLocator.cs index f9bbe38bec..3163d15c1b 100644 --- a/src/Avalonia.Base/AvaloniaLocator.cs +++ b/src/Avalonia.Base/AvaloniaLocator.cs @@ -54,6 +54,23 @@ namespace Avalonia return _locator; } + public AvaloniaLocator ToLazy(Func 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() where TImpl : class, TService, new() { TImpl instance = null; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e34b3b145f..4317d795f1 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -848,21 +848,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Sets an item container's 'selected' class or . - /// - /// The index of the item. - /// Whether the item should be selected or deselected. - 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) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 4f6af0a41b..6a78f4c6e7 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/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)); } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index ea2fe0a99c..28b62136da 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/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")] diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 4d21068492..abc02a6371 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -439,7 +439,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml b/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml index 88c6b661f1..902fc74c0c 100644 --- a/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml +++ b/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml @@ -31,12 +31,16 @@ - - + diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 148916932f..7fb44152c9 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/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.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. +Total Issues: 35 diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 14ab083b4f..155339b985 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/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 _glyphIndices; private ReadOnlySlice _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]; } - /// - /// Calculates the default baseline origin of the . - /// - /// The baseline origin. - private Point CalculateBaselineOrigin() - { - return new Point(0, -GlyphTypeface.Ascent * Scale); - } - /// /// Calculates the size of the . /// @@ -611,8 +600,6 @@ namespace Avalonia.Media throw new InvalidOperationException(); } - _baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale); - var platformRenderInterface = AvaloniaLocator.Current.GetService(); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width); diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 09ecc0a026..9f6f2b2f43 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/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; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3e85f0f6f0..4a7282af27 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting } /// - /// Measures the number of characters that fits into available width. + /// Measures the number of characters that fit into available width. /// /// The text run. /// The available width. - /// - internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) + /// The count of fitting characters. + /// + /// true if characters fit into the available width; otherwise, false. + /// + 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; } /// @@ -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; } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index f5e87d097b..d13b4836ea 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/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); diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 019614ae80..cfe2cf979a 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/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. /// - IRenderTargetBitmapImpl CreateLayer(Size size); + IDrawingContextLayerImpl CreateLayer(Size size); /// /// Pushes a clip rectangle. @@ -155,4 +155,18 @@ namespace Avalonia.Platform /// Custom draw operation void Custom(ICustomDrawOperation custom); } + + public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl + { + /// + /// Does optimized blit with Src blend mode + /// + /// + void Blit(IDrawingContextImpl context); + + /// + /// Returns true if layer supports optimized blit + /// + bool CanBlit { get; } + } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 8c020fc073..15e14935ca 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/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) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index ddf5f4e5cf..7a79e45716 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering IsEmpty = true; } - public IRef Bitmap { get; private set; } + public IRef Bitmap { get; private set; } public bool IsEmpty { get; set; } public double Scaling { get; private set; } public Size Size { get; private set; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index cb6b1f59d4..28f426266d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/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"); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 05a8daaec8..9a486b60aa 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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) /// public GRContext GrContext; + + /// + /// Skia GPU provider context (optional) + /// + public ISkiaGpu Gpu; } /// @@ -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 } /// - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { - return CreateRenderTarget(size); + return CreateRenderTarget( size); } /// @@ -956,7 +963,8 @@ namespace Avalonia.Skia Dpi = _dpi, Format = format, DisableTextLcdRendering = !_canTextUseLcdRendering, - GrContext = _grContext + GrContext = _grContext, + Gpu = _gpu }; return new SurfaceRenderTarget(createInfo); diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 1a7a9b75cf..869c261f1b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/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 /// Surfaces. /// Created render target or if it fails. ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces); + + /// + /// Creates an offscreen render target surface + /// + /// size in pixels + ISkiaSurface TryCreateSurface(PixelSize size); + } + + public interface ISkiaSurface : IDisposable + { + SKSurface Surface { get; } + bool CanBlit { get; } + void Blit(SKCanvas canvas); } public interface IOpenGlAwareSkiaGpu : ISkiaGpu diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs new file mode 100644 index 0000000000..9ee8d698ba --- /dev/null +++ b/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); + } + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 46d42dfdab..c02d813e24 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/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); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index ef5da5eb08..9992c9ba8c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -8,10 +8,12 @@ namespace Avalonia.Skia /// 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); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b9c1cbc673..d6f76a2c20 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/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) diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 428087ac56..61b599a731 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/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 /// /// Skia render target that writes to a surface. /// - 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; + } + } /// /// 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 } } - /// - 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; + + /// + public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) + { + using var image = SnapshotImage(); + context.Canvas.DrawImage(image, sourceRect, destRect, paint); } /// @@ -127,7 +165,7 @@ namespace Avalonia.Skia /// Image snapshot. public SKImage SnapshotImage() { - return _surface.Snapshot(); + return _surface.Surface.Snapshot(); } /// @@ -178,6 +216,8 @@ namespace Avalonia.Skia /// GPU-accelerated context (optional) /// public GRContext GrContext; + + public ISkiaGpu Gpu; } } } diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index aad50331d2..2c0adcac32 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/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); diff --git a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs index 08273f1bb2..a15bc0056a 100644 --- a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs +++ b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs @@ -4,6 +4,6 @@ namespace Avalonia.Direct2D1 { public interface ILayerFactory { - IRenderTargetBitmapImpl CreateLayer(Size size); + IDrawingContextLayerImpl CreateLayer(Size size); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 258a51db5a..59292d605c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/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(); 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); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 3a3f9a9f7d..9a0e2ec00c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/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); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index e8e27f3f5d..1265a7bdf0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/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; } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 4433c8d50a..d04c616bd9 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/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); } diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 5f4d06dab0..f319cfae03 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/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) { diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b7c68c4b95..5feb6c9e46 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/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; + } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs b/src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs new file mode 100644 index 0000000000..3cb3dc25bb --- /dev/null +++ b/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; + + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs new file mode 100644 index 0000000000..d6633ddb61 --- /dev/null +++ b/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; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs new file mode 100644 index 0000000000..dc3c16b4a3 --- /dev/null +++ b/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(), + 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(), + 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( + wglGetProcAddress("wglCreateContextAttribsARB")); + + WglChoosePixelFormatArb = + Marshal.GetDelegateForFunctionPointer( + wglGetProcAddress("wglChoosePixelFormatARB")); + + GlDebugMessageCallback = + Marshal.GetDelegateForFunctionPointer( + 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(), 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); + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs new file mode 100644 index 0000000000..72bcffd447 --- /dev/null +++ b/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; } + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..b948495b99 --- /dev/null +++ b/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() ?? 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; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs new file mode 100644 index 0000000000..265f078a5c --- /dev/null +++ b/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); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index fbc56e7703..523a059e0e 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/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 { - /// This property is initialized if drawing platform requests OpenGL support - public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; } - private static bool s_attemptedToInitialize; public static void Initialize() { - AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => + AvaloniaLocator.CurrentMutable.Bind().ToLazy(() => { - if (!s_attemptedToInitialize) + var opts = AvaloniaLocator.Current.GetService(); + 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; }); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index af6058d197..61c1a4f45e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/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 WglProfiles { get; set; } = new List + { + new GlVersion(GlProfileType.OpenGL, 4, 0), + new GlVersion(GlProfileType.OpenGL, 3, 2), + }; } } @@ -96,8 +103,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(new NonPumpingWaitProvider()) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); - if (options.AllowEglInitialization) - Win32GlManager.Initialize(); + Win32GlManager.Initialize(); _uiThread = Thread.CurrentThread; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index a03e1ffc22..01576500e7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/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; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cb85e14e5a..7079a0120c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/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(); + + 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(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index adcc79e029..7f9713930a 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/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 diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index f3e1c37705..26e8ce4797 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/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 diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index b6f3a020e8..b69bf990d9 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -72,7 +72,7 @@ namespace Avalonia.UnitTests dc.Setup(x => x.CreateLayer(It.IsAny())).Returns(() => { var layerDc = new Mock(); - var layer = new Mock(); + var layer = new Mock(); layer.Setup(x => x.CreateDrawingContext(It.IsAny())).Returns(layerDc.Object); return layer.Object; });