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:
2026-08-06 03:21:26 +08:00
parent d77348ad9f
commit edbd3913af
170 changed files with 818511 additions and 358 deletions
+96
View File
@@ -126,3 +126,99 @@ nm -D oaknode 构建产物 | grep -c " U _ZN5olive.*render" # 必须 0
```
node→render 的 47 次引用此时应全部经 oakrender C ABIM3 遗留的
"暂不断链"在此结清)。
## 实施现状(2026-08-05
M7 已落地:oakrender 整库编译通过、纯 C ABI 与测试就位、独立构建
全绿(37 个用例:33 通过 + 4 个 GTEST_SKIP0 失败),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.hPlaybackCache/
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 个 skipGL 相关的
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` 的存储断言
留待 M5oakcodec):`codec/frame.h` 仍是 transition stubFrame
无法 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
链接 liboakrendernode→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 个由
oakcommonVideoParams/XmlStream*/SubtitleParams/QtUtils)、
oakundoUndoCommand/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 includec_api 源用相对路径包含公共头,测试目标
`BEFORE PRIVATE``include/` 提到最前。
+33
View File
@@ -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()`)。
+146
View File
@@ -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
+138
View File
@@ -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
+39
View File
@@ -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
+157
View File
@@ -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
+286
View File
@@ -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
View File
@@ -1,2 +1,3 @@
add_subdirectory(common)
add_subdirectory(undo)add_subdirectory(node)
add_subdirectory(render)
+58
View File
@@ -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
)
+174
View File
@@ -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"
+299
View File
@@ -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_;
};
}
+140
View File
@@ -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
+34
View File
@@ -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"
+3 -7
View File
@@ -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"
+3 -23
View File
@@ -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"
+3 -8
View File
@@ -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"
+3 -31
View File
@@ -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"
+3 -9
View File
@@ -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"
+3 -26
View File
@@ -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"
+3 -10
View File
@@ -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"
+3 -15
View File
@@ -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"
+3 -30
View File
@@ -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"
+3 -24
View File
@@ -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"
+3 -14
View File
@@ -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"
+3 -44
View File
@@ -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"
+3 -11
View File
@@ -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"
+3 -11
View File
@@ -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"
+3 -8
View File
@@ -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"
+5 -24
View File
@@ -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 &params() const { static VideoParams p; return p; }
template <typename T> TexturePtr to_job(const T &) { return nullptr; }
};
}
#include "../../../render/src/texture.h"
+6
View File
@@ -0,0 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+6
View File
@@ -0,0 +1,6 @@
target_sources(oakrender PRIVATE
renderer.cpp
cache.cpp
color.cpp
manager.cpp
)
+40
View File
@@ -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
+208
View File
@@ -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 (...) {
}
}
+221
View File
@@ -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;
}
}
+55
View File
@@ -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
+221
View File
@@ -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;
}
}
+506
View File
@@ -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);
}
+50
View File
@@ -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}
)
+32
View File
@@ -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
+162
View File
@@ -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 &params)
{
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();
}
}
+87
View File
@@ -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 &params);
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
+85
View File
@@ -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);
}
}
+83
View File
@@ -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
+407
View File
@@ -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 &params,
const void *data, int linesize)
{
upload_to_texture_(handle_, &handle, &params, data, linesize);
}
// Downloads backend texture data into a caller-provided CPU buffer.
void DynamicRenderer::download_from_texture(const Variant &handle,
const VideoParams &params, void *data,
int linesize)
{
download_from_texture_(handle_, &handle, &params, 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_);
}
}
}
+133
View File
@@ -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 &params, const void *data,
int linesize) override;
// Downloads backend texture pixels to CPU memory.
virtual void download_from_texture(const Variant &handle,
const VideoParams &params, 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
+122
View File
@@ -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
+121
View File
@@ -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
+66
View File
@@ -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
+247
View File
@@ -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 &params)
{
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());
}
}
}
+176
View File
@@ -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());
}
}
+81
View File
@@ -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
+37
View File
@@ -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
+561
View File
@@ -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);
}
}
}
+231
View File
@@ -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
+526
View File
@@ -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;
}
}
}
+118
View File
@@ -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
+152
View File
@@ -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();
}
}
+99
View File
@@ -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
+59
View File
@@ -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 &params)
{
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;
}
}
+229
View File
@@ -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
+380
View File
@@ -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
+171
View File
@@ -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
+27
View File
@@ -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
{
}
+79
View File
@@ -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
+67
View File
@@ -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
+186
View File
@@ -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
+199
View File
@@ -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
+43
View File
@@ -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
+27
View File
@@ -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
+85
View File
@@ -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
+75
View File
@@ -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
+126
View File
@@ -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
+151
View File
@@ -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;
}
}
+75
View File
@@ -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
+25
View File
@@ -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).
+31
View File
@@ -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
+401
View File
@@ -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}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/ocioconf">
@QRC_BODY@
</qresource>
</RCC>
+239
View File
@@ -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();
}
+427
View File
@@ -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