diff --git a/.gitignore b/.gitignore
index 4bbefdde..96d1605d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,4 @@ build.ninja
BenchmarkDotNet.Artifacts/
/StgSharp.Native/lib/
-!/StgSharp.Native/lib/**/CMakeLists.txt
\ No newline at end of file
+!/StgSharp.Native/lib/**/CMakeLists.txt
diff --git a/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs
new file mode 100644
index 00000000..379ced11
--- /dev/null
+++ b/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs
@@ -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
+{
+ ///
+ /// Replaces registry object-class GLuint values with the managed GlHandle type.
+ ///
+ internal sealed class GlHandleApiFilter : IGlApiFilter
+ {
+
+ public IReadOnlyList Apply(
+ GlCommandDefinition command,
+ IReadOnlyList 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 spanByLengthName = new Dictionary(
+ 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 oldToNew = new Dictionary();
+ List parameters = new List();
+ 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 arguments = new List(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"
+ : "Span";
+ }
+ 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 == '_');
+ }
+
+ }
+}
diff --git a/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs
new file mode 100644
index 00000000..3c82424f
--- /dev/null
+++ b/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs
@@ -0,0 +1,391 @@
+// -----------------------------------------------------------------------------
+// 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
+{
+ ///
+ /// Replaces verified C-style command families with managed overloads.
+ ///
+ internal sealed class GlOverloadApiFilter : IGlApiFilter
+ {
+
+ private static readonly Regex _matrixUniformPattern = new Regex(
+ @"^gl(?ProgramUniform|Uniform)Matrix(?[2-4])" +
+ @"(?:x(?[2-4]))?(?f|d)v$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ private static readonly Regex _uniformPattern = new Regex(
+ @"^gl(?ProgramUniform|Uniform)(?[1-4])" +
+ @"(?ui|f|i|d)(?v?)$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ private static readonly Regex _vertexAttribPattern = new Regex(
+ @"^glVertexAttrib(?[1-4])(?d|f|s)(?v?)$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ private static readonly string[] _components = { "X", "Y", "Z", "W" };
+
+ public IReadOnlyList Apply(
+ GlCommandDefinition command,
+ IReadOnlyList methods,
+ GlEquivalentTypeMapper typeMapper
+ )
+ {
+ ArgumentNullException.ThrowIfNull(command);
+ ArgumentNullException.ThrowIfNull(methods);
+ ArgumentNullException.ThrowIfNull(typeMapper);
+
+ 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();
+ }
+
+ return methods;
+ }
+
+ private static ReadOnlyCollection 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 prefix = ReadNativeParameters(
+ command,
+ prefixCount,
+ typeMapper);
+ List prefixArguments = ReadParameterArguments(prefix.Count);
+ if (isVector)
+ {
+ if (width is 2 or 3)
+ {
+ return Array.AsReadOnly(Array.Empty());
+ }
+
+ GlParameterDefinition valueParameter = command.Parameters[^1];
+ string spanElementType = width == 1
+ ? componentType
+ : $"Vec{width}<{componentType}>";
+ List parameters = new List(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 methods = new List();
+ List 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 vectorParameters = new List(prefix)
+ {
+ new ManagedParameterDefinition(
+ $"Vec{width}<{componentType}>",
+ "value"),
+ };
+ List vectorArguments = new List(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 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();
+ }
+
+ List prefix = ReadNativeParameters(
+ command,
+ prefixCount,
+ typeMapper);
+ List prefixArguments = ReadParameterArguments(prefix.Count);
+ GlParameterDefinition transposeSource = command.Parameters[prefixCount + 1];
+ GlParameterDefinition valueSource = command.Parameters[prefixCount + 2];
+
+ List singleParameters = new List(prefix)
+ {
+ new ManagedParameterDefinition("bool", "transpose", transposeSource),
+ new ManagedParameterDefinition("GraphicsMatrix", "value", valueSource),
+ };
+ List spanParameters = new List(prefix)
+ {
+ new ManagedParameterDefinition("bool", "transpose", transposeSource),
+ new ManagedParameterDefinition(
+ "ReadOnlySpan",
+ "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 ReadNativeParameters(
+ GlCommandDefinition command,
+ int count,
+ GlEquivalentTypeMapper typeMapper
+ )
+ {
+ List parameters = new List(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 ReadParameterArguments(
+ int count
+ )
+ {
+ List arguments = new List(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}'."),
+ };
+ }
+
+ }
+}
diff --git a/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs
new file mode 100644
index 00000000..723ac218
--- /dev/null
+++ b/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs
@@ -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 Apply(
+ GlCommandDefinition command,
+ IReadOnlyList methods,
+ GlEquivalentTypeMapper typeMapper
+ );
+
+ }
+}
diff --git a/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs b/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs
new file mode 100644
index 00000000..da29c4ad
--- /dev/null
+++ b/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs
@@ -0,0 +1,71 @@
+// -----------------------------------------------------------------------------
+// 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 Parameters,
+ GlCommandDefinition NativeCommand,
+ ManagedInvocation Invocation,
+ bool WrapHandleReturn = false
+ );
+
+ internal sealed record ManagedParameterDefinition(
+ string Type,
+ string Name,
+ GlParameterDefinition? NativeParameter = null,
+ bool IsHandle = false,
+ bool IsHandleSpan = false
+ );
+
+ internal abstract record ManagedInvocation;
+
+ internal sealed record DirectInvocation(
+ IReadOnlyList Arguments
+ ) : ManagedInvocation;
+
+ internal sealed record PinnedSpanInvocation(
+ IReadOnlyList PrefixArguments,
+ int SpanParameterIndex,
+ string ElementType
+ ) : ManagedInvocation;
+
+ internal sealed record PinnedVectorSpanInvocation(
+ IReadOnlyList PrefixArguments,
+ int SpanParameterIndex,
+ string ComponentType,
+ string VectorType
+ ) : ManagedInvocation;
+
+ internal sealed record Matrix4Invocation(
+ IReadOnlyList PrefixArguments,
+ int TransposeParameterIndex,
+ int MatrixParameterIndex,
+ bool IsSpan
+ ) : 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;
+
+}
diff --git a/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs b/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs
new file mode 100644
index 00000000..aab0cc18
--- /dev/null
+++ b/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs
@@ -0,0 +1,119 @@
+// -----------------------------------------------------------------------------
+// file="ManagedApiPlanner"
+// 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
+{
+ internal sealed class ManagedApiPlanner
+ {
+
+ private readonly IReadOnlyList _filters = Array.AsReadOnly(
+ new IGlApiFilter[]
+ {
+ new GlOverloadApiFilter(),
+ new GlHandleApiFilter(),
+ });
+ private readonly GlEquivalentTypeMapper _typeMapper;
+
+ public ManagedApiPlanner(
+ GlEquivalentTypeMapper typeMapper
+ )
+ {
+ ArgumentNullException.ThrowIfNull(typeMapper);
+ _typeMapper = typeMapper;
+ }
+
+ public IReadOnlyList Plan(
+ IReadOnlyList commands
+ )
+ {
+ ArgumentNullException.ThrowIfNull(commands);
+
+ List result = new List();
+ foreach (GlCommandDefinition command in commands)
+ {
+ IReadOnlyList methods = Array.AsReadOnly(
+ new[]
+ {
+ CreateRawMethod(command),
+ });
+ foreach (IGlApiFilter filter in _filters)
+ {
+ methods = filter.Apply(command, methods, _typeMapper);
+ }
+
+ result.AddRange(methods);
+ }
+
+ return Array.AsReadOnly(result.ToArray());
+ }
+
+ public static void Validate(
+ IEnumerable methods
+ )
+ {
+ ArgumentNullException.ThrowIfNull(methods);
+
+ Dictionary signatures = new Dictionary(
+ 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 ManagedMethodDefinition CreateRawMethod(
+ GlCommandDefinition command
+ )
+ {
+ List parameters = new List(
+ command.Parameters.Count);
+ List arguments = new List(
+ 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;
+ }
+
+ }
+}
diff --git a/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs b/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs
index 2f03650e..4422eaa3 100644
--- a/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs
+++ b/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs
@@ -49,22 +49,41 @@ namespace StgSharp.GenerateGL.Generation
{
ArgumentNullException.ThrowIfNull(registry);
- IReadOnlyList commands = ReadCoreCommands(registry);
+ IReadOnlyList commandGroups = ReadCoreCommandGroups(registry);
+ IReadOnlyList commands = Array.AsReadOnly(
+ commandGroups.SelectMany(value => value.Commands).ToArray());
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(registry);
- return new GeneratedFileSet(
- new[]
- {
- new GeneratedFile("glconst.cs", GenerateConstants(registry)),
+ ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper);
+ List managedGroups = commandGroups.Select(
+ group => new ManagedCommandGroup(
+ group,
+ apiPlanner.Plan(group.Commands))).ToList();
+ ManagedApiPlanner.Validate(
+ managedGroups.SelectMany(value => value.Methods));
+ List files = new List
+ {
+ new GeneratedFile("glconst.cs", GenerateConstants(registry)),
+ new GeneratedFile(
+ "OpenglContext.g.cs",
+ GenerateContext(commands, typeMapper)),
+ };
+ for (int index = 0; index < managedGroups.Count; index++)
+ {
+ ManagedCommandGroup managedGroup = managedGroups[index];
+ GlCommandGroup group = managedGroup.Source;
+ files.Add(
new GeneratedFile(
- "OpenglContext.g.cs",
- GenerateContext(commands, typeMapper)),
- new GeneratedFile(
- "OpenGLFunction.Raw.g.cs",
- GenerateFunctions(commands, typeMapper)),
- });
+ group.FileName,
+ GenerateFunctions(
+ managedGroup.Methods,
+ group.Description,
+ index == 0)));
+ }
+
+ return new GeneratedFileSet(files);
}
- private static ReadOnlyCollection ReadCoreCommands(
+ private static ReadOnlyCollection ReadCoreCommandGroups(
GlRegistryModel registry
)
{
@@ -82,6 +101,8 @@ namespace StgSharp.GenerateGL.Generation
List order = new List();
HashSet known = new HashSet(StringComparer.Ordinal);
HashSet active = new HashSet(StringComparer.Ordinal);
+ Dictionary introducedVersions = new Dictionary(
+ StringComparer.Ordinal);
foreach (GlFeatureDefinition feature in registry.Features.Where(
value => string.Equals(value.Api, ApiName, StringComparison.Ordinal)))
{
@@ -103,6 +124,7 @@ namespace StgSharp.GenerateGL.Generation
if (known.Add(reference.Name))
{
order.Add(reference.Name);
+ introducedVersions.Add(reference.Name, feature.Number);
}
}
else
@@ -113,8 +135,72 @@ namespace StgSharp.GenerateGL.Generation
}
}
- return Array.AsReadOnly(
- order.Where(active.Contains).Select(value => definitions[value]).ToArray());
+ List foundation = new List();
+ Dictionary> versionGroups = new Dictionary>(
+ StringComparer.Ordinal);
+ List versionOrder = new List();
+ foreach (string commandName in order.Where(active.Contains))
+ {
+ string version = introducedVersions[commandName];
+ if (IsFoundationVersion(version))
+ {
+ foundation.Add(definitions[commandName]);
+ continue;
+ }
+
+ if (!versionGroups.TryGetValue(
+ version,
+ out List? commands))
+ {
+ commands = new List();
+ versionGroups.Add(version, commands);
+ versionOrder.Add(version);
+ }
+
+ commands.Add(definitions[commandName]);
+ }
+
+ List groups = new List
+ {
+ 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 bool AppliesToCoreGl(
@@ -319,62 +405,53 @@ namespace StgSharp.GenerateGL.Generation
}
private static string GenerateFunctions(
- IReadOnlyList commands,
- GlEquivalentTypeMapper typeMapper
+ IReadOnlyList methods,
+ string description,
+ bool includeContextState
)
{
StringBuilder builder = new StringBuilder(512 * 1024);
- AppendGeneratedHeader(builder, "Desktop OpenGL core forwarding methods");
+ AppendGeneratedHeader(builder, description);
+ builder.Append("using StgSharp.Mathematics.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");
- 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 (GlCommandDefinition command in commands)
+ 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)
{
- string returnType = typeMapper.ReadReturnType(command);
- string methodName = ReadMethodName(command.Name);
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
builder.Append(" public ");
- builder.Append(returnType);
+ builder.Append(method.ReturnType);
builder.Append(' ');
- builder.Append(methodName);
+ builder.Append(method.Name);
builder.Append('(');
- for (int index = 0; index < command.Parameters.Count; index++)
+ for (int index = 0; index < method.Parameters.Count; index++)
{
if (index != 0)
{
builder.Append(", ");
}
- GlParameterDefinition parameter = command.Parameters[index];
- builder.Append(typeMapper.ReadParameterType(parameter));
+ ManagedParameterDefinition parameter = method.Parameters[index];
+ builder.Append(parameter.Type);
builder.Append(' ');
builder.Append(EscapeIdentifier(parameter.Name));
}
builder.Append(")\n");
builder.Append(" {\n");
- builder.Append(" ");
- if (returnType != "void")
- {
- builder.Append("return ");
- }
-
- builder.Append("__context->");
- builder.Append(command.Name);
- builder.Append('(');
- builder.Append(
- string.Join(
- ", ",
- command.Parameters.Select(
- value => EscapeIdentifier(value.Name))));
- builder.Append(");\n");
+ AppendInvocation(builder, method);
builder.Append(" }\n");
}
@@ -383,14 +460,252 @@ namespace StgSharp.GenerateGL.Generation
return builder.ToString();
}
- private static string ReadMethodName(
- string commandName
+ private static void AppendInvocation(
+ StringBuilder builder,
+ ManagedMethodDefinition method
)
{
- return commandName.StartsWith("gl", StringComparison.Ordinal) &&
- commandName.Length > 2
- ? commandName[2..]
- : commandName;
+ 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;
+ default:
+ throw new InvalidOperationException(
+ $"Unsupported managed invocation '{method.Invocation.GetType().Name}'.");
+ }
+ }
+
+ private static void AppendDirectInvocation(
+ StringBuilder builder,
+ ManagedMethodDefinition method,
+ DirectInvocation invocation
+ )
+ {
+ List fixedParameters = new List();
+ 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.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 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 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.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(
@@ -418,5 +733,16 @@ namespace StgSharp.GenerateGL.Generation
string Literal
);
+ private sealed record GlCommandGroup(
+ string FileName,
+ string Description,
+ IReadOnlyList Commands
+ );
+
+ private sealed record ManagedCommandGroup(
+ GlCommandGroup Source,
+ IReadOnlyList Methods
+ );
+
}
}