GPU 硬件抽象层
目录
- 一、设计背景
- 二、架构概览
- 三、GPU 抽象层接口设计
- 3.1 核心对象模型
- 3.2 设备与能力查询:GPU / GPUInfo / GPUFeatures / GPULimits
- 3.3 命令流水线:CommandEncoder → RenderPass → CommandBuffer → CommandQueue
- 3.4 渲染管线状态:RenderPipeline 与 RenderPipelineDescriptor
- 3.5 资源对象:Texture / GPUBuffer / Sampler / ShaderModule
- 3.6 同步原语:Semaphore 与跨渲染系统协作
- 3.7 设备管理与渲染上下文:Device / Context / Window
- 四、后端实现与适配策略
- 五、关键设计决策
- 六、着色器编译与跨平台适配
- 七、附录:接口映射总表与命令提交数据流
- 八、总结
一、设计背景
TGFX 最初作为 PAG(Portable Animated Graphics)的默认图形引擎诞生,服务于动效渲染、视频编辑、文档排版等场景。作为移动端 SDK,它面临三个核心约束:
- 包体 < 2 MB:移动端 SDK 每增加 1 MB 都影响接入方的 App 体积
- 全平台覆盖:iOS、Android、macOS、Windows、Linux、HarmonyOS、Web 七大平台
- 嵌入已有渲染系统:作为 SDK 必须与宿主应用的 Flutter / Unreal Engine / Unity / 原生 UI 等渲染系统共存
这三个约束排除了直接使用某个原生图形 API(只覆盖部分平台)或嵌入通用图形库(包体通常在数 MB 级别)的方案,也排除了直接使用 Vulkan/Metal 的原生 API(无法跨平台)。
因此,TGFX 需要一个统一的 GPU 硬件抽象层(HAL),封装不同平台的 GPU 能力差异,为上层渲染管线提供一致的编程模型。
这个 HAL 层的设计重点参考了 WebGPU 的接口规范。WebGPU 作为 W3C 标准,在吸收了 Metal、Vulkan、Direct3D 12 三大现代图形 API 的设计精华后,提炼出了一套简洁而强大的抽象。TGFX 选择 WebGPU 作为参照系,核心原因是它在"足够现代"和"足够简洁"之间取得了最佳平衡,这恰好与 TGFX 作为轻量级 2D SDK 的定位相契合。
本文聚焦于 TGFX HAL 层本身的接口设计、后端适配策略和关键架构决策,不涉及 HAL 之上的渲染中间层(如绘制合批、着色器生成、资源缓存等)。
二、架构概览
在深入每个接口的细节之前,先从全局视角了解 HAL 层的整体架构。
2.1 HAL 层架构总览
从全景图可以看到,HAL 层分为公开接口和后端实现两大部分。公开接口定义了一组与后端无关的抽象类型(GPU、CommandEncoder、RenderPass 等),后端实现则为每个图形 API(OpenGL、Metal、Vulkan)提供对应的子类。两者之间通过虚函数机制解耦,上层渲染管线只依赖公开接口,完全不感知底层使用的是哪个图形 API。
2.2 多渲染后端适配
三个后端在命令模型、适配策略、着色语言和线程模型上各有不同。其中 OpenGL 是唯一需要额外适配层(GLState 状态跟踪 + 延迟绑定)的后端,因为它的全局状态机编程模型与 HAL 的命令缓冲区模型存在根本差异。Metal 和 Vulkan 则可以直接映射。
三、GPU 抽象层接口设计
3.1 核心对象模型
TGFX HAL 层的对象模型由三组核心概念构成:设备与队列、命令编码、资源与管线。
与 WebGPU 的对应关系一目了然:
| TGFX | WebGPU | 说明 |
|---|---|---|
GPU | GPUDevice | 设备抽象,资源工厂 |
CommandQueue | GPUQueue | 命令提交与数据写入 |
CommandEncoder | GPUCommandEncoder | 编码命令序列 |
RenderPass | GPURenderPassEncoder | 编码渲染命令 |
CommandBuffer | GPUCommandBuffer | 已编码的命令包 |
RenderPipeline | GPURenderPipeline | 渲染管线状态对象 |
Texture | GPUTexture | 纹理资源 |
GPUBuffer | GPUBuffer | 缓冲区资源 |
Sampler | GPUSampler | 采样器 |
ShaderModule | GPUShaderModule | 着色器模块 |
Semaphore | 无直接对应 | GPU-GPU 同步(WebGPU 隐式管理) |
下面逐一分析每个核心接口的设计。
3.2 设备与能力查询:GPU / GPUInfo / GPUFeatures / GPULimits
GPU 是整个 HAL 的入口,等价于 WebGPU 的 GPUDevice。它承担两个核心职责:资源工厂和能力查询。
资源创建方法:
class GPU {
// 创建 GPU 资源
std::shared_ptr<GPUBuffer> createBuffer(size_t size, uint32_t usage);
std::shared_ptr<Texture> createTexture(const TextureDescriptor& descriptor);
std::shared_ptr<Sampler> createSampler(const SamplerDescriptor& descriptor);
std::shared_ptr<ShaderModule> createShaderModule(const ShaderModuleDescriptor& descriptor);
std::shared_ptr<RenderPipeline> createRenderPipeline(const RenderPipelineDescriptor& descriptor);
std::unique_ptr<CommandEncoder> createCommandEncoder();
// 导入外部资源(SDK 嵌入场景)
std::shared_ptr<Texture> importHardwareTextures(const HardwareBufferRef textures[], size_t count);
std::shared_ptr<Texture> importBackendTexture(const BackendTexture& backendTexture);
// ...
};
与 WebGPU 的差异:
资源导入系列方法(
importHardwareTextures、importBackendTexture、importBackendRenderTarget、importBackendSemaphore),这是 TGFX 作为 SDK 的特有需求。WebGPU 运行在浏览器沙盒中,不需要与外部渲染系统共享 GPU 资源;TGFX 作为嵌入式 SDK,必须能导入宿主应用的纹理、渲染目标和信号量。CommandQueue通过GPU::queue()获取,WebGPU 的GPUDevice也只有一个queue属性。TGFX 维持了这个单队列设计,对于 2D 渲染场景足够。
能力查询通过三个结构体暴露:
GPUInfo:后端类型(Backend枚举)、版本号、渲染器名称、厂商、扩展列表,用于调试和诊断。GPUFeatures:布尔能力开关。TGFX 只暴露了三个 feature flag:semaphore:是否支持 GPU 信号量同步clampToBorder:纹理边缘采样是否支持 border 颜色textureBarrier:是否支持纹理屏障(同一纹理读写互斥场景)
对比 WebGPU 的
GPUSupportedFeatures(包含depth-clip-control、float32-filterable等数十个 feature),TGFX 的 feature 集极度精简,只暴露上层渲染管线实际需要做分支判断的能力。GPULimits:硬件限制参数。同样极度精简:maxTextureDimension2D:最大纹理尺寸(对应 WebGPU 同名限制)maxSamplersPerShaderStage:单阶段最大采样器数(对应 WebGPUmaxSamplersPerShaderStage)maxUniformBufferBindingSize:单个 Uniform Buffer 最大绑定尺寸minUniformBufferOffsetAlignment:UBO 偏移对齐要求
WebGPU 定义了 30+ 个 limits,TGFX 只取了 4 个,恰好是 2D 渲染管线真正需要查询的那几个。
3.3 命令流水线:CommandEncoder → RenderPass → CommandBuffer → CommandQueue
TGFX 的命令流水线与 WebGPU 几乎一一对应,是 HAL 层最核心的设计。
CommandEncoder,命令编码器,收集一帧的 GPU 命令:
class CommandEncoder {
// 开始一个渲染通道(同一时间只能有一个活跃的 RenderPass)
RenderPass* beginRenderPass(const RenderPassDescriptor& descriptor);
// 纹理间拷贝
void copyTextureToTexture(std::shared_ptr<Texture> source, ...);
// 纹理到 Buffer 的异步回读
void copyTextureToBuffer(std::shared_ptr<Texture> source, std::shared_ptr<GPUBuffer> buffer, ...);
// Mipmap 生成
void generateMipmapsForTexture(std::shared_ptr<Texture> texture);
// 完成编码,返回不可变的命令缓冲区
std::unique_ptr<CommandBuffer> finish();
};
与 WebGPU GPUCommandEncoder 的差异:
- WebGPU 还有
beginComputePass()返回GPUComputePassEncoder,TGFX 不需要计算管线(2D 渲染不涉及通用计算) - WebGPU 的
copyTextureToTexture/copyTextureToBuffer方法签名几乎相同 - TGFX 额外提供了
generateMipmapsForTexture(),WebGPU 标准不包含这个便捷方法,需要应用自行通过 compute 或 render pass 生成
RenderPass,渲染通道,编码实际的绘制命令:
class RenderPass {
// 视口与裁剪
void setViewport(float x, float y, float width, float height);
void setScissorRect(int x, int y, int width, int height);
// 管线与资源绑定
void setPipeline(std::shared_ptr<RenderPipeline> renderPipeline);
void setUniformBuffer(std::shared_ptr<GPUBuffer> buffer, size_t offset, unsigned index, ShaderStage stage);
void setTexture(std::shared_ptr<Texture> texture, unsigned index, ShaderStage stage);
void setSampler(std::shared_ptr<Sampler> sampler, unsigned index, ShaderStage stage);
void setVertexBuffer(std::shared_ptr<GPUBuffer> buffer, size_t offset, unsigned slot);
void setIndexBuffer(std::shared_ptr<GPUBuffer> buffer, IndexFormat format);
void setStencilReference(uint32_t referenceValue);
// 绘制调用
void draw(PrimitiveType primitiveType, unsigned vertexStart, unsigned vertexCount,
unsigned instanceCount = 1, unsigned baseInstance = 0);
void drawIndexed(PrimitiveType primitiveType, unsigned indexCount,
unsigned instanceCount = 1, unsigned baseIndex = 0, unsigned baseInstance = 0);
// 结束渲染通道
void end();
};
与 WebGPU GPURenderPassEncoder 的对比:
| 方面 | WebGPU | TGFX | 差异原因 |
|---|---|---|---|
| 资源绑定 | setBindGroup(index, bindGroup) | 逐个绑定 setUniformBuffer/setTexture/setSampler | TGFX 不引入 BindGroup 概念以简化实现 |
| Vertex Buffer | setVertexBuffer(slot, buffer, offset) | 相同签名 | 完全一致 |
| Index Buffer | setIndexBuffer(buffer, format, offset) | 相同签名 | 完全一致 |
| Draw | draw(vertexCount, instanceCount, firstVertex, firstInstance) | draw(primitiveType, vertexStart, vertexCount, instanceCount, baseInstance) | TGFX 多一个 primitiveType 参数 |
| Stencil | setStencilReference(ref) | 相同 | 完全一致 |
最大的差异在资源绑定模型。WebGPU 要求应用将 Uniform Buffer、Texture、Sampler 打包为 GPUBindGroup,通过 setBindGroup 整体绑定。TGFX 省去了 BindGroup 这一层,资源直接通过 setUniformBuffer、setTexture、setSampler 逐个绑定到指定 index 和 stage。这个简化的动机是:
- 2D 渲染的绑定模式简单:一次绘制通常只需 1 个 UBO + 1-2 张 Texture + 1 个 Sampler,BindGroup 的分组机制收益不大
- 减少对象数量:BindGroup 需要预创建和缓存,增加了接口数量和实现复杂度
- OpenGL 后端无 BindGroup 概念:OpenGL 的资源绑定本来就是逐个指定 unit/location 的
另一个设计选择是 PrimitiveType 在 draw 调用时传入而非管线创建时固定。WebGPU 将 topology 写入 GPURenderPipelineDescriptor.primitive,TGFX 则将其作为 draw() 的参数。这给了调用者更大的灵活性,同一个 Pipeline 可以绘制不同的图元类型,但代价是 OpenGL 后端需要在 draw 时动态设置图元类型,而 Metal 后端需要忽略这个参数(Metal 的 primitive type 也在 draw 时指定,恰好契合)。
RenderPassDescriptor,渲染通道描述符:
struct RenderPassDescriptor {
ColorAttachment colorAttachment;
std::optional<DepthStencilAttachment> depthStencilAttachment;
};
struct ColorAttachment {
std::shared_ptr<Texture> texture; // 渲染目标
LoadAction loadAction = LoadAction::Load; // 加载行为
StoreAction storeAction = StoreAction::Store; // 存储行为
Color clearColor; // 清除颜色(loadAction 为 Clear 时有效)
std::shared_ptr<Texture> resolveTexture; // MSAA resolve 目标
};
TGFX 只支持单个颜色附件,WebGPU 支持 colorAttachments 数组(MRT,多渲染目标)。对于 2D 渲染,单颜色附件已经足够。resolveTexture 的设计与 WebGPU 一致,当使用 MSAA 时,多采样纹理在渲染通道结束时自动 resolve 到这个单采样纹理。
CommandBuffer 与 CommandQueue:
CommandBuffer 是一个纯虚基类,由 CommandEncoder::finish() 产出,代表一组已编码、不可变的 GPU 命令。它是 HAL 中最薄的抽象,OpenGL 后端的 GLCommandBuffer 几乎是空实现(因为 GL 是立即模式),而 Metal 后端的 MetalCommandBuffer 只是 id<MTLCommandBuffer> 的 RAII 包装。
CommandQueue 对应 WebGPU 的 GPUQueue,职责包括:
class CommandQueue {
void writeBuffer(std::shared_ptr<GPUBuffer> buffer, size_t offset, const void* data, size_t size);
void writeTexture(std::shared_ptr<Texture> texture, const Rect& rect, const void* pixels, size_t rowBytes);
void submit(std::unique_ptr<CommandBuffer> commandBuffer);
std::shared_ptr<Semaphore> insertSemaphore();
void waitSemaphore(std::shared_ptr<Semaphore> semaphore);
void waitUntilCompleted();
};
writeBuffer 和 writeTexture 的存在与 WebGPU 的 GPUQueue.writeBuffer() / GPUQueue.writeTexture() 完全对应,这是一种便捷路径,允许不经过 CommandEncoder 直接向 GPU 资源写入数据。waitUntilCompleted() 对应 WebGPU 的 GPUQueue.onSubmittedWorkDone() 语义,但采用同步阻塞而非异步 Promise 风格。
3.4 渲染管线状态:RenderPipeline 与 RenderPipelineDescriptor
RenderPipeline 是预创建的不可变管线状态对象(Pipeline State Object,PSO),包含了渲染管线的全部固定状态。这与 WebGPU 的 GPURenderPipeline 设计一致,现代图形 API 的核心理念之一就是将管线状态从绘制时的动态设置改为创建时的静态定义,以减少 GPU 状态切换开销。
RenderPipelineDescriptor 的结构层次:
与 WebGPU GPURenderPipelineDescriptor 的关键差异:
BindingLayout 替代 PipelineLayout:WebGPU 使用
GPUPipelineLayout(包含多个GPUBindGroupLayout),TGFX 简化为扁平的BindingLayout,每个BindingEntry直接声明 index、stage 和类型。这与前面 RenderPass 的逐资源绑定模型一致。Primitive topology 不在 descriptor 中:如前所述,TGFX 将图元类型放在
draw()调用中。只支持 Triangles 和 TriangleStrip:WebGPU 还支持 Points、Lines、LineStrip。
BlendFactor 支持双源混合:TGFX 的
BlendFactor枚举包含Src1、OneMinusSrc1、Src1Alpha、OneMinusSrc1Alpha,这是 WebGPU 的dual-source-blending可选特性。TGFX 将其纳入核心 API,因为 2D 渲染中的 Porter-Duff 混合模式(如 DstIn、DstOut)需要双源混合来实现正确性。VertexFormat 更丰富:TGFX 的
VertexFormat包含Half、Half2、Half4(半精度浮点)和Int、Int2(整数类型),部分是 WebGPU 标准中可选支持的格式。
3.5 资源对象:Texture / GPUBuffer / Sampler / ShaderModule
Texture:
class Texture {
int width() const;
int height() const;
PixelFormat format() const;
int mipLevelCount() const;
int sampleCount() const;
uint32_t usage() const; // TextureUsage 位掩码
TextureType type() const; // TwoD / Rectangle / External
BackendTexture getBackendTexture() const;
BackendRenderTarget getBackendRenderTarget() const;
HardwareBufferRef getHardwareBuffer() const;
};
TextureUsage 的设计与 WebGPU GPUTextureUsage 对应:
TEXTURE_BINDING (0x04)↔ WebGPUTEXTURE_BINDING (0x04),可作为着色器采样输入RENDER_ATTACHMENT (0x10)↔ WebGPURENDER_ATTACHMENT (0x10),可作为渲染目标
TGFX 省去了 WebGPU 的 COPY_SRC、COPY_DST、STORAGE_BINDING 等 usage,2D 渲染不需要存储纹理(compute shader),拷贝行为由 CommandEncoder 的方法隐式确定。
TextureType 包含一个 WebGPU 不存在的类型 Rectangle。这是 OpenGL 特有的 GL_TEXTURE_RECTANGLE 纹理类型(非归一化坐标寻址),由外部 OpenGL 纹理导入时可能触发。TGFX 通过 TextureView::getTextureCoord() 在 HAL 层统一处理 Rectangle 与标准纹理的坐标差异。
GPUBuffer:
class GPUBuffer {
size_t size() const;
uint32_t usage() const; // INDEX / VERTEX / UNIFORM / READBACK
bool isReady() const; // 异步回读是否完成
void* map() const;
void unmap() const;
};
GPUBufferUsage 与 WebGPU GPUBufferUsage 对应:
INDEX (0x10)↔INDEX (0x10)VERTEX (0x20)↔VERTEX (0x20)UNIFORM (0x40)↔UNIFORM (0x40)READBACK (0x800),这是 TGFX 特有的用途标记,用于 GPU → CPU 的异步数据回读(对应 WebGPU 的MAP_READusage)。isReady()用于轮询回读是否完成。OpenGL 后端通过glFenceSync实现,Metal 后端通过保存pendingCommandBuffer并等待完成实现。
Sampler:
struct SamplerDescriptor {
AddressMode addressModeX = AddressMode::ClampToEdge;
AddressMode addressModeY = AddressMode::ClampToEdge;
FilterMode minFilter = FilterMode::Linear;
FilterMode magFilter = FilterMode::Linear;
MipmapMode mipmapMode = MipmapMode::None;
};
与 WebGPU GPUSamplerDescriptor 基本一致。AddressMode 枚举包含 ClampToEdge、Repeat、MirrorRepeat 和 ClampToBorder,最后一个需要通过 GPUFeatures::clampToBorder 查询支持性(OpenGL ES 不一定支持)。
ShaderModule:
struct ShaderModuleDescriptor {
std::string code; // 着色器代码
std::optional<ShaderStage> stage; // 仅 OpenGL 需要
};
与 WebGPU 的关键差异:WebGPU 使用 WGSL 作为统一着色语言;TGFX 的着色器代码是后端特定的,OpenGL 用 GLSL,Metal 用 MSL。stage 字段只有 OpenGL 后端需要(因为 GL program 需要区分 vertex/fragment shader 编译),Metal 后端会忽略此字段(MSL 函数通过 entryPoint 名称选择)。
3.6 同步原语:Semaphore 与跨渲染系统协作
class Semaphore {
BackendSemaphore getBackendSemaphore() const;
};
Semaphore 是 WebGPU 标准中不存在的概念,因为 WebGPU 运行在浏览器中,GPU 同步由浏览器运行时管理,应用不需要手动同步。
TGFX 需要 Semaphore 的原因是 SDK 嵌入场景的跨渲染系统同步。当 TGFX 和宿主渲染系统共享 GPU 时(例如 TGFX 渲染到一个纹理,然后宿主应用使用这个纹理),需要确保 TGFX 的 GPU 工作完成后宿主才能读取结果。
后端实现差异:
- OpenGL:基于
glFenceSync/glWaitSync,GL 的同步原语较弱,fence 只能在同一上下文内同步 - Metal:基于
MTLEvent+ 递增值,类似 Vulkan 的 Timeline Semaphore,每次 signal 递增值,wait 等待指定值
BackendSemaphore 包装了后端特定的同步对象引用(GLSyncInfo 或 MetalSyncInfo),允许宿主应用获取原生句柄进行互操作。
3.7 设备管理与渲染上下文:Device / Context / Window
Device、Context 和 Window 严格来说不是 HAL 层的"硬件抽象",而是 HAL 之上的协调层,它们不映射到具体的 GPU 硬件概念,而是提供生命周期管理、线程安全、渲染提交等上层功能。但因为它们在 include/tgfx/gpu/ 中定义,属于 GPU 模块的公开 API,所以在此简要说明。
Device:GPU 设备的线程安全包装。通过 lockContext() / unlock() 提供显式的上下文访问控制。这是 TGFX 作为 SDK 的特有设计,详见 5.2 节。
Context:渲染上下文,持有 GPU 实例并管理其生命周期。提供 flush() → Recording → submit() 的延迟提交模型,以及 memoryUsage() / cacheLimit() / purge() 等资源缓存管理能力。Context 是连接 HAL 层与上层渲染中间层的桥梁。
Window:原生可显示表面的抽象。封装了 getSurface() → 绘制 → present() 的显示流程,管理 Surface 的创建和生命周期。Window 不对应 WebGPU 中的任何概念(WebGPU 通过 Canvas 元素获取渲染表面),它更类似于 Android 的 SurfaceView 或 iOS 的 CAMetalLayer 在引擎侧的抽象。
四、后端实现与适配策略
4.1 后端架构总览
TGFX 当前支持四个图形 API 后端,另有两个 Direct3D 后端在规划中:
| 后端 | 覆盖平台 | 编程模型 | 着色语言 | 实现成熟度 |
|---|---|---|---|---|
| OpenGL (ES) | Android、Linux、Windows、Web (WebGL) | 状态机 | GLSL | 完整 |
| Metal | iOS、macOS | 命令缓冲区 | MSL | 完整 |
| Vulkan | Android、Windows、Linux | 命令缓冲区 | SPIR-V | 开发中 |
| WebGPU | Web | 命令缓冲区 | WGSL | 开发中 |
| D3D11 | Windows | 状态机 | HLSL | 规划中 |
| D3D12 | Windows | 命令缓冲区 | HLSL | 规划中 |
HAL 接口层的每个纯虚类在每个后端都有对应的实现子类:
GPU → GLGPU / MetalGPU
CommandEncoder → GLCommandEncoder / MetalCommandEncoder
RenderPass → GLRenderPass / MetalRenderPass
CommandBuffer → GLCommandBuffer / MetalCommandBuffer
CommandQueue → GLCommandQueue / MetalCommandQueue
Texture → GLTexture / MetalTexture
GPUBuffer → GLBuffer / MetalBuffer
Sampler → GLSampler / MetalSampler
RenderPipeline → GLRenderPipeline / MetalRenderPipeline
ShaderModule → GLShaderModule / MetalShaderModule
Semaphore → GLSemaphore / MetalSemaphore
Device → GLDevice / MetalDevice
4.2 OpenGL 后端:状态机到命令模型的桥接
OpenGL 是 TGFX 支持的唯一一个基于全局状态机编程模型的图形 API。将命令缓冲区模型映射到 OpenGL 是 HAL 层实现中最有挑战性的部分。
GLState:冗余状态消除
OpenGL 的全局状态特性意味着每次状态切换(如 glUseProgram、glBindTexture、glEnable)都是一次 GL 调用,即使值没有变化。GLState 类缓存了所有 GL 状态的当前值,只在实际需要改变时才发出 GL 调用:
RenderPass::setPipeline(pipeline)
│
↓
GLRenderPipeline::activate(glState)
│
├─ glState.setProgram(programID)
│ ├─ currentProgram == programID → 跳过
│ └─ currentProgram != programID → glUseProgram(programID)
│
├─ glState.setBlendState(blendState)
│ ├─ 相同 → 跳过
│ └─ 不同 → glBlendFuncSeparate() + glBlendEquationSeparate()
│
├─ glState.setStencilState(stencilState)
└─ glState.setDepthState(depthState)
GLState 跟踪的状态类别包括:
- 能力开关(
glEnable/glDisable),通过unordered_map<unsigned, bool>缓存 - 视口/裁剪:
viewport、scissorRect - 颜色状态:
clearColor、colorWriteMask - 混合状态:
blendFunc+blendEquation(front/back 分离) - 深度/模板状态:
depthFunc+stencilFunc+stencilOp(front/back 分离) - 面剔除:
cullMode+frontFace - 纹理绑定:每个 texture unit 的绑定纹理(通过
uniqueID对比,避免资源释放重建导致的 ID 冲突) - FBO 绑定:区分 Read/Draw/Both 三种目标
- Pipeline 绑定:VAO + program
关键设计:每帧结束后 GLState 会 reset() 全部缓存状态。这是因为在 SDK 嵌入场景中,宿主应用的 GL 调用可能修改了状态,GLState 无法感知,因此必须在重新获取上下文时清空缓存。
GLRenderPass:延迟绑定
Metal 的 MTLRenderCommandEncoder 是即时编码模型,调用 setVertexBuffer 立即编码命令。OpenGL 没有显式的编码阶段,GL 调用会立即执行。
GLRenderPass 采用延迟绑定策略来弥合差异:setUniformBuffer、setTexture、setSampler 等调用先将绑定信息暂存到 pendingUniformBuffers、pendingTextures 等列表中,直到 draw() / drawIndexed() 调用时才通过 flushPendingBindings() 真正执行 GL 命令。
setUniformBuffer(buffer, offset, 0, Vertex) → pendingUniformBuffers 暂存
setTexture(texture, 0, Fragment) → pendingTextures 暂存
setSampler(sampler, 0, Fragment) → pendingSamplers 暂存
draw(Triangles, 0, 6)
│
└─ flushPendingBindings()
├─ glBindBufferRange(GL_UNIFORM_BUFFER, 0, bufferID, offset, size)
├─ glActiveTexture(GL_TEXTURE0) → glBindTexture(target, textureID)
├─ glTexParameteri(...) ← 应用 sampler 参数
└─ glDrawArrays(GL_TRIANGLES, 0, 6)
这种延迟绑定还有一个好处:当连续绘制使用相同的资源绑定时,flushPendingBindings 可以通过 GLState 的状态对比自动跳过冗余绑定。
GLCommandBuffer:空实现
由于 OpenGL 是立即模式,GL 命令在调用时就已经提交到 GPU 驱动的命令队列,GLCommandBuffer 实际上是一个空壳。GLCommandQueue::submit() 只调用 glFlush() 并重置 GLState,不需要真正"提交"命令。
4.3 Metal 后端:天然映射的参考实现
Metal 的编程模型与 TGFX HAL 几乎天然对应,TGFX HAL 层的设计除了参考 WebGPU 之外,也借鉴了 Metal 的设计理念。
直接映射
即时编码
与 GLRenderPass 的延迟绑定不同,MetalRenderPass 的 setUniformBuffer、setTexture 等方法立即调用 Metal API:
void MetalRenderPass::onSetTexture(std::shared_ptr<Texture> texture, unsigned index, ShaderStage stage) {
auto mtlTexture = static_cast<MetalTexture*>(texture.get())->mtlTexture();
if (stage == ShaderStage::Vertex) {
[encoder setVertexTexture:mtlTexture atIndex:index];
} else {
[encoder setFragmentTexture:mtlTexture atIndex:index];
}
}
Vertex Buffer Index 策略
Metal 的 vertex buffer 和 uniform buffer 共享 buffer index 空间。MetalRenderPipeline 采用倒序分配策略:vertex buffer 从高索引(30, 29, ...)开始分配,uniform buffer 从低索引(0, 1, ...)开始,避免冲突:
Buffer Index: 0 1 2 ... 28 29 30
│ │ │ │ │ │
UBO UBO UBO VBO VBO VBO
← uniform → ← vertex →
这在 MetalRenderPipeline::Make() 中通过 VertexBufferIndexStart - slot 计算实现。
无状态跟踪
Metal 不需要类似 GLState 那样的全量状态缓存层,Metal 的管线状态是不可变对象(MTLRenderPipelineState 和 MTLDepthStencilState),在创建时就固定了所有状态。setPipeline() 只需将这些预创建的状态对象绑定到 encoder,无需逐项检查差异。
不过,Metal 后端同样引入了轻量级的渲染通道级状态跟踪来消除冗余的 Metal API 调用。与 OpenGL 后端的 GLState(跟踪全局 GL 状态机,跨帧持久)不同,Metal 的状态缓存局限于单个 RenderPass 的生命周期内。具体的优化包括:
RenderPass 状态缓存:
MetalRenderPass跟踪当前绑定的 scissor rect、cull mode、uniform buffer、texture、sampler 和 vertex buffer 状态。当连续的绘制调用使用相同的资源绑定时,跳过冗余的[encoder set*:...]调用。这在 2D 渲染中效果显著,同一帧内大量绘制调用通常共享相同的 sampler 和部分 uniform buffer。Sampler 缓存:
MetalGPU维护一个SamplerDescriptor → id<MTLSamplerState>的缓存映射。由于 2D 渲染中常用的采样器配置非常有限(通常只有 Linear + ClampToEdge、Nearest + ClampToEdge 等少数几种),sampler 缓存可以避免每帧重复创建MTLSamplerState对象。ShaderVisibility 精确绑定:通过在
BindingEntry中添加ShaderVisibility标志,Metal 后端可以精确地只向需要的着色器阶段绑定 uniform buffer(例如只绑定到 vertex 或 fragment),而非无差别地同时绑定到两个阶段。
4.4 OpenGL 与 Metal 的关键实现差异
| 对比项 | OpenGL 后端 | Metal 后端 |
|---|---|---|
| 命令模型 | 立即执行,CommandBuffer 为空 | 延迟提交,CommandBuffer 包装 MTLCommandBuffer |
| RenderPass 绑定 | 延迟绑定(draw 时 flush) | 即时编码(直接调用 Metal API) |
| 状态管理 | GLState 缓存层,跟踪全部状态 | 无全量缓存层,PSO 不可变;RenderPass 级轻量状态跟踪消除冗余调用 |
| 管线状态 | 运行时在 activate() 中动态设置 blend/depth/stencil | 创建时固定 MTLRenderPipelineState + MTLDepthStencilState |
| MSAA Resolve | 手动 glBlitFramebuffer 在 onEnd() 中执行 | Metal RenderPass 自动 resolve(StoreAndMultisampleResolve) |
| Sampler | 纯数据,绑定纹理时通过 glTexParameteri 设置 | id<MTLSamplerState> 不可变对象,直接绑定到 encoder |
| 上下文线程安全 | GLDevice::onLockContext() 激活 GL 上下文(makeCurrent) | MetalDevice::onLockContext() 为空(Metal 天然线程安全) |
| Readback 同步 | glFenceSync + 轮询 glClientWaitSync | retain MTLCommandBuffer,map() 时 waitUntilCompleted |
| Texture 写入 | glTexSubImage2D(处理 UNPACK_ROW_LENGTH 和 alignment) | Shared 纹理直接 replaceRegion;Private 纹理需 staging buffer + blit |
| Buffer 写入 | glBindBuffer + glBufferSubData | buffer->map() + memcpy + buffer->unmap() |
4.5 后端资源互操作:BackendTexture / BackendRenderTarget / BackendSemaphore
SDK 嵌入场景要求 TGFX 能与宿主渲染系统交换 GPU 资源。HAL 通过三个 Backend* 类型实现这一能力:
BackendTexture,跨后端纹理句柄:
class BackendTexture {
// OpenGL 路径
BackendTexture(const GLTextureInfo& glInfo, int width, int height);
bool getGLTextureInfo(GLTextureInfo* glInfo) const;
// Metal 路径
BackendTexture(const MetalTextureInfo& mtlInfo, int width, int height);
bool getMetalTextureInfo(MetalTextureInfo* mtlInfo) const;
Backend backend() const;
PixelFormat format() const; // 从后端格式自动推导统一格式
};
GPU::importBackendTexture() 接收一个 BackendTexture,包装为 TGFX 的 Texture 对象。这允许宿主应用传入一个 GLuint 纹理 ID 或 id<MTLTexture> 指针,TGFX 在其上进行渲染,完成后宿主应用继续使用原始纹理。
BackendRenderTarget,跨后端渲染目标。与 BackendTexture 类似,但代表一个可渲染的表面(可能是 FBO、offscreen buffer 等)。GPU::importBackendRenderTarget() 将其包装为可用于 RenderPass 的渲染目标。
BackendSemaphore,跨后端同步原语。允许导入/导出 GPU 同步信号,实现 TGFX 与宿主渲染系统的 GPU-GPU 同步。
这三个 Backend 类型的设计采用了 tagged union 模式,内部通过 Backend 枚举标识当前存储的后端类型,通过 getGL*Info() / getMetal*Info() 获取具体后端的原生句柄。这种设计避免了模板化或虚函数的开销,同时保持了类型安全。
五、关键设计决策
5.1 为什么参考 WebGPU 而非 Vulkan
TGFX 面对 OpenGL、Metal、Vulkan、WebGPU 四个后端,需要选择一个"公约数"作为 HAL 接口的参照系。三个候选方案的取舍:
方案 A:以 OpenGL 为基础:OpenGL 拥有成熟的生态和广泛的平台支持。但 OpenGL 的全局状态机模型与 Metal/Vulkan 等命令缓冲区模型差异较大,以此为基础设计的抽象层在适配现代 API 时需要较多的概念转换。
方案 B:以 Vulkan 为基础:Vulkan 提供了最精细的 GPU 控制能力。但 Vulkan 的 API 接口比较繁琐(如创建 Render Pass 需要 VkRenderPassCreateInfo → VkSubpassDescription → VkAttachmentDescription 等多层描述符),对于 2D 渲染场景而言接口复杂度偏高。同时,在 OpenGL 后端实现 Vulkan 的显式同步、内存管理、管线屏障等概念需要大量的适配工作。
方案 C:以 WebGPU 为基础:WebGPU 是 W3C 在 Metal、Vulkan、D3D12 之上提炼的统一抽象。它继承了现代 API 的核心思想(命令缓冲区、显式管线状态、不可变资源描述符),同时相比 Vulkan 简化了同步模型和内存管理(无显式同步、无内存分配策略、无 subpass 依赖),在 OpenGL 后端的适配成本也相对可控。
具体来说,WebGPU 与 TGFX 需求的契合点:
单队列模型:WebGPU 只暴露一个
GPUQueue,TGFX 也只需要一个命令队列。Vulkan 提供的多队列能力(Graphics/Compute/Transfer)适合需要异步计算和传输的 3D 渲染场景,但 TGFX 的 2D 渲染不需要用到。内存管理隐式化:WebGPU 不暴露 GPU 内存分配细节(无
VkDeviceMemory),资源创建时由运行时自动分配。TGFX 同样隐藏了内存管理,GPU::createTexture()直接返回可用的纹理,调用者不需要关心底层的内存池策略。同步简化:WebGPU 的命令序列是有序的(
GPUQueue.submit()按顺序执行),不需要应用手动插入管线屏障。TGFX 也遵循此模型,只在跨渲染系统场景才暴露显式同步(Semaphore)。管线状态显式化:
RenderPipeline作为不可变的 PSO 预创建,这是 Metal/Vulkan/WebGPU 共有的理念。OpenGL 后端通过 GLState 在运行时动态设置对应的状态来适配这一模型。
5.2 面向 SDK 嵌入的 Device 锁模型
WebGPU 的 GPUDevice 不需要锁,浏览器确保 JavaScript 的单线程执行模型。Vulkan/Metal 的 device 天然线程安全。但 TGFX 面对的 OpenGL 有一个严格约束:GL context 在同一时刻只能被一个线程持有。
作为 SDK,TGFX 无法控制宿主应用的线程模型,宿主可能在 UI 线程、渲染线程、或者任意工作线程调用 TGFX 的 API。如果两个线程同时操作同一个 GL context,结果是未定义行为(通常是 crash 或渲染错误)。
Device::lockContext() / unlock() 强制要求调用者显式获取 GPU 访问权:
auto context = device->lockContext(); // 阻塞等待,直到获取 GL context
if (context == nullptr) return; // 设备不可用
// ... 在此作用域内安全操作 GPU ...
device->unlock(); // 释放 GL context
在 OpenGL 后端,lockContext() 内部执行 eglMakeCurrent() / CGLSetCurrentContext() 等平台特定的上下文绑定;在 Metal 后端,lockContext() / unlock() 内部是空操作(Metal 不需要上下文绑定)。
这个设计的代价是增加了 API 使用的仪式感(每次操作都需要 lock/unlock),但它将线程安全问题从"运行时崩溃"转变为"编译期可见的 API 约束",在 SDK 场景中利大于弊。
5.3 OpenGL 兼容的代价与收益
以现代 API(Metal/WebGPU)为参照设计 HAL,同时向下兼容 OpenGL,带来了具体的工程代价:
代价一:GLState 的维护成本
GLState 需要跟踪 10+ 类 GL 状态,涉及 ~260 行实现代码。每新增一个 HAL 接口涉及的 GL 状态,都需要同步更新 GLState 的缓存逻辑。
代价二:GLRenderPass 的延迟绑定复杂性
Metal 的 setVertexBuffer 直接调用一行 Metal API;OpenGL 的 setVertexBuffer 需要暂存到 pending 列表,在 draw 时通过 glBindBuffer + glVertexAttribPointer + glEnableVertexAttribArray 多步设置。
代价三:管线状态的运行时设置
Metal/Vulkan 的管线状态在创建时固定;OpenGL 的 blend、depth、stencil 状态需要在 GLRenderPipeline::activate() 时通过 glBlendFunc、glDepthFunc、glStencilFunc 等调用动态设置。这意味着 OpenGL 的 setPipeline() 成本高于 Metal。
代价四:每帧 GLState 重置
由于 SDK 嵌入场景中宿主应用可能修改 GL 状态,GLState 必须在每帧结束时 reset() 清空缓存。这意味着每帧的第一次绘制一定会产生全量的 GL 状态设置调用。
收益:
- 覆盖 Android(OpenGL ES 3.0+)、Windows(OpenGL 3.3+)、Linux、Web(WebGL 2.0)四大平台
- 对于尚未在某些平台部署 Vulkan/WebGPU 后端的过渡期,OpenGL 作为 fallback
- OpenGL ES 3.0+ 的特性集(UBO、Instanced Rendering、VAO、MSAA)足以支撑 2D 渲染需求
- TGFX 明确不支持 ES 2.0,放弃旧设备兼容减少了约 30% 的 GL 后端代码
5.4 资源类型的精简策略
对比 WebGPU 定义的资源类型,TGFX 做了系统性的精简:
| WebGPU 资源 | TGFX 对应 | 精简说明 |
|---|---|---|
GPUTexture | Texture | 只支持 2D 纹理,不支持 3D / Cube / 2DArray |
GPUTextureView | 无公开接口 | 内部有 TextureView(在 src/gpu/resources/ 中),但不暴露为 HAL 公开接口 |
GPUBuffer | GPUBuffer | 不支持 STORAGE / INDIRECT / QUERY_RESOLVE 用途 |
GPUSampler | Sampler | 保留 |
GPUShaderModule | ShaderModule | 保留,但着色语言是后端特定的 |
GPUBindGroup | 无 | 资源通过 RenderPass 逐个绑定 |
GPUBindGroupLayout | 无 | 绑定布局嵌入 RenderPipelineDescriptor |
GPUPipelineLayout | 无 | 简化为扁平的 BindingLayout |
GPURenderBundle | 无 | 不支持命令重用 |
GPUComputePipeline | 无 | 不支持计算管线 |
GPUQuerySet | 无 | 不支持 GPU 时间戳/遮挡查询 |
GPUExternalTexture | TextureType::External | 通过类型标记集成,而非独立类型 |
精简的原则是:只保留 2D 渲染实际使用的能力。每一个被省去的类型都意味着更少的接口定义、更少的后端适配代码、更小的包体。
PixelFormat 的精简同样显著,TGFX 只定义了 7 种格式:
ALPHA_8 / GRAY_8 / RG_88 / RGBA_8888 / BGRA_8888 / RGBA_8888_Premultiplied / DEPTH24_STENCIL8
WebGPU 定义了 90+ 种格式(包括 BC/ETC/ASTC 压缩格式、16bit/32bit 浮点格式等)。TGFX 只保留了 2D 渲染需要的 8bit 颜色格式和一个深度模板格式。
5.5 MSAA 的透明化处理
多重采样抗锯齿(MSAA)是 2D 渲染中提升边缘质量的关键技术。TGFX HAL 将 MSAA 的复杂性封装在 HAL 内部,对上层尽可能透明。
纹理层面:TextureDescriptor 的 sampleCount 字段控制采样数。GPU::getSampleCount() 方法返回给定格式在当前设备上支持的实际采样数(不一定等于请求值,如果请求的采样数不被支持,会向下取最近的支持值)。
RenderPass 层面:当 ColorAttachment 同时设置了 texture(多采样)和 resolveTexture(单采样)时,渲染通道结束时自动执行 MSAA resolve:
- Metal 后端:通过
MTLStoreActionStoreAndMultisampleResolve在硬件层面自动完成 - OpenGL 后端:在
GLRenderPass::onEnd()中手动调用glBlitFramebuffer从多采样 FBO 拷贝到单采样 FBO
这个差异对 HAL 的调用者完全不可见,同样的 RenderPassDescriptor 在两个后端产生相同的最终结果,只是实现路径不同。
5.6 Metal 后端:天然对齐与额外挑战
Metal 是 TGFX 实现的第二个 GPU 后端,也是 HAL 抽象正确性的关键验证。由于 HAL 本身参照 WebGPU(而 WebGPU 大量借鉴 Metal)设计,Metal 后端的核心映射关系非常直接,如 GPU → MTLDevice、RenderPass → MTLRenderCommandEncoder、RenderPipeline → MTLRenderPipelineState。但在实际落地中,Metal 后端仍然面临几个特有的设计挑战:
着色器复用而非重写
最核心的决策是如何处理着色器。Metal 使用 MSL(Metal Shading Language),与 OpenGL 的 GLSL 完全不同。TGFX 没有选择为 Metal 单独维护一套着色器源码,而是采用 GLSL → SPIR-V → MSL 的跨编译工具链,复用 OpenGL 后端已有的全部 GLSL 着色器(详见 第六章)。TGFX 额外引入了 shaderc 和 SPIRV-Cross 两个依赖库,但收益巨大,TGFX 的渲染中间层(着色器生成、绘制合批等)完全不需要为 Metal 后端做任何修改。
Buffer Index 空间冲突
Metal 的 vertex buffer 和 uniform buffer 共享同一个 buffer index 空间(0-30)。而 SPIRV-Cross 在跨编译时自动从低索引开始分配 uniform buffer binding。如果 vertex buffer 也从低索引开始,两者会冲突。TGFX 采用了与 MoltenVK 和 Google Dawn 相同的方案,vertex buffer 从索引 30 开始倒序分配(30, 29, ...),uniform buffer 使用低索引(0, 1, ...),在 31 个可用 slot 中两端不会交叉。
Framebuffer Fetch 的 subpassInput 映射
Metal 原生支持 framebuffer fetch(通过 [[color(0)]] 属性直接读取当前片段的颜色附件值),这在 2D 渲染的混合模式中非常有用。TGFX 的 GLSL 着色器使用 subpassInput / subpassLoad() 语法(OpenGL 的 GL_EXT_shader_framebuffer_fetch 扩展),跨编译为 MSL 时需要正确映射为 Metal 的 framebuffer fetch 语义。
资源生命周期与 AutoreleasePool
Metal 的资源创建返回 Objective-C 对象,在非 ARC 环境中需要手动管理引用计数。MetalGPU 通过 MetalResource 基类统一管理所有 Metal 资源的生命周期,配合 autoreleasepool 确保每帧创建的临时 Metal 对象(如 MTLRenderPassDescriptor)被及时释放,避免内存峰值。
六、着色器编译与跨平台适配
TGFX 面临一个着色器管理的关键决策:是为每个后端分别维护一套着色器代码(GLSL + MSL + WGSL),还是采用跨编译方案复用同一份源码?
选择:GLSL 为唯一源语言,跨编译到其他后端
TGFX 选择了后者,所有着色器以 GLSL 作为唯一源码,通过工具链跨编译到各个后端:
各后端的跨编译路径如下:
Metal 后端:MetalShaderModule 在创建时接收 GLSL 源码和 ShaderStage 信息,内部调用 convertGLSLToMSL() 方法完成以下步骤:
- GLSL → SPIR-V:通过 shaderc(Google 的着色器编译器库)将 GLSL 编译为 SPIR-V 中间表示
- SPIR-V → MSL:通过 SPIRV-Cross(Khronos 官方的跨编译器)将 SPIR-V 转换为 MSL
- MSL → MTLLibrary:通过 Metal 运行时将 MSL 编译为
id<MTLLibrary>
Vulkan 后端:GLSL 通过 shaderc 编译为 SPIR-V 后可直接使用,无需二次转换,因为 SPIR-V 是 Vulkan 的原生着色器格式。
D3D11 / D3D12 后端(规划中):GLSL 先通过 shaderc 编译为 SPIR-V,再通过 SPIRV-Cross 转换为 HLSL,最后由 Direct3D 运行时编译为 GPU 可执行的字节码。
WebGPU 后端:GLSL 先通过 shaderc 编译为 SPIR-V,再通过 Tint(Google Dawn 项目的着色器编译器)转换为 WGSL,最后由浏览器的 WebGPU 实现编译执行。
这套 GLSL → SPIR-V → 目标语言的方案与 MoltenVK(Vulkan → Metal 转换层)和 Google Dawn(WebGPU 参考实现)采用的策略一致。
跨编译方案的关键设计细节:
Buffer Index 隔离:SPIRV-Cross 在转换 MSL 时会自动从低索引(0, 1, ...)分配 Uniform Buffer 的 binding index。为避免与 vertex buffer 冲突,TGFX 采用了与 MoltenVK 和 Dawn 相同的策略,vertex buffer 从高索引(30, 29, ...)倒序分配,uniform buffer 使用低索引,两端不会交叉。
Sample Mask 注入:Metal 后端在需要 sample mask 功能时,会对片段着色器进行二次编译。
CompileFragmentShaderWithSampleMask()先将 GLSL 编译为 SPIR-V 以发现已使用的constant_id,选择一个未使用的 ID,向 GLSL 中注入tgfx_SampleMask和gl_SampleMask输出,再重新走一遍 GLSL → SPIR-V → MSL 的完整流程。保留 GLSL 源码:
MetalShaderModule会保留原始 GLSL 代码(glslCode()方法),以便MetalRenderPipeline在需要时能用修改后的 GLSL 重新编译。
跨编译方案的取舍:
| 跨编译方案(TGFX 选择) | 多份源码方案 | |
|---|---|---|
| 维护成本 | 只维护一份 GLSL | 每个后端一份着色器代码 |
| 一致性 | 逻辑一致性由工具链保证 | 需要人工确保多份代码行为一致 |
| 运行时开销 | 首次编译时有跨编译耗时 | 无额外编译开销 |
| 包体影响 | 需要打包 shaderc、SPIRV-Cross 库,WebGPU 后端额外需要 Tint | 无额外依赖 |
| 调试难度 | MSL 是生成代码,调试需要对照 GLSL 源码 | 直接调试原生着色器 |
对于 TGFX 而言,维护成本和一致性的优势远超运行时开销的劣势,着色器编译通常只在管线创建时发生一次,后续通过缓存复用。
七、附录:接口映射总表与命令提交数据流
A.1 与 WebGPU 的接口映射总表
| 层次 | WebGPU | TGFX HAL | 差异 |
|---|---|---|---|
| 设备 | GPUDevice | GPU | TGFX 额外提供资源导入方法 |
| 队列 | GPUQueue | CommandQueue | TGFX 额外提供 insertSemaphore/waitSemaphore |
| 命令编码 | GPUCommandEncoder | CommandEncoder | TGFX 无 beginComputePass,额外提供 generateMipmaps |
| 渲染通道 | GPURenderPassEncoder | RenderPass | TGFX 逐资源绑定(无 BindGroup),PrimitiveType 在 draw 时传入 |
| 命令缓冲 | GPUCommandBuffer | CommandBuffer | 基本一致 |
| 渲染管线 | GPURenderPipeline | RenderPipeline | TGFX 无 PipelineLayout,简化为 BindingLayout |
| 计算管线 | GPUComputePipeline | 无 | 不支持 |
| 纹理 | GPUTexture | Texture | 只支持 2D,无 3D/Cube/Array |
| 纹理视图 | GPUTextureView | 无公开接口 | 内部使用 |
| 缓冲区 | GPUBuffer | GPUBuffer | 无 STORAGE/INDIRECT 用途 |
| 采样器 | GPUSampler | Sampler | 基本一致 |
| 着色器 | GPUShaderModule (WGSL) | ShaderModule (后端特定) | 非统一着色语言 |
| 绑定组 | GPUBindGroup + GPUBindGroupLayout | 无 | 简化为逐资源绑定 |
| 管线布局 | GPUPipelineLayout | BindingLayout | 扁平化 |
| 渲染包 | GPURenderBundle | 无 | 不支持命令重用 |
| 查询集 | GPUQuerySet | 无 | 不支持 |
| 同步 | 隐式(浏览器管理) | Semaphore | TGFX 需要显式跨系统同步 |
| 错误处理 | GPUError + pushErrorScope | 无公开错误 API | 内部断言处理 |
| 像素格式 | 90+ 种 | 7 种 | 只保留 8bit + 深度模板 |
| Feature | 数十个 feature flag | 3 个 | 极度精简 |
| Limits | 30+ 个限制参数 | 4 个 | 只取 2D 渲染必需的 |
A.2 命令提交数据流
以下是一次完整的渲染命令从编码到 GPU 执行的数据流:
八、总结
TGFX 的 GPU 硬件抽象层是一个面向 2D 渲染精简的 WebGPU 子集,加上面向 SDK 嵌入场景的扩展(Device 锁模型、资源导入、显式同步)。
它的设计可以用三句话总结:
接口参照 WebGPU:
GPU/CommandEncoder/RenderPass/CommandBuffer/CommandQueue的概念和流程与 WebGPU 一一对应,确保了与现代图形 API 的设计方向一致。精简到 2D 渲染所需:砍掉计算管线、BindGroup、3D 纹理、渲染包、查询集等 2D 用不到的能力,Feature 从数十个精简到 3 个,Limits 从 30+ 精简到 4 个,PixelFormat 从 90+ 精简到 7 种。
向下兼容 OpenGL:通过 GLState 状态跟踪、延迟绑定、运行时管线状态设置等适配策略,将命令缓冲区模型映射到 OpenGL 的状态机模型,代价是 OpenGL 后端的实现复杂度高于 Metal 后端。
