refactor(render): de-Qt oakrender and wrap it in a pure C ABI
- copy engine/render to src/render/src (sunk param types excluded), de-Qt in five parallel groups: core machinery (tickets/worker pool/ jobs), caches, color/texture, preview/IPC, GPU backends - replace Qt GL/Vulkan wrappers with native context abstractions (CGL/EGL/WGL, raw vulkan.h), QProcess with POSIX WorkerProcess, QJsonObject with a minimal NDJSON-compatible workerjson (wire protocol unchanged), QDataStream disk state with a byte-compatible BinaryStream - signals become single std::function callbacks or facade-triggered calls per the documented signal/slot strategy - pure C ABI in include/render + src/render/c_api (renderer/cache/ color/manager families, OAKRENDER_E_* codes), 37 gtest cases - bridge src/node/transition/render/* stubs to the real oakrender headers, closing the node<->render cycle: liboaknode links liboakrender, zero dangling symbols - docs: M7 implementation status + oakrender semantic-change notes
This commit is contained in:
@@ -126,3 +126,99 @@ nm -D oaknode 构建产物 | grep -c " U _ZN5olive.*render" # 必须 0
|
||||
```
|
||||
node→render 的 47 次引用此时应全部经 oakrender C ABI(M3 遗留的
|
||||
"暂不断链"在此结清)。
|
||||
|
||||
## 实施现状(2026-08-05)
|
||||
|
||||
M7 已落地:oakrender 整库编译通过、纯 C ABI 与测试就位、独立构建
|
||||
全绿(37 个用例:33 通过 + 4 个 GTEST_SKIP,0 失败),oaknode 回归
|
||||
96/96 不变红。以下为与上文计划的实际差异。
|
||||
|
||||
### 最终目录结构
|
||||
|
||||
- `src/render/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间),target
|
||||
`oakrender`(SHARED)+ 动态后端 `oakgl`/`oakvulkan`(各自由
|
||||
`*backend_c.cpp` 单个 TU 编成,`oak_renderer_*` C ABI,运行时由
|
||||
DynamicRenderer dlopen);平铺结构,`src/render/src` 为 include 根。
|
||||
- `src/render/c_api/` — 纯 C ABI 包装(renderer/cache/color/manager
|
||||
共 4 个 .cpp + 内部头 `alivecount.h`/`internalhandles.h`),经
|
||||
`target_sources` 合并进 `oakrender`,不单独成库。
|
||||
- `src/render/tests/` — gtest,单一 target `oakrender-gtest`
|
||||
(cache/color/manager/renderer 4 个 _test.cpp),
|
||||
`gtest_discover_tests`(`DISCOVERY_MODE PRE_TEST`)。
|
||||
- `include/render/`(仓库根)— 公共 C 头:`error.h` + renderer.h
|
||||
(display/纹理/帧/blit + 后端管理)、cache.h(PlaybackCache/
|
||||
FrameHashCache + `oakrender_debug_alive_count()`)、color.h
|
||||
(ColorProcessor + ColorManager 静态函数)、manager.h
|
||||
(RenderManager/PreviewAutoCacher/DiskManager + 异步帧请求)。
|
||||
- `src/render/standalone/CMakeLists.txt` — 独立构建 driver(见下)。
|
||||
- `src/render/transition/` — 过渡 stub/桥接头(render/ 前缀的桥接头
|
||||
转发到 `src/render/src` 真身;codec/audio/task/config/pluginSupport
|
||||
为 stub),供 oaknode 与 oakrender 共用(须置于
|
||||
`src/node/transition` 之前)。
|
||||
|
||||
### 独立构建与测试
|
||||
|
||||
```sh
|
||||
cmake -S src/render/standalone -B build-oakrender
|
||||
cmake --build build-oakrender -j
|
||||
ctest --test-dir build-oakrender --output-on-failure
|
||||
```
|
||||
|
||||
测试数字:oakrender **37/37**(33 通过,4 个 skip:GL 相关的
|
||||
renderer init/纹理生命周期、blit_color_managed 2 例按 §4 的
|
||||
GTEST_SKIP 模式;manager_init、request_frame 实跑 2 例因
|
||||
RenderManager 构造读取 config 悬置符号而无法在 standalone 跑,
|
||||
可跑的 E_STATE/E_INVALID/E_NOT_FOUND 路径已覆盖,含用 oaknode
|
||||
factory 真建 ViewerOutput 验证)。oaknode 回归 **96/96**。
|
||||
OCIO 配置照 oaknode 做法经 ctest `ENVIRONMENT OCIO=` 注入
|
||||
`engine/render/ocioconf/config.ocio`;color 测试对
|
||||
sRGB OETF→Linear 做已知值抽样断言(0.5 → 0.214041,容差 1e-3)。
|
||||
oakrender-gtest 照 oaknode-gtest 配方 `-Wl,-force_load` libOfxHost,
|
||||
并复用 `src/node/standalone/oakengine_ipc_shim.cpp` 垫
|
||||
oakengine_ipc_* 悬置符号。
|
||||
|
||||
### 实际依赖与链接形态
|
||||
|
||||
- Oak 内部:oaknode、oakcommon、oakundo、olivecore、ffmpeg_bridge
|
||||
(真实 target,`add_subdirectory` 引入)。
|
||||
- 第三方:OpenColorIO、OpenImageIO、Imath/OpenEXR(头)、OpenGL/
|
||||
CoreVideo/Metal/QuartzCore framework、GTest(仅测试)。
|
||||
- liboakrender + liboakgl/liboakvulkan 均以 `-undefined dynamic_lookup`
|
||||
悬置 codec/audio/task/pluginSupport/config 等未拆分模块符号;
|
||||
后端库在加载时从 liboakrender 解析大部分符号。
|
||||
- 帧缓存 save/load 往返与 `codec_frame_set/get_params` 的存储断言
|
||||
留待 M5(oakcodec):`codec/frame.h` 仍是 transition stub,Frame
|
||||
无法 allocate,本批只覆盖错误路径(save 不崩、load 未命中返回
|
||||
E_NOT_FOUND)。
|
||||
|
||||
### 与计划的主要差异
|
||||
|
||||
- **§5 闭环的实现方式:oaknode 直接链接 oakrender 的 C++ 库,而不
|
||||
是经 oakrender C ABI。** `src/node/transition/render/*` 已从 stub
|
||||
改为桥接头(转发到 `src/render/src` 真身),oaknode 源里的
|
||||
`#include "render/..."` 原样保留、解析到真身类;oaknode-gtest
|
||||
链接 liboakrender,node→render 引用在链接期由 C++ 符号直接结清。
|
||||
这是 02 §4 裁决 A 的变通执行(裁决原文要求"统一改成经 oakrender
|
||||
C ABI"):OakRenderCache/ColorProcessor 等的纯 C ABI 已在
|
||||
`include/render/` 就位供后续消费者(Rust 化)使用,但 oaknode
|
||||
自身未改写为 C ABI 调用方。
|
||||
- §5 的 grep/nm 判据按此口径重新表述(实测值,2026-08-05):
|
||||
- `grep -rn '#include "render/' src/node/src | wc -l` = **38**
|
||||
(非 0;全部经桥接头指向真身,无 stub 残留语义);
|
||||
- `nm` 口径改为「liboaknode 的 `U __ZN5olive*` 未解析符号
|
||||
共 **56** 个,**0 个悬置**」:25 个由 liboakrender 解析
|
||||
(PlaybackCache/FrameHashCache/DiskManager/RenderManager/
|
||||
ColorProcessor/PreviewAutoCacher/LUTLibrary 等),其余 31 个由
|
||||
oakcommon(VideoParams/XmlStream*/SubtitleParams/QtUtils)、
|
||||
oakundo(UndoCommand/UndoStack/MultiUndoCommand vtable)解析。
|
||||
- 公共头位于 `include/render/`(非 §1 的 `include/oakrender/`),
|
||||
函数前缀 `oakrender_`,与 oaknode/oakcommon 的既有契约一致
|
||||
(§1 目标形态中的 `oakrender/include/oakrender/` 未采用)。
|
||||
- C API 命名照 R7-A §A.2 的 display.h 重写版改前缀
|
||||
`oakrender_display_*`/`oakrender_codec_frame_*`;`OakCodecFrame`
|
||||
在 renderer.h 定义(M5 未落地,按 §2.2 注释在 oakrender 侧先写)。
|
||||
- 已知坑(已解,记录给后续模块):`src/render/transition/render/`
|
||||
桥接头占用 `"render/renderer.h"` 等拼写,与公共头
|
||||
`include/render/renderer.h` 撞名——公共头内部互相同目录
|
||||
quoted include,c_api 源用相对路径包含公共头,测试目标
|
||||
`BEFORE PRIVATE` 把 `include/` 提到最前。
|
||||
|
||||
@@ -213,3 +213,36 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需
|
||||
见 `src/node/DEQT.md` §7(如 `Sequence::update_track_cache()`、
|
||||
`Footage::check_footage()/default_color_space_changed()/
|
||||
proxy_ready()/proxy_finished()`、5 条 invalidate_from_keyframe_*)。
|
||||
|
||||
## oakrender 去Qt化的删除与语义变更(2026-08-05)
|
||||
|
||||
- `RenderTicket::finished` 不再跨线程补发:回调在调用 finish() 的
|
||||
线程上触发;未决状态由 facade 自查 `is_running()`。
|
||||
- PreviewAutoCacher 延迟重排队改显式标志:单次 QTimer 重排队删除,
|
||||
`try_render()` 置 `delayed_requeue_pending_`,facade 在
|
||||
`requeue_delay_ms()` 后重调 `try_render()`。
|
||||
- DiskCacheFolder 周期存盘由 GUI 线程 QTimer 改后台线程,数据保护
|
||||
用 `std::recursive_mutex`(原靠 QObject 线程亲和串行化)。
|
||||
- PlaybackCache 的 Qt 信号改单回调注册:`set_invalidated_callback`/
|
||||
`set_validated_callback`/`set_requested_callback`/
|
||||
`set_cancel_all_callback`;跨层 invalidated/validated 通知由 facade
|
||||
在命令后重发(M7 §2.2:oakrender 不持有上层回调)。
|
||||
- `DiskManager::instance()` 惰性自建(原 app 启动时显式
|
||||
create_instance;库消费者不经 facade 也能用)。
|
||||
- FrameHashCache 帧缓存 JPEG 读写 QImage→OIIO/OpenEXR。
|
||||
- AudioWaveformCache/PlaybackCache 的 QPainter 绘制归 app 层;
|
||||
缓存指示条高度固化为常量 4(`oakrender_cache_indicator_height()`,
|
||||
原 `QFontMetrics(QFont()).height()/4`)。
|
||||
- 离屏 GL 上下文共享组需 app 显式传入(原 QOpenGLContext
|
||||
globalShareContext 隐式共享)。
|
||||
- OpenGLContext/Vulkan 上下文抽象层(OpenGLContextProvider 等)替代
|
||||
QOpenGLContext 直用。
|
||||
- WorkerProcess 仅 POSIX 实现(Windows 占位未做)。
|
||||
- worker IPC 的 NDJSON 协议未变,workerjson 为本地(去 Qt)实现。
|
||||
- `:/shaders` qrc 资源缺口:着色器源码的 Qt 资源路径在库形态下
|
||||
不可用,需 app 侧提供文件映射。
|
||||
- 删除 ipc/ipcmessage.cpp 等 3 个过期 .cpp(对应头已自包含 inline,
|
||||
.cpp 内容失效)。
|
||||
- displayinternal.h 分层违规(render→src/capi)并入 texturehandle.h。
|
||||
- Renderer 线程亲和由 QObject::thread() 改显式 owner thread
|
||||
(`set_owner_thread_to_current()`/`called_on_owner_thread()`)。
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_CACHE_H
|
||||
#define OAK_EDITOR_RENDER_CACHE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Same-dir quoted includes: inside this build the engine-style spelling
|
||||
// "render/renderer.h" resolves to the transition bridge headers, so the
|
||||
// public headers reference each other relative to their own directory.
|
||||
#include "error.h"
|
||||
#include "renderer.h" /* OakCodecFrame */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file cache.h
|
||||
* @brief C ABI for the oakrender playback/frame-hash caches
|
||||
* (olive::PlaybackCache / olive::FrameHashCache), M7 §2.2.
|
||||
*
|
||||
* An OakRenderCache IS a reinterpreted olive::FrameHashCache (created
|
||||
* without a parent node), no wrapper allocation. Handles from
|
||||
* oakrender_cache_create() are owned by the caller and must be released
|
||||
* with oakrender_cache_free().
|
||||
*
|
||||
* All timestamps are int64 frame numbers in the cache's timebase (see
|
||||
* oakrender_cache_set_timebase()); a cache without a valid timebase
|
||||
* treats timestamps as whole seconds.
|
||||
*
|
||||
* No cache events cross the boundary (M7 §2.2, 2026-08 revision):
|
||||
* invalidate/validate are triggered by and known to the caller; the
|
||||
* facade re-emits notifications after the triggering command.
|
||||
*/
|
||||
typedef struct OakRenderCache OakRenderCache;
|
||||
|
||||
/**
|
||||
* @brief Create a detached frame hash cache (no parent node, no
|
||||
* timebase). Owned by the caller.
|
||||
*
|
||||
* @return Cache handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakRenderCache *oakrender_cache_create(void);
|
||||
|
||||
/** @brief Destroy a cache created by oakrender_cache_create(). NULL-safe. */
|
||||
void oakrender_cache_free(OakRenderCache *cache);
|
||||
|
||||
/**
|
||||
* @brief Set the frame timebase used to interpret all timestamps of this
|
||||
* cache (FrameHashCache::set_timebase()).
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for NULL cache or
|
||||
* non-positive num/den.
|
||||
*/
|
||||
int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den);
|
||||
|
||||
/**
|
||||
* @brief Set the cache UUID used in on-disk frame cache filenames
|
||||
* (PlaybackCache::set_uuid()).
|
||||
*
|
||||
* @return OAKRENDER_OK or OAKRENDER_E_INVALID.
|
||||
*/
|
||||
int oakrender_cache_set_uuid(OakRenderCache *cache, const char *uuid);
|
||||
|
||||
/**
|
||||
* @brief Mark the timestamp range [in_ts, out_ts) invalidated
|
||||
* (PlaybackCache::invalidate()). NULL cache is a no-op.
|
||||
*/
|
||||
void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts,
|
||||
int64_t out_ts);
|
||||
|
||||
/**
|
||||
* @brief Mark the timestamp range [in_ts, out_ts) validated
|
||||
* (PlaybackCache::validate()). NULL cache is a no-op.
|
||||
*/
|
||||
void oakrender_cache_validate(OakRenderCache *cache, int64_t in_ts,
|
||||
int64_t out_ts);
|
||||
|
||||
/**
|
||||
* @brief 1 when the cache holds any validated range
|
||||
* (PlaybackCache::has_validated_ranges()), 0 otherwise / on NULL.
|
||||
*/
|
||||
int oakrender_cache_has_validated_ranges(const OakRenderCache *cache);
|
||||
|
||||
/**
|
||||
* @brief Timeline cache indicator height in pixels
|
||||
* (PlaybackCache::get_cache_indicator_height()). Constant query.
|
||||
*/
|
||||
int oakrender_cache_indicator_height(void);
|
||||
|
||||
/**
|
||||
* @brief Load a cached frame from disk
|
||||
* (FrameHashCache::load_cache_frame(cache_path, uuid, ts)).
|
||||
*
|
||||
* @param path Cache directory (e.g. oakrender_disk_cache_path()).
|
||||
* @param uuid Cache UUID of the producing node.
|
||||
* @param out_frame Receives an owned frame handle (release with
|
||||
* oakrender_codec_frame_free()).
|
||||
*
|
||||
* @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL argument), or
|
||||
* OAKRENDER_E_NOT_FOUND (no cached frame at `ts` / undecodable).
|
||||
*/
|
||||
int oakrender_frame_cache_load(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, int64_t ts,
|
||||
OakCodecFrame **out_frame);
|
||||
|
||||
/**
|
||||
* @brief Save a frame to the disk cache under the cache's timebase and
|
||||
* the frame's own timestamp (FrameHashCache::save_cache_frame()).
|
||||
* NULL arguments are a no-op.
|
||||
*/
|
||||
void oakrender_frame_cache_save(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, const OakCodecFrame *frame);
|
||||
|
||||
/* ---- Debug --------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Number of live oakrender-owned objects (caches, textures,
|
||||
* frames, color processors) for leak assertions in tests.
|
||||
*/
|
||||
int oakrender_debug_alive_count(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_CACHE_H
|
||||
@@ -0,0 +1,138 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_COLOR_H
|
||||
#define OAK_EDITOR_RENDER_COLOR_H
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file color.h
|
||||
* @brief C ABI for oakrender color processing (olive::ColorProcessor) and
|
||||
* the process-wide default OCIO config (olive::ColorManager
|
||||
* statics), M7 §2.3.
|
||||
*
|
||||
* An OakColorProcessor IS a wrapper allocation holding a
|
||||
* ColorProcessorPtr (ColorProcessor is shared_ptr-managed); release with
|
||||
* oakrender_color_processor_free(). NULL is accepted by every function
|
||||
* and yields a no-op / OAKRENDER_E_INVALID.
|
||||
*
|
||||
* Processors are built against the process-wide default OCIO config
|
||||
* (olive::ColorManager::get_default_config()): the $OCIO config when the
|
||||
* environment variable is set, otherwise the config extracted to the
|
||||
* user configuration location. oakrender_color_manager_set_up_default_config()
|
||||
* (re)builds it.
|
||||
*/
|
||||
|
||||
/** Direction values for oakrender_color_processor_create(). */
|
||||
enum {
|
||||
OAKRENDER_COLOR_DIRECTION_NORMAL = 0,
|
||||
OAKRENDER_COLOR_DIRECTION_INVERSE = 1
|
||||
};
|
||||
|
||||
typedef struct OakColorProcessor OakColorProcessor;
|
||||
|
||||
/**
|
||||
* @brief Create a colorspace-to-colorspace processor on the default
|
||||
* OCIO config.
|
||||
*
|
||||
* @param src_space Source colorspace name (role names are resolved).
|
||||
* @param dst_transform Destination colorspace / output transform name.
|
||||
* @param direction OAKRENDER_COLOR_DIRECTION_NORMAL (src -> dst) or
|
||||
* OAKRENDER_COLOR_DIRECTION_INVERSE (dst -> src).
|
||||
*
|
||||
* OCIO failures are non-fatal (matching the C++ behavior): the handle is
|
||||
* still returned but oakrender_color_processor_is_valid() reports 0 and
|
||||
* conversions are pass-through.
|
||||
*
|
||||
* @return Processor handle, or NULL for NULL/empty strings, an unknown
|
||||
* direction, no default config, or allocation failure.
|
||||
*/
|
||||
OakColorProcessor *oakrender_color_processor_create(const char *src_space,
|
||||
const char *dst_transform,
|
||||
int direction);
|
||||
|
||||
/** @brief Release a processor handle. NULL-safe no-op. */
|
||||
void oakrender_color_processor_free(OakColorProcessor *processor);
|
||||
|
||||
/**
|
||||
* @brief 1 when the processor holds a valid OCIO processor
|
||||
* (ColorProcessor::get_processor() != null), 0 otherwise / on NULL.
|
||||
*/
|
||||
int oakrender_color_processor_is_valid(const OakColorProcessor *processor);
|
||||
|
||||
/**
|
||||
* @brief Convert a single RGBA color (ColorProcessor::convert_color()).
|
||||
* On an invalid processor the input is copied through.
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for NULL arguments.
|
||||
*/
|
||||
int oakrender_color_processor_convert(OakColorProcessor *processor,
|
||||
double ir, double ig, double ib,
|
||||
double ia, double *out_r, double *out_g,
|
||||
double *out_b, double *out_a);
|
||||
|
||||
/* ---- ColorManager statics ------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief (Re)build the process-wide default OCIO config
|
||||
* (ColorManager::set_up_default_config()).
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_FAILED when no config could be
|
||||
* created.
|
||||
*/
|
||||
int oakrender_color_manager_set_up_default_config(void);
|
||||
|
||||
/**
|
||||
* @brief Describe the active default config: the $OCIO path when set,
|
||||
* otherwise the extracted default config's path. Two-stage string
|
||||
* getter: returns the required buffer size including NUL; pass
|
||||
* buf == NULL or too small a buffer to query the size.
|
||||
*
|
||||
* @return Required size (non-negative), or OAKRENDER_E_STATE when no
|
||||
* default config exists.
|
||||
*/
|
||||
int oakrender_color_manager_get_config(char *buf, int n);
|
||||
|
||||
/**
|
||||
* @brief OCIO cache id of the display/view transform of the active
|
||||
* default config, computed from the config's reference colorspace
|
||||
* (a stable identifier usable as a conversion cache key).
|
||||
*
|
||||
* Two-stage string getter (same convention as
|
||||
* oakrender_color_manager_get_config()).
|
||||
*
|
||||
* @return Required size (non-negative), OAKRENDER_E_INVALID (NULL/empty
|
||||
* display or view), OAKRENDER_E_STATE (no default config), or
|
||||
* OAKRENDER_E_NOT_FOUND (unknown display/view).
|
||||
*/
|
||||
int oakrender_color_manager_display_transform(const char *display,
|
||||
const char *view, char *buf,
|
||||
int n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_COLOR_H
|
||||
@@ -0,0 +1,39 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_ERROR_H
|
||||
#define OAK_EDITOR_RENDER_ERROR_H
|
||||
|
||||
/**
|
||||
* @brief Status and error codes shared by all oakrender C API families.
|
||||
*
|
||||
* Return-code convention (mirrors include/node/error.h):
|
||||
* 0 (OAKRENDER_OK) on success, a negative OAKRENDER_E_* error code on
|
||||
* failure. String getters return the required buffer size in bytes
|
||||
* (including the terminating NUL) as a non-negative value instead.
|
||||
*/
|
||||
#define OAKRENDER_OK 0 /**< Success. */
|
||||
#define OAKRENDER_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKRENDER_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKRENDER_E_FAILED (-3) /**< The underlying operation failed. */
|
||||
#define OAKRENDER_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
|
||||
#define OAKRENDER_E_NOMEM (-5) /**< Allocation failed. */
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_ERROR_H
|
||||
@@ -0,0 +1,157 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_MANAGER_H
|
||||
#define OAK_EDITOR_RENDER_MANAGER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// See cache.h for why these are same-dir relative includes.
|
||||
#include "cache.h" /* OakCodecFrame */
|
||||
#include "color.h" /* OakColorProcessor */
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file manager.h
|
||||
* @brief C ABI for the oakrender render manager / preview auto-cacher /
|
||||
* disk cache singletons (olive::RenderManager,
|
||||
* olive::PreviewAutoCacher, olive::DiskManager), M7 §2.4.
|
||||
*
|
||||
* The render manager is a process-wide singleton gated by
|
||||
* oakrender_manager_init() / oakrender_manager_shutdown(). Functions
|
||||
* that need it return OAKRENDER_E_STATE when it is not up.
|
||||
*
|
||||
* The frame request callback is the asynchronous command return channel
|
||||
* (M7 §2.2 note): it fires on a render worker thread, possibly after
|
||||
* cancellation. The delivered OakCodecFrame is owned by the callback
|
||||
* recipient (release with oakrender_codec_frame_free()); a NULL frame
|
||||
* signals "no result" (cancelled or failed). Beyond this callback there
|
||||
* are no event subscription interfaces.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Borrowed node handle (olive::Node) from the oaknode ABI,
|
||||
* re-declared here so this header is self-contained.
|
||||
*/
|
||||
typedef struct OakNodeNode OakNodeNode;
|
||||
|
||||
/**
|
||||
* @brief Create the RenderManager singleton (spawns render/audio
|
||||
* threads, loads the configured backend).
|
||||
*
|
||||
* @return OAKRENDER_OK, OAKRENDER_E_STATE (already initialized), or
|
||||
* OAKRENDER_E_FAILED.
|
||||
*/
|
||||
int oakrender_manager_init(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy the RenderManager singleton. No-op when not
|
||||
* initialized.
|
||||
*/
|
||||
void oakrender_manager_shutdown(void);
|
||||
|
||||
/**
|
||||
* @brief Completion callback of an asynchronous frame request.
|
||||
*
|
||||
* @param frame Owned frame handle, or NULL when the request finished
|
||||
* without a result (cancelled/failed).
|
||||
* @param ts The request's timestamp, passed back verbatim.
|
||||
*/
|
||||
typedef void (*oakrender_frame_ready_fn)(OakCodecFrame *frame, int64_t ts,
|
||||
void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously render one frame of `viewer` at `ts`
|
||||
* (PreviewAutoCacher::get_single_frame()).
|
||||
*
|
||||
* `ts` is a frame number in the viewer node's video timebase (a whole
|
||||
* second count when the viewer carries no valid timebase). The
|
||||
* completion is delivered through `cb`; until then the request can be
|
||||
* cancelled with oakrender_cancel_request().
|
||||
*
|
||||
* @return A positive request id, or a negative OAKRENDER_E_* code
|
||||
* (OAKRENDER_E_INVALID for NULL viewer/callback,
|
||||
* OAKRENDER_E_STATE when the manager is not initialized,
|
||||
* OAKRENDER_E_FAILED when no ticket could be created).
|
||||
*/
|
||||
int64_t oakrender_request_frame(OakNodeNode *viewer, int64_t ts,
|
||||
oakrender_frame_ready_fn cb, void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Cancel a pending frame request. The callback still fires with a
|
||||
* NULL frame.
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_NOT_FOUND for an unknown id.
|
||||
*/
|
||||
int oakrender_cancel_request(int64_t request_id);
|
||||
|
||||
/**
|
||||
* @brief Set the multicam node on the manager's auto-cacher
|
||||
* (PreviewAutoCacher::set_multicam_node()). `multicam_or_NULL` is a
|
||||
* borrowed oaknode handle to a MultiCamNode (NULL to clear).
|
||||
*
|
||||
* @return OAKRENDER_OK or OAKRENDER_E_STATE.
|
||||
*/
|
||||
int oakrender_set_cacher_multicam(OakNodeNode *multicam_or_NULL);
|
||||
|
||||
/**
|
||||
* @brief Set the display color processor on the manager's auto-cacher
|
||||
* (PreviewAutoCacher::set_display_color_processor()). Borrowed handle,
|
||||
* NULL to clear.
|
||||
*
|
||||
* @return OAKRENDER_OK or OAKRENDER_E_STATE.
|
||||
*/
|
||||
int oakrender_set_display_color_processor(OakColorProcessor *p_or_NULL);
|
||||
|
||||
/* ---- Disk cache (olive::DiskManager) -------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief The default disk cache directory
|
||||
* (DiskManager::get_default_disk_cache_path()). Two-stage string getter:
|
||||
* returns the required buffer size including NUL; pass buf == NULL or
|
||||
* too small a buffer to query the size. Does not require the manager.
|
||||
*/
|
||||
int oakrender_disk_cache_path(char *buf, int n);
|
||||
|
||||
/**
|
||||
* @brief Bytes currently consumed by the default disk cache folder.
|
||||
* Lazily creates the DiskManager singleton on first use.
|
||||
*
|
||||
* @return Consumption in bytes (>= 0), or OAKRENDER_E_FAILED.
|
||||
*/
|
||||
int64_t oakrender_disk_cache_size(void);
|
||||
|
||||
/**
|
||||
* @brief Clear the default disk cache folder
|
||||
* (DiskManager::clear_disk_cache()).
|
||||
*
|
||||
* @return OAKRENDER_OK or OAKRENDER_E_FAILED.
|
||||
*/
|
||||
int oakrender_disk_cache_clear(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_MANAGER_H
|
||||
@@ -0,0 +1,286 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_RENDERER_H
|
||||
#define OAK_EDITOR_RENDER_RENDERER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file renderer.h
|
||||
* @brief C ABI for the oakrender display renderer (olive::Renderer) —
|
||||
* renderer/texture/frame/blit families plus backend management.
|
||||
*
|
||||
* Signatures follow the R7-A display.h rewrite
|
||||
* (docs/zh/plans/completed/r7-pure-abi-plan.md §A.2) with the
|
||||
* oakrender_ prefix (M7 §2.1).
|
||||
*
|
||||
* Ownership protocol: textures and frames are opaque handles pointing to
|
||||
* oakrender-heap control blocks (internally holding std::shared_ptr;
|
||||
* invisible to the ABI). Ownership transfers via explicit retain/free.
|
||||
* Every retain must be paired with exactly one free. NULL is accepted by
|
||||
* every function and yields a no-op / zero result / OAKRENDER_E_INVALID.
|
||||
*
|
||||
* Cross-thread handoff (§A.3): the producing side retains before
|
||||
* publishing a handle into a shared slot; the consuming side frees the
|
||||
* handle it replaced. The side holding the slot when it is torn down
|
||||
* frees the remaining handle.
|
||||
*
|
||||
* Handles:
|
||||
* - OakRenderRenderer IS a reinterpreted olive::Renderer (no wrapper).
|
||||
* - OakRenderTexture / OakCodecFrame are refcounted control blocks.
|
||||
* - `gl_context` is an opaque borrowed olive::OpenGLContext* (or NULL
|
||||
* to let the backend create its own offscreen surface).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief POD mirror of olive::VideoParams' user-facing fields.
|
||||
*
|
||||
* Same layout and field semantics as oak_video_params
|
||||
* (engine/include/oakengine/videoparams.h): `time_base_*` is the frame
|
||||
* duration (frame rate flipped), `format` an olive::PixelFormat::Format
|
||||
* value, `interlacing` an olive::VideoParams::Interlacing value,
|
||||
* `color_range` an olive::VideoParams::ColorRange value. The video
|
||||
* channel count is an engine-internal constant and not exposed.
|
||||
*/
|
||||
typedef struct oakrender_video_params {
|
||||
int width;
|
||||
int height;
|
||||
int time_base_num; /**< Frame duration numerator (e.g. 1001/30000 s). */
|
||||
int time_base_den;
|
||||
int format; /**< olive::PixelFormat::Format. */
|
||||
int pixel_aspect_num;
|
||||
int pixel_aspect_den;
|
||||
int interlacing; /**< olive::VideoParams::Interlacing. */
|
||||
int color_range; /**< olive::VideoParams::ColorRange. */
|
||||
int divider; /**< Preview resolution divider (1 = full). */
|
||||
int video_type; /**< olive::VideoParams::Type (0 = video). */
|
||||
int premultiplied_alpha; /**< 0/1. */
|
||||
} oakrender_video_params;
|
||||
|
||||
typedef struct OakRenderRenderer OakRenderRenderer;
|
||||
typedef struct OakRenderTexture OakRenderTexture;
|
||||
|
||||
/**
|
||||
* @brief Opaque CPU frame handle (refcounted control block around an
|
||||
* olive::FramePtr). Declared here so the cache family (render/cache.h)
|
||||
* can use the same type; the frame functions live in this header.
|
||||
* Named OakCodecFrame per the M7 §2.2 contract; the oakcodec wave (M5)
|
||||
* adopts the same handle.
|
||||
*/
|
||||
typedef struct OakCodecFrame OakCodecFrame;
|
||||
|
||||
/**
|
||||
* @brief Flattened POD of olive::ColorTransformJob for the display blit
|
||||
* path. `matrix`/`crop_matrix` are column-major 4x4; an all-zero matrix
|
||||
* means identity.
|
||||
*/
|
||||
typedef struct oakrender_color_transform_job {
|
||||
const void *processor; /**< OakColorProcessor* (borrowed), may be NULL. */
|
||||
void *input_texture; /**< OakRenderTexture* (borrowed, not retained). */
|
||||
int input_alpha_association; /**< 0=none, 1=associated. */
|
||||
int clear_destination; /**< 0/1. */
|
||||
int force_opaque; /**< 0/1. */
|
||||
float matrix[16];
|
||||
float crop_matrix[16];
|
||||
} oakrender_color_transform_job;
|
||||
|
||||
/* ---- Renderer lifecycle -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a renderer on the named dynamic backend ("opengl",
|
||||
* "vulkan"; olive::DynamicRenderer). Loads the backend shared library;
|
||||
* falls back per DynamicRenderer rules.
|
||||
*
|
||||
* @return Renderer handle, or NULL on NULL/empty backend id, load
|
||||
* failure, or allocation failure.
|
||||
*/
|
||||
OakRenderRenderer *oakrender_display_renderer_create_dynamic(
|
||||
const char *backend_id);
|
||||
|
||||
/**
|
||||
* @brief Create an OpenGL renderer (olive::OpenGLRenderer). The renderer
|
||||
* is not initialized; call oakrender_display_renderer_init() before use.
|
||||
*
|
||||
* @return Renderer handle, or NULL on allocation failure.
|
||||
*/
|
||||
OakRenderRenderer *oakrender_display_renderer_create_opengl(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize a renderer. `gl_context` is a borrowed opaque
|
||||
* olive::OpenGLContext*, or NULL to use the backend's default
|
||||
* device/context path (Renderer::init()).
|
||||
*
|
||||
* @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL renderer), or
|
||||
* OAKRENDER_E_FAILED (backend init failed).
|
||||
*/
|
||||
int oakrender_display_renderer_init(OakRenderRenderer *renderer,
|
||||
void *gl_context);
|
||||
|
||||
/**
|
||||
* @brief Destroy a renderer (Renderer::destroy() + delete). NULL-safe
|
||||
* no-op.
|
||||
*/
|
||||
void oakrender_display_renderer_destroy(OakRenderRenderer *renderer);
|
||||
|
||||
/* ---- Renderer queries ---------------------------------------------------- */
|
||||
|
||||
/** @brief 1 when the renderer is OpenGL-based, 0 otherwise / on NULL. */
|
||||
int oakrender_display_renderer_is_open_gl(const OakRenderRenderer *renderer);
|
||||
|
||||
/** @brief 1 when the renderer is Vulkan-based, 0 otherwise / on NULL. */
|
||||
int oakrender_display_renderer_is_vulkan(const OakRenderRenderer *renderer);
|
||||
|
||||
/* ---- Texture handle (opaque, refcounted) --------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a GPU texture on `renderer`.
|
||||
*
|
||||
* @param pixels Initial pixel data, or NULL for an uninitialized texture.
|
||||
* @param linesize Stride of `pixels` in bytes (0 when pixels is NULL).
|
||||
* @return New texture handle (refcount=1), or NULL on invalid arguments /
|
||||
* allocation failure.
|
||||
*/
|
||||
OakRenderTexture *oakrender_display_texture_create(
|
||||
OakRenderRenderer *renderer, const oakrender_video_params *params,
|
||||
const void *pixels, int linesize);
|
||||
|
||||
/** @brief Increment refcount, return the same handle. NULL-safe. */
|
||||
OakRenderTexture *oakrender_display_texture_retain(OakRenderTexture *texture);
|
||||
|
||||
/** @brief Decrement refcount; frees at zero. NULL-safe. */
|
||||
void oakrender_display_texture_free(OakRenderTexture *texture);
|
||||
|
||||
int oakrender_display_texture_upload(OakRenderTexture *texture,
|
||||
const void *pixels, int linesize);
|
||||
|
||||
int oakrender_display_texture_download(OakRenderTexture *texture, void *pixels,
|
||||
int linesize);
|
||||
|
||||
/* ---- Texture queries ----------------------------------------------------- */
|
||||
|
||||
int oakrender_display_texture_get_params(const OakRenderTexture *texture,
|
||||
oakrender_video_params *out);
|
||||
|
||||
/** @brief Native texture id (0 on NULL or a dummy/id-less texture). */
|
||||
int oakrender_display_texture_id(const OakRenderTexture *texture);
|
||||
|
||||
/* ---- Frame handle (opaque, refcounted) ----------------------------------- */
|
||||
|
||||
/** @brief Create an empty CPU frame. Returns handle (refcount=1). */
|
||||
OakCodecFrame *oakrender_codec_frame_create(void);
|
||||
|
||||
/** @brief Increment refcount, return the same handle. NULL-safe. */
|
||||
OakCodecFrame *oakrender_codec_frame_retain(OakCodecFrame *frame);
|
||||
|
||||
/** @brief Decrement refcount; frees at zero. NULL-safe. */
|
||||
void oakrender_codec_frame_free(OakCodecFrame *frame);
|
||||
|
||||
int oakrender_codec_frame_set_video_params(
|
||||
OakCodecFrame *frame, const oakrender_video_params *params);
|
||||
|
||||
int oakrender_codec_frame_get_params(const OakCodecFrame *frame,
|
||||
oakrender_video_params *out);
|
||||
|
||||
/**
|
||||
* @brief Allocate the pixel buffer per the frame's video params
|
||||
* (Frame::allocate()).
|
||||
*
|
||||
* @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL frame), or
|
||||
* OAKRENDER_E_FAILED (invalid params / allocation failed).
|
||||
*/
|
||||
int oakrender_codec_frame_allocate(OakCodecFrame *frame);
|
||||
|
||||
/** @brief Borrowed pixel data pointer (valid until free). */
|
||||
void *oakrender_codec_frame_data(OakCodecFrame *frame);
|
||||
|
||||
/** @brief Borrowed const pixel data pointer. */
|
||||
const void *oakrender_codec_frame_const_data(const OakCodecFrame *frame);
|
||||
|
||||
/** @brief Line stride in bytes. */
|
||||
int oakrender_codec_frame_linesize_bytes(const OakCodecFrame *frame);
|
||||
|
||||
/** @brief 1 when the pixel buffer is allocated, 0 otherwise / on NULL. */
|
||||
int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame);
|
||||
|
||||
/* ---- Color-managed blit -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Blit a color-managed image through the OCIO pipeline
|
||||
* (Renderer::blit_color_managed()).
|
||||
*
|
||||
* @param dst_texture Destination texture handle, or NULL for the current
|
||||
* output target.
|
||||
* @param params Destination video params, or NULL to use dst_texture's.
|
||||
*/
|
||||
int oakrender_display_renderer_blit_color_managed(
|
||||
OakRenderRenderer *renderer, const oakrender_color_transform_job *job,
|
||||
OakRenderTexture *dst_texture, const oakrender_video_params *params);
|
||||
|
||||
/* ---- Cross-backend texture download -------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_download_from_texture(
|
||||
OakRenderRenderer *renderer, int texture_id,
|
||||
const oakrender_video_params *params, void *dst_pixels, int linesize);
|
||||
|
||||
/* ---- Backend management (M7 §2.1) ---------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Number of known render backends (olive::RenderManager::Backend:
|
||||
* opengl, vulkan, multiprocess, dummy).
|
||||
*/
|
||||
int oakrender_backend_count(void);
|
||||
|
||||
/**
|
||||
* @brief Id string of the `i`-th backend ("opengl", ...). Two-stage
|
||||
* string getter: returns the required buffer size including NUL; pass
|
||||
* buf == NULL or too small a buffer to query the size.
|
||||
*
|
||||
* @return Required size (non-negative), or OAKRENDER_E_NOT_FOUND when
|
||||
* `i` is out of range.
|
||||
*/
|
||||
int oakrender_backend_id_at(int i, char *buf, int n);
|
||||
|
||||
/**
|
||||
* @brief Record the requested backend id (applied to the RenderManager
|
||||
* instance when one exists).
|
||||
*
|
||||
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for a NULL/unknown id.
|
||||
*/
|
||||
int oakrender_set_backend(const char *backend_id);
|
||||
|
||||
/**
|
||||
* @brief The effective backend: the RenderManager instance's backend when
|
||||
* an instance exists, otherwise the requested backend. Two-stage string
|
||||
* getter (same convention as oakrender_backend_id_at()).
|
||||
*/
|
||||
int oakrender_current_backend(char *buf, int n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_RENDERER_H
|
||||
@@ -1,2 +1,3 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(undo)add_subdirectory(node)
|
||||
add_subdirectory(render)
|
||||
|
||||
@@ -74,8 +74,14 @@ add_subdirectory(${OAK_REPO_ROOT}/src/node ${CMAKE_BINARY_DIR}/node)
|
||||
# The transition stub dir must precede ${OAK_REPO_ROOT}/include so that e.g.
|
||||
# "common/current.h" resolves to the transitional forwarder, not the stale
|
||||
# public mirror in include/common.
|
||||
# M7 收尾:src/node/transition/render/* 已改为桥接头,转发到
|
||||
# src/render/src 真身;因此 oaknode 需要 src/render/src 在 include 路径上,
|
||||
# 且 src/render/transition 必须置于 src/node/transition 之前(两侧共用
|
||||
# render 的扩展 stub,契约一致)。
|
||||
target_include_directories(oaknode BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
${OAK_REPO_ROOT}/src/render/src
|
||||
)
|
||||
|
||||
target_include_directories(oaknode PUBLIC
|
||||
@@ -89,3 +95,55 @@ target_include_directories(oaknode PUBLIC
|
||||
target_link_options(oaknode PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
|
||||
# M7 收尾:oaknode 的 render/ 引用现在解析到 src/render/src 的真身类,
|
||||
# 其成员函数(如 PlaybackCache::InvalidateEvent)定义在 liboakrender 里。
|
||||
# 把 oakrender 构建进来并链进测试二进制;未拆分模块符号同样悬置。
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
|
||||
set(BUILD_TESTS ON)
|
||||
|
||||
target_include_directories(oakrender BEFORE PUBLIC
|
||||
${OAK_REPO_ROOT}/src/render/transition
|
||||
${OAK_REPO_ROOT}/src/node/transition
|
||||
)
|
||||
|
||||
target_include_directories(oakrender PUBLIC
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
|
||||
/opt/homebrew/include
|
||||
/opt/homebrew/include/Imath
|
||||
/opt/homebrew/include/OpenEXR
|
||||
)
|
||||
|
||||
foreach(t oakrender oakgl oakvulkan)
|
||||
target_link_options(${t} PRIVATE
|
||||
"-undefined" "dynamic_lookup"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(oakrender PRIVATE
|
||||
oaknode
|
||||
oakcommon
|
||||
oakundo
|
||||
olivecore
|
||||
ffmpeg_bridge
|
||||
${OCIO_LIBRARIES}
|
||||
${OIIO_LIBRARIES}
|
||||
"-framework OpenGL"
|
||||
"-framework CoreVideo"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
|
||||
target_link_libraries(oaknode-gtest PRIVATE oakrender)
|
||||
|
||||
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
|
||||
# dynamic_lookup; the real implementation (engine/src/capi/ipc.cpp) is still
|
||||
# Qt-based, so the test binary links an inert shim instead.
|
||||
target_sources(oaknode-gtest PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/oakengine_ipc_shim.cpp
|
||||
)
|
||||
target_include_directories(oaknode-gtest PRIVATE
|
||||
${OAK_REPO_ROOT}/engine/include
|
||||
)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// Oak Video Editor - Non-Linear Video Editor
|
||||
// Copyright (C) 2026 Oak Team
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Test-only inert shim for the oakengine_ipc_* C ABI (real implementation:
|
||||
// engine/src/capi/ipc.cpp, still Qt-based and not buildable here).
|
||||
// liboakrender references these symbols via -undefined dynamic_lookup; the
|
||||
// oaknode test binary must provide them in the flat namespace so dyld can
|
||||
// bind liboakrender at startup. The node-level tests never drive the render
|
||||
// worker IPC paths, so every entry point fails/inert-safe: open returns 0,
|
||||
// pools report invalid, acquire/consume report empty.
|
||||
|
||||
#include "oakengine/ipc.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
struct OakSharedMemoryRegion {
|
||||
int unused;
|
||||
};
|
||||
|
||||
struct OakFrameSlotPool {
|
||||
int unused;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
OakSharedMemoryRegion *oakengine_ipc_shm_create(void)
|
||||
{
|
||||
return new OakSharedMemoryRegion{};
|
||||
}
|
||||
|
||||
void oakengine_ipc_shm_free(OakSharedMemoryRegion *self)
|
||||
{
|
||||
delete self;
|
||||
}
|
||||
|
||||
int oakengine_ipc_shm_open(OakSharedMemoryRegion *, const char *, size_t,
|
||||
oak_ipc_shm_mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void oakengine_ipc_shm_close(OakSharedMemoryRegion *) {}
|
||||
|
||||
int oakengine_ipc_shm_is_valid(const OakSharedMemoryRegion *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *oakengine_ipc_shm_data(OakSharedMemoryRegion *)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t oakengine_ipc_shm_size(const OakSharedMemoryRegion *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oakengine_ipc_shm_key(const OakSharedMemoryRegion *, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (buf && buf_size > 0) {
|
||||
buf[0] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oakengine_ipc_shm_error(const OakSharedMemoryRegion *, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
static const char k_msg[] = "oakengine_ipc test shim: IPC unavailable";
|
||||
if (buf && buf_size > 0) {
|
||||
std::strncpy(buf, k_msg, size_t(buf_size) - 1);
|
||||
buf[buf_size - 1] = '\0';
|
||||
}
|
||||
return int(sizeof(k_msg) - 1);
|
||||
}
|
||||
|
||||
int oakengine_ipc_shm_make_key(int64_t owner_pid, int worker_index, char *buf,
|
||||
int buf_size)
|
||||
{
|
||||
if (buf && buf_size > 0) {
|
||||
std::snprintf(buf, size_t(buf_size), "olive-rw-%lld-%d",
|
||||
(long long) owner_pid, worker_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t oakengine_ipc_framepool_bytes_needed(uint32_t, size_t)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
OakFrameSlotPool *oakengine_ipc_framepool_create(void *, uint32_t, size_t)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OakFrameSlotPool *oakengine_ipc_framepool_attach(void *)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OakFrameSlotPool *oakengine_ipc_framepool_copy(const OakFrameSlotPool *)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void oakengine_ipc_framepool_free(OakFrameSlotPool *) {}
|
||||
|
||||
int oakengine_ipc_framepool_is_valid(const OakFrameSlotPool *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t oakengine_ipc_framepool_slot_count(const OakFrameSlotPool *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t oakengine_ipc_framepool_slot_data_bytes(const OakFrameSlotPool *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oakengine_ipc_framepool_acquire(OakFrameSlotPool *, uint32_t *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *oakengine_ipc_framepool_slot_data(OakFrameSlotPool *, uint32_t)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const void *oakengine_ipc_framepool_slot_data_const(const OakFrameSlotPool *,
|
||||
uint32_t)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
oak_frame_slot_meta *oakengine_ipc_framepool_meta(OakFrameSlotPool *, uint32_t)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const oak_frame_slot_meta *
|
||||
oakengine_ipc_framepool_meta_const(const OakFrameSlotPool *, uint32_t)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int oakengine_ipc_framepool_publish(OakFrameSlotPool *, uint32_t)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oakengine_ipc_framepool_consume(OakFrameSlotPool *, uint32_t *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oakengine_ipc_framepool_release(OakFrameSlotPool *, uint32_t)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,299 @@
|
||||
#pragma once
|
||||
// Transitional: QDataStream replacement for the render disk-cache state and
|
||||
// index files. Mirrors QDataStream's default (big-endian) wire format for the
|
||||
// subset of types those files use, so existing cache files stay readable:
|
||||
// quint32/qint32/qint64 -> big-endian bytes
|
||||
// bool -> quint8
|
||||
// QString -> quint32 byte length + UTF-16BE code units
|
||||
// QUuid -> 16 raw bytes in RFC 4122 order
|
||||
// Reads past EOF yield zeroed values (QDataStream::ReadPastEnd semantics).
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace binarystream_detail
|
||||
{
|
||||
|
||||
inline void append_utf16be(std::string &out, uint32_t cp)
|
||||
{
|
||||
if (cp >= 0x10000) {
|
||||
cp -= 0x10000;
|
||||
uint16_t hi = 0xD800 + (cp >> 10);
|
||||
uint16_t lo = 0xDC00 + (cp & 0x3FF);
|
||||
out += char(hi >> 8);
|
||||
out += char(hi & 0xFF);
|
||||
out += char(lo >> 8);
|
||||
out += char(lo & 0xFF);
|
||||
} else {
|
||||
out += char(cp >> 8);
|
||||
out += char(cp & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string utf8_to_utf16be(const std::string &s)
|
||||
{
|
||||
std::string out;
|
||||
size_t i = 0;
|
||||
while (i < s.size()) {
|
||||
unsigned char c = s[i];
|
||||
uint32_t cp;
|
||||
size_t extra;
|
||||
if (c < 0x80) {
|
||||
cp = c;
|
||||
extra = 0;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
cp = c & 0x1F;
|
||||
extra = 1;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
cp = c & 0x0F;
|
||||
extra = 2;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
cp = c & 0x07;
|
||||
extra = 3;
|
||||
} else {
|
||||
// Invalid byte, emit U+FFFD like Qt's UTF-8 handling
|
||||
cp = 0xFFFD;
|
||||
extra = 0;
|
||||
}
|
||||
for (size_t j = 0; j < extra; j++) {
|
||||
if (i + 1 < s.size()) {
|
||||
cp = (cp << 6) | (uint8_t(s[i + 1]) & 0x3F);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
append_utf16be(out, cp);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void append_utf8(std::string &out, uint32_t cp)
|
||||
{
|
||||
if (cp < 0x80) {
|
||||
out += char(cp);
|
||||
} else if (cp < 0x800) {
|
||||
out += char(0xC0 | (cp >> 6));
|
||||
out += char(0x80 | (cp & 0x3F));
|
||||
} else if (cp < 0x10000) {
|
||||
out += char(0xE0 | (cp >> 12));
|
||||
out += char(0x80 | ((cp >> 6) & 0x3F));
|
||||
out += char(0x80 | (cp & 0x3F));
|
||||
} else {
|
||||
out += char(0xF0 | (cp >> 18));
|
||||
out += char(0x80 | ((cp >> 12) & 0x3F));
|
||||
out += char(0x80 | ((cp >> 6) & 0x3F));
|
||||
out += char(0x80 | (cp & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string utf16be_to_utf8(const std::string &be)
|
||||
{
|
||||
std::string out;
|
||||
size_t units = be.size() / 2;
|
||||
for (size_t i = 0; i < units; i++) {
|
||||
uint16_t u = (uint8_t(be[i * 2]) << 8) | uint8_t(be[i * 2 + 1]);
|
||||
uint32_t cp = u;
|
||||
if (u >= 0xD800 && u < 0xDC00 && i + 1 < units) {
|
||||
uint16_t lo = (uint8_t(be[i * 2 + 2]) << 8) |
|
||||
uint8_t(be[i * 2 + 3]);
|
||||
if (lo >= 0xDC00 && lo < 0xE000) {
|
||||
cp = 0x10000 + ((uint32_t(u) - 0xD800) << 10) + (lo - 0xDC00);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
append_utf8(out, cp);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline int hex_digit(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace binarystream_detail
|
||||
|
||||
class BinaryStreamWriter {
|
||||
public:
|
||||
explicit BinaryStreamWriter(std::FILE *f)
|
||||
: f_(f)
|
||||
{
|
||||
}
|
||||
|
||||
BinaryStreamWriter &operator<<(uint32_t v)
|
||||
{
|
||||
write_be(v, 4);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamWriter &operator<<(int32_t v)
|
||||
{
|
||||
write_be(uint32_t(v), 4);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamWriter &operator<<(int64_t v)
|
||||
{
|
||||
write_be(uint64_t(v), 8);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamWriter &operator<<(bool v)
|
||||
{
|
||||
write_be(v ? 1 : 0, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// QString: quint32 byte length + UTF-16BE data
|
||||
BinaryStreamWriter &operator<<(const std::string &v)
|
||||
{
|
||||
std::string be = binarystream_detail::utf8_to_utf16be(v);
|
||||
write_be(uint32_t(be.size()), 4);
|
||||
if (!be.empty()) {
|
||||
std::fwrite(be.data(), 1, be.size(), f_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// QUuid: 16 raw bytes in RFC 4122 order. Accepts the canonical text form
|
||||
// "{8-4-4-4-12}" (braces optional), which is what cache uuids are stored as.
|
||||
void write_uuid_text(const std::string &uuid_text)
|
||||
{
|
||||
uint8_t bytes[16] = { 0 };
|
||||
int nibble = 0;
|
||||
for (char c : uuid_text) {
|
||||
if (c == '{' || c == '}' || c == '-') {
|
||||
continue;
|
||||
}
|
||||
if (nibble >= 32) {
|
||||
break;
|
||||
}
|
||||
int d = binarystream_detail::hex_digit(c);
|
||||
if (nibble % 2 == 0) {
|
||||
bytes[nibble / 2] = uint8_t(d << 4);
|
||||
} else {
|
||||
bytes[nibble / 2] |= uint8_t(d);
|
||||
}
|
||||
nibble++;
|
||||
}
|
||||
std::fwrite(bytes, 1, 16, f_);
|
||||
}
|
||||
|
||||
private:
|
||||
void write_be(uint64_t v, int bytes)
|
||||
{
|
||||
uint8_t b[8];
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
b[i] = uint8_t(v >> ((bytes - 1 - i) * 8));
|
||||
}
|
||||
std::fwrite(b, 1, bytes, f_);
|
||||
}
|
||||
|
||||
std::FILE *f_;
|
||||
};
|
||||
|
||||
class BinaryStreamReader {
|
||||
public:
|
||||
explicit BinaryStreamReader(std::FILE *f)
|
||||
: f_(f)
|
||||
{
|
||||
}
|
||||
|
||||
BinaryStreamReader &operator>>(uint32_t &v)
|
||||
{
|
||||
v = uint32_t(read_be(4));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamReader &operator>>(int32_t &v)
|
||||
{
|
||||
v = int32_t(read_be(4));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamReader &operator>>(int64_t &v)
|
||||
{
|
||||
v = int64_t(read_be(8));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamReader &operator>>(bool &v)
|
||||
{
|
||||
v = read_be(1) != 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStreamReader &operator>>(std::string &v)
|
||||
{
|
||||
uint32_t len = uint32_t(read_be(4));
|
||||
v.clear();
|
||||
if (len == 0xFFFFFFFF || len == 0) {
|
||||
return *this;
|
||||
}
|
||||
std::string be(len, '\0');
|
||||
size_t got = std::fread(&be[0], 1, len, f_);
|
||||
be.resize(got);
|
||||
v = binarystream_detail::utf16be_to_utf8(be);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// QUuid: 16 raw bytes -> canonical "{8-4-4-4-12}" lowercase text
|
||||
std::string read_uuid_text()
|
||||
{
|
||||
uint8_t bytes[16] = { 0 };
|
||||
std::fread(bytes, 1, 16, f_);
|
||||
|
||||
static const char k_hex[] = "0123456789abcdef";
|
||||
std::string out;
|
||||
out.reserve(38);
|
||||
out += '{';
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
||||
out += '-';
|
||||
}
|
||||
out += k_hex[bytes[i] >> 4];
|
||||
out += k_hex[bytes[i] & 0xF];
|
||||
}
|
||||
out += '}';
|
||||
return out;
|
||||
}
|
||||
|
||||
bool at_end() const
|
||||
{
|
||||
int c = std::fgetc(f_);
|
||||
if (c == EOF) {
|
||||
return true;
|
||||
}
|
||||
std::ungetc(c, f_);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t read_be(int bytes)
|
||||
{
|
||||
uint8_t b[8] = { 0 };
|
||||
std::fread(b, 1, bytes, f_);
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
v = (v << 8) | b[i];
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
std::FILE *f_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Transitional copy of engine/common/avframeptr.h(本身已无 Qt,待下沉 oakcommon)。只增不删。
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_AVFRAMEPTR_H
|
||||
#define OAK_AVFRAMEPTR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief C++ adapter around the ffmpeg_bridge frame handle
|
||||
*
|
||||
* Mirrors the AVFrame field access the codebase used to perform directly,
|
||||
* but every operation goes through the pure C bridge API so the editor
|
||||
* never touches FFmpeg itself. The underlying frame object always lives
|
||||
* inside the bridge library.
|
||||
*/
|
||||
class AVFrame {
|
||||
public:
|
||||
AVFrame() :
|
||||
handle_(fb_frame_alloc())
|
||||
{
|
||||
}
|
||||
|
||||
explicit AVFrame(FBFrame *handle) :
|
||||
handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
~AVFrame()
|
||||
{
|
||||
if (handle_) {
|
||||
fb_frame_free(&handle_);
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame(const AVFrame &) = delete;
|
||||
AVFrame &operator=(const AVFrame &) = delete;
|
||||
|
||||
FBFrame *handle() const { return handle_; }
|
||||
|
||||
int width() const { return fb_frame_get_width(handle_); }
|
||||
void set_width(int w) { fb_frame_set_width(handle_, w); }
|
||||
int height() const { return fb_frame_get_height(handle_); }
|
||||
void set_height(int h) { fb_frame_set_height(handle_, h); }
|
||||
int format() const { return fb_frame_get_format(handle_); }
|
||||
void set_format(int f) { fb_frame_set_format(handle_, f); }
|
||||
int64_t pts() const { return fb_frame_get_pts(handle_); }
|
||||
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
|
||||
int64_t best_effort_timestamp() const
|
||||
{
|
||||
return fb_frame_get_best_effort_timestamp(handle_);
|
||||
}
|
||||
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
|
||||
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
|
||||
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
|
||||
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
|
||||
int color_range() const { return fb_frame_get_color_range(handle_); }
|
||||
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
|
||||
int colorspace() const { return fb_frame_get_colorspace(handle_); }
|
||||
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
|
||||
uint64_t channel_layout_mask() const
|
||||
{
|
||||
return fb_frame_get_channel_layout_mask(handle_);
|
||||
}
|
||||
void set_channel_layout_mask(uint64_t m)
|
||||
{
|
||||
fb_frame_set_channel_layout_mask(handle_, m);
|
||||
}
|
||||
|
||||
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
|
||||
int hw_transfer_data(const AVFrame *src)
|
||||
{
|
||||
return fb_frame_hw_transfer_data(handle_, src->handle_);
|
||||
}
|
||||
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
|
||||
int make_writable() { return fb_frame_make_writable(handle_); }
|
||||
|
||||
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
|
||||
const uint8_t *data(int plane) const
|
||||
{
|
||||
return fb_frame_get_data_const(handle_, plane);
|
||||
}
|
||||
void set_data(int plane, uint8_t *d)
|
||||
{
|
||||
fb_frame_set_data(handle_, plane, d);
|
||||
}
|
||||
int linesize(int plane) const
|
||||
{
|
||||
return fb_frame_get_linesize(handle_, plane);
|
||||
}
|
||||
void set_linesize(int plane, int l)
|
||||
{
|
||||
fb_frame_set_linesize(handle_, plane, l);
|
||||
}
|
||||
|
||||
private:
|
||||
FBFrame *handle_;
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr(FBFrame *f)
|
||||
{
|
||||
return std::make_shared<AVFrame>(f);
|
||||
}
|
||||
|
||||
inline AVFramePtr create_av_frame_ptr()
|
||||
{
|
||||
return std::make_shared<AVFrame>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AVFRAMEPTR_H
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
// Transitional: engine/common/jobtime.h de-Qt target form. Original kept a
|
||||
// process-global mutex-protected counter; the atomic below preserves the same
|
||||
// monotonic-unique semantics. oakcommon has no jobtime.h yet (M-common folds
|
||||
// it in); renderjobtracker.h includes "common/jobtime.h".
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace olive {
|
||||
|
||||
class JobTime {
|
||||
public:
|
||||
JobTime() { acquire(); }
|
||||
|
||||
void acquire()
|
||||
{
|
||||
static std::atomic<uint64_t> index{ 0 };
|
||||
value_ = index.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t value() const { return value_; }
|
||||
|
||||
bool operator==(const JobTime &rhs) const { return value_ == rhs.value_; }
|
||||
bool operator!=(const JobTime &rhs) const { return value_ != rhs.value_; }
|
||||
bool operator<(const JobTime &rhs) const { return value_ < rhs.value_; }
|
||||
bool operator>(const JobTime &rhs) const { return value_ > rhs.value_; }
|
||||
bool operator<=(const JobTime &rhs) const { return value_ <= rhs.value_; }
|
||||
bool operator>=(const JobTime &rhs) const { return value_ >= rhs.value_; }
|
||||
|
||||
private:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,14 +1,4 @@
|
||||
#pragma once
|
||||
// Syntax-check stub only (not in repo).
|
||||
#include <string>
|
||||
#include "olive/core/util/timerange.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
namespace olive { using core::AudioParams; }
|
||||
#include "render/playbackcache.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
class AudioPlaybackCache : public PlaybackCache {
|
||||
public:
|
||||
template <typename T> explicit AudioPlaybackCache(T *) : PlaybackCache(this) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/audioplaybackcache.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/audioplaybackcache.h"
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
#pragma once
|
||||
// Syntax-check stub only (not in repo).
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "olive/core/util/timerange.h"
|
||||
#include "render/playbackcache.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
class AudioWaveformCache : public PlaybackCache {
|
||||
public:
|
||||
template <typename T> explicit AudioWaveformCache(T *) : PlaybackCache(this) {}
|
||||
void set_saving_enabled(bool) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/audiowaveformcache.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/audiowaveformcache.h"
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#pragma once
|
||||
namespace olive {
|
||||
class CancelAtom {
|
||||
public:
|
||||
bool is_cancelled() const { return false; }
|
||||
bool heard_cancel() const { return false; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/cancelatom.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/cancelatom.h"
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "ocioutils.h"
|
||||
#include "colortransform.h"
|
||||
namespace olive {
|
||||
class ColorManager;
|
||||
class ColorProcessor;
|
||||
using ColorProcessorPtr = std::shared_ptr<ColorProcessor>;
|
||||
class ColorProcessor {
|
||||
public:
|
||||
enum Direction { k_normal, k_inverse };
|
||||
static ColorProcessorPtr create(ColorManager *, const std::string &,
|
||||
const ColorTransform &,
|
||||
Direction = k_normal)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
static ColorProcessorPtr create(ocio::ConstProcessorRcPtr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/colorprocessor.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/colorprocessor.h"
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
namespace olive {
|
||||
class DiskManager {
|
||||
public:
|
||||
static DiskManager *instance() { static DiskManager d; return &d; }
|
||||
std::string get_default_cache_path() const { return std::string(); }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/diskmanager.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/diskmanager.h"
|
||||
|
||||
@@ -1,32 +1,4 @@
|
||||
#pragma once
|
||||
// Transitional stub for engine/render/framehashcache.h (still Qt-based).
|
||||
// Only the surface oaknode uses. M7 replaces this with the real oakrender
|
||||
// boundary.
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "olive/core/util/rational.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
#include "render/playbackcache.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
class FrameHashCache : public PlaybackCache {
|
||||
public:
|
||||
template <typename T> explicit FrameHashCache(T *) : PlaybackCache(this) {}
|
||||
const core::Rational &get_timebase() const { return timebase_; }
|
||||
void set_timebase(const core::Rational &t) { timebase_ = t; }
|
||||
std::mutex &mutex() { return mutex_; }
|
||||
void load_state() {}
|
||||
std::string get_valid_cache_filename(const core::Rational &) const
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
private:
|
||||
core::Rational timebase_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
class ThumbnailCache : public FrameHashCache {
|
||||
public:
|
||||
template <typename T> explicit ThumbnailCache(T *) : FrameHashCache(this) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/framehashcache.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/framehashcache.h"
|
||||
|
||||
@@ -1,23 +1,4 @@
|
||||
#pragma once
|
||||
#include "param.h"
|
||||
#include "value.h"
|
||||
namespace olive {
|
||||
class AcceleratedJob {
|
||||
public:
|
||||
virtual ~AcceleratedJob() = default;
|
||||
virtual void insert(const std::string &input, const NodeValueRow &row)
|
||||
{
|
||||
(void) input; (void) row;
|
||||
}
|
||||
virtual void insert(const std::string &input, const NodeValue &value)
|
||||
{
|
||||
(void) input; (void) value;
|
||||
}
|
||||
virtual void insert(const NodeValueRow &row)
|
||||
{
|
||||
(void) row;
|
||||
}
|
||||
virtual const NodeValueRow &get_values() const { static NodeValueRow r; return r; }
|
||||
virtual NodeValueRow &get_values() { static NodeValueRow r; return r; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/acceleratedjob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/acceleratedjob.h"
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "render/job/acceleratedjob.h"
|
||||
namespace olive {
|
||||
class CacheJob : public AcceleratedJob {
|
||||
public:
|
||||
CacheJob(const std::string &, const NodeValue &) {}
|
||||
TexturePtr get_fallback() const { return nullptr; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/cachejob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/cachejob.h"
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
#pragma once
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "render/colorprocessor.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
class ColorTransformJob : public AcceleratedJob {
|
||||
public:
|
||||
ColorTransformJob() = default;
|
||||
ColorTransformJob(const NodeValueRow &) {}
|
||||
NodeValue get_input_texture() const { return NodeValue(); }
|
||||
void set_input_texture(const NodeValue &) {}
|
||||
void set_color_processor(ColorProcessorPtr) {}
|
||||
void set_needs_custom_shader(const Node *) {}
|
||||
void set_function_name(const std::string &) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/colortransformjob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/colortransformjob.h"
|
||||
|
||||
@@ -1,27 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "videoparams.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
namespace olive { using core::AudioParams; }
|
||||
#include "loopmode.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
#include "output/track/track.h"
|
||||
namespace olive {
|
||||
class FootageJob : public AcceleratedJob {
|
||||
public:
|
||||
FootageJob() {}
|
||||
FootageJob(const core::TimeRange &, const std::string &,
|
||||
const std::string &, Track::Type, const core::Rational &,
|
||||
LoopMode) {}
|
||||
void set_proxy(const std::string &, const std::string &, int) {}
|
||||
void set_video_params(const VideoParams &) {}
|
||||
void set_audio_params(const AudioParams &) {}
|
||||
void set_cache_path(const std::string &) {}
|
||||
core::TimeRange time() const { return core::TimeRange(); }
|
||||
int loop_mode_as_int() const { return 0; }
|
||||
LoopMode loop_mode() const { return LoopMode::k_loop_mode_off; }
|
||||
core::Rational length() const { return core::Rational(); }
|
||||
const VideoParams &video_params() const { static VideoParams p; return p; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/footagejob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/footagejob.h"
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
#pragma once
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "value.h"
|
||||
namespace olive {
|
||||
class GenerateJob : public AcceleratedJob {
|
||||
public:
|
||||
GenerateJob() {}
|
||||
explicit GenerateJob(const NodeValueRow &) {}
|
||||
NodeValue get(const std::string &) const { return NodeValue(); }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/generatejob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/generatejob.h"
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
#pragma once
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
namespace plugin {
|
||||
class PluginJob : public AcceleratedJob {
|
||||
public:
|
||||
template <typename InstanceT>
|
||||
PluginJob(InstanceT *, const Node *, const NodeValueRow &,
|
||||
const core::Rational &)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/pluginjob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/pluginjob.h"
|
||||
|
||||
@@ -1,31 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "value.h"
|
||||
#include "render/samplebuffer.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
namespace olive {
|
||||
class SampleJob {
|
||||
public:
|
||||
SampleJob() = default;
|
||||
SampleJob(const core::TimeRange &time, const NodeValue &value)
|
||||
{
|
||||
(void) time; (void) value;
|
||||
}
|
||||
SampleJob(const core::TimeRange &time, const std::string &from,
|
||||
const NodeValueRow &values)
|
||||
{
|
||||
(void) time; (void) from; (void) values;
|
||||
}
|
||||
void insert(const std::string &input, const NodeValueRow &row)
|
||||
{
|
||||
(void) input; (void) row;
|
||||
}
|
||||
void insert(const std::string &input, const NodeValue &value)
|
||||
{
|
||||
(void) input; (void) value;
|
||||
}
|
||||
bool operator==(const SampleJob &) const { return true; }
|
||||
const SampleBuffer &samples() const { static SampleBuffer b; return b; }
|
||||
core::TimeRange time() const { return core::TimeRange(); }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/samplejob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/samplejob.h"
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "render/job/acceleratedjob.h"
|
||||
namespace olive {
|
||||
class ShaderJob : public AcceleratedJob {
|
||||
public:
|
||||
ShaderJob() = default;
|
||||
ShaderJob(const NodeValueRow &row) { (void) row; }
|
||||
NodeValue get(const std::string &id) const { (void) id; return NodeValue(); }
|
||||
void set_shader_id(const std::string &id) { (void) id; }
|
||||
void set_iterations(int iterations, const std::string &iterative_input)
|
||||
{
|
||||
(void) iterations; (void) iterative_input;
|
||||
}
|
||||
void set_interpolation(const std::string &id, int mode)
|
||||
{
|
||||
(void) id; (void) mode;
|
||||
}
|
||||
void set_vertex_coordinates(const std::vector<float> &coords)
|
||||
{
|
||||
(void) coords;
|
||||
}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/job/shaderjob.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../../render/src/job/shaderjob.h"
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
#pragma once
|
||||
// Transitional stub for engine/render/lutlibrary.h (still Qt-based). Only the
|
||||
// surface oaknode uses. M7 replaces this with the real oakrender boundary.
|
||||
#include "variant.h"
|
||||
namespace olive {
|
||||
class LUTLibrary {
|
||||
public:
|
||||
static const StringList &supported_extensions()
|
||||
{
|
||||
static const StringList e;
|
||||
return e;
|
||||
}
|
||||
static bool is_supported_extension(const std::string &) { return false; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/lutlibrary.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/lutlibrary.h"
|
||||
|
||||
@@ -1,45 +1,4 @@
|
||||
#pragma once
|
||||
// Syntax-check stub only (not in repo). Minimal de-Qt shape of engine PlaybackCache.
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "olive/core/util/timerange.h"
|
||||
namespace olive {
|
||||
class Node;
|
||||
class ViewerOutput;
|
||||
class PlaybackCache {
|
||||
public:
|
||||
template <typename T> explicit PlaybackCache(T *) {}
|
||||
virtual ~PlaybackCache() {}
|
||||
void set_uuid(const std::string &) {}
|
||||
std::string get_uuid() const { return {}; }
|
||||
core::TimeRangeList get_invalidated_ranges(core::TimeRange intersecting) const
|
||||
{
|
||||
(void) intersecting;
|
||||
return {};
|
||||
}
|
||||
core::TimeRangeList get_invalidated_ranges(const core::Rational &length) const
|
||||
{
|
||||
return get_invalidated_ranges(core::TimeRange(0, length));
|
||||
}
|
||||
void invalidate(const core::TimeRange &) {}
|
||||
void invalidate_all() {}
|
||||
void request(ViewerOutput *, const core::TimeRange &) {}
|
||||
virtual void set_passthrough(PlaybackCache *) {}
|
||||
class Passthrough : public core::TimeRange {
|
||||
public:
|
||||
Passthrough(const core::TimeRange &r, PlaybackCache *cache = nullptr)
|
||||
: core::TimeRange(r)
|
||||
, cache_(cache)
|
||||
{
|
||||
}
|
||||
PlaybackCache *cache() const { return cache_; }
|
||||
|
||||
private:
|
||||
PlaybackCache *cache_;
|
||||
};
|
||||
const std::vector<Passthrough> &get_passthroughs() const { return passthroughs_; }
|
||||
|
||||
private:
|
||||
std::vector<Passthrough> passthroughs_;
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/playbackcache.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/playbackcache.h"
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
#pragma once
|
||||
// Transitional stub for engine/render/previewautocacher.h (still Qt-based).
|
||||
// Only the surface oaknode uses. M7 replaces this with the real oakrender
|
||||
// boundary.
|
||||
namespace olive {
|
||||
class PreviewAutoCacher {
|
||||
public:
|
||||
void cancel_video_tasks(bool) {}
|
||||
void cancel_audio_tasks(bool) {}
|
||||
void cancel_tasks(bool) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/previewautocacher.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/previewautocacher.h"
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
#pragma once
|
||||
// Transitional stub for engine/render/rendermanager.h (still Qt-based). Only
|
||||
// the surface oaknode uses. M7 replaces this with the real oakrender
|
||||
// boundary.
|
||||
namespace olive {
|
||||
class PreviewAutoCacher;
|
||||
class RenderManager {
|
||||
public:
|
||||
static RenderManager *instance() { return nullptr; }
|
||||
PreviewAutoCacher *get_cacher() { return nullptr; }
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/rendermanager.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/rendermanager.h"
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "filefunctions.h"
|
||||
namespace olive {
|
||||
class ShaderCode {
|
||||
public:
|
||||
ShaderCode(const std::string &frag = std::string(), const std::string &vert = std::string()) {}
|
||||
};
|
||||
}
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/shadercode.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
#include "../../../render/src/shadercode.h"
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "videoparams.h"
|
||||
// Bridge: routed to the real de-Qt oakrender header (src/render/src/texture.h,
|
||||
// M7) so oaknode and oakrender see the same class definition.
|
||||
// samplebuffer.h is included first because oaknode's value.h relied on the
|
||||
// old transition texture.h pulling it in transitively.
|
||||
#include "render/samplebuffer.h"
|
||||
#include "olive/core/render/audioparams.h"
|
||||
namespace olive { using core::AudioParams; }
|
||||
namespace olive {
|
||||
class AcceleratedJob;
|
||||
class CacheJob;
|
||||
class Texture;
|
||||
using TexturePtr = std::shared_ptr<Texture>;
|
||||
class Texture {
|
||||
public:
|
||||
enum Interpolation { k_nearest, k_linear, k_mipmapped_linear };
|
||||
Texture(const VideoParams &) {}
|
||||
int width() const { return 0; }
|
||||
int height() const { return 0; }
|
||||
int channel_count() const { return 0; }
|
||||
core::Rational pixel_aspect_ratio() const { return core::Rational(); }
|
||||
Vector2D virtual_resolution() const { return Vector2D(); }
|
||||
AcceleratedJob *job() { return nullptr; }
|
||||
template <typename T> static TexturePtr job(const VideoParams &, const T &) { return nullptr; }
|
||||
const VideoParams ¶ms() const { static VideoParams p; return p; }
|
||||
template <typename T> TexturePtr to_job(const T &) { return nullptr; }
|
||||
};
|
||||
}
|
||||
#include "../../../render/src/texture.h"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(c_api)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,6 @@
|
||||
target_sources(oakrender PRIVATE
|
||||
renderer.cpp
|
||||
cache.cpp
|
||||
color.cpp
|
||||
manager.cpp
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_ALIVECOUNT_H
|
||||
#define OAK_EDITOR_RENDER_ALIVECOUNT_H
|
||||
|
||||
/**
|
||||
* @brief Shared live-object counter hooks (internal, not installed).
|
||||
*
|
||||
* The counter itself and the public oakrender_debug_alive_count() live in
|
||||
* the cache family (src/render/c_api/cache.cpp); these hooks have
|
||||
* external linkage so the other families' create/free functions can
|
||||
* participate. Mirrors src/node/c_api/alivecount.h.
|
||||
*/
|
||||
namespace oakrender_c_api
|
||||
{
|
||||
|
||||
void alive_inc();
|
||||
void alive_dec();
|
||||
|
||||
}
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_ALIVECOUNT_H
|
||||
@@ -0,0 +1,208 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/cache.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "framehashcache.h"
|
||||
#include "playbackcache.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::atomic<int> g_alive_count(0);
|
||||
|
||||
/**
|
||||
* @brief FrameHashCache with the protected PlaybackCache::validate()
|
||||
* exposed for the ABI. Adds no data members, so a handle created
|
||||
* as OakRenderCacheImpl reinterpret-casts safely both ways.
|
||||
*/
|
||||
class OakRenderCacheImpl : public olive::FrameHashCache {
|
||||
public:
|
||||
OakRenderCacheImpl()
|
||||
: olive::FrameHashCache(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
using olive::PlaybackCache::validate;
|
||||
};
|
||||
|
||||
OakRenderCacheImpl *impl(OakRenderCache *c)
|
||||
{
|
||||
return reinterpret_cast<OakRenderCacheImpl *>(c);
|
||||
}
|
||||
|
||||
const OakRenderCacheImpl *impl(const OakRenderCache *c)
|
||||
{
|
||||
return reinterpret_cast<const OakRenderCacheImpl *>(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert an int64 timestamp to a time using the cache's
|
||||
* timebase; timestamps are whole seconds when no valid timebase
|
||||
* is set.
|
||||
*/
|
||||
olive::Rational ts_to_time(const OakRenderCacheImpl *c, int64_t ts)
|
||||
{
|
||||
const olive::Rational &tb = c->get_timebase();
|
||||
if (tb.isNull()) {
|
||||
return olive::Rational::from_double(double(ts));
|
||||
}
|
||||
return olive::core::Timecode::timestamp_to_time(ts, tb);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace oakrender_c_api
|
||||
{
|
||||
|
||||
void alive_inc()
|
||||
{
|
||||
g_alive_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void alive_dec()
|
||||
{
|
||||
g_alive_count.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int oakrender_debug_alive_count(void)
|
||||
{
|
||||
return g_alive_count.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
OakRenderCache *oakrender_cache_create(void)
|
||||
{
|
||||
try {
|
||||
auto *c = new OakRenderCacheImpl();
|
||||
oakrender_c_api::alive_inc();
|
||||
return reinterpret_cast<OakRenderCache *>(c);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_cache_free(OakRenderCache *cache)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
delete impl(cache);
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den)
|
||||
{
|
||||
if (!cache || num <= 0 || den <= 0) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
impl(cache)->set_timebase(olive::Rational(num, den));
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_cache_set_uuid(OakRenderCache *cache, const char *uuid)
|
||||
{
|
||||
if (!cache || !uuid) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
impl(cache)->set_uuid(uuid);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts,
|
||||
int64_t out_ts)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
c->invalidate(
|
||||
olive::core::TimeRange(ts_to_time(c, in_ts), ts_to_time(c, out_ts)));
|
||||
}
|
||||
|
||||
void oakrender_cache_validate(OakRenderCache *cache, int64_t in_ts,
|
||||
int64_t out_ts)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
c->validate(
|
||||
olive::core::TimeRange(ts_to_time(c, in_ts), ts_to_time(c, out_ts)));
|
||||
}
|
||||
|
||||
int oakrender_cache_has_validated_ranges(const OakRenderCache *cache)
|
||||
{
|
||||
return cache && impl(cache)->has_validated_ranges() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oakrender_cache_indicator_height(void)
|
||||
{
|
||||
return olive::PlaybackCache::get_cache_indicator_height();
|
||||
}
|
||||
|
||||
int oakrender_frame_cache_load(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, int64_t ts,
|
||||
OakCodecFrame **out_frame)
|
||||
{
|
||||
if (!cache || !path || !uuid || !out_frame) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::FramePtr f =
|
||||
olive::FrameHashCache::load_cache_frame(path, uuid, ts);
|
||||
if (!f) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
auto *block = new OakCodecFrame;
|
||||
block->ptr = std::move(f);
|
||||
oakrender_c_api::alive_inc();
|
||||
*out_frame = block;
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_frame_cache_save(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, const OakCodecFrame *frame)
|
||||
{
|
||||
if (!cache || !path || !uuid || !frame || !frame->ptr) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
olive::Rational tb = c->get_timebase();
|
||||
if (tb.isNull()) {
|
||||
tb = olive::Rational(1, 1);
|
||||
}
|
||||
olive::FrameHashCache::save_cache_frame(path, uuid,
|
||||
frame->ptr->timestamp(), tb,
|
||||
frame->ptr);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/color.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "filefunctions.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OakColorProcessor *oakrender_color_processor_create(const char *src_space,
|
||||
const char *dst_transform,
|
||||
int direction)
|
||||
{
|
||||
if (!src_space || !*src_space || !dst_transform || !*dst_transform) {
|
||||
return nullptr;
|
||||
}
|
||||
if (direction != OAKRENDER_COLOR_DIRECTION_NORMAL &&
|
||||
direction != OAKRENDER_COLOR_DIRECTION_INVERSE) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Resolve role names (e.g. "scene_linear") to canonical colorspace
|
||||
// names, mirroring ColorProcessor's constructor.
|
||||
std::string src = src_space;
|
||||
if (config->hasRole(src_space)) {
|
||||
src = config->getCanonicalName(src_space);
|
||||
}
|
||||
|
||||
// OCIO failures are non-fatal (matching the C++ behavior): the
|
||||
// handle is still returned, but holds a null processor and
|
||||
// conversions pass through.
|
||||
ocio::ConstProcessorRcPtr processor;
|
||||
try {
|
||||
if (direction == OAKRENDER_COLOR_DIRECTION_NORMAL) {
|
||||
processor = config->getProcessor(src.c_str(), dst_transform);
|
||||
} else {
|
||||
processor = config->getProcessor(dst_transform, src.c_str());
|
||||
}
|
||||
} catch (ocio::Exception &) {
|
||||
processor = nullptr;
|
||||
}
|
||||
|
||||
auto *block = new OakColorProcessor;
|
||||
block->ptr = olive::ColorProcessor::create(processor);
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_color_processor_free(OakColorProcessor *processor)
|
||||
{
|
||||
if (!processor) {
|
||||
return;
|
||||
}
|
||||
delete processor;
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oakrender_color_processor_is_valid(const OakColorProcessor *processor)
|
||||
{
|
||||
return processor && processor->ptr && processor->ptr->get_processor() ? 1 :
|
||||
0;
|
||||
}
|
||||
|
||||
int oakrender_color_processor_convert(OakColorProcessor *processor,
|
||||
double ir, double ig, double ib,
|
||||
double ia, double *out_r, double *out_g,
|
||||
double *out_b, double *out_a)
|
||||
{
|
||||
if (!processor || !processor->ptr || !out_r || !out_g || !out_b || !out_a) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::Color out =
|
||||
processor->ptr->convert_color(olive::Color(ir, ig, ib, ia));
|
||||
*out_r = out.red();
|
||||
*out_g = out.green();
|
||||
*out_b = out.blue();
|
||||
*out_a = out.alpha();
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- ColorManager statics ------------------------------------------------- */
|
||||
|
||||
int oakrender_color_manager_set_up_default_config(void)
|
||||
{
|
||||
try {
|
||||
olive::ColorManager::set_up_default_config();
|
||||
return olive::ColorManager::get_default_config() ? OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_color_manager_get_config(char *buf, int n)
|
||||
{
|
||||
try {
|
||||
const char *ocio_env = std::getenv("OCIO");
|
||||
if (ocio_env && *ocio_env) {
|
||||
return write_string(ocio_env, buf, n);
|
||||
}
|
||||
if (!olive::ColorManager::get_default_config()) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
// The default config is extracted next to the configuration
|
||||
// location (ColorManager::set_up_default_config()).
|
||||
return write_string(FileFunctions::get_configuration_location() +
|
||||
"/ocioconf/config.ocio",
|
||||
buf, n);
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_color_manager_display_transform(const char *display,
|
||||
const char *view, char *buf,
|
||||
int n)
|
||||
{
|
||||
if (!display || !*display || !view || !*view) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
|
||||
bool display_found = false;
|
||||
for (int i = 0; i < config->getNumDisplays(); i++) {
|
||||
if (display == std::string(config->getDisplay(i))) {
|
||||
display_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!display_found) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool view_found = false;
|
||||
for (int i = 0; i < config->getNumViews(display); i++) {
|
||||
if (view == std::string(config->getView(display, i))) {
|
||||
view_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!view_found) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Source = the config's reference colorspace (role lookup).
|
||||
ocio::ConstColorSpaceRcPtr ref_cs =
|
||||
config->getColorSpace(ocio::ROLE_REFERENCE);
|
||||
if (!ref_cs) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
|
||||
auto dvt = ocio::DisplayViewTransform::Create();
|
||||
dvt->setSrc(ref_cs->getName());
|
||||
dvt->setDisplay(display);
|
||||
dvt->setView(view);
|
||||
|
||||
ocio::ConstProcessorRcPtr processor = config->getProcessor(dvt);
|
||||
if (!processor) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
return write_string(processor->getCacheID(), buf, n);
|
||||
} catch (ocio::Exception &) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
#define OAK_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
|
||||
/**
|
||||
* @brief Control-block definitions behind the public opaque handles
|
||||
* (internal, not installed).
|
||||
*
|
||||
* Textures, frames and color processors wrap shared_ptr-managed engine
|
||||
* objects, so their handles are heap control blocks (the R7-A §A.2
|
||||
* ownership protocol). The refcount on textures/frames implements the
|
||||
* retain/free pairing rule; every alive control block participates in
|
||||
* oakrender_debug_alive_count().
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colorprocessor.h"
|
||||
#include "texture.h"
|
||||
|
||||
struct OakRenderTexture {
|
||||
olive::TexturePtr ptr;
|
||||
std::atomic<int> refcount{ 1 };
|
||||
};
|
||||
|
||||
struct OakCodecFrame {
|
||||
olive::FramePtr ptr;
|
||||
std::atomic<int> refcount{ 1 };
|
||||
};
|
||||
|
||||
struct OakColorProcessor {
|
||||
olive::ColorProcessorPtr ptr;
|
||||
};
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
@@ -0,0 +1,221 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/manager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "diskmanager.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
#include "previewautocacher.h"
|
||||
#include "rendermanager.h"
|
||||
#include "renderticket.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
std::mutex g_requests_mutex;
|
||||
std::map<int64_t, olive::RenderTicketPtr> g_requests;
|
||||
std::atomic<int64_t> g_next_request_id(1);
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakrender_manager_init(void)
|
||||
{
|
||||
if (olive::RenderManager::instance()) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
try {
|
||||
olive::RenderManager::create_instance();
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_manager_shutdown(void)
|
||||
{
|
||||
try {
|
||||
olive::RenderManager::destroy_instance();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oakrender_request_frame(OakNodeNode *viewer, int64_t ts,
|
||||
oakrender_frame_ready_fn cb, void *userdata)
|
||||
{
|
||||
if (!viewer || !cb) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
olive::RenderManager *manager = olive::RenderManager::instance();
|
||||
if (!manager) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
auto *v = dynamic_cast<olive::ViewerOutput *>(
|
||||
reinterpret_cast<olive::Node *>(viewer));
|
||||
if (!v) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
// ts is a frame number in the viewer's video timebase; fall back
|
||||
// to whole seconds when the viewer carries no valid timebase.
|
||||
olive::Rational tb = v->get_video_params().time_base();
|
||||
const olive::Rational time = tb.isNull() ?
|
||||
olive::Rational::from_double(double(ts)) :
|
||||
olive::core::Timecode::timestamp_to_time(ts, tb);
|
||||
|
||||
olive::RenderTicketPtr ticket =
|
||||
manager->get_cacher()->get_single_frame(v, time);
|
||||
if (!ticket) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
|
||||
const int64_t id =
|
||||
g_next_request_id.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
olive::RenderTicket *raw_ticket = ticket.get();
|
||||
ticket->set_finished_callback([ticket, cb, ts, userdata, id]() {
|
||||
OakCodecFrame *handle = nullptr;
|
||||
if (ticket->has_result()) {
|
||||
olive::FramePtr f = ticket->get().value<olive::FramePtr>();
|
||||
if (f) {
|
||||
handle = new (std::nothrow) OakCodecFrame;
|
||||
if (handle) {
|
||||
handle->ptr = std::move(f);
|
||||
oakrender_c_api::alive_inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
g_requests.erase(id);
|
||||
}
|
||||
cb(handle, ts, userdata);
|
||||
});
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
g_requests[id] = ticket;
|
||||
}
|
||||
(void) raw_ticket;
|
||||
return id;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_cancel_request(int64_t request_id)
|
||||
{
|
||||
olive::RenderTicketPtr ticket;
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
auto it = g_requests.find(request_id);
|
||||
if (it == g_requests.end()) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
ticket = it->second;
|
||||
g_requests.erase(it);
|
||||
}
|
||||
ticket->cancel();
|
||||
if (olive::RenderManager::instance()) {
|
||||
olive::RenderManager::instance()->remove_ticket(ticket);
|
||||
}
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_set_cacher_multicam(OakNodeNode *multicam_or_NULL)
|
||||
{
|
||||
olive::RenderManager *manager = olive::RenderManager::instance();
|
||||
if (!manager) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
manager->get_cacher()->set_multicam_node(
|
||||
reinterpret_cast<olive::MultiCamNode *>(multicam_or_NULL));
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_set_display_color_processor(OakColorProcessor *p_or_NULL)
|
||||
{
|
||||
olive::RenderManager *manager = olive::RenderManager::instance();
|
||||
if (!manager) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
manager->get_cacher()->set_display_color_processor(
|
||||
p_or_NULL ? p_or_NULL->ptr : nullptr);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
/* ---- Disk cache ----------------------------------------------------------- */
|
||||
|
||||
int oakrender_disk_cache_path(char *buf, int n)
|
||||
{
|
||||
try {
|
||||
return write_string(olive::DiskManager::get_default_disk_cache_path(),
|
||||
buf, n);
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oakrender_disk_cache_size(void)
|
||||
{
|
||||
try {
|
||||
olive::DiskManager *dm = olive::DiskManager::instance();
|
||||
if (!dm || !dm->get_default_cache_folder()) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
return dm->get_default_cache_folder()->get_consumption();
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_disk_cache_clear(void)
|
||||
{
|
||||
try {
|
||||
olive::DiskManager *dm = olive::DiskManager::instance();
|
||||
if (!dm) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
return dm->clear_disk_cache(
|
||||
olive::DiskManager::get_default_disk_cache_path()) ?
|
||||
OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/***
|
||||
|
||||
Oak Video Editor - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/renderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "backend/dynamicrenderer.h"
|
||||
#include "job/colortransformjob.h"
|
||||
#include "opengl/openglrenderer.h"
|
||||
#include "renderer.h"
|
||||
#include "rendermanager.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::VideoParams pod_to_cpp(const oakrender_video_params &v)
|
||||
{
|
||||
olive::VideoParams vp(
|
||||
v.width, v.height, olive::Rational(v.time_base_num, v.time_base_den),
|
||||
static_cast<olive::PixelFormat::Format>(v.format),
|
||||
olive::VideoParams::k_internal_channel_count,
|
||||
olive::Rational(v.pixel_aspect_num, v.pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(v.interlacing),
|
||||
v.divider > 0 ? v.divider : 1);
|
||||
vp.set_color_range(static_cast<olive::VideoParams::ColorRange>(v.color_range));
|
||||
vp.set_premultiplied_alpha(v.premultiplied_alpha != 0);
|
||||
return vp;
|
||||
}
|
||||
|
||||
oakrender_video_params cpp_to_pod(const olive::VideoParams &vp)
|
||||
{
|
||||
oakrender_video_params p = {};
|
||||
p.width = vp.width();
|
||||
p.height = vp.height();
|
||||
p.time_base_num = vp.time_base().numerator();
|
||||
p.time_base_den = vp.time_base().denominator();
|
||||
p.format = static_cast<int>(vp.format());
|
||||
p.pixel_aspect_num = vp.pixel_aspect_ratio().numerator();
|
||||
p.pixel_aspect_den = vp.pixel_aspect_ratio().denominator();
|
||||
p.interlacing = static_cast<int>(vp.interlacing());
|
||||
p.color_range = static_cast<int>(vp.color_range());
|
||||
p.divider = vp.divider();
|
||||
p.video_type = 0;
|
||||
p.premultiplied_alpha = vp.premultiplied_alpha() ? 1 : 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
OakRenderTexture *tex(OakRenderTexture *h)
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
OakCodecFrame *frm(OakCodecFrame *h)
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
olive::Renderer *ren(OakRenderRenderer *h)
|
||||
{
|
||||
return reinterpret_cast<olive::Renderer *>(h);
|
||||
}
|
||||
|
||||
const olive::Renderer *ren(const OakRenderRenderer *h)
|
||||
{
|
||||
return reinterpret_cast<const olive::Renderer *>(h);
|
||||
}
|
||||
|
||||
olive::Matrix4x4 mat_from_float(const float *f)
|
||||
{
|
||||
olive::Matrix4x4 m;
|
||||
if (!f) {
|
||||
return m;
|
||||
}
|
||||
// All-zero means identity (R7-A §A.2 convention)
|
||||
bool all_zero = true;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (f[i] != 0.0f) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_zero) {
|
||||
return m;
|
||||
}
|
||||
// POD is column-major (QMatrix4x4 layout); Matrix4x4 stores [row][col]
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
m(row, col) = f[col * 4 + row];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
// Requested backend recorded through oakrender_set_backend(); applied to
|
||||
// the RenderManager instance when one is created by the facade.
|
||||
std::string g_requested_backend = "opengl";
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---- Renderer lifecycle -------------------------------------------------- */
|
||||
|
||||
OakRenderRenderer *oakrender_display_renderer_create_dynamic(
|
||||
const char *backend_id)
|
||||
{
|
||||
if (!backend_id || !*backend_id) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
auto *r = new olive::DynamicRenderer(backend_id);
|
||||
if (!r->load()) {
|
||||
delete r;
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<OakRenderRenderer *>(r);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakRenderRenderer *oakrender_display_renderer_create_opengl(void)
|
||||
{
|
||||
try {
|
||||
return reinterpret_cast<OakRenderRenderer *>(new olive::OpenGLRenderer());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_renderer_init(OakRenderRenderer *renderer,
|
||||
void *gl_context)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
if (gl_context) {
|
||||
auto *ctx = static_cast<olive::OpenGLContext *>(gl_context);
|
||||
if (auto *gl = dynamic_cast<olive::OpenGLRenderer *>(r)) {
|
||||
gl->init(ctx);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
if (auto *dyn = dynamic_cast<olive::DynamicRenderer *>(r)) {
|
||||
return dyn->init_with_open_gl_context(ctx) ? OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
}
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return r->init() ? OAKRENDER_OK : OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_display_renderer_destroy(OakRenderRenderer *renderer)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
r->destroy();
|
||||
delete r;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Renderer queries ---------------------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_is_open_gl(const OakRenderRenderer *renderer)
|
||||
{
|
||||
return renderer && ren(renderer)->is_open_gl() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oakrender_display_renderer_is_vulkan(const OakRenderRenderer *renderer)
|
||||
{
|
||||
return renderer && ren(renderer)->is_vulkan() ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ---- Texture handle ------------------------------------------------------ */
|
||||
|
||||
OakRenderTexture *oakrender_display_texture_create(
|
||||
OakRenderRenderer *renderer, const oakrender_video_params *params,
|
||||
const void *pixels, int linesize)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !params) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
olive::TexturePtr t = r->create_texture(pod_to_cpp(*params), pixels,
|
||||
linesize);
|
||||
if (!t) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *block = new OakRenderTexture;
|
||||
block->ptr = std::move(t);
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakRenderTexture *oakrender_display_texture_retain(OakRenderTexture *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
}
|
||||
tex(texture)->refcount.fetch_add(1, std::memory_order_relaxed);
|
||||
return texture;
|
||||
}
|
||||
|
||||
void oakrender_display_texture_free(OakRenderTexture *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
if (tex(texture)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete tex(texture);
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_texture_upload(OakRenderTexture *texture,
|
||||
const void *pixels, int linesize)
|
||||
{
|
||||
if (!texture || !pixels || !tex(texture)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
tex(texture)->ptr->upload(const_cast<void *>(pixels), linesize);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_texture_download(OakRenderTexture *texture, void *pixels,
|
||||
int linesize)
|
||||
{
|
||||
if (!texture || !pixels || !tex(texture)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
tex(texture)->ptr->download(pixels, linesize);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_texture_get_params(const OakRenderTexture *texture,
|
||||
oakrender_video_params *out)
|
||||
{
|
||||
if (!texture || !out || !tex(const_cast<OakRenderTexture *>(texture))->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
*out = cpp_to_pod(tex(const_cast<OakRenderTexture *>(texture))->ptr->params());
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_display_texture_id(const OakRenderTexture *texture)
|
||||
{
|
||||
if (!texture || !tex(const_cast<OakRenderTexture *>(texture))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<OakRenderTexture *>(texture))->ptr->id().to_int();
|
||||
}
|
||||
|
||||
/* ---- Frame handle -------------------------------------------------------- */
|
||||
|
||||
OakCodecFrame *oakrender_codec_frame_create(void)
|
||||
{
|
||||
try {
|
||||
auto *block = new OakCodecFrame;
|
||||
block->ptr = olive::Frame::create();
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakCodecFrame *oakrender_codec_frame_retain(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
frm(frame)->refcount.fetch_add(1, std::memory_order_relaxed);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void oakrender_codec_frame_free(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
if (frm(frame)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete frm(frame);
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_set_video_params(
|
||||
OakCodecFrame *frame, const oakrender_video_params *params)
|
||||
{
|
||||
if (!frame || !params || !frm(frame)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
frm(frame)->ptr->set_video_params(pod_to_cpp(*params));
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_get_params(const OakCodecFrame *frame,
|
||||
oakrender_video_params *out)
|
||||
{
|
||||
if (!frame || !out || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
*out = cpp_to_pod(
|
||||
frm(const_cast<OakCodecFrame *>(frame))->ptr->video_params());
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_allocate(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(frame)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return frm(frame)->ptr->allocate() ? OAKRENDER_OK : OAKRENDER_E_FAILED;
|
||||
}
|
||||
|
||||
void *oakrender_codec_frame_data(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(frame)->ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(frame)->ptr->data();
|
||||
}
|
||||
|
||||
const void *oakrender_codec_frame_const_data(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->ptr->const_data();
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_linesize_bytes(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->ptr->linesize_bytes();
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->ptr->is_allocated() ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ---- Color-managed blit -------------------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_blit_color_managed(
|
||||
OakRenderRenderer *renderer, const oakrender_color_transform_job *job,
|
||||
OakRenderTexture *dst_texture, const oakrender_video_params *params)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !job) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::ColorTransformJob ctj;
|
||||
if (job->processor) {
|
||||
ctj.set_color_processor(
|
||||
static_cast<const OakColorProcessor *>(job->processor)->ptr);
|
||||
}
|
||||
if (job->input_texture) {
|
||||
ctj.set_input_texture(
|
||||
static_cast<OakRenderTexture *>(job->input_texture)->ptr);
|
||||
}
|
||||
ctj.set_input_alpha_association(
|
||||
static_cast<olive::AlphaAssociated>(job->input_alpha_association));
|
||||
ctj.set_clear_destination_enabled(job->clear_destination != 0);
|
||||
ctj.set_force_opaque(job->force_opaque != 0);
|
||||
ctj.set_transform_matrix(mat_from_float(job->matrix));
|
||||
ctj.set_crop_matrix(mat_from_float(job->crop_matrix));
|
||||
|
||||
olive::Texture *dst = dst_texture ? tex(dst_texture)->ptr.get() : nullptr;
|
||||
if (params) {
|
||||
r->blit_color_managed(ctj, dst, pod_to_cpp(*params));
|
||||
} else if (dst) {
|
||||
r->blit_color_managed(ctj, dst, dst->params());
|
||||
} else {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Cross-backend texture download -------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_download_from_texture(
|
||||
OakRenderRenderer *renderer, int texture_id,
|
||||
const oakrender_video_params *params, void *dst_pixels, int linesize)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !params || !dst_pixels) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
r->download_from_texture(texture_id, pod_to_cpp(*params), dst_pixels,
|
||||
linesize);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Backend management -------------------------------------------------- */
|
||||
|
||||
int oakrender_backend_count(void)
|
||||
{
|
||||
// olive::RenderManager::Backend: k_open_gl, k_vulkan, k_multi_process,
|
||||
// k_dummy
|
||||
return 4;
|
||||
}
|
||||
|
||||
int oakrender_backend_id_at(int i, char *buf, int n)
|
||||
{
|
||||
if (i < 0 || i >= oakrender_backend_count()) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
return write_string(
|
||||
olive::RenderManager::backend_to_string(
|
||||
static_cast<olive::RenderManager::Backend>(i)),
|
||||
buf, n);
|
||||
}
|
||||
|
||||
int oakrender_set_backend(const char *backend_id)
|
||||
{
|
||||
if (!backend_id) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
std::string lower = backend_id;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return char(std::tolower(c)); });
|
||||
if (lower != "opengl" && lower != "vulkan" && lower != "multiprocess" &&
|
||||
lower != "dummy") {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
g_requested_backend = lower;
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_current_backend(char *buf, int n)
|
||||
{
|
||||
if (olive::RenderManager::instance()) {
|
||||
return write_string(
|
||||
olive::RenderManager::backend_to_string(
|
||||
olive::RenderManager::instance()->backend()),
|
||||
buf, n);
|
||||
}
|
||||
return write_string(g_requested_backend, buf, n);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
file(GLOB_RECURSE OAKRENDER_SOURCES CONFIGURE_DEPENDS *.cpp)
|
||||
|
||||
# The backend_c translation units are the C ABI entry points of the
|
||||
# dynamically loaded backend libraries (liboakgl/liboakvulkan, see
|
||||
# backend/dynamicrenderer.cpp). They export the same oak_renderer_* symbol
|
||||
# table, so they must NOT be linked into liboakrender itself.
|
||||
list(REMOVE_ITEM OAKRENDER_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl/openglbackend_c.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan/vulkanbackend_c.cpp
|
||||
)
|
||||
|
||||
add_library(oakrender SHARED ${OAKRENDER_SOURCES})
|
||||
|
||||
# Dynamically loaded render backends (oak_renderer_* C ABI). Loaded via
|
||||
# dlopen by DynamicRenderer from the app render_backends/ dir.
|
||||
add_library(oakgl SHARED opengl/openglbackend_c.cpp)
|
||||
add_library(oakvulkan SHARED vulkan/vulkanbackend_c.cpp)
|
||||
foreach(backend oakgl oakvulkan)
|
||||
target_link_libraries(${backend} PRIVATE oakrender)
|
||||
endforeach()
|
||||
|
||||
target_include_directories(oakrender PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${OAK_REPO_ROOT}/include
|
||||
${OAK_REPO_ROOT}/src/common/src
|
||||
${OAK_REPO_ROOT}/src/undo/src
|
||||
${OAK_REPO_ROOT}/src/node/src
|
||||
${OAK_REPO_ROOT}/core/include
|
||||
${OAK_REPO_ROOT}/ffmpeg_bridge/include
|
||||
${OAK_REPO_ROOT}/third_party/openfx/include
|
||||
${OCIO_INCLUDE_DIRS}
|
||||
${OIIO_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_ALPHAASSOC_H
|
||||
#define OAK_ALPHAASSOC_H
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
enum AlphaAssociated { k_alpha_none, k_alpha_unassociated, k_alpha_associated };
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_ALPHAASSOC_H
|
||||
@@ -0,0 +1,162 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "audioplaybackcache.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "filefunctions.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const int64_t AudioPlaybackCache::k_default_segment_size_per_channel =
|
||||
10 * 1024 * 1024;
|
||||
|
||||
AudioPlaybackCache::AudioPlaybackCache(Node *parent)
|
||||
: PlaybackCache(parent)
|
||||
{
|
||||
}
|
||||
|
||||
AudioPlaybackCache::~AudioPlaybackCache()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::set_parameters(const AudioParams ¶ms)
|
||||
{
|
||||
if (params_ == params) {
|
||||
return;
|
||||
}
|
||||
|
||||
params_ = params;
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::write_pcm(const TimeRange &range,
|
||||
const TimeRangeList &valid_ranges,
|
||||
const SampleBuffer &samples)
|
||||
{
|
||||
for (const TimeRange &r : valid_ranges) {
|
||||
if (write_part_of_sample_buffer(samples, r.in(), r.in() - range.in(),
|
||||
r.length())) {
|
||||
validate(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::write_silence(const TimeRange &range)
|
||||
{
|
||||
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
|
||||
// it an empty sample buffer
|
||||
write_pcm(range, { range }, SampleBuffer());
|
||||
}
|
||||
|
||||
bool AudioPlaybackCache::write_part_of_sample_buffer(const SampleBuffer &samples,
|
||||
const Rational &write_start,
|
||||
const Rational &buffer_start,
|
||||
const Rational &length)
|
||||
{
|
||||
int64_t length_in_bytes = params_.time_to_bytes_per_channel(length);
|
||||
|
||||
int64_t start_cache_offset = params_.time_to_bytes_per_channel(write_start);
|
||||
int64_t end_cache_offset = start_cache_offset + length_in_bytes;
|
||||
|
||||
int64_t start_buffer_offset =
|
||||
params_.time_to_bytes_per_channel(buffer_start);
|
||||
int64_t end_buffer_offset =
|
||||
std::min(start_buffer_offset + length_in_bytes,
|
||||
params_.samples_to_bytes_per_channel(samples.sample_count()));
|
||||
|
||||
int64_t current_cache_offset = start_cache_offset;
|
||||
int64_t current_buffer_offset = start_buffer_offset;
|
||||
|
||||
bool success = true;
|
||||
|
||||
while (current_cache_offset != end_cache_offset) {
|
||||
int64_t segment = current_cache_offset / k_default_segment_size_per_channel;
|
||||
int64_t segment_start = segment * k_default_segment_size_per_channel;
|
||||
int64_t segment_end = segment_start + k_default_segment_size_per_channel;
|
||||
|
||||
int64_t offset_in_segment = current_cache_offset - segment_start;
|
||||
// Never write past the end of the requested range
|
||||
int64_t write_len = std::min(segment_end - current_cache_offset,
|
||||
end_cache_offset - current_cache_offset);
|
||||
int64_t max_buffer_len = end_buffer_offset - current_buffer_offset;
|
||||
int64_t zero_len = 0;
|
||||
|
||||
if (write_len > max_buffer_len) {
|
||||
zero_len = write_len - max_buffer_len;
|
||||
write_len = max_buffer_len;
|
||||
}
|
||||
|
||||
for (int channel = 0; channel < params_.channel_count(); channel++) {
|
||||
std::string filename = get_segment_filename(segment, channel);
|
||||
|
||||
std::filesystem::path dir =
|
||||
std::filesystem::path(filename).parent_path();
|
||||
if (!FileFunctions::directory_is_valid(dir.string())) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// QFile::ReadWrite creates the file if it doesn't exist; fopen's
|
||||
// "r+b" does not, so fall back to "w+b".
|
||||
std::FILE *f = std::fopen(filename.c_str(), "r+b");
|
||||
if (!f) {
|
||||
f = std::fopen(filename.c_str(), "w+b");
|
||||
}
|
||||
if (f) {
|
||||
std::fseek(f, offset_in_segment, SEEK_SET);
|
||||
if (write_len > 0) {
|
||||
std::fwrite(reinterpret_cast<const char *>(samples.data(channel)) +
|
||||
current_buffer_offset,
|
||||
1, write_len, f);
|
||||
}
|
||||
|
||||
if (zero_len > 0) {
|
||||
std::vector<char> b(zero_len, 0);
|
||||
std::fwrite(b.data(), 1, b.size(), f);
|
||||
}
|
||||
|
||||
std::fclose(f);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
current_cache_offset += write_len + zero_len;
|
||||
current_buffer_offset += write_len;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::string AudioPlaybackCache::get_segment_filename(int64_t segment_index,
|
||||
int channel)
|
||||
{
|
||||
return (std::filesystem::path(get_this_cache_directory()) /
|
||||
(std::to_string(segment_index) + "." + std::to_string(channel)))
|
||||
.string();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_AUDIOPLAYBACKCACHE_H
|
||||
#define OAK_AUDIOPLAYBACKCACHE_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "playbackcache.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A fully integrated system of storing and playing cached audio
|
||||
*
|
||||
* All audio in Olive is processed and rendered in advance. This makes playback extremely smooth
|
||||
* and reliable, but provides some challenges as far as storing and manipulating this audio while
|
||||
* minimizing the amount of re-renders necessary.
|
||||
*
|
||||
* Olive's PlaybackCaches support "shifting"; moving cached data to a different spot on the
|
||||
* timeline without requiring a costly re-render. While video is naturally stored on disk as
|
||||
* separate frames that are easy to swap out, audio works a little differently. It would be
|
||||
* extremely inefficient to store each sample as a separate file on the disk, but storing in
|
||||
* one single contiguous file would be detrimental to shifting, particularly for longer timelines
|
||||
* since the data will actually have to be shifted on disk.
|
||||
*
|
||||
* As such, AudioPlaybackCache compromises by storing audio in several "segments". This makes
|
||||
* operations like shifting much easier since segments can simply be removed from the playlist
|
||||
* rather than having to shift or re-render potentially hours of audio in every operation.
|
||||
*
|
||||
* Naturally, storing in segments means you can't simply play the PCM data like a file, so
|
||||
* AudioPlaybackCache also provides a playback device (accessible from CreatePlaybackDevice()) that
|
||||
* acts identically to a file-based IO device, transparently joining segments together and acting
|
||||
* like one contiguous file.
|
||||
*/
|
||||
class AudioPlaybackCache : public PlaybackCache {
|
||||
public:
|
||||
AudioPlaybackCache(Node *parent = nullptr);
|
||||
|
||||
virtual ~AudioPlaybackCache() override;
|
||||
|
||||
AudioParams get_parameters()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
void set_parameters(const AudioParams ¶ms);
|
||||
|
||||
void write_pcm(const TimeRange &range, const TimeRangeList &valid_ranges,
|
||||
const SampleBuffer &samples);
|
||||
|
||||
void write_silence(const TimeRange &range);
|
||||
|
||||
private:
|
||||
bool write_part_of_sample_buffer(const SampleBuffer &samples,
|
||||
const Rational &write_start,
|
||||
const Rational &buffer_start,
|
||||
const Rational &length);
|
||||
|
||||
std::string get_segment_filename(int64_t segment_index, int channel);
|
||||
|
||||
static const int64_t k_default_segment_size_per_channel;
|
||||
|
||||
AudioParams params_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AUDIOPLAYBACKCACHE_H
|
||||
@@ -0,0 +1,85 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "audiowaveformcache.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super PlaybackCache
|
||||
|
||||
AudioWaveformCache::AudioWaveformCache(Node *parent)
|
||||
: super{ parent }
|
||||
{
|
||||
waveforms_ = std::make_shared<AudioVisualWaveform>();
|
||||
}
|
||||
|
||||
void AudioWaveformCache::write_waveform(const TimeRange &range,
|
||||
const TimeRangeList &valid_ranges,
|
||||
const AudioVisualWaveform *waveform)
|
||||
{
|
||||
// Write each valid range to the segments
|
||||
for (const TimeRange &r : valid_ranges) {
|
||||
if (waveform) {
|
||||
waveforms_->overwrite_sums(*waveform, r.in(), r.in() - range.in(),
|
||||
r.length());
|
||||
}
|
||||
|
||||
validate(r);
|
||||
}
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample
|
||||
AudioWaveformCache::get_summary_from_time(const Rational &start,
|
||||
const Rational &length) const
|
||||
{
|
||||
return waveforms_->get_summary_from_time(start, length);
|
||||
}
|
||||
|
||||
Rational AudioWaveformCache::length() const
|
||||
{
|
||||
return waveforms_->length();
|
||||
}
|
||||
|
||||
void AudioWaveformCache::set_passthrough(PlaybackCache *cache)
|
||||
{
|
||||
AudioWaveformCache *c = static_cast<AudioWaveformCache *>(cache);
|
||||
|
||||
for (const TimeRange &r : c->get_validated_ranges()) {
|
||||
WaveformPassthrough t = r;
|
||||
t.waveform = c->waveforms_;
|
||||
passthroughs_.push_back(t);
|
||||
}
|
||||
passthroughs_.insert(passthroughs_.end(), c->passthroughs_.begin(),
|
||||
c->passthroughs_.end());
|
||||
|
||||
set_parameters(c->get_parameters());
|
||||
set_saving_enabled(c->is_saving_enabled());
|
||||
}
|
||||
|
||||
void AudioWaveformCache::InvalidateEvent(const TimeRange &range)
|
||||
{
|
||||
TimeRangeList::util_remove(&passthroughs_, range);
|
||||
|
||||
super::InvalidateEvent(range);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_AUDIOWAVEFORMCACHE_H
|
||||
#define OAK_AUDIOWAVEFORMCACHE_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "playbackcache.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class AudioWaveformCache : public PlaybackCache {
|
||||
public:
|
||||
AudioWaveformCache(Node *parent = nullptr);
|
||||
|
||||
void write_waveform(const TimeRange &range,
|
||||
const TimeRangeList &valid_ranges,
|
||||
const AudioVisualWaveform *waveform);
|
||||
|
||||
const AudioParams &get_parameters() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
void set_parameters(const AudioParams &p)
|
||||
{
|
||||
params_ = p;
|
||||
waveforms_->set_channel_count(p.channel_count());
|
||||
}
|
||||
|
||||
// The QPainter-based Draw() was UI rendering and moved to the app layer.
|
||||
|
||||
AudioVisualWaveform::Sample
|
||||
get_summary_from_time(const Rational &start, const Rational &length) const;
|
||||
|
||||
Rational length() const;
|
||||
|
||||
virtual void set_passthrough(PlaybackCache *cache) override;
|
||||
|
||||
protected:
|
||||
virtual void InvalidateEvent(const TimeRange &range) override;
|
||||
|
||||
private:
|
||||
using WaveformPtr = std::shared_ptr<AudioVisualWaveform>;
|
||||
|
||||
WaveformPtr waveforms_;
|
||||
|
||||
AudioParams params_;
|
||||
|
||||
class WaveformPassthrough : public TimeRange {
|
||||
public:
|
||||
WaveformPassthrough(const TimeRange &r)
|
||||
: TimeRange(r)
|
||||
{
|
||||
}
|
||||
|
||||
WaveformPtr waveform;
|
||||
};
|
||||
|
||||
std::vector<WaveformPassthrough> passthroughs_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AUDIOWAVEFORMCACHE_H
|
||||
@@ -0,0 +1,407 @@
|
||||
#include "dynamicrenderer.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
#include "../paths.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
static std::string to_lower_copy(std::string s)
|
||||
{
|
||||
for (char &c : s) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c = char(c - 'A' + 'a');
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Stores the requested backend name; the actual backend may later become
|
||||
// OpenGL if loading or availability checks require a Vulkan fallback.
|
||||
DynamicRenderer::DynamicRenderer(const std::string &backend)
|
||||
: backend_(to_lower_copy(backend))
|
||||
{
|
||||
}
|
||||
|
||||
// Tears down the backend in the reverse order used by Load(): release renderer
|
||||
// resources, then destroy the opaque backend object. The shared library itself
|
||||
// is deliberately NOT unloaded: multiple DynamicRenderer instances can wrap the
|
||||
// same backend library, and one instance's dlclose can unmap code that other
|
||||
// instances still reference, producing calls into unmapped memory.
|
||||
// Backend libraries stay mapped until process exit.
|
||||
DynamicRenderer::~DynamicRenderer()
|
||||
{
|
||||
destroy();
|
||||
post_destroy();
|
||||
if (handle_ && destroy_) {
|
||||
destroy_(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the private backend library path for the current platform.
|
||||
// The search is intentionally restricted to Oak-controlled directories so a
|
||||
// system libGL/libvulkan loader is never mistaken for an Oak render backend.
|
||||
std::string DynamicRenderer::library_filename() const
|
||||
{
|
||||
std::string base;
|
||||
if (backend_ == "opengl") {
|
||||
base = "oakgl";
|
||||
} else if (backend_ == "vulkan") {
|
||||
base = "oakvulkan";
|
||||
} else {
|
||||
// Unknown backend: use the name verbatim so the load fails and the
|
||||
// caller's OpenGL fallback engages
|
||||
base = backend_;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
const std::string filename = base + ".dll";
|
||||
#elif defined(__APPLE__)
|
||||
const std::string filename = "lib" + base + ".dylib";
|
||||
#else
|
||||
const std::string filename = "lib" + base + ".so";
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path app_dir(application_dir_path());
|
||||
const std::vector<std::string> candidates = {
|
||||
(app_dir / filename).string(),
|
||||
(app_dir / "render_backends" / filename).lexically_normal().string(),
|
||||
(app_dir / ".." / "lib" / filename).lexically_normal().string(),
|
||||
(app_dir / ".." / ".." / "lib" / filename).lexically_normal().string(),
|
||||
(app_dir / ".." / "engine" / filename).lexically_normal().string(),
|
||||
(app_dir / ".." / ".." / "engine" / filename).lexically_normal().string()
|
||||
};
|
||||
for (const std::string &candidate : candidates) {
|
||||
std::error_code ec;
|
||||
if (fs::exists(candidate, ec)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return candidates.front();
|
||||
}
|
||||
|
||||
// Loads the selected backend, resolves its C ABI table, creates the opaque
|
||||
// backend object, and optionally falls back from Vulkan to OpenGL when runtime
|
||||
// availability checks fail.
|
||||
bool DynamicRenderer::load()
|
||||
{
|
||||
if (handle_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
library_.set_file_name(library_filename());
|
||||
if (!library_.load()) {
|
||||
if (backend_ == "vulkan") {
|
||||
fprintf(stderr,
|
||||
"Failed to load Vulkan render backend %s: %s; falling back "
|
||||
"to OpenGL backend\n",
|
||||
library_.file_name().c_str(),
|
||||
library_.error_string().c_str());
|
||||
backend_ = "opengl";
|
||||
library_.set_file_name(library_filename());
|
||||
}
|
||||
|
||||
if (!library_.load()) {
|
||||
fprintf(stderr, "Failed to load render backend %s %s: %s\n",
|
||||
backend_.c_str(), library_.file_name().c_str(),
|
||||
library_.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolve_functions()) {
|
||||
fprintf(stderr, "Render backend is missing required symbols %s\n",
|
||||
backend_.c_str());
|
||||
library_.unload();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass this so the backend renderer is anchored to the adapter; that way it
|
||||
// follows DynamicRenderer when the latter is adopted by the render thread.
|
||||
// Otherwise it stays in the thread where Load() was called and every GL
|
||||
// operation is rejected as "wrong thread", producing a black screen.
|
||||
handle_ = create_(this);
|
||||
if (!handle_) {
|
||||
library_.unload();
|
||||
return false;
|
||||
}
|
||||
if (is_available_ && !is_available_(handle_)) {
|
||||
fprintf(stderr, "Render backend is not available %s %s\n",
|
||||
backend_.c_str(), library_.file_name().c_str());
|
||||
if (backend_ == "vulkan") {
|
||||
return fallback_to_open_gl();
|
||||
}
|
||||
destroy_(handle_);
|
||||
handle_ = nullptr;
|
||||
library_.unload();
|
||||
return false;
|
||||
}
|
||||
return handle_ != nullptr;
|
||||
}
|
||||
|
||||
// Resolves the mandatory C ABI entry points from the loaded shared library.
|
||||
// Optional information probes are resolved after the required render interface.
|
||||
bool DynamicRenderer::resolve_functions()
|
||||
{
|
||||
reset_functions();
|
||||
#define RESOLVE(member, type, symbol) \
|
||||
member = reinterpret_cast<type>(library_.resolve(symbol)); \
|
||||
if (!member) \
|
||||
return false
|
||||
|
||||
RESOLVE(create_, OakBackendCreateFn, "oak_renderer_create");
|
||||
RESOLVE(destroy_, OakBackendDestroyFn, "oak_renderer_destroy");
|
||||
RESOLVE(init_, OakBackendInitFn, "oak_renderer_init");
|
||||
RESOLVE(init_with_context_, OakBackendInitWithContextFn,
|
||||
"oak_renderer_init_with_context");
|
||||
RESOLVE(post_init_, OakBackendPostInitFn, "oak_renderer_post_init");
|
||||
RESOLVE(post_destroy_, OakBackendPostDestroyFn,
|
||||
"oak_renderer_post_destroy");
|
||||
RESOLVE(destroy_internal_, OakBackendDestroyInternalFn,
|
||||
"oak_renderer_destroy_internal");
|
||||
RESOLVE(clear_destination_, OakBackendClearDestinationFn,
|
||||
"oak_renderer_clear_destination");
|
||||
RESOLVE(create_native_texture_, OakBackendCreateNativeTextureFn,
|
||||
"oak_renderer_create_native_texture");
|
||||
RESOLVE(destroy_native_texture_, OakBackendDestroyNativeTextureFn,
|
||||
"oak_renderer_destroy_native_texture");
|
||||
RESOLVE(create_native_shader_, OakBackendCreateNativeShaderFn,
|
||||
"oak_renderer_create_native_shader");
|
||||
RESOLVE(destroy_native_shader_, OakBackendDestroyNativeShaderFn,
|
||||
"oak_renderer_destroy_native_shader");
|
||||
RESOLVE(upload_to_texture_, OakBackendUploadToTextureFn,
|
||||
"oak_renderer_upload_to_texture");
|
||||
RESOLVE(download_from_texture_, OakBackendDownloadFromTextureFn,
|
||||
"oak_renderer_download_from_texture");
|
||||
RESOLVE(flush_, OakBackendFlushFn, "oak_renderer_flush");
|
||||
RESOLVE(get_pixel_from_texture_, OakBackendGetPixelFromTextureFn,
|
||||
"oak_renderer_get_pixel_from_texture");
|
||||
RESOLVE(blit_, OakBackendBlitFn, "oak_renderer_blit");
|
||||
RESOLVE(attach_output_texture_, OakBackendAttachOutputTextureFn,
|
||||
"oak_renderer_attach_output_texture");
|
||||
RESOLVE(detach_output_texture_, OakBackendDetachOutputTextureFn,
|
||||
"oak_renderer_detach_output_texture");
|
||||
RESOLVE(opengl_context_, OakBackendOpenGLContextFn,
|
||||
"oak_renderer_opengl_context");
|
||||
#undef RESOLVE
|
||||
get_info_ = reinterpret_cast<OakBackendGetInfoFn>(
|
||||
library_.resolve("oak_renderer_get_info"));
|
||||
is_available_ = reinterpret_cast<OakBackendIsAvailableFn>(
|
||||
library_.resolve("oak_renderer_is_available"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Discards a partially-created backend and restarts loading with the OpenGL
|
||||
// backend. This keeps RenderManager's fallback path inside the adapter.
|
||||
bool DynamicRenderer::fallback_to_open_gl()
|
||||
{
|
||||
if (handle_ && destroy_) {
|
||||
destroy_(handle_);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
if (library_.is_loaded()) {
|
||||
library_.unload();
|
||||
}
|
||||
reset_functions();
|
||||
backend_ = "opengl";
|
||||
return load();
|
||||
}
|
||||
|
||||
// Clears all cached C function pointers so a failed backend cannot leave stale
|
||||
// call targets behind for a later fallback load.
|
||||
void DynamicRenderer::reset_functions()
|
||||
{
|
||||
create_ = nullptr;
|
||||
destroy_ = nullptr;
|
||||
get_info_ = nullptr;
|
||||
is_available_ = nullptr;
|
||||
init_ = nullptr;
|
||||
init_with_context_ = nullptr;
|
||||
post_init_ = nullptr;
|
||||
post_destroy_ = nullptr;
|
||||
destroy_internal_ = nullptr;
|
||||
clear_destination_ = nullptr;
|
||||
create_native_texture_ = nullptr;
|
||||
destroy_native_texture_ = nullptr;
|
||||
create_native_shader_ = nullptr;
|
||||
destroy_native_shader_ = nullptr;
|
||||
upload_to_texture_ = nullptr;
|
||||
download_from_texture_ = nullptr;
|
||||
flush_ = nullptr;
|
||||
get_pixel_from_texture_ = nullptr;
|
||||
blit_ = nullptr;
|
||||
attach_output_texture_ = nullptr;
|
||||
detach_output_texture_ = nullptr;
|
||||
opengl_context_ = nullptr;
|
||||
}
|
||||
|
||||
// Returns backend metadata exposed by the dynamic library when available.
|
||||
bool DynamicRenderer::get_backend_info(OakRenderBackendInfo *out_info) const
|
||||
{
|
||||
return handle_ && get_info_ && out_info && get_info_(handle_, out_info);
|
||||
}
|
||||
|
||||
// Initializes the loaded backend using its own context/device creation path.
|
||||
bool DynamicRenderer::init()
|
||||
{
|
||||
return load() && init_(handle_);
|
||||
}
|
||||
|
||||
// Initializes an OpenGL backend against an existing widget context; non-OpenGL
|
||||
// backends may ignore the context on the library side.
|
||||
bool DynamicRenderer::init_with_open_gl_context(OpenGLContext *context)
|
||||
{
|
||||
if (!load()) {
|
||||
return false;
|
||||
}
|
||||
init_with_context_(handle_, context);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forwards post-destroy cleanup to the backend while the library is still
|
||||
// loaded and its symbols are still valid.
|
||||
void DynamicRenderer::post_destroy()
|
||||
{
|
||||
if (handle_ && post_destroy_) {
|
||||
post_destroy_(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs backend post-initialization after Init/InitWithOpenGLContext has
|
||||
// established the device or GL context.
|
||||
void DynamicRenderer::post_init()
|
||||
{
|
||||
if (handle_) {
|
||||
post_init_(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
// Forwards render target clearing through the C ABI.
|
||||
void DynamicRenderer::clear_destination(Texture *texture, double r, double g,
|
||||
double b, double a)
|
||||
{
|
||||
clear_destination_(handle_, texture, r, g, b, a);
|
||||
}
|
||||
|
||||
// Creates a backend-native shader and receives the result as an opaque Variant
|
||||
// because this first-generation ABI still shares C++ types between modules.
|
||||
Variant DynamicRenderer::create_native_shader(ShaderCode code)
|
||||
{
|
||||
Variant out;
|
||||
create_native_shader_(handle_, &code, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Releases a backend-native shader handle.
|
||||
void DynamicRenderer::destroy_native_shader(Variant shader)
|
||||
{
|
||||
destroy_native_shader_(handle_, &shader);
|
||||
}
|
||||
|
||||
// Uploads CPU pixel data into a backend texture through the dynamic ABI.
|
||||
void DynamicRenderer::upload_to_texture(const Variant &handle,
|
||||
const VideoParams ¶ms,
|
||||
const void *data, int linesize)
|
||||
{
|
||||
upload_to_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||
}
|
||||
|
||||
// Downloads backend texture data into a caller-provided CPU buffer.
|
||||
void DynamicRenderer::download_from_texture(const Variant &handle,
|
||||
const VideoParams ¶ms, void *data,
|
||||
int linesize)
|
||||
{
|
||||
download_from_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||
}
|
||||
|
||||
// Waits for backend work to become visible to subsequent CPU or GPU consumers.
|
||||
void DynamicRenderer::flush()
|
||||
{
|
||||
flush_(handle_);
|
||||
}
|
||||
|
||||
// Reads a single pixel through the backend-provided readback hook.
|
||||
Color DynamicRenderer::get_pixel_from_texture(Texture *texture, const PointF &pt)
|
||||
{
|
||||
Color out;
|
||||
get_pixel_from_texture_(handle_, texture, &pt, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Exposes the wrapped OpenGL context when the backend is OpenGL; Vulkan returns
|
||||
// null so callers can avoid GL-only paths.
|
||||
OpenGLContext *DynamicRenderer::open_gl_context() const
|
||||
{
|
||||
return opengl_context_ && handle_ ?
|
||||
static_cast<OpenGLContext *>(opengl_context_(handle_)) :
|
||||
nullptr;
|
||||
}
|
||||
|
||||
// Reports the effective backend after any load-time fallback has completed.
|
||||
bool DynamicRenderer::is_open_gl() const
|
||||
{
|
||||
return backend_ == "opengl";
|
||||
}
|
||||
|
||||
bool DynamicRenderer::is_vulkan() const
|
||||
{
|
||||
return backend_ == "vulkan";
|
||||
}
|
||||
|
||||
// Dispatches a shader blit to the loaded backend.
|
||||
void DynamicRenderer::blit(Variant shader, AcceleratedJob &job,
|
||||
Texture *destination, VideoParams destination_params,
|
||||
bool clear_destination)
|
||||
{
|
||||
blit_(handle_, &shader, &job, destination, &destination_params,
|
||||
clear_destination);
|
||||
}
|
||||
|
||||
// Allocates a backend-native texture and wraps its opaque handle in Variant.
|
||||
Variant DynamicRenderer::create_native_texture(int width, int height, int depth,
|
||||
PixelFormat format,
|
||||
int channel_count,
|
||||
const void *data, int linesize)
|
||||
{
|
||||
Variant out;
|
||||
create_native_texture_(handle_, width, height, depth, format, channel_count,
|
||||
data, linesize, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Releases a backend-native texture handle.
|
||||
void DynamicRenderer::destroy_native_texture(Variant texture)
|
||||
{
|
||||
destroy_native_texture_(handle_, &texture);
|
||||
}
|
||||
|
||||
// Releases renderer-owned backend resources before the backend object itself is
|
||||
// destroyed.
|
||||
void DynamicRenderer::destroy_internal()
|
||||
{
|
||||
if (handle_) {
|
||||
destroy_internal_(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes OFX OpenGL output binding through the dynamic backend when supported.
|
||||
void DynamicRenderer::attach_output_texture(Texture *texture)
|
||||
{
|
||||
if (attach_output_texture_ && texture) {
|
||||
Variant id = texture->id();
|
||||
attach_output_texture_(handle_, &id);
|
||||
}
|
||||
}
|
||||
|
||||
// Clears any OFX output texture binding owned by the backend.
|
||||
void DynamicRenderer::detach_output_texture()
|
||||
{
|
||||
if (detach_output_texture_) {
|
||||
detach_output_texture_(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#ifndef OAK_DYNAMICRENDERER_H
|
||||
#define OAK_DYNAMICRENDERER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "dynlib.h"
|
||||
#include "renderbackend_c.h"
|
||||
#include "../opengl/openglcontextprovider.h"
|
||||
#include "../renderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
// C++ Renderer adapter that loads an Oak render backend shared library and
|
||||
// forwards Renderer calls through the backend's C ABI.
|
||||
class DynamicRenderer : public Renderer, public OpenGLContextProvider {
|
||||
public:
|
||||
// Stores the requested backend name; Load() may change it after fallback.
|
||||
explicit DynamicRenderer(const std::string &backend);
|
||||
// Destroys backend resources and unloads the dynamic library.
|
||||
virtual ~DynamicRenderer() override;
|
||||
|
||||
using Renderer::blit;
|
||||
|
||||
// Loads the backend library, resolves C ABI symbols, and creates the handle.
|
||||
bool load();
|
||||
// Initializes an OpenGL backend with a caller-owned viewer context.
|
||||
bool init_with_open_gl_context(OpenGLContext *context);
|
||||
// Retrieves backend metadata through the optional info entry point.
|
||||
bool get_backend_info(OakRenderBackendInfo *out_info) const;
|
||||
// Returns the effective backend after any load-time fallback.
|
||||
std::string backend_name() const
|
||||
{
|
||||
return backend_;
|
||||
}
|
||||
|
||||
// Initializes the backend using its default device/context path.
|
||||
virtual bool init() override;
|
||||
// Runs backend post-destroy cleanup.
|
||||
virtual void post_destroy() override;
|
||||
// Runs backend post-init setup.
|
||||
virtual void post_init() override;
|
||||
// Clears either a native texture destination or the backend output target.
|
||||
virtual void clear_destination(Texture *texture = nullptr, double r = 0.0,
|
||||
double g = 0.0, double b = 0.0,
|
||||
double a = 0.0) override;
|
||||
// Creates a native shader through the dynamic backend.
|
||||
virtual Variant create_native_shader(ShaderCode code) override;
|
||||
// Destroys a native shader through the dynamic backend.
|
||||
virtual void destroy_native_shader(Variant shader) override;
|
||||
// Uploads CPU pixels to a backend texture.
|
||||
virtual void upload_to_texture(const Variant &handle,
|
||||
const VideoParams ¶ms, const void *data,
|
||||
int linesize) override;
|
||||
// Downloads backend texture pixels to CPU memory.
|
||||
virtual void download_from_texture(const Variant &handle,
|
||||
const VideoParams ¶ms, void *data,
|
||||
int linesize) override;
|
||||
// Waits for backend work to complete.
|
||||
virtual void flush() override;
|
||||
// Reads one pixel from a backend texture.
|
||||
virtual Color get_pixel_from_texture(Texture *texture,
|
||||
const PointF &pt) override;
|
||||
// Returns the wrapped OpenGL context for OpenGL backends.
|
||||
virtual OpenGLContext *open_gl_context() const override;
|
||||
|
||||
// Reports whether the effective backend is OpenGL.
|
||||
virtual bool is_open_gl() const override;
|
||||
// Reports whether the effective backend is Vulkan.
|
||||
virtual bool is_vulkan() const override;
|
||||
|
||||
// Attaches a texture for OFX OpenGL output when supported.
|
||||
virtual void attach_output_texture(Texture *texture) override;
|
||||
|
||||
// Detaches any OFX output texture binding when supported.
|
||||
virtual void detach_output_texture() override;
|
||||
|
||||
protected:
|
||||
// Dispatches a shader blit through the dynamic backend.
|
||||
virtual void blit(Variant shader, AcceleratedJob &job,
|
||||
Texture *destination, VideoParams destination_params,
|
||||
bool clear_destination) override;
|
||||
// Allocates a native texture through the dynamic backend.
|
||||
virtual Variant create_native_texture(int width, int height, int depth,
|
||||
PixelFormat format, int channel_count,
|
||||
const void *data = nullptr,
|
||||
int linesize = 0) override;
|
||||
// Releases a native texture through the dynamic backend.
|
||||
virtual void destroy_native_texture(Variant texture) override;
|
||||
// Releases backend-owned renderer resources.
|
||||
virtual void destroy_internal() override;
|
||||
|
||||
private:
|
||||
// Resolves required backend C ABI symbols.
|
||||
bool resolve_functions();
|
||||
// Replaces a failed Vulkan backend with OpenGL.
|
||||
bool fallback_to_open_gl();
|
||||
// Clears all cached function pointers.
|
||||
void reset_functions();
|
||||
// Resolves the private backend library path.
|
||||
std::string library_filename() const;
|
||||
|
||||
std::string backend_;
|
||||
DynLib library_;
|
||||
OakRenderBackendHandle handle_ = nullptr;
|
||||
|
||||
OakBackendCreateFn create_ = nullptr;
|
||||
OakBackendDestroyFn destroy_ = nullptr;
|
||||
OakBackendGetInfoFn get_info_ = nullptr;
|
||||
OakBackendIsAvailableFn is_available_ = nullptr;
|
||||
OakBackendInitFn init_ = nullptr;
|
||||
OakBackendInitWithContextFn init_with_context_ = nullptr;
|
||||
OakBackendPostInitFn post_init_ = nullptr;
|
||||
OakBackendPostDestroyFn post_destroy_ = nullptr;
|
||||
OakBackendDestroyInternalFn destroy_internal_ = nullptr;
|
||||
OakBackendClearDestinationFn clear_destination_ = nullptr;
|
||||
OakBackendCreateNativeTextureFn create_native_texture_ = nullptr;
|
||||
OakBackendDestroyNativeTextureFn destroy_native_texture_ = nullptr;
|
||||
OakBackendCreateNativeShaderFn create_native_shader_ = nullptr;
|
||||
OakBackendDestroyNativeShaderFn destroy_native_shader_ = nullptr;
|
||||
OakBackendUploadToTextureFn upload_to_texture_ = nullptr;
|
||||
OakBackendDownloadFromTextureFn download_from_texture_ = nullptr;
|
||||
OakBackendFlushFn flush_ = nullptr;
|
||||
OakBackendGetPixelFromTextureFn get_pixel_from_texture_ = nullptr;
|
||||
OakBackendBlitFn blit_ = nullptr;
|
||||
OakBackendAttachOutputTextureFn attach_output_texture_ = nullptr;
|
||||
OakBackendDetachOutputTextureFn detach_output_texture_ = nullptr;
|
||||
OakBackendOpenGLContextFn opengl_context_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DYNAMICRENDERER_H
|
||||
@@ -0,0 +1,122 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_DYNLIB_H
|
||||
#define OAK_DYNLIB_H
|
||||
|
||||
// Minimal QLibrary replacement for loading render backend shared libraries.
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DynLib {
|
||||
public:
|
||||
DynLib() = default;
|
||||
|
||||
~DynLib()
|
||||
{
|
||||
unload();
|
||||
}
|
||||
|
||||
DynLib(const DynLib &) = delete;
|
||||
DynLib &operator=(const DynLib &) = delete;
|
||||
|
||||
void set_file_name(const std::string &path)
|
||||
{
|
||||
unload();
|
||||
path_ = path;
|
||||
}
|
||||
|
||||
const std::string &file_name() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
bool load()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
handle_ = LoadLibraryA(path_.c_str());
|
||||
if (!handle_) {
|
||||
error_string_ = "LoadLibrary failed";
|
||||
}
|
||||
#else
|
||||
handle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle_) {
|
||||
const char *err = dlerror();
|
||||
error_string_ = err ? err : "dlopen failed";
|
||||
}
|
||||
#endif
|
||||
return handle_ != nullptr;
|
||||
}
|
||||
|
||||
bool unload()
|
||||
{
|
||||
if (!handle_) {
|
||||
return true;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
const bool ok = FreeLibrary(HMODULE(handle_)) != 0;
|
||||
#else
|
||||
const bool ok = dlclose(handle_) == 0;
|
||||
#endif
|
||||
handle_ = nullptr;
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool is_loaded() const
|
||||
{
|
||||
return handle_ != nullptr;
|
||||
}
|
||||
|
||||
void *resolve(const char *symbol)
|
||||
{
|
||||
if (!handle_) {
|
||||
return nullptr;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
return reinterpret_cast<void *>(
|
||||
GetProcAddress(HMODULE(handle_), symbol));
|
||||
#else
|
||||
return dlsym(handle_, symbol);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string error_string() const
|
||||
{
|
||||
return error_string_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
void *handle_ = nullptr;
|
||||
std::string error_string_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DYNLIB_H
|
||||
@@ -0,0 +1,121 @@
|
||||
#ifndef OAK_RENDERBACKEND_C_H
|
||||
#define OAK_RENDERBACKEND_C_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define OAK_RENDER_BACKEND_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define OAK_RENDER_BACKEND_EXPORT \
|
||||
extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Opaque pointer to the backend-owned C++ renderer object. */
|
||||
typedef void *OakRenderBackendHandle;
|
||||
|
||||
/* Identifies the concrete backend behind a dynamically loaded library. */
|
||||
enum OakRenderBackendKind {
|
||||
oak_render_backend_unknown = 0,
|
||||
oak_render_backend_opengl = 1,
|
||||
oak_render_backend_vulkan = 2
|
||||
};
|
||||
|
||||
/* Capability bits advertised by a backend through oak_renderer_get_info(). */
|
||||
enum OakRenderBackendCapability {
|
||||
oak_render_backend_cap_textures = 1ULL << 0,
|
||||
oak_render_backend_cap_shaders = 1ULL << 1,
|
||||
oak_render_backend_cap_blit = 1ULL << 2,
|
||||
oak_render_backend_cap_readback = 1ULL << 3,
|
||||
oak_render_backend_cap_viewer_context = 1ULL << 4,
|
||||
oak_render_backend_cap_instance = 1ULL << 5,
|
||||
oak_render_backend_cap_device = 1ULL << 6
|
||||
};
|
||||
|
||||
/* Static and runtime metadata returned by the backend. */
|
||||
struct OakRenderBackendInfo {
|
||||
uint32_t abi_version;
|
||||
uint32_t kind;
|
||||
uint64_t capabilities;
|
||||
const char *name;
|
||||
const char *status;
|
||||
};
|
||||
|
||||
/* Creates a backend renderer object. */
|
||||
typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent);
|
||||
/* Destroys a backend renderer object created by OakBackendCreateFn. */
|
||||
typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle);
|
||||
/* Queries backend metadata and capability bits. */
|
||||
typedef bool (*OakBackendGetInfoFn)(OakRenderBackendHandle handle,
|
||||
struct OakRenderBackendInfo *out_info);
|
||||
/* Checks whether the backend can run on the current machine. */
|
||||
typedef bool (*OakBackendIsAvailableFn)(OakRenderBackendHandle handle);
|
||||
/* Initializes backend-owned device/context resources. */
|
||||
typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle);
|
||||
/* Initializes the backend against a caller-supplied GL context when applicable. */
|
||||
typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle,
|
||||
void *context);
|
||||
/* Runs backend post-initialization after the device/context exists. */
|
||||
typedef void (*OakBackendPostInitFn)(OakRenderBackendHandle handle);
|
||||
/* Runs backend post-destroy cleanup before the library unloads. */
|
||||
typedef void (*OakBackendPostDestroyFn)(OakRenderBackendHandle handle);
|
||||
/* Destroys renderer-owned native resources. */
|
||||
typedef void (*OakBackendDestroyInternalFn)(OakRenderBackendHandle handle);
|
||||
/* Clears a texture destination or implicit output target. */
|
||||
typedef void (*OakBackendClearDestinationFn)(OakRenderBackendHandle handle,
|
||||
void *texture, double r, double g,
|
||||
double b, double a);
|
||||
/* Creates a native texture and writes a QVariant-compatible handle. */
|
||||
typedef void (*OakBackendCreateNativeTextureFn)(
|
||||
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
||||
int channel_count, const void *data, int linesize, void *out_variant);
|
||||
/* Destroys a native texture represented by a QVariant-compatible handle. */
|
||||
typedef void (*OakBackendDestroyNativeTextureFn)(OakRenderBackendHandle handle,
|
||||
const void *variant);
|
||||
/* Creates a native shader and writes a QVariant-compatible handle. */
|
||||
typedef void (*OakBackendCreateNativeShaderFn)(OakRenderBackendHandle handle,
|
||||
const void *shader_code,
|
||||
void *out_variant);
|
||||
/* Destroys a native shader represented by a QVariant-compatible handle. */
|
||||
typedef void (*OakBackendDestroyNativeShaderFn)(OakRenderBackendHandle handle,
|
||||
const void *variant);
|
||||
/* Uploads CPU pixel data to a native texture. */
|
||||
typedef void (*OakBackendUploadToTextureFn)(OakRenderBackendHandle handle,
|
||||
const void *variant,
|
||||
const void *video_params,
|
||||
const void *data, int linesize);
|
||||
/* Downloads native texture pixels into caller-owned CPU memory. */
|
||||
typedef void (*OakBackendDownloadFromTextureFn)(OakRenderBackendHandle handle,
|
||||
const void *variant,
|
||||
const void *video_params,
|
||||
void *data, int linesize);
|
||||
/* Waits for backend work that must be visible to later operations. */
|
||||
typedef void (*OakBackendFlushFn)(OakRenderBackendHandle handle);
|
||||
/* Reads one pixel from a texture. */
|
||||
typedef void (*OakBackendGetPixelFromTextureFn)(OakRenderBackendHandle handle,
|
||||
void *texture,
|
||||
const void *point,
|
||||
void *out_color);
|
||||
/* Executes a shader blit job. */
|
||||
typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
|
||||
const void *shader, void *job,
|
||||
void *destination,
|
||||
const void *destination_params,
|
||||
bool clear_destination);
|
||||
/* Attaches an output texture for OFX OpenGL rendering when supported. */
|
||||
typedef void (*OakBackendAttachOutputTextureFn)(OakRenderBackendHandle handle,
|
||||
const void *texture_id);
|
||||
/* Detaches an OFX output texture when supported. */
|
||||
typedef void (*OakBackendDetachOutputTextureFn)(OakRenderBackendHandle handle);
|
||||
/* Returns the backend OpenGL context, or null for non-OpenGL backends. */
|
||||
typedef void *(*OakBackendOpenGLContextFn)(OakRenderBackendHandle handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // OAK_RENDERBACKEND_C_H
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OAK_CANCELATOM_H
|
||||
#define OAK_CANCELATOM_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CancelAtom {
|
||||
public:
|
||||
CancelAtom()
|
||||
: cancelled_(false)
|
||||
, heard_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_cancelled()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
if (cancelled_) {
|
||||
heard_ = true;
|
||||
}
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
bool heard_cancel()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
return heard_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
|
||||
bool cancelled_;
|
||||
|
||||
bool heard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CANCELATOM_H
|
||||
@@ -0,0 +1,247 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "filefunctions.h"
|
||||
#include "node.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
// Replaces the first "%1" marker, mirroring the QString::arg() call the
|
||||
// shader template substitution below used before de-Qt.
|
||||
std::string arg1(const std::string &fmt, const std::string &arg)
|
||||
{
|
||||
std::string result = fmt;
|
||||
std::string::size_type pos = result.find("%1");
|
||||
if (pos != std::string::npos) {
|
||||
result.replace(pos, 2, arg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::get_color_context(const ColorTransformJob &color_job,
|
||||
Renderer::ColorContext *ctx)
|
||||
{
|
||||
std::unique_lock<std::mutex> locker(color_cache_mutex_);
|
||||
|
||||
ColorContext &color_ctx = *ctx;
|
||||
|
||||
std::string proc_id = color_job.id();
|
||||
|
||||
if (color_cache_.count(proc_id)) {
|
||||
color_ctx = color_cache_.at(proc_id);
|
||||
return true;
|
||||
} else {
|
||||
locker.unlock();
|
||||
|
||||
// Create shader description
|
||||
std::string ocio_func_name;
|
||||
if (color_job.get_function_name().empty()) {
|
||||
ocio_func_name = "OCIODisplay";
|
||||
} else {
|
||||
ocio_func_name = color_job.get_function_name();
|
||||
}
|
||||
auto shader_desc = ocio::GpuShaderDesc::CreateShaderDesc();
|
||||
shader_desc->setLanguage(ocio::GPU_LANGUAGE_GLSL_ES_3_0);
|
||||
shader_desc->setFunctionName(ocio_func_name.c_str());
|
||||
shader_desc->setResourcePrefix("ocio_");
|
||||
|
||||
// Generate shader
|
||||
color_job.get_color_processor()
|
||||
->get_processor()
|
||||
->getDefaultGPUProcessor()
|
||||
->extractGpuShaderInfo(shader_desc);
|
||||
|
||||
ShaderCode code;
|
||||
if (const Node *shader_src = color_job.custom_shader_source()) {
|
||||
// Use shader code from associated node
|
||||
code = shader_src->get_shader_code(
|
||||
{ color_job.custom_shader_id(), shader_desc->getShaderText() });
|
||||
} else {
|
||||
// Generate shader code using OCIO stub and our auto-generated name
|
||||
code = FileFunctions::read_file_as_string(
|
||||
":/shaders/colormanage.frag");
|
||||
code.set_frag_code(
|
||||
arg1(code.frag_code(), shader_desc->getShaderText()));
|
||||
}
|
||||
|
||||
// Try to compile shader
|
||||
color_ctx.compiled_shader = create_native_shader(code);
|
||||
|
||||
if (color_ctx.compiled_shader.is_null()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNum3DTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int edge_len = 0;
|
||||
ocio::Interpolation interpolation = ocio::INTERP_LINEAR;
|
||||
|
||||
shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len,
|
||||
interpolation);
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!edge_len) {
|
||||
fprintf(stderr, "3D LUT texture data is corrupted\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->get3DTextureValues(i, values);
|
||||
if (!values) {
|
||||
fprintf(stderr, "3D LUT texture values are missing\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 3D LUT
|
||||
color_ctx.lut3d_textures[i].texture = create_texture(
|
||||
VideoParams(edge_len, edge_len, edge_len, PixelFormat::f32,
|
||||
VideoParams::k_rgb_channel_count),
|
||||
values);
|
||||
color_ctx.lut3d_textures[i].name = sampler_name;
|
||||
color_ctx.lut3d_textures[i].interpolation =
|
||||
(interpolation == ocio::INTERP_NEAREST) ? Texture::k_nearest :
|
||||
Texture::k_linear;
|
||||
}
|
||||
|
||||
color_ctx.lut1d_textures.resize(shader_desc->getNumTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNumTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int width = 0, height = 0;
|
||||
ocio::GpuShaderDesc::TextureType channel =
|
||||
ocio::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
|
||||
ocio::Interpolation interpolation = ocio::INTERP_LINEAR;
|
||||
#if OCIO_VERSION_MAJOR > 2 || \
|
||||
(OCIO_VERSION_MAJOR == 2 && OCIO_VERSION_MINOR >= 3)
|
||||
ocio::GpuShaderDesc::TextureDimensions dimensions =
|
||||
ocio::GpuShaderDesc::TEXTURE_2D;
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, dimensions, interpolation);
|
||||
#else
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, interpolation);
|
||||
#endif
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!width) {
|
||||
fprintf(stderr, "1D LUT texture data is corrupted\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->getTextureValues(i, values);
|
||||
if (!values) {
|
||||
fprintf(stderr, "1D LUT texture values are missing\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 1D LUT
|
||||
int lut_channels =
|
||||
(channel == ocio::GpuShaderDesc::TEXTURE_RED_CHANNEL) ?
|
||||
1 :
|
||||
VideoParams::k_rgb_channel_count;
|
||||
VideoParams lut_params(width, height, PixelFormat::f32,
|
||||
lut_channels);
|
||||
color_ctx.lut1d_textures[i].texture =
|
||||
create_texture(lut_params, values);
|
||||
color_ctx.lut1d_textures[i].name = sampler_name;
|
||||
color_ctx.lut1d_textures[i].interpolation =
|
||||
(interpolation == ocio::INTERP_NEAREST) ? Texture::k_nearest :
|
||||
Texture::k_linear;
|
||||
}
|
||||
|
||||
locker.lock();
|
||||
color_cache_.insert({ proc_id, color_ctx });
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::blit_color_managed(const ColorTransformJob &color_job,
|
||||
Texture *destination, const VideoParams ¶ms)
|
||||
{
|
||||
ColorContext color_ctx;
|
||||
if (!get_color_context(color_job, &color_ctx)) {
|
||||
ShaderJob fallback_job;
|
||||
fallback_job.insert("ove_maintex",
|
||||
color_job.get_input_texture());
|
||||
fallback_job.insert("ove_mvpmat",
|
||||
NodeValue(NodeValue::k_matrix,
|
||||
color_job.get_transform_matrix()));
|
||||
|
||||
if (destination) {
|
||||
blit_to_texture(get_default_shader(), fallback_job, destination,
|
||||
color_job.is_clear_destination_enabled());
|
||||
} else {
|
||||
blit(get_default_shader(), fallback_job, params,
|
||||
color_job.is_clear_destination_enabled());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ShaderJob job;
|
||||
job.insert("ove_maintex", color_job.get_input_texture());
|
||||
job.insert("ove_mvpmat",
|
||||
NodeValue(NodeValue::k_matrix, color_job.get_transform_matrix()));
|
||||
job.insert("ove_cropmatrix",
|
||||
NodeValue(NodeValue::k_matrix,
|
||||
color_job.get_crop_matrix().inverted()));
|
||||
job.insert("ove_maintex_alpha",
|
||||
NodeValue(NodeValue::k_int,
|
||||
int(color_job.get_input_alpha_association())));
|
||||
job.insert("ove_force_opaque",
|
||||
NodeValue(NodeValue::k_boolean, color_job.get_force_opaque()));
|
||||
job.insert(color_job.get_values());
|
||||
|
||||
for (const ColorContext::LUT &l : color_ctx.lut3d_textures) {
|
||||
job.insert(l.name, NodeValue(NodeValue::k_texture,
|
||||
Variant::from_value(l.texture)));
|
||||
job.set_interpolation(l.name, l.interpolation);
|
||||
}
|
||||
for (const ColorContext::LUT &l : color_ctx.lut1d_textures) {
|
||||
job.insert(l.name, NodeValue(NodeValue::k_texture,
|
||||
Variant::from_value(l.texture)));
|
||||
job.set_interpolation(l.name, l.interpolation);
|
||||
}
|
||||
|
||||
if (destination) {
|
||||
blit_to_texture(color_ctx.compiled_shader, job, destination,
|
||||
color_job.is_clear_destination_enabled());
|
||||
} else {
|
||||
blit(color_ctx.compiled_shader, job, params,
|
||||
color_job.is_clear_destination_enabled());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "colorprocessor.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "define.h"
|
||||
#include "ocioutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ColorProcessor::ColorProcessor(ColorManager *config, const std::string &input,
|
||||
const ColorTransform &transform,
|
||||
Direction direction)
|
||||
{
|
||||
processor_ = nullptr;
|
||||
cpu_processor_ = nullptr;
|
||||
|
||||
try {
|
||||
// Resolve role names (e.g. "scene_linear") to canonical colorspace names
|
||||
// so they can be passed to getProcessor()/DisplayViewTransform.
|
||||
std::string resolved_input = input;
|
||||
ocio::ConstConfigRcPtr ocio_config = config->get_config();
|
||||
if (ocio_config && ocio_config->hasRole(input.c_str())) {
|
||||
resolved_input = ocio_config->getCanonicalName(input.c_str());
|
||||
}
|
||||
|
||||
const std::string &output = (transform.output().empty()) ?
|
||||
config->get_default_display() :
|
||||
transform.output();
|
||||
|
||||
if (transform.is_display()) {
|
||||
const std::string &view = (transform.view().empty()) ?
|
||||
config->get_default_view(output) :
|
||||
transform.view();
|
||||
|
||||
auto display_transform = ocio::DisplayViewTransform::Create();
|
||||
|
||||
display_transform->setSrc(resolved_input.c_str());
|
||||
display_transform->setDisplay(output.c_str());
|
||||
display_transform->setView(view.c_str());
|
||||
display_transform->setDirection(direction == k_normal ?
|
||||
ocio::TRANSFORM_DIR_FORWARD :
|
||||
ocio::TRANSFORM_DIR_INVERSE);
|
||||
|
||||
if (transform.look().empty()) {
|
||||
processor_ = ocio_config->getProcessor(display_transform);
|
||||
} else {
|
||||
auto group = ocio::GroupTransform::Create();
|
||||
|
||||
const char *out_cs =
|
||||
ocio::LookTransform::GetLooksResultColorSpace(
|
||||
ocio_config, ocio_config->getCurrentContext(),
|
||||
transform.look().c_str());
|
||||
|
||||
auto lt = ocio::LookTransform::Create();
|
||||
lt->setSrc(resolved_input.c_str());
|
||||
lt->setDst(out_cs);
|
||||
lt->setLooks(transform.look().c_str());
|
||||
lt->setSkipColorSpaceConversion(false);
|
||||
group->appendTransform(lt);
|
||||
|
||||
display_transform->setSrc(out_cs);
|
||||
group->appendTransform(display_transform);
|
||||
|
||||
processor_ = ocio_config->getProcessor(group);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (direction == k_normal) {
|
||||
processor_ = ocio_config->getProcessor(resolved_input.c_str(),
|
||||
output.c_str());
|
||||
} else {
|
||||
processor_ = ocio_config->getProcessor(output.c_str(),
|
||||
resolved_input.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (processor_) {
|
||||
cpu_processor_ = processor_->getDefaultCPUProcessor();
|
||||
}
|
||||
} catch (ocio::Exception &e) {
|
||||
fprintf(stderr, "ColorProcessor exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ColorProcessor::ColorProcessor(ocio::ConstProcessorRcPtr processor)
|
||||
{
|
||||
processor_ = processor;
|
||||
cpu_processor_ = processor_ ? processor_->getDefaultCPUProcessor() :
|
||||
nullptr;
|
||||
}
|
||||
|
||||
void ColorProcessor::convert_frame(Frame *f)
|
||||
{
|
||||
if (!cpu_processor_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ocio::BitDepth ocio_bit_depth =
|
||||
OCIOUtils::get_ocio_bit_depth_from_pixel_format(f->format());
|
||||
|
||||
if (ocio_bit_depth == ocio::BIT_DEPTH_UNKNOWN) {
|
||||
fprintf(stderr, "Tried to color convert frame with no format\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ocio::PackedImageDesc img(f->data(), f->width(), f->height(),
|
||||
f->channel_count(), ocio_bit_depth,
|
||||
ocio::AutoStride, ocio::AutoStride,
|
||||
f->linesize_bytes());
|
||||
|
||||
cpu_processor_->apply(img);
|
||||
}
|
||||
|
||||
Color ColorProcessor::convert_color(const Color &in)
|
||||
{
|
||||
if (!cpu_processor_) {
|
||||
return in;
|
||||
}
|
||||
|
||||
// I've been bamboozled
|
||||
float c[4] = { float(in.red()), float(in.green()), float(in.blue()),
|
||||
float(in.alpha()) };
|
||||
|
||||
cpu_processor_->applyRGBA(c);
|
||||
|
||||
return Color(c[0], c[1], c[2], c[3]);
|
||||
}
|
||||
|
||||
ColorProcessorPtr ColorProcessor::create(ColorManager *config,
|
||||
const std::string &input,
|
||||
const ColorTransform &transform,
|
||||
Direction direction)
|
||||
{
|
||||
return std::make_shared<ColorProcessor>(config, input, transform,
|
||||
direction);
|
||||
}
|
||||
|
||||
ColorProcessorPtr ColorProcessor::create(ocio::ConstProcessorRcPtr processor)
|
||||
{
|
||||
return std::make_shared<ColorProcessor>(processor);
|
||||
}
|
||||
|
||||
ocio::ConstProcessorRcPtr ColorProcessor::get_processor()
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
|
||||
void ColorProcessor::convert_frame(FramePtr f)
|
||||
{
|
||||
convert_frame(f.get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_COLORPROCESSOR_H
|
||||
#define OAK_COLORPROCESSOR_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colortransform.h"
|
||||
#include "define.h"
|
||||
#include "ocioutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ColorManager;
|
||||
|
||||
class ColorProcessor;
|
||||
using ColorProcessorPtr = std::shared_ptr<ColorProcessor>;
|
||||
|
||||
class ColorProcessor {
|
||||
public:
|
||||
enum Direction { k_normal, k_inverse };
|
||||
|
||||
ColorProcessor(ColorManager *config, const std::string &input,
|
||||
const ColorTransform &dest_space,
|
||||
Direction direction = k_normal);
|
||||
ColorProcessor(ocio::ConstProcessorRcPtr processor);
|
||||
|
||||
DISABLE_COPY_MOVE(ColorProcessor)
|
||||
|
||||
static ColorProcessorPtr create(ColorManager *config,
|
||||
const std::string &input,
|
||||
const ColorTransform &dest_space,
|
||||
Direction direction = k_normal);
|
||||
static ColorProcessorPtr create(ocio::ConstProcessorRcPtr processor);
|
||||
|
||||
ocio::ConstProcessorRcPtr get_processor();
|
||||
|
||||
void convert_frame(FramePtr f);
|
||||
void convert_frame(Frame *f);
|
||||
|
||||
Color convert_color(const Color &in);
|
||||
|
||||
const char *id() const
|
||||
{
|
||||
return processor_->getCacheID();
|
||||
}
|
||||
|
||||
private:
|
||||
ocio::ConstProcessorRcPtr processor_;
|
||||
|
||||
ocio::ConstCPUProcessorRcPtr cpu_processor_;
|
||||
};
|
||||
|
||||
using ColorProcessorChain = std::vector<ColorProcessorPtr>;
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORPROCESSOR_H
|
||||
@@ -0,0 +1,37 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_COLORPROCESSORCACHE_H
|
||||
#define OAK_COLORPROCESSORCACHE_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "colorprocessor.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using ColorProcessorCache = std::map<std::string, ColorProcessorPtr>;
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORPROCESSORCACHE_H
|
||||
@@ -0,0 +1,561 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "diskmanager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "coreengine.h"
|
||||
#include "filefunctions.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
DiskManager *DiskManager::instance_ = nullptr;
|
||||
|
||||
DiskManager::ShowDiskCacheSettingsHandler
|
||||
DiskManager::show_disk_cache_settings_handler_;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int64_t current_msecs_since_epoch()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DiskManager::DiskManager()
|
||||
{
|
||||
// Add default cache location
|
||||
std::ifstream default_disk_cache_file(get_default_disk_cache_config_file(),
|
||||
std::ios::binary);
|
||||
if (default_disk_cache_file.is_open()) {
|
||||
std::stringstream ss;
|
||||
ss << default_disk_cache_file.rdbuf();
|
||||
std::string default_dir = ss.str();
|
||||
|
||||
if (!default_dir.empty()) {
|
||||
if (FileFunctions::directory_is_valid(default_dir)) {
|
||||
get_open_folder(default_dir);
|
||||
} else {
|
||||
// The UI warning (QMessageBox) moved to the app layer; the
|
||||
// engine falls back to the default cache location.
|
||||
fprintf(stderr,
|
||||
"Disk Cache Error: Unable to set custom application disk "
|
||||
"cache. Using default instead.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no custom default was loaded, load default
|
||||
if (open_folders_.empty()) {
|
||||
get_open_folder(get_default_disk_cache_path());
|
||||
}
|
||||
|
||||
std::string disk_cache_index_path =
|
||||
(std::filesystem::path(FileFunctions::get_configuration_location()) /
|
||||
"diskcache2")
|
||||
.string();
|
||||
|
||||
std::ifstream disk_cache_index(disk_cache_index_path);
|
||||
if (disk_cache_index.is_open()) {
|
||||
std::string line;
|
||||
while (std::getline(disk_cache_index, line)) {
|
||||
get_open_folder(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DiskManager::~DiskManager()
|
||||
{
|
||||
std::ofstream default_disk_cache_file(get_default_disk_cache_config_file(),
|
||||
std::ios::binary | std::ios::trunc);
|
||||
if (default_disk_cache_file.is_open()) {
|
||||
if (get_default_disk_cache_path() != get_default_cache_path()) {
|
||||
default_disk_cache_file << get_default_cache_path();
|
||||
}
|
||||
}
|
||||
|
||||
// DiskCacheFolder children used to be deleted via QObject parentship
|
||||
for (DiskCacheFolder *f : open_folders_) {
|
||||
delete f;
|
||||
}
|
||||
open_folders_.clear();
|
||||
}
|
||||
|
||||
void DiskManager::create_instance()
|
||||
{
|
||||
instance_ = new DiskManager();
|
||||
}
|
||||
|
||||
void DiskManager::destroy_instance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
DiskManager *DiskManager::instance()
|
||||
{
|
||||
// Lazy self-create: the Qt app called create_instance() at startup, but
|
||||
// library consumers (oaknode standalone tests) may reach instance()
|
||||
// without any facade having run. Matches FrameManager callers' tolerance
|
||||
// for a missing instance by guaranteeing one exists instead.
|
||||
if (!instance_) {
|
||||
create_instance();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void DiskManager::accessed(const std::string &cache_folder,
|
||||
const std::string &filename)
|
||||
{
|
||||
DiskCacheFolder *f = get_open_folder(cache_folder);
|
||||
|
||||
f->accessed(filename);
|
||||
}
|
||||
|
||||
void DiskManager::created_file(const std::string &cache_folder,
|
||||
const std::string &filename)
|
||||
{
|
||||
DiskCacheFolder *f = get_open_folder(cache_folder);
|
||||
|
||||
f->created_file(filename);
|
||||
}
|
||||
|
||||
void DiskManager::delete_specific_file(const std::string &filename)
|
||||
{
|
||||
for (DiskCacheFolder *f : open_folders_) {
|
||||
f->delete_specific_file(filename);
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskManager::clear_disk_cache(const std::string &cache_folder)
|
||||
{
|
||||
DiskCacheFolder *f = get_open_folder(cache_folder);
|
||||
|
||||
return f->clear_cache();
|
||||
}
|
||||
|
||||
DiskCacheFolder *DiskManager::get_open_folder(const std::string &path)
|
||||
{
|
||||
// If path is empty, this must mean default
|
||||
if (path.empty()) {
|
||||
return get_default_cache_folder();
|
||||
}
|
||||
|
||||
// See if we have an existing path with this name
|
||||
for (DiskCacheFolder *f : open_folders_) {
|
||||
if (f->get_path() == path) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
// We must have to open this folder
|
||||
DiskCacheFolder *f = new DiskCacheFolder(path);
|
||||
f->add_deleted_frame_handler(
|
||||
[this](const std::string &p, const std::string &fn) {
|
||||
emit_deleted_frame(p, fn);
|
||||
});
|
||||
open_folders_.push_back(f);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
std::string DiskManager::get_default_disk_cache_config_file()
|
||||
{
|
||||
return (std::filesystem::path(FileFunctions::get_configuration_location()) /
|
||||
"defaultdiskcache")
|
||||
.string();
|
||||
}
|
||||
|
||||
std::string DiskManager::get_default_disk_cache_path()
|
||||
{
|
||||
// QStandardPaths::AppLocalDataLocation equivalent: the configuration
|
||||
// location is the app data root on all platforms.
|
||||
return (std::filesystem::path(FileFunctions::get_configuration_location()) /
|
||||
"mediacache")
|
||||
.string();
|
||||
}
|
||||
|
||||
void DiskManager::set_show_disk_cache_settings_handler(
|
||||
ShowDiskCacheSettingsHandler handler)
|
||||
{
|
||||
show_disk_cache_settings_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void DiskManager::show_disk_cache_settings_dialog(DiskCacheFolder *folder)
|
||||
{
|
||||
if (show_disk_cache_settings_handler_) {
|
||||
show_disk_cache_settings_handler_(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"No disk cache settings dialog handler registered, skipping\n");
|
||||
}
|
||||
|
||||
void DiskManager::show_disk_cache_settings_dialog(const std::string &path)
|
||||
{
|
||||
if (!FileFunctions::directory_is_valid(path)) {
|
||||
// The UI error dialog (QMessageBox) moved to the app layer
|
||||
fprintf(stderr,
|
||||
"Disk Cache Error: Failed to open disk cache at \"%s\". Try a "
|
||||
"different folder.\n",
|
||||
path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
DiskCacheFolder *folder = get_open_folder(path);
|
||||
|
||||
show_disk_cache_settings_dialog(folder);
|
||||
}
|
||||
|
||||
size_t DiskManager::add_deleted_frame_handler(DeletedFrameHandler handler)
|
||||
{
|
||||
size_t id = next_handler_id_++;
|
||||
deleted_frame_handlers_[id] = std::move(handler);
|
||||
return id;
|
||||
}
|
||||
|
||||
void DiskManager::remove_deleted_frame_handler(size_t id)
|
||||
{
|
||||
deleted_frame_handlers_.erase(id);
|
||||
}
|
||||
|
||||
size_t DiskManager::add_invalidate_project_handler(
|
||||
InvalidateProjectHandler handler)
|
||||
{
|
||||
size_t id = next_handler_id_++;
|
||||
invalidate_project_handlers_[id] = std::move(handler);
|
||||
return id;
|
||||
}
|
||||
|
||||
void DiskManager::remove_invalidate_project_handler(size_t id)
|
||||
{
|
||||
invalidate_project_handlers_.erase(id);
|
||||
}
|
||||
|
||||
void DiskManager::emit_deleted_frame(const std::string &path,
|
||||
const std::string &filename)
|
||||
{
|
||||
for (const auto &e : deleted_frame_handlers_) {
|
||||
e.second(path, filename);
|
||||
}
|
||||
}
|
||||
|
||||
void DiskManager::emit_invalidate_project(Project *p)
|
||||
{
|
||||
for (const auto &e : invalidate_project_handlers_) {
|
||||
e.second(p);
|
||||
}
|
||||
}
|
||||
|
||||
DiskCacheFolder::DiskCacheFolder(const std::string &path)
|
||||
{
|
||||
set_path(path);
|
||||
|
||||
// QTimer replacement: periodic index save on a background thread. The
|
||||
// timer used to fire in DiskManager's (GUI) thread; cross-thread callers
|
||||
// reached the folder through queued QMetaObject invocations.
|
||||
int interval = OAK_CONFIG("DiskCacheSaveInterval").toInt();
|
||||
if (interval <= 0) {
|
||||
// Config default (10000 ms); the transition config stub returns 0
|
||||
interval = 10000;
|
||||
}
|
||||
save_thread_stop_ = false;
|
||||
save_thread_ = std::thread([this, interval]() {
|
||||
int64_t elapsed = 0;
|
||||
while (!save_thread_stop_) {
|
||||
int64_t step = std::min<int64_t>(50, interval - elapsed);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(step));
|
||||
if (save_thread_stop_) {
|
||||
break;
|
||||
}
|
||||
elapsed += step;
|
||||
if (elapsed >= interval) {
|
||||
elapsed = 0;
|
||||
save_disk_cache_index();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DiskCacheFolder::~DiskCacheFolder()
|
||||
{
|
||||
save_thread_stop_ = true;
|
||||
if (save_thread_.joinable()) {
|
||||
save_thread_.join();
|
||||
}
|
||||
|
||||
close_cache_folder();
|
||||
}
|
||||
|
||||
bool DiskCacheFolder::clear_cache()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
bool deleted_files = true;
|
||||
|
||||
auto i = disk_data_.begin();
|
||||
|
||||
while (i != disk_data_.end()) {
|
||||
// We return a false result if any of the files fail to delete, but still try to delete as many as we can
|
||||
std::string filename = i->first;
|
||||
|
||||
std::error_code ec;
|
||||
bool removed = std::filesystem::remove(filename, ec);
|
||||
std::error_code ec2;
|
||||
if (removed || !std::filesystem::exists(filename, ec2)) {
|
||||
emit_deleted_frame(path_, filename);
|
||||
i = disk_data_.erase(i);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to delete %s\n", filename.c_str());
|
||||
deleted_files = false;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return deleted_files;
|
||||
}
|
||||
|
||||
void DiskCacheFolder::accessed(const std::string &filename)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
if (!disk_data_.count(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disk_data_[filename].access_time = current_msecs_since_epoch();
|
||||
}
|
||||
|
||||
void DiskCacheFolder::created_file(const std::string &filename)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
std::error_code ec;
|
||||
int64_t file_size = int64_t(std::filesystem::file_size(filename, ec));
|
||||
if (ec) {
|
||||
file_size = 0;
|
||||
}
|
||||
|
||||
disk_data_.insert({ filename, { file_size, current_msecs_since_epoch() } });
|
||||
|
||||
consumption_ += file_size;
|
||||
|
||||
while (consumption_ > limit_) {
|
||||
delete_least_recent();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheFolder::set_path(const std::string &path)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
// If this is currently set to a folder, close it out now
|
||||
close_cache_folder();
|
||||
|
||||
// Signal that disk cache is gone
|
||||
if (!disk_data_.empty()) {
|
||||
for (auto it = disk_data_.cbegin(); it != disk_data_.cend(); it++) {
|
||||
emit_deleted_frame(path_, it->first);
|
||||
}
|
||||
disk_data_.clear();
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
clear_on_close_ = false;
|
||||
consumption_ = 0;
|
||||
limit_ = 21474836480; // Default to 20 GB
|
||||
|
||||
// Set path
|
||||
path_ = path;
|
||||
|
||||
// Attempt to load existing index file from path
|
||||
FileFunctions::directory_is_valid(path_);
|
||||
|
||||
index_path_ = (std::filesystem::path(path_) / "index").string();
|
||||
|
||||
// Try to load any current cache index from file
|
||||
std::FILE *cache_index_file = std::fopen(index_path_.c_str(), "rb");
|
||||
|
||||
if (cache_index_file) {
|
||||
BinaryStreamReader ds(cache_index_file);
|
||||
|
||||
ds >> limit_;
|
||||
ds >> clear_on_close_;
|
||||
|
||||
while (!ds.at_end()) {
|
||||
std::string filename;
|
||||
HashTime h;
|
||||
|
||||
ds >> filename;
|
||||
ds >> h.file_size;
|
||||
ds >> h.access_time;
|
||||
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(filename, ec)) {
|
||||
consumption_ += h.file_size;
|
||||
disk_data_.insert({ filename, h });
|
||||
}
|
||||
}
|
||||
|
||||
std::fclose(cache_index_file);
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskCacheFolder::delete_file_internal(
|
||||
std::map<std::string, HashTime>::iterator hash_to_delete)
|
||||
{
|
||||
// Cache HashTime object
|
||||
std::string filename = hash_to_delete->first;
|
||||
HashTime ht = hash_to_delete->second;
|
||||
|
||||
// Remove from disk
|
||||
std::error_code ec;
|
||||
bool removed = std::filesystem::remove(filename, ec);
|
||||
std::error_code ec2;
|
||||
bool exists = std::filesystem::exists(filename, ec2);
|
||||
|
||||
if (!exists || removed) {
|
||||
// Remove from internal map
|
||||
disk_data_.erase(hash_to_delete);
|
||||
|
||||
// Reduce consumption
|
||||
consumption_ -= ht.file_size;
|
||||
|
||||
emit_deleted_frame(path_, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskCacheFolder::delete_specific_file(const std::string &f)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
for (auto it = disk_data_.begin(); it != disk_data_.end(); it++) {
|
||||
if (it->first == f) {
|
||||
// Break out of this loop, assuming we'll only have one instance_ of each filename
|
||||
return delete_file_internal(it);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskCacheFolder::delete_least_recent()
|
||||
{
|
||||
auto hash_to_delete = disk_data_.begin();
|
||||
|
||||
if (disk_data_.begin() != disk_data_.end()) {
|
||||
for (auto it = std::next(disk_data_.begin()); it != disk_data_.end(); it++) {
|
||||
if (it->second.access_time < hash_to_delete->second.access_time) {
|
||||
hash_to_delete = it;
|
||||
}
|
||||
}
|
||||
|
||||
bool e = delete_file_internal(hash_to_delete);
|
||||
|
||||
if (e) {
|
||||
EngineCore::instance()->warn_cache_full();
|
||||
}
|
||||
|
||||
return e;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheFolder::close_cache_folder()
|
||||
{
|
||||
if (path_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clear_on_close_) {
|
||||
// If we're not moving to new and we're set to clear on close, clear now or else it'll never
|
||||
// get cleared later
|
||||
clear_cache();
|
||||
}
|
||||
|
||||
// Save current cache index
|
||||
save_disk_cache_index();
|
||||
}
|
||||
|
||||
void DiskCacheFolder::save_disk_cache_index()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(data_mutex_);
|
||||
|
||||
std::FILE *cache_index_file = std::fopen(index_path_.c_str(), "wb");
|
||||
|
||||
if (cache_index_file) {
|
||||
BinaryStreamWriter ds(cache_index_file);
|
||||
|
||||
ds << limit_;
|
||||
ds << clear_on_close_;
|
||||
|
||||
for (auto it = disk_data_.cbegin(); it != disk_data_.cend(); it++) {
|
||||
const HashTime &ht = it->second;
|
||||
|
||||
ds << it->first;
|
||||
ds << ht.file_size;
|
||||
ds << ht.access_time;
|
||||
}
|
||||
|
||||
std::fclose(cache_index_file);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to write cache index: %s\n", index_path_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
size_t DiskCacheFolder::add_deleted_frame_handler(DeletedFrameHandler handler)
|
||||
{
|
||||
size_t id = next_handler_id_++;
|
||||
deleted_frame_handlers_[id] = std::move(handler);
|
||||
return id;
|
||||
}
|
||||
|
||||
void DiskCacheFolder::remove_deleted_frame_handler(size_t id)
|
||||
{
|
||||
deleted_frame_handlers_.erase(id);
|
||||
}
|
||||
|
||||
void DiskCacheFolder::emit_deleted_frame(const std::string &path,
|
||||
const std::string &filename)
|
||||
{
|
||||
for (const auto &e : deleted_frame_handlers_) {
|
||||
e.second(path, filename);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_DISKMANAGER_H
|
||||
#define OAK_DISKMANAGER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "binarystream.h"
|
||||
#include "define.h"
|
||||
#include "project.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DiskCacheFolder {
|
||||
public:
|
||||
DiskCacheFolder(const std::string &path);
|
||||
|
||||
~DiskCacheFolder();
|
||||
|
||||
bool clear_cache();
|
||||
|
||||
void accessed(const std::string &filename);
|
||||
|
||||
void created_file(const std::string &filename);
|
||||
|
||||
const std::string &get_path() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
void set_path(const std::string &path);
|
||||
|
||||
int64_t get_limit() const
|
||||
{
|
||||
return limit_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bytes currently consumed by tracked files in this folder.
|
||||
*
|
||||
* Non-const because the counter is guarded by the (non-mutable)
|
||||
* data mutex. Exposed for the oakrender C ABI
|
||||
* (oakrender_disk_cache_size(), M7 §2.4).
|
||||
*/
|
||||
int64_t get_consumption()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> locker(data_mutex_);
|
||||
return consumption_;
|
||||
}
|
||||
|
||||
bool get_clear_on_close() const
|
||||
{
|
||||
return clear_on_close_;
|
||||
}
|
||||
|
||||
void set_limit(int64_t l)
|
||||
{
|
||||
limit_ = l;
|
||||
}
|
||||
|
||||
void set_clear_on_close(bool e)
|
||||
{
|
||||
clear_on_close_ = e;
|
||||
}
|
||||
|
||||
bool delete_specific_file(const std::string &f);
|
||||
|
||||
// Explicit handler list replacing the `deleted_frame` signal
|
||||
using DeletedFrameHandler =
|
||||
std::function<void(const std::string &path, const std::string &filename)>;
|
||||
size_t add_deleted_frame_handler(DeletedFrameHandler handler);
|
||||
void remove_deleted_frame_handler(size_t id);
|
||||
|
||||
private:
|
||||
struct HashTime {
|
||||
int64_t file_size;
|
||||
int64_t access_time;
|
||||
};
|
||||
|
||||
bool delete_file_internal(std::map<std::string, HashTime>::iterator hash_to_delete);
|
||||
|
||||
bool delete_least_recent();
|
||||
|
||||
void close_cache_folder();
|
||||
|
||||
void emit_deleted_frame(const std::string &path, const std::string &filename);
|
||||
|
||||
std::string path_;
|
||||
|
||||
std::string index_path_;
|
||||
|
||||
std::map<std::string, HashTime> disk_data_;
|
||||
|
||||
int64_t consumption_;
|
||||
|
||||
int64_t limit_;
|
||||
|
||||
bool clear_on_close_;
|
||||
|
||||
// Guards disk_data_/consumption_. The QObject version was serialized by
|
||||
// thread affinity (queued QMetaObject calls + GUI-thread timer); with
|
||||
// direct cross-thread calls and a background save thread, a mutex takes
|
||||
// that role.
|
||||
std::recursive_mutex data_mutex_;
|
||||
|
||||
// QTimer replacement: periodic save of the disk cache index on a
|
||||
// background thread (the timer used to fire in the GUI thread)
|
||||
std::thread save_thread_;
|
||||
std::atomic<bool> save_thread_stop_;
|
||||
|
||||
std::map<size_t, DeletedFrameHandler> deleted_frame_handlers_;
|
||||
size_t next_handler_id_ = 1;
|
||||
|
||||
// Formerly a QTimer timeout slot
|
||||
void save_disk_cache_index();
|
||||
};
|
||||
|
||||
class DiskManager {
|
||||
public:
|
||||
static void create_instance();
|
||||
|
||||
static void destroy_instance();
|
||||
|
||||
static DiskManager *instance();
|
||||
|
||||
bool clear_disk_cache(const std::string &cache_folder);
|
||||
|
||||
DiskCacheFolder *get_default_cache_folder() const
|
||||
{
|
||||
// The first folder will always be the default
|
||||
return open_folders_.front();
|
||||
}
|
||||
|
||||
const std::string &get_default_cache_path() const
|
||||
{
|
||||
return get_default_cache_folder()->get_path();
|
||||
}
|
||||
|
||||
DiskCacheFolder *get_open_folder(const std::string &path);
|
||||
|
||||
const std::vector<DiskCacheFolder *> &get_open_folders() const
|
||||
{
|
||||
return open_folders_;
|
||||
}
|
||||
|
||||
static std::string get_default_disk_cache_config_file();
|
||||
|
||||
static std::string get_default_disk_cache_path();
|
||||
|
||||
/**
|
||||
* @brief Handler showing the disk cache settings dialog for a folder
|
||||
*
|
||||
* Registered by the UI layer (e.g. a DiskCacheDialog-based
|
||||
* implementation), since the engine cannot show dialogs itself. Without
|
||||
* a handler, the request is logged and skipped.
|
||||
*/
|
||||
using ShowDiskCacheSettingsHandler =
|
||||
std::function<void(DiskCacheFolder *folder)>;
|
||||
|
||||
static void set_show_disk_cache_settings_handler(
|
||||
ShowDiskCacheSettingsHandler handler);
|
||||
|
||||
void show_disk_cache_settings_dialog(DiskCacheFolder *folder);
|
||||
void show_disk_cache_settings_dialog(const std::string &path);
|
||||
|
||||
// Formerly slots invoked cross-thread via QMetaObject; now direct calls.
|
||||
void accessed(const std::string &cache_folder, const std::string &filename);
|
||||
|
||||
void created_file(const std::string &cache_folder, const std::string &filename);
|
||||
|
||||
void delete_specific_file(const std::string &filename);
|
||||
|
||||
// Explicit handler lists replacing the `deleted_frame` /
|
||||
// `invalidate_project` signals (subscribers: FrameHashCache et al.)
|
||||
using DeletedFrameHandler = DiskCacheFolder::DeletedFrameHandler;
|
||||
size_t add_deleted_frame_handler(DeletedFrameHandler handler);
|
||||
void remove_deleted_frame_handler(size_t id);
|
||||
|
||||
using InvalidateProjectHandler = std::function<void(Project *p)>;
|
||||
size_t add_invalidate_project_handler(InvalidateProjectHandler handler);
|
||||
void remove_invalidate_project_handler(size_t id);
|
||||
|
||||
void emit_deleted_frame(const std::string &path, const std::string &filename);
|
||||
void emit_invalidate_project(Project *p);
|
||||
|
||||
private:
|
||||
DiskManager();
|
||||
|
||||
~DiskManager();
|
||||
|
||||
static DiskManager *instance_;
|
||||
|
||||
static ShowDiskCacheSettingsHandler show_disk_cache_settings_handler_;
|
||||
|
||||
std::vector<DiskCacheFolder *> open_folders_;
|
||||
|
||||
std::map<size_t, DeletedFrameHandler> deleted_frame_handlers_;
|
||||
std::map<size_t, InvalidateProjectHandler> invalidate_project_handlers_;
|
||||
size_t next_handler_id_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DISKMANAGER_H
|
||||
@@ -0,0 +1,526 @@
|
||||
/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "framehashcache.h"
|
||||
|
||||
#include <OpenEXR/ImfFloatAttribute.h>
|
||||
#include <OpenEXR/ImfFrameBuffer.h>
|
||||
#include <OpenEXR/ImfHeader.h>
|
||||
#include <OpenEXR/ImfInputFile.h>
|
||||
#include <OpenEXR/ImfIntAttribute.h>
|
||||
#include <OpenEXR/ImfOutputFile.h>
|
||||
#include <OpenEXR/ImfChannelList.h>
|
||||
#include <OpenImageIO/imageio.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "diskmanager.h"
|
||||
#include "filefunctions.h"
|
||||
#include "oiioutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super PlaybackCache
|
||||
|
||||
FrameHashCache::FrameHashCache(Node *parent)
|
||||
: super(parent)
|
||||
, deleted_frame_handler_id_(0)
|
||||
, invalidate_project_handler_id_(0)
|
||||
{
|
||||
if (DiskManager::instance()) {
|
||||
deleted_frame_handler_id_ = DiskManager::instance()->add_deleted_frame_handler(
|
||||
[this](const std::string &path, const std::string &filename) {
|
||||
hash_deleted(path, filename);
|
||||
});
|
||||
invalidate_project_handler_id_ =
|
||||
DiskManager::instance()->add_invalidate_project_handler(
|
||||
[this](Project *p) { project_invalidated(p); });
|
||||
}
|
||||
}
|
||||
|
||||
FrameHashCache::~FrameHashCache()
|
||||
{
|
||||
// QObject used to auto-disconnect on destruction; unregister explicitly.
|
||||
if (DiskManager::instance()) {
|
||||
if (deleted_frame_handler_id_) {
|
||||
DiskManager::instance()->remove_deleted_frame_handler(
|
||||
deleted_frame_handler_id_);
|
||||
}
|
||||
if (invalidate_project_handler_id_) {
|
||||
DiskManager::instance()->remove_invalidate_project_handler(
|
||||
invalidate_project_handler_id_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameHashCache::set_timebase(const Rational &tb)
|
||||
{
|
||||
timebase_ = tb;
|
||||
}
|
||||
|
||||
void FrameHashCache::validate_timestamp(const int64_t &ts)
|
||||
{
|
||||
TimeRange frame_range(to_time(ts), to_time(ts + 1));
|
||||
validate(frame_range);
|
||||
}
|
||||
|
||||
void FrameHashCache::validate_time(const Rational &time)
|
||||
{
|
||||
validate(TimeRange(time, time + timebase_));
|
||||
}
|
||||
|
||||
std::string FrameHashCache::get_valid_cache_filename(const Rational &time) const
|
||||
{
|
||||
if (is_frame_cached(time)) {
|
||||
return cache_path_name(time);
|
||||
} else if (!get_passthroughs().empty()) {
|
||||
for (const Passthrough &p : get_passthroughs()) {
|
||||
if (p.contains(time)) {
|
||||
return cache_path_name(get_cache_directory(), p.cache, time,
|
||||
timebase_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool FrameHashCache::save_cache_frame(const int64_t &time, FramePtr frame) const
|
||||
{
|
||||
return save_cache_frame(get_cache_directory(), get_uuid(), time, frame);
|
||||
}
|
||||
|
||||
bool FrameHashCache::save_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid,
|
||||
const int64_t &time, FramePtr frame)
|
||||
{
|
||||
if (cache_path.empty()) {
|
||||
fprintf(stderr, "Failed to save cache frame with empty path\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fn = cache_path_name(cache_path, uuid, time);
|
||||
|
||||
bool ret = save_cache_frame(fn, frame);
|
||||
|
||||
// Register frame with the disk manager
|
||||
if (ret) {
|
||||
// Was a queued cross-thread QMetaObject::invokeMethod; now a direct call
|
||||
DiskManager::instance()->created_file(cache_path, fn);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FrameHashCache::save_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid,
|
||||
const Rational &time, const Rational &tb,
|
||||
FramePtr frame)
|
||||
{
|
||||
if (cache_path.empty()) {
|
||||
fprintf(stderr, "Failed to save cache frame with empty path\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fn = cache_path_name(cache_path, uuid, time, tb);
|
||||
|
||||
bool ret = save_cache_frame(fn, frame);
|
||||
|
||||
// Register frame with the disk manager
|
||||
if (ret) {
|
||||
// Was a queued cross-thread QMetaObject::invokeMethod; now a direct call
|
||||
DiskManager::instance()->created_file(cache_path, fn);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FramePtr FrameHashCache::load_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid,
|
||||
const int64_t &time)
|
||||
{
|
||||
// Minor optimization, we store frames currently being saved just in case something tries to load
|
||||
// while we're saving. This should *occasionally* optimize and also prevent scenarios where
|
||||
// we try to load a frame that's half way through being saved.
|
||||
std::string filename = cache_path_name(cache_path, uuid, time);
|
||||
|
||||
if (cache_path.empty()) {
|
||||
fprintf(stderr, "Failed to load cache frame with empty path\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return load_cache_frame(filename);
|
||||
}
|
||||
|
||||
FramePtr FrameHashCache::load_cache_frame(const int64_t &hash) const
|
||||
{
|
||||
return load_cache_frame(get_cache_directory(), get_uuid(), hash);
|
||||
}
|
||||
|
||||
FramePtr FrameHashCache::load_cache_frame(const std::string &fn)
|
||||
{
|
||||
FramePtr frame = nullptr;
|
||||
|
||||
std::error_code ec;
|
||||
if (!fn.empty() && std::filesystem::exists(fn, ec)) {
|
||||
try {
|
||||
Imf::InputFile file(fn.c_str(), 0);
|
||||
|
||||
Imath::Box2i dw = file.header().dataWindow();
|
||||
Imf::PixelType pix_type =
|
||||
file.header().channels().begin().channel().type;
|
||||
int width = dw.max.x - dw.min.x + 1;
|
||||
int height = dw.max.y - dw.min.y + 1;
|
||||
bool has_alpha = file.header().channels().findChannel("A");
|
||||
|
||||
int div = std::max(1, static_cast<const Imf::IntAttribute &>(
|
||||
file.header()["oliveDivider"])
|
||||
.value());
|
||||
|
||||
PixelFormat image_format;
|
||||
if (pix_type == Imf::HALF) {
|
||||
image_format = PixelFormat::f16;
|
||||
} else {
|
||||
image_format = PixelFormat::f32;
|
||||
}
|
||||
|
||||
int channel_count = has_alpha ? VideoParams::k_rgba_channel_count :
|
||||
VideoParams::k_rgb_channel_count;
|
||||
|
||||
frame = Frame::create();
|
||||
frame->set_video_params(VideoParams(
|
||||
width * div, height * div, image_format, channel_count,
|
||||
Rational::from_double(file.header().pixelAspectRatio()),
|
||||
VideoParams::k_interlace_none, div));
|
||||
|
||||
frame->allocate();
|
||||
|
||||
int bpc = VideoParams::get_bytes_per_channel(image_format);
|
||||
|
||||
size_t xs = channel_count * bpc;
|
||||
size_t ys = frame->linesize_bytes();
|
||||
|
||||
Imf::FrameBuffer framebuffer;
|
||||
framebuffer.insert("R",
|
||||
Imf::Slice(pix_type, frame->data(), xs, ys));
|
||||
framebuffer.insert("G", Imf::Slice(pix_type, frame->data() + bpc,
|
||||
xs, ys));
|
||||
framebuffer.insert(
|
||||
"B", Imf::Slice(pix_type, frame->data() + 2 * bpc, xs, ys));
|
||||
if (has_alpha) {
|
||||
framebuffer.insert(
|
||||
"A", Imf::Slice(pix_type, frame->data() + 3 * bpc, xs, ys));
|
||||
}
|
||||
|
||||
file.setFrameBuffer(framebuffer);
|
||||
|
||||
file.readPixels(dw.min.y, dw.max.y);
|
||||
} catch (const std::exception &e) {
|
||||
// Not an EXR, maybe it's a JPEG?
|
||||
std::unique_ptr<OIIO::ImageInput> in = OIIO::ImageInput::open(fn);
|
||||
|
||||
if (in) {
|
||||
// FIXME: Hardcoded
|
||||
const int div = 1;
|
||||
const PixelFormat image_format = PixelFormat::u8;
|
||||
const int channel_count = 4;
|
||||
const Rational par(1, 1);
|
||||
|
||||
const OIIO::ImageSpec &spec = in->spec();
|
||||
const int src_channels = spec.nchannels;
|
||||
|
||||
// Read native channels as u8, then expand to RGBA with opaque
|
||||
// alpha (what QImage::convertTo(Format_RGBA8888_Premultiplied)
|
||||
// did; with alpha=255 premultiplication is the identity).
|
||||
std::vector<unsigned char> src(spec.width * spec.height *
|
||||
src_channels);
|
||||
if (in->read_image(0, 0, 0, src_channels, OIIO::TypeDesc::UINT8,
|
||||
src.data())) {
|
||||
frame = Frame::create();
|
||||
frame->set_video_params(VideoParams(
|
||||
spec.width * div, spec.height * div, image_format,
|
||||
channel_count, par, VideoParams::k_interlace_none,
|
||||
div));
|
||||
|
||||
frame->allocate();
|
||||
|
||||
size_t src_linesize = size_t(spec.width) * src_channels;
|
||||
for (int i = 0; i < spec.height; i++) {
|
||||
const unsigned char *src_row =
|
||||
src.data() + src_linesize * i;
|
||||
char *dst_row =
|
||||
frame->data() + frame->linesize_bytes() * i;
|
||||
for (int x = 0; x < spec.width; x++) {
|
||||
char *dst_px = dst_row + x * channel_count;
|
||||
const unsigned char *src_px =
|
||||
src_row + x * src_channels;
|
||||
for (int c = 0; c < 3; c++) {
|
||||
dst_px[c] = char(c < src_channels ? src_px[c] : 0);
|
||||
}
|
||||
dst_px[3] = char(0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in->close();
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
fprintf(stderr, "Failed to read cache frame: %s\n", e.what());
|
||||
|
||||
// Clear frame to signal that nothing was loaded
|
||||
frame = nullptr;
|
||||
|
||||
// Assume this frame is corrupt in some way and delete it
|
||||
// (was a queued QMetaObject::invokeMethod; now a direct call)
|
||||
DiskManager::instance()->delete_specific_file(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void FrameHashCache::set_passthrough(PlaybackCache *cache)
|
||||
{
|
||||
super::set_passthrough(cache);
|
||||
set_timebase(static_cast<FrameHashCache *>(cache)->get_timebase());
|
||||
}
|
||||
|
||||
void FrameHashCache::LoadStateEvent(BinaryStreamReader &stream)
|
||||
{
|
||||
uint32_t version;
|
||||
int32_t num, den;
|
||||
|
||||
stream >> version;
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
stream >> num;
|
||||
stream >> den;
|
||||
timebase_ = Rational(num, den);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameHashCache::SaveStateEvent(BinaryStreamWriter &stream)
|
||||
{
|
||||
uint32_t version = 1;
|
||||
|
||||
stream << version;
|
||||
|
||||
stream << int32_t(timebase_.numerator());
|
||||
stream << int32_t(timebase_.denominator());
|
||||
}
|
||||
|
||||
Rational FrameHashCache::to_time(const int64_t &ts) const
|
||||
{
|
||||
return Timecode::timestamp_to_time(ts, timebase_);
|
||||
}
|
||||
|
||||
int64_t FrameHashCache::to_timestamp(const Rational &ts,
|
||||
Timecode::Rounding rounding) const
|
||||
{
|
||||
return Timecode::time_to_timestamp(ts, timebase_, rounding);
|
||||
}
|
||||
|
||||
void FrameHashCache::hash_deleted(const std::string &path,
|
||||
const std::string &filename)
|
||||
{
|
||||
std::string cache_dir = get_cache_directory();
|
||||
if (cache_dir.empty() || path != cache_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path info(filename);
|
||||
if (get_uuid() != info.parent_path().filename().string()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t timestamp = strtoll(info.filename().string().c_str(), nullptr, 10);
|
||||
invalidate(TimeRange(to_time(timestamp), to_time(timestamp + 1)));
|
||||
}
|
||||
|
||||
void FrameHashCache::project_invalidated(Project *p)
|
||||
{
|
||||
if (get_project() == p) {
|
||||
invalidate_all();
|
||||
}
|
||||
}
|
||||
|
||||
std::string FrameHashCache::cache_path_name(const int64_t &time) const
|
||||
{
|
||||
return cache_path_name(get_cache_directory(), get_uuid(), time);
|
||||
}
|
||||
|
||||
std::string FrameHashCache::cache_path_name(const Rational &time) const
|
||||
{
|
||||
return cache_path_name(get_cache_directory(), get_uuid(), time, timebase_);
|
||||
}
|
||||
|
||||
std::string FrameHashCache::cache_path_name(const std::string &cache_path,
|
||||
const std::string &cache_id,
|
||||
const int64_t &time)
|
||||
{
|
||||
std::string filename =
|
||||
(std::filesystem::path(get_this_cache_directory(cache_path, cache_id)) /
|
||||
std::to_string(time))
|
||||
.string();
|
||||
|
||||
// Register that in some way this hash has been accessed
|
||||
if (DiskManager::instance()) {
|
||||
// Was a queued cross-thread QMetaObject::invokeMethod; now a direct call
|
||||
DiskManager::instance()->accessed(cache_path, filename);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
std::string FrameHashCache::cache_path_name(const std::string &cache_path,
|
||||
const std::string &cache_id,
|
||||
const Rational &time,
|
||||
const Rational &tb)
|
||||
{
|
||||
return cache_path_name(cache_path, cache_id,
|
||||
Timecode::time_to_timestamp(time, tb,
|
||||
Timecode::k_round));
|
||||
}
|
||||
|
||||
bool FrameHashCache::save_cache_frame(const std::string &filename,
|
||||
const FramePtr frame)
|
||||
{
|
||||
// Ensure directory is created
|
||||
std::filesystem::path cache_dir =
|
||||
std::filesystem::path(filename).parent_path();
|
||||
if (!FileFunctions::directory_is_valid(cache_dir.string())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VideoParams::format_is_float(frame->format())) {
|
||||
// Floating point types are stored in EXR
|
||||
Imf::PixelType pix_type;
|
||||
|
||||
if (frame->format() == PixelFormat::f16) {
|
||||
pix_type = Imf::HALF;
|
||||
} else {
|
||||
pix_type = Imf::FLOAT;
|
||||
}
|
||||
|
||||
Imf::Header header(frame->width(), frame->height());
|
||||
header.channels().insert("R", Imf::Channel(pix_type));
|
||||
header.channels().insert("G", Imf::Channel(pix_type));
|
||||
header.channels().insert("B", Imf::Channel(pix_type));
|
||||
if (frame->channel_count() == VideoParams::k_rgba_channel_count) {
|
||||
header.channels().insert("A", Imf::Channel(pix_type));
|
||||
}
|
||||
|
||||
header.compression() = Imf::DWAA_COMPRESSION;
|
||||
header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f));
|
||||
header.pixelAspectRatio() =
|
||||
frame->video_params().pixel_aspect_ratio().to_double();
|
||||
|
||||
header.insert("oliveDivider",
|
||||
Imf::IntAttribute(frame->video_params().divider()));
|
||||
|
||||
try {
|
||||
Imf::OutputFile out(filename.c_str(), header, 0);
|
||||
|
||||
int bpc = VideoParams::get_bytes_per_channel(frame->format());
|
||||
|
||||
size_t xs = frame->channel_count() * bpc;
|
||||
size_t ys = frame->linesize_bytes();
|
||||
|
||||
Imf::FrameBuffer framebuffer;
|
||||
framebuffer.insert("R",
|
||||
Imf::Slice(pix_type, frame->data(), xs, ys));
|
||||
framebuffer.insert("G", Imf::Slice(pix_type, frame->data() + bpc,
|
||||
xs, ys));
|
||||
framebuffer.insert(
|
||||
"B", Imf::Slice(pix_type, frame->data() + 2 * bpc, xs, ys));
|
||||
if (frame->channel_count() == VideoParams::k_rgba_channel_count) {
|
||||
framebuffer.insert(
|
||||
"A", Imf::Slice(pix_type, frame->data() + 3 * bpc, xs, ys));
|
||||
}
|
||||
out.setFrameBuffer(framebuffer);
|
||||
|
||||
out.writePixels(frame->height());
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
fprintf(stderr, "Failed to write cache frame: %s\n", e.what());
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Integer types are stored as JPEG via OIIO (was QImage). The JPEG
|
||||
// writer drops the alpha channel, as Qt's JPEG handler did.
|
||||
OIIO::TypeDesc base_type = OIIO::TypeDesc::UNKNOWN;
|
||||
|
||||
switch (frame->format()) {
|
||||
case PixelFormat::u8:
|
||||
if (frame->channel_count() == VideoParams::k_rgba_channel_count ||
|
||||
frame->channel_count() == VideoParams::k_rgb_channel_count) {
|
||||
base_type = OIIO::TypeDesc::UINT8;
|
||||
}
|
||||
break;
|
||||
case PixelFormat::u10:
|
||||
break;
|
||||
case PixelFormat::u16:
|
||||
if (frame->channel_count() == VideoParams::k_rgba_channel_count) {
|
||||
base_type = OIIO::TypeDesc::UINT16;
|
||||
}
|
||||
break;
|
||||
case PixelFormat::f16:
|
||||
case PixelFormat::f32:
|
||||
case PixelFormat::count:
|
||||
case PixelFormat::invalid:
|
||||
break;
|
||||
}
|
||||
|
||||
if (base_type == OIIO::TypeDesc::UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<OIIO::ImageOutput> out =
|
||||
OIIO::ImageOutput::create(filename);
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bpc = VideoParams::get_bytes_per_channel(frame->format());
|
||||
OIIO::ImageSpec spec(frame->width(), frame->height(),
|
||||
frame->channel_count(), base_type);
|
||||
|
||||
if (!out->open(filename, spec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = out->write_image(
|
||||
base_type, frame->const_data(), frame->channel_count() * bpc,
|
||||
frame->linesize_bytes());
|
||||
ok = out->close() && ok;
|
||||
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_VIDEORENDERFRAMECACHE_H
|
||||
#define OAK_VIDEORENDERFRAMECACHE_H
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "playbackcache.h"
|
||||
#include "videoparams.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class FrameHashCache : public PlaybackCache {
|
||||
public:
|
||||
FrameHashCache(Node *parent = nullptr);
|
||||
|
||||
virtual ~FrameHashCache() override;
|
||||
|
||||
const Rational &get_timebase() const
|
||||
{
|
||||
return timebase_;
|
||||
}
|
||||
|
||||
void set_timebase(const Rational &tb);
|
||||
|
||||
void validate_timestamp(const int64_t &ts);
|
||||
void validate_time(const Rational &time);
|
||||
|
||||
bool is_frame_cached(const Rational &time) const
|
||||
{
|
||||
return get_validated_ranges().contains(time);
|
||||
}
|
||||
|
||||
std::string get_valid_cache_filename(const Rational &time) const;
|
||||
|
||||
static bool save_cache_frame(const std::string &filename, FramePtr frame);
|
||||
bool save_cache_frame(const int64_t &time, FramePtr frame) const;
|
||||
static bool save_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid, const int64_t &time,
|
||||
FramePtr frame);
|
||||
static bool save_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid, const Rational &time,
|
||||
const Rational &tb, FramePtr frame);
|
||||
static FramePtr load_cache_frame(const std::string &cache_path,
|
||||
const std::string &uuid,
|
||||
const int64_t &time);
|
||||
FramePtr load_cache_frame(const int64_t &time) const;
|
||||
static FramePtr load_cache_frame(const std::string &fn);
|
||||
|
||||
virtual void set_passthrough(PlaybackCache *cache) override;
|
||||
|
||||
// Formerly slots connected to DiskManager's `deleted_frame` /
|
||||
// `invalidate_project` signals; registered as explicit handlers now and
|
||||
// still public so the facade can re-wire if needed.
|
||||
void hash_deleted(const std::string &path, const std::string &filename);
|
||||
|
||||
void project_invalidated(Project *p);
|
||||
|
||||
protected:
|
||||
virtual void LoadStateEvent(BinaryStreamReader &stream) override;
|
||||
virtual void SaveStateEvent(BinaryStreamWriter &stream) override;
|
||||
|
||||
private:
|
||||
Rational to_time(const int64_t &ts) const;
|
||||
int64_t to_timestamp(const Rational &ts,
|
||||
Timecode::Rounding rounding = Timecode::k_round) const;
|
||||
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
std::string cache_path_name(const int64_t &time) const;
|
||||
std::string cache_path_name(const Rational &time) const;
|
||||
|
||||
static std::string cache_path_name(const std::string &cache_path,
|
||||
const std::string &cache_id,
|
||||
const int64_t &time);
|
||||
static std::string cache_path_name(const std::string &cache_path,
|
||||
const std::string &cache_id,
|
||||
const Rational &time, const Rational &tb);
|
||||
|
||||
Rational timebase_;
|
||||
|
||||
// DiskManager handler registration ids (0 = not registered)
|
||||
size_t deleted_frame_handler_id_;
|
||||
size_t invalidate_project_handler_id_;
|
||||
};
|
||||
|
||||
class ThumbnailCache : public FrameHashCache {
|
||||
public:
|
||||
ThumbnailCache(Node *parent = nullptr)
|
||||
: FrameHashCache(parent)
|
||||
{
|
||||
set_timebase(Rational(1, 10));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_VIDEORENDERFRAMECACHE_H
|
||||
@@ -0,0 +1,152 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "framemanager.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
FrameManager *FrameManager::instance_ = nullptr;
|
||||
const int FrameManager::k_frame_lifetime = 5000;
|
||||
|
||||
static int64_t current_msecs_since_epoch()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
void FrameManager::create_instance()
|
||||
{
|
||||
instance_ = new FrameManager();
|
||||
}
|
||||
|
||||
void FrameManager::destroy_instance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
FrameManager *FrameManager::instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
char *FrameManager::allocate(int size)
|
||||
{
|
||||
if (instance()) {
|
||||
return instance()->allocate_from_pool(size);
|
||||
} else {
|
||||
return new char[size];
|
||||
}
|
||||
}
|
||||
|
||||
void FrameManager::deallocate(int size, char *buffer)
|
||||
{
|
||||
if (instance()) {
|
||||
instance()->deallocate_to_pool(size, buffer);
|
||||
} else {
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
FrameManager::FrameManager()
|
||||
: gc_thread_stop_(false)
|
||||
{
|
||||
// Replaces the QTimer that fired garbage_collection() every
|
||||
// k_frame_lifetime ms
|
||||
gc_thread_ = std::thread([this]() {
|
||||
while (!gc_thread_stop_.load()) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(k_frame_lifetime));
|
||||
if (gc_thread_stop_.load()) {
|
||||
break;
|
||||
}
|
||||
garbage_collection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
char *FrameManager::allocate_from_pool(int size)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
std::list<Buffer> &buffer_list = pool_[size];
|
||||
char *buf = nullptr;
|
||||
|
||||
if (buffer_list.empty()) {
|
||||
buf = new char[size];
|
||||
} else {
|
||||
// Take this buffer from the list
|
||||
buf = buffer_list.front().data;
|
||||
buffer_list.pop_front();
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void FrameManager::deallocate_to_pool(int size, char *buffer)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
std::list<Buffer> &buffer_list = pool_[size];
|
||||
|
||||
buffer_list.push_back({ current_msecs_since_epoch(), buffer });
|
||||
}
|
||||
|
||||
void FrameManager::garbage_collection()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
int64_t min_life = current_msecs_since_epoch() - k_frame_lifetime;
|
||||
|
||||
for (auto it = pool_.begin(); it != pool_.end(); it++) {
|
||||
std::list<Buffer> &list = it->second;
|
||||
|
||||
while (list.size() > 0 && list.front().time < min_life) {
|
||||
delete[] list.front().data;
|
||||
list.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameManager::~FrameManager()
|
||||
{
|
||||
gc_thread_stop_.store(true);
|
||||
if (gc_thread_.joinable()) {
|
||||
gc_thread_.join();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
|
||||
for (auto it = pool_.begin(); it != pool_.end(); it++) {
|
||||
std::list<Buffer> &list = it->second;
|
||||
for (auto jt = list.begin(); jt != list.end(); jt++) {
|
||||
delete[](*jt).data;
|
||||
}
|
||||
}
|
||||
|
||||
pool_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FRAMEMANAGER_H
|
||||
#define OAK_FRAMEMANAGER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class FrameManager {
|
||||
public:
|
||||
static void create_instance();
|
||||
|
||||
static void destroy_instance();
|
||||
|
||||
static FrameManager *instance();
|
||||
|
||||
static char *allocate(int size);
|
||||
|
||||
static void deallocate(int size, char *buffer);
|
||||
|
||||
private:
|
||||
FrameManager();
|
||||
|
||||
~FrameManager();
|
||||
|
||||
FrameManager(const FrameManager &) = delete;
|
||||
FrameManager &operator=(const FrameManager &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Allocate buffer
|
||||
*
|
||||
* Caller takes ownership of buffer and can delete it if they want. It can also be returned to
|
||||
* the manager with Deallocate and potentially be re-used later.
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
char *allocate_from_pool(int size);
|
||||
|
||||
/**
|
||||
* @brief Deallocate buffer
|
||||
*
|
||||
* Manager will take ownership and buffer will stay allocated for some time in case it can be
|
||||
* re-used.
|
||||
*
|
||||
* Thread-safe.
|
||||
*/
|
||||
void deallocate_to_pool(int size, char *buffer);
|
||||
|
||||
static FrameManager *instance_;
|
||||
|
||||
static const int k_frame_lifetime;
|
||||
|
||||
struct Buffer {
|
||||
int64_t time;
|
||||
char *data;
|
||||
};
|
||||
|
||||
std::map<int, std::list<Buffer>> pool_;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
// QTimer replacement: periodic garbage collection on a background
|
||||
// thread (the timer used to fire in the GUI thread)
|
||||
std::thread gc_thread_;
|
||||
std::atomic<bool> gc_thread_stop_;
|
||||
|
||||
// Formerly a QTimer timeout slot
|
||||
void garbage_collection();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FRAMEMANAGER_H
|
||||
@@ -0,0 +1,59 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include "filefunctions.h"
|
||||
#include "value.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
TexturePtr Renderer::interlace_texture(TexturePtr top, TexturePtr bottom,
|
||||
const VideoParams ¶ms)
|
||||
{
|
||||
color_cache_mutex_.lock();
|
||||
if (interlace_texture_.is_null()) {
|
||||
interlace_texture_ =
|
||||
create_native_shader(ShaderCode(FileFunctions::read_file_as_string(
|
||||
":/shaders/interlace.frag")));
|
||||
}
|
||||
color_cache_mutex_.unlock();
|
||||
|
||||
ShaderJob job;
|
||||
job.insert("top_tex_in",
|
||||
NodeValue(NodeValue::k_texture, Variant::from_value(top)));
|
||||
job.insert("bottom_tex_in",
|
||||
NodeValue(NodeValue::k_texture, Variant::from_value(bottom)));
|
||||
job.insert("resolution_in",
|
||||
NodeValue(NodeValue::k_vec2,
|
||||
Vector2D(params.effective_width(),
|
||||
params.effective_height())));
|
||||
|
||||
TexturePtr output = create_texture(params);
|
||||
|
||||
blit_to_texture(interlace_texture_, job, output.get());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_IPC_FRAMESLOTPOOL_H
|
||||
#define OAK_IPC_FRAMESLOTPOOL_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "oakengine/ipc.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace ipc
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Per-slot metadata describing the frame currently occupying a slot.
|
||||
*
|
||||
* Trivially-copyable POD that lives in shared memory alongside the pixel data, part of the
|
||||
* version-1 wire protocol with the render worker. This is the C ABI oak_frame_slot_meta struct,
|
||||
* aliased so the shared-memory layout is defined exactly once, in oakengine/ipc.h.
|
||||
*/
|
||||
typedef oak_frame_slot_meta FrameSlotMeta;
|
||||
|
||||
/**
|
||||
* @brief A fixed-size pool of equal-sized frame slots in shared memory, with lock-free hand-off.
|
||||
*
|
||||
* Consumer-side wrapper over the liboakengine C ABI: the object only holds an opaque
|
||||
* OakFrameSlotPool handle and forwards every call across the C boundary. The public API is
|
||||
* unchanged from the original implementation; see oakengine/ipc.h for the protocol description.
|
||||
*
|
||||
* One pool models a single direction of frame flow (e.g. worker -> main for rendered output, or
|
||||
* main -> worker for decoded input). The pool does NOT own the memory; it is constructed over a
|
||||
* SharedMemoryRegion mapping. Use bytes_needed() to size that region.
|
||||
*/
|
||||
class FrameSlotPool {
|
||||
public:
|
||||
FrameSlotPool() = default;
|
||||
|
||||
FrameSlotPool(const FrameSlotPool &rhs)
|
||||
: handle_(oakengine_ipc_framepool_copy(rhs.handle_))
|
||||
{
|
||||
}
|
||||
|
||||
FrameSlotPool(FrameSlotPool &&rhs) noexcept
|
||||
: handle_(rhs.handle_)
|
||||
{
|
||||
rhs.handle_ = nullptr;
|
||||
}
|
||||
|
||||
~FrameSlotPool()
|
||||
{
|
||||
oakengine_ipc_framepool_free(handle_);
|
||||
}
|
||||
|
||||
FrameSlotPool &operator=(const FrameSlotPool &rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
oakengine_ipc_framepool_free(handle_);
|
||||
handle_ = oakengine_ipc_framepool_copy(rhs.handle_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
FrameSlotPool &operator=(FrameSlotPool &&rhs) noexcept
|
||||
{
|
||||
if (this != &rhs) {
|
||||
oakengine_ipc_framepool_free(handle_);
|
||||
handle_ = rhs.handle_;
|
||||
rhs.handle_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Total bytes a region must provide to back a pool of `slot_count` x `slot_data_bytes`.
|
||||
*/
|
||||
static size_t bytes_needed(uint32_t slot_count, size_t slot_data_bytes)
|
||||
{
|
||||
return oakengine_ipc_framepool_bytes_needed(slot_count, slot_data_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lay out and initialize a brand-new pool over `mem` (owner side, once).
|
||||
*
|
||||
* Initializes both rings, seeds the free ring with every slot index, and zeroes metadata.
|
||||
* `mem` must provide at least bytes_needed(slot_count, slot_data_bytes) bytes.
|
||||
*/
|
||||
static FrameSlotPool create(void *mem, uint32_t slot_count,
|
||||
size_t slot_data_bytes)
|
||||
{
|
||||
return from_handle(oakengine_ipc_framepool_create(mem, slot_count,
|
||||
slot_data_bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map an existing, already-initialized pool (peer side).
|
||||
*
|
||||
* Reads slot_count/slot_data_bytes from the in-memory header written by create().
|
||||
*/
|
||||
static FrameSlotPool attach(void *mem)
|
||||
{
|
||||
return from_handle(oakengine_ipc_framepool_attach(mem));
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return oakengine_ipc_framepool_is_valid(handle_) != 0;
|
||||
}
|
||||
|
||||
uint32_t slot_count() const
|
||||
{
|
||||
return oakengine_ipc_framepool_slot_count(handle_);
|
||||
}
|
||||
|
||||
size_t slot_data_bytes() const
|
||||
{
|
||||
return oakengine_ipc_framepool_slot_data_bytes(handle_);
|
||||
}
|
||||
|
||||
// ---- Filler side ----
|
||||
|
||||
/**
|
||||
* @brief Take ownership of a free slot. Returns false (and leaves *index untouched) if none free.
|
||||
*/
|
||||
bool acquire(uint32_t *index)
|
||||
{
|
||||
return oakengine_ipc_framepool_acquire(handle_, index) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pointer to a slot's pixel data block (slot_data_bytes available).
|
||||
*/
|
||||
void *slot_data(uint32_t index)
|
||||
{
|
||||
return oakengine_ipc_framepool_slot_data(handle_, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mutable metadata for a slot. Filler writes this before publish().
|
||||
*/
|
||||
FrameSlotMeta *meta(uint32_t index)
|
||||
{
|
||||
return oakengine_ipc_framepool_meta(handle_, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish a filled slot to the drainer. Must follow a successful acquire() of `index`.
|
||||
*/
|
||||
bool publish(uint32_t index)
|
||||
{
|
||||
return oakengine_ipc_framepool_publish(handle_, index) != 0;
|
||||
}
|
||||
|
||||
// ---- Drainer side ----
|
||||
|
||||
/**
|
||||
* @brief Take the next published slot. Returns false if nothing is ready.
|
||||
*/
|
||||
bool consume(uint32_t *index)
|
||||
{
|
||||
return oakengine_ipc_framepool_consume(handle_, index) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a consumed slot to the free pool for reuse. Must follow consume() of `index`.
|
||||
*/
|
||||
bool release(uint32_t index)
|
||||
{
|
||||
return oakengine_ipc_framepool_release(handle_, index) != 0;
|
||||
}
|
||||
|
||||
const FrameSlotMeta *meta(uint32_t index) const
|
||||
{
|
||||
return oakengine_ipc_framepool_meta_const(handle_, index);
|
||||
}
|
||||
|
||||
const void *slot_data(uint32_t index) const
|
||||
{
|
||||
return oakengine_ipc_framepool_slot_data_const(handle_, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
|
||||
*/
|
||||
OakFrameSlotPool *handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wraps an owned C handle (takes ownership)
|
||||
*/
|
||||
static FrameSlotPool from_handle(OakFrameSlotPool *handle)
|
||||
{
|
||||
return FrameSlotPool(handle);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit FrameSlotPool(OakFrameSlotPool *handle)
|
||||
: handle_(handle)
|
||||
{
|
||||
}
|
||||
|
||||
OakFrameSlotPool *handle_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_IPC_FRAMESLOTPOOL_H
|
||||
@@ -0,0 +1,380 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_IPC_IPCMESSAGE_H
|
||||
#define OAK_IPC_IPCMESSAGE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "oakengine/ipc.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace ipc
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Control-plane protocol exchanged over stdio between main and render worker.
|
||||
*
|
||||
* The wire format is NDJSON: one compact JSON object per line, terminated by '\n'. This is
|
||||
* deliberately human-readable so the channel can be inspected live with `tee`/`cat` and test
|
||||
* messages can be injected by hand. The stdio channel carries only low-frequency control traffic;
|
||||
* bulk pixel data travels through the shared-memory FrameSlotPool, and the (potentially large)
|
||||
* serialized node graph travels via a temporary file referenced by path.
|
||||
*
|
||||
* Consumer-side wrapper over the liboakengine C ABI: the typed builders/parsers below convert
|
||||
* through the oakengine_ipc_*_to_json/parse functions, so the JSON field names and the wire
|
||||
* format are defined exactly once, inside the library, and stay in lockstep with the worker.
|
||||
* A message "object" on this side is simply the compact JSON text (`JsonMessage`); no Qt JSON
|
||||
* types are involved anymore.
|
||||
*
|
||||
* Every message object has a "type" string field. Directionality (M = main, W = worker):
|
||||
* "handshake" M<->W Negotiate protocol version and announce shared-memory key/geometry.
|
||||
* "load_graph" M ->W Path to a temporary file holding the serialized node graph.
|
||||
* "render_frame" M ->W Request a frame: node uuid, time, video params.
|
||||
* "frame_ready" W ->M A rendered frame is published; carries the output-slot index + ticket.
|
||||
* "cancel" M ->W Abandon an in-flight ticket by id.
|
||||
* "graph_update" M ->W (Reserved, Phase 6) Incremental graph mutation, mirrors ProjectCopier.
|
||||
* "shutdown" M ->W Finish current work and exit cleanly.
|
||||
* "error" W ->M Worker-side failure report (human-readable "message" field).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief One control-plane message as compact JSON object text (no trailing newline).
|
||||
*/
|
||||
using JsonMessage = std::string;
|
||||
|
||||
namespace msgtype
|
||||
{
|
||||
constexpr const char *k_handshake = OAKENGINE_IPC_MSGTYPE_HANDSHAKE;
|
||||
constexpr const char *k_load_graph = OAKENGINE_IPC_MSGTYPE_LOAD_GRAPH;
|
||||
constexpr const char *k_render_frame = OAKENGINE_IPC_MSGTYPE_RENDER_FRAME;
|
||||
constexpr const char *k_frame_ready = OAKENGINE_IPC_MSGTYPE_FRAME_READY;
|
||||
constexpr const char *k_cancel = OAKENGINE_IPC_MSGTYPE_CANCEL;
|
||||
constexpr const char *k_graph_update = OAKENGINE_IPC_MSGTYPE_GRAPH_UPDATE;
|
||||
constexpr const char *k_shutdown = OAKENGINE_IPC_MSGTYPE_SHUTDOWN;
|
||||
constexpr const char *k_error = OAKENGINE_IPC_MSGTYPE_ERROR;
|
||||
} // namespace msgtype
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
inline void copy_str(const std::string &s, char *dst, size_t cap)
|
||||
{
|
||||
const size_t n = std::min(s.size(), cap - 1);
|
||||
memcpy(dst, s.data(), n);
|
||||
dst[n] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run a C to_json function (buf/size convention) and return the compact JSON text.
|
||||
*/
|
||||
template <typename F> std::string via_c_json(F &&to_json)
|
||||
{
|
||||
const int size = to_json(nullptr, 0);
|
||||
std::string buf(size + 1, '\0');
|
||||
to_json(buf.data(), size + 1);
|
||||
buf.resize(size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Write one NDJSON message line to `device`.
|
||||
*
|
||||
* Appends '\n' to the compact JSON text and writes the whole line in one call. Returns true only
|
||||
* if the full line was written. `Device` is anything with a
|
||||
* `write(const char *, int64_t)`-shaped method (QProcess during the transition, a plain pipe
|
||||
* wrapper later).
|
||||
*/
|
||||
template <typename Device>
|
||||
bool write_message(Device *device, const JsonMessage &obj)
|
||||
{
|
||||
std::string line = obj;
|
||||
line.push_back('\n');
|
||||
return device->write(line.data(), int64_t(line.size())) ==
|
||||
int64_t(line.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pull one complete NDJSON line out of `buffer`.
|
||||
*
|
||||
* If `buffer` contains at least one '\n', the leading line is removed, validated, and returned
|
||||
* via `out` (true). If no complete line is buffered yet, leaves `buffer` untouched and returns
|
||||
* false. Malformed lines are skipped (removed) and reported via `*ok = false` so the reader can
|
||||
* log and continue rather than wedge. Supports the typical "append bytes as they arrive, then
|
||||
* drain complete lines" reader loop on a pipe.
|
||||
*
|
||||
* Validation is delegated to oakengine_ipc_message_type(): a line counts as well-formed when it
|
||||
* is a JSON object carrying a recognized "type" field. (The Qt original accepted any syntactically
|
||||
* valid JSON object here; the per-type from_json() parsers still reject wrong-type lines, so the
|
||||
* only behavioral difference is that valid-JSON-but-unknown-type lines are now reported as
|
||||
* malformed at this layer.)
|
||||
*/
|
||||
inline bool read_message(std::string *buffer, JsonMessage *out,
|
||||
bool *ok = nullptr)
|
||||
{
|
||||
while (true) {
|
||||
const std::string::size_type newline = buffer->find('\n');
|
||||
if (newline == std::string::npos) {
|
||||
// No complete line buffered yet.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line = buffer->substr(0, newline);
|
||||
buffer->erase(0, newline + 1);
|
||||
|
||||
// Skip blank lines silently (e.g. a stray newline) without flagging an error.
|
||||
const std::string::size_type first_non_space =
|
||||
line.find_first_not_of(" \t\r");
|
||||
if (first_non_space == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oakengine_ipc_message_type(line.c_str()) ==
|
||||
OAK_IPC_MSGTYPE_UNKNOWN) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = std::move(line);
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Typed message builders / parsers -------------------------------------------------------
|
||||
//
|
||||
// Thin wrappers that convert each struct to/from the C ABI POD form and let the library build or
|
||||
// read the JSON, keeping field names in one place so main and worker agree. Fields use plain JSON
|
||||
// numbers/strings; 64-bit ids are stored as JSON numbers (doubles exactly represent integers up
|
||||
// to 2^53, ample for our counters).
|
||||
|
||||
struct HandshakeMsg {
|
||||
int protocol_version = 0;
|
||||
std::string shm_key; ///< Worker->main output shared-memory segment key.
|
||||
std::string
|
||||
input_shm_key; ///< Main->worker input shared-memory segment key (optional).
|
||||
int input_slots = 0; ///< Number of main->worker input frame slots.
|
||||
int output_slots = 0; ///< Number of worker->main output frame slots.
|
||||
int64_t slot_data_bytes = 0; ///< Per-output-slot pixel block size.
|
||||
int64_t input_slot_data_bytes = 0; ///< Per-input-slot pixel block size.
|
||||
|
||||
JsonMessage to_json() const
|
||||
{
|
||||
oak_ipc_handshake c;
|
||||
c.protocol_version = protocol_version;
|
||||
detail::copy_str(shm_key, c.shm_key, sizeof(c.shm_key));
|
||||
detail::copy_str(input_shm_key, c.input_shm_key,
|
||||
sizeof(c.input_shm_key));
|
||||
c.input_slots = input_slots;
|
||||
c.output_slots = output_slots;
|
||||
c.slot_data_bytes = slot_data_bytes;
|
||||
c.input_slot_data_bytes = input_slot_data_bytes;
|
||||
return detail::via_c_json([&](char *buf, int size) {
|
||||
return oakengine_ipc_handshake_to_json(&c, buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
static bool from_json(const JsonMessage &o, HandshakeMsg *out)
|
||||
{
|
||||
oak_ipc_handshake c;
|
||||
if (!oakengine_ipc_handshake_parse(o.c_str(), &c)) {
|
||||
return false;
|
||||
}
|
||||
out->protocol_version = c.protocol_version;
|
||||
out->shm_key = c.shm_key;
|
||||
out->input_shm_key = c.input_shm_key;
|
||||
out->input_slots = c.input_slots;
|
||||
out->output_slots = c.output_slots;
|
||||
out->slot_data_bytes = c.slot_data_bytes;
|
||||
out->input_slot_data_bytes = c.input_slot_data_bytes;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderFrameMsg {
|
||||
int64_t ticket_id =
|
||||
0; ///< Correlates this request with the eventual frame_ready.
|
||||
std::string
|
||||
node_uuid; ///< Output/viewer node to render, by stable uuid in the loaded graph.
|
||||
int64_t time_num = 0;
|
||||
int64_t time_den = 1;
|
||||
int width = 0; ///< Forced output size (0 = use graph default).
|
||||
int height = 0;
|
||||
int format = -1; ///< Forced PixelFormat::Format (-1 = default/INVALID).
|
||||
int channel_count = 0; ///< 0 = default.
|
||||
int mode = 0; ///< RenderMode::Mode.
|
||||
int input_slot =
|
||||
-1; ///< Optional main->worker decoded input slot for footage nodes.
|
||||
std::vector<int>
|
||||
input_slots; ///< Optional ordered decoded input slots for footage nodes.
|
||||
|
||||
// Output color transform to apply before returning the frame. When empty,
|
||||
// the worker returns the image in the project's reference space.
|
||||
bool has_color_transform = false;
|
||||
bool color_is_display = false;
|
||||
std::string color_output;
|
||||
std::string color_view;
|
||||
std::string color_look;
|
||||
|
||||
JsonMessage to_json() const
|
||||
{
|
||||
oak_ipc_render_frame c;
|
||||
c.ticket_id = ticket_id;
|
||||
detail::copy_str(node_uuid, c.node_uuid, sizeof(c.node_uuid));
|
||||
c.time_num = time_num;
|
||||
c.time_den = time_den;
|
||||
c.width = width;
|
||||
c.height = height;
|
||||
c.format = format;
|
||||
c.channel_count = channel_count;
|
||||
c.mode = mode;
|
||||
c.input_slot = input_slot;
|
||||
c.input_slot_count = std::min(int(input_slots.size()),
|
||||
OAK_IPC_INPUT_SLOTS_CAP);
|
||||
for (int i = 0; i < c.input_slot_count; i++) {
|
||||
c.input_slots[i] = input_slots.at(i);
|
||||
}
|
||||
c.has_color_transform = has_color_transform ? 1 : 0;
|
||||
c.color_is_display = color_is_display ? 1 : 0;
|
||||
detail::copy_str(color_output, c.color_output,
|
||||
sizeof(c.color_output));
|
||||
detail::copy_str(color_view, c.color_view, sizeof(c.color_view));
|
||||
detail::copy_str(color_look, c.color_look, sizeof(c.color_look));
|
||||
return detail::via_c_json([&](char *buf, int size) {
|
||||
return oakengine_ipc_render_frame_to_json(&c, buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
static bool from_json(const JsonMessage &o, RenderFrameMsg *out)
|
||||
{
|
||||
oak_ipc_render_frame c;
|
||||
if (!oakengine_ipc_render_frame_parse(o.c_str(), &c)) {
|
||||
return false;
|
||||
}
|
||||
out->ticket_id = c.ticket_id;
|
||||
out->node_uuid = c.node_uuid;
|
||||
out->time_num = c.time_num;
|
||||
out->time_den = c.time_den;
|
||||
out->width = c.width;
|
||||
out->height = c.height;
|
||||
out->format = c.format;
|
||||
out->channel_count = c.channel_count;
|
||||
out->mode = c.mode;
|
||||
out->input_slot = c.input_slot;
|
||||
out->input_slots.clear();
|
||||
for (int i = 0; i < c.input_slot_count; i++) {
|
||||
out->input_slots.push_back(c.input_slots[i]);
|
||||
}
|
||||
out->has_color_transform = c.has_color_transform != 0;
|
||||
out->color_is_display = c.color_is_display != 0;
|
||||
out->color_output = c.color_output;
|
||||
out->color_view = c.color_view;
|
||||
out->color_look = c.color_look;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct FrameReadyMsg {
|
||||
int64_t ticket_id = 0;
|
||||
int output_slot = 0; ///< Index into the worker->main output FrameSlotPool.
|
||||
|
||||
JsonMessage to_json() const
|
||||
{
|
||||
oak_ipc_frame_ready c;
|
||||
c.ticket_id = ticket_id;
|
||||
c.output_slot = output_slot;
|
||||
return detail::via_c_json([&](char *buf, int size) {
|
||||
return oakengine_ipc_frame_ready_to_json(&c, buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
static bool from_json(const JsonMessage &o, FrameReadyMsg *out)
|
||||
{
|
||||
oak_ipc_frame_ready c;
|
||||
if (!oakengine_ipc_frame_ready_parse(o.c_str(), &c)) {
|
||||
return false;
|
||||
}
|
||||
out->ticket_id = c.ticket_id;
|
||||
out->output_slot = c.output_slot;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct CancelMsg {
|
||||
int64_t ticket_id = 0;
|
||||
|
||||
JsonMessage to_json() const
|
||||
{
|
||||
oak_ipc_cancel c;
|
||||
c.ticket_id = ticket_id;
|
||||
return detail::via_c_json([&](char *buf, int size) {
|
||||
return oakengine_ipc_cancel_to_json(&c, buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
static bool from_json(const JsonMessage &o, CancelMsg *out)
|
||||
{
|
||||
oak_ipc_cancel c;
|
||||
if (!oakengine_ipc_cancel_parse(o.c_str(), &c)) {
|
||||
return false;
|
||||
}
|
||||
out->ticket_id = c.ticket_id;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct LoadGraphMsg {
|
||||
std::string path; ///< Temporary file holding the serialized node graph.
|
||||
|
||||
JsonMessage to_json() const
|
||||
{
|
||||
oak_ipc_load_graph c;
|
||||
detail::copy_str(path, c.path, sizeof(c.path));
|
||||
return detail::via_c_json([&](char *buf, int size) {
|
||||
return oakengine_ipc_load_graph_to_json(&c, buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
static bool from_json(const JsonMessage &o, LoadGraphMsg *out)
|
||||
{
|
||||
oak_ipc_load_graph c;
|
||||
if (!oakengine_ipc_load_graph_parse(o.c_str(), &c)) {
|
||||
return false;
|
||||
}
|
||||
out->path = c.path;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_IPC_IPCMESSAGE_H
|
||||
@@ -0,0 +1,171 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_IPC_SHAREDMEMORYREGION_H
|
||||
#define OAK_IPC_SHAREDMEMORYREGION_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "oakengine/ipc.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace ipc
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A named, fixed-size shared memory segment mapped into the process address space.
|
||||
*
|
||||
* Consumer-side wrapper over the liboakengine C ABI: the object only holds an opaque
|
||||
* OakSharedMemoryRegion handle and forwards every call across the C boundary. The public API is
|
||||
* unchanged from the original implementation (QString -> std::string notwithstanding).
|
||||
*
|
||||
* One process open()s the segment with k_create (owner); the peer process open()s it by the same
|
||||
* key with k_attach. The mapping is a raw contiguous byte range accessible via data() — the IPC
|
||||
* ring buffers and frame slot pools are laid out inside it. Nothing here is locked;
|
||||
* synchronization is entirely the caller's responsibility via the lock-free structures placed in
|
||||
* the mapping.
|
||||
*/
|
||||
class SharedMemoryRegion {
|
||||
public:
|
||||
enum Mode {
|
||||
/// Create (and own) the segment. Fails if it already exists; unlinks on destruction.
|
||||
k_create = OAK_IPC_SHM_MODE_CREATE,
|
||||
/// Attach to a segment created by the peer. Does not unlink on destruction.
|
||||
k_attach = OAK_IPC_SHM_MODE_ATTACH
|
||||
};
|
||||
|
||||
SharedMemoryRegion()
|
||||
: handle_(oakengine_ipc_shm_create())
|
||||
{
|
||||
}
|
||||
|
||||
~SharedMemoryRegion()
|
||||
{
|
||||
oakengine_ipc_shm_free(handle_);
|
||||
}
|
||||
|
||||
SharedMemoryRegion(const SharedMemoryRegion &) = delete;
|
||||
SharedMemoryRegion &operator=(const SharedMemoryRegion &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Open the segment identified by `key` with the given `size` in bytes.
|
||||
*
|
||||
* `key` is a short identifier (no leading slash needed; the platform prefix is added internally).
|
||||
* Returns true on success. On failure, error() carries a human-readable reason.
|
||||
*/
|
||||
bool open(const std::string &key, size_t size, Mode mode)
|
||||
{
|
||||
const bool ok = oakengine_ipc_shm_open(
|
||||
handle_, key.c_str(), size,
|
||||
static_cast<oak_ipc_shm_mode>(mode)) != 0;
|
||||
refresh_caches();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unmap and (if owner) unlink the segment. Called automatically by the destructor.
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
oakengine_ipc_shm_close(handle_);
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return oakengine_ipc_shm_is_valid(handle_) != 0;
|
||||
}
|
||||
|
||||
void *data() const
|
||||
{
|
||||
return oakengine_ipc_shm_data(handle_);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return oakengine_ipc_shm_size(handle_);
|
||||
}
|
||||
|
||||
const std::string &key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
const std::string &error() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a unique segment key for a worker, e.g. "olive-rw-<pid>-<index>".
|
||||
*
|
||||
* Centralized so the owner and the spawned worker agree on the same name.
|
||||
*/
|
||||
static std::string make_key(int64_t owner_pid, int worker_index)
|
||||
{
|
||||
const int size = oakengine_ipc_shm_make_key(owner_pid, worker_index,
|
||||
nullptr, 0);
|
||||
std::string buf(size + 1, '\0');
|
||||
oakengine_ipc_shm_make_key(owner_pid, worker_index, buf.data(),
|
||||
size + 1);
|
||||
buf.resize(size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The wrapped C handle, for cross-type wrappers and direct C API use
|
||||
*/
|
||||
OakSharedMemoryRegion *handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string query_string(int (*query)(const OakSharedMemoryRegion *,
|
||||
char *, int),
|
||||
const OakSharedMemoryRegion *handle)
|
||||
{
|
||||
const int size = query(handle, nullptr, 0);
|
||||
if (size <= 0) {
|
||||
return std::string();
|
||||
}
|
||||
std::string buf(size + 1, '\0');
|
||||
query(handle, buf.data(), size + 1);
|
||||
buf.resize(size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void refresh_caches()
|
||||
{
|
||||
key_ = query_string(oakengine_ipc_shm_key, handle_);
|
||||
error_ = query_string(oakengine_ipc_shm_error, handle_);
|
||||
}
|
||||
|
||||
OakSharedMemoryRegion *handle_;
|
||||
std::string key_;
|
||||
std::string error_;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_IPC_SHAREDMEMORYREGION_H
|
||||
@@ -0,0 +1,27 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_ACCELERATEDJOB_H
|
||||
#define OAK_ACCELERATEDJOB_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "param.h"
|
||||
#include "valuedatabase.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class AcceleratedJob {
|
||||
public:
|
||||
AcceleratedJob() = default;
|
||||
|
||||
virtual ~AcceleratedJob()
|
||||
{
|
||||
}
|
||||
|
||||
virtual NodeValue get(const std::string &input) const
|
||||
{
|
||||
auto it = value_map_.find(input);
|
||||
return it == value_map_.end() ? NodeValue() : it->second;
|
||||
}
|
||||
|
||||
virtual void insert(const std::string &input, const NodeValueRow &row)
|
||||
{
|
||||
// QHash::value() semantics: a missing key yields a default NodeValue
|
||||
auto it = row.find(input);
|
||||
value_map_[input] = it == row.end() ? NodeValue() : it->second;
|
||||
}
|
||||
|
||||
virtual void insert(const std::string &input, const NodeValue &value)
|
||||
{
|
||||
value_map_[input] = value;
|
||||
}
|
||||
|
||||
virtual void insert(const NodeValueRow &row)
|
||||
{
|
||||
value_map_.insert(row.begin(), row.end());
|
||||
}
|
||||
|
||||
virtual const NodeValueRow &get_values() const
|
||||
{
|
||||
return value_map_;
|
||||
}
|
||||
virtual NodeValueRow &get_values()
|
||||
{
|
||||
return value_map_;
|
||||
}
|
||||
|
||||
protected:
|
||||
NodeValueRow value_map_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_ACCELERATEDJOB_H
|
||||
@@ -0,0 +1,67 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_CACHEJOB_H
|
||||
#define OAK_CACHEJOB_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "value.h"
|
||||
#include "acceleratedjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CacheJob : public AcceleratedJob {
|
||||
public:
|
||||
CacheJob() = default;
|
||||
CacheJob(const std::string &filename, const NodeValue &fallback = NodeValue())
|
||||
{
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
const std::string &get_filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
void set_filename(const std::string &s)
|
||||
{
|
||||
filename_ = s;
|
||||
}
|
||||
|
||||
const NodeValue &get_fallback() const
|
||||
{
|
||||
return fallback_;
|
||||
}
|
||||
void set_fallback(const NodeValue &val)
|
||||
{
|
||||
fallback_ = val;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
|
||||
NodeValue fallback_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CACHEJOB_H
|
||||
@@ -0,0 +1,186 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_COLORTRANSFORMJOB_H
|
||||
#define OAK_COLORTRANSFORMJOB_H
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "alphaassoc.h"
|
||||
#include "colorprocessor.h"
|
||||
#include "mathtypes.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class Node;
|
||||
|
||||
class ColorTransformJob : public AcceleratedJob {
|
||||
public:
|
||||
ColorTransformJob()
|
||||
{
|
||||
processor_ = nullptr;
|
||||
custom_shader_src_ = nullptr;
|
||||
input_alpha_association_ = k_alpha_none;
|
||||
clear_destination_ = true;
|
||||
force_opaque_ = false;
|
||||
}
|
||||
|
||||
ColorTransformJob(const NodeValueRow &row)
|
||||
: ColorTransformJob()
|
||||
{
|
||||
insert(row);
|
||||
}
|
||||
|
||||
std::string id() const
|
||||
{
|
||||
if (id_.empty()) {
|
||||
return processor_->id();
|
||||
} else {
|
||||
return id_;
|
||||
}
|
||||
}
|
||||
|
||||
void set_override_id(const std::string &id)
|
||||
{
|
||||
id_ = id;
|
||||
}
|
||||
|
||||
const NodeValue &get_input_texture() const
|
||||
{
|
||||
return input_texture_;
|
||||
}
|
||||
void set_input_texture(const NodeValue &tex)
|
||||
{
|
||||
input_texture_ = tex;
|
||||
}
|
||||
void set_input_texture(TexturePtr tex)
|
||||
{
|
||||
assert(!tex->is_dummy());
|
||||
input_texture_ = NodeValue(NodeValue::k_texture, tex);
|
||||
}
|
||||
|
||||
ColorProcessorPtr get_color_processor() const
|
||||
{
|
||||
return processor_;
|
||||
}
|
||||
void set_color_processor(ColorProcessorPtr p)
|
||||
{
|
||||
processor_ = p;
|
||||
}
|
||||
|
||||
const AlphaAssociated &get_input_alpha_association() const
|
||||
{
|
||||
return input_alpha_association_;
|
||||
}
|
||||
void set_input_alpha_association(const AlphaAssociated &e)
|
||||
{
|
||||
input_alpha_association_ = e;
|
||||
}
|
||||
|
||||
const Node *custom_shader_source() const
|
||||
{
|
||||
return custom_shader_src_;
|
||||
}
|
||||
const std::string &custom_shader_id() const
|
||||
{
|
||||
return custom_shader_id_;
|
||||
}
|
||||
void set_needs_custom_shader(const Node *node,
|
||||
const std::string &id = std::string())
|
||||
{
|
||||
custom_shader_src_ = node;
|
||||
custom_shader_id_ = id;
|
||||
}
|
||||
|
||||
bool is_clear_destination_enabled() const
|
||||
{
|
||||
return clear_destination_;
|
||||
}
|
||||
void set_clear_destination_enabled(bool e)
|
||||
{
|
||||
clear_destination_ = e;
|
||||
}
|
||||
|
||||
const Matrix4x4 &get_transform_matrix() const
|
||||
{
|
||||
return matrix_;
|
||||
}
|
||||
void set_transform_matrix(const Matrix4x4 &m)
|
||||
{
|
||||
matrix_ = m;
|
||||
}
|
||||
|
||||
const Matrix4x4 &get_crop_matrix() const
|
||||
{
|
||||
return crop_matrix_;
|
||||
}
|
||||
void set_crop_matrix(const Matrix4x4 &m)
|
||||
{
|
||||
crop_matrix_ = m;
|
||||
}
|
||||
|
||||
const std::string &get_function_name() const
|
||||
{
|
||||
return function_name_;
|
||||
}
|
||||
void set_function_name(const std::string &function_name = std::string())
|
||||
{
|
||||
function_name_ = function_name;
|
||||
};
|
||||
|
||||
bool get_force_opaque() const
|
||||
{
|
||||
return force_opaque_;
|
||||
}
|
||||
void set_force_opaque(bool e)
|
||||
{
|
||||
force_opaque_ = e;
|
||||
}
|
||||
|
||||
private:
|
||||
ColorProcessorPtr processor_;
|
||||
std::string id_;
|
||||
|
||||
NodeValue input_texture_;
|
||||
|
||||
const Node *custom_shader_src_;
|
||||
std::string custom_shader_id_;
|
||||
|
||||
AlphaAssociated input_alpha_association_;
|
||||
|
||||
bool clear_destination_;
|
||||
|
||||
Matrix4x4 matrix_;
|
||||
|
||||
Matrix4x4 crop_matrix_;
|
||||
|
||||
std::string function_name_;
|
||||
|
||||
bool force_opaque_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORTRANSFORMJOB_H
|
||||
@@ -0,0 +1,199 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_FOOTAGEJOB_H
|
||||
#define OAK_FOOTAGEJOB_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "loopmode.h"
|
||||
#include "rendermodes.h"
|
||||
#include "output/track/track.h"
|
||||
#include "project/footage/footage.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class FootageJob : public AcceleratedJob {
|
||||
public:
|
||||
FootageJob()
|
||||
: type_(Track::k_none)
|
||||
{
|
||||
}
|
||||
|
||||
FootageJob(const TimeRange &time, const std::string &decoder,
|
||||
const std::string &filename, Track::Type type,
|
||||
const Rational &length, LoopMode loop_mode)
|
||||
: time_(time)
|
||||
, decoder_(decoder)
|
||||
, filename_(filename)
|
||||
, type_(type)
|
||||
, length_(length)
|
||||
, loop_mode_(loop_mode)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &decoder() const
|
||||
{
|
||||
return decoder_;
|
||||
}
|
||||
|
||||
const std::string &filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
bool has_proxy() const
|
||||
{
|
||||
return has_proxy_;
|
||||
}
|
||||
|
||||
const std::string &proxy_filename() const
|
||||
{
|
||||
return proxy_filename_;
|
||||
}
|
||||
|
||||
const std::string &proxy_decoder() const
|
||||
{
|
||||
return proxy_decoder_;
|
||||
}
|
||||
|
||||
int proxy_stream_index() const
|
||||
{
|
||||
return proxy_stream_index_;
|
||||
}
|
||||
|
||||
void set_proxy(const std::string &filename, const std::string &decoder,
|
||||
int stream_index)
|
||||
{
|
||||
proxy_filename_ = filename;
|
||||
proxy_decoder_ = decoder;
|
||||
proxy_stream_index_ = stream_index;
|
||||
has_proxy_ = !filename.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether decoding for the given render mode should use the proxy
|
||||
*
|
||||
* Proxies are a preview accelerator only: offline (realtime preview)
|
||||
* renders may decode from them, online (export/master) renders must
|
||||
* always decode the original media. The proxy file must also still
|
||||
* exist on disk, otherwise decoding falls back to the original.
|
||||
*/
|
||||
bool should_use_proxy(RenderMode::Mode mode) const
|
||||
{
|
||||
std::error_code ec;
|
||||
return mode == RenderMode::k_offline && has_proxy() &&
|
||||
std::filesystem::exists(proxy_filename_, ec);
|
||||
}
|
||||
|
||||
Track::Type type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const VideoParams &video_params() const
|
||||
{
|
||||
return video_params_;
|
||||
}
|
||||
|
||||
void set_video_params(const VideoParams &p)
|
||||
{
|
||||
video_params_ = p;
|
||||
}
|
||||
|
||||
const AudioParams &audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
void set_audio_params(const AudioParams &p)
|
||||
{
|
||||
audio_params_ = p;
|
||||
}
|
||||
|
||||
const std::string &cache_path() const
|
||||
{
|
||||
return cache_path_;
|
||||
}
|
||||
|
||||
void set_cache_path(const std::string &p)
|
||||
{
|
||||
cache_path_ = p;
|
||||
}
|
||||
|
||||
const Rational &length() const
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
|
||||
void set_length(const Rational &length)
|
||||
{
|
||||
length_ = length;
|
||||
}
|
||||
|
||||
const TimeRange &time() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
|
||||
LoopMode loop_mode() const
|
||||
{
|
||||
return loop_mode_;
|
||||
}
|
||||
void set_loop_mode(LoopMode m)
|
||||
{
|
||||
loop_mode_ = m;
|
||||
}
|
||||
|
||||
private:
|
||||
TimeRange time_;
|
||||
|
||||
std::string decoder_;
|
||||
|
||||
std::string filename_;
|
||||
|
||||
bool has_proxy_ = false;
|
||||
|
||||
std::string proxy_filename_;
|
||||
|
||||
std::string proxy_decoder_;
|
||||
|
||||
int proxy_stream_index_ = -1;
|
||||
|
||||
Track::Type type_;
|
||||
|
||||
VideoParams video_params_;
|
||||
|
||||
AudioParams audio_params_;
|
||||
|
||||
std::string cache_path_;
|
||||
|
||||
Rational length_;
|
||||
|
||||
LoopMode loop_mode_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_FOOTAGEJOB_H
|
||||
@@ -0,0 +1,43 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_GENERATEJOB_H
|
||||
#define OAK_GENERATEJOB_H
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "codec/frame.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class GenerateJob : public AcceleratedJob {
|
||||
public:
|
||||
GenerateJob() = default;
|
||||
GenerateJob(const NodeValueRow &row)
|
||||
: GenerateJob()
|
||||
{
|
||||
insert(row);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_GENERATEJOB_H
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "pluginjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
} // plugin
|
||||
} // olive
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OAK_PLUGINJOB_H
|
||||
#define OAK_PLUGINJOB_H
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
#include "plugins/plugin.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
|
||||
#include <any>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
|
||||
class PluginJob : public AcceleratedJob {
|
||||
public:
|
||||
explicit PluginJob(const OFX::Host::ImageEffect::Instance *plugin_instance,
|
||||
const PluginNode *node, NodeValueRow row,
|
||||
const olive::core::Rational &time)
|
||||
: AcceleratedJob()
|
||||
, time_seconds_(time.to_double())
|
||||
{
|
||||
this->pluginInstance_ = plugin_instance;
|
||||
this->node_ = node;
|
||||
insert(row);
|
||||
}
|
||||
explicit PluginJob(const OFX::Host::ImageEffect::Instance *plugin_instance,
|
||||
const PluginNode *node, NodeValueRow row)
|
||||
: PluginJob(plugin_instance, node, row, olive::core::Rational(0))
|
||||
{
|
||||
}
|
||||
|
||||
PluginNode *node() const
|
||||
{
|
||||
return const_cast<PluginNode *>(node_);
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::Instance *plugin_instance()
|
||||
{
|
||||
return const_cast<OFX::Host::ImageEffect::Instance *>(pluginInstance_);
|
||||
}
|
||||
|
||||
double time_seconds() const
|
||||
{
|
||||
return time_seconds_;
|
||||
}
|
||||
|
||||
private:
|
||||
const OFX::Host::ImageEffect::Instance *pluginInstance_ = nullptr;
|
||||
|
||||
std::map<OfxTime, std::map<std::string, std::any>> paramsOnTime_;
|
||||
|
||||
std::map<std::string, std::any> params_;
|
||||
|
||||
const PluginNode *node_ = nullptr;
|
||||
double time_seconds_ = 0.0;
|
||||
};
|
||||
|
||||
} // plugin
|
||||
} // olive
|
||||
|
||||
#endif //OAK_PLUGINJOB_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SAMPLEJOB_H
|
||||
#define OAK_SAMPLEJOB_H
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "olive/core/util/timerange.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using core::TimeRange;
|
||||
|
||||
class SampleJob : public AcceleratedJob {
|
||||
public:
|
||||
SampleJob()
|
||||
{
|
||||
}
|
||||
|
||||
SampleJob(const TimeRange &time, const NodeValue &value)
|
||||
{
|
||||
samples_ = value.to_samples();
|
||||
time_ = time;
|
||||
}
|
||||
|
||||
SampleJob(const TimeRange &time, const std::string &from,
|
||||
const NodeValueRow &row)
|
||||
{
|
||||
samples_ = row.at(from).to_samples();
|
||||
time_ = time;
|
||||
}
|
||||
|
||||
const SampleBuffer &samples() const
|
||||
{
|
||||
return samples_;
|
||||
}
|
||||
|
||||
bool has_samples() const
|
||||
{
|
||||
return samples_.is_allocated();
|
||||
}
|
||||
|
||||
const TimeRange &time() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
|
||||
private:
|
||||
SampleBuffer samples_;
|
||||
|
||||
TimeRange time_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SAMPLEJOB_H
|
||||
@@ -0,0 +1,126 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_SHADERJOB_H
|
||||
#define OAK_SHADERJOB_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "acceleratedjob.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ShaderJob : public AcceleratedJob {
|
||||
public:
|
||||
ShaderJob()
|
||||
{
|
||||
iterations_ = 1;
|
||||
}
|
||||
|
||||
ShaderJob(const NodeValueRow &row)
|
||||
: ShaderJob()
|
||||
{
|
||||
insert(row);
|
||||
}
|
||||
|
||||
const std::string &get_shader_id() const
|
||||
{
|
||||
return shader_id_;
|
||||
}
|
||||
|
||||
void set_shader_id(const std::string &id)
|
||||
{
|
||||
shader_id_ = id;
|
||||
}
|
||||
|
||||
void set_iterations(int iterations, const NodeInput &iterative_input)
|
||||
{
|
||||
set_iterations(iterations, iterative_input.input());
|
||||
}
|
||||
|
||||
void set_iterations(int iterations, const std::string &iterative_input)
|
||||
{
|
||||
iterations_ = iterations;
|
||||
iterative_input_ = iterative_input;
|
||||
}
|
||||
|
||||
int get_iteration_count() const
|
||||
{
|
||||
return iterations_;
|
||||
}
|
||||
|
||||
const std::string &get_iterative_input() const
|
||||
{
|
||||
return iterative_input_;
|
||||
}
|
||||
|
||||
Texture::Interpolation get_interpolation(const std::string &id) const
|
||||
{
|
||||
auto it = interpolation_.find(id);
|
||||
return it == interpolation_.end() ? Texture::k_default_interpolation :
|
||||
it->second;
|
||||
}
|
||||
|
||||
const std::map<std::string, Texture::Interpolation> &
|
||||
get_interpolation_map() const
|
||||
{
|
||||
return interpolation_;
|
||||
}
|
||||
|
||||
void set_interpolation(const NodeInput &input, Texture::Interpolation interp)
|
||||
{
|
||||
interpolation_[input.input()] = interp;
|
||||
}
|
||||
|
||||
void set_interpolation(const std::string &id, Texture::Interpolation interp)
|
||||
{
|
||||
interpolation_[id] = interp;
|
||||
}
|
||||
|
||||
void set_vertex_coordinates(const std::vector<float> &vertex_coords)
|
||||
{
|
||||
vertex_overrides_ = vertex_coords;
|
||||
}
|
||||
|
||||
const std::vector<float> &get_vertex_coordinates()
|
||||
{
|
||||
return vertex_overrides_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string shader_id_;
|
||||
|
||||
int iterations_;
|
||||
|
||||
std::string iterative_input_;
|
||||
|
||||
std::map<std::string, Texture::Interpolation> interpolation_;
|
||||
|
||||
std::vector<float> vertex_overrides_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SHADERJOB_H
|
||||
@@ -0,0 +1,151 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "lutlibrary.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string trim(const std::string &s)
|
||||
{
|
||||
const char *ws = " \t\n\r";
|
||||
const std::string::size_type first = s.find_first_not_of(ws);
|
||||
if (first == std::string::npos) {
|
||||
return std::string();
|
||||
}
|
||||
const std::string::size_type last = s.find_last_not_of(ws);
|
||||
return s.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
std::vector<std::string> split_skip_empty(const std::string &s, char sep)
|
||||
{
|
||||
std::vector<std::string> parts;
|
||||
std::string::size_type start = 0;
|
||||
while (true) {
|
||||
const std::string::size_type pos = s.find(sep, start);
|
||||
const std::string part =
|
||||
s.substr(start, pos == std::string::npos ? pos : pos - start);
|
||||
if (!part.empty()) {
|
||||
parts.push_back(part);
|
||||
}
|
||||
if (pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = pos + 1;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &LUTLibrary::supported_extensions()
|
||||
{
|
||||
// LUT formats OCIO FileTransform can load
|
||||
static const std::vector<std::string> extensions = {
|
||||
"cube", "3dl", "spi1d",
|
||||
"spi3d", "spimtx", "csp",
|
||||
"clf", "ctf", "cub",
|
||||
};
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool LUTLibrary::is_supported_extension(const std::string &suffix)
|
||||
{
|
||||
std::string s = suffix;
|
||||
if (!s.empty() && s.front() == '.') {
|
||||
s.erase(0, 1);
|
||||
}
|
||||
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
const std::vector<std::string> &exts = supported_extensions();
|
||||
return std::find(exts.begin(), exts.end(), s) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> LUTLibrary::get_directories()
|
||||
{
|
||||
const std::string serialized = OAK_CONFIG("LUTLibraryPaths").toString();
|
||||
|
||||
std::vector<std::string> dirs = split_skip_empty(serialized, ';');
|
||||
for (std::string &dir : dirs) {
|
||||
// QDir::fromNativeSeparators is a no-op off Windows, so only the
|
||||
// trim from the original code remains here.
|
||||
dir = trim(dir);
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
void LUTLibrary::set_directories(const std::vector<std::string> &dirs)
|
||||
{
|
||||
std::vector<std::string> cleaned;
|
||||
for (const std::string &dir : dirs) {
|
||||
const std::string trimmed = trim(dir);
|
||||
if (!trimmed.empty() &&
|
||||
std::find(cleaned.begin(), cleaned.end(), trimmed) ==
|
||||
cleaned.end()) {
|
||||
cleaned.push_back(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
std::string joined;
|
||||
for (size_t i = 0; i < cleaned.size(); i++) {
|
||||
if (i > 0) {
|
||||
joined += ';';
|
||||
}
|
||||
joined += cleaned[i];
|
||||
}
|
||||
|
||||
Config::current()["LUTLibraryPaths"] = joined;
|
||||
}
|
||||
|
||||
std::vector<std::string> LUTLibrary::get_lut_files()
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
||||
for (const std::string &dir : get_directories()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::recursive_directory_iterator it(
|
||||
dir, std::filesystem::directory_options::none, ec);
|
||||
const std::filesystem::recursive_directory_iterator end;
|
||||
for (; !ec && it != end; it.increment(ec)) {
|
||||
if (!it->is_regular_file(ec)) {
|
||||
continue;
|
||||
}
|
||||
// Mirrors the original QDir name filters "*.cube" / "*.3dl"
|
||||
// (case-sensitive).
|
||||
const std::string ext = it->path().extension().string();
|
||||
if (ext == ".cube" || ext == ".3dl") {
|
||||
files.push_back(it->path().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_LUTLIBRARY_H
|
||||
#define OAK_LUTLIBRARY_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A global, user-configurable library of LUT files
|
||||
*
|
||||
* The library is a list of directories (stored in the application config
|
||||
* under "LUTLibraryPaths") that are scanned for supported LUT files. LUT
|
||||
* nodes can offer the library contents as quick picks instead of forcing
|
||||
* the user to browse for a file path on every node.
|
||||
*/
|
||||
class LUTLibrary {
|
||||
public:
|
||||
/**
|
||||
* @brief All LUT file extensions supported by the library
|
||||
*
|
||||
* Extensions OCIO FileTransform can load, lowercase, without the dot.
|
||||
*/
|
||||
static const std::vector<std::string> &supported_extensions();
|
||||
|
||||
/**
|
||||
* @brief Returns true if the given file suffix is a supported LUT
|
||||
* extension (case-insensitive, leading dot tolerated)
|
||||
*/
|
||||
static bool is_supported_extension(const std::string &suffix);
|
||||
|
||||
/**
|
||||
* @brief The directories that make up the LUT library
|
||||
*/
|
||||
static std::vector<std::string> get_directories();
|
||||
|
||||
/**
|
||||
* @brief Replaces the LUT library directories and saves them to the
|
||||
* application config
|
||||
*/
|
||||
static void set_directories(const std::vector<std::string> &dirs);
|
||||
|
||||
/**
|
||||
* @brief All supported LUT files found under the library directories
|
||||
*
|
||||
* Directories are scanned recursively. Files in earlier directories
|
||||
* are listed first.
|
||||
*/
|
||||
static std::vector<std::string> get_lut_files();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_LUTLIBRARY_H
|
||||
@@ -0,0 +1,25 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
// ManagedColor has moved to application code
|
||||
// (app/widget/manageddisplay/colorprocessorhandle.h) as part of the C ABI
|
||||
// migration. This translation unit is intentionally left empty (the file is
|
||||
// kept so the existing build rules keep working).
|
||||
@@ -0,0 +1,31 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_MANAGEDCOLOR_H
|
||||
#define OAK_MANAGEDCOLOR_H
|
||||
|
||||
// ManagedColor has moved to application code
|
||||
// (app/widget/manageddisplay/colorprocessorhandle.h) as part of the C ABI
|
||||
// migration: it is a pure UI value type that the engine never uses. This
|
||||
// header is intentionally left empty (the file is kept so the existing
|
||||
// build rules keep working) and must not be included by new code.
|
||||
|
||||
#endif // OAK_MANAGEDCOLOR_H
|
||||
Executable
+401
@@ -0,0 +1,401 @@
|
||||
# Film Emulsion-like configuration for
|
||||
# Blender. Crafted by Troy James Sobotka with
|
||||
# special thanks, feedback, and knowledge from Guillermo
|
||||
# Espertino, Claudio Rocha, Bassam Kurdali, Eugenio
|
||||
# Pignataro, Henri Hebeisen, Jason Clarke,
|
||||
# Haarm-Peter Duiker, Thomas Mansencal, Andrew
|
||||
# Price, Nick Shaw, and Timothy
|
||||
# Lottes.
|
||||
|
||||
ocio_profile_version: 2
|
||||
|
||||
search_path: "luts:looks"
|
||||
strictparsing: true
|
||||
luma: [0.2126, 0.7152, 0.0722]
|
||||
|
||||
description: A filmlike dynamic range encoding set for Blender
|
||||
|
||||
roles:
|
||||
default: Rec.709 OETF
|
||||
reference: Linear
|
||||
scene_linear: Linear
|
||||
data: Non-Colour Data
|
||||
compositing_log: Filmic Log Encoding
|
||||
color_timing: Filmic Log Encoding
|
||||
default_byte: sRGB OETF
|
||||
default_float: Linear
|
||||
default_sequencer: sRGB OETF
|
||||
color_picking: sRGB OETF
|
||||
texture_paint: sRGB OETF
|
||||
matte_paint: Filmic Log Encoding
|
||||
cie_xyz_d65_interchange: CIE-XYZ D65
|
||||
|
||||
displays:
|
||||
sRGB:
|
||||
- !<View> {name: sRGB OETF, colorspace: sRGB OETF}
|
||||
- !<View> {name: Non-Colour Data, colorspace: Non-Colour Data}
|
||||
- !<View> {name: Linear Raw, colorspace: Linear}
|
||||
- !<View> {name: Filmic Log Encoding Base, colorspace: Filmic Log Encoding}
|
||||
BT.1886:
|
||||
- !<View> {name: BT.1886 EOTF, colorspace: BT.1886 EOTF}
|
||||
- !<View> {name: Non-Colour Data, colorspace: Non-Colour Data}
|
||||
- !<View> {name: Linear Raw, colorspace: Linear}
|
||||
- !<View> {name: Filmic Log Encoding Base, colorspace: BT.1886 Filmic Log Encoding}
|
||||
Apple Display P3:
|
||||
- !<View> {name: sRGB OETF, colorspace: AppleP3 sRGB OETF}
|
||||
- !<View> {name: Non-Colour Data, colorspace: Non-Colour Data}
|
||||
- !<View> {name: Linear Raw, colorspace: Linear}
|
||||
- !<View> {name: Filmic Log Encoding Base, colorspace: AppleP3 Filmic Log Encoding}
|
||||
|
||||
active_displays: [sRGB, BT.1886, Apple Display P3, None]
|
||||
#active_views: [Filmic Log Encoding Base, sRGB OETF, Non-Colour Data, Linear Raw, No View]
|
||||
|
||||
inactive_colorspaces: [CIE-XYZ D65]
|
||||
|
||||
colorspaces:
|
||||
- !<ColorSpace>
|
||||
name: Linear
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
ITU BT.709 primaries based scene referred linear space.
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
|
||||
- !<ColorSpace>
|
||||
name: CIE-XYZ D65
|
||||
family: display
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Linear CIE XYZ space with D65 white point
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<MatrixTransform> {matrix: [0.4124, 0.3576, 0.1805, 0, 0.2126, 0.7152, 0.0722, 0, 0.0193, 0.1192, 0.9505, 0, 0, 0, 0, 1], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Filmic Log Encoding
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Log based filmic shaper with 16.5 stops of latitude, and 25 stops of dynamic range.
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
from_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<AllocationTransform> {allocation: lg2, vars: [-12.4739311883, 12.5260688117, 0.00392156862]}
|
||||
- !<FileTransform> {src: desat65cube.spi3d, interpolation: best}
|
||||
- !<AllocationTransform> {allocation: uniform, vars: [0, 0.66]}
|
||||
to_reference: !<AllocationTransform> {allocation: lg2, vars: [-12.4739311883, 4.02606881167, 0.00392156862], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: sRGB OETF
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
sRGB specification display referred Optical-Electro Transfer Function.
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0.0, 1.0]
|
||||
to_reference: !<FileTransform> {src: sRGB_OETF_to_Linear.spi1d, interpolation: linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Apple DCI-P3 D65
|
||||
family: display
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<MatrixTransform> {matrix: [0.515121, 0.291977, 0.157104, 0, 0.241196, 0.692245, 0.0665741, 0, -0.00105286, 0.0418854, 0.784073, 0, 0, 0, 0, 1]}
|
||||
- !<MatrixTransform> {matrix: [1.04788, 0.0229187, -0.0502014, 0, 0.0295868, 0.990479, -0.0170593, 0, -0.00923157, 0.0150757, 0.751678, 0, 0, 0, 0, 1], direction: inverse}
|
||||
- !<MatrixTransform> {matrix: [0.412391, 0.357584, 0.180481, 0, 0.212639, 0.715169, 0.0721923, 0, 0.0193308, 0.119195, 0.950532, 0, 0, 0, 0, 1], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: AppleP3 sRGB OETF
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
sRGB specification display referred Optical-Electro Transfer Function with Apple DCI-P3 primaries.
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0.0, 1.0]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: sRGB_OETF_to_Linear.spi1d, interpolation: linear}
|
||||
- !<ColorSpaceTransform> {src: Apple DCI-P3 D65, dst: Linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: BT.1886 EOTF
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
BT.1886 specification display referred Electro-Optical Transfer Function with REC.709 primaries.
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0.0, 1.0]
|
||||
to_reference: !<ExponentTransform> {value: [2.4, 2.4, 2.4, 1.0]}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: AppleP3 Filmic Log Encoding
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Log based filmic shaper with 16.5 stops of latitude, and 25 stops of dynamic range with Apple P3 primaries.
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
from_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<ColorSpaceTransform> {src: Linear, dst: Filmic Log Encoding}
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0]}
|
||||
- !<ColorSpaceTransform> {src: Linear, dst: Apple DCI-P3 D65}
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0], direction: inverse}
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0]}
|
||||
- !<ColorSpaceTransform> {src: Apple DCI-P3 D65, dst: Linear}
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0], direction: inverse}
|
||||
- !<AllocationTransform> {allocation: lg2, vars: [-12.4739311883, 4.02606881167, 0.00392156862], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: BT.1886 Filmic Log Encoding
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Log based filmic shaper with 16.5 stops of latitude, and 25 stops of dynamic range with REC.709 primaries.
|
||||
isdata: false
|
||||
allocation: lg2
|
||||
allocationvars: [-12.4739311883, 12.5260688117, 0.00392156862]
|
||||
from_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<ColorSpaceTransform> {src: Linear, dst: Filmic Log Encoding}
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0]}
|
||||
- !<ExponentTransform> {value: [2.4, 2.4, 2.4, 1.0], direction: inverse}
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<ExponentTransform> {value: [2.4, 2.4, 2.4, 1.0]}
|
||||
- !<ExponentTransform> {value: [2.2, 2.2, 2.2, 1.0], direction: inverse}
|
||||
- !<AllocationTransform> {allocation: lg2, vars: [-12.4739311883, 4.02606881167, 0.00392156862], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Fuji F-Log OETF
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Fuji F-Log transfer function
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<FileTransform> {src: F-Log_to_Linear.spi1d, interpolation: linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Fuji F-Log F-Gamut
|
||||
family: ""
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Fuji F-Log / F-Gamut
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<ColorSpaceTransform> {src: Fuji F-Log OETF, dst: Linear}
|
||||
- !<MatrixTransform> {matrix: [0.636958048000, 0.144616904000, 0.168880975000, 0.000000000000, 0.262700212000, 0.677998072000, 0.059301716500, 0.000000000000, 4.994106570E-17, 0.028072693000, 1.060985060000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 1.000000000000]}
|
||||
- !<MatrixTransform> {matrix: [0.412390800000, 0.357584340000, 0.180480790000, 0.000000000000, 0.212639010000, 0.715168680000, 0.072192320000, 0.000000000000, 0.019330820000, 0.119194780000, 0.950532150000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 1.000000000000], direction: inverse}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Panasonic V-Log V-Gamut
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Panasonic V-Log / V-Gamut
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: V-Log_to_linear.spi1d, interpolation: linear}
|
||||
- !<MatrixTransform> {matrix: [1.806576, -0.695697, -0.110879, 0, -0.170090 , 1.305955, -0.135865, 0, -0.025206, -0.154468, 1.179674, 0, 0, 0, 0, 1]}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Arri Wide Gamut / LogC EI 800
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Panasonic V-Log / V-Gamut
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: V3_LogC_800_to_linear.spi1d, interpolation: linear}
|
||||
- !<MatrixTransform> {matrix: [1.617523, -0.537287, -0.080237, 0, -0.070573, 1.334613, -0.26404, 0, -0.021102, -0.226954, 1.248056, 0, 0, 0, 0, 1]}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Arri Wide Gamut / LogC EI 400
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Panasonic V-Log / V-Gamut
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: V3_LogC_400_to_linear.spi1d, interpolation: linear}
|
||||
- !<MatrixTransform> {matrix: [1.617523, -0.537287, -0.080237, 0, -0.070573, 1.334613, -0.26404, 0, -0.021102, -0.226954, 1.248056, 0, 0, 0, 0, 1]}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Arri Wide Gamut 4/ LogC4
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Arri Wide Gamut 4 LogC4 input
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<LogCameraTransform> {log_side_slope: 0.0647954196341293, log_side_offset: -0.295908392682586, lin_side_slope: 2231.82630906769, lin_side_offset: 64, lin_side_break: -0.0180569961199113, direction: inverse}
|
||||
- !<MatrixTransform> {matrix: [1.893123, -0.780882, -0.112242, 0, -0.205700, 1.340257, -0.134557, 0, -0.012706, -0.152185, 1.164891, 0, 0, 0, 0, 1]}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Blackmagic Film Wide Gamut (Gen 5)
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Blackmagic Film Wide Gamut (Gen 5)
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: Blackmagic_FilmWideGamut_Gen5_to_linear.spi1d, interpolation: linear}
|
||||
- !<MatrixTransform> {matrix: [0.606530, 0.220408, 0.123479, 0, 0.267989, 0.832731, -0.100720, 0, -0.029442, -0.086611, 1.204861, 0, 0, 0, 0, 1]}
|
||||
- !<ColorSpaceTransform> {src: CIE-XYZ D65, dst: reference}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Rec.709 OETF
|
||||
family: Camera Footage
|
||||
equalitygroup: ""
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Rec.709 OETF
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<FileTransform> {src: rec709_to_linear.spi1d, interpolation: linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Rec.601 OETF (NTSC)
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Rec.601 Optical-Electro Transfer Function.
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0.0, 1.0]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<MatrixTransform> {matrix: [0.939542, 0.050181, 0.010277, 0, 0.017772, 0.965793, 0.016435, 0, -0.001622, -0.004370, 1.005991, 0, 0, 0, 0, 1]}
|
||||
- !<ColorSpaceTransform> {src: Rec.709 OETF, dst: Linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Rec.601 OETF (PAL)
|
||||
family:
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
description: |
|
||||
Rec.601 Optical-Electro Transfer Function.
|
||||
isdata: false
|
||||
allocation: uniform
|
||||
allocationvars: [0.0, 1.0]
|
||||
to_reference: !<GroupTransform>
|
||||
children:
|
||||
- !<MatrixTransform> {matrix: [1.044043, -0.044043, 0.000000, 0, 0.000000, 1.000000, -0.000000, 0, -0.000000, 0.011793, 0.988207, 0, 0, 0, 0, 1]}
|
||||
- !<ColorSpaceTransform> {src: Rec.709 OETF, dst: Linear}
|
||||
|
||||
- !<ColorSpace>
|
||||
name: Non-Colour Data
|
||||
family:
|
||||
description: |
|
||||
Transform to flag data as non-colour, strictly data, and avoid OCIO colour specific transforms.
|
||||
equalitygroup:
|
||||
bitdepth: 32f
|
||||
isdata: true
|
||||
allocation: uniform
|
||||
allocationvars: [0, 1]
|
||||
|
||||
looks:
|
||||
- !<Look>
|
||||
name: Greyscale
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<MatrixTransform> {matrix: [0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0, 0, 0, 1]}
|
||||
|
||||
- !<Look>
|
||||
name: False Colour
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<GroupTransform>
|
||||
children:
|
||||
- !<MatrixTransform> {matrix: [0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0, 0, 0, 1]}
|
||||
- !<FileTransform> {src: Filmic_False_Colour.spi3d, interpolation: best}
|
||||
|
||||
- !<Look>
|
||||
name: Very High Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_1.20_1-00.spi1d, interpolation: linear}
|
||||
|
||||
- !<Look>
|
||||
name: High Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0.99_1-0075.spi1d, interpolation: linear}
|
||||
|
||||
- !<Look>
|
||||
name: Medium High Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0-85_1-011.spi1d, interpolation: best}
|
||||
|
||||
- !<Look>
|
||||
name: Base Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0-70_1-03.spi1d, interpolation: linear}
|
||||
|
||||
- !<Look>
|
||||
name: Medium Low Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0-60_1-04.spi1d, interpolation: linear}
|
||||
|
||||
- !<Look>
|
||||
name: Low Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0-48_1-09.spi1d, interpolation: linear}
|
||||
|
||||
- !<Look>
|
||||
name: Very Low Contrast
|
||||
process_space: Filmic Log Encoding
|
||||
transform: !<FileTransform> {src: Filmic_to_0-35_1-30.spi1d, interpolation: linear}
|
||||
+274628
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
+65542
File diff suppressed because it is too large
Load Diff
+65542
File diff suppressed because it is too large
Load Diff
+274628
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+4102
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/ocioconf">
|
||||
@QRC_BODY@
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -0,0 +1,239 @@
|
||||
#include "render/backend/renderbackend_c.h"
|
||||
|
||||
#include "mathtypes.h"
|
||||
#include "variant.h"
|
||||
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
#include "render/shadercode.h"
|
||||
#include "render/texture.h"
|
||||
#include "videoparams.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class BackendOpenGLRenderer : public olive::OpenGLRenderer {
|
||||
public:
|
||||
using olive::OpenGLRenderer::OpenGLRenderer;
|
||||
using olive::OpenGLRenderer::blit;
|
||||
using olive::OpenGLRenderer::create_native_texture;
|
||||
using olive::OpenGLRenderer::destroy_internal;
|
||||
using olive::OpenGLRenderer::destroy_native_texture;
|
||||
using olive::OpenGLRenderer::attach_texture_as_destination;
|
||||
using olive::OpenGLRenderer::detach_texture_as_destination;
|
||||
};
|
||||
|
||||
// Converts the opaque C ABI handle back to the C++ renderer used internally.
|
||||
BackendOpenGLRenderer *renderer(OakRenderBackendHandle handle)
|
||||
{
|
||||
return static_cast<BackendOpenGLRenderer *>(handle);
|
||||
}
|
||||
|
||||
// Interprets ABI Variant payloads without copying; both modules are built
|
||||
// against the same C++ ABI in this first-generation dynamic backend.
|
||||
const olive::Variant &variant_ref(const void *variant)
|
||||
{
|
||||
return *static_cast<const olive::Variant *>(variant);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Creates the backend object and returns it as an opaque C handle. The
|
||||
// QObject-style parent argument is retained for ABI parity and ignored.
|
||||
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle
|
||||
oak_renderer_create(void *parent)
|
||||
{
|
||||
(void) parent;
|
||||
return new BackendOpenGLRenderer();
|
||||
}
|
||||
|
||||
// Destroys the opaque backend object created by oak_renderer_create().
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_destroy(OakRenderBackendHandle handle)
|
||||
{
|
||||
delete renderer(handle);
|
||||
}
|
||||
|
||||
// Reports static OpenGL backend capabilities to the adapter.
|
||||
OAK_RENDER_BACKEND_EXPORT bool
|
||||
oak_renderer_get_info(OakRenderBackendHandle handle,
|
||||
OakRenderBackendInfo *out_info)
|
||||
{
|
||||
if (!handle || !out_info) {
|
||||
return false;
|
||||
}
|
||||
out_info->abi_version = 1;
|
||||
out_info->kind = oak_render_backend_opengl;
|
||||
out_info->capabilities =
|
||||
oak_render_backend_cap_textures | oak_render_backend_cap_shaders |
|
||||
oak_render_backend_cap_blit | oak_render_backend_cap_readback |
|
||||
oak_render_backend_cap_viewer_context;
|
||||
out_info->name = "opengl";
|
||||
out_info->status = "available";
|
||||
return true;
|
||||
}
|
||||
|
||||
// OpenGL availability is context-dependent, so object creation is the minimum
|
||||
// availability signal for this backend.
|
||||
OAK_RENDER_BACKEND_EXPORT bool
|
||||
oak_renderer_is_available(OakRenderBackendHandle handle)
|
||||
{
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
// Initializes an offscreen OpenGL context for non-viewer users.
|
||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
||||
{
|
||||
return renderer(handle)->init();
|
||||
}
|
||||
|
||||
// Initializes the backend against a caller-owned viewer OpenGL context.
|
||||
// `context` is an olive::OpenGLContext * adopted from the app layer.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_init_with_context(OakRenderBackendHandle handle, void *context)
|
||||
{
|
||||
renderer(handle)->init(static_cast<olive::OpenGLContext *>(context));
|
||||
}
|
||||
|
||||
// Runs renderer post-initialization once the GL context is available.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_post_init(OakRenderBackendHandle handle)
|
||||
{
|
||||
renderer(handle)->post_init();
|
||||
}
|
||||
|
||||
// Releases post-init OpenGL surface/context state.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_post_destroy(OakRenderBackendHandle handle)
|
||||
{
|
||||
renderer(handle)->post_destroy();
|
||||
}
|
||||
|
||||
// Releases renderer-owned GL resources before object destruction.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_destroy_internal(OakRenderBackendHandle handle)
|
||||
{
|
||||
renderer(handle)->destroy_internal();
|
||||
}
|
||||
|
||||
// Clears either the widget framebuffer or a texture destination.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_clear_destination(OakRenderBackendHandle handle, void *texture,
|
||||
double r, double g, double b, double a)
|
||||
{
|
||||
renderer(handle)->clear_destination(static_cast<olive::Texture *>(texture),
|
||||
r, g, b, a);
|
||||
}
|
||||
|
||||
// Creates an OpenGL texture and writes its Variant handle to out_variant.
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
||||
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
||||
int channel_count, const void *data, int linesize, void *out_variant)
|
||||
{
|
||||
*static_cast<olive::Variant *>(out_variant) =
|
||||
renderer(handle)->create_native_texture(
|
||||
width, height, depth,
|
||||
static_cast<olive::PixelFormat::Format>(format), channel_count,
|
||||
data, linesize);
|
||||
}
|
||||
|
||||
// Destroys an OpenGL texture represented by a Variant handle.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_destroy_native_texture(OakRenderBackendHandle handle,
|
||||
const void *variant)
|
||||
{
|
||||
renderer(handle)->destroy_native_texture(variant_ref(variant));
|
||||
}
|
||||
|
||||
// Compiles an OpenGL shader program and returns its Variant handle.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_create_native_shader(OakRenderBackendHandle handle,
|
||||
const void *shader_code, void *out_variant)
|
||||
{
|
||||
*static_cast<olive::Variant *>(out_variant) =
|
||||
renderer(handle)->create_native_shader(
|
||||
*static_cast<const olive::ShaderCode *>(shader_code));
|
||||
}
|
||||
|
||||
// Destroys an OpenGL shader program represented by a Variant handle.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_destroy_native_shader(OakRenderBackendHandle handle,
|
||||
const void *variant)
|
||||
{
|
||||
renderer(handle)->destroy_native_shader(variant_ref(variant));
|
||||
}
|
||||
|
||||
// Uploads CPU pixel data into an OpenGL texture.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_upload_to_texture(OakRenderBackendHandle handle,
|
||||
const void *variant, const void *video_params,
|
||||
const void *data, int linesize)
|
||||
{
|
||||
renderer(handle)->upload_to_texture(
|
||||
variant_ref(variant),
|
||||
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
|
||||
}
|
||||
|
||||
// Reads an OpenGL texture back to CPU memory.
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
||||
OakRenderBackendHandle handle, const void *variant,
|
||||
const void *video_params, void *data, int linesize)
|
||||
{
|
||||
renderer(handle)->download_from_texture(
|
||||
variant_ref(variant),
|
||||
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
|
||||
}
|
||||
|
||||
// Flushes/waits for pending OpenGL work as required by the renderer.
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
||||
{
|
||||
renderer(handle)->flush();
|
||||
}
|
||||
|
||||
// Reads one pixel from an OpenGL texture.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_get_pixel_from_texture(OakRenderBackendHandle handle,
|
||||
void *texture, const void *point,
|
||||
void *out_color)
|
||||
{
|
||||
*static_cast<olive::Color *>(out_color) =
|
||||
renderer(handle)->get_pixel_from_texture(
|
||||
static_cast<olive::Texture *>(texture),
|
||||
*static_cast<const olive::PointF *>(point));
|
||||
}
|
||||
|
||||
// Executes a shader blit through the wrapped C++ OpenGL renderer.
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(OakRenderBackendHandle handle,
|
||||
const void *shader, void *job,
|
||||
void *destination,
|
||||
const void *destination_params,
|
||||
bool clear_destination)
|
||||
{
|
||||
renderer(handle)->blit(
|
||||
variant_ref(shader), *static_cast<olive::AcceleratedJob *>(job),
|
||||
static_cast<olive::Texture *>(destination),
|
||||
*static_cast<const olive::VideoParams *>(destination_params),
|
||||
clear_destination);
|
||||
}
|
||||
|
||||
// Exposes the wrapped OpenGL context for GL-specific integrations.
|
||||
OAK_RENDER_BACKEND_EXPORT void *
|
||||
oak_renderer_opengl_context(OakRenderBackendHandle handle)
|
||||
{
|
||||
return renderer(handle)->context();
|
||||
}
|
||||
|
||||
// Binds an output texture for OFX OpenGL rendering.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_attach_output_texture(OakRenderBackendHandle handle,
|
||||
const void *texture_id)
|
||||
{
|
||||
renderer(handle)->attach_texture_as_destination(variant_ref(texture_id));
|
||||
}
|
||||
|
||||
// Detaches any OFX OpenGL output texture binding.
|
||||
OAK_RENDER_BACKEND_EXPORT void
|
||||
oak_renderer_detach_output_texture(OakRenderBackendHandle handle)
|
||||
{
|
||||
renderer(handle)->detach_texture_as_destination();
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "openglcontext.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#elif defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <EGL/egl.h>
|
||||
#endif
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
static CGLPixelFormatObj create_cgl_pixel_format()
|
||||
{
|
||||
CGLPixelFormatAttribute attrs[] = {
|
||||
kCGLPFAOpenGLProfile,
|
||||
static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_3_2_Core),
|
||||
kCGLPFAAccelerated,
|
||||
static_cast<CGLPixelFormatAttribute>(0),
|
||||
};
|
||||
CGLPixelFormatObj pix = nullptr;
|
||||
GLint npix = 0;
|
||||
if (CGLChoosePixelFormat(attrs, &pix, &npix) != kCGLNoError || !pix) {
|
||||
return nullptr;
|
||||
}
|
||||
return pix;
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::create_offscreen(const OpenGLContext *share)
|
||||
{
|
||||
CGLPixelFormatObj pix = create_cgl_pixel_format();
|
||||
if (!pix) {
|
||||
fprintf(stderr, "OpenGLContext: failed to choose CGL pixel format\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CGLContextObj share_ctx =
|
||||
share ? static_cast<CGLContextObj>(share->native_context_) : nullptr;
|
||||
CGLContextObj ctx = nullptr;
|
||||
if (CGLCreateContext(pix, share_ctx, &ctx) != kCGLNoError || !ctx) {
|
||||
CGLReleasePixelFormat(pix);
|
||||
fprintf(stderr, "OpenGLContext: failed to create CGL context\n");
|
||||
return nullptr;
|
||||
}
|
||||
CGLReleasePixelFormat(pix);
|
||||
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = true;
|
||||
self->valid_ = true;
|
||||
self->major_version_ = 3;
|
||||
self->native_context_ = ctx;
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::adopt_external(void *native_context,
|
||||
void *native_surface)
|
||||
{
|
||||
if (!native_context) {
|
||||
return nullptr;
|
||||
}
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = false;
|
||||
self->valid_ = true;
|
||||
self->native_context_ = native_context;
|
||||
self->native_surface_ = native_surface;
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
void OpenGLContext::destroy_native()
|
||||
{
|
||||
if (owned_ && native_context_) {
|
||||
CGLSetCurrentContext(nullptr);
|
||||
CGLReleaseContext(static_cast<CGLContextObj>(native_context_));
|
||||
}
|
||||
native_context_ = nullptr;
|
||||
}
|
||||
|
||||
bool OpenGLContext::make_current()
|
||||
{
|
||||
CGLContextObj ctx = static_cast<CGLContextObj>(native_context_);
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
if (!owned_) {
|
||||
// External contexts are made current by their owner (app layer).
|
||||
return CGLGetCurrentContext() == ctx;
|
||||
}
|
||||
return CGLSetCurrentContext(ctx) == kCGLNoError;
|
||||
}
|
||||
|
||||
bool OpenGLContext::is_current() const
|
||||
{
|
||||
return native_context_ &&
|
||||
CGLGetCurrentContext() == static_cast<CGLContextObj>(native_context_);
|
||||
}
|
||||
|
||||
bool OpenGLContext::resolve_functions(OpenGLFunctions *out) const
|
||||
{
|
||||
return resolve_open_gl_functions(out, nullptr);
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
// WGL requires a current HDC to create an enhanced context; a hidden window
|
||||
// provides one for offscreen rendering.
|
||||
typedef HGLRC(WINAPI *PFN_wglCreateContextAttribsARB)(HDC, HGLRC, const int *);
|
||||
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
|
||||
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
||||
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
|
||||
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
|
||||
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
||||
#endif
|
||||
|
||||
static void *wgl_get_proc(const char *name)
|
||||
{
|
||||
void *p = reinterpret_cast<void *>(wglGetProcAddress(name));
|
||||
if (!p) {
|
||||
static HMODULE gl_module = LoadLibraryA("opengl32.dll");
|
||||
p = reinterpret_cast<void *>(GetProcAddress(gl_module, name));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK oak_wgl_wnd_proc(HWND hwnd, UINT msg, WPARAM wp,
|
||||
LPARAM lp)
|
||||
{
|
||||
return DefWindowProc(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::create_offscreen(const OpenGLContext *share)
|
||||
{
|
||||
static ATOM wnd_class = 0;
|
||||
if (!wnd_class) {
|
||||
WNDCLASSA wc = {};
|
||||
wc.lpfnWndProc = oak_wgl_wnd_proc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = "OakOpenGLOffscreen";
|
||||
wnd_class = RegisterClassA(&wc);
|
||||
}
|
||||
HWND hwnd = CreateWindowExA(0, "OakOpenGLOffscreen", "", 0, 0, 0, 1, 1,
|
||||
nullptr, nullptr, GetModuleHandle(nullptr),
|
||||
nullptr);
|
||||
if (!hwnd) {
|
||||
return nullptr;
|
||||
}
|
||||
HDC hdc = GetDC(hwnd);
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd = {};
|
||||
pfd.nSize = sizeof(pfd);
|
||||
pfd.nVersion = 1;
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||
pfd.iPixelType = PFD_TYPE_RGBA;
|
||||
pfd.cColorBits = 32;
|
||||
pfd.iLayerType = PFD_MAIN_PLANE;
|
||||
int pf = ChoosePixelFormat(hdc, &pfd);
|
||||
if (!pf || !SetPixelFormat(hdc, pf, &pfd)) {
|
||||
DestroyWindow(hwnd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HGLRC bootstrap = wglCreateContext(hdc);
|
||||
if (!bootstrap) {
|
||||
DestroyWindow(hwnd);
|
||||
return nullptr;
|
||||
}
|
||||
wglMakeCurrent(hdc, bootstrap);
|
||||
|
||||
HGLRC ctx = bootstrap;
|
||||
auto create_attribs = reinterpret_cast<PFN_wglCreateContextAttribsARB>(
|
||||
wgl_get_proc("wglCreateContextAttribsARB"));
|
||||
if (create_attribs) {
|
||||
const int attrs[] = {
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
|
||||
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
||||
0,
|
||||
};
|
||||
HGLRC share_ctx =
|
||||
share ? static_cast<HGLRC>(share->native_context_) : nullptr;
|
||||
HGLRC modern = create_attribs(hdc, share_ctx, attrs);
|
||||
if (modern) {
|
||||
wglMakeCurrent(nullptr, nullptr);
|
||||
wglDeleteContext(bootstrap);
|
||||
ctx = modern;
|
||||
}
|
||||
} else if (share && share->native_context_) {
|
||||
wglShareLists(static_cast<HGLRC>(share->native_context_), ctx);
|
||||
}
|
||||
wglMakeCurrent(nullptr, nullptr);
|
||||
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = true;
|
||||
self->valid_ = true;
|
||||
self->major_version_ = 3;
|
||||
self->native_context_ = ctx;
|
||||
self->native_surface_ = hdc;
|
||||
self->native_window_ = hwnd;
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::adopt_external(void *native_context,
|
||||
void *native_surface)
|
||||
{
|
||||
if (!native_context) {
|
||||
return nullptr;
|
||||
}
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = false;
|
||||
self->valid_ = true;
|
||||
self->native_context_ = native_context;
|
||||
self->native_surface_ = native_surface;
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
void OpenGLContext::destroy_native()
|
||||
{
|
||||
if (owned_ && native_context_) {
|
||||
wglMakeCurrent(nullptr, nullptr);
|
||||
wglDeleteContext(static_cast<HGLRC>(native_context_));
|
||||
if (native_window_) {
|
||||
DestroyWindow(static_cast<HWND>(native_window_));
|
||||
}
|
||||
}
|
||||
native_context_ = nullptr;
|
||||
native_window_ = nullptr;
|
||||
}
|
||||
|
||||
bool OpenGLContext::make_current()
|
||||
{
|
||||
if (!native_context_) {
|
||||
return false;
|
||||
}
|
||||
if (!owned_) {
|
||||
return wglGetCurrentContext() ==
|
||||
static_cast<HGLRC>(native_context_);
|
||||
}
|
||||
return wglMakeCurrent(static_cast<HDC>(native_surface_),
|
||||
static_cast<HGLRC>(native_context_)) == TRUE;
|
||||
}
|
||||
|
||||
bool OpenGLContext::is_current() const
|
||||
{
|
||||
return native_context_ &&
|
||||
wglGetCurrentContext() == static_cast<HGLRC>(native_context_);
|
||||
}
|
||||
|
||||
bool OpenGLContext::resolve_functions(OpenGLFunctions *out) const
|
||||
{
|
||||
return resolve_open_gl_functions(out, wgl_get_proc);
|
||||
}
|
||||
|
||||
#else // Linux: EGL + pbuffer surface
|
||||
|
||||
static void *egl_get_proc(const char *name)
|
||||
{
|
||||
return reinterpret_cast<void *>(eglGetProcAddress(name));
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::create_offscreen(const OpenGLContext *share)
|
||||
{
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY || !eglInitialize(display, nullptr, nullptr)) {
|
||||
fprintf(stderr, "OpenGLContext: failed to initialize EGL\n");
|
||||
return nullptr;
|
||||
}
|
||||
eglBindAPI(EGL_OPENGL_API);
|
||||
|
||||
const EGLint config_attrs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLConfig config = nullptr;
|
||||
EGLint num_configs = 0;
|
||||
if (!eglChooseConfig(display, config_attrs, &config, 1, &num_configs) ||
|
||||
num_configs < 1) {
|
||||
eglTerminate(display);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const EGLint pbuffer_attrs[] = {
|
||||
EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE,
|
||||
};
|
||||
EGLSurface surface =
|
||||
eglCreatePbufferSurface(display, config, pbuffer_attrs);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
eglTerminate(display);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const EGLint ctx_attrs[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3,
|
||||
EGL_CONTEXT_MINOR_VERSION, 2,
|
||||
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLContext share_ctx = share ?
|
||||
static_cast<EGLContext>(share->native_context_) :
|
||||
EGL_NO_CONTEXT;
|
||||
EGLContext ctx =
|
||||
eglCreateContext(display, config, share_ctx, ctx_attrs);
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
// Fall back to a default-version context on drivers without 3.2 core.
|
||||
ctx = eglCreateContext(display, config, share_ctx, nullptr);
|
||||
}
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
eglDestroySurface(display, surface);
|
||||
eglTerminate(display);
|
||||
fprintf(stderr, "OpenGLContext: failed to create EGL context\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = true;
|
||||
self->valid_ = true;
|
||||
self->major_version_ = 3;
|
||||
self->native_context_ = ctx;
|
||||
self->native_surface_ = surface;
|
||||
self->native_display_ = display;
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
OpenGLContext *OpenGLContext::adopt_external(void *native_context,
|
||||
void *native_surface)
|
||||
{
|
||||
if (!native_context) {
|
||||
return nullptr;
|
||||
}
|
||||
OpenGLContext *self = new OpenGLContext();
|
||||
self->owned_ = false;
|
||||
self->valid_ = true;
|
||||
self->native_context_ = native_context;
|
||||
self->native_surface_ = native_surface;
|
||||
self->native_display_ = eglGetCurrentDisplay();
|
||||
self->owner_thread_ = std::this_thread::get_id();
|
||||
return self;
|
||||
}
|
||||
|
||||
void OpenGLContext::destroy_native()
|
||||
{
|
||||
if (owned_ && native_context_) {
|
||||
EGLDisplay display = static_cast<EGLDisplay>(native_display_);
|
||||
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
EGL_NO_CONTEXT);
|
||||
eglDestroyContext(display, static_cast<EGLContext>(native_context_));
|
||||
if (native_surface_) {
|
||||
eglDestroySurface(display,
|
||||
static_cast<EGLSurface>(native_surface_));
|
||||
}
|
||||
}
|
||||
native_context_ = nullptr;
|
||||
native_surface_ = nullptr;
|
||||
}
|
||||
|
||||
bool OpenGLContext::make_current()
|
||||
{
|
||||
if (!native_context_) {
|
||||
return false;
|
||||
}
|
||||
if (!owned_) {
|
||||
return eglGetCurrentContext() ==
|
||||
static_cast<EGLContext>(native_context_);
|
||||
}
|
||||
return eglMakeCurrent(static_cast<EGLDisplay>(native_display_),
|
||||
static_cast<EGLSurface>(native_surface_),
|
||||
static_cast<EGLSurface>(native_surface_),
|
||||
static_cast<EGLContext>(native_context_)) == EGL_TRUE;
|
||||
}
|
||||
|
||||
bool OpenGLContext::is_current() const
|
||||
{
|
||||
return native_context_ &&
|
||||
eglGetCurrentContext() == static_cast<EGLContext>(native_context_);
|
||||
}
|
||||
|
||||
bool OpenGLContext::resolve_functions(OpenGLFunctions *out) const
|
||||
{
|
||||
return resolve_open_gl_functions(out, egl_get_proc);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
OpenGLContext::~OpenGLContext()
|
||||
{
|
||||
destroy_native();
|
||||
}
|
||||
|
||||
bool OpenGLContext::is_valid() const
|
||||
{
|
||||
return valid_;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user