2026 年 9 月 29 日。这几天 zjchina 从「一个用 Dioxus/Drogon 的站」长成了「一个有三个身份层的框架」。最下面是把契约钉死的 zjchina-core,中间是双端共用的 zjchina-ui,最上面——是我们决定自己造的那一层:zjchina-render,一个领域无关的自绘渲染内核。这篇文章交代这三层怎么长出来,以及为什么最后一步是「自己画」。
一、三层,各管一件事
框架的地基不是「代码写得多」,而是「同一份知识只有一处定义」。这个原则落到仓库里,就是三个 crate,依赖方向单向向内:
zjchina-render —— L0 自绘内核(wgpu + winit + fontdue,领域无关)
↑ 未来真机设备端消费
zjchina-ui —— L2 组件层(双端共用的页面/组件,SSR 与 CSR 同一棵树)
↑
zjchina-core —— L2 中立契约层(serde DTO = Schema,禁 web-sys/axum)
- zjchina-core 是所有「同一语义」的家:markdown 渲染、文章 HTML 增强、公开与管理两套共享 DTO、事件台账、决策日志、路由表与三轨道(Content/App/Shell)。它不碰
web-sys、不碰axum,所以能同时编译到 wasm32(CSR)和 native(SSR)。你往这里放一行,双端就少一次「各写一份然后祈祷一致」。 - zjchina-ui 是组件层:导航、首页的分栏、文章页、决策页、404、标签,SSR 侧车和 CSR 薄壳用的都是这几个文件。加上今天刚补的信号生命周期回归测试,它开始长出自己的「行为地基」。
- zjchina-render 是这次的新成员,也是身份最重的一层。它不渲染 DOM,也不渲染 HTML——它直接在 GPU 上画。下面专门讲它。
二、为什么是「自己画」
故事得从 ADR-0005 说起。zjchina 的渲染场景天生分裂:
- 对外是 zjchina.net,靠 SEO、零安装访问、零 JS 首屏吃饭,只能走浏览器 DOM + SSR;
- 对内是真机设备端:工厂大屏、车间看板、KUKA 工业平板、机器人 HMI。这些设备没有浏览器,只能自绘。
二者不是「谁取代谁」,而是需要一条明确的渲染路由:自绘为尊,web 走浏览器渲染兜底。于是我们把自绘引擎定为 L0 身份层——一旦是第一公民,就禁不起轻换,得像地基一样稳。
最初我们以为答案现成:Dioxus 的自绘后端 blitz/wgpu 号称对应 Flutter Impeller。一个 spike 下去,结论反转了两次:
- blitz 出局。dioxus-blitz 在 crates.io 未发布、官方 pre-alpha;更致命的是,
desktop/mobile这两个 feature 实际映射到 wry/WKWebView——根本不是自绘真机,是套了个 WebView 壳。所谓「缺的只是优先级固化」是错觉,恰恰需要再造。 - 独立 wgpu 内核可行。我们在
tidewatch/crates/tw-render里做了最小 spike:5 万根蜡烛(10 万 instanced 图元)+ 中文坐标标签,在 macOS/Metal 上稳定 120 FPS,字型图集渲染无误。
于是方向定死:独立 wgpu 内核,不复现 Dioxus 组件渲染(那等于自己重写 blitz)。spike 转正为 zjchina-render。
三、zjchina-render:一个领域无关的最小内核
它刻意做得「反专用」。内核里没有任何「蜡烛」概念——蜡烛是消费者(bin/candle.rs)用公开 API 拼出来的,恰好用来证明它领域无关。
模块就这么几块:
primitive三类原语:Lines(网格/刻度线段批)、Rects(轴对齐矩形批)、Mesh(instanced 单位 quad,蜡烛 body+wick 的主路径),外加Text(已栅格化字形)。scene保留式场景:Vec<DrawItem>,每个带世界坐标Aabb2,按视口裁剪,脏标记区分「几何变更」与「相机变更」。camera一个Camera2D,pan/zoom 折叠成 16 字节的投影 uniform。renderer拥有 wgpu 的 surface/device/queue/双管线(solid instanced + text),不垄断事件循环——宿主在自己的ApplicationHandler里调它,方便 tidewatch 和未来的 desktop 嵌入。text用 fontdue 把中文字形栅格化进 512×512 的 R8 atlas,(char, px)键缓存,alpha blend 采样。
依赖全部走 MIT/Apache-2.0 红线,其中 fontdue 替代 cosmic-text 是为了避开 MPL——许可证不干净,再快的引擎也不能进身份层。
它的性能回答只有一个,就是我们 spike 里验证过的那条:
静态几何 instanced 一次性上传,每帧只写 16 字节投影 uniform。pan/zoom 不重建顶点,CPU→GPU 带宽压到最小。
四、为什么把它隔离出根 workspace
一个容易漏掉的工程决策:zjchina-render 在 crate 内开了一个独立 [workspace],不加入根 Cargo.toml 的 members。
理由是实打实的:根 workspace 是 dioxus/wasm/web 一脉,把 wgpu/winit 这类重型原生依赖并入 members,会让 cargo build --workspace 和 cargo test --workspace 全量拉入 GPU 编译链——这正是我们在 3.5G 内存小机上「全量编译饿死整机」换来的教训。位置仍在 zjchina 仓库(= 框架 L0 身份),只是构建图隔离。身份层与构建图解耦,是它能安静长大的前提。
五、这一层意味着什么
三层 crate 摆在一起,zjchina 的身份才算完整:
- core 钉住「知识只有一份」——契约、Schema、漂移锁;
- ui 钉住「体验只有一棵树」——双端不再各写一份;
- render 钉住「真机设备端的最后一块」——不再等一个别人维护、且根本不存在的自绘后端。
自绘是 zjchina 未来的刚需,不是可选。工厂大屏、机器人 HMI 这些没浏览器的设备,才是一个多端框架真正的深水区。我们把这条路的第一步,踩实了。
用别人的渲染器,是把身份交出去;自己画,才是把身份握回来。