Browse Source

Merge branch 'master' into SetterTargetType

Luis von der Eltz 3 years ago
parent
commit
b2b95f75df
41 changed files with 1627 additions and 1202 deletions
  1. 7 37
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  2. 1 1
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  3. 96 13
      src/Avalonia.Base/Media/GlyphRun.cs
  4. 1 1
      src/Avalonia.Base/Media/TextDecoration.cs
  5. 25 26
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  6. 227 206
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  7. 7 2
      src/Avalonia.Controls/Documents/LineBreak.cs
  8. 10 1
      src/Avalonia.Controls/ProgressBar.cs
  9. 85 34
      src/Avalonia.Controls/RichTextBlock.cs
  10. 6 1
      src/Avalonia.Controls/TextBlock.cs
  11. 169 116
      src/Avalonia.Controls/TextBox.cs
  12. 2 2
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  13. 1 0
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  14. 5 9
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  15. 1 1
      src/Avalonia.OpenGL/Egl/EglContext.cs
  16. 2 2
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  17. 83 142
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  18. 11 33
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  19. 20 83
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  20. 219 275
      src/Avalonia.OpenGL/GlInterface.cs
  21. 0 79
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  22. 10 0
      src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml
  23. 1 0
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  24. 1 0
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  25. 14 0
      src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml
  26. 1 1
      src/Avalonia.X11/Avalonia.X11.csproj
  27. 48 63
      src/Avalonia.X11/Glx/Glx.cs
  28. 1 1
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  29. 1 1
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  30. 24 0
      src/Shared/SourceGeneratorAttributes.cs
  31. 11 17
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  32. 1 1
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  33. 7 4
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  34. 23 29
      src/iOS/Avalonia.iOS/LayerFbo.cs
  35. 338 0
      src/tools/DevGenerators/GetProcAddressInitialization.cs
  36. 31 0
      src/tools/DevGenerators/Helpers.cs
  37. BIN
      tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf
  38. 3 1
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  39. 2 6
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs
  40. 123 6
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  41. 9 8
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

+ 7 - 37
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@@ -90,7 +90,6 @@ namespace ControlCatalog.Pages
         private int _vertexBufferObject;
         private int _indexBufferObject;
         private int _vertexArrayObject;
-        private GlExtrasInterface _glExt;
 
         private string GetShader(bool fragment, string shader)
         {
@@ -258,7 +257,6 @@ namespace ControlCatalog.Pages
         protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
         {
             CheckError(GL);
-            _glExt = new GlExtrasInterface(GL);
 
             Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}";
             
@@ -298,8 +296,8 @@ namespace ControlCatalog.Pages
                 GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata),
                     GL_STATIC_DRAW);
             CheckError(GL);
-            _vertexArrayObject = _glExt.GenVertexArray();
-            _glExt.BindVertexArray(_vertexArrayObject);
+            _vertexArrayObject = GL.GenVertexArray();
+            GL.BindVertexArray(_vertexArrayObject);
             CheckError(GL);
             GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
                 0, vertexSize, IntPtr.Zero);
@@ -316,12 +314,13 @@ namespace ControlCatalog.Pages
             // Unbind everything
             GL.BindBuffer(GL_ARRAY_BUFFER, 0);
             GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-            _glExt.BindVertexArray(0);
+            GL.BindVertexArray(0);
             GL.UseProgram(0);
 
             // Delete all resources.
-            GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject });
-            _glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject });
+            GL.DeleteBuffer(_vertexBufferObject);
+            GL.DeleteBuffer(_indexBufferObject);
+            GL.DeleteVertexArray(_vertexArrayObject);
             GL.DeleteProgram(_shaderProgram);
             GL.DeleteShader(_fragmentShader);
             GL.DeleteShader(_vertexShader);
@@ -338,7 +337,7 @@ namespace ControlCatalog.Pages
 
             GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
             GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
-            _glExt.BindVertexArray(_vertexArrayObject);
+            GL.BindVertexArray(_vertexArrayObject);
             GL.UseProgram(_shaderProgram);
             CheckError(GL);
             var projection =
@@ -369,34 +368,5 @@ namespace ControlCatalog.Pages
             if (_disco > 0.01)
                 Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
         }
-
-        class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo>
-        {
-            public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo)
-            {
-            }
-            
-            public delegate void GlDeleteVertexArrays(int count, int[] buffers);
-            [GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)]
-            [GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
-            public GlDeleteVertexArrays DeleteVertexArrays { get; }
-            
-            public delegate void GlBindVertexArray(int array);
-            [GlMinVersionEntryPoint("glBindVertexArray", 3,0)]
-            [GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
-            public GlBindVertexArray BindVertexArray { get; }
-            public delegate void GlGenVertexArrays(int n, int[] rv);
-        
-            [GlMinVersionEntryPoint("glGenVertexArrays",3,0)]
-            [GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
-            public GlGenVertexArrays GenVertexArrays { get; }
-            
-            public int GenVertexArray()
-            {
-                var rv = new int[1];
-                GenVertexArrays(1, rv);
-                return rv[0];
-            }
-        }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/TextBlockPage.xaml

@@ -118,7 +118,7 @@
         </StackPanel>
       </Border>
       <Border>
-        <RichTextBlock Margin="10" TextWrapping="Wrap">
+        <RichTextBlock SelectionBrush="LightBlue" IsTextSelectionEnabled="True" Margin="10" TextWrapping="Wrap">
           This <Span FontWeight="Bold">is</Span> a
           <Span Background="Silver" Foreground="Maroon">TextBlock</Span>
           with <Span TextDecorations="Underline">several</Span>

+ 96 - 13
src/Avalonia.Base/Media/GlyphRun.cs

@@ -445,7 +445,7 @@ namespace Avalonia.Media
         /// </returns>
         public int FindGlyphIndex(int characterIndex)
         {
-            if (GlyphClusters == null)
+            if (GlyphClusters == null || GlyphClusters.Count == 0)
             {
                 return characterIndex;
             }
@@ -614,17 +614,29 @@ namespace Avalonia.Media
 
         private GlyphRunMetrics CreateGlyphRunMetrics()
         {
+            var firstCluster = 0;
+            var lastCluster = Characters.Length - 1;
+
+            if (!IsLeftToRight)
+            {
+                var cluster = firstCluster;
+                firstCluster = lastCluster;
+                lastCluster = cluster;
+            }
+
             if (GlyphClusters != null && GlyphClusters.Count > 0)
             {
-                var firstCluster = GlyphClusters[0];
+                firstCluster = GlyphClusters[0];
+                lastCluster = GlyphClusters[GlyphClusters.Count - 1];
 
                 _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
             }
 
+            var isReversed = firstCluster > lastCluster;
             var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
             var widthIncludingTrailingWhitespace = 0d;
 
-            var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount);
+            var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
 
             for (var index = 0; index < GlyphIndices.Count; index++)
             {
@@ -635,16 +647,16 @@ namespace Avalonia.Media
 
             var width = widthIncludingTrailingWhitespace;
 
-            if (IsLeftToRight)
+            if (isReversed)
             {
-                for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
+                for (var index = 0; index < glyphCount; index++)
                 {
                     width -= GetGlyphAdvance(index, out _);
                 }
             }
             else
             {
-                for (var index = 0; index < glyphCount; index++)
+                for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
                 {
                     width -= GetGlyphAdvance(index, out _);
                 }
@@ -654,16 +666,15 @@ namespace Avalonia.Media
                 height);
         }
 
-        private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount)
-        {
-            glyphCount = 0;
-            newLineLength = 0;
-
-            if (Characters.IsEmpty)
+        private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
+        {          
+            if (isReversed)
             {
-                return 0;
+                return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
             }
 
+            glyphCount = 0;
+            newLineLength = 0;
             var trailingWhitespaceLength = 0;
 
             if (GlyphClusters == null)
@@ -732,6 +743,78 @@ namespace Avalonia.Media
             return trailingWhitespaceLength;
         }
 
+        private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount)
+        {
+            glyphCount = 0;
+            newLineLength = 0;
+            var trailingWhitespaceLength = 0;
+
+            if (GlyphClusters == null)
+            {
+                for (var i = 0; i < Characters.Length;)
+                {
+                    var codepoint = Codepoint.ReadAt(_characters, i, out var count);
+
+                    if (!codepoint.IsWhiteSpace)
+                    {
+                        break;
+                    }
+
+                    if (codepoint.IsBreakChar)
+                    {
+                        newLineLength++;
+                    }
+
+                    trailingWhitespaceLength++;
+
+                    i += count;
+                    glyphCount++;
+                }
+            }
+            else
+            {
+                for (var i = 0; i < GlyphClusters.Count; i++)
+                {
+                    var currentCluster = GlyphClusters[i];
+                    var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
+                    var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
+
+                    if (!codepoint.IsWhiteSpace)
+                    {
+                        break;
+                    }
+
+                    var clusterLength = 1;
+
+                    while (i - 1 >= 0)
+                    {
+                        var nextCluster = GlyphClusters[i - 1];
+
+                        if (currentCluster == nextCluster)
+                        {
+                            clusterLength++;
+                            i--;
+
+                            continue;
+                        }
+
+                        break;
+                    }
+
+                    if (codepoint.IsBreakChar)
+                    {
+                        newLineLength += clusterLength;
+                    }
+
+                    trailingWhitespaceLength += clusterLength;
+
+                    glyphCount++;
+                }
+            }
+
+            return trailingWhitespaceLength;
+        }
+
         private void Set<T>(ref T field, T value)
         {
             _glyphRunImpl?.Dispose();

+ 1 - 1
src/Avalonia.Base/Media/TextDecoration.cs

@@ -209,7 +209,7 @@ namespace Avalonia.Media
             var pen = new Pen(Stroke ?? defaultBrush, thickness,
                 new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
 
-            drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Size.Width, 0));
+            drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0));
         }
     }
 }

+ 25 - 26
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Media.TextFormatting
 
             MaxHeight = maxHeight;
 
-            MaxLines = maxLines;      
+            MaxLines = maxLines;
 
             TextLines = CreateTextLines();
         }
@@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="maxLines">The maximum number of text lines.</param>
         public TextLayout(
             ITextSource textSource,
-            TextParagraphProperties paragraphProperties, 
+            TextParagraphProperties paragraphProperties,
             TextTrimming? textTrimming = null,
             double maxWidth = double.PositiveInfinity,
             double maxHeight = double.PositiveInfinity,
@@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting
                 return new Rect();
             }
 
-            if (textPosition < 0 || textPosition >= _textSourceLength)
+            if (textPosition < 0)
             {
-                var lastLine = TextLines[TextLines.Count - 1];
-
-                var lineX = lastLine.Width;
-
-                var lineY = Bounds.Bottom - lastLine.Height;
-
-                return new Rect(lineX, lineY, 0, lastLine.Height);
+                textPosition = _textSourceLength;
             }
 
             var currentY = 0.0;
 
             foreach (var textLine in TextLines)
             {
-                var end = textLine.FirstTextSourceIndex + textLine.Length - 1;
+                var end = textLine.FirstTextSourceIndex + textLine.Length;
 
-                if (end < textPosition)
+                if (end <= textPosition && end < _textSourceLength)
                 {
                     currentY += textLine.Height;
 
@@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting
             }
 
             var result = new List<Rect>(TextLines.Count);
-            
+
             var currentY = 0d;
 
             foreach (var textLine in TextLines)
@@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting
 
                 var textBounds = textLine.GetTextBounds(start, length);
 
-                if(textBounds.Count > 0)
+                if (textBounds.Count > 0)
                 {
                     foreach (var bounds in textBounds)
                     {
@@ -262,7 +256,7 @@ namespace Avalonia.Media.TextFormatting
                     }
                 }
 
-                if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)
+                if (textLine.FirstTextSourceIndex + textLine.Length >= start + length)
                 {
                     break;
                 }
@@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting
             return GetHitTestResult(currentLine, characterHit, point);
         }
 
-        
+
         public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge)
         {
             if (charIndex < 0)
@@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting
                     continue;
                 }
 
-                if (charIndex >= textLine.FirstTextSourceIndex && 
+                if (charIndex >= textLine.FirstTextSourceIndex &&
                     charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
                 {
                     return index;
@@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="left">The current left.</param>
         /// <param name="width">The current width.</param>
         /// <param name="height">The current height.</param>
-        private static void UpdateBounds(TextLine textLine,ref double left,  ref double width, ref double height)
+        private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height)
         {
             var lineWidth = textLine.WidthIncludingTrailingWhitespace;
 
@@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting
             {
                 var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
 
-                Bounds = new Rect(0,0,0, textLine.Height);
+                Bounds = new Rect(0, 0, 0, textLine.Height);
 
                 return new List<TextLine> { textLine };
             }
@@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting
                 var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
                     _paragraphProperties, previousLine?.TextLineBreak);
 
-                if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
+                if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
                 {
-                    if(previousLine != null && previousLine.NewLineLength  > 0)
+                    if (previousLine != null && previousLine.NewLineLength > 0)
                     {
                         var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
 
@@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
                 }
 
                 _textSourceLength += textLine.Length;
-                
+
                 //Fulfill max height constraint
                 if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
                 {
@@ -485,12 +479,17 @@ namespace Avalonia.Media.TextFormatting
                 //Fulfill max lines constraint
                 if (MaxLines > 0 && textLines.Count >= MaxLines)
                 {
+                    if(textLine.TextLineBreak is TextLineBreak lineBreak && lineBreak.RemainingRuns != null)
+                    {
+                        textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
+                    }
+
                     break;
                 }
             }
 
             //Make sure the TextLayout always contains at least on empty line
-            if(textLines.Count == 0)
+            if (textLines.Count == 0)
             {
                 var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
 
@@ -501,7 +500,7 @@ namespace Avalonia.Media.TextFormatting
 
             Bounds = new Rect(left, 0, width, height);
 
-            if(_paragraphProperties.TextAlignment == TextAlignment.Justify)
+            if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
             {
                 var whitespaceWidth = 0d;
 
@@ -509,7 +508,7 @@ namespace Avalonia.Media.TextFormatting
                 {
                     var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
 
-                    if(lineWhitespaceWidth > whitespaceWidth)
+                    if (lineWhitespaceWidth > whitespaceWidth)
                     {
                         whitespaceWidth = lineWhitespaceWidth;
                     }
@@ -517,7 +516,7 @@ namespace Avalonia.Media.TextFormatting
 
                 var justificationWidth = width - whitespaceWidth;
 
-                if(justificationWidth > 0)
+                if (justificationWidth > 0)
                 {
                     var justificationProperties = new InterWordJustification(justificationWidth);
 

+ 227 - 206
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -166,58 +166,74 @@ namespace Avalonia.Media.TextFormatting
 
             if (distance <= 0)
             {
-                // hit happens before the line, return the first position
                 var firstRun = _textRuns[0];
 
-                if (firstRun is ShapedTextCharacters shapedTextCharacters)
-                {
-                    return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
-                }
+                return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
+            }
 
-                return _resolvedFlowDirection == FlowDirection.LeftToRight ?
-                    new CharacterHit(FirstTextSourceIndex) :
-                    new CharacterHit(FirstTextSourceIndex + Length);
+            if (distance > WidthIncludingTrailingWhitespace)
+            {
+                var lastRun = _textRuns[_textRuns.Count - 1];
+
+                return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width);
             }
 
             // process hit that happens within the line
             var characterHit = new CharacterHit();
             var currentPosition = FirstTextSourceIndex;
+            var currentDistance = 0.0;
 
             foreach (var currentRun in _textRuns)
             {
-                switch (currentRun)
+                if (currentDistance + currentRun.Size.Width < distance)
                 {
-                    case ShapedTextCharacters shapedRun:
-                        {
-                            characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
+                    currentDistance += currentRun.Size.Width;
+                    currentPosition += currentRun.TextSourceLength;
 
-                            var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
+                    continue;
+                }
 
-                            characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
+                characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
 
-                            break;
-                        }
-                    default:
+                break;
+            }
+
+            return characterHit;
+        }
+
+        private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance)
+        {
+            CharacterHit characterHit;
+
+            switch (run)
+            {
+                case ShapedTextCharacters shapedRun:
+                    {
+                        characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
+
+                        var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
+
+                        if (!shapedRun.GlyphRun.IsLeftToRight)
                         {
-                            if (distance < currentRun.Size.Width / 2)
-                            {
-                                characterHit = new CharacterHit(currentPosition);
-                            }
-                            else
-                            {
-                                characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength);
-                            }
-                            break;
+                            offset = Math.Max(0, offset - shapedRun.Text.End);
                         }
-                }
 
-                if (distance <= currentRun.Size.Width)
-                {
-                    break;
-                }
+                        characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
 
-                distance -= currentRun.Size.Width;
-                currentPosition += currentRun.TextSourceLength;
+                        break;
+                    }
+                default:
+                    {
+                        if (distance < run.Size.Width / 2)
+                        {
+                            characterHit = new CharacterHit(currentPosition);
+                        }
+                        else
+                        {
+                            characterHit = new CharacterHit(currentPosition, run.TextSourceLength);
+                        }
+                        break;
+                    }
             }
 
             return characterHit;
@@ -226,136 +242,122 @@ namespace Avalonia.Media.TextFormatting
         /// <inheritdoc/>
         public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
         {
-            var isTrailingHit = characterHit.TrailingLength > 0;
+            var flowDirection = _paragraphProperties.FlowDirection;
             var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
-            var currentDistance = Start;
             var currentPosition = FirstTextSourceIndex;
             var remainingLength = characterIndex - FirstTextSourceIndex;
 
-            GlyphRun? lastRun = null;
+            var currentDistance = Start;
 
-            for (var index = 0; index < _textRuns.Count; index++)
+            if (flowDirection == FlowDirection.LeftToRight)
+            {
+                for (var index = 0; index < _textRuns.Count; index++)
+                {
+                    var currentRun = _textRuns[index];
+
+                    if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
+                        flowDirection, out var distance, out _))
+                    {
+                        return currentDistance + distance;
+                    }
+
+                    //No hit hit found so we add the full width
+                    currentDistance += currentRun.Size.Width;
+                    currentPosition += currentRun.TextSourceLength;
+                    remainingLength -= currentRun.TextSourceLength;
+                }
+            }
+            else
             {
-                var textRun = _textRuns[index];
+                currentDistance += WidthIncludingTrailingWhitespace;
 
-                switch (textRun)
+                for (var index = _textRuns.Count - 1; index >= 0; index--)
                 {
-                    case ShapedTextCharacters shapedTextCharacters:
-                        {
-                            var currentRun = shapedTextCharacters.GlyphRun;
+                    var currentRun = _textRuns[index];
 
-                            if (lastRun != null)
-                            {
-                                if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight &&
-                                    currentRun.Characters.Start == characterHit.FirstCharacterIndex &&
-                                    characterHit.TrailingLength == 0)
-                                {
-                                    return currentDistance;
-                                }
-                            }
+                    if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
+                        flowDirection, out var distance, out var currentGlyphRun))
+                    {
+                        if (currentGlyphRun != null)
+                        {
+                            distance = currentGlyphRun.Size.Width - distance;
+                        }
 
-                            //Look for a hit in within the current run
-                            if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
-                            {
-                                characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
+                        return currentDistance - distance;
+                    }
 
-                                var distance = currentRun.GetDistanceFromCharacterHit(characterHit);
+                    //No hit hit found so we add the full width
+                    currentDistance -= currentRun.Size.Width;
+                    currentPosition += currentRun.TextSourceLength;
+                    remainingLength -= currentRun.TextSourceLength;
+                }
+            }
 
-                                return currentDistance + distance;
-                            }
+            return currentDistance;
+        }
 
-                            //Look at the left and right edge of the current run
-                            if (currentRun.IsLeftToRight)
-                            {
-                                if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
-                                {
-                                    if (characterIndex <= currentPosition)
-                                    {
-                                        return currentDistance;
-                                    }
-                                }
-                                else
-                                {
-                                    if (characterIndex == currentPosition)
-                                    {
-                                        return currentDistance;
-                                    }
-                                }
+        private static bool TryGetDistanceFromCharacterHit(
+            DrawableTextRun currentRun,
+            CharacterHit characterHit,
+            int currentPosition,
+            int remainingLength,
+            FlowDirection flowDirection,
+            out double distance,
+            out GlyphRun? currentGlyphRun)
+        {
+            var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+            var isTrailingHit = characterHit.TrailingLength > 0;
 
-                                if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
-                                {
-                                    return currentDistance + currentRun.Size.Width;
-                                }
-                            }
-                            else
-                            {
-                                if (characterIndex == currentPosition)
-                                {
-                                    return currentDistance + currentRun.Size.Width;
-                                }
+            distance = 0;
+            currentGlyphRun = null;
 
-                                var nextRun = index + 1 < _textRuns.Count ?
-                                    _textRuns[index + 1] as ShapedTextCharacters :
-                                    null;
+            switch (currentRun)
+            {
+                case ShapedTextCharacters shapedTextCharacters:
+                    {
+                        currentGlyphRun = shapedTextCharacters.GlyphRun;
 
-                                if (nextRun != null)
-                                {
-                                    if (nextRun.ShapedBuffer.IsLeftToRight)
-                                    {
-                                        if (characterIndex == currentPosition + textRun.Text.Length)
-                                        {
-                                            return currentDistance;
-                                        }
-                                    }
-                                    else
-                                    {
-                                        if (currentPosition + nextRun.Text.Length == characterIndex)
-                                        {
-                                            return currentDistance;
-                                        }
-                                    }
-                                }
-                                else
-                                {
-                                    if (characterIndex > currentPosition + textRun.Text.Length)
-                                    {
-                                        return currentDistance;
-                                    }
-                                }
-                            }
+                        if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length)
+                        {
+                            characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
 
-                            lastRun = currentRun;
+                            distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit);
 
-                            break;
+                            return true;
                         }
-                    default:
+
+                        if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit)
                         {
-                            if (characterIndex == currentPosition)
+                            if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
                             {
-                                return currentDistance;
+                                distance = currentGlyphRun.Size.Width;
                             }
 
-                            if (characterIndex == currentPosition + textRun.TextSourceLength)
-                            {
-                                return currentDistance + textRun.Size.Width;
-                            }
+                            return true;
+                        }
 
-                            break;
+                        break;
+                    }
+                default:
+                    {
+                        if (characterIndex == currentPosition)
+                        {
+                            return true;
                         }
-                }
 
-                //No hit hit found so we add the full width
-                currentDistance += textRun.Size.Width;
-                currentPosition += textRun.TextSourceLength;
-                remainingLength -= textRun.TextSourceLength;
+                        if (characterIndex == currentPosition + currentRun.TextSourceLength)
+                        {
+                            distance = currentRun.Size.Width;
 
-                if (remainingLength <= 0)
-                {
-                    break;
-                }
+                            return true;
+
+                        }
+
+                        break;
+                    }
             }
 
-            return currentDistance;
+            return false;
         }
 
         /// <inheritdoc/>
@@ -460,20 +462,33 @@ namespace Avalonia.Media.TextFormatting
 
                     var startIndex = currentRun.Text.Start + offset;
 
-                    var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                       currentShapedRun.ShapedBuffer.IsLeftToRight ?
-                            new CharacterHit(startIndex + remainingLength) :
-                            new CharacterHit(startIndex));
+                    double startOffset;
+                    double endOffset;
 
-                    endX += endOffset;
+                    if (currentShapedRun.ShapedBuffer.IsLeftToRight)
+                    {
+                        startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+
+                        endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+                    }
+                    else
+                    {
+                        endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
 
-                    var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
-                            new CharacterHit(startIndex) :
-                            new CharacterHit(startIndex + remainingLength));
+                        if (currentPosition < startIndex)
+                        {
+                            startOffset = endOffset;
+                        }
+                        else
+                        {
+                            startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+                        }
+                    }
 
                     startX += startOffset;
 
+                    endX += endOffset;
+
                     var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
                     var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
 
@@ -504,47 +519,40 @@ namespace Avalonia.Media.TextFormatting
                 }
 
                 //Lines that only contain a linebreak need to be covered here
-                if(characterLength == 0)
+                if (characterLength == 0)
                 {
                     characterLength = NewLineLength;
                 }
 
-                var runwidth = endX - startX;
-                var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
+                var runWidth = endX - startX;
+                var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
 
-                if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
                 {
-                    currentRect = currentRect.WithWidth(currentWidth + runwidth);
+                    if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                    {
+                        currentRect = currentRect.WithWidth(currentWidth + runWidth);
 
-                    var textBounds = result[result.Count - 1];
+                        var textBounds = result[result.Count - 1];
 
-                    textBounds.Rectangle = currentRect;
+                        textBounds.Rectangle = currentRect;
 
-                    textBounds.TextRunBounds.Add(currentRunBounds);
-                }
-                else
-                {
-                    currentRect = currentRunBounds.Rectangle;
+                        textBounds.TextRunBounds.Add(currentRunBounds);
+                    }
+                    else
+                    {
+                        currentRect = currentRunBounds.Rectangle;
 
-                    result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
+                        result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
+                    }
                 }
 
-                currentWidth += runwidth;
+                currentWidth += runWidth;
                 currentPosition += characterLength;
 
-                if (currentDirection == FlowDirection.LeftToRight)
-                {
-                    if (currentPosition > characterIndex)
-                    {
-                        break;
-                    }
-                }
-                else
+                if (currentPosition > characterIndex)
                 {
-                    if (currentPosition <= firstTextSourceIndex)
-                    {
-                        break;
-                    }
+                    break;
                 }
 
                 startX = endX;
@@ -571,7 +579,7 @@ namespace Avalonia.Media.TextFormatting
             var currentPosition = FirstTextSourceIndex;
             var remainingLength = textLength;
 
-            var startX = Start + WidthIncludingTrailingWhitespace;
+            var startX = WidthIncludingTrailingWhitespace;
             double currentWidth = 0;
             var currentRect = Rect.Empty;
 
@@ -582,7 +590,7 @@ namespace Avalonia.Media.TextFormatting
                     continue;
                 }
 
-                if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
+                if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex)
                 {
                     startX -= currentRun.Size.Width;
 
@@ -601,20 +609,31 @@ namespace Avalonia.Media.TextFormatting
                     currentPosition += offset;
 
                     var startIndex = currentRun.Text.Start + offset;
+                    double startOffset;
+                    double endOffset;
 
-                    var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
-                            new CharacterHit(startIndex + remainingLength) :
-                            new CharacterHit(startIndex));
+                    if (currentShapedRun.ShapedBuffer.IsLeftToRight)
+                    {
+                        if (currentPosition < startIndex)
+                        {
+                            startOffset = endOffset = 0;
+                        }
+                        else
+                        {
+                            endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
 
-                    endX += endOffset - currentShapedRun.Size.Width;
+                            startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+                        }
+                    }
+                    else
+                    {
+                        endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
 
-                    var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
-                            new CharacterHit(startIndex) :
-                            new CharacterHit(startIndex + remainingLength));
+                        startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+                    }
 
-                    startX += startOffset - currentShapedRun.Size.Width;
+                    startX -= currentRun.Size.Width - startOffset;
+                    endX -= currentRun.Size.Width - endOffset;
 
                     var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
                     var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@@ -652,41 +671,35 @@ namespace Avalonia.Media.TextFormatting
                 }
 
                 var runWidth = endX - startX;
-                var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
 
-                if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
+
+                if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
                 {
-                    currentRect = currentRect.WithWidth(currentWidth + runWidth);
+                    if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
+                    {
+                        currentRect = currentRect.WithWidth(currentWidth + runWidth);
 
-                    var textBounds = result[result.Count - 1];
+                        var textBounds = result[result.Count - 1];
 
-                    textBounds.Rectangle = currentRect;
+                        textBounds.Rectangle = currentRect;
 
-                    textBounds.TextRunBounds.Add(currentRunBounds);
-                }
-                else
-                {
-                    currentRect = currentRunBounds.Rectangle;
+                        textBounds.TextRunBounds.Add(currentRunBounds);
+                    }
+                    else
+                    {
+                        currentRect = currentRunBounds.Rectangle;
 
-                    result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
-                }
+                        result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
+                    }
+                }               
 
                 currentWidth += runWidth;
                 currentPosition += characterLength;
 
-                if (currentDirection == FlowDirection.LeftToRight)
-                {
-                    if (currentPosition > characterIndex)
-                    {
-                        break;
-                    }
-                }
-                else
+                if (currentPosition > characterIndex)
                 {
-                    if (currentPosition <= firstTextSourceIndex)
-                    {
-                        break;
-                    }
+                    break;
                 }
 
                 lastDirection = currentDirection;
@@ -698,6 +711,8 @@ namespace Avalonia.Media.TextFormatting
                 }
             }
 
+            result.Reverse();
+
             return result;
         }
 
@@ -1302,8 +1317,14 @@ namespace Avalonia.Media.TextFormatting
             switch (textAlignment)
             {
                 case TextAlignment.Center:
-                    return Math.Max(0, (_paragraphWidth - width) / 2);
+                    var start = (_paragraphWidth - width) / 2;
+
+                    if(paragraphFlowDirection == FlowDirection.RightToLeft)
+                    {
+                        start -= (widthIncludingTrailingWhitespace - width);
+                    }
 
+                    return Math.Max(0,  start);                         
                 case TextAlignment.Right:
                     return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);
 

+ 7 - 2
src/Avalonia.Controls/Documents/LineBreak.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
-using Avalonia.LogicalTree;
 using Avalonia.Media.TextFormatting;
 using Avalonia.Metadata;
 
@@ -22,7 +21,13 @@ namespace Avalonia.Controls.Documents
 
         internal override void BuildTextRun(IList<TextRun> textRuns)
         {
-            textRuns.Add(new TextEndOfLine());
+            var text = Environment.NewLine.AsMemory();
+
+            var textRunProperties = CreateTextRunProperties();
+
+            var textCharacters = new TextCharacters(text, textRunProperties);
+
+            textRuns.Add(textCharacters);
         }
 
         internal override void AppendText(StringBuilder stringBuilder)

+ 10 - 1
src/Avalonia.Controls/ProgressBar.cs

@@ -100,6 +100,7 @@ namespace Avalonia.Controls
         private double _indeterminateStartingOffset;
         private double _indeterminateEndingOffset;
         private Border? _indicator;
+        private IDisposable? _trackSizeChangedListener;
 
         public static readonly StyledProperty<bool> IsIndeterminateProperty =
             AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
@@ -195,8 +196,9 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
         {
+            var result = base.ArrangeOverride(finalSize);
             UpdateIndicator();
-            return base.ArrangeOverride(finalSize);
+            return result;
         }
 
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@@ -216,8 +218,15 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
+            // dispose any previous track size listener
+            _trackSizeChangedListener?.Dispose();
+            
             _indicator = e.NameScope.Get<Border>("PART_Indicator");
 
+            // listen to size changes of the indicators track (parent) and update the indicator there. 
+            _trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty)
+                .Subscribe(_ => UpdateIndicator());
+            
             UpdateIndicator();
         }
 

+ 85 - 34
src/Avalonia.Controls/RichTextBlock.cs

@@ -41,9 +41,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
             AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
 
-        public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
-            AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionForegroundBrush));
-
         /// <summary>
         /// Defines the <see cref="Inlines"/> property.
         /// </summary>
@@ -63,12 +60,13 @@ namespace Avalonia.Controls
         private bool _canCopy;
         private int _selectionStart;
         private int _selectionEnd;
+        private int _wordSelectionStart = -1;
 
         static RichTextBlock()
         {
             FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true);
 
-            AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty);
+            AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty);
         }
 
         public RichTextBlock()
@@ -89,15 +87,6 @@ namespace Avalonia.Controls
             set => SetValue(SelectionBrushProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets a value that defines the brush used for selected text.
-        /// </summary>
-        public IBrush? SelectionForegroundBrush
-        {
-            get => GetValue(SelectionForegroundBrushProperty);
-            set => SetValue(SelectionForegroundBrushProperty, value);
-        }
-
         /// <summary>
         /// Gets or sets a character index for the beginning of the current selection.
         /// </summary>
@@ -200,7 +189,7 @@ namespace Avalonia.Controls
             }
         }
 
