Browse Source

Merge pull request #3386 from AvaloniaUI/glcontrol

GLControl
pull/3926/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
ceb2f09f9e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog.NetCore/Program.cs
  2. 4
      samples/ControlCatalog/ControlCatalog.csproj
  3. 1
      samples/ControlCatalog/MainView.xaml
  4. 29
      samples/ControlCatalog/Pages/OpenGlPage.xaml
  5. 401
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  6. BIN
      samples/ControlCatalog/Pages/teapot.bin
  7. 50
      src/Avalonia.Base/Utilities/DisposableLock.cs
  8. 86
      src/Avalonia.Native/GlPlatformFeature.cs
  9. 3
      src/Avalonia.Native/PopupImpl.cs
  10. 5
      src/Avalonia.Native/WindowImpl.cs
  11. 6
      src/Avalonia.Native/WindowImplBase.cs
  12. 2
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  13. 55
      src/Avalonia.OpenGL/EglContext.cs
  14. 56
      src/Avalonia.OpenGL/EglDisplay.cs
  15. 17
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  16. 24
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  17. 37
      src/Avalonia.OpenGL/EglInterface.cs
  18. 4
      src/Avalonia.OpenGL/EglSurface.cs
  19. 72
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  20. 4674
      src/Avalonia.OpenGL/GlConsts.cs
  21. 8
      src/Avalonia.OpenGL/GlDisplayType.cs
  22. 113
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  23. 284
      src/Avalonia.OpenGL/GlInterface.cs
  24. 68
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  25. 22
      src/Avalonia.OpenGL/GlVersion.cs
  26. 11
      src/Avalonia.OpenGL/IGlContext.cs
  27. 11
      src/Avalonia.OpenGL/IGlDisplay.cs
  28. 2
      src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs
  29. 9
      src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs
  30. 3
      src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs
  31. 13
      src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs
  32. 46
      src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs
  33. 215
      src/Avalonia.OpenGL/OpenGlControlBase.cs
  34. 45
      src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs
  35. 41
      src/Avalonia.X11/Glx/Glx.cs
  36. 2
      src/Avalonia.X11/Glx/GlxConsts.cs
  37. 56
      src/Avalonia.X11/Glx/GlxContext.cs
  38. 134
      src/Avalonia.X11/Glx/GlxDisplay.cs
  39. 14
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  40. 13
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  41. 12
      src/Avalonia.X11/X11Platform.cs
  42. 2
      src/Avalonia.X11/X11Window.cs
  43. 44
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  44. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  45. 15
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  46. 2
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs
  47. 7
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs
  48. 73
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
  49. 46
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  50. 81
      src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs
  51. 12
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  52. 68
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  53. 8
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  54. 5
      src/Windows/Avalonia.Win32/WindowImpl.cs

2
samples/ControlCatalog.NetCore/Program.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@ -6,6 +7,7 @@ using System.Threading;
using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs;
using Avalonia.OpenGL;
namespace ControlCatalog.NetCore
{

4
samples/ControlCatalog/ControlCatalog.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
@ -17,6 +18,7 @@
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Italic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Regular.ttf" />
<EmbeddedResource Include="Pages\teapot.bin" />
</ItemGroup>
<ItemGroup>

1
samples/ControlCatalog/MainView.xaml

@ -47,6 +47,7 @@
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="OpenGL"><pages:OpenGlPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>

29
samples/ControlCatalog/Pages/OpenGlPage.xaml

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.OpenGlPage"
xmlns:pages="clr-namespace:ControlCatalog.Pages">
<Grid>
<pages:OpenGlPageControl x:Name="GL"/>
<StackPanel>
<TextBlock Text="{Binding #GL.Info}"/>
</StackPanel>
<Grid ColumnDefinitions="*,Auto" Margin="20">
<StackPanel Grid.Column="1" MinWidth="300">
<TextBlock>Yaw</TextBlock>
<Slider Value="{Binding Yaw, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<TextBlock>Pitch</TextBlock>
<Slider Value="{Binding Pitch, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<TextBlock>Roll</TextBlock>
<Slider Value="{Binding Roll, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock>
</StackPanel>
<Slider Value="{Binding Disco, Mode=TwoWay, ElementName=GL}" Maximum="1"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

401
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -0,0 +1,401 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using static Avalonia.OpenGL.GlConsts;
// ReSharper disable StringLiteralTypo
namespace ControlCatalog.Pages
{
public class OpenGlPage : UserControl
{
}
public class OpenGlPageControl : OpenGlControlBase
{
private float _yaw;
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v);
public float Yaw
{
get => _yaw;
set => SetAndRaise(YawProperty, ref _yaw, value);
}
private float _pitch;
public static readonly DirectProperty<OpenGlPageControl, float> PitchProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v);
public float Pitch
{
get => _pitch;
set => SetAndRaise(PitchProperty, ref _pitch, value);
}
private float _roll;
public static readonly DirectProperty<OpenGlPageControl, float> RollProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Roll", o => o.Roll, (o, v) => o.Roll = v);
public float Roll
{
get => _roll;
set => SetAndRaise(RollProperty, ref _roll, value);
}
private float _disco;
public static readonly DirectProperty<OpenGlPageControl, float> DiscoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Disco", o => o.Disco, (o, v) => o.Disco = v);
public float Disco
{
get => _disco;
set => SetAndRaise(DiscoProperty, ref _disco, value);
}
private string _info;
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v);
public string Info
{
get => _info;
private set => SetAndRaise(InfoProperty, ref _info, value);
}
static OpenGlPageControl()
{
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty);
}
private int _vertexShader;
private int _fragmentShader;
private int _shaderProgram;
private int _vertexBufferObject;
private int _indexBufferObject;
private int _vertexArrayObject;
private GlExtrasInterface _glExt;
private string GetShader(bool fragment, string shader)
{
var version = (GlVersion.Type == GlProfileType.OpenGL ?
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 :
100);
var data = "#version " + version + "\n";
if (GlVersion.Type == GlProfileType.OpenGLES)
data += "precision mediump float;\n";
if (version >= 150)
{
shader = shader.Replace("attribute", "in");
if (fragment)
shader = shader
.Replace("varying", "in")
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;")
.Replace("gl_FragColor", "outFragColor");
else
shader = shader.Replace("varying", "out");
}
data += shader;
return data;
}
private string VertexShaderSource => GetShader(false, @"
attribute vec3 aPos;
attribute vec3 aNormal;
uniform mat4 uModel;
uniform mat4 uProjection;
uniform mat4 uView;
varying vec3 FragPos;
varying vec3 VecPos;
varying vec3 Normal;
uniform float uTime;
uniform float uDisco;
void main()
{
float discoScale = sin(uTime * 10.0) / 10.0;
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0;
float scale = 1.0 + uDisco * discoScale;
vec3 scaledPos = aPos;
scaledPos.x = scaledPos.x * distortionX;
scaledPos *= scale;
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0);
FragPos = vec3(uModel * vec4(aPos, 1.0));
VecPos = aPos;
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0)));
}
");
private string FragmentShaderSource => GetShader(true, @"
varying vec3 FragPos;
varying vec3 VecPos;
varying vec3 Normal;
uniform float uMaxY;
uniform float uMinY;
uniform float uTime;
uniform float uDisco;
//DECLAREGLFRAG
void main()
{
float y = (VecPos.y - uMinY) / (uMaxY - uMinY);
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0);
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0);
vec3 discoColor = vec3(
0.5 + abs(0.5 - y) * cos(uTime * 10.0),
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)),
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0))));
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25);
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco;
float ambientStrength = 0.3;
vec3 lightColor = vec3(1.0, 1.0, 1.0);
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0);
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 result = (ambient + diffuse) * objectColor;
gl_FragColor = vec4(result, 1.0);
}
");
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct Vertex
{
public Vector3 Position;
public Vector3 Normal;
}
private readonly Vertex[] _points;
private readonly ushort[] _indices;
private readonly float _minY;
private readonly float _maxY;
public OpenGlPageControl()
{
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)))
{
var buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
var points = new float[buf.Length / 4];
Buffer.BlockCopy(buf, 0, points, 0, buf.Length);
buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
_indices = new ushort[buf.Length / 2];
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length);
_points = new Vertex[points.Length / 3];
for (var primitive = 0; primitive < points.Length / 3; primitive++)
{
var srci = primitive * 3;
_points[primitive] = new Vertex
{
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2])
};
}
for (int i = 0; i < _indices.Length; i += 3)
{
Vector3 a = _points[_indices[i]].Position;
Vector3 b = _points[_indices[i + 1]].Position;
Vector3 c = _points[_indices[i + 2]].Position;
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b));
_points[_indices[i]].Normal += normal;
_points[_indices[i + 1]].Normal += normal;
_points[_indices[i + 2]].Normal += normal;
}
for (int i = 0; i < _points.Length; i++)
{
_points[i].Normal = Vector3.Normalize(_points[i].Normal);
_maxY = Math.Max(_maxY, _points[i].Position.Y);
_minY = Math.Min(_minY, _points[i].Position.Y);
}
}
}
private void CheckError(GlInterface gl)
{
int err;
while ((err = gl.GetError()) != GL_NO_ERROR)
Console.WriteLine(err);
}
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)}";
// Load the source of the vertex shader and compile it.
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER);
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource));
// Load the source of the fragment shader and compile it.
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER);
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource));
// Create the shader program, attach the vertex and fragment shaders and link the program.
_shaderProgram = GL.CreateProgram();
GL.AttachShader(_shaderProgram, _vertexShader);
GL.AttachShader(_shaderProgram, _fragmentShader);
const int positionLocation = 0;
const int normalLocation = 1;
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos");
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal");
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram));
CheckError(GL);
// Create the vertex buffer object (VBO) for the vertex data.
_vertexBufferObject = GL.GenBuffer();
// Bind the VBO and copy the vertex data into it.
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
CheckError(GL);
var vertexSize = Marshal.SizeOf<Vertex>();
fixed (void* pdata = _points)
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize),
new IntPtr(pdata), GL_STATIC_DRAW);
_indexBufferObject = GL.GenBuffer();
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
CheckError(GL);
fixed (void* pdata = _indices)
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);
CheckError(GL);
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
0, vertexSize, IntPtr.Zero);
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT,
0, vertexSize, new IntPtr(12));
GL.EnableVertexAttribArray(positionLocation);
GL.EnableVertexAttribArray(normalLocation);
CheckError(GL);
}
protected override void OnOpenGlDeinit(GlInterface GL, int fb)
{
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
_glExt.BindVertexArray(0);
GL.UseProgram(0);
// Delete all resources.
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject });
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject });
GL.DeleteProgram(_shaderProgram);
GL.DeleteShader(_fragmentShader);
GL.DeleteShader(_vertexShader);
}
static Stopwatch St = Stopwatch.StartNew();
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb)
{
gl.ClearColor(0, 0, 0, 0);
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.Enable(GL_DEPTH_TEST);
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height);
var GL = gl;
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
_glExt.BindVertexArray(_vertexArrayObject);
GL.UseProgram(_shaderProgram);
CheckError(GL);
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height),
0.01f, 1000);
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll);
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel");
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView");
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection");
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY");
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY");
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime");
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco");
GL.UniformMatrix4fv(modelLoc, 1, false, &model);
GL.UniformMatrix4fv(viewLoc, 1, false, &view);
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection);
GL.Uniform1f(maxYLoc, _maxY);
GL.Uniform1f(minYLoc, _minY);
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds);
GL.Uniform1f(discoLoc, _disco);
CheckError(GL);
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero);
CheckError(GL);
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];
}
}
}
}

