switch root namespace name to NITLOAD

This commit is contained in:
Youmu Liu
2026-09-02 18:15:03 +08:00
parent 4218baad48
commit 7c7101ee48
502 changed files with 710 additions and 721 deletions
@@ -0,0 +1,64 @@
// -----------------------------------------------------------------------------
// file="CSharpAbiTypePolicy"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Defines C ABI primitives and external Khronos platform types that cannot
/// be inferred from gl.xml.
/// </summary>
internal static class CSharpAbiTypePolicy
{
private static readonly Dictionary<string, string> _types = new Dictionary<string, string>(
StringComparer.Ordinal)
{
["void"] = "void",
["char"] = "byte",
["signed char"] = "sbyte",
["unsigned char"] = "byte",
["short"] = "short",
["short int"] = "short",
["signed short"] = "short",
["signed short int"] = "short",
["unsigned short"] = "ushort",
["unsigned short int"] = "ushort",
["int"] = "int",
["signed"] = "int",
["signed int"] = "int",
["unsigned"] = "uint",
["unsigned int"] = "uint",
["float"] = "float",
["double"] = "double",
["khronos_int8_t"] = "sbyte",
["khronos_uint8_t"] = "byte",
["khronos_int16_t"] = "short",
["khronos_uint16_t"] = "ushort",
["khronos_int32_t"] = "int",
["khronos_uint32_t"] = "uint",
["khronos_int64_t"] = "long",
["khronos_uint64_t"] = "ulong",
["khronos_intptr_t"] = "nint",
["khronos_uintptr_t"] = "nuint",
["khronos_ssize_t"] = "nint",
["khronos_usize_t"] = "nuint",
["khronos_float_t"] = "float",
};
public static bool TryResolve(
string typeName,
out string equivalentType
)
{
return _types.TryGetValue(typeName, out equivalentType!);
}
}
}
@@ -0,0 +1,63 @@
// -----------------------------------------------------------------------------
// file="GeneratedFileSet"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace StgSharp.GenerateGL.Generation
{
internal sealed record GeneratedFile(
string Name,
string Content
);
internal sealed class GeneratedFileSet
{
public static GeneratedFileSet Empty { get; } = new GeneratedFileSet(
Array.Empty<GeneratedFile>());
private readonly ReadOnlyDictionary<string, GeneratedFile> _filesByName;
public GeneratedFileSet(
IEnumerable<GeneratedFile> files
)
{
ArgumentNullException.ThrowIfNull(files);
ReadOnlyCollection<GeneratedFile> values = Array.AsReadOnly(files.ToArray());
Dictionary<string, GeneratedFile> index = new Dictionary<string, GeneratedFile>(
StringComparer.OrdinalIgnoreCase);
foreach (GeneratedFile file in values)
{
ArgumentException.ThrowIfNullOrWhiteSpace(file.Name);
if (!index.TryAdd(file.Name, file))
{
throw new InvalidOperationException(
$"Generated file name '{file.Name}' is duplicated.");
}
}
Files = values;
_filesByName = new ReadOnlyDictionary<string, GeneratedFile>(index);
}
public IReadOnlyList<GeneratedFile> Files { get; }
public bool TryGetFile(
string name,
out GeneratedFile file
)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
return _filesByName.TryGetValue(name, out file!);
}
}
}
@@ -0,0 +1,336 @@
// -----------------------------------------------------------------------------
// file="GlEnumProjector"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using StgSharp.GenerateGL.Naming;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Projects registry GLenum and GLbitfield parameter groups into C# enum types.
/// </summary>
internal static class GlEnumProjector
{
private static readonly string[] _coreExtensionSuffixes =
{
"ARB",
"EXT",
"KHR",
"NV",
};
private static readonly ReadOnlyDictionary<string, string> _managedTypeNameOverrides =
new ReadOnlyDictionary<string, string>(
new Dictionary<string, string>(StringComparer.Ordinal)
{
["Buffer"] = "ClearBuffer",
});
public static IReadOnlyList<ManagedEnumDefinition> Project(
GlRegistryProjection projection
)
{
ArgumentNullException.ThrowIfNull(projection);
Dictionary<string, bool> referencedGroups = ReadReferencedGroups(projection.Commands);
Dictionary<string, GlEnumDefinition> selectedEnums = projection.Enums
.Where(value => value.Value is not null)
.GroupBy(value => value.Name, StringComparer.Ordinal)
.ToDictionary(
value => value.Key,
value => value.First(),
StringComparer.Ordinal);
Dictionary<string, List<GlEnumDefinition>> membersByGroup =
new Dictionary<string, List<GlEnumDefinition>>(StringComparer.Ordinal);
foreach (GlEnumDefinition value in selectedEnums.Values)
{
foreach (string group in SplitGroups(value.Group))
{
if (!referencedGroups.ContainsKey(group))
{
continue;
}
if (!membersByGroup.TryGetValue(group, out List<GlEnumDefinition>? members))
{
members = new List<GlEnumDefinition>();
membersByGroup.Add(group, members);
}
members.Add(value);
}
}
Dictionary<string, EnumBuilder> enumsByManagedName =
new Dictionary<string, EnumBuilder>(StringComparer.Ordinal);
foreach ((string nativeGroup, bool isFlags) in referencedGroups.OrderBy(
value => value.Key,
StringComparer.Ordinal))
{
if (!membersByGroup.TryGetValue(nativeGroup, out List<GlEnumDefinition>? members) ||
members.Count == 0)
{
continue;
}
string managedName = ReadManagedTypeName(nativeGroup);
if (!enumsByManagedName.TryGetValue(managedName, out EnumBuilder? builder))
{
builder = new EnumBuilder(managedName);
enumsByManagedName.Add(managedName, builder);
}
builder.AddGroup(nativeGroup, isFlags, members);
}
return Array.AsReadOnly(
enumsByManagedName.Values
.OrderBy(value => value.Name, StringComparer.Ordinal)
.Select(value => value.Build())
.ToArray());
}
private static Dictionary<string, bool> ReadReferencedGroups(
IReadOnlyList<GlCommandDefinition> commands
)
{
Dictionary<string, bool> result = new Dictionary<string, bool>(
StringComparer.Ordinal);
foreach (GlCommandDefinition command in commands)
{
AddReferencedGroups(
result,
command.Return.Group,
command.Return.TypeName);
foreach (GlParameterDefinition parameter in command.Parameters)
{
AddReferencedGroups(result, parameter.Group, parameter.TypeName);
}
}
return result;
}
private static void AddReferencedGroups(
Dictionary<string, bool> result,
string? groupNames,
string? nativeType
)
{
if (groupNames is null || nativeType is not ("GLenum" or "GLbitfield"))
{
return;
}
bool isFlags = string.Equals(nativeType, "GLbitfield", StringComparison.Ordinal);
foreach (string group in SplitGroups(groupNames))
{
result[group] = result.TryGetValue(group, out bool previous)
? previous || isFlags
: isFlags;
}
}
private static IEnumerable<string> SplitGroups(
string? value
)
{
if (string.IsNullOrWhiteSpace(value))
{
yield break;
}
foreach (string item in value.Split(','))
{
string group = item.Trim();
if (group.Length != 0)
{
yield return group;
}
}
}
private static string ReadManagedTypeName(
string nativeGroup
)
{
if (_managedTypeNameOverrides.TryGetValue(nativeGroup, out string? overrideName))
{
return overrideName;
}
string value = nativeGroup;
foreach (string suffix in _coreExtensionSuffixes)
{
if (value.Length > suffix.Length && value.EndsWith(
suffix,
StringComparison.Ordinal))
{
value = value[..^suffix.Length];
break;
}
}
return CSharpIdentifierPolicy.NormalizeMemberName(SanitizeIdentifier(value));
}
private static string ReadManagedMemberName(
string nativeName
)
{
string value = nativeName.StartsWith("GL_", StringComparison.Ordinal)
? nativeName[3..]
: nativeName;
StringBuilder builder = new StringBuilder(value.Length);
foreach (string component in value.Split('_', StringSplitOptions.RemoveEmptyEntries))
{
if (component.Length == 0)
{
continue;
}
if (char.IsLetter(component[0]))
{
builder.Append(char.ToUpperInvariant(component[0]));
if (component.Length > 1)
{
foreach (char character in component.AsSpan(1))
{
builder.Append(ToAsciiLowerInvariant(character));
}
}
}
else
{
builder.Append(component);
}
}
string result = builder.Length == 0 ? "Value" : builder.ToString();
return CSharpIdentifierPolicy.NormalizeMemberName(SanitizeIdentifier(result));
}
private static char ToAsciiLowerInvariant(
char value
)
{
return value is >= 'A' and <= 'Z' ? (char)(value + ('a' - 'A')) : value;
}
private static string SanitizeIdentifier(
string value
)
{
StringBuilder builder = new StringBuilder(value.Length + 1);
if (value.Length == 0 || !char.IsLetter(value[0]) && value[0] != '_')
{
builder.Append('_');
}
foreach (char character in value)
{
builder.Append(char.IsLetterOrDigit(character) || character == '_'
? character
: '_');
}
return builder.ToString();
}
private sealed class EnumBuilder
{
private readonly Dictionary<string, ManagedEnumMemberDefinition> _members =
new Dictionary<string, ManagedEnumMemberDefinition>(StringComparer.Ordinal);
private readonly HashSet<string> _nativeGroups = new HashSet<string>(
StringComparer.Ordinal);
public EnumBuilder(
string name
)
{
Name = name;
}
public bool IsFlags { get; private set; }
public string Name { get; }
public void AddGroup(
string nativeGroup,
bool isFlags,
IEnumerable<GlEnumDefinition> members
)
{
_nativeGroups.Add(nativeGroup);
IsFlags |= isFlags;
foreach (GlEnumDefinition value in members)
{
string name = ReadManagedMemberName(value.Name);
if (string.Equals(name, Name, StringComparison.Ordinal))
{
name = $"{name}Value";
}
if (_members.TryGetValue(name, out ManagedEnumMemberDefinition? previous))
{
if (!string.Equals(
previous.NativeValue.Value,
value.Value,
StringComparison.Ordinal))
{
throw new InvalidDataException(
$"Enum member '{Name}.{name}' maps to conflicting native values " +
$"'{previous.NativeValue.Name}' and '{value.Name}'.");
}
continue;
}
_members.Add(name, new ManagedEnumMemberDefinition(name, value));
}
}
public ManagedEnumDefinition Build()
{
List<ManagedEnumMemberDefinition> members = _members.Values
.OrderBy(value => value.Name, StringComparer.Ordinal)
.ToList();
if (IsFlags && members.All(value => value.NativeValue.Value != "0" &&
value.NativeValue.Value != "0x0" &&
value.NativeValue.Value != "0x00000000"))
{
members.Insert(
0,
new ManagedEnumMemberDefinition(
"None",
new GlEnumDefinition(
"GL_NONE",
"0",
null,
null,
string.Join(",", _nativeGroups),
null,
"Synthetic zero value for a generated flags enum.",
members[0].NativeValue.Element)));
}
return new ManagedEnumDefinition(
Name,
IsFlags,
Array.AsReadOnly(
_nativeGroups.OrderBy(value => value, StringComparer.Ordinal).ToArray()),
Array.AsReadOnly(members.ToArray()));
}
}
}
}
@@ -0,0 +1,122 @@
// -----------------------------------------------------------------------------
// file="GlEnumTypeMap"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Resolves unambiguous scalar GLenum and GLbitfield groups to generated C# types.
/// </summary>
internal sealed class GlEnumTypeMap
{
private readonly Dictionary<string, string> _typesByNativeGroup;
public GlEnumTypeMap(
IEnumerable<ManagedEnumDefinition> definitions
)
{
ArgumentNullException.ThrowIfNull(definitions);
_typesByNativeGroup = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (ManagedEnumDefinition definition in definitions)
{
foreach (string nativeGroup in definition.NativeGroups)
{
if (_typesByNativeGroup.TryGetValue(nativeGroup, out string? previous) &&
!string.Equals(previous, definition.Name, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Native enum group '{nativeGroup}' maps to both " +
$"'{previous}' and '{definition.Name}'.");
}
_typesByNativeGroup[nativeGroup] = definition.Name;
}
}
}
public bool TryReadParameterType(
GlParameterDefinition parameter,
out string managedType
)
{
ArgumentNullException.ThrowIfNull(parameter);
if (!IsEnumDeclaration(parameter.TypeName, parameter.Group))
{
managedType = string.Empty;
return false;
}
return TryReadGroupType(parameter.Group!, out managedType);
}
public bool TryReadReturnType(
GlReturnDefinition value,
out string managedType
)
{
ArgumentNullException.ThrowIfNull(value);
if (!IsEnumDeclaration(value.TypeName, value.Group))
{
managedType = string.Empty;
return false;
}
return TryReadGroupType(value.Group!, out managedType);
}
private static bool IsEnumDeclaration(
string? nativeType,
string? group
)
{
return group is not null &&
nativeType is "GLenum" or "GLbitfield";
}
private bool TryReadGroupType(
string groups,
out string managedType
)
{
string[] nativeGroups = groups.Split(',')
.Select(value => value.Trim())
.Where(value => value.Length != 0)
.ToArray();
if (nativeGroups.Length == 0)
{
managedType = string.Empty;
return false;
}
HashSet<string> candidates = new HashSet<string>(StringComparer.Ordinal);
foreach (string nativeGroup in nativeGroups)
{
if (!_typesByNativeGroup.TryGetValue(nativeGroup, out string? candidate))
{
managedType = string.Empty;
return false;
}
candidates.Add(candidate);
}
if (candidates.Count == 1)
{
managedType = candidates.First();
return true;
}
managedType = string.Empty;
return false;
}
}
}
@@ -0,0 +1,242 @@
// -----------------------------------------------------------------------------
// file="GlEquivalentTypeMapper"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Resolves OpenGL typedef declarations recursively to equivalent C# ABI types.
/// </summary>
internal sealed class GlEquivalentTypeMapper
{
private readonly Dictionary<string, string> _resolvedTypes = new Dictionary<string, string>(
StringComparer.Ordinal);
private readonly GlRegistryModel _registry;
public GlEquivalentTypeMapper(
GlRegistryModel registry
)
{
ArgumentNullException.ThrowIfNull(registry);
_registry = registry;
}
public string ReadReturnType(
GlCommandDefinition command
)
{
ArgumentNullException.ThrowIfNull(command);
return ReadDeclarationType(
command.Return.TypeName,
command.Return.DeclarationText,
command.Name);
}
public string ReadParameterType(
GlParameterDefinition parameter
)
{
ArgumentNullException.ThrowIfNull(parameter);
return ReadDeclarationType(
parameter.TypeName,
parameter.DeclarationText,
parameter.Name);
}
private string ReadDeclarationType(
string? typeName,
string declaration,
string identifier
)
{
if (typeName is null)
{
return ResolveTypeExpression(RemoveIdentifier(declaration, identifier));
}
string equivalentType = ResolveNamedType(
typeName,
new HashSet<string>(StringComparer.Ordinal));
int pointerDepth = declaration.Count(value => value == '*');
if (declaration.Contains('[', StringComparison.Ordinal))
{
pointerDepth++;
}
return equivalentType + new string('*', pointerDepth);
}
private string ResolveNamedType(
string typeName,
HashSet<string> resolving
)
{
if (CSharpAbiTypePolicy.TryResolve(typeName, out string abiType))
{
return abiType;
}
if (_resolvedTypes.TryGetValue(typeName, out string? resolvedType))
{
return resolvedType;
}
if (!resolving.Add(typeName))
{
throw new InvalidDataException(
$"OpenGL type alias cycle contains '{typeName}'.");
}
if (!_registry.TypesByName.TryGetValue(
typeName,
out IReadOnlyList<GlTypeDefinition>? definitions) ||
definitions.Count != 1)
{
throw new InvalidDataException(
$"OpenGL type '{typeName}' does not have one registry definition.");
}
GlTypeDefinition definition = definitions[0];
string declaration = NormalizeWhitespace(definition.DeclarationText);
string value;
if (TryReadCallbackType(typeName, declaration, resolving, out string callbackType))
{
value = callbackType;
}
else
{
MatchCollection aliases = Regex.Matches(
declaration,
$@"typedef\s+(?<target>.+?)\s*\b{Regex.Escape(typeName)}\b\s*;",
RegexOptions.CultureInvariant);
if (aliases.Count == 1)
{
value = ResolveTypeExpression(
aliases[0].Groups["target"].Value,
resolving);
}
else if (declaration == $"{typeName};" || declaration.StartsWith(
"struct ",
StringComparison.Ordinal))
{
value = "void";
}
else if (aliases.Count > 1)
{
throw new InvalidDataException(
$"OpenGL type '{typeName}' has platform-dependent typedefs and " +
"requires an explicit target policy.");
}
else
{
throw new InvalidDataException(
$"Cannot read typedef declaration '{definition.DeclarationText}'.");
}
}
resolving.Remove(typeName);
_resolvedTypes.Add(typeName, value);
return value;
}
private bool TryReadCallbackType(
string typeName,
string declaration,
HashSet<string> resolving,
out string type
)
{
Match match = Regex.Match(
declaration,
$@"^typedef\s+(?<return>.+?)\s*\(\s*\*\s*{Regex.Escape(typeName)}\s*\)" +
@"\s*\((?<parameters>.*)\)\s*;$",
RegexOptions.CultureInvariant);
if (!match.Success)
{
type = string.Empty;
return false;
}
List<string> types = new List<string>();
string parameters = match.Groups["parameters"].Value.Trim();
if (parameters.Length != 0 && parameters != "void")
{
foreach (string parameter in parameters.Split(','))
{
types.Add(ResolveTypeExpression(RemoveLastIdentifier(parameter), resolving));
}
}
types.Add(ResolveTypeExpression(match.Groups["return"].Value, resolving));
type = $"delegate* unmanaged<{string.Join(", ", types)}>";
return true;
}
private string ResolveTypeExpression(
string expression,
HashSet<string>? resolving = null
)
{
resolving ??= new HashSet<string>(StringComparer.Ordinal);
string value = Regex.Replace(
NormalizeWhitespace(expression),
@"\b(?:const|volatile|restrict)\b",
string.Empty,
RegexOptions.CultureInvariant).Trim();
int pointerDepth = value.Count(character => character == '*');
value = value.Replace("*", string.Empty, StringComparison.Ordinal).Trim();
if (value.StartsWith("struct ", StringComparison.Ordinal))
{
value = "void";
}
string equivalentType = ResolveNamedType(value, resolving);
return equivalentType + new string('*', pointerDepth);
}
private static string RemoveIdentifier(
string declaration,
string identifier
)
{
int index = declaration.LastIndexOf(identifier, StringComparison.Ordinal);
return index < 0
? declaration
: declaration.Remove(index, identifier.Length);
}
private static string RemoveLastIdentifier(
string declaration
)
{
Match match = Regex.Match(
declaration.Trim(),
@"^(?<type>.+?)(?<name>[A-Za-z_][A-Za-z0-9_]*)$",
RegexOptions.CultureInvariant);
return match.Success ? match.Groups["type"].Value.Trim() : declaration.Trim();
}
private static string NormalizeWhitespace(
string value
)
{
return Regex.Replace(
value,
@"\s+",
" ",
RegexOptions.CultureInvariant).Trim();
}
}
}
@@ -0,0 +1,20 @@
// -----------------------------------------------------------------------------
// file="GlGenerationTarget"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
namespace StgSharp.GenerateGL.Generation
{
[Flags]
internal enum GlGenerationTarget
{
None = 0,
Api = 1,
Enum = 2,
All = Api | Enum,
}
}
@@ -0,0 +1,111 @@
// -----------------------------------------------------------------------------
// file="GlGetValueApiFilter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Replaces desktop state query pointer families with typed Span overloads.
/// </summary>
internal sealed class GlGetValueApiFilter : IGlApiFilter
{
private static readonly Regex _pattern = new Regex(
@"^glGet(?<type>Boolean|Double|Float|Integer64|Integer)(?<indexed>i_)?v$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
public IReadOnlyList<ManagedMethodDefinition> Apply(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods,
GlEquivalentTypeMapper typeMapper
)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentNullException.ThrowIfNull(methods);
ArgumentNullException.ThrowIfNull(typeMapper);
Match match = _pattern.Match(command.Name);
if (!match.Success)
{
return methods;
}
bool isIndexed = match.Groups["indexed"].Success;
int prefixCount = isIndexed ? 2 : 1;
if (typeMapper.ReadReturnType(command) != "void" ||
command.Parameters.Count != prefixCount + 1)
{
throw new InvalidOperationException(
$"State query '{command.Name}' does not match its recognized shape.");
}
GlParameterDefinition output = command.Parameters[^1];
string elementType = ReadElementType(match.Groups["type"].Value);
string expectedLength = $"COMPSIZE({command.Parameters[0].Name})";
if (output.DeclarationText.Contains("const", StringComparison.Ordinal) ||
typeMapper.ReadParameterType(output) != $"{elementType}*" ||
!string.Equals(output.Length, expectedLength, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"State query '{command.Name}' has inconsistent output metadata.");
}
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>();
List<ManagedArgument> arguments = new List<ManagedArgument>();
for (int index = 0; index < prefixCount; index++)
{
GlParameterDefinition source = command.Parameters[index];
parameters.Add(
new ManagedParameterDefinition(
typeMapper.ReadParameterType(source),
source.Name,
source));
arguments.Add(new ParameterArgument(index));
}
parameters.Add(
new ManagedParameterDefinition(
$"Span<{elementType}>",
"values",
output));
return Array.AsReadOnly(
new[]
{
new ManagedMethodDefinition(
"GetValue",
"void",
Array.AsReadOnly(parameters.ToArray()),
command,
new QuerySpanInvocation(
Array.AsReadOnly(arguments.ToArray()),
parameters.Count - 1,
elementType)),
});
}
private static string ReadElementType(
string name
)
{
return name switch
{
"Boolean" => "byte",
"Double" => "double",
"Float" => "float",
"Integer" => "int",
"Integer64" => "long",
_ => throw new InvalidOperationException(
$"Unsupported state query component type '{name}'."),
};
}
}
}
@@ -0,0 +1,169 @@
// -----------------------------------------------------------------------------
// file="GlHandleApiFilter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Replaces registry object-class GLuint values with the managed GlHandle type.
/// </summary>
internal sealed class GlHandleApiFilter : IGlApiFilter
{
public IReadOnlyList<ManagedMethodDefinition> Apply(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods,
GlEquivalentTypeMapper typeMapper
)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentNullException.ThrowIfNull(methods);
ArgumentNullException.ThrowIfNull(typeMapper);
bool wrapsReturn = string.Equals(
command.Return.TypeName,
"GLuint",
StringComparison.Ordinal) &&
command.Return.ObjectClass is not null;
return Array.AsReadOnly(
methods.Select(
method => ConvertMethod(method, wrapsReturn)).ToArray());
}
private static ManagedMethodDefinition ConvertMethod(
ManagedMethodDefinition method,
bool wrapsReturn
)
{
ManagedParameterDefinition[] converted = method.Parameters.Select(
ConvertParameter).ToArray();
if (method.Invocation is not DirectInvocation direct ||
!converted.Any(value => value.IsHandleSpan))
{
return method with
{
ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType,
Parameters = Array.AsReadOnly(converted),
WrapHandleReturn = wrapsReturn,
};
}
Dictionary<string, int> spanByLengthName = new Dictionary<string, int>(
StringComparer.Ordinal);
for (int index = 0; index < converted.Length; index++)
{
ManagedParameterDefinition parameter = converted[index];
string? length = parameter.NativeParameter?.Length;
if (parameter.IsHandleSpan &&
length is not null &&
IsIdentifier(length))
{
spanByLengthName.TryAdd(length, index);
}
}
Dictionary<int, int> oldToNew = new Dictionary<int, int>();
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>();
for (int oldIndex = 0; oldIndex < converted.Length; oldIndex++)
{
ManagedParameterDefinition parameter = converted[oldIndex];
if (spanByLengthName.ContainsKey(parameter.Name))
{
continue;
}
oldToNew.Add(oldIndex, parameters.Count);
parameters.Add(parameter);
}
List<ManagedArgument> arguments = new List<ManagedArgument>(direct.Arguments.Count);
foreach (ManagedArgument argument in direct.Arguments)
{
if (argument is not ParameterArgument sourceArgument)
{
arguments.Add(argument);
continue;
}
ManagedParameterDefinition sourceParameter = converted[sourceArgument.ParameterIndex];
if (spanByLengthName.TryGetValue(
sourceParameter.Name,
out int spanOldIndex))
{
arguments.Add(new SpanLengthArgument(oldToNew[spanOldIndex]));
}
else
{
arguments.Add(
new ParameterArgument(oldToNew[sourceArgument.ParameterIndex]));
}
}
return method with
{
ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType,
Parameters = Array.AsReadOnly(parameters.ToArray()),
Invocation = new DirectInvocation(Array.AsReadOnly(arguments.ToArray())),
WrapHandleReturn = wrapsReturn,
};
}
private static ManagedParameterDefinition ConvertParameter(
ManagedParameterDefinition parameter
)
{
GlParameterDefinition? source = parameter.NativeParameter;
if (source is null ||
source.ObjectClass is null ||
!string.Equals(source.TypeName, "GLuint", StringComparison.Ordinal))
{
return parameter;
}
int pointerDepth = source.DeclarationText.Count(value => value == '*');
bool isSpan = pointerDepth == 1 && source.Length is not null;
string type;
if (isSpan)
{
type = source.DeclarationText.Contains("const", StringComparison.Ordinal)
? "ReadOnlySpan<GlHandle>"
: "Span<GlHandle>";
}
else
{
type = pointerDepth == 0
? "GlHandle"
: $"GlHandle{new string('*', pointerDepth)}";
}
return parameter with
{
Type = type,
IsHandle = true,
IsHandleSpan = isSpan,
};
}
private static bool IsIdentifier(
string value
)
{
if (value.Length == 0 ||
!char.IsLetter(value[0]) && value[0] != '_')
{
return false;
}
return value.Skip(1).All(character => char.IsLetterOrDigit(character) || character == '_');
}
}
}
@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------------
// file="GlManagedApiSuppressionFilter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Omits raw managed forwards for commands that have safer handwritten public APIs.
/// </summary>
internal sealed class GlManagedApiSuppressionFilter : IGlApiFilter
{
private static readonly HashSet<string> _suppressedCommands = new HashSet<string>(
new[]
{
"glCreateShader",
"glShaderSource",
},
StringComparer.Ordinal);
public IReadOnlyList<ManagedMethodDefinition> Apply(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods,
GlEquivalentTypeMapper typeMapper
)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentNullException.ThrowIfNull(methods);
ArgumentNullException.ThrowIfNull(typeMapper);
return _suppressedCommands.Contains(command.Name)
? Array.Empty<ManagedMethodDefinition>()
: methods;
}
}
}
@@ -0,0 +1,400 @@
// -----------------------------------------------------------------------------
// file="GlOverloadApiFilter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Text.RegularExpressions;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Replaces verified C-style command families with managed overloads.
/// </summary>
internal sealed class GlOverloadApiFilter : IGlApiFilter
{
private static readonly Regex _matrixUniformPattern = new Regex(
@"^gl(?<family>ProgramUniform|Uniform)Matrix(?<columns>[2-4])" +
@"(?:x(?<rows>[2-4]))?(?<type>f|d)v$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _uniformPattern = new Regex(
@"^gl(?<family>ProgramUniform|Uniform)(?<width>[1-4])" +
@"(?<type>ui|f|i|d)(?<vector>v?)$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _vertexAttribPattern = new Regex(
@"^glVertexAttrib(?<width>[1-4])(?<type>d|f|s)(?<vector>v?)$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _redundantVertexAttributeVectorPattern = new Regex(
@"^glVertexAttrib(?:I[1-4](?:i|ui)|L[1-4]d|P[1-4]ui|4Nub)v$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly string[] _components = { "X", "Y", "Z", "W" };
public IReadOnlyList<ManagedMethodDefinition> Apply(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods,
GlEquivalentTypeMapper typeMapper
)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentNullException.ThrowIfNull(methods);
ArgumentNullException.ThrowIfNull(typeMapper);
if (_redundantVertexAttributeVectorPattern.IsMatch(command.Name))
{
return Array.Empty<ManagedMethodDefinition>();
}
Match matrixMatch = _matrixUniformPattern.Match(command.Name);
if (matrixMatch.Success)
{
return RewriteMatrixUniform(command, matrixMatch, typeMapper);
}
Match uniformMatch = _uniformPattern.Match(command.Name);
if (uniformMatch.Success)
{
return RewriteUniform(command, uniformMatch, typeMapper);
}
Match vertexAttribMatch = _vertexAttribPattern.Match(command.Name);
if (vertexAttribMatch.Success)
{
return vertexAttribMatch.Groups["vector"].Value.Length == 0
? Array.AsReadOnly(
new[]
{
methods[0] with { Name = "VertexAttrib" },
})
: Array.Empty<ManagedMethodDefinition>();
}
return methods;
}
private static ReadOnlyCollection<ManagedMethodDefinition> RewriteUniform(
GlCommandDefinition command,
Match match,
GlEquivalentTypeMapper typeMapper
)
{
int width = int.Parse(
match.Groups["width"].Value,
CultureInfo.InvariantCulture);
string componentType = ReadComponentType(match.Groups["type"].Value);
bool isVector = match.Groups["vector"].Value.Length != 0;
int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1;
string methodName = match.Groups["family"].Value;
ValidateUniformSignature(
command,
width,
componentType,
prefixCount,
isVector,
typeMapper);
List<ManagedParameterDefinition> prefix = ReadNativeParameters(
command,
prefixCount,
typeMapper);
List<ManagedArgument> prefixArguments = ReadParameterArguments(prefix.Count);
if (isVector)
{
if (width is 2 or 3)
{
return Array.AsReadOnly(Array.Empty<ManagedMethodDefinition>());
}
GlParameterDefinition valueParameter = command.Parameters[^1];
string spanElementType = width == 1
? componentType
: $"Vec{width}<{componentType}>";
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(prefix)
{
new ManagedParameterDefinition(
$"ReadOnlySpan<{spanElementType}>",
"values",
valueParameter),
};
ManagedInvocation invocation = width == 1
? new PinnedSpanInvocation(
prefixArguments,
parameters.Count - 1,
componentType)
: new PinnedVectorSpanInvocation(
prefixArguments,
parameters.Count - 1,
componentType,
spanElementType);
return Array.AsReadOnly(
new[]
{
new ManagedMethodDefinition(
methodName,
"void",
Array.AsReadOnly(parameters.ToArray()),
command,
invocation),
});
}
List<ManagedMethodDefinition> methods = new List<ManagedMethodDefinition>();
List<ManagedParameterDefinition> scalarParameters = ReadNativeParameters(
command,
command.Parameters.Count,
typeMapper);
methods.Add(
new ManagedMethodDefinition(
methodName,
"void",
Array.AsReadOnly(scalarParameters.ToArray()),
command,
new DirectInvocation(ReadParameterArguments(scalarParameters.Count))));
if (width > 1)
{
List<ManagedParameterDefinition> vectorParameters = new List<ManagedParameterDefinition>(prefix)
{
new ManagedParameterDefinition(
$"Vec{width}<{componentType}>",
"value"),
};
List<ManagedArgument> vectorArguments = new List<ManagedArgument>(prefixArguments);
for (int index = 0; index < width; index++)
{
vectorArguments.Add(
new VectorComponentArgument(
vectorParameters.Count - 1,
_components[index]));
}
methods.Add(
new ManagedMethodDefinition(
methodName,
"void",
Array.AsReadOnly(vectorParameters.ToArray()),
command,
new DirectInvocation(Array.AsReadOnly(vectorArguments.ToArray()))));
}
return Array.AsReadOnly(methods.ToArray());
}
private static IReadOnlyList<ManagedMethodDefinition> RewriteMatrixUniform(
GlCommandDefinition command,
Match match,
GlEquivalentTypeMapper typeMapper
)
{
int columns = int.Parse(
match.Groups["columns"].Value,
CultureInfo.InvariantCulture);
int rows = match.Groups["rows"].Success
? int.Parse(
match.Groups["rows"].Value,
CultureInfo.InvariantCulture)
: columns;
string componentType = ReadComponentType(match.Groups["type"].Value);
int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1;
string methodName = match.Groups["family"].Value;
ValidateMatrixSignature(
command,
columns,
rows,
componentType,
prefixCount,
typeMapper);
if (columns != 4 || rows != 4 || componentType != "float")
{
return Array.Empty<ManagedMethodDefinition>();
}
List<ManagedParameterDefinition> prefix = ReadNativeParameters(
command,
prefixCount,
typeMapper);
List<ManagedArgument> prefixArguments = ReadParameterArguments(prefix.Count);
GlParameterDefinition transposeSource = command.Parameters[prefixCount + 1];
GlParameterDefinition valueSource = command.Parameters[prefixCount + 2];
List<ManagedParameterDefinition> singleParameters = new List<ManagedParameterDefinition>(prefix)
{
new ManagedParameterDefinition("bool", "transpose", transposeSource),
new ManagedParameterDefinition("GraphicsMatrix", "value", valueSource),
};
List<ManagedParameterDefinition> spanParameters = new List<ManagedParameterDefinition>(prefix)
{
new ManagedParameterDefinition("bool", "transpose", transposeSource),
new ManagedParameterDefinition(
"ReadOnlySpan<GraphicsMatrix>",
"values",
valueSource),
};
return Array.AsReadOnly(
new[]
{
new ManagedMethodDefinition(
methodName,
"void",
Array.AsReadOnly(singleParameters.ToArray()),
command,
new Matrix4Invocation(
prefixArguments,
singleParameters.Count - 2,
singleParameters.Count - 1,
false)),
new ManagedMethodDefinition(
methodName,
"void",
Array.AsReadOnly(spanParameters.ToArray()),
command,
new Matrix4Invocation(
prefixArguments,
spanParameters.Count - 2,
spanParameters.Count - 1,
true)),
});
}
private static void ValidateUniformSignature(
GlCommandDefinition command,
int width,
string componentType,
int prefixCount,
bool isVector,
GlEquivalentTypeMapper typeMapper
)
{
int expectedCount = prefixCount + (isVector ? 2 : width);
if (command.Parameters.Count != expectedCount)
{
throw new InvalidOperationException(
$"Uniform command '{command.Name}' does not match its recognized shape.");
}
if (isVector)
{
GlParameterDefinition count = command.Parameters[prefixCount];
GlParameterDefinition value = command.Parameters[prefixCount + 1];
string expectedLength = $"{count.Name}*{width}";
string expectedKind = $"Vector{width}";
if (typeMapper.ReadParameterType(value) != $"{componentType}*" ||
!string.Equals(value.Length, expectedLength, StringComparison.Ordinal) &&
!(width == 1 && string.Equals(
value.Length,
count.Name,
StringComparison.Ordinal)) ||
!string.Equals(value.Kind, expectedKind, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Uniform command '{command.Name}' has inconsistent vector metadata.");
}
return;
}
for (int index = prefixCount; index < command.Parameters.Count; index++)
{
if (typeMapper.ReadParameterType(command.Parameters[index]) != componentType)
{
throw new InvalidOperationException(
$"Uniform command '{command.Name}' has inconsistent component types.");
}
}
}
private static void ValidateMatrixSignature(
GlCommandDefinition command,
int columns,
int rows,
string componentType,
int prefixCount,
GlEquivalentTypeMapper typeMapper
)
{
if (command.Parameters.Count != prefixCount + 3)
{
throw new InvalidOperationException(
$"Matrix command '{command.Name}' does not match its recognized shape.");
}
GlParameterDefinition count = command.Parameters[prefixCount];
GlParameterDefinition value = command.Parameters[prefixCount + 2];
if (typeMapper.ReadParameterType(value) != $"{componentType}*" ||
!string.Equals(
value.Kind,
$"Matrix{columns}x{rows}",
StringComparison.Ordinal) ||
!string.Equals(
value.Length,
$"{count.Name}*{columns * rows}",
StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Matrix command '{command.Name}' has inconsistent matrix metadata.");
}
}
private static List<ManagedParameterDefinition> ReadNativeParameters(
GlCommandDefinition command,
int count,
GlEquivalentTypeMapper typeMapper
)
{
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(count);
for (int index = 0; index < count; index++)
{
GlParameterDefinition source = command.Parameters[index];
parameters.Add(
new ManagedParameterDefinition(
typeMapper.ReadParameterType(source),
source.Name,
source));
}
return parameters;
}
private static List<ManagedArgument> ReadParameterArguments(
int count
)
{
List<ManagedArgument> arguments = new List<ManagedArgument>(count);
for (int index = 0; index < count; index++)
{
arguments.Add(new ParameterArgument(index));
}
return arguments;
}
private static string ReadComponentType(
string suffix
)
{
return suffix switch
{
"f" => "float",
"i" => "int",
"ui" => "uint",
"d" => "double",
_ => throw new InvalidOperationException(
$"Unsupported uniform component suffix '{suffix}'."),
};
}
}
}
@@ -0,0 +1,23 @@
// -----------------------------------------------------------------------------
// file="IGlApiFilter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
internal interface IGlApiFilter
{
IReadOnlyList<ManagedMethodDefinition> Apply(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods,
GlEquivalentTypeMapper typeMapper
);
}
}
@@ -0,0 +1,80 @@
// -----------------------------------------------------------------------------
// file="ManagedApiModel"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
internal sealed record ManagedMethodDefinition(
string Name,
string ReturnType,
IReadOnlyList<ManagedParameterDefinition> Parameters,
GlCommandDefinition NativeCommand,
ManagedInvocation Invocation,
bool WrapHandleReturn = false,
bool IsEnumReturn = false,
string? DocumentationPage = null
);
internal sealed record ManagedParameterDefinition(
string Type,
string Name,
GlParameterDefinition? NativeParameter = null,
bool IsHandle = false,
bool IsHandleSpan = false,
bool IsEnum = false
);
internal abstract record ManagedInvocation;
internal sealed record DirectInvocation(
IReadOnlyList<ManagedArgument> Arguments
) : ManagedInvocation;
internal sealed record PinnedSpanInvocation(
IReadOnlyList<ManagedArgument> PrefixArguments,
int SpanParameterIndex,
string ElementType
) : ManagedInvocation;
internal sealed record PinnedVectorSpanInvocation(
IReadOnlyList<ManagedArgument> PrefixArguments,
int SpanParameterIndex,
string ComponentType,
string VectorType
) : ManagedInvocation;
internal sealed record Matrix4Invocation(
IReadOnlyList<ManagedArgument> PrefixArguments,
int TransposeParameterIndex,
int MatrixParameterIndex,
bool IsSpan
) : ManagedInvocation;
internal sealed record QuerySpanInvocation(
IReadOnlyList<ManagedArgument> PrefixArguments,
int SpanParameterIndex,
string ElementType
) : ManagedInvocation;
internal abstract record ManagedArgument;
internal sealed record ParameterArgument(
int ParameterIndex
) : ManagedArgument;
internal sealed record VectorComponentArgument(
int ParameterIndex,
string Component
) : ManagedArgument;
internal sealed record SpanLengthArgument(
int ParameterIndex
) : ManagedArgument;
}
@@ -0,0 +1,268 @@
// -----------------------------------------------------------------------------
// file="ManagedApiPlanner"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Naming;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
internal sealed class ManagedApiPlanner
{
private readonly IReadOnlyList<IGlApiFilter> _filters = Array.AsReadOnly<IGlApiFilter>(
new IGlApiFilter[]
{
new GlManagedApiSuppressionFilter(),
new GlOverloadApiFilter(),
new GlGetValueApiFilter(),
new GlHandleApiFilter(),
});
private readonly GlEquivalentTypeMapper _typeMapper;
private readonly GlEnumTypeMap _enumTypeMap;
private readonly GlManagedApiNamePolicy _namePolicy;
public ManagedApiPlanner(
GlEquivalentTypeMapper typeMapper,
GlFunctionFamilyAnalysis familyAnalysis,
GlEnumTypeMap enumTypeMap
)
{
ArgumentNullException.ThrowIfNull(typeMapper);
ArgumentNullException.ThrowIfNull(familyAnalysis);
ArgumentNullException.ThrowIfNull(enumTypeMap);
_typeMapper = typeMapper;
_enumTypeMap = enumTypeMap;
_namePolicy = new GlManagedApiNamePolicy(familyAnalysis);
}
public IReadOnlyList<ManagedMethodDefinition> Plan(
IReadOnlyList<GlCommandDefinition> commands
)
{
ArgumentNullException.ThrowIfNull(commands);
List<ManagedMethodDefinition> result = new List<ManagedMethodDefinition>();
foreach (GlCommandDefinition command in commands)
{
IReadOnlyList<ManagedMethodDefinition> methods = Array.AsReadOnly(
new[]
{
CreateRawMethod(command),
});
foreach (IGlApiFilter filter in _filters)
{
methods = filter.Apply(command, methods, _typeMapper);
}
methods = ApplyEnumTypes(command, methods);
result.AddRange(
methods.Select(
method => method with
{
Name = _namePolicy.ReadMethodName(command, method.Name),
DocumentationPage = _namePolicy.ReadDocumentationPage(command),
Parameters = Array.AsReadOnly(
method.Parameters.Select(
parameter => parameter with
{
Name = CSharpIdentifierPolicy.NormalizeParameterName(
parameter.Name),
}).ToArray()),
}));
}
ResolveNameCollisions(result);
return Array.AsReadOnly(result.ToArray());
}
private ReadOnlyCollection<ManagedMethodDefinition> ApplyEnumTypes(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> methods
)
{
bool hasEnumReturn = _enumTypeMap.TryReadReturnType(
command.Return,
out string enumReturnType);
return Array.AsReadOnly(
methods.Select(
method => ConvertEnumReturn(method, hasEnumReturn, enumReturnType)).ToArray());
}
private ManagedMethodDefinition ConvertEnumReturn(
ManagedMethodDefinition method,
bool hasEnumReturn,
string enumReturnType
)
{
string returnType = string.Empty;
bool isEnumReturn = hasEnumReturn && TryReplaceNativeEnumType(
method.ReturnType,
enumReturnType,
out returnType);
return method with
{
ReturnType = isEnumReturn ? returnType : method.ReturnType,
IsEnumReturn = isEnumReturn,
Parameters = Array.AsReadOnly(
method.Parameters.Select(ConvertEnumParameter).ToArray()),
};
}
private ManagedParameterDefinition ConvertEnumParameter(
ManagedParameterDefinition parameter
)
{
if (parameter.NativeParameter is null ||
!_enumTypeMap.TryReadParameterType(
parameter.NativeParameter,
out string enumType) ||
!TryReplaceNativeEnumType(parameter.Type, enumType, out string convertedType))
{
return parameter;
}
return parameter with
{
Type = convertedType,
IsEnum = true,
};
}
private static bool TryReplaceNativeEnumType(
string nativeType,
string enumType,
out string convertedType
)
{
const string nativeBaseType = "uint";
if (!nativeType.StartsWith(nativeBaseType, StringComparison.Ordinal) ||
nativeType.AsSpan(nativeBaseType.Length).ContainsAnyExcept('*'))
{
convertedType = string.Empty;
return false;
}
convertedType = enumType + nativeType[nativeBaseType.Length..];
return true;
}
public static void Validate(
IEnumerable<ManagedMethodDefinition> methods
)
{
ArgumentNullException.ThrowIfNull(methods);
Dictionary<string, ManagedMethodDefinition> signatures = new Dictionary<string, ManagedMethodDefinition>(
StringComparer.Ordinal);
foreach (ManagedMethodDefinition method in methods)
{
string signature = $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})";
if (signatures.TryGetValue(signature, out ManagedMethodDefinition? previous))
{
throw new InvalidOperationException(
$"Managed overload '{signature}' is produced by both " +
$"'{previous.NativeCommand.Name}' and '{method.NativeCommand.Name}'.");
}
signatures.Add(signature, method);
}
}
private static void ResolveNameCollisions(
List<ManagedMethodDefinition> methods
)
{
ILookup<string, int> indicesBySignature = methods
.Select(
(method, index) => new
{
Index = index,
Signature = ReadManagedSignature(method),
})
.ToLookup(value => value.Signature, value => value.Index, StringComparer.Ordinal);
foreach (IGrouping<string, int> group in indicesBySignature)
{
int[] indices = group.ToArray();
if (indices.Length < 2)
{
continue;
}
string[] nativeNames = indices
.Select(value => methods[value].NativeCommand.Name)
.Distinct(StringComparer.Ordinal)
.ToArray();
if (nativeNames.Length != indices.Length)
{
throw new InvalidOperationException(
$"Managed overload '{group.Key}' is duplicated by one native command.");
}
foreach (int index in indices)
{
ManagedMethodDefinition method = methods[index];
methods[index] = method with
{
Name = GlManagedApiNamePolicy.ReadNativeMethodName(
method.NativeCommand),
};
}
}
}
private static string ReadManagedSignature(
ManagedMethodDefinition method
)
{
return $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})";
}
private ManagedMethodDefinition CreateRawMethod(
GlCommandDefinition command
)
{
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(
command.Parameters.Count);
List<ManagedArgument> arguments = new List<ManagedArgument>(
command.Parameters.Count);
for (int index = 0; index < command.Parameters.Count; index++)
{
GlParameterDefinition source = command.Parameters[index];
parameters.Add(
new ManagedParameterDefinition(
_typeMapper.ReadParameterType(source),
source.Name,
source));
arguments.Add(new ParameterArgument(index));
}
return new ManagedMethodDefinition(
ReadMethodName(command.Name),
_typeMapper.ReadReturnType(command),
Array.AsReadOnly(parameters.ToArray()),
command,
new DirectInvocation(Array.AsReadOnly(arguments.ToArray())));
}
private static string ReadMethodName(
string commandName
)
{
return commandName.StartsWith("gl", StringComparison.Ordinal) &&
commandName.Length > 2
? commandName[2..]
: commandName;
}
}
}
@@ -0,0 +1,24 @@
// -----------------------------------------------------------------------------
// file="ManagedEnumModel"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
internal sealed record ManagedEnumDefinition(
string Name,
bool IsFlags,
IReadOnlyList<string> NativeGroups,
IReadOnlyList<ManagedEnumMemberDefinition> Members
);
internal sealed record ManagedEnumMemberDefinition(
string Name,
GlEnumDefinition NativeValue
);
}
@@ -0,0 +1,916 @@
// -----------------------------------------------------------------------------
// file="OpenGlGenerator"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
{
/// <summary>
/// Produces the first in-memory desktop OpenGL binding files.
/// </summary>
internal static class OpenGlGenerator
{
private static readonly HashSet<string> _csharpKeywords = new HashSet<string>(
new[]
{
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
"char", "checked", "class", "const", "continue", "decimal", "default",
"delegate", "do", "double", "else", "enum", "event", "explicit", "extern",
"false", "finally", "fixed", "float", "for", "foreach", "goto", "if",
"implicit", "in", "int", "interface", "internal", "is", "lock", "long",
"namespace", "new", "null", "object", "operator", "out", "override",
"params", "private", "protected", "public", "readonly", "ref", "return",
"sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string",
"struct", "switch", "this", "throw", "true", "try", "typeof", "uint",
"ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
"volatile", "while",
},
StringComparer.Ordinal);
public static GeneratedFileSet Generate(
GlRegistryProjection projection,
GlGenerationTarget target = GlGenerationTarget.All
)
{
ArgumentNullException.ThrowIfNull(projection);
if (target == GlGenerationTarget.None || (target & ~GlGenerationTarget.All) != 0)
{
throw new ArgumentOutOfRangeException(nameof(target));
}
IReadOnlyList<ManagedEnumDefinition> enums = GlEnumProjector.Project(projection);
GlEnumTypeMap enumTypeMap = new GlEnumTypeMap(enums);
List<GeneratedFile> files = new List<GeneratedFile>();
if ((target & GlGenerationTarget.Enum) != 0)
{
files.Add(new GeneratedFile("glconst.cs", GenerateConstants(projection.Enums)));
files.Add(new GeneratedFile("GlEnums.g.cs", GenerateEnums(enums)));
}
if ((target & GlGenerationTarget.Api) != 0)
{
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(projection);
IReadOnlyList<GlCommandDefinition> commands = Array.AsReadOnly(
commandGroups.SelectMany(value => value.Commands).ToArray());
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(projection.Source);
GlFunctionFamilyAnalysis familyAnalysis = GlFunctionFamilyAnalyzer.Analyze(projection);
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(
typeMapper,
familyAnalysis,
enumTypeMap);
IReadOnlyList<ManagedMethodDefinition> managedMethods = apiPlanner.Plan(commands);
Dictionary<string, IReadOnlyList<ManagedMethodDefinition>> methodsByCommand =
managedMethods.GroupBy(value => value.NativeCommand.Name, StringComparer.Ordinal)
.ToDictionary(
value => value.Key,
value => (IReadOnlyList<ManagedMethodDefinition>)Array.AsReadOnly(
value.ToArray()),
StringComparer.Ordinal);
List<ManagedCommandGroup> managedGroups = commandGroups.Select(
group => new ManagedCommandGroup(
group,
Array.AsReadOnly(
group.Commands.SelectMany(
command => methodsByCommand.TryGetValue(
command.Name,
out IReadOnlyList<ManagedMethodDefinition>? methods)
? methods
: Array.Empty<ManagedMethodDefinition>())
.ToArray()))).ToList();
ManagedApiPlanner.Validate(
managedGroups.SelectMany(value => value.Methods));
files.Add(new GeneratedFile(
"OpenglContext.g.cs",
GenerateContext(commands, typeMapper)));
files.Add(new GeneratedFile(
"glContextShadow.g.cs",
GenerateContextShadow(commands)));
for (int index = 0; index < managedGroups.Count; index++)
{
ManagedCommandGroup managedGroup = managedGroups[index];
GlCommandGroup group = managedGroup.Source;
files.Add(
new GeneratedFile(
group.FileName,
GenerateFunctions(
managedGroup.Methods,
group.FileName,
group.Description,
index == 0)));
}
}
return new GeneratedFileSet(files);
}
private static ReadOnlyCollection<GlCommandGroup> ReadCoreCommandGroups(
GlRegistryProjection projection
)
{
List<GlCommandDefinition> foundation = new List<GlCommandDefinition>();
Dictionary<string, List<GlCommandDefinition>> versionGroups = new Dictionary<string, List<GlCommandDefinition>>(
StringComparer.Ordinal);
List<string> versionOrder = new List<string>();
foreach (GlCommandDefinition command in projection.Commands)
{
if (!projection.CommandIntroducedVersions.TryGetValue(
command.Name,
out string? version))
{
throw new InvalidDataException(
$"Command '{command.Name}' has no introduction version.");
}
if (IsFoundationVersion(version))
{
foundation.Add(command);
continue;
}
if (!versionGroups.TryGetValue(
version,
out List<GlCommandDefinition>? commands))
{
commands = new List<GlCommandDefinition>();
versionGroups.Add(version, commands);
versionOrder.Add(version);
}
commands.Add(command);
}
List<GlCommandGroup> groups = new List<GlCommandGroup>
{
new GlCommandGroup(
"OpenGLFunction.Base.g.cs",
"Desktop OpenGL forwarding methods introduced through 3.2 and retained by the target core profile",
Array.AsReadOnly(foundation.ToArray())),
};
foreach (string version in versionOrder)
{
groups.Add(
new GlCommandGroup(
$"OpenGLFunction.v{version.Replace('.', '_')}.g.cs",
$"Desktop OpenGL {version} core-profile forwarding methods",
Array.AsReadOnly(versionGroups[version].ToArray())));
}
return Array.AsReadOnly(groups.ToArray());
}
private static bool IsFoundationVersion(
string version
)
{
string[] components = version.Split('.');
if (components.Length != 2 ||
!int.TryParse(
components[0],
NumberStyles.None,
CultureInfo.InvariantCulture,
out int major) ||
!int.TryParse(
components[1],
NumberStyles.None,
CultureInfo.InvariantCulture,
out int minor))
{
throw new InvalidDataException(
$"OpenGL feature version '{version}' is not a major.minor number.");
}
return major < 3 || major == 3 && minor <= 2;
}
private static string GenerateConstants(
IReadOnlyList<GlEnumDefinition> enums
)
{
StringBuilder builder = new StringBuilder(512 * 1024);
AppendGeneratedHeader(builder, "glconst.cs", "Desktop OpenGL constants");
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
builder.Append("{\n");
builder.Append(" public static class glConst\n");
builder.Append(" {\n\n");
Dictionary<string, GlEnumDefinition> emitted = new Dictionary<string, GlEnumDefinition>(
StringComparer.Ordinal);
foreach (GlEnumDefinition value in enums)
{
if (value.Value is null)
{
continue;
}
if (emitted.TryGetValue(value.Name, out GlEnumDefinition? previous))
{
if (!string.Equals(previous.Value, value.Value, StringComparison.Ordinal))
{
throw new InvalidDataException(
$"Desktop enum '{value.Name}' has conflicting values " +
$"'{previous.Value}' and '{value.Value}'.");
}
continue;
}
emitted.Add(value.Name, value);
ConstantExpression expression = ReadConstantExpression(value);
builder.Append(" public const ");
builder.Append(expression.Type);
builder.Append(' ');
builder.Append(ReadConstantName(value.Name));
builder.Append(" = ");
builder.Append(expression.Literal);
builder.Append(";\n");
}
builder.Append("\n }\n");
builder.Append("}\n");
return builder.ToString();
}
private static string GenerateEnums(
IReadOnlyList<ManagedEnumDefinition> enums
)
{
StringBuilder builder = new StringBuilder(512 * 1024);
AppendGeneratedHeader(
builder,
"GlEnums.g.cs",
"Strongly typed desktop OpenGL enum groups");
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
builder.Append("{\n");
foreach (ManagedEnumDefinition value in enums)
{
builder.Append(" /// <summary>Native OpenGL group");
builder.Append(value.NativeGroups.Count == 1 ? " " : "s ");
builder.Append(string.Join(", ", value.NativeGroups));
builder.Append(".</summary>\n");
if (value.IsFlags)
{
builder.Append(" [System.Flags]\n");
}
builder.Append(" public enum ");
builder.Append(value.Name);
builder.Append(" : uint\n");
builder.Append(" {\n");
foreach (ManagedEnumMemberDefinition member in value.Members)
{
builder.Append(" ");
builder.Append(member.Name);
builder.Append(" = ");
if (member.NativeValue.Name == "GL_NONE" &&
member.NativeValue.Comment ==
"Synthetic zero value for a generated flags enum.")
{
builder.Append("0u");
}
else
{
builder.Append("unchecked((uint)glConst.");
builder.Append(ReadConstantName(member.NativeValue.Name));
builder.Append(')');
}
builder.Append(",\n");
}
builder.Append(" }\n\n");
}
builder.Append("}\n");
return builder.ToString();
}
private static ConstantExpression ReadConstantExpression(
GlEnumDefinition value
)
{
string source = value.Value!;
bool isHex = source.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
BigInteger number = isHex
? BigInteger.Parse(
$"0{source[2..]}",
NumberStyles.AllowHexSpecifier,
CultureInfo.InvariantCulture)
: BigInteger.Parse(source, NumberStyles.Integer, CultureInfo.InvariantCulture);
if (string.Equals(value.TypeSuffix, "ull", StringComparison.OrdinalIgnoreCase))
{
EnsureRange(number, BigInteger.Zero, ulong.MaxValue, value);
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
}
if (string.Equals(value.TypeSuffix, "u", StringComparison.OrdinalIgnoreCase))
{
EnsureRange(number, BigInteger.Zero, uint.MaxValue, value);
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
}
if (number >= int.MinValue && number <= int.MaxValue)
{
return new ConstantExpression("int", source);
}
if (number >= BigInteger.Zero && number <= uint.MaxValue)
{
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
}
if (number >= long.MinValue && number <= long.MaxValue)
{
return new ConstantExpression("long", ReadLiteral(source, isHex, "L"));
}
if (number >= BigInteger.Zero && number <= ulong.MaxValue)
{
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
}
throw new InvalidDataException(
$"Enum '{value.Name}' value '{source}' does not fit a C# integral type.");
}
private static void EnsureRange(
BigInteger value,
BigInteger minimum,
BigInteger maximum,
GlEnumDefinition definition
)
{
if (value < minimum || value > maximum)
{
throw new InvalidDataException(
$"Enum '{definition.Name}' value '{definition.Value}' is outside its " +
"declared numeric suffix range.");
}
}
private static string ReadLiteral(
string source,
bool isHex,
string suffix
)
{
return isHex ? $"{source}{suffix}" : $"{source}{suffix}";
}
private static string ReadConstantName(
string name
)
{
string value = name.StartsWith("GL_", StringComparison.Ordinal)
? name[3..]
: name;
StringBuilder result = new StringBuilder(value.Length + 1);
if (value.Length == 0 || char.IsDigit(value[0]))
{
result.Append('_');
}
foreach (char character in value)
{
result.Append(char.IsLetterOrDigit(character) || character == '_'
? character
: '_');
}
return result.ToString();
}
private static string GenerateContext(
IReadOnlyList<GlCommandDefinition> commands,
GlEquivalentTypeMapper typeMapper
)
{
StringBuilder builder = new StringBuilder(128 * 1024);
AppendGeneratedHeader(
builder,
"OpenglContext.g.cs",
"Desktop OpenGL core function pointer table");
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
builder.Append("{\n");
builder.Append(" [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]\n");
builder.Append(" public unsafe partial struct OpenglContext\n");
builder.Append(" {\n");
foreach (GlCommandDefinition command in OrderCommands(commands))
{
builder.Append(" internal delegate* unmanaged<");
foreach (GlParameterDefinition parameter in command.Parameters)
{
builder.Append(typeMapper.ReadParameterType(parameter));
builder.Append(", ");
}
builder.Append(typeMapper.ReadReturnType(command));
builder.Append("> ");
builder.Append(command.Name);
builder.Append(";\n");
}
builder.Append(" }\n");
builder.Append("}\n");
return builder.ToString();
}
private static string GenerateContextShadow(
IReadOnlyList<GlCommandDefinition> commands
)
{
StringBuilder builder = new StringBuilder(64 * 1024);
AppendGeneratedHeader(
builder,
"glContextShadow.g.cs",
"Native-address shadow of the OpenGL function pointer table");
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
builder.Append("{\n");
builder.Append(" [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]\n");
builder.Append(" internal struct glContextShadow\n");
builder.Append(" {\n");
foreach (GlCommandDefinition command in OrderCommands(commands))
{
builder.Append(" internal nint ");
builder.Append(command.Name);
builder.Append(";\n");
}
builder.Append(" }\n");
builder.Append("}\n");
return builder.ToString();
}
private static IEnumerable<GlCommandDefinition> OrderCommands(
IReadOnlyList<GlCommandDefinition> commands
)
{
return commands.OrderBy(
value => value.Name,
StringComparer.Ordinal);
}
private static string GenerateFunctions(
IReadOnlyList<ManagedMethodDefinition> methods,
string outputFileName,
string description,
bool includeContextState
)
{
StringBuilder builder = new StringBuilder(512 * 1024);
AppendGeneratedHeader(builder, outputFileName, description);
builder.Append("using StgSharp.Mathematics.Numeric.Graphics;\n\n");
builder.Append("using System;\n");
builder.Append("using System.Runtime.CompilerServices;\n\n");
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
builder.Append("{\n");
builder.Append(" public unsafe partial class OpenGLFunction\n");
builder.Append(" {\n");
if (includeContextState)
{
builder.Append("\n private readonly OpenglContext* __context;\n");
builder.Append("\n internal OpenGLFunction(OpenglContext* context)\n");
builder.Append(" {\n");
builder.Append(" __context = context;\n");
builder.Append(" }\n");
}
foreach (ManagedMethodDefinition method in methods)
{
AppendMethodDocumentation(builder, method);
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
builder.Append(" public ");
builder.Append(method.ReturnType);
builder.Append(' ');
builder.Append(method.Name);
builder.Append('(');
for (int index = 0; index < method.Parameters.Count; index++)
{
if (index != 0)
{
builder.Append(", ");
}
ManagedParameterDefinition parameter = method.Parameters[index];
builder.Append(parameter.Type);
builder.Append(' ');
builder.Append(EscapeIdentifier(parameter.Name));
}
builder.Append(")\n");
builder.Append(" {\n");
AppendInvocation(builder, method);
builder.Append(" }\n");
}
builder.Append("\n }\n");
builder.Append("}\n");
return builder.ToString();
}
private static void AppendMethodDocumentation(
StringBuilder builder,
ManagedMethodDefinition method
)
{
string declaration = ReadNativeDeclaration(method.NativeCommand);
string page = method.DocumentationPage ?? method.NativeCommand.Name;
builder.Append("\n /// <summary>\n");
builder.Append(" /// <para>Forwards to the native OpenGL entry point.</para>\n");
builder.Append(" /// <para><c>");
builder.Append(EscapeXml(declaration));
builder.Append("</c></para>\n");
builder.Append(" /// <para>See <see href=\"https://docs.gl/gl4/");
builder.Append(page);
builder.Append("\">docs.gl</see>.</para>\n");
builder.Append(" /// </summary>");
}
private static string ReadNativeDeclaration(
GlCommandDefinition command
)
{
return $"{command.Return.DeclarationText}(" +
string.Join(", ", command.Parameters.Select(value => value.DeclarationText)) +
");";
}
private static string EscapeXml(
string value
)
{
return value.Replace("&", "&amp;", StringComparison.Ordinal)
.Replace("<", "&lt;", StringComparison.Ordinal)
.Replace(">", "&gt;", StringComparison.Ordinal)
.Replace("\"", "&quot;", StringComparison.Ordinal)
.Replace("'", "&apos;", StringComparison.Ordinal);
}
private static void AppendInvocation(
StringBuilder builder,
ManagedMethodDefinition method
)
{
switch (method.Invocation)
{
case DirectInvocation direct:
AppendDirectInvocation(builder, method, direct);
break;
case PinnedSpanInvocation pinned:
AppendPinnedSpanInvocation(builder, method, pinned);
break;
case PinnedVectorSpanInvocation pinnedVector:
AppendPinnedVectorSpanInvocation(builder, method, pinnedVector);
break;
case Matrix4Invocation matrix:
AppendMatrix4Invocation(builder, method, matrix);
break;
case QuerySpanInvocation query:
AppendQuerySpanInvocation(builder, method, query);
break;
default:
throw new InvalidOperationException(
$"Unsupported managed invocation '{method.Invocation.GetType().Name}'.");
}
}
private static void AppendQuerySpanInvocation(
StringBuilder builder,
ManagedMethodDefinition method,
QuerySpanInvocation invocation
)
{
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
string spanName = EscapeIdentifier(span.Name);
builder.Append(" fixed (");
builder.Append(invocation.ElementType);
builder.Append("* __value = ");
builder.Append(spanName);
builder.Append(")\n");
builder.Append(" {\n");
builder.Append(" __context->");
builder.Append(method.NativeCommand.Name);
builder.Append('(');
AppendArguments(builder, method, invocation.PrefixArguments);
if (invocation.PrefixArguments.Count != 0)
{
builder.Append(", ");
}
builder.Append("__value);\n");
builder.Append(" }\n");
}
private static void AppendDirectInvocation(
StringBuilder builder,
ManagedMethodDefinition method,
DirectInvocation invocation
)
{
List<int> fixedParameters = new List<int>();
for (int index = 0; index < method.Parameters.Count; index++)
{
if (method.Parameters[index].IsHandleSpan)
{
fixedParameters.Add(index);
}
}
int indentation = 12;
foreach (int parameterIndex in fixedParameters)
{
ManagedParameterDefinition parameter = method.Parameters[parameterIndex];
builder.Append(' ', indentation);
builder.Append("fixed (GlHandle* __");
builder.Append(parameter.Name);
builder.Append(" = ");
builder.Append(EscapeIdentifier(parameter.Name));
builder.Append(")\n");
builder.Append(' ', indentation);
builder.Append("{\n");
indentation += 4;
}
builder.Append(' ', indentation);
if (method.WrapHandleReturn)
{
builder.Append("return GlHandle.Create(");
}
else if (method.IsEnumReturn)
{
builder.Append("return (");
builder.Append(method.ReturnType);
builder.Append(')');
}
else if (method.ReturnType != "void")
{
builder.Append("return ");
}
AppendNativeCall(builder, method, invocation.Arguments);
builder.Append(method.WrapHandleReturn ? ");\n" : ";\n");
for (int index = fixedParameters.Count - 1; index >= 0; index--)
{
indentation -= 4;
builder.Append(' ', indentation);
builder.Append("}\n");
}
}
private static void AppendPinnedSpanInvocation(
StringBuilder builder,
ManagedMethodDefinition method,
PinnedSpanInvocation invocation
)
{
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
builder.Append(" fixed (");
builder.Append(invocation.ElementType);
builder.Append("* __value = ");
builder.Append(EscapeIdentifier(span.Name));
builder.Append(")\n");
builder.Append(" {\n");
builder.Append(" __context->");
builder.Append(method.NativeCommand.Name);
builder.Append('(');
AppendArguments(builder, method, invocation.PrefixArguments);
if (invocation.PrefixArguments.Count != 0)
{
builder.Append(", ");
}
builder.Append(EscapeIdentifier(span.Name));
builder.Append(".Length, __value);\n");
builder.Append(" }\n");
}
private static void AppendPinnedVectorSpanInvocation(
StringBuilder builder,
ManagedMethodDefinition method,
PinnedVectorSpanInvocation invocation
)
{
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
string spanName = EscapeIdentifier(span.Name);
builder.Append(" fixed (");
builder.Append(invocation.VectorType);
builder.Append("* __vector = ");
builder.Append(spanName);
builder.Append(")\n");
builder.Append(" {\n");
builder.Append(" __context->");
builder.Append(method.NativeCommand.Name);
builder.Append('(');
AppendArguments(builder, method, invocation.PrefixArguments);
if (invocation.PrefixArguments.Count != 0)
{
builder.Append(", ");
}
builder.Append(spanName);
builder.Append(".Length, (");
builder.Append(invocation.ComponentType);
builder.Append("*)__vector);\n");
builder.Append(" }\n");
}
private static void AppendMatrix4Invocation(
StringBuilder builder,
ManagedMethodDefinition method,
Matrix4Invocation invocation
)
{
ManagedParameterDefinition transpose = method.Parameters[invocation.TransposeParameterIndex];
ManagedParameterDefinition matrix = method.Parameters[invocation.MatrixParameterIndex];
string transposeName = EscapeIdentifier(transpose.Name);
string matrixName = EscapeIdentifier(matrix.Name);
if (invocation.IsSpan)
{
builder.Append(" fixed (GraphicsMatrix* __value = ");
builder.Append(matrixName);
builder.Append(")\n");
builder.Append(" {\n");
builder.Append(" __context->");
}
else
{
builder.Append(" __context->");
}
builder.Append(method.NativeCommand.Name);
builder.Append('(');
AppendArguments(builder, method, invocation.PrefixArguments);
if (invocation.PrefixArguments.Count != 0)
{
builder.Append(", ");
}
builder.Append(invocation.IsSpan ? $"{matrixName}.Length" : "1");
builder.Append(", ");
builder.Append(transposeName);
builder.Append(" ? (byte)1 : (byte)0, (float*)");
builder.Append(invocation.IsSpan ? "__value" : $"&{matrixName}");
builder.Append(");\n");
if (invocation.IsSpan)
{
builder.Append(" }\n");
}
}
private static void AppendNativeCall(
StringBuilder builder,
ManagedMethodDefinition method,
IReadOnlyList<ManagedArgument> arguments
)
{
builder.Append("__context->");
builder.Append(method.NativeCommand.Name);
builder.Append('(');
AppendArguments(builder, method, arguments);
builder.Append(')');
}
private static void AppendArguments(
StringBuilder builder,
ManagedMethodDefinition method,
IReadOnlyList<ManagedArgument> arguments
)
{
for (int index = 0; index < arguments.Count; index++)
{
if (index != 0)
{
builder.Append(", ");
}
switch (arguments[index])
{
case ParameterArgument parameterArgument:
ManagedParameterDefinition parameter = method.Parameters[
parameterArgument.ParameterIndex];
string parameterName = EscapeIdentifier(parameter.Name);
if (parameter.IsEnum)
{
builder.Append("(uint");
builder.Append('*', parameter.Type.Count(value => value == '*'));
builder.Append(')');
builder.Append(parameterName);
}
else if (!parameter.IsHandle)
{
builder.Append(parameterName);
}
else if (parameter.Type.EndsWith('*'))
{
builder.Append("(uint*)");
builder.Append(parameterName);
}
else if (parameter.IsHandleSpan)
{
builder.Append("(uint*)__");
builder.Append(parameter.Name);
}
else
{
builder.Append(parameterName);
builder.Append(".Value");
}
break;
case SpanLengthArgument spanLengthArgument:
builder.Append(
EscapeIdentifier(
method.Parameters[spanLengthArgument.ParameterIndex].Name));
builder.Append(".Length");
break;
case VectorComponentArgument componentArgument:
builder.Append(
EscapeIdentifier(
method.Parameters[componentArgument.ParameterIndex].Name));
builder.Append('.');
builder.Append(componentArgument.Component);
break;
default:
throw new InvalidOperationException(
$"Unsupported managed argument '{arguments[index].GetType().Name}'.");
}
}
}
private static string EscapeIdentifier(
string value
)
{
return _csharpKeywords.Contains(value) ? $"@{value}" : value;
}
private static void AppendGeneratedHeader(
StringBuilder builder,
string outputFileName,
string description
)
{
builder.Append("// -----------------------------------------------------------------------------\n");
builder.Append("// file=\"");
builder.Append(ReadGeneratedFileName(outputFileName));
builder.Append("\"\n");
builder.Append("// Project: StgSharp\n");
builder.Append("// Copyright (c) Nitload.\n");
builder.Append("// SPDX-License-Identifier: MIT\n");
builder.Append("// -----------------------------------------------------------------------------\n\n");
builder.Append("// <auto-generated>\n");
builder.Append("// Generated by StgSharp.GenerateGL.\n");
builder.Append("// ");
builder.Append(description);
builder.Append(".\n");
builder.Append("// </auto-generated>\n\n");
}
private static string ReadGeneratedFileName(
string outputFileName
)
{
const string generatedExtension = ".g.cs";
const string sourceExtension = ".cs";
if (outputFileName.EndsWith(generatedExtension, StringComparison.Ordinal))
{
return outputFileName[..^generatedExtension.Length];
}
return outputFileName.EndsWith(sourceExtension, StringComparison.Ordinal)
? outputFileName[..^sourceExtension.Length]
: outputFileName;
}
private sealed record ConstantExpression(
string Type,
string Literal
);
private sealed record GlCommandGroup(
string FileName,
string Description,
IReadOnlyList<GlCommandDefinition> Commands
);
private sealed record ManagedCommandGroup(
GlCommandGroup Source,
IReadOnlyList<ManagedMethodDefinition> Methods
);
}
}