-        public override void Render(DrawingContext context)
+        protected override void RenderTextLayout(DrawingContext context, Point origin)
         {
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
@@ -215,13 +204,16 @@ namespace Avalonia.Controls
 
                 var rects = TextLayout.HitTestTextRange(start, length);
 
-                foreach (var rect in rects)
+                using (context.PushPostTransform(Matrix.CreateTranslation(origin)))
                 {
-                    context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
+                    foreach (var rect in rects)
+                    {
+                        context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
+                    }
                 }
             }
 
-            base.Render(context);
+            base.RenderTextLayout(context, origin);
         }
 
         /// <summary>
@@ -297,8 +289,9 @@ namespace Avalonia.Controls
         /// <returns>A <see cref="TextLayout"/> object.</returns>
         protected override TextLayout CreateTextLayout(string? text)
         {
+            var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
             var defaultProperties = new GenericTextRunProperties(
-                new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
+                typeface,
                 FontSize,
                 TextDecorations,
                 Foreground);
@@ -345,6 +338,8 @@ namespace Avalonia.Controls
 
         protected override void OnKeyDown(KeyEventArgs e)
         {
+            base.OnKeyDown(e);
+
             var handled = false;
             var modifiers = e.KeyModifiers;
             var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
@@ -363,6 +358,8 @@ namespace Avalonia.Controls
 
         protected override void OnPointerPressed(PointerPressedEventArgs e)
         {
+            base.OnPointerPressed(e);
+
             if (!IsTextSelectionEnabled)
             {
                 return;
@@ -373,7 +370,9 @@ namespace Avalonia.Controls
 
             if (text != null && clickInfo.Properties.IsLeftButtonPressed)
             {
-                var point = e.GetPosition(this);
+                var padding = Padding;
+
+                var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
 
                 var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
 
@@ -382,8 +381,6 @@ namespace Avalonia.Controls
                 var hit = TextLayout.HitTestPoint(point);
                 var index = hit.TextPosition;
 
-                SelectionStart = SelectionEnd = index;
-
 #pragma warning disable CS0618 // Type or member is obsolete
                 switch (e.ClickCount)
 #pragma warning restore CS0618 // Type or member is obsolete
@@ -391,12 +388,34 @@ namespace Avalonia.Controls
                     case 1:
                         if (clickToSelect)
                         {
-                            SelectionStart = Math.Min(oldIndex, index);
-                            SelectionEnd = Math.Max(oldIndex, index);
+                            if (_wordSelectionStart >= 0)
+                            {
+                                var previousWord = StringUtils.PreviousWord(text, index);
+
+                                if (index > _wordSelectionStart)
+                                {
+                                    SelectionEnd = StringUtils.NextWord(text, index);
+                                }
+
+                                if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
+                                {
+                                    SelectionStart = previousWord;
+                                }
+                            }
+                            else
+                            {
+                                SelectionStart = Math.Min(oldIndex, index);
+                                SelectionEnd = Math.Max(oldIndex, index);
+                            }
                         }
                         else
                         {
-                            SelectionStart = SelectionEnd = index;
+                            if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
+                            {
+                                SelectionStart = SelectionEnd = index;
+
+                                _wordSelectionStart = -1;
+                            }
                         }
 
                         break;
@@ -406,9 +425,13 @@ namespace Avalonia.Controls
                             SelectionStart = StringUtils.PreviousWord(text, index);
                         }
 
+                        _wordSelectionStart = SelectionStart;
+
                         SelectionEnd = StringUtils.NextWord(text, index);
                         break;
                     case 3:
+                        _wordSelectionStart = -1;
+
                         SelectAll();
                         break;
                 }
@@ -420,6 +443,8 @@ namespace Avalonia.Controls
 
         protected override void OnPointerMoved(PointerEventArgs e)
         {
+            base.OnPointerMoved(e);
+
             if (!IsTextSelectionEnabled)
             {
                 return;
@@ -428,20 +453,49 @@ namespace Avalonia.Controls
             // selection should not change during pointer move if the user right clicks
             if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
             {
-                var point = e.GetPosition(this);
+                var text = Text;
+                var padding = Padding;
+
+                var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
 
                 point = new Point(
-                    MathUtilities.Clamp(point.X, 0, Math.Max(Bounds.Width - 1, 0)),
-                    MathUtilities.Clamp(point.Y, 0, Math.Max(Bounds.Height - 1, 0)));
+                    MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)),
+                    MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0)));
 
                 var hit = TextLayout.HitTestPoint(point);
+                var textPosition = hit.TextPosition;
+
+                if (text != null && _wordSelectionStart >= 0)
+                {
+                    var distance = textPosition - _wordSelectionStart;
+
+                    if (distance <= 0)
+                    {
+                        SelectionStart = StringUtils.PreviousWord(text, textPosition);
+                    }
+
+                    if (distance >= 0)
+                    {
+                        if (SelectionStart != _wordSelectionStart)
+                        {
+                            SelectionStart = _wordSelectionStart;
+                        }
+
+                        SelectionEnd = StringUtils.NextWord(text, textPosition);
+                    }
+                }
+                else
+                {
+                    SelectionEnd = textPosition;
+                }
 
-                SelectionEnd = hit.TextPosition;
             }
         }
 
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
+            base.OnPointerReleased(e);
+
             if (!IsTextSelectionEnabled)
             {
                 return;
@@ -454,7 +508,9 @@ namespace Avalonia.Controls
 
             if (e.InitialPressMouseButton == MouseButton.Right)
             {
-                var point = e.GetPosition(this);
+                var padding = Padding;
+
+                var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
 
                 var hit = TextLayout.HitTestPoint(point);
 
@@ -487,11 +543,6 @@ namespace Avalonia.Controls
                         InvalidateTextLayout();
                         break;
                     }
-                case nameof(TextProperty):
-                    {
-                        InvalidateTextLayout();
-                        break;
-                    }
             }
         }
 

+ 6 - 1
src/Avalonia.Controls/TextBlock.cs

@@ -505,7 +505,12 @@ namespace Avalonia.Controls
                 }
             }
 
-            TextLayout.Draw(context, new Point(padding.Left, top));
+            RenderTextLayout(context, new Point(padding.Left, top));
+        }
+
+        protected virtual void RenderTextLayout(DrawingContext context, Point origin)
+        {
+            TextLayout.Draw(context, origin);
         }
 
         void IAddChild<string>.AddChild(string text)

+ 169 - 116
src/Avalonia.Controls/TextBox.cs

@@ -53,7 +53,7 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<char> PasswordCharProperty =
             AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
-            
+
         public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
             AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush));
 
@@ -80,7 +80,7 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<int> MaxLinesProperty =
             AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
-        
+
         public static readonly DirectProperty<TextBox, string?> TextProperty =
             TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
                 o => o.Text,
@@ -105,7 +105,7 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             TextBlock.TextWrappingProperty.AddOwner<TextBox>();
-        
+
         /// <summary>
         /// Defines see <see cref="TextPresenter.LineHeight"/> property.
         /// </summary>
@@ -202,6 +202,7 @@ namespace Avalonia.Controls
         private string _newLine = Environment.NewLine;
         private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
 
+        private int _wordSelectionStart = -1;
         private int _selectedTextChangesMadeSinceLastUndoSnapshot;
         private bool _hasDoneSnapshotOnce;
         private const int _maxCharsBeforeUndoSnapshot = 7;
@@ -275,7 +276,7 @@ namespace Avalonia.Controls
             get => GetValue(IsReadOnlyProperty);
             set => SetValue(IsReadOnlyProperty, value);
         }
-        
+
         public char PasswordChar
         {
             get => GetValue(PasswordCharProperty);
@@ -307,7 +308,7 @@ namespace Avalonia.Controls
             {
                 value = CoerceCaretIndex(value);
                 var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
-                
+
                 if (changed)
                 {
                     UpdateCommandStates();
@@ -327,12 +328,12 @@ namespace Avalonia.Controls
             {
                 value = CoerceCaretIndex(value);
                 var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
-                
+
                 if (changed)
                 {
                     UpdateCommandStates();
                 }
-                
+
                 if (SelectionStart == value && CaretIndex != value)
                 {
                     CaretIndex = value;
@@ -351,7 +352,7 @@ namespace Avalonia.Controls
             get => GetValue(MaxLinesProperty);
             set => SetValue(MaxLinesProperty, value);
         }
-        
+
         /// <summary>
         /// Gets or sets the line height.
         /// </summary>
@@ -370,7 +371,7 @@ namespace Avalonia.Controls
                 var caretIndex = CaretIndex;
                 var selectionStart = SelectionStart;
                 var selectionEnd = SelectionEnd;
-                    
+
                 CaretIndex = CoerceCaretIndex(caretIndex, value);
                 SelectionStart = CoerceCaretIndex(selectionStart, value);
                 SelectionEnd = CoerceCaretIndex(selectionEnd, value);
@@ -567,7 +568,7 @@ namespace Avalonia.Controls
             _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
 
             _imClient.SetPresenter(_presenter, this);
-            
+
             if (IsFocused)
             {
                 _presenter?.ShowCaret();
@@ -577,7 +578,7 @@ namespace Avalonia.Controls
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTree(e);
-            
+
             if (IsFocused)
             {
                 _presenter?.ShowCaret();
@@ -587,7 +588,7 @@ namespace Avalonia.Controls
         protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnDetachedFromVisualTree(e);
-            
+
             _imClient.SetPresenter(null, null);
         }
 
@@ -637,7 +638,7 @@ namespace Avalonia.Controls
             }
 
             UpdateCommandStates();
-            
+
             _imClient.SetPresenter(_presenter, this);
 
             _presenter?.ShowCaret();
@@ -657,7 +658,7 @@ namespace Avalonia.Controls
             UpdateCommandStates();
 
             _presenter?.HideCaret();
-            
+
             _imClient.SetPresenter(null, null);
         }
 
@@ -700,14 +701,14 @@ namespace Avalonia.Controls
 
                     if (grapheme.FirstCodepoint.IsBreakChar)
                     {
-                        if(lineCount + 1 > MaxLines)
+                        if (lineCount + 1 > MaxLines)
                         {
                             break;
                         }
                         else
                         {
                             lineCount++;
-                        }                       
+                        }
                     }
 
                     length += grapheme.Text.Length;
@@ -736,7 +737,7 @@ namespace Avalonia.Controls
                 text = Text ?? string.Empty;
                 SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
                 ClearSelection();
-                    
+
                 if (IsUndoEnabled)
                 {
                     _undoRedoHelper.DiscardRedo();
@@ -746,7 +747,7 @@ namespace Avalonia.Controls
                 {
                     RaisePropertyChanged(TextProperty, oldText, _text);
                 }
-                    
+
                 CaretIndex = caretIndex + input.Length;
             }
         }
@@ -828,7 +829,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
             var movement = false;
@@ -985,87 +986,87 @@ namespace Avalonia.Controls
                         break;
 
                     case Key.Up:
-                    {
-                        selection = DetectSelection();
-                        
-                        _presenter.MoveCaretVertical(LogicalDirection.Backward);
-                        
-                        if (caretIndex != _presenter.CaretIndex)
                         {
-                            movement = true;
-                        }
+                            selection = DetectSelection();
 
-                        if (selection)
-                        {
-                            SelectionEnd = _presenter.CaretIndex;
-                        }
-                        else
-                        {
-                            CaretIndex = _presenter.CaretIndex;
+                            _presenter.MoveCaretVertical(LogicalDirection.Backward);
+
+                            if (caretIndex != _presenter.CaretIndex)
+                            {
+                                movement = true;
+                            }
+
+                            if (selection)
+                            {
+                                SelectionEnd = _presenter.CaretIndex;
+                            }
+                            else
+                            {
+                                CaretIndex = _presenter.CaretIndex;
+                            }
+
+                            break;
                         }
-                        
-                        break;
-                    }
                     case Key.Down:
-                    {
-                        selection = DetectSelection();
-                        
-                        _presenter.MoveCaretVertical();
-                        
-                        if (caretIndex != _presenter.CaretIndex)
-                        {
-                            movement = true;
-                        }
-  
-                        if (selection)
-                        {
-                            SelectionEnd = _presenter.CaretIndex;
-                        }
-                        else
                         {
-                            CaretIndex = _presenter.CaretIndex;
+                            selection = DetectSelection();
+
+                            _presenter.MoveCaretVertical();
+
+                            if (caretIndex != _presenter.CaretIndex)
+                            {
+                                movement = true;
+                            }
+
+                            if (selection)
+                            {
+                                SelectionEnd = _presenter.CaretIndex;
+                            }
+                            else
+                            {
+                                CaretIndex = _presenter.CaretIndex;
+                            }
+
+                            break;
                         }
-                        
-                        break;
-                    }
                     case Key.Back:
-                    {
-                        SnapshotUndoRedo();
-                        
-                        if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
                         {
-                            SetSelectionForControlBackspace();
-                        }
-
-                        if (!DeleteSelection())
-                        {
-                            var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
+                            SnapshotUndoRedo();
 
-                            var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+                            if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
+                            {
+                                SetSelectionForControlBackspace();
+                            }
 
-                            if (caretIndex != backspacePosition)
+                            if (!DeleteSelection())
                             {
-                                var start = Math.Min(backspacePosition, caretIndex);
-                                var end = Math.Max(backspacePosition, caretIndex);
+                                var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
 
-                                var length = end - start;
+                                var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
 
-                                var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
+                                if (caretIndex != backspacePosition)
+                                {
+                                    var start = Math.Min(backspacePosition, caretIndex);
+                                    var end = Math.Max(backspacePosition, caretIndex);
 
-                                SetTextInternal(editedText);
+                                    var length = end - start;
 
-                                CaretIndex = start;
-                            } 
-                        }
-                        
-                        SnapshotUndoRedo();
+                                    var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
 
-                        handled = true;
-                        break;
-                    }
+                                    SetTextInternal(editedText);
+
+                                    CaretIndex = start;
+                                }
+                            }
+
+                            SnapshotUndoRedo();
+
+                            handled = true;
+                            break;
+                        }
                     case Key.Delete:
                         SnapshotUndoRedo();
-                        
+
                         if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
                         {
                             SetSelectionForControlDelete();
@@ -1077,7 +1078,7 @@ namespace Avalonia.Controls
 
                             var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
 
-                            if(nextPosition != caretIndex)
+                            if (nextPosition != caretIndex)
                             {
                                 var start = Math.Min(nextPosition, caretIndex);
                                 var end = Math.Max(nextPosition, caretIndex);
@@ -1144,7 +1145,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             var text = Text;
             var clickInfo = e.GetCurrentPoint(this);
 
@@ -1170,24 +1171,50 @@ namespace Avalonia.Controls
                     case 1:
                         if (clickToSelect)
                         {
-                            SelectionStart = Math.Min(oldIndex, index);
-                            SelectionEnd = Math.Max(oldIndex, index);
+                            if (_wordSelectionStart >= 0)
+                            {
+                                var previousWord = StringUtils.PreviousWord(text, index);
+
+                                if (index > _wordSelectionStart)
+                                {
+                                    SelectionEnd = StringUtils.NextWord(text, index);
+                                }
+
+                                if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
+                                {
+                                    SelectionStart = previousWord;
+                                }
+                            }
+                            else
+                            {
+                                SelectionStart = Math.Min(oldIndex, index);
+                                SelectionEnd = Math.Max(oldIndex, index);
+                            }
                         }
                         else
                         {
-                            SelectionStart = SelectionEnd = index;
+                            if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
+                            {
+                                SelectionStart = SelectionEnd = index;
+                                _wordSelectionStart = -1;
+                            }                           
                         }
 
                         break;
-                    case 2:
+                    case 2:                       
+
                         if (!StringUtils.IsStartOfWord(text, index))
                         {
                             SelectionStart = StringUtils.PreviousWord(text, index);
                         }
 
+                        _wordSelectionStart = SelectionStart;
+
                         SelectionEnd = StringUtils.NextWord(text, index);
                         break;
                     case 3:
+                        _wordSelectionStart = -1;
+
                         SelectAll();
                         break;
                 }
@@ -1203,7 +1230,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             // selection should not change during pointer move if the user right clicks
             if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
             {
@@ -1215,7 +1242,32 @@ namespace Avalonia.Controls
 
                 _presenter.MoveCaretToPoint(point);
 
-                SelectionEnd = _presenter.CaretIndex;
+                var caretIndex = _presenter.CaretIndex;
+                var text = Text;
+
+                if (text != null && _wordSelectionStart >= 0)
+                {
+                    var distance = caretIndex - _wordSelectionStart;                 
+
+                    if (distance <= 0)
+                    {
+                        SelectionStart = StringUtils.PreviousWord(text, caretIndex);
+                    }
+
+                    if (distance >= 0)
+                    {
+                        if(SelectionStart != _wordSelectionStart)
+                        {
+                            SelectionStart = _wordSelectionStart;
+                        }
+
+                        SelectionEnd = StringUtils.NextWord(text, caretIndex);
+                    }
+                }
+                else
+                {
+                    SelectionEnd = _presenter.CaretIndex;
+                }
             }
         }
 
