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