make some changes to opengl api gen, discussing on rewrite of all opengl binding, making it neat and structral

This commit is contained in:
Youmu Liu
2026-08-25 17:33:45 +08:00
parent 708a915549
commit 65041ff8bc
63 changed files with 14404 additions and 8851 deletions
@@ -41,7 +41,7 @@ namespace GenerateGL.CodeGen
return $"byte{new string('*', pointerLevel)}";
}
string mapped = MapType(param.CType, allowVoid:false);
string mapped = MapType(param.CType);
if (pointerLevel > 0)
{
if (mapped == "void") {
@@ -57,7 +57,7 @@ namespace GenerateGL.CodeGen
int pointerLevel
)
{
string mapped = MapType(cType, allowVoid:true);
string mapped = MapType(cType);
if (pointerLevel > 0) {
return $"{mapped}{new string('*', pointerLevel)}";
}
@@ -70,12 +70,6 @@ namespace GenerateGL.CodeGen
{
if (param.PointerLevel == 0)
{
if (param.CType.Equals("GLenum", StringComparison.Ordinal) &&
LooksLikeShaderType(param.Name) &&
!param.IsPointer) {
return "ShaderType";
}
if (param.CType.Equals("GLuint", StringComparison.Ordinal) &&
LooksLikeHandle(param.Name) &&
!param.IsPointer) {
@@ -88,7 +82,7 @@ namespace GenerateGL.CodeGen
return $"byte{new string('*', pointerLevel)}";
}
string mapped = MapType(param.CType, allowVoid:false);
string mapped = MapType(param.CType);
if (pointerLevel > 0)
{
if (mapped == "void") {
@@ -104,29 +98,42 @@ namespace GenerateGL.CodeGen
int pointerLevel
)
{
string mapped = MapType(cType, allowVoid:true);
string mapped = MapType(cType);
if (pointerLevel > 0) {
return $"{mapped}{new string('*', pointerLevel)}";
}
return mapped;
}
public static bool TryMapConstPointerToSpan(
GlParam param,
out string spanElementType
public static bool IsHandleReturn(
GlCommand command
)
{
spanElementType = string.Empty;
if (!param.IsConst || param.PointerLevel != 1) {
if (command.ReturnPointerLevel != 0 ||
!command.ReturnType.Equals("GLuint", StringComparison.Ordinal)) {
return false;
}
string mapped = MapType(param.CType, allowVoid:false);
return command.Name is "glCreateProgram" or "glCreateShader" or
"glCreateShaderProgramv";
}
public static bool TryMapPointerElement(
GlParam param,
out string elementType
)
{
elementType = string.Empty;
if (param.PointerLevel != 1) {
return false;
}
string mapped = MapType(param.CType);
if (mapped is "void") {
return false;
}
spanElementType = mapped;
elementType = mapped;
return true;
}
@@ -150,41 +157,34 @@ namespace GenerateGL.CodeGen
name.Contains("transformfeedback", StringComparison.OrdinalIgnoreCase);
}
private static bool LooksLikeShaderType(
string name
)
{
if (string.IsNullOrWhiteSpace(name)) {
return false;
}
return name.Contains("shadertype", StringComparison.OrdinalIgnoreCase) ||
name.Equals("type", StringComparison.OrdinalIgnoreCase);
}
private static string MapType(
string cType,
bool allowVoid
string cType
)
{
return cType switch
{
"void" when allowVoid => "void",
"GLenum" or "GLbitfield" => "uint",
"GLboolean" => "byte",
"GLuint" or "GLhandleARB" => "uint",
"GLint" or "GLsizei" or "GLfixed" => "int",
"GLint64" => "long",
"GLuint64" => "ulong",
"GLshort" => "short",
"GLbyte" => "sbyte",
"GLubyte" => "byte",
"GLushort" => "ushort",
"GLfloat" => "float",
"GLdouble" => "double",
"GLclampf" or "GLclampd" => "float",
"GLhalf" => "ushort",
"GLclampf" => "float",
"GLclampd" => "double",
"GLintptr" => "nint",
"GLsizeiptr" => "nuint",
"GLsizeiptr" => "nint",
"GLchar" => "byte",
"GLsync" => "GLsync",
_ => allowVoid && cType == "void" ? "void" : "IntPtr"
"GLsync" => "glSync",
"GLDEBUGPROC" => "IntPtr",
"void" => "void",
_ => throw new NotSupportedException($"Unsupported OpenGL C type: {cType}")
};
}
+170 -60
View File
@@ -32,6 +32,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace GenerateGL.CodeGen
{
@@ -69,6 +70,7 @@ namespace GenerateGL.CodeGen
}
Directory.CreateDirectory(_outputRoot);
List<GlCommand> all = new List<GlCommand>();
List<(string Version, List<GlCommand> Commands)> commandsByVersion = new();
HashSet<string> previous = new HashSet<string>(StringComparer.Ordinal);
foreach (string v in TargetVersions)
{
@@ -79,8 +81,15 @@ namespace GenerateGL.CodeGen
previous.Add(cmd.Name);
}
all.AddRange(versionCmds);
WriteOpenGLFunctionFile(v, versionCmds);
WriteGlContextFile(v, versionCmds);
commandsByVersion.Add((v, versionCmds));
}
IReadOnlyDictionary<string, string> managedNames = ManagedNameResolver.Resolve(
all.Where(ShouldGenerateWrapper));
foreach ((string version, List<GlCommand> commands) in commandsByVersion)
{
WriteOpenGLFunctionFile(version, commands, managedNames);
UniformOverloadEmitter.Write(_outputRoot, version, commands);
}
WriteMergedGlContextFile(all);
WriteCommandHistoryFile(BuildCommandHistory());
@@ -249,41 +258,7 @@ namespace GenerateGL.CodeGen
sb.AppendLine(" }");
sb.AppendLine("}");
string fileName = Path.Combine(_outputRoot, "glCommandHistory.cs");
File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);
}
private void WriteGlContextFile(
string version,
IReadOnlyList<GlCommand> commands
)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("// <auto-generated>");
sb.AppendLine($"// Generated by StgSharp.GenerateGL on {DateTime.UtcNow.ToString("u", CultureInfo.InvariantCulture)}");
sb.AppendLine($"// Version: {version}");
sb.AppendLine("// </auto-generated>");
sb.AppendLine("using System;\nusing System.Runtime.CompilerServices;\n");
sb.AppendLine("namespace StgSharp.Graphics.OpenGL");
sb.AppendLine("{");
sb.AppendLine(" public unsafe partial struct OpenglContext");
sb.AppendLine(" {");
foreach (GlCommand cmd in commands.OrderBy(c => c!.Name, StringComparer.Ordinal))
{
string ret = CsTypeMapper.MapDelegateReturn(cmd.ReturnType, cmd.ReturnPointerLevel);
List<string> paramTypes = new List<string>();
foreach (GlParam p in cmd.Parameters)
{
string mapped = CsTypeMapper.MapDelegateParameter(p);
paramTypes.Add(mapped);
}
string decl = DelegateSignature(ret, paramTypes);
sb.AppendLine($" internal unsafe {decl} {cmd.Name};");
}
sb.AppendLine(" }");
sb.AppendLine("}");
string fileName = Path.Combine(_outputRoot, $"glContext.v{version.Replace('.', '_')}.cs");
string fileName = Path.Combine(_outputRoot, "GlCommandHistory.g.cs");
File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);
}
@@ -322,13 +297,14 @@ namespace GenerateGL.CodeGen
sb.AppendLine(" }");
sb.AppendLine("}");
string fileName = Path.Combine(_outputRoot, "glContext.cs");
string fileName = Path.Combine(_outputRoot, "OpenglContext.g.cs");
File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);
}
private void WriteOpenGLFunctionFile(
string version,
IReadOnlyList<GlCommand> commands
IReadOnlyList<GlCommand> commands,
IReadOnlyDictionary<string, string> managedNames
)
{
StringBuilder sb = new StringBuilder();
@@ -341,33 +317,52 @@ namespace GenerateGL.CodeGen
sb.AppendLine("{");
sb.AppendLine(" public unsafe partial class OpenGLFunction");
sb.AppendLine(" {");
foreach (GlCommand cmd in commands.OrderBy(c => c!.Name, StringComparer.Ordinal))
foreach (GlCommand cmd in commands.Where(ShouldGenerateWrapper)
.OrderBy(c => c!.Name, StringComparer.Ordinal))
{
string ret = CsTypeMapper.MapReturn(cmd.ReturnType, cmd.ReturnPointerLevel);
bool returnsHandle = CsTypeMapper.IsHandleReturn(cmd);
string ret = returnsHandle
? "GlHandle"
: CsTypeMapper.MapReturn(cmd.ReturnType, cmd.ReturnPointerLevel);
string rawName = cmd.Name; // original glXxx
string methodName = TrimGlPrefix(rawName);
string methodName = managedNames[rawName];
ManagedCommandPlan commandPlan = ManagedCommandPlan.Create(cmd);
List<string> paramList = new List<string>();
List<string> callArgs = new List<string>();
List<(string Type,string Name)> pinned = new();
List<(string Type,string Name,bool IsOut)> pinned = new();
foreach (ManagedParameterPlan parameterPlan in commandPlan.PublicParameters)
{
GlParam p = parameterPlan.Source;
string pname = SafeName(p.Name);
paramList.Add($"{parameterPlan.PublicType} {pname}");
if (parameterPlan.IsSpan)
{
pinned.Add((parameterPlan.ElementType!, pname, false));
} else if (parameterPlan.Kind == ManagedParameterKind.Out) {
pinned.Add((parameterPlan.ElementType!, pname, true));
}
}
foreach (GlParam p in cmd.Parameters)
{
string pname = SafeName(p.Name);
if (CsTypeMapper.TryMapConstPointerToSpan(p, out string spanElementType))
if (commandPlan.DerivedLengthSources.TryGetValue(
p.Name, out string? sourceSpanName))
{
string nativeType = CsTypeMapper.MapParameter(p);
callArgs.Add($"checked(({nativeType}){SafeName(sourceSpanName)}.Length)");
continue;
}
ManagedParameterPlan parameterPlan = commandPlan.GetParameter(p.Name);
if (parameterPlan.IsSpan || parameterPlan.Kind == ManagedParameterKind.Out)
{
paramList.Add($"ReadOnlySpan<{spanElementType}> {pname}");
pinned.Add((spanElementType, pname));
callArgs.Add($"{pname}Ptr");
} else if (parameterPlan.PublicType == "GlHandle")
{
callArgs.Add($"{pname}.Value");
} else
{
string mapped = CsTypeMapper.MapParameter(p);
paramList.Add($"{mapped} {pname}");
if (mapped == "GlHandle")
{
callArgs.Add($"{pname}.Value");
} else
{
callArgs.Add(pname);
}
callArgs.Add(pname);
}
}
string signature = string.Join(", ", paramList);
@@ -375,20 +370,29 @@ namespace GenerateGL.CodeGen
sb.AppendLine($" [MethodImpl(MethodImplOptions.AggressiveInlining)]");
sb.AppendLine($" public {ret} {methodName}({signature})");
sb.AppendLine(" {");
foreach ((string _, string name, bool isOut) in pinned.Where(p => p.IsOut)) {
sb.AppendLine($" {name} = default;");
}
AppendSpanLengthGuards(sb, cmd, commandPlan);
string prefix = ret == "void" ? string.Empty : "return ";
string callExpression = $"Context.{rawName}({call})";
if (returnsHandle) {
callExpression = $"GlHandle.Create({callExpression})";
}
if (pinned.Count == 0)
{
sb.AppendLine($" {prefix}Context.{rawName}({call});");
sb.AppendLine($" {prefix}{callExpression};");
} else
{
string indent = " ";
foreach ((string Type, string Name) pin in pinned)
foreach ((string Type, string Name, bool IsOut) pin in pinned)
{
sb.AppendLine($"{indent}fixed ({pin.Type}* {pin.Name}Ptr = {pin.Name})");
string pinSource = pin.IsOut ? $"&{pin.Name}" : pin.Name;
sb.AppendLine($"{indent}fixed ({pin.Type}* {pin.Name}Ptr = {pinSource})");
sb.AppendLine($"{indent}{{");
indent += " ";
}
sb.AppendLine($"{indent}{prefix}Context.{rawName}({call});");
sb.AppendLine($"{indent}{prefix}{callExpression};");
for (int i = 0; i < pinned.Count; i++)
{
indent = indent.Substring(0, indent.Length - 4);
@@ -401,10 +405,116 @@ namespace GenerateGL.CodeGen
sb.AppendLine(" }");
sb.AppendLine("}");
string fileName = Path.Combine(_outputRoot, $"glFunction.v{version.Replace('.', '_')}.cs");
string fileName = Path.Combine(
_outputRoot,
$"OpenGLFunction.Raw.v{version.Replace('.', '_')}.g.cs");
File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);
}
private static void AppendSpanLengthGuards(
StringBuilder sb,
GlCommand command,
ManagedCommandPlan plan
)
{
foreach ((string lengthName, string sourceName) in plan.DerivedLengthSources)
{
foreach (ManagedParameterPlan related in command.Parameters
.Select(p => plan.GetParameter(p.Name))
.Where(p => p.IsSpan && p.Source.Length == lengthName &&
p.Source.Name != sourceName))
{
string relatedName = SafeName(related.Source.Name);
string safeSourceName = SafeName(sourceName);
sb.AppendLine($" if ({relatedName}.Length != {safeSourceName}.Length) {{");
sb.AppendLine($" throw new ArgumentException(\"Span lengths controlled by {lengthName} must match.\", nameof({relatedName}));");
sb.AppendLine(" }");
}
}
foreach (ManagedParameterPlan parameter in command.Parameters
.Select(p => plan.GetParameter(p.Name)).Where(p => p.IsSpan))
{
string? expectedLength = BuildExpectedLengthExpression(parameter.Source, plan);
if (expectedLength is null) {
continue;
}
string parameterName = SafeName(parameter.Source.Name);
sb.AppendLine($" if ({parameterName}.Length < {expectedLength}) {{");
sb.AppendLine($" throw new ArgumentException(\"Span is shorter than the OpenGL length expression '{parameter.Source.Length}'.\", nameof({parameterName}));");
sb.AppendLine(" }");
}
}
private static string? BuildExpectedLengthExpression(
GlParam parameter,
ManagedCommandPlan plan
)
{
string length = parameter.Length?.Replace(" ", string.Empty,
StringComparison.Ordinal) ?? string.Empty;
if (length.Length == 0 || length.StartsWith("COMPSIZE(", StringComparison.Ordinal) ||
plan.DerivedLengthSources.ContainsKey(length)) {
return null;
}
if (int.TryParse(length, NumberStyles.None, CultureInfo.InvariantCulture,
out int constantLength)) {
return constantLength.ToString(CultureInfo.InvariantCulture);
}
ManagedParameterPlan? scalar = plan.PublicParameters.FirstOrDefault(p =>
p.Source.PointerLevel == 0 && p.Source.Name == length);
if (scalar is not null) {
return $"checked((int){SafeName(length)})";
}
Match multiply = Regex.Match(
length, "^(?<name>[A-Za-z_][A-Za-z0-9_]*)\\*(?<factor>[0-9]+)$",
RegexOptions.CultureInvariant);
if (!multiply.Success) {
multiply = Regex.Match(
length, "^(?<factor>[0-9]+)\\*(?<name>[A-Za-z_][A-Za-z0-9_]*)$",
RegexOptions.CultureInvariant);
}
if (multiply.Success) {
return $"checked((int){SafeName(multiply.Groups["name"].Value)} * {multiply.Groups["factor"].Value})";
}
Match divide = Regex.Match(
length, "^(?<name>[A-Za-z_][A-Za-z0-9_]*)/(?<divisor>[0-9]+)$",
RegexOptions.CultureInvariant);
return divide.Success
? $"checked((int){SafeName(divide.Groups["name"].Value)} / {divide.Groups["divisor"].Value})"
: null;
}
private bool ShouldGenerateWrapper(
GlCommand command
)
{
if (UniformOverloadEmitter.IsSetterCommand(command)) {
return false;
}
return !IsRedundantVertexAttribVectorCommand(command);
}
private bool IsRedundantVertexAttribVectorCommand(
GlCommand command
)
{
string name = command.Name;
if (!name.StartsWith("glVertexAttrib", StringComparison.Ordinal) ||
!name.EndsWith('v')) {
return false;
}
string scalarCommandName = name.Substring(0, name.Length - 1);
return _commands.ContainsKey(scalarCommandName);
}
private enum CommandHistoryAction
{
@@ -101,7 +101,8 @@ namespace GenerateGL.CodeGen
{
string pName = p.Element("name")?.Value ?? string.Empty;
string cType = BuildCType(p, out int pointerLevel, out _, out bool isConst);
parameters.Add(new GlParam(pName, cType, pointerLevel, isConst));
string? length = p.Attribute("len")?.Value;
parameters.Add(new GlParam(pName, cType, pointerLevel, isConst, length));
}
if (!string.IsNullOrEmpty(name)) {
@@ -0,0 +1,140 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="ManagedNameResolver"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace GenerateGL.CodeGen
{
internal static partial class ManagedNameResolver
{
private static readonly HashSet<string> TypedSuffixFamilies = new(
StringComparer.Ordinal) {
"ClearBuffer", "ClearNamedFramebuffer", "GetBufferParameter",
"GetNamedBufferParameter", "GetQueryBufferObject", "GetQueryObject",
"GetSamplerParameter", "GetTexLevelParameter", "GetTexParameter",
"GetTextureLevelParameter", "GetTextureParameter", "GetUniform", "GetnUniform",
"GetVertexAttrib", "PatchParameter", "PixelStore", "PointParameter",
"SamplerParameter", "TexParameter", "TextureParameter"
};
private static readonly Dictionary<string, string[]> ExactCandidates =
new Dictionary<string, string[]>(StringComparer.Ordinal) {
{ "BlendEquationi", [ "BlendEquation" ] },
{ "BlendEquationSeparatei", [ "BlendEquationSeparate" ] },
{ "BlendFunci", [ "BlendFunc" ] },
{ "BlendFuncSeparatei", [ "BlendFuncSeparate" ] },
{ "ClearDepthf", [ "ClearDepth" ] },
{ "ColorMaski", [ "ColorMask" ] },
{ "DepthRangef", [ "DepthRange" ] },
{ "Disablei", [ "Disable" ] },
{ "Enablei", [ "Enable" ] },
{ "FramebufferParameteri", [ "FramebufferParameter" ] },
{ "GetStringi", [ "GetString" ] },
{ "IsEnabledi", [ "IsEnabled" ] },
{ "NamedFramebufferParameteri", [ "NamedFramebufferParameter" ] },
{ "ProgramParameteri", [ "ProgramParameter" ] },
{ "ScissorIndexedv", [ "ScissorIndexed" ] },
{ "ViewportIndexedf", [ "ViewportIndexed" ] },
{ "ViewportIndexedfv", [ "ViewportIndexed" ] }
};
public static IReadOnlyDictionary<string, string> Resolve(
IEnumerable<GlCommand> commands
)
{
Dictionary<string, string> result = new(StringComparer.Ordinal);
HashSet<string> usedSignatures = new(StringComparer.Ordinal);
foreach (GlCommand command in commands.OrderBy(c => c.Name, StringComparer.Ordinal))
{
string signature = BuildParameterSignature(command);
foreach (string candidate in GetCandidates(command.Name))
{
if (usedSignatures.Add($"{candidate}({signature})"))
{
result.Add(command.Name, candidate);
break;
}
}
}
return result;
}
private static string BuildParameterSignature(
GlCommand command
)
{
ManagedCommandPlan plan = ManagedCommandPlan.Create(command);
return string.Join(",", plan.PublicParameters.Select(p => p.PublicType));
}
private static IEnumerable<string> GetCandidates(
string glName
)
{
string name = TrimGlPrefix(glName);
Match vertexAttrib = VertexAttribRegex().Match(name);
if (vertexAttrib.Success)
{
string stem = vertexAttrib.Groups[1].Value;
string dimension = vertexAttrib.Groups[2].Value;
bool normalized = vertexAttrib.Groups[3].Success;
yield return normalized ? $"{stem}Normalized" : stem;
yield return normalized ? $"{stem}{dimension}Normalized" : $"{stem}{dimension}";
yield return name;
yield break;
}
Match typed = TypedSuffixRegex().Match(name);
if (typed.Success && TypedSuffixFamilies.Contains(typed.Groups[1].Value))
{
string stem = typed.Groups[1].Value;
string suffix = typed.Groups[2].Value;
if (suffix.StartsWith('I')) {
yield return $"{stem}I";
}
yield return stem;
yield return name;
yield break;
}
if (ExactCandidates.TryGetValue(name, out string[]? candidates))
{
foreach (string candidate in candidates) {
yield return candidate;
}
}
yield return name;
}
private static string TrimGlPrefix(
string name
)
{
return name.StartsWith("gl", StringComparison.Ordinal) && name.Length > 2
? name.Substring(2)
: name;
}
[GeneratedRegex("^(VertexAttrib(?:I|L|P)?)([1-4])(N)?(?:ui|ub|us|i|b|s|f|d)(v)?$",
RegexOptions.CultureInvariant)]
private static partial Regex VertexAttribRegex();
[GeneratedRegex("^(.+?)(Iuiv|Iiv|ui64v|i64v|uiv|ui|iv|fi|i|fv|f|dv|d)$",
RegexOptions.CultureInvariant)]
private static partial Regex TypedSuffixRegex();
}
}
@@ -0,0 +1,138 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="ManagedParameterPlanner"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
namespace GenerateGL.CodeGen
{
internal enum ManagedParameterKind
{
Value,
RawPointer,
ReadOnlySpan,
Span,
Out
}
internal sealed record ManagedParameterPlan(
GlParam Source,
string PublicType,
ManagedParameterKind Kind,
string? ElementType
)
{
public bool IsSpan => Kind is ManagedParameterKind.ReadOnlySpan or
ManagedParameterKind.Span;
}
internal sealed class ManagedCommandPlan
{
private readonly Dictionary<string, ManagedParameterPlan> _allParameters;
private readonly Dictionary<string, string> _derivedLengthSources;
private ManagedCommandPlan(
IReadOnlyList<ManagedParameterPlan> publicParameters,
Dictionary<string, ManagedParameterPlan> allParameters,
Dictionary<string, string> derivedLengthSources
)
{
PublicParameters = publicParameters;
_allParameters = allParameters;
_derivedLengthSources = derivedLengthSources;
}
public IReadOnlyList<ManagedParameterPlan> PublicParameters { get; }
public IReadOnlyDictionary<string, string> DerivedLengthSources =>
_derivedLengthSources;
public static ManagedCommandPlan Create(
GlCommand command
)
{
Dictionary<string, ManagedParameterPlan> all = new(StringComparer.Ordinal);
foreach (GlParam parameter in command.Parameters)
{
ManagedParameterPlan plan;
if (CsTypeMapper.TryMapPointerElement(parameter, out string elementType) &&
(parameter.IsConst || !string.IsNullOrWhiteSpace(parameter.Length) ||
command.Name.StartsWith("glGet", StringComparison.Ordinal)))
{
if (!parameter.IsConst && parameter.Length == "1")
{
plan = new ManagedParameterPlan(
parameter, $"out {elementType}", ManagedParameterKind.Out,
elementType);
} else
{
ManagedParameterKind kind = parameter.IsConst
? ManagedParameterKind.ReadOnlySpan
: ManagedParameterKind.Span;
string container = parameter.IsConst ? "ReadOnlySpan" : "Span";
plan = new ManagedParameterPlan(
parameter, $"{container}<{elementType}>", kind, elementType);
}
} else
{
string mapped = CsTypeMapper.MapParameter(parameter);
ManagedParameterKind kind = parameter.PointerLevel == 0
? ManagedParameterKind.Value
: ManagedParameterKind.RawPointer;
plan = new ManagedParameterPlan(parameter, mapped, kind, null);
}
all.Add(parameter.Name, plan);
}
Dictionary<string, string> derivedLengths = new(StringComparer.Ordinal);
foreach (GlParam scalar in command.Parameters.Where(p => p.PointerLevel == 0))
{
List<ManagedParameterPlan> spans = command.Parameters
.Select(p => all[p.Name])
.Where(p => p.IsSpan &&
string.Equals(p.Source.Length, scalar.Name,
StringComparison.Ordinal))
.ToList();
if (spans.Count == 0) {
continue;
}
bool lengthAlsoControlsRawPointer = command.Parameters
.Select(p => all[p.Name]).Any(p =>
p.Kind == ManagedParameterKind.RawPointer &&
string.Equals(p.Source.Length, scalar.Name, StringComparison.Ordinal));
if (lengthAlsoControlsRawPointer) {
continue;
}
string scalarType = all[scalar.Name].PublicType;
if (scalarType is not ("int" or "uint" or "nint" or "nuint")) {
continue;
}
derivedLengths.Add(scalar.Name, spans[0].Source.Name);
}
IReadOnlyList<ManagedParameterPlan> publicParameters = command.Parameters
.Where(p => !derivedLengths.ContainsKey(p.Name))
.Select(p => all[p.Name])
.ToArray();
return new ManagedCommandPlan(publicParameters, all, derivedLengths);
}
public ManagedParameterPlan GetParameter(
string name
) => _allParameters[name];
}
}
+2 -1
View File
@@ -41,7 +41,8 @@ namespace GenerateGL.CodeGen
string Name,
string CType,
int PointerLevel,
bool IsConst
bool IsConst,
string? Length
)
{
@@ -0,0 +1,219 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="UniformOverloadEmitter"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
#pragma warning disable CA1305
namespace GenerateGL.CodeGen
{
internal static partial class UniformOverloadEmitter
{
public static bool IsValueCommand(
GlCommand command
)
{
return ValueCommandRegex().IsMatch(command.Name);
}
public static bool IsSetterCommand(
GlCommand command
)
{
return IsValueCommand(command) || MatrixCommandRegex().IsMatch(command.Name);
}
public static void Write(
string outputRoot,
string version,
IReadOnlyList<GlCommand> commands
)
{
List<GlCommand> valueCommands = commands.Where(IsValueCommand)
.OrderBy(c => c.Name, StringComparer.Ordinal)
.ToList();
List<GlCommand> matrixConvenienceCommands = commands
.Where(c => Matrix4FloatCommandRegex().IsMatch(c.Name))
.OrderBy(c => c.Name, StringComparer.Ordinal)
.ToList();
if (valueCommands.Count == 0 && matrixConvenienceCommands.Count == 0) {
return;
}
StringBuilder sb = new();
sb.AppendLine("// <auto-generated>");
sb.AppendLine($"// Generated by StgSharp.GenerateGL on {DateTime.UtcNow.ToString("u", CultureInfo.InvariantCulture)}");
sb.AppendLine($"// Uniform overloads introduced in OpenGL {version}");
sb.AppendLine("// </auto-generated>");
sb.AppendLine("using System;");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine("using StgSharp.Mathematics.Graphics;");
sb.AppendLine();
sb.AppendLine("namespace StgSharp.Graphics.OpenGL");
sb.AppendLine("{");
sb.AppendLine(" public unsafe partial class OpenGLFunction");
sb.AppendLine(" {");
foreach (GlCommand command in valueCommands) {
EmitValueOverload(sb, command);
}
foreach (GlCommand command in valueCommands.Where(IsFloatVectorCommand)) {
EmitVectorConvenienceOverload(sb, command);
}
foreach (GlCommand command in matrixConvenienceCommands) {
EmitMatrixConvenienceOverload(sb, command);
}
sb.AppendLine(" }");
sb.AppendLine("}");
string fileName = Path.Combine(
outputRoot,
$"OpenGLFunction.Uniform.v{version.Replace('.', '_')}.g.cs");
File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);
}
private static void EmitValueOverload(
StringBuilder sb,
GlCommand command
)
{
Match match = ValueCommandRegex().Match(command.Name);
bool hasProgram = match.Groups[1].Success;
int dimension = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
string scalarType = MapScalarType(match.Groups[3].Value);
bool isVector = match.Groups[4].Success;
string uniformType = $"Uniform<{string.Join(", ", Enumerable.Repeat(scalarType, dimension))}>";
sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveInlining)]");
if (isVector)
{
string programParameter = hasProgram ? "GlHandle program, " : string.Empty;
sb.AppendLine($" public void SetUniformValue({programParameter}{uniformType} uniform, ReadOnlySpan<{scalarType}> values)");
sb.AppendLine(" {");
sb.AppendLine(" ArgumentNullException.ThrowIfNull(uniform);");
if (dimension > 1)
{
sb.AppendLine($" if ((values.Length % {dimension}) != 0) {{");
sb.AppendLine($" throw new ArgumentException(\"Uniform data length must be divisible by {dimension}.\", nameof(values));");
sb.AppendLine(" }");
}
sb.AppendLine($" fixed ({scalarType}* valuesPtr = values) {{");
string count = dimension == 1 ? "values.Length" : $"values.Length / {dimension}";
sb.AppendLine($" Context.{command.Name}({BuildPrefixArguments(hasProgram)}{count}, valuesPtr);");
sb.AppendLine(" }");
sb.AppendLine(" }");
} else
{
string programParameter = hasProgram ? "GlHandle program, " : string.Empty;
string values = string.Join(", ", Enumerable.Range(0, dimension)
.Select(i => $"{scalarType} v{i}"));
string valueArguments = string.Join(", ", Enumerable.Range(0, dimension)
.Select(i => $"v{i}"));
sb.AppendLine($" public void SetUniformValue({programParameter}{uniformType} uniform, {values})");
sb.AppendLine(" {");
sb.AppendLine(" ArgumentNullException.ThrowIfNull(uniform);");
sb.AppendLine($" Context.{command.Name}({BuildPrefixArguments(hasProgram)}{valueArguments});");
sb.AppendLine(" }");
}
sb.AppendLine();
}
private static void EmitVectorConvenienceOverload(
StringBuilder sb,
GlCommand command
)
{
Match match = ValueCommandRegex().Match(command.Name);
bool hasProgram = match.Groups[1].Success;
int dimension = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
if (dimension < 2) {
return;
}
string vectorType = $"Vec{dimension}";
string programParameter = hasProgram ? "GlHandle program, " : string.Empty;
sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveInlining)]");
sb.AppendLine($" public void SetUniformValue({programParameter}Uniform<{vectorType}> uniform, {vectorType} value)");
sb.AppendLine(" {");
sb.AppendLine(" ArgumentNullException.ThrowIfNull(uniform);");
sb.AppendLine($" Context.{command.Name}({BuildPrefixArguments(hasProgram)}1, (float*)&value);");
sb.AppendLine(" }");
sb.AppendLine();
}
private static void EmitMatrixConvenienceOverload(
StringBuilder sb,
GlCommand command
)
{
bool hasProgram = command.Name.StartsWith("glProgram", StringComparison.Ordinal);
string programParameter = hasProgram ? "GlHandle program, " : string.Empty;
sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveInlining)]");
sb.AppendLine($" public void SetUniformValue({programParameter}Uniform<GraphicsMatrix> uniform, GraphicsMatrix value, bool transpose = false)");
sb.AppendLine(" {");
sb.AppendLine(" ArgumentNullException.ThrowIfNull(uniform);");
sb.AppendLine($" Context.{command.Name}({BuildPrefixArguments(hasProgram)}1, transpose ? (byte)1 : (byte)0, (float*)&value);");
sb.AppendLine(" }");
sb.AppendLine();
}
private static string BuildPrefixArguments(
bool hasProgram
)
{
return hasProgram
? "program.Value, uniform.id.SignedValue, "
: "uniform.id.SignedValue, ";
}
private static bool IsFloatVectorCommand(
GlCommand command
)
{
Match match = ValueCommandRegex().Match(command.Name);
return match.Success && match.Groups[3].Value == "f" && match.Groups[4].Success;
}
private static string MapScalarType(
string suffix
)
{
return suffix switch
{
"f" => "float",
"d" => "double",
"i" => "int",
"ui" => "uint",
_ => throw new InvalidOperationException($"Unsupported uniform suffix: {suffix}")
};
}
[GeneratedRegex("^gl(Program)?Uniform([1-4])(ui|[fdi])(v)?$",
RegexOptions.CultureInvariant)]
private static partial Regex ValueCommandRegex();
[GeneratedRegex("^gl(?:Program)?UniformMatrix4fv$", RegexOptions.CultureInvariant)]
private static partial Regex Matrix4FloatCommandRegex();
[GeneratedRegex("^gl(?:Program)?UniformMatrix[234](?:x[234])?[fd]v$",
RegexOptions.CultureInvariant)]
private static partial Regex MatrixCommandRegex();
}
}
#pragma warning restore CA1305