Browse Source

Support for conditional gl imports

pull/3386/head
Nikita Tsukanov 6 years ago
parent
commit
ce018324a0
  1. 27
      src/Avalonia.Native/GlPlatformFeature.cs
  2. 3
      src/Avalonia.OpenGL/EglContext.cs
  3. 17
      src/Avalonia.OpenGL/EglInterface.cs
  4. 46
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  5. 106
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  6. 48
      src/Avalonia.OpenGL/GlInterface.cs
  7. 84
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  8. 6
      src/Avalonia.X11/Glx/Glx.cs
  9. 3
      src/Avalonia.X11/Glx/GlxContext.cs
  10. 2
      src/Avalonia.X11/Glx/GlxDisplay.cs

27
src/Avalonia.Native/GlPlatformFeature.cs

@ -15,16 +15,25 @@ namespace Avalonia.Native
_display = display;
var immediate = display.CreateContext(null);
var deferred = display.CreateContext(immediate);
GlDisplay = new GlDisplay(display, immediate.SampleCount, immediate.StencilSize);
int major, minor;
GlInterface glInterface;
using (immediate.MakeCurrent())
{
GlDisplay.GlInterface.GetIntegerv(GlConsts.GL_MAJOR_VERSION, out major);
GlDisplay.GlInterface.GetIntegerv(GlConsts.GL_MINOR_VERSION, out minor);
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;
});
}
_version = new GlVersion(GlProfileType.OpenGL, major, minor);
GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize);
ImmediateContext = new GlContext(GlDisplay, immediate, _version);
DeferredContext = new GlContext(GlDisplay, deferred, _version);
}
@ -43,18 +52,12 @@ namespace Avalonia.Native
{
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 GlInterface GlInterface { get; }

3
src/Avalonia.OpenGL/EglContext.cs

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

17
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);

46
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@ -0,0 +1,46 @@
using System;
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 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; }
public string GetString(int v)
{
var ptr = GetStringNative(v);
if (ptr != IntPtr.Zero)
return Marshal.PtrToStringAnsi(ptr);
return null;
}
}
}

106
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,21 +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 bool Optional { get; set; }
public GlEntryPointAttribute(string entryPoint, bool optional = false)
public GlEntryPointAttribute(string entryPoint)
{
EntryPoints = new []{entryPoint};
Optional = optional;
}
/*
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>
{
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly int _minVersionMajor;
private readonly int _minVersionMinor;
public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor,
int minVersionMinor)
{
_entry = entry;
_profile = profile;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
}
public GlMinVersionEntryPoint(string entry, int minVersionMajor,
int minVersionMinor)
{
_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);
}
}
}

48
src/Avalonia.OpenGL/GlInterface.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Platform.Interop;
@ -8,26 +9,51 @@ namespace Avalonia.OpenGL
{
public delegate IntPtr GlGetProcAddressDelegate(string procName);
public unsafe 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.GetString(GL_EXTENSIONS).Split(' ');
return new GlContextInfo(version, new HashSet<string>(exts));
}
}
private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress, info)
{
Version = GetString(GlConsts.GL_VERSION);
Renderer = GetString(GlConsts.GL_RENDERER);
Vendor = GetString(GlConsts.GL_VENDOR);
Vendor = GetString(GlConsts.GL_VENDOR);
}
public GlInterface(GlVersion version, Func<string, IntPtr> getProcAddress) : this(
GlContextInfo.Create(version, getProcAddress), getProcAddress)
{
}
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
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));
@ -283,7 +309,9 @@ namespace Avalonia.OpenGL
public delegate void GlGenVertexArrays(int n, int[] rv);
[GlEntryPoint("glGenVertexArrays", "glGenVertexArraysOES")]
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)]
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public GlGenVertexArrays GenVertexArrays { get; }
public int GenVertexArray()
@ -294,7 +322,8 @@ namespace Avalonia.OpenGL
}
public delegate void GlBindVertexArray(int array);
[GlEntryPoint("glBindVertexArray", "glBindVertexArrayOES")]
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)]
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
public GlBindVertexArray BindVertexArray { get; }
public delegate int GlGetUniformLocation(int program, IntPtr name);
@ -325,7 +354,8 @@ namespace Avalonia.OpenGL
public GlDeleteBuffers DeleteBuffers { get; }
public delegate void GlDeleteVertexArrays(int count, int[] buffers);
[GlEntryPoint("glDeleteVertexArrays", "glDeleteVertexArraysOES")]
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)]
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
public GlDeleteVertexArrays DeleteVertexArrays { get; }
public delegate void GlDeleteProgram(int program);

84
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,69 +1,77 @@
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}");
IntPtr proc = IntPtr.Zero;
if (a.EntryPoints.Length == 1)
proc = getProcAddress(a.EntryPoints[0], a.Optional);
else
{
foreach (var ep in a.EntryPoints)
{
proc = getProcAddress(ep, true);
if(proc != IntPtr.Zero)
break;
}
if (proc == IntPtr.Zero && !a.Optional)
throw new OpenGlException("Unable to resolve function by any of aliases " +
string.Join(", ", a.EntryPoints));
}
if (proc != IntPtr.Zero)
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType));
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));
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);
}
}

6
src/Avalonia.X11/Glx/Glx.cs

@ -115,17 +115,17 @@ 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)
{

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

@ -27,7 +27,8 @@ namespace Avalonia.X11.Glx
Version = version;
SampleCount = sampleCount;
StencilSize = stencilSize;
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
using (MakeCurrent())
GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress);
}
public GlxDisplay Display { get; }

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

@ -87,7 +87,7 @@ namespace Avalonia.X11.Glx
sampleCount, stencilSize, true);
using (DeferredContext.MakeCurrent())
{
var glInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
var glInterface = DeferredContext.GlInterface;
if (glInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (glInterface.Renderer == null)

Loading…
Cancel
Save