姚姚吹牛皮 發表於 2026-4-1 11:36:00

Zenith.NET v0.0.7:Metal 后端落地,.NET GPU 抽象的跨平台旅程

<p>从第一行代码写下 <code>GraphicsContext.CreateDirectX12()</code> 到今天 <code>GraphicsContext.CreateMetal()</code> 跑通全部测试,Zenith.NET 终于实现了最初的承诺——<strong>用同一套 .NET API 覆盖三大图形后端</strong>。</p>
<p>这篇文章聊聊 Metal 后端的技术选型、架构设计,以及 Zenith.NET 作为一个 .NET GPU 抽象层的设计哲学。</p>
<h2 id="为什么要做-zenithnet">为什么要做 Zenith.NET?</h2>
<p>.NET 生态有不少图形相关的库——绑定层如 Silk.NET、Vortice,抽象层如 Veldrid、Evergine。但现有的抽象层要么停留在较旧的 API 版本(如 DX11/OpenGL),要么是商业引擎的一部分,难以作为独立的 GPU 抽象层使用。</p>
<p>Zenith.NET 的定位是:<strong>一个面向现代图形 API(DirectX 12、Metal 4、Vulkan 1.4)的轻量 GPU 抽象层</strong>,只做抽象、不做引擎,让开发者写一次代码、跑在所有平台上。</p>
<p>这就是 Zenith.NET 要做的事:</p>
<table>
<thead>
<tr>
<th>后端</th>
<th>策略</th>
</tr>
</thead>
<tbody>
<tr>
<td>DirectX 12</td>
<td>Windows 独占,性能天花板</td>
</tr>
<tr>
<td>Metal 4</td>
<td>Apple 全平台,仅支持 Apple Silicon</td>
</tr>
<tr>
<td>Vulkan 1.4</td>
<td>跨平台兜底,覆盖 Linux/Android</td>
</tr>
</tbody>
</table>
<p>三个后端不是互相替代的关系,而是各守一方——<strong>在每个平台上选最原生的那个 API</strong>。</p>
<h2 id="metal-后端架构决策">Metal 后端:架构决策</h2>
<h3 id="为什么选-metalnet">为什么选 Metal.NET?</h3>
<p>v0.0.6 的 release notes 里提到过,当时在 SharpMetal 和 .NET <code>macios</code> TFM 之间评估。最终选了 <strong>Metal.NET</strong>(NuGet 包 <code>Metal.NET 2.3.0</code>)——这是我在开发期间制作的绑定库。相比 SharpMetal,Metal.NET 提供更完善的 Metal 4 API 覆盖,并且所有接口都是类型安全的。</p>
<p>不过坦率地说,Metal.NET 基于 class 封装 Objective-C 对象,在 GC 方面会有一定开销。</p>
<h3 id="整体结构">整体结构</h3>
<table>
<thead>
<tr>
<th>Zenith.NET 抽象</th>
<th>Metal 实现</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GraphicsContext</code></td>
<td><code>MTLDevice</code> + <code>MTL4Compiler</code> + <code>MTLResidencySet</code></td>
</tr>
<tr>
<td><code>CommandBuffer</code></td>
<td><code>MTL4CommandBuffer</code> + 双编码器(Render/Compute)</td>
</tr>
<tr>
<td><code>ResourceLayout</code></td>
<td>绑定槽位计数(Buffer/Texture/Sampler)</td>
</tr>
<tr>
<td><code>ResourceTable</code></td>
<td><code>MTL4ArgumentTable</code>,通过 GPU 地址绑定资源</td>
</tr>
<tr>
<td><code>Pipeline</code></td>
<td><code>MTLRenderPipelineState</code> + <code>MTLDepthStencilState</code></td>
</tr>
<tr>
<td><code>SwapChain</code></td>
<td><code>CAMetalLayer</code> + <code>CAMetalDrawable</code></td>
</tr>
<tr>
<td><code>AccelerationStructure</code></td>
<td><code>MTLAccelerationStructure</code> + 实例缓冲区</td>
</tr>
</tbody>
</table>
<h3 id="metal-4-新特性的应用">Metal 4 新特性的应用</h3>
<p>Metal 4 引入了几个对抽象层至关重要的新特性,Zenith.NET 的 Metal 后端全面采用了它们。</p>
<p><strong>MTL4ArgumentTable</strong>——这是 Metal 4 全新的资源绑定模型。相比旧版 Metal 需要逐个 <code>setBuffer/setTexture/setSampler</code> 绑定资源,Argument Table 允许将所有资源打包到一张表中,通过 GPU 地址一次性绑定。这与 Zenith.NET 的 <code>ResourceLayout</code> + <code>ResourceTable</code> 抽象天然吻合:</p>
<table>
<thead>
<tr>
<th>Zenith.NET</th>
<th>Metal 4</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ResourceLayout</code></td>
<td>声明 Buffer/Texture/Sampler 槽位计数</td>
</tr>
<tr>
<td><code>ResourceTable</code></td>
<td>创建 <code>MTL4ArgumentTable</code>,填入 GPU 地址</td>
</tr>
<tr>
<td><code>SetResourceTable()</code></td>
<td>一次调用绑定整张表</td>
</tr>
</tbody>
</table>
<p><strong>MTL4CommandBuffer</strong> 采用<strong>双编码器模型</strong>——同一时刻只能有一种活跃编码器。CommandBuffer 默认开启 Compute 编码器,当用户开启渲染 Pass 时关闭 Compute、切换到 Render 编码器;Pass 结束后自动切回 Compute:</p>
<pre><code> → 开启 Pass → → 结束 Pass →
</code></pre>
<p>这样设计的好处是:Compute 编码器始终可用于拷贝(Blit)和计算调度,用户无需手动管理编码器生命周期。所有拷贝操作都走 Compute Encoder 的 Blit 路径,统一了屏障语义。</p>
<p><strong>MTL4Compiler</strong> 支持<strong>设备端编译</strong>——把 Slang 输出的 metallib IR 在目标 GPU 上编译为最终 ISA,比传统 offline 编译能更好地利用 GPU 特定优化。</p>
<h3 id="objective-c-内存桥接">Objective-C 内存桥接</h3>
<p>Metal API 基于 Objective-C 运行时,返回的对象都是 <strong>autoreleased</strong> 的——出了当前 autorelease pool 就会被回收。在 .NET 的托管环境里,这是个隐蔽的坑。</p>
<p>解决方案是一个统一的桥接工具:</p>
<pre><code class="language-csharp">public static T Own&lt;T&gt;(Func&lt;T&gt; func) where T : NSObject
{
    using NSAutoreleasePool _ = new();
    return func().Retain();
}
</code></pre>
<p>所有从 Metal API 获取的对象都通过 <code>NSAutorelease.Own()</code> 包装,确保 <code>Retain</code> 延长生命周期,后续由 .NET 的 <code>Dispose</code> 模式释放。</p>
<h3 id="shader-编译slang-统一管线">Shader 编译:Slang 统一管线</h3>
<p>三个后端共享同一套 Slang 着色器源码:</p>
<pre><code>.slang 源文件
    ├─→ metallib (Metal Shader Library)
    ├─→ dxil   (DirectX Intermediate Language)
    └─→ spirv    (SPIR-V for Vulkan)
