opengl source gen tool works

This commit is contained in:
Youmu Liu
2026-08-27 15:05:43 +08:00
parent 5db9f39a46
commit 648cac9d9b
42 changed files with 10247 additions and 10189 deletions
@@ -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}'."),
};
}
}
}
@@ -34,6 +34,10 @@ namespace StgSharp.GenerateGL.Generation
@"^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(
@@ -46,6 +50,11 @@ namespace StgSharp.GenerateGL.Generation
ArgumentNullException.ThrowIfNull(methods);
ArgumentNullException.ThrowIfNull(typeMapper);
if (_redundantVertexAttributeVectorPattern.IsMatch(command.Name))
{
return Array.Empty<ManagedMethodDefinition>();
}
Match matrixMatch = _matrixUniformPattern.Match(command.Name);
if (matrixMatch.Success)
{
@@ -16,7 +16,8 @@ namespace StgSharp.GenerateGL.Generation
IReadOnlyList<ManagedParameterDefinition> Parameters,
GlCommandDefinition NativeCommand,
ManagedInvocation Invocation,
bool WrapHandleReturn = false
bool WrapHandleReturn = false,
string? DocumentationPage = null
);
internal sealed record ManagedParameterDefinition(
@@ -53,6 +54,12 @@ namespace StgSharp.GenerateGL.Generation
bool IsSpan
) : ManagedInvocation;
internal sealed record QuerySpanInvocation(
IReadOnlyList<ManagedArgument> PrefixArguments,
int SpanParameterIndex,
string ElementType
) : ManagedInvocation;
internal abstract record ManagedArgument;
internal sealed record ParameterArgument(
@@ -8,6 +8,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Naming;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
@@ -19,16 +21,21 @@ namespace StgSharp.GenerateGL.Generation
new IGlApiFilter[]
{
new GlOverloadApiFilter(),
new GlGetValueApiFilter(),
new GlHandleApiFilter(),
});
private readonly GlEquivalentTypeMapper _typeMapper;
private readonly GlManagedApiNamePolicy _namePolicy;
public ManagedApiPlanner(
GlEquivalentTypeMapper typeMapper
GlEquivalentTypeMapper typeMapper,
GlFunctionFamilyAnalysis familyAnalysis
)
{
ArgumentNullException.ThrowIfNull(typeMapper);
ArgumentNullException.ThrowIfNull(familyAnalysis);
_typeMapper = typeMapper;
_namePolicy = new GlManagedApiNamePolicy(familyAnalysis);
}
public IReadOnlyList<ManagedMethodDefinition> Plan(
@@ -50,9 +57,23 @@ namespace StgSharp.GenerateGL.Generation
methods = filter.Apply(command, methods, _typeMapper);
}
result.AddRange(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());
}
@@ -78,6 +99,55 @@ namespace StgSharp.GenerateGL.Generation
}
}
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
)
@@ -13,6 +13,7 @@ using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Generation
@@ -23,9 +24,6 @@ namespace StgSharp.GenerateGL.Generation
internal static class OpenGlGenerator
{
private const string ApiName = "gl";
private const string CoreProfile = "core";
private static readonly HashSet<string> _csharpKeywords = new HashSet<string>(
new[]
{
@@ -44,25 +42,41 @@ namespace StgSharp.GenerateGL.Generation
StringComparer.Ordinal);
public static GeneratedFileSet Generate(
GlRegistryModel registry
GlRegistryProjection projection
)
{
ArgumentNullException.ThrowIfNull(registry);
ArgumentNullException.ThrowIfNull(projection);
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(registry);
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(projection);
IReadOnlyList<GlCommandDefinition> commands = Array.AsReadOnly(
commandGroups.SelectMany(value => value.Commands).ToArray());
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(registry);
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper);
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(projection.Source);
GlFunctionFamilyAnalysis familyAnalysis = GlFunctionFamilyAnalyzer.Analyze(projection);
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper, familyAnalysis);
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,
apiPlanner.Plan(group.Commands))).ToList();
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));
List<GeneratedFile> files = new List<GeneratedFile>
{
new GeneratedFile("glconst.cs", GenerateConstants(registry)),
new GeneratedFile("glconst.cs", GenerateConstants(projection.Enums)),
new GeneratedFile(
"OpenglContext.g.cs",
GenerateContext(commands, typeMapper)),
@@ -84,67 +98,26 @@ namespace StgSharp.GenerateGL.Generation
}
private static ReadOnlyCollection<GlCommandGroup> ReadCoreCommandGroups(
GlRegistryModel registry
GlRegistryProjection projection
)
{
Dictionary<string, GlCommandDefinition> definitions = new Dictionary<string, GlCommandDefinition>(
StringComparer.Ordinal);
foreach (GlCommandDefinition command in registry.Commands)
{
if (!definitions.TryAdd(command.Name, command))
{
throw new InvalidDataException(
$"Command '{command.Name}' has more than one definition.");
}
}
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)))
{
foreach (GlRequirementBlock block in feature.Requirements.Where(AppliesToCoreGl))
{
foreach (GlRegistryReference reference in block.References.Where(
value => value.Kind == GlRegistryReferenceKind.Command))
{
if (!definitions.ContainsKey(reference.Name))
{
throw new InvalidDataException(
$"Feature '{feature.Name}' references undefined command " +
$"'{reference.Name}'.");
}
if (block.Operation == GlRequirementOperation.Require)
{
active.Add(reference.Name);
if (known.Add(reference.Name))
{
order.Add(reference.Name);
introducedVersions.Add(reference.Name, feature.Number);
}
}
else
{
active.Remove(reference.Name);
}
}
}
}
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))
foreach (GlCommandDefinition command in projection.Commands)
{
string version = introducedVersions[commandName];
if (!projection.CommandIntroducedVersions.TryGetValue(
command.Name,
out string? version))
{
throw new InvalidDataException(
$"Command '{command.Name}' has no introduction version.");
}
if (IsFoundationVersion(version))
{
foundation.Add(definitions[commandName]);
foundation.Add(command);
continue;
}
@@ -157,7 +130,7 @@ namespace StgSharp.GenerateGL.Generation
versionOrder.Add(version);
}
commands.Add(definitions[commandName]);
commands.Add(command);
}
List<GlCommandGroup> groups = new List<GlCommandGroup>
@@ -203,23 +176,8 @@ namespace StgSharp.GenerateGL.Generation
return major < 3 || major == 3 && minor <= 2;
}
private static bool AppliesToCoreGl(
GlRequirementBlock block
)
{
bool matchesApi = block.Api is null || string.Equals(
block.Api,
ApiName,
StringComparison.Ordinal);
bool matchesProfile = block.Profile is null || string.Equals(
block.Profile,
CoreProfile,
StringComparison.Ordinal);
return matchesApi && matchesProfile;
}
private static string GenerateConstants(
GlRegistryModel registry
IReadOnlyList<GlEnumDefinition> enums
)
{
StringBuilder builder = new StringBuilder(512 * 1024);
@@ -231,8 +189,7 @@ namespace StgSharp.GenerateGL.Generation
Dictionary<string, GlEnumDefinition> emitted = new Dictionary<string, GlEnumDefinition>(
StringComparer.Ordinal);
foreach (GlEnumDefinition value in registry.EnumBlocks.SelectMany(
block => block.Enums).Where(IsDesktopEnum))
foreach (GlEnumDefinition value in enums)
{
if (value.Value is null)
{
@@ -267,16 +224,6 @@ namespace StgSharp.GenerateGL.Generation
return builder.ToString();
}
private static bool IsDesktopEnum(
GlEnumDefinition value
)
{
return value.Api is null || string.Equals(
value.Api,
ApiName,
StringComparison.Ordinal);
}
private static ConstantExpression ReadConstantExpression(
GlEnumDefinition value
)
@@ -430,6 +377,7 @@ namespace StgSharp.GenerateGL.Generation
foreach (ManagedMethodDefinition method in methods)
{
AppendMethodDocumentation(builder, method);
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
builder.Append(" public ");
builder.Append(method.ReturnType);
@@ -460,6 +408,41 @@ namespace StgSharp.GenerateGL.Generation
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>Forwards to the native C API <c>");
builder.Append(EscapeXml(declaration));
builder.Append("</c>.</summary>\n");
builder.Append(" /// <seealso href=\"https://docs.gl/gl4/");
builder.Append(page);
builder.Append("\" />");
}
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
@@ -479,12 +462,42 @@ namespace StgSharp.GenerateGL.Generation
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,