@@ -1234,9 +1286,9 @@ namespace Avalonia.Controls
             if (e.InitialPressMouseButton == MouseButton.Right)
             {
                 var point = e.GetPosition(_presenter);
-                    
+
                 _presenter.MoveCaretToPoint(point);
-                    
+
                 var caretIndex = _presenter.CaretIndex;
 
                 // see if mouse clicked inside current selection
@@ -1250,7 +1302,7 @@ namespace Avalonia.Controls
                     CaretIndex = SelectionEnd = SelectionStart = caretIndex;
                 }
             }
-            
+
             e.Pointer.Capture(null);
         }
 
@@ -1309,7 +1361,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             var text = Text ?? string.Empty;
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
@@ -1319,11 +1371,11 @@ namespace Avalonia.Controls
                 if (isSelecting)
                 {
                     _presenter.MoveCaretToTextPosition(selectionEnd);
-                    
+
                     _presenter.MoveCaretHorizontal(direction > 0 ?
                         LogicalDirection.Forward :
                         LogicalDirection.Backward);
-                    
+
                     SelectionEnd = _presenter.CaretIndex;
                 }
                 else
@@ -1347,7 +1399,7 @@ namespace Avalonia.Controls
             else
             {
                 int offset;
-                
+
                 if (direction > 0)
                 {
                     offset = StringUtils.NextWord(text, selectionEnd) - selectionEnd;
@@ -1356,7 +1408,7 @@ namespace Avalonia.Controls
                 {
                     offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
                 }
-                
+
                 SelectionEnd += offset;
 
                 _presenter.MoveCaretToTextPosition(SelectionEnd);
@@ -1378,7 +1430,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             var caretIndex = CaretIndex;
 
             if (document)
@@ -1401,7 +1453,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
@@ -1432,8 +1484,9 @@ namespace Avalonia.Controls
 
         private bool DeleteSelection(bool raiseTextChanged = true)
         {
-            if (IsReadOnly) return true;
-            
+            if (IsReadOnly)
+                return true;
+
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
 
@@ -1444,40 +1497,40 @@ namespace Avalonia.Controls
                 var text = Text!;
 
                 SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
-                    
+
                 _presenter?.MoveCaretToTextPosition(start);
-                    
-                CaretIndex= start;
-                    
+
+                CaretIndex = start;
+
                 ClearSelection();
-                    
+
                 return true;
             }
-                
+
             CaretIndex = SelectionStart;
-            
+
             return false;
         }
 
         private string GetSelection()
         {
             var text = Text;
-            
+
             if (string.IsNullOrEmpty(text))
             {
                 return "";
             }
-               
+
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
             var start = Math.Min(selectionStart, selectionEnd);
             var end = Math.Max(selectionStart, selectionEnd);
-            
+
             if (start == end || (Text?.Length ?? 0) < end)
             {
                 return "";
             }
-            
+
             return text.Substring(start, end - start);
         }
 
@@ -1496,7 +1549,7 @@ namespace Avalonia.Controls
         private void SetSelectionForControlBackspace()
         {
             var selectionStart = CaretIndex;
-            
+
             MoveHorizontal(-1, true, false);
 
             SelectionStart = selectionStart;
@@ -1508,9 +1561,9 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             SelectionStart = CaretIndex;
-            
+
             MoveHorizontal(1, true, true);
 
             if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')

+ 2 - 2
src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs

@@ -21,7 +21,7 @@ namespace Avalonia.OpenGL.Angle
             var display = IntPtr.Zero;
             AngleOptions.PlatformApi angleApi = default;
             {
-                if (_egl.GetPlatformDisplayEXT == null)
+                if (!_egl.IsGetPlatformDisplayExtAvailable)
                     throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
 
                 var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
@@ -37,7 +37,7 @@ namespace Avalonia.OpenGL.Angle
                     else
                         continue;
 
-                    display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
+                    display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
                         new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
                     if (display != IntPtr.Zero)
                     {

+ 1 - 0
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@@ -11,4 +11,5 @@
     </ItemGroup>
 
     <Import Project="..\..\build\DevAnalyzers.props" />
+    <Import Project="..\..\build\SourceGenerators.props" />
 </Project>

+ 5 - 9
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@@ -66,11 +66,9 @@ namespace Avalonia.OpenGL.Controls
                 return;
                     
             gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
-            if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
+            if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
 
-            var oneArr = new int[1];
-            gl.GenRenderbuffers(1, oneArr);
-            _depthBuffer = oneArr[0];
+            _depthBuffer = gl.GenRenderbuffer();
             gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
             gl.RenderbufferStorage(GL_RENDERBUFFER,
                 GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
@@ -88,9 +86,9 @@ namespace Avalonia.OpenGL.Controls
                     var gl = _context.GlInterface;
                     gl.BindTexture(GL_TEXTURE_2D, 0);
                     gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
-                    gl.DeleteFramebuffers(1, new[] { _fb });
+                    gl.DeleteFramebuffer(_fb);
                     _fb = 0;
-                    gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
+                    gl.DeleteRenderbuffer(_depthBuffer);
                     _depthBuffer = 0;
                     _attachment?.Dispose();
                     _attachment = null;
@@ -174,9 +172,7 @@ namespace Avalonia.OpenGL.Controls
                 {
                     _depthBufferSize = GetPixelSize();
                     var gl = _context.GlInterface;
-                    var oneArr = new int[1];
-                    gl.GenFramebuffers(1, oneArr);
-                    _fb = oneArr[0];
+                    _fb = gl.GenFramebuffer();
                     gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
                     
                     EnsureDepthBufferAttachment(gl);

+ 1 - 1
src/Avalonia.OpenGL/Egl/EglContext.cs

@@ -24,7 +24,7 @@ namespace Avalonia.OpenGL.Egl
             SampleCount = sampleCount;
             StencilSize = stencilSize;
             using (MakeCurrent())
-                GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, b => _egl.GetProcAddress(b));
+                GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
         }
 
         public IntPtr Context { get; }

+ 2 - 2
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@@ -34,9 +34,9 @@ namespace Avalonia.OpenGL.Egl
             }
             else
             {
-                if (egl.GetPlatformDisplayEXT == null)
+                if (!egl.IsGetPlatformDisplayExtAvailable)
                     throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
-                display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs);
+                display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs);
             }
             
             if (display == IntPtr.Zero)

+ 83 - 142
src/Avalonia.OpenGL/Egl/EglInterface.cs

@@ -2,31 +2,26 @@
 using System.Runtime.InteropServices;
 using Avalonia.Platform;
 using Avalonia.Platform.Interop;
+using Avalonia.SourceGenerator;
 
 namespace Avalonia.OpenGL.Egl
 {
-    public class EglInterface : GlInterfaceBase
+    public unsafe partial class EglInterface
     {
-        public EglInterface() : base(Load())
+        public EglInterface(Func<string, IntPtr> getProcAddress)
         {
-            
-        }
-
-        public EglInterface(Func<Utf8Buffer,IntPtr> getProcAddress) : base(getProcAddress)
-        {
-            
+            Initialize(getProcAddress);
         }
         
-        public EglInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress)
+        public EglInterface(string library) : this(Load(library))
         {
-            
         }
-        
-        public EglInterface(string library) : base(Load(library))
+
+        public EglInterface() : this(Load())
         {
+            
         }
 
-        
         static Func<string, IntPtr> Load()
         {
             var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
@@ -46,119 +41,75 @@ namespace Avalonia.OpenGL.Egl
         }
 
         // ReSharper disable UnassignedGetOnlyAutoProperty
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int EglGetError();
-        [GlEntryPoint("eglGetError")]
-        public EglGetError GetError { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
-        [GlEntryPoint("eglGetDisplay")]
-        public EglGetDisplay GetDisplay { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
-        [GlEntryPoint("eglGetPlatformDisplayEXT")]
-        [GlOptionalEntryPoint]
-        public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
-        [GlEntryPoint("eglInitialize")]
-        public EglInitialize Initialize { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
-        [GlEntryPoint("eglGetProcAddress")]
-        public EglGetProcAddress GetProcAddress { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglBindApi(int api);
-        [GlEntryPoint("eglBindAPI")]
-        public EglBindApi BindApi { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
-            out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
-        [GlEntryPoint("eglChooseConfig")]
-        public EglChooseConfig ChooseConfig { get; }
+        
+        [GetProcAddress("eglGetError")]
+        public partial int GetError();
+        
+        [GetProcAddress("eglGetDisplay")]
+        public partial IntPtr GetDisplay(IntPtr nativeDisplay);
+        
+        [GetProcAddress("eglGetPlatformDisplayEXT", true)]
+        public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs);
+
+        [GetProcAddress("eglInitialize")]        
+        public partial bool Initialize(IntPtr display, out int major, out int minor);
+
+        [GetProcAddress("eglGetProcAddress")]        
+        public partial IntPtr GetProcAddress(IntPtr proc);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config,
+        [GetProcAddress("eglBindAPI")]
+        public partial bool BindApi(int api);
+
+        [GetProcAddress("eglChooseConfig")]
+        public partial bool ChooseConfig(IntPtr display, int[] attribs,
+            out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
+        
+        [GetProcAddress("eglCreateContext")]
+        public partial IntPtr CreateContext(IntPtr display, IntPtr config,
             IntPtr share, int[] attrs);
-        [GlEntryPoint("eglCreateContext")]
-        public EglCreateContext CreateContext { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglDestroyContext(IntPtr display, IntPtr context);
-        [GlEntryPoint("eglDestroyContext")]
-        public EglDestroyContext DestroyContext { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
-        [GlEntryPoint("eglCreatePbufferSurface")]
-        public EglCreatePBufferSurface CreatePBufferSurface { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
-        [GlEntryPoint("eglMakeCurrent")]
-        public EglMakeCurrent MakeCurrent { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetCurrentContext();
-        [GlEntryPoint("eglGetCurrentContext")]
-        public EglGetCurrentContext GetCurrentContext { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetCurrentDisplay();
-        [GlEntryPoint("eglGetCurrentDisplay")]
-        public EglGetCurrentContext GetCurrentDisplay { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglGetCurrentSurface(int readDraw);
-        [GlEntryPoint("eglGetCurrentSurface")] 
-        public EglGetCurrentSurface GetCurrentSurface { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
-        [GlEntryPoint("eglDestroySurface")]
-        public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
-        
-        [GlEntryPoint("eglSwapBuffers")]
-        public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr
-            EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
-        [GlEntryPoint("eglCreateWindowSurface")]
-        public EglCreateWindowSurface CreateWindowSurface { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
-        [GlEntryPoint("eglGetConfigAttrib")]
-        public EglGetConfigAttrib GetConfigAttrib { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglWaitGL();
-        [GlEntryPoint("eglWaitGL")]
-        public EglWaitGL WaitGL { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglWaitClient();
-        [GlEntryPoint("eglWaitClient")]
-        public EglWaitGL WaitClient { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglWaitNative(int engine);
-        [GlEntryPoint("eglWaitNative")]
-        public EglWaitNative WaitNative { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglQueryString(IntPtr display, int i);
-        
-        [GlEntryPoint("eglQueryString")]
-        public EglQueryString QueryStringNative { get; }
+        
+        [GetProcAddress("eglDestroyContext")]
+        public partial bool DestroyContext(IntPtr display, IntPtr context);
+        
+        [GetProcAddress("eglCreatePbufferSurface")]
+        public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
 
+        [GetProcAddress("eglMakeCurrent")]
+        public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
+        
+        [GetProcAddress("eglGetCurrentContext")]
+        public partial IntPtr GetCurrentContext();
+
+        [GetProcAddress("eglGetCurrentDisplay")]
+        public partial IntPtr GetCurrentDisplay();
+        
+        [GetProcAddress("eglGetCurrentSurface")] 
+        public partial IntPtr GetCurrentSurface(int readDraw);
+
+        [GetProcAddress("eglDestroySurface")]
+        public partial void DestroySurface(IntPtr display, IntPtr surface);
+
+        [GetProcAddress("eglSwapBuffers")]
+        public partial void SwapBuffers(IntPtr display, IntPtr surface);
+
+        [GetProcAddress("eglCreateWindowSurface")]
+        public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
+
+        [GetProcAddress("eglGetConfigAttrib")]
+        public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
+        
+        [GetProcAddress("eglWaitGL")]
+        public partial bool WaitGL();
+        
+        [GetProcAddress("eglWaitClient")]
+        public partial bool WaitClient();
+        
+        [GetProcAddress("eglWaitNative")]
+        public partial bool WaitNative(int engine);
+        
+        [GetProcAddress("eglQueryString")]
+        public partial IntPtr QueryStringNative(IntPtr display, int i);
+        
         public string QueryString(IntPtr display, int i)
         {
             var rv = QueryStringNative(display, i);
@@ -166,25 +117,15 @@ namespace Avalonia.OpenGL.Egl
                 return null;
             return Marshal.PtrToStringAnsi(rv);
         }
+        
+        [GetProcAddress("eglCreatePbufferFromClientBuffer")]
+        public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
+        
+        [GetProcAddress("eglQueryDisplayAttribEXT", true)]
+        public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
-        [GlEntryPoint("eglCreatePbufferFromClientBuffer")]
-
-        public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res);
-
-        [GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint]
-        public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res);
-
-        [GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint]
-        public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; }
-
-        // ReSharper restore UnassignedGetOnlyAutoProperty
+        
+        [GetProcAddress("eglQueryDeviceAttribEXT", true)]
+        public partial bool QueryDeviceAttribExt(IntPtr display, int attr, out IntPtr res);
     }
 }

+ 11 - 33
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@@ -3,46 +3,24 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.InteropServices;
 using Avalonia.Platform.Interop;
+using Avalonia.SourceGenerator;
 
 namespace Avalonia.OpenGL
 {
-    public class GlBasicInfoInterface : GlBasicInfoInterface<object>
+    public unsafe partial class GlBasicInfoInterface
     {
-        public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null)
-        {
-        }
-
-        public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
-        {
+        public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress){
+            Initialize(getProcAddress);
         }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetIntegerv(int name, out int rv);
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr GlGetString(int v);
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr GlGetStringi(int v, int v1);
-    }
     
-    public class GlBasicInfoInterface<TContextInfo> : GlInterfaceBase<TContextInfo>
-    {
-        public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress, TContextInfo context) : base(getProcAddress, context)
-        {
-        }
+        [GetProcAddress("glGetIntegerv")]
+        public partial void GetIntegerv(int name, out int rv);
 
-        public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContextInfo context) : base(nativeGetProcAddress, context)
-        {
-        }
-        
-        [GlEntryPoint("glGetIntegerv")]
-        public GlBasicInfoInterface.GlGetIntegerv GetIntegerv { get; }
-        
-        
-        [GlEntryPoint("glGetString")]
-        public GlBasicInfoInterface.GlGetString GetStringNative { get; }
-        
-        [GlEntryPoint("glGetStringi")]
-        public GlBasicInfoInterface.GlGetStringi GetStringiNative { get; }
+        [GetProcAddress("glGetString")]
+        public partial IntPtr GetStringNative(int v);
+
+        [GetProcAddress("glGetStringi")]
+        public partial IntPtr GetStringiNative(int v, int v1);
 
         public string GetString(int v)
         {

+ 20 - 83
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@@ -2,117 +2,54 @@ using System;
 
 namespace Avalonia.OpenGL
 {
-    public interface IGlEntryPointAttribute
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+    class GlMinVersionEntryPoint : Attribute
     {
-        IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress);
-    }
-    
-    public interface IGlEntryPointAttribute<in TContext>
-    {
-        IntPtr GetProcAddress(TContext context, Func<string, IntPtr> getProcAddress);
-    }
-
-    [AttributeUsage(AttributeTargets.Property)]
-    public class GlOptionalEntryPoint : Attribute
-    {
-        
-    }
-    
-    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
-    public class GlEntryPointAttribute : Attribute, IGlEntryPointAttribute
-    {
-        public string[] EntryPoints { get; }
-
-        public GlEntryPointAttribute(string entryPoint)
-        {
-            EntryPoints = new []{entryPoint};
-        }
-/*
-        public GlEntryPointAttribute(params string[] entryPoints)
+        public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor)
         {
-            EntryPoints = entryPoints;
-        }
-*/
-        public IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress)
-        {
-            foreach (var name in EntryPoints)
-            {
-                var rv = getProcAddress(name);
-                if (rv != IntPtr.Zero)
-                    return rv;
-            }
-            return IntPtr.Zero;
-        }
-    }
-    
-    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
-    public class GlMinVersionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
-    {
-        private readonly string _entry;
-        private readonly GlProfileType? _profile;
-        private readonly int _minVersionMajor;
-        private readonly int _minVersionMinor;
-
-        public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor,
-            int minVersionMinor)
-        {
-            _entry = entry;
-            _profile = profile;
-            _minVersionMajor = minVersionMajor;
-            _minVersionMinor = minVersionMinor;
         }
         
-        public GlMinVersionEntryPoint(string entry, int minVersionMajor,
-            int minVersionMinor)
+        public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor, GlProfileType profile)
         {
-            _entry = entry;
-            _minVersionMajor = minVersionMajor;
-            _minVersionMinor = minVersionMinor;
         }
+
         
-        public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
+        public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
+            string entry, int minVersionMajor, int minVersionMinor, GlProfileType? profile = null)
         {
-            if(_profile.HasValue && context.Version.Type != _profile)
+            if(profile.HasValue && context.Version.Type != profile)
                 return IntPtr.Zero;
-            if(context.Version.Major<_minVersionMajor)
+            if(context.Version.Major<minVersionMajor)
                 return IntPtr.Zero;
-            if (context.Version.Major == _minVersionMajor && context.Version.Minor < _minVersionMinor)
+            if (context.Version.Major == minVersionMajor && context.Version.Minor < minVersionMinor)
                 return IntPtr.Zero;
-            return getProcAddress(_entry);
+            return getProcAddress(entry);
         }
     }
     
-    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
-    public class GlExtensionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+    class GlExtensionEntryPoint : Attribute
     {
-        private readonly string _entry;
-        private readonly GlProfileType? _profile;
-        private readonly string _extension;
-
-        public GlExtensionEntryPoint(string entry, GlProfileType profile, string extension)
+        public GlExtensionEntryPoint(string entry, string extension)
         {
-            _entry = entry;
-            _profile = profile;
-            _extension = extension;
         }
         
-        public GlExtensionEntryPoint(string entry, string extension)
+        public GlExtensionEntryPoint(string entry, string extension, GlProfileType profile)
         {
-            _entry = entry;
-            _extension = extension;
         }
         
-        public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
+        public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
+            string entry, string extension, GlProfileType? profile = null)
         {
             // Ignore different profile type
-            if (_profile.HasValue && _profile != context.Version.Type)
+            if (profile.HasValue && profile != context.Version.Type)
                 return IntPtr.Zero;
 
             // Check if extension is supported by the current context
-            if (!context.Extensions.Contains(_extension))
+            if (!context.Extensions.Contains(extension))
                 return IntPtr.Zero;
 
-            return getProcAddress(_entry);
+            return getProcAddress(entry);
         }
     }
 }

+ 219 - 275
src/Avalonia.OpenGL/GlInterface.cs

@@ -3,14 +3,14 @@ using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Text;
 using Avalonia.Platform.Interop;
+using Avalonia.SourceGenerator;
 using static Avalonia.OpenGL.GlConsts;
 
 namespace Avalonia.OpenGL
 {
-    public delegate IntPtr GlGetProcAddressDelegate(string procName);
-    
-    public unsafe class GlInterface : GlBasicInfoInterface<GlInterface.GlContextInfo>
+    public unsafe partial class GlInterface : GlBasicInfoInterface
     {
+        private readonly Func<string, IntPtr> _getProcAddress;
         public string Version { get; }
         public string Vendor { get; }
         public string Renderer { get; }
@@ -35,12 +35,14 @@ namespace Avalonia.OpenGL
             }
         }
 
-        private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress, info)
+        private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress)
         {
+            _getProcAddress = getProcAddress;
             ContextInfo = info;
             Version = GetString(GlConsts.GL_VERSION);
             Renderer = GetString(GlConsts.GL_RENDERER);
-            Vendor = GetString(GlConsts.GL_VENDOR);   
+            Vendor = GetString(GlConsts.GL_VENDOR);
+            Initialize(getProcAddress, ContextInfo);
         }
 
         public GlInterface(GlVersion version, Func<string, IntPtr> getProcAddress) : this(
@@ -48,92 +50,58 @@ namespace Avalonia.OpenGL
         {
         }
 
-        public GlInterface(GlVersion version, Func<Utf8Buffer, IntPtr> n) : this(version, ConvertNative(n))
+        public IntPtr GetProcAddress(string proc) => _getProcAddress(proc);
+
+        [GetProcAddress("glGetError")]
+        public partial int GetError();
+
+        [GetProcAddress("glClearStencil")]
+        public partial void ClearStencil(int s);
+
+        [GetProcAddress("glClearColor")]
+        public partial void ClearColor(float r, float g, float b, float a);
+
+        [GetProcAddress("glClear")]
+        public partial void Clear(int bits);
+
+        [GetProcAddress("glViewport")]
+        public partial void Viewport(int x, int y, int width, int height);
+
+        [GetProcAddress("glFlush")]
+        public partial void Flush();
+
+        [GetProcAddress("glFinish")]
+        public partial void Finish();
+
+        [GetProcAddress("glGetIntegerv")]
+        public partial void GetIntegerv(int name, out int rv);
+
+        [GetProcAddress("glGenFramebuffers")]
+        public partial void GenFramebuffers(int count, int* res);
+
+        public int GenFramebuffer()
         {
-            
+            int rv = 0;
+            GenFramebuffers(1, &rv);
+            return rv;
         }
 
-        public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func<Utf8Buffer, IntPtr> getProcAddress) =>
-            new GlInterface(version, getProcAddress);
-
-        
-        public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
-
-        // ReSharper disable UnassignedGetOnlyAutoProperty
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlGetError();
-        [GlEntryPoint("glGetError")]
-        public GlGetError GetError { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlClearStencil(int s);
-        [GlEntryPoint("glClearStencil")]
-        public GlClearStencil ClearStencil { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlClearColor(float r, float g, float b, float a);
-        [GlEntryPoint("glClearColor")]
-        public GlClearColor ClearColor { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlClear(int bits);
-        [GlEntryPoint("glClear")]
-        public GlClear Clear { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlViewport(int x, int y, int width, int height);
-        [GlEntryPoint("glViewport")]
-        public GlViewport Viewport { get; }
-        
-        [GlEntryPoint("glFlush")]
-        public UnmanagedAction Flush { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void UnmanagedAction();
-        
-        [GlEntryPoint("glFinish")]
-        public UnmanagedAction Finish { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate IntPtr GlGetString(int v);
-        [GlEntryPoint("glGetString")]
-        public GlGetString GetStringNative { get; }
-
-        public string GetString(int v)
+        [GetProcAddress("glDeleteFramebuffers")]
+        public partial void DeleteFramebuffers(int count, int* framebuffers);
+
+        public void DeleteFramebuffer(int fb)
         {
-            var ptr = GetStringNative(v);
-            if (ptr != IntPtr.Zero)
-                return Marshal.PtrToStringAnsi(ptr);
-            return null;
+            DeleteFramebuffers(1, &fb);
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetIntegerv(int name, out int rv);
-        [GlEntryPoint("glGetIntegerv")]
-        public GlGetIntegerv GetIntegerv { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGenFramebuffers(int count, int[] res);
-        [GlEntryPoint("glGenFramebuffers")]
-        public GlGenFramebuffers GenFramebuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteFramebuffers(int count, int[] framebuffers);
-        [GlEntryPoint("glDeleteFramebuffers")]
-        public GlDeleteFramebuffers DeleteFramebuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBindFramebuffer(int target, int fb);
-        [GlEntryPoint("glBindFramebuffer")]
-        public GlBindFramebuffer BindFramebuffer { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlCheckFramebufferStatus(int target);
-        [GlEntryPoint("glCheckFramebufferStatus")]
-        public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBlitFramebuffer(int srcX0,
+        [GetProcAddress("glBindFramebuffer")]
+        public partial void BindFramebuffer(int target, int fb);
+
+        [GetProcAddress("glCheckFramebufferStatus")]
+        public partial int CheckFramebufferStatus(int target);
+
+        [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GetProcAddress(true)]
+        public partial void BlitFramebuffer(int srcX0,
             int srcY0,
             int srcX1,
             int srcY1,
@@ -143,89 +111,78 @@ namespace Avalonia.OpenGL
             int dstY1,
             int mask,
             int filter);
-        [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint]
-        public GlBlitFramebuffer BlitFramebuffer { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGenRenderbuffers(int count, int[] res);
-        [GlEntryPoint("glGenRenderbuffers")]
-        public GlGenRenderbuffers GenRenderbuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteRenderbuffers(int count, int[] renderbuffers);
-        [GlEntryPoint("glDeleteRenderbuffers")]
-        public GlDeleteTextures DeleteRenderbuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBindRenderbuffer(int target, int fb);
-        [GlEntryPoint("glBindRenderbuffer")]
-        public GlBindRenderbuffer BindRenderbuffer { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlRenderbufferStorage(int target, int internalFormat, int width, int height);
-        [GlEntryPoint("glRenderbufferStorage")]
-        public GlRenderbufferStorage RenderbufferStorage { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlFramebufferRenderbuffer(int target, int attachment,
+
+
+        [GetProcAddress("glGenRenderbuffers")]
+        public partial void GenRenderbuffers(int count, int* res);
+
+        public int GenRenderbuffer()
+        {
+            int rv = 0;
+            GenRenderbuffers(1, &rv);
+            return rv;
+        }
+
+        [GetProcAddress("glDeleteRenderbuffers")]
+        public partial void DeleteRenderbuffers(int count, int* renderbuffers);
+
+        public void DeleteRenderbuffer(int renderbuffer)
+        {
+            DeleteRenderbuffers(1, &renderbuffer);
+        }
+
+        [GetProcAddress("glBindRenderbuffer")]
+        public partial void BindRenderbuffer(int target, int fb);
+
+        [GetProcAddress("glRenderbufferStorage")]
+        public partial void RenderbufferStorage(int target, int internalFormat, int width, int height);
+
+        [GetProcAddress("glFramebufferRenderbuffer")]
+        public partial void FramebufferRenderbuffer(int target, int attachment,
             int renderbufferTarget, int renderbuffer);
-        [GlEntryPoint("glFramebufferRenderbuffer")]
-        public GlFramebufferRenderbuffer FramebufferRenderbuffer { get; }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGenTextures(int count, int[] res);
-        [GlEntryPoint("glGenTextures")]
-        public GlGenTextures GenTextures { get; }
+        [GetProcAddress("glGenTextures")]
+        public partial void GenTextures(int count, int* res);
+
+        public int GenTexture()
+        {
+            int rv = 0;
+            GenTextures(1, &rv);
+            return rv;
+        }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBindTexture(int target, int fb);
-        [GlEntryPoint("glBindTexture")]
-        public GlBindTexture BindTexture { get; }
+        [GetProcAddress("glBindTexture")]
+        public partial void BindTexture(int target, int fb);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlActiveTexture(int texture);
-        [GlEntryPoint("glActiveTexture")]
-        public GlActiveTexture ActiveTexture { get; }
+        [GetProcAddress("glActiveTexture")]
+        public partial void ActiveTexture(int texture);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteTextures(int count, int[] textures);
-        [GlEntryPoint("glDeleteTextures")]
-        public GlDeleteTextures DeleteTextures { get; }
+        [GetProcAddress("glDeleteTextures")]
+        public partial void DeleteTextures(int count, int* textures);
 
+        public void DeleteTexture(int texture) => DeleteTextures(1, &texture);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border,
+        [GetProcAddress("glTexImage2D")]
+        public partial void TexImage2D(int target, int level, int internalFormat, int width, int height, int border,
             int format, int type, IntPtr data);
-        [GlEntryPoint("glTexImage2D")]
-        public GlTexImage2D TexImage2D { get; }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y,
+        [GetProcAddress("glCopyTexSubImage2D")]
+        public partial void CopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y,
             int width, int height);
-        
-        [GlEntryPoint("glCopyTexSubImage2D")]
-        public GlCopyTexSubImage2D CopyTexSubImage2D { get; }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlTexParameteri(int target, int name, int value);
-        [GlEntryPoint("glTexParameteri")]
-        public GlTexParameteri TexParameteri { get; }
+        [GetProcAddress("glTexParameteri")]
+        public partial void TexParameteri(int target, int name, int value);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlFramebufferTexture2D(int target, int attachment,
+
+        [GetProcAddress("glFramebufferTexture2D")]
+        public partial void FramebufferTexture2D(int target, int attachment,
             int texTarget, int texture, int level);
-        [GlEntryPoint("glFramebufferTexture2D")]
-        public GlFramebufferTexture2D FramebufferTexture2D { get; }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlCreateShader(int shaderType);
-        [GlEntryPoint("glCreateShader")]
-        public GlCreateShader CreateShader { get; }
+        [GetProcAddress("glCreateShader")]
+        public partial int CreateShader(int shaderType);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlShaderSource(int shader, int count, IntPtr strings, IntPtr lengths);
-        [GlEntryPoint("glShaderSource")]
-        public GlShaderSource ShaderSource { get; }
+        [GetProcAddress("glShaderSource")]
+        public partial void ShaderSource(int shader, int count, IntPtr strings, IntPtr lengths);
 
         public void ShaderSourceString(int shader, string source)
         {
@@ -237,20 +194,14 @@ namespace Avalonia.OpenGL
             }
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlCompileShader(int shader);
-        [GlEntryPoint("glCompileShader")]
-        public GlCompileShader CompileShader { get; }
+        [GetProcAddress("glCompileShader")]
+        public partial void CompileShader(int shader);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetShaderiv(int shader, int name, int* parameters);
-        [GlEntryPoint("glGetShaderiv")]
-        public GlGetShaderiv GetShaderiv { get; }
+        [GetProcAddress("glGetShaderiv")]
+        public partial void GetShaderiv(int shader, int name, int* parameters);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetShaderInfoLog(int shader, int maxLength, out int length, void*infoLog);
-        [GlEntryPoint("glGetShaderInfoLog")]
-        public GlGetShaderInfoLog GetShaderInfoLog { get; }
+        [GetProcAddress("glGetShaderInfoLog")]
+        public partial void GetShaderInfoLog(int shader, int maxLength, out int length, void* infoLog);
 
         public unsafe string CompileShaderAndGetError(int shader, string source)
         {
@@ -268,33 +219,24 @@ namespace Avalonia.OpenGL
             int len;
             fixed (void* ptr = logData)
                 GetShaderInfoLog(shader, logLength, out len, ptr);
-            return Encoding.UTF8.GetString(logData,0, len);
+            return Encoding.UTF8.GetString(logData, 0, len);
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlCreateProgram();
-        [GlEntryPoint("glCreateProgram")]
-        public GlCreateProgram CreateProgram { get; }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlAttachShader(int program, int shader);
-        [GlEntryPoint("glAttachShader")]
-        public GlAttachShader AttachShader { get; }
+        [GetProcAddress("glCreateProgram")]
+        public partial int CreateProgram();
+
+        [GetProcAddress("glAttachShader")]
+        public partial void AttachShader(int program, int shader);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlLinkProgram(int program);
-        [GlEntryPoint("glLinkProgram")]
-        public GlLinkProgram LinkProgram { get; }
+        [GetProcAddress("glLinkProgram")]
+        public partial void LinkProgram(int program);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetProgramiv(int program, int name, int* parameters);
-        [GlEntryPoint("glGetProgramiv")]
-        public GlGetProgramiv GetProgramiv { get; }
+        [GetProcAddress("glGetProgramiv")]
+        public partial void GetProgramiv(int program, int name, int* parameters);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
-        [GlEntryPoint("glGetProgramInfoLog")]
-        public GlGetProgramInfoLog GetProgramInfoLog { get; }
+        [GetProcAddress("glGetProgramInfoLog")]
+        public partial void GetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
 
         public unsafe string LinkProgramAndGetError(int program)
         {
@@ -309,13 +251,11 @@ namespace Avalonia.OpenGL
             int len;
             fixed (void* ptr = logData)
                 GetProgramInfoLog(program, logLength, out len, ptr);
-            return Encoding.UTF8.GetString(logData,0, len);
+            return Encoding.UTF8.GetString(logData, 0, len);
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBindAttribLocation(int program, int index, IntPtr name);
-        [GlEntryPoint("glBindAttribLocation")]
-        public GlBindAttribLocation BindAttribLocation { get; }
+        [GetProcAddress("glBindAttribLocation")]
+        public partial void BindAttribLocation(int program, int index, IntPtr name);
 
         public void BindAttribLocationString(int program, int index, string name)
         {
@@ -323,32 +263,24 @@ namespace Avalonia.OpenGL
                 BindAttribLocation(program, index, b.DangerousGetHandle());
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlGenBuffers(int len, int[] rv);
-        [GlEntryPoint("glGenBuffers")]
-        public GlGenBuffers GenBuffers { get; }
+        [GetProcAddress("glGenBuffers")]
+        public partial void GenBuffers(int len, int* rv);
 
         public int GenBuffer()
         {
-            var rv = new int[1];
-            GenBuffers(1, rv);
-            return rv[0];
+            int rv;
+            GenBuffers(1, &rv);
+            return rv;
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBindBuffer(int target, int buffer);
-        [GlEntryPoint("glBindBuffer")]
-        public GlBindBuffer BindBuffer { get; }
+        [GetProcAddress("glBindBuffer")]
+        public partial void BindBuffer(int target, int buffer);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlBufferData(int target, IntPtr size, IntPtr data, int usage);
-        [GlEntryPoint("glBufferData")]
-        public GlBufferData BufferData { get; }
+        [GetProcAddress("glBufferData")]
+        public partial void BufferData(int target, IntPtr size, IntPtr data, int usage);
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlGetAttribLocation(int program, IntPtr name);
-        [GlEntryPoint("glGetAttribLocation")]
-        public GlGetAttribLocation GetAttribLocation { get; }
+        [GetProcAddress("glGetAttribLocation")]
+        public partial int GetAttribLocation(int program, IntPtr name);
 
         public int GetAttribLocationString(int program, string name)
         {
@@ -356,36 +288,24 @@ namespace Avalonia.OpenGL
                 return GetAttribLocation(program, b.DangerousGetHandle());
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlVertexAttribPointer(int index, int size, int type,
+        [GetProcAddress("glVertexAttribPointer")]
+        public partial void VertexAttribPointer(int index, int size, int type,
             int normalized, int stride, IntPtr pointer);
-        [GlEntryPoint("glVertexAttribPointer")]
-        public GlVertexAttribPointer VertexAttribPointer { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlEnableVertexAttribArray(int index);
-        [GlEntryPoint("glEnableVertexAttribArray")]
-        public GlEnableVertexAttribArray EnableVertexAttribArray { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlUseProgram(int program);
-        [GlEntryPoint("glUseProgram")]
-        public GlUseProgram UseProgram { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDrawArrays(int mode, int first, IntPtr count);
-        [GlEntryPoint("glDrawArrays")]
-        public GlDrawArrays DrawArrays { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDrawElements(int mode, int count, int type, IntPtr indices);
-        [GlEntryPoint("glDrawElements")]
-        public GlDrawElements DrawElements { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate int GlGetUniformLocation(int program, IntPtr name);
-        [GlEntryPoint("glGetUniformLocation")]
-        public GlGetUniformLocation GetUniformLocation { get; }
+
+        [GetProcAddress("glEnableVertexAttribArray")]
+        public partial void EnableVertexAttribArray(int index);
+
+        [GetProcAddress("glUseProgram")]
+        public partial void UseProgram(int program);
+
+        [GetProcAddress("glDrawArrays")]
+        public partial void DrawArrays(int mode, int first, IntPtr count);
+
+        [GetProcAddress("glDrawElements")]
+        public partial void DrawElements(int mode, int count, int type, IntPtr indices);
+
+        [GetProcAddress("glGetUniformLocation")]
+        public partial int GetUniformLocation(int program, IntPtr name);
 
         public int GetUniformLocationString(int program, string name)
         {
@@ -393,41 +313,65 @@ namespace Avalonia.OpenGL
                 return GetUniformLocation(program, b.DangerousGetHandle());
         }
 
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlUniform1f(int location, float falue);
-        [GlEntryPoint("glUniform1f")]
-        public GlUniform1f Uniform1f { get; }
-
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlUniformMatrix4fv(int location, int count, bool transpose, void* value);
-        [GlEntryPoint("glUniformMatrix4fv")]
-        public GlUniformMatrix4fv UniformMatrix4fv { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlEnable(int what);
-        [GlEntryPoint("glEnable")]
-        public GlEnable Enable { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteBuffers(int count, int[] buffers);
-        [GlEntryPoint("glDeleteBuffers")]
-        public GlDeleteBuffers DeleteBuffers { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteProgram(int program);
-        [GlEntryPoint("glDeleteProgram")]
-        public GlDeleteProgram DeleteProgram { get; }
-
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GlDeleteShader(int shader);
-        [GlEntryPoint("glDeleteShader")]
-        public GlDeleteShader DeleteShader { get; }
-        
-        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
-        public delegate void GLGetRenderbufferParameteriv(int target, int name, int[] value);
-        [GlEntryPoint("glGetRenderbufferParameteriv")]
-        public GLGetRenderbufferParameteriv GetRenderbufferParameteriv { get; }
+        [GetProcAddress("glUniform1f")]
+        public partial void Uniform1f(int location, float falue);
+
+
+        [GetProcAddress("glUniformMatrix4fv")]
+        public partial void UniformMatrix4fv(int location, int count, bool transpose, void* value);
+
+        [GetProcAddress("glEnable")]
+        public partial void Enable(int what);
+
+        [GetProcAddress("glDeleteBuffers")]
+        public partial void DeleteBuffers(int count, int* buffers);
+
+        public void DeleteBuffer(int buffer) => DeleteBuffers(1, &buffer);
+
+        [GetProcAddress("glDeleteProgram")]
+        public partial void DeleteProgram(int program);
+
+        [GetProcAddress("glDeleteShader")]
+        public partial void DeleteShader(int shader);
+
+        [GetProcAddress("glGetRenderbufferParameteriv")]
+        public partial void GetRenderbufferParameteriv(int target, int name, out int value);
         // ReSharper restore UnassignedGetOnlyAutoProperty
+
+        [GetProcAddress(true)]
+        [GlMinVersionEntryPoint("glDeleteVertexArrays", 3, 0)]
+        [GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
+        public partial void DeleteVertexArrays(int count, int* arrays);
+
+        public void DeleteVertexArray(int array) => DeleteVertexArrays(1, &array);
+
+        [GetProcAddress(true)]
+        [GlMinVersionEntryPoint("glBindVertexArray", 3, 0)]
+        [GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
+        public partial void BindVertexArray(int array);
+
+
+        [GetProcAddress(true)]
+        [GlMinVersionEntryPoint("glGenVertexArrays", 3, 0)]
+        [GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
+        public partial void GenVertexArrays(int n, int* rv);
+
+        public int GenVertexArray()
+        {
+            int rv = 0;
+            GenVertexArrays(1, &rv);
+            return rv;
+        }
+
+        public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func<IntPtr, IntPtr> getProcAddress)
+        {
+            return new GlInterface(version, s =>
+            {
+                var ptr = Marshal.StringToHGlobalAnsi(s);
+                var rv = getProcAddress(ptr);
+                Marshal.FreeHGlobal(ptr);
+                return rv;
+            });
+        }
     }
-}
+}

+ 0 - 79
src/Avalonia.OpenGL/GlInterfaceBase.cs

@@ -1,79 +0,0 @@
-using System;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using Avalonia.Platform.Interop;
-
-namespace Avalonia.OpenGL
-{
-    public class GlInterfaceBase : GlInterfaceBase<object>
-    {
-        public GlInterfaceBase(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null)
-        {
-        }
-
-        public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
-        {
-        }
-    }
-
-    public class GlInterfaceBase<TContext>
-    {
-        private readonly Func<string, IntPtr> _getProcAddress;
-        public GlInterfaceBase(Func<string, IntPtr> getProcAddress, TContext context)
-        {
-            _getProcAddress = getProcAddress;
-            foreach (var prop in this.GetType().GetProperties())
-            {
-                var attrs = prop.GetCustomAttributes()
-                    .Where(a =>
-                        a is IGlEntryPointAttribute || a is IGlEntryPointAttribute<TContext>)
-                    .ToList();
-                if(attrs.Count == 0)
-                    continue;
-                
-                var isOptional = prop.GetCustomAttribute<GlOptionalEntryPoint>() != null;
-                
-                var fieldName = $"<{prop.Name}>k__BackingField";
-                var field = prop.DeclaringType.GetField(fieldName,
-                    BindingFlags.Instance | BindingFlags.NonPublic);
-                if (field == null)
-                    throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}");
-                
-                
-                IntPtr proc = IntPtr.Zero;
-                foreach (var attr in attrs)
-                {
-                    if (attr is IGlEntryPointAttribute<TContext> typed)
-                        proc = typed.GetProcAddress(context, getProcAddress);
-                    else if (attr is IGlEntryPointAttribute untyped)
-                        proc = untyped.GetProcAddress(getProcAddress);
-                    if (proc != IntPtr.Zero)
-                        break;
-                }
-                
-                if (proc != IntPtr.Zero)
-                    field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType));
-                else if (!isOptional)
-                    throw new OpenGlException("Unable to find a suitable GL function for " + prop.Name);
-            }
-        }
-
-        protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
-            (proc) =>
-            {
-                using (var u = new Utf8Buffer(proc))
-                {
-                    var rv = func(u);
-                    return rv;
-                }
-            };
-        
-        public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context)
-        {
-            
-        }
-        
-        public IntPtr GetProcAddress(string proc) => _getProcAddress(proc);
-    }
-}

+ 10 - 0
src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml

@@ -0,0 +1,10 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Design.PreviewWith>
+        <RichTextBlock IsTextSelectionEnabled="True" Text="Preview"/>
+    </Design.PreviewWith>
+
+    <Style Selector="RichTextBlock[IsTextSelectionEnabled=true]">
+      <Setter Property="Cursor" Value="IBeam" />
+    </Style>
+</Styles>

+ 1 - 0
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -66,4 +66,5 @@
   <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml"/>
   <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml"/>
   <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml"/>
+  <StyleInclude Source="avares://Avalonia.Themes.Default/Controls/RichTextBlock.xaml"/>
 </Styles>

+ 1 - 0
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@@ -68,6 +68,7 @@
                 <ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/Slider.xaml" />
                 <!--  ManagedFileChooser comes last because it uses (and overrides) styles for a multitude of other controls...the dialogs were originally UserControls, after all  -->
                 <ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml" />
+                <ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Styles.Resources>

+ 14 - 0
src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml

@@ -0,0 +1,14 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Design.PreviewWith>
+        <RichTextBlock IsTextSelectionEnabled="True" Text="Preview"/>
+    </Design.PreviewWith>
+
+  <ControlTheme x:Key="{x:Type RichTextBlock}" TargetType="RichTextBlock">
+    <Style Selector="^[IsTextSelectionEnabled=True]">
+      <Setter Property="Cursor" Value="IBeam" />
+    </Style>
+  </ControlTheme>
+
+
+</ResourceDictionary>

+ 1 - 1
src/Avalonia.X11/Avalonia.X11.csproj

@@ -11,5 +11,5 @@
         <ProjectReference Include="..\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj" />
         <Compile Include="..\Shared\RawEventGrouping.cs" />
     </ItemGroup>
-
+    <Import Project="..\..\build\SourceGenerators.props" />
 </Project>

+ 48 - 63
src/Avalonia.X11/Glx/Glx.cs

@@ -4,111 +4,97 @@ using System.Linq;
 using System.Runtime.InteropServices;
 using Avalonia.OpenGL;
 using Avalonia.Platform.Interop;
+using Avalonia.SourceGenerator;
+
 // ReSharper disable UnassignedGetOnlyAutoProperty
 
 namespace Avalonia.X11.Glx
 {
-    unsafe class GlxInterface : GlInterfaceBase
+    unsafe partial class GlxInterface
     {
         private const string libGL = "libGL.so.1";
-        [GlEntryPointAttribute("glXMakeContextCurrent")]
-        public GlxMakeContextCurrent MakeContextCurrent { get; }
-        public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
+        [GetProcAddress("glXMakeContextCurrent")]
+        public partial bool MakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
 
-        [GlEntryPoint("glXGetCurrentContext")]
-        public GlxGetCurrentContext GetCurrentContext { get; }
-        public delegate IntPtr GlxGetCurrentContext();
+        [GetProcAddress("glXGetCurrentContext")]
+        public partial IntPtr GetCurrentContext();
 
-        [GlEntryPoint("glXGetCurrentDisplay")]
-        public GlxGetCurrentDisplay GetCurrentDisplay { get; }
-        public delegate IntPtr GlxGetCurrentDisplay();
+        [GetProcAddress("glXGetCurrentDisplay")]
+        public partial IntPtr GetCurrentDisplay();
         
-        [GlEntryPoint("glXGetCurrentDrawable")]
-        public GlxGetCurrentDrawable GetCurrentDrawable { get; }
-        public delegate IntPtr GlxGetCurrentDrawable();
+        [GetProcAddress("glXGetCurrentDrawable")]
+        public partial IntPtr GetCurrentDrawable();
         
-        [GlEntryPoint("glXGetCurrentReadDrawable")]
-        public GlxGetCurrentReadDrawable GetCurrentReadDrawable { get; }
-        public delegate IntPtr GlxGetCurrentReadDrawable();
+        [GetProcAddress("glXGetCurrentReadDrawable")]
+        public partial IntPtr GetCurrentReadDrawable();
         
-        [GlEntryPoint("glXCreatePbuffer")]
-        public GlxCreatePbuffer CreatePbuffer { get; }
-        public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
+        [GetProcAddress("glXCreatePbuffer")]
+        public partial IntPtr CreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
         
-        [GlEntryPoint("glXDestroyPbuffer")]
-        public GlxDestroyPbuffer DestroyPbuffer { get; }
-        public delegate IntPtr GlxDestroyPbuffer(IntPtr dpy, IntPtr fb);
+        [GetProcAddress("glXDestroyPbuffer")]
+        public partial IntPtr DestroyPbuffer(IntPtr dpy, IntPtr fb);
         
-        [GlEntryPointAttribute("glXChooseVisual")]
-        public GlxChooseVisual ChooseVisual { get; }
-        public delegate  XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
+        [GetProcAddress("glXChooseVisual")]
+        public partial  XVisualInfo* ChooseVisual(IntPtr dpy, int screen, int[] attribList);
         
         
-        [GlEntryPointAttribute("glXCreateContext")]
-        public GlxCreateContext CreateContext { get; }
-        public delegate  IntPtr GlxCreateContext(IntPtr dpy,  XVisualInfo* vis,  IntPtr shareList,  bool direct);
+        [GetProcAddress("glXCreateContext")]
+        public partial  IntPtr CreateContext(IntPtr dpy,  XVisualInfo* vis,  IntPtr shareList,  bool direct);
         
 
-        [GlEntryPointAttribute("glXCreateContextAttribsARB")]
-        public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
-        public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
+        [GetProcAddress("glXCreateContextAttribsARB")]
+        public partial IntPtr CreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
             bool direct, int[] attribs);
         
 
         [DllImport(libGL, EntryPoint = "glXGetProcAddress")]
-        public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
+        public static extern IntPtr GlxGetProcAddress(string buffer);
 
         
-        [GlEntryPointAttribute("glXDestroyContext")]
-        public GlxDestroyContext DestroyContext { get; }
-        public delegate  void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
+        [GetProcAddress("glXDestroyContext")]
+        public partial  void DestroyContext(IntPtr dpy, IntPtr ctx);
         
         
-        [GlEntryPointAttribute("glXChooseFBConfig")]
-        public GlxChooseFBConfig ChooseFBConfig { get; }
-        public delegate  IntPtr* GlxChooseFBConfig(IntPtr dpy,  int screen,  int[] attrib_list,  out int nelements);
+        [GetProcAddress("glXChooseFBConfig")]
+        public partial  IntPtr* ChooseFBConfig(IntPtr dpy,  int screen,  int[] attrib_list,  out int nelements);
         
         
-        public  IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
+        public  IntPtr* ChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
         {
             var arr = attribs.Concat(new[]{0}).ToArray();
             return ChooseFBConfig(dpy, screen, arr, out nelements);
         }
         
-        [GlEntryPointAttribute("glXGetVisualFromFBConfig")]
-        public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
-        public delegate  XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy,  IntPtr config);
+        [GetProcAddress("glXGetVisualFromFBConfig")]
+        public partial  XVisualInfo * GetVisualFromFBConfig(IntPtr dpy,  IntPtr config);
         
         
-        [GlEntryPointAttribute("glXGetFBConfigAttrib")]
-        public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
-        public delegate  int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
+        [GetProcAddress("glXGetFBConfigAttrib")]
+        public partial  int GetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
         
         
-        [GlEntryPointAttribute("glXSwapBuffers")]
-        public GlxSwapBuffers SwapBuffers { get; }
-        public delegate  void GlxSwapBuffers(IntPtr dpy,  IntPtr drawable);
+        [GetProcAddress("glXSwapBuffers")]
+        public partial  void SwapBuffers(IntPtr dpy,  IntPtr drawable);
         
         
-        [GlEntryPointAttribute("glXWaitX")]
-        public GlxWaitX WaitX { get; }
-        public delegate  void GlxWaitX();
+        [GetProcAddress("glXWaitX")]
+        public partial  void WaitX();
         
         
-        [GlEntryPointAttribute("glXWaitGL")]
-        public GlxWaitGL WaitGL { get; }
-        public delegate void GlxWaitGL();
+        [GetProcAddress("glXWaitGL")]
+        public partial void WaitGL();
         
-        public delegate int GlGetError();
-        [GlEntryPoint("glGetError")]
-        public GlGetError GetError { get; }
 
-        public delegate IntPtr GlxQueryExtensionsString(IntPtr display, int screen);
-        [GlEntryPoint("glXQueryExtensionsString")]
-        public GlxQueryExtensionsString QueryExtensionsString { get; }
+        [GetProcAddress("glGetError")]
+        public partial int GlGetError();
+
+        
+        [GetProcAddress("glXQueryExtensionsString")]
+        public partial IntPtr QueryExtensionsString(IntPtr display, int screen);
 
-        public GlxInterface() : base(SafeGetProcAddress)
+        public GlxInterface()
         {
+            Initialize(SafeGetProcAddress);
         }
 
         // Ignores egl functions.
@@ -122,10 +108,9 @@ namespace Avalonia.X11.Glx
                 return IntPtr.Zero;
             }
 
-            return GlxConverted(proc);
+            return GlxGetProcAddress(proc);
         }
 
-        private static readonly Func<string, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
 
         public string[] GetExtensions(IntPtr display)
         {

+ 1 - 1
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@@ -164,7 +164,7 @@ namespace Avalonia.LinuxFramebuffer.Output
             else 
             {
                 Fd = open(path, 2, 0);
-                if(Fd != -1) throw new Win32Exception($"Couldn't open {path}");
+                if(Fd == -1) throw new Win32Exception($"Couldn't open {path}");
             }
         }
 

+ 1 - 1
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@@ -50,7 +50,7 @@ namespace Avalonia.LinuxFramebuffer.Output
         }
 
         [DllImport("libEGL.so.1")]
-        static extern IntPtr eglGetProcAddress(Utf8Buffer proc);
+        static extern IntPtr eglGetProcAddress(string proc);
 
         private GbmBoUserDataDestroyCallbackDelegate FbDestroyDelegate;
         private drmModeModeInfo _mode;

+ 24 - 0
src/Shared/SourceGeneratorAttributes.cs

@@ -14,4 +14,28 @@ namespace Avalonia.SourceGenerator
         public string Namespace { get; }
         public Type BaseType { get; }
     }
+    
+    
+    internal class GetProcAddressAttribute : Attribute
+    {
+        public GetProcAddressAttribute(string proc)
+        {
+            
+        }
+        
+        public GetProcAddressAttribute(string proc, bool optional = false)
+        {
+
+        }
+
+        public GetProcAddressAttribute(bool optional)
+        {
+
+        }
+
+        public GetProcAddressAttribute()
+        {
+
+        }
+    }
 }

+ 11 - 17
src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs

@@ -27,16 +27,13 @@ namespace Avalonia.Skia
             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];
+            _fbo = gl.GenFramebuffer();
             gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
 
             // Create a texture to render into
-            gl.GenTextures(1, arr);
-            _texture = arr[0];
+            _texture = gl.GenTexture();
             gl.BindTexture(GL_TEXTURE_2D, _texture);
             gl.TexImage2D(GL_TEXTURE_2D, 0,
                 InternalFormat, pixelSize.Width, pixelSize.Height,
@@ -48,8 +45,7 @@ namespace Avalonia.Skia
             var success = false;
             foreach (var useStencil8 in TrueFalse)
             {
-                gl.GenRenderbuffers(1, arr);
-                _depthStencil = arr[0];
+                _depthStencil = gl.GenRenderbuffer();
                 gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
 
                 if (useStencil8)
@@ -73,7 +69,7 @@ namespace Avalonia.Skia
                 else
                 {
                     gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
-                    gl.DeleteRenderbuffers(1, arr);
+                    gl.DeleteRenderbuffer(_depthStencil);
                 }
             }
             
@@ -83,10 +79,8 @@ namespace Avalonia.Skia
 
             if (!success)
             {
-                arr[0] = _fbo;
-                gl.DeleteFramebuffers(1, arr);
-                arr[0] = _texture;
-                gl.DeleteTextures(1, arr);
+                gl.DeleteFramebuffer(_fbo);
+                gl.DeleteTexture(_texture);
                 throw new OpenGlException("Unable to create FBO with stencil");
             }
 
@@ -94,7 +88,7 @@ namespace Avalonia.Skia
                 new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat()));
             Surface = SKSurface.Create(_grContext, target,
                 surfaceOrigin, SKColorType.Rgba8888);
-            CanBlit = gl.BlitFramebuffer != null;
+            CanBlit = gl.IsBlitFramebufferAvailable;
         }
         
         public void Dispose()
@@ -106,9 +100,9 @@ namespace Avalonia.Skia
                 var gl = _glContext.GlInterface;
                 if (_fbo != 0)
                 {
-                    gl.DeleteFramebuffers(1, new[] { _fbo });
-                    gl.DeleteTextures(1, new[] { _texture });
-                    gl.DeleteRenderbuffers(1, new[] { _depthStencil });
+                    gl.DeleteFramebuffer(_fbo);
+                    gl.DeleteTexture(_texture);
+                    gl.DeleteRenderbuffer(_depthStencil);
                     _fbo = _texture = _depthStencil = 0;
                 }
             }

+ 1 - 1
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@@ -54,7 +54,7 @@ namespace Avalonia.Skia
                 return null;
 
             // Blit feature requires glBlitFramebuffer
-            if (_glContext.GlInterface.BlitFramebuffer == null)
+            if (!_glContext.GlInterface.IsBlitFramebufferAvailable)
                 return null;
             
             size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1));

+ 7 - 4
src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs

@@ -101,7 +101,7 @@ namespace Avalonia.Skia
         private bool _disposed;
         private readonly DisposableLock _lock = new DisposableLock();
 
-        public SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
+        public unsafe SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
         {
             _bitmap = bitmap;
             _context = context;
@@ -119,7 +119,8 @@ namespace Avalonia.Skia
                     var gl = _context.GlInterface;
                     
                     var textures = new int[2];
-                    gl.GenTextures(2, textures);
+                    fixed (int* ptex = textures)
+                        gl.GenTextures(2, ptex);
                     _texture = textures[0];
                     _frontBuffer = textures[1];
 
@@ -178,7 +179,7 @@ namespace Avalonia.Skia
             _presentCallback();
         }
 
-        public void Dispose()
+        public unsafe void Dispose()
         {
             var gl = _context.GlInterface;
             _bitmap.Present(null);
@@ -191,7 +192,9 @@ namespace Avalonia.Skia
                 if(_disposed)
                     return;
                 _disposed = true;
-                gl.DeleteTextures(2, new[] { _texture, _frontBuffer });
+                var tex = new[] { _texture, _frontBuffer };
+                fixed (int* ptex = tex)
+                    gl.DeleteTextures(2, ptex);
             }
         }
 

+ 23 - 29
src/iOS/Avalonia.iOS/LayerFbo.cs

@@ -10,12 +10,12 @@ namespace Avalonia.iOS
         private readonly EAGLContext _context;
         private readonly GlInterface _gl;
         private readonly CAEAGLLayer _layer;
-        private int[] _framebuffer;
-        private int[] _renderbuffer;
-        private int[] _depthBuffer;
+        private int _framebuffer;
+        private int _renderbuffer;
+        private int _depthBuffer;
         private bool _disposed;
 
-        private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int[] framebuffer, int[] renderbuffer, int[] depthBuffer)
+        private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int framebuffer, int renderbuffer, int depthBuffer)
         {
             _context = context;
             _gl = gl;
@@ -30,42 +30,36 @@ namespace Avalonia.iOS
             if (context != EAGLContext.CurrentContext)
                 return null;
 
-            var fb = new int[2];
-            var rb = new int[2];
-            var db = new int[2];
-            
-            gl.GenRenderbuffers(1, rb);
-            gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER,  rb[0]);
+            var rb = gl.GenRenderbuffer();
+            gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER,  rb);
             context.RenderBufferStorage(GlConsts.GL_RENDERBUFFER, layer);
 
-            gl.GenFramebuffers(1, fb);
-            gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb[0]);
-            gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb[0]);
-
-            int[] w = new int[1];
-            int[] h = new int[1];
-            gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, w);
-            gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, h);
+            var fb = gl.GenFramebuffer();
+            gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb);
+            gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb);
             
-            gl.GenRenderbuffers(1, db);
+            gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, out var w);
+            gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, out var h);
+
+            var db = gl.GenRenderbuffer();
             
             //GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthBuffer);
             //GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.DepthComponent16, w, h);
-            gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db[0]);
+            gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db);
 
             var frameBufferError = gl.CheckFramebufferStatus(GlConsts.GL_FRAMEBUFFER);
             if(frameBufferError != GlConsts.GL_FRAMEBUFFER_COMPLETE)
             {
-                gl.DeleteFramebuffers(1, fb);
-                gl.DeleteRenderbuffers(1, db);
-                gl.DeleteRenderbuffers(1, rb);
+                gl.DeleteFramebuffer(fb);
+                gl.DeleteRenderbuffer(db);
+                gl.DeleteRenderbuffer(rb);
                 return null;
             }
 
             return new LayerFbo(context, gl, layer, fb, rb, db)
             {
-                Width = w[0],
-                Height = h[0]
+                Width = w,
+                Height = h
             };
         }
         
@@ -74,7 +68,7 @@ namespace Avalonia.iOS
 
         public void Bind()
         {
-            _gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer[0]);
+            _gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer);
         }
 
         public void Present()
@@ -88,9 +82,9 @@ namespace Avalonia.iOS
             if(_disposed)
                 return;
             _disposed = true;
-            _gl.DeleteFramebuffers(1, _framebuffer);
-            _gl.DeleteRenderbuffers(1, _depthBuffer);
-            _gl.DeleteRenderbuffers(1, _renderbuffer);
+            _gl.DeleteFramebuffer(_framebuffer);
+            _gl.DeleteRenderbuffer(_depthBuffer);
+            _gl.DeleteRenderbuffer(_renderbuffer);
             if (_context != EAGLContext.CurrentContext)
                 throw new InvalidOperationException("Associated EAGLContext is not current");
         }

