From 5a564f30ca5b98881706ca8afce07372a482f761 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sat, 8 Aug 2026 03:56:17 +0800 Subject: [PATCH] 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). --- docs/zh/plans/riir/notes.md | 43 ++++ include/node/colormanager.h | 25 ++ include/node/node.h | 11 +- include/render/cache.h | 131 ++++++++++ include/render/color.h | 88 +++++++ include/render/manager.h | 14 ++ include/render/ticket.h | 3 +- src/node/c_api/colormanager.cpp | 13 + src/node/c_api/node.cpp | 14 +- src/node/src/block/clip/clip.cpp | 107 +++++--- src/node/src/block/clip/clip.h | 27 +- .../displaytransform/displaytransform.cpp | 25 +- src/node/src/color/ociobase/ociobase.cpp | 11 +- src/node/src/color/ociobase/ociobase.h | 18 +- .../ociogradingtransformlinear.cpp | 24 +- .../ociogradingtransformlog.cpp | 24 +- src/node/src/color/ociolut/ociolut.cpp | 103 ++++---- src/node/src/color/ociolut/ociolut.h | 5 +- src/node/src/keying/chromakey/chromakey.cpp | 33 ++- src/node/src/node.cpp | 101 ++++++-- src/node/src/node.h | 25 +- src/node/src/output/viewer/viewer.cpp | 58 ++++- src/node/src/output/viewer/viewer.h | 11 +- src/node/src/project.cpp | 12 +- .../project/serializer/serializer220403.cpp | 16 +- src/node/src/traverser.cpp | 21 +- src/node/tests/colormanager_test.cpp | 32 +++ src/render/c_api/cache.cpp | 230 ++++++++++++++++++ src/render/c_api/color.cpp | 124 ++++++++++ src/render/c_api/manager.cpp | 16 ++ src/render/c_api/ticket.cpp | 8 +- src/render/src/previewautocacher.cpp | 40 +-- src/render/src/rendermanager.h | 1 + src/render/tests/cache_test.cpp | 204 ++++++++++++++++ src/render/tests/color_test.cpp | 136 +++++++++++ src/render/tests/manager_test.cpp | 15 ++ src/task/src/export/export.cpp | 2 +- src/task/src/precache/precachetask.cpp | 11 +- src/task/src/render/render.cpp | 4 +- src/task/src/render/render.h | 4 +- 40 files changed, 1553 insertions(+), 237 deletions(-) diff --git a/docs/zh/plans/riir/notes.md b/docs/zh/plans/riir/notes.md index cdb4a96e8..ec6c36249 100644 --- a/docs/zh/plans/riir/notes.md +++ b/docs/zh/plans/riir/notes.md @@ -352,3 +352,46 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需 oakcommon_xml_writer_wrap_native(C++ only 借用包装), XmlReaderState/XmlWriterState 支持 owning/borrowed 双模式。 - liboaknode 对 liboaktimeline 的 C++ 符号引用降为 0(nm 验证)。 + +## oaknode→oakrender 切到 C ABI(2026-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_native(C++ 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 后的 + OakRenderCache(out 参数,node.h 前置声明 struct OakRenderCache); + OakNodeFrameCache 不透明指针类型删除;oaktask 的 precachetask + 直接收句柄,不再 wrap_borrowed;RenderTask::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/at;oaknode 新增 + colormanager_wrap_borrowed/get_native(C++ only)。 + OCIO 的 FileTransform/GradingPrimaryTransform 构造全部内收到 + oakrender(grading log/linear、LUT 加载)。 +- RenderManager::instance() 空检查 → oakrender_manager_available(); + PreviewAutoCacher::cancel_video_tasks → oakrender_cancel_video_tasks; + DiskManager 默认缓存路径 → 已有 oakrender_disk_cache_path。 +- PreviewAutoCacher(oakrender 内部)改用 oakrender_cache_get_native + 解包节点缓存,回调接线保持模块内 C++。 +- **例外(记录为 01 §5 例外)**:NodeValue 的纹理载荷 + TexturePtr=shared_ptr 经 Variant 类型擦除流经 + oaknode 的 35 个 TU,析构引用 olive::Texture::~Texture——值系统 + 载荷问题,与 UndoCommand 跨模块继承同类,留待值系统重做。 + 除此之外 liboaknode→liboakrender 的 C++ 符号引用为 0。 diff --git a/include/node/colormanager.h b/include/node/colormanager.h index b2571548a..f43b3910d 100644 --- a/include/node/colormanager.h +++ b/include/node/colormanager.h @@ -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 diff --git a/include/node/node.h b/include/node/node.h index 5b74f75c3..cfb03b304 100644 --- a/include/node/node.h +++ b/include/node/node.h @@ -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 diff --git a/include/render/cache.h b/include/render/cache.h index e5a881f34..402ce317c 100644 --- a/include/render/cache.h +++ b/include/render/cache.h @@ -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. diff --git a/include/render/color.h b/include/render/color.h index a8277ae7b..d805c2440 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -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 +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 oakrender_color_processor_get_native( + OakColorProcessor processor); + #ifdef __cplusplus } #endif diff --git a/include/render/manager.h b/include/render/manager.h index a4b76919c..9a99233b7 100644 --- a/include/render/manager.h +++ b/include/render/manager.h @@ -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) -------------------------------------- */ /** diff --git a/include/render/ticket.h b/include/render/ticket.h index eaee23e76..eb24e18e5 100644 --- a/include/render/ticket.h +++ b/include/render/ticket.h @@ -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; /** diff --git a/src/node/c_api/colormanager.cpp b/src/node/c_api/colormanager.cpp index efb88c2a8..28ca75016 100644 --- a/src/node/c_api/colormanager.cpp +++ b/src/node/c_api/colormanager.cpp @@ -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( + native_manager, false, nullptr); +} + +olive::ColorManager *oaknode_colormanager_get_native( + OakNodeColorManager manager) +{ + return oaknode_c_api::to_native(manager); +} + int oaknode_colormanager_initialize(OakNodeColorManager manager) { olive::ColorManager *cm = diff --git a/src/node/c_api/node.cpp b/src/node/c_api/node.cpp index 887f37e1a..0ae5e11b9 100644 --- a/src/node/c_api/node.cpp +++ b/src/node/c_api/node.cpp @@ -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( - oaknode_c_api::to_native(node)->video_frame_cache()); + olive::Node *n = oaknode_c_api::to_native(node); + if (!n) { + *out = OakRenderCache{}; + return OAKNODE_OK; + } + *out = n->video_frame_cache(); + if (out->ctx) { + out->addref(out->ctx); + } return OAKNODE_OK; } diff --git a/src/node/src/block/clip/clip.cpp b/src/node/src/block/clip/clip.cpp index 8f6171346..292d79f7a 100644 --- a/src/node/src/block/clip/clip.cpp +++ b/src/node/src/block/clip/clip.cpp @@ -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( + 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 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 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() diff --git a/src/node/src/block/clip/clip.h b/src/node/src/block/clip/clip.h index 2f4978b35..172b37cc1 100644 --- a/src/node/src/block/clip/clip.h +++ b/src/node/src/block/clip/clip.h @@ -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; diff --git a/src/node/src/color/displaytransform/displaytransform.cpp b/src/node/src/color/displaytransform/displaytransform.cpp index 7a0f5ebd5..bf12197c0 100644 --- a/src/node/src/color/displaytransform/displaytransform.cpp +++ b/src/node/src/color/displaytransform/displaytransform.cpp @@ -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); } } diff --git a/src/node/src/color/ociobase/ociobase.cpp b/src/node/src/color/ociobase/ociobase.cpp index a6893a0cf..c46057ede 100644 --- a/src/node/src/color/ociobase/ociobase.cpp +++ b/src/node/src/color/ociobase/ociobase.cpp @@ -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); diff --git a/src/node/src/color/ociobase/ociobase.h b/src/node/src/color/ociobase/ociobase.h index d5ab58982..f7d694879 100644 --- a/src/node/src/color/ociobase/ociobase.h +++ b/src/node/src/color/ociobase/ociobase.h @@ -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_ = {}; }; } diff --git a/src/node/src/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp b/src/node/src/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp index ea7935023..2d59bd33b 100644 --- a/src/node/src/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp +++ b/src/node/src/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp @@ -20,6 +20,8 @@ ***/ #include "ociogradingtransformlinear.h" +#include "node/colormanager.h" +#include "render/color.h" #include #include @@ -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: diff --git a/src/node/src/color/ociogradingtransformlog/ociogradingtransformlog.cpp b/src/node/src/color/ociogradingtransformlog/ociogradingtransformlog.cpp index e6f4464c2..2635b5247 100644 --- a/src/node/src/color/ociogradingtransformlog/ociogradingtransformlog.cpp +++ b/src/node/src/color/ociogradingtransformlog/ociogradingtransformlog.cpp @@ -20,6 +20,8 @@ ***/ #include "ociogradingtransformlog.h" +#include "node/colormanager.h" +#include "render/color.h" #include #include @@ -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: diff --git a/src/node/src/color/ociolut/ociolut.cpp b/src/node/src/color/ociolut/ociolut.cpp index 918cfad8e..6cf1025d4 100644 --- a/src/node/src/color/ociolut/ociolut.cpp +++ b/src/node/src/color/ociolut/ociolut.cpp @@ -19,6 +19,9 @@ ***/ #include "ociolut.h" +#include "node/colormanager.h" +#include "render/color.h" +#include "render/manager.h" #include #include @@ -47,9 +50,20 @@ bool is_main_process() // Qt-free replacement for qobject_cast(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 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(this)->set_processor(nullptr); - last_processor_.reset(); + const_cast(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(this)->set_processor(nullptr); - last_processor_.reset(); + const_cast(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(this)->set_processor(nullptr); - last_processor_.reset(); + const_cast(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(this)->set_processor(nullptr); - last_processor_.reset(); + const_cast(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( - 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(this)->set_processor(processor); processor_dirty_ = false; diff --git a/src/node/src/color/ociolut/ociolut.h b/src/node/src/color/ociolut/ociolut.h index 66f3f890e..571ada742 100644 --- a/src/node/src/color/ociolut/ociolut.h +++ b/src/node/src/color/ociolut/ociolut.h @@ -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_; }; diff --git a/src/node/src/keying/chromakey/chromakey.cpp b/src/node/src/keying/chromakey/chromakey.cpp index 98e1310f3..6cb75220c 100644 --- a/src/node/src/keying/chromakey/chromakey.cpp +++ b/src/node/src/keying/chromakey/chromakey.cpp @@ -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"); diff --git a/src/node/src/node.cpp b/src/node/src/node.cpp index a5207a6fb..19feec445 100644 --- a/src/node/src/node.cpp +++ b/src/node/src/node.cpp @@ -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( + 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 diff --git a/src/node/src/node.h b/src/node/src/node.h index b05bfa9d1..0f4b6f19d 100644 --- a/src/node/src/node.h +++ b/src/node/src/node.h @@ -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_; }; diff --git a/src/node/src/output/viewer/viewer.cpp b/src/node/src/output/viewer/viewer.cpp index d7ddb7912..60c565e9e 100644 --- a/src/node/src/output/viewer/viewer.cpp +++ b/src/node/src/output/viewer/viewer.cpp @@ -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( + 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 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]))); + } } } } diff --git a/src/node/src/output/viewer/viewer.h b/src/node/src/output/viewer/viewer.h index 3014f37e3..4fd3fbd91 100644 --- a/src/node/src/output/viewer/viewer.h +++ b/src/node/src/output/viewer/viewer.h @@ -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 {}; } } diff --git a/src/node/src/project.cpp b/src/node/src/project.cpp index 103cd7a73..fad5f974b 100644 --- a/src/node/src/project.cpp +++ b/src/node/src/project.cpp @@ -31,6 +31,7 @@ #include #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() diff --git a/src/node/src/project/serializer/serializer220403.cpp b/src/node/src/project/serializer/serializer220403.cpp index dd2402782..c8f75b951 100644 --- a/src/node/src/project/serializer/serializer220403.cpp +++ b/src/node/src/project/serializer/serializer220403.cpp @@ -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(); } diff --git a/src/node/src/traverser.cpp b/src/node/src/traverser.cpp index b84965a84..2d5442b77 100644 --- a/src/node/src/traverser.cpp +++ b/src/node/src/traverser.cpp @@ -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 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))); } diff --git a/src/node/tests/colormanager_test.cpp b/src/node/tests/colormanager_test.cpp index 51a74867f..c036788bd 100644 --- a/src/node/tests/colormanager_test.cpp +++ b/src/node/tests/colormanager_test.cpp @@ -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); +} diff --git a/src/render/c_api/cache.cpp b/src/render/c_api/cache.cpp index feed4ff24..5de7af51f 100644 --- a/src/render/c_api/cache.cpp +++ b/src/render/c_api/cache.cpp @@ -21,13 +21,18 @@ #include "../../../include/render/cache.h" #include +#include #include #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( + 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(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(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( + cache, true, &oakrender_c_api::delete_as); +} + +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(context); + auto *viewer = dynamic_cast(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(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 &all = + c->get_passthroughs(); + int count = static_cast(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(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) { diff --git a/src/render/c_api/color.cpp b/src/render/c_api/color.cpp index 592b5a2e5..1bb83f199 100644 --- a/src/render/c_api/color.cpp +++ b/src/render/c_api/color.cpp @@ -30,6 +30,7 @@ #include "color/colormanager/colormanager.h" #include "filefunctions.h" +#include "lutlibrary.h" #include 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( + impl, true, &oakrender_c_api::delete_as); + } 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( + impl, true, &oakrender_c_api::delete_as); + } 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( + impl, true, &oakrender_c_api::delete_as); + } catch (...) { + return OakColorProcessor{}; + } +} + +std::shared_ptr oakrender_color_processor_get_native( + OakColorProcessor processor) +{ + OakColorProcessorImpl *p = + oakrender_c_api::to_native(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( + olive::LUTLibrary::supported_extensions().size()); +} + +int oakrender_lut_supported_extension_at(int index, char *buf, + int buf_size) +{ + const std::vector &all = + olive::LUTLibrary::supported_extensions(); + if (index < 0 || index >= static_cast(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, diff --git a/src/render/c_api/manager.cpp b/src/render/c_api/manager.cpp index b2e7bb9f8..cd2593503 100644 --- a/src/render/c_api/manager.cpp +++ b/src/render/c_api/manager.cpp @@ -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(); diff --git a/src/render/c_api/ticket.cpp b/src/render/c_api/ticket.cpp index 0539e0b6d..aecb8fa06 100644 --- a/src/render/c_api/ticket.cpp +++ b/src/render/c_api/ticket.cpp @@ -149,9 +149,11 @@ OakRenderTicket oakrender_ticket_render_frame( } } - if (params->cache) { - rvp.add_cache(reinterpret_cast( - params->cache)); + if (params->cache.ctx) { + if (auto *fc = dynamic_cast( + oakrender_cache_get_native(params->cache))) { + rvp.add_cache(fc); + } } auto *watcher = new olive::RenderTicketWatcher(); diff --git a/src/render/src/previewautocacher.cpp b/src/render/src/previewautocacher.cpp index 456bf303e..b2ec5ee61 100644 --- a/src/render/src/previewautocacher.cpp +++ b/src/render/src/previewautocacher.cpp @@ -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() diff --git a/src/render/src/rendermanager.h b/src/render/src/rendermanager.h index d16fecef9..5d335258b 100644 --- a/src/render/src/rendermanager.h +++ b/src/render/src/rendermanager.h @@ -31,6 +31,7 @@ #include "configaccessor.h" #include "colorprocessorcache.h" +#include "framehashcache.h" #include "output/viewer/viewer.h" #include "project.h" #include "traverser.h" diff --git a/src/render/tests/cache_test.cpp b/src/render/tests/cache_test.cpp index 2a973e406..633b772c0 100644 --- a/src/render/tests/cache_test.cpp +++ b/src/render/tests/cache_test.cpp @@ -22,6 +22,12 @@ #include +#include + +#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 buf(static_cast(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 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); +} diff --git a/src/render/tests/color_test.cpp b/src/render/tests/color_test.cpp index 31ef3e509..3e7eb3a79 100644 --- a/src/render/tests/color_test.cpp +++ b/src/render/tests/color_test.cpp @@ -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); +} diff --git a/src/render/tests/manager_test.cpp b/src/render/tests/manager_test.cpp index 9efcb0828..9d1d9f785 100644 --- a/src/render/tests/manager_test.cpp +++ b/src/render/tests/manager_test.cpp @@ -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); +} diff --git a/src/task/src/export/export.cpp b/src/task/src/export/export.cpp index e3d96f033..75e3433f0 100644 --- a/src/task/src/export/export.cpp +++ b/src/task/src/export/export.cpp @@ -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; diff --git a/src/task/src/precache/precachetask.cpp b/src/task/src/precache/precachetask.cpp index 7aa823eed..63afbd2e3 100644 --- a/src/task/src/precache/precachetask.cpp +++ b/src/task/src/precache/precachetask.cpp @@ -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); diff --git a/src/task/src/render/render.cpp b/src/task/src/render/render.cpp index c7123cc48..2c7b02646 100644 --- a/src/task/src/render/render.cpp +++ b/src/task/src/render/render.cpp @@ -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); diff --git a/src/task/src/render/render.h b/src/task/src/render/render.h index 3ad3c40d1..0e8bb4392 100644 --- a/src/task/src/render/render.h +++ b/src/task/src/render/render.h @@ -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_;