16 KiB
16 KiB
libolivenode.so — 节点图系统
依赖:
libolivecore.so,libolivecodec.so
外部依赖:Qt::Core(QObject, QString, XML)
包含源码:app/node/,app/timeline/,app/undo/,app/config/
当前状态:整个项目的核心枢纽,被render/,widget/,panel/,task/等几乎所有上层模块依赖
改造难度:⭐⭐⭐⭐⭐(最困难,耦合最深)
1. 当前状态分析
app/node/ 是整个编辑器的数据与计算模型核心,采用节点图范式组织所有处理。其特点是:
- 被几乎所有模块依赖:
render/遍历节点图,widget/绘制节点连接,panel/包装节点编辑器,task/在导出时读取节点图。 - 头文件耦合严重:
Node.h直接#include了codec/frame.h和render/下的多个头文件(缓存类型、作业类型等)。 - Qt 深度集成:
Node继承QObject,使用信号槽、元对象系统、QVariant。 - 序列化内建:
ProjectSerializer支持将节点图保存/加载为 XML。
1.1 关键耦合点与解耦策略
| 耦合点 | 当前状态 | 解耦策略 |
|---|---|---|
Node.h 包含 render/rendercache.h |
Node 直接操作 FrameHashCache |
将缓存失效抽象为虚函数 InvalidateCache(),或注入 NodeCacheCallbacks 接口指针。移除 rendercache.h 的包含。 |
Node.h 包含 render/job/*.h |
Node::ProcessShader() 等虚函数使用具体 Job 类型 |
将 ProcessShader 等改为接受 const void* job_data + JobType 枚举,内部再 static_cast。或前向声明 Job 类(若已是不透明指针)。 |
Footage(node/project/footage/)依赖 Decoder |
Footage 需要解码器信息预览 |
保留此依赖,libolivenode.so 链接 libolivecodec.so 是合理的。 |
ViewerOutput 被 UI 直接引用 |
ViewerOutput 是节点图与 UI 的桥梁 |
ViewerOutput 保留在 node/ 中,C API 暴露 OliveViewerOutput* 句柄。 |
timeline/ 依赖 node/ |
TimelineMarker 等引用 Node |
timeline/ 并入 libolivenode.so,不单独拆分。 |
undo/ 依赖 node/ |
UndoCommand 操作 Node 对象 |
undo/ 并入 libolivenode.so。 |
2. C API 设计
2.1 头文件:c_api/include/olive/node_api.h
#ifndef OLIVE_NODE_API_H
#define OLIVE_NODE_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include "core_api.h"
#define OLIVE_NODE_API_VERSION 1
#ifdef OLIVE_BUILDING_NODE
# define OLIVE_NODE_API __attribute__((visibility("default")))
#else
# define OLIVE_NODE_API
#endif
/* ========== 不透明类型 ========== */
typedef struct OliveNodeGraph OliveNodeGraph;
typedef struct OliveNode OliveNode;
typedef struct OliveNodeInput OliveNodeInput;
typedef struct OliveNodeOutput OliveNodeOutput;
typedef struct OliveParam OliveParam;
typedef struct OliveKeyframe OliveKeyframe;
typedef struct OliveProject OliveProject;
typedef struct OliveSequence OliveSequence;
typedef struct OliveTrack OliveTrack;
typedef struct OliveClip OliveClip;
typedef struct OliveViewerOutput OliveViewerOutput;
/* ========== 枚举 ========== */
typedef enum {
OLIVE_NODE_TYPE_UNKNOWN = 0,
OLIVE_NODE_TYPE_INPUT,
OLIVE_NODE_TYPE_OUTPUT,
OLIVE_NODE_TYPE_FILTER,
OLIVE_NODE_TYPE_DISTORT,
OLIVE_NODE_TYPE_GENERATOR,
OLIVE_NODE_TYPE_COLOR,
OLIVE_NODE_TYPE_AUDIO,
OLIVE_NODE_TYPE_TRANSITION,
OLIVE_NODE_TYPE_PLUGIN,
OLIVE_NODE_TYPE_GROUP,
} OliveNodeType;
typedef enum {
OLIVE_PARAM_TYPE_INT = 0,
OLIVE_PARAM_TYPE_DOUBLE,
OLIVE_PARAM_TYPE_STRING,
OLIVE_PARAM_TYPE_RATIONAL,
OLIVE_PARAM_TYPE_COLOR,
OLIVE_PARAM_TYPE_BOOL,
OLIVE_PARAM_TYPE_VECTOR2,
OLIVE_PARAM_TYPE_VECTOR3,
OLIVE_PARAM_TYPE_VECTOR4,
} OliveParamType;
/* ========== API 版本 ========== */
OLIVE_NODE_API int olive_node_api_version(void);
/* ========== NodeGraph ========== */
OLIVE_NODE_API OliveNodeGraph* olive_node_graph_create(void);
OLIVE_NODE_API void olive_node_graph_destroy(OliveNodeGraph* g);
// 序列化
OLIVE_NODE_API int olive_node_graph_load_xml(OliveNodeGraph* g,
const char* xml_data,
size_t xml_len);
OLIVE_NODE_API char* olive_node_graph_save_xml(OliveNodeGraph* g,
size_t* out_len);
// 节点增删查
OLIVE_NODE_API OliveNode* olive_node_graph_add_node(OliveNodeGraph* g,
const char* node_type_id,
const char* node_id);
OLIVE_NODE_API int olive_node_graph_remove_node(OliveNodeGraph* g, OliveNode* node);
OLIVE_NODE_API OliveNode* olive_node_graph_find_node(OliveNodeGraph* g,
const char* node_id);
OLIVE_NODE_API int olive_node_graph_node_count(OliveNodeGraph* g);
OLIVE_NODE_API OliveNode* olive_node_graph_get_node(OliveNodeGraph* g, int index);
// 连接管理
OLIVE_NODE_API int olive_node_connect(OliveNode* from_node,
int output_index,
OliveNode* to_node,
int input_index);
OLIVE_NODE_API int olive_node_disconnect(OliveNode* node, int input_index);
OLIVE_NODE_API OliveNode* olive_node_get_connected_node(OliveNode* node,
int input_index);
/* ========== Node 属性 ========== */
OLIVE_NODE_API const char* olive_node_get_id(OliveNode* node);
OLIVE_NODE_API const char* olive_node_get_label(OliveNode* node);
OLIVE_NODE_API OliveNodeType olive_node_get_type(OliveNode* node);
OLIVE_NODE_API const char* olive_node_get_type_id(OliveNode* node);
OLIVE_NODE_API int olive_node_input_count(OliveNode* node);
OLIVE_NODE_API int olive_node_output_count(OliveNode* node);
/* ========== Param 操作 ========== */
OLIVE_NODE_API int olive_node_param_count(OliveNode* node);
OLIVE_NODE_API OliveParam* olive_node_get_param(OliveNode* node, int index);
OLIVE_NODE_API OliveParam* olive_node_find_param(OliveNode* node,
const char* param_name);
OLIVE_NODE_API const char* olive_param_get_name(OliveParam* param);
OLIVE_NODE_API OliveParamType olive_param_get_type(OliveParam* param);
OLIVE_NODE_API int olive_param_set_int(OliveParam* param, int64_t value);
OLIVE_NODE_API int olive_param_set_double(OliveParam* param, double value);
OLIVE_NODE_API int olive_param_set_rational(OliveParam* param, OliveRational value);
OLIVE_NODE_API int olive_param_set_color(OliveParam* param, OliveColor value);
OLIVE_NODE_API int olive_param_set_string(OliveParam* param, const char* value);
OLIVE_NODE_API int64_t olive_param_get_int(OliveParam* param);
OLIVE_NODE_API double olive_param_get_double(OliveParam* param);
OLIVE_NODE_API OliveRational olive_param_get_rational(OliveParam* param);
OLIVE_NODE_API OliveColor olive_param_get_color(OliveParam* param);
/* ========== Keyframe ========== */
OLIVE_NODE_API int olive_param_add_keyframe(OliveParam* param,
OliveRational time,
double value);
OLIVE_NODE_API int olive_param_remove_keyframe(OliveParam* param,
OliveRational time);
OLIVE_NODE_API int olive_param_keyframe_count(OliveParam* param);
/* ========== Project ========== */
OLIVE_NODE_API OliveProject* olive_project_create(const char* name);
OLIVE_NODE_API void olive_project_destroy(OliveProject* proj);
OLIVE_NODE_API int olive_project_load_file(OliveProject* proj, const char* filename);
OLIVE_NODE_API int olive_project_save_file(OliveProject* proj, const char* filename);
OLIVE_NODE_API OliveNodeGraph* olive_project_get_graph(OliveProject* proj);
/* ========== Sequence / Timeline ========== */
OLIVE_NODE_API OliveSequence* olive_sequence_create(const char* name,
OliveVideoParams vparams,
OliveAudioParams aparams);
OLIVE_NODE_API OliveViewerOutput* olive_sequence_get_viewer_output(OliveSequence* seq);
/* ========== ViewerOutput(渲染目标) ========== */
OLIVE_NODE_API const char* olive_viewer_output_get_node_id(OliveViewerOutput* viewer);
OLIVE_NODE_API OliveVideoParams olive_viewer_output_get_video_params(OliveViewerOutput* viewer);
OLIVE_NODE_API OliveAudioParams olive_viewer_output_get_audio_params(OliveViewerOutput* viewer);
/* ========== Undo ========== */
typedef struct OliveUndoStack OliveUndoStack;
OLIVE_NODE_API OliveUndoStack* olive_undo_stack_create(void);
OLIVE_NODE_API void olive_undo_stack_destroy(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_push(OliveUndoStack* stack,
const char* action_name,
void* undo_data,
void (*undo_fn)(void*),
void (*redo_fn)(void*),
void (*free_fn)(void*));
OLIVE_NODE_API int olive_undo_stack_can_undo(OliveUndoStack* stack);
OLIVE_NODE_API int olive_undo_stack_can_redo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_undo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_redo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_clear(OliveUndoStack* stack);
#ifdef __cplusplus
}
#endif
#endif // OLIVE_NODE_API_H
2.2 关键解耦实现:NodeCacheCallbacks
// app/node/node.h(改造后,移除 render/ 头文件包含)
// 前向声明
class NodeCacheCallbacks;
class Node : public QObject {
// ...
void SetCacheCallbacks(NodeCacheCallbacks* callbacks);
protected:
virtual void InvalidateCacheInternal(const TimeRange& range);
private:
NodeCacheCallbacks* cache_callbacks_ = nullptr;
};
// app/node/nodecachecallbacks.h(新增)
class NodeCacheCallbacks {
public:
virtual ~NodeCacheCallbacks() = default;
virtual void InvalidateCache(const QString& cache_id, const TimeRange& range) = 0;
virtual void InvalidateAllCaches() = 0;
};
RenderManager(在 liboliverender.so 中)实现 NodeCacheCallbacks,并在创建节点时注入:
class RenderCacheCallbacks : public NodeCacheCallbacks {
void InvalidateCache(const QString& cache_id, const TimeRange& range) override {
// 原有 FrameHashCache 的失效逻辑
}
// ...
};
这样 Node.h 不再需要包含 render/rendercache.h,编译期依赖被打破。
2.3 关键解耦实现:RenderJob 虚函数参数抽象
当前 Node 有虚函数:
// 改造前
virtual void ProcessShader(TexturePtr destination, const Node* node, const ShaderJob* job);
改造后:
// app/node/jobtypes.h(新增,只含枚举和基类,无 render/ 依赖)
enum class NodeJobType {
kShader,
kGenerate,
kFootage,
kColorTransform,
kSample,
kCache,
};
struct NodeJobData {
NodeJobType type;
void* data; // 实际数据由 render/ 中的具体类解释
};
// app/node/node.h
virtual void ProcessJob(TexturePtr destination, const NodeJobData& job);
RenderProcessor(在 liboliverender.so 中)调用时:
ShaderJob job = ...;
NodeJobData data{NodeJobType::kShader, &job};
node->ProcessJob(destination, data);
3. CMake 改造
# app/node/CMakeLists.txt
set(NODE_INTERNAL_SOURCES
node.cpp node.h
traverser.cpp traverser.h
traverserproxy.cpp traverserproxy.h
nodevalue.cpp nodevalue.h
# ... 所有 node/ 子目录源文件
)
set(TIMELINE_SOURCES
../timeline/timelinecoordinate.cpp ../timeline/timelinecoordinate.h
../timeline/timelinemarker.cpp ../timeline/timelinemarker.h
../timeline/timelineworkarea.cpp ../timeline/timelineworkarea.h
../timeline/undo/*.cpp ../timeline/undo/*.h
)
set(UNDO_SOURCES
../undo/undocommand.cpp ../undo/undocommand.h
../undo/undostack.cpp ../undo/undostack.h
)
set(CONFIG_SOURCES
../config/config.cpp ../config/config.h
)
set(NODE_API_SOURCES
${CMAKE_SOURCE_DIR}/c_api/src/node_api.cpp
)
add_library(olivenode SHARED
${NODE_INTERNAL_SOURCES}
${TIMELINE_SOURCES}
${UNDO_SOURCES}
${CONFIG_SOURCES}
${NODE_API_SOURCES}
)
target_compile_definitions(olivenode PRIVATE OLIVE_BUILDING_NODE)
target_include_directories(olivenode
PRIVATE
${CMAKE_SOURCE_DIR}/app
${CMAKE_SOURCE_DIR}/c_api/include
PUBLIC
$<INSTALL_INTERFACE:include>
)
target_link_libraries(olivenode
PUBLIC
olivecore
olivecodec
Qt${QT_VERSION_MAJOR}::Core
)
set_target_properties(olivenode PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
install(TARGETS olivenode DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/c_api/include/olive/node_api.h DESTINATION include/olive)
4. 小步快跑实施步骤
Step 0: 头文件解耦(3–4 天,最关键)
- 创建
app/node/nodecachecallbacks.h,定义NodeCacheCallbacks接口。 - 修改
Node.h:移除render/rendercache.h包含,添加NodeCacheCallbacks*成员和SetCacheCallbacks()方法。 - 创建
app/node/jobtypes.h,定义NodeJobType枚举和NodeJobData结构体。 - 修改
Node.h:将所有ProcessXxx虚函数合并为ProcessJob(TexturePtr, const NodeJobData&),或保留原签名但将参数类型改为前向声明。 - 修改
RenderProcessor:适配新的NodeCacheCallbacks和NodeJobData。
验收标准:app/node/ 目录可以独立编译,不直接或间接包含 app/render/ 下的任何头文件。
Step 1: 独立编译 libolivenode.so(1 天)
- 将
node/,timeline/,undo/,config/的源文件聚合,构建为olivenode SHARED。 - 处理
node/下的add_subdirectory嵌套,确保所有源文件被正确收集。
验收标准:libolivenode.so 编译成功,nm -D libolivenode.so | grep olive_node 能看到导出的 C 符号。
Step 2: 最小 C API(2 天)
- 先实现项目级接口:
olive_project_create/destroy/load_file/save_fileolive_node_graph_create/destroy/load_xml/save_xml
- 这些接口是渲染子进程最需要的:子进程需要加载 XML 节点图并渲染。
验收标准:可以用 C API 创建一个项目、保存为 XML、再加载回来,内容一致。
Step 3: 节点操作 API(2 天)
- 实现节点增删查改:
add_node,remove_node,find_node,connect,disconnect。 - 实现参数读写:
set_param_double,get_param_double,set_param_rational等。
验收标准:可以用 C API 构建一个简单的节点图(如 Generator -> ViewerOutput),并序列化为 XML。
Step 4: Undo API(1 天)
- 实现
olive_undo_stack_*系列函数。 - C API 的 undo 采用函数指针回调模式,避免暴露 C++ 的
UndoCommand类。
Step 5: ViewerOutput 和 Sequence(1 天)
- 实现
OliveSequence*和OliveViewerOutput*的 C API。 - 这是渲染的入口:渲染子进程需要知道哪个
ViewerOutput是输出目标。
5. 风险与回退
| 风险 | 对策 |
|---|---|
| Node.h 解耦工作量过大,影响面太广 | 分阶段:第一阶段只做"编译期解耦"(移除 include),不改虚函数签名。若仍然困难,允许 libolivenode.so 和 liboliverender.so 暂时合并为 libolive-engine.so,后续再拆分。 |
QObject 信号槽跨动态库 |
Qt 信号槽跨动态库在正确链接 Qt 的情况下工作正常。确保所有含 Q_OBJECT 的类在动态库内被 moc 处理。 |
NodeValueTable 等模板类难以导出 C 接口 |
不在 C API 中暴露模板类。NodeTraverser 的遍历结果(NodeValueTable)在 C++ 内部处理,C API 只提供高阶函数如 olive_node_graph_evaluate_at_time。 |
| 序列化 XML 格式变更 | C API 中的 load_xml/save_xml 直接使用现有的 ProjectSerializer,XML 格式完全不变,向下兼容。 |
Footage 节点持有 Decoder |
Footage 内部持有 DecoderPtr,C API 不暴露 Decoder 细节,只暴露 Footage 的文件路径设置/获取。 |