BIN
samples/ControlCatalog/Pages/teapot.bin

Binary file not shown.

50
src/Avalonia.Base/Utilities/DisposableLock.cs

@ -0,0 +1,50 @@
using System;
using System.Threading;
namespace Avalonia.Utilities
{
public class DisposableLock
{
private readonly object _lock = new object();
/// <summary>
/// Tries to take a lock
/// </summary>
/// <returns>IDisposable if succeeded to obtain the lock</returns>
public IDisposable TryLock()
{
if (Monitor.TryEnter(_lock))
return new UnlockDisposable(_lock);
return null;
}
/// <summary>
/// Enters a waiting lock
/// </summary>
public IDisposable Lock()
{
Monitor.Enter(_lock);
return new UnlockDisposable(_lock);
}
private sealed class UnlockDisposable : IDisposable
{
private object _lock;
public UnlockDisposable(object @lock)
{
_lock = @lock;
}
public void Dispose()
{
object @lock = Interlocked.Exchange(ref _lock, null);
if (@lock != null)
{
Monitor.Exit(@lock);
}
}
}
}
}

86
src/Avalonia.Native/GlPlatformFeature.cs

@ -8,42 +8,58 @@ namespace Avalonia.Native
{
class GlPlatformFeature : IWindowingPlatformGlFeature
{
private readonly IAvnGlDisplay _display;
public GlPlatformFeature(IAvnGlDisplay display)
{
_display = display;
var immediate = display.CreateContext(null);
var deferred = display.CreateContext(immediate);
GlDisplay = new GlDisplay(display, immediate.SampleCount, immediate.StencilSize);
ImmediateContext = new GlContext(Display, immediate);
DeferredContext = new GlContext(Display, deferred);
int major, minor;
GlInterface glInterface;
using (immediate.MakeCurrent())
{
var basic = new GlBasicInfoInterface(display.GetProcAddress);
basic.GetIntegerv(GlConsts.GL_MAJOR_VERSION, out major);
basic.GetIntegerv(GlConsts.GL_MINOR_VERSION, out minor);
_version = new GlVersion(GlProfileType.OpenGL, major, minor);
glInterface = new GlInterface(_version, (name) =>
{
var rv = _display.GetProcAddress(name);
return rv;
});
}
GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize);
ImmediateContext = new GlContext(GlDisplay, immediate, _version);
DeferredContext = new GlContext(GlDisplay, deferred, _version);
}
public IGlContext ImmediateContext { get; }
internal IGlContext ImmediateContext { get; }
public IGlContext MainContext => DeferredContext;
internal GlContext DeferredContext { get; }
internal GlDisplay GlDisplay;
public GlDisplay Display => GlDisplay;
private readonly GlVersion _version;
public IGlContext CreateContext() => new GlContext(GlDisplay,
_display.CreateContext(((GlContext)ImmediateContext).Context), _version);
}
class GlDisplay : IGlDisplay
class GlDisplay
{
private readonly IAvnGlDisplay _display;
public GlDisplay(IAvnGlDisplay display, int sampleCount, int stencilSize)
public GlDisplay(IAvnGlDisplay display, GlInterface glInterface, int sampleCount, int stencilSize)
{
_display = display;
SampleCount = sampleCount;
StencilSize = stencilSize;
GlInterface = new GlInterface((name, optional) =>
{
var rv = _display.GetProcAddress(name);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException($"{name} not found in system OpenGL");
return rv;
});
GlInterface = glInterface;
}
public GlDisplayType Type => GlDisplayType.OpenGL2;
public GlInterface GlInterface { get; }
public int SampleCount { get; }
@ -55,19 +71,26 @@ namespace Avalonia.Native
class GlContext : IGlContext
{
public IAvnGlContext Context { get; }
private readonly GlDisplay _display;
public IAvnGlContext Context { get; private set; }
public GlContext(GlDisplay display, IAvnGlContext context)
public GlContext(GlDisplay display, IAvnGlContext context, GlVersion version)
{
Display = display;
_display = display;
Context = context;
Version = version;
}
public IGlDisplay Display { get; }
public GlVersion Version { get; }
public GlInterface GlInterface => _display.GlInterface;
public int SampleCount => _display.SampleCount;
public int StencilSize => _display.StencilSize;
public IDisposable MakeCurrent() => Context.MakeCurrent();
public void MakeCurrent()
public void Dispose()
{
Context.LegacyMakeCurrent();
Context.Dispose();
Context = null;
}
}
@ -75,15 +98,18 @@ namespace Avalonia.Native
class GlPlatformSurfaceRenderTarget : IGlPlatformSurfaceRenderTarget
{
private IAvnGlSurfaceRenderTarget _target;
public GlPlatformSurfaceRenderTarget(IAvnGlSurfaceRenderTarget target)
private readonly IGlContext _context;
public GlPlatformSurfaceRenderTarget(IAvnGlSurfaceRenderTarget target, IGlContext context)
{
_target = target;
_context = context;
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var feature = (GlPlatformFeature)AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
return new GlPlatformSurfaceRenderingSession(feature.Display, _target.BeginDrawing());
return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing());
}
public void Dispose()
@ -97,13 +123,13 @@ namespace Avalonia.Native
{
private IAvnGlSurfaceRenderingSession _session;
public GlPlatformSurfaceRenderingSession(GlDisplay display, IAvnGlSurfaceRenderingSession session)
public GlPlatformSurfaceRenderingSession(IGlContext context, IAvnGlSurfaceRenderingSession session)
{
Display = display;
Context = context;
_session = session;
}
public IGlDisplay Display { get; }
public IGlContext Context { get; }
public PixelSize Size
{
@ -129,14 +155,16 @@ namespace Avalonia.Native
class GlPlatformSurface : IGlPlatformSurface
{
private readonly IAvnWindowBase _window;
private readonly IGlContext _context;
public GlPlatformSurface(IAvnWindowBase window)
public GlPlatformSurface(IAvnWindowBase window, IGlContext context)
{
_window = window;
_context = context;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget());
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), _context);
}
}

3
src/Avalonia.Native/PopupImpl.cs

@ -21,7 +21,8 @@ namespace Avalonia.Native
_glFeature = glFeature;
using (var e = new PopupEvents(this))
{
Init(factory.CreatePopup(e, _opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context);
}
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}

5
src/Avalonia.Native/WindowImpl.cs

@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
@ -21,8 +22,8 @@ namespace Avalonia.Native
_glFeature = glFeature;
using (var e = new WindowEvents(this))
{
Init(_native = factory.CreateWindow(e,
_opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context);
}
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);

6
src/Avalonia.Native/WindowImplBase.cs

@ -56,6 +56,7 @@ namespace Avalonia.Native
private Size _lastRenderedLogicalSize;
private double _savedScaling;
private GlPlatformSurface _glSurface;
private IGlContext _glContext;
internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
{
@ -67,13 +68,14 @@ namespace Avalonia.Native
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
}
protected void Init(IAvnWindowBase window, IAvnScreens screens)
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
{
_native = window;
_glContext = glContext;
Handle = new MacOSTopLevelWindowHandle(window);
if (_gpu)
_glSurface = new GlPlatformSurface(window);
_glSurface = new GlPlatformSurface(window, _glContext);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;

2
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -2,10 +2,12 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>

55
src/Avalonia.OpenGL/EglContext.cs

@ -1,6 +1,7 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL
{
@ -10,17 +11,27 @@ namespace Avalonia.OpenGL
private readonly EglInterface _egl;
private readonly object _lock = new object();
public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, EglSurface offscreenSurface)
public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, EglSurface offscreenSurface,
GlVersion version, int sampleCount, int stencilSize)
{
_disp = display;
_egl = egl;
Context = ctx;
OffscreenSurface = offscreenSurface;
Version = version;
SampleCount = sampleCount;
StencilSize = stencilSize;
using (MakeCurrent())
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, b => _egl.GetProcAddress(b));
}
public IntPtr Context { get; }
public EglSurface OffscreenSurface { get; }
public IGlDisplay Display => _disp;
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
public EglDisplay Display => _disp;
public IDisposable Lock()
{
@ -28,17 +39,53 @@ namespace Avalonia.OpenGL
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent()
class RestoreContext : IDisposable
{
private readonly EglInterface _egl;
private readonly IntPtr _display;
private IntPtr _context, _read, _draw;
public RestoreContext(EglInterface egl, IntPtr defDisplay)
{
_egl = egl;
_display = _egl.GetCurrentDisplay();
if (_display == IntPtr.Zero)
_display = defDisplay;
_context = _egl.GetCurrentContext();
_read = _egl.GetCurrentSurface(EGL_READ);
_draw = _egl.GetCurrentSurface(EGL_DRAW);
}
public void Dispose()
{
_egl.MakeCurrent(_display, _draw, _read, _context);
}
}
public IDisposable MakeCurrent()
{
var old = new RestoreContext(_egl, _disp.Handle);
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
return old;
}
public void MakeCurrent(EglSurface surface)
public IDisposable MakeCurrent(EglSurface surface)
{
var old = new RestoreContext(_egl, _disp.Handle);
var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
return old;
}
public void Dispose()
{
_egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose();
}
}
}

