Files
stgsharp/test/StgSharp.GenerateGL/Docs/ArchitectureAssessment.md
T

21 KiB
Raw Blame History

GenerateGL 与 StgSharp.Graphics OpenGL 绑定架构评估

评估日期:2026-08-24

实施状态(2026-08-25)

已完成第一轮无公开 API 破坏的文件整理:

  • 手写 OpenGLFunction 已拆分到 src/StgSharp.Graphics/OpenGL/Api;
  • 版本 Raw wrapper 已移动到 src/StgSharp.Graphics/OpenGL/Generated 并使用 .g.cs 后缀;
  • OpenglContext 已移动为 Generated/OpenglContext.g.cs;
  • SetUniformValue 已从 Uniform 模型文件移动到 Api/OpenGLFunction.Uniform.cs;
  • Texture Unit 状态已移动到 Api/OpenGLFunction.TextureUnits.cs;
  • 已修复两个 Vec4 overload 对 glUniform1fv 的错误调用,现调用 glUniform4fv(location, 1, ...);
  • GenerateGL 已改为只生成一个合并 Context,Raw wrapper 继续按版本输出;
  • GenerateGL 输出文件名已与新的 Generated 目录约定对齐。

第二轮生成器实验已经完成并输出到 test/StgSharp.GenerateGL/Generated:

  • glUniform1/2/3/4[f|i|ui|d][v] 与 glProgramUniform* 已生成为 SetUniformValue(...) 重载;
  • 已生成 scalar、ReadOnlySpan<T>、Vec2/3/4 和 GraphicsMatrix 入口;
  • 所有 uniform setter 已从 Raw wrapper 排除;没有对应托管矩阵类型的形状暂时只存在于 OpenglContext;
  • TexParameter、SamplerParameter、VertexAttrib、ClearBuffer 及 indexed API 等可由 C# 参数签名区分的函数已归并重载;
  • 归并发生签名冲突时,生成器会保留必要的维度或语义尾缀;
  • OpenglContext 仍保持纯 ABI 函数指针表,GlHandle 暂不拆分;
  • 已移除“参数名为 type 就映射为 ShaderType”的不安全启发式规则。

Loader/ContextShadow 合并、BufferObject 专项生成和生成快照测试尚未实施。

1. 评估目的

本文评估 StgSharp.GenerateGL 与 src/StgSharp.Graphics 当前 OpenGL 绑定代码之间的关系,重点回答以下问题:

  • GenerateGL 为什么会生成大量保留 C 命名风格的方法;
  • Uniform 系列应如何生成符合 C# 使用习惯的方法重载;
  • 现有手写封装散落在哪些文件中;
  • 修改生成器时还需要同步调整哪些类型、加载器和调用方;
  • 如何在保留现有功能的前提下逐步完成迁移。

本次评估基于静态代码检查,不包含代码重构或 API 变更。

2. 总体结论

当前 GenerateGL 完成的是“将 Khronos OpenGL C API 转换为可调用的 C# 代码”,尚未建立从 C API 函数族到惯用 C# API 的语义映射。

glUniform1f、glUniform2f、glUniform4fv 等名称中的数字和后缀分别编码了分量数、元素类型和指针形式。当前生成器只删除 gl 前缀,因此得到的仍然是 C 风格方法,而不是 C# 重载。

解决该问题不能只修改方法命名。当前 OpenGL 代码同时存在以下结构性问题:

  1. 原始函数指针、自动生成的薄封装和手写高级封装没有清晰分层;
  2. Uniform.cs 同时定义 Uniform 类型和 partial OpenGLFunction 方法;
  3. ShaderStruct.cs 绕过 OpenGLFunction,直接调用原始函数指针;
  4. uniform location 被建模为通用 GlHandle,语义不正确;
  5. Context、ContextShadow、Loader 和 Wrapper 分别维护,已经出现函数清单漂移;
  6. OpenGL 版本能力判断存在错误,可能加载当前上下文不支持的函数;
  7. GenerateGL 文档描述的是增量源生成器,但当前实现是独立控制台代码生成程序。

