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
+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);
}