56
src/Avalonia.OpenGL/EglDisplay.cs

@ -6,7 +6,7 @@ using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL
{
public class EglDisplay : IGlDisplay
public class EglDisplay
{
private readonly EglInterface _egl;
private readonly IntPtr _display;
@ -16,6 +16,9 @@ namespace Avalonia.OpenGL
public IntPtr Handle => _display;
private AngleOptions.PlatformApi? _angleApi;
private int _sampleCount;
private int _stencilSize;
private GlVersion _version;
public EglDisplay(EglInterface egl) : this(egl, -1, IntPtr.Zero, null)
{
@ -76,13 +79,6 @@ namespace Avalonia.OpenGL
foreach (var cfg in new[]
{
new
{
Attributes = new[] {EGL_NONE},
Api = EGL_OPENGL_API,
RenderableTypeBit = EGL_OPENGL_BIT,
Type = GlDisplayType.OpenGL2
},
new
{
Attributes = new[]
@ -92,7 +88,7 @@ namespace Avalonia.OpenGL
},
Api = EGL_OPENGL_ES_API,
RenderableTypeBit = EGL_OPENGL_ES2_BIT,
Type = GlDisplayType.OpenGLES2
Version = new GlVersion(GlProfileType.OpenGLES, 2, 0)
}
})
{
@ -120,14 +116,16 @@ namespace Avalonia.OpenGL
continue;
_contextAttributes = cfg.Attributes;
_surfaceType = surfaceType;
Type = cfg.Type;
_version = cfg.Version;
_egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out _sampleCount);
_egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out _stencilSize);
goto Found;
}
}
}
Found:
if (_contextAttributes == null)
throw new OpenGlException("No suitable EGL config was found");
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b));
}
public EglDisplay() : this(new EglInterface())
@ -135,8 +133,6 @@ namespace Avalonia.OpenGL
}
public GlDisplayType Type { get; }
public GlInterface GlInterface { get; }
public EglInterface EglInterface => _egl;
public EglContext CreateContext(IGlContext share)
{
@ -154,8 +150,8 @@ namespace Avalonia.OpenGL
});
if (surf == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
var rv = new EglContext(this, _egl, ctx, new EglSurface(this, _egl, surf));
rv.MakeCurrent(null);
var rv = new EglContext(this, _egl, ctx, new EglSurface(this, _egl, surf),
_version, _sampleCount, _stencilSize);
return rv;
}
@ -164,17 +160,11 @@ namespace Avalonia.OpenGL
var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var rv = new EglContext(this, _egl, ctx, offscreenSurface);
var rv = new EglContext(this, _egl, ctx, offscreenSurface, _version, _sampleCount, _stencilSize);
rv.MakeCurrent(null);
return rv;
}
public void ClearContext()
{
if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public EglSurface CreateWindowSurface(IntPtr window)
{
var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE});
@ -182,23 +172,5 @@ namespace Avalonia.OpenGL
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl);
return new EglSurface(this, _egl, s);
}
public int SampleCount
{
get
{
_egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out var rv);
return rv;
}
}
public int StencilSize
{
get
{
_egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out var rv);
return rv;
}
}
}
}

17
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@ -5,9 +5,14 @@ namespace Avalonia.OpenGL
{
public class EglGlPlatformFeature : IWindowingPlatformGlFeature
{
public IGlDisplay Display { get; set; }
public IGlContext ImmediateContext { get; set; }
public EglContext DeferredContext { get; set; }
private EglDisplay _display;
public EglDisplay Display => _display;
public IGlContext CreateContext()
{
return _display.CreateContext(DeferredContext);
}
public EglContext DeferredContext { get; private set; }
public IGlContext MainContext => DeferredContext;
public static void TryInitialize()
{
@ -21,12 +26,10 @@ namespace Avalonia.OpenGL
try
{
var disp = new EglDisplay();
var ctx = disp.CreateContext(null);
return new EglGlPlatformFeature
{
Display = disp,
ImmediateContext = ctx,
DeferredContext = (EglContext)disp.CreateContext(ctx)
_display = disp,
DeferredContext = disp.CreateContext(null)
};
}
catch(Exception e)

24
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -16,9 +16,9 @@ namespace Avalonia.OpenGL
private readonly EglContext _context;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
public EglGlPlatformSurface(EglDisplay display, EglContext context, IEglWindowGlPlatformSurfaceInfo info)
public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info)
{
_display = display;
_display = context.Display;
_context = context;
_info = info;
}
@ -58,12 +58,12 @@ namespace Avalonia.OpenGL
{
if (IsCorrupted)
throw new RenderTargetCorruptedException();
_context.MakeCurrent(_glSurface);
var restoreContext = _context.MakeCurrent(_glSurface);
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
return new Session(_display, _context, _glSurface, _info, l);
return new Session(_display, _context, _glSurface, _info, l, restoreContext);
}
catch
{
@ -79,32 +79,34 @@ namespace Avalonia.OpenGL
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private IDisposable _lock;
private readonly IDisposable _restoreContext;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
IDisposable @lock, IDisposable restoreContext)
{
_context = context;
_display = display;
_glSurface = glSurface;
_info = info;
_lock = @lock;
_restoreContext = restoreContext;
}
public void Dispose()
{
_context.Display.GlInterface.Flush();
_context.GlInterface.Flush();
_display.EglInterface.WaitGL();
_glSurface.SwapBuffers();
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
_context.Display.ClearContext();
_display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
_restoreContext.Dispose();
_lock.Dispose();
}
public IGlDisplay Display => _context.Display;
public IGlContext Context => _context;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }

37
src/Avalonia.OpenGL/EglInterface.cs

@ -24,7 +24,7 @@ namespace Avalonia.OpenGL
[DllImport("libegl.dll", CharSet = CharSet.Ansi)]
static extern IntPtr eglGetProcAddress(string proc);
static Func<string, bool, IntPtr> Load()
static Func<string, IntPtr> Load()
{
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
@ -34,23 +34,17 @@ namespace Avalonia.OpenGL
var disp = eglGetProcAddress("eglGetPlatformDisplayEXT");
if (disp == IntPtr.Zero)
throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point");
return (name, optional) =>
{
var r = eglGetProcAddress(name);
if (r == IntPtr.Zero && !optional)
throw new OpenGlException($"Entry point {r} is not found");
return r;
};
return eglGetProcAddress;
}
throw new PlatformNotSupportedException();
}
static Func<string, bool, IntPtr> Load(string library)
static Func<string, IntPtr> Load(string library)
{
var dyn = AvaloniaLocator.Current.GetService<IDynamicLibraryLoader>();
var lib = dyn.LoadLibrary(library);
return (s, o) => dyn.GetProcAddress(lib, s, o);
return (s) => dyn.GetProcAddress(lib, s, true);
}
// ReSharper disable UnassignedGetOnlyAutoProperty
@ -63,7 +57,8 @@ namespace Avalonia.OpenGL
public EglGetDisplay GetDisplay { get; }
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
[GlEntryPoint("eglGetPlatformDisplayEXT", true)]
[GlEntryPoint("eglGetPlatformDisplayEXT")]
[GlOptionalEntryPoint]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
@ -87,6 +82,10 @@ namespace Avalonia.OpenGL
IntPtr share, int[] attrs);
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
public delegate bool EglDestroyContext(IntPtr display, IntPtr context);
[GlEntryPoint("eglDestroyContext")]
public EglDestroyContext DestroyContext { get; }
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[GlEntryPoint("eglCreatePbufferSurface")]
@ -96,6 +95,18 @@ namespace Avalonia.OpenGL
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
public delegate IntPtr EglGetCurrentContext();
[GlEntryPoint("eglGetCurrentContext")]
public EglGetCurrentContext GetCurrentContext { get; }
public delegate IntPtr EglGetCurrentDisplay();
[GlEntryPoint("eglGetCurrentDisplay")]
public EglGetCurrentContext GetCurrentDisplay { get; }
public delegate IntPtr EglGetCurrentSurface(int readDraw);
[GlEntryPoint("eglGetCurrentSurface")]
public EglGetCurrentSurface GetCurrentSurface { get; }
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
@ -120,9 +131,9 @@ namespace Avalonia.OpenGL
[GlEntryPoint("eglWaitClient")]
public EglWaitGL WaitClient { get; }
public delegate bool EglWaitNative();
public delegate bool EglWaitNative(int engine);
[GlEntryPoint("eglWaitNative")]
public EglWaitGL WaitNative { get; }
public EglWaitNative WaitNative { get; }
public delegate IntPtr EglQueryString(IntPtr display, int i);

4
src/Avalonia.OpenGL/EglSurface.cs

@ -21,8 +21,6 @@ namespace Avalonia.OpenGL
}
public override bool IsInvalid => handle == IntPtr.Zero;
public IGlDisplay Display => _display;
public void SwapBuffers() => _egl.SwapBuffers(_display.Handle, handle);
}
}
}

72
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlBasicInfoInterface : GlBasicInfoInterface<object>
{
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null)
{
}
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
{
}
public delegate void GlGetIntegerv(int name, out int rv);
public delegate IntPtr GlGetString(int v);
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)
{
}
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; }
public string GetString(int v)
{
var ptr = GetStringNative(v);
if (ptr != IntPtr.Zero)
return Marshal.PtrToStringAnsi(ptr);
return null;
}
public string GetString(int v, int index)
{
var ptr = GetStringiNative(v, index);
if (ptr != IntPtr.Zero)
return Marshal.PtrToStringAnsi(ptr);
return null;
}
public List<string> GetExtensions()
{
var sp = GetString(GlConsts.GL_EXTENSIONS);
if (sp != null)
return sp.Split(' ').ToList();
GetIntegerv(GlConsts.GL_NUM_EXTENSIONS, out int count);
var rv = new List<string>(count);
for (var c = 0; c < count; c++)
rv.Add(GetString(GlConsts.GL_EXTENSIONS, c));
return rv;
}
}
}