建议将架构拆分为四个明确层次:

  1. Registry 模型层:解析 gl.xml 并建立函数、版本和类型模型;
  2. ABI 层:生成原始函数指针和加载代码;
  3. Raw API 层:提供与 OpenGL 一一对应的薄封装;
  4. Idiomatic API 层:按函数族生成 C# 重载,并使用 StgSharp 的向量、矩阵和资源类型。

3. 当前 GenerateGL 行为

3.1 输入

GenerateGL 从 xml/gl.xml 读取 Khronos OpenGL Registry。

当前解析内容包括:

  • <commands> 中的函数名、返回值和参数;
  • <feature> 中各 OpenGL 版本新增的命令;
  • Core Profile 中删除的命令。

当前未完整实现:

  • 枚举和常量生成;
  • 扩展 API 生成;
  • Roslyn Incremental Generator;
  • 自动写入或接入 StgSharp.Graphics 项目。

3.2 输出

当前生成器针对 OpenGL 3.3 至 4.6 生成:

  • 分版本 OpenglContext 函数指针字段;
  • 分版本 OpenGLFunction 薄封装方法;
  • 合并后的 glContext.cs;
  • OpenGL 命令新增和删除历史。

输出目录为 GenerateGL 可执行文件所在目录下的 Generated,而不是 src/StgSharp.Graphics。

3.3 当前生成策略的限制

生成器对公开方法的处理主要包括:

  • 删除 gl 前缀;
  • 将部分 GLuint 参数启发式转换为 GlHandle;
  • 将部分 const T* 转换为 ReadOnlySpan<T>;
  • 对 C# 保留字进行转义。

它没有分析 OpenGL 函数族,因此下列函数会被当作互不相关的方法:

glUniform1f
glUniform2f
glUniform3f
glUniform4f
glUniform1fv
glUniform2fv
glUniform3fv
glUniform4fv

4. StgSharp.Graphics 当前代码分布

4.1 原始函数指针

src/StgSharp.Graphics/OpenGL/glContext.cs 定义 OpenglContext,当前包含 657 个函数指针字段。

这些字段是 OpenGL ABI 的核心,应完全由生成器维护,不应与手写高级 API 混合。

4.2 ContextShadow

src/StgSharp.Graphics/OpenGL/glContextShadow.cs 使用 657 个 IntPtr 字段镜像 OpenglContext。

加载器将 OpenglContext* 强制转换为 glContextShadow* 后写入函数地址。因此两个结构体必须始终满足:

  • 字段数量一致;
  • 字段顺序一致;
  • 每个字段占用相同大小。

当前两者字段数量和顺序一致,但这种设计非常脆弱。生成器目前不生成 ContextShadow,任何一侧发生变化都可能破坏内存布局。

4.3 函数加载器

src/StgSharp.Graphics/OpenGL/glApiManager.Load.cs 按 OpenGL 版本加载函数。

静态检查结果:

  • OpenglContext 字段数:657;
  • glContextShadow 字段数:657;
  • 实际 Loader 赋值数:655。

当前未被实际加载的字段:

  • glGetPointerv;
  • glVertexAttribLFormat。

这说明 Context、Shadow 和 Loader 已经发生漂移。

4.4 自动生成的公开薄封装

以下文件定义 partial OpenGLFunction:

  • glFunction.v3_3.cs;
  • glFunction.v4_0.cs;
  • glFunction.v4_1.cs;
  • glFunction.v4_2.cs;
  • glFunction.v4_3.cs;
  • glFunction.v4_4.cs;
  • glFunction.v4_5.cs;
  • glFunction.v4_6.cs。

这些文件合计约 656 个公开方法。它们已经进行了一部分 C# 类型转换,但函数名和参数模型仍基本对应 C API。

这使它们处于不稳定的中间层:

  • 不再是完全精确的 Raw ABI;
  • 又没有形成惯用的 C# API;
  • count 与 ReadOnlySpan<T> 长度可能不一致;
  • byte、裸 uint 和裸指针仍大量暴露;
  • 资源句柄识别依赖参数名启发式规则。

