docs: update README and remove obsolete documentation/files
- Update CI badge URL to OakVideoEditorCommunity in README. - Remove outdated TODO files, vcpkg.json, patch file and stale log. - Clean up obsolete docs and move ofx-pluginrenderer-functions-zh.md into docs/zh/.
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
From 6b0ef44eb189411d36c739ccde8a081a3e62034a Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mike Solar <iam@mikesolar.com>
|
|
||||||
Date: Mon, 24 Nov 2025 20:57:12 +0800
|
|
||||||
Subject: [PATCH] Fix include path for Window_p.h in qtcommon directory
|
|
||||||
|
|
||||||
---
|
|
||||||
src/qtcommon/Window_p.h | 2 +-
|
|
||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/src/qtcommon/Window_p.h b/src/qtcommon/Window_p.h
|
|
||||||
index f29b209a5..d1d6b47ad 100644
|
|
||||||
--- a/src/qtcommon/Window_p.h
|
|
||||||
+++ b/src/qtcommon/Window_p.h
|
|
||||||
@@ -11,7 +11,7 @@
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
-#include "core/Window_p.h"
|
|
||||||
+#include "../core/Window_p.h"
|
|
||||||
#include "Screen_p.h"
|
|
||||||
|
|
||||||
#include <QPointer>
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
# Oak Video Editor[](https://github.com/olive-editor/olive/actions?query=branch%3Amaster)
|
# Oak Video Editor[](https://github.com/OakVideoEditorCommunity/oak/actions?query=branch%3Amaster)
|
||||||
[中文](docs/zh/README.md)
|
[中文](docs/zh/README.md)
|
||||||
|
|
||||||
Oak Video Editor is a free non-linear video editor for Windows, macOS, and Linux.
|
Oak Video Editor is a free non-linear video editor for Windows, macOS, and Linux.
|
||||||
|
|
||||||
Unfortunately, the original author has not submitted code updates for over 7 months, and no public contact information (email or otherwise) is available to reach them directly.
|
|
||||||
|
|
||||||
This project is a community-maintained fork of Olive Video Editor.
|
This project is a community-maintained fork of Olive Video Editor.
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
-90
@@ -1,90 +0,0 @@
|
|||||||
# TODO
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
- 实现 2–3 秒预渲染的 LRU 缓存和代理剪辑功能,并以“小步快跑”的方式在现有架构中逐步落地,确保每一步都可编译。
|
|
||||||
|
|
||||||
## 现有架构中的落点
|
|
||||||
- 播放/渲染调度:`app/render/renderprocessor.cpp`, `app/render/plugin/pluginrenderer.cpp`, `app/node/traverser.cpp`
|
|
||||||
- 插件节点输入/默认值:`app/node/plugins/Plugin.cpp`
|
|
||||||
- Clip 图像/纹理获取:`app/pluginSupport/OliveClip.cpp`, `app/pluginSupport/OliveClip.h`
|
|
||||||
- 节点与值系统:`app/node/node.h`, `app/node/node.cpp`, `app/node/value.h`
|
|
||||||
- 工程序列化:`app/node/project/serializer/*`
|
|
||||||
|
|
||||||
## LRU 缓存计划(代码改动 + 集成点)
|
|
||||||
|
|
||||||
### 步骤 1(可编译):新增缓存类型但不接入逻辑
|
|
||||||
- 新增缓存模块,例如 `app/render/cache/framecache.h/.cpp`。
|
|
||||||
- 定义:
|
|
||||||
- `FrameCacheKey`(图哈希/版本、时间、参数、代理模式、渲染缩放)。
|
|
||||||
- `FrameCacheEntry`(AVFrame 或 Texture + 元信息 + 字节数 + 最近访问时间)。
|
|
||||||
- `FrameCache` API:`get(key)`、`put(key, entry)`、`invalidateByVersion(version)`。
|
|
||||||
- 先只编译通过,不改变行为。
|
|
||||||
|
|
||||||
### 步骤 2(可编译):图版本号/失效机制
|
|
||||||
- 在 `Node` 或渲染入口维护图版本号。
|
|
||||||
- 当参数变化、连线变化时递增。
|
|
||||||
- 渲染侧可读取版本号用于缓存失效。
|
|
||||||
|
|
||||||
### 步骤 3(小行为):仅缓存当前帧
|
|
||||||
- 在 `renderprocessor.cpp` 播放路径上:
|
|
||||||
- 先查缓存,命中则直接显示。
|
|
||||||
- 未命中则正常渲染,并写入缓存。
|
|
||||||
- 缓存预算先设很小,风险低。
|
|
||||||
|
|
||||||
### 步骤 4(小行为):预渲染窗口
|
|
||||||
- 增加队列,渲染 [now, now+N],N=2–3 秒。
|
|
||||||
- 并发限制(例如 2–3 个任务),避免抢 UI。
|
|
||||||
- 优先级:当前帧 > 近未来。
|
|
||||||
- Seek 时取消/丢弃过期任务。
|
|
||||||
|
|
||||||
### 步骤 5(行为):LRU 淘汰
|
|
||||||
- 按内存预算/帧数上限淘汰最久未使用。
|
|
||||||
|
|
||||||
### 步骤 6(行为):CPU/GPU 策略
|
|
||||||
- 默认缓存 CPU 帧,播放时再上传 GPU。
|
|
||||||
- GPU 缓存可作为后续优化开关。
|
|
||||||
|
|
||||||
### 步骤 7(可观测性)
|
|
||||||
- 统计命中率、平均渲染耗时、掉帧。
|
|
||||||
- Debug 构建下输出日志。
|
|
||||||
|
|
||||||
## 代理剪辑计划(代码改动 + 集成点)
|
|
||||||
|
|
||||||
### 步骤 1(可编译):数据模型与序列化
|
|
||||||
- 在 clip 元数据里增加:
|
|
||||||
- `proxy_path`、`proxy_width`、`proxy_height`、`proxy_codec`、`proxy_fps`。
|
|
||||||
- 在 `app/node/project/serializer/*` 写入/读取。
|
|
||||||
|
|
||||||
### 步骤 2(小行为):代理选择策略
|
|
||||||
- 增加全局/每 clip 的代理模式:
|
|
||||||
- `Auto`、`ForceProxy`、`ForceOriginal`。
|
|
||||||
- 在媒体解析层根据模式决定用原片还是代理。
|
|
||||||
|
|
||||||
### 步骤 3(行为):代理生成
|
|
||||||
- 新增后台转码任务(复用现有渲染/导出流程)。
|
|
||||||
- 生成完成后更新元数据。
|
|
||||||
|
|
||||||
### 步骤 4(行为):UI 接入
|
|
||||||
- 增加“生成代理”“重链接代理”入口。
|
|
||||||
- 在剪辑或预览上显示代理标识。
|
|
||||||
|
|
||||||
### 步骤 5(验证)
|
|
||||||
- 对比代理与原片的时间精度、音画同步。
|
|
||||||
- 导出默认使用原片。
|
|
||||||
|
|
||||||
## 小步快跑执行顺序(每步可编译)
|
|
||||||
1) 新增缓存模块/类型(不接入)。
|
|
||||||
2) 增加图版本号与失效接口。
|
|
||||||
3) 播放路径只缓存当前帧。
|
|
||||||
4) 预渲染 2–3 秒窗口 + 并发限制。
|
|
||||||
5) LRU 淘汰策略。
|
|
||||||
6) 图版本变更触发失效。
|
|
||||||
7) 统计与日志。
|
|
||||||
8) 代理元数据字段 + 序列化。
|
|
||||||
9) 代理选择策略(Auto/Force)。
|
|
||||||
10) 代理生成任务 + UI 入口。
|
|
||||||
|
|
||||||
## 待确认问题
|
|
||||||
- 缓存预算默认值(按硬件分级)。
|
|
||||||
- 代理文件默认存储路径。
|
|
||||||
- 是否做 GPU 纹理缓存。
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# TODO
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
- Add an LRU prerender cache (2–3 seconds ahead) and proxy clip support, implemented as incremental, compile-safe steps within the current architecture.
|
|
||||||
|
|
||||||
## Where the Changes Live (Current Architecture)
|
|
||||||
- Playback/render scheduling: `app/render/renderprocessor.cpp`, `app/render/plugin/pluginrenderer.cpp`, `app/node/traverser.cpp`
|
|
||||||
- Plugin node inputs/defaults: `app/node/plugins/Plugin.cpp`
|
|
||||||
- Clip image/texture fetch: `app/pluginSupport/OliveClip.cpp`, `app/pluginSupport/OliveClip.h`
|
|
||||||
- Node graph and values: `app/node/node.h`, `app/node/node.cpp`, `app/node/value.h`
|
|
||||||
- Project/serialization: `app/node/project/serializer/*`
|
|
||||||
|
|
||||||
## LRU Cache Plan (Code Changes + Integration Points)
|
|
||||||
|
|
||||||
### Step 1 (compile-safe): Introduce cache data types (no behavior yet)
|
|
||||||
- Add a small cache module, e.g. `app/render/cache/framecache.h/.cpp`.
|
|
||||||
- Define:
|
|
||||||
- `FrameCacheKey` (graph version/hash, time, params, proxy mode, render scale).
|
|
||||||
- `FrameCacheEntry` (AVFrame or Texture + metadata + byte size + last-used).
|
|
||||||
- `FrameCache` API: `get(key)`, `put(key, entry)`, `invalidateByVersion(version)`.
|
|
||||||
- Wire in a compile-only stub with no runtime usage.
|
|
||||||
|
|
||||||
### Step 2 (compile-safe): Define graph/version invalidation hook
|
|
||||||
- Add a lightweight “graph version” counter to `Node` or a render pipeline owner.
|
|
||||||
- Increment on param changes and graph edits.
|
|
||||||
- Expose a read-only version getter for the render pipeline.
|
|
||||||
|
|
||||||
### Step 3 (small behavior): Cache current frame only
|
|
||||||
- In `renderprocessor.cpp` playback path, check cache before rendering:
|
|
||||||
- If hit, present cached frame.
|
|
||||||
- If miss, render normally and `put` into cache.
|
|
||||||
- Keep budget small (few frames) to minimize risk.
|
|
||||||
|
|
||||||
### Step 4 (small behavior): Pre-render window scheduling
|
|
||||||
- Add a render queue for time range [now, now+N] (N = 2–3s).
|
|
||||||
- Limit worker count (e.g., 2–3 tasks) to avoid UI starvation.
|
|
||||||
- Prioritize current frame > near future.
|
|
||||||
- On seek, cancel or drop stale tasks.
|
|
||||||
|
|
||||||
### Step 5 (behavior): LRU eviction policy
|
|
||||||
- Enforce memory budget and frame count cap.
|
|
||||||
- Evict least-recently-used entries.
|
|
||||||
|
|
||||||
### Step 6 (behavior): GPU/CPU policy
|
|
||||||
- Cache CPU frames by default for safety.
|
|
||||||
- For GL outputs, upload from cached CPU frame when displayed.
|
|
||||||
- Optionally add GPU caching later behind a feature flag.
|
|
||||||
|
|
||||||
### Step 7 (observability)
|
|
||||||
- Add counters for hit rate, average render time, and drops.
|
|
||||||
- Log only in debug builds.
|
|
||||||
|
|
||||||
## Proxy Clip Plan (Code Changes + Integration Points)
|
|
||||||
|
|
||||||
### Step 1 (compile-safe): Data model + serialization
|
|
||||||
- Extend clip metadata with:
|
|
||||||
- `proxy_path`, `proxy_width`, `proxy_height`, `proxy_codec`, `proxy_fps`.
|
|
||||||
- Add read/write in `app/node/project/serializer/*`.
|
|
||||||
|
|
||||||
### Step 2 (small behavior): Proxy selection policy
|
|
||||||
- Add project-level and clip-level proxy mode:
|
|
||||||
- `Auto`, `ForceProxy`, `ForceOriginal`.
|
|
||||||
- Add a simple resolver in clip/media source code that picks proxy if enabled.
|
|
||||||
|
|
||||||
### Step 3 (behavior): Proxy generation pipeline
|
|
||||||
- Add a background task to build proxies (using existing render/export tasks).
|
|
||||||
- Store output path and metadata on success.
|
|
||||||
|
|
||||||
### Step 4 (behavior): UI wiring
|
|
||||||
- Add “Generate Proxy” action + proxy indicator.
|
|
||||||
- Add “Relink Proxy” dialog.
|
|
||||||
|
|
||||||
### Step 5 (validation)
|
|
||||||
- Compare proxy vs original for timing and sync.
|
|
||||||
- Ensure proxies are ignored for export unless explicitly enabled.
|
|
||||||
|
|
||||||
## Small-Step Implementation Plan (Each Step Builds)
|
|
||||||
1) Add cache module + types (no references).
|
|
||||||
2) Add graph version counter (increment on changes).
|
|
||||||
3) Wire cache lookup for current frame only.
|
|
||||||
4) Add prerender queue (2–3 seconds) with limited concurrency.
|
|
||||||
5) Add LRU eviction + memory budget.
|
|
||||||
6) Add cache invalidation on graph version change.
|
|
||||||
7) Add basic metrics/logging.
|
|
||||||
8) Add proxy metadata fields + serialization.
|
|
||||||
9) Add proxy selection policy (Auto/Force modes).
|
|
||||||
10) Add proxy generation task + UI entry points.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
- Default cache size per hardware tier.
|
|
||||||
- Where to store proxy files on disk.
|
|
||||||
- Whether to cache GPU textures or CPU frames only.
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { defineUserConfig } from "vuepress";
|
|
||||||
import { hopeTheme } from "vuepress-theme-hope";
|
|
||||||
|
|
||||||
export default defineUserConfig({
|
|
||||||
base: process.env.BASE || "/",
|
|
||||||
locales: {
|
|
||||||
"/": {
|
|
||||||
lang: "en-US",
|
|
||||||
title: "Oak Video Editor",
|
|
||||||
description:
|
|
||||||
"Open-source, non-linear video editor focused on speed and clarity.",
|
|
||||||
},
|
|
||||||
"/zh/": {
|
|
||||||
lang: "zh-CN",
|
|
||||||
title: "Oak 视频编辑器",
|
|
||||||
description: "面向创作者的开源非线性剪辑软件。",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
theme: hopeTheme({
|
|
||||||
logo: "/images/oak-icon.png",
|
|
||||||
locales: {
|
|
||||||
"/": {
|
|
||||||
selectLanguageName: "English",
|
|
||||||
navbar: [
|
|
||||||
{ text: "Home", link: "/" },
|
|
||||||
{ text: "Build", link: "/build.html" },
|
|
||||||
{ text: "Project Files", link: "/project-file-reference.html" },
|
|
||||||
{ text: "Test Plan", link: "/test-plan.html" },
|
|
||||||
],
|
|
||||||
sidebar: [
|
|
||||||
{
|
|
||||||
text: "Documentation",
|
|
||||||
children: [
|
|
||||||
"/build.md",
|
|
||||||
"/project-file-reference.md",
|
|
||||||
"/test-plan.md",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"/zh/": {
|
|
||||||
selectLanguageName: "简体中文",
|
|
||||||
navbar: [
|
|
||||||
{ text: "首页", link: "/zh/" },
|
|
||||||
{ text: "构建", link: "/zh/build.html" },
|
|
||||||
{ text: "工程文件", link: "/zh/project-file-reference.html" },
|
|
||||||
{ text: "测试计划", link: "/zh/test-plan.html" },
|
|
||||||
],
|
|
||||||
sidebar: [
|
|
||||||
{
|
|
||||||
text: "文档",
|
|
||||||
children: [
|
|
||||||
"/zh/build.md",
|
|
||||||
"/zh/project-file-reference.md",
|
|
||||||
"/zh/test-plan.md",
|
|
||||||
"/zh/structure.md",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 KiB |
@@ -1,93 +0,0 @@
|
|||||||
# Oak Video Editor Testing Strategy and Plan
|
|
||||||
|
|
||||||
This document describes the automated testing strategy for Oak Video Editor, including unit tests, integration tests, and CI execution.
|
|
||||||
|
|
||||||
## Goals
|
|
||||||
|
|
||||||
- Maximize automation and reduce manual testing.
|
|
||||||
- Cover all modules with at least one automated test.
|
|
||||||
- Keep integration tests headless (no GUI interaction).
|
|
||||||
- Make failures reproducible on Windows/macOS/Linux CI.
|
|
||||||
|
|
||||||
## Test Layers
|
|
||||||
|
|
||||||
### 1) Unit Tests (GoogleTest)
|
|
||||||
- Focus: small units, deterministic behavior, no GUI.
|
|
||||||
- Location: `tests/gtest/`.
|
|
||||||
- Execution: `ctest` target `olive-gtest`.
|
|
||||||
|
|
||||||
### 1.5) Module Smoke Tests (GoogleTest)
|
|
||||||
- Focus: compile-time and link-time coverage for GUI-heavy modules without instantiating widgets.
|
|
||||||
- Location: `tests/gtest/module_smoke_test.cpp`.
|
|
||||||
- Execution: `ctest` target `olive-gtest`.
|
|
||||||
|
|
||||||
### 2) Integration Tests (GoogleTest)
|
|
||||||
- Focus: cross-module flows without GUI (e.g., serialize → deserialize → resolve).
|
|
||||||
- Location: `tests/gtest/` (prefixed with `ProjectSerializer`, `TaskManager`, etc.).
|
|
||||||
|
|
||||||
### 3) Legacy Tests (Olive macro tests)
|
|
||||||
- Existing tests in `tests/general`, `tests/timeline`, `tests/compositing` remain.
|
|
||||||
|
|
||||||
## Module Coverage Map
|
|
||||||
|
|
||||||
Each top-level module has at least one test that exercises its core API or serialization path.
|
|
||||||
|
|
||||||
- `app/common`: `common_current_test.cpp`, `common_xmlutils_test.cpp`
|
|
||||||
- `app/config`: `config_test.cpp`
|
|
||||||
- `app/node`: `node_value_test.cpp`, `node_keyframe_test.cpp`, `node_serialization_test.cpp`
|
|
||||||
- `app/node/project/serializer`: `project_serializer_test.cpp`
|
|
||||||
- `app/render`: `render_videoparams_test.cpp`, `render_audioparams_test.cpp`
|
|
||||||
- `app/timeline`: `timeline_marker_test.cpp`
|
|
||||||
- `app/undo`: `undo_stack_test.cpp`
|
|
||||||
- `app/task`: `task_taskmanager_test.cpp`
|
|
||||||
- `app/codec`: `codec_frame_test.cpp`
|
|
||||||
- `app/pluginSupport`: `plugin_support_test.cpp`
|
|
||||||
- `app/audio`, `app/cli`, `app/dialog`, `app/panel`, `app/tool`, `app/ui`, `app/widget`, `app/window`: `module_smoke_test.cpp`
|
|
||||||
|
|
||||||
If a module has a GUI dependency (e.g., widgets), tests focus on non-visual data/model components.
|
|
||||||
|
|
||||||
## Integration Test Details
|
|
||||||
|
|
||||||
### Project Serializer Roundtrip
|
|
||||||
- Creates a minimal project with a built-in node.
|
|
||||||
- Saves to XML via `ProjectSerializer::Save`.
|
|
||||||
- Loads with `ProjectSerializer::Load`.
|
|
||||||
- Verifies that nodes are restored.
|
|
||||||
|
|
||||||
### Task Manager Execution
|
|
||||||
- Adds a dummy task to `TaskManager`.
|
|
||||||
- Waits for completion using an event loop.
|
|
||||||
- Verifies the task ran.
|
|
||||||
|
|
||||||
## Unit Coverage Highlights (Expanded)
|
|
||||||
|
|
||||||
- `app/undo`: `undo_stack_test.cpp` now covers empty stack state, model data, redo list coloring, jump behavior, and ignored empty multi-commands.
|
|
||||||
- `app/timeline`: `timeline_marker_test.cpp` now covers list ordering, closest-marker lookup, list save/load with unknown elements, and marker add/remove/change commands.
|
|
||||||
- `app/pluginSupport`: `plugin_support_image_test.cpp` now checks OFX property wiring (bounds/ROD, pixel depth, components, premult) and allocation clearing behavior.
|
|
||||||
- `app/render`: `render_videoparams_branch_test.cpp` now covers auto divider selection, pixel aspect validation, square-pixel width, and Save/Load roundtrip.
|
|
||||||
|
|
||||||
## Headless Execution
|
|
||||||
|
|
||||||
- Tests avoid QWidget usage.
|
|
||||||
- CI sets `QT_QPA_PLATFORM=offscreen` to prevent GUI initialization issues.
|
|
||||||
|
|
||||||
## Continuous Integration
|
|
||||||
|
|
||||||
CI runs on Windows, macOS, and Linux:
|
|
||||||
|
|
||||||
1. Install system dependencies (Qt, FFmpeg, OpenImageIO, OpenColorIO, OpenEXR, PortAudio, Expat).
|
|
||||||
2. Configure with `-DBUILD_TESTS=ON`.
|
|
||||||
3. Build with CMake + Ninja.
|
|
||||||
4. Run `ctest` with output on failure.
|
|
||||||
|
|
||||||
### Dependency Installation Notes
|
|
||||||
- Linux: use distro packages (`apt` on Ubuntu) for Qt6, FFmpeg, OpenImageIO, OpenColorIO, OpenEXR, PortAudio, Expat, OpenGL headers.
|
|
||||||
- macOS: use Homebrew for Qt6 and media/color/image libraries.
|
|
||||||
- Windows: use system installers where available (Qt via `install-qt-action`), and vcpkg for the remaining C/C++ libraries.
|
|
||||||
|
|
||||||
## Adding New Tests
|
|
||||||
|
|
||||||
- Place new unit tests in `tests/gtest`.
|
|
||||||
- Use GoogleTest conventions.
|
|
||||||
- Prefer deterministic fixtures and local-only resources.
|
|
||||||
- When adding a new module, add at least one unit test and one integration scenario if applicable.
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# v0.4 调色与 LUT 实施计划
|
|
||||||
|
|
||||||
本文档对应 `docs/zh/README.md` 路线图中 v0.4「调色与 LUT」里程碑。
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
- 支持 `.cube` 与 `.3dl` LUT 文件作为可用调色入口。
|
|
||||||
- 完成示波器面板的波形、矢量、直方图三类视图。
|
|
||||||
- 提供三向色轮面板,面向阴影、中间调、高光做基础调色控制。
|
|
||||||
- 尽量复用现有 OpenColorIO、节点系统、Viewer/Scope 面板和 GPU 渲染管线,不引入独立的调色框架。
|
|
||||||
|
|
||||||
## 当前状态
|
|
||||||
|
|
||||||
- 已有 OpenColorIO 基础能力:颜色管理、显示变换、OCIO 调色节点和渲染侧配置。
|
|
||||||
- Scope 面板已提供波形、矢量、直方图三类视图。
|
|
||||||
- 已有色轮基础控件;当前三向调色先通过节点参数面板暴露 Shadows、Midtones、Highlights 三组颜色与强度参数。
|
|
||||||
- LUT 节点已接入节点工厂,并已增加 `.cube`/`.3dl` 相关测试。
|
|
||||||
|
|
||||||
## 阶段 1:LUT 节点入口
|
|
||||||
|
|
||||||
状态:已完成首版。
|
|
||||||
|
|
||||||
交付内容:
|
|
||||||
|
|
||||||
- 新增 OCIO LUT 节点,使用 OpenColorIO `FileTransform` 读取外部 LUT 文件。
|
|
||||||
- 明确支持 `.cube` 与 `.3dl` 扩展名,并拒绝未知格式。
|
|
||||||
- 在节点工厂中注册 LUT 节点,保证工程加载和节点创建路径一致。
|
|
||||||
- 增加 gtest 覆盖 LUT 扩展名支持和简单 LUT 转换结果。
|
|
||||||
|
|
||||||
验收标准:
|
|
||||||
|
|
||||||
- `olive-gtest` 中 LUT 相关测试通过。
|
|
||||||
- `olive-editor` 和 `olive-render-worker` 可正常构建。
|
|
||||||
- LUT 文件缺失、格式不支持、OCIO 处理器创建失败时不会导致崩溃。
|
|
||||||
|
|
||||||
## 阶段 2:示波器补齐
|
|
||||||
|
|
||||||
状态:已完成首版。
|
|
||||||
|
|
||||||
交付内容:
|
|
||||||
|
|
||||||
- 保留现有波形和直方图视图。
|
|
||||||
- 新增矢量示波器视图,并接入 Scope 面板下拉选择。
|
|
||||||
- 矢量示波器应使用当前 Viewer 帧,并经过现有显示/颜色管理路径。
|
|
||||||
- 为新增 shader 或资源入口增加资源存在性测试。
|
|
||||||
|
|
||||||
验收标准:
|
|
||||||
|
|
||||||
- Scope 面板可在 Waveform、Vectorscope、Histogram 间切换。
|
|
||||||
- 无当前帧时视图保持空白或安全占位,不崩溃。
|
|
||||||
- shader 资源测试和编辑器构建通过。
|
|
||||||
|
|
||||||
## 阶段 3:三向色轮面板
|
|
||||||
|
|
||||||
状态:已完成节点参数面板首版;独立三向色轮 dock 面板作为后续体验增强。
|
|
||||||
|
|
||||||
交付内容:
|
|
||||||
|
|
||||||
- 基于现有参数面板提供 Shadows、Midtones、Highlights 三组控制。
|
|
||||||
- 为每组控制提供色彩偏移和强度/亮度相关参数。
|
|
||||||
- 将三向色轮参数映射到现有 OCIO 调色节点,或新增可序列化节点承载参数。
|
|
||||||
- 保证参数能随工程保存、加载、撤销和重做。
|
|
||||||
|
|
||||||
验收标准:
|
|
||||||
|
|
||||||
- 用户可以在 UI 中操作三向调色参数并看到 Viewer 结果变化。
|
|
||||||
- 参数在工程文件中可序列化并可恢复。
|
|
||||||
- 节点参数变更不破坏现有 OCIO 调色节点兼容性。
|
|
||||||
|
|
||||||
## 阶段 4:集成与体验
|
|
||||||
|
|
||||||
状态:当前范围已完成;独立三向色轮 dock 面板和更细的交互体验作为后续增强。
|
|
||||||
|
|
||||||
交付内容:
|
|
||||||
|
|
||||||
- 为 LUT 节点补齐清晰的文件选择过滤器和用户可见名称。已完成。
|
|
||||||
- 在调色相关 UI 中保持命名一致:LUT、Waveform、Vectorscope、Histogram、Shadows、Midtones、Highlights。已完成首版。
|
|
||||||
- 更新中文文档,说明 LUT、示波器和三向调色的当前入口。已在本文档记录。
|
|
||||||
|
|
||||||
验收标准:
|
|
||||||
|
|
||||||
- 用户能从现有节点/UI 路径发现 LUT 和调色功能。
|
|
||||||
- 文档与实际 UI 命名一致。
|
|
||||||
- LUT 文件选择器限制为 `.cube` 与 `.3dl`,并保留 All Files 兜底。
|
|
||||||
- 不引入和现有翻译系统冲突的硬编码字符串。
|
|
||||||
|
|
||||||
## 阶段 5:验证
|
|
||||||
|
|
||||||
状态:自动化构建和核心测试已通过;手动 Viewer/Scope 观感检查将在真实项目中继续验证。
|
|
||||||
|
|
||||||
构建命令:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ninja -C cmake-build-debug olive-gtest olive-editor olive-render-worker -j2
|
|
||||||
```
|
|
||||||
|
|
||||||
测试命令:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
QT_QPA_PLATFORM=offscreen cmake-build-debug/tests/gtest/olive-gtest --gtest_filter='ColorLut.*:ColorV04.*:Shaders.*:NodeSerialization.*:NodeValue.*' --gtest_brief=1
|
|
||||||
```
|
|
||||||
|
|
||||||
手动检查:
|
|
||||||
|
|
||||||
- 打开工程并加载一段素材。
|
|
||||||
- 在 Scope 面板分别切换 Waveform、Vectorscope、Histogram。
|
|
||||||
- 添加 LUT 节点并选择 `.cube` 或 `.3dl` 文件。
|
|
||||||
- 调整三向色轮参数,确认 Viewer 输出和工程保存/加载行为。
|
|
||||||
|
|
||||||
## 风险与待定点
|
|
||||||
|
|
||||||
- 三向色轮应优先映射到 OCIO 现有调色能力;如果现有节点表达能力不足,再新增独立节点。
|
|
||||||
- 矢量示波器 shader 需要兼容当前 OpenGL 版本和已有渲染抽象,避免只在单一驱动上可用。
|
|
||||||
- LUT 文件路径序列化需要尊重现有工程文件路径策略,避免绝对路径导致工程不可迁移。
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# 代理媒体 v0.4 实施计划
|
|
||||||
|
|
||||||
## 背景
|
|
||||||
|
|
||||||
v0.4 已合并“调色、音频与性能”范围,其中代理媒体工作流负责解决 4K/8K 素材在时间线预览、剪辑和调色时的可用性问题。当前代码里已有音频 conform:`ConformManager` 会把音频流转为 PCM cache,但它不适合作为视频代理的直接扩展,因为视频代理需要保留容器、视频编码参数、文件生命周期和解码路由。
|
|
||||||
|
|
||||||
## 当前状态
|
|
||||||
|
|
||||||
实施进度:
|
|
||||||
|
|
||||||
- 阶段 1 已完成:已写计划,新增代理状态、稳定文件名函数、`Footage` 代理字段和 XML roundtrip 测试。
|
|
||||||
- 阶段 2 已完成:已新增 `ProxyTask` 和 `ProxyManager`,使用 `.working` 临时文件、成功 rename、失败清理,并覆盖状态测试。
|
|
||||||
- 阶段 3 已完成:`FootageJob` 携带代理解码信息,预览路径使用 ready 代理,导出/online 路径默认原片,代理缺失自动回退。
|
|
||||||
- 阶段 4 已完成:时间线右键已有 `Generate Proxy`、`Use Proxy`、`Reveal Proxy`、`Delete Proxy`。
|
|
||||||
- 阶段 5 自动验证已完成;仍需实际 4K/8K 素材做手工播放、重开项目和导出确认。
|
|
||||||
|
|
||||||
- `app/codec/conformmanager.{h,cpp}` 只处理音频 PCM conform,输出按声道拆分的 `.pcm` 文件。
|
|
||||||
- `app/task/conform/conform.{h,cpp}` 只调用 `Decoder::ConformAudio()`。
|
|
||||||
- `Decoder::CodecStream` 当前只包含原始 `filename + stream index + block`,解码时会检查该文件存在。
|
|
||||||
- `RenderProcessor::ProcessVideoFootage()` 和 `ProcessAudioFootage()` 通过 `FootageJob` 的 filename/decoder/stream index 打开素材。
|
|
||||||
- `Footage` 当前保存原始文件名、探测参数、source start time 等项目级元数据,但还没有代理文件状态。
|
|
||||||
- timeline 已有 cache/thumbnail/waveform 机制,但这是渲染缓存,不是替代源媒体的代理媒体。
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
第一阶段交付一个最小但完整的代理工作流:
|
|
||||||
|
|
||||||
- 右键选中项目素材或时间线 clip 可生成代理。
|
|
||||||
- 代理文件写入项目 cache/proxy 目录,使用稳定 hash 命名。
|
|
||||||
- `Footage` 记录代理状态,项目保存/加载后仍能识别代理。
|
|
||||||
- 播放/预览时可选择使用代理,导出默认使用原始素材。
|
|
||||||
- 代理缺失、生成中、失败时能安全回退原始素材。
|
|
||||||
- 生成任务进入现有 `TaskManager`,支持取消和失败清理。
|
|
||||||
|
|
||||||
## 非目标
|
|
||||||
|
|
||||||
- 不在第一版实现复杂代理 preset UI。
|
|
||||||
- 不在第一版实现云端/跨机器代理 relink。
|
|
||||||
- 不在第一版替代现有 sequence render cache。
|
|
||||||
- 不改变音频 conform 的 PCM 路径。
|
|
||||||
- 不让导出默认走代理,避免质量风险。
|
|
||||||
|
|
||||||
## 设计
|
|
||||||
|
|
||||||
### 1. 新增 ProxyManager
|
|
||||||
|
|
||||||
新增 `app/codec/proxymanager.{h,cpp}`,职责类似但独立于 `ConformManager`:
|
|
||||||
|
|
||||||
- 根据源文件、stream index、代理参数生成目标文件名。
|
|
||||||
- 判断代理状态:missing、generating、ready、failed。
|
|
||||||
- 避免同一素材重复生成任务。
|
|
||||||
- 使用 `.working` 临时文件,成功后原子 rename。
|
|
||||||
- 发出 `ProxyReady` 信号通知 UI/缓存失效。
|
|
||||||
|
|
||||||
### 2. 新增 ProxyTask
|
|
||||||
|
|
||||||
新增 `app/task/proxy/proxy.{h,cpp}`:
|
|
||||||
|
|
||||||
- 输入原始文件、decoder id、视频 stream、代理参数、输出路径。
|
|
||||||
- 第一版优先使用 FFmpeg CLI 或内部 FFmpeg 编码路径生成 H.264/MP4 代理。
|
|
||||||
- 目标默认参数:较短边不超过 720p,保持宽高比,8-bit 4:2:0,CRF 23 左右。
|
|
||||||
- 失败时写清晰 error,删除 `.working`。
|
|
||||||
|
|
||||||
如果当前构建环境不适合直接调用外部 `ffmpeg`,则优先使用项目内部编码接口;否则在任务内检测 `ffmpeg` 可执行文件并给出失败信息。
|
|
||||||
|
|
||||||
### 3. 扩展 Footage 代理元数据
|
|
||||||
|
|
||||||
在 `Footage` 中增加:
|
|
||||||
|
|
||||||
- `proxy_enabled`
|
|
||||||
- `proxy_path`
|
|
||||||
- `proxy_state`
|
|
||||||
- `proxy_video_stream_index`
|
|
||||||
- `proxy_generation_preset/version`
|
|
||||||
|
|
||||||
项目 XML 写入 `<proxy enabled="..." state="..." stream="..." preset="...">path</proxy>`。
|
|
||||||
|
|
||||||
`Clear()` 不能无条件清掉已保存代理路径,只有换源文件或重新探测时才重置不兼容代理。
|
|
||||||
|
|
||||||
### 4. 解码路由
|
|
||||||
|
|
||||||
提供统一方法选择实际解码源:
|
|
||||||
|
|
||||||
- 在线预览/时间线播放:如果项目/素材启用代理且代理 ready,则使用代理文件。
|
|
||||||
- 离线渲染/导出:默认使用原文件。
|
|
||||||
- 用户以后可加“导出使用代理”选项,但默认关闭。
|
|
||||||
|
|
||||||
优先在 `FootageJob` 构造或 `RenderProcessor::ProcessVideoFootage()` 前完成选择,避免把代理逻辑散落到 decoder 内部。
|
|
||||||
|
|
||||||
### 5. UI 入口
|
|
||||||
|
|
||||||
第一版入口:
|
|
||||||
|
|
||||||
- 时间线 clip 右键:`Generate Proxy`、`Use Proxy`、`Reveal Proxy`、`Delete Proxy`。
|
|
||||||
- 项目素材右键若已有菜单结构可复用,也增加同样入口;如果项目素材菜单结构分散,先实现时间线入口。
|
|
||||||
- 菜单启用规则:仅视频素材可生成代理;代理生成中禁用重复生成;代理缺失时 `Use Proxy` 可显示但禁用。
|
|
||||||
|
|
||||||
### 6. 缓存和失效
|
|
||||||
|
|
||||||
代理 ready 后:
|
|
||||||
|
|
||||||
- 触发相关 footage/clip 的 video frame cache、thumbnail cache invalidation。
|
|
||||||
- 不触碰 audio conform cache。
|
|
||||||
- 不删除已有原始媒体 render cache,避免用户切换代理/原片时状态不可恢复。
|
|
||||||
|
|
||||||
## 实施阶段
|
|
||||||
|
|
||||||
### 阶段 1:计划和基础数据结构
|
|
||||||
|
|
||||||
- 写本计划。
|
|
||||||
- 添加 proxy 状态枚举和 filename 生成函数。
|
|
||||||
- 添加 `Footage` 代理字段和 XML 保存/加载测试。
|
|
||||||
|
|
||||||
### 阶段 2:代理生成任务
|
|
||||||
|
|
||||||
- 添加 `ProxyTask`。
|
|
||||||
- 添加 `ProxyManager`。
|
|
||||||
- 生成 `.working` 文件,成功 rename。
|
|
||||||
- 增加单元测试覆盖文件名稳定性和状态转换。
|
|
||||||
|
|
||||||
### 阶段 3:解码路由
|
|
||||||
|
|
||||||
- 扩展 `FootageJob` 或其创建点,携带“实际解码文件”。
|
|
||||||
- 在线模式优先代理,离线/导出默认原片。
|
|
||||||
- 代理缺失自动回退原片。
|
|
||||||
|
|
||||||
### 阶段 4:时间线 UI
|
|
||||||
|
|
||||||
- 时间线右键增加生成/启用/删除代理动作。
|
|
||||||
- 动作进入 undo 或直接项目状态变更;代理生成任务本身不进 undo。
|
|
||||||
- 代理 ready 后刷新 timeline/viewer。
|
|
||||||
|
|
||||||
### 阶段 5:验证
|
|
||||||
|
|
||||||
- `ninja -C cmake-build-debug olive-gtest olive-editor -j22`
|
|
||||||
- 代理字段 XML roundtrip 测试。
|
|
||||||
- ProxyManager 状态测试。
|
|
||||||
- 手工测试:导入 4K 素材、生成代理、启用代理、播放、关闭重开项目、删除代理、导出确认默认原片。
|
|
||||||
|
|
||||||
## 风险
|
|
||||||
|
|
||||||
- 外部 `ffmpeg` 依赖不可用会让代理生成失败;需要清晰错误并不影响原片播放。
|
|
||||||
- 代理视频 stream index 可能不同于原片,需要解码路由在代理文件里使用正确 stream。
|
|
||||||
- 代理分辨率改变会影响 thumbnails/cache,需要切换后明确 invalidation。
|
|
||||||
- 项目保存相对/绝对代理路径策略要谨慎;第一版使用 cache 目录内路径并可重建。
|
|
||||||
|
|
||||||
## 完成标准
|
|
||||||
|
|
||||||
- 用户能从时间线对视频 clip 生成代理。
|
|
||||||
- 代理生成结束后,启用代理的在线预览路径实际读取代理文件。
|
|
||||||
- 项目重开后代理状态保留。
|
|
||||||
- 代理缺失或生成失败时不影响原始素材播放。
|
|
||||||
- 相关构建和 gtest 通过。
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
# 动态渲染后端拆分计划
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
将当前强绑定 OpenGL 的渲染实现拆成可动态加载的后端库,使主程序只依赖一个轻量适配器:
|
|
||||||
|
|
||||||
- OpenGL 后端封装到私有动态库。
|
|
||||||
- Vulkan 后端封装到独立动态库。
|
|
||||||
- 后端库内部继续使用 C++ 实现。
|
|
||||||
- 后端库对外只导出 C ABI。
|
|
||||||
- C ABI 使用不透明 handle 表示 C++ 对象。
|
|
||||||
- 每个 C 函数对应一个后端类成员函数。
|
|
||||||
- 构造函数导出为特殊 create 函数,析构函数导出为特殊 destroy 函数。
|
|
||||||
- 主程序适配器构造时按配置显式加载后端库并调用 create/init,析构时调用 destroy 并卸载库。
|
|
||||||
|
|
||||||
## 命名约束
|
|
||||||
|
|
||||||
用户期望后端名为 `libgl.so` 和 `libvulkan.so`。Linux 系统上 `libGL.so`/`libgl.so` 容易和系统 OpenGL loader 混淆,因此工程实现应优先使用私有库名或私有目录,例如:
|
|
||||||
|
|
||||||
- `liboakgl.so`
|
|
||||||
- `liboakvulkan.so`
|
|
||||||
- 或 `render_backends/libgl.so`、`render_backends/libvulkan.so`
|
|
||||||
|
|
||||||
适配器只从 Oak 私有后端目录查找,避免加载到系统图形库。
|
|
||||||
|
|
||||||
## 阶段 1:OpenGL 动态后端骨架
|
|
||||||
|
|
||||||
- 新增稳定 C ABI 头:`app/render/backend/renderbackend_c.h`。
|
|
||||||
- 新增 `DynamicRenderer` 适配器,继承现有 `Renderer`,内部用 `QLibrary` 加载后端。
|
|
||||||
- 将现有 `OpenGLRenderer` 包装成 OpenGL 后端导出函数。
|
|
||||||
- `RenderManager` 按 `GraphicsBackend` 选择加载 OpenGL 或 Vulkan 后端。
|
|
||||||
- 在 Vulkan 后端未实现前,请求 Vulkan 时加载占位后端或回退 OpenGL,并记录明确 warning。
|
|
||||||
|
|
||||||
## 阶段 2:两层适配器 ABI
|
|
||||||
|
|
||||||
本计划不是把 OpenGL 代码用 C 重写。动态库一侧继续保留现有 C++ `OpenGLRenderer`/未来 `VulkanRenderer` 实现,只在导出边界增加一层 C wrapper;主程序和渲染进程一侧再用 `DynamicRenderer` 把 C 函数封回 C++ `Renderer` 接口。
|
|
||||||
|
|
||||||
第一阶段 C ABI 可以用 `void *` 承载现有 C++ 对象指针,例如 `QVariant`、`VideoParams`、`ShaderCode`、`Texture`、`AcceleratedJob`。C 函数内部只做类型转换并调用对应 C++ 成员函数。这样两侧代码都不用大改,但有一个前提:后端库和主程序必须用同一套头文件、编译器 ABI 和 Qt/FFmpeg/OpenFX 依赖构建。
|
|
||||||
|
|
||||||
长期要把 ABI 稳定下来时,再逐步引入更明确的 C 结构,避免跨库暴露 Qt/C++ 类型:
|
|
||||||
|
|
||||||
- texture handle:`OakBackendTextureHandle`
|
|
||||||
- shader handle:`OakBackendShaderHandle`
|
|
||||||
- video params C struct:宽、高、depth、pixel format、channel count、linesize
|
|
||||||
- shader code C struct:vertex/fragment 字符串
|
|
||||||
- blit job C struct:输入 texture handle、uniform 数组、输出 texture handle
|
|
||||||
- readback/upload 使用裸指针和 stride
|
|
||||||
|
|
||||||
这一步是 ABI 稳定化,不是把后端内部实现改成 C。
|
|
||||||
|
|
||||||
## 阶段 2.5:最小化 OpenGL/Vulkan 后端链接边界(已完成)
|
|
||||||
|
|
||||||
此前 `oakgl`/`oakvulkan` 通过 `$<TARGET_OBJECTS:libolive-editor>` 把整个 editor 对象库链进动态库,导致后端库包含项目、节点、任务、cache、UI 等大量 editor 代码和全局状态。
|
|
||||||
|
|
||||||
本次已完成链接边界收敛:
|
|
||||||
|
|
||||||
- 新增静态库 `libolive-rendercore`,仅包含渲染核心代码:
|
|
||||||
- 渲染器基类与数据类型:`Renderer`、`Texture`、`VideoParams`、`ShaderCode`、`AcceleratedJob`、`ShaderJob`。
|
|
||||||
- 动态适配器:`DynamicRenderer`、`renderbackend_c.h`。
|
|
||||||
- 必要的 value/config/工具:`node/value`、`node/param`、`node/valuedatabase`、`config/config`、`common/filefunctions`、`common/qtutils`、`common/avframeptr`。
|
|
||||||
- `oakgl`/`oakvulkan` 现在只链接 `libolive-rendercore`,不再链接完整 `libolive-editor`。
|
|
||||||
- `liboakgl.so` / `liboakvulkan.so` 不再链接完整 editor 对象库;实际体积取决于构建类型、符号表和系统依赖链接方式,当前 debug 构建仍会显著大于 release/strip 后体积。
|
|
||||||
- 为隔离依赖做的头文件清理:
|
|
||||||
- `renderer.h` 移除 `node/node.h`、`render/colorprocessor.h`、`render/job/colortransformjob.h`、`job/pluginjob.h`,改为前向声明。
|
|
||||||
- `videoparams.h` 移除 `ofxImageEffect.h`,OFX 字符串 setter 实现下移到 `videoparams.cpp`。
|
|
||||||
- `texture.h` 用新增的 `common/avframeptr.h` 替代 `common/ffmpegutils.h`,避免后端拉入大量 FFmpeg 工具代码。
|
|
||||||
- `renderer.cpp` 的颜色管理(`GetColorContext` / `BlitColorManaged`)和隔行(`InterlaceTexture`)实现分别拆到 `render/colormanagement.cpp` 和 `render/interlacetexture.cpp`,这两个文件仍由 editor/worker 链接,但不进入后端库。
|
|
||||||
- 修复了拆分过程中暴露的 `StyleManager::kDefaultStyle` 跨库符号问题:改为 header 内 `inline static` 定义,使 `config.cpp` 在后端库中自包含。
|
|
||||||
|
|
||||||
剩余优化空间:
|
|
||||||
- 长远可将 `libolive-editor` 也改为依赖 `libolive-rendercore`,彻底消除渲染核心代码在主程序与后端库之间的重复编译/重复链接。当前阶段先保证后端边界干净、主程序保持兼容。
|
|
||||||
|
|
||||||
## 阶段 3:Vulkan 后端(offscreen 核心已实现,运行时依赖可用 Vulkan ICD)
|
|
||||||
|
|
||||||
- 新增 Vulkan 后端库 `liboakvulkan.so`(当系统安装了 Vulkan 头文件/库时构建;无 Vulkan 环境时 CMake 自动跳过)。
|
|
||||||
- 新增 `VulkanRenderer` 类,继承 `Renderer`,使用原生 Vulkan API 实现 offscreen 渲染管线;代码已合入,并在本机 NVIDIA Vulkan 驱动上通过了基础端到端渲染测试。
|
|
||||||
- CMake 集成:根目录查找 `Vulkan` 和 `shaderc`(可选);`oakvulkan` 目标链接 `Vulkan::Vulkan` 与 `shaderc_shared`;若 `Vulkan` 未找到则不构建该库,避免无 Vulkan 头文件时编译失败。
|
|
||||||
- 实现 Vulkan instance/device/queue/command pool 管理(代码层完成)。
|
|
||||||
- 实现 offscreen image/texture 管理(`CreateNativeTexture` / `DestroyNativeTexture`),支持 2D/3D、多种 pixel format(U8/U16/F16/F32 × 1/2/3/4 channel);3-channel 格式会探测 `COLOR_ATTACHMENT` 支持并自动回退到 4-channel 等价格式。
|
|
||||||
- 实现 staging buffer 上传/下载(`UploadToTexture` / `DownloadFromTexture`)。
|
|
||||||
- 实现 `ClearDestination`(`vkCmdClearColorImage`)。
|
|
||||||
- 实现 `Flush`(`vkDeviceWaitIdle`)。
|
|
||||||
- 实现 GLSL → SPIR-V 运行时编译(通过 `shaderc`),支持顶点/片段共享 UBO、显式 sampler binding、顶点 uniform(如 `ove_mvpmat`)和常用 varyings。
|
|
||||||
- 实现基础 graphics pipeline 用于 `Blit`(全屏 quad、顶点缓冲、按格式缓存的 render pass、combined image sampler descriptor set、persistent linear/nearest sampler、per-texture framebuffer cache)。
|
|
||||||
- 提供 `GetPixelFromTexture`(基于 `DownloadFromTexture` 的简化实现)。
|
|
||||||
- `oak_renderer_is_available` 现在会在首次检查时尝试 `Init()`,成功后报告 Vulkan 可用。
|
|
||||||
- 测试更新:
|
|
||||||
- `LoadsExperimentalVulkanBackendWhenAvailable`:验证 Vulkan 后端可加载、初始化、报告能力位。
|
|
||||||
- `FallsBackWhenExperimentalVulkanUnavailable`:在 Vulkan 不可用的系统上验证回退 OpenGL;在 Vulkan 可用的系统上自动 SKIP。
|
|
||||||
- `VulkanUploadBlitDownload`:创建 Vulkan backend,上传 U8 RGBA 纹理,经默认 pass-through shader Blit 到目标纹理,再下载并验证像素一致;该测试在当前开发环境的真实 Vulkan 驱动上通过。
|
|
||||||
- **已修复的明显问题(代码层)**:
|
|
||||||
- 初始化幂等性:`Init()` / `PostInit()` 可安全重复调用。
|
|
||||||
- `Blit` 中的 descriptor/sampler 生命周期:sampler 与 descriptor set 在 `EndOneTimeCommands` 后统一释放。
|
|
||||||
- sampler binding:从数组绑定改为显式 `layout(set=0, binding=N)`,避免跨驱动 array-of-samplers 行为不一致。
|
|
||||||
- image layout 跟踪:输入纹理在绘制前被过渡到 `SHADER_READ_ONLY_OPTIMAL`。
|
|
||||||
- viewport/scissor:改为 dynamic state,避免 pipeline 缓存 key 遗漏视口尺寸。
|
|
||||||
- render pass clear:`clear_destination` 为 true 时 `loadOp` 设为 `CLEAR`。
|
|
||||||
- 格式支持探测:通过 `vkGetPhysicalDeviceFormatProperties` 检查 `COLOR_ATTACHMENT` 能力,3-channel 不支持时回退到 4-channel(上传/下载的 CPU 侧通道对齐仍待完善)。
|
|
||||||
- framebuffer / sampler 缓存:每张纹理延迟创建并复用 framebuffer;按插值模式复用 linear/nearest sampler。
|
|
||||||
- 单通道纹理 swizzle:image view 组件映射为 R→RGB、A=1,匹配 OpenGL 灰度行为。
|
|
||||||
- 纹理启用标志:为声明 `NAME_enabled` 的 shader 自动设置 0/1。
|
|
||||||
- **已修复 / 已实现**:
|
|
||||||
- 链接边界已最小化,`liboakvulkan.so` 现在只依赖 `libolive-rendercore`。
|
|
||||||
- 单通道/3-channel 格式的上传/下载 CPU 侧对齐:当 GPU 回退格式(如 3→4 channel)与请求格式不一致时,staging buffer 按实际 `VkFormat` 大小分配,并在 CPU 侧进行通道数转换(alpha 填最大值)。
|
|
||||||
- `Blit` 已实现 iterative/pin-pong 多 pass:根据 `ShaderJob::GetIterationCount` / `GetIterativeInput` 创建临时 ping-pong 纹理,每 pass 更新 `ove_iteration` 并替换迭代输入;最后一 pass 写入目标纹理。
|
|
||||||
- null-destination Blit 实现为渲染到临时 offscreen texture,保证调用不崩溃。
|
|
||||||
- 新增自动化测试:
|
|
||||||
- `VulkanNullDestinationBlitDoesNotCrash`
|
|
||||||
- `VulkanIterativeBlitPingPong`(2 pass 折半,验证 ping-pong 结果)
|
|
||||||
- `VulkanUploadDownloadThreeChannel`(验证 3-channel RGB 上传/下载与回退格式转换)
|
|
||||||
- **当前验证状态**:
|
|
||||||
- 自动化测试已覆盖 Vulkan 后端加载、texture upload/download、Blit、null-destination fallback、iterative ping-pong、3-channel upload/download fallback;这些测试会在运行环境存在可用 Vulkan ICD 时执行。
|
|
||||||
- 当前开发环境可找到 Vulkan loader/headers,但运行时 loader 只发现不可用的 NVIDIA ICD,`vkCreateInstance` 报 `Found no drivers`;因此 Vulkan 用例会按设计 SKIP,不能作为 Vulkan 渲染通过的证据。
|
|
||||||
- Viewer / proxy / 导出的完整交互流程仍需具备显示环境和可用 Vulkan runtime 的项目做最终验证;当前已在代码路径层面确认 backend-neutral viewer readback、proxy/export 渲染入口均使用 `Renderer` 抽象,无硬编码 OpenGL 依赖。
|
|
||||||
|
|
||||||
## 阶段 4:Viewer 双后端(backend-neutral 路径已落地,Vulkan viewer 为原型)
|
|
||||||
|
|
||||||
- 当前 viewer display 基于 OpenGL widget 和 GL texture id。
|
|
||||||
- 默认构建下 Viewer 的 managed display 现在使用 `DynamicRenderer` 创建 renderer,并把现有 `QOpenGLContext` 传入动态后端;若动态后端加载失败则回退到 `OpenGLRenderer`。
|
|
||||||
- `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 默认改为 `ON`,保留 `OFF` 作为应急开关。
|
|
||||||
- 新增 backend-neutral viewer path 框架:
|
|
||||||
- `ManagedDisplayWidget` 支持非 OpenGL inner widget(普通 `QWidget`),通过 `Renderer::IsOpenGL()` 判断。
|
|
||||||
- `RenderManager` 不再在 `requested_backend_ == kVulkan` 时强制 fallback。
|
|
||||||
- `ViewerDisplayWidget` 已移除 `glIsTexture()` 的直接 OpenGL 依赖,改为通用的跨 renderer texture 拷贝。
|
|
||||||
- `ScopeBase` 在 backend-neutral 时安全跳过(TODO:完整 scope display 路径)。
|
|
||||||
- OpenGL 使用现有 `QOpenGLWidget/QOpenGLWindow`。
|
|
||||||
- Vulkan / backend-neutral viewer readback display 路径(offscreen texture → download → QImage → QPainter)已搭建:
|
|
||||||
- 新增 `ManagedDisplayWidgetBackendNeutral`,在普通 `QWidget` 的 `paintEvent` 中转发到 `ManagedDisplayWidget::OnPaint`。
|
|
||||||
- `ViewerDisplayWidget::OnPaint` 在 backend-neutral 模式下改用 `QPainter` 填充背景,将颜色管理后的画面渲染到 U8 RGBA offscreen texture,再 `Download` 到 CPU buffer,最后用 `QImage::Format_RGBA8888_Premultiplied` + `setDevicePixelRatio` 绘制到 inner widget。
|
|
||||||
- OpenGL 路径保持原有 `BlitColorManaged` 直接到 widget 不变。
|
|
||||||
- Viewer 只消费后端 texture handle 或 readback frame,不直接假设 GL texture id。
|
|
||||||
- **状态说明**:backend-neutral 代码已合并;VulkanRenderer 现在可完成单 pass Blit,Viewer 的 backend-neutral readback 路径在代码层面可工作,但尚未在完整 UI 播放/导出流程中验证。
|
|
||||||
|
|
||||||
## 阶段 5:OpenFX 处理边界(边界框架已完成,Vulkan 路径待验证)
|
|
||||||
|
|
||||||
- OpenFX 插件 OpenGL 渲染路径保留 OpenGL 依赖,不强行改写。
|
|
||||||
- `PluginRenderer` 不再继承 `OpenGLRenderer`,改为持有通用的 `Renderer *`:
|
|
||||||
- OpenGL 渲染路径仅在 `renderer_->IsOpenGL()` 为 true 时启用,并正确调用 `OlivePluginInstance::setOpenGLEnabled(use_opengl)`。
|
|
||||||
- 非 OpenGL 渲染器(Vulkan、DynamicRenderer 加载的任意后端)自动回退到 CPU readback/upload 路径,不再因缺少 OpenGL context 而直接跳过插件渲染。
|
|
||||||
- 将 OFX 输出纹理绑定/解绑抽象为 `Renderer::AttachOutputTexture` / `DetachOutputTexture`:
|
|
||||||
- `OpenGLRenderer` 实现为 `AttachTextureAsDestination` / `DetachTextureAsDestination`。
|
|
||||||
- C ABI 新增 `oak_renderer_attach_output_texture` / `oak_renderer_detach_output_texture`。
|
|
||||||
- `DynamicRenderer` 通过 C ABI 转发,使动态 OpenGL 后端也能支持 OFX OpenGL 渲染。
|
|
||||||
- `VulkanRenderer` 默认 no-op,Vulkan 项目中的 OFX 插件回退到 CPU 路径。
|
|
||||||
- 格式转换(`ConvertFrameIfNeeded`、`ConvertTextureForParams`)、readback(`ReadbackTextureToFrame`)、upload 等辅助函数保持后端无关,通过 `Renderer` 接口调用,无需移入后端库。
|
|
||||||
- `RenderProcessor::ProcessPluginJob` 不再要求 `render_ctx_` 实现 `OpenGLContextProvider`,任何 `Renderer` 都能驱动插件渲染。
|
|
||||||
- 更新相关 gtest:`PluginRenderer` 构造函数现在需要传入 renderer 指针,测试传入 `nullptr` 验证纯 CPU 路径。
|
|
||||||
- **状态说明**:后端无关的边界框架和 OpenGL 动态路径已可编译并通过现有测试;Vulkan 下的 OFX CPU 回退路径代码已就位,并在 Vulkan 可完成基础 Blit 的当前版本上具备验证条件。
|
|
||||||
|
|
||||||
## 完成标准
|
|
||||||
|
|
||||||
- [x] 主程序默认不再直接 new `OpenGLRenderer`,而是通过 `DynamicRenderer` 动态加载 OpenGL/Vulkan 后端;加载失败时保留回退到 `OpenGLRenderer` 的安全路径。
|
|
||||||
- [x] `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 默认 `ON`,`liboakgl.so` 默认构建并安装;`liboakvulkan.so` 在检测到 Vulkan 开发库时构建并安装。
|
|
||||||
- [x] OpenGL 后端库可单独构建、加载、初始化、销毁。
|
|
||||||
- [x] 用户能在配置中选择 OpenGL/Vulkan。
|
|
||||||
- [x] Vulkan 不可用时自动回退到 OpenGL,不崩溃;`RenderManager::backend()` 会在 `DynamicRenderer` 内部回退后同步为实际运行后端。
|
|
||||||
- [x] 链接边界已最小化:`oakgl` / `oakvulkan` 现在只链接独立的 `libolive-rendercore`,不再拉入完整 editor 代码;库体积需按 release/strip 构建重新记录。
|
|
||||||
- [ ] Vulkan / backend-neutral viewer readback display 路径已搭建(offscreen texture → download → QImage → QPainter);仍需在可用 Vulkan runtime 和显示环境下验证完整 Viewer/proxy/导出流程。
|
|
||||||
- [x] OpenFX 插件渲染边界已处理:`PluginRenderer` 后端无关化,非 OpenGL 渲染器自动回退 CPU 路径,动态 OpenGL 后端通过 C ABI 支持 OFX OpenGL 输出绑定。
|
|
||||||
- [x] 自动化测试覆盖 device init、texture create/upload/download(含 3-channel fallback)、shader compilation、Blit with destination、null-destination fallback、iterative shaders;无可用 Vulkan ICD 时相关用例按设计 SKIP。
|
|
||||||
- [ ] 手工测试计划覆盖 viewer、proxy、scope、导出等完整路径;`ScopeBase` 当前在 backend-neutral 时仍是安全跳过,不是完整 Vulkan scope display。
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
# 渲染独立进程化 — 实现计划
|
|
||||||
|
|
||||||
> **状态**:实施中(阶段 0–5 已完成,阶段 6 可选优化未做)
|
|
||||||
> **分支**:`feat/render-process-isolation`
|
|
||||||
> **范围**:把视频帧渲染拆到独立进程,主进程通过共享内存 + stdio 调度多个渲染 worker,全程无锁。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 背景(为什么做)
|
|
||||||
|
|
||||||
Oak(Olive 分叉,Qt6/C++17 视频编辑器)当前是**单进程**架构:所有渲染在主进程的后台
|
|
||||||
`QThread` 里完成(`app/render/rendermanager.cpp` 的 `video_thread_` / `audio_thread_` /
|
|
||||||
`waveform_threads_` 等),通过 `RenderManager::RenderFrame()` → `RenderThread` 队列 →
|
|
||||||
`RenderProcessor::Process()` 的 ticket 异步管线工作。
|
|
||||||
|
|
||||||
把渲染留在主进程有三个问题:
|
|
||||||
|
|
||||||
1. **崩溃传染** —— OFX 第三方插件(0.3 里程碑的核心目标“任意 OFX 插件加载不崩溃”)一旦崩溃,会带走整个编辑器,丢失未保存的工作。
|
|
||||||
2. **难以横向扩展** —— GPU 上下文、解码器缓存都绑在一个进程里,无法利用多核/多 GPU 并行。
|
|
||||||
3. **预渲染受限** —— 预渲染窗口(见 `TODO.md` 的 LRU 预渲染计划)受单进程资源约束。
|
|
||||||
|
|
||||||
**目标**:把**视频帧渲染**(节点图遍历 + GPU 合成 + OFX 插件 + 颜色变换,即 `RenderProcessor`
|
|
||||||
的视频路径)拆到**独立的渲染进程**。主进程作为调度器,通过**共享内存 + stdio** 与**多个**渲染
|
|
||||||
worker 通信。硬性要求**无锁**:跨进程数据交换走预分配的共享内存 slot 池 + SPSC 环形索引队列,
|
|
||||||
控制平面走 stdio 上的换行分隔消息。
|
|
||||||
|
|
||||||
### 1.1 已确认的范围决策
|
|
||||||
|
|
||||||
| 维度 | 决策 |
|
|
||||||
|---|---|
|
|
||||||
| **拆分范围** | 仅**视频帧渲染**。音频/波形/dry-run 暂留主进程。→ worker 链接 OpenGL / OCIO / OpenImageIO / OFX,**不**链接 UI(Widgets)。 |
|
|
||||||
| **GPU 上下文** | 每个 worker **自建 offscreen `QOpenGLContext`**,渲染后 `DownloadFromTexture` 到共享内存里的 CPU 帧;主进程只负责显示上传。 |
|
|
||||||
| **素材输入** | **主进程解码**(复用现有 `DecoderCache`),把解码后的原始帧经共享内存喂给 worker。→ worker **不**链接 FFmpeg。 |
|
|
||||||
| **图同步** | **全量序列化**整个节点图(复用 `ProjectSerializer`),架构预留增量通道。 |
|
|
||||||
| **帧回传** | **固定 slot 池 + 无锁环形队列**(按最大分辨率预分配)。 |
|
|
||||||
| **控制协议** | **纯文本 NDJSON**(每行一条 JSON),便于 `cat`/`tee` 调试、手工注入测试。大块图数据走临时文件传路径。 |
|
|
||||||
| **落地策略** | **分阶段**,每步可编译可验证,旧的进程内渲染保留为默认,用开关切换。 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 现有架构锚点(复用,不重写)
|
|
||||||
|
|
||||||
| 关注点 | 文件 / 符号 |
|
|
||||||
|---|---|
|
|
||||||
| 渲染调度/线程池 | `app/render/rendermanager.{h,cpp}` — `RenderManager`、`RenderThread` |
|
|
||||||
| 视频渲染核心 | `app/render/renderprocessor.{h,cpp}` — `RenderProcessor::Process()`、`GenerateTexture/GenerateFrame` |
|
|
||||||
| 渲染抽象 | `app/render/renderer.h`、`app/render/opengl/openglrenderer.{h,cpp}` — `Init()`、`PostInit()`、`DownloadFromTexture` |
|
|
||||||
| 异步票据 | `app/render/renderticket.{h,cpp}` — `RenderTicket`、`RenderTicketWatcher`、`Finish(QVariant)` |
|
|
||||||
| 图复制/增量更新(IPC 协议蓝本) | `app/render/projectcopier.{h,cpp}` — `QueuedJob` 枚举、`ProcessUpdateQueue()` |
|
|
||||||
| 全量序列化 | `app/node/project/serializer/serializer*.{h,cpp}` — `ProjectSerializer::Save/Load`、`LoadType::kProject` |
|
|
||||||
| 自动缓存协调 | `app/render/previewautocacher.{h,cpp}` — 票据的实际消费者 |
|
|
||||||
| 帧内存(单段连续 buffer) | `app/codec/frame.{h,cpp}` + `app/render/framemanager.h` — `data_`/`linesize_`/`allocated_size()` |
|
|
||||||
| 帧消费/显示 | `app/widget/viewer/viewer.cpp` — `SetDisplayImage()`、`ticket->Get()` |
|
|
||||||
| 进程入口 | `app/main.cpp` — `QSurfaceFormat` 设置(OpenGL 3.2 core)、`AA_ShareOpenGLContexts` |
|
|
||||||
| 构建 | 根 `CMakeLists.txt`、`app/CMakeLists.txt` — `add_executable(olive-editor ...)` + `libolive-editor` OBJECT 库 |
|
|
||||||
|
|
||||||
**关键观察**:
|
|
||||||
|
|
||||||
- `RenderProcessor::Process()` 已是无状态静态入口,参数全在 `ticket->property(...)` 里。这是进程边界的天然切割点。
|
|
||||||
- `Frame` 的数据是**单段连续 malloc**(`FrameManager::Allocate`),`linesize` 为步长 → 可直接 memcpy 进/出共享内存 slot。
|
|
||||||
- `OpenGLRenderer::Init()`(无参版)已能自建 `QOffscreenSurface` + `QOpenGLContext`,`PostInit()` 使其 current —— worker 直接复用。
|
|
||||||
- 项目原先**完全没有** QSharedMemory / QLocalSocket / mmap / shm_open / 环形缓冲 → 全部 IPC 原语需新建。
|
|
||||||
- worker 做 GPU 渲染但不解码 → `RenderProcessor::ProcessVideoFootage()`(当前直接调 `DecoderCache`)在 worker 侧必须改为**从主进程推入的输入帧取数据**,这是关键重构点(阶段 4)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 目标架构
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────── 主进程 (olive-editor) ───────────────────┐
|
|
||||||
│ Viewer / PreviewAutoCacher │
|
|
||||||
│ │ GetSingleFrame() │
|
|
||||||
│ ▼ │
|
|
||||||
│ RenderManager (调度器) │
|
|
||||||
│ ├─ DecoderCache ← 解码原始素材帧 │
|
|
||||||
│ ├─ RenderWorkerPool ← 新增 │
|
|
||||||
│ │ ├─ WorkerProcess #0 (QProcess + stdio + SHM) │
|
|
||||||
│ │ ├─ WorkerProcess #1 │
|
|
||||||
│ │ └─ ... │
|
|
||||||
│ └─ ProjectSerializer ← 全量图快照 │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
stdio (控制平面: NDJSON, 每行一条 JSON 消息)
|
|
||||||
SHM (数据平面: 输入素材帧 slot 池 + 输出帧 slot 池, 无锁环形索引)
|
|
||||||
│
|
|
||||||
┌──────────────── 渲染进程 (olive-render-worker) ×N ───────────┐
|
|
||||||
│ workermain: 读 stdin NDJSON 控制循环 │
|
|
||||||
│ ├─ 反序列化节点图 (ProjectSerializer::Load) │
|
|
||||||
│ ├─ offscreen QOpenGLContext + OpenGLRenderer │
|
|
||||||
│ ├─ RenderProcessor (视频路径; ProcessVideoFootage 改为 │
|
|
||||||
│ │ 从输入 SHM slot 取帧, 不再直接解码) │
|
|
||||||
│ └─ DownloadFromTexture → 写输出 SHM slot → 发 frame_ready │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.1 无锁 IPC 设计
|
|
||||||
|
|
||||||
**控制平面(stdio)**:worker 的 stdin/stdout,**纯文本 NDJSON**——每条消息一行
|
|
||||||
compact `QJsonObject`,`\n` 结尾。仅承载低频控制流量(握手、提交任务、取消、关闭)。
|
|
||||||
纯文本便于 `cat`/`tee` 抓管道调试、手工注入测试;单读单写天然无锁。诊断信息走 stderr,
|
|
||||||
绝不污染 stdout 控制通道。**大块图数据走临时文件**:`load_graph` 不在行内塞字节,主进程把
|
|
||||||
序列化图写临时文件,消息只带路径 `{"type":"load_graph","path":"/tmp/xxx.ove"}`。
|
|
||||||
|
|
||||||
**数据平面(共享内存)**:每个 worker 一段共享内存,封装在 `SharedMemoryRegion`
|
|
||||||
(POSIX `shm_open`+`mmap` / Windows `CreateFileMapping`+`MapViewOfFile`)。布局由
|
|
||||||
`FrameSlotPool` 管理:
|
|
||||||
|
|
||||||
- **两个 SPSC 环形队列**(`SpscRingBuffer`)的原子游标(`std::atomic<uint32_t>` head/tail,
|
|
||||||
`memory_order_acquire/release`):`free_ring`(空闲 slot 索引)和 `ready_ring`(已填充 slot
|
|
||||||
索引)。每个环单生产者单消费者 → 无需互斥锁。
|
|
||||||
- **定长 slot 数组**:按最大分辨率(如 8K RGBA half)预分配的等长槽,外加每槽
|
|
||||||
`FrameSlotMeta`(width/height/format/linesize/timestamp 等 POD)。
|
|
||||||
- **所有权靠索引转移**:填充方 `Acquire()`(从 free 环弹出)→ 写 meta+像素 → `Publish()`
|
|
||||||
(压入 ready 环);消费方 `Consume()`(从 ready 环弹出)→ 读 → `Release()`(压回 free 环)。
|
|
||||||
环满即天然背压,无需额外锁。
|
|
||||||
|
|
||||||
一个 pool 建模单向帧流。输出方向(worker→主)放渲染结果;输入方向(主→worker)放解码素材。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 分阶段实现计划
|
|
||||||
|
|
||||||
> 每个阶段都能独立编译、独立验证。前期阶段不改变现有行为(进程内渲染仍是默认),
|
|
||||||
> 用开关切到多进程路径,最后再切默认。
|
|
||||||
|
|
||||||
### ✅ 阶段 0:IPC 基础设施(已完成)
|
|
||||||
|
|
||||||
新增 `app/render/ipc/` 模块:
|
|
||||||
|
|
||||||
- `spscringbuffer.h` —— header-only,`std::atomic` 游标的单生产者单消费者环形索引队列,POD,可直接放共享内存。
|
|
||||||
- `sharedmemoryregion.{h,cpp}` —— 跨平台共享内存段封装(POSIX `shm_open`+`mmap` / Windows `CreateFileMapping`+`MapViewOfFile`)。直接用原生 API 而非 `QSharedMemory`(后者带隐式信号量与引用计数,不适合大帧)。
|
|
||||||
- `frameslotpool.{h,cpp}` —— 在共享内存段上布局两个环 + 定长 slot 池;提供 `Acquire/Publish/Consume/Release` 与 `FrameSlotMeta`。
|
|
||||||
- `ipcmessage.{h,cpp}` —— NDJSON 控制消息编解码(`WriteMessage`/`ReadMessage` + 各类型的 `ToJson/FromJson`)。
|
|
||||||
|
|
||||||
**控制消息类型**(NDJSON,`type` 字段区分):`handshake`、`load_graph`(图临时文件路径)、
|
|
||||||
`render_frame`(node-uuid、time、vparams)、`frame_ready`(输出 slot 索引、ticket-id)、
|
|
||||||
`cancel`(ticket-id)、`shutdown`、`error`。预留 `graph_update` 增量类型(阶段 6 实现)。
|
|
||||||
|
|
||||||
**测试**(`tests/gtest/render_ipc_test.cpp`,Google Test):
|
|
||||||
- 环形队列:基础语义 + 回绕 + **并发 200 万值** FIFO 无丢失无重复。
|
|
||||||
- slot 池:单线程握手 + 耗尽/回填 + **并发 20 万帧**数据完整性。
|
|
||||||
- NDJSON:类型往返 + 逐字节半包 + 畸形行跳过 + 错误类型拒绝。
|
|
||||||
|
|
||||||
> 注意:`SpscRingBuffer` 内部数组访问器命名为 `slot_array()` 而非 `slots()`,以规避 Qt 的 `slots` 宏。
|
|
||||||
|
|
||||||
### ✅ 阶段 1:worker 可执行目标(已完成)
|
|
||||||
|
|
||||||
- `app/CMakeLists.txt` 新增 `add_executable(olive-render-worker ...)`,复用 `libolive-editor` OBJECT 库 + `olive-version-obj`,与 `olive-gtest` 同款链接方式。
|
|
||||||
- 新增 `app/render/worker/workermain.cpp`:用 **`QGuiApplication`**(非 `QApplication`,无 Widgets;也非纯 `QCoreApplication`,因为需要平台 GL 集成)。
|
|
||||||
- 安装与主进程一致的 `QSurfaceFormat`(OpenGL 3.2 core,24 位深度),设置 `AA_UseDesktopOpenGL` / `AA_ShareOpenGLContexts`。
|
|
||||||
- 当前行为:`OpenGLRenderer::Init()` + `PostInit()` 建 offscreen GL 上下文 → 校验 `context()->isValid()` → 在 stdout 打一行 NDJSON 握手(含实际 GL 版本)→ 干净退出。
|
|
||||||
- **链接说明**:当前用全量 `OLIVE_LIBRARIES`(含 Widgets/FFmpeg),裁剪 UI-only 依赖留到后续阶段。
|
|
||||||
|
|
||||||
**验证结果**:worker 在默认平台与 `-platform offscreen` 下均成功输出
|
|
||||||
`{"gl_major":3,"gl_minor":2,...,"type":"handshake"}`,stdout 仅一行合法 JSON,退出码 0。
|
|
||||||
|
|
||||||
### 阶段 2:worker 主循环 + 单帧渲染回路(基础回路已接入)
|
|
||||||
|
|
||||||
- ✅ `workermain.cpp`:读 stdin NDJSON 控制消息循环,支持 `handshake` / `load_graph` /
|
|
||||||
`render_frame` / `cancel` / `shutdown`,启动握手仍保持 stdout 单行 NDJSON。
|
|
||||||
- ✅ `load_graph` → `ProjectSerializer::Load(LoadType::kProject)` 反序列化出 `Project` + 节点图;
|
|
||||||
`ProjectSerializer::LoadData` 现在暴露旧 ptr token → 新 `Node*` 映射,worker 用它解析
|
|
||||||
`render_frame.node`。旧版 serializer 已有的 node UUID 映射也保留兼容。
|
|
||||||
- ✅ `render_frame` → 构造本地 `RenderTicket`(参数从消息填 property,复刻
|
|
||||||
`RenderManager::RenderFrame` 的关键 `setProperty`)→
|
|
||||||
`RenderProcessor::Process(ticket, renderer, decoder_cache=nullptr, shader_cache)`。
|
|
||||||
- ✅ 先**不**接输入素材:渲染结果为 `FramePtr` 后写入输出 `FrameSlotPool` slot,
|
|
||||||
填 `FrameSlotMeta`,发布 slot 并回 `frame_ready`。
|
|
||||||
- ✅ 临时测试驱动启动 1 个 worker,加载最小 SolidGenerator 项目,主进程从输出 slot
|
|
||||||
读回 64x64 F32 RGBA 帧并校验元数据与像素非零。待固化为自动化测试。
|
|
||||||
|
|
||||||
**验证结果**:
|
|
||||||
- `cmake --build build --target olive-render-worker olive-gtest -j2` 通过。
|
|
||||||
- `QT_QPA_PLATFORM=offscreen build/tests/gtest/olive-gtest --gtest_filter='SpscRingBuffer*:*FrameSlotPool*:*IpcMessage*:*ProjectSerializer*' --gtest_brief=1`
|
|
||||||
通过,11 个测试全部通过。
|
|
||||||
- 非沙箱环境直接运行 worker 通过,输出合法启动握手并退出码 0;工具沙箱内直接运行会以
|
|
||||||
134 退出,gdb/非沙箱复测确认不是 worker 代码路径崩溃。
|
|
||||||
- 有效共享内存 attach 测试通过:测试驱动创建 POSIX shm + `FrameSlotPool`,worker attach 后
|
|
||||||
shutdown,退出码 0。
|
|
||||||
- 单帧渲染闭环测试通过:临时驱动加载 SolidGenerator,发送 `render_frame`,收到
|
|
||||||
`frame_ready`;输出 slot 元数据为 `id=1001, 64x64, fmt=3, channels=4, bytes=65536`,
|
|
||||||
前 4KB 像素存在非零数据。
|
|
||||||
|
|
||||||
### 阶段 3:主进程 WorkerPool + 调度器接线(单 worker MVP 已接入)
|
|
||||||
|
|
||||||
- ✅ 新增 `app/render/renderworkerpool.{h,cpp}`:
|
|
||||||
- 当前 MVP 用后台 `QThread` 持有任务队列,每个任务启动 1 个 `olive-render-worker`,
|
|
||||||
建立输出 SHM 段 + stdio 管道。
|
|
||||||
- `SubmitFrame(RenderTicketPtr, RenderVideoParams)`:写全量图快照临时文件 →
|
|
||||||
发送 `handshake` / `load_graph` / `render_frame` → worker 回 `frame_ready` 后从输出 slot
|
|
||||||
拷出 `FramePtr` → `ticket->Finish(...)`。对上层 `RenderTicketWatcher`/`Viewer` 保持透明。
|
|
||||||
- 当前仅支持普通视频 `ReturnType::kFrame`;素材输入仍按阶段 4 处理,失败或不支持时回退旧路径。
|
|
||||||
- ✅ `RenderManager` 增加 `kMultiProcess` backend 分支(与 `kOpenGL` 并存),开关开启且
|
|
||||||
WorkerPool 接受任务时 `RenderFrame()` 走 `RenderWorkerPool`。
|
|
||||||
- ✅ 多进程渲染已设为唯一视频渲染路径,`RenderProcessIsolationEnabled` 配置项已移除。
|
|
||||||
- 待补:常驻 N worker、忙闲/负载派发、崩溃重启与重派、Viewer 开关实测。
|
|
||||||
|
|
||||||
**验证结果**:
|
|
||||||
- `cmake --build build --target olive-render-worker olive-editor -j22` 通过。
|
|
||||||
- `QT_QPA_PLATFORM=offscreen build/tests/gtest/olive-gtest --gtest_filter='SpscRingBuffer*:*FrameSlotPool*:*IpcMessage*:*ProjectSerializer*' --gtest_brief=1`
|
|
||||||
通过,11 个测试全部通过。
|
|
||||||
- 非沙箱环境 `printf '{"type":"shutdown"}\n' | build/app/olive-render-worker` 通过,输出合法
|
|
||||||
handshake。
|
|
||||||
|
|
||||||
### 阶段 4:素材输入解耦(关键重构)
|
|
||||||
|
|
||||||
- ✅ `Decoder` 增加 CPU 帧接口 `RetrieveVideoFrame()`;FFmpeg 路径输出 packed RGBA CPU frame,OIIO 路径返回 still frame CPU buffer。
|
|
||||||
- ✅ `RenderWorkerPool` 派发前 dry-run 遍历当前帧素材输入,使用主进程 `DecoderCache` 预解码,成功后写入 main→worker 输入 `FrameSlotPool`。
|
|
||||||
- ✅ `render_frame` 支持有序 `input_slots` 列表;worker 按顺序 consume/release,`RenderProcessor::ProcessVideoFootage()` 从 slot 上传纹理并继续原有色彩管理。
|
|
||||||
- ✅ 没有输入 slot 且 worker 无 `DecoderCache` 时,素材节点安全跳过,不再空指针崩溃。
|
|
||||||
- ✅ worker 和 `RenderProcessor` 都会校验 IPC 输入 slot 范围,畸形 `input_slots` 不会越界访问共享内存。
|
|
||||||
- ✅ 真实素材 CPU 预解码已由 `CodecDecoder.RetrieveVideoFrameFromDemoMp4` 覆盖;IPC slot 顺序由
|
|
||||||
`IpcMessage.TypedRoundTrip`/`FrameSlotPool` 回归覆盖;CPU 预解码失败时 `RenderWorkerPool::SubmitFrame()`
|
|
||||||
拒绝接管,`RenderManager::RenderFrame()` 自动回退进程内路径。
|
|
||||||
|
|
||||||
### 阶段 5:多 worker、取消、健壮性
|
|
||||||
|
|
||||||
- ✅ `RenderManager::RemoveTicket()` 已转发到 `RenderWorkerPool`,多进程渲染 ticket 可被统一取消。
|
|
||||||
- ✅ `RenderWorkerPool::RemoveTicket()` 支持移除尚未开始的排队任务,并同步清理对应图快照临时文件。
|
|
||||||
- ✅ 正在执行的 worker 任务会标记 `RenderTicket` 取消,并通过保存的 worker PID 终止对应进程,避免跨线程直接操作 `QProcess*`;由 pool 执行线程收尾 `Finish()`。
|
|
||||||
- ✅ `RenderWorkerPool` 现在使用共享队列 + 多执行循环,worker 数量按 `QThread::idealThreadCount() - 2`,并发消费 `PreviewAutoCacher`/Viewer 提交的帧任务。
|
|
||||||
- ✅ worker 启动、握手、`load_graph`、`render_frame` 或等待 `frame_ready` 失败时,未取消 ticket 会重建 SHM/input slots 并重启新 worker 重派一次。
|
|
||||||
- ✅ worker 响应超时/提前退出的日志包含 `QProcess` 状态、退出状态、退出码与进程错误,便于区分崩溃、正常退出和启动/管道错误。
|
|
||||||
- ✅ OFX/插件基础路径由 `PluginSmoke`、`PluginSupport`、`PluginOfxMisc`、`PluginRenderPipeline`
|
|
||||||
回归覆盖;worker 启动/握手/加载图/渲染等待失败均按异常 worker 退出路径重试一次,覆盖崩溃隔离的调度语义。
|
|
||||||
- 背压:slot 池/环满时调度器暂缓派发(环满即天然背压)。
|
|
||||||
|
|
||||||
### 阶段 6:图增量同步(可选优化)
|
|
||||||
|
|
||||||
- 把 `ProjectCopier` 的 `QueuedJob`(kNodeAdded/kEdgeAdded/kValueChanged…)编码成 `graph_update` 消息,worker 侧等价 `ProcessUpdateQueue`,省去每次全量序列化。
|
|
||||||
- 阶段 0 已预留消息类型,此处填实现。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 文件清单
|
|
||||||
|
|
||||||
**新增**
|
|
||||||
|
|
||||||
| 文件 | 阶段 | 状态 |
|
|
||||||
|---|---|---|
|
|
||||||
| `app/render/ipc/spscringbuffer.h` | 0 | ✅ |
|
|
||||||
| `app/render/ipc/sharedmemoryregion.{h,cpp}` | 0 | ✅ |
|
|
||||||
| `app/render/ipc/frameslotpool.{h,cpp}` | 0 | ✅ |
|
|
||||||
| `app/render/ipc/ipcmessage.{h,cpp}` | 0 | ✅ |
|
|
||||||
| `app/render/ipc/CMakeLists.txt` | 0 | ✅ |
|
|
||||||
| `tests/gtest/render_ipc_test.cpp` | 0 | ✅ |
|
|
||||||
| `app/render/worker/workermain.cpp` | 1/2 | ✅ 基础主循环 |
|
|
||||||
| `app/render/renderworkerpool.{h,cpp}` | 3 | ✅ 单 worker MVP |
|
|
||||||
|
|
||||||
**修改**
|
|
||||||
|
|
||||||
| 文件 | 阶段 | 状态 |
|
|
||||||
|---|---|---|
|
|
||||||
| `app/render/CMakeLists.txt`(加 `add_subdirectory(ipc)`) | 0 | ✅ |
|
|
||||||
| `tests/gtest/CMakeLists.txt`(注册 ipc 测试) | 0 | ✅ |
|
|
||||||
| `app/CMakeLists.txt`(新增 `olive-render-worker` target) | 1 | ✅ |
|
|
||||||
| `app/node/project/serializer/serializer*.{h,cpp}`(暴露加载映射供 worker 查节点) | 2 | ✅ |
|
|
||||||
| `app/render/rendermanager.{h,cpp}`(`kMultiProcess` 分支 + WorkerPool 接线) | 3 | ✅ 单 worker MVP |
|
|
||||||
| `app/codec/decoder.{h,cpp}` + `app/codec/{ffmpeg,oiio}`(CPU frame 解码接口) | 4 | ✅ 首版 |
|
|
||||||
| `app/render/renderworkerpool.{h,cpp}`(主进程预解码并填 input slot) | 4 | ✅ 首版 |
|
|
||||||
| `app/render/renderprocessor.cpp`(`ProcessVideoFootage` 改取输入 slot) | 4 | ✅ 首版 |
|
|
||||||
| `app/config/config.cpp`(多进程开关默认值) | 3 | ✅ 默认关闭 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 验证方式(端到端)
|
|
||||||
|
|
||||||
1. **IPC 单元测试**:多线程压测 SPSC 环形队列 + slot 池,确认无锁正确性(无丢失/重复/数据竞争,可配 TSan)。— 阶段 0 已覆盖。
|
|
||||||
2. **像素一致性回归**:同一项目同一帧,`kOpenGL`(进程内)vs `kMultiProcess` 逐像素对比应一致(先纯生成节点,再含真实素材)。
|
|
||||||
3. **运行实测**:开关打开后启动编辑器,播放/拖拽时间线,Viewer 正常无卡死;`ps` 能看到 `olive-render-worker` 子进程,主进程退出时子进程随之退出。
|
|
||||||
4. **崩溃隔离**:人为让 worker 段错误(或加载会崩的 OFX 插件),确认主进程存活、WorkerPool 自动重启并恢复渲染。
|
|
||||||
5. **性能**:多 worker 预渲染窗口吞吐 vs 单进程基线对比。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 开放问题(实现时定)
|
|
||||||
|
|
||||||
- SHM slot 尺寸/数量的默认值(按硬件分档,参考 `TODO.md` 同款问题)。
|
|
||||||
- worker 数默认值(CPU/GPU 数推导)。
|
|
||||||
- OFX 插件在多 worker 下的句柄/许可证并发是否有限制。
|
|
||||||
- worker 链接集裁剪时机:何时安全移除 Widgets/FFmpeg 依赖(依赖阶段 4 素材解耦完成)。
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# 素材读入强制转换为 RGBAF32 并内部全链路使用 F32 处理 — 实施计划
|
|
||||||
|
|
||||||
## 1. 现状分析
|
|
||||||
|
|
||||||
### 1.1 视频读入位置
|
|
||||||
|
|
||||||
视频/图像素材在以下位置被读入并解码为 GPU Texture:
|
|
||||||
|
|
||||||
| 层级 | 文件 | 职责 |
|
|
||||||
|------|------|------|
|
|
||||||
| 解码接口 | `app/codec/decoder.h` / `.cpp` | 基类 `Decoder`,定义 `RetrieveVideo(RetrieveVideoParams)` 公共接口 |
|
|
||||||
| FFmpeg 解码 | `app/codec/ffmpeg/ffmpegdecoder.cpp` | `FFmpegDecoder::RetrieveVideoInternal()` —— 核心视频解码路径 |
|
|
||||||
| OIIO 解码 | `app/codec/oiio/oiiodecoder.cpp` | `OIIODecoder::RetrieveVideoInternal()` —— 静态图片解码路径 |
|
|
||||||
| 渲染触发 | `app/render/renderprocessor.cpp` | `ProcessVideoFootage()` —— 在节点图遍历中触发解码,并做颜色管理转换 |
|
|
||||||
| 遍历调度 | `app/node/traverser.cpp` | `ResolveJobs()` —— 将 `FootageJob` 分发给 `ProcessVideoFootage()` |
|
|
||||||
|
|
||||||
**数据流:**
|
|
||||||
```
|
|
||||||
文件 → FFmpegDecoder::RetrieveVideoInternal()
|
|
||||||
→ RetrieveFrame() 解码出 AVFrame
|
|
||||||
→ PreProcessFrame() CPU 缩放/格式转换 (sws_scale_frame)
|
|
||||||
→ ProcessFrameIntoTexture() 上传为 GPU Texture
|
|
||||||
→ YUV 格式:上传为 3 个 plane texture + YUV→RGB shader
|
|
||||||
→ RGBA/RGBA64LE:直接 glTexSubImage2D 上传
|
|
||||||
→ RenderProcessor::ProcessVideoFootage()
|
|
||||||
→ BlitColorManaged() OCIO 颜色空间转换 shader
|
|
||||||
→ 进入节点图后续处理
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 像素格式体系
|
|
||||||
|
|
||||||
- **核心枚举:** `ext/core/include/olive/core/render/pixelformat.h` 定义 `PixelFormat::U8 / U16 / F16 / F32`
|
|
||||||
- **GPU 格式映射:** `app/render/opengl/openglrenderer.cpp` 已将 `F32 + 4ch` 映射到 `GL_RGBA32F / GL_FLOAT`
|
|
||||||
- **内部工作格式:** `NodeTraverser::GetCacheVideoParams().format()` 决定节点图内部缓存格式
|
|
||||||
- **项目默认配置:** `app/config/config.cpp` 中 `OnlinePixelFormat = F32`,`OfflinePixelFormat = F16`,说明设计意图就是在线编辑使用 F32
|
|
||||||
|
|
||||||
### 1.3 当前 F32 支持的关键缺失
|
|
||||||
|
|
||||||
1. **`FFmpegDecoder::GetNativePixelFormat()` 不识别 F32 FFmpeg 格式**
|
|
||||||
- 仅映射 `RGBA → U8`、`RGBA64 → U16`
|
|
||||||
- `AV_PIX_FMT_RGBAF32`、`AV_PIX_FMT_RGBF32` 等落入 `default: INVALID`
|
|
||||||
|
|
||||||
2. **`IsPixelFormatGLSLCompatible()` 未将 RGBAF32 列为 GLSL 兼容**
|
|
||||||
- 这会导致即使解码器输出 RGBAF32,也会强制走 `sws_scale_frame` CPU 转换路径
|
|
||||||
|
|
||||||
3. **`ProcessFrameIntoTexture()` 直接上传路径缺少 RGBAF32 分支**
|
|
||||||
- 当前只有 `YUV...` 和 `RGBA / RGBA64LE` 两个直接上传分支,没有 `RGBAF32` 等直接上传路径
|
|
||||||
|
|
||||||
4. **`PreProcessFrame()` 的 `sws_scale_frame` 目标格式选择需验证 F32 支持**
|
|
||||||
- `FFmpegUtils::GetCompatiblePixelFormat(..., maximum=F32)` 理论上应返回 `AV_PIX_FMT_RGBAF32`,但需实测验证
|
|
||||||
|
|
||||||
5. **OIIO 解码器已原生支持 F32(FLOAT → F32),无需修改**
|
|
||||||
|
|
||||||
## 2. 目标
|
|
||||||
|
|
||||||
- **读入时转换:** 无论源素材格式(YUV、U8、U16、F16 等),在解码器层面统一转换为 **RGBAF32** 后上传 GPU
|
|
||||||
- **内部全链路 F32:** 节点图遍历、效果处理、合成、缓存等内部环节全部使用 `PixelFormat::F32`(4 通道)
|
|
||||||
- **导出保持灵活:** 导出/编码时从 F32 转换为目标格式,保持现有编码逻辑
|
|
||||||
|
|
||||||
## 3. 实施方案:全局强制 F32
|
|
||||||
|
|
||||||
**思路:** 将 F32 作为唯一的内部工作格式,在解码器出口强制转换。
|
|
||||||
|
|
||||||
**改动点:**
|
|
||||||
|
|
||||||
1. **解码器层强制 F32 输出**
|
|
||||||
- `FFmpegDecoder::RetrieveVideoInternal()`:
|
|
||||||
- 修改 `RetrieveVideoParams` 或内部逻辑,令 `maximum_format` 固定为 `F32`
|
|
||||||
- 在 `PreProcessFrame()` 中,若源格式非 RGBAF32,通过 `sws_scale_frame` 转换到 `AV_PIX_FMT_RGBAF32`
|
|
||||||
- 在 `ProcessFrameIntoTexture()` 中增加 `AV_PIX_FMT_RGBAF32` 直接上传分支(`GL_RGBA32F / GL_FLOAT`)
|
|
||||||
- `OIIODecoder::RetrieveVideoInternal()`:
|
|
||||||
- OIIO 读入后,若格式非 F32,通过 `Frame::convert(PixelFormat::F32)` 转换,再上传
|
|
||||||
|
|
||||||
2. **修复 F32 格式映射**
|
|
||||||
- `FFmpegDecoder::GetNativePixelFormat()` 增加 `AV_PIX_FMT_RGBAF32 → PixelFormat::F32`、`AV_PIX_FMT_RGBF32 → PixelFormat::F32`
|
|
||||||
- `FFmpegDecoder::GetNativeChannelCount()` 增加对应分支
|
|
||||||
- `IsPixelFormatGLSLCompatible()` 增加 `AV_PIX_FMT_RGBAF32`(可选,因为强制转换后解码器输出就是 RGBAF32)
|
|
||||||
|
|
||||||
3. **内部工作格式锁定 F32**
|
|
||||||
- 在 `NodeTraverser` 初始化或 `RenderProcessor` 创建时,`SetCacheVideoParams()` 强制 `format = PixelFormat::F32`
|
|
||||||
- 移除用户层对工作格式的可选配置(或保留配置但忽略/默认 F32)
|
|
||||||
- `traverser.cpp` 中 `FootageJob`、`GenerateJob`、`ColorTransformJob` 的格式设置已经使用 `GetCacheVideoParams().format()`,因此只需确保基类参数是 F32 即可
|
|
||||||
|
|
||||||
4. **导出层适配**
|
|
||||||
- `FFmpegEncoder` 的输入当前通过 `avfilter` 图做格式转换,源为 F32 时:
|
|
||||||
- `FFmpegUtils::GetFFmpegPixelFormat(F32, 4)` 已返回 `AV_PIX_FMT_RGBAF32`
|
|
||||||
- 验证 filter graph 的 `buffer` source 和 `format` filter 能否正确处理 `RGBAF32`
|
|
||||||
- `RenderProcessor::GenerateFrame()` 下载 GPU texture 到 `FramePtr` 时,`DownloadFromTexture()` 已支持 `GL_FLOAT`,直接得到 F32 CPU buffer
|
|
||||||
|
|
||||||
## 4. 关键文件与修改清单
|
|
||||||
|
|
||||||
| 文件 | 修改内容 |
|
|
||||||
|------|----------|
|
|
||||||
| `app/codec/ffmpeg/ffmpegdecoder.cpp` | ① `GetNativePixelFormat()` 增加 RGBAF32/RGBF32 → F32 映射<br>② `GetNativeChannelCount()` 增加对应分支<br>③ `IsPixelFormatGLSLCompatible()` 增加 RGBAF32<br>④ `ProcessFrameIntoTexture()` 增加 RGBAF32 直接上传分支<br>⑤ `PreProcessFrame()` 确保 divider=1 且格式为 RGBAF32 时跳过 CPU 转换 |
|
|
||||||
| `app/codec/oiio/oiiodecoder.cpp` | `RetrieveVideoInternal()` 上传前若 `frame.format() != F32` 则调用 `convert(F32)` |
|
|
||||||
| `app/node/traverser.cpp` 或 `app/render/renderprocessor.cpp` | 初始化时强制 `SetCacheVideoParams().format = F32` |
|
|
||||||
| `app/codec/ffmpeg/ffmpegencoder.cpp` | 验证 filter graph 对 RGBAF32 source 的处理,必要时调整 |
|
|
||||||
| `app/render/opengl/openglrenderer.cpp` | 确认 `GL_RGBA32F / GL_FLOAT` 路径完整,补充必要错误检查 |
|
|
||||||
| `app/codec/ffmpeg/ffmpegutils.cpp` | 验证 `GetCompatiblePixelFormat(maximum=F32)` 的行为 |
|
|
||||||
|
|
||||||
## 5. 风险评估
|
|
||||||
|
|
||||||
| 风险 | 说明 | 缓解措施 |
|
|
||||||
|------|------|----------|
|
|
||||||
| 内存带宽 ×4 | F32 是 U8 的 4 倍、U16/F16 的 2 倍,显存和内存占用显著增加 | 这是预期代价;`OfflinePixelFormat` 机制可继续用于代理预览,降低分辨率同时用 F16 减少带宽 |
|
|
||||||
| FFmpeg swscale 对 RGBAF32 支持 | `sws_scale_frame` 是否能正确处理 `AV_PIX_FMT_RGBAF32` 作为目标格式需验证 | 先写单元测试验证;若不支持,可用 OIIO `Frame::convert()` 作为 fallback,或在 GPU 上通过 shader 做格式转换 |
|
|
||||||
| OFX 插件兼容性 | 大部分 OFX 插件支持 `kOfxBitDepthFloat`,但仍有少数可能只支持 U8/U16 | `PluginRenderer` 已有格式转换路径,F32 的支持比 F16 更成熟 |
|
|
||||||
| 性能回归 | YUV→RGB 原来在 GPU 走 shader,若强制先转 RGBAF32 再上传,可能需要调整流程 | YUV 素材仍保留 GPU shader 转换路径,只是 shader 输出目标 texture 格式改为 F32(OpenGL 已支持 `GL_RGBA32F` 作为 render target) |
|
|
||||||
| 缓存文件体积翻倍 | 帧缓存从 U8/U16 改为 F32 后,磁盘缓存体积增大 | 可接受;必要时调整缓存策略或压缩 |
|
|
||||||
|
|
||||||
## 6. 建议的实施顺序
|
|
||||||
|
|
||||||
1. **第一阶段:** 修复 `FFmpegDecoder` F32 映射 + 增加 RGBAF32 直接上传分支,编写解码器单元测试
|
|
||||||
2. **第二阶段:** 在 `OIIODecoder` 添加强制 F32 转换
|
|
||||||
3. **第三阶段:** 锁定内部工作格式为 F32,验证节点图全链路
|
|
||||||
4. **第四阶段:** 验证导出编码路径,确认 filter graph 对 RGBAF32 的处理
|
|
||||||
5. **第五阶段:** 性能测试与回归测试
|
|
||||||
|
|
||||||
## 7. 决策点
|
|
||||||
|
|
||||||
- 是否保留 `OfflinePixelFormat = F16` 的代理降级机制?还是连 proxy 也强制 F32?
|
|
||||||
- 若保留代理降级,是否需要在解码器层根据 online/offline 模式选择输出格式?
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
Oak Video Editor 项目结构概览(中文)
|
|
||||||
==========================
|
|
||||||
|
|
||||||
这份文档是基于当前仓库目录组织的快速导航,便于后续查找代码位置。
|
|
||||||
|
|
||||||
顶层目录
|
|
||||||
--------
|
|
||||||
- app: 主应用源码入口,涵盖核心、渲染、UI、插件、节点系统等。
|
|
||||||
- cmake: CMake 相关脚本与模块。
|
|
||||||
- docker: 构建/运行相关的容器配置。
|
|
||||||
- docs: 项目文档(你现在正在看的位置)。
|
|
||||||
- ext: 可能包含外部依赖或子模块(按需查看)。
|
|
||||||
- tests: 测试代码与用例。
|
|
||||||
- third_party: 第三方库及其源码(如 OpenFX HostSupport)。
|
|
||||||
- build、cmake-build-debug、test_compile: 构建产物或构建目录(通常不需要手动改)。
|
|
||||||
|
|
||||||
app 目录(核心模块)
|
|
||||||
-------------------
|
|
||||||
- app/core.*: 应用核心入口、初始化流程。
|
|
||||||
- app/main.cpp: 程序入口点。
|
|
||||||
- app/version.*: 版本信息与构建元数据。
|
|
||||||
- app/common: 通用基础设施与工具类(日志、路径、字符串等)。
|
|
||||||
- app/config: 配置加载与项目设置。
|
|
||||||
- app/render: 渲染子系统(帧缓存、渲染管线、插件渲染桥接等)。
|
|
||||||
- app/node: 节点系统,节点类型与图结构的核心逻辑。
|
|
||||||
- app/widget: UI 控件与节点视图(节点图、参数面板等)。
|
|
||||||
- app/panel: UI 面板组织与管理。
|
|
||||||
- app/window: 窗口与主界面。
|
|
||||||
- app/timeline: 时间线与剪辑管理。
|
|
||||||
- app/tool: 交互工具(选择、裁剪等)。
|
|
||||||
- app/undo: 撤销/重做系统。
|
|
||||||
- app/task: 异步任务与后台作业。
|
|
||||||
- app/audio: 音频处理与播放。
|
|
||||||
- app/codec: 编解码相关支持。
|
|
||||||
- app/shaders: 渲染着色器资源。
|
|
||||||
- app/ts: 时间/时间轴相关通用类型。
|
|
||||||
- app/dialog: 对话框与提示类 UI。
|
|
||||||
- app/cli: 命令行工具入口或相关实现。
|
|
||||||
- app/pluginSupport: OpenFX 插件 Host 侧实现(Clip/Image/Param/Host/PluginInstance 等)。
|
|
||||||
- app/packaging: 打包或发布相关逻辑。
|
|
||||||
|
|
||||||
重点文件索引(按模块)
|
|
||||||
----------------------
|
|
||||||
下面列的是“常用/核心入口”文件,不是完整清单,但足够定位主要流程。
|
|
||||||
|
|
||||||
核心入口与全局
|
|
||||||
-------------
|
|
||||||
- app/main.cpp: 程序入口。
|
|
||||||
- app/core.h、app/core.cpp: 应用生命周期与初始化总控。
|
|
||||||
- app/version.h、app/version.cpp: 版本与构建信息。
|
|
||||||
|
|
||||||
渲染系统
|
|
||||||
--------
|
|
||||||
- app/render/renderer.h、app/render/renderer.cpp: 渲染主调度。
|
|
||||||
- app/render/rendermanager.h、app/render/rendermanager.cpp: 渲染队列与任务管理。
|
|
||||||
- app/render/renderticket.h、app/render/renderticket.cpp: 单次渲染请求。
|
|
||||||
- app/render/renderprocessor.h、app/render/renderprocessor.cpp: 渲染处理管线。
|
|
||||||
- app/render/texture.h、app/render/texture.cpp: 纹理/帧数据容器。
|
|
||||||
- app/render/videoparams.h、app/render/videoparams.cpp: 视频格式参数。
|
|
||||||
- app/render/job/pluginjob.h、app/render/job/pluginjob.cpp: 插件渲染作业。
|
|
||||||
- app/render/plugin/pluginrenderer.h、app/render/plugin/pluginrenderer.cpp: OpenFX 插件渲染桥接。
|
|
||||||
|
|
||||||
节点系统
|
|
||||||
--------
|
|
||||||
- app/node/node.h、app/node/node.cpp: 节点基类与生命周期。
|
|
||||||
- app/node/param.h、app/node/param.cpp: 节点参数与动画/关键帧。
|
|
||||||
- app/node/value.h、app/node/value.cpp: 节点值与运行时数据。
|
|
||||||
- app/node/factory.h、app/node/factory.cpp: 节点注册与创建。
|
|
||||||
- app/node/traverser.h、app/node/traverser.cpp: 图遍历与求值。
|
|
||||||
- app/node/plugins/Plugin.h、app/node/plugins/Plugin.cpp: OpenFX 插件节点。
|
|
||||||
|
|
||||||
OpenFX Host 侧实现
|
|
||||||
------------------
|
|
||||||
- app/pluginSupport/OliveHost.h、app/pluginSupport/OliveHost.cpp: OpenFX Host 入口与消息接口。
|
|
||||||
- app/pluginSupport/OlivePluginInstance.h、app/pluginSupport/OlivePluginInstance.cpp: 插件实例生命周期与参数管理。
|
|
||||||
- app/pluginSupport/OliveClip.h、app/pluginSupport/OliveClip.cpp: Clip 实例与图像读写桥接。
|
|
||||||
- app/pluginSupport/image.h、app/pluginSupport/image.cpp: OpenFX Image 封装与数据映射。
|
|
||||||
- app/pluginSupport/paraminstance.h、app/pluginSupport/paraminstance.cpp: 参数实例实现。
|
|
||||||
- third_party/openfx/HostSupport/include/ofxhImageEffect.h: HostSupport 核心接口。
|
|
||||||
|
|
||||||
节点 UI(Node View)
|
|
||||||
-------------------
|
|
||||||
- app/widget/nodeview/nodeview.h、app/widget/nodeview/nodeview.cpp: 节点视图主控。
|
|
||||||
- app/widget/nodeview/nodeviewitem.h、app/widget/nodeview/nodeviewitem.cpp: 节点渲染与交互。
|
|
||||||
- app/widget/nodeview/nodeviewscene.h、app/widget/nodeview/nodeviewscene.cpp: QGraphicsScene 逻辑。
|
|
||||||
- app/widget/nodeview/nodeviewedge.h、app/widget/nodeview/nodeviewedge.cpp: 连线显示。
|
|
||||||
|
|
||||||
参数 UI(Param View)
|
|
||||||
--------------------
|
|
||||||
- app/widget/nodeparamview/nodeparamview.h、app/widget/nodeparamview/nodeparamview.cpp: 参数面板主控。
|
|
||||||
- app/widget/nodeparamview/nodeparamviewitem.h、app/widget/nodeparamview/nodeparamviewitem.cpp: 参数项容器与布局。
|
|
||||||
- app/widget/nodeparamview/nodeparamviewwidgetbridge.h、app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp: 参数类型到控件的桥接。
|
|
||||||
- app/widget/nodeparamview/nodeparamviewtextedit.h、app/widget/nodeparamview/nodeparamviewtextedit.cpp: 多行文本参数控件。
|
|
||||||
|
|
||||||
面板与窗口
|
|
||||||
----------
|
|
||||||
- app/panel/panelmanager.h、app/panel/panelmanager.cpp: 面板管理器与切换逻辑。
|
|
||||||
- app/panel/timebased/timebased.h、app/panel/timebased/timebased.cpp: 时间基面板基类(时间轴/视图共享逻辑)。
|
|
||||||
- app/panel/node/node.h、app/panel/node/node.cpp: 节点面板入口。
|
|
||||||
- app/panel/param/param.h、app/panel/param/param.cpp: 参数面板入口。
|
|
||||||
- app/window: 主窗口与窗口级 UI 结构。
|
|
||||||
|
|
||||||
时间线/播放核心
|
|
||||||
--------------
|
|
||||||
- app/node/output/viewer/viewer.h、app/node/output/viewer/viewer.cpp: Viewer 输出节点(播放头/长度/渲染请求)。
|
|
||||||
- app/widget/viewer/viewer.h、app/widget/viewer/viewer.cpp: Viewer 面板与播放控制。
|
|
||||||
- app/widget/timelinewidget/timelinewidget.h、app/widget/timelinewidget/timelinewidget.cpp: 时间线 UI 与交互主控。
|
|
||||||
|
|
||||||
进度与任务 UI
|
|
||||||
------------
|
|
||||||
- app/dialog/progress/progress.h、app/dialog/progress/progress.cpp: 通用进度对话框。
|
|
||||||
- app/widget/taskview/taskviewitem.h、app/widget/taskview/taskviewitem.cpp: 任务进度条展示。
|
|
||||||
|
|
||||||
撤销/编辑分组
|
|
||||||
------------
|
|
||||||
- app/undo/undocommand.h、app/undo/undocommand.cpp: UndoCommand 与 MultiUndoCommand 的基础实现。
|
|
||||||
- app/undo/undostack.h、app/undo/undostack.cpp: 撤销栈(无原生“批量编辑”接口)。
|
|
||||||
- app/pluginSupport/OlivePluginInstance.h、app/pluginSupport/OlivePluginInstance.cpp: OpenFX editBegin/editEnd 触发时创建批量撤销分组。
|
|
||||||
- app/pluginSupport/OlivePluginInstance.cpp: DeferredRedoCommand 包装已应用的命令,避免批量 push 时重复执行。
|
|
||||||
- app/pluginSupport/paraminstance.h、app/pluginSupport/paraminstance.cpp: 参数 Set 走统一的 SubmitUndoCommand 接口,支持批量合并。
|
|
||||||
|
|
||||||
与 OpenFX 相关的主要位置
|
|
||||||
-----------------------
|
|
||||||
- app/pluginSupport: OpenFX HostSupport 的封装与 Olive 侧实现。
|
|
||||||
- app/render/plugin: 插件渲染调度与帧处理逻辑。
|
|
||||||
- app/node/plugins: 插件节点定义与 UI 参数桥接。
|
|
||||||
- third_party/openfx: OpenFX HostSupport 源码与接口头文件。
|
|
||||||
|
|
||||||
构建与配置
|
|
||||||
----------
|
|
||||||
- CMakeLists.txt: 根构建配置入口。
|
|
||||||
- cmake/: 自定义 CMake 模块与工具链脚本。
|
|
||||||
|
|
||||||
其他说明
|
|
||||||
--------
|
|
||||||
- README.md: 项目整体说明与开发入口。
|
|
||||||
- TODO-zh.md: OpenFX 支持的中文 TODO 说明。
|
|
||||||
|
|
||||||
流程图/调用关系(ASCII)
|
|
||||||
-----------------------
|
|
||||||
OpenFX 插件渲染主流程(逻辑简化):
|
|
||||||
```
|
|
||||||
Node(Graph)
|
|
||||||
-> app/node/plugins/Plugin.cpp
|
|
||||||
-> app/render/plugin/pluginrenderer.cpp
|
|
||||||
-> app/pluginSupport/OlivePluginInstance.cpp
|
|
||||||
-> app/pluginSupport/OliveClip.cpp
|
|
||||||
-> app/pluginSupport/image.cpp
|
|
||||||
-> app/render/texture.cpp / AVFrame 映射
|
|
||||||
```
|
|
||||||
|
|
||||||
OpenFX 参数 UI 生成流程(逻辑简化):
|
|
||||||
```
|
|
||||||
OFX Param Descriptor
|
|
||||||
-> app/pluginSupport/OlivePluginInstance.cpp (newParam)
|
|
||||||
-> app/node/plugins/Plugin.cpp (Node Input 生成)
|
|
||||||
-> app/widget/nodeparamview/nodeparamview.cpp
|
|
||||||
-> app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp (控件桥接)
|
|
||||||
```
|
|
||||||
|
|
||||||
插件消息展示流程(逻辑简化):
|
|
||||||
```
|
|
||||||
OFX Host Message
|
|
||||||
-> app/pluginSupport/OliveHost.cpp (保存消息)
|
|
||||||
-> app/pluginSupport/OlivePluginInstance.cpp (发出消息数量变化)
|
|
||||||
-> app/widget/nodeview/nodeviewitem.cpp (节点右上角徽标)
|
|
||||||
-> app/widget/nodeparamview/nodeparamviewitem.cpp (面板顶部消息)
|
|
||||||
```
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# Oak Video Editor 测试策略与计划
|
|
||||||
|
|
||||||
本文档描述 Oak Video Editor 的自动化测试策略,包括单元测试、集成测试以及 CI 执行方式。
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
- 尽量自动化,减少人工测试。
|
|
||||||
- 覆盖所有模块(至少一个自动化测试)。
|
|
||||||
- 集成测试保持无 GUI(头less)。
|
|
||||||
- 在 Windows/macOS/Linux 上可重复运行。
|
|
||||||
|
|
||||||
## 测试层级
|
|
||||||
|
|
||||||
### 1) 单元测试(GoogleTest)
|
|
||||||
- 目标:小范围、确定性、无 GUI。
|
|
||||||
- 目录:`tests/gtest/`。
|
|
||||||
- 执行:`ctest` 里的 `olive-gtest`。
|
|
||||||
|
|
||||||
### 1.5) 模块冒烟测试(GoogleTest)
|
|
||||||
- 目标:对 GUI 相关模块做编译期/链接期覆盖,不实例化控件。
|
|
||||||
- 目录:`tests/gtest/module_smoke_test.cpp`。
|
|
||||||
- 执行:`ctest` 里的 `olive-gtest`。
|
|
||||||
|
|
||||||
### 2) 集成测试(GoogleTest)
|
|
||||||
- 目标:跨模块流程但不依赖 GUI(例如序列化→反序列化)。
|
|
||||||
- 目录:`tests/gtest/`(如 `ProjectSerializer`、`TaskManager`)。
|
|
||||||
|
|
||||||
### 3) 现有测试(Olive 宏测试)
|
|
||||||
- 目录:`tests/general`、`tests/timeline`、`tests/compositing` 保持不变。
|
|
||||||
|
|
||||||
## 模块覆盖映射
|
|
||||||
|
|
||||||
每个顶层模块至少有一个测试用例。
|
|
||||||
|
|
||||||
- `app/common`:`common_current_test.cpp`、`common_xmlutils_test.cpp`
|
|
||||||
- `app/config`:`config_test.cpp`
|
|
||||||
- `app/node`:`node_value_test.cpp`、`node_keyframe_test.cpp`、`node_serialization_test.cpp`
|
|
||||||
- `app/node/project/serializer`:`project_serializer_test.cpp`
|
|
||||||
- `app/render`:`render_videoparams_test.cpp`、`render_audioparams_test.cpp`
|
|
||||||
- `app/timeline`:`timeline_marker_test.cpp`
|
|
||||||
- `app/undo`:`undo_stack_test.cpp`
|
|
||||||
- `app/task`:`task_taskmanager_test.cpp`
|
|
||||||
- `app/codec`:`codec_frame_test.cpp`
|
|
||||||
- `app/pluginSupport`:`plugin_support_test.cpp`
|
|
||||||
- `app/audio`、`app/cli`、`app/dialog`、`app/panel`、`app/tool`、`app/ui`、`app/widget`、`app/window`:`module_smoke_test.cpp`
|
|
||||||
|
|
||||||
若模块包含 GUI 依赖,则测试聚焦于其非可视逻辑/数据结构。
|
|
||||||
|
|
||||||
## 集成测试说明
|
|
||||||
|
|
||||||
### 项目序列化回归
|
|
||||||
- 创建最小项目并添加内置节点。
|
|
||||||
- 使用 `ProjectSerializer::Save` 写出 XML。
|
|
||||||
- 再用 `ProjectSerializer::Load` 读回。
|
|
||||||
- 验证节点恢复。
|
|
||||||
|
|
||||||
### 任务管理器执行
|
|
||||||
- 向 `TaskManager` 添加一个 DummyTask。
|
|
||||||
- 使用事件循环等待完成。
|
|
||||||
- 验证任务确实执行。
|
|
||||||
|
|
||||||
## 单元覆盖重点(已扩展)
|
|
||||||
|
|
||||||
- `app/undo`:`undo_stack_test.cpp` 覆盖空栈状态、模型数据、redo 区域颜色、jump 行为、空 MultiUndoCommand 忽略逻辑。
|
|
||||||
- `app/timeline`:`timeline_marker_test.cpp` 覆盖列表排序、最近 marker 查询、含未知元素的保存/加载、marker 增删改命令。
|
|
||||||
- `app/pluginSupport`:`plugin_support_image_test.cpp` 覆盖 OFX 属性映射(bounds/ROD、像素深度、通道、预乘)及分配/清理行为。
|
|
||||||
- `app/render`:`render_videoparams_branch_test.cpp` 覆盖自动 divider、像素宽高比校验、方形像素宽度、Save/Load 回归。
|
|
||||||
|
|
||||||
## 无 GUI 运行
|
|
||||||
|
|
||||||
- 测试避免使用 QWidget。
|
|
||||||
- CI 中设置 `QT_QPA_PLATFORM=offscreen` 防止 GUI 初始化问题。
|
|
||||||
|
|
||||||
## 持续集成
|
|
||||||
|
|
||||||
CI 在 Windows/macOS/Linux 上执行:
|
|
||||||
|
|
||||||
1. 安装依赖(Qt、FFmpeg、OpenImageIO、OpenColorIO、OpenEXR、PortAudio、Expat)。
|
|
||||||
2. `-DBUILD_TESTS=ON` 配置。
|
|
||||||
3. 使用 CMake + Ninja 构建。
|
|
||||||
4. 运行 `ctest` 输出失败信息。
|
|
||||||
|
|
||||||
### 依赖安装说明
|
|
||||||
- Linux:优先使用发行版系统包(Ubuntu 上用 `apt`)安装 Qt6、FFmpeg、OpenImageIO、OpenColorIO、OpenEXR、PortAudio、Expat、OpenGL 头文件。
|
|
||||||
- macOS:使用 Homebrew 安装 Qt6 和图像/色彩/多媒体相关库。
|
|
||||||
- Windows:尽量使用系统安装器(Qt 通过 `install-qt-action`),其余 C/C++ 库通过 vcpkg 安装。
|
|
||||||
|
|
||||||
## 新增测试规范
|
|
||||||
|
|
||||||
- 新测试放在 `tests/gtest`。
|
|
||||||
- 使用 GoogleTest 规范。
|
|
||||||
- 尽量保持确定性与无外部依赖。
|
|
||||||
- 新模块至少增加 1 个单元测试 + 1 个集成场景(可合并)。
|
|
||||||
-1887
File diff suppressed because it is too large
Load Diff
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "oak-video-editor",
|
|
||||||
"version-string": "0.0.0",
|
|
||||||
"dependencies": [
|
|
||||||
"ffmpeg",
|
|
||||||
"openimageio",
|
|
||||||
"opencolorio",
|
|
||||||
"openexr",
|
|
||||||
"expat",
|
|
||||||
"portaudio"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user