From 3c05f7107b974c61776f39c15ee2163e4ca700b3 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Fri, 11 Sep 2026 10:01:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20harden=20the=20pipeline=20plan=20?= =?UTF-8?q?=E2=80=94=20GPU=20decode=20zero-copy,=20Job-graph=20BFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two user-mandated amendments: - Decode must be GPU wherever possible and share the render GPU's memory: hardware surfaces (NV12/P010) are imported as GPU textures via the platform interop paths (DMA-BUF / DXGI / IOSurface / CUDA-Vulkan), av_hwframe_transfer_data is never executed on the hw path, and CPU decode + staging upload demotes to fallback only. FFmpeg hwaccel first (the hwdecode.rs device model already builds the device contexts; upstream Olive has no hw decode at all, so the reference for this part is FFmpeg + the existing crate), hand-written GPU decode strictly second. YUV->RGB becomes a built-in GPU pass replacing CPU swscale. Milestone M5 becomes the GPU-decode zero-copy track with HW_TRANSFERS zero as its acceptance counter. - The Job graph becomes a real adjacency structure (no linear table, no 2D array, possibly not fully connected) with a fixed pair of virtual GraphInput/GraphOutput nodes per graph: connected by default, undeletable, un-duplicable, shown in the node editor. resolve is a Kahn-style BFS from the input node — multi-input joins wait for every input, multi-output fans out, the order is deterministic and graph-explicit, cycles error out, unreachable nodes never run — until every branch converges at the output node. M0 splits into M0a (Job enum + single-loop match) and M0b (virtual endpoints + BFS + node-editor display). --- docs/zh/plans/README.md | 2 +- docs/zh/plans/render-pipeline-threads.md | 180 +++++++++++++++++++---- 2 files changed, 151 insertions(+), 31 deletions(-) diff --git a/docs/zh/plans/README.md b/docs/zh/plans/README.md index eb2c693ce..43030b8da 100644 --- a/docs/zh/plans/README.md +++ b/docs/zh/plans/README.md @@ -10,7 +10,7 @@ | [`ai-agent-design.md`](ai-agent-design.md) | **AI Agent 插件设计**(已按 OPP/1 重写):多模态 LLM 作为外部插件经策展工具面自动剪辑,`render.*` 取帧回喂形成"编辑→看图→再编辑"视觉闭环;事务化编辑、双层确认、声明式 AI 面板、Mock LLM/回放夹具测试、A1–A5 里程碑 | external-plugin-system P1–P3 完成(面板需 P4) | | [`external-plugin-system.md`](external-plugin-system.md) | **外部功能插件系统**:插件=独立进程(非库加载),JSON-RPC over stdio 控制面 + shm 数据面(泛化 M15 render-worker 传输);策展宿主 API(事务化可撤销编辑、取帧回喂 AI)、声明式/像素面双 UI 路径、能力位与确认模式;含与 ai-agent-design.md 的关系与 P1–P6 里程碑 | 已解锁(RIIR + M15 完成),随时启动 | | [`external-plugin-protocol.md`](external-plugin-protocol.md) | **插件协议规范 OPP/1**:NDJSON 分帧 + JSON-RPC 2.0 双向信封、握手/心跳/关闭、全量方法/事件/错误码、编辑事务协议、shm 数据面(无头部无锁)、声明式与像素面 UI 协议、限流配额、版本演进规则、AI 粗剪报文示例 | 随 external-plugin-system 启动 | -| [`render-pipeline-threads.md`](render-pipeline-threads.md) | **渲染管线改造**:单解码线程 + 单渲染线程 + 主进程上屏(GPU 单队列,多进程无意义)、OpenFX 收编进唯一隔离进程(崩溃重生)、decode→render→present 三队列流水线、内置效果全 GPU 零拷贝(CPU OFX 才回读,平台分支 CUDA/Vulkan/OpenGL/DirectX)、resolve 重写为 match+Job 单循环(含 CacheJob);M0–M5 里程碑 | 已解锁,随时启动(M0 独立先行) | +| [`render-pipeline-threads.md`](render-pipeline-threads.md) | **渲染管线改造**:单解码线程 + 单渲染线程 + 主进程上屏(GPU 单队列,多进程无意义);解码硬解优先且零拷贝(FFmpeg hwaccel 经 DMA-BUF/DXGI/IOSurface/CUDA 导入,CPU 解码上传仅 fallback,手写 GPU 解码为次选);OpenFX 收编进唯一隔离进程;decode→render→present 三队列流水线;内置效果全 GPU 零拷贝;resolve 重写为 match+Job 单循环并升级为 Job 图(GraphInput/GraphOutput 虚拟端点可见、禁删,从输入节点 Kahn 形态 BFS 至全部分支汇聚输出节点);M0a–M5 里程碑 | 已解锁,随时启动(M0a 独立先行) | ## 其他参考 diff --git a/docs/zh/plans/render-pipeline-threads.md b/docs/zh/plans/render-pipeline-threads.md index fd2f9b0ec..5beb784fb 100644 --- a/docs/zh/plans/render-pipeline-threads.md +++ b/docs/zh/plans/render-pipeline-threads.md @@ -15,6 +15,17 @@ > 9. 参考原版 Olive 重写 resolve:match + Job 枚举(crates/oak-node/src/jobs.rs) > 单次循环完成,而不是四次扫描 > +> 补充硬性要求(2026-09-11,用户追加): +> **解码必须尽可能 GPU,并与渲染共用同一片 GPU 内存——解码这一步也是零拷贝; +> CPU 解码后上传只作为 fallback。解码实现优先 FFmpeg(硬解),次选手写 GPU +> 解码代码。** 本文 §2.7/§3.1/§3.4/§3.6/§4/§6 均按此修订。 +> +> 补充硬性要求二(2026-09-11,用户追加): +> **Job 图做成货真价实的图存储结构**(不是线性表,也不是二维数组),允许非 +> 全连通;每个项目图固定一对**虚拟输入节点 / 虚拟输出节点**(默认相连、不可 +> 删除、要在节点编辑器里显示出来);resolve 从输入节点开始**广度优先搜索**, +> 逐个 match 处理 Job,直到全部分支汇聚于输出节点。见 §3.8。 +> > 配套调研:上游 Olive 已浅克隆到 `.cache/olive-upstream`(gitignore 内),本文 > 引用其文件时写作 `upstream/app/...`。 @@ -62,13 +73,15 @@ **零拷贝改造的数据类型基础已经存在**,缺的是"全链路不把 GPU 纹理下载回来" 的管线。 -### 2.3 解码在 worker 内随叫随做,无流水线 +### 2.3 解码在 worker 内随叫随做,硬解帧全部回读 CPU,无流水线 - `crates/oak-render/src/eval.rs:988-990`:解码器会话本就进程级共享 (按 (filename, stream) 键控、内部互斥),但解码动作发生在每个 worker 的 ticket 处理里:一帧的"解码→渲染→回读"是串行的,帧与帧之间没有重叠。 - `render_footage_frame`(eval.rs,FootageJob 的落点)同步解码一帧并缩放到 目标尺寸。 +- **硬件解码已经存在,但硬件帧全部下载回 CPU**(详见 §2.7)——GPU 解码 + 这块砖已经有了,缺的是"不把砖搬回 CPU"的下半截。 ### 2.4 resolve 四次扫描表 @@ -110,6 +123,24 @@ - `render/job/*.h`:AcceleratedJob 基类 + 各 Job 的字段集(与我们 jobs.rs 的载荷一一对应,可对照补全)。 +### 2.7 硬解已接通但逐帧下载(解码零拷贝的现状缺口) + +- `crates/oak-codec/src/hwdecode.rs:17-42`:FFmpeg 8 的 hwaccel 模型已落地 + ——建平台硬件设备上下文(`av_hwdevice_ctx_create`)挂到软解码器上, + VideoToolbox(macOS)/ CUDA(NVDEC)→VAAPI(Linux)/ D3D11VA→CUDA + (Windows)按候选顺序尝试,软解兜底;`OAK_HWACCEL=0` 与配置项 + `HardwareDecoding` 双开关;`HW_TRANSFERS` 计数器证明 hwaccel 真生效。 +- **但每帧硬件表面都用 `av_hwframe_transfer_data` 下载到系统内存**再走 + swscale(hwdecode.rs:41-42 自述)——这就是"解码零拷贝"要消灭的那次 + 下载。项目 FFmpeg 构建已带齐平台硬解 + (`--enable-nvdec/vaapi/vdpau/d3d11va/dxva2/videotoolbox`, + tooling/ffmpeg/build-ffmpeg.sh)。 +- 上游 Olive **没有硬解**(`.cache/olive-upstream/app/codec/decoder.cpp` + 全文无 hw_device/hwaccel 引用,纯软解+上传)——本计划的 GPU 解码超出 + Olive 的范围,参考对象改为 FFmpeg hwaccel API 与本仓库 hwdecode.rs 已有 + 的设备模型;FFmpeg 覆盖不到的路径才手写 GPU 解码(Vulkan Video / + 直接 NVDEC 绑定,见 §3.6)。 + ## 3. 目标架构 ```text @@ -124,8 +155,9 @@ │ │ 图求值 + 全部 GPU pass │ │ │ ▼ │ │ │ decode 队列 ◄── 解码线程(唯一)─────────┘ │ -│ │ 持有全部解码器会话 │ -│ │ 解码→上传 GPU→投 render 队列 │ +│ │ 持有全部解码器会话+硬解设备 │ +│ │ 硬解→GPU 导入(零拷贝,§3.6) │ +│ │ 软解→staging 上传(仅 fallback) │ │ │ │ ┌─── 仅当链上出现 CPU OpenFX 特效时 ───┐ │ │ ▼ │ │ @@ -139,8 +171,14 @@ 1. **解码线程(唯一)**:独占全部解码器会话(eval.rs:988 的进程级会话表 顺理成章地归它所有——会话本就互斥,集中后连互斥都可以去掉)。从 - decode 队列取 FootageJob,解码、按需要缩放/色彩预变换、**上传为 GPU - 纹理**,把结果投到 render 队列。连续播放时由调度层预取(下一条 §3.4)。 + decode 队列取 FootageJob 解码,**产出直接落在 GPU 上、与渲染共用同一 + 片 GPU 内存**:硬解帧(NV12/P010 等 YUV 表面)经平台互操作原样导入为 + GPU 纹理(§3.6,零拷贝),**不执行** `av_hwframe_transfer_data`; + CPU 解码 + staging 上传只作为硬解不可用时的 fallback(沿用 + `OAK_HWACCEL` / `HardwareDecoding` 开关,hwdecode.rs:56/102)。 + 硬解表面到工作色彩空间 RGBA 的转换不再走 CPU swscale,而是渲染线程 + 上的一个内置 "YUV→RGB" GPU pass(色度上采样 + 色彩矩阵着色器, + 与 §3.5 的全 GPU 中间纹理一致)。连续播放时由调度层预取(§3.4)。 2. **渲染线程(唯一)**:独占进程唯一 `GpuContext`(`GpuContext::shared()` 之外的第二条获取路径收编到这里;texture.rs:178-181 的 Arc 自持模型 不变)。从 render 队列取"图快照 + 时刻"作业,跑图求值与全部 GPU @@ -180,6 +218,11 @@ FootageJob"时不是同步解码,而是把该渲染作业挂起到依赖完成(依赖计数 到位后自动入 render 队列)——Olive 是同步解码(renderprocessor.cpp:325 ProcessVideoFootage 在渲染线程内解码),我们按用户要求做真流水线。 +- **硬解帧的流水走向**:解码线程产出的硬件表面(YUV)以平台句柄形态 + (DMA-BUF fd / D3D11 纹理 / CVPixelBuffer / CUDA 数组)经 §3.6 的互操作 + 导入为 GPU 纹理后投 render 队列;帧的生命周期由"持有 AVHWFramesContext + 引用的 GPU 纹理包装"管理,渲染线程消费完(YUV→RGB pass 采样结束)即 + 释放回硬解帧池。全路径无 `av_hwframe_transfer_data`。 - 背压:三条队列都有界;上屏消费慢(暂停、窗口最小化)时 render 队列满 → 解码暂停预取;导出时上屏队列直通导出消费者,不存在"没人收"的积压。 @@ -196,22 +239,34 @@ - CPU 边界的显式回读点只有三处:CPU OFX 插件(§3.2)、导出编码器输入、 磁盘帧缓存写入(FrameHashCache::SaveCacheFrame 对应物)。 -### 3.6 平台互操作分支(为 7 预留) +### 3.6 平台互操作分支(解码零拷贝与上屏,用户硬性要求) -解码上传与上屏共用一层 `gpuinteop` 抽象,按后端分支实现: +**解码必须尽可能 GPU,并与渲染共用同一片 GPU 内存;CPU 解码后上传只作为 +fallback。** 解码上传与上屏共用一层 `gpuinteop` 抽象,按后端分支实现。 +**解码实现优先 FFmpeg 硬解**(hwdecode.rs 的设备模型 + 下表的导入路径), +**FFmpeg 覆盖不到的才手写 GPU 解码**(Vulkan Video 扩展 / 直接 NVDEC +绑定——仅当某种编码或平台组合 FFmpeg 没有 hwaccel 时立项,不提前写): -| 平台/后端 | 解码→GPU | 上屏/外部共享 | -|---|---|---| -| Linux Vulkan | 软解→staging 上传;后续 VAAPI→DMA-BUF→Vulkan 外部内存 | wgpu 同源纹理直通;跨进程必要时 DMA-BUF fd | -| Windows | 软解→staging;后续 D3D11VA/NVDEC→D3D11 纹理 | D3D11 shared HANDLE / DXGI | -| CUDA(可选加速) | NVDEC→cuArray→CUDA-Vulkan 互操作(cuImportExternalMemory 系) | 仅导出/插件边界使用 | -| macOS | 软解→staging;后续 VideoToolbox→IOSurface→Metal | IOSurface 共享 | -| OpenGL(遗留/调试用) | 软解→glTexSubImage2D | 仅调试后端 | +| 平台/后端 | 硬解(FFmpeg hwaccel)→ GPU 导入(零拷贝) | fallback | 上屏/外部共享 | +|---|---|---|---| +| Linux + NVIDIA | NVDEC(CUDA 设备)→ cuArray → CUDA-Vulkan 外部内存互操作(`cuImportExternalMemory` 系)导入 wgpu 纹理 | 软解→staging 上传 | wgpu 同源纹理直通 | +| Linux + AMD/Intel | VAAPI → VASurface → DMA-BUF fd → Vulkan 外部内存(`VK_EXT_external_memory_dma_buf` + `VK_EXT_image_drm_format_modifier`) | 软解→staging 上传 | 同源直通;跨进程必要时 DMA-BUF fd | +| Windows | D3D11VA → D3D11 纹理 → DXGI shared HANDLE(NT 句柄)导入 wgpu(DX12/Vulkan 后端均可);NVDEC 候选经 CUDA 互操作 | 软解→staging 上传 | D3D11 shared HANDLE / DXGI | +| macOS | VideoToolbox → CVPixelBuffer → IOSurface → Metal 纹理(wgpu Metal 后端直接包裹) | 软解→staging 上传 | IOSurface 共享 | +| OpenGL(遗留/调试用) | 不做硬解导入 | 软解→glTexSubImage2D | 仅调试后端 | -实施顺序:staging 上传先行(正确性),DMA-BUF/D3D11/IOSurface 零拷贝上传 -作为后续优化项逐个落地(每一项独立可测、可回退)。 +要点: -### 3.7 resolve 重写(M0,独立先行) +- 硬解表面通常是 YUV(NV12/P010),**导入后不是 RGB**——YUV→RGB(色度 + 上采样 + BT.601/709/2020 矩阵 + 全/窄范围)是渲染线程上的内置 GPU + pass(着色器),替代今天的 CPU swscale;HDR 元数据随纹理流转。 +- 导入失败(驱动缺 modifier、句柄类型不支持等)按帧回退到 + `av_hwframe_transfer_data` + staging 上传,单帧失败不污染后续帧。 +- 实施顺序:staging fallback 先行(正确性基线,任何平台都可跑),然后按 + Linux(NVDEC/VAAPI) → Windows(D3D11VA) → macOS(VideoToolbox) 的顺序 + 落地零拷贝导入,每行一个独立 PR、独立开关、可单独回退。 + +### 3.7 resolve 重写(M0a,独立先行) 对照 upstream `node/traverser.cpp:351 ResolveJobs`: @@ -231,30 +286,83 @@ 行为不变(本步是纯结构改造,现有测试全部应无修改通过;新增 CacheJob 的磁盘加载测试)。 +### 3.8 Job 图:虚拟输入/输出节点与广度优先求值(用户硬性要求) + +节点图的求值不再把"每个节点往一张线性表里 push 值、resolve 扫表找活干" +当作模型(jobs.rs:31-33 自述的临时形态、§2.4 的四次扫描即其症状),而是 +**Job 图即图**: + +1. **图存储是货真价实的邻接结构**:求值以 `oak_node::graph::Graph` 的 + 节点/连接为骨架,节点把 `Job` 枚举(§3.7-1 补全后的)挂在自己的输出 + 上;没有"全图拍平成表"的中间形态,也没有二维数组。允许**非全连通**: + 与输出无关的分支不参与成帧(下述活集规则)。 +2. **每个项目图固定一对虚拟节点**: + - `GraphInput`(虚拟输入节点):BFS 的唯一**起点**。它自身不产生 + 像素——它是"图的数据入口"语义(单帧单 clip 时其后挂素材/生成器; + 序列合成时由轨道合成结构向其馈入)。只有输出端口。 + - `GraphOutput`(虚拟输出节点):所有终末分支的**汇聚点**,它的值 + 就是这一帧。只有输入端口。 + - 两节点**默认相连、不可删除、不可复制**(图模型层强约束:新建图 + 自带这对节点;`remove_node`/`duplicate` 对它们拒绝;序列化把它们 + 作为图的固定端点写入/读出)。 + - **要在节点编辑器里显示出来**(`crates/oak-app/src/panels/node_editor.rs`): + 与普通节点同等的渲染与连线交互,但禁删、禁复制、禁改名;样式上 + 与真节点区分(固定标题/图标),连线规则校验(输入节点不接受入线、 + 输出节点不接受出线)。 +3. **resolve = 从输入节点出发的广度优先搜索**:每个项目/序列的求值以 + `GraphInput` 为根做 BFS,出队一个节点就用一次 `match`(§3.7-2 的 + 五个 process_*)处理它挂出的 Job,**直到全部分支汇聚于 `GraphOutput` + 为止**。遍历规则: + - **多输入汇合**:入度到零才出队(Kahn 形态的 BFS)——一个节点的 + 全部输入都 resolve 完毕它才进入处理队列;这保证汇合节点拿到的 + 每一路输入都是成品纹理。 + - **多输出分叉**:一个节点的输出可以喂多个后继,沿邻接边自然扇出; + 后继各自按自己的入度等待。 + - **顺序语义**:出队序即 Job 处理序,且对同一图是确定性的(邻接按 + 连接建立序迭代)——用户明确要求"节点被应用的先后顺序可能影响 + 最终画面",该顺序由图结构显式表达,而非由表扫描次序隐含。 + - **环**:visited 集防死循环;发现回边时报错并断开该分支(图模型 + 现有约束本就不鼓励环,此处把行为写成明文)。 + - **活集**:正向(从 GraphInput)可达 ∩ 反向(从 GraphOutput)可达 + 的节点才是活集;不可达节点从不入队(非全连通图的天然剪枝), + 正向可达但到不了输出的分支是可选的二次剪枝(先求正确,再求省)。 +4. **与 resolve 重写的合流**:§3.7 的单循环 match 是本 BFS 的"出队即 + 处理"循环体;M0 分两步走——先在现有线性表上完成枚举化与单循环 + (M0a,纯结构改造、独立可验收),再把表升级为图、引入虚拟端点与 + BFS(M0b,见 §4)。上游 Olive 没有虚拟端点概念(其遍历从 viewer + 输出节点倒推),这一对端点是 Oak 自己的设计,语义对齐用户的 + "从输入开始、汇聚于输出"。 + ## 4. 里程碑 | 里程碑 | 内容 | 验收 | |---|---|---| -| **M0 resolve 重写** | §3.7 全量:Job 枚举进表、单循环 match、CacheJob 落地 | 全 workspace 测试绿;新增 CacheJob 磁盘缓存往返测试;`resolve_*_jobs` 四函数删除 | +| **M0a Job 枚举化 + 单循环 resolve** | §3.7 全量:Job 枚举补全(含 CacheJob)并挂进输出表、resolve 单循环 match、子 job 递归 resolve | 全 workspace 测试绿;新增 CacheJob 磁盘缓存往返测试;`resolve_*_jobs` 四函数删除 | +| **M0b Job 图 + 虚拟端点 + BFS** | §3.8 全量:图固定 GraphInput/GraphOutput 虚拟节点(默认相连、禁删禁复制、序列化往返)、节点编辑器显示两节点、resolve 改为从输入节点的 Kahn 形态 BFS | 新增测试:多输入汇合等齐全部输入、多输出分叉各自成帧、非全连通图不可达节点不执行、环报错断支、虚拟节点删除/复制被拒、序列化往返后端点仍在;节点编辑器 UI 测试(端点可见、入线/出线规则);既有测试全绿 | | **M1 线程管线骨架** | 解码/渲染/上屏三线程+三队列进 oak-render(`pipeline` 模块);RenderManager 增加线程后端,进程池后端保留,`OAK_PIPELINE=processes` 可回退 | 同一套渲染测试在两个后端下都绿(测试矩阵化);播放/seek/导出 smoke 等价 | -| **M2 GPU 零拷贝** | 图内全程 `Texture::Gpu`;上屏互操作攻关(§3.5)落地;导出/缓存/OFX 三处边界显式回读 | 播放路径 GPU↔CPU 搬运次数为 0(计数断言,参照 M15 S2 的 `main_heap_frame_copies` 范式);`to_display` 不再接收 CPU 帧 | +| **M2 GPU 零拷贝** | 图内全程 `Texture::Gpu`;上屏互操作攻关(§3.5)落地;导出/缓存/OFX 三处边界显式回读;内置 YUV→RGB GPU pass 替代 CPU swscale | 播放路径 GPU↔CPU 搬运次数为 0(计数断言,参照 M15 S2 的 `main_heap_frame_copies` 范式);`to_display` 不再接收 CPU 帧 | | **M3 OFX 独立进程** | oak-ofx-host 单进程宿主;PluginJob 经 IPC;崩溃重生+紫帧回退;进度/取消协议搬运 | 杀掉 ofx-host 进程 → 在途 job 重投成功;连续三次崩溃 → 紫帧;进度条/取消行为与现状一致 | | **M4 流水线预取** | 调度层按 §3.4 投依赖窗口;背压策略 | 1080p 播放 CPU 占用不升、fps 不低于进程池后端;首帧延迟不劣化(基准对比留档) | -| **M5 平台互操作** | §3.6 表逐行落地(每行一个独立 PR) | 每平台 CI 绿;零拷贝上传路径有开关可回退 staging | +| **M5 GPU 解码零拷贝** | §3.6 表逐行落地:staging fallback 基线 → Linux NVDEC/VAAPI 导入 → Windows D3D11VA 导入 → macOS VideoToolbox 导入;FFmpeg 无 hwaccel 的组合才评估手写 GPU 解码 | 硬解路径 `HW_TRANSFERS` 计数归零(不再下载);逐平台导入开/关对比测试;每行独立 PR 可回退 | -依赖关系:M0 独立;M1 依赖 M0;M2 依赖 M1;M3 依赖 M1(与 M2 可并行); -M4 依赖 M2;M5 依赖 M2,可拆成并行子项。 +依赖关系:M0a 独立;M0b 依赖 M0a;M1 依赖 M0a+M0b;M2 依赖 M1;M3 依赖 +M1(与 M2 可并行);M4 依赖 M2;M5 依赖 M2(YUV→RGB pass 与互操作抽象), +可拆成并行子项。 ## 5. 不变量与边界 -- **不动**:ticket API(oak-app/oak-cli 无感)、撤销/重做、图模型与序列化、 - 缓存磁盘格式、OFX 插件 ABI、CI 的 OFX fixture 探针(ci.yml 的 - scan_probe 流程)。 +- **不动**:ticket API(oak-app/oak-cli 无感)、撤销/重做、缓存磁盘格式、 + OFX 插件 ABI、CI 的 OFX fixture 探针(ci.yml 的 + scan_probe 流程)。**例外(M0b 明确改变的两处)**:图模型新增 + GraphInput/GraphOutput 固定端点,序列化格式随之携带这对端点(旧工程 + 载入时自动补挂,等价于一次无损迁移,配套序列化往返测试)。 - **回退开关**:线程后端落地期间进程池后端完整保留,`OAK_PIPELINE` 环境 - 变量 + 配置项双开关;M4 验收通过前进程池仍是默认。 + 变量 + 配置项双开关;M4 验收通过前进程池仍是默认。硬解导入逐平台 + 独立开关(§3.6),`OAK_HWACCEL=0` 一键回到全软解。 - **线程亲和**:wgpu device 可在专用线程独占使用(Device/Queue 均 Send); - 渲染线程是唯一触摸 `GpuContext` 的线程,解码线程只做"CPU 帧→staging" - 的上传请求(经渲染线程代执行或经 device 的线程安全提交,M1 攻关确定)。 + 渲染线程是唯一触摸 `GpuContext` 的线程;硬解帧池与硬件设备上下文 + (AVHWDeviceContext)归解码线程所有,硬件表面经互操作导入后才交给 + 渲染线程消费(§3.4)。 - **音频**:本计划只覆盖视频管线;音频采样链(SampleJob 对应路径)维持 现状,如需统一另立计划。 @@ -271,5 +379,17 @@ M4 依赖 M2;M5 依赖 M2,可拆成并行子项。 4. **测试环境无 GPU**:CI 的 lavapipe/xvfb 路径已在跑 wgpu(ci.yml 的 Test 步骤),线程后端必须在该环境下同样可用;`GpuContext::create` 的 CPU 回退(backend.rs:1422 测试所示)保持可用。 -5. **范围蔓延**:Job 图的 BFS 化(v0.6 议题,用户已叫停过一次)**不在** - 本计划内;M0 只做单循环 match 分发,不改图求值顺序语义。 +5. **BFS 求值的兼容性**(M0b):虚拟端点改变了图模型与求值顺序的显性 + 语义,是全部里程碑里对既有行为扰动最大的一步。对策:M0a 先把 + 枚举化与单循环做掉(行为不变、纯结构),M0b 单独成 PR、配全套 + §4 列举的行为测试;旧工程载入自动补挂端点。 +6. **GPU 解码零拷贝的平台碎片化**(M5):各平台导入路径(DMA-BUF + modifier 协商、DXGI NT 句柄、IOSurface、CUDA-Vulkan 互操作)都有 + 驱动/版本坑,且 wgpu 对外部内存导入的公开 API 覆盖有限,可能要 + 落到 wgpu-hal 原生句柄层写 unsafe 胶水。对策:staging fallback 是 + 永久基线(任何导入失败按帧回退);按 §3.6 顺序逐平台落地,每平台 + 独立开关;CUDA-Vulkan 互操作作为 NVDEC 路径的最后手段(先试 + 更通用的 DMA-BUF——Linux 上 NVIDIA 也经 VAAPI 可达时优先 VAAPI)。 +7. **范围蔓延**:手写 GPU 解码严格限于"FFmpeg 没有对应 hwaccel"的 + 组合(用户原话"次选"),不提前立项;Job 图的序列化格式版本化 + 不在本期(沿用工程文件现有版本策略)。