4.5 手写 OpenGLFunction 方法

src/StgSharp.Graphics/OpenGL/glFunction.cs 约 795 行,包含约 55 个手写公开方法,职责包括:

  • Context 与 OpenGLFunction 实例管理;
  • Buffer 创建、删除和数据上传;
  • Texture 创建和上传;
  • Framebuffer 和 Renderbuffer 操作;
  • Shader 创建、源码上传和状态检查;
  • Draw 调用;
  • 错误检查;
  • Vertex Attribute 配置。

这些方法与版本文件中的公开方法并存。例如同时存在:

  • ActiveTextureUnit 与 ActiveTexture;
  • BindFrameBuffer 与 BindFramebuffer;
  • FrameBufferTexture2d 与 FramebufferTexture2D;
  • TextureImage2d 与 TexImage2D;
  • SetBufferData 与 BufferData。

这种重复不是编译错误,但会让调用者无法判断哪一层是推荐 API,也会妨碍生成器安全地产生新重载。

4.6 Uniform 类型与方法

src/StgSharp.Graphics/OpenGL/ShaderEdit/Uniform.cs 同时包含:

  • Uniform 基类;
  • Uniform<T>;
  • Uniform<T, U>;
  • Uniform<T, U, V>;
  • Uniform<T, U, V, W>;
  • partial OpenGLFunction 的 SetUniformValue 重载。

代码位置本身不影响编译,但该文件跨越了两个职责边界:

  • Uniform 数据模型;
  • OpenGL 命令封装。

这会导致生成器无法拥有 Uniform 方法的明确输出区域。

4.7 Shader 相关代码

src/StgSharp.Graphics/OpenGL/ShaderEdit/Shader.cs 同时定义:

  • partial glRender 的 Shader 工厂方法;
  • Shader;
  • ShaderStatus;
  • ShaderProgram;
  • ShaderProgram.GetUniform<T...>。

此外还存在 src/StgSharp.Graphics/ShaderEdit,其中包含另一套 Shader 抽象。OpenGL/ShaderEdit/IglConvertable.cs 的文件路径位于 OpenGL 子目录,但命名空间是 StgSharp.Graphics.ShaderEdit。

当前文件位置和命名空间没有形成一致边界。

4.8 绕过公开封装的调用

除 Loader 和绑定实现外,发现 src/StgSharp.Graphics/ShaderEdit/ShaderStruct.cs 直接获取:

OpenglContext* gl =
    (OpenglContext*)OpenGLFunction.CurrentGL.ContextHandle;

随后直接调用 glUniform*。

这是高级代码对 ABI 层的越层依赖。新的 Uniform API 建立后,这里应只调用 OpenGLFunction.SetUniformValue 或对应的 Uniform 对象方法。

5. 已确认的正确性问题

5.1 Vec4 使用了错误的 OpenGL 函数

当前 Uniform.cs 中,Vec4 被传给:

glUniform1fv(location, 4, pointer);

该调用表示上传 4 个标量 uniform,而不是上传 1 个四分量向量。

正确调用应为:

glUniform4fv(location, 1, pointer);

5.2 整数 Uniform 调用了浮点函数

ShaderStruct.SetAllUniforms 的整数分支调用 glUniform1f,应调用 glUniform1i。

5.3 Uniform location 被错误建模为 GlHandle

OpenGL uniform location 的原始类型是有符号 GLint,并允许 -1 表示未找到。

当前实现使用 GlHandle 保存 uniform location。GlHandle 同时被用于 Buffer、Texture、Program 和 Shader 等 GLuint 对象名称,因此无法表达 uniform location 的不同语义和有效性规则。

建议引入:

public readonly record struct UniformLocation(int Value)
{
    public bool IsValid => Value >= 0;
}

5.4 版本判断错误

glManager.CheckCoreVersion 当前使用类似以下判断:

majorVersion >= 4 ||
(majorVersion == 4 && minorVersion == requiredMinor)

当上下文版本为 OpenGL 4.0 时,majorVersion >= 4 对 4.1 至 4.6 的检查也全部为真。