+ 338 - 0
src/tools/DevGenerators/GetProcAddressInitialization.cs

@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Generator;
+
+[Generator(LanguageNames.CSharp)]
+public class GetProcAddressInitializationGenerator : IIncrementalGenerator
+{
+    const string GetProcAddressFullName = "global::Avalonia.SourceGenerator.GetProcAddressAttribute";
+    
+    public void Initialize(IncrementalGeneratorInitializationContext context)
+    {
+        var allMethodsWithAttributes = context.SyntaxProvider
+            .CreateSyntaxProvider(
+                static (s, _) => s is MethodDeclarationSyntax
+                {
+                    AttributeLists.Count: > 0,
+                } md && md.Modifiers.Any(m=>m.IsKind(SyntaxKind.PartialKeyword)),
+                static (context, _) =>
+                    (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
+
+        var fieldsWithAttribute = allMethodsWithAttributes
+            .Where(s => s.HasAttributeWithFullyQualifiedName(GetProcAddressFullName));
+
+        var all = fieldsWithAttribute.Collect();
+        context.RegisterSourceOutput(all, static (context, methods) =>
+        {
+            foreach (var typeGroup in methods.GroupBy(f => f.ContainingType))
+            {
+                var nextContext = 0;
+                var contexts = new Dictionary<string, int>();
+
+                string GetContextNameFromIndex(int c) => "context" + (c == 0 ? "" : c);
+                string GetContextName(string type)
+                {
+                    if (contexts.TryGetValue(type, out var idx))
+                        return GetContextNameFromIndex(idx);
+                    if (nextContext != 0)
+                        idx += nextContext;
+                    nextContext++;
+                    return GetContextNameFromIndex(contexts[type] = idx);
+                }
+                
+                var classBuilder = new StringBuilder();
+                if (typeGroup.Key.ContainingNamespace != null)
+                    classBuilder
+                        .AppendLine("using System;")
+                        .Append("namespace ")
+                        .Append(typeGroup.Key.ContainingNamespace)
+                        .AppendLine(";");
+                classBuilder
+                    .Append("unsafe partial class ")
+                    .AppendLine(typeGroup.Key.Name)
+                    .AppendLine("{");
+                var initializeBody = new StringBuilder()
+                    .Pad(2)
+                    .AppendLine("var addr = IntPtr.Zero;");
+                
+                foreach (var method in typeGroup)
+                {
+                    var isOptional = false;
+                    var first = true;
+                    var fieldName = "_addr_" + method.Name;
+                    var delegateType = BuildDelegateType(method);
+
+                    void AppendNextAddr()
+                    {
+                        if (first)
+                        {
+                            first = false;
+                            initializeBody.Pad(2);
+                        }
+                        else
+                            initializeBody
+                                .Pad(2)
+                                .Append("if(addr == IntPtr.Zero) ");
+                    }
+
+                    initializeBody
+                        .Pad(2).Append("// Initializing ").AppendLine(method.Name)
+                        .Pad(2)
+                        .AppendLine("addr = IntPtr.Zero;");
+                    foreach (var attr in method.GetAttributes())
+                    {
+                        if (attr.AttributeClass?.HasFullyQualifiedName(GetProcAddressFullName) == true)
+                        {
+                            string? primaryName = null;
+                            foreach (var arg in attr.ConstructorArguments)
+                            {
+                                if (arg.Value is string name)
+                                    primaryName = name;
+                                if (arg.Value is bool opt)
+                                    isOptional = opt;
+                            }
+
+                            if (primaryName != null)
+                            {
+                                AppendNextAddr();
+                                initializeBody
+                                    .Append("addr = getProcAddress(\"")
+                                    .Append(primaryName)
+                                    .AppendLine("\");");
+                            }
+                        }
+                        else
+                        {
+                            if (attr.AttributeClass != null
+                                && attr.AttributeClass.MemberNames.Contains("GetProcAddress"))
+                            {
+                                var getProcMethod = attr.AttributeClass.GetMembers()
+                                    .FirstOrDefault(m => m.Name == "GetProcAddress") as IMethodSymbol;
+                                if (getProcMethod == null || !getProcMethod.IsStatic || getProcMethod.Parameters.Length < 2)
+                                    continue;
+                                var contextName =
+                                    GetContextName(getProcMethod
+                                        .Parameters[1].Type
+                                        .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+                                AppendNextAddr();
+                                initializeBody
+                                    .Append("addr = ")
+                                    .Append(attr.AttributeClass.GetFullyQualifiedName())
+                                    .Append(".GetProcAddress(")
+                                    .Append("getProcAddress, ")
+                                    .Append(contextName);
+                                
+                                var syntaxNode = (AttributeSyntax)attr.ApplicationSyntaxReference.GetSyntax();
+                                foreach (var arg in syntaxNode.ArgumentList.Arguments)
+                                    initializeBody.Append(", ").Append(arg.GetText());
+                                initializeBody.AppendLine(");");
+                            }
+                        }
+                    }
+
+                    if (!isOptional)
+                    {
+                        initializeBody
+                            .Pad(2)
+                            .Append("if (addr == IntPtr.Zero) throw new System.EntryPointNotFoundException(\"")
+                            .Append(fieldName).AppendLine("\");");
+                    }
+
+                    initializeBody
+                        .Pad(2)
+                        .Append(fieldName)
+                        .Append(" = (")
+                        .Append(delegateType)
+                        .AppendLine(")addr;");
+
+                    classBuilder
+                        .Pad(1)
+                        .Append(delegateType);
+                    classBuilder
+                        .Append(fieldName)
+                        .AppendLine(";");
+
+                    classBuilder
+                        .Pad(1)
+                        .Append("public partial ")
+                        .Append(method.ReturnType.GetFullyQualifiedName())
+                        .Append(" ")
+                        .Append(method.Name)
+                        .Append("(");
+                    var firstArg = true;
+                    foreach (var p in method.Parameters)
+                    {
+                        if (firstArg)
+                            firstArg = false;
+                        else
+                            classBuilder.Append(", ");
+                        AppendRefKind(classBuilder, p.RefKind);
+                        classBuilder
+                            .Append(p.Type.GetFullyQualifiedName())
+                            .Append(" @")
+                            .Append(p.Name);
+                    }
+                    classBuilder
+                        .AppendLine(")")
+                        .Pad(1)
+                        .AppendLine("{");
+                    if (isOptional)
+                        classBuilder
+                            .Pad(2)
+                            .Append("if (")
+                            .Append(fieldName)
+                            .Append(" == null) throw new System.EntryPointNotFoundException(\"")
+                            .Append(method.Name)
+                            .AppendLine("\");");
+
+                    foreach(var p in method.Parameters)
+                        if (NeedsPin(p.Type))
+                            classBuilder.Pad(2)
+                                .Append("fixed(")
+                                .Append(MapToNative(p.Type))
+                                .Append(" @__p_")
+                                .Append(p.Name)
+                                .Append(" = ")
+                                .Append(p.Name)
+                                .AppendLine(")");
+                    
+                    classBuilder.Pad(2);
+                    if (!method.ReturnsVoid)
+                        classBuilder.Append("return ");
+
+                    var invokeBuilder = new StringBuilder();
+                    
+                    invokeBuilder
+                        .Append(fieldName)
+                        .Append("(");
+                    firstArg = true;
+                    foreach (var p in method.Parameters)
+                    {
+                        if (firstArg)
+                            firstArg = false;
+                        else
+                            invokeBuilder.Append(", ");
+                        AppendRefKind(invokeBuilder, p.RefKind);
+                        invokeBuilder
+                            .Append("@")
+                            .Append(ConvertToNative(p.Name, p.Type));
+                    }
+
+                    invokeBuilder.Append(")");
+                    classBuilder.Append(ConvertToManaged(method.ReturnType, invokeBuilder.ToString()));
+                    
+                    classBuilder.AppendLine(";").Pad(1).AppendLine("}");
+                    if (isOptional)
+                        classBuilder
+                            .Pad(1)
+                            .Append("public bool Is")
+                            .Append(method.Name)
+                            .Append("Available => ")
+                            .Append(fieldName)
+                            .AppendLine(" != null;");
+                }
+                
+                classBuilder
+                    .Pad(1)
+                    .Append("void Initialize(Func<string, IntPtr> getProcAddress");
+                foreach (var kv in contexts.OrderBy(x => x.Value))
+                { 
+                    classBuilder
+                        .Append(", ")
+                        .Append(kv.Key)
+                        .Append(" ")
+                        .Append(GetContextNameFromIndex(kv.Value));
+                }
+
+                classBuilder.AppendLine(")").Pad(1).AppendLine("{");
+                classBuilder.Append(initializeBody.ToString());
+                classBuilder.Append("}\n}");
+
+
+                context.AddSource(typeGroup.Key.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString());
+            }
+        });
+
+        
+    }
+
+    static StringBuilder AppendRefKind(StringBuilder sb, RefKind kind)
+    {
+        if (kind == RefKind.Ref)
+            sb.Append("ref ");
+        if (kind == RefKind.Out)
+            sb.Append("out ");
+        return sb;
+    }
+
+    static bool NeedsPin(ITypeSymbol type)
+    {
+        if (type.TypeKind == TypeKind.Array)
+            return true;
+        return false;
+    }
+
+    static string ConvertToNative(string name, ITypeSymbol type)
+    {
+        if (NeedsPin(type))
+            return "__p_" + name;
+        if (IsBool(type))
+            return $"{name} ? 1 : 0";
+        return name;
+    }
+
+    static string ConvertToManaged(ITypeSymbol type, string expr)
+    {
+        if (IsBool(type))
+            return expr + " != 0";
+        return expr;
+    }
+
+    static bool IsBool(ITypeSymbol type) => type.GetFullyQualifiedName() == "global::System.Boolean" ||
+                                            type.GetFullyQualifiedName() == "bool";
+    
+    static string MapToNative(ITypeSymbol type)
+    {
+        if (type.TypeKind == TypeKind.Array)
+            return ((IArrayTypeSymbol)type).ElementType.GetFullyQualifiedName() + "*";
+        if (IsBool(type))
+            return "int";
+        return type.GetFullyQualifiedName();
+    }
+
+    static string BuildDelegateType(IMethodSymbol method)
+    {
+        StringBuilder name = new("delegate* unmanaged[Stdcall]<");
+        var firstArg = true;
+
+        void AppendArg(string a, RefKind kind)
+        {
+            if (firstArg)
+                firstArg = false;
+            else
+                name.Append(",");
+            AppendRefKind(name, kind);
+            name.Append(a);
+        }
+
+        foreach (var p in method.Parameters)
+        {
+            AppendArg(MapToNative(p.Type), p.RefKind);
+        }
+
+        AppendArg(MapToNative(method.ReturnType), RefKind.None);
+        name.Append(">");
+        return name.ToString();
+    }
+
+}

+ 31 - 0
src/tools/DevGenerators/Helpers.cs

@@ -0,0 +1,31 @@
+using System.Collections.Immutable;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Generator;
+
+static class Helpers
+{
+    public static StringBuilder Pad(this StringBuilder sb, int count) => sb.Append(' ', count * 4);
+
+    public static string GetFullyQualifiedName(this ISymbol symbol)
+    {
+        return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+    }
+    
+    public static bool HasFullyQualifiedName(this ISymbol symbol, string name)
+    {
+        return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name;
+    }
+
+    public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name)
+    {
+        ImmutableArray<AttributeData> attributes = symbol.GetAttributes();
+
+        foreach (AttributeData attribute in attributes)
+            if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true)
+                return true;
+
+        return false;
+    }
+}

BIN
tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf


+ 3 - 1
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@@ -15,6 +15,8 @@ namespace Avalonia.Skia.UnitTests.Media
 
         private readonly Typeface _defaultTypeface =
             new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
+        private readonly Typeface _arabicTypeface =
+           new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Kufi Arabic");
         private readonly Typeface _italicTypeface =
             new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic);
         private readonly Typeface _emojiTypeface =
