From 5db9f39a46caeffa7c29516bd7a20274a5c307ea Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:19:25 +0800 Subject: [PATCH] make new opengl source generator --- .gitignore | 2 +- .../Generation/GlHandleApiFilter.cs | 169 +++++++ .../Generation/GlOverloadApiFilter.cs | 391 ++++++++++++++++ .../Generation/IGlApiFilter.cs | 23 + .../Generation/ManagedApiModel.cs | 71 +++ .../Generation/ManagedApiPlanner.cs | 119 +++++ .../Generation/OpenGlGenerator.cs | 428 +++++++++++++++--- 7 files changed, 1151 insertions(+), 52 deletions(-) create mode 100644 tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs create mode 100644 tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs create mode 100644 tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs create mode 100644 tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs create mode 100644 tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs diff --git a/.gitignore b/.gitignore index 4bbefdde..96d1605d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,4 @@ build.ninja BenchmarkDotNet.Artifacts/ /StgSharp.Native/lib/ -!/StgSharp.Native/lib/**/CMakeLists.txt \ No newline at end of file +!/StgSharp.Native/lib/**/CMakeLists.txt diff --git a/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs new file mode 100644 index 00000000..379ced11 --- /dev/null +++ b/tools/StgSharp.GenerateGL/Generation/GlHandleApiFilter.cs @@ -0,0 +1,169 @@ +// ----------------------------------------------------------------------------- +// file="GlHandleApiFilter" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using StgSharp.GenerateGL.Registry; + +namespace StgSharp.GenerateGL.Generation +{ + /// + /// Replaces registry object-class GLuint values with the managed GlHandle type. + /// + internal sealed class GlHandleApiFilter : IGlApiFilter + { + + public IReadOnlyList Apply( + GlCommandDefinition command, + IReadOnlyList methods, + GlEquivalentTypeMapper typeMapper + ) + { + ArgumentNullException.ThrowIfNull(command); + ArgumentNullException.ThrowIfNull(methods); + ArgumentNullException.ThrowIfNull(typeMapper); + + bool wrapsReturn = string.Equals( + command.Return.TypeName, + "GLuint", + StringComparison.Ordinal) && + command.Return.ObjectClass is not null; + return Array.AsReadOnly( + methods.Select( + method => ConvertMethod(method, wrapsReturn)).ToArray()); + } + + private static ManagedMethodDefinition ConvertMethod( + ManagedMethodDefinition method, + bool wrapsReturn + ) + { + ManagedParameterDefinition[] converted = method.Parameters.Select( + ConvertParameter).ToArray(); + if (method.Invocation is not DirectInvocation direct || + !converted.Any(value => value.IsHandleSpan)) + { + return method with + { + ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType, + Parameters = Array.AsReadOnly(converted), + WrapHandleReturn = wrapsReturn, + }; + } + + Dictionary spanByLengthName = new Dictionary( + StringComparer.Ordinal); + for (int index = 0; index < converted.Length; index++) + { + ManagedParameterDefinition parameter = converted[index]; + string? length = parameter.NativeParameter?.Length; + if (parameter.IsHandleSpan && + length is not null && + IsIdentifier(length)) + { + spanByLengthName.TryAdd(length, index); + } + } + + Dictionary oldToNew = new Dictionary(); + List parameters = new List(); + for (int oldIndex = 0; oldIndex < converted.Length; oldIndex++) + { + ManagedParameterDefinition parameter = converted[oldIndex]; + if (spanByLengthName.ContainsKey(parameter.Name)) + { + continue; + } + + oldToNew.Add(oldIndex, parameters.Count); + parameters.Add(parameter); + } + + List arguments = new List(direct.Arguments.Count); + foreach (ManagedArgument argument in direct.Arguments) + { + if (argument is not ParameterArgument sourceArgument) + { + arguments.Add(argument); + continue; + } + + ManagedParameterDefinition sourceParameter = converted[sourceArgument.ParameterIndex]; + if (spanByLengthName.TryGetValue( + sourceParameter.Name, + out int spanOldIndex)) + { + arguments.Add(new SpanLengthArgument(oldToNew[spanOldIndex])); + } + else + { + arguments.Add( + new ParameterArgument(oldToNew[sourceArgument.ParameterIndex])); + } + } + + return method with + { + ReturnType = wrapsReturn ? "GlHandle" : method.ReturnType, + Parameters = Array.AsReadOnly(parameters.ToArray()), + Invocation = new DirectInvocation(Array.AsReadOnly(arguments.ToArray())), + WrapHandleReturn = wrapsReturn, + }; + } + + private static ManagedParameterDefinition ConvertParameter( + ManagedParameterDefinition parameter + ) + { + GlParameterDefinition? source = parameter.NativeParameter; + if (source is null || + source.ObjectClass is null || + !string.Equals(source.TypeName, "GLuint", StringComparison.Ordinal)) + { + return parameter; + } + + int pointerDepth = source.DeclarationText.Count(value => value == '*'); + bool isSpan = pointerDepth == 1 && source.Length is not null; + string type; + if (isSpan) + { + type = source.DeclarationText.Contains("const", StringComparison.Ordinal) + ? "ReadOnlySpan" + : "Span"; + } + else + { + type = pointerDepth == 0 + ? "GlHandle" + : $"GlHandle{new string('*', pointerDepth)}"; + } + + return parameter with + { + Type = type, + IsHandle = true, + IsHandleSpan = isSpan, + }; + } + + private static bool IsIdentifier( + string value + ) + { + if (value.Length == 0 || + !char.IsLetter(value[0]) && value[0] != '_') + { + return false; + } + + return value.Skip(1).All(character => char.IsLetterOrDigit(character) || character == '_'); + } + + } +} diff --git a/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs new file mode 100644 index 00000000..3c82424f --- /dev/null +++ b/tools/StgSharp.GenerateGL/Generation/GlOverloadApiFilter.cs @@ -0,0 +1,391 @@ +// ----------------------------------------------------------------------------- +// file="GlOverloadApiFilter" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Text.RegularExpressions; +using StgSharp.GenerateGL.Registry; + +namespace StgSharp.GenerateGL.Generation +{ + /// + /// Replaces verified C-style command families with managed overloads. + /// + internal sealed class GlOverloadApiFilter : IGlApiFilter + { + + private static readonly Regex _matrixUniformPattern = new Regex( + @"^gl(?ProgramUniform|Uniform)Matrix(?[2-4])" + + @"(?:x(?[2-4]))?(?f|d)v$", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static readonly Regex _uniformPattern = new Regex( + @"^gl(?ProgramUniform|Uniform)(?[1-4])" + + @"(?ui|f|i|d)(?v?)$", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static readonly Regex _vertexAttribPattern = new Regex( + @"^glVertexAttrib(?[1-4])(?d|f|s)(?v?)$", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static readonly string[] _components = { "X", "Y", "Z", "W" }; + + public IReadOnlyList Apply( + GlCommandDefinition command, + IReadOnlyList methods, + GlEquivalentTypeMapper typeMapper + ) + { + ArgumentNullException.ThrowIfNull(command); + ArgumentNullException.ThrowIfNull(methods); + ArgumentNullException.ThrowIfNull(typeMapper); + + Match matrixMatch = _matrixUniformPattern.Match(command.Name); + if (matrixMatch.Success) + { + return RewriteMatrixUniform(command, matrixMatch, typeMapper); + } + + Match uniformMatch = _uniformPattern.Match(command.Name); + if (uniformMatch.Success) + { + return RewriteUniform(command, uniformMatch, typeMapper); + } + + Match vertexAttribMatch = _vertexAttribPattern.Match(command.Name); + if (vertexAttribMatch.Success) + { + return vertexAttribMatch.Groups["vector"].Value.Length == 0 + ? Array.AsReadOnly( + new[] + { + methods[0] with { Name = "VertexAttrib" }, + }) + : Array.Empty(); + } + + return methods; + } + + private static ReadOnlyCollection RewriteUniform( + GlCommandDefinition command, + Match match, + GlEquivalentTypeMapper typeMapper + ) + { + int width = int.Parse( + match.Groups["width"].Value, + CultureInfo.InvariantCulture); + string componentType = ReadComponentType(match.Groups["type"].Value); + bool isVector = match.Groups["vector"].Value.Length != 0; + int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1; + string methodName = match.Groups["family"].Value; + + ValidateUniformSignature( + command, + width, + componentType, + prefixCount, + isVector, + typeMapper); + + List prefix = ReadNativeParameters( + command, + prefixCount, + typeMapper); + List prefixArguments = ReadParameterArguments(prefix.Count); + if (isVector) + { + if (width is 2 or 3) + { + return Array.AsReadOnly(Array.Empty()); + } + + GlParameterDefinition valueParameter = command.Parameters[^1]; + string spanElementType = width == 1 + ? componentType + : $"Vec{width}<{componentType}>"; + List parameters = new List(prefix) + { + new ManagedParameterDefinition( + $"ReadOnlySpan<{spanElementType}>", + "values", + valueParameter), + }; + ManagedInvocation invocation = width == 1 + ? new PinnedSpanInvocation( + prefixArguments, + parameters.Count - 1, + componentType) + : new PinnedVectorSpanInvocation( + prefixArguments, + parameters.Count - 1, + componentType, + spanElementType); + return Array.AsReadOnly( + new[] + { + new ManagedMethodDefinition( + methodName, + "void", + Array.AsReadOnly(parameters.ToArray()), + command, + invocation), + }); + } + + List methods = new List(); + List scalarParameters = ReadNativeParameters( + command, + command.Parameters.Count, + typeMapper); + methods.Add( + new ManagedMethodDefinition( + methodName, + "void", + Array.AsReadOnly(scalarParameters.ToArray()), + command, + new DirectInvocation(ReadParameterArguments(scalarParameters.Count)))); + + if (width > 1) + { + List vectorParameters = new List(prefix) + { + new ManagedParameterDefinition( + $"Vec{width}<{componentType}>", + "value"), + }; + List vectorArguments = new List(prefixArguments); + for (int index = 0; index < width; index++) + { + vectorArguments.Add( + new VectorComponentArgument( + vectorParameters.Count - 1, + _components[index])); + } + + methods.Add( + new ManagedMethodDefinition( + methodName, + "void", + Array.AsReadOnly(vectorParameters.ToArray()), + command, + new DirectInvocation(Array.AsReadOnly(vectorArguments.ToArray())))); + } + + return Array.AsReadOnly(methods.ToArray()); + } + + private static IReadOnlyList RewriteMatrixUniform( + GlCommandDefinition command, + Match match, + GlEquivalentTypeMapper typeMapper + ) + { + int columns = int.Parse( + match.Groups["columns"].Value, + CultureInfo.InvariantCulture); + int rows = match.Groups["rows"].Success + ? int.Parse( + match.Groups["rows"].Value, + CultureInfo.InvariantCulture) + : columns; + string componentType = ReadComponentType(match.Groups["type"].Value); + int prefixCount = match.Groups["family"].Value == "ProgramUniform" ? 2 : 1; + string methodName = match.Groups["family"].Value; + + ValidateMatrixSignature( + command, + columns, + rows, + componentType, + prefixCount, + typeMapper); + if (columns != 4 || rows != 4 || componentType != "float") + { + return Array.Empty(); + } + + List prefix = ReadNativeParameters( + command, + prefixCount, + typeMapper); + List prefixArguments = ReadParameterArguments(prefix.Count); + GlParameterDefinition transposeSource = command.Parameters[prefixCount + 1]; + GlParameterDefinition valueSource = command.Parameters[prefixCount + 2]; + + List singleParameters = new List(prefix) + { + new ManagedParameterDefinition("bool", "transpose", transposeSource), + new ManagedParameterDefinition("GraphicsMatrix", "value", valueSource), + }; + List spanParameters = new List(prefix) + { + new ManagedParameterDefinition("bool", "transpose", transposeSource), + new ManagedParameterDefinition( + "ReadOnlySpan", + "values", + valueSource), + }; + + return Array.AsReadOnly( + new[] + { + new ManagedMethodDefinition( + methodName, + "void", + Array.AsReadOnly(singleParameters.ToArray()), + command, + new Matrix4Invocation( + prefixArguments, + singleParameters.Count - 2, + singleParameters.Count - 1, + false)), + new ManagedMethodDefinition( + methodName, + "void", + Array.AsReadOnly(spanParameters.ToArray()), + command, + new Matrix4Invocation( + prefixArguments, + spanParameters.Count - 2, + spanParameters.Count - 1, + true)), + }); + } + + private static void ValidateUniformSignature( + GlCommandDefinition command, + int width, + string componentType, + int prefixCount, + bool isVector, + GlEquivalentTypeMapper typeMapper + ) + { + int expectedCount = prefixCount + (isVector ? 2 : width); + if (command.Parameters.Count != expectedCount) + { + throw new InvalidOperationException( + $"Uniform command '{command.Name}' does not match its recognized shape."); + } + + if (isVector) + { + GlParameterDefinition count = command.Parameters[prefixCount]; + GlParameterDefinition value = command.Parameters[prefixCount + 1]; + string expectedLength = $"{count.Name}*{width}"; + string expectedKind = $"Vector{width}"; + if (typeMapper.ReadParameterType(value) != $"{componentType}*" || + !string.Equals(value.Length, expectedLength, StringComparison.Ordinal) && + !(width == 1 && string.Equals( + value.Length, + count.Name, + StringComparison.Ordinal)) || + !string.Equals(value.Kind, expectedKind, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"Uniform command '{command.Name}' has inconsistent vector metadata."); + } + + return; + } + + for (int index = prefixCount; index < command.Parameters.Count; index++) + { + if (typeMapper.ReadParameterType(command.Parameters[index]) != componentType) + { + throw new InvalidOperationException( + $"Uniform command '{command.Name}' has inconsistent component types."); + } + } + } + + private static void ValidateMatrixSignature( + GlCommandDefinition command, + int columns, + int rows, + string componentType, + int prefixCount, + GlEquivalentTypeMapper typeMapper + ) + { + if (command.Parameters.Count != prefixCount + 3) + { + throw new InvalidOperationException( + $"Matrix command '{command.Name}' does not match its recognized shape."); + } + + GlParameterDefinition count = command.Parameters[prefixCount]; + GlParameterDefinition value = command.Parameters[prefixCount + 2]; + if (typeMapper.ReadParameterType(value) != $"{componentType}*" || + !string.Equals( + value.Kind, + $"Matrix{columns}x{rows}", + StringComparison.Ordinal) || + !string.Equals( + value.Length, + $"{count.Name}*{columns * rows}", + StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"Matrix command '{command.Name}' has inconsistent matrix metadata."); + } + } + + private static List ReadNativeParameters( + GlCommandDefinition command, + int count, + GlEquivalentTypeMapper typeMapper + ) + { + List parameters = new List(count); + for (int index = 0; index < count; index++) + { + GlParameterDefinition source = command.Parameters[index]; + parameters.Add( + new ManagedParameterDefinition( + typeMapper.ReadParameterType(source), + source.Name, + source)); + } + + return parameters; + } + + private static List ReadParameterArguments( + int count + ) + { + List arguments = new List(count); + for (int index = 0; index < count; index++) + { + arguments.Add(new ParameterArgument(index)); + } + + return arguments; + } + + private static string ReadComponentType( + string suffix + ) + { + return suffix switch + { + "f" => "float", + "i" => "int", + "ui" => "uint", + "d" => "double", + _ => throw new InvalidOperationException( + $"Unsupported uniform component suffix '{suffix}'."), + }; + } + + } +} diff --git a/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs b/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs new file mode 100644 index 00000000..723ac218 --- /dev/null +++ b/tools/StgSharp.GenerateGL/Generation/IGlApiFilter.cs @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------------- +// file="IGlApiFilter" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using System.Collections.Generic; +using StgSharp.GenerateGL.Registry; + +namespace StgSharp.GenerateGL.Generation +{ + internal interface IGlApiFilter + { + + IReadOnlyList Apply( + GlCommandDefinition command, + IReadOnlyList methods, + GlEquivalentTypeMapper typeMapper + ); + + } +} diff --git a/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs b/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs new file mode 100644 index 00000000..da29c4ad --- /dev/null +++ b/tools/StgSharp.GenerateGL/Generation/ManagedApiModel.cs @@ -0,0 +1,71 @@ +// ----------------------------------------------------------------------------- +// file="ManagedApiModel" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using System.Collections.Generic; +using StgSharp.GenerateGL.Registry; + +namespace StgSharp.GenerateGL.Generation +{ + internal sealed record ManagedMethodDefinition( + string Name, + string ReturnType, + IReadOnlyList Parameters, + GlCommandDefinition NativeCommand, + ManagedInvocation Invocation, + bool WrapHandleReturn = false + ); + + internal sealed record ManagedParameterDefinition( + string Type, + string Name, + GlParameterDefinition? NativeParameter = null, + bool IsHandle = false, + bool IsHandleSpan = false + ); + + internal abstract record ManagedInvocation; + + internal sealed record DirectInvocation( + IReadOnlyList Arguments + ) : ManagedInvocation; + + internal sealed record PinnedSpanInvocation( + IReadOnlyList PrefixArguments, + int SpanParameterIndex, + string ElementType + ) : ManagedInvocation; + + internal sealed record PinnedVectorSpanInvocation( + IReadOnlyList PrefixArguments, + int SpanParameterIndex, + string ComponentType, + string VectorType + ) : ManagedInvocation; + + internal sealed record Matrix4Invocation( + IReadOnlyList PrefixArguments, + int TransposeParameterIndex, + int MatrixParameterIndex, + bool IsSpan + ) : ManagedInvocation; + + internal abstract record ManagedArgument; + + internal sealed record ParameterArgument( + int ParameterIndex + ) : ManagedArgument; + + internal sealed record VectorComponentArgument( + int ParameterIndex, + string Component + ) : ManagedArgument; + + internal sealed record SpanLengthArgument( + int ParameterIndex + ) : ManagedArgument; + +} diff --git a/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs b/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs new file mode 100644 index 00000000..aab0cc18 --- /dev/null +++ b/tools/StgSharp.GenerateGL/Generation/ManagedApiPlanner.cs @@ -0,0 +1,119 @@ +// ----------------------------------------------------------------------------- +// file="ManagedApiPlanner" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using StgSharp.GenerateGL.Registry; + +namespace StgSharp.GenerateGL.Generation +{ + internal sealed class ManagedApiPlanner + { + + private readonly IReadOnlyList _filters = Array.AsReadOnly( + new IGlApiFilter[] + { + new GlOverloadApiFilter(), + new GlHandleApiFilter(), + }); + private readonly GlEquivalentTypeMapper _typeMapper; + + public ManagedApiPlanner( + GlEquivalentTypeMapper typeMapper + ) + { + ArgumentNullException.ThrowIfNull(typeMapper); + _typeMapper = typeMapper; + } + + public IReadOnlyList Plan( + IReadOnlyList commands + ) + { + ArgumentNullException.ThrowIfNull(commands); + + List result = new List(); + foreach (GlCommandDefinition command in commands) + { + IReadOnlyList methods = Array.AsReadOnly( + new[] + { + CreateRawMethod(command), + }); + foreach (IGlApiFilter filter in _filters) + { + methods = filter.Apply(command, methods, _typeMapper); + } + + result.AddRange(methods); + } + + return Array.AsReadOnly(result.ToArray()); + } + + public static void Validate( + IEnumerable methods + ) + { + ArgumentNullException.ThrowIfNull(methods); + + Dictionary signatures = new Dictionary( + StringComparer.Ordinal); + foreach (ManagedMethodDefinition method in methods) + { + string signature = $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})"; + if (signatures.TryGetValue(signature, out ManagedMethodDefinition? previous)) + { + throw new InvalidOperationException( + $"Managed overload '{signature}' is produced by both " + + $"'{previous.NativeCommand.Name}' and '{method.NativeCommand.Name}'."); + } + + signatures.Add(signature, method); + } + } + + private ManagedMethodDefinition CreateRawMethod( + GlCommandDefinition command + ) + { + List parameters = new List( + command.Parameters.Count); + List arguments = new List( + command.Parameters.Count); + for (int index = 0; index < command.Parameters.Count; index++) + { + GlParameterDefinition source = command.Parameters[index]; + parameters.Add( + new ManagedParameterDefinition( + _typeMapper.ReadParameterType(source), + source.Name, + source)); + arguments.Add(new ParameterArgument(index)); + } + + return new ManagedMethodDefinition( + ReadMethodName(command.Name), + _typeMapper.ReadReturnType(command), + Array.AsReadOnly(parameters.ToArray()), + command, + new DirectInvocation(Array.AsReadOnly(arguments.ToArray()))); + } + + private static string ReadMethodName( + string commandName + ) + { + return commandName.StartsWith("gl", StringComparison.Ordinal) && + commandName.Length > 2 + ? commandName[2..] + : commandName; + } + + } +} diff --git a/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs b/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs index 2f03650e..4422eaa3 100644 --- a/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs +++ b/tools/StgSharp.GenerateGL/Generation/OpenGlGenerator.cs @@ -49,22 +49,41 @@ namespace StgSharp.GenerateGL.Generation { ArgumentNullException.ThrowIfNull(registry); - IReadOnlyList commands = ReadCoreCommands(registry); + IReadOnlyList commandGroups = ReadCoreCommandGroups(registry); + IReadOnlyList commands = Array.AsReadOnly( + commandGroups.SelectMany(value => value.Commands).ToArray()); GlEquivalentTypeMapper typeMapper = new GlEquivalentTypeMapper(registry); - return new GeneratedFileSet( - new[] - { - new GeneratedFile("glconst.cs", GenerateConstants(registry)), + ManagedApiPlanner apiPlanner = new ManagedApiPlanner(typeMapper); + List managedGroups = commandGroups.Select( + group => new ManagedCommandGroup( + group, + apiPlanner.Plan(group.Commands))).ToList(); + ManagedApiPlanner.Validate( + managedGroups.SelectMany(value => value.Methods)); + List files = new List + { + new GeneratedFile("glconst.cs", GenerateConstants(registry)), + new GeneratedFile( + "OpenglContext.g.cs", + GenerateContext(commands, typeMapper)), + }; + for (int index = 0; index < managedGroups.Count; index++) + { + ManagedCommandGroup managedGroup = managedGroups[index]; + GlCommandGroup group = managedGroup.Source; + files.Add( new GeneratedFile( - "OpenglContext.g.cs", - GenerateContext(commands, typeMapper)), - new GeneratedFile( - "OpenGLFunction.Raw.g.cs", - GenerateFunctions(commands, typeMapper)), - }); + group.FileName, + GenerateFunctions( + managedGroup.Methods, + group.Description, + index == 0))); + } + + return new GeneratedFileSet(files); } - private static ReadOnlyCollection ReadCoreCommands( + private static ReadOnlyCollection ReadCoreCommandGroups( GlRegistryModel registry ) { @@ -82,6 +101,8 @@ namespace StgSharp.GenerateGL.Generation List order = new List(); HashSet known = new HashSet(StringComparer.Ordinal); HashSet active = new HashSet(StringComparer.Ordinal); + Dictionary introducedVersions = new Dictionary( + StringComparer.Ordinal); foreach (GlFeatureDefinition feature in registry.Features.Where( value => string.Equals(value.Api, ApiName, StringComparison.Ordinal))) { @@ -103,6 +124,7 @@ namespace StgSharp.GenerateGL.Generation if (known.Add(reference.Name)) { order.Add(reference.Name); + introducedVersions.Add(reference.Name, feature.Number); } } else @@ -113,8 +135,72 @@ namespace StgSharp.GenerateGL.Generation } } - return Array.AsReadOnly( - order.Where(active.Contains).Select(value => definitions[value]).ToArray()); + List foundation = new List(); + Dictionary> versionGroups = new Dictionary>( + StringComparer.Ordinal); + List versionOrder = new List(); + foreach (string commandName in order.Where(active.Contains)) + { + string version = introducedVersions[commandName]; + if (IsFoundationVersion(version)) + { + foundation.Add(definitions[commandName]); + continue; + } + + if (!versionGroups.TryGetValue( + version, + out List? commands)) + { + commands = new List(); + versionGroups.Add(version, commands); + versionOrder.Add(version); + } + + commands.Add(definitions[commandName]); + } + + List groups = new List + { + new GlCommandGroup( + "OpenGLFunction.Base.g.cs", + "Desktop OpenGL forwarding methods introduced through 3.2 and retained by the target core profile", + Array.AsReadOnly(foundation.ToArray())), + }; + foreach (string version in versionOrder) + { + groups.Add( + new GlCommandGroup( + $"OpenGLFunction.v{version.Replace('.', '_')}.g.cs", + $"Desktop OpenGL {version} core-profile forwarding methods", + Array.AsReadOnly(versionGroups[version].ToArray()))); + } + + return Array.AsReadOnly(groups.ToArray()); + } + + private static bool IsFoundationVersion( + string version + ) + { + string[] components = version.Split('.'); + if (components.Length != 2 || + !int.TryParse( + components[0], + NumberStyles.None, + CultureInfo.InvariantCulture, + out int major) || + !int.TryParse( + components[1], + NumberStyles.None, + CultureInfo.InvariantCulture, + out int minor)) + { + throw new InvalidDataException( + $"OpenGL feature version '{version}' is not a major.minor number."); + } + + return major < 3 || major == 3 && minor <= 2; } private static bool AppliesToCoreGl( @@ -319,62 +405,53 @@ namespace StgSharp.GenerateGL.Generation } private static string GenerateFunctions( - IReadOnlyList commands, - GlEquivalentTypeMapper typeMapper + IReadOnlyList methods, + string description, + bool includeContextState ) { StringBuilder builder = new StringBuilder(512 * 1024); - AppendGeneratedHeader(builder, "Desktop OpenGL core forwarding methods"); + AppendGeneratedHeader(builder, description); + builder.Append("using StgSharp.Mathematics.Graphics;\n\n"); + builder.Append("using System;\n"); builder.Append("using System.Runtime.CompilerServices;\n\n"); builder.Append("namespace StgSharp.Graphics.OpenGL\n"); builder.Append("{\n"); builder.Append(" public unsafe partial class OpenGLFunction\n"); builder.Append(" {\n"); - 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) + if (includeContextState) + { + builder.Append("\n private readonly OpenglContext* __context;\n"); + builder.Append("\n internal OpenGLFunction(OpenglContext* context)\n"); + builder.Append(" {\n"); + builder.Append(" __context = context;\n"); + builder.Append(" }\n"); + } + + foreach (ManagedMethodDefinition method in methods) { - 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(method.ReturnType); builder.Append(' '); - builder.Append(methodName); + builder.Append(method.Name); builder.Append('('); - for (int index = 0; index < command.Parameters.Count; index++) + for (int index = 0; index < method.Parameters.Count; index++) { if (index != 0) { builder.Append(", "); } - GlParameterDefinition parameter = command.Parameters[index]; - builder.Append(typeMapper.ReadParameterType(parameter)); + ManagedParameterDefinition parameter = method.Parameters[index]; + builder.Append(parameter.Type); 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"); + AppendInvocation(builder, method); builder.Append(" }\n"); } @@ -383,14 +460,252 @@ namespace StgSharp.GenerateGL.Generation return builder.ToString(); } - private static string ReadMethodName( - string commandName + private static void AppendInvocation( + StringBuilder builder, + ManagedMethodDefinition method ) { - return commandName.StartsWith("gl", StringComparison.Ordinal) && - commandName.Length > 2 - ? commandName[2..] - : commandName; + switch (method.Invocation) + { + case DirectInvocation direct: + AppendDirectInvocation(builder, method, direct); + break; + case PinnedSpanInvocation pinned: + AppendPinnedSpanInvocation(builder, method, pinned); + break; + case PinnedVectorSpanInvocation pinnedVector: + AppendPinnedVectorSpanInvocation(builder, method, pinnedVector); + break; + case Matrix4Invocation matrix: + AppendMatrix4Invocation(builder, method, matrix); + break; + default: + throw new InvalidOperationException( + $"Unsupported managed invocation '{method.Invocation.GetType().Name}'."); + } + } + + private static void AppendDirectInvocation( + StringBuilder builder, + ManagedMethodDefinition method, + DirectInvocation invocation + ) + { + List fixedParameters = new List(); + for (int index = 0; index < method.Parameters.Count; index++) + { + if (method.Parameters[index].IsHandleSpan) + { + fixedParameters.Add(index); + } + } + + int indentation = 12; + foreach (int parameterIndex in fixedParameters) + { + ManagedParameterDefinition parameter = method.Parameters[parameterIndex]; + builder.Append(' ', indentation); + builder.Append("fixed (GlHandle* __"); + builder.Append(parameter.Name); + builder.Append(" = "); + builder.Append(EscapeIdentifier(parameter.Name)); + builder.Append(")\n"); + builder.Append(' ', indentation); + builder.Append("{\n"); + indentation += 4; + } + + builder.Append(' ', indentation); + if (method.WrapHandleReturn) + { + builder.Append("return GlHandle.Create("); + } + else if (method.ReturnType != "void") + { + builder.Append("return "); + } + + AppendNativeCall(builder, method, invocation.Arguments); + builder.Append(method.WrapHandleReturn ? ");\n" : ";\n"); + for (int index = fixedParameters.Count - 1; index >= 0; index--) + { + indentation -= 4; + builder.Append(' ', indentation); + builder.Append("}\n"); + } + } + + private static void AppendPinnedSpanInvocation( + StringBuilder builder, + ManagedMethodDefinition method, + PinnedSpanInvocation invocation + ) + { + ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex]; + builder.Append(" fixed ("); + builder.Append(invocation.ElementType); + builder.Append("* __value = "); + builder.Append(EscapeIdentifier(span.Name)); + builder.Append(")\n"); + builder.Append(" {\n"); + builder.Append(" __context->"); + builder.Append(method.NativeCommand.Name); + builder.Append('('); + AppendArguments(builder, method, invocation.PrefixArguments); + if (invocation.PrefixArguments.Count != 0) + { + builder.Append(", "); + } + + builder.Append(EscapeIdentifier(span.Name)); + builder.Append(".Length, __value);\n"); + builder.Append(" }\n"); + } + + private static void AppendPinnedVectorSpanInvocation( + StringBuilder builder, + ManagedMethodDefinition method, + PinnedVectorSpanInvocation invocation + ) + { + ManagedParameterDefinition span = method.Parameters[invocation.SpanParameterIndex]; + string spanName = EscapeIdentifier(span.Name); + builder.Append(" fixed ("); + builder.Append(invocation.VectorType); + builder.Append("* __vector = "); + builder.Append(spanName); + builder.Append(")\n"); + builder.Append(" {\n"); + builder.Append(" __context->"); + builder.Append(method.NativeCommand.Name); + builder.Append('('); + AppendArguments(builder, method, invocation.PrefixArguments); + if (invocation.PrefixArguments.Count != 0) + { + builder.Append(", "); + } + + builder.Append(spanName); + builder.Append(".Length, ("); + builder.Append(invocation.ComponentType); + builder.Append("*)__vector);\n"); + builder.Append(" }\n"); + } + + private static void AppendMatrix4Invocation( + StringBuilder builder, + ManagedMethodDefinition method, + Matrix4Invocation invocation + ) + { + ManagedParameterDefinition transpose = method.Parameters[invocation.TransposeParameterIndex]; + ManagedParameterDefinition matrix = method.Parameters[invocation.MatrixParameterIndex]; + string transposeName = EscapeIdentifier(transpose.Name); + string matrixName = EscapeIdentifier(matrix.Name); + if (invocation.IsSpan) + { + builder.Append(" fixed (GraphicsMatrix* __value = "); + builder.Append(matrixName); + builder.Append(")\n"); + builder.Append(" {\n"); + builder.Append(" __context->"); + } + else + { + builder.Append(" __context->"); + } + + builder.Append(method.NativeCommand.Name); + builder.Append('('); + AppendArguments(builder, method, invocation.PrefixArguments); + if (invocation.PrefixArguments.Count != 0) + { + builder.Append(", "); + } + + builder.Append(invocation.IsSpan ? $"{matrixName}.Length" : "1"); + builder.Append(", "); + builder.Append(transposeName); + builder.Append(" ? (byte)1 : (byte)0, (float*)"); + builder.Append(invocation.IsSpan ? "__value" : $"&{matrixName}"); + builder.Append(");\n"); + if (invocation.IsSpan) + { + builder.Append(" }\n"); + } + } + + private static void AppendNativeCall( + StringBuilder builder, + ManagedMethodDefinition method, + IReadOnlyList arguments + ) + { + builder.Append("__context->"); + builder.Append(method.NativeCommand.Name); + builder.Append('('); + AppendArguments(builder, method, arguments); + builder.Append(')'); + } + + private static void AppendArguments( + StringBuilder builder, + ManagedMethodDefinition method, + IReadOnlyList arguments + ) + { + for (int index = 0; index < arguments.Count; index++) + { + if (index != 0) + { + builder.Append(", "); + } + + switch (arguments[index]) + { + case ParameterArgument parameterArgument: + ManagedParameterDefinition parameter = method.Parameters[ + parameterArgument.ParameterIndex]; + string parameterName = EscapeIdentifier(parameter.Name); + if (!parameter.IsHandle) + { + builder.Append(parameterName); + } + else if (parameter.Type.EndsWith('*')) + { + builder.Append("(uint*)"); + builder.Append(parameterName); + } + else if (parameter.IsHandleSpan) + { + builder.Append("(uint*)__"); + builder.Append(parameter.Name); + } + else + { + builder.Append(parameterName); + builder.Append(".Value"); + } + + break; + case SpanLengthArgument spanLengthArgument: + builder.Append( + EscapeIdentifier( + method.Parameters[spanLengthArgument.ParameterIndex].Name)); + builder.Append(".Length"); + break; + case VectorComponentArgument componentArgument: + builder.Append( + EscapeIdentifier( + method.Parameters[componentArgument.ParameterIndex].Name)); + builder.Append('.'); + builder.Append(componentArgument.Component); + break; + default: + throw new InvalidOperationException( + $"Unsupported managed argument '{arguments[index].GetType().Name}'."); + } + } } private static string EscapeIdentifier( @@ -418,5 +733,16 @@ namespace StgSharp.GenerateGL.Generation string Literal ); + private sealed record GlCommandGroup( + string FileName, + string Description, + IReadOnlyList Commands + ); + + private sealed record ManagedCommandGroup( + GlCommandGroup Source, + IReadOnlyList Methods + ); + } }