正确判断应为:

majorVersion > requiredMajor ||
(majorVersion == requiredMajor && minorVersion >= requiredMinor)

版本和扩展能力还应保存在每个 OpenGL context 中,而不是静态全局字段中。

5.5 上下文内存没有完整清零

glRender.InitializeContext 通过 Marshal.AllocHGlobal 分配 OpenglContext,但只将 glGetString 设置为零。

如果当前版本不加载某些较新函数,其字段可能保留未初始化值。公开版本方法仍然存在,错误调用时可能跳转到无效地址。

上下文分配后应完整清零,并通过 capability 检查限制不受支持的方法调用。

5.6 函数指针调用约定需要统一审计

GLFW 返回的是非托管函数地址。当前 OpenglContext 多数使用 delegate*<...>,没有显式标记 unmanaged 调用约定。

生成器应根据目标平台统一生成并验证函数指针调用约定,例如:

delegate* unmanaged<...>

或在确有需要时生成平台对应的显式调用约定。

6. Uniform 函数族的推荐建模

6.1 函数名解析

生成器应将以下函数族解析为结构化模型:

glUniform{1|2|3|4}{f|d|i|ui}{v?}
glProgramUniform{1|2|3|4}{f|d|i|ui}{v?}
glUniformMatrix{2|3|4|2x3|2x4|3x2|3x4|4x2|4x3}{f|d}v
glProgramUniformMatrix{...}{f|d}v

建议模型至少包含:

Operation       = Uniform | ProgramUniform
Shape           = Scalar | Vector | Matrix
Columns         = 1..4
Rows            = 1..4
ElementType     = Float | Double | Int | UInt
InputForm       = Components | Array
NativeCommand   = glUniform4fv 等原始名称
IntroducedIn    = OpenGL 版本

不要直接在字符串 emitter 中通过多组 StartsWith 和 EndsWith 拼接逻辑。应先完成函数族识别,再根据统一模型生成代码。

6.2 推荐的 Uniform 类型

建议保留单泛型参数:

public readonly struct Uniform<T> where T : unmanaged
{
    public UniformLocation Location { get; }
}

向量和矩阵形状由 T 表达:

Uniform<float>
Uniform<Vec2>
Uniform<Vec3>
Uniform<Vec4>
Uniform<GraphicsMatrix>

不建议继续使用 Uniform<T, U, V, W> 表达四分量值。

要完整支持 double、int 和 uint 向量,数学库还需要提供对应的向量类型,或者 OpenGL 层需要定义布局稳定的专用值类型,例如:

DVec2 / DVec3 / DVec4
IVec2 / IVec3 / IVec4
UVec2 / UVec3 / UVec4

6.3 推荐重载

标量与向量:

void SetUniformValue(Uniform<float> uniform, float value);
void SetUniformValue(Uniform<Vec2> uniform, Vec2 value);
void SetUniformValue(Uniform<Vec3> uniform, Vec3 value);
void SetUniformValue(Uniform<Vec4> uniform, Vec4 value);

数组:

void SetUniformValue(
    Uniform<float> uniform,
    ReadOnlySpan<float> values);

void SetUniformValue(
    Uniform<Vec4> uniform,
    ReadOnlySpan<Vec4> values);

矩阵:

void SetUniformValue(
    Uniform<GraphicsMatrix> uniform,
    in GraphicsMatrix value,
    bool transpose = false);

调用者不应手工传入 count。生成方法应根据 Span 长度和元素形状计算 OpenGL 的 count 参数。

6.4 Raw API 的处理

建议保留与 OpenGL 一一对应的 Raw 方法,至少在迁移期内不要立即删除。

可选策略:

  1. 将 Raw 方法改为 internal;
  2. 放入单独的 OpenGLRaw 类型;
  3. 暂时保持 public,但使用 EditorBrowsable(Never) 降低 IntelliSense 噪音;
  4. 为旧 API 提供一个兼容周期,再逐步弃用。

Idiomatic API 不应与 Raw API 写入同一生成文件。