4674
src/Avalonia.OpenGL/GlConsts.cs

File diff suppressed because it is too large

8
src/Avalonia.OpenGL/GlDisplayType.cs

@ -1,8 +0,0 @@
namespace Avalonia.OpenGL
{
public enum GlDisplayType
{
OpenGL2,
OpenGLES2
}
}

113
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,16 +2,117 @@ using System;
namespace Avalonia.OpenGL
{
public interface IGlEntryPointAttribute
{
IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress);
}
public interface IGlEntryPointAttribute<in TContext>
{
IntPtr GetProcAddress(TContext context, Func<string, IntPtr> getProcAddress);
}
[AttributeUsage(AttributeTargets.Property)]
public class GlEntryPointAttribute : Attribute
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)
{
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>
{
public string EntryPoint { get; }
public bool Optional { get; }
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly int _minVersionMajor;
private readonly int _minVersionMinor;
public GlEntryPointAttribute(string entryPoint, bool optional = false)
public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor,
int minVersionMinor)
{
EntryPoint = entryPoint;
Optional = optional;
_entry = entry;
_profile = profile;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
}
public GlMinVersionEntryPoint(string entry, int minVersionMajor,
int minVersionMinor)
{
_entry = entry;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
}
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
{
if(_profile.HasValue && context.Version.Type != _profile)
return IntPtr.Zero;
if(context.Version.Major<_minVersionMajor)
return IntPtr.Zero;
if (context.Version.Major == _minVersionMajor && context.Version.Minor < _minVersionMinor)
return IntPtr.Zero;
return getProcAddress(_entry);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlExtensionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
{
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly string _extension;
public GlExtensionEntryPoint(string entry, GlProfileType profile, string extension)
{
_entry = entry;
_profile = profile;
_extension = extension;
}
public GlExtensionEntryPoint(string entry, string extension)
{
_entry = entry;
_extension = extension;
}
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
{
// Ignore different profile 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))
return IntPtr.Zero;
return getProcAddress(_entry);
}
}
}

284
src/Avalonia.OpenGL/GlInterface.cs

@ -1,31 +1,60 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Platform.Interop;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL
{
public delegate IntPtr GlGetProcAddressDelegate(string procName);
public class GlInterface : GlInterfaceBase
public unsafe class GlInterface : GlBasicInfoInterface<GlInterface.GlContextInfo>
{
public string Version { get; }
public string Vendor { get; }
public string Renderer { get; }
public GlContextInfo ContextInfo { get; }
public GlInterface(Func<string, bool, IntPtr> getProcAddress) : base(getProcAddress)
public class GlContextInfo
{
public GlVersion Version { get; }
public HashSet<string> Extensions { get; }
public GlContextInfo(GlVersion version, HashSet<string> extensions)
{
Version = version;
Extensions = extensions;
}
public static GlContextInfo Create(GlVersion version, Func<string, IntPtr> getProcAddress)
{
var basicInfoInterface = new GlBasicInfoInterface(getProcAddress);
var exts = basicInfoInterface.GetExtensions();
return new GlContextInfo(version, new HashSet<string>(exts));
}
}
private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress, info)
{
ContextInfo = info;
Version = GetString(GlConsts.GL_VERSION);
Renderer = GetString(GlConsts.GL_RENDERER);
Vendor = GetString(GlConsts.GL_VENDOR);
Vendor = GetString(GlConsts.GL_VENDOR);
}
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
public GlInterface(GlVersion version, Func<string, IntPtr> getProcAddress) : this(
GlContextInfo.Create(version, getProcAddress), getProcAddress)
{
}
public GlInterface(GlVersion version, Func<Utf8Buffer, IntPtr> n) : this(version, ConvertNative(n))
{
}
public static GlInterface FromNativeUtf8GetProcAddress(Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface(getProcAddress);
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));
@ -70,6 +99,249 @@ namespace Avalonia.OpenGL
[GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
public delegate void GlGenFramebuffers(int count, int[] res);
[GlEntryPoint("glGenFramebuffers")]
public GlGenFramebuffers GenFramebuffers { get; }
public delegate void GlDeleteFramebuffers(int count, int[] framebuffers);
[GlEntryPoint("glDeleteFramebuffers")]
public GlDeleteFramebuffers DeleteFramebuffers { get; }
public delegate void GlBindFramebuffer(int target, int fb);
[GlEntryPoint("glBindFramebuffer")]
public GlBindFramebuffer BindFramebuffer { get; }
public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")]
public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
public delegate void GlGenRenderbuffers(int count, int[] res);
[GlEntryPoint("glGenRenderbuffers")]
public GlGenRenderbuffers GenRenderbuffers { get; }
public delegate void GlDeleteRenderbuffers(int count, int[] renderbuffers);
[GlEntryPoint("glDeleteRenderbuffers")]
public GlDeleteTextures DeleteRenderbuffers { get; }
public delegate void GlBindRenderbuffer(int target, int fb);
[GlEntryPoint("glBindRenderbuffer")]
public GlBindRenderbuffer BindRenderbuffer { get; }
public delegate void GlRenderbufferStorage(int target, int internalFormat, int width, int height);
[GlEntryPoint("glRenderbufferStorage")]
public GlRenderbufferStorage RenderbufferStorage { get; }
public delegate void GlFramebufferRenderbuffer(int target, int attachment,
int renderbufferTarget, int renderbuffer);
[GlEntryPoint("glFramebufferRenderbuffer")]
public GlFramebufferRenderbuffer FramebufferRenderbuffer { get; }
public delegate void GlGenTextures(int count, int[] res);
[GlEntryPoint("glGenTextures")]
public GlGenTextures GenTextures { get; }
public delegate void GlBindTexture(int target, int fb);
[GlEntryPoint("glBindTexture")]
public GlBindTexture BindTexture { get; }
public delegate void GlDeleteTextures(int count, int[] textures);
[GlEntryPoint("glDeleteTextures")]
public GlDeleteTextures DeleteTextures { get; }
public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border,
int format, int type, IntPtr data);
[GlEntryPoint("glTexImage2D")]
public GlTexImage2D TexImage2D { get; }
public delegate void GlTexParameteri(int target, int name, int value);
[GlEntryPoint("glTexParameteri")]
public GlTexParameteri TexParameteri { get; }
public delegate void GlFramebufferTexture2D(int target, int attachment,
int texTarget, int texture, int level);
[GlEntryPoint("glFramebufferTexture2D")]
public GlFramebufferTexture2D FramebufferTexture2D { get; }
public delegate int GlCreateShader(int shaderType);
[GlEntryPoint("glCreateShader")]
public GlCreateShader CreateShader { get; }
public delegate void GlShaderSource(int shader, int count, IntPtr strings, IntPtr lengths);
[GlEntryPoint("glShaderSource")]
public GlShaderSource ShaderSource { get; }
public void ShaderSourceString(int shader, string source)
{
using (var b = new Utf8Buffer(source))
{
var ptr = b.DangerousGetHandle();
var len = new IntPtr(b.ByteLen);
ShaderSource(shader, 1, new IntPtr(&ptr), new IntPtr(&len));
}
}
public delegate void GlCompileShader(int shader);
[GlEntryPoint("glCompileShader")]
public GlCompileShader CompileShader { get; }
public delegate void GlGetShaderiv(int shader, int name, int* parameters);
[GlEntryPoint("glGetShaderiv")]
public GlGetShaderiv GetShaderiv { get; }
public delegate void GlGetShaderInfoLog(int shader, int maxLength, out int length, void*infoLog);
[GlEntryPoint("glGetShaderInfoLog")]
public GlGetShaderInfoLog GetShaderInfoLog { get; }
public unsafe string CompileShaderAndGetError(int shader, string source)
{
ShaderSourceString(shader, source);
CompileShader(shader);
int compiled;
GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled != 0)
return null;
int logLength;
GetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength == 0)
logLength = 4096;
var logData = new byte[logLength];
int len;
fixed (void* ptr = logData)
GetShaderInfoLog(shader, logLength, out len, ptr);
return Encoding.UTF8.GetString(logData,0, len);
}
public delegate int GlCreateProgram();
[GlEntryPoint("glCreateProgram")]
public GlCreateProgram CreateProgram { get; }
public delegate void GlAttachShader(int program, int shader);
[GlEntryPoint("glAttachShader")]
public GlAttachShader AttachShader { get; }
public delegate void GlLinkProgram(int program);
[GlEntryPoint("glLinkProgram")]
public GlLinkProgram LinkProgram { get; }
public delegate void GlGetProgramiv(int program, int name, int* parameters);
[GlEntryPoint("glGetProgramiv")]
public GlGetProgramiv GetProgramiv { get; }
public delegate void GlGetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
[GlEntryPoint("glGetProgramInfoLog")]
public GlGetProgramInfoLog GetProgramInfoLog { get; }
public unsafe string LinkProgramAndGetError(int program)
{
LinkProgram(program);
int compiled;
GetProgramiv(program, GL_LINK_STATUS, &compiled);
if (compiled != 0)
return null;
int logLength;
GetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
var logData = new byte[logLength];
int len;
fixed (void* ptr = logData)
GetProgramInfoLog(program, logLength, out len, ptr);
return Encoding.UTF8.GetString(logData,0, len);
}
public delegate void GlBindAttribLocation(int program, int index, IntPtr name);
[GlEntryPoint("glBindAttribLocation")]
public GlBindAttribLocation BindAttribLocation { get; }
public void BindAttribLocationString(int program, int index, string name)
{
using (var b = new Utf8Buffer(name))
BindAttribLocation(program, index, b.DangerousGetHandle());
}
public delegate void GlGenBuffers(int len, int[] rv);
[GlEntryPoint("glGenBuffers")]
public GlGenBuffers GenBuffers { get; }
public int GenBuffer()
{
var rv = new int[1];
GenBuffers(1, rv);
return rv[0];
}
public delegate void GlBindBuffer(int target, int buffer);
[GlEntryPoint("glBindBuffer")]
public GlBindBuffer BindBuffer { get; }
public delegate void GlBufferData(int target, IntPtr size, IntPtr data, int usage);
[GlEntryPoint("glBufferData")]
public GlBufferData BufferData { get; }
public delegate int GlGetAttribLocation(int program, IntPtr name);
[GlEntryPoint("glGetAttribLocation")]
public GlGetAttribLocation GetAttribLocation { get; }
public int GetAttribLocationString(int program, string name)
{
using (var b = new Utf8Buffer(name))
return GetAttribLocation(program, b.DangerousGetHandle());
}
public delegate void GlVertexAttribPointer(int index, int size, int type,
int normalized, int stride, IntPtr pointer);
[GlEntryPoint("glVertexAttribPointer")]
public GlVertexAttribPointer VertexAttribPointer { get; }
public delegate void GlEnableVertexAttribArray(int index);
[GlEntryPoint("glEnableVertexAttribArray")]
public GlEnableVertexAttribArray EnableVertexAttribArray { get; }
public delegate void GlUseProgram(int program);
[GlEntryPoint("glUseProgram")]
public GlUseProgram UseProgram { get; }
public delegate void GlDrawArrays(int mode, int first, IntPtr count);
[GlEntryPoint("glDrawArrays")]
public GlDrawArrays DrawArrays { get; }
public delegate void GlDrawElements(int mode, int count, int type, IntPtr indices);
[GlEntryPoint("glDrawElements")]
public GlDrawElements DrawElements { get; }
public delegate int GlGetUniformLocation(int program, IntPtr name);
[GlEntryPoint("glGetUniformLocation")]
public GlGetUniformLocation GetUniformLocation { get; }
public int GetUniformLocationString(int program, string name)
{
using (var b = new Utf8Buffer(name))
return GetUniformLocation(program, b.DangerousGetHandle());
}
public delegate void GlUniform1f(int location, float falue);
[GlEntryPoint("glUniform1f")]
public GlUniform1f Uniform1f { get; }
public delegate void GlUniformMatrix4fv(int location, int count, bool transpose, void* value);
[GlEntryPoint("glUniformMatrix4fv")]
public GlUniformMatrix4fv UniformMatrix4fv { get; }
public delegate void GlEnable(int what);
[GlEntryPoint("glEnable")]
public GlEnable Enable { get; }
public delegate void GlDeleteBuffers(int count, int[] buffers);
[GlEntryPoint("glDeleteBuffers")]
public GlDeleteBuffers DeleteBuffers { get; }
public delegate void GlDeleteProgram(int program);
[GlEntryPoint("glDeleteProgram")]
public GlDeleteProgram DeleteProgram { get; }
public delegate void GlDeleteShader(int shader);
[GlEntryPoint("glDeleteShader")]
public GlDeleteShader DeleteShader { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
}
}

