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:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "configaccessor.h"
|
||||
#include "colorprocessorcache.h"
|
||||
#include "framehashcache.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
#include "project.h"
|
||||
#include "traverser.h"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user