7. 推荐目录结构

src/StgSharp.Graphics/OpenGL/
  Generated/
    OpenglContext.g.cs
    OpenglLoader.v33.g.cs
    OpenGLFunction.Raw.v33.g.cs
    OpenGLFunction.Raw.v40.g.cs
    OpenGLFunction.Uniform.g.cs
    OpenGLFunction.ProgramUniform.g.cs

  Api/
    OpenGLFunction.Context.cs
    OpenGLFunction.Diagnostics.cs
    OpenGLFunction.Buffer.cs
    OpenGLFunction.Texture.cs
    OpenGLFunction.Drawing.cs
    OpenGLFunction.Shader.cs

  Objects/
    Uniform.cs
    UniformLocation.cs
    Shader.cs
    ShaderProgram.cs
    ShaderStatus.cs
    Buffer/
    Texture/

  Rendering/
    GlRender.cs
    GlRender.Resources.cs
    GlRender.Threading.cs

具体目录名称可以调整,但必须保持以下边界:

  • Generated 中的文件可以被完整覆盖;
  • 手写文件永远不写入 Generated;
  • Uniform 类型定义和 Uniform 调用方法分离;
  • Shader 资源对象不直接访问 OpenglContext*;
  • Loader 与 Context 来自同一个生成模型。

8. 建议的文件迁移

8.1 Uniform.cs

保留:

  • Uniform<T>;
  • uniform location;
  • 与 uniform 数据模型直接相关的成员。

移出:

  • 所有 partial OpenGLFunction 方法。

8.2 Shader.cs

拆分为:

  • Shader.cs;
  • ShaderProgram.cs;
  • ShaderStatus.cs;
  • ShaderProgram.Uniform.cs;
  • GlRender.ShaderFactory.cs。

8.3 ShaderStruct.cs

移除:

  • ContextHandle 访问;
  • OpenglContext* 转换;
  • 直接 glUniform* 调用。

改为通过类型安全的 Uniform API 分发。

8.4 glFunction.cs

保留 Context 生命周期和实例管理,然后按功能拆分手写便利方法:

OpenGLFunction.Context.cs
OpenGLFunction.Diagnostics.cs
OpenGLFunction.Buffer.cs
OpenGLFunction.Texture.cs
OpenGLFunction.Framebuffer.cs
OpenGLFunction.Drawing.cs
OpenGLFunction.Shader.cs

8.5 glFunction.SourceManager.cs

该文件实际管理 Texture Unit,而不是 Source。建议重命名为:

OpenGLFunction.TextureUnits.cs

进一步可以将 Texture Unit 分配状态移出函数绑定类,放入每个 Render Context 的资源管理对象。

8.6 IglConvertable.cs

该文件位于 OpenGL/ShaderEdit,命名空间却是 StgSharp.Graphics.ShaderEdit。应移动到与命名空间一致的 ShaderEdit 目录。

9. GenerateGL 推荐内部结构

建议将当前单一 Generator 拆分为以下阶段。

9.1 Registry 解析

GlXmlParser
  -> RegistryModel

负责准确保留:

  • C 类型;
  • 指针层级;
  • const;
  • 参数组信息;
  • feature/profile/version;
  • alias;
  • enum group。

9.2 ABI 类型映射

RegistryModel
  -> AbiCommandModel

这里只做精确映射,不做 GlHandle、Span 或 ShaderType 等高级推断。

9.3 函数族分析

AbiCommandModel
  -> CommandFamilyModel

由专门规则识别:

  • Uniform;
  • ProgramUniform;
  • UniformMatrix;
  • VertexAttrib;
  • TexParameter;
  • Get 系列;
  • Gen/Delete/Create 系列。

无法识别的命令仍可生成 Raw API,但不得用不确定的启发式规则擅自改变资源类型。

9.4 Emitters

建议拆分:

ContextEmitter
LoaderEmitter
RawFunctionEmitter
UniformEmitter
ProgramUniformEmitter
ConstantEmitter
HistoryEmitter

每个 emitter 只负责一种输出,便于单独进行 golden-file 测试。

