MIT 许可证 NuGet

首页 / 功能 / 跨平台

以隔离实现跨平台

六个目标、四种图形 API、一个面向应用的模型。没有 OpenGL 或 WebGL 回退 路径:每个平台得到该平台的当前 API,配上一套自己的手写着色器。 所有真正不同的东西都被推到一个静态服务对象背后,所以你的 场景代码永远不会包含平台条件编译。

目标平台

Windows Linux macOS iOS Android Direct3D 12 Vulkan Metal WebGPU
平台目标框架入口点后端
Windowsnet10.0-windows10.0.19041.0WindowsApp.Run(app)Direct3D 12
Linuxnet10.0LinuxApp.Run(app)Vulkan
Androidnet10.0-androidAndroidApp.Run(app)Vulkan
iOSnet10.0-iosiOSApp.Run(app)Metal
Mac Catalystnet10.0-maccatalystMacCatalystApp.Run(app)Metal
Webnet10.0-browserWebApp.Run(app, jsRuntime, httpClient, ...)WebGPU

Windows 目标是有条件加入的,仅当构建主机是 Windows 时才有,所以 Linux 或 macOS 机器可以不装 Windows SDK 就构建另外五个目标。Android、iOS 和 Mac Catalyst 用 MAUI 宿主层来做应用壳;Linux 和 Web 不用。

Vulkan 由 Linux 和 Android 共享。 它们住在同一个 Platforms/Shared/LinuxAndroid 后端里,iOS 和 Mac Catalyst 共享 Platforms/Shared/Apple。四种图形实现, 六个平台:共享发生在 API 层面,而这是唯一诚实共享的层面。

着色器刻意写四遍

Direct3D 12 用 HLSL,Vulkan 用编译为 SPIR-V 的 GLSL,Apple 用 Metal Shading Language, WebGPU 用 WGSL。没有交叉编译器,没有着色器抽象层,没有运行时 翻译。代价是一次光照改动要改四处;收益是当一个 后端看起来不对时,你读到的正是实际运行的那份着色器,而且一个后端可以 使用其他后端没有的特性,而不必发明一个最小公分母。

设备服务

DeviceServices 是一组静态接口,在你的应用运行之前 由平台入口点初始化一次。所有平台相关的东西都通过它到达, 这就是引擎源码在面向场景的代码里不含任何 #if ANDROID 的原因。

服务接口覆盖范围
CoreIDeviceCore平台与渠道、方向、资源加载、深色模式、权限。
MediaIMediaPlayer带独立音量的音乐与音效播放。
VideoIVideoPlayerService视频帧以 RGBA8 推入引擎。Windows 上硬件加速。
DialogIDialogService原生消息框和原生键盘。
FileIFileService选择器和平台文件访问。
ImageIImageService原生图像解码与编码。
GalleryIGalleryService从系统相册读取和保存。
RecordIRecordService麦克风采集。
RecorderIMediaRecorder录制应用自身的渲染输出。未实现处为 null。
DownloadIDownloadService带进度的后台下载。
StoreIStoreService应用内购买与许可证检查。
AdsIAds广告集成,自愿加入。
WindowsFeaturesIWindowsFeatures仅 Windows 的额外能力,隔离在一个接口背后,而不是散落在引擎各处。
覆盖不均匀,而且不做假

平台没有实现的地方,服务就是 null 或一个文档化的空操作。它 不会静默返回一个看起来合理的值。调用方做空检查 ——DeviceServices.Recorder 是你最先撞上的那个—— 作为交换,你永远不会发布一个开发时看似能用、 在用户设备上却什么都不做的功能。

两个体现这种不对称的具体例子。 屏幕捕获干净地一分为二: CaptureSelf 把引擎自己的后备缓冲读回为 RGBA8,处处可用,因为它走图形 API。 CaptureWindow 抓取整个屏幕,仅限桌面 ——移动端和 Web 沙箱不允许,方法返回 null 而不是 黑图。资源加载也一分为二:每个平台都有同步的 LoadFile,但 Web 用 HTTP 下载覆盖异步路径, 因为 Blazor WebAssembly 没有同步 I/O,那里的任务绝不能被同步等待。

平台入口点做什么

你的应用是一个带面板的 BaseApp 子类, 在所有六个目标上都是同一个类。每个平台头部都是一个小项目, 它的全部工作就是创建窗口或画布、构建平台服务实现, 并把你的应用交给循环。

// Windows head
WindowsApp.Run(new MyApp());

// Web head, inside a Blazor component
await WebApp.Run(new MyApp(), JS, Http, canvasId: "season-canvas");

Web 头部接收更多参数,因为浏览器给它的更少:一个用于互操作的 JavaScript 运行时、 一个 HttpClient(因为资产走网络到达), 以及要渲染进去的画布 id。那一行之上的一切完全一致。

WebGPU 是真的,不是模拟的。 GPU 对象住在 JavaScript 里—— seasonWebGPU.js——C# 层通过 [JSImport] 转发。着色器是 WGSL,由浏览器编译, JS 侧不携带任何着色器源码。Compute 特效在浏览器里运行;全局 光照、TAA 和大气模型与桌面上是同一条代码路径。

存储、设置与本地化

StorageService 从一个 DirectoryBase 名称解析出每个平台的设置与文件位置, 所以应用只需声明一次数据住在哪里,就在每个目标上得到正确答案。 Database 是本地持久化层, Localization 支撑 Texts.Translate 标志——这就是为什么文本在布局 之前而非之后翻译,一段更长的德语字符串会正确重排, 而不是溢出它的盒子。

已知缺口

  • 缺失 没有主机平台。 没有 PlayStation、Xbox 或 Switch 后端, 也没有不涉及 NDA 的通路。
  • 缺失 没有原生 macOS 窗口。 桌面 macOS 通过 Mac Catalyst 到达, 不是一等公民的 AppKit 头部。
  • 部分 服务覆盖因平台而异。 录制、商店和广告是 最薄的。上面的表格如实说明了哪些可能为 null; 在设计围绕某个服务的功能之前先检查。
  • 缺失 没有着色器交叉编译。 加一项光照项意味着编辑四套 着色器。这是一个刻意的取舍,但它是真实的成本, 并随后端数量而增长。