refactor(node,render): route oaknode->oakrender calls through the C ABI

- oakrender cache C API: add create_for_node (four cache kinds),
  get_uuid, request, load/save_state, set_saving_enabled,
  set_passthrough, get_passthroughs, get_valid_cache_filename,
  get_timebase, lock/unlock, invalidate_range (rational variant),
  get_native (C++ only, for the in-module PreviewAutoCacher)
- Node's four caches become owned OakRenderCache value handles; all
  cache access in node.cpp/clip/viewer/traverser/serializers goes
  through the C ABI
- oaknode_node_get_video_frame_cache returns an addref'd
  OakRenderCache; drop OakNodeFrameCache; oaktask precache/export and
  oakrender_video_ticket_params take the value handle
- color: OCIOBaseNode/OCIOLutNode hold OakColorProcessor handles
  (dtors added); oakrender gains color_processor_create_transform/
  create_lut/create_grading_primary/get_native and LUT library
  queries, keeping all OCIO transform construction inside oakrender;
  oaknode gains colormanager_wrap_borrowed/get_native
- RenderManager::instance() check -> oakrender_manager_available();
  cancel_video_tasks and disk cache path go through manager C API
- remaining node->render C++ symbol: only olive::Texture::~Texture
  via Variant's shared_ptr payload (recorded exception, same class as
  the UndoCommand inheritance)

