make new opengl source generator

This commit is contained in:
Youmu Liu
2026-08-26 20:19:25 +08:00
parent 75bf7fc139
commit 5db9f39a46
7 changed files with 1151 additions and 52 deletions
@@ -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,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
{
/// <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 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);
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,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<ManagedParameterDefinition> 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<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 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,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<IGlApiFilter> _filters = Array.AsReadOnly<IGlApiFilter>(
new IGlApiFilter[]
{
new GlOverloadApiFilter(),
new GlHandleApiFilter(),
});
private readonly GlEquivalentTypeMapper _typeMapper;
public ManagedApiPlanner(
GlEquivalentTypeMapper typeMapper
)
{
ArgumentNullException.ThrowIfNull(typeMapper);
_typeMapper = typeMapper;
}
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);
}
result.AddRange(methods);
}
return Array.AsReadOnly(result.ToArray());
}
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 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;
}
}
}
@@ -49,22 +49,41 @@ namespace StgSharp.GenerateGL.Generation
{
ArgumentNullException.ThrowIfNull(registry);
IReadOnlyList<GlCommandDefinition> commands = ReadCoreCommands(registry);
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(registry);
IReadOnlyList<GlCommandDefinition> 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<ManagedCommandGroup> managedGroups = commandGroups.Select(
group => new ManagedCommandGroup(
group,
apiPlanner.Plan(group.Commands))).ToList();
ManagedApiPlanner.Validate(
managedGroups.SelectMany(value => value.Methods));
List<GeneratedFile> files = new List<GeneratedFile>
{
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<GlCommandDefinition> ReadCoreCommands(
private static ReadOnlyCollection<GlCommandGroup> ReadCoreCommandGroups(
GlRegistryModel registry
)
{
@@ -82,6 +101,8 @@ namespace StgSharp.GenerateGL.Generation
List<string> order = new List<string>();
HashSet<string> known = new HashSet<string>(StringComparer.Ordinal);
HashSet<string> active = new HashSet<string>(StringComparer.Ordinal);
Dictionary<string, string> introducedVersions = new Dictionary<string, string>(
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<GlCommandDefinition> foundation = new List<GlCommandDefinition>();
Dictionary<string, List<GlCommandDefinition>> versionGroups = new Dictionary<string, List<GlCommandDefinition>>(
StringComparer.Ordinal);
List<string> versionOrder = new List<string>();
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<GlCommandDefinition>? commands))
{
commands = new List<GlCommandDefinition>();
versionGroups.Add(version, commands);
versionOrder.Add(version);
}
commands.Add(definitions[commandName]);
}
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 bool AppliesToCoreGl(
@@ -319,62 +405,53 @@ namespace StgSharp.GenerateGL.Generation
}
private static string GenerateFunctions(
IReadOnlyList<GlCommandDefinition> commands,
GlEquivalentTypeMapper typeMapper
IReadOnlyList<ManagedMethodDefinition> 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<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.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.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<GlCommandDefinition> Commands
);
private sealed record ManagedCommandGroup(
GlCommandGroup Source,
IReadOnlyList<ManagedMethodDefinition> Methods
);
}
}