// ----------------------------------------------------------------------------- // 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 { /// /// Produces the first in-memory desktop OpenGL binding files. /// internal static class OpenGlGenerator { private const string ApiName = "gl"; private const string CoreProfile = "core"; private static readonly HashSet _csharpKeywords = new HashSet( 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 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 ReadCoreCommands( GlRegistryModel registry ) { Dictionary definitions = new Dictionary( 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 order = new List(); HashSet known = new HashSet(StringComparer.Ordinal); HashSet active = new HashSet(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 emitted = new Dictionary( 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 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 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("// \n"); builder.Append("// Generated by StgSharp.GenerateGL.\n"); builder.Append("// "); builder.Append(description); builder.Append(".\n"); builder.Append("// \n\n"); } private sealed record ConstantExpression( string Type, string Literal ); } }