68
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,53 +1,79 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase
public class GlInterfaceBase : GlInterfaceBase<object>
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress)
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 a = prop.GetCustomAttribute<GlEntryPointAttribute>();
if (a != null)
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)
{
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}");
var proc = getProcAddress(a.EntryPoint, a.Optional);
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)
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType));
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, bool, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc, optional) =>
protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = func(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
};
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress))
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context)
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional);
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc);
}
}

22
src/Avalonia.OpenGL/GlVersion.cs

@ -0,0 +1,22 @@
namespace Avalonia.OpenGL
{
public enum GlProfileType
{
OpenGL,
OpenGLES
}
public struct GlVersion
{
public GlProfileType Type { get; }
public int Major { get; }
public int Minor { get; }
public GlVersion(GlProfileType type, int major, int minor)
{
Type = type;
Major = major;
Minor = minor;
}
}
}

11
src/Avalonia.OpenGL/IGlContext.cs

@ -1,8 +1,13 @@
using System;
namespace Avalonia.OpenGL
{
public interface IGlContext
public interface IGlContext : IDisposable
{
IGlDisplay Display { get; }
void MakeCurrent();
GlVersion Version { get; }
GlInterface GlInterface { get; }
int SampleCount { get; }
int StencilSize { get; }
IDisposable MakeCurrent();
}
}

11
src/Avalonia.OpenGL/IGlDisplay.cs

@ -1,11 +0,0 @@
namespace Avalonia.OpenGL
{
public interface IGlDisplay
{
GlDisplayType Type { get; }
GlInterface GlInterface { get; }
void ClearContext();
int SampleCount { get; }
int StencilSize { get; }
}
}

2
src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs

@ -4,7 +4,7 @@ namespace Avalonia.OpenGL
{
public interface IGlPlatformSurfaceRenderingSession : IDisposable
{
IGlDisplay Display { get; }
IGlContext Context { get; }
PixelSize Size { get; }
double Scaling { get; }
bool IsYFlipped { get; }

9
src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs

@ -0,0 +1,9 @@
using Avalonia.OpenGL.Imaging;
namespace Avalonia.OpenGL
{
public interface IOpenGlAwarePlatformRenderInterface
{
IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap();
}
}

3
src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs

@ -2,6 +2,7 @@ namespace Avalonia.OpenGL
{
public interface IWindowingPlatformGlFeature
{
IGlContext ImmediateContext { get; }
IGlContext CreateContext();
IGlContext MainContext { get; }
}
}

13
src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs

@ -0,0 +1,13 @@
using System;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace Avalonia.OpenGL.Imaging
{
public interface IOpenGlTextureBitmapImpl : IBitmapImpl
{
IDisposable Lock();
void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling);
void SetDirty();
}
}

46
src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs

@ -0,0 +1,46 @@
using System;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.OpenGL.Imaging
{
public class OpenGlTextureBitmap : Bitmap, IAffectsRender
{
private IOpenGlTextureBitmapImpl _impl;
static IOpenGlTextureBitmapImpl CreateOrThrow()
{
if (!(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is IOpenGlAwarePlatformRenderInterface
glAware))
throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration");
return glAware.CreateOpenGlTextureBitmap();
}
public OpenGlTextureBitmap()
: base(CreateOrThrow())
{
_impl = (IOpenGlTextureBitmapImpl)PlatformImpl.Item;
}
public IDisposable Lock() => _impl.Lock();
public void SetTexture(int textureId, int internalFormat, PixelSize size, double dpiScaling)
{
_impl.SetBackBuffer(textureId, internalFormat, size, dpiScaling);
SetIsDirty();
}
public void SetIsDirty()
{
if (Dispatcher.UIThread.CheckAccess())
CallInvalidated();
else
Dispatcher.UIThread.Post(CallInvalidated);
}
private void CallInvalidated() => Invalidated?.Invoke(this, EventArgs.Empty);
public event EventHandler Invalidated;
}
}

215
src/Avalonia.OpenGL/OpenGlControlBase.cs

@ -0,0 +1,215 @@
using System;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.OpenGL.Imaging;
using Avalonia.Rendering;
using Avalonia.VisualTree;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL
{
public abstract class OpenGlControlBase : Control
{
private IGlContext _context;
private int _fb, _texture, _renderBuffer;
private OpenGlTextureBitmap _bitmap;
private PixelSize _oldSize;
private bool _glFailed;
protected GlVersion GlVersion { get; private set; }
public sealed override void Render(DrawingContext context)
{
if(!EnsureInitialized())
return;
using (_context.MakeCurrent())
{
using (_bitmap.Lock())
{
var gl = _context.GlInterface;
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
if (_oldSize != GetPixelSize())
ResizeTexture(gl);
OnOpenGlRender(gl, _fb);
gl.Flush();
}
}
context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds);
base.Render(context);
}
void DoCleanup(bool callUserDeinit)
{
if (_context != null)
{
using (_context.MakeCurrent())
{
var gl = _context.GlInterface;
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffers(1, new[] { _fb });
using (_bitmap.Lock())
_bitmap.SetTexture(0, 0, new PixelSize(1, 1), 1);
gl.DeleteTextures(1, new[] { _texture });
gl.DeleteRenderbuffers(1, new[] { _renderBuffer });
_bitmap.Dispose();
try
{
if (callUserDeinit)
OnOpenGlDeinit(_context.GlInterface, _fb);
}
finally
{
_context.Dispose();
_context = null;
}
}
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DoCleanup(true);
base.OnDetachedFromVisualTree(e);
}
bool EnsureInitialized()
{
if (_context != null)
return true;
if (_glFailed)
return false;
var feature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
if (feature == null)
return false;
try
{
_context = feature.CreateContext();
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase",
"Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
_glFailed = true;
return false;
}
GlVersion = _context.Version;
try
{
_bitmap = new OpenGlTextureBitmap();
}
catch (Exception e)
{
_context.Dispose();
_context = null;
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase",
"Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e);
_glFailed = true;
return false;
}
using (_context.MakeCurrent())
{
try
{
_oldSize = GetPixelSize();
var gl = _context.GlInterface;
var oneArr = new int[1];
gl.GenFramebuffers(1, oneArr);
_fb = oneArr[0];
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
gl.GenTextures(1, oneArr);
_texture = oneArr[0];
ResizeTexture(gl);
gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
int code;
while ((code = gl.GetError()) != 0)
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase",
"Unable to initialize OpenGL FBO: {code}", code);
_glFailed = true;
return false;
}
}
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase",
"Unable to initialize OpenGL FBO: {exception}", e);
_glFailed = true;
}
if (!_glFailed)
OnOpenGlInit(_context.GlInterface, _fb);
}
if (_glFailed)
{
DoCleanup(false);
}
return true;
}
void ResizeTexture(GlInterface gl)
{
var size = GetPixelSize();
gl.GetIntegerv( GL_TEXTURE_BINDING_2D, out var oldTexture);
gl.BindTexture(GL_TEXTURE_2D, _texture);
gl.TexImage2D(GL_TEXTURE_2D, 0,
GlVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8,
size.Width, size.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl.BindTexture(GL_TEXTURE_2D, oldTexture);
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
gl.DeleteRenderbuffers(1, new[] { _renderBuffer });
var oneArr = new int[1];
gl.GenRenderbuffers(1, oneArr);
_renderBuffer = oneArr[0];
gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
gl.RenderbufferStorage(GL_RENDERBUFFER,
GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
size.Width, size.Height);
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer);
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
using (_bitmap.Lock())
_bitmap.SetTexture(_texture, GL_RGBA8, size, 1);
}
PixelSize GetPixelSize()
{
var scaling = VisualRoot.RenderScaling;
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
Math.Max(1, (int)(Bounds.Height * scaling)));
}
protected virtual void OnOpenGlInit(GlInterface gl, int fb)
{
}
protected virtual void OnOpenGlDeinit(GlInterface gl, int fb)
{
}
protected abstract void OnOpenGlRender(GlInterface gl, int fb);
}
}

