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,429 @@
// -----------------------------------------------------------------------------
// file="GlFunctionFamilyAnalyzer"
// 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.Naming;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Analysis
{
/// <summary>
/// Builds inspectable function-family candidates without changing generation.
/// </summary>
internal static class GlFunctionFamilyAnalyzer
{
public static GlFunctionFamilyAnalysis Analyze(
GlRegistryModel registry
)
{
ArgumentNullException.ThrowIfNull(registry);
return Analyze(registry, registry.Commands);
}
public static GlFunctionFamilyAnalysis Analyze(
GlRegistryProjection projection
)
{
ArgumentNullException.ThrowIfNull(projection);
return Analyze(projection.Source, projection.Commands);
}
private static GlFunctionFamilyAnalysis Analyze(
GlRegistryModel registry,
IReadOnlyList<GlCommandDefinition> commands
)
{
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName = commands
.GroupBy(value => value.Name, StringComparer.Ordinal)
.ToDictionary(
value => value.Key,
value => (IReadOnlyList<GlCommandDefinition>)Array.AsReadOnly(
value.ToArray()),
StringComparer.Ordinal);
Dictionary<string, GlFunctionNameShape> shapes =
GlFunctionNameFactorizer.Factor(registry, commands);
Dictionary<string, FamilyBuilder> builders = new Dictionary<string, FamilyBuilder>(
StringComparer.Ordinal);
List<GlFunctionRelation> relations = new List<GlFunctionRelation>();
foreach (GlCommandDefinition command in commands)
{
if (shapes.TryGetValue(command.Name, out GlFunctionNameShape? shape))
{
ReadBuilder(builders, shape.FamilyName).Add(command, shape);
}
}
AddRegistryRelations(commands, commandsByName, shapes, builders, relations);
AddSingleValuePointerRelations(commandsByName, shapes, builders, relations);
GlFunctionFamily[] families = builders.Values
.Where(value => value.Members.Count > 1 || value.Relations.Count != 0)
.Select(value => value.Build())
.OrderBy(value => value.Name, StringComparer.Ordinal)
.ToArray();
HashSet<string> classifiedNames = families
.SelectMany(value => value.Members)
.Select(value => value.Command.Name)
.ToHashSet(StringComparer.Ordinal);
GlCommandDefinition[] unclassified = commands
.Where(value => !classifiedNames.Contains(value.Name))
.OrderBy(value => value.Name, StringComparer.Ordinal)
.ToArray();
return new GlFunctionFamilyAnalysis(
Freeze(families),
Freeze(unclassified),
Freeze(relations));
}
private static void AddRegistryRelations(
IReadOnlyList<GlCommandDefinition> commands,
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName,
Dictionary<string, GlFunctionNameShape> shapes,
IDictionary<string, FamilyBuilder> builders,
List<GlFunctionRelation> relations
)
{
foreach (GlCommandDefinition command in commands)
{
foreach (GlNamedReference reference in command.VectorEquivalents)
{
bool exists = commandsByName.TryGetValue(
reference.Name,
out IReadOnlyList<GlCommandDefinition>? targets);
GlFunctionRelation relation = new GlFunctionRelation(
command.Name,
reference.Name,
GlFunctionRelationKind.RegistryVectorEquivalent,
exists
? GlFunctionRelationState.Verified
: GlFunctionRelationState.MissingTarget,
exists
? "declared by gl.xml <vecequiv>"
: "gl.xml <vecequiv> target is not registered");
relations.Add(relation);
string familyName = ReadRelationFamilyName(
command.Name,
reference.Name,
shapes);
FamilyBuilder builder = ReadBuilder(builders, familyName);
builder.Add(
command,
shapes.TryGetValue(command.Name, out GlFunctionNameShape? sourceShape)
? sourceShape
: null);
builder.Relations.Add(relation);
if (!exists)
{
builder.Diagnostics.Add(
$"Missing vector-equivalent target '{reference.Name}'.");
continue;
}
foreach (GlCommandDefinition target in targets!)
{
builder.Add(
target,
shapes.TryGetValue(target.Name, out GlFunctionNameShape? targetShape)
? targetShape
: null);
}
}
}
}
private static void AddSingleValuePointerRelations(
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName,
Dictionary<string, GlFunctionNameShape> shapes,
IDictionary<string, FamilyBuilder> builders,
List<GlFunctionRelation> relations
)
{
HashSet<string> existingPairs = relations
.Select(value => $"{value.SourceName}\0{value.TargetName}")
.ToHashSet(StringComparer.Ordinal);
foreach ((string commandName, GlFunctionNameShape pointerShape) in shapes)
{
if (!pointerShape.IsVectorForm ||
!commandsByName.TryGetValue(
commandName,
out IReadOnlyList<GlCommandDefinition>? pointerCommands))
{
continue;
}
string scalarName = commandName[..^1];
if (!shapes.TryGetValue(scalarName, out GlFunctionNameShape? scalarShape) ||
!commandsByName.TryGetValue(
scalarName,
out IReadOnlyList<GlCommandDefinition>? scalarCommands))
{
continue;
}
foreach (GlCommandDefinition pointer in pointerCommands)
{
if (pointer.Parameters.Count == 0 ||
!string.Equals(pointer.Parameters[^1].Length, "1", StringComparison.Ordinal))
{
continue;
}
foreach (GlCommandDefinition scalar in scalarCommands)
{
if (existingPairs.Contains($"{scalar.Name}\0{pointer.Name}"))
{
continue;
}
bool valid = IsSingleValuePointerPair(scalar, pointer, out string detail);
GlFunctionRelation relation = new GlFunctionRelation(
scalar.Name,
pointer.Name,
GlFunctionRelationKind.InferredSingleValuePointer,
valid
? GlFunctionRelationState.Verified
: GlFunctionRelationState.Rejected,
detail);
relations.Add(relation);
FamilyBuilder builder = ReadBuilder(builders, pointerShape.FamilyName);
builder.Add(scalar, scalarShape);
builder.Add(pointer, pointerShape);
builder.Relations.Add(relation);
if (!valid)
{
builder.Diagnostics.Add(
$"Rejected scalar/pointer pair {scalar.Name} -> {pointer.Name}: " +
detail);
}
}
}
}
}
private static bool IsSingleValuePointerPair(
GlCommandDefinition scalar,
GlCommandDefinition pointer,
out string detail
)
{
if (!string.Equals(
scalar.Return.TypeName,
pointer.Return.TypeName,
StringComparison.Ordinal) ||
scalar.Parameters.Count != pointer.Parameters.Count)
{
detail = "return type or parameter count differs";
return false;
}
int lastIndex = scalar.Parameters.Count - 1;
for (int index = 0; index < lastIndex; index++)
{
if (!HaveEquivalentParameterTypes(
scalar.Parameters[index],
pointer.Parameters[index]))
{
detail = $"parameter {index + 1} differs";
return false;
}
}
GlParameterDefinition scalarValue = scalar.Parameters[lastIndex];
GlParameterDefinition pointerValue = pointer.Parameters[lastIndex];
if (!string.Equals(
scalarValue.TypeName,
pointerValue.TypeName,
StringComparison.Ordinal) ||
scalarValue.DeclarationText.Contains('*', StringComparison.Ordinal) ||
!pointerValue.DeclarationText.Contains('*', StringComparison.Ordinal) ||
!pointerValue.DeclarationText.TrimStart().StartsWith(
"const ",
StringComparison.Ordinal) ||
!string.Equals(pointerValue.Length, "1", StringComparison.Ordinal))
{
detail = "last parameter is not a matching T / const T* len=1 pair";
return false;
}
detail = "verified T / const T* len=1 pair";
return true;
}
private static bool HaveEquivalentParameterTypes(
GlParameterDefinition left,
GlParameterDefinition right
)
{
return string.Equals(left.TypeName, right.TypeName, StringComparison.Ordinal) &&
left.DeclarationText.Contains('*', StringComparison.Ordinal) ==
right.DeclarationText.Contains('*', StringComparison.Ordinal);
}
private static string ReadRelationFamilyName(
string sourceName,
string targetName,
Dictionary<string, GlFunctionNameShape> shapes
)
{
if (shapes.TryGetValue(sourceName, out GlFunctionNameShape? sourceShape))
{
return sourceShape.FamilyName;
}
if (shapes.TryGetValue(targetName, out GlFunctionNameShape? targetShape))
{
return targetShape.FamilyName;
}
string source = RemoveGlPrefix(sourceName);
string target = RemoveGlPrefix(targetName);
if (target.EndsWith('v') &&
string.Equals(source, target[..^1], StringComparison.Ordinal))
{
return CSharpIdentifierPolicy.NormalizeMemberName(source);
}
int length = 0;
int maximum = Math.Min(source.Length, target.Length);
while (length < maximum && source[length] == target[length])
{
length++;
}
string familyName = length == 0 ? source : source[..length].TrimEnd('_');
return CSharpIdentifierPolicy.NormalizeMemberName(familyName);
}
private static string RemoveGlPrefix(
string name
)
{
return name.StartsWith("gl", StringComparison.Ordinal) && name.Length > 2
? name[2..]
: name;
}
private static FamilyBuilder ReadBuilder(
IDictionary<string, FamilyBuilder> builders,
string familyName
)
{
if (!builders.TryGetValue(familyName, out FamilyBuilder? builder))
{
builder = new FamilyBuilder(familyName);
builders.Add(familyName, builder);
}
return builder;
}
private static ReadOnlyCollection<T> Freeze<T>(
IEnumerable<T> values
)
{
return Array.AsReadOnly(values.ToArray());
}
private sealed class FamilyBuilder
{
private readonly Dictionary<string, GlFunctionFamilyMember> _members = new Dictionary<string, GlFunctionFamilyMember>(
StringComparer.Ordinal);
public FamilyBuilder(
string name
)
{
Name = name;
}
public string Name { get; }
public Dictionary<string, GlFunctionFamilyMember>.ValueCollection Members =>
_members.Values;
public List<GlFunctionRelation> Relations { get; } = new List<GlFunctionRelation>();
public List<string> Diagnostics { get; } = new List<string>();
public void Add(
GlCommandDefinition command,
GlFunctionNameShape? shape
)
{
_members.TryAdd(
command.Name,
new GlFunctionFamilyMember(command, shape));
}
public GlFunctionFamily Build()
{
GlFunctionFamilyMember[] members = _members.Values
.OrderBy(value => value.Command.Name, StringComparer.Ordinal)
.ToArray();
string[] diagnostics = Diagnostics
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
return new GlFunctionFamily(
Name,
Freeze(members),
Freeze(Relations.Distinct().ToArray()),
Freeze(diagnostics),
ReadShapeNamePolicy(members));
}
private static GlFunctionShapeNamePolicy ReadShapeNamePolicy(
IReadOnlyCollection<GlFunctionFamilyMember> members
)
{
GlFunctionFamilyMember[] scalarMembers = members
.Where(
value => value.NameShape is
{ IsVectorForm: false, Shape: not null })
.ToArray();
if (scalarMembers.Select(value => value.NameShape!.Shape)
.Distinct(StringComparer.Ordinal)
.Count() < 2)
{
return GlFunctionShapeNamePolicy.NotApplicable;
}
bool collision = scalarMembers
.GroupBy(ReadNativeSignature, StringComparer.Ordinal)
.Any(group => group.Select(value => value.NameShape!.Shape)
.Distinct(StringComparer.Ordinal)
.Count() > 1);
return collision
? GlFunctionShapeNamePolicy.MustRetain
: GlFunctionShapeNamePolicy.CanRemove;
}
private static string ReadNativeSignature(
GlFunctionFamilyMember member
)
{
GlCommandDefinition command = member.Command;
return $"{command.Return.TypeName}:" + string.Join(
",",
command.Parameters.Select(
value => $"{value.TypeName}:{value.DeclarationText.Contains('*', StringComparison.Ordinal)}"));
}
}
}
}
@@ -0,0 +1,68 @@
// -----------------------------------------------------------------------------
// file="GlFunctionFamilyModel"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Analysis
{
internal sealed record GlFunctionNameShape(
string FamilyName,
string? Shape,
int? Columns,
int? Rows,
string TypeSuffix,
bool IsVectorForm
);
internal sealed record GlFunctionFamilyMember(
GlCommandDefinition Command,
GlFunctionNameShape? NameShape
);
internal sealed record GlFunctionRelation(
string SourceName,
string TargetName,
GlFunctionRelationKind Kind,
GlFunctionRelationState State,
string Detail
);
internal sealed record GlFunctionFamily(
string Name,
IReadOnlyList<GlFunctionFamilyMember> Members,
IReadOnlyList<GlFunctionRelation> Relations,
IReadOnlyList<string> Diagnostics,
GlFunctionShapeNamePolicy ShapeNamePolicy
);
internal sealed record GlFunctionFamilyAnalysis(
IReadOnlyList<GlFunctionFamily> Families,
IReadOnlyList<GlCommandDefinition> UnclassifiedCommands,
IReadOnlyList<GlFunctionRelation> Relations
);
internal enum GlFunctionRelationKind
{
RegistryVectorEquivalent,
InferredSingleValuePointer,
}
internal enum GlFunctionRelationState
{
Verified,
MissingTarget,
Rejected,
}
internal enum GlFunctionShapeNamePolicy
{
NotApplicable,
CanRemove,
MustRetain,
}
}
@@ -0,0 +1,472 @@
// -----------------------------------------------------------------------------
// file="GlFunctionNameFactorizer"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using StgSharp.GenerateGL.Naming;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Analysis
{
/// <summary>
/// Factors command-name alternatives by their common prefix and interprets only the
/// remaining suffix as overload axes.
/// </summary>
internal static class GlFunctionNameFactorizer
{
private static readonly string[] _typeSuffixes =
{
"ui64",
"i64",
"ui",
"ub",
"us",
"b",
"s",
"i",
"f",
"d",
"h",
"x",
};
public static Dictionary<string, GlFunctionNameShape> Factor(
GlRegistryModel registry,
IReadOnlyList<GlCommandDefinition> commands
)
{
ArgumentNullException.ThrowIfNull(registry);
ArgumentNullException.ThrowIfNull(commands);
string[] vendorSuffixes = ReadVendorSuffixes(registry);
NameEntry[] entries = commands
.Select(
command => new NameEntry(
command,
command.Name,
ReadCoreName(command.Name, vendorSuffixes)))
.ToArray();
TrieNode root = new TrieNode(string.Empty);
for (int index = 0; index < entries.Length; index++)
{
Add(root, entries[index].CoreName, index);
}
List<FactorCandidate>[] candidatesByEntry = Enumerable.Range(0, entries.Length)
.Select(_ => new List<FactorCandidate>())
.ToArray();
CollectCandidates(root, entries, candidatesByEntry);
Dictionary<string, GlFunctionNameShape> result = new Dictionary<string, GlFunctionNameShape>(
StringComparer.Ordinal);
for (int index = 0; index < entries.Length; index++)
{
FactorCandidate? candidate = candidatesByEntry[index]
.OrderByDescending(value => value.DistinctVariantCount)
.ThenByDescending(value => value.MemberCount)
.ThenBy(value => value.FamilyName.Length)
.FirstOrDefault();
if (candidate is null ||
!candidate.Shapes.TryGetValue(index, out GlFunctionNameShape? shape))
{
continue;
}
result.TryAdd(entries[index].CommandName, shape);
}
AddFixedVectorPairs(result, commands);
return result;
}
private static void AddFixedVectorPairs(
Dictionary<string, GlFunctionNameShape> shapes,
IReadOnlyList<GlCommandDefinition> commands
)
{
Dictionary<string, GlCommandDefinition> commandsByName = commands.ToDictionary(
value => value.Name,
StringComparer.Ordinal);
foreach (GlCommandDefinition vector in commands)
{
if (!vector.Name.EndsWith('v') ||
shapes.ContainsKey(vector.Name) ||
vector.Parameters.Count == 0 ||
!int.TryParse(vector.Parameters[^1].Length, out int componentCount) ||
componentCount < 2)
{
continue;
}
string scalarName = vector.Name[..^1];
if (shapes.ContainsKey(scalarName) ||
!commandsByName.TryGetValue(
scalarName,
out GlCommandDefinition? scalar) ||
!IsFixedVectorPair(scalar, vector, componentCount))
{
continue;
}
string familyName = CSharpIdentifierPolicy.NormalizeMemberName(
scalarName.StartsWith("gl", StringComparison.Ordinal) &&
scalarName.Length > 2
? scalarName[2..]
: scalarName);
shapes.Add(
scalarName,
new GlFunctionNameShape(
familyName,
null,
null,
null,
string.Empty,
false));
shapes.Add(
vector.Name,
new GlFunctionNameShape(
familyName,
null,
null,
null,
string.Empty,
true));
}
}
private static bool IsFixedVectorPair(
GlCommandDefinition scalar,
GlCommandDefinition vector,
int componentCount
)
{
if (!string.Equals(
scalar.Return.TypeName,
vector.Return.TypeName,
StringComparison.Ordinal) ||
scalar.Parameters.Count != vector.Parameters.Count - 1 + componentCount)
{
return false;
}
int prefixCount = vector.Parameters.Count - 1;
for (int index = 0; index < prefixCount; index++)
{
if (!HaveEquivalentScalarTypes(
scalar.Parameters[index].TypeName,
vector.Parameters[index].TypeName) ||
scalar.Parameters[index].DeclarationText.Contains('*', StringComparison.Ordinal) !=
vector.Parameters[index].DeclarationText.Contains('*', StringComparison.Ordinal))
{
return false;
}
}
GlParameterDefinition vectorValue = vector.Parameters[^1];
if (!vectorValue.DeclarationText.Contains('*', StringComparison.Ordinal))
{
return false;
}
for (int index = prefixCount; index < scalar.Parameters.Count; index++)
{
if (!HaveEquivalentScalarTypes(
scalar.Parameters[index].TypeName,
vectorValue.TypeName) ||
scalar.Parameters[index].DeclarationText.Contains('*', StringComparison.Ordinal))
{
return false;
}
}
return true;
}
private static bool HaveEquivalentScalarTypes(
string? left,
string? right
)
{
if (string.Equals(left, right, StringComparison.Ordinal))
{
return true;
}
return left is "GLint" or "GLsizei" &&
right is "GLint" or "GLsizei";
}
private static List<int> CollectCandidates(
TrieNode node,
IReadOnlyList<NameEntry> entries,
IReadOnlyList<List<FactorCandidate>> candidatesByEntry
)
{
List<int> descendants = new List<int>(node.Terminals);
foreach (TrieNode child in node.Children.Values)
{
descendants.AddRange(CollectCandidates(child, entries, candidatesByEntry));
}
if (node.Prefix.Length == 0 || descendants.Count < 2)
{
return descendants;
}
Dictionary<int, GlFunctionNameShape> shapes = new Dictionary<int, GlFunctionNameShape>();
foreach (int index in descendants)
{
string coreName = entries[index].CoreName;
if (coreName.Length <= node.Prefix.Length ||
!TryParseVariantSuffix(
coreName[node.Prefix.Length..],
node.Prefix,
entries[index].Command,
out GlFunctionNameShape? shape))
{
continue;
}
shapes.Add(index, shape!);
}
int distinctVariantCount = shapes.Keys
.Select(index => entries[index].CoreName)
.Distinct(StringComparer.Ordinal)
.Count();
if (distinctVariantCount < 2)
{
return descendants;
}
FactorCandidate candidate = new FactorCandidate(
node.Prefix,
shapes,
distinctVariantCount);
foreach (int index in shapes.Keys)
{
candidatesByEntry[index].Add(candidate);
}
return descendants;
}
private static bool TryParseVariantSuffix(
string suffix,
string familyName,
GlCommandDefinition command,
out GlFunctionNameShape? shape
)
{
bool isVectorForm = suffix.EndsWith('v');
if (isVectorForm)
{
suffix = suffix[..^1];
}
string? typeSuffix = null;
foreach (string candidate in _typeSuffixes)
{
if (suffix.EndsWith(candidate, StringComparison.Ordinal))
{
typeSuffix = candidate;
suffix = suffix[..^candidate.Length];
break;
}
}
if (typeSuffix is null ||
!IsTypeSuffixConsistent(command, typeSuffix) ||
!TryParseShape(
suffix,
out string? shapeText,
out int? columns,
out int? rows))
{
shape = null;
return false;
}
shape = new GlFunctionNameShape(
CSharpIdentifierPolicy.NormalizeMemberName(familyName),
shapeText,
columns,
rows,
typeSuffix,
isVectorForm);
return true;
}
private static bool IsTypeSuffixConsistent(
GlCommandDefinition command,
string typeSuffix
)
{
string? glType = typeSuffix switch
{
"b" => "GLbyte",
"ub" => "GLubyte",
"s" => "GLshort",
"us" => "GLushort",
"i" => "GLint",
"ui" => "GLuint",
"i64" => "GLint64",
"ui64" => "GLuint64",
"f" => "GLfloat",
"d" => "GLdouble",
"h" => "GLhalf",
"x" => "GLfixed",
_ => null,
};
if (glType is null)
{
return false;
}
return string.Equals(command.Return.TypeName, glType, StringComparison.Ordinal) ||
command.Parameters.Any(
value => string.Equals(
value.TypeName,
glType,
StringComparison.Ordinal));
}
private static bool TryParseShape(
string value,
out string? shape,
out int? columns,
out int? rows
)
{
if (value.Length == 0)
{
shape = null;
columns = null;
rows = null;
return true;
}
if (value.Length == 1 && value[0] is >= '1' and <= '4')
{
shape = value;
columns = value[0] - '0';
rows = 1;
return true;
}
if (value.Length == 3 &&
value[0] is >= '2' and <= '4' &&
value[1] == 'x' &&
value[2] is >= '2' and <= '4')
{
shape = value;
columns = value[0] - '0';
rows = value[2] - '0';
return true;
}
shape = null;
columns = null;
rows = null;
return false;
}
private static void Add(
TrieNode root,
string value,
int entryIndex
)
{
TrieNode current = root;
foreach (char character in value)
{
if (!current.Children.TryGetValue(character, out TrieNode? child))
{
child = new TrieNode(current.Prefix + character);
current.Children.Add(character, child);
}
current = child;
}
current.Terminals.Add(entryIndex);
}
private static string ReadCoreName(
string commandName,
IReadOnlyList<string> vendorSuffixes
)
{
string name = commandName.StartsWith("gl", StringComparison.Ordinal) &&
commandName.Length > 2
? commandName[2..]
: commandName;
foreach (string vendorSuffix in vendorSuffixes)
{
if (name.Length > vendorSuffix.Length && name.EndsWith(
vendorSuffix,
StringComparison.Ordinal))
{
return name[..^vendorSuffix.Length];
}
}
return name;
}
private static string[] ReadVendorSuffixes(
GlRegistryModel registry
)
{
return registry.Extensions
.Select(value => value.Name.Split('_', StringSplitOptions.RemoveEmptyEntries))
.Where(value => value.Length > 2 && value[0] == "GL")
.Select(value => value[1])
.Where(value => value.All(character => char.IsUpper(character) || char.IsDigit(character)))
.Distinct(StringComparer.Ordinal)
.OrderByDescending(value => value.Length)
.ThenBy(value => value, StringComparer.Ordinal)
.ToArray();
}
private sealed record NameEntry(
GlCommandDefinition Command,
string CommandName,
string CoreName
);
private sealed record FactorCandidate(
string FamilyName,
IReadOnlyDictionary<int, GlFunctionNameShape> Shapes,
int DistinctVariantCount
)
{
public int MemberCount => Shapes.Count;
}
private sealed class TrieNode
{
public TrieNode(
string prefix
)
{
Prefix = prefix;
}
public string Prefix { get; }
public Dictionary<char, TrieNode> Children { get; } = new Dictionary<char, TrieNode>();
public List<int> Terminals { get; } = new List<int>();
}
}
}
@@ -0,0 +1,556 @@
// -----------------------------------------------------------------------------
// file="FunctionFamilyCli"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StgSharp.GenerateGL.Analysis;
namespace StgSharp.GenerateGL
{
/// <summary>
/// Presents the function-family analysis without affecting generation.
/// </summary>
internal static class FunctionFamilyCli
{
private const int ShowAllResults = int.MaxValue;
private const int QueryErrorExitCode = 1;
private const int SuccessExitCode = 0;
private const int UsageErrorExitCode = 2;
public static int Execute(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList<string> arguments
)
{
ArgumentNullException.ThrowIfNull(analysis);
ArgumentNullException.ThrowIfNull(arguments);
if (arguments.Count == 0)
{
WriteHelp();
return SuccessExitCode;
}
string command = arguments[0].ToUpperInvariant();
string[] queryArguments = arguments.Skip(1).ToArray();
return command switch
{
"SUMMARY" => QuerySummary(analysis, queryArguments),
"LIST" or "LS" => QueryList(analysis, queryArguments),
"SHOW" or "INFO" => QueryShow(analysis, queryArguments),
"RELATIONS" or "PAIRS" => QueryRelations(analysis, queryArguments),
"UNRESOLVED" or "UNCLASSIFIED" => QueryUnclassified(
analysis,
queryArguments),
"CONFLICTS" or "CONSTRAINTS" => QueryConstraints(
analysis,
queryArguments),
"HELP" or "?" => WriteHelpResult(queryArguments),
_ => WriteUsageError($"Unknown family command '{arguments[0]}'."),
};
}
public static void WriteHelp()
{
Console.WriteLine("Function-family analysis commands:");
Console.WriteLine(" families summary");
Console.WriteLine(" families list [filter] [--showcount <n>]");
Console.WriteLine(" families show <family>");
Console.WriteLine(" families relations [filter] [--showcount <n>]");
Console.WriteLine(" families unresolved [filter] [--showcount <n>]");
Console.WriteLine(" families conflicts [filter] [--showcount <n>]");
}
private static int QuerySummary(
GlFunctionFamilyAnalysis analysis,
string[] arguments
)
{
if (arguments.Length != 0)
{
return WriteUsageError("families summary does not accept arguments.");
}
Console.WriteLine("Function-family analysis");
WriteProperty("families", analysis.Families.Count);
WriteProperty(
"classified commands",
analysis.Families.SelectMany(value => value.Members)
.Select(value => value.Command.Name)
.Distinct(StringComparer.Ordinal)
.Count());
WriteProperty("unclassified commands", analysis.UnclassifiedCommands.Count);
WriteProperty("relations", analysis.Relations.Count);
WriteProperty(
"registry vector relations",
analysis.Relations.Count(
value => value.Kind == GlFunctionRelationKind.RegistryVectorEquivalent));
WriteProperty(
"inferred len=1 relations",
analysis.Relations.Count(
value => value.Kind == GlFunctionRelationKind.InferredSingleValuePointer));
WriteProperty(
"verified relations",
analysis.Relations.Count(
value => value.State == GlFunctionRelationState.Verified));
WriteProperty(
"relations requiring review",
analysis.Relations.Count(
value => value.State != GlFunctionRelationState.Verified));
WriteProperty(
"shape suffix must remain",
analysis.Families.Count(
value => value.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain));
return SuccessExitCode;
}
private static int QueryList(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList<string> arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionFamily[] families = FilterFamilies(analysis, filter).ToArray();
if (families.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No function families were recognized."
: $"No function families contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(families.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
GlFunctionFamily family = families[index];
Console.WriteLine($"[{index + 1}] {family.Name}");
WriteProperty("members", family.Members.Count);
WriteProperty(
"shapes",
JoinDistinct(
family.Members.Select(value => value.NameShape?.Shape)));
WriteProperty(
"types",
JoinDistinct(
family.Members.Select(value => value.NameShape?.TypeSuffix)));
WriteProperty(
"forms",
JoinDistinct(
family.Members.Where(value => value.NameShape is not null)
.Select(
value => value.NameShape!.IsVectorForm
? "pointer"
: "scalar")));
WriteProperty("relations", family.Relations.Count);
WriteProperty("shape name", ReadShapePolicy(family.ShapeNamePolicy));
}
WriteTruncation(families.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryShow(
GlFunctionFamilyAnalysis analysis,
string[] arguments
)
{
if (arguments.Length != 1)
{
return WriteUsageError("families show requires one exact family name.");
}
GlFunctionFamily? family = analysis.Families.FirstOrDefault(
value => string.Equals(
value.Name,
arguments[0],
StringComparison.OrdinalIgnoreCase));
if (family is null)
{
Console.Error.WriteLine($"No function family named '{arguments[0]}'.");
Console.Error.WriteLine($"Try: families list {arguments[0]}");
return QueryErrorExitCode;
}
Console.WriteLine($"function family: {family.Name}");
WriteProperty("members", family.Members.Count);
WriteProperty("shape name", ReadShapePolicy(family.ShapeNamePolicy));
for (int index = 0; index < family.Members.Count; index++)
{
GlFunctionFamilyMember member = family.Members[index];
Console.WriteLine($" [{index + 1}] {member.Command.Name}");
if (member.NameShape is not null)
{
if (member.NameShape.Shape is not null)
{
WriteIndentedProperty("shape", member.NameShape.Shape, 8);
}
WriteIndentedProperty(
"type suffix",
string.IsNullOrWhiteSpace(member.NameShape.TypeSuffix)
? "-"
: member.NameShape.TypeSuffix,
8);
WriteIndentedProperty(
"form",
member.NameShape.IsVectorForm ? "pointer" : "scalar",
8);
}
WriteIndentedProperty(
"parameters",
ReadParameterList(member.Command),
8);
}
WriteRelations(family.Relations);
if (family.Diagnostics.Count != 0)
{
Console.WriteLine(" diagnostics:");
foreach (string diagnostic in family.Diagnostics)
{
Console.WriteLine($" - {diagnostic}");
}
}
return SuccessExitCode;
}
private static int QueryRelations(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList<string> arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionRelation[] relations = analysis.Relations
.Where(
value => filter is null ||
value.SourceName.Contains(
filter,
StringComparison.OrdinalIgnoreCase) ||
value.TargetName.Contains(
filter,
StringComparison.OrdinalIgnoreCase))
.OrderBy(value => value.SourceName, StringComparer.Ordinal)
.ThenBy(value => value.TargetName, StringComparer.Ordinal)
.ToArray();
if (relations.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No function relations were recognized."
: $"No function relations contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(relations.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
WriteRelation(index + 1, relations[index]);
}
WriteTruncation(relations.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryUnclassified(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList<string> arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
string[] commands = analysis.UnclassifiedCommands
.Select(value => value.Name)
.Where(
value => filter is null || value.Contains(
filter,
StringComparison.OrdinalIgnoreCase))
.ToArray();
if (commands.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "Every command belongs to a candidate family."
: $"No unclassified commands contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(commands.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
Console.WriteLine($"[{index + 1}] {commands[index]}");
}
WriteTruncation(commands.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryConstraints(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList<string> arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionFamily[] families = FilterFamilies(analysis, filter)
.Where(
value => value.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain ||
value.Diagnostics.Count != 0 ||
value.Relations.Any(
relation => relation.State != GlFunctionRelationState.Verified))
.ToArray();
if (families.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No family constraints require review."
: $"No constrained families contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(families.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
GlFunctionFamily family = families[index];
Console.WriteLine($"[{index + 1}] {family.Name}");
if (family.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain)
{
WriteProperty(
"constraint",
"shape suffix must remain because normalized signatures collide");
}
foreach (string diagnostic in family.Diagnostics)
{
WriteProperty("diagnostic", diagnostic);
}
}
WriteTruncation(families.Length, displayedCount);
return SuccessExitCode;
}
private static IEnumerable<GlFunctionFamily> FilterFamilies(
GlFunctionFamilyAnalysis analysis,
string? filter
)
{
return analysis.Families.Where(
value => filter is null || value.Name.Contains(
filter,
StringComparison.OrdinalIgnoreCase));
}
private static bool TryReadFilterOptions(
IReadOnlyList<string> arguments,
out string? filter,
out int limit,
out string? error
)
{
filter = null;
limit = ShowAllResults;
int index = 0;
if (index < arguments.Count && !arguments[index].StartsWith(
"--",
StringComparison.Ordinal))
{
filter = arguments[index++];
}
while (index < arguments.Count)
{
string argument = arguments[index++];
if ((argument == "--showcount" || argument == "--limit") &&
index < arguments.Count && int.TryParse(
arguments[index++],
NumberStyles.None,
CultureInfo.InvariantCulture,
out limit) && limit > 0)
{
continue;
}
error = $"Unknown or invalid family option '{argument}'.";
return false;
}
error = null;
return true;
}
private static string ReadParameterList(
Registry.GlCommandDefinition command
)
{
return string.Join(
", ",
command.Parameters.Select(value => value.DeclarationText));
}
private static void WriteRelations(
IReadOnlyList<GlFunctionRelation> relations
)
{
if (relations.Count == 0)
{
return;
}
Console.WriteLine(" relations:");
for (int index = 0; index < relations.Count; index++)
{
WriteRelation(index + 1, relations[index], 4);
}
}
private static void WriteRelation(
int index,
GlFunctionRelation relation,
int indentation = 0
)
{
string prefix = new string(' ', indentation);
Console.WriteLine(
$"{prefix}[{index}] {relation.SourceName} -> {relation.TargetName}");
WriteIndentedProperty(
"source",
relation.Kind == GlFunctionRelationKind.RegistryVectorEquivalent
? "gl.xml vecequiv"
: "inferred len=1 pair",
indentation + 4);
WriteIndentedProperty(
"state",
ReadRelationState(relation.State),
indentation + 4);
WriteIndentedProperty("detail", relation.Detail, indentation + 4);
}
private static string ReadShapePolicy(
GlFunctionShapeNamePolicy policy
)
{
return policy switch
{
GlFunctionShapeNamePolicy.NotApplicable => "not applicable",
GlFunctionShapeNamePolicy.CanRemove => "may be removed",
GlFunctionShapeNamePolicy.MustRetain => "must remain",
_ => throw new ArgumentOutOfRangeException(nameof(policy)),
};
}
private static string ReadRelationState(
GlFunctionRelationState state
)
{
return state switch
{
GlFunctionRelationState.Verified => "verified",
GlFunctionRelationState.MissingTarget => "missing target",
GlFunctionRelationState.Rejected => "rejected",
_ => throw new ArgumentOutOfRangeException(nameof(state)),
};
}
private static string JoinDistinct(
IEnumerable<string?> values
)
{
string[] distinct = values.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!)
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
return distinct.Length == 0 ? "-" : string.Join(", ", distinct);
}
private static int WriteHelpResult(
string[] arguments
)
{
if (arguments.Length != 0)
{
return WriteUsageError("families help does not accept arguments.");
}
WriteHelp();
return SuccessExitCode;
}
private static int WriteUsageError(
string? error
)
{
Console.Error.WriteLine($"error: {error}");
return UsageErrorExitCode;
}
private static void WriteProperty(
string name,
object value
)
{
WriteIndentedProperty(name, value, 2);
}
private static void WriteIndentedProperty(
string name,
object value,
int indentation
)
{
Console.WriteLine($"{new string(' ', indentation)}{name}: {value}");
}
private static void WriteTruncation(
int totalCount,
int displayedCount
)
{
if (displayedCount != totalCount)
{
Console.WriteLine($"... {totalCount - displayedCount} more result(s)");
}
}
}
}
+149 -11
View File
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using StgSharp.GenerateGL.Generation;
using StgSharp.GenerateGL.Registry;
@@ -23,14 +24,15 @@ namespace StgSharp.GenerateGL
private const int QueryErrorExitCode = 1;
private const int SuccessExitCode = 0;
private const int UsageErrorExitCode = 2;
private const string GeneratedFileMarker = "// <auto-generated>";
public static int Generate(
GlRegistryModel registry,
GlRegistryProjection projection,
IReadOnlyList<string> arguments,
out GeneratedFileSet? generatedFiles
)
{
ArgumentNullException.ThrowIfNull(registry);
ArgumentNullException.ThrowIfNull(projection);
ArgumentNullException.ThrowIfNull(arguments);
generatedFiles = null;
@@ -40,17 +42,24 @@ namespace StgSharp.GenerateGL
return SuccessExitCode;
}
if (arguments.Count != 0)
if (arguments.Count > 1)
{
Console.Error.WriteLine(
$"error: unknown generate option '{arguments[0]}'.");
Console.Error.WriteLine("error: generate accepts at most one output directory.");
Console.Error.WriteLine("Run 'generate --help' for command information.");
return UsageErrorExitCode;
}
string? outputDirectory = arguments.Count == 1 ? arguments[0] : null;
string? resolvedOutputDirectory = null;
try
{
generatedFiles = OpenGlGenerator.Generate(registry);
generatedFiles = OpenGlGenerator.Generate(projection);
if (outputDirectory is not null)
{
resolvedOutputDirectory = WriteGeneratedFiles(
generatedFiles,
outputDirectory);
}
}
catch (InvalidDataException exception)
{
@@ -62,14 +71,136 @@ namespace StgSharp.GenerateGL
Console.Error.WriteLine($"error: {exception.Message}");
return QueryErrorExitCode;
}
catch (UnauthorizedAccessException exception)
{
Console.Error.WriteLine($"error: {exception.Message}");
return QueryErrorExitCode;
}
catch (IOException exception)
{
Console.Error.WriteLine($"error: {exception.Message}");
return QueryErrorExitCode;
}
catch (ArgumentException exception)
{
Console.Error.WriteLine($"error: {exception.Message}");
return QueryErrorExitCode;
}
catch (NotSupportedException exception)
{
Console.Error.WriteLine($"error: {exception.Message}");
return QueryErrorExitCode;
}
Console.WriteLine(
$"Generated {generatedFiles.Files.Count} file(s) in memory.");
Console.WriteLine("Use 'list files' and 'preview <filename>' to inspect them.");
Console.WriteLine("No files were written to disk.");
Console.WriteLine(
resolvedOutputDirectory is null
? "No files were written to disk."
: $"Wrote {generatedFiles.Files.Count} file(s) to " +
$"'{resolvedOutputDirectory}'.");
return SuccessExitCode;
}
private static string WriteGeneratedFiles(
GeneratedFileSet files,
string outputDirectory
)
{
ArgumentNullException.ThrowIfNull(files);
ArgumentException.ThrowIfNullOrWhiteSpace(outputDirectory);
string directory = Path.TrimEndingDirectorySeparator(
Path.GetFullPath(outputDirectory));
if (!Directory.Exists(directory))
{
throw new DirectoryNotFoundException(
$"Output directory '{directory}' does not exist.");
}
StringComparison pathComparison = OperatingSystem.IsWindows()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
List<StagedGeneratedFile> stagedFiles = new List<StagedGeneratedFile>(
files.Files.Count);
try
{
foreach (GeneratedFile file in files.Files)
{
string fileName = Path.GetFileName(file.Name);
if (!string.Equals(fileName, file.Name, StringComparison.Ordinal) ||
string.IsNullOrWhiteSpace(fileName))
{
throw new InvalidDataException(
$"Generated file name '{file.Name}' is not a plain file name.");
}
string targetPath = Path.GetFullPath(Path.Combine(directory, fileName));
if (!string.Equals(
Path.GetDirectoryName(targetPath),
directory,
pathComparison))
{
throw new InvalidDataException(
$"Generated file '{file.Name}' escapes the output directory.");
}
EnsureTargetCanBeReplaced(targetPath);
string temporaryPath = Path.Combine(
directory,
$".{fileName}.{Guid.NewGuid():N}.tmp");
using (FileStream stream = new FileStream(
temporaryPath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None))
using (StreamWriter writer = new StreamWriter(
stream,
new UTF8Encoding(false)))
{
writer.Write(file.Content);
}
stagedFiles.Add(new StagedGeneratedFile(temporaryPath, targetPath));
}
foreach (StagedGeneratedFile file in stagedFiles)
{
File.Move(file.TemporaryPath, file.TargetPath, true);
}
}
finally
{
foreach (StagedGeneratedFile file in stagedFiles)
{
if (File.Exists(file.TemporaryPath))
{
File.Delete(file.TemporaryPath);
}
}
}
return directory;
}
private static void EnsureTargetCanBeReplaced(
string targetPath
)
{
if (!File.Exists(targetPath))
{
return;
}
string? firstLine = File.ReadLines(targetPath).FirstOrDefault();
if (!string.Equals(firstLine, GeneratedFileMarker, StringComparison.Ordinal))
{
throw new IOException(
$"Refusing to overwrite non-generated file '{targetPath}'.");
}
}
public static int ListFiles(
GeneratedFileSet files,
IReadOnlyList<string> arguments
@@ -90,7 +221,7 @@ namespace StgSharp.GenerateGL
return QueryErrorExitCode;
}
Console.WriteLine("Generated files (memory only)");
Console.WriteLine("Generated files held in the current session");
for (int index = 0; index < files.Files.Count; index++)
{
GeneratedFile file = files.Files[index];
@@ -141,16 +272,23 @@ namespace StgSharp.GenerateGL
Console.WriteLine();
Console.WriteLine("Usage:");
Console.WriteLine(" generate");
Console.WriteLine(" generate <output-directory>");
Console.WriteLine(" generate --help");
Console.WriteLine();
Console.WriteLine("Status:");
Console.WriteLine(" Output is retained in the current process only.");
Console.WriteLine(" Running 'generate' does not create or modify disk files.");
Console.WriteLine("Output:");
Console.WriteLine(" Without a directory, output is retained in memory only.");
Console.WriteLine(" The output directory must already exist and be writable.");
Console.WriteLine(" Existing files are replaced only when marked as generated.");
Console.WriteLine();
Console.WriteLine("Inspection:");
Console.WriteLine(" list files");
Console.WriteLine(" preview <filename>");
}
private sealed record StagedGeneratedFile(
string TemporaryPath,
string TargetPath
);
}
}
@@ -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,
@@ -0,0 +1,94 @@
// -----------------------------------------------------------------------------
// file="CSharpIdentifierPolicy"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Text.RegularExpressions;
namespace StgSharp.GenerateGL.Naming
{
internal static class CSharpIdentifierPolicy
{
private static readonly Regex _attributeWord = new Regex(
@"Attrib(?!ute)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _attributeParameterWord = new Regex(
@"attrib(?!ute)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _fragmentWord = new Regex(
@"Frag(?!ment)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
public static string NormalizeMemberName(
string value
)
{
ArgumentException.ThrowIfNullOrWhiteSpace(value);
string result = value.Replace(
"Framebuffer",
"FrameBuffer",
StringComparison.Ordinal)
.Replace(
"Renderbuffer",
"RenderBuffer",
StringComparison.Ordinal)
.Replace(
"Internalformat",
"InternalFormat",
StringComparison.Ordinal);
result = _attributeWord.Replace(result, "Attribute");
return _fragmentWord.Replace(result, "Fragment");
}
public static string NormalizeParameterName(
string value
)
{
ArgumentException.ThrowIfNullOrWhiteSpace(value);
string result = value.Replace(
"Framebuffer",
"FrameBuffer",
StringComparison.Ordinal)
.Replace(
"framebuffer",
"frameBuffer",
StringComparison.Ordinal)
.Replace(
"Renderbuffer",
"RenderBuffer",
StringComparison.Ordinal)
.Replace(
"renderbuffer",
"renderBuffer",
StringComparison.Ordinal)
.Replace(
"Internalformat",
"InternalFormat",
StringComparison.Ordinal)
.Replace(
"internalformat",
"internalFormat",
StringComparison.Ordinal)
.Replace(
"attribindex",
"attributeIndex",
StringComparison.Ordinal)
.Replace(
"bindingindex",
"bindingIndex",
StringComparison.Ordinal)
.Replace(
"relativeoffset",
"relativeOffset",
StringComparison.Ordinal);
return _attributeParameterWord.Replace(result, "attribute");
}
}
}
@@ -0,0 +1,345 @@
// -----------------------------------------------------------------------------
// file="GlManagedApiNamePolicy"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL.Naming
{
/// <summary>
/// Translates native overload families into stable, semantic C# method names.
/// </summary>
internal sealed class GlManagedApiNamePolicy
{
private static readonly Regex _getVertexAttributeModePattern = new Regex(
@"^glGetVertexAttrib(?<mode>I|L)(?:i|ui|d)v$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex _vertexAttributeModePattern = new Regex(
@"^glVertexAttrib(?<mode>I|L|P)[1-4]",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private readonly Dictionary<string, FamilyEntry> _familiesByCommand;
public GlManagedApiNamePolicy(
GlFunctionFamilyAnalysis analysis
)
{
ArgumentNullException.ThrowIfNull(analysis);
_familiesByCommand = analysis.Families
.SelectMany(
family => family.Members.Select(
member => new KeyValuePair<string, FamilyEntry>(
member.Command.Name,
new FamilyEntry(family, member.NameShape))))
.ToDictionary(
value => value.Key,
value => value.Value,
StringComparer.Ordinal);
}
public string ReadMethodName(
GlCommandDefinition command,
string currentName
)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentException.ThrowIfNullOrWhiteSpace(currentName);
string rawName = CSharpIdentifierPolicy.NormalizeMemberName(
RemoveGlPrefix(command.Name));
string normalizedCurrentName = CSharpIdentifierPolicy.NormalizeMemberName(
currentName);
if (!string.Equals(normalizedCurrentName, rawName, StringComparison.Ordinal))
{
return normalizedCurrentName;
}
string? specialName = ReadSpecialMethodName(command.Name);
if (specialName is not null)
{
return specialName;
}
if (!_familiesByCommand.TryGetValue(
command.Name,
out FamilyEntry? entry))
{
return rawName;
}
string familyName = ReadSemanticFamilyName(entry.Family.Name);
if (entry.Family.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain &&
entry.Shape?.Shape is not null)
{
familyName += entry.Shape.Shape;
}
return CSharpIdentifierPolicy.NormalizeMemberName(familyName);
}
public static string ReadNativeMethodName(
GlCommandDefinition command
)
{
ArgumentNullException.ThrowIfNull(command);
return CSharpIdentifierPolicy.NormalizeMemberName(RemoveGlPrefix(command.Name));
}
public string ReadDocumentationPage(
GlCommandDefinition command
)
{
ArgumentNullException.ThrowIfNull(command);
string? specialPage = ReadSpecialDocumentationPage(command.Name);
if (specialPage is not null)
{
return specialPage;
}
if (!_familiesByCommand.TryGetValue(
command.Name,
out FamilyEntry? entry))
{
return command.Name;
}
string familyName = entry.Family.Name
.Replace("FrameBuffer", "Framebuffer", StringComparison.Ordinal)
.Replace("RenderBuffer", "Renderbuffer", StringComparison.Ordinal)
.Replace("InternalFormat", "Internalformat", StringComparison.Ordinal)
.Replace("Attribute", "Attrib", StringComparison.Ordinal)
.Replace("Fragment", "Frag", StringComparison.Ordinal);
return $"gl{ReadDocumentationFamilyName(familyName)}";
}
private static string? ReadSpecialMethodName(
string commandName
)
{
if (commandName == "glClearBufferfi")
{
return "ClearBuffer";
}
if (commandName == "glClearNamedFramebufferfi")
{
return "ClearNamedFrameBuffer";
}
if (commandName == "glGetVertexAttribPointerv")
{
return "GetVertexAttributePointer";
}
if (commandName.StartsWith("glVertexAttrib4N", StringComparison.Ordinal))
{
return "VertexAttributeNormalized";
}
Match getMatch = _getVertexAttributeModePattern.Match(commandName);
if (getMatch.Success)
{
return getMatch.Groups["mode"].Value == "I"
? "GetVertexAttributeInteger"
: "GetVertexAttributeDouble";
}
if (commandName is "glVertexAttribIFormat" or "glVertexArrayAttribIFormat")
{
return CSharpIdentifierPolicy.NormalizeMemberName(
RemoveGlPrefix(commandName).Replace(
"AttribIFormat",
"AttributeIntegerFormat",
StringComparison.Ordinal));
}
if (commandName is "glVertexAttribLFormat" or "glVertexArrayAttribLFormat")
{
return CSharpIdentifierPolicy.NormalizeMemberName(
RemoveGlPrefix(commandName).Replace(
"AttribLFormat",
"AttributeDoubleFormat",
StringComparison.Ordinal));
}
if (commandName == "glVertexAttribIPointer")
{
return "VertexAttributeIntegerPointer";
}
if (commandName == "glVertexAttribLPointer")
{
return "VertexAttributeDoublePointer";
}
Match vertexMatch = _vertexAttributeModePattern.Match(commandName);
if (!vertexMatch.Success)
{
return null;
}
return vertexMatch.Groups["mode"].Value switch
{
"I" => "VertexAttributeInteger",
"L" => "VertexAttributeDouble",
"P" => $"VertexAttributePacked{ReadVertexAttributeWidth(commandName, 'P')}",
_ => null,
};
}
private static string ReadSemanticFamilyName(
string familyName
)
{
return familyName switch
{
"VertexAttrib" or "VertexAttribute" => "VertexAttribute",
"VertexAttrib4N" or "VertexAttribute4N" => "VertexAttributeNormalized",
"VertexAttribI" or "VertexAttributeI" => "VertexAttributeInteger",
"VertexAttribL" or "VertexAttributeL" => "VertexAttributeDouble",
"VertexAttribP" or "VertexAttributeP" => "VertexAttributePacked",
"GetVertexAttrib" or "GetVertexAttribute" => "GetVertexAttribute",
"GetVertexAttribI" or "GetVertexAttributeI" => "GetVertexAttributeInteger",
"GetnUniform" => "GetUniform",
"SamplerParameterI" => "SamplerParameterInteger",
"GetSamplerParameterI" => "GetSamplerParameterInteger",
"TexParameter" => "TextureParameter",
"TexParameterI" => "TextureParameterInteger",
"GetTexParameter" => "GetTextureParameter",
"GetTexParameterI" => "GetTextureParameterInteger",
"GetTexLevelParameter" => "GetTextureLevelParameter",
"TextureParameter" => "NamedTextureParameter",
"TextureParameterI" => "NamedTextureParameterInteger",
"GetTextureParameter" => "GetNamedTextureParameter",
"GetTextureParameterI" => "GetNamedTextureParameterInteger",
"GetTextureLevelParameter" => "GetNamedTextureLevelParameter",
_ => familyName,
};
}
private static string ReadDocumentationFamilyName(
string familyName
)
{
return familyName switch
{
"VertexAttrib4N" or "VertexAttribI" or "VertexAttribL" or
"VertexAttribP" => "VertexAttrib",
"GetVertexAttribI" => "GetVertexAttrib",
"SamplerParameterI" => "SamplerParameter",
"GetSamplerParameterI" => "GetSamplerParameter",
"TexParameterI" => "TexParameter",
"GetTexParameterI" => "GetTexParameter",
"TextureParameterI" => "TextureParameter",
"GetTextureParameterI" => "GetTextureParameter",
_ => familyName,
};
}
private static string? ReadSpecialDocumentationPage(
string commandName
)
{
if (commandName.StartsWith("glProgramUniformMatrix", StringComparison.Ordinal))
{
return "glProgramUniform";
}
if (commandName.StartsWith("glUniformMatrix", StringComparison.Ordinal))
{
return "glUniform";
}
if (commandName is "glVertexAttribIPointer" or "glVertexAttribLPointer")
{
return "glVertexAttribPointer";
}
if (commandName is "glVertexAttribIFormat" or "glVertexAttribLFormat" or
"glVertexArrayAttribFormat" or "glVertexArrayAttribIFormat" or
"glVertexArrayAttribLFormat")
{
return "glVertexAttribFormat";
}
if (commandName == "glClearBufferfi")
{
return "glClearBuffer";
}
if (commandName == "glClearNamedFramebufferfi")
{
return "glClearNamedFramebuffer";
}
if (commandName is "glGetBooleanv" or "glGetDoublev" or
"glGetFloatv" or "glGetIntegerv" or "glGetInteger64v" or
"glGetBooleani_v" or "glGetDoublei_v" or "glGetFloati_v" or
"glGetIntegeri_v" or "glGetInteger64i_v")
{
return "glGet";
}
if (commandName == "glGetVertexAttribPointerv")
{
return commandName;
}
if (commandName.StartsWith("glGetVertexAttrib", StringComparison.Ordinal))
{
return "glGetVertexAttrib";
}
if (commandName.StartsWith("glVertexAttrib", StringComparison.Ordinal) &&
commandName.Length > "glVertexAttrib".Length &&
char.IsDigit(commandName["glVertexAttrib".Length]) ||
commandName.StartsWith("glVertexAttribI", StringComparison.Ordinal) &&
commandName != "glVertexAttribIFormat" &&
commandName != "glVertexAttribIPointer" ||
commandName.StartsWith("glVertexAttribL", StringComparison.Ordinal) &&
commandName != "glVertexAttribLFormat" &&
commandName != "glVertexAttribLPointer" ||
commandName.StartsWith("glVertexAttribP", StringComparison.Ordinal))
{
return "glVertexAttrib";
}
return null;
}
private static char ReadVertexAttributeWidth(
string commandName,
char mode
)
{
int index = commandName.IndexOf(mode, "glVertexAttrib".Length) + 1;
return commandName[index];
}
private static string RemoveGlPrefix(
string name
)
{
return name.StartsWith("gl", StringComparison.Ordinal) && name.Length > 2
? name[2..]
: name;
}
private sealed record FamilyEntry(
GlFunctionFamily Family,
GlFunctionNameShape? Shape
);
}
}
+157
View File
@@ -0,0 +1,157 @@
# StgSharp.GenerateGL
`StgSharp.GenerateGL` generates the StgSharp desktop OpenGL C# bindings from the Khronos `gl.xml` registry.
The current target is OpenGL 4.6 Core. The generator does not emit OpenGL ES or mix extension entry points into the core API. `gl.xml` is the authoritative generation input; the retained `gl.h` is reference material only.
## Usage
Start the interactive CLI:
```powershell
dotnet run --project tools/StgSharp.GenerateGL/StgSharp.GenerateGL.csproj
```
Common commands:
```text
summary
find glBindBuffer
show command glBindBuffer
target summary
families list Vertex
families show VertexAttributeP
generate
list files
preview OpenGLFunction.v4_1.g.cs
```
Generate directly into an existing directory:
```powershell
dotnet run --project tools/StgSharp.GenerateGL/StgSharp.GenerateGL.csproj -- generate src/StgSharp.Graphics/OpenGL/Generated
```
Without a directory, `generate` retains its output in the current CLI session. With a directory:
- the directory must already exist and be writable;
- each file is staged in the destination directory before replacing its target;
- an existing file is replaced only when its first line is `// <auto-generated>`;
- unrelated and obsolete generated files are not deleted.
Use a directory dedicated to generated source files.
## Pipeline
```text
gl.xml
-> lossless XML and registry models
-> desktop OpenGL 4.6 Core projection
-> function-family and scalar/vector relation analysis
-> managed API filters
-> C# naming and signature validation
-> in-memory generated file set
-> preview or directory output
```
The target projection removes GLES and ARB/NV/EXT extension entry points. It retains only commands and constants present in the target Core Profile.
Generated files:
- `glconst.cs`: core constants;
- `OpenglContext.g.cs`: the function-pointer table with native names and ABI types;
- `OpenGLFunction.Base.g.cs`: APIs introduced through OpenGL 3.2 and retained in the target profile;
- `OpenGLFunction.v3_3.g.cs` through `OpenGLFunction.v4_6.g.cs`: APIs introduced by each later core version.
`OpenglContext` intentionally remains close to the C ABI. Overloads, `Span<T>`, `GlHandle`, and managed naming exist only in the `OpenGLFunction` layer.
## Overload rules
The generator merges a family only when it can validate the resulting signatures. Every managed method retains a reference to its original `gl*` command.
| Native C API | Generated API |
| --- | --- |
| `glUniform1f` through `glUniform4f` | Scalar `Uniform(...)` overloads |
| `glUniform*i`, `glUniform*ui`, and `glUniform*d` | Merged into `Uniform(...)` by C# parameter type |
| `glProgramUniform*` | Merged into `ProgramUniform(...)` by the same rules |
| `glUniform1*v` | `Uniform(..., ReadOnlySpan<T>)` |
| `glUniform4*v` | `Uniform(..., ReadOnlySpan<Vec4<T>>)` |
| `glUniform2*v` and `glUniform3*v` | Omitted because `Vec2` and `Vec3` storage is not a tightly packed C array |
| `glUniformMatrix4fv` | `Uniform(..., GraphicsMatrix)` and matrix-span overloads |
| Other matrix sizes and double matrices | Omitted because only the graphics-oriented 4x4 float layout is defined |
| `glGetBooleanv`, `glGetFloatv`, `glGetIntegerv`, and related queries | `GetValue(..., Span<T>)` |
| `glVertexAttrib1f/2f/3f/4f` and equivalent scalar forms | `VertexAttribute(...)`; component count is expressed by the parameter list |
| `glVertexAttrib*fv/*iv/...` with a scalar equivalent | Omitted instead of exposing a duplicate C-style pointer entry point |
| `glVertexAttrib4N*` | `VertexAttributeNormalized(...)` |
| `glVertexAttribI*` | `VertexAttributeInteger(...)` |
| `glVertexAttribL*` | `VertexAttributeDouble(...)`; `L` does not mean C# `long` |
| `glVertexAttribP1ui` through `P4ui` | `VertexAttributePacked1(...)` through `Packed4(...)`; the width prevents signature collisions |
| `glGetVertexAttribI*` and `L*` | `GetVertexAttributeInteger(...)` and `GetVertexAttributeDouble(...)` |
| `glScissorIndexed` and `glScissorIndexedv` | `ScissorIndexed(...)` overloads |
Function families are inferred from common name prefixes, shape suffixes, type suffixes, and vector forms. Candidates are checked against:
- the `gl.xml` `<vecequiv>` relation;
- parameter `len`, `kind`, and native types;
- scalar `T` versus `const T*` relationships;
- uniqueness of the final C# signature.
If incomplete type mapping causes two methods to collapse to the same C# signature, the generator conservatively restores their normalized native names instead of emitting uncompilable overloads.
## Renaming rules
Safe lexical normalization applies to all public methods:
| OpenGL spelling | C# spelling |
| --- | --- |
| `Framebuffer` / `framebuffer` | `FrameBuffer` / `frameBuffer` |
| `Renderbuffer` / `renderbuffer` | `RenderBuffer` / `renderBuffer` |
| `Internalformat` / `internalformat` | `InternalFormat` / `internalFormat` |
| `Attrib` / `attrib` | `Attribute` / `attribute` |
| `Frag` | `Fragment` |
| `attribindex` | `attributeIndex` |
| `bindingindex` | `bindingIndex` |
| `relativeoffset` | `relativeOffset` |
Families that represent distinct semantics receive explicit names:
| Native family | Managed family |
| --- | --- |
| `TexParameter*` | `TextureParameter(...)` |
| DSA `TextureParameter*` | `NamedTextureParameter(...)` |
| `TexParameterI*` | `TextureParameterInteger(...)` |
| DSA `TextureParameterI*` | `NamedTextureParameterInteger(...)` |
| `SamplerParameterI*` | `SamplerParameterInteger(...)` |
| `GetnUniform*` | Merged into `GetUniform(...)` when signatures permit |
| `VertexAttribI/L/P/N` | Expanded to `Integer/Double/Packed/Normalized` |
Renaming never changes the native target. For example, `VertexAttributeInteger(...)` still calls `__context->glVertexAttribI*`.
## Type rules
- `gl.xml` typedefs map to equivalent C# ABI types; the generator does not emit separate type aliases;
- a `GLuint` is converted to `GlHandle` only when object class and parameter semantics identify it as an object handle;
- handle arrays use `Span<GlHandle>` or `ReadOnlySpan<GlHandle>`;
- `OpenGLFunction` stores an `OpenglContext*` and forwards through `__context->glFunction(...)`;
- unknown-length arrays, complex outputs, multiple indirection, and `void*` remain pointers.
A `T*` alone does not determine whether a managed API should use `Span<T>`, `ReadOnlySpan<T>`, `out T`, or a return value. Future pointer improvements should be implemented as family-specific filters validated with `len`, parameter direction, and OpenGL state semantics.
## Documentation comments
Every generated public method contains:
- its original C API declaration;
- a `<seealso>` link to the corresponding docs.gl OpenGL 4 page or family page.
The generator does not emit `<param>` or `<returns>` elements and does not copy docs.gl content.
## Source layout
- `Registry/`: the `gl.xml` object model and Core Profile projection;
- `Analysis/`: function families, name shapes, and scalar/vector relations;
- `Generation/`: type mapping, API filters, signature planning, and file generation;
- `Naming/`: C# identifier and semantic-family naming policies;
- `XmlCli.cs`, `RegistryCli.cs`, and `FunctionFamilyCli.cs`: interactive inspection;
- `GenerateCli.cs`: in-memory preview and protected directory output.
@@ -0,0 +1,258 @@
// -----------------------------------------------------------------------------
// file="DesktopGlCoreRegistryFilter"
// 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;
namespace StgSharp.GenerateGL.Registry
{
/// <summary>
/// Selects the active desktop OpenGL core API without extension requirements.
/// </summary>
internal static class DesktopGlCoreRegistryFilter
{
public const string ApiName = "gl";
public const string CoreProfile = "core";
public const string LatestVersion = "4.6";
public static GlRegistryProjection Apply(
GlRegistryModel registry,
string maximumVersion = LatestVersion
)
{
ArgumentNullException.ThrowIfNull(registry);
Version maximum = ReadVersion(maximumVersion);
GlFeatureDefinition[] features = registry.Features
.Where(
value => string.Equals(value.Api, ApiName, StringComparison.Ordinal) &&
ReadVersion(value.Number) <= maximum)
.ToArray();
Dictionary<string, GlCommandDefinition> commandDefinitions = ReadCommandDefinitions(
registry);
Dictionary<string, GlEnumDefinition> enumDefinitions = ReadEnumDefinitions(registry);
SymbolSelection commands = new SymbolSelection();
SymbolSelection enums = new SymbolSelection();
foreach (GlFeatureDefinition feature in features)
{
foreach (GlRequirementBlock block in feature.Requirements.Where(AppliesToTarget))
{
foreach (GlRegistryReference reference in block.References)
{
SymbolSelection? selection = reference.Kind switch
{
GlRegistryReferenceKind.Command => commands,
GlRegistryReferenceKind.Enum => enums,
GlRegistryReferenceKind.Type => null,
_ => throw new InvalidDataException(
$"Unknown registry reference kind '{reference.Kind}'."),
};
if (selection is null)
{
continue;
}
if (block.Operation == GlRequirementOperation.Require)
{
selection.Require(reference.Name, feature.Number);
}
else
{
selection.Remove(reference.Name);
}
}
}
}
GlCommandDefinition[] selectedCommands = commands.ReadActiveNames()
.Select(
name => ReadDefinition(
commandDefinitions,
name,
"command"))
.ToArray();
GlEnumDefinition[] selectedEnums = enums.ReadActiveNames()
.Select(
name => ReadDefinition(
enumDefinitions,
name,
"enum"))
.ToArray();
return new GlRegistryProjection(
registry,
ApiName,
CoreProfile,
maximumVersion,
Freeze(features),
Freeze(selectedCommands),
Freeze(selectedEnums),
commands.FreezeIntroducedVersions(),
enums.FreezeIntroducedVersions());
}
private static bool AppliesToTarget(
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 Dictionary<string, GlCommandDefinition> ReadCommandDefinitions(
GlRegistryModel registry
)
{
Dictionary<string, GlCommandDefinition> result = new Dictionary<string, GlCommandDefinition>(
StringComparer.Ordinal);
foreach (GlCommandDefinition command in registry.Commands)
{
if (!result.TryAdd(command.Name, command))
{
throw new InvalidDataException(
$"Command '{command.Name}' has more than one definition.");
}
}
return result;
}
private static Dictionary<string, GlEnumDefinition> ReadEnumDefinitions(
GlRegistryModel registry
)
{
Dictionary<string, GlEnumDefinition> result = new Dictionary<string, GlEnumDefinition>(
StringComparer.Ordinal);
foreach (GlEnumDefinition value in registry.EnumBlocks.SelectMany(
block => block.Enums).Where(IsDesktopDefinition))
{
if (!result.TryGetValue(value.Name, out GlEnumDefinition? previous))
{
result.Add(value.Name, value);
continue;
}
if (previous.Value is not null && value.Value is not null && !string.Equals(
previous.Value,
value.Value,
StringComparison.Ordinal))
{
throw new InvalidDataException(
$"Enum '{value.Name}' has conflicting desktop values " +
$"'{previous.Value}' and '{value.Value}'.");
}
if (previous.Value is null && value.Value is not null)
{
result[value.Name] = value;
}
}
return result;
}
private static bool IsDesktopDefinition(
GlEnumDefinition value
)
{
return value.Api is null || string.Equals(
value.Api,
ApiName,
StringComparison.Ordinal);
}
private static T ReadDefinition<T>(
IReadOnlyDictionary<string, T> definitions,
string name,
string kind
)
{
if (!definitions.TryGetValue(name, out T? definition))
{
throw new InvalidDataException(
$"Desktop OpenGL core references undefined {kind} '{name}'.");
}
return definition;
}
private static Version ReadVersion(
string value
)
{
if (!Version.TryParse(value, out Version? version))
{
throw new InvalidDataException(
$"OpenGL feature version '{value}' is not a valid version.");
}
return version;
}
private static ReadOnlyCollection<T> Freeze<T>(
IEnumerable<T> values
)
{
return Array.AsReadOnly(values.ToArray());
}
private sealed class SymbolSelection
{
private readonly HashSet<string> _active = new HashSet<string>(StringComparer.Ordinal);
private readonly Dictionary<string, string> _introducedVersions = new Dictionary<string, string>(
StringComparer.Ordinal);
private readonly List<string> _order = new List<string>();
public void Require(
string name,
string version
)
{
_active.Add(name);
if (_introducedVersions.TryAdd(name, version))
{
_order.Add(name);
}
}
public void Remove(
string name
)
{
_active.Remove(name);
}
public IEnumerable<string> ReadActiveNames()
{
return _order.Where(_active.Contains);
}
public ReadOnlyDictionary<string, string> FreezeIntroducedVersions()
{
return new ReadOnlyDictionary<string, string>(
new Dictionary<string, string>(
_introducedVersions,
StringComparer.Ordinal));
}
}
}
}
@@ -0,0 +1,26 @@
// -----------------------------------------------------------------------------
// file="GlRegistryProjection"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
namespace StgSharp.GenerateGL.Registry
{
/// <summary>
/// Represents one API/profile target selected from the complete registry.
/// </summary>
internal sealed record GlRegistryProjection(
GlRegistryModel Source,
string Api,
string Profile,
string MaximumVersion,
IReadOnlyList<GlFeatureDefinition> Features,
IReadOnlyList<GlCommandDefinition> Commands,
IReadOnlyList<GlEnumDefinition> Enums,
IReadOnlyDictionary<string, string> CommandIntroducedVersions,
IReadOnlyDictionary<string, string> EnumIntroducedVersions
);
}
+10 -8
View File
@@ -19,8 +19,7 @@ namespace StgSharp.GenerateGL
internal static class RegistryCli
{
private const int DefaultFindLimit = 10;
private const int DefaultListLimit = 20;
private const int ShowAllResults = int.MaxValue;
private const int QueryErrorExitCode = 1;
private const int SuccessExitCode = 0;
private const int UsageErrorExitCode = 2;
@@ -559,7 +558,7 @@ namespace StgSharp.GenerateGL
}
filter = null;
limit = DefaultListLimit;
limit = ShowAllResults;
int index = 1;
if (index < arguments.Count && !arguments[index].StartsWith(
"--",
@@ -571,7 +570,8 @@ namespace StgSharp.GenerateGL
while (index < arguments.Count)
{
string argument = arguments[index++];
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -608,7 +608,7 @@ namespace StgSharp.GenerateGL
text = arguments[0];
exact = false;
limit = DefaultFindLimit;
limit = ShowAllResults;
int index = 1;
while (index < arguments.Count)
{
@@ -619,7 +619,8 @@ namespace StgSharp.GenerateGL
continue;
}
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -653,12 +654,13 @@ namespace StgSharp.GenerateGL
}
name = arguments[1];
limit = DefaultListLimit;
limit = ShowAllResults;
int index = 2;
while (index < arguments.Count)
{
string argument = arguments[index++];
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -0,0 +1,59 @@
// -----------------------------------------------------------------------------
// file="TargetRegistryCli"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using StgSharp.GenerateGL.Registry;
namespace StgSharp.GenerateGL
{
internal static class TargetRegistryCli
{
private const int SuccessExitCode = 0;
private const int UsageErrorExitCode = 2;
public static int Execute(
GlRegistryProjection projection,
IReadOnlyList<string> arguments
)
{
ArgumentNullException.ThrowIfNull(projection);
ArgumentNullException.ThrowIfNull(arguments);
if (arguments.Count > 1 || arguments.Count == 1 && !string.Equals(
arguments[0],
"summary",
StringComparison.OrdinalIgnoreCase))
{
Console.Error.WriteLine("error: target accepts only the 'summary' command.");
return UsageErrorExitCode;
}
Console.WriteLine("Registry target projection");
WriteProperty("api", projection.Api);
WriteProperty("profile", projection.Profile);
WriteProperty("maximum version", projection.MaximumVersion);
WriteProperty("features", projection.Features.Count);
WriteProperty("commands", projection.Commands.Count);
WriteProperty("enums", projection.Enums.Count);
WriteProperty("extensions", 0);
WriteProperty("source commands", projection.Source.Commands.Count);
WriteProperty("source extensions", projection.Source.Extensions.Count);
return SuccessExitCode;
}
private static void WriteProperty(
string name,
object value
)
{
Console.WriteLine($" {name}: {value}");
}
}
}
+65 -25
View File
@@ -14,6 +14,7 @@ using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using StgSharp.GenerateGL.Analysis;
using StgSharp.GenerateGL.Generation;
using StgSharp.GenerateGL.Registry;
using StgSharp.GenerateGL.Tree;
@@ -26,10 +27,9 @@ namespace StgSharp.GenerateGL
internal const int LoadErrorExitCode = 3;
private const int DefaultDepth = 2;
private const int DefaultLimit = 20;
private const int DefaultSearchLimit = 5;
private const int ShowAllResults = int.MaxValue;
private const int ReadableDepth = 6;
private const int SearchReadableChildLimit = 8;
private const int SearchContextParentChildThreshold = 20;
private const int SearchReadableDepth = 4;
private const int QueryErrorExitCode = 1;
private const int SuccessExitCode = 0;
@@ -85,6 +85,12 @@ namespace StgSharp.GenerateGL
command,
arguments),
"GENERATE" or "GEN" => QueryGenerate(session, arguments),
"TARGET" => TargetRegistryCli.Execute(
session.TargetRegistry,
arguments),
"FAMILY" or "FAMILIES" => FunctionFamilyCli.Execute(
session.FunctionFamilies,
arguments),
"PREVIEW" => GenerateCli.Preview(
session.GeneratedFiles,
arguments),
@@ -113,7 +119,7 @@ namespace StgSharp.GenerateGL
)
{
int result = GenerateCli.Generate(
session.Registry,
session.TargetRegistry,
arguments,
out GeneratedFileSet? generatedFiles);
if (generatedFiles is not null)
@@ -192,7 +198,7 @@ namespace StgSharp.GenerateGL
context,
0,
SearchReadableDepth,
SearchReadableChildLimit);
ShowAllResults);
}
WriteTruncation(matches.Length, displayedCount);
@@ -229,7 +235,7 @@ namespace StgSharp.GenerateGL
}
Console.WriteLine($"[{index + 1}] {GetElementPath(element)}");
WriteReadableElement(element, 0, ReadableDepth, DefaultLimit);
WriteReadableElement(element, 0, ReadableDepth, ShowAllResults);
}
WriteTruncation(matches.Length, displayedCount);
@@ -610,8 +616,11 @@ namespace StgSharp.GenerateGL
break;
}
if (parentLevels is null && parent.Elements().Take(DefaultLimit + 1).Count() >
DefaultLimit)
if (parentLevels is null && parent.Elements()
.Take(
SearchContextParentChildThreshold + 1)
.Count() >
SearchContextParentChildThreshold)
{
break;
}
@@ -1115,7 +1124,7 @@ namespace StgSharp.GenerateGL
{
string xpath = "/*";
int depth = DefaultDepth;
int limit = DefaultLimit;
int limit = ShowAllResults;
bool includeWhitespace = false;
int index = 0;
if (arguments.Count != 0 && !arguments[0].StartsWith("--", StringComparison.Ordinal))
@@ -1141,7 +1150,8 @@ namespace StgSharp.GenerateGL
continue;
}
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -1173,7 +1183,7 @@ namespace StgSharp.GenerateGL
}
string text = arguments[0];
int limit = DefaultSearchLimit;
int limit = ShowAllResults;
int? parentLevels = null;
bool exact = false;
int index = 1;
@@ -1186,7 +1196,8 @@ namespace StgSharp.GenerateGL
continue;
}
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -1228,12 +1239,13 @@ namespace StgSharp.GenerateGL
}
string xpath = arguments[0];
int limit = DefaultLimit;
int limit = ShowAllResults;
int index = 1;
while (index < arguments.Count)
{
string argument = arguments[index++];
if (argument == "--limit" && TryReadPositiveInt(
if ((argument == "--showcount" || argument == "--limit") &&
TryReadPositiveInt(
arguments,
ref index,
out limit))
@@ -1391,9 +1403,24 @@ namespace StgSharp.GenerateGL
return;
}
if (arguments is { Length: 1 } && (
string.Equals(
arguments[0],
"family",
StringComparison.OrdinalIgnoreCase) ||
string.Equals(
arguments[0],
"families",
StringComparison.OrdinalIgnoreCase)))
{
FunctionFamilyCli.WriteHelp();
return;
}
if (arguments is { Length: > 0 })
{
Console.Error.WriteLine("error: help accepts only the 'generate' topic.");
Console.Error.WriteLine(
"error: help accepts only the 'generate' or 'families' topic.");
return;
}
@@ -1404,12 +1431,15 @@ namespace StgSharp.GenerateGL
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] shell");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] summary");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] list <category> " +
"[filter] [--limit <n>]");
"[filter] [--showcount <n>]");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] find <text> " +
"[--exact] [--limit <n>]");
"[--exact] [--showcount <n>]");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] show <category> <name> " +
"[--limit <n>]");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] generate");
"[--showcount <n>]");
Console.WriteLine(
" StgSharp.GenerateGL [--xml <path>] generate [output-directory]");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] target summary");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] families <command>");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] list files");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] preview <filename>");
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] xml <command> [...]");
@@ -1424,7 +1454,11 @@ namespace StgSharp.GenerateGL
Console.WriteLine(" StgSharp.GenerateGL find glBindBuffer");
Console.WriteLine(" StgSharp.GenerateGL show command glBindBuffer");
Console.WriteLine(" StgSharp.GenerateGL show extension GL_KHR_debug");
Console.WriteLine(" StgSharp.GenerateGL target summary");
Console.WriteLine(" StgSharp.GenerateGL families show VertexAttribP");
Console.WriteLine(" StgSharp.GenerateGL generate");
Console.WriteLine(
" StgSharp.GenerateGL generate src/StgSharp.Graphics/OpenGL/Generated");
Console.WriteLine(" StgSharp.GenerateGL list files");
Console.WriteLine(" StgSharp.GenerateGL preview glconst.cs");
Console.WriteLine(" StgSharp.GenerateGL xml tree --depth 1");
@@ -1443,13 +1477,13 @@ namespace StgSharp.GenerateGL
Console.WriteLine(" xml summary");
Console.WriteLine(" xml raw");
Console.WriteLine(
" xml tree [xpath] [--depth <n>] [--limit <n>] [--whitespace]");
" xml tree [xpath] [--depth <n>] [--showcount <n>] [--whitespace]");
Console.WriteLine(
" xml find <text> [--exact] [--up <n>] [--limit <n>]");
Console.WriteLine(" xml show <xpath> [--limit <n>]");
Console.WriteLine(" xml select <xpath> [--limit <n>]");
Console.WriteLine(" xml text <xpath> [--limit <n>]");
Console.WriteLine(" xml attributes <xpath> [--limit <n>]");
" xml find <text> [--exact] [--up <n>] [--showcount <n>]");
Console.WriteLine(" xml show <xpath> [--showcount <n>]");
Console.WriteLine(" xml select <xpath> [--showcount <n>]");
Console.WriteLine(" xml text <xpath> [--showcount <n>]");
Console.WriteLine(" xml attributes <xpath> [--showcount <n>]");
}
private sealed class CliSession
@@ -1462,12 +1496,18 @@ namespace StgSharp.GenerateGL
{
Document = document;
Registry = registry;
TargetRegistry = DesktopGlCoreRegistryFilter.Apply(registry);
FunctionFamilies = GlFunctionFamilyAnalyzer.Analyze(TargetRegistry);
}
public XmlTreeDocument Document { get; }
public GlRegistryModel Registry { get; }
public GlRegistryProjection TargetRegistry { get; }
public GlFunctionFamilyAnalysis FunctionFamilies { get; }
public GeneratedFileSet GeneratedFiles { get; set; } = GeneratedFileSet.Empty;
}