opengl source gen tool works
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)");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Generation;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
@@ -23,14 +24,15 @@ namespace StgSharp.GenerateGL
|
||||
private const int QueryErrorExitCode = 1;
|
||||
private const int SuccessExitCode = 0;
|
||||
private const int UsageErrorExitCode = 2;
|
||||
private const string GeneratedFileMarker = "// <auto-generated>";
|
||||
|
||||
public static int Generate(
|
||||
GlRegistryModel registry,
|
||||
GlRegistryProjection projection,
|
||||
IReadOnlyList<string> arguments,
|
||||
out GeneratedFileSet? generatedFiles
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
ArgumentNullException.ThrowIfNull(arguments);
|
||||
generatedFiles = null;
|
||||
|
||||
@@ -40,17 +42,24 @@ namespace StgSharp.GenerateGL
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
if (arguments.Count != 0)
|
||||
if (arguments.Count > 1)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"error: unknown generate option '{arguments[0]}'.");
|
||||
Console.Error.WriteLine("error: generate accepts at most one output directory.");
|
||||
Console.Error.WriteLine("Run 'generate --help' for command information.");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
string? outputDirectory = arguments.Count == 1 ? arguments[0] : null;
|
||||
string? resolvedOutputDirectory = null;
|
||||
try
|
||||
{
|
||||
generatedFiles = OpenGlGenerator.Generate(registry);
|
||||
generatedFiles = OpenGlGenerator.Generate(projection);
|
||||
if (outputDirectory is not null)
|
||||
{
|
||||
resolvedOutputDirectory = WriteGeneratedFiles(
|
||||
generatedFiles,
|
||||
outputDirectory);
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
@@ -62,14 +71,136 @@ namespace StgSharp.GenerateGL
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
catch (NotSupportedException exception)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {exception.Message}");
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"Generated {generatedFiles.Files.Count} file(s) in memory.");
|
||||
Console.WriteLine("Use 'list files' and 'preview <filename>' to inspect them.");
|
||||
Console.WriteLine("No files were written to disk.");
|
||||
Console.WriteLine(
|
||||
resolvedOutputDirectory is null
|
||||
? "No files were written to disk."
|
||||
: $"Wrote {generatedFiles.Files.Count} file(s) to " +
|
||||
$"'{resolvedOutputDirectory}'.");
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static string WriteGeneratedFiles(
|
||||
GeneratedFileSet files,
|
||||
string outputDirectory
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(files);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(outputDirectory);
|
||||
|
||||
string directory = Path.TrimEndingDirectorySeparator(
|
||||
Path.GetFullPath(outputDirectory));
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
$"Output directory '{directory}' does not exist.");
|
||||
}
|
||||
|
||||
StringComparison pathComparison = OperatingSystem.IsWindows()
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
List<StagedGeneratedFile> stagedFiles = new List<StagedGeneratedFile>(
|
||||
files.Files.Count);
|
||||
try
|
||||
{
|
||||
foreach (GeneratedFile file in files.Files)
|
||||
{
|
||||
string fileName = Path.GetFileName(file.Name);
|
||||
if (!string.Equals(fileName, file.Name, StringComparison.Ordinal) ||
|
||||
string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Generated file name '{file.Name}' is not a plain file name.");
|
||||
}
|
||||
|
||||
string targetPath = Path.GetFullPath(Path.Combine(directory, fileName));
|
||||
if (!string.Equals(
|
||||
Path.GetDirectoryName(targetPath),
|
||||
directory,
|
||||
pathComparison))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Generated file '{file.Name}' escapes the output directory.");
|
||||
}
|
||||
|
||||
EnsureTargetCanBeReplaced(targetPath);
|
||||
string temporaryPath = Path.Combine(
|
||||
directory,
|
||||
$".{fileName}.{Guid.NewGuid():N}.tmp");
|
||||
using (FileStream stream = new FileStream(
|
||||
temporaryPath,
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write,
|
||||
FileShare.None))
|
||||
using (StreamWriter writer = new StreamWriter(
|
||||
stream,
|
||||
new UTF8Encoding(false)))
|
||||
{
|
||||
writer.Write(file.Content);
|
||||
}
|
||||
|
||||
stagedFiles.Add(new StagedGeneratedFile(temporaryPath, targetPath));
|
||||
}
|
||||
|
||||
foreach (StagedGeneratedFile file in stagedFiles)
|
||||
{
|
||||
File.Move(file.TemporaryPath, file.TargetPath, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (StagedGeneratedFile file in stagedFiles)
|
||||
{
|
||||
if (File.Exists(file.TemporaryPath))
|
||||
{
|
||||
File.Delete(file.TemporaryPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private static void EnsureTargetCanBeReplaced(
|
||||
string targetPath
|
||||
)
|
||||
{
|
||||
if (!File.Exists(targetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? firstLine = File.ReadLines(targetPath).FirstOrDefault();
|
||||
if (!string.Equals(firstLine, GeneratedFileMarker, StringComparison.Ordinal))
|
||||
{
|
||||
throw new IOException(
|
||||
$"Refusing to overwrite non-generated file '{targetPath}'.");
|
||||
}
|
||||
}
|
||||
|
||||
public static int ListFiles(
|
||||
GeneratedFileSet files,
|
||||
IReadOnlyList<string> arguments
|
||||
@@ -90,7 +221,7 @@ namespace StgSharp.GenerateGL
|
||||
return QueryErrorExitCode;
|
||||
}
|
||||
|
||||
Console.WriteLine("Generated files (memory only)");
|
||||
Console.WriteLine("Generated files held in the current session");
|
||||
for (int index = 0; index < files.Files.Count; index++)
|
||||
{
|
||||
GeneratedFile file = files.Files[index];
|
||||
@@ -141,16 +272,23 @@ namespace StgSharp.GenerateGL
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" generate");
|
||||
Console.WriteLine(" generate <output-directory>");
|
||||
Console.WriteLine(" generate --help");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Status:");
|
||||
Console.WriteLine(" Output is retained in the current process only.");
|
||||
Console.WriteLine(" Running 'generate' does not create or modify disk files.");
|
||||
Console.WriteLine("Output:");
|
||||
Console.WriteLine(" Without a directory, output is retained in memory only.");
|
||||
Console.WriteLine(" The output directory must already exist and be writable.");
|
||||
Console.WriteLine(" Existing files are replaced only when marked as generated.");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Inspection:");
|
||||
Console.WriteLine(" list files");
|
||||
Console.WriteLine(" preview <filename>");
|
||||
}
|
||||
|
||||
private sealed record StagedGeneratedFile(
|
||||
string TemporaryPath,
|
||||
string TargetPath
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlGetValueApiFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces desktop state query pointer families with typed Span overloads.
|
||||
/// </summary>
|
||||
internal sealed class GlGetValueApiFilter : IGlApiFilter
|
||||
{
|
||||
|
||||
private static readonly Regex _pattern = new Regex(
|
||||
@"^glGet(?<type>Boolean|Double|Float|Integer64|Integer)(?<indexed>i_)?v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
GlCommandDefinition command,
|
||||
IReadOnlyList<ManagedMethodDefinition> methods,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
Match match = _pattern.Match(command.Name);
|
||||
if (!match.Success)
|
||||
{
|
||||
return methods;
|
||||
}
|
||||
|
||||
bool isIndexed = match.Groups["indexed"].Success;
|
||||
int prefixCount = isIndexed ? 2 : 1;
|
||||
if (typeMapper.ReadReturnType(command) != "void" ||
|
||||
command.Parameters.Count != prefixCount + 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"State query '{command.Name}' does not match its recognized shape.");
|
||||
}
|
||||
|
||||
GlParameterDefinition output = command.Parameters[^1];
|
||||
string elementType = ReadElementType(match.Groups["type"].Value);
|
||||
string expectedLength = $"COMPSIZE({command.Parameters[0].Name})";
|
||||
if (output.DeclarationText.Contains("const", StringComparison.Ordinal) ||
|
||||
typeMapper.ReadParameterType(output) != $"{elementType}*" ||
|
||||
!string.Equals(output.Length, expectedLength, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"State query '{command.Name}' has inconsistent output metadata.");
|
||||
}
|
||||
|
||||
List<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>();
|
||||
List<ManagedArgument> arguments = new List<ManagedArgument>();
|
||||
for (int index = 0; index < prefixCount; index++)
|
||||
{
|
||||
GlParameterDefinition source = command.Parameters[index];
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
typeMapper.ReadParameterType(source),
|
||||
source.Name,
|
||||
source));
|
||||
arguments.Add(new ParameterArgument(index));
|
||||
}
|
||||
|
||||
parameters.Add(
|
||||
new ManagedParameterDefinition(
|
||||
$"Span<{elementType}>",
|
||||
"values",
|
||||
output));
|
||||
return Array.AsReadOnly(
|
||||
new[]
|
||||
{
|
||||
new ManagedMethodDefinition(
|
||||
"GetValue",
|
||||
"void",
|
||||
Array.AsReadOnly(parameters.ToArray()),
|
||||
command,
|
||||
new QuerySpanInvocation(
|
||||
Array.AsReadOnly(arguments.ToArray()),
|
||||
parameters.Count - 1,
|
||||
elementType)),
|
||||
});
|
||||
}
|
||||
|
||||
private static string ReadElementType(
|
||||
string name
|
||||
)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"Boolean" => "byte",
|
||||
"Double" => "double",
|
||||
"Float" => "float",
|
||||
"Integer" => "int",
|
||||
"Integer64" => "long",
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Unsupported state query component type '{name}'."),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,10 @@ namespace StgSharp.GenerateGL.Generation
|
||||
@"^glVertexAttrib(?<width>[1-4])(?<type>d|f|s)(?<vector>v?)$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _redundantVertexAttributeVectorPattern = new Regex(
|
||||
@"^glVertexAttrib(?:I[1-4](?:i|ui)|L[1-4]d|P[1-4]ui|4Nub)v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly string[] _components = { "X", "Y", "Z", "W" };
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Apply(
|
||||
@@ -46,6 +50,11 @@ namespace StgSharp.GenerateGL.Generation
|
||||
ArgumentNullException.ThrowIfNull(methods);
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
|
||||
if (_redundantVertexAttributeVectorPattern.IsMatch(command.Name))
|
||||
{
|
||||
return Array.Empty<ManagedMethodDefinition>();
|
||||
}
|
||||
|
||||
Match matrixMatch = _matrixUniformPattern.Match(command.Name);
|
||||
if (matrixMatch.Success)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,8 @@ namespace StgSharp.GenerateGL.Generation
|
||||
IReadOnlyList<ManagedParameterDefinition> Parameters,
|
||||
GlCommandDefinition NativeCommand,
|
||||
ManagedInvocation Invocation,
|
||||
bool WrapHandleReturn = false
|
||||
bool WrapHandleReturn = false,
|
||||
string? DocumentationPage = null
|
||||
);
|
||||
|
||||
internal sealed record ManagedParameterDefinition(
|
||||
@@ -53,6 +54,12 @@ namespace StgSharp.GenerateGL.Generation
|
||||
bool IsSpan
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal sealed record QuerySpanInvocation(
|
||||
IReadOnlyList<ManagedArgument> PrefixArguments,
|
||||
int SpanParameterIndex,
|
||||
string ElementType
|
||||
) : ManagedInvocation;
|
||||
|
||||
internal abstract record ManagedArgument;
|
||||
|
||||
internal sealed record ParameterArgument(
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Naming;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
@@ -19,16 +21,21 @@ namespace StgSharp.GenerateGL.Generation
|
||||
new IGlApiFilter[]
|
||||
{
|
||||
new GlOverloadApiFilter(),
|
||||
new GlGetValueApiFilter(),
|
||||
new GlHandleApiFilter(),
|
||||
});
|
||||
private readonly GlEquivalentTypeMapper _typeMapper;
|
||||
private readonly GlManagedApiNamePolicy _namePolicy;
|
||||
|
||||
public ManagedApiPlanner(
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
GlEquivalentTypeMapper typeMapper,
|
||||
GlFunctionFamilyAnalysis familyAnalysis
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(typeMapper);
|
||||
ArgumentNullException.ThrowIfNull(familyAnalysis);
|
||||
_typeMapper = typeMapper;
|
||||
_namePolicy = new GlManagedApiNamePolicy(familyAnalysis);
|
||||
}
|
||||
|
||||
public IReadOnlyList<ManagedMethodDefinition> Plan(
|
||||
@@ -50,9 +57,23 @@ namespace StgSharp.GenerateGL.Generation
|
||||
methods = filter.Apply(command, methods, _typeMapper);
|
||||
}
|
||||
|
||||
result.AddRange(methods);
|
||||
result.AddRange(
|
||||
methods.Select(
|
||||
method => method with
|
||||
{
|
||||
Name = _namePolicy.ReadMethodName(command, method.Name),
|
||||
DocumentationPage = _namePolicy.ReadDocumentationPage(command),
|
||||
Parameters = Array.AsReadOnly(
|
||||
method.Parameters.Select(
|
||||
parameter => parameter with
|
||||
{
|
||||
Name = CSharpIdentifierPolicy.NormalizeParameterName(
|
||||
parameter.Name),
|
||||
}).ToArray()),
|
||||
}));
|
||||
}
|
||||
|
||||
ResolveNameCollisions(result);
|
||||
return Array.AsReadOnly(result.ToArray());
|
||||
}
|
||||
|
||||
@@ -78,6 +99,55 @@ namespace StgSharp.GenerateGL.Generation
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResolveNameCollisions(
|
||||
List<ManagedMethodDefinition> methods
|
||||
)
|
||||
{
|
||||
ILookup<string, int> indicesBySignature = methods
|
||||
.Select(
|
||||
(method, index) => new
|
||||
{
|
||||
Index = index,
|
||||
Signature = ReadManagedSignature(method),
|
||||
})
|
||||
.ToLookup(value => value.Signature, value => value.Index, StringComparer.Ordinal);
|
||||
foreach (IGrouping<string, int> group in indicesBySignature)
|
||||
{
|
||||
int[] indices = group.ToArray();
|
||||
if (indices.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] nativeNames = indices
|
||||
.Select(value => methods[value].NativeCommand.Name)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (nativeNames.Length != indices.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Managed overload '{group.Key}' is duplicated by one native command.");
|
||||
}
|
||||
|
||||
foreach (int index in indices)
|
||||
{
|
||||
ManagedMethodDefinition method = methods[index];
|
||||
methods[index] = method with
|
||||
{
|
||||
Name = GlManagedApiNamePolicy.ReadNativeMethodName(
|
||||
method.NativeCommand),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadManagedSignature(
|
||||
ManagedMethodDefinition method
|
||||
)
|
||||
{
|
||||
return $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})";
|
||||
}
|
||||
|
||||
private ManagedMethodDefinition CreateRawMethod(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
@@ -23,9 +24,6 @@ namespace StgSharp.GenerateGL.Generation
|
||||
internal static class OpenGlGenerator
|
||||
{
|
||||
|
||||
private const string ApiName = "gl";
|
||||
private const string CoreProfile = "core";
|
||||
|
||||
private static readonly HashSet<string> _csharpKeywords = new HashSet<string>(
|
||||
new[]
|
||||
{
|
||||
@@ -44,25 +42,41 @@ namespace StgSharp.GenerateGL.Generation
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public static GeneratedFileSet Generate(
|
||||
GlRegistryModel registry
|
||||
GlRegistryProjection projection
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
|
||||
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(registry);
|
||||
IReadOnlyList<GlCommandGroup> commandGroups = ReadCoreCommandGroups(projection);
|
||||
IReadOnlyList<GlCommandDefinition> commands = Array.AsReadOnly(
|
||||
commandGroups.SelectMany(value => value.Commands).ToArray());
|
||||
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(registry);
|
||||
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper);
|
||||
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(projection.Source);
|
||||
GlFunctionFamilyAnalysis familyAnalysis = GlFunctionFamilyAnalyzer.Analyze(projection);
|
||||
ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper, familyAnalysis);
|
||||
IReadOnlyList<ManagedMethodDefinition> managedMethods = apiPlanner.Plan(commands);
|
||||
Dictionary<string, IReadOnlyList<ManagedMethodDefinition>> methodsByCommand =
|
||||
managedMethods.GroupBy(value => value.NativeCommand.Name, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
value => value.Key,
|
||||
value => (IReadOnlyList<ManagedMethodDefinition>)Array.AsReadOnly(
|
||||
value.ToArray()),
|
||||
StringComparer.Ordinal);
|
||||
List<ManagedCommandGroup> managedGroups = commandGroups.Select(
|
||||
group => new ManagedCommandGroup(
|
||||
group,
|
||||
apiPlanner.Plan(group.Commands))).ToList();
|
||||
Array.AsReadOnly(
|
||||
group.Commands.SelectMany(
|
||||
command => methodsByCommand.TryGetValue(
|
||||
command.Name,
|
||||
out IReadOnlyList<ManagedMethodDefinition>? methods)
|
||||
? methods
|
||||
: Array.Empty<ManagedMethodDefinition>())
|
||||
.ToArray()))).ToList();
|
||||
ManagedApiPlanner.Validate(
|
||||
managedGroups.SelectMany(value => value.Methods));
|
||||
List<GeneratedFile> files = new List<GeneratedFile>
|
||||
{
|
||||
new GeneratedFile("glconst.cs", GenerateConstants(registry)),
|
||||
new GeneratedFile("glconst.cs", GenerateConstants(projection.Enums)),
|
||||
new GeneratedFile(
|
||||
"OpenglContext.g.cs",
|
||||
GenerateContext(commands, typeMapper)),
|
||||
@@ -84,67 +98,26 @@ namespace StgSharp.GenerateGL.Generation
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<GlCommandGroup> ReadCoreCommandGroups(
|
||||
GlRegistryModel registry
|
||||
GlRegistryProjection projection
|
||||
)
|
||||
{
|
||||
Dictionary<string, GlCommandDefinition> definitions = new Dictionary<string, GlCommandDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlCommandDefinition command in registry.Commands)
|
||||
{
|
||||
if (!definitions.TryAdd(command.Name, command))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Command '{command.Name}' has more than one definition.");
|
||||
}
|
||||
}
|
||||
|
||||
List<string> order = new List<string>();
|
||||
HashSet<string> known = new HashSet<string>(StringComparer.Ordinal);
|
||||
HashSet<string> active = new HashSet<string>(StringComparer.Ordinal);
|
||||
Dictionary<string, string> introducedVersions = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlFeatureDefinition feature in registry.Features.Where(
|
||||
value => string.Equals(value.Api, ApiName, StringComparison.Ordinal)))
|
||||
{
|
||||
foreach (GlRequirementBlock block in feature.Requirements.Where(AppliesToCoreGl))
|
||||
{
|
||||
foreach (GlRegistryReference reference in block.References.Where(
|
||||
value => value.Kind == GlRegistryReferenceKind.Command))
|
||||
{
|
||||
if (!definitions.ContainsKey(reference.Name))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Feature '{feature.Name}' references undefined command " +
|
||||
$"'{reference.Name}'.");
|
||||
}
|
||||
|
||||
if (block.Operation == GlRequirementOperation.Require)
|
||||
{
|
||||
active.Add(reference.Name);
|
||||
if (known.Add(reference.Name))
|
||||
{
|
||||
order.Add(reference.Name);
|
||||
introducedVersions.Add(reference.Name, feature.Number);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
active.Remove(reference.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<GlCommandDefinition> foundation = new List<GlCommandDefinition>();
|
||||
Dictionary<string, List<GlCommandDefinition>> versionGroups = new Dictionary<string, List<GlCommandDefinition>>(
|
||||
StringComparer.Ordinal);
|
||||
List<string> versionOrder = new List<string>();
|
||||
foreach (string commandName in order.Where(active.Contains))
|
||||
foreach (GlCommandDefinition command in projection.Commands)
|
||||
{
|
||||
string version = introducedVersions[commandName];
|
||||
if (!projection.CommandIntroducedVersions.TryGetValue(
|
||||
command.Name,
|
||||
out string? version))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Command '{command.Name}' has no introduction version.");
|
||||
}
|
||||
|
||||
if (IsFoundationVersion(version))
|
||||
{
|
||||
foundation.Add(definitions[commandName]);
|
||||
foundation.Add(command);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -157,7 +130,7 @@ namespace StgSharp.GenerateGL.Generation
|
||||
versionOrder.Add(version);
|
||||
}
|
||||
|
||||
commands.Add(definitions[commandName]);
|
||||
commands.Add(command);
|
||||
}
|
||||
|
||||
List<GlCommandGroup> groups = new List<GlCommandGroup>
|
||||
@@ -203,23 +176,8 @@ namespace StgSharp.GenerateGL.Generation
|
||||
return major < 3 || major == 3 && minor <= 2;
|
||||
}
|
||||
|
||||
private static bool AppliesToCoreGl(
|
||||
GlRequirementBlock block
|
||||
)
|
||||
{
|
||||
bool matchesApi = block.Api is null || string.Equals(
|
||||
block.Api,
|
||||
ApiName,
|
||||
StringComparison.Ordinal);
|
||||
bool matchesProfile = block.Profile is null || string.Equals(
|
||||
block.Profile,
|
||||
CoreProfile,
|
||||
StringComparison.Ordinal);
|
||||
return matchesApi && matchesProfile;
|
||||
}
|
||||
|
||||
private static string GenerateConstants(
|
||||
GlRegistryModel registry
|
||||
IReadOnlyList<GlEnumDefinition> enums
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
@@ -231,8 +189,7 @@ namespace StgSharp.GenerateGL.Generation
|
||||
|
||||
Dictionary<string, GlEnumDefinition> emitted = new Dictionary<string, GlEnumDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlEnumDefinition value in registry.EnumBlocks.SelectMany(
|
||||
block => block.Enums).Where(IsDesktopEnum))
|
||||
foreach (GlEnumDefinition value in enums)
|
||||
{
|
||||
if (value.Value is null)
|
||||
{
|
||||
@@ -267,16 +224,6 @@ namespace StgSharp.GenerateGL.Generation
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static bool IsDesktopEnum(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
return value.Api is null || string.Equals(
|
||||
value.Api,
|
||||
ApiName,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static ConstantExpression ReadConstantExpression(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
@@ -430,6 +377,7 @@ namespace StgSharp.GenerateGL.Generation
|
||||
|
||||
foreach (ManagedMethodDefinition method in methods)
|
||||
{
|
||||
AppendMethodDocumentation(builder, method);
|
||||
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
|
||||
builder.Append(" public ");
|
||||
builder.Append(method.ReturnType);
|
||||
@@ -460,6 +408,41 @@ namespace StgSharp.GenerateGL.Generation
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendMethodDocumentation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method
|
||||
)
|
||||
{
|
||||
string declaration = ReadNativeDeclaration(method.NativeCommand);
|
||||
string page = method.DocumentationPage ?? method.NativeCommand.Name;
|
||||
builder.Append("\n /// <summary>Forwards to the native C API <c>");
|
||||
builder.Append(EscapeXml(declaration));
|
||||
builder.Append("</c>.</summary>\n");
|
||||
builder.Append(" /// <seealso href=\"https://docs.gl/gl4/");
|
||||
builder.Append(page);
|
||||
builder.Append("\" />");
|
||||
}
|
||||
|
||||
private static string ReadNativeDeclaration(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
return $"{command.Return.DeclarationText}(" +
|
||||
string.Join(", ", command.Parameters.Select(value => value.DeclarationText)) +
|
||||
");";
|
||||
}
|
||||
|
||||
private static string EscapeXml(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return value.Replace("&", "&", StringComparison.Ordinal)
|
||||
.Replace("<", "<", StringComparison.Ordinal)
|
||||
.Replace(">", ">", StringComparison.Ordinal)
|
||||
.Replace("\"", """, StringComparison.Ordinal)
|
||||
.Replace("'", "'", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void AppendInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method
|
||||
@@ -479,12 +462,42 @@ namespace StgSharp.GenerateGL.Generation
|
||||
case Matrix4Invocation matrix:
|
||||
AppendMatrix4Invocation(builder, method, matrix);
|
||||
break;
|
||||
case QuerySpanInvocation query:
|
||||
AppendQuerySpanInvocation(builder, method, query);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported managed invocation '{method.Invocation.GetType().Name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendQuerySpanInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
QuerySpanInvocation invocation
|
||||
)
|
||||
{
|
||||
ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex];
|
||||
string spanName = EscapeIdentifier(span.Name);
|
||||
builder.Append(" fixed (");
|
||||
builder.Append(invocation.ElementType);
|
||||
builder.Append("* __value = ");
|
||||
builder.Append(spanName);
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context->");
|
||||
builder.Append(method.NativeCommand.Name);
|
||||
builder.Append('(');
|
||||
AppendArguments(builder, method, invocation.PrefixArguments);
|
||||
if (invocation.PrefixArguments.Count != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append("__value);\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
private static void AppendDirectInvocation(
|
||||
StringBuilder builder,
|
||||
ManagedMethodDefinition method,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="CSharpIdentifierPolicy"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StgSharp.GenerateGL.Naming
|
||||
{
|
||||
internal static class CSharpIdentifierPolicy
|
||||
{
|
||||
|
||||
private static readonly Regex _attributeWord = new Regex(
|
||||
@"Attrib(?!ute)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _attributeParameterWord = new Regex(
|
||||
@"attrib(?!ute)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _fragmentWord = new Regex(
|
||||
@"Frag(?!ment)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
public static string NormalizeMemberName(
|
||||
string value
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(value);
|
||||
string result = value.Replace(
|
||||
"Framebuffer",
|
||||
"FrameBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"Renderbuffer",
|
||||
"RenderBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"Internalformat",
|
||||
"InternalFormat",
|
||||
StringComparison.Ordinal);
|
||||
result = _attributeWord.Replace(result, "Attribute");
|
||||
return _fragmentWord.Replace(result, "Fragment");
|
||||
}
|
||||
|
||||
public static string NormalizeParameterName(
|
||||
string value
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(value);
|
||||
string result = value.Replace(
|
||||
"Framebuffer",
|
||||
"FrameBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"framebuffer",
|
||||
"frameBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"Renderbuffer",
|
||||
"RenderBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"renderbuffer",
|
||||
"renderBuffer",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"Internalformat",
|
||||
"InternalFormat",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"internalformat",
|
||||
"internalFormat",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"attribindex",
|
||||
"attributeIndex",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"bindingindex",
|
||||
"bindingIndex",
|
||||
StringComparison.Ordinal)
|
||||
.Replace(
|
||||
"relativeoffset",
|
||||
"relativeOffset",
|
||||
StringComparison.Ordinal);
|
||||
return _attributeParameterWord.Replace(result, "attribute");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlManagedApiNamePolicy"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Naming
|
||||
{
|
||||
/// <summary>
|
||||
/// Translates native overload families into stable, semantic C# method names.
|
||||
/// </summary>
|
||||
internal sealed class GlManagedApiNamePolicy
|
||||
{
|
||||
|
||||
private static readonly Regex _getVertexAttributeModePattern = new Regex(
|
||||
@"^glGetVertexAttrib(?<mode>I|L)(?:i|ui|d)v$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex _vertexAttributeModePattern = new Regex(
|
||||
@"^glVertexAttrib(?<mode>I|L|P)[1-4]",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private readonly Dictionary<string, FamilyEntry> _familiesByCommand;
|
||||
|
||||
public GlManagedApiNamePolicy(
|
||||
GlFunctionFamilyAnalysis analysis
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(analysis);
|
||||
_familiesByCommand = analysis.Families
|
||||
.SelectMany(
|
||||
family => family.Members.Select(
|
||||
member => new KeyValuePair<string, FamilyEntry>(
|
||||
member.Command.Name,
|
||||
new FamilyEntry(family, member.NameShape))))
|
||||
.ToDictionary(
|
||||
value => value.Key,
|
||||
value => value.Value,
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public string ReadMethodName(
|
||||
GlCommandDefinition command,
|
||||
string currentName
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(currentName);
|
||||
|
||||
string rawName = CSharpIdentifierPolicy.NormalizeMemberName(
|
||||
RemoveGlPrefix(command.Name));
|
||||
string normalizedCurrentName = CSharpIdentifierPolicy.NormalizeMemberName(
|
||||
currentName);
|
||||
if (!string.Equals(normalizedCurrentName, rawName, StringComparison.Ordinal))
|
||||
{
|
||||
return normalizedCurrentName;
|
||||
}
|
||||
|
||||
string? specialName = ReadSpecialMethodName(command.Name);
|
||||
if (specialName is not null)
|
||||
{
|
||||
return specialName;
|
||||
}
|
||||
|
||||
if (!_familiesByCommand.TryGetValue(
|
||||
command.Name,
|
||||
out FamilyEntry? entry))
|
||||
{
|
||||
return rawName;
|
||||
}
|
||||
|
||||
string familyName = ReadSemanticFamilyName(entry.Family.Name);
|
||||
if (entry.Family.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain &&
|
||||
entry.Shape?.Shape is not null)
|
||||
{
|
||||
familyName += entry.Shape.Shape;
|
||||
}
|
||||
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(familyName);
|
||||
}
|
||||
|
||||
public static string ReadNativeMethodName(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(RemoveGlPrefix(command.Name));
|
||||
}
|
||||
|
||||
public string ReadDocumentationPage(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
string? specialPage = ReadSpecialDocumentationPage(command.Name);
|
||||
if (specialPage is not null)
|
||||
{
|
||||
return specialPage;
|
||||
}
|
||||
|
||||
if (!_familiesByCommand.TryGetValue(
|
||||
command.Name,
|
||||
out FamilyEntry? entry))
|
||||
{
|
||||
return command.Name;
|
||||
}
|
||||
|
||||
string familyName = entry.Family.Name
|
||||
.Replace("FrameBuffer", "Framebuffer", StringComparison.Ordinal)
|
||||
.Replace("RenderBuffer", "Renderbuffer", StringComparison.Ordinal)
|
||||
.Replace("InternalFormat", "Internalformat", StringComparison.Ordinal)
|
||||
.Replace("Attribute", "Attrib", StringComparison.Ordinal)
|
||||
.Replace("Fragment", "Frag", StringComparison.Ordinal);
|
||||
return $"gl{ReadDocumentationFamilyName(familyName)}";
|
||||
}
|
||||
|
||||
private static string? ReadSpecialMethodName(
|
||||
string commandName
|
||||
)
|
||||
{
|
||||
if (commandName == "glClearBufferfi")
|
||||
{
|
||||
return "ClearBuffer";
|
||||
}
|
||||
|
||||
if (commandName == "glClearNamedFramebufferfi")
|
||||
{
|
||||
return "ClearNamedFrameBuffer";
|
||||
}
|
||||
|
||||
if (commandName == "glGetVertexAttribPointerv")
|
||||
{
|
||||
return "GetVertexAttributePointer";
|
||||
}
|
||||
|
||||
if (commandName.StartsWith("glVertexAttrib4N", StringComparison.Ordinal))
|
||||
{
|
||||
return "VertexAttributeNormalized";
|
||||
}
|
||||
|
||||
Match getMatch = _getVertexAttributeModePattern.Match(commandName);
|
||||
if (getMatch.Success)
|
||||
{
|
||||
return getMatch.Groups["mode"].Value == "I"
|
||||
? "GetVertexAttributeInteger"
|
||||
: "GetVertexAttributeDouble";
|
||||
}
|
||||
|
||||
if (commandName is "glVertexAttribIFormat" or "glVertexArrayAttribIFormat")
|
||||
{
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(
|
||||
RemoveGlPrefix(commandName).Replace(
|
||||
"AttribIFormat",
|
||||
"AttributeIntegerFormat",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
if (commandName is "glVertexAttribLFormat" or "glVertexArrayAttribLFormat")
|
||||
{
|
||||
return CSharpIdentifierPolicy.NormalizeMemberName(
|
||||
RemoveGlPrefix(commandName).Replace(
|
||||
"AttribLFormat",
|
||||
"AttributeDoubleFormat",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
if (commandName == "glVertexAttribIPointer")
|
||||
{
|
||||
return "VertexAttributeIntegerPointer";
|
||||
}
|
||||
|
||||
if (commandName == "glVertexAttribLPointer")
|
||||
{
|
||||
return "VertexAttributeDoublePointer";
|
||||
}
|
||||
|
||||
Match vertexMatch = _vertexAttributeModePattern.Match(commandName);
|
||||
if (!vertexMatch.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return vertexMatch.Groups["mode"].Value switch
|
||||
{
|
||||
"I" => "VertexAttributeInteger",
|
||||
"L" => "VertexAttributeDouble",
|
||||
"P" => $"VertexAttributePacked{ReadVertexAttributeWidth(commandName, 'P')}",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static string ReadSemanticFamilyName(
|
||||
string familyName
|
||||
)
|
||||
{
|
||||
return familyName switch
|
||||
{
|
||||
"VertexAttrib" or "VertexAttribute" => "VertexAttribute",
|
||||
"VertexAttrib4N" or "VertexAttribute4N" => "VertexAttributeNormalized",
|
||||
"VertexAttribI" or "VertexAttributeI" => "VertexAttributeInteger",
|
||||
"VertexAttribL" or "VertexAttributeL" => "VertexAttributeDouble",
|
||||
"VertexAttribP" or "VertexAttributeP" => "VertexAttributePacked",
|
||||
"GetVertexAttrib" or "GetVertexAttribute" => "GetVertexAttribute",
|
||||
"GetVertexAttribI" or "GetVertexAttributeI" => "GetVertexAttributeInteger",
|
||||
"GetnUniform" => "GetUniform",
|
||||
"SamplerParameterI" => "SamplerParameterInteger",
|
||||
"GetSamplerParameterI" => "GetSamplerParameterInteger",
|
||||
"TexParameter" => "TextureParameter",
|
||||
"TexParameterI" => "TextureParameterInteger",
|
||||
"GetTexParameter" => "GetTextureParameter",
|
||||
"GetTexParameterI" => "GetTextureParameterInteger",
|
||||
"GetTexLevelParameter" => "GetTextureLevelParameter",
|
||||
"TextureParameter" => "NamedTextureParameter",
|
||||
"TextureParameterI" => "NamedTextureParameterInteger",
|
||||
"GetTextureParameter" => "GetNamedTextureParameter",
|
||||
"GetTextureParameterI" => "GetNamedTextureParameterInteger",
|
||||
"GetTextureLevelParameter" => "GetNamedTextureLevelParameter",
|
||||
_ => familyName,
|
||||
};
|
||||
}
|
||||
|
||||
private static string ReadDocumentationFamilyName(
|
||||
string familyName
|
||||
)
|
||||
{
|
||||
return familyName switch
|
||||
{
|
||||
"VertexAttrib4N" or "VertexAttribI" or "VertexAttribL" or
|
||||
"VertexAttribP" => "VertexAttrib",
|
||||
"GetVertexAttribI" => "GetVertexAttrib",
|
||||
"SamplerParameterI" => "SamplerParameter",
|
||||
"GetSamplerParameterI" => "GetSamplerParameter",
|
||||
"TexParameterI" => "TexParameter",
|
||||
"GetTexParameterI" => "GetTexParameter",
|
||||
"TextureParameterI" => "TextureParameter",
|
||||
"GetTextureParameterI" => "GetTextureParameter",
|
||||
_ => familyName,
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ReadSpecialDocumentationPage(
|
||||
string commandName
|
||||
)
|
||||
{
|
||||
if (commandName.StartsWith("glProgramUniformMatrix", StringComparison.Ordinal))
|
||||
{
|
||||
return "glProgramUniform";
|
||||
}
|
||||
|
||||
if (commandName.StartsWith("glUniformMatrix", StringComparison.Ordinal))
|
||||
{
|
||||
return "glUniform";
|
||||
}
|
||||
|
||||
if (commandName is "glVertexAttribIPointer" or "glVertexAttribLPointer")
|
||||
{
|
||||
return "glVertexAttribPointer";
|
||||
}
|
||||
|
||||
if (commandName is "glVertexAttribIFormat" or "glVertexAttribLFormat" or
|
||||
"glVertexArrayAttribFormat" or "glVertexArrayAttribIFormat" or
|
||||
"glVertexArrayAttribLFormat")
|
||||
{
|
||||
return "glVertexAttribFormat";
|
||||
}
|
||||
|
||||
if (commandName == "glClearBufferfi")
|
||||
{
|
||||
return "glClearBuffer";
|
||||
}
|
||||
|
||||
if (commandName == "glClearNamedFramebufferfi")
|
||||
{
|
||||
return "glClearNamedFramebuffer";
|
||||
}
|
||||
|
||||
if (commandName is "glGetBooleanv" or "glGetDoublev" or
|
||||
"glGetFloatv" or "glGetIntegerv" or "glGetInteger64v" or
|
||||
"glGetBooleani_v" or "glGetDoublei_v" or "glGetFloati_v" or
|
||||
"glGetIntegeri_v" or "glGetInteger64i_v")
|
||||
{
|
||||
return "glGet";
|
||||
}
|
||||
|
||||
if (commandName == "glGetVertexAttribPointerv")
|
||||
{
|
||||
return commandName;
|
||||
}
|
||||
|
||||
if (commandName.StartsWith("glGetVertexAttrib", StringComparison.Ordinal))
|
||||
{
|
||||
return "glGetVertexAttrib";
|
||||
}
|
||||
|
||||
if (commandName.StartsWith("glVertexAttrib", StringComparison.Ordinal) &&
|
||||
commandName.Length > "glVertexAttrib".Length &&
|
||||
char.IsDigit(commandName["glVertexAttrib".Length]) ||
|
||||
commandName.StartsWith("glVertexAttribI", StringComparison.Ordinal) &&
|
||||
commandName != "glVertexAttribIFormat" &&
|
||||
commandName != "glVertexAttribIPointer" ||
|
||||
commandName.StartsWith("glVertexAttribL", StringComparison.Ordinal) &&
|
||||
commandName != "glVertexAttribLFormat" &&
|
||||
commandName != "glVertexAttribLPointer" ||
|
||||
commandName.StartsWith("glVertexAttribP", StringComparison.Ordinal))
|
||||
{
|
||||
return "glVertexAttrib";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static char ReadVertexAttributeWidth(
|
||||
string commandName,
|
||||
char mode
|
||||
)
|
||||
{
|
||||
int index = commandName.IndexOf(mode, "glVertexAttrib".Length) + 1;
|
||||
return commandName[index];
|
||||
}
|
||||
|
||||
private static string RemoveGlPrefix(
|
||||
string name
|
||||
)
|
||||
{
|
||||
return name.StartsWith("gl", StringComparison.Ordinal) && name.Length > 2
|
||||
? name[2..]
|
||||
: name;
|
||||
}
|
||||
|
||||
private sealed record FamilyEntry(
|
||||
GlFunctionFamily Family,
|
||||
GlFunctionNameShape? Shape
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
# StgSharp.GenerateGL
|
||||
|
||||
`StgSharp.GenerateGL` generates the StgSharp desktop OpenGL C# bindings from the Khronos `gl.xml` registry.
|
||||
|
||||
The current target is OpenGL 4.6 Core. The generator does not emit OpenGL ES or mix extension entry points into the core API. `gl.xml` is the authoritative generation input; the retained `gl.h` is reference material only.
|
||||
|
||||
## Usage
|
||||
|
||||
Start the interactive CLI:
|
||||
|
||||
```powershell
|
||||
dotnet run --project tools/StgSharp.GenerateGL/StgSharp.GenerateGL.csproj
|
||||
```
|
||||
|
||||
Common commands:
|
||||
|
||||
```text
|
||||
summary
|
||||
find glBindBuffer
|
||||
show command glBindBuffer
|
||||
target summary
|
||||
families list Vertex
|
||||
families show VertexAttributeP
|
||||
generate
|
||||
list files
|
||||
preview OpenGLFunction.v4_1.g.cs
|
||||
```
|
||||
|
||||
Generate directly into an existing directory:
|
||||
|
||||
```powershell
|
||||
dotnet run --project tools/StgSharp.GenerateGL/StgSharp.GenerateGL.csproj -- generate src/StgSharp.Graphics/OpenGL/Generated
|
||||
```
|
||||
|
||||
Without a directory, `generate` retains its output in the current CLI session. With a directory:
|
||||
|
||||
- the directory must already exist and be writable;
|
||||
- each file is staged in the destination directory before replacing its target;
|
||||
- an existing file is replaced only when its first line is `// <auto-generated>`;
|
||||
- unrelated and obsolete generated files are not deleted.
|
||||
|
||||
Use a directory dedicated to generated source files.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```text
|
||||
gl.xml
|
||||
-> lossless XML and registry models
|
||||
-> desktop OpenGL 4.6 Core projection
|
||||
-> function-family and scalar/vector relation analysis
|
||||
-> managed API filters
|
||||
-> C# naming and signature validation
|
||||
-> in-memory generated file set
|
||||
-> preview or directory output
|
||||
```
|
||||
|
||||
The target projection removes GLES and ARB/NV/EXT extension entry points. It retains only commands and constants present in the target Core Profile.
|
||||
|
||||
Generated files:
|
||||
|
||||
- `glconst.cs`: core constants;
|
||||
- `OpenglContext.g.cs`: the function-pointer table with native names and ABI types;
|
||||
- `OpenGLFunction.Base.g.cs`: APIs introduced through OpenGL 3.2 and retained in the target profile;
|
||||
- `OpenGLFunction.v3_3.g.cs` through `OpenGLFunction.v4_6.g.cs`: APIs introduced by each later core version.
|
||||
|
||||
`OpenglContext` intentionally remains close to the C ABI. Overloads, `Span<T>`, `GlHandle`, and managed naming exist only in the `OpenGLFunction` layer.
|
||||
|
||||
## Overload rules
|
||||
|
||||
The generator merges a family only when it can validate the resulting signatures. Every managed method retains a reference to its original `gl*` command.
|
||||
|
||||
| Native C API | Generated API |
|
||||
| --- | --- |
|
||||
| `glUniform1f` through `glUniform4f` | Scalar `Uniform(...)` overloads |
|
||||
| `glUniform*i`, `glUniform*ui`, and `glUniform*d` | Merged into `Uniform(...)` by C# parameter type |
|
||||
| `glProgramUniform*` | Merged into `ProgramUniform(...)` by the same rules |
|
||||
| `glUniform1*v` | `Uniform(..., ReadOnlySpan<T>)` |
|
||||
| `glUniform4*v` | `Uniform(..., ReadOnlySpan<Vec4<T>>)` |
|
||||
| `glUniform2*v` and `glUniform3*v` | Omitted because `Vec2` and `Vec3` storage is not a tightly packed C array |
|
||||
| `glUniformMatrix4fv` | `Uniform(..., GraphicsMatrix)` and matrix-span overloads |
|
||||
| Other matrix sizes and double matrices | Omitted because only the graphics-oriented 4x4 float layout is defined |
|
||||
| `glGetBooleanv`, `glGetFloatv`, `glGetIntegerv`, and related queries | `GetValue(..., Span<T>)` |
|
||||
| `glVertexAttrib1f/2f/3f/4f` and equivalent scalar forms | `VertexAttribute(...)`; component count is expressed by the parameter list |
|
||||
| `glVertexAttrib*fv/*iv/...` with a scalar equivalent | Omitted instead of exposing a duplicate C-style pointer entry point |
|
||||
| `glVertexAttrib4N*` | `VertexAttributeNormalized(...)` |
|
||||
| `glVertexAttribI*` | `VertexAttributeInteger(...)` |
|
||||
| `glVertexAttribL*` | `VertexAttributeDouble(...)`; `L` does not mean C# `long` |
|
||||
| `glVertexAttribP1ui` through `P4ui` | `VertexAttributePacked1(...)` through `Packed4(...)`; the width prevents signature collisions |
|
||||
| `glGetVertexAttribI*` and `L*` | `GetVertexAttributeInteger(...)` and `GetVertexAttributeDouble(...)` |
|
||||
| `glScissorIndexed` and `glScissorIndexedv` | `ScissorIndexed(...)` overloads |
|
||||
|
||||
Function families are inferred from common name prefixes, shape suffixes, type suffixes, and vector forms. Candidates are checked against:
|
||||
|
||||
- the `gl.xml` `<vecequiv>` relation;
|
||||
- parameter `len`, `kind`, and native types;
|
||||
- scalar `T` versus `const T*` relationships;
|
||||
- uniqueness of the final C# signature.
|
||||
|
||||
If incomplete type mapping causes two methods to collapse to the same C# signature, the generator conservatively restores their normalized native names instead of emitting uncompilable overloads.
|
||||
|
||||
## Renaming rules
|
||||
|
||||
Safe lexical normalization applies to all public methods:
|
||||
|
||||
| OpenGL spelling | C# spelling |
|
||||
| --- | --- |
|
||||
| `Framebuffer` / `framebuffer` | `FrameBuffer` / `frameBuffer` |
|
||||
| `Renderbuffer` / `renderbuffer` | `RenderBuffer` / `renderBuffer` |
|
||||
| `Internalformat` / `internalformat` | `InternalFormat` / `internalFormat` |
|
||||
| `Attrib` / `attrib` | `Attribute` / `attribute` |
|
||||
| `Frag` | `Fragment` |
|
||||
| `attribindex` | `attributeIndex` |
|
||||
| `bindingindex` | `bindingIndex` |
|
||||
| `relativeoffset` | `relativeOffset` |
|
||||
|
||||
Families that represent distinct semantics receive explicit names:
|
||||
|
||||
| Native family | Managed family |
|
||||
| --- | --- |
|
||||
| `TexParameter*` | `TextureParameter(...)` |
|
||||
| DSA `TextureParameter*` | `NamedTextureParameter(...)` |
|
||||
| `TexParameterI*` | `TextureParameterInteger(...)` |
|
||||
| DSA `TextureParameterI*` | `NamedTextureParameterInteger(...)` |
|
||||
| `SamplerParameterI*` | `SamplerParameterInteger(...)` |
|
||||
| `GetnUniform*` | Merged into `GetUniform(...)` when signatures permit |
|
||||
| `VertexAttribI/L/P/N` | Expanded to `Integer/Double/Packed/Normalized` |
|
||||
|
||||
Renaming never changes the native target. For example, `VertexAttributeInteger(...)` still calls `__context->glVertexAttribI*`.
|
||||
|
||||
## Type rules
|
||||
|
||||
- `gl.xml` typedefs map to equivalent C# ABI types; the generator does not emit separate type aliases;
|
||||
- a `GLuint` is converted to `GlHandle` only when object class and parameter semantics identify it as an object handle;
|
||||
- handle arrays use `Span<GlHandle>` or `ReadOnlySpan<GlHandle>`;
|
||||
- `OpenGLFunction` stores an `OpenglContext*` and forwards through `__context->glFunction(...)`;
|
||||
- unknown-length arrays, complex outputs, multiple indirection, and `void*` remain pointers.
|
||||
|
||||
A `T*` alone does not determine whether a managed API should use `Span<T>`, `ReadOnlySpan<T>`, `out T`, or a return value. Future pointer improvements should be implemented as family-specific filters validated with `len`, parameter direction, and OpenGL state semantics.
|
||||
|
||||
## Documentation comments
|
||||
|
||||
Every generated public method contains:
|
||||
|
||||
- its original C API declaration;
|
||||
- a `<seealso>` link to the corresponding docs.gl OpenGL 4 page or family page.
|
||||
|
||||
The generator does not emit `<param>` or `<returns>` elements and does not copy docs.gl content.
|
||||
|
||||
## Source layout
|
||||
|
||||
- `Registry/`: the `gl.xml` object model and Core Profile projection;
|
||||
- `Analysis/`: function families, name shapes, and scalar/vector relations;
|
||||
- `Generation/`: type mapping, API filters, signature planning, and file generation;
|
||||
- `Naming/`: C# identifier and semantic-family naming policies;
|
||||
- `XmlCli.cs`, `RegistryCli.cs`, and `FunctionFamilyCli.cs`: interactive inspection;
|
||||
- `GenerateCli.cs`: in-memory preview and protected directory output.
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="DesktopGlCoreRegistryFilter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects the active desktop OpenGL core API without extension requirements.
|
||||
/// </summary>
|
||||
internal static class DesktopGlCoreRegistryFilter
|
||||
{
|
||||
|
||||
public const string ApiName = "gl";
|
||||
public const string CoreProfile = "core";
|
||||
public const string LatestVersion = "4.6";
|
||||
|
||||
public static GlRegistryProjection Apply(
|
||||
GlRegistryModel registry,
|
||||
string maximumVersion = LatestVersion
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
Version maximum = ReadVersion(maximumVersion);
|
||||
|
||||
GlFeatureDefinition[] features = registry.Features
|
||||
.Where(
|
||||
value => string.Equals(value.Api, ApiName, StringComparison.Ordinal) &&
|
||||
ReadVersion(value.Number) <= maximum)
|
||||
.ToArray();
|
||||
Dictionary<string, GlCommandDefinition> commandDefinitions = ReadCommandDefinitions(
|
||||
registry);
|
||||
Dictionary<string, GlEnumDefinition> enumDefinitions = ReadEnumDefinitions(registry);
|
||||
SymbolSelection commands = new SymbolSelection();
|
||||
SymbolSelection enums = new SymbolSelection();
|
||||
|
||||
foreach (GlFeatureDefinition feature in features)
|
||||
{
|
||||
foreach (GlRequirementBlock block in feature.Requirements.Where(AppliesToTarget))
|
||||
{
|
||||
foreach (GlRegistryReference reference in block.References)
|
||||
{
|
||||
SymbolSelection? selection = reference.Kind switch
|
||||
{
|
||||
GlRegistryReferenceKind.Command => commands,
|
||||
GlRegistryReferenceKind.Enum => enums,
|
||||
GlRegistryReferenceKind.Type => null,
|
||||
_ => throw new InvalidDataException(
|
||||
$"Unknown registry reference kind '{reference.Kind}'."),
|
||||
};
|
||||
if (selection is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block.Operation == GlRequirementOperation.Require)
|
||||
{
|
||||
selection.Require(reference.Name, feature.Number);
|
||||
}
|
||||
else
|
||||
{
|
||||
selection.Remove(reference.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlCommandDefinition[] selectedCommands = commands.ReadActiveNames()
|
||||
.Select(
|
||||
name => ReadDefinition(
|
||||
commandDefinitions,
|
||||
name,
|
||||
"command"))
|
||||
.ToArray();
|
||||
GlEnumDefinition[] selectedEnums = enums.ReadActiveNames()
|
||||
.Select(
|
||||
name => ReadDefinition(
|
||||
enumDefinitions,
|
||||
name,
|
||||
"enum"))
|
||||
.ToArray();
|
||||
|
||||
return new GlRegistryProjection(
|
||||
registry,
|
||||
ApiName,
|
||||
CoreProfile,
|
||||
maximumVersion,
|
||||
Freeze(features),
|
||||
Freeze(selectedCommands),
|
||||
Freeze(selectedEnums),
|
||||
commands.FreezeIntroducedVersions(),
|
||||
enums.FreezeIntroducedVersions());
|
||||
}
|
||||
|
||||
private static bool AppliesToTarget(
|
||||
GlRequirementBlock block
|
||||
)
|
||||
{
|
||||
bool matchesApi = block.Api is null || string.Equals(
|
||||
block.Api,
|
||||
ApiName,
|
||||
StringComparison.Ordinal);
|
||||
bool matchesProfile = block.Profile is null || string.Equals(
|
||||
block.Profile,
|
||||
CoreProfile,
|
||||
StringComparison.Ordinal);
|
||||
return matchesApi && matchesProfile;
|
||||
}
|
||||
|
||||
private static Dictionary<string, GlCommandDefinition> ReadCommandDefinitions(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
Dictionary<string, GlCommandDefinition> result = new Dictionary<string, GlCommandDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlCommandDefinition command in registry.Commands)
|
||||
{
|
||||
if (!result.TryAdd(command.Name, command))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Command '{command.Name}' has more than one definition.");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, GlEnumDefinition> ReadEnumDefinitions(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
Dictionary<string, GlEnumDefinition> result = new Dictionary<string, GlEnumDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlEnumDefinition value in registry.EnumBlocks.SelectMany(
|
||||
block => block.Enums).Where(IsDesktopDefinition))
|
||||
{
|
||||
if (!result.TryGetValue(value.Name, out GlEnumDefinition? previous))
|
||||
{
|
||||
result.Add(value.Name, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previous.Value is not null && value.Value is not null && !string.Equals(
|
||||
previous.Value,
|
||||
value.Value,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Enum '{value.Name}' has conflicting desktop values " +
|
||||
$"'{previous.Value}' and '{value.Value}'.");
|
||||
}
|
||||
|
||||
if (previous.Value is null && value.Value is not null)
|
||||
{
|
||||
result[value.Name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsDesktopDefinition(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
return value.Api is null || string.Equals(
|
||||
value.Api,
|
||||
ApiName,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static T ReadDefinition<T>(
|
||||
IReadOnlyDictionary<string, T> definitions,
|
||||
string name,
|
||||
string kind
|
||||
)
|
||||
{
|
||||
if (!definitions.TryGetValue(name, out T? definition))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Desktop OpenGL core references undefined {kind} '{name}'.");
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static Version ReadVersion(
|
||||
string value
|
||||
)
|
||||
{
|
||||
if (!Version.TryParse(value, out Version? version))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL feature version '{value}' is not a valid version.");
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<T> Freeze<T>(
|
||||
IEnumerable<T> values
|
||||
)
|
||||
{
|
||||
return Array.AsReadOnly(values.ToArray());
|
||||
}
|
||||
|
||||
private sealed class SymbolSelection
|
||||
{
|
||||
|
||||
private readonly HashSet<string> _active = new HashSet<string>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, string> _introducedVersions = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal);
|
||||
private readonly List<string> _order = new List<string>();
|
||||
|
||||
public void Require(
|
||||
string name,
|
||||
string version
|
||||
)
|
||||
{
|
||||
_active.Add(name);
|
||||
if (_introducedVersions.TryAdd(name, version))
|
||||
{
|
||||
_order.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(
|
||||
string name
|
||||
)
|
||||
{
|
||||
_active.Remove(name);
|
||||
}
|
||||
|
||||
public IEnumerable<string> ReadActiveNames()
|
||||
{
|
||||
return _order.Where(_active.Contains);
|
||||
}
|
||||
|
||||
public ReadOnlyDictionary<string, string> FreezeIntroducedVersions()
|
||||
{
|
||||
return new ReadOnlyDictionary<string, string>(
|
||||
new Dictionary<string, string>(
|
||||
_introducedVersions,
|
||||
StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlRegistryProjection"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Registry
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents one API/profile target selected from the complete registry.
|
||||
/// </summary>
|
||||
internal sealed record GlRegistryProjection(
|
||||
GlRegistryModel Source,
|
||||
string Api,
|
||||
string Profile,
|
||||
string MaximumVersion,
|
||||
IReadOnlyList<GlFeatureDefinition> Features,
|
||||
IReadOnlyList<GlCommandDefinition> Commands,
|
||||
IReadOnlyList<GlEnumDefinition> Enums,
|
||||
IReadOnlyDictionary<string, string> CommandIntroducedVersions,
|
||||
IReadOnlyDictionary<string, string> EnumIntroducedVersions
|
||||
);
|
||||
}
|
||||
@@ -19,8 +19,7 @@ namespace StgSharp.GenerateGL
|
||||
internal static class RegistryCli
|
||||
{
|
||||
|
||||
private const int DefaultFindLimit = 10;
|
||||
private const int DefaultListLimit = 20;
|
||||
private const int ShowAllResults = int.MaxValue;
|
||||
private const int QueryErrorExitCode = 1;
|
||||
private const int SuccessExitCode = 0;
|
||||
private const int UsageErrorExitCode = 2;
|
||||
@@ -559,7 +558,7 @@ namespace StgSharp.GenerateGL
|
||||
}
|
||||
|
||||
filter = null;
|
||||
limit = DefaultListLimit;
|
||||
limit = ShowAllResults;
|
||||
int index = 1;
|
||||
if (index < arguments.Count && !arguments[index].StartsWith(
|
||||
"--",
|
||||
@@ -571,7 +570,8 @@ namespace StgSharp.GenerateGL
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
string argument = arguments[index++];
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
@@ -608,7 +608,7 @@ namespace StgSharp.GenerateGL
|
||||
|
||||
text = arguments[0];
|
||||
exact = false;
|
||||
limit = DefaultFindLimit;
|
||||
limit = ShowAllResults;
|
||||
int index = 1;
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
@@ -619,7 +619,8 @@ namespace StgSharp.GenerateGL
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
@@ -653,12 +654,13 @@ namespace StgSharp.GenerateGL
|
||||
}
|
||||
|
||||
name = arguments[1];
|
||||
limit = DefaultListLimit;
|
||||
limit = ShowAllResults;
|
||||
int index = 2;
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
string argument = arguments[index++];
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TargetRegistryCli"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL
|
||||
{
|
||||
internal static class TargetRegistryCli
|
||||
{
|
||||
|
||||
private const int SuccessExitCode = 0;
|
||||
private const int UsageErrorExitCode = 2;
|
||||
|
||||
public static int Execute(
|
||||
GlRegistryProjection projection,
|
||||
IReadOnlyList<string> arguments
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(projection);
|
||||
ArgumentNullException.ThrowIfNull(arguments);
|
||||
|
||||
if (arguments.Count > 1 || arguments.Count == 1 && !string.Equals(
|
||||
arguments[0],
|
||||
"summary",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.Error.WriteLine("error: target accepts only the 'summary' command.");
|
||||
return UsageErrorExitCode;
|
||||
}
|
||||
|
||||
Console.WriteLine("Registry target projection");
|
||||
WriteProperty("api", projection.Api);
|
||||
WriteProperty("profile", projection.Profile);
|
||||
WriteProperty("maximum version", projection.MaximumVersion);
|
||||
WriteProperty("features", projection.Features.Count);
|
||||
WriteProperty("commands", projection.Commands.Count);
|
||||
WriteProperty("enums", projection.Enums.Count);
|
||||
WriteProperty("extensions", 0);
|
||||
WriteProperty("source commands", projection.Source.Commands.Count);
|
||||
WriteProperty("source extensions", projection.Source.Extensions.Count);
|
||||
return SuccessExitCode;
|
||||
}
|
||||
|
||||
private static void WriteProperty(
|
||||
string name,
|
||||
object value
|
||||
)
|
||||
{
|
||||
Console.WriteLine($" {name}: {value}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using StgSharp.GenerateGL.Analysis;
|
||||
using StgSharp.GenerateGL.Generation;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
using StgSharp.GenerateGL.Tree;
|
||||
@@ -26,10 +27,9 @@ namespace StgSharp.GenerateGL
|
||||
internal const int LoadErrorExitCode = 3;
|
||||
|
||||
private const int DefaultDepth = 2;
|
||||
private const int DefaultLimit = 20;
|
||||
private const int DefaultSearchLimit = 5;
|
||||
private const int ShowAllResults = int.MaxValue;
|
||||
private const int ReadableDepth = 6;
|
||||
private const int SearchReadableChildLimit = 8;
|
||||
private const int SearchContextParentChildThreshold = 20;
|
||||
private const int SearchReadableDepth = 4;
|
||||
private const int QueryErrorExitCode = 1;
|
||||
private const int SuccessExitCode = 0;
|
||||
@@ -85,6 +85,12 @@ namespace StgSharp.GenerateGL
|
||||
command,
|
||||
arguments),
|
||||
"GENERATE" or "GEN" => QueryGenerate(session, arguments),
|
||||
"TARGET" => TargetRegistryCli.Execute(
|
||||
session.TargetRegistry,
|
||||
arguments),
|
||||
"FAMILY" or "FAMILIES" => FunctionFamilyCli.Execute(
|
||||
session.FunctionFamilies,
|
||||
arguments),
|
||||
"PREVIEW" => GenerateCli.Preview(
|
||||
session.GeneratedFiles,
|
||||
arguments),
|
||||
@@ -113,7 +119,7 @@ namespace StgSharp.GenerateGL
|
||||
)
|
||||
{
|
||||
int result = GenerateCli.Generate(
|
||||
session.Registry,
|
||||
session.TargetRegistry,
|
||||
arguments,
|
||||
out GeneratedFileSet? generatedFiles);
|
||||
if (generatedFiles is not null)
|
||||
@@ -192,7 +198,7 @@ namespace StgSharp.GenerateGL
|
||||
context,
|
||||
0,
|
||||
SearchReadableDepth,
|
||||
SearchReadableChildLimit);
|
||||
ShowAllResults);
|
||||
}
|
||||
|
||||
WriteTruncation(matches.Length, displayedCount);
|
||||
@@ -229,7 +235,7 @@ namespace StgSharp.GenerateGL
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{index + 1}] {GetElementPath(element)}");
|
||||
WriteReadableElement(element, 0, ReadableDepth, DefaultLimit);
|
||||
WriteReadableElement(element, 0, ReadableDepth, ShowAllResults);
|
||||
}
|
||||
|
||||
WriteTruncation(matches.Length, displayedCount);
|
||||
@@ -610,8 +616,11 @@ namespace StgSharp.GenerateGL
|
||||
break;
|
||||
}
|
||||
|
||||
if (parentLevels is null && parent.Elements().Take(DefaultLimit + 1).Count() >
|
||||
DefaultLimit)
|
||||
if (parentLevels is null && parent.Elements()
|
||||
.Take(
|
||||
SearchContextParentChildThreshold + 1)
|
||||
.Count() >
|
||||
SearchContextParentChildThreshold)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -1115,7 +1124,7 @@ namespace StgSharp.GenerateGL
|
||||
{
|
||||
string xpath = "/*";
|
||||
int depth = DefaultDepth;
|
||||
int limit = DefaultLimit;
|
||||
int limit = ShowAllResults;
|
||||
bool includeWhitespace = false;
|
||||
int index = 0;
|
||||
if (arguments.Count != 0 && !arguments[0].StartsWith("--", StringComparison.Ordinal))
|
||||
@@ -1141,7 +1150,8 @@ namespace StgSharp.GenerateGL
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
@@ -1173,7 +1183,7 @@ namespace StgSharp.GenerateGL
|
||||
}
|
||||
|
||||
string text = arguments[0];
|
||||
int limit = DefaultSearchLimit;
|
||||
int limit = ShowAllResults;
|
||||
int? parentLevels = null;
|
||||
bool exact = false;
|
||||
int index = 1;
|
||||
@@ -1186,7 +1196,8 @@ namespace StgSharp.GenerateGL
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
@@ -1228,12 +1239,13 @@ namespace StgSharp.GenerateGL
|
||||
}
|
||||
|
||||
string xpath = arguments[0];
|
||||
int limit = DefaultLimit;
|
||||
int limit = ShowAllResults;
|
||||
int index = 1;
|
||||
while (index < arguments.Count)
|
||||
{
|
||||
string argument = arguments[index++];
|
||||
if (argument == "--limit" && TryReadPositiveInt(
|
||||
if ((argument == "--showcount" || argument == "--limit") &&
|
||||
TryReadPositiveInt(
|
||||
arguments,
|
||||
ref index,
|
||||
out limit))
|
||||
@@ -1391,9 +1403,24 @@ namespace StgSharp.GenerateGL
|
||||
return;
|
||||
}
|
||||
|
||||
if (arguments is { Length: 1 } && (
|
||||
string.Equals(
|
||||
arguments[0],
|
||||
"family",
|
||||
StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(
|
||||
arguments[0],
|
||||
"families",
|
||||
StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
FunctionFamilyCli.WriteHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (arguments is { Length: > 0 })
|
||||
{
|
||||
Console.Error.WriteLine("error: help accepts only the 'generate' topic.");
|
||||
Console.Error.WriteLine(
|
||||
"error: help accepts only the 'generate' or 'families' topic.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1404,12 +1431,15 @@ namespace StgSharp.GenerateGL
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] shell");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] summary");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] list <category> " +
|
||||
"[filter] [--limit <n>]");
|
||||
"[filter] [--showcount <n>]");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] find <text> " +
|
||||
"[--exact] [--limit <n>]");
|
||||
"[--exact] [--showcount <n>]");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] show <category> <name> " +
|
||||
"[--limit <n>]");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] generate");
|
||||
"[--showcount <n>]");
|
||||
Console.WriteLine(
|
||||
" StgSharp.GenerateGL [--xml <path>] generate [output-directory]");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] target summary");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] families <command>");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] list files");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] preview <filename>");
|
||||
Console.WriteLine(" StgSharp.GenerateGL [--xml <path>] xml <command> [...]");
|
||||
@@ -1424,7 +1454,11 @@ namespace StgSharp.GenerateGL
|
||||
Console.WriteLine(" StgSharp.GenerateGL find glBindBuffer");
|
||||
Console.WriteLine(" StgSharp.GenerateGL show command glBindBuffer");
|
||||
Console.WriteLine(" StgSharp.GenerateGL show extension GL_KHR_debug");
|
||||
Console.WriteLine(" StgSharp.GenerateGL target summary");
|
||||
Console.WriteLine(" StgSharp.GenerateGL families show VertexAttribP");
|
||||
Console.WriteLine(" StgSharp.GenerateGL generate");
|
||||
Console.WriteLine(
|
||||
" StgSharp.GenerateGL generate src/StgSharp.Graphics/OpenGL/Generated");
|
||||
Console.WriteLine(" StgSharp.GenerateGL list files");
|
||||
Console.WriteLine(" StgSharp.GenerateGL preview glconst.cs");
|
||||
Console.WriteLine(" StgSharp.GenerateGL xml tree --depth 1");
|
||||
@@ -1443,13 +1477,13 @@ namespace StgSharp.GenerateGL
|
||||
Console.WriteLine(" xml summary");
|
||||
Console.WriteLine(" xml raw");
|
||||
Console.WriteLine(
|
||||
" xml tree [xpath] [--depth <n>] [--limit <n>] [--whitespace]");
|
||||
" xml tree [xpath] [--depth <n>] [--showcount <n>] [--whitespace]");
|
||||
Console.WriteLine(
|
||||
" xml find <text> [--exact] [--up <n>] [--limit <n>]");
|
||||
Console.WriteLine(" xml show <xpath> [--limit <n>]");
|
||||
Console.WriteLine(" xml select <xpath> [--limit <n>]");
|
||||
Console.WriteLine(" xml text <xpath> [--limit <n>]");
|
||||
Console.WriteLine(" xml attributes <xpath> [--limit <n>]");
|
||||
" xml find <text> [--exact] [--up <n>] [--showcount <n>]");
|
||||
Console.WriteLine(" xml show <xpath> [--showcount <n>]");
|
||||
Console.WriteLine(" xml select <xpath> [--showcount <n>]");
|
||||
Console.WriteLine(" xml text <xpath> [--showcount <n>]");
|
||||
Console.WriteLine(" xml attributes <xpath> [--showcount <n>]");
|
||||
}
|
||||
|
||||
private sealed class CliSession
|
||||
@@ -1462,12 +1496,18 @@ namespace StgSharp.GenerateGL
|
||||
{
|
||||
Document = document;
|
||||
Registry = registry;
|
||||
TargetRegistry = DesktopGlCoreRegistryFilter.Apply(registry);
|
||||
FunctionFamilies = GlFunctionFamilyAnalyzer.Analyze(TargetRegistry);
|
||||
}
|
||||
|
||||
public XmlTreeDocument Document { get; }
|
||||
|
||||
public GlRegistryModel Registry { get; }
|
||||
|
||||
public GlRegistryProjection TargetRegistry { get; }
|
||||
|
||||
public GlFunctionFamilyAnalysis FunctionFamilies { get; }
|
||||
|
||||
public GeneratedFileSet GeneratedFiles { get; set; } = GeneratedFileSet.Empty;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user