45
src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs

@ -1,50 +1,11 @@
using System;
using System.Threading;
using Avalonia.Utilities;
namespace Avalonia.Rendering
{
public class ManagedDeferredRendererLock : IDeferredRendererLock
public class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock
{
private readonly object _lock = new object();
/// <summary>
/// Tries to lock the target surface or window
/// </summary>
/// <returns>IDisposable if succeeded to obtain the lock</returns>
public IDisposable TryLock()
{
if (Monitor.TryEnter(_lock))
return new UnlockDisposable(_lock);
return null;
}
/// <summary>
/// Enters a waiting lock, only use from platform code, not from the renderer
/// </summary>
public IDisposable Lock()
{
Monitor.Enter(_lock);
return new UnlockDisposable(_lock);
}
private sealed class UnlockDisposable : IDisposable
{
private object _lock;
public UnlockDisposable(object @lock)
{
_lock = @lock;
}
public void Dispose()
{
object @lock = Interlocked.Exchange(ref _lock, null);
if (@lock != null)
{
Monitor.Exit(@lock);
}
}
}
}
}

41
src/Avalonia.X11/Glx/Glx.cs

@ -15,11 +15,30 @@ namespace Avalonia.X11.Glx
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXGetCurrentContext")]
public GlxGetCurrentContext GetCurrentContext { get; }
public delegate IntPtr GlxGetCurrentContext();
[GlEntryPoint("glXGetCurrentDisplay")]
public GlxGetCurrentDisplay GetCurrentDisplay { get; }
public delegate IntPtr GlxGetCurrentDisplay();
[GlEntryPoint("glXGetCurrentDrawable")]
public GlxGetCurrentDrawable GetCurrentDrawable { get; }
public delegate IntPtr GlxGetCurrentDrawable();
[GlEntryPoint("glXGetCurrentReadDrawable")]
public GlxGetCurrentReadDrawable GetCurrentReadDrawable { get; }
public delegate IntPtr GlxGetCurrentReadDrawable();
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPoint("glXDestroyPbuffer")]
public GlxDestroyPbuffer DestroyPbuffer { get; }
public delegate IntPtr GlxDestroyPbuffer(IntPtr dpy, IntPtr fb);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
@ -78,12 +97,16 @@ namespace Avalonia.X11.Glx
[GlEntryPointAttribute("glXWaitGL")]
public GlxWaitGL WaitGL { get; }
public delegate void GlxWaitGL();
public delegate void GlxWaitGL();
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate IntPtr GlxQueryExtensionsString(IntPtr display, int screen);
[GlEntryPoint("glXQueryExtensionsString")]
public GlxQueryExtensionsString QueryExtensionsString { get; }
public GlxInterface() : base(SafeGetProcAddress)
{
}
@ -92,16 +115,24 @@ namespace Avalonia.X11.Glx
// On some Linux systems, glXGetProcAddress will return valid pointers for even EGL functions.
// This makes Skia try to load some data from EGL,
// which can then cause segmentation faults because they return garbage.
public static IntPtr SafeGetProcAddress(string proc, bool optional)
public static IntPtr SafeGetProcAddress(string proc)
{
if (proc.StartsWith("egl", StringComparison.InvariantCulture))
{
return IntPtr.Zero;
}
return GlxConverted(proc, optional);
return GlxConverted(proc);
}
private static readonly Func<string, bool, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
private static readonly Func<string, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
public string[] GetExtensions(IntPtr display)
{
var s = Marshal.PtrToStringAnsi(QueryExtensionsString(display, 0));
return s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim()).ToArray();
}
}
}

2
src/Avalonia.X11/Glx/GlxConsts.cs

@ -100,6 +100,8 @@ namespace Avalonia.X11.Glx
public const int GLX_CONTEXT_FLAGS_ARB = 0x2094;
public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_ES2_PROFILE_BIT_EXT = 0x00000004;
public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
}

56
src/Avalonia.X11/Glx/GlxContext.cs

@ -10,32 +10,80 @@ namespace Avalonia.X11.Glx
public GlxInterface Glx { get; }
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly bool _ownsPBuffer;
private readonly object _lock = new object();
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display,
GlVersion version, int sampleCount, int stencilSize,
X11Info x11, IntPtr defaultXid,
bool ownsPBuffer)
{
Handle = handle;
Glx = glx;
_x11 = x11;
_defaultXid = defaultXid;
_ownsPBuffer = ownsPBuffer;
Display = display;
Version = version;
SampleCount = sampleCount;
StencilSize = stencilSize;
using (MakeCurrent())
GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress);
}
public GlxDisplay Display { get; }
IGlDisplay IGlContext.Display => Display;
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
public int SampleCount { get; }
public int StencilSize { get; }
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
}
class RestoreContext : IDisposable
{
private GlxInterface _glx;
private IntPtr _defaultDisplay;
private IntPtr _display;
private IntPtr _context;
private IntPtr _read;
private IntPtr _draw;
public RestoreContext(GlxInterface glx, IntPtr defaultDisplay)
{
_glx = glx;
_defaultDisplay = defaultDisplay;
_display = _glx.GetCurrentDisplay();
_context = _glx.GetCurrentContext();
_read = _glx.GetCurrentReadDrawable();
_draw = _glx.GetCurrentDrawable();
}
public void Dispose()
{
var disp = _display == IntPtr.Zero ? _defaultDisplay : _display;
_glx.MakeContextCurrent(disp, _draw, _read, _context);
}
}
public void MakeCurrent() => MakeCurrent(_defaultXid);
public IDisposable MakeCurrent() => MakeCurrent(_defaultXid);
public void MakeCurrent(IntPtr xid)
public IDisposable MakeCurrent(IntPtr xid)
{
var old = new RestoreContext(Glx, _x11.Display);
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
throw new OpenGlException("glXMakeContextCurrent failed ");
return old;
}
public void Dispose()
{
Glx.DestroyContext(_x11.Display, Handle);
if (_ownsPBuffer)
Glx.DestroyPbuffer(_x11.Display, _defaultXid);
}
}
}

134
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -1,28 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.OpenGL;
using static Avalonia.X11.Glx.GlxConsts;
namespace Avalonia.X11.Glx
{
unsafe class GlxDisplay : IGlDisplay
unsafe class GlxDisplay
{
private readonly X11Info _x11;
private readonly List<GlVersion> _probeProfiles;
private readonly IntPtr _fbconfig;
private readonly XVisualInfo* _visual;
public GlDisplayType Type => GlDisplayType.OpenGL2;
public GlInterface GlInterface { get; }
private string[] _displayExtensions;
private GlVersion? _version;
public XVisualInfo* VisualInfo => _visual;
public int SampleCount { get; }
public int StencilSize { get; }
public GlxContext ImmediateContext { get; }
public GlxContext DeferredContext { get; }
public GlxInterface Glx { get; } = new GlxInterface();
public GlxDisplay(X11Info x11)
public GlxDisplay(X11Info x11, List<GlVersion> probeProfiles)
{
_x11 = x11;
_probeProfiles = probeProfiles.ToList();
_displayExtensions = Glx.GetExtensions(_x11.Display);
var baseAttribs = new[]
{
@ -38,7 +38,8 @@ namespace Avalonia.X11.Glx
GLX_STENCIL_SIZE, 8,
};
int sampleCount = 0;
int stencilSize = 0;
foreach (var attribs in new[]
{
//baseAttribs.Concat(multiattribs),
@ -71,9 +72,9 @@ namespace Avalonia.X11.Glx
if (_visual == null)
throw new OpenGlException("Unable to get visual info from FBConfig");
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
SampleCount = samples;
sampleCount = samples;
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
StencilSize = stencil;
stencilSize = stencil;
var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[]
{
@ -81,67 +82,104 @@ namespace Avalonia.X11.Glx
})).ToList();
XLib.XFlush(_x11.Display);
ImmediateContext = CreateContext(pbuffers[0],null);
DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)
throw new OpenGlException("GL renderer string is null, aborting");
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
DeferredContext = CreateContext(CreatePBuffer(), null,
sampleCount, stencilSize, true);
using (DeferredContext.MakeCurrent())
{
var blacklist = AvaloniaLocator.Current.GetService<X11PlatformOptions>()
?.GlxRendererBlacklist;
if (blacklist != null)
foreach(var item in blacklist)
if (GlInterface.Renderer.Contains(item))
throw new OpenGlException($"Renderer '{GlInterface.Renderer}' is blacklisted by '{item}'");
var glInterface = DeferredContext.GlInterface;
if (glInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (glInterface.Renderer == null)
throw new OpenGlException("GL renderer string is null, aborting");
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
{
var blacklist = AvaloniaLocator.Current.GetService<X11PlatformOptions>()
?.GlxRendererBlacklist;
if (blacklist != null)
foreach (var item in blacklist)
if (glInterface.Renderer.Contains(item))
throw new OpenGlException(
$"Renderer '{glInterface.Renderer}' is blacklisted by '{item}'");
}
}
}
IntPtr CreatePBuffer()
{
return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 });
}
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
public GlxContext CreateContext() => CreateContext(DeferredContext);
GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share,
share.SampleCount, share.StencilSize, true);
GlxContext CreateContext(IntPtr defaultXid, IGlContext share,
int sampleCount, int stencilSize, bool ownsPBuffer)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
IntPtr handle = default;
foreach (var ver in new[]
{
new Version(4, 0), new Version(3, 2),
new Version(3, 0), new Version(2, 0)
})
GlxContext Create(GlVersion profile)
{
var profileMask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
if (profile.Type == GlProfileType.OpenGLES)
profileMask = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
var attrs = new[]
var attrs = new int[]
{
GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major,
GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor,
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
GLX_CONTEXT_MAJOR_VERSION_ARB, profile.Major,
GLX_CONTEXT_MINOR_VERSION_ARB, profile.Minor,
GLX_CONTEXT_PROFILE_MASK_ARB, profileMask,
0
};
try
{
handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs);
if (handle != IntPtr.Zero)
break;
{
_version = profile;
return new GlxContext(new GlxInterface(), handle, this, profile,
sampleCount, stencilSize, _x11, defaultXid, ownsPBuffer);
}
}
catch
{
break;
return null;
}
return null;
}
GlxContext rv = null;
if (_version.HasValue)
{
rv = Create(_version.Value);
}
if (handle == IntPtr.Zero)
throw new OpenGlException("Unable to create direct GLX context");
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
foreach (var v in _probeProfiles)
{
if (v.Type == GlProfileType.OpenGLES
&& !_displayExtensions.Contains("GLX_EXT_create_context_es2_profile"))
continue;
rv = Create(v);
if (rv != null)
{
_version = v;
break;
}
}
if (rv != null)
return rv;
throw new OpenGlException("Unable to create direct GLX context");
}
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);

