docs(api): deepen OpenGL fence layout and sampler docs

This commit is contained in:
2026-03-28 01:39:57 +08:00
parent 664fec73bc
commit 36097869c0
36 changed files with 463 additions and 372 deletions

View File

@@ -1,4 +1,4 @@
# OpenGLFence::OpenGLFence
# OpenGLFence::OpenGLFence()
```cpp
OpenGLFence();
@@ -6,13 +6,19 @@ OpenGLFence();
## 作用
构造一个 fence 对象。
构造一个尚未创建 `GLsync` 的 OpenGL fence 对象。
## 当前实现行为
- 不创建 `GLsync`
- `m_signaledValue` `m_completedValue` 初始化为 `0`
- `m_sync` 置为 `nullptr`
- `m_signaledValue` `m_completedValue` 初始化为 `0`
- 不创建 OpenGL 同步对象
## 设计说明
当前类采用的是“懒创建同步对象”的思路。只有在 [Signal](Signal.md) 或 [GetNativeHandle](GetNativeHandle.md) 真正需要同步句柄时,才会插入 `glFenceSync`
## 相关文档
- [Initialize](Initialize.md)
- [Signal](Signal.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::~OpenGLFence
# OpenGLFence::~OpenGLFence()
```cpp
~OpenGLFence() override;
@@ -6,13 +6,13 @@
## 作用
销毁 fence 并释放内部 `GLsync`
析构 fence 对象并释放当前仍然持有的同步句柄
## 当前实现行为
- 析构函数调用 [`Shutdown`](Shutdown.md)
- 如果内部仍持有 sync会执行 `glDeleteSync()`
- 析构函数直接调用 [Shutdown](Shutdown.md)
## 相关文档
- [Shutdown](Shutdown.md)
- [OpenGLFence](OpenGLFence.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::GetCompletedValue
# OpenGLFence::GetCompletedValue()
```cpp
uint64_t GetCompletedValue() const override;
@@ -6,19 +6,26 @@ uint64_t GetCompletedValue() const override;
## 作用
查询 fence 当前已完成到哪个值。
## 返回值
- 如果没有活动中的 `GLsync`,返回 `m_completedValue`
- 如果 `GLsync` 已被驱动标记为 `GL_SIGNALED`,返回 `m_signaledValue`
- 否则返回 `m_completedValue`
查询当前 fence 已完成到哪个值。
## 当前实现行为
- 该函数会查询 `GL_SYNC_STATUS`,但不会主动删除 sync也不会把 `m_completedValue` 写回最新值。
- 如果 `m_sync == nullptr`,直接返回 `m_completedValue`
- 否则调用 `glGetSynciv(..., GL_SYNC_STATUS, ...)` 读取同步对象状态
- 当状态为 `GL_SIGNALED` 时,返回 `m_signaledValue`
- 当状态仍未完成时,返回 `m_completedValue`
## 需要特别注意
- 这个方法不会在 `GL_SIGNALED` 时自动更新 `m_completedValue`
- 也不会删除当前 `m_sync`
- 因为当前实现不强制值单调递增,所以返回值理论上也可能比历史更小
## 设计说明
这体现了当前类的本质: 它只是用一个 `GLsync` 加两份 CPU 侧原子计数去“近似” timeline fence 接口,而不是严格复刻显式 API 的时间线语义。
## 相关文档
- [Wait](Wait.md)
- [Signal](Signal.md)
- [Wait](Wait.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::GetNativeHandle
# OpenGLFence::GetNativeHandle()
```cpp
void* GetNativeHandle() override;
@@ -6,17 +6,20 @@ void* GetNativeHandle() override;
## 作用
返回底层 `GLsync` 句柄。
## 返回值
- 返回内部 `GLsync`,以 `void*` 形式暴露。
返回底层 OpenGL 同步句柄。
## 当前实现行为
- 如果当前还没有 sync会立即调用 `glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)` 创建一个新的
- 因此它不是纯 getter而是带副作用的懒初始化接口。
- 如果当前 `m_sync == nullptr`,会立刻执行 `glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)` 创建一个新的同步对象
- 返回 `m_sync`
## 需要特别注意
- 这个方法有副作用,不是纯查询接口
- 它不会同步更新 `m_signaledValue``m_completedValue`
- 因此“拿 native handle”本身就可能额外插入一段新的 GPU 栅栏
## 相关文档
- [Signal](Signal.md)
- [GetCompletedValue](GetCompletedValue.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::Initialize
# OpenGLFence::Initialize()
```cpp
bool Initialize(uint64_t initialValue = 0);
@@ -6,22 +6,29 @@ bool Initialize(uint64_t initialValue = 0);
## 作用
初始化 CPU 侧 fence 计数状态。
初始化 fence 的 CPU 侧计数状态。
## 参数
- `initialValue`: 初始 signaled / completed 值
- `initialValue`: 初始 signal/completed 值
## 返回值
- 当前实现始终返回 `true`
`bool`当前实现始终返回 `true`
## 当前实现行为
- 不会立即创建 `GLsync`
- `m_signaledValue``m_completedValue` 设为 `initialValue`
- `m_signaledValue` 设置为 `initialValue`
-`m_completedValue``initialValue`
- 不创建 `GLsync`
- 不执行任何 OpenGL 同步调用
## 需要特别注意
直接调用这个方法时,`initialValue` 会被完整保留;但如果走 `OpenGLDevice::CreateFence(const FenceDesc&)`,当前设备实现传入的是 `desc.initialValue > 0`,所以任何正值都会被折叠成 `1`
## 相关文档
- [Signal](Signal.md)
- [GetCompletedValue](GetCompletedValue.md)
- [Wait](Wait.md)
- [OpenGLFence](OpenGLFence.md)

View File

@@ -6,46 +6,64 @@
**头文件**: `XCEngine/RHI/OpenGL/OpenGLFence.h`
**描述**: OpenGL 后端的同步对象封装,使用单个 `GLsync` 配合 CPU 侧计数值模拟 timeline 风格的 fence 接口。
**描述**: OpenGL 后端的同步对象封装,使用单个 `GLsync` 和两份 CPU 侧原子计数,近似实现 `RHIFence` timeline 风格接口。
## 概
## 概
OpenGL 没有 D3D12 那种原生 timeline fence 接口,更多是一次性的 `GLsync` 栅栏`OpenGLFence` 的实现方式,是把:
OpenGL 原生提供的是一次性的 `GLsync` 栅栏,而不是 D3D12/Vulkan 那种严格单调、可长期复用的 timeline fence`OpenGLFence` 的实现思路是:
- 一个可被替换`GLsync`
- 一个“最近 signal 的值”
- 一个“最近确认完成的值”
- GPU 侧只保留一个当前有效`GLsync`
- CPU 侧记录“最近一次 signal 的值”
- 再记录“最近一次确认完成的值”
组合起来,向 RHI 暴露出近似 timeline fence 的 API 形状
这类适配在跨后端引擎里很常见,但要明确一点: 它的接口看起来像 timeline fence不代表底层语义真的和 D3D12 / Vulkan 完全一致。
这样做可以让跨后端 RHI 保持统一的 `Signal(value)` / `Wait(value)` / `GetCompletedValue()` 接口形状,但底层语义仍然是 OpenGL 风格的单栅栏模型
## 当前实现的真实行为
- 对象内部同一时间只维护一个 `GLsync`
- 每次 [`Signal`](Signal.md) 新值时,如果旧 `GLsync` 还存在,会先等待并删除旧同步对象
- [`Wait`](Wait.md) 等待成功后,会把 `m_completedValue` 直接更新为当前 `m_signaledValue`
- [`GetNativeHandle`](GetNativeHandle.md) 在还没有 sync 时会懒创建一个新的 `GLsync`
- 同一时间只维护一个 `GLsync`
- [Initialize](Initialize.md) 只设置计数值,不创建同步对象
- [Signal](Signal.md) 新值时,如果旧 `GLsync` 还在,会先等待并删除旧栅栏
- `Signal(value)` 会先 `glFlush()`,再插入新的 `glFenceSync`
- [Wait](Wait.md) 成功等待后,会把 `m_completedValue` 直接写成当前 `m_signaledValue`
- [GetCompletedValue](GetCompletedValue.md) 只做轮询,不会在查询时清理同步对象
- [GetNativeHandle](GetNativeHandle.md) 是懒创建接口,本身可能新增一段栅栏
- `Signal()` 的无参重载并不是“递增 1”而是固定转发为 `Signal(1)`
## 设计取舍
## 设计背景
- 这种实现足够支撑当前引擎的基础“提交后等待完成”流程。
- 代价是它不能同时保留多段独立的 in-flight timeline 历史。
商业级引擎在 OpenGL 后端里,通常都会做这类“接口现代化,底层实现务实化”的适配。原因很简单:
## 当前限制
- 上层系统希望用统一的 fence API 驱动命令队列、截图、测试和提交流程
- OpenGL 没有真正的 timeline fence可选方案本来就有限
- 单个 `GLsync` 包装器已经足够覆盖当前引擎里最常见的“提交后等待完成”需求
- 不是严格意义上的 timeline fence
- 如果通过 [`OpenGLDevice::CreateFence`](../OpenGLDevice/CreateFence.md) 创建,`initialValue > 0` 当前只会被折叠成 `1`,不会保留原始数值。
- `GetNativeHandle()` 本身带有副作用: 可能会额外插入新的 `GLsync`
换句话说,当前类的目标不是完美模拟显式 API而是把 OpenGL 的同步原语整理成对引擎友好的形式
## 重点方法
## 生命周期
- [OpenGLFence()](Constructor.md) 初始化为空状态
- [Initialize](Initialize.md) 设置起始计数
- [Signal](Signal.md) 插入新的 GPU 栅栏并记录 signal 值
- [Wait](Wait.md) 在 CPU 侧等待当前栅栏完成
- [Shutdown](Shutdown.md) 删除同步对象并清零计数
## 重要限制
- 不是严格意义上的 timeline fence
- 不保证值单调递增
- 不保留多段 in-flight 历史,只关注“当前这一个 sync”
- `GetNativeHandle()` 有副作用
- 通过 `OpenGLDevice::CreateFence(...)` 创建时,`initialValue > 0` 会被折叠成 `1`
## 关键方法
- [Initialize](Initialize.md)
- [Signal](Signal.md)
- [Wait](Wait.md)
- [GetCompletedValue](GetCompletedValue.md)
- [GetNativeHandle](GetNativeHandle.md)
## 相关文档
- [OpenGL](../OpenGL.md)
- [OpenGLCommandQueue](../OpenGLCommandQueue/OpenGLCommandQueue.md)
- [OpenGLDevice](../OpenGLDevice/OpenGLDevice.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::Shutdown
# OpenGLFence::Shutdown()
```cpp
void Shutdown() override;
@@ -6,13 +6,20 @@ void Shutdown() override;
## 作用
释放内部同步对象并重置计数状态
释放当前同步对象,并把 fence 状态重置为初始值
## 当前实现行为
- 如果 `m_sync` 存在,则调用 `glDeleteSync()` 并清空指针。
-`m_signaledValue``m_completedValue` 都重置为 `0`
- 如果 `m_sync` 非空,则调用 `glDeleteSync`
-`m_sync` 置为 `nullptr`
-`m_signaledValue` 置为 `0`
-`m_completedValue` 置为 `0`
## 设计说明
由于当前类只拥有一个 `GLsync``Shutdown()` 的职责非常直接,就是删除它并清空 CPU 侧状态。
## 相关文档
- [Destructor](Destructor.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLFence::Signal
# OpenGLFence::Signal()
```cpp
void Signal() override;
@@ -7,19 +7,41 @@ void Signal(uint64_t value) override;
## 作用
插入一条新的 OpenGL 同步点,并记录对应的 fence 值
为当前 fence 记录一个新的 signal 点,并插入新的 GPU 栅栏
## 当前实现行为
## 无参重载
- 无参版本等价于 `Signal(1)`
- 如果旧 `GLsync` 还存在,会先等待它完成并删除,再创建新的 `GLsync`
- 在创建新 sync 前会先调用 `glFlush()`
- `m_signaledValue` 被更新为传入值。
```cpp
void Signal() override;
```
## 当前限制
### 当前实现行为
- 不强制值单调递增。
- 因为旧 sync 会被替换,它不能保留多段未完成 timeline 的历史。
- 直接转发到 `Signal(1)`
### 需要特别注意
这不是“在当前值基础上递增 1”而是固定把 signal 值设置为 `1`
## 带值重载
```cpp
void Signal(uint64_t value) override;
```
### 当前实现行为
- 如果已有旧 `m_sync`
-`glClientWaitSync(..., GL_TIMEOUT_IGNORED)`
-`glDeleteSync`
- 然后把 `m_sync` 置空
- 调用 `glFlush()`
-`m_signaledValue` 更新为 `value`
- 调用 `glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)` 创建新的同步对象
## 设计说明
这套实现的本质是“用最新的一次 signal 覆盖上一段历史”。它适合当前引擎的基础提交模型,但和真正能保留完整时间线历史的 fence 仍然不同。
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLFence::Wait
# OpenGLFence::Wait()
```cpp
void Wait(uint64_t value) override;
@@ -6,22 +6,28 @@ void Wait(uint64_t value) override;
## 作用
在 CPU 侧等待 fence 达到定值。
## 参数
- `value`: 希望等待到的 fence 值。
等待 fence 至少达到定值。
## 当前实现行为
- 如果 `m_completedValue >= value`,直接返回。
- 如果内部存在 `GLsync`,会调用 `glClientWaitSync(..., GL_TIMEOUT_IGNORED)` 阻塞等待,然后删除该 sync。
- 等待结束后,把 `m_completedValue` 更新为当前 `m_signaledValue`
- 先读取 `m_completedValue`
- 如果 `m_completedValue >= value`,直接返回
- 否则如果 `m_sync` 存在:
- 调用 `glClientWaitSync(..., GL_TIMEOUT_IGNORED)`
- 删除当前 `m_sync`
-`m_sync` 置为 `nullptr`
- 最后读取 `m_signaledValue`
-`m_completedValue` 直接设置为该 `m_signaledValue`
## 当前限制
## 需要特别注意
- 它并不会验证“当前 signaled 值是否真的大于等于请求值”。
- 这再次说明它是“单 sync + 计数缓存”的模拟方案,而不是真 timeline fence。
- 这里写回的是“当前最近 signal 的值”,不是传入的 `value`
- 因此 `Wait(3)` 在最近一次 signal 是 `5` 时,会把 completed 推到 `5`
- 反过来,如果之后又 `Signal(3)`,那么新的等待结果也可能把 completed 从更大的历史值降回 `3`
## 设计说明
这再次说明当前类不维护严格单调 timeline。它只是在单个 `GLsync` 完成后,把“最近一次 signal 值”认定为已完成。
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::OpenGLPipelineLayout
# OpenGLPipelineLayout::OpenGLPipelineLayout()
```cpp
OpenGLPipelineLayout() = default;
@@ -6,14 +6,21 @@ OpenGLPipelineLayout() = default;
## 作用
构造一个的 pipeline layout 元数据对象。
构造一个尚未初始化的 pipeline layout 适配对象。
## 当前实现行为
- 使用编译器默认构造
- 不会自动初始化映射表。
- 真正的布局复制与 binding point 计算发生在 [`Initialize`](Initialize.md)。
- 使用编译器生成的默认构造函数
- 依赖成员默认值:
- `m_desc = {}`
- `m_initialized = false`
- 各个映射与缓存容器为空
## 设计说明
这是一个纯 CPU 侧元数据对象,不对应 OpenGL 驱动里的原生 layout 句柄,因此构造阶段不需要做额外 API 调用。
## 相关文档
- [Initialize](Initialize.md)
- [UsesSetLayouts](UsesSetLayouts.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::~OpenGLPipelineLayout
# OpenGLPipelineLayout::~OpenGLPipelineLayout()
```cpp
~OpenGLPipelineLayout() override = default;
@@ -6,14 +6,19 @@
## 作用
销毁对象。
析构 pipeline layout 对象。
## 当前实现行为
- 依赖成员对象自身析构释放 CPU 侧容器。
- 不会显式调用 [`Shutdown`](Shutdown.md)。
- 因为它不持有 OpenGL 驱动对象,这种默认析构方式是可行的。
- 使用默认析构函数
- 由成员容器自动释放 CPU 侧缓存
- 不会主动调用 [Shutdown](Shutdown.md)
## 设计说明
由于当前类只管理 CPU 内存,没有 OpenGL 原生资源,默认析构已经足够完成清理。
## 相关文档
- [Shutdown](Shutdown.md)
- [OpenGLPipelineLayout](OpenGLPipelineLayout.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetConstantBufferBindingPoint
# OpenGLPipelineLayout::GetConstantBufferBindingPoint()
```cpp
uint32_t GetConstantBufferBindingPoint(uint32_t setIndex, uint32_t binding) const;
@@ -6,19 +6,19 @@ uint32_t GetConstantBufferBindingPoint(uint32_t setIndex, uint32_t binding) cons
## 作用
查询某个常量缓冲 binding 在 OpenGL 中应使用的实际 binding point。
## 返回值
- set-aware 模式下返回映射后的连续 binding point。
- flat 模式下,如果 `setIndex == 0`,直接返回 `binding`
- 找不到映射时返回 `UINT32_MAX`
查询常量缓冲绑定在 OpenGL 的实际 binding point。
## 当前实现行为
- 不同 set 会被映射到不同的全局 binding point即使它们声明了相同的 binding 编号。
- set-aware 模式下:
-`setIndex` 越界,返回 `UINT32_MAX`
- 若对应映射表里找不到 `binding`,返回 `UINT32_MAX`
- 否则返回预计算得到的 UBO binding point
- 在 flat 模式下:
- 仅当 `setIndex == 0` 时直接返回 `binding`
- 否则返回 `UINT32_MAX`
## 相关文档
- [HasConstantBufferBinding](HasConstantBufferBinding.md)
- [OpenGLDescriptorSet::BindWithPipelineLayout](../OpenGLDescriptorSet/BindWithPipelineLayout.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetDesc
# OpenGLPipelineLayout::GetDesc()
```cpp
const RHIPipelineLayoutDesc& GetDesc() const;
@@ -6,19 +6,19 @@ const RHIPipelineLayoutDesc& GetDesc() const;
## 作用
返回内部保存的、已经过后端归一化处理的 pipeline layout 描述。
## 返回值
- 返回对内部 `m_desc` 的常量引用。
返回当前对象内部保存的 layout 描述。
## 当前实现行为
- 在 set-aware 模式下,`setLayouts` 指向的是内部深拷贝后的数组。
- `constantBufferCount``textureCount``uavCount``samplerCount` 也可能是重新累计后的值。
- 调用 [`Shutdown`](Shutdown.md) 后,这个引用不再可用。
- 直接返回 `m_desc` 的常量引用
## 需要特别注意
- 在 set-aware 模式下,`m_desc.setLayouts` 指向对象内部深拷贝后的布局数组
- 在 flat 模式下,`m_desc` 只是对传入描述的浅拷贝
- 调用 [Shutdown](Shutdown.md) 后,这个引用语义会随内部数据被清空而失效
## 相关文档
- [Initialize](Initialize.md)
- [GetSetLayoutCount](GetSetLayoutCount.md)
- [Shutdown](Shutdown.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetNativeHandle
# OpenGLPipelineLayout::GetNativeHandle()
```cpp
void* GetNativeHandle() override;
@@ -6,19 +6,18 @@ void* GetNativeHandle() override;
## 作用
返回后端原生句柄
## 返回值
- 已初始化时返回 `this`
- 未初始化时返回 `nullptr`
以统一句柄形式返回当前 layout 对象
## 当前实现行为
- OpenGL 没有原生的 pipeline layout 对象,因此这里返回的并不是驱动句柄。
- 这个接口主要用于维持跨后端统一的 RHI 形状。
- `m_initialized` 为真,返回 `this`
- 否则返回 `nullptr`
## 设计说明
当前类没有真正的 OpenGL 原生 layout 句柄,所以 `native handle` 只是引擎内部对象地址,用于统一接口兼容。
## 相关文档
- [Initialize](Initialize.md)
- [GetDesc](GetDesc.md)
- [UsesSetLayouts](UsesSetLayouts.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetSamplerBindingPoint
# OpenGLPipelineLayout::GetSamplerBindingPoint()
```cpp
uint32_t GetSamplerBindingPoint(uint32_t setIndex, uint32_t binding) const;
@@ -6,20 +6,15 @@ uint32_t GetSamplerBindingPoint(uint32_t setIndex, uint32_t binding) const;
## 作用
查询 sampler binding 在 OpenGL sampler unit 空间中的实际编号
## 返回值
- set-aware 模式下返回映射后的 sampler unit。
- flat 模式下 `setIndex == 0` 时直接返回 `binding`
- 未命中时返回 `UINT32_MAX`
查询 sampler 描述符在 OpenGL 中对应的 sampler binding point
## 当前实现行为
- sampler 的编号空间独立于 SRV / UAV。
- 这让同一个 `binding = 0` 可以分别映射为 `texture unit 0``image unit 0``sampler unit 0`
- set-aware 模式下,从 `samplerBindingPoints` 映射表查找
- flat 模式下,仅 `setIndex == 0` 时直接返回 `binding`
- 查找失败或 `setIndex` 越界时返回 `UINT32_MAX`
## 相关文档
- [HasSamplerBinding](HasSamplerBinding.md)
- [OpenGLDescriptorSet::BindWithPipelineLayout](../OpenGLDescriptorSet/BindWithPipelineLayout.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetSetLayoutCount
# OpenGLPipelineLayout::GetSetLayoutCount()
```cpp
uint32_t GetSetLayoutCount() const;
@@ -6,16 +6,15 @@ uint32_t GetSetLayoutCount() const;
## 作用
返回当前保存的 set layout 数量。
## 返回值
- 返回内部 `m_desc.setLayoutCount`
返回当前描述中记录的 set layout 数量。
## 当前实现行为
- 在 flat 模式下通常为 `0`
- 在 set-aware 模式下,数量来自 `Initialize()` 深拷贝后的内部描述。
- 直接返回 `m_desc.setLayoutCount`
## 注意事项
在 set-aware 模式下,这通常等于内部深拷贝后的 layout 数量;在 flat 模式下,它可能是 `0`
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetShaderResourceBindingPoint
# OpenGLPipelineLayout::GetShaderResourceBindingPoint()
```cpp
uint32_t GetShaderResourceBindingPoint(uint32_t setIndex, uint32_t binding) const;
@@ -6,19 +6,15 @@ uint32_t GetShaderResourceBindingPoint(uint32_t setIndex, uint32_t binding) cons
## 作用
查询采样纹理 binding 在 OpenGL texture unit 空间中的实际编号
## 返回值
- set-aware 模式下返回内部计算出的连续 texture unit。
- flat 模式下 `setIndex == 0` 时直接返回 `binding`
- 未命中时返回 `UINT32_MAX`
查询 SRV 在 OpenGL 中对应的纹理单元 binding point
## 当前实现行为
- 该映射与 `CBV``UAV``Sampler` 的 binding point 计数器彼此独立。
- set-aware 模式下,从 `shaderResourceBindingPoints` 映射表查找
- flat 模式下,仅 `setIndex == 0` 时直接返回 `binding`
- 查找失败或 `setIndex` 越界时返回 `UINT32_MAX`
## 相关文档
- [HasShaderResourceBinding](HasShaderResourceBinding.md)
- [OpenGLDescriptorSet::BindWithPipelineLayout](../OpenGLDescriptorSet/BindWithPipelineLayout.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::GetUnorderedAccessBindingPoint
# OpenGLPipelineLayout::GetUnorderedAccessBindingPoint()
```cpp
uint32_t GetUnorderedAccessBindingPoint(uint32_t setIndex, uint32_t binding) const;
@@ -6,19 +6,15 @@ uint32_t GetUnorderedAccessBindingPoint(uint32_t setIndex, uint32_t binding) con
## 作用
查询 `UAV` / image binding 在 OpenGL image unit 空间中的实际编号
## 返回值
- set-aware 模式下返回重排后的 image unit。
- flat 模式下 `setIndex == 0` 时直接返回 `binding`
- 未命中时返回 `UINT32_MAX`
查询 UAV 在 OpenGL 中对应的 image unit binding point
## 当前实现行为
- 当前只是做编号重映射,不涉及 image format、访问权限或 mip / layer 细节。
- set-aware 模式下,从 `unorderedAccessBindingPoints` 映射表查找
- flat 模式下,仅 `setIndex == 0` 时直接返回 `binding`
- 查找失败或 `setIndex` 越界时返回 `UINT32_MAX`
## 相关文档
- [HasUnorderedAccessBinding](HasUnorderedAccessBinding.md)
- [OpenGLDescriptorSet::BindWithPipelineLayout](../OpenGLDescriptorSet/BindWithPipelineLayout.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::HasConstantBufferBinding
# OpenGLPipelineLayout::HasConstantBufferBinding()
```cpp
bool HasConstantBufferBinding(uint32_t setIndex, uint32_t binding) const;
@@ -6,22 +6,16 @@ bool HasConstantBufferBinding(uint32_t setIndex, uint32_t binding) const;
## 作用
判断某个 set 中是否存在常量缓冲 binding 映射。
## 参数
- `setIndex`: 目标 set 索引。
- `binding`: 目标 binding 编号。
## 返回值
- set-aware 模式下,只有映射表中真的存在该 binding 才返回 `true`
- flat 模式下,只要 `setIndex == 0` 就返回 `true`
判断某个 `set + binding` 是否存在常量缓冲绑定映射。
## 当前实现行为
- 在 flat 模式下并不会严格检查 `binding` 是否超出 count。
- 这意味着它更像“当前模式是否允许按 binding 直接访问”,而不是严格的边界验证函数。
- set-aware 模式下:
- `setIndex` 越界时返回 `false`
- 否则检查 `constantBufferBindingPoints` 中是否存在该 `binding`
- flat 模式下:
- 仅根据 `setIndex == 0` 返回 `true``false`
- 不会进一步验证 `binding` 是否越界
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::HasSamplerBinding
# OpenGLPipelineLayout::HasSamplerBinding()
```cpp
bool HasSamplerBinding(uint32_t setIndex, uint32_t binding) const;
@@ -6,13 +6,14 @@ bool HasSamplerBinding(uint32_t setIndex, uint32_t binding) const;
## 作用
判断某个 set 是否存在 sampler binding 的映射。
判断某个 `set + binding` 是否存在 sampler 绑定映射。
## 返回值
## 当前实现行为
- set-aware 模式下按映射表判断。
- flat 模式下,只要 `setIndex == 0` 就返回 `true`
- set-aware 模式下查询 `samplerBindingPoints`
- flat 模式下仅校验 `setIndex == 0`
## 相关文档
- [GetSamplerBindingPoint](GetSamplerBindingPoint.md)
- [UsesSetLayouts](UsesSetLayouts.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::HasShaderResourceBinding
# OpenGLPipelineLayout::HasShaderResourceBinding()
```cpp
bool HasShaderResourceBinding(uint32_t setIndex, uint32_t binding) const;
@@ -6,17 +6,14 @@ bool HasShaderResourceBinding(uint32_t setIndex, uint32_t binding) const;
## 作用
判断某个 set 中是否存在采样纹理 binding 的映射。
## 返回值
- set-aware 模式下按映射表判断。
- flat 模式下,只要 `setIndex == 0` 就返回 `true`
判断某个 `set + binding` 是否存在 SRV 绑定映射。
## 当前实现行为
- 与常量缓冲版本一样flat 模式不会按 texture 数量做严格越界校验。
- set-aware 模式下查询 `shaderResourceBindingPoints`
- flat 模式下仅校验 `setIndex == 0`
## 相关文档
- [GetShaderResourceBindingPoint](GetShaderResourceBindingPoint.md)
- [UsesSetLayouts](UsesSetLayouts.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::HasUnorderedAccessBinding
# OpenGLPipelineLayout::HasUnorderedAccessBinding()
```cpp
bool HasUnorderedAccessBinding(uint32_t setIndex, uint32_t binding) const;
@@ -6,13 +6,14 @@ bool HasUnorderedAccessBinding(uint32_t setIndex, uint32_t binding) const;
## 作用
判断某个 set 中是否存在 `UAV` / image binding 的映射。
判断某个 `set + binding` 是否存在 UAV 绑定映射。
## 返回值
## 当前实现行为
- set-aware 模式下按映射表判断。
- flat 模式下,只要 `setIndex == 0` 就返回 `true`
- set-aware 模式下查询 `unorderedAccessBindingPoints`
- flat 模式下仅校验 `setIndex == 0`
## 相关文档
- [GetUnorderedAccessBindingPoint](GetUnorderedAccessBindingPoint.md)
- [UsesSetLayouts](UsesSetLayouts.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::Initialize
# OpenGLPipelineLayout::Initialize()
```cpp
bool Initialize(const RHIPipelineLayoutDesc& desc) override;
@@ -6,29 +6,55 @@ bool Initialize(const RHIPipelineLayoutDesc& desc) override;
## 作用
复制 pipeline layout 描述,并为 OpenGL 后端生成查询用的 binding point 映射
## 参数
- `desc`: 通用 RHI 管线布局描述,可使用 count 方式,也可使用 `setLayouts` 方式。
初始化 pipeline layout 元数据,并在需要时把 `set + binding` 映射压平成 OpenGL 的全局 binding point。
## 返回值
- 当前实现始终返回 `true`
`bool`当前实现始终返回 `true`
## 当前实现行为
-保存原始 `desc`,然后清空旧的映射和深拷贝缓存。
- 如果提供了 `setLayouts`,会重新拷贝所有 set layout 并重新累计各类 descriptor 数量。
- `CBV``SRV``UAV``Sampler` 四类资源分别独立分配连续 binding point
- 映射顺序按 set 顺序遍历set 内再按 binding 升序遍历。
- 初始化完成后 `GetNativeHandle()` 会返回 `this`
-把传入的 `desc` 复制到 `m_desc`
- 清空:
- `m_setBindingPointMappings`
- `m_setLayouts`
- `m_setLayoutBindings`
## 失败与限制
## 模式 1: set-aware
- 当前没有做参数合法性检查,例如重复 binding、空 binding 数组配合非零 count 等情况
`desc.setLayoutCount > 0 && desc.setLayouts != nullptr` 时进入该模式
### 具体流程
-`m_desc.constantBufferCount``textureCount``samplerCount``uavCount` 清零
- 为每个 set 深拷贝 `DescriptorSetLayoutDesc``DescriptorSetLayoutBinding` 数组
- 重新累计四类描述符总数
-`m_desc.setLayouts` 指向内部 `m_setLayouts` 缓存
- 为每个 set 构建一份 `SetBindingPointMapping`
- 对每种 descriptor 类型分别维护独立的全局计数器:
- CBV
- SRV
- UAV
- Sampler
- 在每个 set 内,先按 `binding` 编号升序排序,再依次分配 binding point
- 分配后,计数器按 `binding.count` 递增;若 `count == 0`,则按 `1` 处理
## 模式 2: flat
当没有 set layout 描述时:
- 不生成 per-set 映射表
- 保留 `m_desc = desc` 的浅拷贝结果
- 后续 getter 会把 `setIndex == 0``binding` 直接当作实际 binding point 使用
## 设计说明
OpenGL 没有 Vulkan/D3D12 那种原生 pipeline layout对应的资源绑定空间也是按类型分裂的全局空间。当前实现的价值就在于把更现代的 `set + binding` 组织方式,转译成 OpenGL 真正需要的编号系统。
## 相关文档
- [UsesSetLayouts](UsesSetLayouts.md)
- [GetDesc](GetDesc.md)
- [GetConstantBufferBindingPoint](GetConstantBufferBindingPoint.md)
- [GetShaderResourceBindingPoint](GetShaderResourceBindingPoint.md)
- [GetUnorderedAccessBindingPoint](GetUnorderedAccessBindingPoint.md)
- [GetSamplerBindingPoint](GetSamplerBindingPoint.md)

View File

@@ -6,63 +6,68 @@
**头文件**: `XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h`
**描述**: OpenGL 后端的 pipeline layout 适配对象,负责把 `set + binding` 关系重排成 OpenGL 的全局 binding point。
**描述**: OpenGL 后端的 pipeline layout 适配对象,负责把现代 RHI 的 `set + binding` 资源布局翻译成 OpenGL 的全局 binding point。
## 概
## 概
OpenGL 的 UBO、采样纹理、image、sampler 都共享各自的全局编号空间,而不是按 `descriptor set` 分组`OpenGLPipelineLayout` 的存在价值,就是让引擎上层依然能写出更接近 Vulkan / D3D12 的资源布局,再由 OpenGL 后端把它们压平到各自的全局 binding space。
OpenGL 不存在真正的原生 pipeline layout也不会像 Vulkan 那样把不同资源先按 descriptor set 分组,再按 binding 解析。它更接近这种模型:
可以把它理解成一张“绑定点翻译表”:
- UBO 有自己的全局 binding 空间
- 纹理采样单元有自己的全局 binding 空间
- image unit 有自己的全局 binding 空间
- sampler object 也依赖自己的编号体系
- 上层看到的是 `setIndex + binding`
- OpenGL 真正需要的是 `uniform block binding``texture unit``image unit``sampler unit`
`OpenGLPipelineLayout` 的职责,就是把上层更现代的 `setIndex + binding` 描述,压平成这些 OpenGL 能直接消费的编号
这种做法的收益,是材质系统和命令列表代码不必为 OpenGL 退化成手写的裸 binding 规则。
## 为什么要这样设计
## 生命周期
这是商业引擎里很典型的跨后端适配策略:
1. 由 [`OpenGLDevice::CreatePipelineLayout`](../OpenGLDevice/CreatePipelineLayout.md) 创建。
2. [`Initialize`](Initialize.md) 复制 layout并计算每个 set 的 binding point 映射。
3. [`OpenGLDescriptorSet`](../OpenGLDescriptorSet/OpenGLDescriptorSet.md) 在 [`BindWithPipelineLayout`](../OpenGLDescriptorSet/BindWithPipelineLayout.md) 中查询这些映射。
4. 销毁前可调用 [`Shutdown`](Shutdown.md) 清空 CPU 侧缓存。
- 上层材质系统、命令列表、descriptor set 代码继续沿用统一的 set/binding 语义
- OpenGL 后端把“不存在的原生 layout 对象”降级成一份 CPU 侧翻译表
- 这样既保留了 API 设计的一致性,也避免了让整个引擎为了 OpenGL 倒退回裸 binding 编号风格
这类对象本质上不是 GPU 资源,而是“绑定规则元数据”。
## 当前实现的真实行为
### 两种工作模式
- [Initialize](Initialize.md) 总是返回 `true`
- 存在两种工作模式:
- set-aware 模式
- flat 模式
- set-aware 模式下会深拷贝 set layout 和 binding 数组
- 同一 descriptor 类型内部binding point 会跨 set 全局递增
- 映射分配前会按 binding 编号排序
- `binding.count == 0` 时,当前算法仍会为其预留 `1` 个槽位
- flat 模式下,只有 `setIndex == 0` 被视为有效
- [GetNativeHandle](GetNativeHandle.md) 返回 `this``nullptr`
- 如果 `desc.setLayouts != nullptr``setLayoutCount > 0`进入“set-aware”模式。
- 否则进入“flat”模式。
## 与其他模块的关系
### set-aware 模式
- [OpenGLDevice](../OpenGLDevice/OpenGLDevice.md) 通过 `CreatePipelineLayout(...)` 创建它
- [OpenGLCommandList](../OpenGLCommandList/OpenGLCommandList.md) 在设置 graphics/compute descriptor set 时把它传下去
- [OpenGLDescriptorSet](../OpenGLDescriptorSet/OpenGLDescriptorSet.md) 在 `BindWithPipelineLayout(...)` 中用它把逻辑 binding 解析成真正的 OpenGL binding point
- 会深拷贝所有 `DescriptorSetLayoutDesc``DescriptorSetLayoutBinding`
- 会重新累加 `constantBufferCount``textureCount``uavCount``samplerCount`
- 对每一种 descriptor 类型分别维护独立的连续 binding point 计数器。
- 同一类型内部,按 binding 编号升序分配 binding point。
- 不同 set 之间会继续递增,因此可以把跨 set 的重名 binding 编号拆开。
## 生命周期
### flat 模式
- 不建立 per-set 映射表。
- getter 直接把 `binding` 当作实际 binding point 返回。
- 只有 `setIndex == 0` 被视为有效。
## 设计取舍
- 这是纯 CPU 侧对象,不对应 OpenGL 驱动里的原生 pipeline layout 句柄。
- 它更像商业引擎里“资源布局元数据”的后端特化版本。
- 这样做的代价是: shader 反射与实际 layout 一致性,当前仍由调用方自己保证。
- [OpenGLPipelineLayout()](Constructor.md) 初始化为空对象
- [Initialize](Initialize.md) 构建描述缓存与映射表
- 运行时通过一系列 `Has*Binding()` / `Get*BindingPoint()` 查询
- [Shutdown](Shutdown.md) 清空内部缓存并把对象标记为未初始化
## 当前限制
-检查当前 shader program 是否真的声明了这些 binding。
-使用 stage visibility 做更细粒度裁剪。
- `Has*Binding()` 在 flat 模式下只验证 `setIndex == 0`,不会根据 count 严格判断某个 binding 是否越界。
-对应 OpenGL 原生对象
-校验 shader 里是否真的声明了这些 binding
- 不使用 stage visibility 做裁剪
- flat 模式下的 `Has*Binding()` 只做 `setIndex == 0` 判断,不做更严格的 binding 越界验证
- `GetDesc()` 暴露的是内部缓存引用,调用方需要注意生命周期
## 重点方法
## 关键方法
- [Initialize](Initialize.md)
- [UsesSetLayouts](UsesSetLayouts.md)
- [HasConstantBufferBinding](HasConstantBufferBinding.md)
- [GetConstantBufferBindingPoint](GetConstantBufferBindingPoint.md)
- [GetShaderResourceBindingPoint](GetShaderResourceBindingPoint.md)
- [GetUnorderedAccessBindingPoint](GetUnorderedAccessBindingPoint.md)
@@ -70,6 +75,6 @@ OpenGL 的 UBO、采样纹理、image、sampler 都共享各自的全局编号
## 相关文档
- [OpenGL](../OpenGL.md)
- [OpenGLDescriptorSet](../OpenGLDescriptorSet/OpenGLDescriptorSet.md)
- [OpenGLCommandList](../OpenGLCommandList/OpenGLCommandList.md)
- [OpenGLUniformBufferManager](../OpenGLUniformBufferManager/OpenGLUniformBufferManager.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::Shutdown
# OpenGLPipelineLayout::Shutdown()
```cpp
void Shutdown() override;
@@ -6,15 +6,21 @@ void Shutdown() override;
## 作用
清空内部保存的布局描述映射表和初始化标志
清空当前 pipeline layout 的描述映射缓存
## 当前实现行为
- 重置 `m_desc`
- 清空 set layout 副本、binding point 映射和初始化标志。
- 因为它是纯 CPU 元数据对象,所以没有 OpenGL 驱动资源需要释放。
- `m_desc` 重置为默认值
- 清空 `m_setBindingPointMappings`
- 清空 `m_setLayouts`
- 清空 `m_setLayoutBindings`
-`m_initialized` 置为 `false`
## 设计说明
由于当前类不持有 GPU 资源,`Shutdown()` 的核心价值是显式回收 CPU 侧映射数据,并让 `GetNativeHandle()` / `UsesSetLayouts()` 等查询重新回到未初始化语义。
## 相关文档
- [Initialize](Initialize.md)
- [GetNativeHandle](GetNativeHandle.md)
- [GetDesc](GetDesc.md)

View File

@@ -1,4 +1,4 @@
# OpenGLPipelineLayout::UsesSetLayouts
# OpenGLPipelineLayout::UsesSetLayouts()
```cpp
bool UsesSetLayouts() const;
@@ -6,16 +6,15 @@ bool UsesSetLayouts() const;
## 作用
判断当前 pipeline layout 是否处于 set-aware 模式。
## 返回值
- `true`: `m_desc.setLayoutCount > 0``m_desc.setLayouts != nullptr`
- `false`: 当前使用 flat 模式。
判断当前对象是否工作在 set-aware 模式。
## 当前实现行为
- 这个判断基于内部保存的 `m_desc`,而不是调用方传入对象的原始地址。
- 返回 `m_desc.setLayoutCount > 0 && m_desc.setLayouts != nullptr`
## 设计说明
这个判断不是看“初始化时是否传过原始 set layout”而是看当前内部描述里是否仍然保存着有效的 set layout 指针和数量。因此它会受到 [Shutdown](Shutdown.md) 影响。
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::Bind
# OpenGLSampler::Bind()
```cpp
void Bind(unsigned int unit) override;
@@ -8,19 +8,13 @@ void Bind(unsigned int unit) override;
把当前 sampler object 绑定到指定纹理单元。
## 参数说明
- `unit`: 目标纹理单元编号。
## 当前实现行为
- 直接调用 `glBindSampler(unit, m_sampler)`
- 不校验 `m_sampler` 是否有效,也不保存之前该纹理单元上的 sampler 绑定。
- 直接调用 `glBindSampler(unit, m_sampler)`
## 使用建议
## 设计说明
- `unit` 必须与纹理绑定和着色器采样槽约定保持一致
- 如果对象尚未初始化,当前实现会把 `0` 绑定到该槽位,相当于未使用独立 sampler object。
在 OpenGL 里,独立 sampler object 的好处是采样状态可以从纹理对象本身拆出来。`Bind(unit)` 正是把这份状态附着到某个纹理单元上,而不是修改纹理对象自身参数
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::OpenGLSampler
# OpenGLSampler::OpenGLSampler()
```cpp
OpenGLSampler();
@@ -6,15 +6,13 @@ OpenGLSampler();
## 作用
构造一个空的 OpenGL sampler 封装对象。
构造一个尚未创建原生 sampler object 的对象。
## 当前实现行为
- `m_sampler` 初始化为 `0`
- `m_desc` 使用结构体默认值。
- 不执行任何 OpenGL 调用。
-`m_sampler` 初始化为 `0`
## 相关文档
- [Initialize](Initialize.md)
- [OpenGLSampler](OpenGLSampler.md)
- [GetID](GetID.md)

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::~OpenGLSampler
# OpenGLSampler::~OpenGLSampler()
```cpp
~OpenGLSampler() override;
@@ -6,19 +6,18 @@
## 作用
销毁 `OpenGLSampler` 对象本身
析构 sampler 对象。
## 当前实现行为
- 当前析构函数是空实现
- 不会自动调用 [Shutdown](Shutdown.md)
- 析构函数是空实现
- 不会自动调用 [Shutdown](Shutdown.md)
## 重要限制
## 需要特别注意
- 这意味着如果调用方在销毁前没有显式 `Shutdown()`,底层 sampler object 可能不会被及时删除
- 它和引擎中许多“析构里自动释放”的包装类行为不同,文档和使用者都必须注意这一点。
如果对象已经成功创建原生 sampler而调用方没有显式 `Shutdown()`,当前实现会把释放责任留给外部生命周期管理
## 相关文档
- [Shutdown](Shutdown.md)
- [Initialize](Initialize.md)
- [OpenGLSampler](OpenGLSampler.md)

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::GetID
# OpenGLSampler::GetID()
```cpp
unsigned int GetID() const;
@@ -7,19 +7,14 @@ unsigned int GetID() override;
## 作用
返回底层 OpenGL sampler object id
## 返回值
- 初始化成功后通常返回非零 id。
- 构造后或 `Shutdown()` 之后返回 `0`
返回底层 OpenGL sampler object ID
## 当前实现行为
- 两个重载都直接返回 `m_sampler`
- 单元测试会校验初始化后该值非零。
- 两个重载都直接返回 `m_sampler`
- `0` 表示当前尚未成功创建 sampler object或者已经 [Shutdown](Shutdown.md)
## 相关文档
- [GetNativeHandle](GetNativeHandle.md)
- [Initialize](Initialize.md)
- [Bind](Bind.md)

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::GetNativeHandle
# OpenGLSampler::GetNativeHandle()
```cpp
void* GetNativeHandle() override;
@@ -6,16 +6,16 @@ void* GetNativeHandle() override;
## 作用
`void*` 形式暴露底层 sampler 句柄
## 返回值
- 返回 `m_sampler` 转换后的值。
统一句柄形式返回底层 sampler object
## 当前实现行为
- 不创建新对象,也不附带其他副作用。
- 其语义与 [GetID](GetID.md) 一致,只是为了适配统一 RHI 的 native handle 接口。
- `m_sampler` 转成 `uintptr_t`
- 再转成 `void*` 返回
## 注意事项
这个句柄本质上是 OpenGL 对象名,不是真正的原生指针对象。
## 相关文档

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::Initialize
# OpenGLSampler::Initialize()
```cpp
bool Initialize(const OpenGLSamplerDesc& desc);
@@ -6,24 +6,26 @@ bool Initialize(const OpenGLSamplerDesc& desc);
## 作用
创建一个真正的 OpenGL sampler object,并把采样参数写入驱动
根据 `OpenGLSamplerDesc` 创建并配置独立 sampler object。
## 参数说明
## 参数
- `desc`: 采样器描述包含过滤模式、寻址模式、比较模式、LOD 范围、各向异性和边界色等参数。
- `desc`: OpenGL 专用采样器描述
## 返回值
- 当前实现恒定返回 `true`
`bool`当前实现始终返回 `true`
## 当前实现行为
- 先复制 `desc``m_desc`
- 调用 `glGenSamplers(1, &m_sampler)` 创建原生 sampler。
- 依次设置:
- `desc` 复制`m_desc`
- 调用 `glGenSamplers(1, &m_sampler)`
- 依次设置
- `GL_TEXTURE_MIN_FILTER`
- `GL_TEXTURE_MAG_FILTER`
- `GL_TEXTURE_WRAP_S/T/R`
- `GL_TEXTURE_WRAP_S`
- `GL_TEXTURE_WRAP_T`
- `GL_TEXTURE_WRAP_R`
- `GL_TEXTURE_LOD_BIAS`
- `GL_TEXTURE_MIN_LOD`
- `GL_TEXTURE_MAX_LOD`
@@ -32,13 +34,24 @@ bool Initialize(const OpenGLSamplerDesc& desc);
- `GL_TEXTURE_COMPARE_FUNC`
- `GL_TEXTURE_BORDER_COLOR`
## 重要限制
## 与设备创建路径的关系
- 当前实现没有检查 `glGenSamplers` 或参数设置是否失败。
- `compareFunc` 作为原始整数直接传给 OpenGL要求调用方或设备工厂提前完成正确映射。
- 代码默认可用 `GL_TEXTURE_MAX_ANISOTROPY`,没有做扩展存在性保护。
通常调用方不会手工拼 `OpenGLSamplerDesc`,而是经由 `OpenGLDevice::CreateSampler(const SamplerDesc&)` 走一层转换:
- `FilterMode` 会被映射到 `SamplerFilter`
- `TextureAddressMode` 会被映射到 `SamplerWrapMode`
- `Comparison*` 过滤模式会启用 `CompareToRef`
- `MirrorOnce` 当前会被折叠为 `ClampToEdge`
- `maxAnisotropy``minLod``maxLod``borderColor` 会直接写入
## 需要特别注意
- 当前实现不检查 `glGenSamplers` 是否真的成功
- 也不检查设置参数时的 OpenGL 错误
- 若直接传入不适合 `GL_TEXTURE_MAG_FILTER` 的过滤枚举,当前代码不会做额外校验
## 相关文档
- [Bind](Bind.md)
- [Shutdown](Shutdown.md)
- [OpenGLSampler](OpenGLSampler.md)

View File

@@ -6,64 +6,60 @@
**头文件**: `XCEngine/RHI/OpenGL/OpenGLSampler.h`
**描述**: OpenGL 后端的独立 sampler object 封装用于保存过滤、寻址、LOD 和比较采样状态。
**描述**: OpenGL 后端的独立 sampler object 封装用于保存过滤、寻址、LOD、比较采样和边框颜色等采样状态。
## 概览
`OpenGLResourceView` 那种“统一抽象视图元数据”不同,`OpenGLSampler` 对应的是一个真实的 OpenGL 原生对象: sampler object。
`OpenGLSampler` 是当前后端里少数真正对应 OpenGL 原生对象的资源包装器之一。它的意义和现代图形 API 里的独立采样器对象一致:
这类对象的作用和商业引擎里的独立采样器资源一致:
- 纹理数据与采样状态解耦
- 同一张纹理可以复用不同采样策略
- descriptor set / resource binding 层更容易和 D3D12 / Vulkan 对齐
- 把纹理采样状态从纹理对象本身拆出来
- 让同一张纹理能以不同采样方式被复用
- 让 descriptor / binding 层更接近现代图形 API 的资源组织方式
## 设计定位
`OpenGLSampler` 在当前引擎中承担的是“真实后端资源包装器”角色,而不是纯元数据对象。
它的好处是:
- 采样状态可以独立于纹理复用
- 更容易与上层 `RHISampler` 抽象对齐
当前实现的代价是:
- 初始化路径非常直接,没有错误处理兜底
- 析构函数没有自动释放原生对象,必须依赖显式 `Shutdown()`
## 生命周期
- 构造后 `m_sampler = 0`
- [Initialize](Initialize.md) 成功后持有真实 sampler id。
- [Shutdown](Shutdown.md) 删除 sampler object。
- 析构函数当前不会自动执行 `Shutdown()`
## 线程语义
- 不包含锁。
- 所有操作都依赖当前 OpenGL 上下文,应视为渲染线程资源对象。
这类设计在商业引擎里非常常见,因为它能让“材质要什么采样方式”和“纹理本身存了什么像素数据”保持分离。
## 当前实现的真实行为
- `OpenGLDevice::CreateSampler()` 会把通用 `SamplerDesc` 转换成 `OpenGLSamplerDesc`,再调用 [Initialize](Initialize.md)。
- `OpenGLDescriptorSet` 在更新采样器时,会读取 [GetID](GetID.md) 保存 sampler id。
- 单元测试 `tests/RHI/OpenGL/unit/test_sampler.cpp` 覆盖了默认初始化、自定义参数初始化、绑定/解绑和 `GetID()`
- [Initialize](Initialize.md) 会创建真实的 sampler object并立即写入全部采样参数
- [Bind](Bind.md) / [Unbind](Unbind.md) 直接作用于指定纹理单元
- [GetID](GetID.md) 返回 OpenGL sampler 对象名
- 析构函数为空实现,不自动 [Shutdown](Shutdown.md)
- 设备创建路径 `OpenGLDevice::CreateSampler(...)` 会先把通用 `SamplerDesc` 转成 `OpenGLSamplerDesc`
- `OpenGLDescriptorSet` 更新 sampler 时,会读取 `GetID()` 存下底层 sampler ID
## 当前限制
## 设计背景
- 析构不自动释放原生 sampler。
- 初始化没有显式失败检测。
- 比较函数和各向异性能力依赖调用环境和扩展支持。
把 sampler 独立成对象有两个现实收益:
- 一张纹理可以被多个材质以不同过滤/寻址方式复用
- 跨后端 RHI 不必因为 OpenGL 而退化回“把采样参数写死在纹理对象里”的旧式接口
这也是商业级引擎常见的抽象路线:高层接口尽量保持现代资源模型,后端尽量映射到平台支持的最接近对象。
## 生命周期
- [OpenGLSampler()](Constructor.md) 初始化 `m_sampler = 0`
- [Initialize](Initialize.md) 创建原生 sampler object
- [Bind](Bind.md) 把它绑定到某个纹理单元
- [Shutdown](Shutdown.md) 删除 sampler object
## 重要限制
- 析构不自动释放原生 sampler
- 初始化没有显式错误检测
- 各向异性和比较采样能力依赖运行环境支持
- 直接使用 `OpenGLSamplerDesc` 时,调用方需要自己保证参数组合适合 OpenGL
## 关键方法
- [Initialize](Initialize.md)
- [Bind](Bind.md)
- [Unbind](Unbind.md)
- [GetID](GetID.md)
- [Shutdown](Shutdown.md)
## 相关文档
- [OpenGL](../OpenGL.md)
- [OpenGLDescriptorSet](../OpenGLDescriptorSet/OpenGLDescriptorSet.md)
- [OpenGLTexture](../OpenGLTexture/OpenGLTexture.md)
- [OpenGLDevice](../OpenGLDevice/OpenGLDevice.md)

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::Shutdown
# OpenGLSampler::Shutdown()
```cpp
void Shutdown() override;
@@ -6,20 +6,19 @@ void Shutdown() override;
## 作用
删除底层 sampler object。
删除当前持有的 sampler object。
## 当前实现行为
- 如果 `m_sampler != 0`,调用 `glDeleteSamplers(1, &m_sampler)`
- 然后把 `m_sampler` 设回 `0`
- 不会清空 `m_desc`
- `m_sampler != 0`,调用 `glDeleteSamplers(1, &m_sampler)`
- 然后把 `m_sampler` 置为 `0`
## 使用建议
## 需要特别注意
- 由于析构函数不会自动调用它,调用方应把 `Shutdown()` 当作释放原生 sampler 的必经步骤。
- 多次调用是安全的,后续调用会因 `m_sampler == 0` 而成为 no-op。
- `m_desc` 不会被重置
- 因此对象失去原生句柄后,仍保留上一次初始化时的 CPU 侧描述缓存
## 相关文档
- [Destructor](Destructor.md)
- [GetID](GetID.md)
- [Initialize](Initialize.md)

View File

@@ -1,4 +1,4 @@
# OpenGLSampler::Unbind
# OpenGLSampler::Unbind()
```cpp
void Unbind(unsigned int unit) override;
@@ -6,18 +6,13 @@ void Unbind(unsigned int unit) override;
## 作用
指定纹理单元解绑独立 sampler object。
## 参数说明
- `unit`: 目标纹理单元编号。
指定纹理单元上的 sampler object 解绑
## 当前实现行为
- 直接执行 `glBindSampler(unit, 0)`
- 不要求当前对象已经初始化,因为它不依赖 `m_sampler`
- 直接调用 `glBindSampler(unit, 0)`
## 相关文档
- [Bind](Bind.md)
- [Shutdown](Shutdown.md)
- [GetID](GetID.md)

View File

@@ -1,6 +1,6 @@
# API 文档重构状态
**生成时间**: `2026-03-28 01:17:27`
**生成时间**: `2026-03-28 01:38:41`
**来源**: `docs/api/_tools/audit_api_docs.py`