@@ -22,7 +24,7 @@ namespace Avalonia.Skia.UnitTests.Media
 
         public CustomFontManagerImpl()
         {
-            _customTypefaces = new[] { _emojiTypeface, _italicTypeface, _defaultTypeface };
+            _customTypefaces = new[] { _emojiTypeface, _italicTypeface, _arabicTypeface, _defaultTypeface };
             _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
         }
 

+ 2 - 6
tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

@@ -33,15 +33,11 @@ namespace Avalonia.Skia.UnitTests.Media
         {
             var fontManager = new FontManagerImpl();
 
-            //we need to have a valid font name different from the default one
-            string fontName = fontManager.GetInstalledFontFamilyNames().First();
-
             var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
-                new Typeface(new FontFamily($"A, B, {fontName}"), weight: FontWeight.Bold));
+                new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold));
 
             var skTypeface = glyphTypeface.Typeface;
-
-            Assert.Equal(fontName, skTypeface.FamilyName);
+            
             Assert.True(skTypeface.FontWeight >= 600);
         }
 

+ 123 - 6
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@@ -154,7 +154,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                     {
                         j += inner.Current.Text.Length;
 
-                        if(j + i > text.Length)
+                        if (j + i > text.Length)
                         {
                             break;
                         }
@@ -738,7 +738,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 var textLine = layout.TextLines[0];
 
                 var start = textLine.GetDistanceFromCharacterHit(new CharacterHit(5, 1));
-                
+
                 var end = textLine.GetDistanceFromCharacterHit(new CharacterHit(6, 1));
 
                 var rects = layout.HitTestTextRange(0, 7).ToArray();
@@ -746,7 +746,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 Assert.Equal(1, rects.Length);
 
                 var expected = rects[0];
-                
+
                 Assert.Equal(expected.Left, start);
                 Assert.Equal(expected.Right, end);
             }
