20 changed files with 821 additions and 750 deletions
@ -1,79 +0,0 @@ |
|||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Reflection; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using Avalonia.Platform.Interop; |
|
||||
|
|
||||
namespace Avalonia.OpenGL |
|
||||
{ |
|
||||
public class GlInterfaceBase : GlInterfaceBase<object> |
|
||||
{ |
|
||||
public GlInterfaceBase(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class GlInterfaceBase<TContext> |
|
||||
{ |
|
||||
private readonly Func<string, IntPtr> _getProcAddress; |
|
||||
public GlInterfaceBase(Func<string, IntPtr> getProcAddress, TContext context) |
|
||||
{ |
|
||||
_getProcAddress = getProcAddress; |
|
||||
foreach (var prop in this.GetType().GetProperties()) |
|
||||
{ |
|
||||
var attrs = prop.GetCustomAttributes() |
|
||||
.Where(a => |
|
||||
a is IGlEntryPointAttribute || a is IGlEntryPointAttribute<TContext>) |
|
||||
.ToList(); |
|
||||
if(attrs.Count == 0) |
|
||||
continue; |
|
||||
|
|
||||
var isOptional = prop.GetCustomAttribute<GlOptionalEntryPoint>() != null; |
|
||||
|
|
||||
var fieldName = $"<{prop.Name}>k__BackingField"; |
|
||||
var field = prop.DeclaringType.GetField(fieldName, |
|
||||
BindingFlags.Instance | BindingFlags.NonPublic); |
|
||||
if (field == null) |
|
||||
throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); |
|
||||
|
|
||||
|
|
||||
IntPtr proc = IntPtr.Zero; |
|
||||
foreach (var attr in attrs) |
|
||||
{ |
|
||||
if (attr is IGlEntryPointAttribute<TContext> typed) |
|
||||
proc = typed.GetProcAddress(context, getProcAddress); |
|
||||
else if (attr is IGlEntryPointAttribute untyped) |
|
||||
proc = untyped.GetProcAddress(getProcAddress); |
|
||||
if (proc != IntPtr.Zero) |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
if (proc != IntPtr.Zero) |
|
||||
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); |
|
||||
else if (!isOptional) |
|
||||
throw new OpenGlException("Unable to find a suitable GL function for " + prop.Name); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) => |
|
||||
(proc) => |
|
||||
{ |
|
||||
using (var u = new Utf8Buffer(proc)) |
|
||||
{ |
|
||||
var rv = func(u); |
|
||||
return rv; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,346 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
|
||||
|
namespace Generator; |
||||
|
|
||||
|
[Generator(LanguageNames.CSharp)] |
||||
|
public class GetProcAddressInitializationGenerator : IIncrementalGenerator |
||||
|
{ |
||||
|
const string GetProcAddressFullName = "global::Avalonia.SourceGenerator.GetProcAddressAttribute"; |
||||
|
|
||||
|
public void Initialize(IncrementalGeneratorInitializationContext context) |
||||
|
{ |
||||
|
/* |
||||
|
Console.WriteLine("PID: " + Process.GetCurrentProcess().Id); |
||||
|
|
||||
|
File.WriteAllText("/tmp/pid", "PID: " + Process.GetCurrentProcess().Id); |
||||
|
while (!Debugger.IsAttached) |
||||
|
{ |
||||
|
Thread.Sleep(1000); |
||||
|
}*/ |
||||
|
var allMethodsWithAttributes = context.SyntaxProvider |
||||
|
.CreateSyntaxProvider( |
||||
|
static (s, _) => s is MethodDeclarationSyntax |
||||
|
{ |
||||
|
AttributeLists.Count: > 0, |
||||
|
} md && md.Modifiers.Any(m=>m.IsKind(SyntaxKind.PartialKeyword)), |
||||
|
static (context, _) => |
||||
|
(IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); |
||||
|
|
||||
|
var fieldsWithAttribute = allMethodsWithAttributes |
||||
|
.Where(s => s.HasAttributeWithFullyQualifiedName(GetProcAddressFullName)); |
||||
|
|
||||
|
var all = fieldsWithAttribute.Collect(); |
||||
|
context.RegisterSourceOutput(all, static (context, methods) => |
||||
|
{ |
||||
|
foreach (var typeGroup in methods.GroupBy(f => f.ContainingType)) |
||||
|
{ |
||||
|
var nextContext = 0; |
||||
|
var contexts = new Dictionary<string, int>(); |
||||
|
|
||||
|
string GetContextNameFromIndex(int c) => "context" + (c == 0 ? "" : c); |
||||
|
string GetContextName(string type) |
||||
|
{ |
||||
|
if (contexts.TryGetValue(type, out var idx)) |
||||
|
return GetContextNameFromIndex(idx); |
||||
|
if (nextContext != 0) |
||||
|
idx += nextContext; |
||||
|
nextContext++; |
||||
|
return GetContextNameFromIndex(contexts[type] = idx); |
||||
|
} |
||||
|
|
||||
|
var classBuilder = new StringBuilder(); |
||||
|
if (typeGroup.Key.ContainingNamespace != null) |
||||
|
classBuilder |
||||
|
.AppendLine("using System;") |
||||
|
.Append("namespace ") |
||||
|
.Append(typeGroup.Key.ContainingNamespace) |
||||
|
.AppendLine(";"); |
||||
|
classBuilder |
||||
|
.Append("unsafe partial class ") |
||||
|
.AppendLine(typeGroup.Key.Name) |
||||
|
.AppendLine("{"); |
||||
|
var initializeBody = new StringBuilder() |
||||
|
.Pad(2) |
||||
|
.AppendLine("var addr = IntPtr.Zero;"); |
||||
|
|
||||
|
foreach (var method in typeGroup) |
||||
|
{ |
||||
|
var isOptional = false; |
||||
|
var first = true; |
||||
|
var fieldName = "_addr_" + method.Name; |
||||
|
var delegateType = BuildDelegateType(method); |
||||
|
|
||||
|
void AppendNextAddr() |
||||
|
{ |
||||
|
if (first) |
||||
|
{ |
||||
|
first = false; |
||||
|
initializeBody.Pad(2); |
||||
|
} |
||||
|
else |
||||
|
initializeBody |
||||
|
.Pad(2) |
||||
|
.Append("if(addr == IntPtr.Zero) "); |
||||
|
} |
||||
|
|
||||
|
initializeBody |
||||
|
.Pad(2).Append("// Initializing ").AppendLine(method.Name) |
||||
|
.Pad(2) |
||||
|
.AppendLine("addr = IntPtr.Zero;"); |
||||
|
foreach (var attr in method.GetAttributes()) |
||||
|
{ |
||||
|
if (attr.AttributeClass?.HasFullyQualifiedName(GetProcAddressFullName) == true) |
||||
|
{ |
||||
|
string? primaryName = null; |
||||
|
foreach (var arg in attr.ConstructorArguments) |
||||
|
{ |
||||
|
if (arg.Value is string name) |
||||
|
primaryName = name; |
||||
|
if (arg.Value is bool opt) |
||||
|
isOptional = opt; |
||||
|
} |
||||
|
|
||||
|
if (primaryName != null) |
||||
|
{ |
||||
|
AppendNextAddr(); |
||||
|
initializeBody |
||||
|
.Append("addr = getProcAddress(\"") |
||||
|
.Append(primaryName) |
||||
|
.AppendLine("\");"); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (attr.AttributeClass != null |
||||
|
&& attr.AttributeClass.MemberNames.Contains("GetProcAddress")) |
||||
|
{ |
||||
|
var getProcMethod = attr.AttributeClass.GetMembers() |
||||
|
.FirstOrDefault(m => m.Name == "GetProcAddress") as IMethodSymbol; |
||||
|
if (getProcMethod == null || !getProcMethod.IsStatic || getProcMethod.Parameters.Length < 2) |
||||
|
continue; |
||||
|
var contextName = |
||||
|
GetContextName(getProcMethod |
||||
|
.Parameters[1].Type |
||||
|
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); |
||||
|
AppendNextAddr(); |
||||
|
initializeBody |
||||
|
.Append("addr = ") |
||||
|
.Append(attr.AttributeClass.GetFullyQualifiedName()) |
||||
|
.Append(".GetProcAddress(") |
||||
|
.Append("getProcAddress, ") |
||||
|
.Append(contextName); |
||||
|
|
||||
|
var syntaxNode = (AttributeSyntax)attr.ApplicationSyntaxReference.GetSyntax(); |
||||
|
foreach (var arg in syntaxNode.ArgumentList.Arguments) |
||||
|
initializeBody.Append(", ").Append(arg.GetText()); |
||||
|
initializeBody.AppendLine(");"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!isOptional) |
||||
|
{ |
||||
|
initializeBody |
||||
|
.Pad(2) |
||||
|
.Append("if (addr == IntPtr.Zero) throw new System.EntryPointNotFoundException(\"") |
||||
|
.Append(fieldName).AppendLine("\");"); |
||||
|
} |
||||
|
|
||||
|
initializeBody |
||||
|
.Pad(2) |
||||
|
.Append(fieldName) |
||||
|
.Append(" = (") |
||||
|
.Append(delegateType) |
||||
|
.AppendLine(")addr;"); |
||||
|
|
||||
|
classBuilder |
||||
|
.Pad(1) |
||||
|
.Append(delegateType); |
||||
|
classBuilder |
||||
|
.Append(fieldName) |
||||
|
.AppendLine(";"); |
||||
|
|
||||
|
classBuilder |
||||
|
.Pad(1) |
||||
|
.Append("public partial ") |
||||
|
.Append(method.ReturnType.GetFullyQualifiedName()) |
||||
|
.Append(" ") |
||||
|
.Append(method.Name) |
||||
|
.Append("("); |
||||
|
var firstArg = true; |
||||
|
foreach (var p in method.Parameters) |
||||
|
{ |
||||
|
if (firstArg) |
||||
|
firstArg = false; |
||||
|
else |
||||
|
classBuilder.Append(", "); |
||||
|
AppendRefKind(classBuilder, p.RefKind); |
||||
|
classBuilder |
||||
|
.Append(p.Type.GetFullyQualifiedName()) |
||||
|
.Append(" @") |
||||
|
.Append(p.Name); |
||||
|
} |
||||
|
classBuilder |
||||
|
.AppendLine(")") |
||||
|
.Pad(1) |
||||
|
.AppendLine("{"); |
||||
|
if (isOptional) |
||||
|
classBuilder |
||||
|
.Pad(2) |
||||
|
.Append("if (") |
||||
|
.Append(fieldName) |
||||
|
.Append(" == null) throw new System.EntryPointNotFoundException(\"") |
||||
|
.Append(method.Name) |
||||
|
.AppendLine("\");"); |
||||
|
|
||||
|
foreach(var p in method.Parameters) |
||||
|
if (NeedsPin(p.Type)) |
||||
|
classBuilder.Pad(2) |
||||
|
.Append("fixed(") |
||||
|
.Append(MapToNative(p.Type)) |
||||
|
.Append(" @__p_") |
||||
|
.Append(p.Name) |
||||
|
.Append(" = ") |
||||
|
.Append(p.Name) |
||||
|
.AppendLine(")"); |
||||
|
|
||||
|
classBuilder.Pad(2); |
||||
|
if (!method.ReturnsVoid) |
||||
|
classBuilder.Append("return "); |
||||
|
|
||||
|
var invokeBuilder = new StringBuilder(); |
||||
|
|
||||
|
invokeBuilder |
||||
|
.Append(fieldName) |
||||
|
.Append("("); |
||||
|
firstArg = true; |
||||
|
foreach (var p in method.Parameters) |
||||
|
{ |
||||
|
if (firstArg) |
||||
|
firstArg = false; |
||||
|
else |
||||
|
invokeBuilder.Append(", "); |
||||
|
AppendRefKind(invokeBuilder, p.RefKind); |
||||
|
invokeBuilder |
||||
|
.Append("@") |
||||
|
.Append(ConvertToNative(p.Name, p.Type)); |
||||
|
} |
||||
|
|
||||
|
invokeBuilder.Append(")"); |
||||
|
classBuilder.Append(ConvertToManaged(method.ReturnType, invokeBuilder.ToString())); |
||||
|
|
||||
|
classBuilder.AppendLine(";").Pad(1).AppendLine("}"); |
||||
|
if (isOptional) |
||||
|
classBuilder |
||||
|
.Pad(1) |
||||
|
.Append("public bool Is") |
||||
|
.Append(method.Name) |
||||
|
.Append("Available => ") |
||||
|
.Append(fieldName) |
||||
|
.AppendLine(" != null;"); |
||||
|
} |
||||
|
|
||||
|
classBuilder |
||||
|
.Pad(1) |
||||
|
.Append("void Initialize(Func<string, IntPtr> getProcAddress"); |
||||
|
foreach (var kv in contexts.OrderBy(x => x.Value)) |
||||
|
{ |
||||
|
classBuilder |
||||
|
.Append(", ") |
||||
|
.Append(kv.Key) |
||||
|
.Append(" ") |
||||
|
.Append(GetContextNameFromIndex(kv.Value)); |
||||
|
} |
||||
|
|
||||
|
classBuilder.AppendLine(")").Pad(1).AppendLine("{"); |
||||
|
classBuilder.Append(initializeBody.ToString()); |
||||
|
classBuilder.Append("}\n}"); |
||||
|
|
||||
|
|
||||
|
context.AddSource(typeGroup.Key.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString()); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
static StringBuilder AppendRefKind(StringBuilder sb, RefKind kind) |
||||
|
{ |
||||
|
if (kind == RefKind.Ref) |
||||
|
sb.Append("ref "); |
||||
|
if (kind == RefKind.Out) |
||||
|
sb.Append("out "); |
||||
|
return sb; |
||||
|
} |
||||
|
|
||||
|
static bool NeedsPin(ITypeSymbol type) |
||||
|
{ |
||||
|
if (type.TypeKind == TypeKind.Array) |
||||
|
return true; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
static string ConvertToNative(string name, ITypeSymbol type) |
||||
|
{ |
||||
|
if (NeedsPin(type)) |
||||
|
return "__p_" + name; |
||||
|
if (IsBool(type)) |
||||
|
return $"{name} ? 1 : 0"; |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
static string ConvertToManaged(ITypeSymbol type, string expr) |
||||
|
{ |
||||
|
if (IsBool(type)) |
||||
|
return expr + " != 0"; |
||||
|
return expr; |
||||
|
} |
||||
|
|
||||
|
static bool IsBool(ITypeSymbol type) => type.GetFullyQualifiedName() == "global::System.Boolean" || |
||||
|
type.GetFullyQualifiedName() == "bool"; |
||||
|
|
||||
|
static string MapToNative(ITypeSymbol type) |
||||
|
{ |
||||
|
if (type.TypeKind == TypeKind.Array) |
||||
|
return ((IArrayTypeSymbol)type).ElementType.GetFullyQualifiedName() + "*"; |
||||
|
if (IsBool(type)) |
||||
|
return "int"; |
||||
|
return type.GetFullyQualifiedName(); |
||||
|
} |
||||
|
|
||||
|
static string BuildDelegateType(IMethodSymbol method) |
||||
|
{ |
||||
|
StringBuilder name = new("delegate* unmanaged[Stdcall]<"); |
||||
|
var firstArg = true; |
||||
|
|
||||
|
void AppendArg(string a, RefKind kind) |
||||
|
{ |
||||
|
if (firstArg) |
||||
|
firstArg = false; |
||||
|
else |
||||
|
name.Append(","); |
||||
|
AppendRefKind(name, kind); |
||||
|
name.Append(a); |
||||
|
} |
||||
|
|
||||
|
foreach (var p in method.Parameters) |
||||
|
{ |
||||
|
AppendArg(MapToNative(p.Type), p.RefKind); |
||||
|
} |
||||
|
|
||||
|
AppendArg(MapToNative(method.ReturnType), RefKind.None); |
||||
|
name.Append(">"); |
||||
|
return name.ToString(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System.Collections.Immutable; |
||||
|
using System.Text; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
|
||||
|
namespace Generator; |
||||
|
|
||||
|
static class Helpers |
||||
|
{ |
||||
|
public static StringBuilder Pad(this StringBuilder sb, int count) => sb.Append(' ', count * 4); |
||||
|
|
||||
|
public static string GetFullyQualifiedName(this ISymbol symbol) |
||||
|
{ |
||||
|
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); |
||||
|
} |
||||
|
|
||||
|
public static bool HasFullyQualifiedName(this ISymbol symbol, string name) |
||||
|
{ |
||||
|
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name; |
||||
|
} |
||||
|
|
||||
|
public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name) |
||||
|
{ |
||||
|
ImmutableArray<AttributeData> attributes = symbol.GetAttributes(); |
||||
|
|
||||
|
foreach (AttributeData attribute in attributes) |
||||
|
if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true) |
||||
|
return true; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue