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