@@ -818,11 +818,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                         var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
 
                         Assert.Equal(expected, actual);
-                    }                  
+                    }
                 }
             }
         }
-        
+
         [Fact]
         public void Should_Layout_Empty_String()
         {
@@ -833,11 +833,128 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                     Typeface.Default,
                     12,
                     Brushes.Black);
-                
+
                 Assert.True(layout.Bounds.Height > 0);
             }
         }
 
+        [Fact]
+        public void Should_HitTestPoint_RightToLeft()
+        {
+            using (Start())
+            {
+                var text = "אאא AAA";
+
+                var layout = new TextLayout(
+                    text,
+                    Typeface.Default,
+                    12,
+                    Brushes.Black,
+                    flowDirection: FlowDirection.RightToLeft);
+
+                var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters;
+
+                var hit = layout.HitTestPoint(new Point());
+
+                Assert.Equal(4, hit.TextPosition);
+
+                var currentX = 0.0;
+
+                for (var i = 0; i < firstRun.GlyphRun.GlyphClusters.Count; i++)
+                {
+                    var cluster = firstRun.GlyphRun.GlyphClusters[i];
+                    var advance = firstRun.GlyphRun.GlyphAdvances[i];
+
+                    hit = layout.HitTestPoint(new Point(currentX, 0));
+
+                    Assert.Equal(cluster, hit.TextPosition);
+
+                    var hitRange = layout.HitTestTextRange(hit.TextPosition, 1);
+
+                    var distance = hitRange.First().Left;
+
+                    Assert.Equal(currentX, distance);
+
+                    currentX += advance;
+                }
+
+                var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters;
+
+                hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0));
+
+                Assert.Equal(7, hit.TextPosition);
+
+                hit = layout.HitTestPoint(new Point(layout.TextLines[0].WidthIncludingTrailingWhitespace, 0));
+
+                Assert.Equal(0, hit.TextPosition);
+
+                currentX = firstRun.Size.Width + 0.5;
+
+                for (var i = 0; i < secondRun.GlyphRun.GlyphClusters.Count; i++)
+                {
+                    var cluster = secondRun.GlyphRun.GlyphClusters[i];
+                    var advance = secondRun.GlyphRun.GlyphAdvances[i];
+
+                    hit = layout.HitTestPoint(new Point(currentX, 0));
+
+                    Assert.Equal(cluster, hit.CharacterHit.FirstCharacterIndex);
+
+                    var hitRange = layout.HitTestTextRange(hit.CharacterHit.FirstCharacterIndex, hit.CharacterHit.TrailingLength);
+
+                    var distance = hitRange.First().Left + 0.5;
+
+                    Assert.Equal(currentX, distance);
+
+                    currentX += advance;
+                }
+            }
+        }
+
+        [Fact]
+        public void Should_Get_CharacterHit_From_Distance_RTL()
+        {
+            using (Start())
+            { 
+                var text = "أَبْجَدِيَّة عَرَبِيَّة";
+
+                var layout = new TextLayout(
+                  text,
+                  Typeface.Default,
+                  12,
+                  Brushes.Black);
+
+                var textLine = layout.TextLines[0];
+
+                var firstRun = (ShapedTextCharacters)textLine.TextRuns[0];
+
+                var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0];
+
+                var characterHit = textLine.GetCharacterHitFromDistance(0);
+
+                Assert.Equal(firstCluster, characterHit.FirstCharacterIndex);
+
+                Assert.Equal(text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
+
+                var distance = textLine.GetDistanceFromCharacterHit(characterHit);
+
+                Assert.Equal(0, distance);
+
+                distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
+
+                var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0];
+
+                Assert.Equal(firstAdvance, distance, 5);
+
+                var rect = layout.HitTestTextPosition(22);
+
+                Assert.Equal(firstAdvance, rect.Left, 5);
+
+                rect = layout.HitTestTextPosition(23);
+
+                Assert.Equal(0, rect.Left, 5);
+            }
+        }
+
         private static IDisposable Start()
         {
             var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

+ 9 - 8
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@@ -867,28 +867,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
 
                 var textBounds = textLine.GetTextBounds(0, 4);
 
-                var firstRun = textLine.TextRuns[1] as ShapedTextCharacters;
+                var secondRun = textLine.TextRuns[1] as ShapedTextCharacters;
 
                 Assert.Equal(1, textBounds.Count);
-                Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
+                Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
 
                 textBounds = textLine.GetTextBounds(4, 3);
 
-                var secondRun = textLine.TextRuns[0] as ShapedTextCharacters;
+                var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
 
                 Assert.Equal(1, textBounds.Count);
 
                 Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length));
-                Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
+                Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
 
                 textBounds = textLine.GetTextBounds(0, 5);
 
                 Assert.Equal(2, textBounds.Count);
                 Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length)));
 
-                Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
-                Assert.Equal(7.201171875, textBounds[1].Rectangle.Width);
-                Assert.Equal(textLine.Start + 7.201171875, textBounds[1].Rectangle.Right);
+                Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
+                Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
+                Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right);
+                Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
 
                 textBounds = textLine.GetTextBounds(0, text.Length);
 
@@ -896,7 +897,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
                 Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
                 Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
             }
-        }
+        }       
 
         private class FixedRunsTextSource : ITextSource
         {