switch root namespace name to NITLOAD
This commit is contained in:
@@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GenerateCli"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Generation;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL
|
||||
{
|
||||
/// <summary>
|
||||
/// Owns the command-line boundary of the future code generation pipeline.
|
||||
/// </summary>
|
||||
internal static class GenerateCli
|
||||
{
|
||||
|
||||
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(
|
||||
GlRegistryProjection projection,
|
||||
IReadOnlyList<string> arguments,
|
||||
out GeneratedFileSet? generatedFiles
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
ArgumentNullException.ThrowIfNull(arguments);
|
||||
generatedFiles = null;
|
||||
|
||||
if (arguments.Count == 1 && arguments[0] is "--help" or "-h")
|
||||
{
|
||||
WriteHelp();
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
if (arguments.Count == 0 || arguments.Count > 2 ||
|
||||
!TryReadTarget(arguments[0], out GlGenerationTarget target))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"error: generate requires api, enum, or all and accepts one optional output directory.");
|
||||
Console.Error.WriteLine("Run 'generate --help' for command information.");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
string? outputDirectory = arguments.Count == 2 ? arguments[1] : null;
|
||||
string? resolvedOutputDirectory = null;
|
||||
try
|
||||
{
|
||||
generatedFiles = OpenGlGenerator.Generate(projection, target);
|
||||
if (outputDirectory is not null)
|
||||
{
|
||||
resolvedOutputDirectory = WriteGeneratedFiles(
|
||||
generatedFiles,
|
||||
outputDirectory);
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
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(
|
||||
resolvedOutputDirectory is null
|
||||
? "No files were written to disk."
|
||||
: $"Wrote {generatedFiles.Files.Count} file(s) to " +
|
||||
$"'{resolvedOutputDirectory}'.");
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static bool TryReadTarget(
|
||||
string value,
|
||||
out GlGenerationTarget target
|
||||
)
|
||||
{
|
||||
target = value.ToUpperInvariant() switch
|
||||
{
|
||||
"API" => GlGenerationTarget.Api,
|
||||
"ENUM" or "ENUMS" => GlGenerationTarget.Enum,
|
||||
"ALL" => GlGenerationTarget.All,
|
||||
_ => GlGenerationTarget.None,
|
||||
};
|
||||
return target != GlGenerationTarget.None;
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(files);
|
||||
ArgumentNullException.ThrowIfNull(arguments);
|
||||
if (arguments.Count != 0)
|
||||
{
|
||||
Console.Error.WriteLine("error: list files does not accept arguments.");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
if (files.Files.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine("No generated files are held in this session.");
|
||||
Console.Error.WriteLine("Run 'generate api', 'generate enum', or 'generate all' first.");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
Console.WriteLine("Generated files held in the current session");
|
||||
for (int index = 0; index < files.Files.Count; index++)
|
||||
{
|
||||
GeneratedFile file = files.Files[index];
|
||||
Console.WriteLine(
|
||||
$" [{index + 1}] {file.Name} ({file.Content.Length} characters)");
|
||||
}
|
||||
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
public static int Preview(
|
||||
GeneratedFileSet files,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(files);
|
||||
ArgumentNullException.ThrowIfNull(arguments);
|
||||
if (arguments.Count != 1)
|
||||
{
|
||||
Console.Error.WriteLine("error: preview requires one generated file name.");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
if (!files.TryGetFile(arguments[0], out GeneratedFile file))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"No generated file named '{arguments[0]}' is held in this session.");
|
||||
if (files.Files.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"Run 'generate api', 'generate enum', or 'generate all' first.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"Available files: {string.Join(", ", files.Files.Select(value => value.Name))}");
|
||||
}
|
||||
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
Console.Write(file.Content);
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
public static void WriteHelp()
|
||||
{
|
||||
Console.WriteLine("Generate C# bindings from the loaded OpenGL registry.");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" generate api [output-directory]");
|
||||
Console.WriteLine(" generate enum [output-directory]");
|
||||
Console.WriteLine(" generate all [output-directory]");
|
||||
Console.WriteLine(" generate --help");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Targets:");
|
||||
Console.WriteLine(" api Generate function tables and managed forwarding APIs.");
|
||||
Console.WriteLine(" enum Generate glConst and strongly typed enum groups.");
|
||||
Console.WriteLine(" all Generate both API and enum files.");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Output:");
|
||||
Console.WriteLine(" Without a directory, output is retained in memory only.");
|
||||
Console.WriteLine(" API output references enum output; use all for a clean directory.");
|
||||
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,64 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="CSharpAbiTypePolicy"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines C ABI primitives and external Khronos platform types that cannot
|
||||
/// be inferred from gl.xml.
|
||||
/// </summary>
|
||||
internal static class CSharpAbiTypePolicy
|
||||
{
|
||||
|
||||
private static readonly Dictionary<string, string> _types = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal)
|
||||
{
|
||||
["void"] = "void",
|
||||
["char"] = "byte",
|
||||
["signed char"] = "sbyte",
|
||||
["unsigned char"] = "byte",
|
||||
["short"] = "short",
|
||||
["short int"] = "short",
|
||||
["signed short"] = "short",
|
||||
["signed short int"] = "short",
|
||||
["unsigned short"] = "ushort",
|
||||
["unsigned short int"] = "ushort",
|
||||
["int"] = "int",
|
||||
["signed"] = "int",
|
||||
["signed int"] = "int",
|
||||
["unsigned"] = "uint",
|
||||
["unsigned int"] = "uint",
|
||||
["float"] = "float",
|
||||
["double"] = "double",
|
||||
["khronos_int8_t"] = "sbyte",
|
||||
["khronos_uint8_t"] = "byte",
|
||||
["khronos_int16_t"] = "short",
|
||||
["khronos_uint16_t"] = "ushort",
|
||||
["khronos_int32_t"] = "int",
|
||||
["khronos_uint32_t"] = "uint",
|
||||
["khronos_int64_t"] = "long",
|
||||
["khronos_uint64_t"] = "ulong",
|
||||
["khronos_intptr_t"] = "nint",
|
||||
["khronos_uintptr_t"] = "nuint",
|
||||
["khronos_ssize_t"] = "nint",
|
||||
["khronos_usize_t"] = "nuint",
|
||||
["khronos_float_t"] = "float",
|
||||
};
|
||||
|
||||
public static bool TryResolve(
|
||||
string typeName,
|
||||
out string equivalentType
|
||||
)
|
||||
{
|
||||
return _types.TryGetValue(typeName, out equivalentType!);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GeneratedFileSet"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal sealed record GeneratedFile(
|
||||
string Name,
|
||||
string Content
|
||||
);
|
||||
|
||||
internal sealed class GeneratedFileSet
|
||||
{
|
||||
|
||||
public static GeneratedFileSet Empty { get; } = new GeneratedFileSet(
|
||||
Array.Empty<GeneratedFile>());
|
||||
|
||||
private readonly ReadOnlyDictionary<string, GeneratedFile> _filesByName;
|
||||
|
||||
public GeneratedFileSet(
|
||||
IEnumerable<GeneratedFile> files
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(files);
|
||||
|
||||
ReadOnlyCollection<GeneratedFile> values = Array.AsReadOnly(files.ToArray());
|
||||
Dictionary<string, GeneratedFile> index = new Dictionary<string, GeneratedFile>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
foreach (GeneratedFile file in values)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(file.Name);
|
||||
if (!index.TryAdd(file.Name, file))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Generated file name '{file.Name}' is duplicated.");
|
||||
}
|
||||
}
|
||||
|
||||
Files = values;
|
||||
_filesByName = new ReadOnlyDictionary<string, GeneratedFile>(index);
|
||||
}
|
||||
|
||||
public IReadOnlyList<GeneratedFile> Files { get; }
|
||||
|
||||
public bool TryGetFile(
|
||||
string name,
|
||||
out GeneratedFile file
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
||||
return _filesByName.TryGetValue(name, out file!);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlEnumProjector"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Naming;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Projects registry GLenum and GLbitfield parameter groups into C# enum types.
|
||||
/// </summary>
|
||||
internal static class GlEnumProjector
|
||||
{
|
||||
private static readonly string[] _coreExtensionSuffixes =
|
||||
{
|
||||
"ARB",
|
||||
"EXT",
|
||||
"KHR",
|
||||
"NV",
|
||||
};
|
||||
|
||||
private static readonly ReadOnlyDictionary<string, string> _managedTypeNameOverrides =
|
||||
new ReadOnlyDictionary<string, string>(
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["Buffer"] = "ClearBuffer",
|
||||
});
|
||||
|
||||
public static IReadOnlyList<ManagedEnumDefinition> Project(
|
||||
GlRegistryProjection projection
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
|
||||
Dictionary<string, bool> referencedGroups = ReadReferencedGroups(projection.Commands);
|
||||
Dictionary<string, GlEnumDefinition> selectedEnums = projection.Enums
|
||||
.Where(value => value.Value is not null)
|
||||
.GroupBy(value => value.Name, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
value => value.Key,
|
||||
value => value.First(),
|
||||
StringComparer.Ordinal);
|
||||
Dictionary<string, List<GlEnumDefinition>> membersByGroup =
|
||||
new Dictionary<string, List<GlEnumDefinition>>(StringComparer.Ordinal);
|
||||
foreach (GlEnumDefinition value in selectedEnums.Values)
|
||||
{
|
||||
foreach (string group in SplitGroups(value.Group))
|
||||
{
|
||||
if (!referencedGroups.ContainsKey(group))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!membersByGroup.TryGetValue(group, out List<GlEnumDefinition>? members))
|
||||
{
|
||||
members = new List<GlEnumDefinition>();
|
||||
membersByGroup.Add(group, members);
|
||||
}
|
||||
|
||||
members.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, EnumBuilder> enumsByManagedName =
|
||||
new Dictionary<string, EnumBuilder>(StringComparer.Ordinal);
|
||||
foreach ((string nativeGroup, bool isFlags) in referencedGroups.OrderBy(
|
||||
value => value.Key,
|
||||
StringComparer.Ordinal))
|
||||
{
|
||||
if (!membersByGroup.TryGetValue(nativeGroup, out List<GlEnumDefinition>? members) ||
|
||||
members.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string managedName = ReadManagedTypeName(nativeGroup);
|
||||
if (!enumsByManagedName.TryGetValue(managedName, out EnumBuilder? builder))
|
||||
{
|
||||
builder = new EnumBuilder(managedName);
|
||||
enumsByManagedName.Add(managedName, builder);
|
||||
}
|
||||
|
||||
builder.AddGroup(nativeGroup, isFlags, members);
|
||||
}
|
||||
|
||||
return Array.AsReadOnly(
|
||||
enumsByManagedName.Values
|
||||
.OrderBy(value => value.Name, StringComparer.Ordinal)
|
||||
.Select(value => value.Build())
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
private static Dictionary<string, bool> ReadReferencedGroups(
|
||||
IReadOnlyList<GlCommandDefinition> commands
|
||||
)
|
||||
{
|
||||
Dictionary<string, bool> result = new Dictionary<string, bool>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlCommandDefinition command in commands)
|
||||
{
|
||||
AddReferencedGroups(
|
||||
result,
|
||||
command.Return.Group,
|
||||
command.Return.TypeName);
|
||||
foreach (GlParameterDefinition parameter in command.Parameters)
|
||||
{
|
||||
AddReferencedGroups(result, parameter.Group, parameter.TypeName);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void AddReferencedGroups(
|
||||
Dictionary<string, bool> result,
|
||||
string? groupNames,
|
||||
string? nativeType
|
||||
)
|
||||
{
|
||||
if (groupNames is null || nativeType is not ("GLenum" or "GLbitfield"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isFlags = string.Equals(nativeType, "GLbitfield", StringComparison.Ordinal);
|
||||
foreach (string group in SplitGroups(groupNames))
|
||||
{
|
||||
result[group] = result.TryGetValue(group, out bool previous)
|
||||
? previous || isFlags
|
||||
: isFlags;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SplitGroups(
|
||||
string? value
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (string item in value.Split(','))
|
||||
{
|
||||
string group = item.Trim();
|
||||
if (group.Length != 0)
|
||||
{
|
||||
yield return group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadManagedTypeName(
|
||||
string nativeGroup
|
||||
)
|
||||
{
|
||||
if (_managedTypeNameOverrides.TryGetValue(nativeGroup, out string? overrideName))
|
||||
{
|
||||
return overrideName;
|
||||
}
|
||||
|
||||
string value = nativeGroup;
|
||||
foreach (string suffix in _coreExtensionSuffixes)
|
||||
{
|
||||
if (value.Length > suffix.Length && value.EndsWith(
|
||||
suffix,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
value = value[..^suffix.Length];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(SanitizeIdentifier(value));
|
||||
}
|
||||
|
||||
private static string ReadManagedMemberName(
|
||||
string nativeName
|
||||
)
|
||||
{
|
||||
string value = nativeName.StartsWith("GL_", StringComparison.Ordinal)
|
||||
? nativeName[3..]
|
||||
: nativeName;
|
||||
StringBuilder builder = new StringBuilder(value.Length);
|
||||
foreach (string component in value.Split('_', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (component.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsLetter(component[0]))
|
||||
{
|
||||
builder.Append(char.ToUpperInvariant(component[0]));
|
||||
if (component.Length > 1)
|
||||
{
|
||||
foreach (char character in component.AsSpan(1))
|
||||
{
|
||||
builder.Append(ToAsciiLowerInvariant(character));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(component);
|
||||
}
|
||||
}
|
||||
|
||||
string result = builder.Length == 0 ? "Value" : builder.ToString();
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(SanitizeIdentifier(result));
|
||||
}
|
||||
|
||||
private static char ToAsciiLowerInvariant(
|
||||
char value
|
||||
)
|
||||
{
|
||||
return value is >= 'A' and <= 'Z' ? (char)(value + ('a' - 'A')) : value;
|
||||
}
|
||||
|
||||
private static string SanitizeIdentifier(
|
||||
string value
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(value.Length + 1);
|
||||
if (value.Length == 0 || !char.IsLetter(value[0]) && value[0] != '_')
|
||||
{
|
||||
builder.Append('_');
|
||||
}
|
||||
|
||||
foreach (char character in value)
|
||||
{
|
||||
builder.Append(char.IsLetterOrDigit(character) || character == '_'
|
||||
? character
|
||||
: '_');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private sealed class EnumBuilder
|
||||
{
|
||||
private readonly Dictionary<string, ManagedEnumMemberDefinition> _members =
|
||||
new Dictionary<string, ManagedEnumMemberDefinition>(StringComparer.Ordinal);
|
||||
private readonly HashSet<string> _nativeGroups = new HashSet<string>(
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public EnumBuilder(
|
||||
string name
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public bool IsFlags { get; private set; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public void AddGroup(
|
||||
string nativeGroup,
|
||||
bool isFlags,
|
||||
IEnumerable<GlEnumDefinition> members
|
||||
)
|
||||
{
|
||||
_nativeGroups.Add(nativeGroup);
|
||||
IsFlags |= isFlags;
|
||||
foreach (GlEnumDefinition value in members)
|
||||
{
|
||||
string name = ReadManagedMemberName(value.Name);
|
||||
if (string.Equals(name, Name, StringComparison.Ordinal))
|
||||
{
|
||||
name = $"{name}Value";
|
||||
}
|
||||
|
||||
if (_members.TryGetValue(name, out ManagedEnumMemberDefinition? previous))
|
||||
{
|
||||
if (!string.Equals(
|
||||
previous.NativeValue.Value,
|
||||
value.Value,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Enum member '{Name}.{name}' maps to conflicting native values " +
|
||||
$"'{previous.NativeValue.Name}' and '{value.Name}'.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_members.Add(name, new ManagedEnumMemberDefinition(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
public ManagedEnumDefinition Build()
|
||||
{
|
||||
List<ManagedEnumMemberDefinition> members = _members.Values
|
||||
.OrderBy(value => value.Name, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
if (IsFlags && members.All(value => value.NativeValue.Value != "0" &&
|
||||
value.NativeValue.Value != "0x0" &&
|
||||
value.NativeValue.Value != "0x00000000"))
|
||||
{
|
||||
members.Insert(
|
||||
0,
|
||||
new ManagedEnumMemberDefinition(
|
||||
"None",
|
||||
new GlEnumDefinition(
|
||||
"GL_NONE",
|
||||
"0",
|
||||
null,
|
||||
null,
|
||||
string.Join(",", _nativeGroups),
|
||||
null,
|
||||
"Synthetic zero value for a generated flags enum.",
|
||||
members[0].NativeValue.Element)));
|
||||
}
|
||||
|
||||
return new ManagedEnumDefinition(
|
||||
Name,
|
||||
IsFlags,
|
||||
Array.AsReadOnly(
|
||||
_nativeGroups.OrderBy(value => value, StringComparer.Ordinal).ToArray()),
|
||||
Array.AsReadOnly(members.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlEnumTypeMap"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves unambiguous scalar GLenum and GLbitfield groups to generated C# types.
|
||||
/// </summary>
|
||||
internal sealed class GlEnumTypeMap
|
||||
{
|
||||
private readonly Dictionary<string, string> _typesByNativeGroup;
|
||||
|
||||
public GlEnumTypeMap(
|
||||
IEnumerable<ManagedEnumDefinition> definitions
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(definitions);
|
||||
|
||||
_typesByNativeGroup = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (ManagedEnumDefinition definition in definitions)
|
||||
{
|
||||
foreach (string nativeGroup in definition.NativeGroups)
|
||||
{
|
||||
if (_typesByNativeGroup.TryGetValue(nativeGroup, out string? previous) &&
|
||||
!string.Equals(previous, definition.Name, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Native enum group '{nativeGroup}' maps to both " +
|
||||
$"'{previous}' and '{definition.Name}'.");
|
||||
}
|
||||
|
||||
_typesByNativeGroup[nativeGroup] = definition.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadParameterType(
|
||||
GlParameterDefinition parameter,
|
||||
out string managedType
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameter);
|
||||
if (!IsEnumDeclaration(parameter.TypeName, parameter.Group))
|
||||
{
|
||||
managedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryReadGroupType(parameter.Group!, out managedType);
|
||||
}
|
||||
|
||||
public bool TryReadReturnType(
|
||||
GlReturnDefinition value,
|
||||
out string managedType
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
if (!IsEnumDeclaration(value.TypeName, value.Group))
|
||||
{
|
||||
managedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryReadGroupType(value.Group!, out managedType);
|
||||
}
|
||||
|
||||
private static bool IsEnumDeclaration(
|
||||
string? nativeType,
|
||||
string? group
|
||||
)
|
||||
{
|
||||
return group is not null &&
|
||||
nativeType is "GLenum" or "GLbitfield";
|
||||
}
|
||||
|
||||
private bool TryReadGroupType(
|
||||
string groups,
|
||||
out string managedType
|
||||
)
|
||||
{
|
||||
string[] nativeGroups = groups.Split(',')
|
||||
.Select(value => value.Trim())
|
||||
.Where(value => value.Length != 0)
|
||||
.ToArray();
|
||||
if (nativeGroups.Length == 0)
|
||||
{
|
||||
managedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
HashSet<string> candidates = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (string nativeGroup in nativeGroups)
|
||||
{
|
||||
if (!_typesByNativeGroup.TryGetValue(nativeGroup, out string? candidate))
|
||||
{
|
||||
managedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
candidates.Add(candidate);
|
||||
}
|
||||
|
||||
if (candidates.Count == 1)
|
||||
{
|
||||
managedType = candidates.First();
|
||||
return true;
|
||||
}
|
||||
|
||||
managedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlEquivalentTypeMapper"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves OpenGL typedef declarations recursively to equivalent C# ABI types.
|
||||
/// </summary>
|
||||
internal sealed class GlEquivalentTypeMapper
|
||||
{
|
||||
|
||||
private readonly Dictionary<string, string> _resolvedTypes = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal);
|
||||
private readonly GlRegistryModel _registry;
|
||||
|
||||
public GlEquivalentTypeMapper(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
public string ReadReturnType(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
return ReadDeclarationType(
|
||||
command.Return.TypeName,
|
||||
command.Return.DeclarationText,
|
||||
command.Name);
|
||||
}
|
||||
|
||||
public string ReadParameterType(
|
||||
GlParameterDefinition parameter
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameter);
|
||||
return ReadDeclarationType(
|
||||
parameter.TypeName,
|
||||
parameter.DeclarationText,
|
||||
parameter.Name);
|
||||
}
|
||||
|
||||
private string ReadDeclarationType(
|
||||
string? typeName,
|
||||
string declaration,
|
||||
string identifier
|
||||
)
|
||||
{
|
||||
if (typeName is null)
|
||||
{
|
||||
return ResolveTypeExpression(RemoveIdentifier(declaration, identifier));
|
||||
}
|
||||
|
||||
string equivalentType = ResolveNamedType(
|
||||
typeName,
|
||||
new HashSet<string>(StringComparer.Ordinal));
|
||||
int pointerDepth = declaration.Count(value => value == '*');
|
||||
if (declaration.Contains('[', StringComparison.Ordinal))
|
||||
{
|
||||
pointerDepth++;
|
||||
}
|
||||
|
||||
return equivalentType + new string('*', pointerDepth);
|
||||
}
|
||||
|
||||
private string ResolveNamedType(
|
||||
string typeName,
|
||||
HashSet<string> resolving
|
||||
)
|
||||
{
|
||||
if (CSharpAbiTypePolicy.TryResolve(typeName, out string abiType))
|
||||
{
|
||||
return abiType;
|
||||
}
|
||||
|
||||
if (_resolvedTypes.TryGetValue(typeName, out string? resolvedType))
|
||||
{
|
||||
return resolvedType;
|
||||
}
|
||||
|
||||
if (!resolving.Add(typeName))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type alias cycle contains '{typeName}'.");
|
||||
}
|
||||
|
||||
if (!_registry.TypesByName.TryGetValue(
|
||||
typeName,
|
||||
out IReadOnlyList<GlTypeDefinition>? definitions) ||
|
||||
definitions.Count != 1)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type '{typeName}' does not have one registry definition.");
|
||||
}
|
||||
|
||||
GlTypeDefinition definition = definitions[0];
|
||||
string declaration = NormalizeWhitespace(definition.DeclarationText);
|
||||
string value;
|
||||
if (TryReadCallbackType(typeName, declaration, resolving, out string callbackType))
|
||||
{
|
||||
value = callbackType;
|
||||
}
|
||||
else
|
||||
{
|
||||
MatchCollection aliases = Regex.Matches(
|
||||
declaration,
|
||||
$@"typedef\s+(?<target>.+?)\s*\b{Regex.Escape(typeName)}\b\s*;",
|
||||
RegexOptions.CultureInvariant);
|
||||
if (aliases.Count == 1)
|
||||
{
|
||||
value = ResolveTypeExpression(
|
||||
aliases[0].Groups["target"].Value,
|
||||
resolving);
|
||||
}
|
||||
else if (declaration == $"{typeName};" || declaration.StartsWith(
|
||||
"struct ",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
value = "void";
|
||||
}
|
||||
else if (aliases.Count > 1)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type '{typeName}' has platform-dependent typedefs and " +
|
||||
"requires an explicit target policy.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Cannot read typedef declaration '{definition.DeclarationText}'.");
|
||||
}
|
||||
}
|
||||
|
||||
resolving.Remove(typeName);
|
||||
_resolvedTypes.Add(typeName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private bool TryReadCallbackType(
|
||||
string typeName,
|
||||
string declaration,
|
||||
HashSet<string> resolving,
|
||||
out string type
|
||||
)
|
||||
{
|
||||
Match match = Regex.Match(
|
||||
declaration,
|
||||
$@"^typedef\s+(?<return>.+?)\s*\(\s*\*\s*{Regex.Escape(typeName)}\s*\)" +
|
||||
@"\s*\((?<parameters>.*)\)\s*;$",
|
||||
RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
type = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> types = new List<string>();
|
||||
string parameters = match.Groups["parameters"].Value.Trim();
|
||||
if (parameters.Length != 0 && parameters != "void")
|
||||
{
|
||||
foreach (string parameter in parameters.Split(','))
|
||||
{
|
||||
types.Add(ResolveTypeExpression(RemoveLastIdentifier(parameter), resolving));
|
||||
}
|
||||
}
|
||||
|
||||
types.Add(ResolveTypeExpression(match.Groups["return"].Value, resolving));
|
||||
type = $"delegate* unmanaged<{string.Join(", ", types)}>";
|
||||
return true;
|
||||
}
|
||||
|
||||
private string ResolveTypeExpression(
|
||||
string expression,
|
||||
HashSet<string>? resolving = null
|
||||
)
|
||||
{
|
||||
resolving ??= new HashSet<string>(StringComparer.Ordinal);
|
||||
string value = Regex.Replace(
|
||||
NormalizeWhitespace(expression),
|
||||
@"\b(?:const|volatile|restrict)\b",
|
||||
string.Empty,
|
||||
RegexOptions.CultureInvariant).Trim();
|
||||
int pointerDepth = value.Count(character => character == '*');
|
||||
value = value.Replace("*", string.Empty, StringComparison.Ordinal).Trim();
|
||||
if (value.StartsWith("struct ", StringComparison.Ordinal))
|
||||
{
|
||||
value = "void";
|
||||
}
|
||||
|
||||
string equivalentType = ResolveNamedType(value, resolving);
|
||||
return equivalentType + new string('*', pointerDepth);
|
||||
}
|
||||
|
||||
private static string RemoveIdentifier(
|
||||
string declaration,
|
||||
string identifier
|
||||
)
|
||||
{
|
||||
int index = declaration.LastIndexOf(identifier, StringComparison.Ordinal);
|
||||
return index < 0
|
||||
? declaration
|
||||
: declaration.Remove(index, identifier.Length);
|
||||
}
|
||||
|
||||
private static string RemoveLastIdentifier(
|
||||
string declaration
|
||||
)
|
||||
{
|
||||
Match match = Regex.Match(
|
||||
declaration.Trim(),
|
||||
@"^(?<type>.+?)(?<name>[A-Za-z_][A-Za-z0-9_]*)$",
|
||||
RegexOptions.CultureInvariant);
|
||||
return match.Success ? match.Groups["type"].Value.Trim() : declaration.Trim();
|
||||
}
|
||||
|
||||
private static string NormalizeWhitespace(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return Regex.Replace(
|
||||
value,
|
||||
@"\s+",
|
||||
" ",
|
||||
RegexOptions.CultureInvariant).Trim();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlGenerationTarget"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
[Flags]
|
||||
internal enum GlGenerationTarget
|
||||
{
|
||||
None = 0,
|
||||
Api = 1,
|
||||
Enum = 2,
|
||||
All = Api | Enum,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlGetValueApiFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces desktop state query pointer families with typed Span overloads.
|
||||
/// </summary>
|
||||
internal sealed class GlGetValueApiFilter : IGlApiFilter
|
||||
{
|
||||
|
||||
private static readonly Regex _pattern = new Regex(
|
||||
@"^glGet(?<type>Boolean|Double|Float|Integer64|Integer)(?<indexed>i_)?v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
Match match = _pattern.Match(command.Name);
|
||||
if (!match.Success)
|
||||
{
|
||||
return methods;
|
||||
}
|
||||
|
||||
bool isIndexed = match.Groups["indexed"].Success;
|
||||
int prefixCount = isIndexed ? 2 : 1;
|
||||
if (typeMapper.ReadReturnType(command) != "void" ||
|
||||
command.Parameters.Count != prefixCount + 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"State query '{command.Name}' does not match its recognized shape.");
|
||||
}
|
||||
|
||||
GlParameterDefinition output = command.Parameters[^1];
|
||||
string elementType = ReadElementType(match.Groups["type"].Value);
|
||||
string expectedLength = $"COMPSIZE({command.Parameters[0].Name})";
|
||||
if (output.DeclarationText.Contains("const", StringComparison.Ordinal) ||
|
||||
typeMapper.ReadParameterType(output) != $"{elementType}*" ||
|
||||
!string.Equals(output.Length, expectedLength, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"State query '{command.Name}' has inconsistent output metadata.");
|
||||
}
|
||||
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>();
|
||||
List<ManagedArgument> arguments = new List<ManagedArgument>();
|
||||
for (int index = 0; index < prefixCount; index++)
|
||||
{
|
||||
GlParameterDefinition source = command.Parameters[index];
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
typeMapper.ReadParameterType(source),
|
||||
source.Name,
|
||||
source));
|
||||
arguments.Add(new ParameterArgument(index));
|
||||
}
|
||||
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
$"Span<{elementType}>",
|
||||
"values",
|
||||
output));
|
||||
return Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
new ManagedMethodDefinition(
|
||||
"GetValue",
|
||||
"void",
|
||||
Array.AsReadOnly(parameters.ToArray()),
|
||||
command,
|
||||
new QuerySpanInvocation(
|
||||
Array.AsReadOnly(arguments.ToArray()),
|
||||
parameters.Count - 1,
|
||||
elementType)),
|
||||
});
|
||||
}
|
||||
|
||||
private static string ReadElementType(
|
||||
string name
|
||||
)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"Boolean" => "byte",
|
||||
"Double" => "double",
|
||||
"Float" => "float",
|
||||
"Integer" => "int",
|
||||
"Integer64" => "long",
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Unsupported state query component type '{name}'."),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlHandleApiFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces registry object-class GLuint values with the managed GlHandle type.
|
||||
/// </summary>
|
||||
internal sealed class GlHandleApiFilter : IGlApiFilter
|
||||
{
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
bool wrapsReturn = string.Equals(
|
||||
command.Return.TypeName,
|
||||
"GLuint",
|
||||
StringComparison.Ordinal) &&
|
||||
command.Return.ObjectClass is not null;
|
||||
return Array.AsReadOnly(
|
||||
methods.Select(
|
||||
method => ConvertMethod(method, wrapsReturn)).ToArray());
|
||||
}
|
||||
|
||||
private static ManagedMethodDefinition ConvertMethod(
|
||||
ManagedMethodDefinition method,
|
||||
bool wrapsReturn
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition[] converted = method.Parameters.Select(
|
||||
ConvertParameter).ToArray();
|
||||
if (method.Invocation is not DirectInvocation direct ||
|
||||
!converted.Any(value => value.IsHandleSpan))
|
||||
{
|
||||
return method with
|
||||
{
|
||||
ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType,
|
||||
Parameters = Array.AsReadOnly(converted),
|
||||
WrapHandleReturn = wrapsReturn,
|
||||
};
|
||||
}
|
||||
|
||||
Dictionary<string, int> spanByLengthName = new Dictionary<string, int>(
|
||||
StringComparer.Ordinal);
|
||||
for (int index = 0; index < converted.Length; index++)
|
||||
{
|
||||
ManagedParameterDefinition parameter = converted[index];
|
||||
string? length = parameter.NativeParameter?.Length;
|
||||
if (parameter.IsHandleSpan &&
|
||||
length is not null &&
|
||||
IsIdentifier(length))
|
||||
{
|
||||
spanByLengthName.TryAdd(length, index);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, int> oldToNew = new Dictionary<int, int>();
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>();
|
||||
for (int oldIndex = 0; oldIndex < converted.Length; oldIndex++)
|
||||
{
|
||||
ManagedParameterDefinition parameter = converted[oldIndex];
|
||||
if (spanByLengthName.ContainsKey(parameter.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
oldToNew.Add(oldIndex, parameters.Count);
|
||||
parameters.Add(parameter);
|
||||
}
|
||||
|
||||
List<ManagedArgument> arguments = new List<ManagedArgument>(direct.Arguments.Count);
|
||||
foreach (ManagedArgument argument in direct.Arguments)
|
||||
{
|
||||
if (argument is not ParameterArgument sourceArgument)
|
||||
{
|
||||
arguments.Add(argument);
|
||||
continue;
|
||||
}
|
||||
|
||||
ManagedParameterDefinition sourceParameter = converted[sourceArgument.ParameterIndex];
|
||||
if (spanByLengthName.TryGetValue(
|
||||
sourceParameter.Name,
|
||||
out int spanOldIndex))
|
||||
{
|
||||
arguments.Add(new SpanLengthArgument(oldToNew[spanOldIndex]));
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments.Add(
|
||||
new ParameterArgument(oldToNew[sourceArgument.ParameterIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
return method with
|
||||
{
|
||||
ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType,
|
||||
Parameters = Array.AsReadOnly(parameters.ToArray()),
|
||||
Invocation = new DirectInvocation(Array.AsReadOnly(arguments.ToArray())),
|
||||
WrapHandleReturn = wrapsReturn,
|
||||
};
|
||||
}
|
||||
|
||||
private static ManagedParameterDefinition ConvertParameter(
|
||||
ManagedParameterDefinition parameter
|
||||
)
|
||||
{
|
||||
GlParameterDefinition? source = parameter.NativeParameter;
|
||||
if (source is null ||
|
||||
source.ObjectClass is null ||
|
||||
!string.Equals(source.TypeName, "GLuint", StringComparison.Ordinal))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
|
||||
int pointerDepth = source.DeclarationText.Count(value => value == '*');
|
||||
bool isSpan = pointerDepth == 1 && source.Length is not null;
|
||||
string type;
|
||||
if (isSpan)
|
||||
{
|
||||
type = source.DeclarationText.Contains("const", StringComparison.Ordinal)
|
||||
? "ReadOnlySpan<GlHandle>"
|
||||
: "Span<GlHandle>";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = pointerDepth == 0
|
||||
? "GlHandle"
|
||||
: $"GlHandle{new string('*', pointerDepth)}";
|
||||
}
|
||||
|
||||
return parameter with
|
||||
{
|
||||
Type = type,
|
||||
IsHandle = true,
|
||||
IsHandleSpan = isSpan,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsIdentifier(
|
||||
string value
|
||||
)
|
||||
{
|
||||
if (value.Length == 0 ||
|
||||
!char.IsLetter(value[0]) && value[0] != '_')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.Skip(1).All(character => char.IsLetterOrDigit(character) || character == '_');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlManagedApiSuppressionFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Omits raw managed forwards for commands that have safer handwritten public APIs.
|
||||
/// </summary>
|
||||
internal sealed class GlManagedApiSuppressionFilter : IGlApiFilter
|
||||
{
|
||||
|
||||
private static readonly HashSet<string> _suppressedCommands = new HashSet<string>(
|
||||
new[]
|
||||
{
|
||||
"glCreateShader",
|
||||
"glShaderSource",
|
||||
},
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
return _suppressedCommands.Contains(command.Name)
|
||||
? Array.Empty<ManagedMethodDefinition>()
|
||||
: methods;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlOverloadApiFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces verified C-style command families with managed overloads.
|
||||
/// </summary>
|
||||
internal sealed class GlOverloadApiFilter : IGlApiFilter
|
||||
{
|
||||
|
||||
private static readonly Regex _matrixUniformPattern = new Regex(
|
||||
@"^gl(?<family>ProgramUniform|Uniform)Matrix(?<columns>[2-4])" +
|
||||
@"(?:x(?<rows>[2-4]))?(?<type>f|d)v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _uniformPattern = new Regex(
|
||||
@"^gl(?<family>ProgramUniform|Uniform)(?<width>[1-4])" +
|
||||
@"(?<type>ui|f|i|d)(?<vector>v?)$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _vertexAttribPattern = new Regex(
|
||||
@"^glVertexAttrib(?<width>[1-4])(?<type>d|f|s)(?<vector>v?)$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _redundantVertexAttributeVectorPattern = new Regex(
|
||||
@"^glVertexAttrib(?:I[1-4](?:i|ui)|L[1-4]d|P[1-4]ui|4Nub)v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly string[] _components = { "X", "Y", "Z", "W" };
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
if (_redundantVertexAttributeVectorPattern.IsMatch(command.Name))
|
||||
{
|
||||
return Array.Empty<ManagedMethodDefinition>();
|
||||
}
|
||||
|
||||
Match matrixMatch = _matrixUniformPattern.Match(command.Name);
|
||||
if (matrixMatch.Success)
|
||||
{
|
||||
return RewriteMatrixUniform(command, matrixMatch, typeMapper);
|
||||
}
|
||||
|
||||
Match uniformMatch = _uniformPattern.Match(command.Name);
|
||||
if (uniformMatch.Success)
|
||||
{
|
||||
return RewriteUniform(command, uniformMatch, typeMapper);
|
||||
}
|
||||
|
||||
Match vertexAttribMatch = _vertexAttribPattern.Match(command.Name);
|
||||
if (vertexAttribMatch.Success)
|
||||
{
|
||||
return vertexAttribMatch.Groups["vector"].Value.Length == 0
|
||||
? Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
methods[0] with { Name = "VertexAttrib" },
|
||||
})
|
||||
: Array.Empty<ManagedMethodDefinition>();
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<ManagedMethodDefinition> RewriteUniform(
|
||||
GlCommandDefinition command,
|
||||
Match match,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
int width = int.Parse(
|
||||
match.Groups["width"].Value,
|
||||
CultureInfo.InvariantCulture);
|
||||
string componentType = ReadComponentType(match.Groups["type"].Value);
|
||||
bool isVector = match.Groups["vector"].Value.Length != 0;
|
||||
int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1;
|
||||
string methodName = match.Groups["family"].Value;
|
||||
|
||||
ValidateUniformSignature(
|
||||
command,
|
||||
width,
|
||||
componentType,
|
||||
prefixCount,
|
||||
isVector,
|
||||
typeMapper);
|
||||
|
||||
List<ManagedParameterDefinition> prefix = ReadNativeParameters(
|
||||
command,
|
||||
prefixCount,
|
||||
typeMapper);
|
||||
List<ManagedArgument> prefixArguments = ReadParameterArguments(prefix.Count);
|
||||
if (isVector)
|
||||
{
|
||||
if (width is 2 or 3)
|
||||
{
|
||||
return Array.AsReadOnly(Array.Empty<ManagedMethodDefinition>());
|
||||
}
|
||||
|
||||
GlParameterDefinition valueParameter = command.Parameters[^1];
|
||||
string spanElementType = width == 1
|
||||
? componentType
|
||||
: $"Vec{width}<{componentType}>";
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(prefix)
|
||||
{
|
||||
new ManagedParameterDefinition(
|
||||
$"ReadOnlySpan<{spanElementType}>",
|
||||
"values",
|
||||
valueParameter),
|
||||
};
|
||||
ManagedInvocation invocation = width == 1
|
||||
? new PinnedSpanInvocation(
|
||||
prefixArguments,
|
||||
parameters.Count - 1,
|
||||
componentType)
|
||||
: new PinnedVectorSpanInvocation(
|
||||
prefixArguments,
|
||||
parameters.Count - 1,
|
||||
componentType,
|
||||
spanElementType);
|
||||
return Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
new ManagedMethodDefinition(
|
||||
methodName,
|
||||
"void",
|
||||
Array.AsReadOnly(parameters.ToArray()),
|
||||
command,
|
||||
invocation),
|
||||
});
|
||||
}
|
||||
|
||||
List<ManagedMethodDefinition> methods = new List<ManagedMethodDefinition>();
|
||||
List<ManagedParameterDefinition> scalarParameters = ReadNativeParameters(
|
||||
command,
|
||||
command.Parameters.Count,
|
||||
typeMapper);
|
||||
methods.Add(
|
||||
new ManagedMethodDefinition(
|
||||
methodName,
|
||||
"void",
|
||||
Array.AsReadOnly(scalarParameters.ToArray()),
|
||||
command,
|
||||
new DirectInvocation(ReadParameterArguments(scalarParameters.Count))));
|
||||
|
||||
if (width > 1)
|
||||
{
|
||||
List<ManagedParameterDefinition> vectorParameters = new List<ManagedParameterDefinition>(prefix)
|
||||
{
|
||||
new ManagedParameterDefinition(
|
||||
$"Vec{width}<{componentType}>",
|
||||
"value"),
|
||||
};
|
||||
List<ManagedArgument> vectorArguments = new List<ManagedArgument>(prefixArguments);
|
||||
for (int index = 0; index < width; index++)
|
||||
{
|
||||
vectorArguments.Add(
|
||||
new VectorComponentArgument(
|
||||
vectorParameters.Count - 1,
|
||||
_components[index]));
|
||||
}
|
||||
|
||||
methods.Add(
|
||||
new ManagedMethodDefinition(
|
||||
methodName,
|
||||
"void",
|
||||
Array.AsReadOnly(vectorParameters.ToArray()),
|
||||
command,
|
||||
new DirectInvocation(Array.AsReadOnly(vectorArguments.ToArray()))));
|
||||
}
|
||||
|
||||
return Array.AsReadOnly(methods.ToArray());
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ManagedMethodDefinition> RewriteMatrixUniform(
|
||||
GlCommandDefinition command,
|
||||
Match match,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
int columns = int.Parse(
|
||||
match.Groups["columns"].Value,
|
||||
CultureInfo.InvariantCulture);
|
||||
int rows = match.Groups["rows"].Success
|
||||
? int.Parse(
|
||||
match.Groups["rows"].Value,
|
||||
CultureInfo.InvariantCulture)
|
||||
: columns;
|
||||
string componentType = ReadComponentType(match.Groups["type"].Value);
|
||||
int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1;
|
||||
string methodName = match.Groups["family"].Value;
|
||||
|
||||
ValidateMatrixSignature(
|
||||
command,
|
||||
columns,
|
||||
rows,
|
||||
componentType,
|
||||
prefixCount,
|
||||
typeMapper);
|
||||
if (columns != 4 || rows != 4 || componentType != "float")
|
||||
{
|
||||
return Array.Empty<ManagedMethodDefinition>();
|
||||
}
|
||||
|
||||
List<ManagedParameterDefinition> prefix = ReadNativeParameters(
|
||||
command,
|
||||
prefixCount,
|
||||
typeMapper);
|
||||
List<ManagedArgument> prefixArguments = ReadParameterArguments(prefix.Count);
|
||||
GlParameterDefinition transposeSource = command.Parameters[prefixCount + 1];
|
||||
GlParameterDefinition valueSource = command.Parameters[prefixCount + 2];
|
||||
|
||||
List<ManagedParameterDefinition> singleParameters = new List<ManagedParameterDefinition>(prefix)
|
||||
{
|
||||
new ManagedParameterDefinition("bool", "transpose", transposeSource),
|
||||
new ManagedParameterDefinition("GraphicsMatrix", "value", valueSource),
|
||||
};
|
||||
List<ManagedParameterDefinition> spanParameters = new List<ManagedParameterDefinition>(prefix)
|
||||
{
|
||||
new ManagedParameterDefinition("bool", "transpose", transposeSource),
|
||||
new ManagedParameterDefinition(
|
||||
"ReadOnlySpan<GraphicsMatrix>",
|
||||
"values",
|
||||
valueSource),
|
||||
};
|
||||
|
||||
return Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
new ManagedMethodDefinition(
|
||||
methodName,
|
||||
"void",
|
||||
Array.AsReadOnly(singleParameters.ToArray()),
|
||||
command,
|
||||
new Matrix4Invocation(
|
||||
prefixArguments,
|
||||
singleParameters.Count - 2,
|
||||
singleParameters.Count - 1,
|
||||
false)),
|
||||
new ManagedMethodDefinition(
|
||||
methodName,
|
||||
"void",
|
||||
Array.AsReadOnly(spanParameters.ToArray()),
|
||||
command,
|
||||
new Matrix4Invocation(
|
||||
prefixArguments,
|
||||
spanParameters.Count - 2,
|
||||
spanParameters.Count - 1,
|
||||
true)),
|
||||
});
|
||||
}
|
||||
|
||||
private static void ValidateUniformSignature(
|
||||
GlCommandDefinition command,
|
||||
int width,
|
||||
string componentType,
|
||||
int prefixCount,
|
||||
bool isVector,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
int expectedCount = prefixCount + (isVector ? 2 : width);
|
||||
if (command.Parameters.Count != expectedCount)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Uniform command '{command.Name}' does not match its recognized shape.");
|
||||
}
|
||||
|
||||
if (isVector)
|
||||
{
|
||||
GlParameterDefinition count = command.Parameters[prefixCount];
|
||||
GlParameterDefinition value = command.Parameters[prefixCount + 1];
|
||||
string expectedLength = $"{count.Name}*{width}";
|
||||
string expectedKind = $"Vector{width}";
|
||||
if (typeMapper.ReadParameterType(value) != $"{componentType}*" ||
|
||||
!string.Equals(value.Length, expectedLength, StringComparison.Ordinal) &&
|
||||
!(width == 1 && string.Equals(
|
||||
value.Length,
|
||||
count.Name,
|
||||
StringComparison.Ordinal)) ||
|
||||
!string.Equals(value.Kind, expectedKind, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Uniform command '{command.Name}' has inconsistent vector metadata.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = prefixCount; index < command.Parameters.Count; index++)
|
||||
{
|
||||
if (typeMapper.ReadParameterType(command.Parameters[index]) != componentType)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Uniform command '{command.Name}' has inconsistent component types.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateMatrixSignature(
|
||||
GlCommandDefinition command,
|
||||
int columns,
|
||||
int rows,
|
||||
string componentType,
|
||||
int prefixCount,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
if (command.Parameters.Count != prefixCount + 3)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Matrix command '{command.Name}' does not match its recognized shape.");
|
||||
}
|
||||
|
||||
GlParameterDefinition count = command.Parameters[prefixCount];
|
||||
GlParameterDefinition value = command.Parameters[prefixCount + 2];
|
||||
if (typeMapper.ReadParameterType(value) != $"{componentType}*" ||
|
||||
!string.Equals(
|
||||
value.Kind,
|
||||
$"Matrix{columns}x{rows}",
|
||||
StringComparison.Ordinal) ||
|
||||
!string.Equals(
|
||||
value.Length,
|
||||
$"{count.Name}*{columns * rows}",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Matrix command '{command.Name}' has inconsistent matrix metadata.");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ManagedParameterDefinition> ReadNativeParameters(
|
||||
GlCommandDefinition command,
|
||||
int count,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(count);
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
GlParameterDefinition source = command.Parameters[index];
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
typeMapper.ReadParameterType(source),
|
||||
source.Name,
|
||||
source));
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static List<ManagedArgument> ReadParameterArguments(
|
||||
int count
|
||||
)
|
||||
{
|
||||
List<ManagedArgument> arguments = new List<ManagedArgument>(count);
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
arguments.Add(new ParameterArgument(index));
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private static string ReadComponentType(
|
||||
string suffix
|
||||
)
|
||||
{
|
||||
return suffix switch
|
||||
{
|
||||
"f" => "float",
|
||||
"i" => "int",
|
||||
"ui" => "uint",
|
||||
"d" => "double",
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Unsupported uniform component suffix '{suffix}'."),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="IGlApiFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal interface IGlApiFilter
|
||||
{
|
||||
|
||||
IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ManagedApiModel"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal sealed record ManagedMethodDefinition(
|
||||
string Name,
|
||||
string ReturnType,
|
||||
IReadOnlyList<ManagedParameterDefinition> Parameters,
|
||||
GlCommandDefinition NativeCommand,
|
||||
ManagedInvocation Invocation,
|
||||
bool WrapHandleReturn = false,
|
||||
bool IsEnumReturn = false,
|
||||
string? DocumentationPage = null
|
||||
);
|
||||
|
||||
internal sealed record ManagedParameterDefinition(
|
||||
string Type,
|
||||
string Name,
|
||||
GlParameterDefinition? NativeParameter = null,
|
||||
bool IsHandle = false,
|
||||
bool IsHandleSpan = false,
|
||||
bool IsEnum = false
|
||||
);
|
||||
|
||||
internal abstract record ManagedInvocation;
|
||||
|
||||
internal sealed record DirectInvocation(
|
||||
IReadOnlyList<ManagedArgument> Arguments
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal sealed record PinnedSpanInvocation(
|
||||
IReadOnlyList<ManagedArgument> PrefixArguments,
|
||||
int SpanParameterIndex,
|
||||
string ElementType
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal sealed record PinnedVectorSpanInvocation(
|
||||
IReadOnlyList<ManagedArgument> PrefixArguments,
|
||||
int SpanParameterIndex,
|
||||
string ComponentType,
|
||||
string VectorType
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal sealed record Matrix4Invocation(
|
||||
IReadOnlyList<ManagedArgument> PrefixArguments,
|
||||
int TransposeParameterIndex,
|
||||
int MatrixParameterIndex,
|
||||
bool IsSpan
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal sealed record QuerySpanInvocation(
|
||||
IReadOnlyList<ManagedArgument> PrefixArguments,
|
||||
int SpanParameterIndex,
|
||||
string ElementType
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal abstract record ManagedArgument;
|
||||
|
||||
internal sealed record ParameterArgument(
|
||||
int ParameterIndex
|
||||
) : ManagedArgument;
|
||||
|
||||
internal sealed record VectorComponentArgument(
|
||||
int ParameterIndex,
|
||||
string Component
|
||||
) : ManagedArgument;
|
||||
|
||||
internal sealed record SpanLengthArgument(
|
||||
int ParameterIndex
|
||||
) : ManagedArgument;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ManagedApiPlanner"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Naming;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal sealed class ManagedApiPlanner
|
||||
{
|
||||
|
||||
private readonly IReadOnlyList<IGlApiFilter> _filters = Array.AsReadOnly<IGlApiFilter>(
|
||||
new IGlApiFilter[]
|
||||
{
|
||||
new GlManagedApiSuppressionFilter(),
|
||||
new GlOverloadApiFilter(),
|
||||
new GlGetValueApiFilter(),
|
||||
new GlHandleApiFilter(),
|
||||
});
|
||||
private readonly GlEquivalentTypeMapper _typeMapper;
|
||||
private readonly GlEnumTypeMap _enumTypeMap;
|
||||
private readonly GlManagedApiNamePolicy _namePolicy;
|
||||
|
||||
public ManagedApiPlanner(
|
||||
GlEquivalentTypeMapper typeMapper,
|
||||
GlFunctionFamilyAnalysis familyAnalysis,
|
||||
GlEnumTypeMap enumTypeMap
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
ArgumentNullException.ThrowIfNull(familyAnalysis);
|
||||
ArgumentNullException.ThrowIfNull(enumTypeMap);
|
||||
_typeMapper = typeMapper;
|
||||
_enumTypeMap = enumTypeMap;
|
||||
_namePolicy = new GlManagedApiNamePolicy(familyAnalysis);
|
||||
}
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Plan(
|
||||
IReadOnlyList<GlCommandDefinition> commands
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(commands);
|
||||
|
||||
List<ManagedMethodDefinition> result = new List<ManagedMethodDefinition>();
|
||||
foreach (GlCommandDefinition command in commands)
|
||||
{
|
||||
IReadOnlyList<ManagedMethodDefinition> methods = Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
CreateRawMethod(command),
|
||||
});
|
||||
foreach (IGlApiFilter filter in _filters)
|
||||
{
|
||||
methods = filter.Apply(command, methods, _typeMapper);
|
||||
}
|
||||
|
||||
methods = ApplyEnumTypes(command, methods);
|
||||
|
||||
result.AddRange(
|
||||
methods.Select(
|
||||
method => method with
|
||||
{
|
||||
Name = _namePolicy.ReadMethodName(command, method.Name),
|
||||
DocumentationPage = _namePolicy.ReadDocumentationPage(command),
|
||||
Parameters = Array.AsReadOnly(
|
||||
method.Parameters.Select(
|
||||
parameter => parameter with
|
||||
{
|
||||
Name = CSharpIdentifierPolicy.NormalizeParameterName(
|
||||
parameter.Name),
|
||||
}).ToArray()),
|
||||
}));
|
||||
}
|
||||
|
||||
ResolveNameCollisions(result);
|
||||
return Array.AsReadOnly(result.ToArray());
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ManagedMethodDefinition> ApplyEnumTypes(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods
|
||||
)
|
||||
{
|
||||
bool hasEnumReturn = _enumTypeMap.TryReadReturnType(
|
||||
command.Return,
|
||||
out string enumReturnType);
|
||||
return Array.AsReadOnly(
|
||||
methods.Select(
|
||||
method => ConvertEnumReturn(method, hasEnumReturn, enumReturnType)).ToArray());
|
||||
}
|
||||
|
||||
private ManagedMethodDefinition ConvertEnumReturn(
|
||||
ManagedMethodDefinition method,
|
||||
bool hasEnumReturn,
|
||||
string enumReturnType
|
||||
)
|
||||
{
|
||||
string returnType = string.Empty;
|
||||
bool isEnumReturn = hasEnumReturn && TryReplaceNativeEnumType(
|
||||
method.ReturnType,
|
||||
enumReturnType,
|
||||
out returnType);
|
||||
return method with
|
||||
{
|
||||
ReturnType = isEnumReturn ? returnType : method.ReturnType,
|
||||
IsEnumReturn = isEnumReturn,
|
||||
Parameters = Array.AsReadOnly(
|
||||
method.Parameters.Select(ConvertEnumParameter).ToArray()),
|
||||
};
|
||||
}
|
||||
|
||||
private ManagedParameterDefinition ConvertEnumParameter(
|
||||
ManagedParameterDefinition parameter
|
||||
)
|
||||
{
|
||||
if (parameter.NativeParameter is null ||
|
||||
!_enumTypeMap.TryReadParameterType(
|
||||
parameter.NativeParameter,
|
||||
out string enumType) ||
|
||||
!TryReplaceNativeEnumType(parameter.Type, enumType, out string convertedType))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
|
||||
return parameter with
|
||||
{
|
||||
Type = convertedType,
|
||||
IsEnum = true,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryReplaceNativeEnumType(
|
||||
string nativeType,
|
||||
string enumType,
|
||||
out string convertedType
|
||||
)
|
||||
{
|
||||
const string nativeBaseType = "uint";
|
||||
if (!nativeType.StartsWith(nativeBaseType, StringComparison.Ordinal) ||
|
||||
nativeType.AsSpan(nativeBaseType.Length).ContainsAnyExcept('*'))
|
||||
{
|
||||
convertedType = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedType = enumType + nativeType[nativeBaseType.Length..];
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Validate(
|
||||
IEnumerable<ManagedMethodDefinition> methods
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
|
||||
Dictionary<string, ManagedMethodDefinition> signatures = new Dictionary<string, ManagedMethodDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (ManagedMethodDefinition method in methods)
|
||||
{
|
||||
string signature = $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})";
|
||||
if (signatures.TryGetValue(signature, out ManagedMethodDefinition? previous))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Managed overload '{signature}' is produced by both " +
|
||||
$"'{previous.NativeCommand.Name}' and '{method.NativeCommand.Name}'.");
|
||||
}
|
||||
|
||||
signatures.Add(signature, method);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResolveNameCollisions(
|
||||
List<ManagedMethodDefinition> methods
|
||||
)
|
||||
{
|
||||
ILookup<string, int> indicesBySignature = methods
|
||||
.Select(
|
||||
(method, index) => new
|
||||
{
|
||||
Index = index,
|
||||
Signature = ReadManagedSignature(method),
|
||||
})
|
||||
.ToLookup(value => value.Signature, value => value.Index, StringComparer.Ordinal);
|
||||
foreach (IGrouping<string, int> group in indicesBySignature)
|
||||
{
|
||||
int[] indices = group.ToArray();
|
||||
if (indices.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] nativeNames = indices
|
||||
.Select(value => methods[value].NativeCommand.Name)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (nativeNames.Length != indices.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Managed overload '{group.Key}' is duplicated by one native command.");
|
||||
}
|
||||
|
||||
foreach (int index in indices)
|
||||
{
|
||||
ManagedMethodDefinition method = methods[index];
|
||||
methods[index] = method with
|
||||
{
|
||||
Name = GlManagedApiNamePolicy.ReadNativeMethodName(
|
||||
method.NativeCommand),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadManagedSignature(
|
||||
ManagedMethodDefinition method
|
||||
)
|
||||
{
|
||||
return $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})";
|
||||
}
|
||||
|
||||
private ManagedMethodDefinition CreateRawMethod(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(
|
||||
command.Parameters.Count);
|
||||
List<ManagedArgument> arguments = new List<ManagedArgument>(
|
||||
command.Parameters.Count);
|
||||
for (int index = 0; index < command.Parameters.Count; index++)
|
||||
{
|
||||
GlParameterDefinition source = command.Parameters[index];
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
_typeMapper.ReadParameterType(source),
|
||||
source.Name,
|
||||
source));
|
||||
arguments.Add(new ParameterArgument(index));
|
||||
}
|
||||
|
||||
return new ManagedMethodDefinition(
|
||||
ReadMethodName(command.Name),
|
||||
_typeMapper.ReadReturnType(command),
|
||||
Array.AsReadOnly(parameters.ToArray()),
|
||||
command,
|
||||
new DirectInvocation(Array.AsReadOnly(arguments.ToArray())));
|
||||
}
|
||||
|
||||
private static string ReadMethodName(
|
||||
string commandName
|
||||
)
|
||||
{
|
||||
return commandName.StartsWith("gl", StringComparison.Ordinal) &&
|
||||
commandName.Length > 2
|
||||
? commandName[2..]
|
||||
: commandName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ManagedEnumModel"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal sealed record ManagedEnumDefinition(
|
||||
string Name,
|
||||
bool IsFlags,
|
||||
IReadOnlyList<string> NativeGroups,
|
||||
IReadOnlyList<ManagedEnumMemberDefinition> Members
|
||||
);
|
||||
|
||||
internal sealed record ManagedEnumMemberDefinition(
|
||||
string Name,
|
||||
GlEnumDefinition NativeValue
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,916 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="OpenGlGenerator"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces the first in-memory desktop OpenGL binding files.
|
||||
/// </summary>
|
||||
internal static class OpenGlGenerator
|
||||
{
|
||||
|
||||
private static readonly HashSet<string> _csharpKeywords = new HashSet<string>(
|
||||
new[]
|
||||
{
|
||||
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
|
||||
"char", "checked", "class", "const", "continue", "decimal", "default",
|
||||
"delegate", "do", "double", "else", "enum", "event", "explicit", "extern",
|
||||
"false", "finally", "fixed", "float", "for", "foreach", "goto", "if",
|
||||
"implicit", "in", "int", "interface", "internal", "is", "lock", "long",
|
||||
"namespace", "new", "null", "object", "operator", "out", "override",
|
||||
"params", "private", "protected", "public", "readonly", "ref", "return",
|
||||
"sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string",
|
||||
"struct", "switch", "this", "throw", "true", "try", "typeof", "uint",
|
||||
"ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
|
||||
"volatile", "while",
|
||||
},
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public static GeneratedFileSet Generate(
|
||||
GlRegistryProjection projection,
|
||||
GlGenerationTarget target = GlGenerationTarget.All
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
if (target == GlGenerationTarget.None || (target & ~GlGenerationTarget.All) != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(target));
|
||||
}
|
||||
|
||||
IReadOnlyList<ManagedEnumDefinition> enums = GlEnumProjector.Project(projection);
|
||||
GlEnumTypeMap enumTypeMap = new GlEnumTypeMap(enums);
|
||||
List<GeneratedFile> files = new List<GeneratedFile>();
|
||||
if ((target & GlGenerationTarget.Enum) != 0)
|
||||
{
|
||||
files.Add(new GeneratedFile("glconst.cs", GenerateConstants(projection.Enums)));
|
||||
files.Add(new GeneratedFile("GlEnums.g.cs", GenerateEnums(enums)));
|
||||
}
|
||||
|
||||
if ((target & GlGenerationTarget.Api) != 0)
|
||||
{
|
||||
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(projection);
|
||||
IReadOnlyList<GlCommandDefinition> commands = Array.AsReadOnly(
|
||||
commandGroups.SelectMany(value => value.Commands).ToArray());
|
||||
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(projection.Source);
|
||||
GlFunctionFamilyAnalysis familyAnalysis = GlFunctionFamilyAnalyzer.Analyze(projection);
|
||||
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(
|
||||
typeMapper,
|
||||
familyAnalysis,
|
||||
enumTypeMap);
|
||||
IReadOnlyList<ManagedMethodDefinition> managedMethods = apiPlanner.Plan(commands);
|
||||
Dictionary<string, IReadOnlyList<ManagedMethodDefinition>> methodsByCommand =
|
||||
managedMethods.GroupBy(value => value.NativeCommand.Name, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
value => value.Key,
|
||||
value => (IReadOnlyList<ManagedMethodDefinition>)Array.AsReadOnly(
|
||||
value.ToArray()),
|
||||
StringComparer.Ordinal);
|
||||
List<ManagedCommandGroup> managedGroups = commandGroups.Select(
|
||||
group => new ManagedCommandGroup(
|
||||
group,
|
||||
Array.AsReadOnly(
|
||||
group.Commands.SelectMany(
|
||||
command => methodsByCommand.TryGetValue(
|
||||
command.Name,
|
||||
out IReadOnlyList<ManagedMethodDefinition>? methods)
|
||||
? methods
|
||||
: Array.Empty<ManagedMethodDefinition>())
|
||||
.ToArray()))).ToList();
|
||||
ManagedApiPlanner.Validate(
|
||||
managedGroups.SelectMany(value => value.Methods));
|
||||
files.Add(new GeneratedFile(
|
||||
"OpenglContext.g.cs",
|
||||
GenerateContext(commands, typeMapper)));
|
||||
files.Add(new GeneratedFile(
|
||||
"glContextShadow.g.cs",
|
||||
GenerateContextShadow(commands)));
|
||||
for (int index = 0; index < managedGroups.Count; index++)
|
||||
{
|
||||
ManagedCommandGroup managedGroup = managedGroups[index];
|
||||
GlCommandGroup group = managedGroup.Source;
|
||||
files.Add(
|
||||
new GeneratedFile(
|
||||
group.FileName,
|
||||
GenerateFunctions(
|
||||
managedGroup.Methods,
|
||||
group.FileName,
|
||||
group.Description,
|
||||
index == 0)));
|
||||
}
|
||||
}
|
||||
|
||||
return new GeneratedFileSet(files);
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<GlCommandGroup> ReadCoreCommandGroups(
|
||||
GlRegistryProjection projection
|
||||
)
|
||||
{
|
||||
List<GlCommandDefinition> foundation = new List<GlCommandDefinition>();
|
||||
Dictionary<string, List<GlCommandDefinition>> versionGroups = new Dictionary<string, List<GlCommandDefinition>>(
|
||||
StringComparer.Ordinal);
|
||||
List<string> versionOrder = new List<string>();
|
||||
foreach (GlCommandDefinition command in projection.Commands)
|
||||
{
|
||||
if (!projection.CommandIntroducedVersions.TryGetValue(
|
||||
command.Name,
|
||||
out string? version))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Command '{command.Name}' has no introduction version.");
|
||||
}
|
||||
|
||||
if (IsFoundationVersion(version))
|
||||
{
|
||||
foundation.Add(command);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!versionGroups.TryGetValue(
|
||||
version,
|
||||
out List<GlCommandDefinition>? commands))
|
||||
{
|
||||
commands = new List<GlCommandDefinition>();
|
||||
versionGroups.Add(version, commands);
|
||||
versionOrder.Add(version);
|
||||
}
|
||||
|
||||
commands.Add(command);
|
||||
}
|
||||
|
||||
List<GlCommandGroup> groups = new List<GlCommandGroup>
|
||||
{
|
||||
new GlCommandGroup(
|
||||
"OpenGLFunction.Base.g.cs",
|
||||
"Desktop OpenGL forwarding methods introduced through 3.2 and retained by the target core profile",
|
||||
Array.AsReadOnly(foundation.ToArray())),
|
||||
};
|
||||
foreach (string version in versionOrder)
|
||||
{
|
||||
groups.Add(
|
||||
new GlCommandGroup(
|
||||
$"OpenGLFunction.v{version.Replace('.', '_')}.g.cs",
|
||||
$"Desktop OpenGL {version} core-profile forwarding methods",
|
||||
Array.AsReadOnly(versionGroups[version].ToArray())));
|
||||
}
|
||||
|
||||
return Array.AsReadOnly(groups.ToArray());
|
||||
}
|
||||
|
||||
private static bool IsFoundationVersion(
|
||||
string version
|
||||
)
|
||||
{
|
||||
string[] components = version.Split('.');
|
||||
if (components.Length != 2 ||
|
||||
!int.TryParse(
|
||||
components[0],
|
||||
NumberStyles.None,
|
||||
CultureInfo.InvariantCulture,
|
||||
out int major) ||
|
||||
!int.TryParse(
|
||||
components[1],
|
||||
NumberStyles.None,
|
||||
CultureInfo.InvariantCulture,
|
||||
out int minor))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL feature version '{version}' is not a major.minor number.");
|
||||
}
|
||||
|
||||
return major < 3 || major == 3 && minor <= 2;
|
||||
}
|
||||
|
||||
private static string GenerateConstants(
|
||||
IReadOnlyList<GlEnumDefinition> enums
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
AppendGeneratedHeader(builder, "glconst.cs", "Desktop OpenGL constants");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" public static class glConst\n");
|
||||
builder.Append(" {\n\n");
|
||||
|
||||
Dictionary<string, GlEnumDefinition> emitted = new Dictionary<string, GlEnumDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlEnumDefinition value in enums)
|
||||
{
|
||||
if (value.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (emitted.TryGetValue(value.Name, out GlEnumDefinition? previous))
|
||||
{
|
||||
if (!string.Equals(previous.Value, value.Value, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Desktop enum '{value.Name}' has conflicting values " +
|
||||
$"'{previous.Value}' and '{value.Value}'.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
emitted.Add(value.Name, value);
|
||||
ConstantExpression expression = ReadConstantExpression(value);
|
||||
builder.Append(" public const ");
|
||||
builder.Append(expression.Type);
|
||||
builder.Append(' ');
|
||||
builder.Append(ReadConstantName(value.Name));
|
||||
builder.Append(" = ");
|
||||
builder.Append(expression.Literal);
|
||||
builder.Append(";\n");
|
||||
}
|
||||
|
||||
builder.Append("\n }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateEnums(
|
||||
IReadOnlyList<ManagedEnumDefinition> enums
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
AppendGeneratedHeader(
|
||||
builder,
|
||||
"GlEnums.g.cs",
|
||||
"Strongly typed desktop OpenGL enum groups");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
foreach (ManagedEnumDefinition value in enums)
|
||||
{
|
||||
builder.Append(" /// <summary>Native OpenGL group");
|
||||
builder.Append(value.NativeGroups.Count == 1 ? " " : "s ");
|
||||
builder.Append(string.Join(", ", value.NativeGroups));
|
||||
builder.Append(".</summary>\n");
|
||||
if (value.IsFlags)
|
||||
{
|
||||
builder.Append(" [System.Flags]\n");
|
||||
}
|
||||
|
||||
builder.Append(" public enum ");
|
||||
builder.Append(value.Name);
|
||||
builder.Append(" : uint\n");
|
||||
builder.Append(" {\n");
|
||||
foreach (ManagedEnumMemberDefinition member in value.Members)
|
||||
{
|
||||
builder.Append(" ");
|
||||
builder.Append(member.Name);
|
||||
builder.Append(" = ");
|
||||
if (member.NativeValue.Name == "GL_NONE" &&
|
||||
member.NativeValue.Comment ==
|
||||
"Synthetic zero value for a generated flags enum.")
|
||||
{
|
||||
builder.Append("0u");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("unchecked((uint)glConst.");
|
||||
builder.Append(ReadConstantName(member.NativeValue.Name));
|
||||
builder.Append(')');
|
||||
}
|
||||
|
||||
builder.Append(",\n");
|
||||
}
|
||||
|
||||
builder.Append(" }\n\n");
|
||||
}
|
||||
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static ConstantExpression ReadConstantExpression(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
string source = value.Value!;
|
||||
bool isHex = source.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
|
||||
BigInteger number = isHex
|
||||
? BigInteger.Parse(
|
||||
$"0{source[2..]}",
|
||||
NumberStyles.AllowHexSpecifier,
|
||||
CultureInfo.InvariantCulture)
|
||||
: BigInteger.Parse(source, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||
|
||||
if (string.Equals(value.TypeSuffix, "ull", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
EnsureRange(number, BigInteger.Zero, ulong.MaxValue, value);
|
||||
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
|
||||
}
|
||||
|
||||
if (string.Equals(value.TypeSuffix, "u", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
EnsureRange(number, BigInteger.Zero, uint.MaxValue, value);
|
||||
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
|
||||
}
|
||||
|
||||
if (number >= int.MinValue && number <= int.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("int", source);
|
||||
}
|
||||
|
||||
if (number >= BigInteger.Zero && number <= uint.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
|
||||
}
|
||||
|
||||
if (number >= long.MinValue && number <= long.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("long", ReadLiteral(source, isHex, "L"));
|
||||
}
|
||||
|
||||
if (number >= BigInteger.Zero && number <= ulong.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
|
||||
}
|
||||
|
||||
throw new InvalidDataException(
|
||||
$"Enum '{value.Name}' value '{source}' does not fit a C# integral type.");
|
||||
}
|
||||
|
||||
private static void EnsureRange(
|
||||
BigInteger value,
|
||||
BigInteger minimum,
|
||||
BigInteger maximum,
|
||||
GlEnumDefinition definition
|
||||
)
|
||||
{
|
||||
if (value < minimum || value > maximum)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Enum '{definition.Name}' value '{definition.Value}' is outside its " +
|
||||
"declared numeric suffix range.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadLiteral(
|
||||
string source,
|
||||
bool isHex,
|
||||
string suffix
|
||||
)
|
||||
{
|
||||
return isHex ? $"{source}{suffix}" : $"{source}{suffix}";
|
||||
}
|
||||
|
||||
private static string ReadConstantName(
|
||||
string name
|
||||
)
|
||||
{
|
||||
string value = name.StartsWith("GL_", StringComparison.Ordinal)
|
||||
? name[3..]
|
||||
: name;
|
||||
StringBuilder result = new StringBuilder(value.Length + 1);
|
||||
if (value.Length == 0 || char.IsDigit(value[0]))
|
||||
{
|
||||
result.Append('_');
|
||||
}
|
||||
|
||||
foreach (char character in value)
|
||||
{
|
||||
result.Append(char.IsLetterOrDigit(character) || character == '_'
|
||||
? character
|
||||
: '_');
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateContext(
|
||||
IReadOnlyList<GlCommandDefinition> commands,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(128 * 1024);
|
||||
AppendGeneratedHeader(
|
||||
builder,
|
||||
"OpenglContext.g.cs",
|
||||
"Desktop OpenGL core function pointer table");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]\n");
|
||||
builder.Append(" public unsafe partial struct OpenglContext\n");
|
||||
builder.Append(" {\n");
|
||||
foreach (GlCommandDefinition command in OrderCommands(commands))
|
||||
{
|
||||
builder.Append(" internal delegate* unmanaged<");
|
||||
foreach (GlParameterDefinition parameter in command.Parameters)
|
||||
{
|
||||
builder.Append(typeMapper.ReadParameterType(parameter));
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append(typeMapper.ReadReturnType(command));
|
||||
builder.Append("> ");
|
||||
builder.Append(command.Name);
|
||||
builder.Append(";\n");
|
||||
}
|
||||
|
||||
builder.Append(" }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateContextShadow(
|
||||
IReadOnlyList<GlCommandDefinition> commands
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(64 * 1024);
|
||||
AppendGeneratedHeader(
|
||||
builder,
|
||||
"glContextShadow.g.cs",
|
||||
"Native-address shadow of the OpenGL function pointer table");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]\n");
|
||||
builder.Append(" internal struct glContextShadow\n");
|
||||
builder.Append(" {\n");
|
||||
foreach (GlCommandDefinition command in OrderCommands(commands))
|
||||
{
|
||||
builder.Append(" internal nint ");
|
||||
builder.Append(command.Name);
|
||||
builder.Append(";\n");
|
||||
}
|
||||
|
||||
builder.Append(" }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static IEnumerable<GlCommandDefinition> OrderCommands(
|
||||
IReadOnlyList<GlCommandDefinition> commands
|
||||
)
|
||||
{
|
||||
return commands.OrderBy(
|
||||
value => value.Name,
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private static string GenerateFunctions(
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
string outputFileName,
|
||||
string description,
|
||||
bool includeContextState
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
AppendGeneratedHeader(builder, outputFileName, description);
|
||||
builder.Append("using StgSharp.Mathematics.Numeric.Graphics;\n\n");
|
||||
builder.Append("using System;\n");
|
||||
builder.Append("using System.Runtime.CompilerServices;\n\n");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" public unsafe partial class OpenGLFunction\n");
|
||||
builder.Append(" {\n");
|
||||
if (includeContextState)
|
||||
{
|
||||
builder.Append("\n private readonly OpenglContext* __context;\n");
|
||||
builder.Append("\n internal OpenGLFunction(OpenglContext* context)\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context = context;\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
foreach (ManagedMethodDefinition method in methods)
|
||||
{
|
||||
AppendMethodDocumentation(builder, method);
|
||||
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
|
||||
builder.Append(" public ");
|
||||
builder.Append(method.ReturnType);
|
||||
builder.Append(' ');
|
||||
builder.Append(method.Name);
|
||||
builder.Append('(');
|
||||
for (int index = 0; index < method.Parameters.Count; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
ManagedParameterDefinition parameter = method.Parameters[index];
|
||||
builder.Append(parameter.Type);
|
||||
builder.Append(' ');
|
||||
builder.Append(EscapeIdentifier(parameter.Name));
|
||||
}
|
||||
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
AppendInvocation(builder, method);
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
builder.Append("\n }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendMethodDocumentation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method
|
||||
)
|
||||
{
|
||||
string declaration = ReadNativeDeclaration(method.NativeCommand);
|
||||
string page = method.DocumentationPage ?? method.NativeCommand.Name;
|
||||
builder.Append("\n /// <summary>\n");
|
||||
builder.Append(" /// <para>Forwards to the native OpenGL entry point.</para>\n");
|
||||
builder.Append(" /// <para><c>");
|
||||
builder.Append(EscapeXml(declaration));
|
||||
builder.Append("</c></para>\n");
|
||||
builder.Append(" /// <para>See <see href=\"https://docs.gl/gl4/");
|
||||
builder.Append(page);
|
||||
builder.Append("\">docs.gl</see>.</para>\n");
|
||||
builder.Append(" /// </summary>");
|
||||
}
|
||||
|
||||
private static string ReadNativeDeclaration(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
return $"{command.Return.DeclarationText}(" +
|
||||
string.Join(", ", command.Parameters.Select(value => value.DeclarationText)) +
|
||||
");";
|
||||
}
|
||||
|
||||
private static string EscapeXml(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return value.Replace("&", "&", StringComparison.Ordinal)
|
||||
.Replace("<", "<", StringComparison.Ordinal)
|
||||
.Replace(">", ">", StringComparison.Ordinal)
|
||||
.Replace("\"", """, StringComparison.Ordinal)
|
||||
.Replace("'", "'", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void AppendInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method
|
||||
)
|
||||
{
|
||||
switch (method.Invocation)
|
||||
{
|
||||
case DirectInvocation direct:
|
||||
AppendDirectInvocation(builder, method, direct);
|
||||
break;
|
||||
case PinnedSpanInvocation pinned:
|
||||
AppendPinnedSpanInvocation(builder, method, pinned);
|
||||
break;
|
||||
case PinnedVectorSpanInvocation pinnedVector:
|
||||
AppendPinnedVectorSpanInvocation(builder, method, pinnedVector);
|
||||
break;
|
||||
case Matrix4Invocation matrix:
|
||||
AppendMatrix4Invocation(builder, method, matrix);
|
||||
break;
|
||||
case QuerySpanInvocation query:
|
||||
AppendQuerySpanInvocation(builder, method, query);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported managed invocation '{method.Invocation.GetType().Name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendQuerySpanInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
QuerySpanInvocation invocation
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
|
||||
string spanName = EscapeIdentifier(span.Name);
|
||||
builder.Append(" fixed (");
|
||||
builder.Append(invocation.ElementType);
|
||||
builder.Append("* __value = ");
|
||||
builder.Append(spanName);
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context->");
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, invocation.PrefixArguments);
|
||||
if (invocation.PrefixArguments.Count != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append("__value);\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void AppendDirectInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
DirectInvocation invocation
|
||||
)
|
||||
{
|
||||
List<int> fixedParameters = new List<int>();
|
||||
for (int index = 0; index < method.Parameters.Count; index++)
|
||||
{
|
||||
if (method.Parameters[index].IsHandleSpan)
|
||||
{
|
||||
fixedParameters.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
int indentation = 12;
|
||||
foreach (int parameterIndex in fixedParameters)
|
||||
{
|
||||
ManagedParameterDefinition parameter = method.Parameters[parameterIndex];
|
||||
builder.Append(' ', indentation);
|
||||
builder.Append("fixed (GlHandle* __");
|
||||
builder.Append(parameter.Name);
|
||||
builder.Append(" = ");
|
||||
builder.Append(EscapeIdentifier(parameter.Name));
|
||||
builder.Append(")\n");
|
||||
builder.Append(' ', indentation);
|
||||
builder.Append("{\n");
|
||||
indentation += 4;
|
||||
}
|
||||
|
||||
builder.Append(' ', indentation);
|
||||
if (method.WrapHandleReturn)
|
||||
{
|
||||
builder.Append("return GlHandle.Create(");
|
||||
}
|
||||
else if (method.IsEnumReturn)
|
||||
{
|
||||
builder.Append("return (");
|
||||
builder.Append(method.ReturnType);
|
||||
builder.Append(')');
|
||||
}
|
||||
else if (method.ReturnType != "void")
|
||||
{
|
||||
builder.Append("return ");
|
||||
}
|
||||
|
||||
AppendNativeCall(builder, method, invocation.Arguments);
|
||||
builder.Append(method.WrapHandleReturn ? ");\n" : ";\n");
|
||||
for (int index = fixedParameters.Count - 1; index >= 0; index--)
|
||||
{
|
||||
indentation -= 4;
|
||||
builder.Append(' ', indentation);
|
||||
builder.Append("}\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendPinnedSpanInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
PinnedSpanInvocation invocation
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
|
||||
builder.Append(" fixed (");
|
||||
builder.Append(invocation.ElementType);
|
||||
builder.Append("* __value = ");
|
||||
builder.Append(EscapeIdentifier(span.Name));
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context->");
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, invocation.PrefixArguments);
|
||||
if (invocation.PrefixArguments.Count != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append(EscapeIdentifier(span.Name));
|
||||
builder.Append(".Length, __value);\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void AppendPinnedVectorSpanInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
PinnedVectorSpanInvocation invocation
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
|
||||
string spanName = EscapeIdentifier(span.Name);
|
||||
builder.Append(" fixed (");
|
||||
builder.Append(invocation.VectorType);
|
||||
builder.Append("* __vector = ");
|
||||
builder.Append(spanName);
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context->");
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, invocation.PrefixArguments);
|
||||
if (invocation.PrefixArguments.Count != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append(spanName);
|
||||
builder.Append(".Length, (");
|
||||
builder.Append(invocation.ComponentType);
|
||||
builder.Append("*)__vector);\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void AppendMatrix4Invocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
Matrix4Invocation invocation
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition transpose = method.Parameters[invocation.TransposeParameterIndex];
|
||||
ManagedParameterDefinition matrix = method.Parameters[invocation.MatrixParameterIndex];
|
||||
string transposeName = EscapeIdentifier(transpose.Name);
|
||||
string matrixName = EscapeIdentifier(matrix.Name);
|
||||
if (invocation.IsSpan)
|
||||
{
|
||||
builder.Append(" fixed (GraphicsMatrix* __value = ");
|
||||
builder.Append(matrixName);
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context->");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(" __context->");
|
||||
}
|
||||
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, invocation.PrefixArguments);
|
||||
if (invocation.PrefixArguments.Count != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append(invocation.IsSpan ? $"{matrixName}.Length" : "1");
|
||||
builder.Append(", ");
|
||||
builder.Append(transposeName);
|
||||
builder.Append(" ? (byte)1 : (byte)0, (float*)");
|
||||
builder.Append(invocation.IsSpan ? "__value" : $"&{matrixName}");
|
||||
builder.Append(");\n");
|
||||
if (invocation.IsSpan)
|
||||
{
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendNativeCall(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
IReadOnlyList<ManagedArgument> arguments
|
||||
)
|
||||
{
|
||||
builder.Append("__context->");
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, arguments);
|
||||
builder.Append(')');
|
||||
}
|
||||
|
||||
private static void AppendArguments(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
IReadOnlyList<ManagedArgument> arguments
|
||||
)
|
||||
{
|
||||
for (int index = 0; index < arguments.Count; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
switch (arguments[index])
|
||||
{
|
||||
case ParameterArgument parameterArgument:
|
||||
ManagedParameterDefinition parameter = method.Parameters[
|
||||
parameterArgument.ParameterIndex];
|
||||
string parameterName = EscapeIdentifier(parameter.Name);
|
||||
if (parameter.IsEnum)
|
||||
{
|
||||
builder.Append("(uint");
|
||||
builder.Append('*', parameter.Type.Count(value => value == '*'));
|
||||
builder.Append(')');
|
||||
builder.Append(parameterName);
|
||||
}
|
||||
else if (!parameter.IsHandle)
|
||||
{
|
||||
builder.Append(parameterName);
|
||||
}
|
||||
else if (parameter.Type.EndsWith('*'))
|
||||
{
|
||||
builder.Append("(uint*)");
|
||||
builder.Append(parameterName);
|
||||
}
|
||||
else if (parameter.IsHandleSpan)
|
||||
{
|
||||
builder.Append("(uint*)__");
|
||||
builder.Append(parameter.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(parameterName);
|
||||
builder.Append(".Value");
|
||||
}
|
||||
|
||||
break;
|
||||
case SpanLengthArgument spanLengthArgument:
|
||||
builder.Append(
|
||||
EscapeIdentifier(
|
||||
method.Parameters[spanLengthArgument.ParameterIndex].Name));
|
||||
builder.Append(".Length");
|
||||
break;
|
||||
case VectorComponentArgument componentArgument:
|
||||
builder.Append(
|
||||
EscapeIdentifier(
|
||||
method.Parameters[componentArgument.ParameterIndex].Name));
|
||||
builder.Append('.');
|
||||
builder.Append(componentArgument.Component);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported managed argument '{arguments[index].GetType().Name}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string EscapeIdentifier(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return _csharpKeywords.Contains(value) ? $"@{value}" : value;
|
||||
}
|
||||
|
||||
private static void AppendGeneratedHeader(
|
||||
StringBuilder builder,
|
||||
string outputFileName,
|
||||
string description
|
||||
)
|
||||
{
|
||||
builder.Append("// -----------------------------------------------------------------------------\n");
|
||||
builder.Append("// file=\"");
|
||||
builder.Append(ReadGeneratedFileName(outputFileName));
|
||||
builder.Append("\"\n");
|
||||
builder.Append("// Project: StgSharp\n");
|
||||
builder.Append("// Copyright (c) Nitload.\n");
|
||||
builder.Append("// SPDX-License-Identifier: MIT\n");
|
||||
builder.Append("// -----------------------------------------------------------------------------\n\n");
|
||||
builder.Append("// <auto-generated>\n");
|
||||
builder.Append("// Generated by StgSharp.GenerateGL.\n");
|
||||
builder.Append("// ");
|
||||
builder.Append(description);
|
||||
builder.Append(".\n");
|
||||
builder.Append("// </auto-generated>\n\n");
|
||||
}
|
||||
|
||||
private static string ReadGeneratedFileName(
|
||||
string outputFileName
|
||||
)
|
||||
{
|
||||
const string generatedExtension = ".g.cs";
|
||||
const string sourceExtension = ".cs";
|
||||
if (outputFileName.EndsWith(generatedExtension, StringComparison.Ordinal))
|
||||
{
|
||||
return outputFileName[..^generatedExtension.Length];
|
||||
}
|
||||
|
||||
return outputFileName.EndsWith(sourceExtension, StringComparison.Ordinal)
|
||||
? outputFileName[..^sourceExtension.Length]
|
||||
: outputFileName;
|
||||
}
|
||||
|
||||
private sealed record ConstantExpression(
|
||||
string Type,
|
||||
string Literal
|
||||
);
|
||||
|
||||
private sealed record GlCommandGroup(
|
||||
string FileName,
|
||||
string Description,
|
||||
IReadOnlyList<GlCommandDefinition> Commands
|
||||
);
|
||||
|
||||
private sealed record ManagedCommandGroup(
|
||||
GlCommandGroup Source,
|
||||
IReadOnlyList<ManagedMethodDefinition> Methods
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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)
|
||||
.Replace(
|
||||
"pname",
|
||||
"pName",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"xoffset",
|
||||
"xOffset",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"yoffset",
|
||||
"yOffset",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"zoffset",
|
||||
"zOffset",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"vaobj",
|
||||
"vertexArray",
|
||||
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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DisablePostBuildCopy>true</DisablePostBuildCopy>
|
||||
<DisableNativeCopy>true</DisableNativeCopy>
|
||||
<!-- CLI output is a stable developer-facing protocol, not localized UI. -->
|
||||
<NoWarn>$(NoWarn);CA1303</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="xml\gl.xml"
|
||||
CopyToOutputDirectory="PreserveNewest"
|
||||
CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,60 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="Program"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace StgSharp.GenerateGL
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
|
||||
private static int Main(
|
||||
string[] args
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
return XmlCli.Run(args);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
catch (XmlException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
catch (NotSupportedException exception)
|
||||
{
|
||||
return WriteError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static int WriteError(
|
||||
Exception exception
|
||||
)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return XmlCli.LoadErrorExitCode;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
# 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/Nitload.GenerateGL/Nitload.GenerateGL.csproj
|
||||
```
|
||||
|
||||
Common commands:
|
||||
|
||||
```text
|
||||
summary
|
||||
find glBindBuffer
|
||||
show command glBindBuffer
|
||||
target summary
|
||||
families list Vertex
|
||||
families show VertexAttributeP
|
||||
generate all
|
||||
list files
|
||||
preview OpenGLFunction.v4_1.g.cs
|
||||
```
|
||||
|
||||
Generate directly into an existing directory:
|
||||
|
||||
```powershell
|
||||
dotnet run --project tools/Nitload.GenerateGL/Nitload.GenerateGL.csproj -- generate all src/Nitload.Graphics/OpenGL/Generated
|
||||
```
|
||||
|
||||
Generation targets are explicit:
|
||||
|
||||
```text
|
||||
generate api [output-directory] # function tables and managed forwarding APIs
|
||||
generate enum [output-directory] # glConst and strongly typed enum groups
|
||||
generate all [output-directory] # both sets
|
||||
```
|
||||
|
||||
The API files reference the generated enum types. Use `generate all` for a clean output
|
||||
directory; use `generate api` when refreshing an output tree that already contains the enum
|
||||
files.
|
||||
|
||||
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
|
||||
-> GLenum/GLbitfield semantic group 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;
|
||||
- `GlEnums.g.cs`: strongly typed enum groups referenced by Core Profile parameters and return values;
|
||||
- `OpenglContext.g.cs`: the function-pointer table with native names and ABI types;
|
||||
- `glContextShadow.g.cs`: an identically ordered `nint` table used while loading native entry points;
|
||||
- `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` |
|
||||
| `pname` | `pName` |
|
||||
| `xoffset` / `yoffset` / `zoffset` | `xOffset` / `yOffset` / `zOffset` |
|
||||
| `vaobj` | `vertexArray` |
|
||||
|
||||
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;
|
||||
- `GLenum` and `GLbitfield` declarations with one unambiguous registry group use the generated enum type in public APIs;
|
||||
- enum pointers preserve their pointer shape (for example, `GLenum*` becomes `DebugSource*`) and are cast back to the native `uint*` representation at the forwarding boundary;
|
||||
- enum return values are converted from the native `uint` representation before being returned;
|
||||
- declarations with missing or conflicting enum group metadata remain on their ABI type;
|
||||
- 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.
|
||||
|
||||
Raw managed forwards are omitted when a safer handwritten public API owns the command. In particular, `glShaderSource` remains in `OpenglContext` for ABI loading, while `OpenGLFunction` exposes the handwritten `ReadOnlySpan<byte>` and `string` overloads instead of a generated `byte**` signature.
|
||||
|
||||
## 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,59 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlApiDefinition"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
internal sealed record GlFeatureDefinition(
|
||||
string Name,
|
||||
string Api,
|
||||
string Number,
|
||||
string? Protect,
|
||||
string? Comment,
|
||||
IReadOnlyList<GlRequirementBlock> Requirements,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlExtensionDefinition(
|
||||
string Name,
|
||||
string? SupportedApis,
|
||||
string? Protect,
|
||||
string? Comment,
|
||||
IReadOnlyList<GlRequirementBlock> Requirements,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlRequirementBlock(
|
||||
GlRequirementOperation Operation,
|
||||
string? Api,
|
||||
string? Profile,
|
||||
string? Comment,
|
||||
IReadOnlyList<GlRegistryReference> References,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlRegistryReference(
|
||||
GlRegistryReferenceKind Kind,
|
||||
string Name,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal enum GlRequirementOperation
|
||||
{
|
||||
Require,
|
||||
Remove,
|
||||
}
|
||||
|
||||
internal enum GlRegistryReferenceKind
|
||||
{
|
||||
Type,
|
||||
Enum,
|
||||
Command,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlCommandDefinition"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
internal sealed record GlCommandBlock(
|
||||
string? Namespace,
|
||||
IReadOnlyList<GlCommandDefinition> Commands,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlCommandDefinition(
|
||||
string Name,
|
||||
GlReturnDefinition Return,
|
||||
IReadOnlyList<GlParameterDefinition> Parameters,
|
||||
IReadOnlyList<GlNamedReference> Aliases,
|
||||
IReadOnlyList<GlNamedReference> VectorEquivalents,
|
||||
IReadOnlyList<GlCommandProtocol> Protocols,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlReturnDefinition(
|
||||
string DeclarationText,
|
||||
string? TypeName,
|
||||
string? Group,
|
||||
string? Kind,
|
||||
string? ObjectClass,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlParameterDefinition(
|
||||
string Name,
|
||||
string DeclarationText,
|
||||
string? TypeName,
|
||||
string? Group,
|
||||
string? Kind,
|
||||
string? ObjectClass,
|
||||
string? Length,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlCommandProtocol(
|
||||
string Type,
|
||||
string Opcode,
|
||||
string? Name,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlEnumDefinition"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
internal sealed record GlEnumDefinition(
|
||||
string Name,
|
||||
string? Value,
|
||||
string? Api,
|
||||
string? TypeSuffix,
|
||||
string? Group,
|
||||
string? Alias,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlEnumBlock(
|
||||
string? Namespace,
|
||||
string? Group,
|
||||
string? Type,
|
||||
string? Start,
|
||||
string? End,
|
||||
string? Vendor,
|
||||
string? Comment,
|
||||
IReadOnlyList<GlEnumDefinition> Enums,
|
||||
IReadOnlyList<GlUnusedEnumRange> UnusedRanges,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlEnumGroup(
|
||||
string Name,
|
||||
string? Comment,
|
||||
IReadOnlyList<GlNamedReference> Members,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlUnusedEnumRange(
|
||||
string Start,
|
||||
string? End,
|
||||
string? Vendor,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlRegistryElement"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
/// <summary>
|
||||
/// Preserves the XML representation shared by every registry model item.
|
||||
/// </summary>
|
||||
internal sealed record GlRegistryElement(
|
||||
GlSourceLocation Location,
|
||||
string RawXml,
|
||||
IReadOnlyDictionary<string, string> Attributes
|
||||
);
|
||||
|
||||
internal readonly record struct GlSourceLocation(
|
||||
int LineNumber,
|
||||
int LinePosition
|
||||
);
|
||||
|
||||
internal sealed record GlNamedReference(
|
||||
string Name,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlRegistryModel"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the structural OpenGL registry model without managed type mapping.
|
||||
/// </summary>
|
||||
internal sealed class GlRegistryModel
|
||||
{
|
||||
|
||||
public GlRegistryModel(
|
||||
string sourcePath,
|
||||
string? comment,
|
||||
IEnumerable<GlTypeDefinition> types,
|
||||
IEnumerable<GlKindDefinition> kinds,
|
||||
IEnumerable<GlEnumGroup> enumGroups,
|
||||
IEnumerable<GlEnumBlock> enumBlocks,
|
||||
IEnumerable<GlCommandBlock> commandBlocks,
|
||||
IEnumerable<GlFeatureDefinition> features,
|
||||
IEnumerable<GlExtensionDefinition> extensions,
|
||||
GlRegistryElement element
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sourcePath);
|
||||
ArgumentNullException.ThrowIfNull(types);
|
||||
ArgumentNullException.ThrowIfNull(kinds);
|
||||
ArgumentNullException.ThrowIfNull(enumGroups);
|
||||
ArgumentNullException.ThrowIfNull(enumBlocks);
|
||||
ArgumentNullException.ThrowIfNull(commandBlocks);
|
||||
ArgumentNullException.ThrowIfNull(features);
|
||||
ArgumentNullException.ThrowIfNull(extensions);
|
||||
ArgumentNullException.ThrowIfNull(element);
|
||||
|
||||
SourcePath = sourcePath;
|
||||
Comment = comment;
|
||||
Types = Freeze(types);
|
||||
Kinds = Freeze(kinds);
|
||||
EnumGroups = Freeze(enumGroups);
|
||||
EnumBlocks = Freeze(enumBlocks);
|
||||
CommandBlocks = Freeze(commandBlocks);
|
||||
Commands = Freeze(CommandBlocks.SelectMany(value => value.Commands));
|
||||
Features = Freeze(features);
|
||||
Extensions = Freeze(extensions);
|
||||
Element = element;
|
||||
|
||||
TypesByName = CreateIndex(Types, value => value.Name);
|
||||
KindsByName = CreateIndex(Kinds, value => value.Name);
|
||||
EnumGroupsByName = CreateIndex(EnumGroups, value => value.Name);
|
||||
EnumsByName = CreateIndex(
|
||||
EnumBlocks.SelectMany(value => value.Enums),
|
||||
value => value.Name);
|
||||
CommandsByName = CreateIndex(Commands, value => value.Name);
|
||||
FeaturesByName = CreateIndex(Features, value => value.Name);
|
||||
ExtensionsByName = CreateIndex(Extensions, value => value.Name);
|
||||
}
|
||||
|
||||
public string SourcePath { get; }
|
||||
|
||||
public string? Comment { get; }
|
||||
|
||||
public IReadOnlyList<GlTypeDefinition> Types { get; }
|
||||
|
||||
public IReadOnlyList<GlKindDefinition> Kinds { get; }
|
||||
|
||||
public IReadOnlyList<GlEnumGroup> EnumGroups { get; }
|
||||
|
||||
public IReadOnlyList<GlEnumBlock> EnumBlocks { get; }
|
||||
|
||||
public IReadOnlyList<GlCommandBlock> CommandBlocks { get; }
|
||||
|
||||
public IReadOnlyList<GlCommandDefinition> Commands { get; }
|
||||
|
||||
public IReadOnlyList<GlFeatureDefinition> Features { get; }
|
||||
|
||||
public IReadOnlyList<GlExtensionDefinition> Extensions { get; }
|
||||
|
||||
public GlRegistryElement Element { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlTypeDefinition>> TypesByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlKindDefinition>> KindsByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlEnumGroup>> EnumGroupsByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlEnumDefinition>> EnumsByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlCommandDefinition>> CommandsByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlFeatureDefinition>> FeaturesByName { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<GlExtensionDefinition>> ExtensionsByName { get; }
|
||||
|
||||
private static ReadOnlyDictionary<string, IReadOnlyList<T>> CreateIndex<T>(
|
||||
IEnumerable<T> values,
|
||||
Func<T, string> readName
|
||||
)
|
||||
{
|
||||
Dictionary<string, IReadOnlyList<T>> index = values.GroupBy(
|
||||
readName,
|
||||
StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
value => value.Key,
|
||||
value => (IReadOnlyList<T>)Freeze(value),
|
||||
StringComparer.Ordinal);
|
||||
return new ReadOnlyDictionary<string, IReadOnlyList<T>>(index);
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<T> Freeze<T>(
|
||||
IEnumerable<T> values
|
||||
)
|
||||
{
|
||||
return Array.AsReadOnly(values.ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlRegistryReader"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using StgSharp.GenerateGL.Tree;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
/// <summary>
|
||||
/// Projects the generic XML tree into the structural OpenGL registry model.
|
||||
/// </summary>
|
||||
internal static class GlRegistryReader
|
||||
{
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, string> _emptyAttributes =
|
||||
new ReadOnlyDictionary<string, string>(
|
||||
new Dictionary<string, string>(StringComparer.Ordinal));
|
||||
|
||||
public static GlRegistryModel Read(
|
||||
XmlTreeDocument document
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
XElement root = document.Tree.Root ?? throw new InvalidDataException(
|
||||
$"XML document '{document.SourcePath}' does not contain a root element.");
|
||||
if (root.Name != "registry")
|
||||
{
|
||||
throw CreateError(
|
||||
document,
|
||||
root,
|
||||
$"Expected <registry>, but found <{root.Name}>.");
|
||||
}
|
||||
|
||||
ValidateChildren(
|
||||
document,
|
||||
root,
|
||||
"comment",
|
||||
"types",
|
||||
"kinds",
|
||||
"groups",
|
||||
"enums",
|
||||
"commands",
|
||||
"feature",
|
||||
"extensions");
|
||||
|
||||
return new GlRegistryModel(
|
||||
document.SourcePath,
|
||||
ReadOptionalElementText(root, "comment"),
|
||||
ReadSection(document, root, "types", "type").Select(
|
||||
value => ReadType(document, value)),
|
||||
ReadSection(document, root, "kinds", "kind").Select(
|
||||
value => ReadKind(document, value)),
|
||||
ReadSection(document, root, "groups", "group").Select(
|
||||
value => ReadEnumGroup(document, value)),
|
||||
root.Elements("enums").Select(value => ReadEnumBlock(document, value)),
|
||||
root.Elements("commands").Select(
|
||||
value => ReadCommandBlock(document, value)),
|
||||
root.Elements("feature").Select(value => ReadFeature(document, value)),
|
||||
ReadSection(document, root, "extensions", "extension").Select(
|
||||
value => ReadExtension(document, value)),
|
||||
ReadElement(root));
|
||||
}
|
||||
|
||||
private static GlTypeDefinition ReadType(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
string name = ReadOptionalAttribute(element, "name") ??
|
||||
ReadOptionalElementText(element, "name") ??
|
||||
throw CreateError(
|
||||
document,
|
||||
element,
|
||||
"A registry <type> requires a name attribute or child element.");
|
||||
|
||||
return new GlTypeDefinition(
|
||||
name,
|
||||
ReadDeclarationText(element),
|
||||
ReadOptionalAttribute(element, "api"),
|
||||
ReadOptionalAttribute(element, "requires"),
|
||||
ReadOptionalAttribute(element, "type"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlKindDefinition ReadKind(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return new GlKindDefinition(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadRequiredAttribute(document, element, "desc"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlEnumGroup ReadEnumGroup(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "enum");
|
||||
return new GlEnumGroup(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
Freeze(
|
||||
element.Elements("enum").Select(
|
||||
value => new GlNamedReference(
|
||||
ReadRequiredAttribute(document, value, "name"),
|
||||
ReadElement(value)))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlEnumBlock ReadEnumBlock(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "enum", "unused");
|
||||
return new GlEnumBlock(
|
||||
ReadOptionalAttribute(element, "namespace"),
|
||||
ReadOptionalAttribute(element, "group"),
|
||||
ReadOptionalAttribute(element, "type"),
|
||||
ReadOptionalAttribute(element, "start"),
|
||||
ReadOptionalAttribute(element, "end"),
|
||||
ReadOptionalAttribute(element, "vendor"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
Freeze(
|
||||
element.Elements("enum").Select(
|
||||
value => ReadEnum(document, value))),
|
||||
Freeze(
|
||||
element.Elements("unused").Select(
|
||||
value => ReadUnusedEnumRange(document, value))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlEnumDefinition ReadEnum(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return new GlEnumDefinition(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadOptionalAttribute(element, "value"),
|
||||
ReadOptionalAttribute(element, "api"),
|
||||
ReadOptionalAttribute(element, "type"),
|
||||
ReadOptionalAttribute(element, "group"),
|
||||
ReadOptionalAttribute(element, "alias"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlUnusedEnumRange ReadUnusedEnumRange(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return new GlUnusedEnumRange(
|
||||
ReadRequiredAttribute(document, element, "start"),
|
||||
ReadOptionalAttribute(element, "end"),
|
||||
ReadOptionalAttribute(element, "vendor"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlCommandBlock ReadCommandBlock(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "command");
|
||||
return new GlCommandBlock(
|
||||
ReadOptionalAttribute(element, "namespace"),
|
||||
Freeze(
|
||||
element.Elements("command").Select(
|
||||
value => ReadCommand(document, value))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlCommandDefinition ReadCommand(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(
|
||||
document,
|
||||
element,
|
||||
"proto",
|
||||
"param",
|
||||
"alias",
|
||||
"vecequiv",
|
||||
"glx");
|
||||
|
||||
XElement prototype = element.Element("proto") ?? throw CreateError(
|
||||
document,
|
||||
element,
|
||||
"A registry <command> requires a <proto> child element.");
|
||||
string name = ReadRequiredElementText(document, prototype, "name");
|
||||
|
||||
return new GlCommandDefinition(
|
||||
name,
|
||||
ReadReturn(document, prototype),
|
||||
Freeze(
|
||||
element.Elements("param").Select(
|
||||
value => ReadParameter(document, value))),
|
||||
Freeze(
|
||||
element.Elements("alias").Select(
|
||||
value => ReadNamedReference(document, value))),
|
||||
Freeze(
|
||||
element.Elements("vecequiv").Select(
|
||||
value => ReadNamedReference(document, value))),
|
||||
Freeze(
|
||||
element.Elements("glx").Select(
|
||||
value => ReadCommandProtocol(document, value))),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlReturnDefinition ReadReturn(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "ptype", "name");
|
||||
_ = ReadRequiredElementText(document, element, "name");
|
||||
return new GlReturnDefinition(
|
||||
ReadDeclarationText(element),
|
||||
ReadOptionalElementText(element, "ptype"),
|
||||
ReadOptionalAttribute(element, "group"),
|
||||
ReadOptionalAttribute(element, "kind"),
|
||||
ReadOptionalAttribute(element, "class"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlParameterDefinition ReadParameter(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "ptype", "name");
|
||||
return new GlParameterDefinition(
|
||||
ReadRequiredElementText(document, element, "name"),
|
||||
ReadDeclarationText(element),
|
||||
ReadOptionalElementText(element, "ptype"),
|
||||
ReadOptionalAttribute(element, "group"),
|
||||
ReadOptionalAttribute(element, "kind"),
|
||||
ReadOptionalAttribute(element, "class"),
|
||||
ReadOptionalAttribute(element, "len"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlNamedReference ReadNamedReference(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return new GlNamedReference(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlCommandProtocol ReadCommandProtocol(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return new GlCommandProtocol(
|
||||
ReadRequiredAttribute(document, element, "type"),
|
||||
ReadRequiredAttribute(document, element, "opcode"),
|
||||
ReadOptionalAttribute(element, "name"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlFeatureDefinition ReadFeature(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "require", "remove");
|
||||
return new GlFeatureDefinition(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadRequiredAttribute(document, element, "api"),
|
||||
ReadRequiredAttribute(document, element, "number"),
|
||||
ReadOptionalAttribute(element, "protect"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
Freeze(
|
||||
element.Elements().Select(
|
||||
value => ReadRequirement(document, value))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlExtensionDefinition ReadExtension(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "require", "remove");
|
||||
return new GlExtensionDefinition(
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadOptionalAttribute(element, "supported"),
|
||||
ReadOptionalAttribute(element, "protect"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
Freeze(
|
||||
element.Elements().Select(
|
||||
value => ReadRequirement(document, value))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlRequirementBlock ReadRequirement(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
ValidateChildren(document, element, "type", "enum", "command");
|
||||
GlRequirementOperation operation = element.Name.LocalName switch
|
||||
{
|
||||
"require" => GlRequirementOperation.Require,
|
||||
"remove" => GlRequirementOperation.Remove,
|
||||
_ => throw CreateError(
|
||||
document,
|
||||
element,
|
||||
$"Expected <require> or <remove>, but found <{element.Name}>."),
|
||||
};
|
||||
|
||||
return new GlRequirementBlock(
|
||||
operation,
|
||||
ReadOptionalAttribute(element, "api"),
|
||||
ReadOptionalAttribute(element, "profile"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
Freeze(
|
||||
element.Elements().Select(
|
||||
value => ReadRegistryReference(document, value))),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static GlRegistryReference ReadRegistryReference(
|
||||
XmlTreeDocument document,
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
GlRegistryReferenceKind kind = element.Name.LocalName switch
|
||||
{
|
||||
"type" => GlRegistryReferenceKind.Type,
|
||||
"enum" => GlRegistryReferenceKind.Enum,
|
||||
"command" => GlRegistryReferenceKind.Command,
|
||||
_ => throw CreateError(
|
||||
document,
|
||||
element,
|
||||
$"Unsupported registry reference <{element.Name}>."),
|
||||
};
|
||||
return new GlRegistryReference(
|
||||
kind,
|
||||
ReadRequiredAttribute(document, element, "name"),
|
||||
ReadOptionalAttribute(element, "comment"),
|
||||
ReadElement(element));
|
||||
}
|
||||
|
||||
private static IEnumerable<XElement> ReadSection(
|
||||
XmlTreeDocument document,
|
||||
XElement root,
|
||||
XName sectionName,
|
||||
XName itemName
|
||||
)
|
||||
{
|
||||
foreach (XElement section in root.Elements(sectionName))
|
||||
{
|
||||
ValidateChildren(document, section, itemName.LocalName);
|
||||
foreach (XElement item in section.Elements(itemName))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadRequiredAttribute(
|
||||
XmlTreeDocument document,
|
||||
XElement element,
|
||||
XName name
|
||||
)
|
||||
{
|
||||
string? value = ReadOptionalAttribute(element, name);
|
||||
if (value is null)
|
||||
{
|
||||
throw CreateError(
|
||||
document,
|
||||
element,
|
||||
$"<{element.Name}> requires the '{name}' attribute.");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string? ReadOptionalAttribute(
|
||||
XElement element,
|
||||
XName name
|
||||
)
|
||||
{
|
||||
string? value = (string?)element.Attribute(name);
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
|
||||
private static string ReadRequiredElementText(
|
||||
XmlTreeDocument document,
|
||||
XElement element,
|
||||
XName name
|
||||
)
|
||||
{
|
||||
string? value = ReadOptionalElementText(element, name);
|
||||
if (value is null)
|
||||
{
|
||||
throw CreateError(
|
||||
document,
|
||||
element,
|
||||
$"<{element.Name}> requires a non-empty <{name}> child element.");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string? ReadOptionalElementText(
|
||||
XElement element,
|
||||
XName name
|
||||
)
|
||||
{
|
||||
string? value = element.Element(name)?.Value.Trim();
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
|
||||
private static string ReadDeclarationText(
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
return element.Value.Trim();
|
||||
}
|
||||
|
||||
private static GlRegistryElement ReadElement(
|
||||
XElement element
|
||||
)
|
||||
{
|
||||
IXmlLineInfo lineInfo = element;
|
||||
GlSourceLocation location = lineInfo.HasLineInfo()
|
||||
? new GlSourceLocation(lineInfo.LineNumber, lineInfo.LinePosition)
|
||||
: default;
|
||||
IReadOnlyDictionary<string, string> attributes = element.HasAttributes
|
||||
? new ReadOnlyDictionary<string, string>(
|
||||
element.Attributes().ToDictionary(
|
||||
value => value.Name.ToString(),
|
||||
value => value.Value,
|
||||
StringComparer.Ordinal))
|
||||
: _emptyAttributes;
|
||||
return new GlRegistryElement(
|
||||
location,
|
||||
element.ToString(SaveOptions.DisableFormatting),
|
||||
attributes);
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<T> Freeze<T>(
|
||||
IEnumerable<T> values
|
||||
)
|
||||
{
|
||||
return Array.AsReadOnly(values.ToArray());
|
||||
}
|
||||
|
||||
private static void ValidateChildren(
|
||||
XmlTreeDocument document,
|
||||
XElement element,
|
||||
params string[] allowedNames
|
||||
)
|
||||
{
|
||||
HashSet<string> allowed = new HashSet<string>(
|
||||
allowedNames,
|
||||
StringComparer.Ordinal);
|
||||
XElement? unsupported = element.Elements().FirstOrDefault(
|
||||
value => !allowed.Contains(value.Name.LocalName));
|
||||
if (unsupported is not null)
|
||||
{
|
||||
throw CreateError(
|
||||
document,
|
||||
unsupported,
|
||||
$"Unsupported <{unsupported.Name}> child in <{element.Name}>.");
|
||||
}
|
||||
}
|
||||
|
||||
private static InvalidDataException CreateError(
|
||||
XmlTreeDocument document,
|
||||
XElement element,
|
||||
string message
|
||||
)
|
||||
{
|
||||
IXmlLineInfo lineInfo = element;
|
||||
string location = lineInfo.HasLineInfo()
|
||||
? $"({lineInfo.LineNumber},{lineInfo.LinePosition})"
|
||||
: string.Empty;
|
||||
return new InvalidDataException(
|
||||
$"{document.SourcePath}{location}: {message}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlTypeDefinition"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
internal sealed record GlTypeDefinition(
|
||||
string Name,
|
||||
string DeclarationText,
|
||||
string? Api,
|
||||
string? RequiredType,
|
||||
string? Category,
|
||||
string? Comment,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
internal sealed record GlKindDefinition(
|
||||
string Name,
|
||||
string Description,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegistryCli"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL
|
||||
{
|
||||
/// <summary>
|
||||
/// Presents the parsed registry model as the primary CLI surface.
|
||||
/// </summary>
|
||||
internal static class RegistryCli
|
||||
{
|
||||
|
||||
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(
|
||||
GlRegistryModel registry,
|
||||
string command,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
return command switch
|
||||
{
|
||||
"SUMMARY" or "REGISTRY" or "MODEL" => QuerySummary(
|
||||
registry,
|
||||
arguments),
|
||||
"LIST" or "LS" => QueryList(registry, arguments),
|
||||
"FIND" or "SEARCH" => QueryFind(registry, arguments),
|
||||
"SHOW" or "INFO" => QueryShow(registry, arguments),
|
||||
_ => WriteUsageError($"Unknown registry command '{command}'."),
|
||||
};
|
||||
}
|
||||
|
||||
private static int QuerySummary(
|
||||
GlRegistryModel registry,
|
||||
IReadOnlyCollection<string> arguments
|
||||
)
|
||||
{
|
||||
if (!ExpectNoArguments("summary", arguments))
|
||||
{
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
GlRequirementBlock[] requirements = registry.Features.SelectMany(
|
||||
value => value.Requirements)
|
||||
.Concat(
|
||||
registry.Extensions.SelectMany(
|
||||
value => value.Requirements))
|
||||
.ToArray();
|
||||
Console.WriteLine("OpenGL registry");
|
||||
WriteProperty("source", registry.SourcePath);
|
||||
WriteProperty("types", registry.Types.Count);
|
||||
WriteProperty("kinds", registry.Kinds.Count);
|
||||
WriteProperty("enum groups", registry.EnumGroups.Count);
|
||||
WriteProperty("enum blocks", registry.EnumBlocks.Count);
|
||||
WriteProperty(
|
||||
"enums",
|
||||
registry.EnumBlocks.Sum(value => value.Enums.Count));
|
||||
WriteProperty(
|
||||
"unused enum ranges",
|
||||
registry.EnumBlocks.Sum(value => value.UnusedRanges.Count));
|
||||
WriteProperty("commands", registry.Commands.Count);
|
||||
WriteProperty(
|
||||
"parameters",
|
||||
registry.Commands.Sum(value => value.Parameters.Count));
|
||||
WriteProperty("features", registry.Features.Count);
|
||||
WriteProperty("extensions", registry.Extensions.Count);
|
||||
WriteProperty(
|
||||
"require blocks",
|
||||
requirements.Count(
|
||||
value => value.Operation == GlRequirementOperation.Require));
|
||||
WriteProperty(
|
||||
"remove blocks",
|
||||
requirements.Count(
|
||||
value => value.Operation == GlRequirementOperation.Remove));
|
||||
WriteProperty(
|
||||
"named references",
|
||||
requirements.Sum(value => value.References.Count));
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static int QueryList(
|
||||
GlRegistryModel registry,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
if (!TryReadListOptions(
|
||||
arguments,
|
||||
out RegistryObjectKind kind,
|
||||
out string? filter,
|
||||
out int limit,
|
||||
out string? error))
|
||||
{
|
||||
return WriteUsageError(error);
|
||||
}
|
||||
|
||||
RegistrySearchResult[] values = Enumerate(registry, kind)
|
||||
.Where(
|
||||
value => filter is null || value.Name.Contains(
|
||||
filter,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
if (values.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
filter is null
|
||||
? $"No {ReadKindName(kind, true)} are registered."
|
||||
: $"No {ReadKindName(kind, true)} contain '{filter}'.");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
int displayedCount = Math.Min(values.Length, limit);
|
||||
for (int index = 0; index < displayedCount; index++)
|
||||
{
|
||||
WriteCompactResult(index + 1, values[index]);
|
||||
}
|
||||
|
||||
WriteTruncation(values.Length, displayedCount);
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static int QueryFind(
|
||||
GlRegistryModel registry,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
if (!TryReadFindOptions(
|
||||
arguments,
|
||||
out string text,
|
||||
out bool exact,
|
||||
out int limit,
|
||||
out string? error))
|
||||
{
|
||||
return WriteUsageError(error);
|
||||
}
|
||||
|
||||
RegistrySearchResult[] values = Enumerate(registry)
|
||||
.Where(
|
||||
value => exact
|
||||
? string.Equals(
|
||||
value.Name,
|
||||
text,
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
: value.Name.Contains(
|
||||
text,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(
|
||||
value => value.Name,
|
||||
StringComparer.Ordinal)
|
||||
.ThenBy(value => value.Kind)
|
||||
.ToArray();
|
||||
if (values.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"No registry names match '{text}'.");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
int displayedCount = Math.Min(values.Length, limit);
|
||||
for (int index = 0; index < displayedCount; index++)
|
||||
{
|
||||
WriteCompactResult(index + 1, values[index]);
|
||||
}
|
||||
|
||||
WriteTruncation(values.Length, displayedCount);
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static int QueryShow(
|
||||
GlRegistryModel registry,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
if (!TryReadShowOptions(
|
||||
arguments,
|
||||
out RegistryObjectKind kind,
|
||||
out string name,
|
||||
out int limit,
|
||||
out string? error))
|
||||
{
|
||||
return WriteUsageError(error);
|
||||
}
|
||||
|
||||
RegistrySearchResult[] values = Enumerate(registry, kind)
|
||||
.Where(
|
||||
value => string.Equals(
|
||||
value.Name,
|
||||
name,
|
||||
StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
if (values.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"No {ReadKindName(kind, false)} named '{name}' is registered.");
|
||||
Console.Error.WriteLine($"Try: find {name}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (values.Length > 1)
|
||||
{
|
||||
Console.WriteLine($"[{index + 1}]");
|
||||
}
|
||||
|
||||
WriteDetailedResult(values[index], limit);
|
||||
}
|
||||
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static IEnumerable<RegistrySearchResult> Enumerate(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
foreach (RegistryObjectKind kind in Enum.GetValues<RegistryObjectKind>())
|
||||
{
|
||||
foreach (RegistrySearchResult value in Enumerate(registry, kind))
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<RegistrySearchResult> Enumerate(
|
||||
GlRegistryModel registry,
|
||||
RegistryObjectKind kind
|
||||
)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
RegistryObjectKind.Type => registry.Types.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.Kind => registry.Kinds.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.EnumGroup => registry.EnumGroups.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.Enum => registry.EnumBlocks.SelectMany(
|
||||
value => value.Enums).Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.Command => registry.Commands.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.Feature => registry.Features.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
RegistryObjectKind.Extension => registry.Extensions.Select(
|
||||
value => new RegistrySearchResult(
|
||||
kind,
|
||||
value.Name,
|
||||
value,
|
||||
value.Element)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||
};
|
||||
}
|
||||
|
||||
private static void WriteCompactResult(
|
||||
int index,
|
||||
RegistrySearchResult result
|
||||
)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[{index}] {ReadKindName(result.Kind, false)}: {result.Name}");
|
||||
switch (result.Value)
|
||||
{
|
||||
case GlTypeDefinition type:
|
||||
WriteProperty("declaration", type.DeclarationText);
|
||||
break;
|
||||
|
||||
case GlKindDefinition kind:
|
||||
WriteProperty("description", kind.Description);
|
||||
break;
|
||||
|
||||
case GlEnumGroup group:
|
||||
WriteProperty("members", group.Members.Count);
|
||||
break;
|
||||
|
||||
case GlEnumDefinition enumValue:
|
||||
WriteOptionalProperty("value", enumValue.Value);
|
||||
WriteOptionalProperty("alias", enumValue.Alias);
|
||||
WriteOptionalProperty("group", enumValue.Group);
|
||||
break;
|
||||
|
||||
case GlCommandDefinition command:
|
||||
WriteProperty("declaration", ReadCommandDeclaration(command));
|
||||
break;
|
||||
|
||||
case GlFeatureDefinition feature:
|
||||
WriteProperty("api", feature.Api);
|
||||
WriteProperty("version", feature.Number);
|
||||
break;
|
||||
|
||||
case GlExtensionDefinition extension:
|
||||
WriteOptionalProperty("supported", extension.SupportedApis);
|
||||
break;
|
||||
}
|
||||
|
||||
WriteProperty("line", result.Element.Location.LineNumber);
|
||||
}
|
||||
|
||||
private static void WriteDetailedResult(
|
||||
RegistrySearchResult result,
|
||||
int referenceLimit
|
||||
)
|
||||
{
|
||||
switch (result.Value)
|
||||
{
|
||||
case GlTypeDefinition type:
|
||||
WriteType(type);
|
||||
break;
|
||||
|
||||
case GlKindDefinition kind:
|
||||
WriteKind(kind);
|
||||
break;
|
||||
|
||||
case GlEnumGroup group:
|
||||
WriteEnumGroup(group, referenceLimit);
|
||||
break;
|
||||
|
||||
case GlEnumDefinition enumValue:
|
||||
WriteEnum(enumValue);
|
||||
break;
|
||||
|
||||
case GlCommandDefinition command:
|
||||
WriteCommand(command);
|
||||
break;
|
||||
|
||||
case GlFeatureDefinition feature:
|
||||
Console.WriteLine($"feature: {feature.Name}");
|
||||
WriteProperty("api", feature.Api);
|
||||
WriteProperty("version", feature.Number);
|
||||
WriteOptionalProperty("protect", feature.Protect);
|
||||
WriteOptionalProperty("comment", feature.Comment);
|
||||
WriteProperty("line", feature.Element.Location.LineNumber);
|
||||
WriteRequirements(feature.Requirements, referenceLimit);
|
||||
break;
|
||||
|
||||
case GlExtensionDefinition extension:
|
||||
Console.WriteLine($"extension: {extension.Name}");
|
||||
WriteOptionalProperty("supported", extension.SupportedApis);
|
||||
WriteOptionalProperty("protect", extension.Protect);
|
||||
WriteOptionalProperty("comment", extension.Comment);
|
||||
WriteProperty("line", extension.Element.Location.LineNumber);
|
||||
WriteRequirements(extension.Requirements, referenceLimit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteType(
|
||||
GlTypeDefinition value
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"type: {value.Name}");
|
||||
WriteProperty("declaration", value.DeclarationText);
|
||||
WriteOptionalProperty("api", value.Api);
|
||||
WriteOptionalProperty("requires", value.RequiredType);
|
||||
WriteOptionalProperty("category", value.Category);
|
||||
WriteOptionalProperty("comment", value.Comment);
|
||||
WriteProperty("line", value.Element.Location.LineNumber);
|
||||
}
|
||||
|
||||
private static void WriteKind(
|
||||
GlKindDefinition value
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"kind: {value.Name}");
|
||||
WriteProperty("description", value.Description);
|
||||
WriteProperty("line", value.Element.Location.LineNumber);
|
||||
}
|
||||
|
||||
private static void WriteEnumGroup(
|
||||
GlEnumGroup value,
|
||||
int limit
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"enum group: {value.Name}");
|
||||
WriteOptionalProperty("comment", value.Comment);
|
||||
WriteProperty("line", value.Element.Location.LineNumber);
|
||||
WriteProperty("members", value.Members.Count);
|
||||
int displayedCount = Math.Min(value.Members.Count, limit);
|
||||
for (int index = 0; index < displayedCount; index++)
|
||||
{
|
||||
Console.WriteLine($" [{index + 1}] {value.Members[index].Name}");
|
||||
}
|
||||
|
||||
WriteIndentedTruncation(value.Members.Count, displayedCount, 4);
|
||||
}
|
||||
|
||||
private static void WriteEnum(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"enum: {value.Name}");
|
||||
WriteOptionalProperty("value", value.Value);
|
||||
WriteOptionalProperty("api", value.Api);
|
||||
WriteOptionalProperty("type suffix", value.TypeSuffix);
|
||||
WriteOptionalProperty("group", value.Group);
|
||||
WriteOptionalProperty("alias", value.Alias);
|
||||
WriteOptionalProperty("comment", value.Comment);
|
||||
WriteProperty("line", value.Element.Location.LineNumber);
|
||||
}
|
||||
|
||||
private static void WriteCommand(
|
||||
GlCommandDefinition value
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"command: {value.Name}");
|
||||
WriteProperty("declaration", ReadCommandDeclaration(value));
|
||||
WriteProperty("line", value.Element.Location.LineNumber);
|
||||
WriteProperty("return declaration", value.Return.DeclarationText);
|
||||
WriteOptionalProperty("return type", value.Return.TypeName);
|
||||
WriteOptionalProperty("return group", value.Return.Group);
|
||||
WriteOptionalProperty("return kind", value.Return.Kind);
|
||||
WriteOptionalProperty("return class", value.Return.ObjectClass);
|
||||
WriteOptionalProperty("comment", value.Comment);
|
||||
WriteProperty("parameters", value.Parameters.Count);
|
||||
for (int index = 0; index < value.Parameters.Count; index++)
|
||||
{
|
||||
GlParameterDefinition parameter = value.Parameters[index];
|
||||
Console.WriteLine($" [{index + 1}] {parameter.Name}");
|
||||
WriteIndentedProperty("declaration", parameter.DeclarationText, 6);
|
||||
WriteIndentedOptionalProperty("type", parameter.TypeName, 6);
|
||||
WriteIndentedOptionalProperty("group", parameter.Group, 6);
|
||||
WriteIndentedOptionalProperty("kind", parameter.Kind, 6);
|
||||
WriteIndentedOptionalProperty("class", parameter.ObjectClass, 6);
|
||||
WriteIndentedOptionalProperty("length", parameter.Length, 6);
|
||||
WriteIndentedProperty("line", parameter.Element.Location.LineNumber, 6);
|
||||
}
|
||||
|
||||
WriteNamedReferences("aliases", value.Aliases);
|
||||
WriteNamedReferences("vector equivalents", value.VectorEquivalents);
|
||||
if (value.Protocols.Count != 0)
|
||||
{
|
||||
WriteProperty("protocols", value.Protocols.Count);
|
||||
foreach (GlCommandProtocol protocol in value.Protocols)
|
||||
{
|
||||
Console.WriteLine($" {protocol.Type}: {protocol.Opcode}");
|
||||
WriteIndentedOptionalProperty("name", protocol.Name, 6);
|
||||
WriteIndentedOptionalProperty("comment", protocol.Comment, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteRequirements(
|
||||
IReadOnlyList<GlRequirementBlock> requirements,
|
||||
int referenceLimit
|
||||
)
|
||||
{
|
||||
WriteProperty("requirement blocks", requirements.Count);
|
||||
for (int index = 0; index < requirements.Count; index++)
|
||||
{
|
||||
GlRequirementBlock block = requirements[index];
|
||||
Console.WriteLine(
|
||||
$" [{index + 1}] " +
|
||||
$"{(block.Operation == GlRequirementOperation.Require ? "require" : "remove")}");
|
||||
WriteIndentedOptionalProperty("api", block.Api, 6);
|
||||
WriteIndentedOptionalProperty("profile", block.Profile, 6);
|
||||
WriteIndentedOptionalProperty("comment", block.Comment, 6);
|
||||
WriteIndentedProperty("references", block.References.Count, 6);
|
||||
int displayedCount = Math.Min(block.References.Count, referenceLimit);
|
||||
for (int referenceIndex = 0;
|
||||
referenceIndex < displayedCount;
|
||||
referenceIndex++)
|
||||
{
|
||||
GlRegistryReference reference = block.References[referenceIndex];
|
||||
Console.WriteLine(
|
||||
$" {ReadReferenceKind(reference.Kind)}: {reference.Name}");
|
||||
}
|
||||
|
||||
WriteIndentedTruncation(block.References.Count, displayedCount, 8);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteNamedReferences(
|
||||
string name,
|
||||
IReadOnlyList<GlNamedReference> values
|
||||
)
|
||||
{
|
||||
if (values.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteProperty(name, values.Count);
|
||||
foreach (GlNamedReference value in values)
|
||||
{
|
||||
Console.WriteLine($" - {value.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadCommandDeclaration(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
string returnType = command.Return.DeclarationText.Replace(
|
||||
command.Name,
|
||||
string.Empty,
|
||||
StringComparison.Ordinal).Trim();
|
||||
return $"{returnType} {command.Name}(" +
|
||||
string.Join(", ", command.Parameters.Select(value => value.DeclarationText)) +
|
||||
")";
|
||||
}
|
||||
|
||||
private static bool TryReadListOptions(
|
||||
IReadOnlyList<string> arguments,
|
||||
out RegistryObjectKind kind,
|
||||
out string? filter,
|
||||
out int limit,
|
||||
out string? error
|
||||
)
|
||||
{
|
||||
kind = default;
|
||||
if (arguments.Count == 0 || !TryReadKind(arguments[0], out kind))
|
||||
{
|
||||
filter = null;
|
||||
limit = 0;
|
||||
error = "list requires a registry category: types, kinds, groups, enums, " +
|
||||
"commands, features, or extensions.";
|
||||
return false;
|
||||
}
|
||||
|
||||
filter = null;
|
||||
limit = ShowAllResults;
|
||||
int index = 1;
|
||||
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") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
error = $"Unknown or invalid list option '{argument}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadFindOptions(
|
||||
IReadOnlyList<string> arguments,
|
||||
out string text,
|
||||
out bool exact,
|
||||
out int limit,
|
||||
out string? error
|
||||
)
|
||||
{
|
||||
if (arguments.Count == 0 || arguments[0].StartsWith(
|
||||
"--",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
text = string.Empty;
|
||||
exact = false;
|
||||
limit = 0;
|
||||
error = "find requires a registry name or partial name.";
|
||||
return false;
|
||||
}
|
||||
|
||||
text = arguments[0];
|
||||
exact = false;
|
||||
limit = ShowAllResults;
|
||||
int index = 1;
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
string argument = arguments[index++];
|
||||
if (argument == "--exact")
|
||||
{
|
||||
exact = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
error = $"Unknown or invalid find option '{argument}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadShowOptions(
|
||||
IReadOnlyList<string> arguments,
|
||||
out RegistryObjectKind kind,
|
||||
out string name,
|
||||
out int limit,
|
||||
out string? error
|
||||
)
|
||||
{
|
||||
kind = default;
|
||||
if (arguments.Count < 2 || !TryReadKind(arguments[0], out kind))
|
||||
{
|
||||
name = string.Empty;
|
||||
limit = 0;
|
||||
error = "show requires a registry category and exact name.";
|
||||
return false;
|
||||
}
|
||||
|
||||
name = arguments[1];
|
||||
limit = ShowAllResults;
|
||||
int index = 2;
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
string argument = arguments[index++];
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
error = $"Unknown or invalid show option '{argument}'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadKind(
|
||||
string value,
|
||||
out RegistryObjectKind kind
|
||||
)
|
||||
{
|
||||
kind = value.ToUpperInvariant() switch
|
||||
{
|
||||
"TYPE" or "TYPES" => RegistryObjectKind.Type,
|
||||
"KIND" or "KINDS" => RegistryObjectKind.Kind,
|
||||
"GROUP" or "GROUPS" or "ENUMGROUP" or "ENUMGROUPS" =>
|
||||
RegistryObjectKind.EnumGroup,
|
||||
"ENUM" or "ENUMS" => RegistryObjectKind.Enum,
|
||||
"COMMAND" or "COMMANDS" or "FUNCTION" or "FUNCTIONS" =>
|
||||
RegistryObjectKind.Command,
|
||||
"FEATURE" or "FEATURES" or "VERSION" or "VERSIONS" =>
|
||||
RegistryObjectKind.Feature,
|
||||
"EXTENSION" or "EXTENSIONS" => RegistryObjectKind.Extension,
|
||||
_ => default,
|
||||
};
|
||||
return value.ToUpperInvariant() is
|
||||
"TYPE" or "TYPES" or
|
||||
"KIND" or "KINDS" or
|
||||
"GROUP" or "GROUPS" or "ENUMGROUP" or "ENUMGROUPS" or
|
||||
"ENUM" or "ENUMS" or
|
||||
"COMMAND" or "COMMANDS" or "FUNCTION" or "FUNCTIONS" or
|
||||
"FEATURE" or "FEATURES" or "VERSION" or "VERSIONS" or
|
||||
"EXTENSION" or "EXTENSIONS";
|
||||
}
|
||||
|
||||
private static string ReadKindName(
|
||||
RegistryObjectKind kind,
|
||||
bool plural
|
||||
)
|
||||
{
|
||||
string name = kind switch
|
||||
{
|
||||
RegistryObjectKind.Type => "type",
|
||||
RegistryObjectKind.Kind => "kind",
|
||||
RegistryObjectKind.EnumGroup => "enum group",
|
||||
RegistryObjectKind.Enum => "enum",
|
||||
RegistryObjectKind.Command => "command",
|
||||
RegistryObjectKind.Feature => "feature",
|
||||
RegistryObjectKind.Extension => "extension",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||
};
|
||||
return plural ? $"{name}s" : name;
|
||||
}
|
||||
|
||||
private static string ReadReferenceKind(
|
||||
GlRegistryReferenceKind kind
|
||||
)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
GlRegistryReferenceKind.Type => "type",
|
||||
GlRegistryReferenceKind.Enum => "enum",
|
||||
GlRegistryReferenceKind.Command => "command",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryReadPositiveInt(
|
||||
IReadOnlyList<string> arguments,
|
||||
ref int index,
|
||||
out int value
|
||||
)
|
||||
{
|
||||
if (index >= arguments.Count)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return int.TryParse(
|
||||
arguments[index++],
|
||||
NumberStyles.None,
|
||||
CultureInfo.InvariantCulture,
|
||||
out value) && value > 0;
|
||||
}
|
||||
|
||||
private static bool ExpectNoArguments(
|
||||
string command,
|
||||
IReadOnlyCollection<string> arguments
|
||||
)
|
||||
{
|
||||
if (arguments.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"error: {command} does not accept arguments.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int WriteUsageError(
|
||||
string? error
|
||||
)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {error}");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
private static void WriteTruncation(
|
||||
int totalCount,
|
||||
int displayedCount
|
||||
)
|
||||
{
|
||||
if (displayedCount != totalCount)
|
||||
{
|
||||
Console.WriteLine($"... {totalCount - displayedCount} more result(s)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteIndentedTruncation(
|
||||
int totalCount,
|
||||
int displayedCount,
|
||||
int indentation
|
||||
)
|
||||
{
|
||||
if (displayedCount != totalCount)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"{new string(' ', indentation)}... " +
|
||||
$"{totalCount - displayedCount} more item(s)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteProperty(
|
||||
string name,
|
||||
object value
|
||||
)
|
||||
{
|
||||
WriteIndentedProperty(name, value, 2);
|
||||
}
|
||||
|
||||
private static void WriteOptionalProperty(
|
||||
string name,
|
||||
string? value
|
||||
)
|
||||
{
|
||||
WriteIndentedOptionalProperty(name, value, 2);
|
||||
}
|
||||
|
||||
private static void WriteIndentedProperty(
|
||||
string name,
|
||||
object value,
|
||||
int indentation
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"{new string(' ', indentation)}{name}: {value}");
|
||||
}
|
||||
|
||||
private static void WriteIndentedOptionalProperty(
|
||||
string name,
|
||||
string? value,
|
||||
int indentation
|
||||
)
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
WriteIndentedProperty(name, value, indentation);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record RegistrySearchResult(
|
||||
RegistryObjectKind Kind,
|
||||
string Name,
|
||||
object Value,
|
||||
GlRegistryElement Element
|
||||
);
|
||||
|
||||
private enum RegistryObjectKind
|
||||
{
|
||||
Type,
|
||||
Kind,
|
||||
EnumGroup,
|
||||
Enum,
|
||||
Command,
|
||||
Feature,
|
||||
Extension,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="XmlTreeDocument"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Tree
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the original XML text alongside its parsed tree.
|
||||
/// </summary>
|
||||
internal sealed record XmlTreeDocument(
|
||||
string SourcePath,
|
||||
string SourceText,
|
||||
XDocument Tree
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="XmlTreeReader"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Tree
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads XML as a generic tree without interpreting OpenGL semantics.
|
||||
/// </summary>
|
||||
internal static class XmlTreeReader
|
||||
{
|
||||
|
||||
public static XmlTreeDocument Load(
|
||||
string path
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
string sourcePath = Path.GetFullPath(path);
|
||||
string sourceText = File.ReadAllText(sourcePath);
|
||||
XmlReaderSettings settings = new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit,
|
||||
IgnoreComments = false,
|
||||
IgnoreWhitespace = false,
|
||||
XmlResolver = null,
|
||||
};
|
||||
|
||||
XDocument tree;
|
||||
using (StringReader textReader = new StringReader(sourceText))
|
||||
using (XmlReader reader = XmlReader.Create(textReader, settings, sourcePath))
|
||||
{
|
||||
tree = XDocument.Load(
|
||||
reader,
|
||||
LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
|
||||
}
|
||||
|
||||
if (tree.Root is null)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"XML document '{sourcePath}' does not contain a root element.");
|
||||
}
|
||||
|
||||
return new XmlTreeDocument(sourcePath, sourceText, tree);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
# Khronos OpenGL Registry input
|
||||
|
||||
This directory intentionally does not track the third-party Khronos registry files. Before building or running `StgSharp.GenerateGL`, supply a registry snapshot from the canonical [KhronosGroup/OpenGL-Registry](https://github.com/KhronosGroup/OpenGL-Registry) repository.
|
||||
|
||||
1. Clone or download one coherent revision of `KhronosGroup/OpenGL-Registry`.
|
||||
2. Copy the contents of its `xml/` directory directly into this directory.
|
||||
3. Keep the upstream directory layout unchanged.
|
||||
|
||||
The generator currently requires this exact path:
|
||||
|
||||
```text
|
||||
tools/Nitload.GenerateGL/xml/gl.xml
|
||||
```
|
||||
|
||||
Do not introduce an additional directory level such as `xml/xml/gl.xml`. Files such as `glx.xml`, `wgl.xml`, `registry.rnc`, and the upstream registry scripts may be copied alongside `gl.xml`, but the current desktop OpenGL generator only consumes `gl.xml`.
|
||||
|
||||
For reproducible generation, use files from the same upstream commit and record that commit hash with the generated-code change. All copied files in this directory are ignored by Git except this README.
|
||||
Reference in New Issue
Block a user