10. 迁移计划

阶段一:建立安全的生成边界

  1. 新增 OpenGL/Generated;
  2. 所有生成文件使用 .g.cs 后缀;
  3. 生成器直接生成 OpenglContext 和 Loader;
  4. 移除或自动生成 glContextShadow;
  5. 增加函数数量、名称和版本一致性测试。

此阶段不改变现有公开 API。

阶段二:修复 Uniform 模型

  1. 新增 UniformLocation;
  2. 将 GetUniformLocation 返回类型改为 UniformLocation;
  3. 将 Uniform 简化为 Uniform<T>;
  4. 修复 Vec4 和整数 uniform 的错误调用;
  5. 迁移 Camera、ShaderProgram 和 ShaderStruct。

阶段三:生成 Uniform 重载

  1. 实现 Uniform 函数族解析;
  2. 生成 scalar/vector/matrix overload;
  3. 自动计算 Span 对应的 count;
  4. 生成 ProgramUniform 对应重载;
  5. 使用 golden-file 测试验证每个原始命令映射到正确 overload。

阶段四:整理其他手写便利 API

  1. 拆分 glFunction.cs;
  2. 统一 Buffer、Texture、Framebuffer 方法命名;
  3. 明确 Raw API 的可见性和兼容策略;
  4. 逐步为 VertexAttrib、TexParameter 等函数族生成重载。

阶段五:清理旧 API

  1. 标记旧 C 风格 public wrapper 为 obsolete 或隐藏;
  2. 提供迁移说明;
  3. 在约定的兼容版本后删除旧入口。

11. 测试与验收标准

11.1 Registry 一致性

  • 每个 Context 字段必须有对应 Loader 赋值;
  • 每个 Raw wrapper 必须对应一个 Context 字段;
  • 不允许 Loader 引用不存在的字段;
  • Context、Loader 和 Wrapper 的版本归属必须一致。

11.2 Uniform golden tests

至少验证:

float                 -> glUniform1f
Vec2                  -> glUniform2fv(count = 1)
Vec3                  -> glUniform3fv(count = 1)
Vec4                  -> glUniform4fv(count = 1)
ReadOnlySpan<Vec4>    -> glUniform4fv(count = span.Length)
GraphicsMatrix        -> glUniformMatrix4fv(count = 1)

同时覆盖 double、int、uint、非方阵矩阵和 ProgramUniform。

11.3 API 约束

  • 高级代码不得访问 OpenglContext*;
  • 高级代码不得手工传入可由 Span 推导的 count;
  • uniform location 不得使用 GlHandle;
  • 生成文件不得包含手写区域;
  • 手写文件不得依赖生成字段的布局顺序;
  • 不支持的 OpenGL 版本方法不得调用未初始化函数指针。

11.4 构建验证

GenerateGL 当前可以成功构建,但存在代码分析警告。

本次检查中,单独构建 StgSharp.Graphics 时,MSBuild 在项目引用 StgSharp.Mathematics 的 GetTargetFrameworks 阶段失败,最终显示 0 个 C# 错误和 0 个警告。该失败发生在 C# 编译前,因此本次评估不将其视为 Graphics 源码编译结果。

12. 推荐的第一批实际改动

为了控制改动规模,第一批建议只完成以下内容:

  1. 新增 UniformLocation;
  2. 将 Uniform<T, ...> 收敛到 Uniform<T>;
  3. 把 SetUniformValue 从 Uniform.cs 移到独立 partial 文件;
  4. 修复 Vec4 与整数 uniform 调用;
  5. 让 ShaderStruct 不再访问 OpenglContext*;
  6. 给 GenerateGL 增加 UniformFamilyModel 和 UniformEmitter;
  7. 同时生成 Context 与 Loader,并消除 ContextShadow 的手工同步;
  8. 增加 Context/Loader/Wrapper 集合一致性测试。

完成这一批后,再决定是否立即重构全部 glFunction.cs。这样可以先解决 Uniform API 和生成同步问题,同时避免一次性扩大到整个 Graphics 框架。