zjchina.net

Drogon · ROS 2 · 独立开发笔记

ADR-00132026-11-07

C ABI v2 —— 表面一脉(`new_surface`)与生命周期行为锁

拍板:zj

背景

ADR-0012 把三个 L0 内核接线收口后,P2「平台与工程」只剩 C ABI 的出口形态问题: v1 只有离屏一脉(new_offscreen → render_to_rgba → PNG 读回),设备端(OHOS 车机/平板)的真实连续渲染仍只能走 Rust 侧的 harmony/render-host。C++ 宿主 (Drogon/ROS2)要上设备,就必须能拿原生窗口句柄直接驱动内核——否则「C ABI = 语言 边界」这句话在设备端不成立。

同时,v1 的生命周期语义只被 render-host 侧测过(tests/lifecycle.rs 的 resize / device-lost / 重建),C ABI 侧没有对位的行为锁:C 宿主在 device lost 后拿到什么 错误码、旧帧会不会被静默回流、resize 后尺寸是否同步,都只写在文档里没人验。

选项

  • A. 表面继续留在 render-host,capi 只离屏。 改动最小,但 C++ 宿主上设备仍需 一个 Rust 桥进程/库,「零 Rust 认知驱动内核」在设备端破功。
  • B. capi 自建 wgpu 实例与表面,内核只认 wgpu::Surface(选定)。 内核已有 Renderer::new_from_surface 与 External target;平台句柄到 wgpu 的翻译留在 capi(等价于 render-host 的既有分工),内核零 #[cfg(ohos)] 污染。
  • C. 内核查平台句柄(吃 OHNativeWindow*)。 会把平台代码搬进 L0 内核,违反 ADR-0005/0011 的「平台代码住 host 桥内」,且内核要多套平台 cfg,收益为零。

证据

  • 内核就绪:Renderer::new_from_surface(instance, surface, size, scale, font, color_space) 已存在,窗口/外部表面两条路径共用 encode_frame;外部表面不支持 rebuild_after_device_lost(只对 winit Window target 做重建),文档即 「重新挂载即恢复」。
  • render_to_rgba 不上屏:它在 External target 上仍渲染到内部帧纹理再读回, 不 present。所以表面句柄的 submit 必须走 render(),与离屏句柄分道。
  • wgpu 单副本:render-host 踩过「两份 wgpu 编译单元 → Surface/Device 类型不 匹配」,结论是精确锁版 =30.0.1。capi 目前经内核传递依赖 wgpu;v2 直接依赖同 版本不会产生第二份编译单元。
  • GLES-only:OHOS 模拟器与车机没有 Vulkan,Backends::GL 是唯一可用后端。
  • CI 无 GPU 设备:GLES 真机路径不能进 CI;可锁的是平台无关语义(resize 尺寸 同步、lost 出口、重建字节一致),与 render-host lifecycle 同一口径。

拍板

  1. 形参扁平、不带结构体: new_surface(surface_kind, window_handle, display_handle, w, h, scale, font_path, font_len)。句柄用 uintptr_t、kind 用 int32_t——守 §7「不跨 struct/enum」;display_handle 为 X11/Wayland 等后续 kind 占位,OHOS 恒 0, 追加 kind 不改签名。v2 只实现 ZJ_SURFACE_OHOS_NDK(0);其余 kind 返回 NULL + zj_last_error,不在 ABI 边 panic。
  2. 依赖:capi 直依赖 wgpu = "=30.0.1" 与 raw-window-handle = "0.6"(与内核 同一编译单元);许可证 MIT/Apache-2.0,已在 docs/DEPENDENCIES.md 收录。
  3. 表面句柄的帧出口 = present:submit/submit_delta 走 render(),不缓存 PNG;readback_png 返回 ZJ_ERR_NOT_READY、readback_png_size 返回 0 (不给设备端截屏,避免每次截图双渲一帧)。
  4. device lost 语义统一:zj_renderer_simulate_device_lost 是演练钩子 (只置标志、不碰 GPU,镜像内核同款 API),供现场演练与 CI 行为锁。置位后 submit / submit_delta / resize / readback_png 一律 ZJ_ERR_DEVICE_LOST, readback_png_size 返回 0(旧帧不得回流);恢复 = destroy + 重新 new (外部表面不做重建,与内核一致)。
  5. 不做:不把平台句柄类型搬进内核;不做 zj_free/回调/asyncio;不为 surface 提供读回;不预实现桌面 kind(无真实消费者,避免锁错平台形状)。

落地与验证

  • 导出:zjchina-render-capi/src/lib.rs(zj_renderer_new_surface、 zj_renderer_simulate_device_lost、zj_capi_version = 2、句柄 Mode Offscreen/Surface、submit 分道、readback/resize 的 lost 守卫);
  • 契约:docs/capi-contract.md §1/§5/§6/§7/§8(new_surface 从预留移入权威块、 ZJ_SURFACE_OHOS_NDK 进 §5)、zjchina-render-capi/zjchina_render.h;
  • 漂移锁:tests/capi_drift.rs(6 项,新增表面类型三处一致断言);
  • 行为锁:tests/lifecycle.rs(resize 尺寸同步 + 末帧作废、lost 三出口响亮报错 且重建字节一致、new_surface 拒绝前不碰 GPU);tests/smoke.rs 版本锚点、 tests/c_smoke.c(纯 C 链接 + 拒绝路径 + golden 比对);
  • 验证:capi cargo test(drift 6 / frame_seq 1 / lifecycle 3 / smoke 4)、 scripts/test-capi.sh(C 冒烟与内核 golden 逐字节一致)、内核 cargo test、render-host --test lifecycle --test article(6 + 1)全绿。
  • 未验:OHOS 真机 GLES 上屏(待 B2,随 scripts/verify-render.sh --device)。

日期:2026-11-07。拍板者 zj。

← 返回决策日志