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

667 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GenerateGL 与 StgSharp.Graphics OpenGL 绑定架构评估
评估日期:2026-08-24
## 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 函数族,因此下列函数会被当作互不相关的方法:
```text
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` 直接获取:
```csharp
OpenglContext* gl =
(OpenglContext*)OpenGLFunction.CurrentGL.ContextHandle;
```
随后直接调用 `glUniform*`。
这是高级代码对 ABI 层的越层依赖。新的 Uniform API 建立后,这里应只调用 `OpenGLFunction.SetUniformValue` 或对应的 Uniform 对象方法。
## 5. 已确认的正确性问题
### 5.1 Vec4 使用了错误的 OpenGL 函数
当前 `Uniform.cs` 中,`Vec4` 被传给:
```csharp
glUniform1fv(location, 4, pointer);
```
该调用表示上传 4 个标量 uniform,而不是上传 1 个四分量向量。
正确调用应为:
```csharp
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 的不同语义和有效性规则。
建议引入:
```csharp
public readonly record struct UniformLocation(int Value)
{
public bool IsValid => Value >= 0;
}
```
### 5.4 版本判断错误
`glManager.CheckCoreVersion` 当前使用类似以下判断:
```csharp
majorVersion >= 4 ||
(majorVersion == 4 && minorVersion == requiredMinor)
```
当上下文版本为 OpenGL 4.0 时,`majorVersion >= 4` 对 4.1 至 4.6 的检查也全部为真。
正确判断应为:
```csharp
majorVersion > requiredMajor ||
(majorVersion == requiredMajor && minorVersion >= requiredMinor)
```
版本和扩展能力还应保存在每个 OpenGL context 中,而不是静态全局字段中。
### 5.5 上下文内存没有完整清零
`glRender.InitializeContext` 通过 `Marshal.AllocHGlobal` 分配 `OpenglContext`,但只将 `glGetString` 设置为零。
如果当前版本不加载某些较新函数,其字段可能保留未初始化值。公开版本方法仍然存在,错误调用时可能跳转到无效地址。
上下文分配后应完整清零,并通过 capability 检查限制不受支持的方法调用。
### 5.6 函数指针调用约定需要统一审计
GLFW 返回的是非托管函数地址。当前 `OpenglContext` 多数使用 `delegate*<...>`,没有显式标记 `unmanaged` 调用约定。
生成器应根据目标平台统一生成并验证函数指针调用约定,例如:
```csharp
delegate* unmanaged<...>
```
或在确有需要时生成平台对应的显式调用约定。
## 6. Uniform 函数族的推荐建模
### 6.1 函数名解析
生成器应将以下函数族解析为结构化模型:
```text
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
```
建议模型至少包含:
```text
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 类型
建议保留单泛型参数:
```csharp
public readonly struct Uniform<T> where T : unmanaged
{
public UniformLocation Location { get; }
}
```
向量和矩阵形状由 `T` 表达:
```text
Uniform<float>
Uniform<Vec2>
Uniform<Vec3>
Uniform<Vec4>
Uniform<GraphicsMatrix>
```
不建议继续使用 `Uniform<T, U, V, W>` 表达四分量值。
要完整支持 `double`、`int` 和 `uint` 向量,数学库还需要提供对应的向量类型,或者 OpenGL 层需要定义布局稳定的专用值类型,例如:
```text
DVec2 / DVec3 / DVec4
IVec2 / IVec3 / IVec4
UVec2 / UVec3 / UVec4
```
### 6.3 推荐重载
标量与向量:
```csharp
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);
```
数组:
```csharp
void SetUniformValue(
Uniform<float> uniform,
ReadOnlySpan<float> values);
void SetUniformValue(
Uniform<Vec4> uniform,
ReadOnlySpan<Vec4> values);
```
矩阵:
```csharp
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. 推荐目录结构
```text
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 生命周期和实例管理,然后按功能拆分手写便利方法:
```text
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。建议重命名为:
```text
OpenGLFunction.TextureUnits.cs
```
进一步可以将 Texture Unit 分配状态移出函数绑定类,放入每个 Render Context 的资源管理对象。
### 8.6 `IglConvertable.cs`
该文件位于 `OpenGL/ShaderEdit`,命名空间却是 `StgSharp.Graphics.ShaderEdit`。应移动到与命名空间一致的 `ShaderEdit` 目录。
## 9. GenerateGL 推荐内部结构
建议将当前单一 `Generator` 拆分为以下阶段。
### 9.1 Registry 解析
```text
GlXmlParser
-> RegistryModel
```
负责准确保留:
- C 类型;
- 指针层级;
- const;
- 参数组信息;
- feature/profile/version;
- alias;
- enum group。
### 9.2 ABI 类型映射
```text
RegistryModel
-> AbiCommandModel
```
这里只做精确映射,不做 `GlHandle`、Span 或 ShaderType 等高级推断。
### 9.3 函数族分析
```text
AbiCommandModel
-> CommandFamilyModel
```
由专门规则识别:
- Uniform;
- ProgramUniform;
- UniformMatrix;
- VertexAttrib;
- TexParameter;
- Get 系列;
- Gen/Delete/Create 系列。
无法识别的命令仍可生成 Raw API,但不得用不确定的启发式规则擅自改变资源类型。
### 9.4 Emitters
建议拆分:
```text
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
至少验证:
```text
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 框架。