14
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -43,8 +43,8 @@ namespace Avalonia.X11.Glx
var l = _context.Lock();
try
{
_context.MakeCurrent(_info.Handle);
return new Session(_context, _info, l);
return new Session(_context, _info, l, _context.MakeCurrent(_info.Handle));
}
catch
{
@ -58,26 +58,28 @@ namespace Avalonia.X11.Glx
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IDisposable _lock;
private readonly IDisposable _clearContext;
public IGlContext Context => _context;
public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
IDisposable @lock, IDisposable clearContext)
{
_context = context;
_info = info;
_lock = @lock;
_clearContext = clearContext;
}
public void Dispose()
{
_context.Display.GlInterface.Flush();
_context.GlInterface.Flush();
_context.Glx.WaitGL();
_context.Display.SwapBuffers(_info.Handle);
_context.Glx.WaitX();
_context.Display.ClearContext();
_clearContext.Dispose();
_lock.Dispose();
}
public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }

13
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.OpenGL;
@ -7,12 +8,13 @@ namespace Avalonia.X11.Glx
class GlxGlPlatformFeature : IWindowingPlatformGlFeature
{
public GlxDisplay Display { get; private set; }
public IGlContext ImmediateContext { get; private set; }
public IGlContext CreateContext() => Display.CreateContext();
public GlxContext DeferredContext { get; private set; }
public IGlContext MainContext => DeferredContext;
public static bool TryInitialize(X11Info x11)
public static bool TryInitialize(X11Info x11, List<GlVersion> glProfiles)
{
var feature = TryCreate(x11);
var feature = TryCreate(x11, glProfiles);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature);
@ -22,15 +24,14 @@ namespace Avalonia.X11.Glx
return false;
}
public static GlxGlPlatformFeature TryCreate(X11Info x11)
public static GlxGlPlatformFeature TryCreate(X11Info x11, List<GlVersion> glProfiles)
{
try
{
var disp = new GlxDisplay(x11);
var disp = new GlxDisplay(x11, glProfiles);
return new GlxGlPlatformFeature
{
Display = disp,
ImmediateContext = disp.ImmediateContext,
DeferredContext = disp.DeferredContext
};
}

12
src/Avalonia.X11/X11Platform.cs

@ -67,7 +67,7 @@ namespace Avalonia.X11
if (options.UseEGL)
EglGlPlatformFeature.TryInitialize();
else
GlxGlPlatformFeature.TryInitialize(Info);
GlxGlPlatformFeature.TryInitialize(Info, Options.GlProfiles);
}
@ -98,6 +98,16 @@ namespace Avalonia
public bool UseDBusMenu { get; set; }
public bool UseDeferredRendering { get; set; } = true;
public List<GlVersion> GlProfiles { get; set; } = new List<GlVersion>
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
new GlVersion(GlProfileType.OpenGL, 3, 2),
new GlVersion(GlProfileType.OpenGL, 3, 0),
new GlVersion(GlProfileType.OpenGLES, 3, 2),
new GlVersion(GlProfileType.OpenGLES, 3, 0),
new GlVersion(GlProfileType.OpenGLES, 2, 0)
};
public List<string> GlxRendererBlacklist { get; set; } = new List<string>
{
// llvmpipe is a software GL rasterizer. If it's returned by glGetString,

2
src/Avalonia.X11/X11Window.cs

@ -165,7 +165,7 @@ namespace Avalonia.X11
if (egl != null)
surfaces.Insert(0,
new EglGlPlatformSurface((EglDisplay)egl.Display, egl.DeferredContext,
new EglGlPlatformSurface(egl.DeferredContext,
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
if (glx != null)
surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,

44
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -15,6 +15,8 @@ namespace Avalonia.LinuxFramebuffer.Output
private readonly EglGlPlatformSurface _eglPlatformSurface;
public PixelSize PixelSize => _mode.Resolution;
public double Scaling { get; set; }
public IGlContext MainContext => _deferredContext;
public DrmOutput(string path = null)
{
var card = new DrmCard(path);
@ -48,7 +50,6 @@ namespace Avalonia.LinuxFramebuffer.Output
private drmModeModeInfo _mode;
private EglDisplay _eglDisplay;
private EglSurface _eglSurface;
private EglContext _immediateContext;
private EglContext _deferredContext;
private IntPtr _currentBo;
private IntPtr _gbmTargetSurface;
@ -129,13 +130,15 @@ namespace Avalonia.LinuxFramebuffer.Output
return _eglDisplay.CreateContext(share, _eglDisplay.CreateWindowSurface(offSurf));
}
_immediateContext = CreateContext(null);
_deferredContext = CreateContext(_immediateContext);
_immediateContext.MakeCurrent(_eglSurface);
_eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
_eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_eglSurface.SwapBuffers();
_deferredContext = CreateContext(null);
using (_deferredContext.MakeCurrent(_eglSurface))
{
_deferredContext.GlInterface.ClearColor(0, 0, 0, 0);
_deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_eglSurface.SwapBuffers();
}
var bo = gbm_surface_lock_front_buffer(_gbmTargetSurface);
var fbId = GetFbIdForBo(bo);
var connectorId = connector.Id;
@ -153,9 +156,10 @@ namespace Avalonia.LinuxFramebuffer.Output
for(var c=0;c<2;c++)
using (CreateGlRenderTarget().BeginDraw())
{
_eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
_eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_deferredContext.GlInterface.ClearColor(0, 0, 0, 0);
_deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
}
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
@ -179,15 +183,17 @@ namespace Avalonia.LinuxFramebuffer.Output
class RenderSession : IGlPlatformSurfaceRenderingSession
{
private readonly DrmOutput _parent;
private readonly IDisposable _clearContext;
public RenderSession(DrmOutput parent)
public RenderSession(DrmOutput parent, IDisposable clearContext)
{
_parent = parent;
_clearContext = clearContext;
}
public void Dispose()
{
_parent._eglDisplay.GlInterface.Flush();
_parent._deferredContext.GlInterface.Flush();
_parent._eglSurface.SwapBuffers();
var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface);
@ -225,11 +231,11 @@ namespace Avalonia.LinuxFramebuffer.Output
gbm_surface_release_buffer(_parent._gbmTargetSurface, _parent._currentBo);
_parent._currentBo = nextBo;
}
_parent._eglDisplay.ClearContext();
_clearContext.Dispose();
}
public IGlDisplay Display => _parent._eglDisplay;
public IGlContext Context => _parent._deferredContext;
public PixelSize Size => _parent._mode.Resolution;
@ -240,14 +246,14 @@ namespace Avalonia.LinuxFramebuffer.Output
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
_parent._deferredContext.MakeCurrent(_parent._eglSurface);
return new RenderSession(_parent);
return new RenderSession(_parent, _parent._deferredContext.MakeCurrent(_parent._eglSurface));
}
}
IGlContext IWindowingPlatformGlFeature.ImmediateContext => _immediateContext;
public IGlContext CreateContext()
{
throw new NotImplementedException();
}
}

4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -28,9 +28,9 @@ namespace Avalonia.Skia
private double _currentOpacity = 1.0f;
private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform;
private GRContext _grContext;
private bool _disposed;
private GRContext _grContext;
public GRContext GrContext => _grContext;
private readonly SKPaint _strokePaint = new SKPaint();
private readonly SKPaint _fillPaint = new SKPaint();
private readonly SKPaint _boxShadowPaint = new SKPaint();

15
src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -6,18 +7,18 @@ namespace Avalonia.Skia
/// <summary>
/// Custom Skia gpu instance.
/// </summary>
public interface ICustomSkiaGpu
public interface ISkiaGpu
{
/// <summary>
/// Skia GrContext used.
/// </summary>
GRContext GrContext { get; }
/// <summary>
/// Attempts to create custom render target from given surfaces.
/// </summary>
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ICustomSkiaRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
}
public interface IOpenGlAwareSkiaGpu : ISkiaGpu
{
IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap();
}
}

2
src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs

@ -6,7 +6,7 @@ namespace Avalonia.Skia
/// <summary>
/// Custom render session for Skia render target.
/// </summary>
public interface ICustomSkiaRenderSession : IDisposable
public interface ISkiaGpuRenderSession : IDisposable
{
/// <summary>
/// GrContext used by this session.

7
src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs

@ -1,16 +1,19 @@
using System;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Custom Skia render target.
/// </summary>
public interface ICustomSkiaRenderTarget : IDisposable
public interface ISkiaGpuRenderTarget : IDisposable
{
/// <summary>
/// Start rendering to this render target.
/// </summary>
/// <returns></returns>
ICustomSkiaRenderSession BeginRendering();
ISkiaGpuRenderSession BeginRenderingSession();
bool IsCorrupted { get; }
}
}

73
src/Skia/Avalonia.Skia/GlRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs

@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
internal class GlRenderTarget : IRenderTargetWithCorruptionInfo
internal class GlRenderTarget : ISkiaGpuRenderTarget
{
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;
@ -22,22 +22,52 @@ namespace Avalonia.Skia
public void Dispose() => _surface.Dispose();
public bool IsCorrupted => (_surface as IGlPlatformSurfaceRenderTargetWithCorruptionInfo)?.IsCorrupted == true;
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
class GlGpuSession : ISkiaGpuRenderSession
{
private readonly GRBackendRenderTarget _backendRenderTarget;
private readonly SKSurface _surface;
private readonly IGlPlatformSurfaceRenderingSession _glSession;
public GlGpuSession(GRContext grContext,
GRBackendRenderTarget backendRenderTarget,
SKSurface surface,
IGlPlatformSurfaceRenderingSession glSession)
{
GrContext = grContext;
_backendRenderTarget = backendRenderTarget;
_surface = surface;
_glSession = glSession;
}
public void Dispose()
{
_surface.Canvas.Flush();
_surface.Dispose();
_backendRenderTarget.Dispose();
GrContext.Flush();
_glSession.Dispose();
}
public GRContext GrContext { get; }
public SKCanvas Canvas => _surface.Canvas;
public double ScaleFactor => _glSession.Scaling;
}
public ISkiaGpuRenderSession BeginRenderingSession()
{
var session = _surface.BeginDraw();
var glSession = _surface.BeginDraw();
bool success = false;
try
{
var disp = session.Display;
var disp = glSession.Context;
var gl = disp.GlInterface;
gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb);
var size = session.Size;
var scaling = session.Scaling;
var size = glSession.Size;
var scaling = glSession.Scaling;
if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
{
session.Dispose();
glSession.Dispose();
throw new InvalidOperationException(
$"Can't create drawing context for surface with {size} size and {scaling} scaling");
}
@ -50,40 +80,21 @@ namespace Avalonia.Skia
{
_grContext.ResetContext();
GRBackendRenderTarget renderTarget =
var renderTarget =
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat()));
var surface = SKSurface.Create(_grContext, renderTarget,
session.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
GRPixelConfig.Rgba8888.ToColorType());
var nfo = new DrawingContextImpl.CreateInfo
{
GrContext = _grContext,
Canvas = surface.Canvas,
Dpi = SkiaPlatform.DefaultDpi * scaling,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
};
var ctx = new DrawingContextImpl(nfo, Disposable.Create(() =>
{
surface.Canvas.Flush();
surface.Dispose();
renderTarget.Dispose();
_grContext.Flush();
session.Dispose();
}));
success = true;
return ctx;
return new GlGpuSession(_grContext, renderTarget, surface, glSession);
}
}
finally
{
if(!success)
session.Dispose();
glSession.Dispose();
}
}
}

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

@ -0,0 +1,46 @@
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
{
class GlSkiaGpu : IOpenGlAwareSkiaGpu
{
private GRContext _grContext;
public GlSkiaGpu(IWindowingPlatformGlFeature gl, long? maxResourceBytes)
{
var context = gl.MainContext;
using (context.MakeCurrent())
{
using (var iface = context.Version.Type == GlProfileType.OpenGL ?
GRGlInterface.AssembleGlInterface((_, proc) => context.GlInterface.GetProcAddress(proc)) :
GRGlInterface.AssembleGlesInterface((_, proc) => context.GlInterface.GetProcAddress(proc)))
{
_grContext = GRContext.Create(GRBackend.OpenGL, iface);
if (maxResourceBytes.HasValue)
{
_grContext.GetResourceCacheLimits(out var maxResources, out _);
_grContext.SetResourceCacheLimits(maxResources, maxResourceBytes.Value);
}
}
}
}
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
{
foreach (var surface in surfaces)
{
if (surface is IGlPlatformSurface glSurface)
{
return new GlRenderTarget(_grContext, glSurface);
}
}
return null;
}
public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() => new OpenGlTextureBitmapImpl();
}
}

81
src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs

@ -0,0 +1,81 @@
using System;
using System.IO;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Skia.Helpers;
using Avalonia.Utilities;
using SkiaSharp;
namespace Avalonia.Skia
{
class OpenGlTextureBitmapImpl : IOpenGlTextureBitmapImpl, IDrawableBitmapImpl
{
private DisposableLock _lock = new DisposableLock();
private int _textureId;
private int _internalFormat;
public void Dispose()
{
using (Lock())
{
_textureId = 0;
PixelSize = new PixelSize(1, 1);
Version++;
}
}
public Vector Dpi { get; private set; } = new Vector(96, 96);
public PixelSize PixelSize { get; private set; } = new PixelSize(1, 1);
public int Version { get; private set; } = 0;
public void Save(string fileName) => throw new System.NotSupportedException();
public void Save(Stream stream) => throw new System.NotSupportedException();
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{
// For now silently ignore
if (context.GrContext == null)
return;
using (Lock())
{
if (_textureId == 0)
return;
using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false,
new GRGlTextureInfo(
GlConsts.GL_TEXTURE_2D, (uint)_textureId,
(uint)_internalFormat)))
using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft,
SKColorType.Rgba8888))
{
// Again, silently ignore, if something went wrong it's not our fault
if (surface == null)
return;
using (var snapshot = surface.Snapshot())
context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint);
}
}
}
public IDisposable Lock() => _lock.Lock();
public void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling)
{
using (_lock.Lock())
{
_textureId = textureId;
_internalFormat = internalFormat;
PixelSize = pixelSize;
Dpi = new Vector(96 * dpiScaling, 96 * dpiScaling);
Version++;
}
}
public void SetDirty()
{
using (_lock.Lock())
Version++;
}
}
}

12
src/Skia/Avalonia.Skia/CustomRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -4,13 +4,13 @@ using Avalonia.Rendering;
namespace Avalonia.Skia
{
/// <summary>
/// Adapts <see cref="ICustomSkiaRenderTarget"/> to be used within Skia rendering pipeline.
/// Adapts <see cref="ISkiaGpuRenderTarget"/> to be used within our rendering pipeline.
/// </summary>
internal class CustomRenderTarget : IRenderTarget
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
{
private readonly ICustomSkiaRenderTarget _renderTarget;
private readonly ISkiaGpuRenderTarget _renderTarget;
public CustomRenderTarget(ICustomSkiaRenderTarget renderTarget)
public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget)
{
_renderTarget = renderTarget;
}
@ -22,7 +22,7 @@ namespace Avalonia.Skia
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
ICustomSkiaRenderSession session = _renderTarget.BeginRendering();
var session = _renderTarget.BeginRenderingSession();
var nfo = new DrawingContextImpl.CreateInfo
{
@ -35,5 +35,7 @@ namespace Avalonia.Skia
return new DrawingContextImpl(nfo, session);
}
public bool IsCorrupted => _renderTarget.IsCorrupted;
}
}

68
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -1,9 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using SkiaSharp;
@ -12,44 +15,21 @@ namespace Avalonia.Skia
/// <summary>
/// Skia platform render interface.
/// </summary>
internal class PlatformRenderInterface : IPlatformRenderInterface
internal class PlatformRenderInterface : IPlatformRenderInterface, IOpenGlAwarePlatformRenderInterface
{
private readonly ICustomSkiaGpu _customSkiaGpu;
private readonly ISkiaGpu _skiaGpu;
private GRContext GrContext { get; }
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu, long maxResourceBytes = 100663296)
public PlatformRenderInterface(ISkiaGpu skiaGpu, long? maxResourceBytes = null)
{
if (customSkiaGpu != null)
if (skiaGpu != null)
{
_customSkiaGpu = customSkiaGpu;
GrContext = _customSkiaGpu.GrContext;
GrContext.GetResourceCacheLimits(out var maxResources, out _);
GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes);
_skiaGpu = skiaGpu;
return;
}
var gl = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
if (gl != null)
{
var display = gl.ImmediateContext.Display;
gl.ImmediateContext.MakeCurrent();
using (var iface = display.Type == GlDisplayType.OpenGL2
? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
GrContext.GetResourceCacheLimits(out var maxResources, out _);
GrContext.SetResourceCacheLimits(maxResources, maxResourceBytes);
}
display.ClearContext();
}
if (gl != null)
_skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
}
/// <inheritdoc />
@ -125,26 +105,18 @@ namespace Avalonia.Skia
/// <inheritdoc />
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
if (_customSkiaGpu != null)
if (!(surfaces is IList))
surfaces = surfaces.ToList();
var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null)
{
ICustomSkiaRenderTarget customRenderTarget = _customSkiaGpu.TryCreateRenderTarget(surfaces);
if (customRenderTarget != null)
{
return new CustomRenderTarget(customRenderTarget);
}
return new SkiaGpuRenderTarget(gpuRenderTarget);
}
foreach (var surface in surfaces)
{
if (surface is IGlPlatformSurface glSurface && GrContext != null)
{
return new GlRenderTarget(GrContext, glSurface);
}
if (surface is IFramebufferPlatformSurface framebufferSurface)
{
return new FramebufferRenderTarget(framebufferSurface);
}
}
throw new NotSupportedException(
@ -256,6 +228,16 @@ namespace Avalonia.Skia
}
public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap()
{
if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
return glAware.CreateOpenGlTextureBitmap();
if (_skiaGpu == null)
throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
public bool SupportsIndividualRoundRects => true;
}
}

8
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -8,18 +8,14 @@ namespace Avalonia
/// </summary>
public class SkiaOptions
{
public SkiaOptions()
{
MaxGpuResourceSizeBytes = 100663296; // Value taken from skia.
}
/// <summary>
/// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
/// </summary>
public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; }
public Func<ISkiaGpu> CustomGpuFactory { get; set; }
/// <summary>
/// The maximum number of bytes for video memory to store textures and resources.
/// </summary>
public long MaxGpuResourceSizeBytes { get; set; }
public long? MaxGpuResourceSizeBytes { get; set; }
}
}

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

@ -97,10 +97,7 @@ namespace Avalonia.Win32
_framebuffer = new FramebufferManager(_hwnd);
if (Win32GlManager.EglFeature != null)
{
_gl = new EglGlPlatformSurface((EglDisplay)Win32GlManager.EglFeature.Display,
Win32GlManager.EglFeature.DeferredContext, this);
}
_gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this);
Screen = new ScreenImpl();

Loading…
Cancel
Save