Zenith.NET v0.0.7:Metal后端落地,.NET GPU抽象的跨平台旅程,能否一帆风顺?
摘要:从第一行代码写下 GraphicsContext.CreateDirectX12() 到今天 GraphicsContext.CreateMetal() 跑通全部测试,Zenith.NET 终于实现了最初的承诺——用同一套 .NET API
从第一行代码写下 GraphicsContext.CreateDirectX12() 到今天 GraphicsContext.CreateMetal() 跑通全部测试,Zenith.NET 终于实现了最初的承诺——用同一套 .NET API 覆盖三大图形后端。
这篇文章聊聊 Metal 后端的技术选型、架构设计,以及 Zenith.NET 作为一个 .NET GPU 抽象层的设计哲学。
为什么要做 Zenith.NET?
.NET 生态有不少图形相关的库——绑定层如 Silk.NET、Vortice,抽象层如 Veldrid、Evergine。但现有的抽象层要么停留在较旧的 API 版本(如 DX11/OpenGL),要么是商业引擎的一部分,难以作为独立的 GPU 抽象层使用。
Zenith.NET 的定位是:一个面向现代图形 API(DirectX 12、Metal 4、Vulkan 1.4)的轻量 GPU 抽象层,只做抽象、不做引擎,让开发者写一次代码、跑在所有平台上。
这就是 Zenith.NET 要做的事:
后端
策略
DirectX 12
Windows 独占,性能天花板
Metal 4
Apple 全平台,仅支持 Apple Silicon
Vulkan 1.4
跨平台兜底,覆盖 Linux/Android
三个后端不是互相替代的关系,而是各守一方——在每个平台上选最原生的那个 API。
Metal 后端:架构决策
为什么选 Metal.NET?
v0.0.6 的 release notes 里提到过,当时在 SharpMetal 和 .NET macios TFM 之间评估。最终选了 Metal.NET(NuGet 包 Metal.NET 2.3.0)——这是我在开发期间制作的绑定库。相比 SharpMetal,Metal.NET 提供更完善的 Metal 4 API 覆盖,并且所有接口都是类型安全的。
不过坦率地说,Metal.NET 基于 class 封装 Objective-C 对象,在 GC 方面会有一定开销。
整体结构
Zenith.NET 抽象
Metal 实现
GraphicsContext
MTLDevice + MTL4Compiler + MTLResidencySet
CommandBuffer
MTL4CommandBuffer + 双编码器(Render/Compute)
ResourceLayout
绑定槽位计数(Buffer/Texture/Sampler)
ResourceTable
MTL4ArgumentTable,通过 GPU 地址绑定资源
Pipeline
MTLRenderPipelineState + MTLDepthStencilState
SwapChain
CAMetalLayer + CAMetalDrawable
AccelerationStructure
MTLAccelerationStructure + 实例缓冲区
Metal 4 新特性的应用
Metal 4 引入了几个对抽象层至关重要的新特性,Zenith.NET 的 Metal 后端全面采用了它们。
MTL4ArgumentTable——这是 Metal 4 全新的资源绑定模型。相比旧版 Metal 需要逐个 setBuffer/setTexture/setSampler 绑定资源,Argument Table 允许将所有资源打包到一张表中,通过 GPU 地址一次性绑定。这与 Zenith.NET 的 ResourceLayout + ResourceTable 抽象天然吻合:
Zenith.NET
Metal 4
ResourceLayout
声明 Buffer/Texture/Sampler 槽位计数
ResourceTable
创建 MTL4ArgumentTable,填入 GPU 地址
SetResourceTable()
一次调用绑定整张表
MTL4CommandBuffer 采用双编码器模型——同一时刻只能有一种活跃编码器。CommandBuffer 默认开启 Compute 编码器,当用户开启渲染 Pass 时关闭 Compute、切换到 Render 编码器;Pass 结束后自动切回 Compute:
[Compute 编码器] → 开启 Pass → [Render 编码器] → 结束 Pass → [Compute 编码器]
这样设计的好处是:Compute 编码器始终可用于拷贝(Blit)和计算调度,用户无需手动管理编码器生命周期。