</code></pre>
<p>开发者只需维护一份 <code>.slang</code> 着色器,编译到哪个后端由 <code>Slangc.NET</code> 自动处理。</p>
<h3 id="光线追踪">光线追踪</h3>
<p>Metal 后端完整支持硬件光线追踪:</p>
<ul>
<li><strong>BLAS/TLAS</strong>:标准的两级加速结构</li>
<li><strong>实例缓冲区</strong>:CPU 可写的间接寻址,更新 transform 无需重建 TLAS</li>
<li><strong>RayQuery</strong>:在任意着色器阶段内联查询,无需专用光追管线</li>
</ul>
<p>这与 v0.0.6 移除 <code>RayTracingPipeline</code> 的决策一脉相承——统一用 <code>RayQuery</code>,三个后端的光追能力完全对齐。</p>
<h2 id="设计哲学">设计哲学</h2>
<h3 id="只暴露共同能力">只暴露共同能力</h3>
<p>Zenith.NET 的核心原则是:<strong>采用最新 API 版本,只暴露三个后端共同支持的能力</strong>。平台特有的特性被刻意排除,以维护一致的跨平台体验。</p>
<p>这意味着你不会在 Zenith.NET 的 API 里看到 DX12 的 Enhanced Barriers、Vulkan 的 Push Descriptors 或 Metal 的 Tile Shading——这些都是某个 API 独有的。暴露出来只会让其他后端无法实现,破坏"一次编写、处处运行"的承诺。</p>
<p>对于硬件能力差异(比如并非所有 GPU 都支持光追),则通过 <code>Capabilities</code> 动态查询:</p>
<pre><code class="language-csharp">if (context.Capabilities.RayTracingSupported) { /* 光追路径 */ }
if (context.Capabilities.MeshShadingSupported) { /* Mesh Shader 路径 */ }
</code></pre>
<p><strong>共同能力统一暴露,硬件差异动态检测</strong>——这是 Zenith.NET 和其他抽象层最大的区别。</p>
<h3 id="每个平台用最原生的-api">每个平台用最原生的 API</h3>
<p>Zenith.NET 不像 bgfx 那样用 Vulkan 覆盖所有平台。在 Windows 上用 DirectX 12,在 Apple 上用 Metal 4,在 Linux/Android 上用 Vulkan 1.4。</p>
<p>虽然上层只暴露共同能力,但每个后端内部都用对应 API 最地道的方式实现——不需要在一种 API 上模拟另一种 API 的行为模式。</p>
<h3 id="aot-友好">AOT 友好</h3>
<p>整个库从第一天就为 Native AOT 设计。没有反射、没有动态代码生成、没有 <code>Activator.CreateInstance</code>。Metal.NET 和 Silk.NET 底层都是 P/Invoke + 函数指针,AOT 编译器能完整处理。</p>
<h2 id="下一步">下一步</h2>
<p>Metal 后端落地后,Zenith.NET 的三大后端全部就位。接下来的重点:</p>
<ul>
<li><strong>SkiaSharp 集成</strong>——用 GPU 后端加速 2D 渲染</li>
<li><strong>API 稳定化</strong>——向 1.0 迈进</li>
</ul>
<hr>
<p>Zenith.NET 是开源项目,欢迎关注:</p>
<ul>
<li>GitHub:github.com/qian-o/Zenith.NET</li>
<li>文档站:qian-o.github.io/Zenith.NET</li>
<li>NuGet:搜索 <code>Zenith.NET</code></li>
</ul>
<blockquote>
<p>本文由 AI 辅助生成,经作者审核校对。</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/xymfblogs/p/19805856
頁: [1]
查看完整版本: Zenith.NET v0.0.7:Metal 后端落地,.NET GPU 抽象的跨平台旅程