make new opengl source generator
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="CSharpAbiTypePolicy"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines C ABI primitives and external Khronos platform types that cannot
|
||||
/// be inferred from gl.xml.
|
||||
/// </summary>
|
||||
internal static class CSharpAbiTypePolicy
|
||||
{
|
||||
|
||||
private static readonly Dictionary<string, string> _types = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal)
|
||||
{
|
||||
["void"] = "void",
|
||||
["char"] = "byte",
|
||||
["signed char"] = "sbyte",
|
||||
["unsigned char"] = "byte",
|
||||
["short"] = "short",
|
||||
["short int"] = "short",
|
||||
["signed short"] = "short",
|
||||
["signed short int"] = "short",
|
||||
["unsigned short"] = "ushort",
|
||||
["unsigned short int"] = "ushort",
|
||||
["int"] = "int",
|
||||
["signed"] = "int",
|
||||
["signed int"] = "int",
|
||||
["unsigned"] = "uint",
|
||||
["unsigned int"] = "uint",
|
||||
["float"] = "float",
|
||||
["double"] = "double",
|
||||
["khronos_int8_t"] = "sbyte",
|
||||
["khronos_uint8_t"] = "byte",
|
||||
["khronos_int16_t"] = "short",
|
||||
["khronos_uint16_t"] = "ushort",
|
||||
["khronos_int32_t"] = "int",
|
||||
["khronos_uint32_t"] = "uint",
|
||||
["khronos_int64_t"] = "long",
|
||||
["khronos_uint64_t"] = "ulong",
|
||||
["khronos_intptr_t"] = "nint",
|
||||
["khronos_uintptr_t"] = "nuint",
|
||||
["khronos_ssize_t"] = "nint",
|
||||
["khronos_usize_t"] = "nuint",
|
||||
["khronos_float_t"] = "float",
|
||||
};
|
||||
|
||||
public static bool TryResolve(
|
||||
string typeName,
|
||||
out string equivalentType
|
||||
)
|
||||
{
|
||||
return _types.TryGetValue(typeName, out equivalentType!);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GeneratedFileSet"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
internal sealed record GeneratedFile(
|
||||
string Name,
|
||||
string Content
|
||||
);
|
||||
|
||||
internal sealed class GeneratedFileSet
|
||||
{
|
||||
|
||||
public static GeneratedFileSet Empty { get; } = new GeneratedFileSet(
|
||||
Array.Empty<GeneratedFile>());
|
||||
|
||||
private readonly ReadOnlyDictionary<string, GeneratedFile> _filesByName;
|
||||
|
||||
public GeneratedFileSet(
|
||||
IEnumerable<GeneratedFile> files
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(files);
|
||||
|
||||
ReadOnlyCollection<GeneratedFile> values = Array.AsReadOnly(files.ToArray());
|
||||
Dictionary<string, GeneratedFile> index = new Dictionary<string, GeneratedFile>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
foreach (GeneratedFile file in values)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(file.Name);
|
||||
if (!index.TryAdd(file.Name, file))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Generated file name '{file.Name}' is duplicated.");
|
||||
}
|
||||
}
|
||||
|
||||
Files = values;
|
||||
_filesByName = new ReadOnlyDictionary<string, GeneratedFile>(index);
|
||||
}
|
||||
|
||||
public IReadOnlyList<GeneratedFile> Files { get; }
|
||||
|
||||
public bool TryGetFile(
|
||||
string name,
|
||||
out GeneratedFile file
|
||||
)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
||||
return _filesByName.TryGetValue(name, out file!);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GlEquivalentTypeMapper"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves OpenGL typedef declarations recursively to equivalent C# ABI types.
|
||||
/// </summary>
|
||||
internal sealed class GlEquivalentTypeMapper
|
||||
{
|
||||
|
||||
private readonly Dictionary<string, string> _resolvedTypes = new Dictionary<string, string>(
|
||||
StringComparer.Ordinal);
|
||||
private readonly GlRegistryModel _registry;
|
||||
|
||||
public GlEquivalentTypeMapper(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
public string ReadReturnType(
|
||||
GlCommandDefinition command
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
return ReadDeclarationType(
|
||||
command.Return.TypeName,
|
||||
command.Return.DeclarationText,
|
||||
command.Name);
|
||||
}
|
||||
|
||||
public string ReadParameterType(
|
||||
GlParameterDefinition parameter
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameter);
|
||||
return ReadDeclarationType(
|
||||
parameter.TypeName,
|
||||
parameter.DeclarationText,
|
||||
parameter.Name);
|
||||
}
|
||||
|
||||
private string ReadDeclarationType(
|
||||
string? typeName,
|
||||
string declaration,
|
||||
string identifier
|
||||
)
|
||||
{
|
||||
if (typeName is null)
|
||||
{
|
||||
return ResolveTypeExpression(RemoveIdentifier(declaration, identifier));
|
||||
}
|
||||
|
||||
string equivalentType = ResolveNamedType(
|
||||
typeName,
|
||||
new HashSet<string>(StringComparer.Ordinal));
|
||||
int pointerDepth = declaration.Count(value => value == '*');
|
||||
if (declaration.Contains('[', StringComparison.Ordinal))
|
||||
{
|
||||
pointerDepth++;
|
||||
}
|
||||
|
||||
return equivalentType + new string('*', pointerDepth);
|
||||
}
|
||||
|
||||
private string ResolveNamedType(
|
||||
string typeName,
|
||||
HashSet<string> resolving
|
||||
)
|
||||
{
|
||||
if (CSharpAbiTypePolicy.TryResolve(typeName, out string abiType))
|
||||
{
|
||||
return abiType;
|
||||
}
|
||||
|
||||
if (_resolvedTypes.TryGetValue(typeName, out string? resolvedType))
|
||||
{
|
||||
return resolvedType;
|
||||
}
|
||||
|
||||
if (!resolving.Add(typeName))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type alias cycle contains '{typeName}'.");
|
||||
}
|
||||
|
||||
if (!_registry.TypesByName.TryGetValue(
|
||||
typeName,
|
||||
out IReadOnlyList<GlTypeDefinition>? definitions) ||
|
||||
definitions.Count != 1)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type '{typeName}' does not have one registry definition.");
|
||||
}
|
||||
|
||||
GlTypeDefinition definition = definitions[0];
|
||||
string declaration = NormalizeWhitespace(definition.DeclarationText);
|
||||
string value;
|
||||
if (TryReadCallbackType(typeName, declaration, resolving, out string callbackType))
|
||||
{
|
||||
value = callbackType;
|
||||
}
|
||||
else
|
||||
{
|
||||
MatchCollection aliases = Regex.Matches(
|
||||
declaration,
|
||||
$@"typedef\s+(?<target>.+?)\s*\b{Regex.Escape(typeName)}\b\s*;",
|
||||
RegexOptions.CultureInvariant);
|
||||
if (aliases.Count == 1)
|
||||
{
|
||||
value = ResolveTypeExpression(
|
||||
aliases[0].Groups["target"].Value,
|
||||
resolving);
|
||||
}
|
||||
else if (declaration == $"{typeName};" || declaration.StartsWith(
|
||||
"struct ",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
value = "void";
|
||||
}
|
||||
else if (aliases.Count > 1)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"OpenGL type '{typeName}' has platform-dependent typedefs and " +
|
||||
"requires an explicit target policy.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Cannot read typedef declaration '{definition.DeclarationText}'.");
|
||||
}
|
||||
}
|
||||
|
||||
resolving.Remove(typeName);
|
||||
_resolvedTypes.Add(typeName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private bool TryReadCallbackType(
|
||||
string typeName,
|
||||
string declaration,
|
||||
HashSet<string> resolving,
|
||||
out string type
|
||||
)
|
||||
{
|
||||
Match match = Regex.Match(
|
||||
declaration,
|
||||
$@"^typedef\s+(?<return>.+?)\s*\(\s*\*\s*{Regex.Escape(typeName)}\s*\)" +
|
||||
@"\s*\((?<parameters>.*)\)\s*;$",
|
||||
RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
type = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> types = new List<string>();
|
||||
string parameters = match.Groups["parameters"].Value.Trim();
|
||||
if (parameters.Length != 0 && parameters != "void")
|
||||
{
|
||||
foreach (string parameter in parameters.Split(','))
|
||||
{
|
||||
types.Add(ResolveTypeExpression(RemoveLastIdentifier(parameter), resolving));
|
||||
}
|
||||
}
|
||||
|
||||
types.Add(ResolveTypeExpression(match.Groups["return"].Value, resolving));
|
||||
type = $"delegate* unmanaged<{string.Join(", ", types)}>";
|
||||
return true;
|
||||
}
|
||||
|
||||
private string ResolveTypeExpression(
|
||||
string expression,
|
||||
HashSet<string>? resolving = null
|
||||
)
|
||||
{
|
||||
resolving ??= new HashSet<string>(StringComparer.Ordinal);
|
||||
string value = Regex.Replace(
|
||||
NormalizeWhitespace(expression),
|
||||
@"\b(?:const|volatile|restrict)\b",
|
||||
string.Empty,
|
||||
RegexOptions.CultureInvariant).Trim();
|
||||
int pointerDepth = value.Count(character => character == '*');
|
||||
value = value.Replace("*", string.Empty, StringComparison.Ordinal).Trim();
|
||||
if (value.StartsWith("struct ", StringComparison.Ordinal))
|
||||
{
|
||||
value = "void";
|
||||
}
|
||||
|
||||
string equivalentType = ResolveNamedType(value, resolving);
|
||||
return equivalentType + new string('*', pointerDepth);
|
||||
}
|
||||
|
||||
private static string RemoveIdentifier(
|
||||
string declaration,
|
||||
string identifier
|
||||
)
|
||||
{
|
||||
int index = declaration.LastIndexOf(identifier, StringComparison.Ordinal);
|
||||
return index < 0
|
||||
? declaration
|
||||
: declaration.Remove(index, identifier.Length);
|
||||
}
|
||||
|
||||
private static string RemoveLastIdentifier(
|
||||
string declaration
|
||||
)
|
||||
{
|
||||
Match match = Regex.Match(
|
||||
declaration.Trim(),
|
||||
@"^(?<type>.+?)(?<name>[A-Za-z_][A-Za-z0-9_]*)$",
|
||||
RegexOptions.CultureInvariant);
|
||||
return match.Success ? match.Groups["type"].Value.Trim() : declaration.Trim();
|
||||
}
|
||||
|
||||
private static string NormalizeWhitespace(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return Regex.Replace(
|
||||
value,
|
||||
@"\s+",
|
||||
" ",
|
||||
RegexOptions.CultureInvariant).Trim();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="OpenGlGenerator"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using StgSharp.GenerateGL.Registry;
|
||||
|
||||
namespace StgSharp.GenerateGL.Generation
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces the first in-memory desktop OpenGL binding files.
|
||||
/// </summary>
|
||||
internal static class OpenGlGenerator
|
||||
{
|
||||
|
||||
private const string ApiName = "gl";
|
||||
private const string CoreProfile = "core";
|
||||
|
||||
private static readonly HashSet<string> _csharpKeywords = new HashSet<string>(
|
||||
new[]
|
||||
{
|
||||
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
|
||||
"char", "checked", "class", "const", "continue", "decimal", "default",
|
||||
"delegate", "do", "double", "else", "enum", "event", "explicit", "extern",
|
||||
"false", "finally", "fixed", "float", "for", "foreach", "goto", "if",
|
||||
"implicit", "in", "int", "interface", "internal", "is", "lock", "long",
|
||||
"namespace", "new", "null", "object", "operator", "out", "override",
|
||||
"params", "private", "protected", "public", "readonly", "ref", "return",
|
||||
"sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string",
|
||||
"struct", "switch", "this", "throw", "true", "try", "typeof", "uint",
|
||||
"ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
|
||||
"volatile", "while",
|
||||
},
|
||||
StringComparer.Ordinal);
|
||||
|
||||
public static GeneratedFileSet Generate(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
|
||||
IReadOnlyList<GlCommandDefinition> commands = ReadCoreCommands(registry);
|
||||
GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(registry);
|
||||
return new GeneratedFileSet(
|
||||
new[]
|
||||
{
|
||||
new GeneratedFile("glconst.cs", GenerateConstants(registry)),
|
||||
new GeneratedFile(
|
||||
"OpenglContext.g.cs",
|
||||
GenerateContext(commands, typeMapper)),
|
||||
new GeneratedFile(
|
||||
"OpenGLFunction.Raw.g.cs",
|
||||
GenerateFunctions(commands, typeMapper)),
|
||||
});
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<GlCommandDefinition> ReadCoreCommands(
|
||||
GlRegistryModel registry
|
||||
)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
active.Remove(reference.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.AsReadOnly(
|
||||
order.Where(active.Contains).Select(value => definitions[value]).ToArray());
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
AppendGeneratedHeader(builder, "Desktop OpenGL constants");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" public static class glConst\n");
|
||||
builder.Append(" {\n\n");
|
||||
|
||||
Dictionary<string, GlEnumDefinition> emitted = new Dictionary<string, GlEnumDefinition>(
|
||||
StringComparer.Ordinal);
|
||||
foreach (GlEnumDefinition value in registry.EnumBlocks.SelectMany(
|
||||
block => block.Enums).Where(IsDesktopEnum))
|
||||
{
|
||||
if (value.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (emitted.TryGetValue(value.Name, out GlEnumDefinition? previous))
|
||||
{
|
||||
if (!string.Equals(previous.Value, value.Value, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Desktop enum '{value.Name}' has conflicting values " +
|
||||
$"'{previous.Value}' and '{value.Value}'.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
emitted.Add(value.Name, value);
|
||||
ConstantExpression expression = ReadConstantExpression(value);
|
||||
builder.Append(" public const ");
|
||||
builder.Append(expression.Type);
|
||||
builder.Append(' ');
|
||||
builder.Append(ReadConstantName(value.Name));
|
||||
builder.Append(" = ");
|
||||
builder.Append(expression.Literal);
|
||||
builder.Append(";\n");
|
||||
}
|
||||
|
||||
builder.Append("\n }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static bool IsDesktopEnum(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
return value.Api is null || string.Equals(
|
||||
value.Api,
|
||||
ApiName,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static ConstantExpression ReadConstantExpression(
|
||||
GlEnumDefinition value
|
||||
)
|
||||
{
|
||||
string source = value.Value!;
|
||||
bool isHex = source.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
|
||||
BigInteger number = isHex
|
||||
? BigInteger.Parse(
|
||||
$"0{source[2..]}",
|
||||
NumberStyles.AllowHexSpecifier,
|
||||
CultureInfo.InvariantCulture)
|
||||
: BigInteger.Parse(source, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||
|
||||
if (string.Equals(value.TypeSuffix, "ull", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
EnsureRange(number, BigInteger.Zero, ulong.MaxValue, value);
|
||||
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
|
||||
}
|
||||
|
||||
if (string.Equals(value.TypeSuffix, "u", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
EnsureRange(number, BigInteger.Zero, uint.MaxValue, value);
|
||||
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
|
||||
}
|
||||
|
||||
if (number >= int.MinValue && number <= int.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("int", source);
|
||||
}
|
||||
|
||||
if (number >= BigInteger.Zero && number <= uint.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("uint", ReadLiteral(source, isHex, "u"));
|
||||
}
|
||||
|
||||
if (number >= long.MinValue && number <= long.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("long", ReadLiteral(source, isHex, "L"));
|
||||
}
|
||||
|
||||
if (number >= BigInteger.Zero && number <= ulong.MaxValue)
|
||||
{
|
||||
return new ConstantExpression("ulong", ReadLiteral(source, isHex, "UL"));
|
||||
}
|
||||
|
||||
throw new InvalidDataException(
|
||||
$"Enum '{value.Name}' value '{source}' does not fit a C# integral type.");
|
||||
}
|
||||
|
||||
private static void EnsureRange(
|
||||
BigInteger value,
|
||||
BigInteger minimum,
|
||||
BigInteger maximum,
|
||||
GlEnumDefinition definition
|
||||
)
|
||||
{
|
||||
if (value < minimum || value > maximum)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Enum '{definition.Name}' value '{definition.Value}' is outside its " +
|
||||
"declared numeric suffix range.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadLiteral(
|
||||
string source,
|
||||
bool isHex,
|
||||
string suffix
|
||||
)
|
||||
{
|
||||
return isHex ? $"{source}{suffix}" : $"{source}{suffix}";
|
||||
}
|
||||
|
||||
private static string ReadConstantName(
|
||||
string name
|
||||
)
|
||||
{
|
||||
string value = name.StartsWith("GL_", StringComparison.Ordinal)
|
||||
? name[3..]
|
||||
: name;
|
||||
StringBuilder result = new StringBuilder(value.Length + 1);
|
||||
if (value.Length == 0 || char.IsDigit(value[0]))
|
||||
{
|
||||
result.Append('_');
|
||||
}
|
||||
|
||||
foreach (char character in value)
|
||||
{
|
||||
result.Append(char.IsLetterOrDigit(character) || character == '_'
|
||||
? character
|
||||
: '_');
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateContext(
|
||||
IReadOnlyList<GlCommandDefinition> commands,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(128 * 1024);
|
||||
AppendGeneratedHeader(builder, "Desktop OpenGL core function pointer table");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" public unsafe partial struct OpenglContext\n");
|
||||
builder.Append(" {\n");
|
||||
foreach (GlCommandDefinition command in commands)
|
||||
{
|
||||
builder.Append(" internal delegate* unmanaged<");
|
||||
foreach (GlParameterDefinition parameter in command.Parameters)
|
||||
{
|
||||
builder.Append(typeMapper.ReadParameterType(parameter));
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append(typeMapper.ReadReturnType(command));
|
||||
builder.Append("> ");
|
||||
builder.Append(command.Name);
|
||||
builder.Append(";\n");
|
||||
}
|
||||
|
||||
builder.Append(" }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateFunctions(
|
||||
IReadOnlyList<GlCommandDefinition> commands,
|
||||
GlEquivalentTypeMapper typeMapper
|
||||
)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(512 * 1024);
|
||||
AppendGeneratedHeader(builder, "Desktop OpenGL core forwarding methods");
|
||||
builder.Append("using System.Runtime.CompilerServices;\n\n");
|
||||
builder.Append("namespace StgSharp.Graphics.OpenGL\n");
|
||||
builder.Append("{\n");
|
||||
builder.Append(" public unsafe partial class OpenGLFunction\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append("\n private readonly OpenglContext* __context;\n");
|
||||
builder.Append("\n internal OpenGLFunction(OpenglContext* context)\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" __context = context;\n");
|
||||
builder.Append(" }\n");
|
||||
foreach (GlCommandDefinition command in commands)
|
||||
{
|
||||
string returnType = typeMapper.ReadReturnType(command);
|
||||
string methodName = ReadMethodName(command.Name);
|
||||
builder.Append("\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
|
||||
builder.Append(" public ");
|
||||
builder.Append(returnType);
|
||||
builder.Append(' ');
|
||||
builder.Append(methodName);
|
||||
builder.Append('(');
|
||||
for (int index = 0; index < command.Parameters.Count; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
GlParameterDefinition parameter = command.Parameters[index];
|
||||
builder.Append(typeMapper.ReadParameterType(parameter));
|
||||
builder.Append(' ');
|
||||
builder.Append(EscapeIdentifier(parameter.Name));
|
||||
}
|
||||
|
||||
builder.Append(")\n");
|
||||
builder.Append(" {\n");
|
||||
builder.Append(" ");
|
||||
if (returnType != "void")
|
||||
{
|
||||
builder.Append("return ");
|
||||
}
|
||||
|
||||
builder.Append("__context->");
|
||||
builder.Append(command.Name);
|
||||
builder.Append('(');
|
||||
builder.Append(
|
||||
string.Join(
|
||||
", ",
|
||||
command.Parameters.Select(
|
||||
value => EscapeIdentifier(value.Name))));
|
||||
builder.Append(");\n");
|
||||
builder.Append(" }\n");
|
||||
}
|
||||
|
||||
builder.Append("\n }\n");
|
||||
builder.Append("}\n");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string ReadMethodName(
|
||||
string commandName
|
||||
)
|
||||
{
|
||||
return commandName.StartsWith("gl", StringComparison.Ordinal) &&
|
||||
commandName.Length > 2
|
||||
? commandName[2..]
|
||||
: commandName;
|
||||
}
|
||||
|
||||
private static string EscapeIdentifier(
|
||||
string value
|
||||
)
|
||||
{
|
||||
return _csharpKeywords.Contains(value) ? $"@{value}" : value;
|
||||
}
|
||||
|
||||
private static void AppendGeneratedHeader(
|
||||
StringBuilder builder,
|
||||
string description
|
||||
)
|
||||
{
|
||||
builder.Append("// <auto-generated>\n");
|
||||
builder.Append("// Generated by StgSharp.GenerateGL.\n");
|
||||
builder.Append("// ");
|
||||
builder.Append(description);
|
||||
builder.Append(".\n");
|
||||
builder.Append("// </auto-generated>\n\n");
|
||||
}
|
||||
|
||||
private sealed record ConstantExpression(
|
||||
string Type,
|
||||
string Literal
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user