Tests: new cache/color/manager/colormanager cases; all standalone
trees green (common 196, codec 22, audio 40, task 112, render 63,
timeline 125, node 98, plugin 102).
This commit is contained in:
2026-08-08 03:56:17 +08:00
parent 81431d180d
commit 5a564f30ca
40 changed files with 1553 additions and 237 deletions
+43
View File
@@ -352,3 +352,46 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需
oakcommon_xml_writer_wrap_nativeC++ only 借用包装),
XmlReaderState/XmlWriterState 支持 owning/borrowed 双模式。
- liboaknode 对 liboaktimeline 的 C++ 符号引用降为 0(nm 验证)。
## oaknode→oakrender 切到 C ABI2026-08-07
- oakrender cache C API 大扩(include/render/cache.h):
cache_create_for_node(parent, kind)(四种缓存:视频帧/缩略图/音频
播放/波形,parent 的原生回指针留在 oakrender 内部)、get_uuid
(两段式)、request、load/save_state、set_saving_enabled、
set_passthrough、get_passthroughs、get_valid_cache_filename、
get_timebase、lock/unlock(替代直交 std::mutex)、
invalidate_range(有理数版,原有 invalidate 是时间戳制)、
get_nativeC++ only,供 oakrender 内部的 PreviewAutoCacher 用)。
- Node 的四个缓存成员(video_cache_/thumbnail_cache_/audio_cache_/
waveform_cache_)从原生指针改为 OakRenderCache 拥有型值句柄,
访问器返回借用副本;构造/析构/拷贝 UUID/加载保存全部走 C ABI。
- ClipBlock 的 connected_video_cache()/thumbnails()/waveform() 等
返回借用句柄;request/invalidate/passthrough 流程走 C ABI
passthrough 列表查询只传 range(原 Passthrough::cache 文本不出界)。
- oaknode_node_get_video_frame_cache 改为返回 addref 后的
OakRenderCacheout 参数,node.h 前置声明 struct OakRenderCache);
OakNodeFrameCache 不透明指针类型删除;oaktask 的 precachetask
直接收句柄,不再 wrap_borrowedRenderTask::render/
start_video_ticket 的 cache 参数与 oakrender_video_ticket_params.cache
改为 OakRenderCache 值(借用语义,空 ctx=无)。
- 色彩:OCIOBaseNode 的 processor_ 改 OakColorProcessor 拥有型句柄
(新增析构释放;OCIOLutNode 同理并补了 ~OCIOLutNode);
job.set_color_processor 经 C++-only
oakrender_color_processor_get_native 取 shared_ptr。
oakrender 新增 color_processor_create_transform/create_lut/
create_grading_primary/get_native 与 lut_is_supported_extension/
supported_extensions_count/atoaknode 新增
colormanager_wrap_borrowed/get_nativeC++ only)。
OCIO 的 FileTransform/GradingPrimaryTransform 构造全部内收到
oakrendergrading log/linear、LUT 加载)。
- RenderManager::instance() 空检查 → oakrender_manager_available()
PreviewAutoCacher::cancel_video_tasks → oakrender_cancel_video_tasks
DiskManager 默认缓存路径 → 已有 oakrender_disk_cache_path。
- PreviewAutoCacheroakrender 内部)改用 oakrender_cache_get_native
解包节点缓存,回调接线保持模块内 C++。
- **例外(记录为 01 §5 例外)**NodeValue 的纹理载荷
TexturePtr=shared_ptr<olive::Texture> 经 Variant 类型擦除流经
oaknode 的 35 个 TU,析构引用 olive::Texture::~Texture——值系统
载荷问题,与 UndoCommand 跨模块继承同类,留待值系统重做。
除此之外 liboaknode→liboakrender 的 C++ 符号引用为 0。
+25
View File
@@ -72,6 +72,16 @@ OakNodeColorManager oaknode_colormanager_init(OakNodeProject project);
*/
void oaknode_colormanager_free(OakNodeColorManager *manager);
/**
* @brief Borrowed handle wrapping a native manager pointer held by a
* node (olive::OCIOBaseNode::manager()).
*
* The manager stays owned by its project: release() on this handle
* only frees the box. Empty handle (ctx == NULL) for a NULL native
* pointer.
*/
OakNodeColorManager oaknode_colormanager_wrap_borrowed(void *native_manager);
/**
* @brief Load the built-in default OCIO config and set the default input
* colorspace (olive::ColorManager::init()).
@@ -189,6 +199,21 @@ int oaknode_colormanager_get_compliant_color_transform(
OakNodeColorManager manager, OakColorTransform transform,
int force_display, OakColorTransform *out);
#ifdef __cplusplus
} /* extern "C" */
namespace olive { class ColorManager; }
extern "C" {
#endif
/**
* @brief Borrowed access to the underlying C++ manager (C++ only, for
* adapter layers). Valid while the handle is held. NULL-safe.
*/
olive::ColorManager *oaknode_colormanager_get_native(
OakNodeColorManager manager);
#ifdef __cplusplus
}
#endif
+7 -4
View File
@@ -134,7 +134,7 @@ struct OakTimelineWorkArea;
* (olive::FrameHashCache in oakrender). oakrender reinterprets this into
* its own handle types.
*/
typedef struct OakNodeFrameCache OakNodeFrameCache;
struct OakRenderCache;
/* oakcore handles used by the viewer setters. */
typedef struct OakAudioParams OakAudioParams;
@@ -575,11 +575,14 @@ int oaknode_node_get_work_area(OakNodeNode node,
struct OakTimelineWorkArea *out);
/**
* @brief Borrowed video frame cache of a node (NULL when the node has
* none or for an empty handle).
* @brief Video frame cache of a node as an addref'd oakrender value
* handle (release with oakrender_cache_free()). *out is an
* empty handle (ctx == NULL) when the node has none or for an
* empty node handle. struct OakRenderCache is forward-declared
* here; include render/cache.h for the definition.
*/
int oaknode_node_get_video_frame_cache(OakNodeNode node,
OakNodeFrameCache **out);
struct OakRenderCache *out);
/**
* @brief Copy input values/connections from one node to another
+131
View File
@@ -28,6 +28,7 @@
// public headers reference each other relative to their own directory.
#include "error.h"
#include "renderer.h" /* OakCodecFrame */
#include "node/node.h" /* OakNodeNode */
#ifdef __cplusplus
extern "C" {
@@ -88,6 +89,128 @@ void oakrender_cache_free(OakRenderCache *cache);
*/
OakRenderCache oakrender_cache_wrap_borrowed(void *native_cache);
/**
* @brief Cache flavours owned by a node
* (olive::Node's video/thumbnail/audio/waveform caches).
*/
enum OakRenderCacheKind {
OAKRENDER_CACHE_VIDEO_FRAME = 0, /**< olive::FrameHashCache */
OAKRENDER_CACHE_THUMBNAIL = 1, /**< olive::ThumbnailCache */
OAKRENDER_CACHE_AUDIO_PLAYBACK = 2, /**< olive::AudioPlaybackCache */
OAKRENDER_CACHE_AUDIO_WAVEFORM = 3 /**< olive::AudioWaveformCache */
};
/**
* @brief Create a cache of the given kind with a parent node (the
* native back-pointer stays inside oakrender; it is used for
* project cache-path resolution and job bookkeeping only).
*
* Owned by the caller (reference count 1); release with
* oakrender_cache_free(). Empty handle for an empty parent handle, an
* unknown kind, or on allocation failure.
*/
OakRenderCache oakrender_cache_create_for_node(OakNodeNode parent,
int kind);
/**
* @brief Cache UUID as canonical text, two-stage
* (PlaybackCache::get_uuid()).
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKRENDER_E_* code for an empty cache.
*/
int oakrender_cache_get_uuid(OakRenderCache cache, char *buf,
int buf_size);
/**
* @brief Request caching of a time range on behalf of a viewer
* (PlaybackCache::request()).
*
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty cache /
* context handle or a context that is not a viewer.
*/
int oakrender_cache_request(OakRenderCache cache, OakNodeNode context,
int64_t in_num, int64_t in_den,
int64_t out_num, int64_t out_den);
/**
* @brief Load/save the cache's on-disk state (PlaybackCache::load_state()
* / save_state()). OAKRENDER_E_INVALID for an empty cache.
*/
int oakrender_cache_load_state(OakRenderCache cache);
int oakrender_cache_save_state(OakRenderCache cache);
/**
* @brief Enable/disable persisting this cache
* (PlaybackCache::set_saving_enabled()).
*/
int oakrender_cache_set_saving_enabled(OakRenderCache cache, int enabled);
/**
* @brief Pass this cache's ranges through to another cache
* (PlaybackCache::set_passthrough()). OAKRENDER_E_INVALID for an
* empty cache or an empty `other`.
*/
int oakrender_cache_set_passthrough(OakRenderCache cache,
OakRenderCache other);
/**
* @brief The on-disk filename for the frame at a time
* (FrameHashCache::get_valid_cache_filename()), two-stage.
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKRENDER_E_* code (OAKRENDER_E_INVALID when the cache is not
* a frame hash cache).
*/
int oakrender_cache_get_valid_cache_filename(OakRenderCache cache,
int64_t time_num,
int64_t time_den, char *buf,
int buf_size);
/**
* @brief The passthrough ranges as flat {in_n, in_d, out_n, out_d}
* quadruples (PlaybackCache::get_passthroughs(); only the ranges
* cross the boundary, the per-range cache UUID text stays
* internal).
*
* Two-stage: call with ranges == NULL (or max_ranges == 0) to get the
* count; then call with a buffer of max_ranges * 4 int64_t values.
*
* @return Range count (>= 0), or a negative OAKRENDER_E_* code.
*/
int oakrender_cache_get_passthroughs(OakRenderCache cache, int64_t *ranges,
int max_ranges);
/**
* @brief The cache's frame timebase (FrameHashCache::get_timebase()).
* Out params may individually be NULL. OAKRENDER_E_INVALID for
* an empty cache or a non-frame-hash cache.
*/
int oakrender_cache_get_timebase(OakRenderCache cache, int *num,
int *den);
/**
* @brief Lock/unlock the cache's internal mutex (PlaybackCache::mutex()).
* Empty cache is a no-op. Always pair the calls.
*/
void oakrender_cache_lock(OakRenderCache cache);
void oakrender_cache_unlock(OakRenderCache cache);
#ifdef __cplusplus
} /* extern "C" */
namespace olive { class PlaybackCache; }
extern "C" {
#endif
/**
* @brief Borrowed access to the underlying C++ cache (C++ only, for
* oakrender-internal adapters such as PreviewAutoCacher). Valid
* while the handle is held. NULL-safe.
*/
olive::PlaybackCache *oakrender_cache_get_native(OakRenderCache cache);
/**
* @brief Set the frame timebase used to interpret all timestamps of this
* cache (FrameHashCache::set_timebase()).
@@ -112,6 +235,14 @@ int oakrender_cache_set_uuid(OakRenderCache cache, const char *uuid);
void oakrender_cache_invalidate(OakRenderCache cache, int64_t in_ts,
int64_t out_ts);
/**
* @brief Mark a rational time range invalidated
* (PlaybackCache::invalidate(TimeRange)). Empty cache is a no-op.
*/
void oakrender_cache_invalidate_range(OakRenderCache cache,
int64_t in_num, int64_t in_den,
int64_t out_num, int64_t out_den);
/**
* @brief Mark the timestamp range [in_ts, out_ts) validated
* (PlaybackCache::validate()). Empty cache is a no-op.
+88
View File
@@ -23,6 +23,8 @@
#include "error.h"
#include "renderer.h"
#include "common/colortransform.h" /* OakColorTransform */
#include "node/colormanager.h" /* OakNodeColorManager */
#ifdef __cplusplus
extern "C" {
@@ -94,6 +96,75 @@ void oakrender_color_processor_free(OakColorProcessor *processor);
*/
int oakrender_color_processor_is_valid(OakColorProcessor processor);
/**
* @brief Create a processor from an input colorspace and a destination
* transform on a node's color manager
* (ColorProcessor::create(ColorManager*, input, dest, dir)).
*
* @param manager Borrowed manager handle (e.g.
* oaknode_colormanager_wrap_borrowed()).
* @param direction OAKRENDER_COLOR_DIRECTION_NORMAL / _INVERSE.
* @return Processor handle with reference count 1; ctx is NULL for
* empty/invalid arguments or allocation failure.
*/
OakColorProcessor oakrender_color_processor_create_transform(
OakNodeColorManager manager, const char *input,
OakColorTransform dest, int direction);
/**
* @brief Create a processor from a LUT file on a node's color manager
* (OCIO FileTransform with linear interpolation; direction
* selects forward/inverse).
*
* @return Processor handle with reference count 1; ctx is NULL for
* empty/invalid arguments, an unreadable LUT, or allocation
* failure.
*/
OakColorProcessor oakrender_color_processor_create_lut(
OakNodeColorManager manager, const char *path, int direction);
/**
* @brief Grading-primary transform styles for
* oakrender_color_processor_create_grading_primary().
*/
enum OakRenderGradingPrimaryStyle {
OAKRENDER_GRADING_PRIMARY_LIN = 0, /**< OCIO GRADING_LIN */
OAKRENDER_GRADING_PRIMARY_LOG = 1 /**< OCIO GRADING_LOG */
};
/**
* @brief Create a dynamic grading-primary processor on a node's color
* manager (OCIO GradingPrimaryTransform, forward direction).
*
* @return Processor handle with reference count 1; ctx is NULL for
* invalid arguments or allocation failure.
*/
OakColorProcessor oakrender_color_processor_create_grading_primary(
OakNodeColorManager manager, int style);
/* ---- LUT library ---------------------------------------------------- */
/**
* @brief 1 when `extension` (without dot, case-insensitive) is a
* supported LUT extension (LUTLibrary::is_supported_extension()).
*/
int oakrender_lut_is_supported_extension(const char *extension);
/**
* @brief Number of supported LUT extensions
* (LUTLibrary::supported_extensions()).
*/
int oakrender_lut_supported_extensions_count(void);
/**
* @brief Supported LUT extension at `index`, two-stage string.
*
* @return Required buffer size in bytes (including NUL), or a negative
* OAKRENDER_E_* code for an out-of-range index.
*/
int oakrender_lut_supported_extension_at(int index, char *buf,
int buf_size);
/**
* @brief Convert a single RGBA color (ColorProcessor::convert_color()).
* On an invalid processor the input is copied through.
@@ -160,6 +231,23 @@ int oakrender_color_manager_display_transform(const char *display,
const char *view, char *buf,
int n);
#ifdef __cplusplus
} /* extern "C" */
#include <memory>
namespace olive { class ColorProcessor; }
extern "C" {
#endif
/**
* @brief Borrowed access to the underlying C++ processor (C++ only, for
* adapter layers; a shared_ptr copy keeps the object alive).
* Empty shared_ptr for an empty handle.
*/
std::shared_ptr<olive::ColorProcessor> oakrender_color_processor_get_native(
OakColorProcessor processor);
#ifdef __cplusplus
}
#endif
+14
View File
@@ -120,6 +120,20 @@ int oakrender_set_cacher_multicam(OakNodeNode multicam_or_NULL);
*/
int oakrender_set_display_color_processor(OakColorProcessor p_or_NULL);
/**
* @brief 1 when the process-wide RenderManager singleton exists
* (RenderManager::instance() != nullptr; only the main GUI
* process creates one), 0 otherwise.
*/
int oakrender_manager_available(void);
/**
* @brief Cancel in-flight video cache tasks on the manager's
* auto-cacher (PreviewAutoCacher::cancel_video_tasks()). No-op
* when no manager/auto-cacher exists (e.g. a worker process).
*/
void oakrender_cancel_video_tasks(int wait_for_done);
/* ---- Disk cache (olive::DiskManager) -------------------------------------- */
/**
+2 -1
View File
@@ -30,6 +30,7 @@
#include "olive/core/oakcore/audioparams.h"
#include "olive/core/oakcore/samplebuffer.h"
#include "render/error.h"
#include "render/cache.h"
#include "render/color.h"
#include "render/renderer.h"
@@ -89,7 +90,7 @@ typedef struct oakrender_video_ticket_params {
int force_channel_count; /**< 0 = off. */
OakColorProcessor force_color_output; /**< Borrowed; empty ctx = none. */
OakColorTransform force_color_transform; /**< By value; empty ctx = default. */
OakNodeFrameCache *cache; /**< Borrowed frame cache, may be NULL. */
OakRenderCache cache; /**< Borrowed frame cache; empty ctx = none. */
} oakrender_video_ticket_params;
/**
+13
View File
@@ -75,6 +75,19 @@ void oaknode_colormanager_free(OakNodeColorManager *manager)
oaknode_c_api::free_handle(manager);
}
OakNodeColorManager oaknode_colormanager_wrap_borrowed(
void *native_manager)
{
return oaknode_c_api::make_handle<OakNodeColorManager>(
native_manager, false, nullptr);
}
olive::ColorManager *oaknode_colormanager_get_native(
OakNodeColorManager manager)
{
return oaknode_c_api::to_native<olive::ColorManager>(manager);
}
int oaknode_colormanager_initialize(OakNodeColorManager manager)
{
olive::ColorManager *cm =
+11 -3
View File
@@ -24,6 +24,7 @@
#include "node/footage.h"
#include "node/project.h"
#include "olive/core/oakcore/audioparams.h"
#include "render/cache.h"
#include "timeline/marker.h"
#include "timeline/workarea.h"
@@ -1320,13 +1321,20 @@ int oaknode_node_get_work_area(OakNodeNode node,
}
int oaknode_node_get_video_frame_cache(OakNodeNode node,
OakNodeFrameCache **out)
struct OakRenderCache *out)
{
if (!node.ctx || !out) {
return OAKNODE_E_INVALID;
}
*out = reinterpret_cast<OakNodeFrameCache *>(
oaknode_c_api::to_native<olive::Node>(node)->video_frame_cache());
olive::Node *n = oaknode_c_api::to_native<olive::Node>(node);
if (!n) {
*out = OakRenderCache{};
return OAKNODE_OK;
}
*out = n->video_frame_cache();
if (out->ctx) {
out->addref(out->ctx);
}
return OAKNODE_OK;
}
+70 -37
View File
@@ -31,6 +31,7 @@
#include "project/sequence/sequence.h"
#include "sliderdisplaytype.h"
#include "timeline/displaymode.h"
#include "../../../c_api/nodehandle.h"
namespace olive
{
@@ -185,11 +186,17 @@ void ClipBlock::discard_cache()
if (Node *connected = get_connected_output(k_buffer_in)) {
Track::Type type = get_track_type();
if (type == Track::k_video) {
connected->video_frame_cache()->invalidate(
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
oakrender_cache_invalidate_range(connected->video_frame_cache(),
RATIONAL_MIN.numerator(),
RATIONAL_MIN.denominator(),
RATIONAL_MAX.numerator(),
RATIONAL_MAX.denominator());
} else if (type == Track::k_audio) {
connected->audio_playback_cache()->invalidate(
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
oakrender_cache_invalidate_range(connected->audio_playback_cache(),
RATIONAL_MIN.numerator(),
RATIONAL_MIN.denominator(),
RATIONAL_MAX.numerator(),
RATIONAL_MAX.denominator());
}
}
}
@@ -284,8 +291,9 @@ void ClipBlock::request_range_from_connected(const TimeRange &range)
{
TimeRange thumb_range = range.intersected(max_range);
if (get_adjusted_thumbnail_range(&thumb_range)) {
connected->thumbnail_cache()->request(
this->track()->sequence(), thumb_range);
request_range_for_cache(connected->thumbnail_cache(),
max_range, thumb_range, false,
true);
}
}
@@ -351,29 +359,69 @@ void ClipBlock::request_invalidated_from_connected(bool force_all,
}
}
void ClipBlock::request_range_for_cache(PlaybackCache *cache,
void ClipBlock::request_range_for_cache(const OakRenderCache &cache,
const TimeRange &max_range,
const TimeRange &range, bool invalidate,
bool request)
{
if (!cache.ctx) {
return;
}
TimeRange r = range.intersected(max_range);
if (invalidate) {
cache->invalidate(r);
oakrender_cache_invalidate_range(cache, r.in().numerator(),
r.in().denominator(),
r.out().numerator(),
r.out().denominator());
}
if (request) {
cache->request(this->track()->sequence(), r);
// Borrowed context handle: the sequence outlives the call
OakNodeNode context = oaknode_c_api::make_handle<OakNodeNode>(
this->track()->sequence(), false, nullptr);
oakrender_cache_request(cache, context, r.in().numerator(),
r.in().denominator(), r.out().numerator(),
r.out().denominator());
context.release(context.ctx);
}
}
void ClipBlock::request_invalidated_for_cache(PlaybackCache *cache,
void ClipBlock::request_invalidated_for_cache(const OakRenderCache &cache,
const TimeRange &max_range)
{
core::TimeRangeList invalid = cache->get_invalidated_ranges(max_range);
if (!cache.ctx) {
return;
}
for (const PlaybackCache::Passthrough &p : cache->get_passthroughs()) {
invalid.remove(p);
core::TimeRangeList invalid;
int n = oakrender_cache_get_invalidated_ranges(
cache, max_range.in().numerator(), max_range.in().denominator(),
max_range.out().numerator(), max_range.out().denominator(), NULL, 0);
if (n > 0) {
std::vector<int64_t> ranges(size_t(n) * 4);
oakrender_cache_get_invalidated_ranges(
cache, max_range.in().numerator(), max_range.in().denominator(),
max_range.out().numerator(), max_range.out().denominator(),
ranges.data(), n);
for (int i = 0; i < n; i++) {
invalid.insert(TimeRange(Rational(ranges[i * 4],
ranges[i * 4 + 1]),
Rational(ranges[i * 4 + 2],
ranges[i * 4 + 3])));
}
}
int np = oakrender_cache_get_passthroughs(cache, NULL, 0);
if (np > 0) {
std::vector<int64_t> passthroughs(size_t(np) * 4);
oakrender_cache_get_passthroughs(cache, passthroughs.data(), np);
for (int i = 0; i < np; i++) {
invalid.remove(TimeRange(
Rational(passthroughs[i * 4], passthroughs[i * 4 + 1]),
Rational(passthroughs[i * 4 + 2], passthroughs[i * 4 + 3])));
}
}
for (const TimeRange &r : invalid) {
@@ -392,7 +440,9 @@ bool ClipBlock::get_adjusted_thumbnail_range(TimeRange *r) const
Rational in = this->media_range().in();
if (r->contains(in)) {
// Cache only the in point
*r = TimeRange(in, in + thumbnail_cache()->get_timebase());
int tb_num = 0, tb_den = 1;
oakrender_cache_get_timebase(thumbnail_cache(), &tb_num, &tb_den);
*r = TimeRange(in, in + Rational(tb_num, tb_den));
return true;
} else {
// Cache nothing
@@ -541,29 +591,12 @@ void ClipBlock::retranslate()
void ClipBlock::add_cache_passthrough_from(ClipBlock *other)
{
if (auto tc = this->video_frame_cache()) {
if (auto oc = other->video_frame_cache()) {
tc->set_passthrough(oc);
}
}
if (auto tc = this->audio_playback_cache()) {
if (auto oc = other->audio_playback_cache()) {
tc->set_passthrough(oc);
}
}
if (auto tc = this->thumbnails()) {
if (auto oc = other->thumbnails()) {
tc->set_passthrough(oc);
}
}
if (auto tc = this->waveform()) {
if (auto oc = other->waveform()) {
tc->set_passthrough(oc);
}
}
oakrender_cache_set_passthrough(this->video_frame_cache(),
other->video_frame_cache());
oakrender_cache_set_passthrough(this->audio_playback_cache(),
other->audio_playback_cache());
oakrender_cache_set_passthrough(this->thumbnails(), other->thumbnails());
oakrender_cache_set_passthrough(this->waveform(), other->waveform());
}
void ClipBlock::ConnectedToPreviewEvent()
+17 -10
View File
@@ -25,6 +25,7 @@
#include "audio/audiovisualwaveform.h"
#include "codec/decoder.h"
#include "block/block.h"
#include "render/cache.h"
#include "input/multicam/multicamnode.h"
#include "output/track/track.h"
@@ -143,39 +144,44 @@ public:
return block_links_;
}
FrameHashCache *connected_video_cache() const
/**
* @brief Borrowed copies of the connected node's cache handles
* (empty handle when nothing is connected). Callers must NOT
* free them.
*/
OakRenderCache connected_video_cache() const
{
if (Node *n = get_connected_output(k_buffer_in)) {
return n->video_frame_cache();
} else {
return nullptr;
return {};
}
}
AudioPlaybackCache *connected_audio_cache() const
OakRenderCache connected_audio_cache() const
{
if (Node *n = get_connected_output(k_buffer_in)) {
return n->audio_playback_cache();
} else {
return nullptr;
return {};
}
}
ThumbnailCache *thumbnails()
OakRenderCache thumbnails()
{
if (Node *n = get_connected_output(k_buffer_in)) {
return n->thumbnail_cache();
} else {
return nullptr;
return {};
}
}
AudioWaveformCache *waveform()
OakRenderCache waveform()
{
if (Node *n = get_connected_output(k_buffer_in)) {
return n->waveform_cache();
} else {
return nullptr;
return {};
}
}
@@ -251,10 +257,11 @@ private:
void request_range_from_connected(const TimeRange &range);
void request_range_for_cache(PlaybackCache *cache, const TimeRange &max_range,
void request_range_for_cache(const OakRenderCache &cache,
const TimeRange &max_range,
const TimeRange &range, bool invalidate,
bool request);
void request_invalidated_for_cache(PlaybackCache *cache,
void request_invalidated_for_cache(const OakRenderCache &cache,
const TimeRange &max_range);
bool get_adjusted_thumbnail_range(TimeRange *r) const;
@@ -20,6 +20,9 @@
***/
#include "displaytransform.h"
#include "common/colortransform.h"
#include "node/colormanager.h"
#include "render/color.h"
#include "color/colormanager/colormanager.h"
@@ -146,10 +149,24 @@ void DisplayTransformNode::config_changed()
void DisplayTransformNode::generate_processor()
{
if (manager()) {
ColorTransform transform(get_display(), get_view(), std::string());
set_processor(ColorProcessor::create(
manager(), manager()->get_reference_color_space(), transform,
get_direction()));
OakNodeColorManager mgr =
oaknode_colormanager_wrap_borrowed(manager());
OakColorTransform transform = oakcommon_colortransform_init_display(
get_display().c_str(), get_view().c_str(), "");
char ref_space[256];
int needed = oaknode_colormanager_get_reference_color_space(
mgr, ref_space, sizeof(ref_space));
if (needed > 0 && needed <= int(sizeof(ref_space))) {
set_processor(oakrender_color_processor_create_transform(
mgr, ref_space, transform,
get_direction() == ColorProcessor::k_normal ?
OAKRENDER_COLOR_DIRECTION_NORMAL :
OAKRENDER_COLOR_DIRECTION_INVERSE));
}
oakcommon_colortransform_free(&transform);
mgr.release(mgr.ctx);
}
}
+8 -3
View File
@@ -31,7 +31,6 @@ const std::string OCIOBaseNode::k_texture_input = "tex_in";
OCIOBaseNode::OCIOBaseNode()
: manager_(nullptr)
, processor_(nullptr)
{
add_input(k_texture_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
@@ -41,6 +40,11 @@ OCIOBaseNode::OCIOBaseNode()
set_flag(k_video_effect);
}
OCIOBaseNode::~OCIOBaseNode()
{
oakrender_color_processor_free(&processor_);
}
void OCIOBaseNode::AddedToGraphEvent(Project *p)
{
manager_ = p->color_manager();
@@ -60,10 +64,11 @@ void OCIOBaseNode::value(const NodeValueRow &value, const NodeGlobals &globals,
auto tex_met = value.at(k_texture_input);
TexturePtr t = tex_met.to_texture();
if (t) {
if (processor_) {
if (processor_.ctx) {
ColorTransformJob job;
job.set_color_processor(processor_);
job.set_color_processor(
oakrender_color_processor_get_native(processor_));
job.set_input_texture(tex_met);
table->push(NodeValue::k_texture, t->to_job(job), this);
+15 -3
View File
@@ -23,6 +23,7 @@
#define OAK_OCIOBASENODE_H
#include "node.h"
#include "render/color.h"
#include "render/job/colortransformjob.h"
namespace olive
@@ -31,6 +32,7 @@ namespace olive
class OCIOBaseNode : public Node {
public:
OCIOBaseNode();
virtual ~OCIOBaseNode() override;
virtual void AddedToGraphEvent(Project *p) override;
virtual void RemovedFromGraphEvent(Project *p) override;
@@ -51,19 +53,29 @@ protected:
return manager_;
}
ColorProcessorPtr processor() const
/**
* @brief Borrowed copy of the processor handle owned by this node.
* Callers must NOT free it.
*/
const OakColorProcessor &processor() const
{
return processor_;
}
void set_processor(ColorProcessorPtr p)
/**
* @brief Take ownership of a processor handle (the old one is
* released).
*/
void set_processor(OakColorProcessor p)
{
oakrender_color_processor_free(&processor_);
processor_ = p;
}
private:
ColorManager *manager_;
ColorProcessorPtr processor_;
OakColorProcessor processor_ = {};
};
}
@@ -20,6 +20,8 @@
***/
#include "ociogradingtransformlinear.h"
#include "node/colormanager.h"
#include "render/color.h"
#include <cmath>
#include <iostream>
@@ -194,16 +196,13 @@ void OCIOGradingTransformLinearNode::update_clamp_white_minimum()
void OCIOGradingTransformLinearNode::generate_processor()
{
if (manager()) {
ocio::GradingPrimaryTransformRcPtr gp =
ocio::GradingPrimaryTransform::Create(ocio::GRADING_LIN);
gp->makeDynamic();
gp->setDirection(ocio::TransformDirection::TRANSFORM_DIR_FORWARD);
try {
set_processor(ColorProcessor::create(
manager()->get_config()->getProcessor(gp)));
} catch (const ocio::Exception &e) {
std::cerr << std::endl << e.what() << std::endl;
OakNodeColorManager mgr =
oaknode_colormanager_wrap_borrowed(manager());
OakColorProcessor processor =
oakrender_color_processor_create_grading_primary(mgr, OAKRENDER_GRADING_PRIMARY_LIN);
mgr.release(mgr.ctx);
if (processor.ctx) {
set_processor(processor);
}
}
}
@@ -213,10 +212,11 @@ void OCIOGradingTransformLinearNode::value(const NodeValueRow &value,
NodeValueTable *table) const
{
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
if (processor()) {
if (processor().ctx) {
ColorTransformJob job(value);
job.set_color_processor(processor());
job.set_color_processor(
oakrender_color_processor_get_native(processor()));
job.set_input_texture(value.at(k_texture_input));
// Vector4D components stand in for the former QVector4D indices:
@@ -20,6 +20,8 @@
***/
#include "ociogradingtransformlog.h"
#include "node/colormanager.h"
#include "render/color.h"
#include <cmath>
#include <iostream>
@@ -190,16 +192,13 @@ void OCIOGradingTransformLogNode::update_clamp_white_minimum()
void OCIOGradingTransformLogNode::generate_processor()
{
if (manager()) {
OCIO_NAMESPACE::GradingPrimaryTransformRcPtr gp =
OCIO_NAMESPACE::GradingPrimaryTransform::Create(OCIO_NAMESPACE::GRADING_LOG);
gp->makeDynamic();
gp->setDirection(OCIO_NAMESPACE::TransformDirection::TRANSFORM_DIR_FORWARD);
try {
set_processor(ColorProcessor::create(
manager()->get_config()->getProcessor(gp)));
} catch (const OCIO_NAMESPACE::Exception &e) {
std::cerr << std::endl << e.what() << std::endl;
OakNodeColorManager mgr =
oaknode_colormanager_wrap_borrowed(manager());
OakColorProcessor processor =
oakrender_color_processor_create_grading_primary(mgr, OAKRENDER_GRADING_PRIMARY_LOG);
mgr.release(mgr.ctx);
if (processor.ctx) {
set_processor(processor);
}
}
}
@@ -209,10 +208,11 @@ void OCIOGradingTransformLogNode::value(const NodeValueRow &value,
NodeValueTable *table) const
{
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
if (processor()) {
if (processor().ctx) {
ColorTransformJob job(value);
job.set_color_processor(processor());
job.set_color_processor(
oakrender_color_processor_get_native(processor()));
job.set_input_texture(value.at(k_texture_input));
// Vector4D components stand in for the former QVector4D indices:
+58 -45
View File
@@ -19,6 +19,9 @@
***/
#include "ociolut.h"
#include "node/colormanager.h"
#include "render/color.h"
#include "render/manager.h"
#include <algorithm>
#include <cctype>
@@ -47,9 +50,20 @@ bool is_main_process()
// Qt-free replacement for qobject_cast<QApplication*>(QCoreApplication::instance()):
// only the main GUI process creates a RenderManager (the render worker
// never does), so its presence identifies the main process.
return RenderManager::instance() != nullptr;
return oakrender_manager_available() != 0;
}
} // namespace
OCIOLutNode::~OCIOLutNode()
{
disconnect_all();
oakrender_color_processor_free(&last_processor_);
}
namespace
{
int read_direction_input(const Node *node)
{
Variant v = node->get_standard_value(OCIOLutNode::k_direction_input);
@@ -82,13 +96,21 @@ OCIOLutNode::OCIOLutNode()
{
add_input(k_file_input, NodeValue::k_file, std::string(),
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
const StringList &extensions = LUTLibrary::supported_extensions();
std::string all_luts = "*.";
for (size_t i = 0; i < extensions.size(); i++) {
if (i > 0) {
all_luts += " *.";
std::string all_luts;
int ext_count = oakrender_lut_supported_extensions_count();
for (int i = 0; i < ext_count; i++) {
char ext[32];
if (oakrender_lut_supported_extension_at(i, ext, sizeof(ext)) <= 0) {
continue;
}
all_luts += extensions[i];
if (!all_luts.empty()) {
all_luts += " ";
}
all_luts += "*.";
all_luts += ext;
}
if (all_luts.empty()) {
all_luts = "*.*";
}
set_input_property(k_file_input, "filter",
"LUT Files (" + all_luts + ");;All Files (*)");
@@ -180,11 +202,7 @@ void OCIOLutNode::generate_processor()
// RenderManager/PreviewAutoCacher, so skip this step to avoid crashing.
if (is_main_process()) {
invalidate_all(k_texture_input);
if (RenderManager *rm = RenderManager::instance()) {
if (PreviewAutoCacher *cacher = rm->get_cacher()) {
cacher->cancel_video_tasks(false);
}
}
oakrender_cancel_video_tasks(0);
}
}
@@ -192,7 +210,7 @@ void OCIOLutNode::ensure_processor() const
{
std::lock_guard<std::mutex> locker(gen_mutex_);
if (!processor_dirty_ && last_processor_ &&
if (!processor_dirty_ && last_processor_.ctx &&
get_standard_value(k_file_input).to_string() == last_path_ &&
read_direction_input(this) == last_direction_) {
return;
@@ -217,8 +235,8 @@ void OCIOLutNode::set_last_error(const std::string &error) const
bool OCIOLutNode::create_processor_from_inputs() const
{
if (!manager()) {
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
const_cast<OCIOLutNode *>(this)->set_processor(OakColorProcessor{});
oakrender_color_processor_free(&last_processor_);
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
@@ -229,8 +247,8 @@ bool OCIOLutNode::create_processor_from_inputs() const
const int direction = read_direction_input(this);
if (path.empty()) {
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
const_cast<OCIOLutNode *>(this)->set_processor(OakColorProcessor{});
oakrender_color_processor_free(&last_processor_);
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
@@ -239,7 +257,8 @@ bool OCIOLutNode::create_processor_from_inputs() const
}
// Re-use the existing processor if the file and direction haven't changed.
if (path == last_path_ && direction == last_direction_ && last_processor_) {
if (path == last_path_ && direction == last_direction_ &&
last_processor_.ctx) {
processor_dirty_ = false;
return false;
}
@@ -249,8 +268,8 @@ bool OCIOLutNode::create_processor_from_inputs() const
std::filesystem::is_regular_file(path, fs_ec) && !fs_ec;
if (!is_file) {
fprintf(stderr, "OCIO LUT file does not exist: %s\n", path.c_str());
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
const_cast<OCIOLutNode *>(this)->set_processor(OakColorProcessor{});
oakrender_color_processor_free(&last_processor_);
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
@@ -262,11 +281,11 @@ bool OCIOLutNode::create_processor_from_inputs() const
if (!suffix.empty() && suffix.front() == '.') {
suffix.erase(suffix.begin());
}
if (!LUTLibrary::is_supported_extension(suffix)) {
if (!oakrender_lut_is_supported_extension(suffix.c_str())) {
fprintf(stderr, "Unsupported OCIO LUT file extension: %s\n",
path.c_str());
const_cast<OCIOLutNode *>(this)->set_processor(nullptr);
last_processor_.reset();
const_cast<OCIOLutNode *>(this)->set_processor(OakColorProcessor{});
oakrender_color_processor_free(&last_processor_);
last_path_.clear();
last_direction_ = -1;
processor_dirty_ = false;
@@ -274,30 +293,23 @@ bool OCIOLutNode::create_processor_from_inputs() const
return false;
}
ColorProcessorPtr processor;
try {
const bool forward = static_cast<ColorProcessor::Direction>(
direction) == ColorProcessor::k_normal;
fprintf(stderr,
"OCIOLutNode: creating processor for %s direction=%d "
"ocio_dir=%s process=%s\n",
path.c_str(), direction, forward ? "FORWARD" : "INVERSE",
is_main_process() ? "main" : "worker");
const bool forward = direction == 0; // ColorProcessor::k_normal
fprintf(stderr,
"OCIOLutNode: creating processor for %s direction=%d "
"ocio_dir=%s process=%s\n",
path.c_str(), direction, forward ? "FORWARD" : "INVERSE",
is_main_process() ? "main" : "worker");
ocio::FileTransformRcPtr transform = ocio::FileTransform::Create();
transform->setSrc(path.c_str());
transform->setInterpolation(ocio::INTERP_LINEAR);
transform->setDirection(forward ? ocio::TRANSFORM_DIR_FORWARD :
ocio::TRANSFORM_DIR_INVERSE);
OakNodeColorManager mgr =
oaknode_colormanager_wrap_borrowed(manager());
OakColorProcessor processor = oakrender_color_processor_create_lut(
mgr, path.c_str(),
forward ? OAKRENDER_COLOR_DIRECTION_NORMAL :
OAKRENDER_COLOR_DIRECTION_INVERSE);
mgr.release(mgr.ctx);
processor = ColorProcessor::create(
manager()->get_config()->getProcessor(transform));
} catch (const std::exception &e) {
fprintf(stderr, "OCIO LUT processor error: %s\n", e.what());
processor = nullptr;
}
if (!processor) {
if (!processor.ctx) {
fprintf(stderr, "OCIO LUT processor error for %s\n", path.c_str());
set_last_error("OCIO LUT: failed to load LUT file: " + path);
} else {
set_last_error(std::string());
@@ -305,6 +317,7 @@ bool OCIOLutNode::create_processor_from_inputs() const
last_path_ = path;
last_direction_ = direction;
oakrender_color_processor_free(&last_processor_);
last_processor_ = processor;
const_cast<OCIOLutNode *>(this)->set_processor(processor);
processor_dirty_ = false;
+3 -2
View File
@@ -33,8 +33,9 @@ namespace olive
class OCIOLutNode : public OCIOBaseNode {
public:
OCIOLutNode();
virtual ~OCIOLutNode() override;
NODE_DEFAULT_FUNCTIONS(OCIOLutNode)
NODE_COPY_FUNCTION(OCIOLutNode)
virtual std::string name() const override;
virtual std::string id() const override;
@@ -76,7 +77,7 @@ private:
mutable bool processor_dirty_ = true;
mutable std::string last_path_;
mutable int last_direction_ = -1;
mutable ColorProcessorPtr last_processor_;
mutable OakColorProcessor last_processor_ = {};
mutable std::string last_error_;
};
+25 -8
View File
@@ -15,6 +15,9 @@
***/
#include "chromakey.h"
#include "common/colortransform.h"
#include "node/colormanager.h"
#include "render/color.h"
#include "color/colormanager/colormanager.h"
#include "render/colorprocessor.h"
@@ -134,12 +137,25 @@ ShaderCode ChromaKeyNode::get_shader_code(const ShaderRequest &request) const
void ChromaKeyNode::generate_processor()
{
if (manager()) {
try {
ColorTransform transform("cie_xyz_d65_interchange");
set_processor(ColorProcessor::create(
manager(), manager()->get_reference_color_space(), transform));
} catch (const ocio::Exception &e) {
std::cerr << std::endl << e.what() << std::endl;
OakNodeColorManager mgr =
oaknode_colormanager_wrap_borrowed(manager());
OakColorTransform transform =
oakcommon_colortransform_init_output("cie_xyz_d65_interchange");
char ref_space[256];
int needed = oaknode_colormanager_get_reference_color_space(
mgr, ref_space, sizeof(ref_space));
OakColorProcessor processor = {};
if (needed > 0 && needed <= int(sizeof(ref_space))) {
processor = oakrender_color_processor_create_transform(
mgr, ref_space, transform, OAKRENDER_COLOR_DIRECTION_NORMAL);
}
oakcommon_colortransform_free(&transform);
mgr.release(mgr.ctx);
if (processor.ctx) {
set_processor(processor);
}
}
}
@@ -148,10 +164,11 @@ void ChromaKeyNode::value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const
{
if (TexturePtr tex = value.at(k_texture_input).to_texture()) {
if (processor()) {
if (processor().ctx) {
ColorTransformJob job(value);
job.set_color_processor(processor());
job.set_color_processor(
oakrender_color_processor_get_native(processor()));
job.set_input_texture(value.at(k_texture_input));
job.set_needs_custom_shader(this);
job.set_function_name("SceneLinearToCIEXYZ_d65");
+77 -24
View File
@@ -32,6 +32,7 @@
#include "lerp.h"
#include "configaccessor.h"
#include "../c_api/nodehandle.h"
#include "group/group.h"
#include "project/serializer/typeserializer.h"
#include "nodeundo.h"
@@ -54,6 +55,39 @@ static std::string number_to_string(double d)
const std::string Node::k_enabled_input = "enabled_in";
namespace
{
/**
* @brief Invalidate a node-owned cache over a rational time range
* through the oakrender C ABI.
*/
void invalidate_handle_cache(const OakRenderCache &cache,
const olive::TimeRange &range)
{
oakrender_cache_invalidate_range(cache, range.in().numerator(),
range.in().denominator(),
range.out().numerator(),
range.out().denominator());
}
/**
* @brief Fetch a cache UUID through the two-stage string API.
*/
std::string cache_uuid_string(const OakRenderCache &cache)
{
int needed = oakrender_cache_get_uuid(cache, nullptr, 0);
if (needed <= 0) {
return std::string();
}
std::string uuid(size_t(needed), '\0');
oakrender_cache_get_uuid(cache, uuid.data(), needed);
uuid.resize(size_t(needed - 1));
return uuid;
}
} // namespace
Node::Node()
: override_color_(-1)
, folder_(nullptr)
@@ -63,12 +97,21 @@ Node::Node()
{
add_input(k_enabled_input, NodeValue::k_boolean, true);
video_cache_ = new FrameHashCache(this);
thumbnail_cache_ = new ThumbnailCache(this);
audio_cache_ = new AudioPlaybackCache(this);
waveform_cache_ = new AudioWaveformCache(this);
// Borrowed self-handle: the caches keep a native back-pointer
// inside oakrender, the box goes away right here
OakNodeNode self = oaknode_c_api::make_handle<OakNodeNode>(
this, false, nullptr);
video_cache_ = oakrender_cache_create_for_node(
self, OAKRENDER_CACHE_VIDEO_FRAME);
thumbnail_cache_ = oakrender_cache_create_for_node(
self, OAKRENDER_CACHE_THUMBNAIL);
audio_cache_ = oakrender_cache_create_for_node(
self, OAKRENDER_CACHE_AUDIO_PLAYBACK);
waveform_cache_ = oakrender_cache_create_for_node(
self, OAKRENDER_CACHE_AUDIO_WAVEFORM);
self.release(self.ctx);
waveform_cache_->set_saving_enabled(false);
oakrender_cache_set_saving_enabled(waveform_cache_, 0);
}
Node::~Node()
@@ -89,6 +132,11 @@ Node::~Node()
delete i;
}
}
oakrender_cache_free(&video_cache_);
oakrender_cache_free(&thumbnail_cache_);
oakrender_cache_free(&audio_cache_);
oakrender_cache_free(&waveform_cache_);
}
std::string Node::short_name() const
@@ -225,10 +273,14 @@ void Node::disconnect_edge(Node *output, const NodeInput &input, bool silent)
void Node::copy_cache_uuids_from(Node *n)
{
video_cache_->set_uuid(n->video_cache_->get_uuid());
audio_cache_->set_uuid(n->audio_cache_->get_uuid());
thumbnail_cache_->set_uuid(n->thumbnail_cache_->get_uuid());
waveform_cache_->set_uuid(n->waveform_cache_->get_uuid());
auto copy_cache_uuid = [](const OakRenderCache &from, OakRenderCache *to) {
oakrender_cache_set_uuid(*to, cache_uuid_string(from).c_str());
};
copy_cache_uuid(n->video_cache_, &video_cache_);
copy_cache_uuid(n->audio_cache_, &audio_cache_);
copy_cache_uuid(n->thumbnail_cache_, &thumbnail_cache_);
copy_cache_uuid(n->waveform_cache_, &waveform_cache_);
}
std::string Node::get_input_name(const std::string &id) const
@@ -996,13 +1048,13 @@ void Node::invalidate_cache(const TimeRange &range, const std::string &from,
if (range.in() != range.out()) {
TimeRange vr = range.intersected(get_video_cache_range());
if (vr.length() != 0) {
video_frame_cache()->invalidate(vr);
thumbnail_cache()->invalidate(vr);
invalidate_handle_cache(video_cache_, vr);
invalidate_handle_cache(thumbnail_cache_, vr);
}
TimeRange ar = range.intersected(get_audio_cache_range());
if (ar.length() != 0) {
audio_playback_cache()->invalidate(ar);
waveform_cache()->invalidate(ar);
invalidate_handle_cache(audio_cache_, ar);
invalidate_handle_cache(waveform_cache_, ar);
}
}
}
@@ -1410,13 +1462,17 @@ bool Node::load(XmlStreamReader *reader, SerializedData *data)
} else if (reader->name() == "caches") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "audio") {
this->audio_playback_cache()->set_uuid(reader->read_element_text());
oakrender_cache_set_uuid(
audio_cache_, reader->read_element_text().c_str());
} else if (reader->name() == "video") {
this->video_frame_cache()->set_uuid(reader->read_element_text());
oakrender_cache_set_uuid(
video_cache_, reader->read_element_text().c_str());
} else if (reader->name() == "thumb") {
this->thumbnail_cache()->set_uuid(reader->read_element_text());
oakrender_cache_set_uuid(
thumbnail_cache_, reader->read_element_text().c_str());
} else if (reader->name() == "waveform") {
this->waveform_cache()->set_uuid(reader->read_element_text());
oakrender_cache_set_uuid(
waveform_cache_, reader->read_element_text().c_str());
} else {
reader->skip_current_element();
}
@@ -1518,14 +1574,11 @@ void Node::save(XmlStreamWriter *writer) const
writer->write_start_element("caches");
writer->write_text_element("audio",
this->audio_playback_cache()->get_uuid());
writer->write_text_element("video",
this->video_frame_cache()->get_uuid());
writer->write_text_element("thumb",
this->thumbnail_cache()->get_uuid());
writer->write_text_element("audio", cache_uuid_string(audio_cache_));
writer->write_text_element("video", cache_uuid_string(video_cache_));
writer->write_text_element("thumb", cache_uuid_string(thumbnail_cache_));
writer->write_text_element("waveform",
this->waveform_cache()->get_uuid());
cache_uuid_string(waveform_cache_));
writer->write_end_element(); // caches
+14 -11
View File
@@ -37,9 +37,7 @@
#include "param.h"
#include "ofxhImageEffectAPI.h"
#include "olive/core/util/timerange.h"
#include "render/audioplaybackcache.h"
#include "render/audiowaveformcache.h"
#include "render/framehashcache.h"
#include "render/cache.h"
#include "render/job/generatejob.h"
#include "render/job/samplejob.h"
#include "render/job/shaderjob.h"
@@ -293,22 +291,27 @@ public:
return has_input_with_id(id);
}
FrameHashCache *video_frame_cache() const
/**
* @brief Borrowed copies of the cache handles owned by this node
* (oakrender caches). Callers must NOT free them; addref
* first to keep one beyond the node's lifetime.
*/
const OakRenderCache &video_frame_cache() const
{
return video_cache_;
}
ThumbnailCache *thumbnail_cache() const
const OakRenderCache &thumbnail_cache() const
{
return thumbnail_cache_;
}
AudioPlaybackCache *audio_playback_cache() const
const OakRenderCache &audio_playback_cache() const
{
return audio_cache_;
}
AudioWaveformCache *waveform_cache() const
const OakRenderCache &waveform_cache() const
{
return waveform_cache_;
}
@@ -1505,11 +1508,11 @@ private:
std::string effect_input_;
FrameHashCache *video_cache_;
ThumbnailCache *thumbnail_cache_;
OakRenderCache video_cache_ = {};
OakRenderCache thumbnail_cache_ = {};
AudioPlaybackCache *audio_cache_;
AudioWaveformCache *waveform_cache_;
OakRenderCache audio_cache_ = {};
OakRenderCache waveform_cache_ = {};
bool caches_enabled_;
};
+48 -10
View File
@@ -23,6 +23,28 @@
#include "common/xmlutils.h"
#include "configaccessor.h"
#include "../../../c_api/nodehandle.h"
namespace
{
/**
* @brief PlaybackCache::request(context, range) through the oakrender
* C ABI; the context handle is a borrowed box made on the spot.
*/
void request_cache_range(const OakRenderCache &cache, olive::Node *context,
const olive::TimeRange &range)
{
OakNodeNode ctx = oaknode_c_api::make_handle<OakNodeNode>(
context, false, nullptr);
oakrender_cache_request(cache, ctx, range.in().numerator(),
range.in().denominator(),
range.out().numerator(),
range.out().denominator());
ctx.release(ctx.ctx);
}
} // namespace
#include "coreengine.h"
#include "traverser.h"
#include "olive/core/util/timecodefunctions.h"
@@ -263,19 +285,19 @@ void ViewerOutput::invalidate_cache(const TimeRange &range,
if (autocache_input_video_) {
TimeRange max_range = input_time_adjustment(
from, element, TimeRange(0, get_video_length()), false);
connected->video_frame_cache()->request(
this, range.intersected(max_range));
request_cache_range(connected->video_frame_cache(), this,
range.intersected(max_range));
}
} else if (from == k_samples_input) {
TimeRange max_range = input_time_adjustment(
from, element, TimeRange(0, get_audio_length()), false);
if (waveform_requests_enabled_) {
connected->waveform_cache()->request(
this, range.intersected(max_range));
request_cache_range(connected->waveform_cache(), this,
range.intersected(max_range));
}
if (autocache_input_audio_) {
connected->audio_playback_cache()->request(
this, range.intersected(max_range));
request_cache_range(connected->audio_playback_cache(), this,
range.intersected(max_range));
}
}
}
@@ -434,10 +456,26 @@ void ViewerOutput::set_waveform_enabled(bool e)
if (Node *connected = this->get_connected_sample_output()) {
TimeRange max_range = input_time_adjustment(
k_samples_input, -1, TimeRange(0, get_audio_length()), false);
core::TimeRangeList invalid =
connected->waveform_cache()->get_invalidated_ranges(max_range);
for (const TimeRange &r : invalid) {
connected->waveform_cache()->request(this, r);
const OakRenderCache &wave_cache =
connected->waveform_cache();
int n = oakrender_cache_get_invalidated_ranges(
wave_cache, max_range.in().numerator(),
max_range.in().denominator(), max_range.out().numerator(),
max_range.out().denominator(), NULL, 0);
if (n > 0) {
std::vector<int64_t> ranges(size_t(n) * 4);
oakrender_cache_get_invalidated_ranges(
wave_cache, max_range.in().numerator(),
max_range.in().denominator(),
max_range.out().numerator(),
max_range.out().denominator(), ranges.data(), n);
for (int i = 0; i < n; i++) {
request_cache_range(
wave_cache, this,
TimeRange(Rational(ranges[i * 4], ranges[i * 4 + 1]),
Rational(ranges[i * 4 + 2],
ranges[i * 4 + 3])));
}
}
}
}
+7 -4
View File
@@ -27,8 +27,6 @@
#include "codec/encoder.h"
#include "node.h"
#include "output/track/track.h"
#include "render/audioplaybackcache.h"
#include "render/framehashcache.h"
#include "subtitleparams.h"
#include "videoparams.h"
#include "timeline/marker.h"
@@ -145,12 +143,17 @@ public:
get_subtitle_stream_count();
}
const AudioWaveformCache *get_connected_waveform()
/**
* @brief Borrowed copy of the connected node's waveform cache handle
* (empty handle when nothing is connected). Callers must NOT
* free it.
*/
OakRenderCache get_connected_waveform()
{
if (Node *n = get_connected_sample_output()) {
return n->waveform_cache();
} else {
return nullptr;
return {};
}
}
+9 -3
View File
@@ -31,6 +31,7 @@
#include <set>
#include "current.h"
#include "render/manager.h"
#include "xmlutils.h"
#include "color/ociobase/ociobase.h"
#include "factory.h"
@@ -469,9 +470,14 @@ std::string Project::cache_path() const
}
}
// ADAPT(M7): render/diskmanager.h is converted in M7; it must return
// std::string here.
return DiskManager::instance()->get_default_cache_path();
int needed = oakrender_disk_cache_path(NULL, 0);
if (needed <= 0) {
return std::string();
}
std::string path(size_t(needed), '\0');
oakrender_disk_cache_path(path.data(), needed);
path.resize(size_t(needed - 1));
return path;
}
void Project::regenerate_uuid()
@@ -516,17 +516,17 @@ void ProjectSerializer220403::load_node(Node *node, XMLNodeData &xml_node_data,
} else if (reader->name() == "caches") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "audio") {
node->audio_playback_cache()->set_uuid(
reader->read_element_text());
oakrender_cache_set_uuid(node->audio_playback_cache(),
reader->read_element_text().c_str());
} else if (reader->name() == "video") {
node->video_frame_cache()->set_uuid(
reader->read_element_text());
oakrender_cache_set_uuid(node->video_frame_cache(),
reader->read_element_text().c_str());
} else if (reader->name() == "thumb") {
node->thumbnail_cache()->set_uuid(
reader->read_element_text());
oakrender_cache_set_uuid(node->thumbnail_cache(),
reader->read_element_text().c_str());
} else if (reader->name() == "waveform") {
node->waveform_cache()->set_uuid(
reader->read_element_text());
oakrender_cache_set_uuid(node->waveform_cache(),
reader->read_element_text().c_str());
} else {
reader->skip_current_element();
}
+17 -4
View File
@@ -147,12 +147,25 @@ NodeValue NodeTraverser::generate_row_value_element(const Node *node,
if (value.type() == NodeValue::k_texture && use_cache()) {
if (TexturePtr tex = value.to_texture()) {
std::lock_guard<std::mutex> locker(node->video_frame_cache()->mutex());
const OakRenderCache &frame_cache = node->video_frame_cache();
oakrender_cache_lock(frame_cache);
node->video_frame_cache()->load_state();
oakrender_cache_load_state(frame_cache);
std::string cache;
int needed = oakrender_cache_get_valid_cache_filename(
frame_cache, time.in().numerator(), time.in().denominator(),
nullptr, 0);
if (needed > 0) {
cache.resize(size_t(needed), '\0');
oakrender_cache_get_valid_cache_filename(
frame_cache, time.in().numerator(),
time.in().denominator(), cache.data(), needed);
cache.resize(size_t(needed - 1));
}
oakrender_cache_unlock(frame_cache);
std::string cache =
node->video_frame_cache()->get_valid_cache_filename(time.in());
if (!cache.empty()) {
value.set_value(tex->to_job(CacheJob(cache, value)));
}
+32
View File
@@ -326,3 +326,35 @@ TEST_F(ColorManagerTest, CompliantColorTransform)
oaknode_colormanager_free(&m);
release_borrowed(ph);
}
TEST_F(ColorManagerTest, WrapBorrowedNull)
{
EXPECT_EQ(oaknode_colormanager_wrap_borrowed(nullptr).ctx, nullptr);
}
TEST_F(ColorManagerTest, WrapBorrowedAndGetNative)
{
olive::Project project;
OakNodeProject ph = borrow_project(&project);
OakNodeColorManager m = oaknode_colormanager_init(ph);
ASSERT_NE(m.ctx, nullptr);
olive::ColorManager *native = oaknode_colormanager_get_native(m);
ASSERT_NE(native, nullptr);
OakNodeColorManager borrowed =
oaknode_colormanager_wrap_borrowed(native);
ASSERT_NE(borrowed.ctx, nullptr);
EXPECT_EQ(oaknode_colormanager_get_native(borrowed), native);
// Releasing the borrowed box must not destroy the object
borrowed.release(borrowed.ctx);
EXPECT_EQ(oaknode_colormanager_get_native(m), native);
EXPECT_EQ(oaknode_colormanager_get_native(OakNodeColorManager{}),
nullptr);
oaknode_colormanager_free(&m);
release_borrowed(ph);
}
+230
View File
@@ -21,13 +21,18 @@
#include "../../../include/render/cache.h"
#include <atomic>
#include <cstring>
#include <new>
#include "alivecount.h"
#include "internalhandles.h"
#include "audioplaybackcache.h"
#include "audiowaveformcache.h"
#include "framehashcache.h"
#include "playbackcache.h"
#include "output/viewer/viewer.h"
#include "../../node/c_api/nodehandle.h"
namespace
{
@@ -114,6 +119,231 @@ OakRenderCache oakrender_cache_wrap_borrowed(void *native_cache)
native_cache, false, nullptr);
}
namespace
{
/**
* @brief Recover the boxed cache as its PlaybackCache base (NULL-safe).
*/
olive::PlaybackCache *base(OakRenderCache c)
{
if (!c.ctx) {
return nullptr;
}
return static_cast<olive::PlaybackCache *>(
oakrender_c_api::box_object(c.ctx));
}
/**
* @brief Copy @p value into the two-stage string buffer.
*
* @return Required buffer size in bytes (including NUL). The buffer is
* only written to when it is large enough.
*/
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = static_cast<int>(value.size()) + 1;
if (buf && buf_size >= needed) {
memcpy(buf, value.c_str(), needed);
}
return needed;
}
} // namespace
OakRenderCache oakrender_cache_create_for_node(OakNodeNode parent, int kind)
{
olive::Node *native_parent =
oaknode_c_api::to_native<olive::Node>(parent);
if (!native_parent) {
return OakRenderCache{};
}
olive::PlaybackCache *cache = nullptr;
switch (kind) {
case OAKRENDER_CACHE_VIDEO_FRAME:
cache = new (std::nothrow) olive::FrameHashCache(native_parent);
break;
case OAKRENDER_CACHE_THUMBNAIL:
cache = new (std::nothrow) olive::ThumbnailCache(native_parent);
break;
case OAKRENDER_CACHE_AUDIO_PLAYBACK:
cache = new (std::nothrow) olive::AudioPlaybackCache(native_parent);
break;
case OAKRENDER_CACHE_AUDIO_WAVEFORM:
cache = new (std::nothrow) olive::AudioWaveformCache(native_parent);
break;
default:
return OakRenderCache{};
}
if (!cache) {
return OakRenderCache{};
}
return oakrender_c_api::make_handle<OakRenderCache>(
cache, true, &oakrender_c_api::delete_as<olive::PlaybackCache>);
}
int oakrender_cache_get_uuid(OakRenderCache cache, char *buf, int buf_size)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return OAKRENDER_E_INVALID;
}
return copy_string(c->get_uuid(), buf, buf_size);
}
void oakrender_cache_invalidate_range(OakRenderCache cache,
int64_t in_num, int64_t in_den,
int64_t out_num, int64_t out_den)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return;
}
c->invalidate(
olive::core::TimeRange(olive::Rational(in_num, in_den),
olive::Rational(out_num, out_den)));
}
int oakrender_cache_request(OakRenderCache cache, OakNodeNode context,
int64_t in_num, int64_t in_den,
int64_t out_num, int64_t out_den)
{
olive::PlaybackCache *c = base(cache);
olive::Node *native_context =
oaknode_c_api::to_native<olive::Node>(context);
auto *viewer = dynamic_cast<olive::ViewerOutput *>(native_context);
if (!c || !viewer) {
return OAKRENDER_E_INVALID;
}
c->request(viewer,
olive::core::TimeRange(olive::Rational(in_num, in_den),
olive::Rational(out_num, out_den)));
return OAKRENDER_OK;
}
int oakrender_cache_load_state(OakRenderCache cache)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return OAKRENDER_E_INVALID;
}
try {
c->load_state();
return OAKRENDER_OK;
} catch (...) {
return OAKRENDER_E_FAILED;
}
}
int oakrender_cache_save_state(OakRenderCache cache)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return OAKRENDER_E_INVALID;
}
try {
c->save_state();
return OAKRENDER_OK;
} catch (...) {
return OAKRENDER_E_FAILED;
}
}
int oakrender_cache_set_saving_enabled(OakRenderCache cache, int enabled)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return OAKRENDER_E_INVALID;
}
c->set_saving_enabled(enabled != 0);
return OAKRENDER_OK;
}
int oakrender_cache_set_passthrough(OakRenderCache cache,
OakRenderCache other)
{
olive::PlaybackCache *c = base(cache);
olive::PlaybackCache *o = base(other);
if (!c || !o) {
return OAKRENDER_E_INVALID;
}
c->set_passthrough(o);
return OAKRENDER_OK;
}
int oakrender_cache_get_valid_cache_filename(OakRenderCache cache,
int64_t time_num,
int64_t time_den, char *buf,
int buf_size)
{
auto *c = dynamic_cast<olive::FrameHashCache *>(base(cache));
if (!c) {
return OAKRENDER_E_INVALID;
}
return copy_string(
c->get_valid_cache_filename(olive::Rational(time_num, time_den)),
buf, buf_size);
}
int oakrender_cache_get_passthroughs(OakRenderCache cache, int64_t *ranges,
int max_ranges)
{
olive::PlaybackCache *c = base(cache);
if (!c) {
return OAKRENDER_E_INVALID;
}
const std::vector<olive::PlaybackCache::Passthrough> &all =
c->get_passthroughs();
int count = static_cast<int>(all.size());
if (ranges && max_ranges > 0) {
int n = count < max_ranges ? count : max_ranges;
for (int i = 0; i < n; i++) {
ranges[i * 4] = all[i].in().numerator();
ranges[i * 4 + 1] = all[i].in().denominator();
ranges[i * 4 + 2] = all[i].out().numerator();
ranges[i * 4 + 3] = all[i].out().denominator();
}
}
return count;
}
int oakrender_cache_get_timebase(OakRenderCache cache, int *num, int *den)
{
auto *c = dynamic_cast<olive::FrameHashCache *>(base(cache));
if (!c) {
return OAKRENDER_E_INVALID;
}
if (num) {
*num = c->get_timebase().numerator();
}
if (den) {
*den = c->get_timebase().denominator();
}
return OAKRENDER_OK;
}
void oakrender_cache_lock(OakRenderCache cache)
{
if (olive::PlaybackCache *c = base(cache)) {
c->mutex().lock();
}
}
void oakrender_cache_unlock(OakRenderCache cache)
{
if (olive::PlaybackCache *c = base(cache)) {
c->mutex().unlock();
}
}
olive::PlaybackCache *oakrender_cache_get_native(OakRenderCache cache)
{
return base(cache);
}
int oakrender_cache_set_timebase(OakRenderCache cache, int num, int den)
{
if (!cache.ctx || num <= 0 || den <= 0) {
+124
View File
@@ -30,6 +30,7 @@
#include "color/colormanager/colormanager.h"
#include "filefunctions.h"
#include "lutlibrary.h"
#include <OpenColorIO/OpenColorIO.h>
namespace
@@ -105,6 +106,129 @@ int oakrender_color_processor_is_valid(OakColorProcessor processor)
return p && p->ptr && p->ptr->get_processor() ? 1 : 0;
}
OakColorProcessor oakrender_color_processor_create_transform(
OakNodeColorManager manager, const char *input,
OakColorTransform dest, int direction)
{
olive::ColorManager *native_manager =
oaknode_colormanager_get_native(manager);
const olive::ColorTransform *native_dest =
oakcommon_colortransform_get_native(dest);
if (!native_manager || !input || !*input || !native_dest) {
return OakColorProcessor{};
}
if (direction != OAKRENDER_COLOR_DIRECTION_NORMAL &&
direction != OAKRENDER_COLOR_DIRECTION_INVERSE) {
return OakColorProcessor{};
}
try {
auto *impl = new OakColorProcessorImpl;
impl->ptr = olive::ColorProcessor::create(
native_manager, input, *native_dest,
direction == OAKRENDER_COLOR_DIRECTION_NORMAL ?
olive::ColorProcessor::k_normal :
olive::ColorProcessor::k_inverse);
return oakrender_c_api::make_handle<OakColorProcessor>(
impl, true, &oakrender_c_api::delete_as<OakColorProcessorImpl>);
} catch (...) {
return OakColorProcessor{};
}
}
OakColorProcessor oakrender_color_processor_create_lut(
OakNodeColorManager manager, const char *path, int direction)
{
olive::ColorManager *native_manager =
oaknode_colormanager_get_native(manager);
if (!native_manager || !path || !*path) {
return OakColorProcessor{};
}
if (direction != OAKRENDER_COLOR_DIRECTION_NORMAL &&
direction != OAKRENDER_COLOR_DIRECTION_INVERSE) {
return OakColorProcessor{};
}
try {
OCIO_NAMESPACE::FileTransformRcPtr transform =
OCIO_NAMESPACE::FileTransform::Create();
transform->setSrc(path);
transform->setInterpolation(OCIO_NAMESPACE::INTERP_LINEAR);
transform->setDirection(
direction == OAKRENDER_COLOR_DIRECTION_NORMAL ?
OCIO_NAMESPACE::TRANSFORM_DIR_FORWARD :
OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE);
auto *impl = new OakColorProcessorImpl;
impl->ptr = olive::ColorProcessor::create(
native_manager->get_config()->getProcessor(transform));
return oakrender_c_api::make_handle<OakColorProcessor>(
impl, true, &oakrender_c_api::delete_as<OakColorProcessorImpl>);
} catch (...) {
return OakColorProcessor{};
}
}
OakColorProcessor oakrender_color_processor_create_grading_primary(
OakNodeColorManager manager, int style)
{
olive::ColorManager *native_manager =
oaknode_colormanager_get_native(manager);
if (!native_manager || (style != OAKRENDER_GRADING_PRIMARY_LIN &&
style != OAKRENDER_GRADING_PRIMARY_LOG)) {
return OakColorProcessor{};
}
try {
OCIO_NAMESPACE::GradingPrimaryTransformRcPtr gp =
OCIO_NAMESPACE::GradingPrimaryTransform::Create(
style == OAKRENDER_GRADING_PRIMARY_LIN ?
OCIO_NAMESPACE::GRADING_LIN :
OCIO_NAMESPACE::GRADING_LOG);
gp->makeDynamic();
gp->setDirection(
OCIO_NAMESPACE::TransformDirection::TRANSFORM_DIR_FORWARD);
auto *impl = new OakColorProcessorImpl;
impl->ptr = olive::ColorProcessor::create(
native_manager->get_config()->getProcessor(gp));
return oakrender_c_api::make_handle<OakColorProcessor>(
impl, true, &oakrender_c_api::delete_as<OakColorProcessorImpl>);
} catch (...) {
return OakColorProcessor{};
}
}
std::shared_ptr<olive::ColorProcessor> oakrender_color_processor_get_native(
OakColorProcessor processor)
{
OakColorProcessorImpl *p =
oakrender_c_api::to_native<OakColorProcessorImpl>(processor);
return p ? p->ptr : nullptr;
}
int oakrender_lut_is_supported_extension(const char *extension)
{
if (!extension) {
return 0;
}
return olive::LUTLibrary::is_supported_extension(extension) ? 1 : 0;
}
int oakrender_lut_supported_extensions_count(void)
{
return static_cast<int>(
olive::LUTLibrary::supported_extensions().size());
}
int oakrender_lut_supported_extension_at(int index, char *buf,
int buf_size)
{
const std::vector<std::string> &all =
olive::LUTLibrary::supported_extensions();
if (index < 0 || index >= static_cast<int>(all.size())) {
return OAKRENDER_E_NOT_FOUND;
}
return write_string(all[size_t(index)], buf, buf_size);
}
int oakrender_color_processor_convert(OakColorProcessor processor,
double ir, double ig, double ib,
double ia, double *out_r, double *out_g,
+16
View File
@@ -161,6 +161,22 @@ int oakrender_cancel_request(int64_t request_id)
return OAKRENDER_OK;
}
int oakrender_manager_available(void)
{
return olive::RenderManager::instance() != nullptr ? 1 : 0;
}
void oakrender_cancel_video_tasks(int wait_for_done)
{
olive::RenderManager *manager = olive::RenderManager::instance();
if (!manager) {
return;
}
if (olive::PreviewAutoCacher *cacher = manager->get_cacher()) {
cacher->cancel_video_tasks(wait_for_done != 0);
}
}
int oakrender_set_cacher_multicam(OakNodeNode multicam_or_NULL)
{
olive::RenderManager *manager = olive::RenderManager::instance();
+5 -3
View File
@@ -149,9 +149,11 @@ OakRenderTicket oakrender_ticket_render_frame(
}
}
if (params->cache) {
rvp.add_cache(reinterpret_cast<olive::FrameHashCache *>(
params->cache));
if (params->cache.ctx) {
if (auto *fc = dynamic_cast<olive::FrameHashCache *>(
oakrender_cache_get_native(params->cache))) {
rvp.add_cache(fc);
}
}
auto *watcher = new olive::RenderTicketWatcher();
+25 -15
View File
@@ -31,6 +31,7 @@
#include "inputdragger.h"
#include "input/multicam/multicamnode.h"
#include "playbackcache.h"
#include "render/cache.h"
#include "project.h"
#include "qtutils.h"
#include "rendermanager.h"
@@ -338,10 +339,12 @@ void PreviewAutoCacher::connect_to_node_cache(Node *node)
return;
}
PlaybackCache *video_cache = node->video_frame_cache();
PlaybackCache *thumb_cache = node->thumbnail_cache();
PlaybackCache *audio_cache = node->audio_playback_cache();
PlaybackCache *wave_cache = node->waveform_cache();
// The caches live behind oakrender handles on the node; unwrap the
// natives for the in-module callback wiring.
PlaybackCache *video_cache = oakrender_cache_get_native(node->video_frame_cache());
PlaybackCache *thumb_cache = oakrender_cache_get_native(node->thumbnail_cache());
PlaybackCache *audio_cache = oakrender_cache_get_native(node->audio_playback_cache());
PlaybackCache *wave_cache = oakrender_cache_get_native(node->waveform_cache());
video_cache->set_requested_callback(
[this, video_cache](ViewerOutput *context, const TimeRange &range) {
@@ -369,21 +372,26 @@ void PreviewAutoCacher::connect_to_node_cache(Node *node)
audio_cache->set_cancel_all_callback(
[this, audio_cache]() { cancel_for_cache(audio_cache); });
node->video_frame_cache()->resignal_requests();
node->thumbnail_cache()->resignal_requests();
node->audio_playback_cache()->resignal_requests();
node->waveform_cache()->resignal_requests();
video_cache->resignal_requests();
thumb_cache->resignal_requests();
audio_cache->resignal_requests();
wave_cache->resignal_requests();
}
void PreviewAutoCacher::disconnect_from_node_cache(Node *node)
{
node->video_frame_cache()->set_requested_callback(nullptr);
node->thumbnail_cache()->set_requested_callback(nullptr);
node->audio_playback_cache()->set_requested_callback(nullptr);
node->waveform_cache()->set_requested_callback(nullptr);
PlaybackCache *video_cache = oakrender_cache_get_native(node->video_frame_cache());
PlaybackCache *thumb_cache = oakrender_cache_get_native(node->thumbnail_cache());
PlaybackCache *audio_cache = oakrender_cache_get_native(node->audio_playback_cache());
PlaybackCache *wave_cache = oakrender_cache_get_native(node->waveform_cache());
node->video_frame_cache()->set_cancel_all_callback(nullptr);
node->audio_playback_cache()->set_cancel_all_callback(nullptr);
video_cache->set_requested_callback(nullptr);
thumb_cache->set_requested_callback(nullptr);
audio_cache->set_requested_callback(nullptr);
wave_cache->set_requested_callback(nullptr);
video_cache->set_cancel_all_callback(nullptr);
audio_cache->set_cancel_all_callback(nullptr);
}
void PreviewAutoCacher::cancel_queued_single_frame_render()
@@ -816,7 +824,9 @@ void PreviewAutoCacher::force_cache_range(ViewerOutput *context,
custom_autocache_range_ = range;
// Re-hash these frames and start rendering
start_caching_video_range(context, context->video_frame_cache(), range);
start_caching_video_range(
context, oakrender_cache_get_native(context->video_frame_cache()),
range);
}
void PreviewAutoCacher::project_destroyed()
+1
View File
@@ -31,6 +31,7 @@
#include "configaccessor.h"
#include "colorprocessorcache.h"
#include "framehashcache.h"
#include "output/viewer/viewer.h"
#include "project.h"
#include "traverser.h"
+204
View File
@@ -22,6 +22,12 @@
#include <gtest/gtest.h>
#include <vector>
#include "node/node.h"
#include "node/project.h"
#include "node/sequence.h"
TEST(OakRenderCacheTest, CreateFree)
{
const int alive_before = oakrender_debug_alive_count();
@@ -186,3 +192,201 @@ TEST(OakRenderCacheTest, AliveCountReturnsToBaseline)
oakrender_cache_free(&b);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
}
namespace
{
/**
* @brief Minimal project+sequence fixture for node-owned caches.
*/
class NodeCacheFixture : public ::testing::Test {
protected:
void SetUp() override
{
project_ = oaknode_project_init();
ASSERT_NE(project_.ctx, nullptr);
sequence_ = oaknode_sequence_create();
ASSERT_NE(sequence_.ctx, nullptr);
node_ = oaknode_sequence_as_node(sequence_);
OakNodeProject owner = {};
if (oaknode_node_get_project(node_, &owner) != OAKNODE_OK ||
!owner.ctx) {
ASSERT_EQ(oaknode_project_add_node(project_, node_),
OAKNODE_OK);
}
}
void TearDown() override
{
oaknode_project_free(&project_);
}
OakNodeProject project_ = {};
OakNodeSequence sequence_ = {};
OakNodeNode node_ = {};
};
} // namespace
TEST_F(NodeCacheFixture, CreateForNodeAllKinds)
{
const int alive_before = oakrender_debug_alive_count();
const int kinds[] = { OAKRENDER_CACHE_VIDEO_FRAME,
OAKRENDER_CACHE_THUMBNAIL,
OAKRENDER_CACHE_AUDIO_PLAYBACK,
OAKRENDER_CACHE_AUDIO_WAVEFORM };
for (int kind : kinds) {
OakRenderCache c = oakrender_cache_create_for_node(node_, kind);
ASSERT_NE(c.ctx, nullptr) << "kind " << kind;
oakrender_cache_free(&c);
}
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
EXPECT_EQ(oakrender_cache_create_for_node(OakNodeNode{},
OAKRENDER_CACHE_VIDEO_FRAME)
.ctx,
nullptr);
EXPECT_EQ(oakrender_cache_create_for_node(node_, -1).ctx, nullptr);
}
TEST_F(NodeCacheFixture, UuidRoundTripTwoStage)
{
OakRenderCache c =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
ASSERT_NE(c.ctx, nullptr);
EXPECT_EQ(oakrender_cache_set_uuid(c, "{test-uuid-1}"), OAKRENDER_OK);
int needed = oakrender_cache_get_uuid(c, nullptr, 0);
ASSERT_GT(needed, 0);
std::vector<char> buf(static_cast<size_t>(needed));
EXPECT_EQ(oakrender_cache_get_uuid(c, buf.data(), needed), needed);
EXPECT_STREQ(buf.data(), "{test-uuid-1}");
EXPECT_EQ(oakrender_cache_set_uuid(OakRenderCache{}, "x"),
OAKRENDER_E_INVALID);
EXPECT_LT(oakrender_cache_get_uuid(OakRenderCache{}, nullptr, 0), 0);
oakrender_cache_free(&c);
}
TEST_F(NodeCacheFixture, InvalidateRangeAndQuery)
{
OakRenderCache c =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
ASSERT_NE(c.ctx, nullptr);
// A fresh cache has nothing validated: the whole query range is
// reported invalidated regardless of prior invalidate() calls.
oakrender_cache_invalidate_range(c, 1, 2, 3, 2);
int n = oakrender_cache_get_invalidated_ranges(c, 0, 1, 2, 1, nullptr,
0);
ASSERT_EQ(n, 1);
std::vector<int64_t> ranges(4);
EXPECT_EQ(oakrender_cache_get_invalidated_ranges(c, 0, 1, 2, 1,
ranges.data(), 1),
1);
EXPECT_EQ(ranges[0], 0);
EXPECT_EQ(ranges[1], 1);
EXPECT_EQ(ranges[2], 2);
EXPECT_EQ(ranges[3], 1);
oakrender_cache_invalidate_range(OakRenderCache{}, 0, 1, 1, 1); // no-op
oakrender_cache_free(&c);
}
TEST_F(NodeCacheFixture, PassthroughRoundTrip)
{
OakRenderCache a =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
OakRenderCache b =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
ASSERT_NE(a.ctx, nullptr);
ASSERT_NE(b.ctx, nullptr);
EXPECT_EQ(oakrender_cache_set_passthrough(a, b), OAKRENDER_OK);
// The passthrough list mirrors the other cache's validated ranges;
// with none, the list stays empty but the link is established.
EXPECT_GE(oakrender_cache_get_passthroughs(a, nullptr, 0), 0);
EXPECT_EQ(oakrender_cache_set_passthrough(a, OakRenderCache{}),
OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_cache_set_passthrough(OakRenderCache{}, b),
OAKRENDER_E_INVALID);
oakrender_cache_free(&a);
oakrender_cache_free(&b);
}
TEST_F(NodeCacheFixture, TimebaseAndFilename)
{
OakRenderCache c =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
ASSERT_NE(c.ctx, nullptr);
EXPECT_EQ(oakrender_cache_set_timebase(c, 1, 25), OAKRENDER_OK);
int num = 0, den = 0;
EXPECT_EQ(oakrender_cache_get_timebase(c, &num, &den), OAKRENDER_OK);
EXPECT_EQ(num, 1);
EXPECT_EQ(den, 25);
// A frame-hash cache reports a filename for a time
int needed = oakrender_cache_get_valid_cache_filename(c, 1, 25,
nullptr, 0);
EXPECT_GT(needed, 0);
// ... but an audio cache is not a frame-hash cache
OakRenderCache audio = oakrender_cache_create_for_node(
node_, OAKRENDER_CACHE_AUDIO_PLAYBACK);
ASSERT_NE(audio.ctx, nullptr);
EXPECT_EQ(oakrender_cache_get_timebase(audio, &num, &den),
OAKRENDER_E_INVALID);
EXPECT_LT(oakrender_cache_get_valid_cache_filename(audio, 0, 1,
nullptr, 0),
0);
oakrender_cache_free(&audio);
oakrender_cache_free(&c);
}
TEST_F(NodeCacheFixture, SavingEnabledLockUnlockStateErrors)
{
OakRenderCache c = oakrender_cache_create_for_node(
node_, OAKRENDER_CACHE_AUDIO_WAVEFORM);
ASSERT_NE(c.ctx, nullptr);
EXPECT_EQ(oakrender_cache_set_saving_enabled(c, 0), OAKRENDER_OK);
EXPECT_EQ(oakrender_cache_set_saving_enabled(OakRenderCache{}, 1),
OAKRENDER_E_INVALID);
// Lock/unlock pair (no-op on empty handle)
oakrender_cache_lock(c);
oakrender_cache_unlock(c);
oakrender_cache_lock(OakRenderCache{});
oakrender_cache_unlock(OakRenderCache{});
EXPECT_EQ(oakrender_cache_load_state(OakRenderCache{}),
OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_cache_save_state(OakRenderCache{}),
OAKRENDER_E_INVALID);
oakrender_cache_free(&c);
}
TEST_F(NodeCacheFixture, RequestValidation)
{
OakRenderCache c =
oakrender_cache_create_for_node(node_, OAKRENDER_CACHE_VIDEO_FRAME);
ASSERT_NE(c.ctx, nullptr);
// The sequence node is a viewer: valid request
EXPECT_EQ(oakrender_cache_request(c, node_, 0, 1, 1, 1), OAKRENDER_OK);
// Empty cache / empty context are rejected
EXPECT_EQ(oakrender_cache_request(OakRenderCache{}, node_, 0, 1, 1, 1),
OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_cache_request(c, OakNodeNode{}, 0, 1, 1, 1),
OAKRENDER_E_INVALID);
oakrender_cache_free(&c);
}
+136
View File
@@ -190,3 +190,139 @@ TEST(OakRenderColorTest, DisplayTransformInvalidArgs)
sizeof(buf)),
OAKRENDER_E_NOT_FOUND);
}
namespace
{
/**
* @brief Project + initialized color manager for processor factories.
*/
class ColorProcessorFactoryFixture : public ::testing::Test {
protected:
void SetUp() override
{
project_ = oaknode_project_init();
ASSERT_NE(project_.ctx, nullptr);
manager_ = oaknode_colormanager_init(project_);
ASSERT_NE(manager_.ctx, nullptr);
ASSERT_EQ(oaknode_colormanager_initialize(manager_), OAKNODE_OK);
}
void TearDown() override
{
oaknode_colormanager_free(&manager_);
oaknode_project_free(&project_);
}
OakNodeProject project_ = {};
OakNodeColorManager manager_ = {};
};
} // namespace
TEST_F(ColorProcessorFactoryFixture, CreateTransformInvalidArgs)
{
OakColorTransform t =
oakcommon_colortransform_init_display("sRGB", "sRGB OETF", "");
ASSERT_NE(t.ctx, nullptr);
EXPECT_EQ(oakrender_color_processor_create_transform(
OakNodeColorManager{}, "Linear", t,
OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_transform(
manager_, nullptr, t, OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_transform(
manager_, "Linear", OakColorTransform{},
OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_transform(
manager_, "Linear", t, 42)
.ctx,
nullptr);
oakcommon_colortransform_free(&t);
}
TEST_F(ColorProcessorFactoryFixture, CreateTransformHappyPath)
{
OakColorTransform t =
oakcommon_colortransform_init_display("sRGB", "sRGB OETF", "");
ASSERT_NE(t.ctx, nullptr);
const int alive_before = oakrender_debug_alive_count();
OakColorProcessor p = oakrender_color_processor_create_transform(
manager_, "Linear", t, OAKRENDER_COLOR_DIRECTION_NORMAL);
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakrender_color_processor_is_valid(p), 1);
EXPECT_NE(oakrender_color_processor_get_native(p), nullptr);
oakrender_color_processor_free(&p);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
oakcommon_colortransform_free(&t);
EXPECT_TRUE(oakrender_color_processor_get_native(OakColorProcessor{}) ==
nullptr);
}
TEST_F(ColorProcessorFactoryFixture, CreateGradingPrimary)
{
OakColorProcessor p = oakrender_color_processor_create_grading_primary(
manager_, OAKRENDER_GRADING_PRIMARY_LIN);
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakrender_color_processor_is_valid(p), 1);
oakrender_color_processor_free(&p);
EXPECT_EQ(oakrender_color_processor_create_grading_primary(manager_, 9)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_grading_primary(
OakNodeColorManager{}, OAKRENDER_GRADING_PRIMARY_LOG)
.ctx,
nullptr);
}
TEST_F(ColorProcessorFactoryFixture, CreateLutErrorPaths)
{
// Nonexistent LUT file fails cleanly
EXPECT_EQ(oakrender_color_processor_create_lut(
manager_, "/nonexistent/never.cube",
OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_lut(
OakNodeColorManager{}, "x.cube",
OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_lut(
manager_, nullptr, OAKRENDER_COLOR_DIRECTION_NORMAL)
.ctx,
nullptr);
EXPECT_EQ(oakrender_color_processor_create_lut(manager_, "x.cube", 7)
.ctx,
nullptr);
}
TEST(OakRenderLutTest, SupportedExtensions)
{
int count = oakrender_lut_supported_extensions_count();
ASSERT_GT(count, 0);
char ext[32];
ASSERT_GT(oakrender_lut_supported_extension_at(0, ext, sizeof(ext)),
0);
EXPECT_EQ(oakrender_lut_is_supported_extension(ext), 1);
EXPECT_EQ(oakrender_lut_is_supported_extension("definitely-not-a-lut"),
0);
EXPECT_EQ(oakrender_lut_is_supported_extension(nullptr), 0);
EXPECT_LT(oakrender_lut_supported_extension_at(-1, ext, sizeof(ext)),
0);
EXPECT_LT(oakrender_lut_supported_extension_at(count, ext, sizeof(ext)),
0);
}
+15
View File
@@ -129,3 +129,18 @@ TEST(OakRenderDiskCacheTest, SizeAndClear)
// Clearing an existing (possibly empty) default folder succeeds.
EXPECT_EQ(oakrender_disk_cache_clear(), OAKRENDER_OK);
}
TEST(OakRenderManagerTest, AvailableReturnsBool)
{
int available = oakrender_manager_available();
EXPECT_TRUE(available == 0 || available == 1);
}
TEST(OakRenderManagerTest, CancelVideoTasksWithoutManagerIsNoOp)
{
if (oakrender_manager_available()) {
GTEST_SKIP() << "manager exists in this process";
}
oakrender_cancel_video_tasks(0);
oakrender_cancel_video_tasks(1);
}
+1 -1
View File
@@ -363,7 +363,7 @@ bool ExportTask::run()
}
render(color_manager_, video_range, audio_range, subtitle_range,
0 /* RenderMode::k_online */, nullptr, force);
0 /* RenderMode::k_online */, {}, force);
bool success = true;
+5 -6
View File
@@ -151,11 +151,10 @@ bool PreCacheTask::run()
intersection = TimeRange(Rational(0), video_length);
}
OakNodeFrameCache *cache = nullptr;
oaknode_node_get_video_frame_cache(viewer(), &cache);
// Borrowed wrapper: the cache stays owned by the viewer node.
OakRenderCache cache_handle = oakrender_cache_wrap_borrowed(cache);
// Addref'd handle: the cache itself stays owned by the viewer node,
// this box is released at the end of the scope.
OakRenderCache cache_handle = {};
oaknode_node_get_video_frame_cache(viewer(), &cache_handle);
int range_count = oakrender_cache_get_invalidated_ranges(
cache_handle,
@@ -183,7 +182,7 @@ bool PreCacheTask::run()
oaknode_colormanager_init(project_);
render(color_manager, video_range, TimeRangeList(), TimeRange(),
0 /* RenderMode::k_online */, cache, ForceParams());
0 /* RenderMode::k_online */, cache_handle, ForceParams());
oaknode_colormanager_free(&color_manager);
+2 -2
View File
@@ -82,7 +82,7 @@ void RenderTask::on_ticket_finished(OakRenderTicket ticket)
bool RenderTask::start_video_ticket(OakNodeColorManager manager,
const Rational &time, int mode,
OakNodeFrameCache *cache,
OakRenderCache cache,
const ForceParams &force)
{
OakNodeNode output_node = {};
@@ -136,7 +136,7 @@ bool RenderTask::render(OakNodeColorManager manager,
const TimeRangeList &video_range,
const TimeRangeList &audio_range,
const TimeRange &subtitle_range, int render_mode,
OakNodeFrameCache *cache, const ForceParams &force)
OakRenderCache cache, const ForceParams &force)
{
oakrender_manager_set_aggressive_gc(1);
+2 -2
View File
@@ -73,7 +73,7 @@ protected:
const TimeRangeList &video_range,
const TimeRangeList &audio_range,
const TimeRange &subtitle_range, int render_mode,
OakNodeFrameCache *cache,
OakRenderCache cache,
const ForceParams &force);
virtual bool download_frame(OakCodecFrame frame, const Rational &time);
@@ -164,7 +164,7 @@ private:
bool start_video_ticket(OakNodeColorManager manager,
const Rational &time, int mode,
OakNodeFrameCache *cache,
OakRenderCache cache,
const ForceParams &force);
OakNodeNode viewer_;