From 0462842f8c3f50981f8acec088b36e4583b29315 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Fri, 7 Aug 2026 18:05:34 +0800 Subject: [PATCH] refactor(render,timeline,task): switch remaining handles to refcounted value structs - oakrender: renderer/texture/frame/cache/colorprocessor/ticket/copier all become by-value {ctx, addref, release, abi_version} handles over a generic box; retain() folds into addref; new oakrender_cache_wrap_borrowed for native caches from oaknode - oaktimeline: marker list/workarea borrowed handles become value handles (non-owning boxes) with explicit free - oaktask: OakTaskTask becomes a value handle; ownership still moves to the manager on start (owns flag flips) - consumers (timeline/task/codec sources) migrated; tests everywhere updated; suites green: render 45, timeline 117, task 106, node 96, common 193, codec 18, audio 36 --- include/render/cache.h | 72 +++++--- include/render/cancelatom.h | 10 -- include/render/color.h | 47 +++-- include/render/copier.h | 35 +++- include/render/error.h | 10 ++ include/render/manager.h | 16 +- include/render/renderer.h | 183 +++++++++++++------- include/render/ticket.h | 60 ++++--- include/task/error.h | 2 + include/task/manager.h | 5 +- include/task/project.h | 53 +++--- include/task/task.h | 41 +++-- include/timeline/error.h | 2 + include/timeline/marker.h | 52 ++++-- include/timeline/workarea.h | 39 +++-- src/codec/src/decoder.cpp | 29 ++-- src/codec/src/decoder.h | 8 +- src/codec/src/ffmpeg/ffmpegdecoder.cpp | 24 +-- src/codec/src/ffmpeg/ffmpegdecoder.h | 4 +- src/codec/src/oiio/oiiodecoder.cpp | 4 +- src/codec/src/oiio/oiiodecoder.h | 2 +- src/render/c_api/cache.cpp | 86 ++++----- src/render/c_api/color.cpp | 55 +++--- src/render/c_api/copier.cpp | 28 +-- src/render/c_api/internalhandles.h | 179 +++++++++++++++++-- src/render/c_api/manager.cpp | 19 +- src/render/c_api/renderer.cpp | 230 +++++++++++++------------ src/render/c_api/ticket.cpp | 124 +++++++------ src/render/tests/cache_test.cpp | 101 ++++++----- src/render/tests/color_test.cpp | 39 +++-- src/render/tests/manager_test.cpp | 4 +- src/render/tests/renderer_test.cpp | 74 ++++---- src/render/tests/ticket_test.cpp | 22 +-- src/task/c_api/manager.cpp | 6 +- src/task/c_api/project.cpp | 92 +++++----- src/task/c_api/task.cpp | 73 ++++---- src/task/c_api/taskhandle.h | 71 ++++++-- src/task/src/export/export.cpp | 24 ++- src/task/src/export/export.h | 10 +- src/task/src/precache/precachetask.cpp | 19 +- src/task/src/precache/precachetask.h | 2 +- src/task/src/render/render.cpp | 30 ++-- src/task/src/render/render.h | 12 +- src/task/tests/task_test.cpp | 86 ++++----- src/timeline/c_api/marker.cpp | 61 ++++--- src/timeline/c_api/timelinehandle.h | 125 ++++++++++++++ src/timeline/c_api/workarea.cpp | 49 +++--- src/timeline/tests/timeline_test.cpp | 62 +++---- 48 files changed, 1462 insertions(+), 919 deletions(-) create mode 100644 src/timeline/c_api/timelinehandle.h diff --git a/include/render/cache.h b/include/render/cache.h index eb33bf49f..e5a881f34 100644 --- a/include/render/cache.h +++ b/include/render/cache.h @@ -38,10 +38,13 @@ extern "C" { * @brief C ABI for the oakrender playback/frame-hash caches * (olive::PlaybackCache / olive::FrameHashCache), M7 §2.2. * - * An OakRenderCache IS a reinterpreted olive::FrameHashCache (created - * without a parent node), no wrapper allocation. Handles from - * oakrender_cache_create() are owned by the caller and must be released - * with oakrender_cache_free(). + * An OakRenderCache is a by-value reference-counted handle (shared_ptr + * semantics, see oakcommon's common/handle.h) boxing an + * olive::FrameHashCache (created without a parent node). Handles from + * oakrender_cache_create() are owned by the caller (reference count 1) + * and must be released with oakrender_cache_free(); handles from + * oakrender_cache_wrap_borrowed() are borrowed (release only frees the + * box). * * All timestamps are int64 frame numbers in the cache's timebase (see * oakrender_cache_set_timebase()); a cache without a valid timebase @@ -51,27 +54,48 @@ extern "C" { * invalidate/validate are triggered by and known to the caller; the * facade re-emits notifications after the triggering command. */ -typedef struct OakRenderCache OakRenderCache; +typedef struct OakRenderCache { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakRenderCache; /** * @brief Create a detached frame hash cache (no parent node, no * timebase). Owned by the caller. * - * @return Cache handle, or NULL on allocation failure. + * @return Cache handle with reference count 1; ctx is NULL on + * allocation failure. */ -OakRenderCache *oakrender_cache_create(void); +OakRenderCache oakrender_cache_create(void); -/** @brief Destroy a cache created by oakrender_cache_create(). NULL-safe. */ +/** + * @brief Release one reference to a cache created by + * oakrender_cache_create(). Convenience wrapper around + * cache->release(cache->ctx). NULL / empty-handle no-op; clears + * cache->ctx after releasing. + */ void oakrender_cache_free(OakRenderCache *cache); +/** + * @brief Borrowed handle wrapping a native frame cache pointer obtained + * through oaknode (oaknode_node_get_video_frame_cache()). + * + * The cache itself stays owned by its node: release() on this handle + * only frees the box. Empty handle (ctx == NULL) for a NULL native + * pointer. + */ +OakRenderCache oakrender_cache_wrap_borrowed(void *native_cache); + /** * @brief Set the frame timebase used to interpret all timestamps of this * cache (FrameHashCache::set_timebase()). * - * @return OAKRENDER_OK, or OAKRENDER_E_INVALID for NULL cache or + * @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty cache or * non-positive num/den. */ -int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den); +int oakrender_cache_set_timebase(OakRenderCache cache, int num, int den); /** * @brief Set the cache UUID used in on-disk frame cache filenames @@ -79,27 +103,27 @@ int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den); * * @return OAKRENDER_OK or OAKRENDER_E_INVALID. */ -int oakrender_cache_set_uuid(OakRenderCache *cache, const char *uuid); +int oakrender_cache_set_uuid(OakRenderCache cache, const char *uuid); /** * @brief Mark the timestamp range [in_ts, out_ts) invalidated - * (PlaybackCache::invalidate()). NULL cache is a no-op. + * (PlaybackCache::invalidate()). Empty cache is a no-op. */ -void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts, +void oakrender_cache_invalidate(OakRenderCache cache, int64_t in_ts, int64_t out_ts); /** * @brief Mark the timestamp range [in_ts, out_ts) validated - * (PlaybackCache::validate()). NULL cache is a no-op. + * (PlaybackCache::validate()). Empty cache is a no-op. */ -void oakrender_cache_validate(OakRenderCache *cache, int64_t in_ts, +void oakrender_cache_validate(OakRenderCache cache, int64_t in_ts, int64_t out_ts); /** * @brief 1 when the cache holds any validated range - * (PlaybackCache::has_validated_ranges()), 0 otherwise / on NULL. + * (PlaybackCache::has_validated_ranges()), 0 otherwise / empty. */ -int oakrender_cache_has_validated_ranges(const OakRenderCache *cache); +int oakrender_cache_has_validated_ranges(OakRenderCache cache); /** * @brief Timeline cache indicator height in pixels @@ -117,7 +141,7 @@ int oakrender_cache_indicator_height(void); * * @return Range count (>= 0), or a negative OAKRENDER_E_* code. */ -int oakrender_cache_get_invalidated_ranges(OakRenderCache *c, +int oakrender_cache_get_invalidated_ranges(OakRenderCache c, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den, int64_t *ranges, int max_ranges); @@ -130,20 +154,20 @@ int oakrender_cache_get_invalidated_ranges(OakRenderCache *c, * @param out_frame Receives an owned frame handle (release with * oakrender_codec_frame_free()). * - * @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL argument), or + * @return OAKRENDER_OK, OAKRENDER_E_INVALID (empty/NULL argument), or * OAKRENDER_E_NOT_FOUND (no cached frame at `ts` / undecodable). */ -int oakrender_frame_cache_load(OakRenderCache *cache, const char *path, +int oakrender_frame_cache_load(OakRenderCache cache, const char *path, const char *uuid, int64_t ts, - OakCodecFrame **out_frame); + OakCodecFrame *out_frame); /** * @brief Save a frame to the disk cache under the cache's timebase and * the frame's own timestamp (FrameHashCache::save_cache_frame()). - * NULL arguments are a no-op. + * Empty/NULL arguments are a no-op. */ -void oakrender_frame_cache_save(OakRenderCache *cache, const char *path, - const char *uuid, const OakCodecFrame *frame); +void oakrender_frame_cache_save(OakRenderCache cache, const char *path, + const char *uuid, OakCodecFrame frame); /* ---- Debug --------------------------------------------------------------- */ diff --git a/include/render/cancelatom.h b/include/render/cancelatom.h index f7ae42604..df06ed8f1 100644 --- a/include/render/cancelatom.h +++ b/include/render/cancelatom.h @@ -37,16 +37,6 @@ namespace olive { class CancelAtom; } extern "C" { #endif -/** - * @brief Current ABI version stamped into every oakrender handle. - * - * Bump whenever a handle layout or the semantics of any exported function - * change incompatibly. Consumers should compare a handle's abi_version - * field against the value they were compiled with before dereferencing - * ctx. - */ -#define OAKRENDER_ABI_VERSION 1 - /** * @file cancelatom.h * @brief C ABI for the oakrender cancellation primitive diff --git a/include/render/color.h b/include/render/color.h index 6c9eef99f..a8277ae7b 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -34,10 +34,11 @@ extern "C" { * the process-wide default OCIO config (olive::ColorManager * statics), M7 §2.3. * - * An OakColorProcessor IS a wrapper allocation holding a - * ColorProcessorPtr (ColorProcessor is shared_ptr-managed); release with - * oakrender_color_processor_free(). NULL is accepted by every function - * and yields a no-op / OAKRENDER_E_INVALID. + * An OakColorProcessor is a by-value reference-counted handle (shared_ptr + * semantics, see oakcommon's common/handle.h) boxing a ColorProcessorPtr + * (ColorProcessor is shared_ptr-managed); release with + * oakrender_color_processor_free(). Empty handles (ctx == NULL) are + * accepted by every function and yield a no-op / OAKRENDER_E_INVALID. * * Processors are built against the process-wide default OCIO config * (olive::ColorManager::get_default_config()): the $OCIO config when the @@ -52,7 +53,12 @@ enum { OAKRENDER_COLOR_DIRECTION_INVERSE = 1 }; -typedef struct OakColorProcessor OakColorProcessor; +typedef struct OakColorProcessor { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakColorProcessor; /** * @brief Create a colorspace-to-colorspace processor on the default @@ -67,29 +73,34 @@ typedef struct OakColorProcessor OakColorProcessor; * still returned but oakrender_color_processor_is_valid() reports 0 and * conversions are pass-through. * - * @return Processor handle, or NULL for NULL/empty strings, an unknown - * direction, no default config, or allocation failure. + * @return Processor handle with reference count 1; ctx is NULL for + * NULL/empty strings, an unknown direction, no default config, + * or allocation failure. */ -OakColorProcessor *oakrender_color_processor_create(const char *src_space, - const char *dst_transform, - int direction); +OakColorProcessor oakrender_color_processor_create(const char *src_space, + const char *dst_transform, + int direction); -/** @brief Release a processor handle. NULL-safe no-op. */ +/** + * @brief Release one reference to a processor handle. Convenience + * wrapper around processor->release(processor->ctx). NULL / + * empty-handle no-op; clears processor->ctx after releasing. + */ void oakrender_color_processor_free(OakColorProcessor *processor); /** * @brief 1 when the processor holds a valid OCIO processor - * (ColorProcessor::get_processor() != null), 0 otherwise / on NULL. + * (ColorProcessor::get_processor() != null), 0 otherwise / empty. */ -int oakrender_color_processor_is_valid(const OakColorProcessor *processor); +int oakrender_color_processor_is_valid(OakColorProcessor processor); /** * @brief Convert a single RGBA color (ColorProcessor::convert_color()). * On an invalid processor the input is copied through. * - * @return OAKRENDER_OK, or OAKRENDER_E_INVALID for NULL arguments. + * @return OAKRENDER_OK, or OAKRENDER_E_INVALID for empty/NULL arguments. */ -int oakrender_color_processor_convert(OakColorProcessor *processor, +int oakrender_color_processor_convert(OakColorProcessor processor, double ir, double ig, double ib, double ia, double *out_r, double *out_g, double *out_b, double *out_a); @@ -105,11 +116,11 @@ int oakrender_color_processor_convert(OakColorProcessor *processor, * non-fatal) is a pass-through and returns OAKRENDER_OK, mirroring the * C++ API. * - * @return OAKRENDER_OK, OAKRENDER_E_INVALID for NULL/uninitialized + * @return OAKRENDER_OK, OAKRENDER_E_INVALID for empty/uninitialized * arguments, or OAKRENDER_E_FAILED on an internal exception. */ -int oakrender_color_processor_convert_frame(OakColorProcessor *processor, - OakCodecFrame *frame); +int oakrender_color_processor_convert_frame(OakColorProcessor processor, + OakCodecFrame frame); /* ---- ColorManager statics ------------------------------------------------- */ diff --git a/include/render/copier.h b/include/render/copier.h index dbf226966..91987ded6 100644 --- a/include/render/copier.h +++ b/include/render/copier.h @@ -30,35 +30,52 @@ extern "C" { #endif /** - * @brief Opaque handle to a project copier (olive::ProjectCopier): - * deep-copies a project graph for background processing - * (export/precache). + * @brief Reference-counted handle to a project copier + * (olive::ProjectCopier): deep-copies a project graph for + * background processing (export/precache). + * + * By-value handle (shared_ptr semantics, see oakcommon's + * common/handle.h): oakrender_project_copier_create() returns a handle + * with reference count 1; release it with + * oakrender_project_copier_free(). */ -typedef struct OakRenderProjectCopier OakRenderProjectCopier; +typedef struct OakRenderProjectCopier { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakRenderProjectCopier; /** * @brief Create a copier. The copy is built by * oakrender_project_copier_set_project(). + * + * @return Copier handle with reference count 1; ctx is NULL on + * allocation failure. */ -OakRenderProjectCopier *oakrender_project_copier_create(void); +OakRenderProjectCopier oakrender_project_copier_create(void); -/** @brief Free the copier AND its copied project. NULL-safe. */ +/** + * @brief Release one reference to a copier; the final release frees the + * copier AND its copied project. NULL / empty-handle no-op; clears + * copier->ctx after releasing. + */ void oakrender_project_copier_free(OakRenderProjectCopier *copier); /** @brief (Re)build the copy from `project` (borrowed handle). */ -int oakrender_project_copier_set_project(OakRenderProjectCopier *copier, +int oakrender_project_copier_set_project(OakRenderProjectCopier copier, OakNodeProject project); /** @brief The copied counterpart of an original node (borrowed handle; * freeing it only releases the handle box), empty handle when the * node is not in the copied project. */ OakNodeNode oakrender_project_copier_get_copy( - OakRenderProjectCopier *copier, OakNodeNode original); + OakRenderProjectCopier copier, OakNodeNode original); /** @brief The copied project (borrowed handle; freeing it only releases * the handle box). */ OakNodeProject oakrender_project_copier_get_copied_project( - OakRenderProjectCopier *copier); + OakRenderProjectCopier copier); #ifdef __cplusplus } diff --git a/include/render/error.h b/include/render/error.h index 87e309fcc..f64a9886f 100644 --- a/include/render/error.h +++ b/include/render/error.h @@ -29,6 +29,16 @@ * failure. String getters return the required buffer size in bytes * (including the terminating NUL) as a non-negative value instead. */ +/** + * @brief Current ABI version stamped into every oakrender handle. + * + * Bump whenever a handle layout or the semantics of any exported function + * change incompatibly. Consumers should compare a handle's abi_version + * field against the value they were compiled with before dereferencing + * ctx. + */ +#define OAKRENDER_ABI_VERSION 1 + #define OAKRENDER_OK 0 /**< Success. */ #define OAKRENDER_E_INVALID (-1) /**< NULL handle or invalid argument. */ #define OAKRENDER_E_STATE (-2) /**< Call not valid in the current state. */ diff --git a/include/render/manager.h b/include/render/manager.h index 6fa440aca..a4b76919c 100644 --- a/include/render/manager.h +++ b/include/render/manager.h @@ -46,9 +46,9 @@ extern "C" { * The frame request callback is the asynchronous command return channel * (M7 §2.2 note): it fires on a render worker thread, possibly after * cancellation. The delivered OakCodecFrame is owned by the callback - * recipient (release with oakrender_codec_frame_free()); a NULL frame - * signals "no result" (cancelled or failed). Beyond this callback there - * are no event subscription interfaces. + * recipient (release with oakrender_codec_frame_free()); an empty frame + * (ctx == NULL) signals "no result" (cancelled or failed). Beyond this + * callback there are no event subscription interfaces. */ /** @@ -69,11 +69,11 @@ void oakrender_manager_shutdown(void); /** * @brief Completion callback of an asynchronous frame request. * - * @param frame Owned frame handle, or NULL when the request finished - * without a result (cancelled/failed). + * @param frame Owned frame handle, or an empty handle (ctx == NULL) + * when the request finished without a result (cancelled/failed). * @param ts The request's timestamp, passed back verbatim. */ -typedef void (*oakrender_frame_ready_fn)(OakCodecFrame *frame, int64_t ts, +typedef void (*oakrender_frame_ready_fn)(OakCodecFrame frame, int64_t ts, void *userdata); /** @@ -114,11 +114,11 @@ int oakrender_set_cacher_multicam(OakNodeNode multicam_or_NULL); /** * @brief Set the display color processor on the manager's auto-cacher * (PreviewAutoCacher::set_display_color_processor()). Borrowed handle, - * NULL to clear. + * empty ctx to clear. * * @return OAKRENDER_OK or OAKRENDER_E_STATE. */ -int oakrender_set_display_color_processor(OakColorProcessor *p_or_NULL); +int oakrender_set_display_color_processor(OakColorProcessor p_or_NULL); /* ---- Disk cache (olive::DiskManager) -------------------------------------- */ diff --git a/include/render/renderer.h b/include/render/renderer.h index 2fb7ab5dd..9cd152f7c 100644 --- a/include/render/renderer.h +++ b/include/render/renderer.h @@ -38,20 +38,25 @@ extern "C" { * (docs/zh/plans/completed/r7-pure-abi-plan.md §A.2) with the * oakrender_ prefix (M7 §2.1). * - * Ownership protocol: textures and frames are opaque handles pointing to - * oakrender-heap control blocks (internally holding std::shared_ptr; - * invisible to the ABI). Ownership transfers via explicit retain/free. - * Every retain must be paired with exactly one free. NULL is accepted by - * every function and yields a no-op / zero result / OAKRENDER_E_INVALID. + * Ownership protocol: every public handle is a by-value + * reference-counted struct (see oakcommon's common/handle.h; shared_ptr + * semantics). init/create functions return a handle with reference + * count 1, handle.addref(handle.ctx) takes another reference, and + * handle.release(handle.ctx) (or the oakrender_*_free() convenience + * wrappers, which also null the caller's ctx) drops one; the object is + * destroyed in this library when the count reaches zero. Empty handles + * (ctx == NULL) are accepted by every function and yield a no-op / zero + * result / OAKRENDER_E_INVALID. * - * Cross-thread handoff (§A.3): the producing side retains before - * publishing a handle into a shared slot; the consuming side frees the - * handle it replaced. The side holding the slot when it is torn down - * frees the remaining handle. + * Cross-thread handoff (§A.3): the producing side addrefs before + * publishing a handle into a shared slot; the consuming side releases + * the handle it replaced. The side holding the slot when it is torn + * down releases the remaining handle. * * Handles: - * - OakRenderRenderer IS a reinterpreted olive::Renderer (no wrapper). - * - OakRenderTexture / OakCodecFrame are refcounted control blocks. + * - OakRenderRenderer wraps a native olive::Renderer. + * - OakRenderTexture / OakCodecFrame box shared_ptr-managed engine + * objects. * - `gl_context` is an opaque borrowed olive::OpenGLContext* (or NULL * to let the backend create its own offscreen surface). */ @@ -81,17 +86,42 @@ typedef struct oakrender_video_params { int premultiplied_alpha; /**< 0/1. */ } oakrender_video_params; -typedef struct OakRenderRenderer OakRenderRenderer; -typedef struct OakRenderTexture OakRenderTexture; +/** + * @brief Reference-counted handle to a display renderer + * (olive::Renderer). See the file-level ownership protocol. + */ +typedef struct OakRenderRenderer { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakRenderRenderer; /** - * @brief Opaque CPU frame handle (refcounted control block around an - * olive::FramePtr). Declared here so the cache family (render/cache.h) - * can use the same type; the frame functions live in this header. + * @brief Reference-counted handle to a GPU texture (olive::Texture). + * See the file-level ownership protocol. + */ +typedef struct OakRenderTexture { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakRenderTexture; + +/** + * @brief Reference-counted handle to a CPU frame (an olive::FramePtr + * boxed in a control block). Declared here so the cache family + * (render/cache.h) can use the same type; the frame functions live in + * this header. * Named OakCodecFrame per the M7 §2.2 contract; the oakcodec wave (M5) * adopts the same handle. */ -typedef struct OakCodecFrame OakCodecFrame; +typedef struct OakCodecFrame { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakCodecFrame; /** * @brief Flattened POD of olive::ColorTransformJob for the display blit @@ -99,8 +129,8 @@ typedef struct OakCodecFrame OakCodecFrame; * means identity. */ typedef struct oakrender_color_transform_job { - const void *processor; /**< OakColorProcessor* (borrowed), may be NULL. */ - void *input_texture; /**< OakRenderTexture* (borrowed, not retained). */ + const void *processor; /**< OakColorProcessor ctx (borrowed), may be NULL. */ + void *input_texture; /**< OakRenderTexture ctx (borrowed, not retained). */ int input_alpha_association; /**< 0=none, 1=associated. */ int clear_destination; /**< 0/1. */ int force_opaque; /**< 0/1. */ @@ -115,116 +145,137 @@ typedef struct oakrender_color_transform_job { * "vulkan"; olive::DynamicRenderer). Loads the backend shared library; * falls back per DynamicRenderer rules. * - * @return Renderer handle, or NULL on NULL/empty backend id, load - * failure, or allocation failure. + * @return Renderer handle with reference count 1; ctx is NULL on + * NULL/empty backend id, load failure, or allocation failure. */ -OakRenderRenderer *oakrender_display_renderer_create_dynamic( +OakRenderRenderer oakrender_display_renderer_create_dynamic( const char *backend_id); /** * @brief Create an OpenGL renderer (olive::OpenGLRenderer). The renderer * is not initialized; call oakrender_display_renderer_init() before use. * - * @return Renderer handle, or NULL on allocation failure. + * @return Renderer handle with reference count 1; ctx is NULL on + * allocation failure. */ -OakRenderRenderer *oakrender_display_renderer_create_opengl(void); +OakRenderRenderer oakrender_display_renderer_create_opengl(void); /** * @brief Initialize a renderer. `gl_context` is a borrowed opaque * olive::OpenGLContext*, or NULL to use the backend's default * device/context path (Renderer::init()). * - * @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL renderer), or + * @return OAKRENDER_OK, OAKRENDER_E_INVALID (empty renderer), or * OAKRENDER_E_FAILED (backend init failed). */ -int oakrender_display_renderer_init(OakRenderRenderer *renderer, +int oakrender_display_renderer_init(OakRenderRenderer renderer, void *gl_context); /** - * @brief Destroy a renderer (Renderer::destroy() + delete). NULL-safe - * no-op. + * @brief Release one reference to a renderer (the final release runs + * Renderer::destroy() + delete). Convenience wrapper around + * renderer->release(renderer->ctx): NULL / empty-handle no-op; clears + * renderer->ctx after releasing. */ void oakrender_display_renderer_destroy(OakRenderRenderer *renderer); /* ---- Renderer queries ---------------------------------------------------- */ -/** @brief 1 when the renderer is OpenGL-based, 0 otherwise / on NULL. */ -int oakrender_display_renderer_is_open_gl(const OakRenderRenderer *renderer); +/** @brief 1 when the renderer is OpenGL-based, 0 otherwise / empty. */ +int oakrender_display_renderer_is_open_gl(OakRenderRenderer renderer); -/** @brief 1 when the renderer is Vulkan-based, 0 otherwise / on NULL. */ -int oakrender_display_renderer_is_vulkan(const OakRenderRenderer *renderer); +/** @brief 1 when the renderer is Vulkan-based, 0 otherwise / empty. */ +int oakrender_display_renderer_is_vulkan(OakRenderRenderer renderer); -/* ---- Texture handle (opaque, refcounted) --------------------------------- */ +/* ---- Texture handle ------------------------------------------------------ */ /** * @brief Create a GPU texture on `renderer`. * * @param pixels Initial pixel data, or NULL for an uninitialized texture. * @param linesize Stride of `pixels` in bytes (0 when pixels is NULL). - * @return New texture handle (refcount=1), or NULL on invalid arguments / - * allocation failure. + * @return New texture handle (reference count 1); ctx is NULL on invalid + * arguments / allocation failure. */ -OakRenderTexture *oakrender_display_texture_create( - OakRenderRenderer *renderer, const oakrender_video_params *params, +OakRenderTexture oakrender_display_texture_create( + OakRenderRenderer renderer, const oakrender_video_params *params, const void *pixels, int linesize); -/** @brief Increment refcount, return the same handle. NULL-safe. */ -OakRenderTexture *oakrender_display_texture_retain(OakRenderTexture *texture); +/** + * @brief Take another reference to a texture and return the same handle. + * + * Convenience wrapper around handle.addref(handle.ctx). An empty handle + * in yields an empty handle out. Every retain must be paired with + * exactly one free/release. + */ +OakRenderTexture oakrender_display_texture_retain(OakRenderTexture texture); -/** @brief Decrement refcount; frees at zero. NULL-safe. */ +/** + * @brief Release one reference to a texture. Convenience wrapper around + * texture->release(texture->ctx): frees the texture when the count + * reaches zero. NULL / empty-handle no-op; clears texture->ctx after + * releasing. + */ void oakrender_display_texture_free(OakRenderTexture *texture); -int oakrender_display_texture_upload(OakRenderTexture *texture, +int oakrender_display_texture_upload(OakRenderTexture texture, const void *pixels, int linesize); -int oakrender_display_texture_download(OakRenderTexture *texture, void *pixels, +int oakrender_display_texture_download(OakRenderTexture texture, void *pixels, int linesize); /* ---- Texture queries ----------------------------------------------------- */ -int oakrender_display_texture_get_params(const OakRenderTexture *texture, +int oakrender_display_texture_get_params(OakRenderTexture texture, oakrender_video_params *out); -/** @brief Native texture id (0 on NULL or a dummy/id-less texture). */ -int oakrender_display_texture_id(const OakRenderTexture *texture); +/** @brief Native texture id (0 on empty or a dummy/id-less texture). */ +int oakrender_display_texture_id(OakRenderTexture texture); -/* ---- Frame handle (opaque, refcounted) ----------------------------------- */ +/* ---- Frame handle -------------------------------------------------------- */ -/** @brief Create an empty CPU frame. Returns handle (refcount=1). */ -OakCodecFrame *oakrender_codec_frame_create(void); +/** @brief Create an empty CPU frame. Returns a handle with count 1. */ +OakCodecFrame oakrender_codec_frame_create(void); -/** @brief Increment refcount, return the same handle. NULL-safe. */ -OakCodecFrame *oakrender_codec_frame_retain(OakCodecFrame *frame); +/** + * @brief Take another reference to a frame and return the same handle. + * Empty in yields empty out (see oakrender_display_texture_retain()). + */ +OakCodecFrame oakrender_codec_frame_retain(OakCodecFrame frame); -/** @brief Decrement refcount; frees at zero. NULL-safe. */ +/** + * @brief Release one reference to a frame. Convenience wrapper around + * frame->release(frame->ctx). NULL / empty-handle no-op; clears + * frame->ctx after releasing. + */ void oakrender_codec_frame_free(OakCodecFrame *frame); int oakrender_codec_frame_set_video_params( - OakCodecFrame *frame, const oakrender_video_params *params); + OakCodecFrame frame, const oakrender_video_params *params); -int oakrender_codec_frame_get_params(const OakCodecFrame *frame, +int oakrender_codec_frame_get_params(OakCodecFrame frame, oakrender_video_params *out); /** * @brief Allocate the pixel buffer per the frame's video params * (Frame::allocate()). * - * @return OAKRENDER_OK, OAKRENDER_E_INVALID (NULL frame), or + * @return OAKRENDER_OK, OAKRENDER_E_INVALID (empty frame), or * OAKRENDER_E_FAILED (invalid params / allocation failed). */ -int oakrender_codec_frame_allocate(OakCodecFrame *frame); +int oakrender_codec_frame_allocate(OakCodecFrame frame); -/** @brief Borrowed pixel data pointer (valid until free). */ -void *oakrender_codec_frame_data(OakCodecFrame *frame); +/** @brief Borrowed pixel data pointer (valid until the final release). */ +void *oakrender_codec_frame_data(OakCodecFrame frame); /** @brief Borrowed const pixel data pointer. */ -const void *oakrender_codec_frame_const_data(const OakCodecFrame *frame); +const void *oakrender_codec_frame_const_data(OakCodecFrame frame); /** @brief Line stride in bytes. */ -int oakrender_codec_frame_linesize_bytes(const OakCodecFrame *frame); +int oakrender_codec_frame_linesize_bytes(OakCodecFrame frame); -/** @brief 1 when the pixel buffer is allocated, 0 otherwise / on NULL. */ -int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame); +/** @brief 1 when the pixel buffer is allocated, 0 otherwise / empty. */ +int oakrender_codec_frame_is_allocated(OakCodecFrame frame); /* ---- Color-managed blit -------------------------------------------------- */ @@ -232,18 +283,18 @@ int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame); * @brief Blit a color-managed image through the OCIO pipeline * (Renderer::blit_color_managed()). * - * @param dst_texture Destination texture handle, or NULL for the current - * output target. + * @param dst_texture Destination texture handle, or an empty handle for + * the current output target. * @param params Destination video params, or NULL to use dst_texture's. */ int oakrender_display_renderer_blit_color_managed( - OakRenderRenderer *renderer, const oakrender_color_transform_job *job, - OakRenderTexture *dst_texture, const oakrender_video_params *params); + OakRenderRenderer renderer, const oakrender_color_transform_job *job, + OakRenderTexture dst_texture, const oakrender_video_params *params); /* ---- Cross-backend texture download -------------------------------------- */ int oakrender_display_renderer_download_from_texture( - OakRenderRenderer *renderer, int texture_id, + OakRenderRenderer renderer, int texture_id, const oakrender_video_params *params, void *dst_pixels, int linesize); /* ---- Backend management (M7 §2.1) ---------------------------------------- */ diff --git a/include/render/ticket.h b/include/render/ticket.h index a8f1efa85..eaee23e76 100644 --- a/include/render/ticket.h +++ b/include/render/ticket.h @@ -38,19 +38,29 @@ extern "C" { #endif /** - * @brief Opaque handle to a render ticket (olive::RenderTicketWatcher). + * @brief Reference-counted handle to a render ticket + * (olive::RenderTicketWatcher). * - * Created by oakrender_ticket_render_frame() / - * oakrender_ticket_render_audio(); free with oakrender_ticket_free(). + * By-value handle (shared_ptr semantics, see oakcommon's + * common/handle.h). Created by oakrender_ticket_render_frame() / + * oakrender_ticket_render_audio() with reference count 1; release with + * oakrender_ticket_free(). */ -typedef struct OakRenderTicket OakRenderTicket; +typedef struct OakRenderTicket { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */ +} OakRenderTicket; /** * @brief Finished callback (async command return channel, 01 §4 * exception). Fires on the ticket's finishing thread, exactly - * once (cancelled tickets fire with a NULL result). + * once (cancelled tickets fire with a NULL result). The ticket + * handle is a borrowed copy of the submitter's handle; the + * submitter keeps ownership and releases it. */ -typedef void (*oakrender_ticket_finished_fn)(OakRenderTicket *ticket, +typedef void (*oakrender_ticket_finished_fn)(OakRenderTicket ticket, void *userdata); /** @brief Ticket types (RenderManager::TicketType). */ @@ -77,7 +87,7 @@ typedef struct oakrender_video_ticket_params { int has_force_matrix; int force_format; /**< PixelFormat as int, -1 = off. */ int force_channel_count; /**< 0 = off. */ - OakColorProcessor *force_color_output; /**< May be NULL. */ + 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. */ } oakrender_video_ticket_params; @@ -85,11 +95,12 @@ typedef struct oakrender_video_ticket_params { /** * @brief Submit a video frame render ticket. * - * @return Ticket handle (caller frees), or NULL on failure. The finished - * callback fires exactly once; NULL `cb` is allowed (poll with + * @return Ticket handle with reference count 1 (caller releases); ctx is + * NULL on failure. The finished callback fires exactly once; + * NULL `cb` is allowed (poll with * oakrender_ticket_wait()/oakrender_ticket_is_finished()). */ -OakRenderTicket *oakrender_ticket_render_frame( +OakRenderTicket oakrender_ticket_render_frame( const oakrender_video_ticket_params *params, oakrender_ticket_finished_fn cb, void *userdata); @@ -99,48 +110,51 @@ OakRenderTicket *oakrender_ticket_render_frame( * @param output_node Connected sample output node. * @param params Audio params (borrowed oakcore handle). */ -OakRenderTicket *oakrender_ticket_render_audio( +OakRenderTicket oakrender_ticket_render_audio( OakNodeNode output_node, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den, const OakAudioParams *params, int mode, oakrender_ticket_finished_fn cb, void *userdata); -int oakrender_ticket_is_finished(OakRenderTicket *ticket); +int oakrender_ticket_is_finished(OakRenderTicket ticket); /** @brief Block until the ticket finishes. */ -int oakrender_ticket_wait(OakRenderTicket *ticket); +int oakrender_ticket_wait(OakRenderTicket ticket); -int oakrender_ticket_cancel(OakRenderTicket *ticket); +int oakrender_ticket_cancel(OakRenderTicket ticket); /** @brief OAKRENDER_TICKET_* or negative error. */ -int oakrender_ticket_get_type(OakRenderTicket *ticket); +int oakrender_ticket_get_type(OakRenderTicket ticket); /** @brief Ticket timestamp (video tickets). */ -int oakrender_ticket_get_time(OakRenderTicket *ticket, int64_t *out_num, +int oakrender_ticket_get_time(OakRenderTicket ticket, int64_t *out_num, int64_t *out_den); /** @brief Ticket time range (audio tickets). */ -int oakrender_ticket_get_range(OakRenderTicket *ticket, int64_t *in_num, +int oakrender_ticket_get_range(OakRenderTicket ticket, int64_t *in_num, int64_t *in_den, int64_t *out_num, int64_t *out_den); /** - * @brief The resulting frame (video tickets). *out receives a retained + * @brief The resulting frame (video tickets). *out receives an owned * OakCodecFrame (release with oakrender_codec_frame_free()). * OAKRENDER_E_STATE when unfinished, OAKRENDER_E_FAILED when the * ticket has no frame result. */ -int oakrender_ticket_get_frame(OakRenderTicket *ticket, - OakCodecFrame **out); +int oakrender_ticket_get_frame(OakRenderTicket ticket, OakCodecFrame *out); /** * @brief The resulting samples (audio tickets). *out receives a copy * (release with oakcore_samplebuffer_free()). */ -int oakrender_ticket_get_samples(OakRenderTicket *ticket, +int oakrender_ticket_get_samples(OakRenderTicket ticket, OakSampleBuffer **out); -/** @brief Free the ticket handle (safe on finished tickets; cancels and - * waits on running ones). No-op on NULL. */ +/** + * @brief Release one reference to a ticket (the final release is safe on + * finished tickets; cancels and waits on running ones). Convenience + * wrapper around ticket->release(ticket->ctx). NULL / empty-handle + * no-op; clears ticket->ctx after releasing. + */ void oakrender_ticket_free(OakRenderTicket *ticket); /** diff --git a/include/task/error.h b/include/task/error.h index a91af1e56..0ed906366 100644 --- a/include/task/error.h +++ b/include/task/error.h @@ -29,6 +29,8 @@ * failure. String getters return the required buffer size in bytes * (including the terminating NUL) as a non-negative value instead. */ +#define OAKTASK_ABI_VERSION 1 + #define OAKTASK_OK 0 /**< Success. */ #define OAKTASK_E_INVALID (-1) /**< NULL handle or invalid argument. */ #define OAKTASK_E_STATE (-2) /**< Call not valid in the current state. */ diff --git a/include/task/manager.h b/include/task/manager.h index 9573508a3..acbebcec5 100644 --- a/include/task/manager.h +++ b/include/task/manager.h @@ -42,8 +42,9 @@ int oaktask_register_codec_submitter(void); int oaktask_manager_count(void); -/** @brief Borrowed task at index, or NULL when out of range. */ -OakTaskTask *oaktask_manager_at(int i); +/** @brief Borrowed task at index (release only frees the box), empty + * handle when out of range or no manager. */ +OakTaskTask oaktask_manager_at(int i); void oaktask_manager_delete_finished(void); diff --git a/include/task/project.h b/include/task/project.h index a58ccd9b1..ee2467b2d 100644 --- a/include/task/project.h +++ b/include/task/project.h @@ -38,54 +38,55 @@ extern "C" { * @brief Project task factories and result accessors (M8 §2.2). */ -/** @brief olive::ProjectLoadTask. */ -OakTaskTask *oaktask_create_project_load(const char *filename); +/** @brief olive::ProjectLoadTask. Empty handle (ctx == NULL) on + * failure. */ +OakTaskTask oaktask_create_project_load(const char *filename); /** @brief Take the loaded project (ownership transfer). Empty handle * (ctx == NULL) when the task has not succeeded or the project was * already taken. */ -OakNodeProject oaktask_load_take_project(OakTaskTask *t); +OakNodeProject oaktask_load_take_project(OakTaskTask t); /** @brief olive::ProjectSaveTask. `filename_or_NULL` overrides the * project's own filename. `project` is borrowed by the task. */ -OakTaskTask *oaktask_create_project_save(OakNodeProject project, - const char *filename_or_NULL, - int use_compression); +OakTaskTask oaktask_create_project_save(OakNodeProject project, + const char *filename_or_NULL, + int use_compression); /** @brief olive::ProjectImportTask. `folder`/`project` are borrowed by * the task. */ -OakTaskTask *oaktask_create_project_import(OakNodeFolder folder, - OakNodeProject project, - const char *const *urls, - int url_count); +OakTaskTask oaktask_create_project_import(OakNodeFolder folder, + OakNodeProject project, + const char *const *urls, + int url_count); /** @brief Take the import's undo command (ownership transfer). */ -OakUndoCommand oaktask_import_take_command(OakTaskTask *t); +OakUndoCommand oaktask_import_take_command(OakTaskTask t); -int oaktask_import_footage_count(OakTaskTask *t); +int oaktask_import_footage_count(OakTaskTask t); /** @brief Footage handle at index (addref'd; release with * handle.release(handle.ctx) - box only, the project owns the * footage). Empty handle when out of range. */ -OakNodeFootage oaktask_import_footage_at(OakTaskTask *t, int index); +OakNodeFootage oaktask_import_footage_at(OakTaskTask t, int index); -int oaktask_import_invalid_count(OakTaskTask *t); +int oaktask_import_invalid_count(OakTaskTask t); /** @brief Invalid filename at index (two-stage). */ -int oaktask_import_invalid_at(OakTaskTask *t, int index, char *buf, - int buf_size); +int oaktask_import_invalid_at(OakTaskTask t, int index, char *buf, + int buf_size); -/** @brief olive::LoadOTIOTask. */ -OakTaskTask *oaktask_create_project_load_otio(const char *filename); +/** @brief olive::LoadOTIOTask. Empty handle (ctx == NULL) on failure. */ +OakTaskTask oaktask_create_project_load_otio(const char *filename); /** @brief Take the loaded project (ownership transfer). Empty handle * (ctx == NULL) when the task has not succeeded or the project was * already taken. */ -OakNodeProject oaktask_load_otio_take_project(OakTaskTask *t); +OakNodeProject oaktask_load_otio_take_project(OakTaskTask t); /** @brief olive::SaveOTIOTask. `project` is borrowed by the task. */ -OakTaskTask *oaktask_create_project_save_otio(OakNodeProject project, - const char *filename); +OakTaskTask oaktask_create_project_save_otio(OakNodeProject project, + const char *filename); /** * @brief OTIO import confirmation callback (facade concern; default @@ -98,14 +99,14 @@ void oaktask_load_otio_set_confirm_cb(oaktask_otio_import_confirm_fn fn, /** @brief olive::PreCacheTask. `footage`/`sequence` are borrowed by the * task. */ -OakTaskTask *oaktask_create_precache(OakNodeFootage footage, int index, - OakNodeSequence sequence); +OakTaskTask oaktask_create_precache(OakNodeFootage footage, int index, + OakNodeSequence sequence); /** @brief olive::ExportTask (params POD from codec/encoder.h). * `viewer`/`color_manager` are borrowed by the task. */ -OakTaskTask *oaktask_create_export(OakNodeNode viewer, - OakNodeColorManager color_manager, - const oakcodec_encoding_params *params); +OakTaskTask oaktask_create_export(OakNodeNode viewer, + OakNodeColorManager color_manager, + const oakcodec_encoding_params *params); /** * @brief Image-sequence confirmation callback (facade/UI concern; diff --git a/include/task/task.h b/include/task/task.h index 051d43c87..a8c1e4ca6 100644 --- a/include/task/task.h +++ b/include/task/task.h @@ -30,12 +30,21 @@ extern "C" { #endif /** - * @brief Opaque handle to a background task (olive::Task). + * @brief Reference-counted handle to a background task (olive::Task). * - * Tasks are created through the factories in task/project.h (and future - * family headers) and must be released with oaktask_task_free(). + * By-value handle (shared_ptr semantics, see oakcommon's + * common/handle.h). Tasks are created through the factories in + * task/project.h (and future family headers) with reference count 1 and + * must be released with oaktask_task_free(). oaktask_task_start() + * transfers the task's lifetime to the task manager: releasing the + * handle afterwards only frees the box. */ -typedef struct OakTaskTask OakTaskTask; +typedef struct OakTaskTask { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKTASK_ABI_VERSION. */ +} OakTaskTask; /** @brief Lifecycle event ids for oaktask_task_subscribe(). */ enum OakTaskEvent { @@ -55,36 +64,38 @@ typedef void (*oaktask_event_fn)(int event_id, double value, void *userdata); /** - * @brief Free a task. No-op on NULL. The task must not be running on the - * manager (oaktask_task_cancel + wait first if it is). + * @brief Release one reference to a task. Convenience wrapper around + * t->release(t->ctx): NULL / empty-handle no-op; clears t->ctx + * after releasing. The task must not be running on the manager + * (oaktask_task_cancel + wait first if it is). */ void oaktask_task_free(OakTaskTask *t); /** @brief Run synchronously in the calling thread. 1 = succeeded. */ -int oaktask_task_start_sync(OakTaskTask *t); +int oaktask_task_start_sync(OakTaskTask t); /** @brief Run asynchronously on the task manager. */ -int oaktask_task_start(OakTaskTask *t); +int oaktask_task_start(OakTaskTask t); -int oaktask_task_cancel(OakTaskTask *t); +int oaktask_task_cancel(OakTaskTask t); /** @brief Wait for an asynchronously started task. */ -int oaktask_task_wait(OakTaskTask *t); +int oaktask_task_wait(OakTaskTask t); -int oaktask_task_is_finished(const OakTaskTask *t); +int oaktask_task_is_finished(OakTaskTask t); -int oaktask_task_succeeded(const OakTaskTask *t); +int oaktask_task_succeeded(OakTaskTask t); /** @brief Two-stage string getters. */ -int oaktask_task_title(OakTaskTask *t, char *buf, int buf_size); -int oaktask_task_error(OakTaskTask *t, char *buf, int buf_size); +int oaktask_task_title(OakTaskTask t, char *buf, int buf_size); +int oaktask_task_error(OakTaskTask t, char *buf, int buf_size); /** * @brief Subscribe to lifecycle events (returns a subscription id >= 0, * or a negative error code). One-shot per event stream: the * subscription is dropped after OAKTASK_EVENT_FINISHED. */ -int64_t oaktask_task_subscribe(OakTaskTask *t, oaktask_event_fn fn, +int64_t oaktask_task_subscribe(OakTaskTask t, oaktask_event_fn fn, void *userdata); /** @brief Alive-count for leak assertions in tests. */ diff --git a/include/timeline/error.h b/include/timeline/error.h index 3c56796d0..3342bffa7 100644 --- a/include/timeline/error.h +++ b/include/timeline/error.h @@ -29,6 +29,8 @@ * failure. String getters return the required buffer size in bytes * (including the terminating NUL) as a non-negative value instead. */ +#define OAKTIMELINE_ABI_VERSION 1 + #define OAKTIMELINE_OK 0 /**< Success. */ #define OAKTIMELINE_E_INVALID (-1) /**< NULL handle or invalid argument. */ #define OAKTIMELINE_E_STATE (-2) /**< Call not valid in the current state. */ diff --git a/include/timeline/marker.h b/include/timeline/marker.h index 99c0e3e18..f291f5146 100644 --- a/include/timeline/marker.h +++ b/include/timeline/marker.h @@ -31,31 +31,45 @@ extern "C" { #endif /** - * @brief Borrowed handle to a timeline marker list + * @brief Borrowed by-value handle to a timeline marker list * (olive::TimelineMarkerList), owned by a viewer node. * - * Obtained via oaktimeline_marker_list_of(); never freed by the caller. + * Obtained via oaktimeline_marker_list_of(). The handle boxes a + * reference into the owning node: addref/release manage the box only, + * never the list. Release the box with oaktimeline_marker_list_free() + * (or handle.release(handle.ctx)) when done. */ -typedef struct OakTimelineMarkerList OakTimelineMarkerList; +typedef struct OakTimelineMarkerList { + void *ctx; /**< Opaque pointer to the borrowed object's box. */ + void (*addref)(void *ctx); /**< Atomically increments the box count. */ + void (*release)(void *ctx); /**< Decrements the count, frees the box. */ + uint32_t abi_version; /**< OAKTIMELINE_ABI_VERSION. */ +} OakTimelineMarkerList; /** - * @brief Borrowed marker list of a viewer node (sequence). NULL for an - * empty handle or when the node is not a viewer. + * @brief Borrowed marker list of a viewer node (sequence). Empty handle + * (ctx == NULL) for an empty node handle or when the node is not a + * viewer. */ -OakTimelineMarkerList *oaktimeline_marker_list_of(OakNodeNode owner); +OakTimelineMarkerList oaktimeline_marker_list_of(OakNodeNode owner); + +/** + * @brief Release a borrowed marker list box (never the list itself). + * NULL / empty-handle no-op; clears list->ctx after releasing. + */ +void oaktimeline_marker_list_free(OakTimelineMarkerList *list); /** * @brief Number of markers. Out-param convention; OAKTIMELINE_E_INVALID - * for NULL arguments. + * for empty/NULL arguments. */ -int oaktimeline_marker_count(const OakTimelineMarkerList *list, - int *out_count); +int oaktimeline_marker_count(OakTimelineMarkerList list, int *out_count); /** * @brief Marker at index: time as num/den pairs, color and name * (two-stage string). OAKTIMELINE_E_NOT_FOUND when out of range. */ -int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, +int oaktimeline_marker_at(OakTimelineMarkerList list, int index, int *in_num, int *in_den, int *out_num, int *out_den, int *color, char *name_buf, int buf_size); @@ -63,25 +77,25 @@ int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, * @brief Create a command that adds a marker (olive::MarkerAddCommand). * * Owned command; free with oakundo_command_free(). Redo it directly or - * push it on an undo stack. Returns NULL on failure. + * push it on an undo stack. Empty handle on failure. */ OakUndoCommand oaktimeline_marker_add_command( - OakTimelineMarkerList *list, int in_num, int in_den, int out_num, + OakTimelineMarkerList list, int in_num, int in_den, int out_num, int out_den, const char *name, int color); /** * @brief Create a command that removes the marker at `index`. - * OAKTIMELINE_E_NOT_FOUND (as NULL result documented by error) is - * reported by returning NULL. + * OAKTIMELINE_E_NOT_FOUND (as an empty result documented by error) is + * reported by returning an empty handle. */ OakUndoCommand oaktimeline_marker_remove_at_command( - OakTimelineMarkerList *list, int index); + OakTimelineMarkerList list, int index); /** * @brief Create a command that sets a marker's time range. */ OakUndoCommand oaktimeline_marker_set_time_command( - OakTimelineMarkerList *list, int index, int in_num, int in_den, + OakTimelineMarkerList list, int index, int in_num, int in_den, int out_num, int out_den); /** @@ -90,15 +104,15 @@ OakUndoCommand oaktimeline_marker_set_time_command( * when >= 0; both NULL-name and color < 0 is a no-op error). */ OakUndoCommand oaktimeline_marker_set_props_command( - OakTimelineMarkerList *list, int index, int color, const char *name); + OakTimelineMarkerList list, int index, int color, const char *name); /** * @brief Load/save the list through oakcommon XML handles. The reader * must be positioned on the wrapping element (e.g. "markers"). */ -int oaktimeline_marker_list_load(OakTimelineMarkerList *list, +int oaktimeline_marker_list_load(OakTimelineMarkerList list, OakXmlReader reader); -int oaktimeline_marker_list_save(const OakTimelineMarkerList *list, +int oaktimeline_marker_list_save(OakTimelineMarkerList list, OakXmlWriter writer); #ifdef __cplusplus diff --git a/include/timeline/workarea.h b/include/timeline/workarea.h index 0bb20ec60..3f90b62ae 100644 --- a/include/timeline/workarea.h +++ b/include/timeline/workarea.h @@ -31,28 +31,45 @@ extern "C" { #endif /** - * @brief Borrowed handle to a timeline work area + * @brief Borrowed by-value handle to a timeline work area * (olive::TimelineWorkArea), owned by a viewer node. + * + * Obtained via oaktimeline_workarea_of(). The handle boxes a reference + * into the owning node: addref/release manage the box only, never the + * work area. Release the box with oaktimeline_workarea_free() (or + * handle.release(handle.ctx)) when done. */ -typedef struct OakTimelineWorkArea OakTimelineWorkArea; +typedef struct OakTimelineWorkArea { + void *ctx; /**< Opaque pointer to the borrowed object's box. */ + void (*addref)(void *ctx); /**< Atomically increments the box count. */ + void (*release)(void *ctx); /**< Decrements the count, frees the box. */ + uint32_t abi_version; /**< OAKTIMELINE_ABI_VERSION. */ +} OakTimelineWorkArea; /** - * @brief Borrowed work area of a viewer node (sequence). NULL for an - * empty handle or when the node is not a viewer. + * @brief Borrowed work area of a viewer node (sequence). Empty handle + * (ctx == NULL) for an empty node handle or when the node is not a + * viewer. */ -OakTimelineWorkArea *oaktimeline_workarea_of(OakNodeNode owner); +OakTimelineWorkArea oaktimeline_workarea_of(OakNodeNode owner); + +/** + * @brief Release a borrowed work area box (never the work area itself). + * NULL / empty-handle no-op; clears w->ctx after releasing. + */ +void oaktimeline_workarea_free(OakTimelineWorkArea *w); /** * @brief Read the work area state. Out params may individually be NULL. */ -int oaktimeline_workarea_get(const OakTimelineWorkArea *w, int *in_num, +int oaktimeline_workarea_get(OakTimelineWorkArea w, int *in_num, int *in_den, int *out_num, int *out_den, int *enabled); /** * @brief Set the range directly (live). */ -int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, +int oaktimeline_workarea_set_range(OakTimelineWorkArea w, int in_num, int in_den, int out_num, int out_den); /** @@ -61,7 +78,7 @@ int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, * changed from). Owned; free with oakundo_command_free(). */ OakUndoCommand oaktimeline_workarea_set_range_command( - OakTimelineWorkArea *w, int in_num, int in_den, int out_num, + OakTimelineWorkArea w, int in_num, int in_den, int out_num, int out_den, int old_in_num, int old_in_den, int old_out_num, int old_out_den); @@ -69,7 +86,7 @@ OakUndoCommand oaktimeline_workarea_set_range_command( * @brief Create a set-enabled command (olive::WorkareaSetEnabledCommand). */ OakUndoCommand oaktimeline_workarea_set_enabled_command( - OakTimelineWorkArea *w, int enabled); + OakTimelineWorkArea w, int enabled); /** * @brief The reset sentinel range (TimelineWorkArea::k_reset_in/out). @@ -81,8 +98,8 @@ int oaktimeline_workarea_reset(int *in_num, int *in_den, int *out_num, * @brief Load/save through oakcommon XML handles. The reader must be * positioned on the "workarea" element. */ -int oaktimeline_workarea_load(OakTimelineWorkArea *w, OakXmlReader reader); -int oaktimeline_workarea_save(const OakTimelineWorkArea *w, +int oaktimeline_workarea_load(OakTimelineWorkArea w, OakXmlReader reader); +int oaktimeline_workarea_save(OakTimelineWorkArea w, OakXmlWriter writer); #ifdef __cplusplus diff --git a/src/codec/src/decoder.cpp b/src/codec/src/decoder.cpp index 300ae5cad..190046666 100644 --- a/src/codec/src/decoder.cpp +++ b/src/codec/src/decoder.cpp @@ -55,14 +55,13 @@ bool cancel_atom_is_cancelled(const OakCancelAtom *cancelled) const Rational Decoder::k_any_timecode = RATIONAL_MIN; Decoder::Decoder() - : cached_texture_(nullptr) { update_last_accessed(); } Decoder::~Decoder() { - oakrender_display_texture_free(cached_texture_); + oakrender_display_texture_free(&cached_texture_); } void Decoder::increment_access_time(int64_t t) @@ -115,7 +114,7 @@ bool Decoder::open(const CodecStream &stream) } } -OakRenderTexture *Decoder::retrieve_video(const RetrieveVideoParams &p) +OakRenderTexture Decoder::retrieve_video(const RetrieveVideoParams &p) { std::lock_guard locker(mutex_); @@ -123,28 +122,29 @@ OakRenderTexture *Decoder::retrieve_video(const RetrieveVideoParams &p) if (!stream_.is_valid()) { fprintf(stderr, "Can't retrieve video on a closed decoder\n"); - return nullptr; + return OakRenderTexture{}; } if (!supports_video()) { fprintf(stderr, "Decoder doesn't support video\n"); - return nullptr; + return OakRenderTexture{}; } if (cancel_atom_is_cancelled(p.cancelled)) { - return nullptr; + return OakRenderTexture{}; } - if (cached_texture_ && cached_time_ == p.time && + if (cached_texture_.ctx && cached_time_ == p.time && cached_divider_ == p.divider) { // Hand the caller its own reference; the cache keeps its own return oakrender_display_texture_retain(cached_texture_); } - OakRenderTexture *texture = retrieve_video_internal(p); - oakrender_display_texture_free(cached_texture_); - cached_texture_ = texture ? oakrender_display_texture_retain(texture) : - nullptr; + OakRenderTexture texture = retrieve_video_internal(p); + oakrender_display_texture_free(&cached_texture_); + cached_texture_ = texture.ctx ? + oakrender_display_texture_retain(texture) : + OakRenderTexture{}; cached_time_ = p.time; cached_divider_ = p.divider; @@ -227,8 +227,7 @@ void Decoder::close() update_last_accessed(); - oakrender_display_texture_free(cached_texture_); - cached_texture_ = nullptr; + oakrender_display_texture_free(&cached_texture_); if (stream_.is_valid()) { close_internal(); @@ -363,10 +362,10 @@ int64_t Decoder::get_image_sequence_index(const std::string &filename) return strtoll(number_only.c_str(), nullptr, 10); } -OakRenderTexture *Decoder::retrieve_video_internal(const RetrieveVideoParams &p) +OakRenderTexture Decoder::retrieve_video_internal(const RetrieveVideoParams &p) { (void) p; - return nullptr; + return OakRenderTexture{}; } FramePtr Decoder::retrieve_video_frame_internal(const RetrieveVideoParams &p) diff --git a/src/codec/src/decoder.h b/src/codec/src/decoder.h index a3de800f9..8538b260a 100644 --- a/src/codec/src/decoder.h +++ b/src/codec/src/decoder.h @@ -200,7 +200,7 @@ public: static const Rational k_any_timecode; struct RetrieveVideoParams { - OakRenderRenderer *renderer = nullptr; + OakRenderRenderer renderer = {}; Rational time; int divider = 1; PixelFormat maximum_format = PixelFormat::invalid; @@ -222,7 +222,7 @@ public: * The returned texture handle is owned by the caller and must be * released with oakrender_display_texture_free(). */ - OakRenderTexture *retrieve_video(const RetrieveVideoParams &p); + OakRenderTexture retrieve_video(const RetrieveVideoParams &p); /** * @brief Retrieves a decoded video frame in CPU memory. @@ -347,7 +347,7 @@ protected: * The returned texture handle is owned by the caller and must be * released with oakrender_display_texture_free(). */ - virtual OakRenderTexture * + virtual OakRenderTexture retrieve_video_internal(const RetrieveVideoParams &p); virtual FramePtr retrieve_video_frame_internal(const RetrieveVideoParams &p); @@ -387,7 +387,7 @@ private: std::atomic_int64_t last_accessed_; - OakRenderTexture *cached_texture_; + OakRenderTexture cached_texture_ = {}; Rational cached_time_; int cached_divider_ = 0; diff --git a/src/codec/src/ffmpeg/ffmpegdecoder.cpp b/src/codec/src/ffmpeg/ffmpegdecoder.cpp index 8870d03b8..ad3fe72ad 100644 --- a/src/codec/src/ffmpeg/ffmpegdecoder.cpp +++ b/src/codec/src/ffmpeg/ffmpegdecoder.cpp @@ -274,7 +274,7 @@ bool FFmpegDecoder::open_internal() return false; } -OakRenderTexture * +OakRenderTexture FFmpegDecoder::process_frame_into_texture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original) @@ -312,27 +312,27 @@ FFmpegDecoder::process_frame_into_texture(AVFramePtr f, fill_render_params(vp, &rvp); oakcommon_videoparams_free(&vp); - OakRenderTexture *tex = + OakRenderTexture tex = oakrender_display_texture_create(p.renderer, &rvp, nullptr, 0); - if (!tex) { - return nullptr; + if (!tex.ctx) { + return OakRenderTexture{}; } if (oakrender_display_texture_upload(tex, f->data(0), f->linesize(0)) != 0) { - oakrender_display_texture_free(tex); - return nullptr; + oakrender_display_texture_free(&tex); + return OakRenderTexture{}; } return tex; } -OakRenderTexture * +OakRenderTexture FFmpegDecoder::retrieve_video_internal(const RetrieveVideoParams &p) { if (AVFramePtr f = retrieve_frame(p.time, p.cancelled)) { if (cancel_atom_is_cancelled(p.cancelled)) { - return nullptr; + return OakRenderTexture{}; } AVFramePtr original = f; @@ -350,20 +350,20 @@ FFmpegDecoder::retrieve_video_internal(const RetrieveVideoParams &p) f = std::move(ptr); if (!f) { fprintf(stderr, "PreProcessFrame failed\n"); - return nullptr; + return OakRenderTexture{}; } // Finally, upload to a texture - OakRenderTexture *texture = process_frame_into_texture(f, p, original); + OakRenderTexture texture = process_frame_into_texture(f, p, original); - if (!texture) { + if (!texture.ctx) { fprintf(stderr, "ProcessFrameIntoTexture returned null\n"); } return texture; } - return nullptr; + return OakRenderTexture{}; } FramePtr FFmpegDecoder::retrieve_video_frame_internal(const RetrieveVideoParams &p) diff --git a/src/codec/src/ffmpeg/ffmpegdecoder.h b/src/codec/src/ffmpeg/ffmpegdecoder.h index 6ad76826a..db5ccf131 100644 --- a/src/codec/src/ffmpeg/ffmpegdecoder.h +++ b/src/codec/src/ffmpeg/ffmpegdecoder.h @@ -66,7 +66,7 @@ public: protected: virtual bool open_internal() override; - virtual OakRenderTexture * + virtual OakRenderTexture retrieve_video_internal(const RetrieveVideoParams &p) override; virtual FramePtr retrieve_video_frame_internal(const RetrieveVideoParams &p) override; @@ -104,7 +104,7 @@ private: AVFramePtr pre_process_frame(AVFramePtr f, const RetrieveVideoParams &p); - OakRenderTexture *process_frame_into_texture(AVFramePtr f, + OakRenderTexture process_frame_into_texture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original); diff --git a/src/codec/src/oiio/oiiodecoder.cpp b/src/codec/src/oiio/oiiodecoder.cpp index c1cec6427..70e677b7f 100644 --- a/src/codec/src/oiio/oiiodecoder.cpp +++ b/src/codec/src/oiio/oiiodecoder.cpp @@ -195,12 +195,12 @@ bool OIIODecoder::open_internal() return open_image_handler(stream().filename(), stream().stream()); } -OakRenderTexture * +OakRenderTexture OIIODecoder::retrieve_video_internal(const RetrieveVideoParams &p) { FramePtr frame = retrieve_video_frame_internal(p); if (!frame) { - return nullptr; + return OakRenderTexture{}; } OakVideoParams frame_params = frame->video_params(); // addref'd copy diff --git a/src/codec/src/oiio/oiiodecoder.h b/src/codec/src/oiio/oiiodecoder.h index b7da2ea28..9dae43d5f 100644 --- a/src/codec/src/oiio/oiiodecoder.h +++ b/src/codec/src/oiio/oiiodecoder.h @@ -52,7 +52,7 @@ public: protected: virtual bool open_internal() override; - virtual OakRenderTexture * + virtual OakRenderTexture retrieve_video_internal(const RetrieveVideoParams &p) override; virtual FramePtr retrieve_video_frame_internal(const RetrieveVideoParams &p) override; diff --git a/src/render/c_api/cache.cpp b/src/render/c_api/cache.cpp index 69c0fc461..feed4ff24 100644 --- a/src/render/c_api/cache.cpp +++ b/src/render/c_api/cache.cpp @@ -49,14 +49,9 @@ public: using olive::PlaybackCache::validate; }; -OakRenderCacheImpl *impl(OakRenderCache *c) +OakRenderCacheImpl *impl(OakRenderCache c) { - return reinterpret_cast(c); -} - -const OakRenderCacheImpl *impl(const OakRenderCache *c) -{ - return reinterpret_cast(c); + return oakrender_c_api::to_native(c); } /** @@ -95,48 +90,52 @@ int oakrender_debug_alive_count(void) return g_alive_count.load(std::memory_order_relaxed); } -OakRenderCache *oakrender_cache_create(void) +OakRenderCache oakrender_cache_create(void) { try { - auto *c = new OakRenderCacheImpl(); - oakrender_c_api::alive_inc(); - return reinterpret_cast(c); + return oakrender_c_api::make_handle( + new OakRenderCacheImpl(), true, + &oakrender_c_api::delete_as); } catch (...) { - return nullptr; + return OakRenderCache{}; } } void oakrender_cache_free(OakRenderCache *cache) { - if (!cache) { - return; - } - delete impl(cache); - oakrender_c_api::alive_dec(); + oakrender_c_api::free_handle(cache); } -int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den) +OakRenderCache oakrender_cache_wrap_borrowed(void *native_cache) { - if (!cache || num <= 0 || den <= 0) { + // Borrowed: the cache is owned by its node; releasing this handle + // only frees the box. + return oakrender_c_api::make_handle( + native_cache, false, nullptr); +} + +int oakrender_cache_set_timebase(OakRenderCache cache, int num, int den) +{ + if (!cache.ctx || num <= 0 || den <= 0) { return OAKRENDER_E_INVALID; } impl(cache)->set_timebase(olive::Rational(num, den)); return OAKRENDER_OK; } -int oakrender_cache_set_uuid(OakRenderCache *cache, const char *uuid) +int oakrender_cache_set_uuid(OakRenderCache cache, const char *uuid) { - if (!cache || !uuid) { + if (!cache.ctx || !uuid) { return OAKRENDER_E_INVALID; } impl(cache)->set_uuid(uuid); return OAKRENDER_OK; } -void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts, +void oakrender_cache_invalidate(OakRenderCache cache, int64_t in_ts, int64_t out_ts) { - if (!cache) { + if (!cache.ctx) { return; } OakRenderCacheImpl *c = impl(cache); @@ -144,10 +143,10 @@ void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts, olive::core::TimeRange(ts_to_time(c, in_ts), ts_to_time(c, out_ts))); } -void oakrender_cache_validate(OakRenderCache *cache, int64_t in_ts, +void oakrender_cache_validate(OakRenderCache cache, int64_t in_ts, int64_t out_ts) { - if (!cache) { + if (!cache.ctx) { return; } OakRenderCacheImpl *c = impl(cache); @@ -155,9 +154,9 @@ void oakrender_cache_validate(OakRenderCache *cache, int64_t in_ts, olive::core::TimeRange(ts_to_time(c, in_ts), ts_to_time(c, out_ts))); } -int oakrender_cache_has_validated_ranges(const OakRenderCache *cache) +int oakrender_cache_has_validated_ranges(OakRenderCache cache) { - return cache && impl(cache)->has_validated_ranges() ? 1 : 0; + return cache.ctx && impl(cache)->has_validated_ranges() ? 1 : 0; } int oakrender_cache_indicator_height(void) @@ -165,11 +164,11 @@ int oakrender_cache_indicator_height(void) return olive::PlaybackCache::get_cache_indicator_height(); } -int oakrender_frame_cache_load(OakRenderCache *cache, const char *path, +int oakrender_frame_cache_load(OakRenderCache cache, const char *path, const char *uuid, int64_t ts, - OakCodecFrame **out_frame) + OakCodecFrame *out_frame) { - if (!cache || !path || !uuid || !out_frame) { + if (!cache.ctx || !path || !uuid || !out_frame) { return OAKRENDER_E_INVALID; } try { @@ -178,20 +177,23 @@ int oakrender_frame_cache_load(OakRenderCache *cache, const char *path, if (!f) { return OAKRENDER_E_NOT_FOUND; } - auto *block = new OakCodecFrame; - block->ptr = std::move(f); - oakrender_c_api::alive_inc(); - *out_frame = block; - return OAKRENDER_OK; + auto *frame_impl = new OakCodecFrameImpl; + frame_impl->ptr = std::move(f); + *out_frame = oakrender_c_api::make_handle( + frame_impl, true, + &oakrender_c_api::delete_as); + return out_frame->ctx ? OAKRENDER_OK : OAKRENDER_E_NOMEM; } catch (...) { return OAKRENDER_E_FAILED; } } -void oakrender_frame_cache_save(OakRenderCache *cache, const char *path, - const char *uuid, const OakCodecFrame *frame) +void oakrender_frame_cache_save(OakRenderCache cache, const char *path, + const char *uuid, OakCodecFrame frame) { - if (!cache || !path || !uuid || !frame || !frame->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!cache.ctx || !path || !uuid || !f || !f->ptr) { return; } try { @@ -201,17 +203,17 @@ void oakrender_frame_cache_save(OakRenderCache *cache, const char *path, tb = olive::Rational(1, 1); } olive::FrameHashCache::save_cache_frame(path, uuid, - frame->ptr->timestamp(), tb, - frame->ptr); + f->ptr->timestamp(), tb, + f->ptr); } catch (...) { } } -int oakrender_cache_get_invalidated_ranges(OakRenderCache *cache, +int oakrender_cache_get_invalidated_ranges(OakRenderCache cache, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den, int64_t *ranges, int max_ranges) { - if (!cache || max_ranges < 0) { + if (!cache.ctx || max_ranges < 0) { return OAKRENDER_E_INVALID; } diff --git a/src/render/c_api/color.cpp b/src/render/c_api/color.cpp index 1811c3927..592b5a2e5 100644 --- a/src/render/c_api/color.cpp +++ b/src/render/c_api/color.cpp @@ -46,21 +46,21 @@ int write_string(const std::string &s, char *buf, int n) } // namespace -OakColorProcessor *oakrender_color_processor_create(const char *src_space, - const char *dst_transform, - int direction) +OakColorProcessor oakrender_color_processor_create(const char *src_space, + const char *dst_transform, + int direction) { if (!src_space || !*src_space || !dst_transform || !*dst_transform) { - return nullptr; + return OakColorProcessor{}; } if (direction != OAKRENDER_COLOR_DIRECTION_NORMAL && direction != OAKRENDER_COLOR_DIRECTION_INVERSE) { - return nullptr; + return OakColorProcessor{}; } try { OCIO_NAMESPACE::ConstConfigRcPtr config = olive::ColorManager::get_default_config(); if (!config) { - return nullptr; + return OakColorProcessor{}; } // Resolve role names (e.g. "scene_linear") to canonical colorspace @@ -84,41 +84,40 @@ OakColorProcessor *oakrender_color_processor_create(const char *src_space, processor = nullptr; } - auto *block = new OakColorProcessor; - block->ptr = olive::ColorProcessor::create(processor); - oakrender_c_api::alive_inc(); - return block; + auto *impl = new OakColorProcessorImpl; + impl->ptr = olive::ColorProcessor::create(processor); + return oakrender_c_api::make_handle( + impl, true, &oakrender_c_api::delete_as); } catch (...) { - return nullptr; + return OakColorProcessor{}; } } void oakrender_color_processor_free(OakColorProcessor *processor) { - if (!processor) { - return; - } - delete processor; - oakrender_c_api::alive_dec(); + oakrender_c_api::free_handle(processor); } -int oakrender_color_processor_is_valid(const OakColorProcessor *processor) +int oakrender_color_processor_is_valid(OakColorProcessor processor) { - return processor && processor->ptr && processor->ptr->get_processor() ? 1 : - 0; + OakColorProcessorImpl *p = + oakrender_c_api::to_native(processor); + return p && p->ptr && p->ptr->get_processor() ? 1 : 0; } -int oakrender_color_processor_convert(OakColorProcessor *processor, +int oakrender_color_processor_convert(OakColorProcessor processor, double ir, double ig, double ib, double ia, double *out_r, double *out_g, double *out_b, double *out_a) { - if (!processor || !processor->ptr || !out_r || !out_g || !out_b || !out_a) { + OakColorProcessorImpl *p = + oakrender_c_api::to_native(processor); + if (!p || !p->ptr || !out_r || !out_g || !out_b || !out_a) { return OAKRENDER_E_INVALID; } try { olive::Color out = - processor->ptr->convert_color(olive::Color(ir, ig, ib, ia)); + p->ptr->convert_color(olive::Color(ir, ig, ib, ia)); *out_r = out.red(); *out_g = out.green(); *out_b = out.blue(); @@ -221,10 +220,14 @@ int oakrender_color_manager_display_transform(const char *display, } } -int oakrender_color_processor_convert_frame(OakColorProcessor *processor, - OakCodecFrame *frame) +int oakrender_color_processor_convert_frame(OakColorProcessor processor, + OakCodecFrame frame) { - if (!processor || !processor->ptr || !frame || !frame->ptr) { + OakColorProcessorImpl *p = + oakrender_c_api::to_native(processor); + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!p || !p->ptr || !f || !f->ptr) { return OAKRENDER_E_INVALID; } try { @@ -233,7 +236,7 @@ int oakrender_color_processor_convert_frame(OakColorProcessor *processor, // OCIO::PackedImageDesc view. A processor whose underlying OCIO // processor is null (creation failure was non-fatal) is a // pass-through and still reports success, mirroring the C++ API. - processor->ptr->convert_frame(frame->ptr); + p->ptr->convert_frame(f->ptr); return OAKRENDER_OK; } catch (...) { return OAKRENDER_E_FAILED; diff --git a/src/render/c_api/copier.cpp b/src/render/c_api/copier.cpp index 843e03ed3..bd11fcb02 100644 --- a/src/render/c_api/copier.cpp +++ b/src/render/c_api/copier.cpp @@ -24,36 +24,38 @@ #include "../../node/c_api/nodehandle.h" #include "../src/projectcopier.h" +#include "internalhandles.h" namespace { -olive::ProjectCopier *impl(OakRenderProjectCopier *h) +olive::ProjectCopier *impl(OakRenderProjectCopier h) { - return reinterpret_cast(h); + return oakrender_c_api::to_native(h); } } // namespace -OakRenderProjectCopier *oakrender_project_copier_create(void) +OakRenderProjectCopier oakrender_project_copier_create(void) { try { - return reinterpret_cast( - new olive::ProjectCopier()); + return oakrender_c_api::make_handle( + new olive::ProjectCopier(), true, + &oakrender_c_api::delete_as); } catch (...) { - return NULL; + return OakRenderProjectCopier{}; } } void oakrender_project_copier_free(OakRenderProjectCopier *copier) { - delete impl(copier); + oakrender_c_api::free_handle(copier); } -int oakrender_project_copier_set_project(OakRenderProjectCopier *copier, +int oakrender_project_copier_set_project(OakRenderProjectCopier copier, OakNodeProject project) { - if (!copier || !project.ctx) { + if (!copier.ctx || !project.ctx) { return OAKRENDER_E_INVALID; } try { @@ -66,9 +68,9 @@ int oakrender_project_copier_set_project(OakRenderProjectCopier *copier, } OakNodeNode oakrender_project_copier_get_copy( - OakRenderProjectCopier *copier, OakNodeNode original) + OakRenderProjectCopier copier, OakNodeNode original) { - if (!copier || !original.ctx) { + if (!copier.ctx || !original.ctx) { return OakNodeNode{}; } // Borrowed handle: releasing it only destroys the handle box, never @@ -79,9 +81,9 @@ OakNodeNode oakrender_project_copier_get_copy( } OakNodeProject oakrender_project_copier_get_copied_project( - OakRenderProjectCopier *copier) + OakRenderProjectCopier copier) { - if (!copier) { + if (!copier.ctx) { return OakNodeProject{}; } // Borrowed handle: the copied project is owned by the copier. diff --git a/src/render/c_api/internalhandles.h b/src/render/c_api/internalhandles.h index 71911f17c..492d3e5d4 100644 --- a/src/render/c_api/internalhandles.h +++ b/src/render/c_api/internalhandles.h @@ -22,34 +22,189 @@ #define OAK_EDITOR_RENDER_INTERNALHANDLES_H /** - * @brief Control-block definitions behind the public opaque handles - * (internal, not installed). + * @brief Internal control block behind every oakrender value handle, + * shared between the c_api translation units (internal, not + * installed). * - * Textures, frames and color processors wrap shared_ptr-managed engine - * objects, so their handles are heap control blocks (the R7-A §A.2 - * ownership protocol). The refcount on textures/frames implements the - * retain/free pairing rule; every alive control block participates in - * oakrender_debug_alive_count(). + * All oakrender public handle structs have the identical layout + * (ctx/addref/release/abi_version), so a single generic box and + * addref/release pair serves every family; `deleter` knows the concrete + * C++ type to destroy. Textures, frames and color processors box the + * *Impl structs below (shared_ptr-managed engine objects); renderers, + * tickets, caches and copiers box their native pointers with custom + * deleters. + * + * `owns` is true for objects created through create/factory functions + * (releasing the last reference destroys the object through `deleter`) + * and false for borrowed wrappers (oakrender_cache_wrap_borrowed()): + * releasing those only destroys the box. + * + * Live-object accounting: make_handle() counts every owned handle it + * creates (alive_inc) and handle_release() un-counts an owned object + * right after destroying it (alive_dec), keeping + * oakrender_debug_alive_count() meaningful for leak checking. */ #include +#include + +#include "render/error.h" + +#include "alivecount.h" #include "codec/frame.h" #include "colorprocessor.h" #include "texture.h" -struct OakRenderTexture { +/** + * @brief Boxed object behind OakRenderTexture: a shared_ptr-managed + * GPU texture. + */ +struct OakRenderTextureImpl { olive::TexturePtr ptr; - std::atomic refcount{ 1 }; }; -struct OakCodecFrame { +/** + * @brief Boxed object behind OakCodecFrame: a shared_ptr-managed CPU + * frame. + */ +struct OakCodecFrameImpl { olive::FramePtr ptr; - std::atomic refcount{ 1 }; }; -struct OakColorProcessor { +/** + * @brief Boxed object behind OakColorProcessor: a shared_ptr-managed + * color processor. + */ +struct OakColorProcessorImpl { olive::ColorProcessorPtr ptr; }; +struct OakRenderBox { + void *object; + bool owns; + std::atomic refs; + void (*deleter)(void *object); + + OakRenderBox(void *o, bool own, void (*del)(void *)) + : object(o) + , owns(own) + , refs(1) + , deleter(del) + { + } +}; + +namespace oakrender_c_api +{ + +inline void handle_addref(void *ctx) +{ + if (ctx) { + static_cast(ctx)->refs.fetch_add(1); + } +} + +inline void handle_release(void *ctx) +{ + if (!ctx) { + return; + } + OakRenderBox *box = static_cast(ctx); + if (box->refs.fetch_sub(1) == 1) { + if (box->owns) { + box->deleter(box->object); + alive_dec(); + } + delete box; + } +} + +/** + * @brief Wrap `object` in a value handle with reference count 1. + * + * `owns` selects whether the final release destroys the object through + * `deleter`. Returns an empty handle (ctx == nullptr) for a null object + * or on allocation failure (an owned object is destroyed via `deleter` + * in the latter case). + */ +template +inline Handle make_handle(void *object, bool owns, void (*deleter)(void *)) +{ + Handle handle = {}; + if (!object) { + return handle; + } + + OakRenderBox *box = new (std::nothrow) OakRenderBox(object, owns, + deleter); + if (!box) { + if (owns && deleter) { + deleter(object); + } + return handle; + } + + if (owns) { + alive_inc(); + } + + handle.ctx = box; + handle.addref = handle_addref; + handle.release = handle_release; + handle.abi_version = OAKRENDER_ABI_VERSION; + return handle; +} + +/** + * @brief Unwrap a value handle to the boxed object (nullptr for an + * empty handle). + */ +template +inline T *to_native(Handle h) +{ + if (!h.ctx) { + return nullptr; + } + return static_cast(static_cast(h.ctx)->object); +} + +/** + * @brief Raw boxed object behind a handle ctx (NULL-safe). Used by the + * blit path, whose job struct carries borrowed ctx pointers. + */ +inline void *box_object(const void *ctx) +{ + if (!ctx) { + return nullptr; + } + return static_cast(ctx)->object; +} + +/** + * @brief Deleter callback stamping the concrete C++ type. + */ +template +inline void delete_as(void *object) +{ + delete static_cast(object); +} + +/** + * @brief Generic free() implementation for every oakrender family: + * release the caller's reference and null out the handle. NULL and + * ctx == NULL are no-ops. + */ +template +inline void free_handle(Handle *h) +{ + if (!h || !h->ctx) { + return; + } + h->release(h->ctx); + h->ctx = nullptr; +} + +} // namespace oakrender_c_api + #endif //OAK_EDITOR_RENDER_INTERNALHANDLES_H diff --git a/src/render/c_api/manager.cpp b/src/render/c_api/manager.cpp index 0070db49b..b2e7bb9f8 100644 --- a/src/render/c_api/manager.cpp +++ b/src/render/c_api/manager.cpp @@ -111,14 +111,16 @@ int64_t oakrender_request_frame(OakNodeNode viewer, int64_t ts, olive::RenderTicket *raw_ticket = ticket.get(); ticket->set_finished_callback([ticket, cb, ts, userdata, id]() { - OakCodecFrame *handle = nullptr; + OakCodecFrame handle = {}; if (ticket->has_result()) { olive::FramePtr f = ticket->get().value(); if (f) { - handle = new (std::nothrow) OakCodecFrame; - if (handle) { - handle->ptr = std::move(f); - oakrender_c_api::alive_inc(); + auto *impl = new (std::nothrow) OakCodecFrameImpl; + if (impl) { + impl->ptr = std::move(f); + handle = oakrender_c_api::make_handle( + impl, true, + &oakrender_c_api::delete_as); } } } @@ -172,14 +174,15 @@ int oakrender_set_cacher_multicam(OakNodeNode multicam_or_NULL) return OAKRENDER_OK; } -int oakrender_set_display_color_processor(OakColorProcessor *p_or_NULL) +int oakrender_set_display_color_processor(OakColorProcessor p_or_NULL) { olive::RenderManager *manager = olive::RenderManager::instance(); if (!manager) { return OAKRENDER_E_STATE; } - manager->get_cacher()->set_display_color_processor( - p_or_NULL ? p_or_NULL->ptr : nullptr); + OakColorProcessorImpl *p = + oakrender_c_api::to_native(p_or_NULL); + manager->get_cacher()->set_display_color_processor(p ? p->ptr : nullptr); return OAKRENDER_OK; } diff --git a/src/render/c_api/renderer.cpp b/src/render/c_api/renderer.cpp index 0ef27aa0f..1b231912b 100644 --- a/src/render/c_api/renderer.cpp +++ b/src/render/c_api/renderer.cpp @@ -71,24 +71,19 @@ oakrender_video_params cpp_to_pod(const olive::VideoParams &vp) return p; } -OakRenderTexture *tex(OakRenderTexture *h) +olive::Renderer *ren(OakRenderRenderer h) { - return h; + return oakrender_c_api::to_native(h); } -OakCodecFrame *frm(OakCodecFrame *h) +void renderer_delete(void *object) { - return h; -} - -olive::Renderer *ren(OakRenderRenderer *h) -{ - return reinterpret_cast(h); -} - -const olive::Renderer *ren(const OakRenderRenderer *h) -{ - return reinterpret_cast(h); + auto *r = static_cast(object); + try { + r->destroy(); + delete r; + } catch (...) { + } } olive::Matrix4x4 mat_from_float(const float *f) @@ -134,34 +129,36 @@ std::string g_requested_backend = "opengl"; /* ---- Renderer lifecycle -------------------------------------------------- */ -OakRenderRenderer *oakrender_display_renderer_create_dynamic( +OakRenderRenderer oakrender_display_renderer_create_dynamic( const char *backend_id) { if (!backend_id || !*backend_id) { - return nullptr; + return OakRenderRenderer{}; } try { auto *r = new olive::DynamicRenderer(backend_id); if (!r->load()) { delete r; - return nullptr; + return OakRenderRenderer{}; } - return reinterpret_cast(r); + return oakrender_c_api::make_handle( + r, true, &renderer_delete); } catch (...) { - return nullptr; + return OakRenderRenderer{}; } } -OakRenderRenderer *oakrender_display_renderer_create_opengl(void) +OakRenderRenderer oakrender_display_renderer_create_opengl(void) { try { - return reinterpret_cast(new olive::OpenGLRenderer()); + return oakrender_c_api::make_handle( + new olive::OpenGLRenderer(), true, &renderer_delete); } catch (...) { - return nullptr; + return OakRenderRenderer{}; } } -int oakrender_display_renderer_init(OakRenderRenderer *renderer, +int oakrender_display_renderer_init(OakRenderRenderer renderer, void *gl_context) { olive::Renderer *r = ren(renderer); @@ -189,220 +186,221 @@ int oakrender_display_renderer_init(OakRenderRenderer *renderer, void oakrender_display_renderer_destroy(OakRenderRenderer *renderer) { - olive::Renderer *r = ren(renderer); - if (!r) { - return; - } - try { - r->destroy(); - delete r; - } catch (...) { - } + oakrender_c_api::free_handle(renderer); } /* ---- Renderer queries ---------------------------------------------------- */ -int oakrender_display_renderer_is_open_gl(const OakRenderRenderer *renderer) +int oakrender_display_renderer_is_open_gl(OakRenderRenderer renderer) { - return renderer && ren(renderer)->is_open_gl() ? 1 : 0; + olive::Renderer *r = ren(renderer); + return r && r->is_open_gl() ? 1 : 0; } -int oakrender_display_renderer_is_vulkan(const OakRenderRenderer *renderer) +int oakrender_display_renderer_is_vulkan(OakRenderRenderer renderer) { - return renderer && ren(renderer)->is_vulkan() ? 1 : 0; + olive::Renderer *r = ren(renderer); + return r && r->is_vulkan() ? 1 : 0; } /* ---- Texture handle ------------------------------------------------------ */ -OakRenderTexture *oakrender_display_texture_create( - OakRenderRenderer *renderer, const oakrender_video_params *params, +OakRenderTexture oakrender_display_texture_create( + OakRenderRenderer renderer, const oakrender_video_params *params, const void *pixels, int linesize) { olive::Renderer *r = ren(renderer); if (!r || !params) { - return nullptr; + return OakRenderTexture{}; } try { olive::TexturePtr t = r->create_texture(pod_to_cpp(*params), pixels, linesize); if (!t) { - return nullptr; + return OakRenderTexture{}; } - auto *block = new OakRenderTexture; - block->ptr = std::move(t); - oakrender_c_api::alive_inc(); - return block; + auto *impl = new OakRenderTextureImpl; + impl->ptr = std::move(t); + return oakrender_c_api::make_handle( + impl, true, &oakrender_c_api::delete_as); } catch (...) { - return nullptr; + return OakRenderTexture{}; } } -OakRenderTexture *oakrender_display_texture_retain(OakRenderTexture *texture) +OakRenderTexture oakrender_display_texture_retain(OakRenderTexture texture) { - if (!texture) { - return nullptr; + if (texture.ctx) { + texture.addref(texture.ctx); } - tex(texture)->refcount.fetch_add(1, std::memory_order_relaxed); return texture; } void oakrender_display_texture_free(OakRenderTexture *texture) { - if (!texture) { - return; - } - if (tex(texture)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) { - delete tex(texture); - oakrender_c_api::alive_dec(); - } + oakrender_c_api::free_handle(texture); } -int oakrender_display_texture_upload(OakRenderTexture *texture, +int oakrender_display_texture_upload(OakRenderTexture texture, const void *pixels, int linesize) { - if (!texture || !pixels || !tex(texture)->ptr) { + OakRenderTextureImpl *t = + oakrender_c_api::to_native(texture); + if (!t || !pixels || !t->ptr) { return OAKRENDER_E_INVALID; } try { - tex(texture)->ptr->upload(const_cast(pixels), linesize); + t->ptr->upload(const_cast(pixels), linesize); return OAKRENDER_OK; } catch (...) { return OAKRENDER_E_FAILED; } } -int oakrender_display_texture_download(OakRenderTexture *texture, void *pixels, +int oakrender_display_texture_download(OakRenderTexture texture, void *pixels, int linesize) { - if (!texture || !pixels || !tex(texture)->ptr) { + OakRenderTextureImpl *t = + oakrender_c_api::to_native(texture); + if (!t || !pixels || !t->ptr) { return OAKRENDER_E_INVALID; } try { - tex(texture)->ptr->download(pixels, linesize); + t->ptr->download(pixels, linesize); return OAKRENDER_OK; } catch (...) { return OAKRENDER_E_FAILED; } } -int oakrender_display_texture_get_params(const OakRenderTexture *texture, +int oakrender_display_texture_get_params(OakRenderTexture texture, oakrender_video_params *out) { - if (!texture || !out || !tex(const_cast(texture))->ptr) { + OakRenderTextureImpl *t = + oakrender_c_api::to_native(texture); + if (!t || !out || !t->ptr) { return OAKRENDER_E_INVALID; } - *out = cpp_to_pod(tex(const_cast(texture))->ptr->params()); + *out = cpp_to_pod(t->ptr->params()); return OAKRENDER_OK; } -int oakrender_display_texture_id(const OakRenderTexture *texture) +int oakrender_display_texture_id(OakRenderTexture texture) { - if (!texture || !tex(const_cast(texture))->ptr) { + OakRenderTextureImpl *t = + oakrender_c_api::to_native(texture); + if (!t || !t->ptr) { return 0; } - return tex(const_cast(texture))->ptr->id().to_int(); + return t->ptr->id().to_int(); } /* ---- Frame handle -------------------------------------------------------- */ -OakCodecFrame *oakrender_codec_frame_create(void) +OakCodecFrame oakrender_codec_frame_create(void) { try { - auto *block = new OakCodecFrame; - block->ptr = olive::Frame::create(); - oakrender_c_api::alive_inc(); - return block; + auto *impl = new OakCodecFrameImpl; + impl->ptr = olive::Frame::create(); + return oakrender_c_api::make_handle( + impl, true, &oakrender_c_api::delete_as); } catch (...) { - return nullptr; + return OakCodecFrame{}; } } -OakCodecFrame *oakrender_codec_frame_retain(OakCodecFrame *frame) +OakCodecFrame oakrender_codec_frame_retain(OakCodecFrame frame) { - if (!frame) { - return nullptr; + if (frame.ctx) { + frame.addref(frame.ctx); } - frm(frame)->refcount.fetch_add(1, std::memory_order_relaxed); return frame; } void oakrender_codec_frame_free(OakCodecFrame *frame) { - if (!frame) { - return; - } - if (frm(frame)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) { - delete frm(frame); - oakrender_c_api::alive_dec(); - } + oakrender_c_api::free_handle(frame); } int oakrender_codec_frame_set_video_params( - OakCodecFrame *frame, const oakrender_video_params *params) + OakCodecFrame frame, const oakrender_video_params *params) { - if (!frame || !params || !frm(frame)->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !params || !f->ptr) { return OAKRENDER_E_INVALID; } - frm(frame)->ptr->set_video_params(pod_to_cpp(*params)); + f->ptr->set_video_params(pod_to_cpp(*params)); return OAKRENDER_OK; } -int oakrender_codec_frame_get_params(const OakCodecFrame *frame, +int oakrender_codec_frame_get_params(OakCodecFrame frame, oakrender_video_params *out) { - if (!frame || !out || !frm(const_cast(frame))->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !out || !f->ptr) { return OAKRENDER_E_INVALID; } - *out = cpp_to_pod( - frm(const_cast(frame))->ptr->video_params()); + *out = cpp_to_pod(f->ptr->video_params()); return OAKRENDER_OK; } -int oakrender_codec_frame_allocate(OakCodecFrame *frame) +int oakrender_codec_frame_allocate(OakCodecFrame frame) { - if (!frame || !frm(frame)->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !f->ptr) { return OAKRENDER_E_INVALID; } - return frm(frame)->ptr->allocate() ? OAKRENDER_OK : OAKRENDER_E_FAILED; + return f->ptr->allocate() ? OAKRENDER_OK : OAKRENDER_E_FAILED; } -void *oakrender_codec_frame_data(OakCodecFrame *frame) +void *oakrender_codec_frame_data(OakCodecFrame frame) { - if (!frame || !frm(frame)->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !f->ptr) { return nullptr; } - return frm(frame)->ptr->data(); + return f->ptr->data(); } -const void *oakrender_codec_frame_const_data(const OakCodecFrame *frame) +const void *oakrender_codec_frame_const_data(OakCodecFrame frame) { - if (!frame || !frm(const_cast(frame))->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !f->ptr) { return nullptr; } - return frm(const_cast(frame))->ptr->const_data(); + return f->ptr->const_data(); } -int oakrender_codec_frame_linesize_bytes(const OakCodecFrame *frame) +int oakrender_codec_frame_linesize_bytes(OakCodecFrame frame) { - if (!frame || !frm(const_cast(frame))->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !f->ptr) { return 0; } - return frm(const_cast(frame))->ptr->linesize_bytes(); + return f->ptr->linesize_bytes(); } -int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame) +int oakrender_codec_frame_is_allocated(OakCodecFrame frame) { - if (!frame || !frm(const_cast(frame))->ptr) { + OakCodecFrameImpl *f = + oakrender_c_api::to_native(frame); + if (!f || !f->ptr) { return 0; } - return frm(const_cast(frame))->ptr->is_allocated() ? 1 : 0; + return f->ptr->is_allocated() ? 1 : 0; } /* ---- Color-managed blit -------------------------------------------------- */ int oakrender_display_renderer_blit_color_managed( - OakRenderRenderer *renderer, const oakrender_color_transform_job *job, - OakRenderTexture *dst_texture, const oakrender_video_params *params) + OakRenderRenderer renderer, const oakrender_color_transform_job *job, + OakRenderTexture dst_texture, const oakrender_video_params *params) { olive::Renderer *r = ren(renderer); if (!r || !job) { @@ -412,11 +410,15 @@ int oakrender_display_renderer_blit_color_managed( olive::ColorTransformJob ctj; if (job->processor) { ctj.set_color_processor( - static_cast(job->processor)->ptr); + static_cast( + oakrender_c_api::box_object(job->processor)) + ->ptr); } if (job->input_texture) { ctj.set_input_texture( - static_cast(job->input_texture)->ptr); + static_cast( + oakrender_c_api::box_object(job->input_texture)) + ->ptr); } ctj.set_input_alpha_association( static_cast(job->input_alpha_association)); @@ -425,7 +427,9 @@ int oakrender_display_renderer_blit_color_managed( ctj.set_transform_matrix(mat_from_float(job->matrix)); ctj.set_crop_matrix(mat_from_float(job->crop_matrix)); - olive::Texture *dst = dst_texture ? tex(dst_texture)->ptr.get() : nullptr; + OakRenderTextureImpl *dst_impl = + oakrender_c_api::to_native(dst_texture); + olive::Texture *dst = dst_impl ? dst_impl->ptr.get() : nullptr; if (params) { r->blit_color_managed(ctj, dst, pod_to_cpp(*params)); } else if (dst) { @@ -442,7 +446,7 @@ int oakrender_display_renderer_blit_color_managed( /* ---- Cross-backend texture download -------------------------------------- */ int oakrender_display_renderer_download_from_texture( - OakRenderRenderer *renderer, int texture_id, + OakRenderRenderer renderer, int texture_id, const oakrender_video_params *params, void *dst_pixels, int linesize) { olive::Renderer *r = ren(renderer); diff --git a/src/render/c_api/ticket.cpp b/src/render/c_api/ticket.cpp index 936896d31..0539e0b6d 100644 --- a/src/render/c_api/ticket.cpp +++ b/src/render/c_api/ticket.cpp @@ -39,25 +39,41 @@ struct TicketHandle { olive::core::TimeRange range; }; -TicketHandle *impl(OakRenderTicket *t) +TicketHandle *impl(OakRenderTicket t) { - return reinterpret_cast(t); + return oakrender_c_api::to_native(t); } -OakRenderTicket *wrap(olive::RenderTicketWatcher *w, - olive::RenderManager::TicketType type) +/** + * @brief Box deleter for tickets: cancels and waits on a running ticket, + * then destroys the watcher and the wrapper. + */ +void ticket_delete(void *object) +{ + auto *h = static_cast(object); + if (h->watcher->is_running()) { + h->watcher->cancel(); + h->watcher->wait_for_finished(); + } + delete h->watcher; + delete h; +} + +OakRenderTicket wrap(olive::RenderTicketWatcher *w, + olive::RenderManager::TicketType type) { if (!w) { - return NULL; + return OakRenderTicket{}; } TicketHandle *h = new (std::nothrow) TicketHandle{ w, type, olive::core::Rational(), olive::core::TimeRange() }; if (!h) { delete w; - return NULL; + return OakRenderTicket{}; } - return reinterpret_cast(h); + return oakrender_c_api::make_handle(h, true, + &ticket_delete); } olive::Node *to_node(OakNodeNode n) @@ -67,17 +83,17 @@ olive::Node *to_node(OakNodeNode n) } // namespace -OakRenderTicket *oakrender_ticket_render_frame( +OakRenderTicket oakrender_ticket_render_frame( const oakrender_video_ticket_params *params, oakrender_ticket_finished_fn cb, void *userdata) { if (!params || !params->output_node.ctx) { - return NULL; + return OakRenderTicket{}; } olive::RenderManager *manager = olive::RenderManager::instance(); if (!manager) { - return NULL; + return OakRenderTicket{}; } try { @@ -86,7 +102,7 @@ OakRenderTicket *oakrender_ticket_render_frame( ? oakcommon_videoparams_get_native(params->video_params) : nullptr; if (!vp) { - return NULL; + return OakRenderTicket{}; } olive::RenderManager::RenderVideoParams rvp( @@ -119,8 +135,11 @@ OakRenderTicket *oakrender_ticket_render_frame( params->force_format); rvp.force_channel_count = params->force_channel_count; rvp.force_color_output = - params->force_color_output ? params->force_color_output->ptr - : nullptr; + params->force_color_output.ctx + ? oakrender_c_api::to_native( + params->force_color_output) + ->ptr + : nullptr; if (params->force_color_transform.ctx) { const olive::ColorTransform *ct = oakcommon_colortransform_get_native( @@ -142,10 +161,10 @@ OakRenderTicket *oakrender_ticket_render_frame( "time", olive::Variant::from_value(olive::core::Rational( int(params->time_num), int(params->time_den)))); - OakRenderTicket *handle = + OakRenderTicket handle = wrap(watcher, olive::RenderManager::k_type_video); - if (!handle) { - return NULL; + if (!handle.ctx) { + return OakRenderTicket{}; } impl(handle)->time = olive::core::Rational( int(params->time_num), int(params->time_den)); @@ -160,22 +179,22 @@ OakRenderTicket *oakrender_ticket_render_frame( watcher->set_ticket(manager->render_frame(rvp)); return handle; } catch (...) { - return NULL; + return OakRenderTicket{}; } } -OakRenderTicket *oakrender_ticket_render_audio( +OakRenderTicket oakrender_ticket_render_audio( OakNodeNode output_node, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den, const OakAudioParams *params, int mode, oakrender_ticket_finished_fn cb, void *userdata) { if (!output_node.ctx || !params) { - return NULL; + return OakRenderTicket{}; } olive::RenderManager *manager = olive::RenderManager::instance(); if (!manager) { - return NULL; + return OakRenderTicket{}; } try { @@ -194,10 +213,10 @@ OakRenderTicket *oakrender_ticket_render_audio( int(olive::RenderManager::k_type_audio))); watcher->set_property("range", olive::Variant::from_value(range)); - OakRenderTicket *handle = + OakRenderTicket handle = wrap(watcher, olive::RenderManager::k_type_audio); - if (!handle) { - return NULL; + if (!handle.ctx) { + return OakRenderTicket{}; } impl(handle)->range = range; @@ -211,48 +230,48 @@ OakRenderTicket *oakrender_ticket_render_audio( watcher->set_ticket(manager->render_audio(rap)); return handle; } catch (...) { - return NULL; + return OakRenderTicket{}; } } -int oakrender_ticket_is_finished(OakRenderTicket *ticket) +int oakrender_ticket_is_finished(OakRenderTicket ticket) { - if (!ticket) { + if (!ticket.ctx) { return OAKRENDER_E_INVALID; } return impl(ticket)->watcher->is_running() ? 0 : 1; } -int oakrender_ticket_wait(OakRenderTicket *ticket) +int oakrender_ticket_wait(OakRenderTicket ticket) { - if (!ticket) { + if (!ticket.ctx) { return OAKRENDER_E_INVALID; } impl(ticket)->watcher->wait_for_finished(); return OAKRENDER_OK; } -int oakrender_ticket_cancel(OakRenderTicket *ticket) +int oakrender_ticket_cancel(OakRenderTicket ticket) { - if (!ticket) { + if (!ticket.ctx) { return OAKRENDER_E_INVALID; } impl(ticket)->watcher->cancel(); return OAKRENDER_OK; } -int oakrender_ticket_get_type(OakRenderTicket *ticket) +int oakrender_ticket_get_type(OakRenderTicket ticket) { - if (!ticket) { + if (!ticket.ctx) { return OAKRENDER_E_INVALID; } return int(impl(ticket)->type); } -int oakrender_ticket_get_time(OakRenderTicket *ticket, int64_t *out_num, +int oakrender_ticket_get_time(OakRenderTicket ticket, int64_t *out_num, int64_t *out_den) { - if (!ticket || !out_num || !out_den) { + if (!ticket.ctx || !out_num || !out_den) { return OAKRENDER_E_INVALID; } *out_num = impl(ticket)->time.numerator(); @@ -260,11 +279,11 @@ int oakrender_ticket_get_time(OakRenderTicket *ticket, int64_t *out_num, return OAKRENDER_OK; } -int oakrender_ticket_get_range(OakRenderTicket *ticket, int64_t *in_num, +int oakrender_ticket_get_range(OakRenderTicket ticket, int64_t *in_num, int64_t *in_den, int64_t *out_num, int64_t *out_den) { - if (!ticket || !in_num || !in_den || !out_num || !out_den) { + if (!ticket.ctx || !in_num || !in_den || !out_num || !out_den) { return OAKRENDER_E_INVALID; } *in_num = impl(ticket)->range.in().numerator(); @@ -274,12 +293,12 @@ int oakrender_ticket_get_range(OakRenderTicket *ticket, int64_t *in_num, return OAKRENDER_OK; } -int oakrender_ticket_get_frame(OakRenderTicket *ticket, OakCodecFrame **out) +int oakrender_ticket_get_frame(OakRenderTicket ticket, OakCodecFrame *out) { - if (!ticket || !out) { + if (!ticket.ctx || !out) { return OAKRENDER_E_INVALID; } - *out = NULL; + *out = OakCodecFrame{}; TicketHandle *h = impl(ticket); if (h->watcher->is_running()) { @@ -296,19 +315,20 @@ int oakrender_ticket_get_frame(OakRenderTicket *ticket, OakCodecFrame **out) return OAKRENDER_E_FAILED; } - OakCodecFrame *handle = new (std::nothrow) OakCodecFrame; - if (!handle) { + auto *frame_impl = new (std::nothrow) OakCodecFrameImpl; + if (!frame_impl) { return OAKRENDER_E_NOMEM; } - handle->ptr = frame; - *out = handle; - return OAKRENDER_OK; + frame_impl->ptr = frame; + *out = oakrender_c_api::make_handle( + frame_impl, true, &oakrender_c_api::delete_as); + return out->ctx ? OAKRENDER_OK : OAKRENDER_E_NOMEM; } -int oakrender_ticket_get_samples(OakRenderTicket *ticket, +int oakrender_ticket_get_samples(OakRenderTicket ticket, OakSampleBuffer **out) { - if (!ticket || !out) { + if (!ticket.ctx || !out) { return OAKRENDER_E_INVALID; } *out = NULL; @@ -330,17 +350,7 @@ int oakrender_ticket_get_samples(OakRenderTicket *ticket, void oakrender_ticket_free(OakRenderTicket *ticket) { - if (!ticket) { - return; - } - - TicketHandle *h = impl(ticket); - if (h->watcher->is_running()) { - h->watcher->cancel(); - h->watcher->wait_for_finished(); - } - delete h->watcher; - delete h; + oakrender_c_api::free_handle(ticket); } int oakrender_manager_set_aggressive_gc(int enabled) diff --git a/src/render/tests/cache_test.cpp b/src/render/tests/cache_test.cpp index 49cdb1ee6..2a973e406 100644 --- a/src/render/tests/cache_test.cpp +++ b/src/render/tests/cache_test.cpp @@ -26,22 +26,23 @@ TEST(OakRenderCacheTest, CreateFree) { const int alive_before = oakrender_debug_alive_count(); - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); EXPECT_EQ(oakrender_debug_alive_count(), alive_before); - // NULL is a no-op + // NULL / already-cleared is a no-op oakrender_cache_free(nullptr); + oakrender_cache_free(&cache); EXPECT_EQ(oakrender_debug_alive_count(), alive_before); } TEST(OakRenderCacheTest, InvalidateValidateStateMachine) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); ASSERT_EQ(oakrender_cache_set_timebase(cache, 1001, 30000), OAKRENDER_OK); // Fresh cache: nothing validated @@ -60,42 +61,49 @@ TEST(OakRenderCacheTest, InvalidateValidateStateMachine) oakrender_cache_invalidate(cache, 0, 30); EXPECT_EQ(oakrender_cache_has_validated_ranges(cache), 0); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); } TEST(OakRenderCacheTest, InvalidateValidateNullCacheIsNoOp) { // Must not crash - oakrender_cache_invalidate(nullptr, 0, 10); - oakrender_cache_validate(nullptr, 0, 10); - EXPECT_EQ(oakrender_cache_has_validated_ranges(nullptr), 0); + oakrender_cache_invalidate(OakRenderCache{}, 0, 10); + oakrender_cache_validate(OakRenderCache{}, 0, 10); + EXPECT_EQ(oakrender_cache_has_validated_ranges(OakRenderCache{}), 0); } TEST(OakRenderCacheTest, SetTimebaseValidation) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); EXPECT_EQ(oakrender_cache_set_timebase(cache, 1, 25), OAKRENDER_OK); - EXPECT_EQ(oakrender_cache_set_timebase(nullptr, 1, 25), + EXPECT_EQ(oakrender_cache_set_timebase(OakRenderCache{}, 1, 25), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_cache_set_timebase(cache, 0, 25), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_cache_set_timebase(cache, 1, 0), OAKRENDER_E_INVALID); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); } TEST(OakRenderCacheTest, SetUuid) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); EXPECT_EQ(oakrender_cache_set_uuid(cache, "{01234567-89ab-cdef-0123-456789abcdef}"), OAKRENDER_OK); EXPECT_EQ(oakrender_cache_set_uuid(cache, nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_cache_set_uuid(nullptr, "x"), OAKRENDER_E_INVALID); + EXPECT_EQ(oakrender_cache_set_uuid(OakRenderCache{}, "x"), + OAKRENDER_E_INVALID); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); +} + +TEST(OakRenderCacheTest, WrapBorrowedNullIsEmpty) +{ + // A borrowed wrapper around a NULL native cache is an empty handle + EXPECT_EQ(oakrender_cache_wrap_borrowed(nullptr).ctx, nullptr); } TEST(OakRenderCacheTest, IndicatorHeightIsConstant) @@ -105,28 +113,29 @@ TEST(OakRenderCacheTest, IndicatorHeightIsConstant) TEST(OakRenderCacheTest, FrameCacheLoadNotFound) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); ASSERT_EQ(oakrender_cache_set_timebase(cache, 1, 25), OAKRENDER_OK); // Nothing cached under this uuid: the load must fail cleanly // (E_NOT_FOUND, or E_FAILED when the decoder layer rejects the read). - OakCodecFrame *frame = nullptr; + OakCodecFrame frame = {}; const int r = oakrender_frame_cache_load(cache, "/tmp", "{no-such-uuid}", 0, &frame); EXPECT_TRUE(r == OAKRENDER_E_NOT_FOUND || r == OAKRENDER_E_FAILED); - EXPECT_EQ(frame, nullptr); + EXPECT_EQ(frame.ctx, nullptr); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); } TEST(OakRenderCacheTest, FrameCacheLoadInvalidArgs) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); - OakCodecFrame *frame = nullptr; + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); + OakCodecFrame frame = {}; - EXPECT_EQ(oakrender_frame_cache_load(nullptr, "/tmp", "u", 0, &frame), + EXPECT_EQ(oakrender_frame_cache_load(OakRenderCache{}, "/tmp", "u", 0, + &frame), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_frame_cache_load(cache, nullptr, "u", 0, &frame), OAKRENDER_E_INVALID); @@ -135,45 +144,45 @@ TEST(OakRenderCacheTest, FrameCacheLoadInvalidArgs) EXPECT_EQ(oakrender_frame_cache_load(cache, "/tmp", "u", 0, nullptr), OAKRENDER_E_INVALID); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); } TEST(OakRenderCacheTest, FrameCacheSaveUnallocatedFrameIsSafe) { - OakRenderCache *cache = oakrender_cache_create(); - ASSERT_NE(cache, nullptr); + OakRenderCache cache = oakrender_cache_create(); + ASSERT_NE(cache.ctx, nullptr); ASSERT_EQ(oakrender_cache_set_timebase(cache, 1, 25), OAKRENDER_OK); // The transitional codec Frame cannot allocate, so the save fails - // internally; the ABI contract is that it never crashes and NULL + // internally; the ABI contract is that it never crashes and empty/NULL // arguments are no-ops. A real save/load round-trip needs the oakcodec // wave (M5) and is covered there. - OakCodecFrame *frame = oakrender_codec_frame_create(); - ASSERT_NE(frame, nullptr); + OakCodecFrame frame = oakrender_codec_frame_create(); + ASSERT_NE(frame.ctx, nullptr); oakrender_frame_cache_save(cache, "/tmp", "{save-test}", frame); - oakrender_frame_cache_save(nullptr, "/tmp", "{save-test}", frame); + oakrender_frame_cache_save(OakRenderCache{}, "/tmp", "{save-test}", frame); oakrender_frame_cache_save(cache, nullptr, "{save-test}", frame); oakrender_frame_cache_save(cache, "/tmp", nullptr, frame); - oakrender_frame_cache_save(cache, "/tmp", "{save-test}", nullptr); - oakrender_codec_frame_free(frame); + oakrender_frame_cache_save(cache, "/tmp", "{save-test}", OakCodecFrame{}); + oakrender_codec_frame_free(&frame); - oakrender_cache_free(cache); + oakrender_cache_free(&cache); } TEST(OakRenderCacheTest, AliveCountReturnsToBaseline) { const int alive_before = oakrender_debug_alive_count(); - OakRenderCache *a = oakrender_cache_create(); - OakRenderCache *b = oakrender_cache_create(); - OakCodecFrame *f = oakrender_codec_frame_create(); - ASSERT_NE(a, nullptr); - ASSERT_NE(b, nullptr); - ASSERT_NE(f, nullptr); + OakRenderCache a = oakrender_cache_create(); + OakRenderCache b = oakrender_cache_create(); + OakCodecFrame f = oakrender_codec_frame_create(); + ASSERT_NE(a.ctx, nullptr); + ASSERT_NE(b.ctx, nullptr); + ASSERT_NE(f.ctx, nullptr); EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 3); - oakrender_codec_frame_free(f); - oakrender_cache_free(a); - oakrender_cache_free(b); + oakrender_codec_frame_free(&f); + oakrender_cache_free(&a); + oakrender_cache_free(&b); EXPECT_EQ(oakrender_debug_alive_count(), alive_before); } diff --git a/src/render/tests/color_test.cpp b/src/render/tests/color_test.cpp index 98b27673d..31ef3e509 100644 --- a/src/render/tests/color_test.cpp +++ b/src/render/tests/color_test.cpp @@ -57,9 +57,9 @@ TEST(OakRenderColorTest, ProcessorCreateConvertKnownValue) { const int alive_before = oakrender_debug_alive_count(); - OakColorProcessor *p = oakrender_color_processor_create( + OakColorProcessor p = oakrender_color_processor_create( "sRGB OETF", "Linear", OAKRENDER_COLOR_DIRECTION_NORMAL); - ASSERT_NE(p, nullptr); + ASSERT_NE(p.ctx, nullptr); ASSERT_EQ(oakrender_color_processor_is_valid(p), 1); // sRGB signal 0.5 -> linear 0.214041 (bundled sRGB_OETF_to_Linear @@ -73,22 +73,25 @@ TEST(OakRenderColorTest, ProcessorCreateConvertKnownValue) EXPECT_NEAR(b, 1.0, 1e-3); EXPECT_NEAR(a, 0.75, 1e-9); // alpha passes through - oakrender_color_processor_free(p); + oakrender_color_processor_free(&p); EXPECT_EQ(oakrender_debug_alive_count(), alive_before); } TEST(OakRenderColorTest, ProcessorCreateInvalidArgs) { EXPECT_EQ(oakrender_color_processor_create(nullptr, "Linear", - OAKRENDER_COLOR_DIRECTION_NORMAL), + OAKRENDER_COLOR_DIRECTION_NORMAL) + .ctx, nullptr); EXPECT_EQ(oakrender_color_processor_create("sRGB OETF", nullptr, - OAKRENDER_COLOR_DIRECTION_NORMAL), + OAKRENDER_COLOR_DIRECTION_NORMAL) + .ctx, nullptr); EXPECT_EQ(oakrender_color_processor_create("", "Linear", - OAKRENDER_COLOR_DIRECTION_NORMAL), + OAKRENDER_COLOR_DIRECTION_NORMAL) + .ctx, nullptr); - EXPECT_EQ(oakrender_color_processor_create("sRGB OETF", "Linear", 7), + EXPECT_EQ(oakrender_color_processor_create("sRGB OETF", "Linear", 7).ctx, nullptr); } @@ -96,9 +99,9 @@ TEST(OakRenderColorTest, ProcessorUnknownColorspaceIsPassThrough) { // OCIO failures are non-fatal: the handle exists, reports invalid, // and conversions pass through (matching the C++ behavior). - OakColorProcessor *p = oakrender_color_processor_create( + OakColorProcessor p = oakrender_color_processor_create( "No Such Colorspace", "Linear", OAKRENDER_COLOR_DIRECTION_NORMAL); - ASSERT_NE(p, nullptr); + ASSERT_NE(p.ctx, nullptr); EXPECT_EQ(oakrender_color_processor_is_valid(p), 0); double r = 0, g = 0, b = 0, a = 0; @@ -111,23 +114,23 @@ TEST(OakRenderColorTest, ProcessorUnknownColorspaceIsPassThrough) EXPECT_NEAR(b, 0.3, 1e-6); EXPECT_NEAR(a, 0.4, 1e-6); - oakrender_color_processor_free(p); + oakrender_color_processor_free(&p); } TEST(OakRenderColorTest, ProcessorConvertInvalidArgs) { double r, g, b, a; - EXPECT_EQ(oakrender_color_processor_convert(nullptr, 0, 0, 0, 0, &r, &g, - &b, &a), + EXPECT_EQ(oakrender_color_processor_convert(OakColorProcessor{}, 0, 0, 0, + 0, &r, &g, &b, &a), OAKRENDER_E_INVALID); - OakColorProcessor *p = oakrender_color_processor_create( + OakColorProcessor p = oakrender_color_processor_create( "Linear", "Linear", OAKRENDER_COLOR_DIRECTION_NORMAL); - ASSERT_NE(p, nullptr); + ASSERT_NE(p.ctx, nullptr); EXPECT_EQ(oakrender_color_processor_convert(p, 0, 0, 0, 0, nullptr, &g, &b, &a), OAKRENDER_E_INVALID); - oakrender_color_processor_free(p); + oakrender_color_processor_free(&p); // NULL free is a no-op oakrender_color_processor_free(nullptr); @@ -135,9 +138,9 @@ TEST(OakRenderColorTest, ProcessorConvertInvalidArgs) TEST(OakRenderColorTest, ProcessorInverseDirection) { - OakColorProcessor *p = oakrender_color_processor_create( + OakColorProcessor p = oakrender_color_processor_create( "Linear", "sRGB OETF", OAKRENDER_COLOR_DIRECTION_NORMAL); - ASSERT_NE(p, nullptr); + ASSERT_NE(p.ctx, nullptr); ASSERT_EQ(oakrender_color_processor_is_valid(p), 1); // Linear 0.214041 -> sRGB ~0.5 (inverse of the forward sample) @@ -146,7 +149,7 @@ TEST(OakRenderColorTest, ProcessorInverseDirection) &b, &a), OAKRENDER_OK); EXPECT_NEAR(r, 0.5, 1e-3); - oakrender_color_processor_free(p); + oakrender_color_processor_free(&p); } TEST(OakRenderColorTest, DisplayTransformKnownDisplay) diff --git a/src/render/tests/manager_test.cpp b/src/render/tests/manager_test.cpp index 82c042a5f..9efcb0828 100644 --- a/src/render/tests/manager_test.cpp +++ b/src/render/tests/manager_test.cpp @@ -30,7 +30,7 @@ namespace { -void noop_frame_ready(OakCodecFrame *frame, int64_t ts, void *userdata) +void noop_frame_ready(OakCodecFrame frame, int64_t ts, void *userdata) { (void) frame; (void) ts; @@ -100,7 +100,7 @@ TEST(OakRenderManagerTest, CancelUnknownRequest) TEST(OakRenderManagerTest, CacherSettersRequireManager) { EXPECT_EQ(oakrender_set_cacher_multicam(OakNodeNode{}), OAKRENDER_E_STATE); - EXPECT_EQ(oakrender_set_display_color_processor(nullptr), + EXPECT_EQ(oakrender_set_display_color_processor(OakColorProcessor{}), OAKRENDER_E_STATE); } diff --git a/src/render/tests/renderer_test.cpp b/src/render/tests/renderer_test.cpp index 3f497add5..723a15618 100644 --- a/src/render/tests/renderer_test.cpp +++ b/src/render/tests/renderer_test.cpp @@ -53,8 +53,8 @@ TEST(OakRenderFrameTest, CreateRetainFree) { const int alive_before = oakrender_debug_alive_count(); - OakCodecFrame *frame = oakrender_codec_frame_create(); - ASSERT_NE(frame, nullptr); + OakCodecFrame frame = oakrender_codec_frame_create(); + ASSERT_NE(frame.ctx, nullptr); EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1); EXPECT_EQ(oakrender_codec_frame_is_allocated(frame), 0); @@ -63,24 +63,26 @@ TEST(OakRenderFrameTest, CreateRetainFree) EXPECT_EQ(oakrender_codec_frame_linesize_bytes(frame), 0); // retain returns the same handle and pairs with exactly one free - EXPECT_EQ(oakrender_codec_frame_retain(frame), frame); + OakCodecFrame retained = oakrender_codec_frame_retain(frame); + EXPECT_EQ(retained.ctx, frame.ctx); EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1); - oakrender_codec_frame_free(frame); + oakrender_codec_frame_free(&retained); EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1); - oakrender_codec_frame_free(frame); + oakrender_codec_frame_free(&frame); EXPECT_EQ(oakrender_debug_alive_count(), alive_before); - // NULL-safe no-ops - EXPECT_EQ(oakrender_codec_frame_retain(nullptr), nullptr); + // Empty-handle / NULL no-ops + EXPECT_EQ(oakrender_codec_frame_retain(OakCodecFrame{}).ctx, nullptr); oakrender_codec_frame_free(nullptr); + oakrender_codec_frame_free(&frame); // already cleared EXPECT_EQ(oakrender_debug_alive_count(), alive_before); } TEST(OakRenderFrameTest, SetGetParams) { - OakCodecFrame *frame = oakrender_codec_frame_create(); - ASSERT_NE(frame, nullptr); + OakCodecFrame frame = oakrender_codec_frame_create(); + ASSERT_NE(frame.ctx, nullptr); const oakrender_video_params in = make_params(); EXPECT_EQ(oakrender_codec_frame_set_video_params(frame, &in), @@ -92,30 +94,31 @@ TEST(OakRenderFrameTest, SetGetParams) oakrender_video_params out = {}; EXPECT_EQ(oakrender_codec_frame_get_params(frame, &out), OAKRENDER_OK); - EXPECT_EQ(oakrender_codec_frame_set_video_params(nullptr, &in), + EXPECT_EQ(oakrender_codec_frame_set_video_params(OakCodecFrame{}, &in), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_codec_frame_set_video_params(frame, nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_codec_frame_get_params(nullptr, &out), + EXPECT_EQ(oakrender_codec_frame_get_params(OakCodecFrame{}, &out), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_codec_frame_get_params(frame, nullptr), OAKRENDER_E_INVALID); - oakrender_codec_frame_free(frame); + oakrender_codec_frame_free(&frame); } TEST(OakRenderFrameTest, AllocateOnTransitionalFrameFails) { // codec/frame.h is still the transition stub (oakcodec, M5): its // allocate() reports failure, which the ABI surfaces as E_FAILED. - OakCodecFrame *frame = oakrender_codec_frame_create(); - ASSERT_NE(frame, nullptr); + OakCodecFrame frame = oakrender_codec_frame_create(); + ASSERT_NE(frame.ctx, nullptr); EXPECT_EQ(oakrender_codec_frame_allocate(frame), OAKRENDER_E_FAILED); - EXPECT_EQ(oakrender_codec_frame_allocate(nullptr), OAKRENDER_E_INVALID); - oakrender_codec_frame_free(frame); + EXPECT_EQ(oakrender_codec_frame_allocate(OakCodecFrame{}), + OAKRENDER_E_INVALID); + oakrender_codec_frame_free(&frame); } -/* ---- Renderer / texture: NULL-argument error paths (no GL) ---------------- */ +/* ---- Renderer / texture: empty-handle error paths (no GL) ----------------- */ TEST(OakRenderDisplayTest, NullArgumentErrorPaths) { @@ -123,47 +126,50 @@ TEST(OakRenderDisplayTest, NullArgumentErrorPaths) oakrender_video_params out = {}; char pixel[4] = {}; - EXPECT_EQ(oakrender_display_texture_create(nullptr, ¶ms, nullptr, 0), + EXPECT_EQ(oakrender_display_texture_create(OakRenderRenderer{}, ¶ms, + nullptr, 0) + .ctx, + nullptr); + EXPECT_EQ(oakrender_display_texture_retain(OakRenderTexture{}).ctx, nullptr); - EXPECT_EQ(oakrender_display_texture_retain(nullptr), nullptr); oakrender_display_texture_free(nullptr); - EXPECT_EQ(oakrender_display_texture_upload(nullptr, pixel, 4), + EXPECT_EQ(oakrender_display_texture_upload(OakRenderTexture{}, pixel, 4), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_display_texture_download(nullptr, pixel, 4), + EXPECT_EQ(oakrender_display_texture_download(OakRenderTexture{}, pixel, 4), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_display_texture_get_params(nullptr, &out), + EXPECT_EQ(oakrender_display_texture_get_params(OakRenderTexture{}, &out), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_display_texture_id(nullptr), 0); + EXPECT_EQ(oakrender_display_texture_id(OakRenderTexture{}), 0); - EXPECT_EQ(oakrender_display_renderer_init(nullptr, nullptr), + EXPECT_EQ(oakrender_display_renderer_init(OakRenderRenderer{}, nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_display_renderer_is_open_gl(nullptr), 0); - EXPECT_EQ(oakrender_display_renderer_is_vulkan(nullptr), 0); + EXPECT_EQ(oakrender_display_renderer_is_open_gl(OakRenderRenderer{}), 0); + EXPECT_EQ(oakrender_display_renderer_is_vulkan(OakRenderRenderer{}), 0); oakrender_display_renderer_destroy(nullptr); oakrender_color_transform_job job = {}; - EXPECT_EQ(oakrender_display_renderer_blit_color_managed(nullptr, &job, - nullptr, ¶ms), + EXPECT_EQ(oakrender_display_renderer_blit_color_managed( + OakRenderRenderer{}, &job, OakRenderTexture{}, ¶ms), OAKRENDER_E_INVALID); EXPECT_EQ(oakrender_display_renderer_download_from_texture( - nullptr, 0, ¶ms, pixel, 4), + OakRenderRenderer{}, 0, ¶ms, pixel, 4), OAKRENDER_E_INVALID); } TEST(OakRenderDisplayTest, CreateDynamicRejectsBadInput) { - EXPECT_EQ(oakrender_display_renderer_create_dynamic(nullptr), nullptr); - EXPECT_EQ(oakrender_display_renderer_create_dynamic(""), nullptr); + EXPECT_EQ(oakrender_display_renderer_create_dynamic(nullptr).ctx, nullptr); + EXPECT_EQ(oakrender_display_renderer_create_dynamic("").ctx, nullptr); } TEST(OakRenderDisplayTest, CreateOpenGLRenderer) { // Construction must not require a GL context; init() would. - OakRenderRenderer *r = oakrender_display_renderer_create_opengl(); - ASSERT_NE(r, nullptr); + OakRenderRenderer r = oakrender_display_renderer_create_opengl(); + ASSERT_NE(r.ctx, nullptr); EXPECT_EQ(oakrender_display_renderer_is_open_gl(r), 1); EXPECT_EQ(oakrender_display_renderer_is_vulkan(r), 0); - oakrender_display_renderer_destroy(r); + oakrender_display_renderer_destroy(&r); } TEST(OakRenderDisplayTest, InitAndTextureLifecycle) diff --git a/src/render/tests/ticket_test.cpp b/src/render/tests/ticket_test.cpp index c92985ccc..f21aa71ec 100644 --- a/src/render/tests/ticket_test.cpp +++ b/src/render/tests/ticket_test.cpp @@ -24,26 +24,28 @@ TEST(OakRenderTicket, NullAndInvalidArgs) { - EXPECT_EQ(oakrender_ticket_render_frame(nullptr, nullptr, nullptr), + EXPECT_EQ(oakrender_ticket_render_frame(nullptr, nullptr, nullptr).ctx, nullptr); oakrender_video_ticket_params params = {}; - EXPECT_EQ(oakrender_ticket_render_frame(¶ms, nullptr, nullptr), + EXPECT_EQ(oakrender_ticket_render_frame(¶ms, nullptr, nullptr).ctx, nullptr); - EXPECT_EQ(oakrender_ticket_is_finished(nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_ticket_wait(nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_ticket_cancel(nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_ticket_get_type(nullptr), OAKRENDER_E_INVALID); - EXPECT_EQ(oakrender_ticket_get_time(nullptr, nullptr, nullptr), + EXPECT_EQ(oakrender_ticket_is_finished(OakRenderTicket{}), + OAKRENDER_E_INVALID); + EXPECT_EQ(oakrender_ticket_wait(OakRenderTicket{}), OAKRENDER_E_INVALID); + EXPECT_EQ(oakrender_ticket_cancel(OakRenderTicket{}), OAKRENDER_E_INVALID); + EXPECT_EQ(oakrender_ticket_get_type(OakRenderTicket{}), + OAKRENDER_E_INVALID); + EXPECT_EQ(oakrender_ticket_get_time(OakRenderTicket{}, nullptr, nullptr), OAKRENDER_E_INVALID); - OakCodecFrame *frame = nullptr; - EXPECT_EQ(oakrender_ticket_get_frame(nullptr, &frame), + OakCodecFrame frame = {}; + EXPECT_EQ(oakrender_ticket_get_frame(OakRenderTicket{}, &frame), OAKRENDER_E_INVALID); OakSampleBuffer *samples = nullptr; - EXPECT_EQ(oakrender_ticket_get_samples(nullptr, &samples), + EXPECT_EQ(oakrender_ticket_get_samples(OakRenderTicket{}, &samples), OAKRENDER_E_INVALID); oakrender_ticket_free(nullptr); // no-op diff --git a/src/task/c_api/manager.cpp b/src/task/c_api/manager.cpp index e3bdd541e..d35010c8d 100644 --- a/src/task/c_api/manager.cpp +++ b/src/task/c_api/manager.cpp @@ -55,13 +55,13 @@ int oaktask_manager_count(void) return olive::TaskManager::instance()->get_task_count(); } -OakTaskTask *oaktask_manager_at(int i) +OakTaskTask oaktask_manager_at(int i) { if (!olive::TaskManager::instance()) { - return NULL; + return OakTaskTask{}; } olive::Task *task = olive::TaskManager::instance()->get_task_at(i); - // Borrowed wrapper: freeing it does not delete the task. + // Borrowed wrapper: releasing it does not delete the task. return oaktask_capi::wrap_borrowed(task); } diff --git a/src/task/c_api/project.cpp b/src/task/c_api/project.cpp index 17b6f2b68..5256a6ee5 100644 --- a/src/task/c_api/project.cpp +++ b/src/task/c_api/project.cpp @@ -36,37 +36,39 @@ using namespace oaktask_capi; namespace { -olive::ProjectImportTask *import_impl(OakTaskTask *t) +olive::ProjectImportTask *import_impl(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return nullptr; } - return dynamic_cast(impl(t)->task); + return dynamic_cast(h->task); } -olive::ProjectLoadBaseTask *load_impl(OakTaskTask *t) +olive::ProjectLoadBaseTask *load_impl(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return nullptr; } - return dynamic_cast(impl(t)->task); + return dynamic_cast(h->task); } } // namespace -OakTaskTask *oaktask_create_project_load(const char *filename) +OakTaskTask oaktask_create_project_load(const char *filename) { if (!filename) { - return NULL; + return OakTaskTask{}; } try { return wrap(new olive::ProjectLoadTask(filename)); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakNodeProject oaktask_load_take_project(OakTaskTask *t) +OakNodeProject oaktask_load_take_project(OakTaskTask t) { olive::ProjectLoadBaseTask *task = load_impl(t); if (!task) { @@ -75,12 +77,12 @@ OakNodeProject oaktask_load_take_project(OakTaskTask *t) return task->take_project(); } -OakTaskTask *oaktask_create_project_save(OakNodeProject project, - const char *filename_or_NULL, - int use_compression) +OakTaskTask oaktask_create_project_save(OakNodeProject project, + const char *filename_or_NULL, + int use_compression) { if (!project.ctx) { - return NULL; + return OakTaskTask{}; } try { auto *task = new olive::ProjectSaveTask(project, @@ -90,36 +92,36 @@ OakTaskTask *oaktask_create_project_save(OakNodeProject project, } return wrap(task); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakTaskTask *oaktask_create_project_import(OakNodeFolder folder, - OakNodeProject project, - const char *const *urls, - int url_count) +OakTaskTask oaktask_create_project_import(OakNodeFolder folder, + OakNodeProject project, + const char *const *urls, + int url_count) { if (!folder.ctx || !project.ctx || (!urls && url_count > 0) || url_count < 0) { - return NULL; + return OakTaskTask{}; } try { std::vector filenames; filenames.reserve(size_t(url_count)); for (int i = 0; i < url_count; i++) { if (!urls[i]) { - return NULL; + return OakTaskTask{}; } filenames.emplace_back(urls[i]); } return wrap( new olive::ProjectImportTask(folder, project, filenames)); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakUndoCommand oaktask_import_take_command(OakTaskTask *t) +OakUndoCommand oaktask_import_take_command(OakTaskTask t) { olive::ProjectImportTask *task = import_impl(t); if (!task) { @@ -128,7 +130,7 @@ OakUndoCommand oaktask_import_take_command(OakTaskTask *t) return task->take_command(); } -int oaktask_import_footage_count(OakTaskTask *t) +int oaktask_import_footage_count(OakTaskTask t) { olive::ProjectImportTask *task = import_impl(t); if (!task) { @@ -137,7 +139,7 @@ int oaktask_import_footage_count(OakTaskTask *t) return int(task->get_imported_footage().size()); } -OakNodeFootage oaktask_import_footage_at(OakTaskTask *t, int index) +OakNodeFootage oaktask_import_footage_at(OakTaskTask t, int index) { olive::ProjectImportTask *task = import_impl(t); if (!task || index < 0 || @@ -151,7 +153,7 @@ OakNodeFootage oaktask_import_footage_at(OakTaskTask *t, int index) return footage; } -int oaktask_import_invalid_count(OakTaskTask *t) +int oaktask_import_invalid_count(OakTaskTask t) { olive::ProjectImportTask *task = import_impl(t); if (!task) { @@ -160,8 +162,8 @@ int oaktask_import_invalid_count(OakTaskTask *t) return int(task->get_invalid_files().size()); } -int oaktask_import_invalid_at(OakTaskTask *t, int index, char *buf, - int buf_size) +int oaktask_import_invalid_at(OakTaskTask t, int index, char *buf, + int buf_size) { olive::ProjectImportTask *task = import_impl(t); if (!task) { @@ -188,46 +190,46 @@ void oaktask_import_set_image_sequence_confirm_cb( }); } -OakTaskTask *oaktask_create_precache(OakNodeFootage footage, int index, - OakNodeSequence sequence) +OakTaskTask oaktask_create_precache(OakNodeFootage footage, int index, + OakNodeSequence sequence) { if (!footage.ctx || !sequence.ctx) { - return NULL; + return OakTaskTask{}; } try { return wrap(new olive::PreCacheTask(footage, index, sequence)); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakTaskTask *oaktask_create_export(OakNodeNode viewer, - OakNodeColorManager color_manager, - const oakcodec_encoding_params *params) +OakTaskTask oaktask_create_export(OakNodeNode viewer, + OakNodeColorManager color_manager, + const oakcodec_encoding_params *params) { if (!viewer.ctx || !params) { - return NULL; + return OakTaskTask{}; } try { return wrap(new olive::ExportTask(viewer, color_manager, *params)); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakTaskTask *oaktask_create_project_load_otio(const char *filename) +OakTaskTask oaktask_create_project_load_otio(const char *filename) { if (!filename) { - return NULL; + return OakTaskTask{}; } try { return wrap(new olive::LoadOTIOTask(filename)); } catch (...) { - return NULL; + return OakTaskTask{}; } } -OakNodeProject oaktask_load_otio_take_project(OakTaskTask *t) +OakNodeProject oaktask_load_otio_take_project(OakTaskTask t) { olive::ProjectLoadBaseTask *task = load_impl(t); if (!task) { @@ -236,16 +238,16 @@ OakNodeProject oaktask_load_otio_take_project(OakTaskTask *t) return task->take_project(); } -OakTaskTask *oaktask_create_project_save_otio(OakNodeProject project, - const char *filename) +OakTaskTask oaktask_create_project_save_otio(OakNodeProject project, + const char *filename) { if (!project.ctx || !filename) { - return NULL; + return OakTaskTask{}; } try { return wrap(new olive::SaveOTIOTask(project, filename)); } catch (...) { - return NULL; + return OakTaskTask{}; } } diff --git a/src/task/c_api/task.cpp b/src/task/c_api/task.cpp index d05b78a72..130a2deb0 100644 --- a/src/task/c_api/task.cpp +++ b/src/task/c_api/task.cpp @@ -27,39 +27,36 @@ using namespace oaktask_capi; void oaktask_task_free(OakTaskTask *t) { - if (!t) { + if (!t || !t->ctx) { return; } - TaskHandle *h = impl(t); - if (h->owns_task) { - delete h->task; - } - delete h; - alive()--; + t->release(t->ctx); + t->ctx = nullptr; } -int oaktask_task_start_sync(OakTaskTask *t) +int oaktask_task_start_sync(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } try { - return impl(t)->task->start() ? 1 : 0; + return h->task->start() ? 1 : 0; } catch (...) { return OAKTASK_E_FAILED; } } -int oaktask_task_start(OakTaskTask *t) +int oaktask_task_start(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } if (!olive::TaskManager::instance()) { return OAKTASK_E_STATE; } - TaskHandle *h = impl(t); if (h->running_on_manager) { return OAKTASK_E_STATE; } @@ -67,26 +64,27 @@ int oaktask_task_start(OakTaskTask *t) olive::TaskManager::instance()->add_task(h->task); h->running_on_manager = true; // Ownership transfers to the manager (deleted by delete_finished() / - // shutdown); freeing this handle later only releases the wrapper. + // shutdown); releasing this handle later only releases the box. h->owns_task = false; return OAKTASK_OK; } -int oaktask_task_cancel(OakTaskTask *t) +int oaktask_task_cancel(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - impl(t)->task->cancel(); + h->task->cancel(); return OAKTASK_OK; } -int oaktask_task_wait(OakTaskTask *t) +int oaktask_task_wait(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - TaskHandle *h = impl(t); if (h->running_on_manager && olive::TaskManager::instance()) { olive::TaskManager::instance()->cancel_task_and_wait(h->task); h->finished = true; @@ -94,47 +92,52 @@ int oaktask_task_wait(OakTaskTask *t) return OAKTASK_OK; } -int oaktask_task_is_finished(const OakTaskTask *t) +int oaktask_task_is_finished(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - return impl(t)->finished.load() ? 1 : 0; + return h->finished.load() ? 1 : 0; } -int oaktask_task_succeeded(const OakTaskTask *t) +int oaktask_task_succeeded(OakTaskTask t) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - return impl(t)->succeeded.load() ? 1 : 0; + return h->succeeded.load() ? 1 : 0; } -int oaktask_task_title(OakTaskTask *t, char *buf, int buf_size) +int oaktask_task_title(OakTaskTask t, char *buf, int buf_size) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - return copy_string(impl(t)->task->get_title(), buf, buf_size); + return copy_string(h->task->get_title(), buf, buf_size); } -int oaktask_task_error(OakTaskTask *t, char *buf, int buf_size) +int oaktask_task_error(OakTaskTask t, char *buf, int buf_size) { - if (!t) { + TaskHandle *h = impl(t); + if (!h) { return OAKTASK_E_INVALID; } - return copy_string(impl(t)->task->get_error(), buf, buf_size); + return copy_string(h->task->get_error(), buf, buf_size); } -int64_t oaktask_task_subscribe(OakTaskTask *t, oaktask_event_fn fn, +int64_t oaktask_task_subscribe(OakTaskTask t, oaktask_event_fn fn, void *userdata) { - if (!t || !fn) { + TaskHandle *h = impl(t); + if (!h || !fn) { return OAKTASK_E_INVALID; } try { - impl(t)->task->add_event_listener( + h->task->add_event_listener( [fn, userdata](olive::Task::EventType type, double value) { int event_id = OAKTASK_EVENT_PROGRESS; switch (type) { diff --git a/src/task/c_api/taskhandle.h b/src/task/c_api/taskhandle.h index da87c5197..58b8c0fb6 100644 --- a/src/task/c_api/taskhandle.h +++ b/src/task/c_api/taskhandle.h @@ -29,8 +29,14 @@ #include "../src/task.h" /** - * @brief Internal handle layout shared by oaktask's c_api translation - * units + * @brief Internal control block behind OakTaskTask, shared by oaktask's + * c_api translation units. + * + * `owns_task` is the owns role: true for factory-created tasks (the last + * release deletes the task) and false once the task runs on the manager + * (oaktask_task_start() flips it; the manager deletes the task) or for + * borrowed wrappers (oaktask_manager_at()): releasing those only + * destroys the box. */ namespace oaktask_capi { @@ -41,6 +47,7 @@ struct TaskHandle { bool running_on_manager; std::atomic finished; std::atomic succeeded; + std::atomic refs; }; inline std::atomic &alive() @@ -49,26 +56,56 @@ inline std::atomic &alive() return g_alive; } -inline TaskHandle *impl(OakTaskTask *t) +inline void task_addref(void *ctx) { - return reinterpret_cast(t); + if (ctx) { + static_cast(ctx)->refs.fetch_add(1); + } } -inline const TaskHandle *impl(const OakTaskTask *t) +inline void task_release(void *ctx) { - return reinterpret_cast(t); + if (!ctx) { + return; + } + TaskHandle *h = static_cast(ctx); + if (h->refs.fetch_sub(1) == 1) { + if (h->owns_task) { + delete h->task; + } + delete h; + alive()--; + } } -inline OakTaskTask *wrap(olive::Task *task) +inline TaskHandle *impl(OakTaskTask t) +{ + if (!t.ctx) { + return nullptr; + } + return static_cast(t.ctx); +} + +inline OakTaskTask make_task_handle(TaskHandle *h) +{ + OakTaskTask handle = {}; + handle.ctx = h; + handle.addref = task_addref; + handle.release = task_release; + handle.abi_version = OAKTASK_ABI_VERSION; + return handle; +} + +inline OakTaskTask wrap(olive::Task *task) { if (!task) { - return NULL; + return OakTaskTask{}; } TaskHandle *h = new (std::nothrow) TaskHandle{ task, true, false, - false, false }; + false, false, 1 }; if (!h) { delete task; - return NULL; + return OakTaskTask{}; } alive()++; @@ -82,26 +119,26 @@ inline OakTaskTask *wrap(olive::Task *task) } }); - return reinterpret_cast(h); + return make_task_handle(h); } /** - * @brief Wrap a manager-owned (borrowed) task. Freeing the handle does + * @brief Wrap a manager-owned (borrowed) task. Releasing the handle does * NOT delete the task. */ -inline OakTaskTask *wrap_borrowed(olive::Task *task) +inline OakTaskTask wrap_borrowed(olive::Task *task) { if (!task) { - return NULL; + return OakTaskTask{}; } TaskHandle *h = new (std::nothrow) TaskHandle{ task, false, true, task->get_start_time() != 0, - false }; + false, 1 }; if (!h) { - return NULL; + return OakTaskTask{}; } alive()++; - return reinterpret_cast(h); + return make_task_handle(h); } inline int copy_string(const std::string &value, char *buf, int buf_size) diff --git a/src/task/src/export/export.cpp b/src/task/src/export/export.cpp index cf4c14c49..e3d96f033 100644 --- a/src/task/src/export/export.cpp +++ b/src/task/src/export/export.cpp @@ -66,10 +66,10 @@ std::string encoder_error(OakEncoder encoder) * * Returns an empty OakFrame (ctx == NULL) on failure. */ -OakFrame copy_frame_to_codec(OakCodecFrame *render_frame) +OakFrame copy_frame_to_codec(OakCodecFrame render_frame) { OakFrame empty = {}; - if (!render_frame) { + if (!render_frame.ctx) { return empty; } @@ -145,12 +145,10 @@ OakNodeSequence sequence_alias_of(OakNodeNode node) ExportTask::ExportTask(OakNodeNode viewer_node, OakNodeColorManager color_manager, const oakcodec_encoding_params ¶ms) - : copier_(nullptr) - , color_manager_({}) + : color_manager_({}) , params_(params) , encoder_({}) , subtitle_encoder_({}) - , color_processor_(nullptr) , frame_time_(0) , null_frame_streak_(0) , audio_time_(0) @@ -162,7 +160,7 @@ ExportTask::ExportTask(OakNodeNode viewer_node, oaknode_node_get_project(viewer_node, &source_project); copier_ = oakrender_project_copier_create(); - if (copier_ && source_project.ctx) { + if (copier_.ctx && source_project.ctx) { oakrender_project_copier_set_project(copier_, source_project); } oaknode_project_free(&source_project); @@ -203,11 +201,9 @@ ExportTask::~ExportTask() if (subtitle_encoder_.ctx) { oakcodec_encoder_free(&subtitle_encoder_); } - if (color_processor_) { - oakrender_color_processor_free(color_processor_); - } + oakrender_color_processor_free(&color_processor_); oaknode_colormanager_free(&color_manager_); - oakrender_project_copier_free(copier_); + oakrender_project_copier_free(&copier_); if (audio_params()) { oakcore_audioparams_free(audio_params()); } @@ -411,13 +407,13 @@ bool ExportTask::run() return success; } -bool ExportTask::frame_downloaded(OakCodecFrame *f, const Rational &time) +bool ExportTask::frame_downloaded(OakCodecFrame f, const Rational &time) { // The worker pool finishes tickets without a result when no worker is // available (or every worker crashed). Grinding through the whole // timeline at several seconds per dead worker looks like a hang, so // fail the export after a short streak of missing frames. - if (!f) { + if (!f.ctx) { if (++null_frame_streak_ >= 8) { set_error("Render workers failed to deliver " + std::to_string(null_frame_streak_) + @@ -457,8 +453,8 @@ bool ExportTask::frame_downloaded(OakCodecFrame *f, const Rational &time) set_error(encoder_error(encoder_)); return false; } - if (it->second) { - oakrender_codec_frame_free(it->second); + if (it->second.ctx) { + oakrender_codec_frame_free(&it->second); } time_map_.erase(it); diff --git a/src/task/src/export/export.h b/src/task/src/export/export.h index e9c0d8c66..f4a1f1d91 100644 --- a/src/task/src/export/export.h +++ b/src/task/src/export/export.h @@ -49,7 +49,7 @@ public: protected: virtual bool run() override; - virtual bool frame_downloaded(OakCodecFrame *frame, + virtual bool frame_downloaded(OakCodecFrame frame, const Rational &time) override; virtual bool audio_downloaded(const TimeRange &range, @@ -60,9 +60,10 @@ protected: private: bool write_audio_loop(const TimeRange &time, OakSampleBuffer *samples); - OakRenderProjectCopier *copier_; + /** Owned handle; the final release frees the copied project too. */ + OakRenderProjectCopier copier_ = {}; - std::map time_map_; + std::map time_map_; struct TimeRangeLess { bool operator()(const TimeRange &a, const TimeRange &b) const @@ -81,7 +82,8 @@ private: OakEncoder subtitle_encoder_; - OakColorProcessor *color_processor_; + /** Owned handle (empty ctx when color conversion is off). */ + OakColorProcessor color_processor_ = {}; int64_t frame_time_; diff --git a/src/task/src/precache/precachetask.cpp b/src/task/src/precache/precachetask.cpp index 3594c0246..7aa823eed 100644 --- a/src/task/src/precache/precachetask.cpp +++ b/src/task/src/precache/precachetask.cpp @@ -128,7 +128,7 @@ bool PreCacheTask::run() // Get list of invalidated ranges TimeRange intersection; - OakTimelineWorkArea *workarea = + OakTimelineWorkArea workarea = oaktimeline_workarea_of(oaknode_footage_as_node(footage_)); int64_t len_n = 0, len_d = 1; @@ -137,12 +137,12 @@ bool PreCacheTask::run() int wa_enabled = 0; int wa_in = 0, wa_ind = 1, wa_out = 0, wa_outd = 1; - if (workarea) { + if (workarea.ctx) { oaktimeline_workarea_get(workarea, &wa_in, &wa_ind, &wa_out, &wa_outd, &wa_enabled); } - if (workarea && wa_enabled) { + if (workarea.ctx && wa_enabled) { // If we're caching only in-out, limit the range to that intersection = TimeRange(Rational(wa_in, wa_ind), Rational(wa_out, wa_outd)); @@ -154,8 +154,11 @@ bool PreCacheTask::run() 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); + int range_count = oakrender_cache_get_invalidated_ranges( - reinterpret_cast(cache), + cache_handle, intersection.in().numerator(), intersection.in().denominator(), intersection.out().numerator(), intersection.out().denominator(), nullptr, 0); @@ -164,7 +167,7 @@ bool PreCacheTask::run() if (range_count > 0) { std::vector flat(size_t(range_count) * 4); oakrender_cache_get_invalidated_ranges( - reinterpret_cast(cache), + cache_handle, intersection.in().numerator(), intersection.in().denominator(), intersection.out().numerator(), intersection.out().denominator(), flat.data(), range_count); @@ -184,10 +187,14 @@ bool PreCacheTask::run() oaknode_colormanager_free(&color_manager); + // Release the borrowed boxes (the objects stay with their owners). + oaktimeline_workarea_free(&workarea); + oakrender_cache_free(&cache_handle); + return true; } -bool PreCacheTask::frame_downloaded(OakCodecFrame *frame, +bool PreCacheTask::frame_downloaded(OakCodecFrame frame, const Rational &time) { // Do nothing. Pre-cache essentially just creates more frames in the cache, it doesn't need to do diff --git a/src/task/src/precache/precachetask.h b/src/task/src/precache/precachetask.h index 6b0112fad..f972196b4 100644 --- a/src/task/src/precache/precachetask.h +++ b/src/task/src/precache/precachetask.h @@ -39,7 +39,7 @@ public: protected: virtual bool run() override; - virtual bool frame_downloaded(OakCodecFrame *frame, + virtual bool frame_downloaded(OakCodecFrame frame, const Rational &time) override; virtual bool audio_downloaded(const TimeRange &range, diff --git a/src/task/src/render/render.cpp b/src/task/src/render/render.cpp index a4ef70801..c7123cc48 100644 --- a/src/task/src/render/render.cpp +++ b/src/task/src/render/render.cpp @@ -71,7 +71,7 @@ RenderTask::~RenderTask() } } -void RenderTask::on_ticket_finished(OakRenderTicket *ticket) +void RenderTask::on_ticket_finished(OakRenderTicket ticket) { finished_mutex_.lock(); finished_tickets_.push_back(ticket); @@ -112,16 +112,16 @@ bool RenderTask::start_video_ticket(OakNodeColorManager manager, params.force_color_transform = force.color_transform; params.cache = cache; - OakRenderTicket *ticket = oakrender_ticket_render_frame( + OakRenderTicket ticket = oakrender_ticket_render_frame( ¶ms, - [](OakRenderTicket *t, void *userdata) { + [](OakRenderTicket t, void *userdata) { static_cast(userdata)->on_ticket_finished(t); }, this); // Per-frame call: release the borrowed handle box (the ticket keeps // the native node, not the handle). oaknode_node_free(&output_node); - if (!ticket) { + if (!ticket.ctx) { return false; } @@ -152,15 +152,15 @@ bool RenderTask::render(OakNodeColorManager manager, continue; } - OakRenderTicket *ticket = oakrender_ticket_render_audio( + OakRenderTicket ticket = oakrender_ticket_render_audio( output_node, range.in().numerator(), range.in().denominator(), range.out().numerator(), range.out().denominator(), audio_params_, render_mode, - [](OakRenderTicket *t, void *userdata) { + [](OakRenderTicket t, void *userdata) { static_cast(userdata)->on_ticket_finished(t); }, this); - if (ticket) { + if (ticket.ctx) { finished_mutex_.lock(); running_ticket_list_.push_back(ticket); running_tickets_++; @@ -309,7 +309,7 @@ bool RenderTask::render(OakNodeColorManager manager, while (result && !is_cancelled()) { while (!finished_tickets_.empty() && !is_cancelled() && result) { - OakRenderTicket *ticket = finished_tickets_.front(); + OakRenderTicket ticket = finished_tickets_.front(); finished_tickets_.pop_front(); loop_lock.unlock(); @@ -344,7 +344,7 @@ bool RenderTask::render(OakNodeColorManager manager, oakrender_ticket_get_time(ticket, &tn, &td); Rational time((int)tn, (int)td); - OakCodecFrame *frame = nullptr; + OakCodecFrame frame = {}; oakrender_ticket_get_frame(ticket, &frame); if (two_step_frame_rendering() && @@ -363,8 +363,8 @@ bool RenderTask::render(OakNodeColorManager manager, emit_progress(progress_counter / total_length); } - if (frame) { - oakrender_codec_frame_free(frame); + if (frame.ctx) { + oakrender_codec_frame_free(&frame); } if (next_frame_index < frame_times.size()) { @@ -374,7 +374,7 @@ bool RenderTask::render(OakNodeColorManager manager, } } - oakrender_ticket_free(ticket); + oakrender_ticket_free(&ticket); loop_lock.lock(); } @@ -393,10 +393,10 @@ bool RenderTask::render(OakNodeColorManager manager, if (is_cancelled() || !result) { // Cancel every ticket we created - for (OakRenderTicket *ticket : running_ticket_list_) { + for (OakRenderTicket &ticket : running_ticket_list_) { oakrender_ticket_cancel(ticket); oakrender_ticket_wait(ticket); - oakrender_ticket_free(ticket); + oakrender_ticket_free(&ticket); } } @@ -405,7 +405,7 @@ bool RenderTask::render(OakNodeColorManager manager, return result; } -bool RenderTask::download_frame(OakCodecFrame *frame, const Rational &time) +bool RenderTask::download_frame(OakCodecFrame frame, const Rational &time) { (void)frame; (void)time; diff --git a/src/task/src/render/render.h b/src/task/src/render/render.h index 6914d2f0d..3ad3c40d1 100644 --- a/src/task/src/render/render.h +++ b/src/task/src/render/render.h @@ -65,7 +65,7 @@ protected: bool has_matrix = false; int format = -1; /**< PixelFormat as int, -1 = off */ int channel_count = 0; /**< 0 = off */ - OakColorProcessor *color_output = nullptr; /**< borrowed */ + OakColorProcessor color_output = {}; /**< borrowed; empty ctx = none */ OakColorTransform color_transform = {}; /**< empty ctx = default */ }; @@ -76,9 +76,9 @@ protected: OakNodeFrameCache *cache, const ForceParams &force); - virtual bool download_frame(OakCodecFrame *frame, const Rational &time); + virtual bool download_frame(OakCodecFrame frame, const Rational &time); - virtual bool frame_downloaded(OakCodecFrame *frame, + virtual bool frame_downloaded(OakCodecFrame frame, const Rational &time) = 0; virtual bool audio_downloaded(const TimeRange &range, @@ -160,7 +160,7 @@ private: Rational time; }; - void on_ticket_finished(OakRenderTicket *ticket); + void on_ticket_finished(OakRenderTicket ticket); bool start_video_ticket(OakNodeColorManager manager, const Rational &time, int mode, @@ -175,8 +175,8 @@ private: std::mutex finished_mutex_; std::condition_variable finished_wait_cond_; - std::deque finished_tickets_; - std::vector running_ticket_list_; + std::deque finished_tickets_; + std::vector running_ticket_list_; int running_tickets_; bool native_progress_signalling_; diff --git a/src/task/tests/task_test.cpp b/src/task/tests/task_test.cpp index c021c4b3b..56b04e602 100644 --- a/src/task/tests/task_test.cpp +++ b/src/task/tests/task_test.cpp @@ -59,26 +59,26 @@ TEST_F(OakTaskFixture, TaskLifecycleSync) { oaktask_task_free(nullptr); // no-op on NULL - OakTaskTask *t = oaktask_create_project_save(project_, nullptr, 0); - ASSERT_NE(t, nullptr); + OakTaskTask t = oaktask_create_project_save(project_, nullptr, 0); + ASSERT_NE(t.ctx, nullptr); char title[128]; EXPECT_GT(oaktask_task_title(t, title, sizeof(title)), 0); - EXPECT_EQ(oaktask_task_title(nullptr, title, sizeof(title)), + EXPECT_EQ(oaktask_task_title(OakTaskTask{}, title, sizeof(title)), OAKTASK_E_INVALID); EXPECT_EQ(oaktask_task_is_finished(t), 0); - EXPECT_EQ(oaktask_task_is_finished(nullptr), OAKTASK_E_INVALID); + EXPECT_EQ(oaktask_task_is_finished(OakTaskTask{}), OAKTASK_E_INVALID); // Save to an override path in the temp dir std::string path = (std::filesystem::temp_directory_path() / "oaktask_save_test.ove") .string(); - oaktask_task_free(t); + oaktask_task_free(&t); t = oaktask_create_project_save(project_, path.c_str(), 0); - ASSERT_NE(t, nullptr); + ASSERT_NE(t.ctx, nullptr); EXPECT_EQ(oaktask_task_start_sync(t), 1); EXPECT_EQ(oaktask_task_is_finished(t), 1); @@ -87,7 +87,7 @@ TEST_F(OakTaskFixture, TaskLifecycleSync) char err[256]; EXPECT_GE(oaktask_task_error(t, err, sizeof(err)), 0); - oaktask_task_free(t); + oaktask_task_free(&t); EXPECT_EQ(oaktask_debug_alive_count(), 0); std::filesystem::remove(path); @@ -95,9 +95,9 @@ TEST_F(OakTaskFixture, TaskLifecycleSync) TEST_F(OakTaskFixture, LoadMissingFileFails) { - OakTaskTask *t = + OakTaskTask t = oaktask_create_project_load("/nonexistent/definitely-missing.ove"); - ASSERT_NE(t, nullptr); + ASSERT_NE(t.ctx, nullptr); EXPECT_EQ(oaktask_task_start_sync(t), 0); @@ -105,9 +105,9 @@ TEST_F(OakTaskFixture, LoadMissingFileFails) EXPECT_GT(oaktask_task_error(t, err, sizeof(err)), 0); EXPECT_EQ(oaktask_load_take_project(t).ctx, nullptr); - oaktask_task_free(t); + oaktask_task_free(&t); - EXPECT_EQ(oaktask_create_project_load(nullptr), nullptr); + EXPECT_EQ(oaktask_create_project_load(nullptr).ctx, nullptr); } TEST_F(OakTaskFixture, SaveLoadRoundTrip) @@ -116,14 +116,14 @@ TEST_F(OakTaskFixture, SaveLoadRoundTrip) (std::filesystem::temp_directory_path() / "oaktask_roundtrip.ove") .string(); - OakTaskTask *save = + OakTaskTask save = oaktask_create_project_save(project_, path.c_str(), 0); - ASSERT_NE(save, nullptr); + ASSERT_NE(save.ctx, nullptr); ASSERT_EQ(oaktask_task_start_sync(save), 1); - oaktask_task_free(save); + oaktask_task_free(&save); - OakTaskTask *load = oaktask_create_project_load(path.c_str()); - ASSERT_NE(load, nullptr); + OakTaskTask load = oaktask_create_project_load(path.c_str()); + ASSERT_NE(load.ctx, nullptr); ASSERT_EQ(oaktask_task_start_sync(load), 1); OakNodeProject loaded = oaktask_load_take_project(load); @@ -131,7 +131,7 @@ TEST_F(OakTaskFixture, SaveLoadRoundTrip) EXPECT_EQ(oaktask_load_take_project(load).ctx, nullptr); oaknode_project_free(&loaded); - oaktask_task_free(load); + oaktask_task_free(&load); std::filesystem::remove(path); } @@ -142,9 +142,9 @@ TEST_F(OakTaskFixture, ImportDemoFootage) ASSERT_NE(folder.ctx, nullptr); const char *urls[] = { OAK_REPO_ROOT "/tests/demo.mp4" }; - OakTaskTask *t = + OakTaskTask t = oaktask_create_project_import(folder, project_, urls, 1); - ASSERT_NE(t, nullptr); + ASSERT_NE(t.ctx, nullptr); ASSERT_EQ(oaktask_task_start_sync(t), 1); @@ -181,12 +181,13 @@ TEST_F(OakTaskFixture, ImportDemoFootage) EXPECT_EQ(oaknode_folder_child_count(folder), 0); oakundo_command_free(&cmd); - oaktask_task_free(t); + oaktask_task_free(&t); EXPECT_EQ( - oaktask_create_project_import(OakNodeFolder{}, project_, urls, 1), + oaktask_create_project_import(OakNodeFolder{}, project_, urls, 1).ctx, nullptr); - EXPECT_EQ(oaktask_import_footage_count(nullptr), OAKTASK_E_INVALID); + EXPECT_EQ(oaktask_import_footage_count(OakTaskTask{}), + OAKTASK_E_INVALID); EXPECT_EQ(oaktask_debug_alive_count(), 0); } @@ -203,7 +204,7 @@ TEST(OakTaskManager, InitShutdownAndCodecSubmitter) EXPECT_GE(oaktask_manager_count(), 0); oaktask_manager_delete_finished(); - EXPECT_EQ(oaktask_manager_at(0), nullptr); + EXPECT_EQ(oaktask_manager_at(0).ctx, nullptr); oaktask_manager_shutdown(); EXPECT_EQ(oakcodec_task_submit_is_registered(), 0); @@ -223,8 +224,8 @@ TEST(OakTaskManager, AsyncStartAndSubscribe) (std::filesystem::temp_directory_path() / "oaktask_async.ove") .string(); - OakTaskTask *t = oaktask_create_project_save(project, path.c_str(), 0); - ASSERT_NE(t, nullptr); + OakTaskTask t = oaktask_create_project_save(project, path.c_str(), 0); + ASSERT_NE(t.ctx, nullptr); struct Seen { int started; @@ -262,7 +263,7 @@ TEST(OakTaskManager, AsyncStartAndSubscribe) EXPECT_TRUE(std::filesystem::exists(path)); std::filesystem::remove(path); - oaktask_task_free(t); + oaktask_task_free(&t); oaknode_project_free(&project); oaktask_manager_shutdown(); @@ -324,15 +325,18 @@ TEST(OakTaskConform, SubmittedConformProducesPcm) TEST(OakTaskRenderFamily, FactoryErrorPaths) { - EXPECT_EQ(oaktask_create_precache(OakNodeFootage{}, 0, OakNodeSequence{}), - nullptr); + EXPECT_EQ( + oaktask_create_precache(OakNodeFootage{}, 0, OakNodeSequence{}).ctx, + nullptr); oakcodec_encoding_params params = {}; EXPECT_EQ(oaktask_create_export(OakNodeNode{}, OakNodeColorManager{}, - ¶ms), + ¶ms) + .ctx, nullptr); EXPECT_EQ(oaktask_create_export(OakNodeNode{}, OakNodeColorManager{}, - nullptr), + nullptr) + .ctx, nullptr); } @@ -358,15 +362,15 @@ TEST(OakTaskRenderFamily, ExportTaskConstruction) sizeof(params.filename) - 1); params.video_enabled = 1; - OakTaskTask *t = oaktask_create_export( + OakTaskTask t = oaktask_create_export( oaknode_sequence_as_node(sequence), cm, ¶ms); - ASSERT_NE(t, nullptr); + ASSERT_NE(t.ctx, nullptr); char title[128]; EXPECT_GT(oaktask_task_title(t, title, sizeof(title)), 0); // Running needs a render manager; not available in this binary - oaktask_task_free(t); + oaktask_task_free(&t); oaknode_colormanager_free(&cm); oaknode_project_free(&project); @@ -407,29 +411,29 @@ TEST(OakTaskOTIO, SaveLoadRoundTrip) std::error_code ec; std::filesystem::remove(path, ec); - OakTaskTask *save = + OakTaskTask save = oaktask_create_project_save_otio(project, path.c_str()); - ASSERT_NE(save, nullptr); + ASSERT_NE(save.ctx, nullptr); ASSERT_EQ(oaktask_task_start_sync(save), 1); - oaktask_task_free(save); + oaktask_task_free(&save); ASSERT_TRUE(std::filesystem::exists(path)); - OakTaskTask *load = oaktask_create_project_load_otio(path.c_str()); - ASSERT_NE(load, nullptr); + OakTaskTask load = oaktask_create_project_load_otio(path.c_str()); + ASSERT_NE(load.ctx, nullptr); ASSERT_EQ(oaktask_task_start_sync(load), 1); OakNodeProject loaded = oaktask_load_otio_take_project(load); ASSERT_NE(loaded.ctx, nullptr); oaknode_project_free(&loaded); - oaktask_task_free(load); + oaktask_task_free(&load); std::filesystem::remove(path, ec); oaknode_project_free(&project); EXPECT_EQ(oaktask_debug_alive_count(), 0); - EXPECT_EQ(oaktask_create_project_load_otio(nullptr), nullptr); - EXPECT_EQ(oaktask_create_project_save_otio(OakNodeProject{}, "x"), + EXPECT_EQ(oaktask_create_project_load_otio(nullptr).ctx, nullptr); + EXPECT_EQ(oaktask_create_project_save_otio(OakNodeProject{}, "x").ctx, nullptr); } diff --git a/src/timeline/c_api/marker.cpp b/src/timeline/c_api/marker.cpp index 38b4de056..a171fef5f 100644 --- a/src/timeline/c_api/marker.cpp +++ b/src/timeline/c_api/marker.cpp @@ -24,23 +24,14 @@ #include "../src/timelinemarker.h" #include "../../undo/c_api/commandhandle.h" +#include "timelinehandle.h" namespace { -olive::TimelineMarkerList *impl(OakTimelineMarkerList *h) +olive::TimelineMarkerList *impl(OakTimelineMarkerList h) { - return reinterpret_cast(h); -} - -const olive::TimelineMarkerList *impl(const OakTimelineMarkerList *h) -{ - return reinterpret_cast(h); -} - -OakTimelineMarkerList *wrap(olive::TimelineMarkerList *l) -{ - return reinterpret_cast(l); + return oaktimeline_capi::to_native(h); } OakUndoCommand wrap_command(olive::UndoCommand *command) @@ -56,9 +47,9 @@ olive::core::Rational rat(int n, int d) return olive::core::Rational(n, d); } -olive::TimelineMarker *marker_at(OakTimelineMarkerList *list, int index) +olive::TimelineMarker *marker_at(OakTimelineMarkerList list, int index) { - if (!list || index < 0 || index >= int(impl(list)->size())) { + if (!list.ctx || index < 0 || index >= int(impl(list)->size())) { return nullptr; } return impl(list)->at(index); @@ -66,34 +57,40 @@ olive::TimelineMarker *marker_at(OakTimelineMarkerList *list, int index) } // namespace -OakTimelineMarkerList *oaktimeline_marker_list_of(OakNodeNode owner) +OakTimelineMarkerList oaktimeline_marker_list_of(OakNodeNode owner) { if (!owner.ctx) { - return NULL; + return OakTimelineMarkerList{}; } OakNodeMarkerList *markers = NULL; if (oaknode_node_get_markers(owner, &markers) != OAKNODE_OK) { - return NULL; + return OakTimelineMarkerList{}; } - return reinterpret_cast(markers); + // Borrowed box: the list stays owned by the viewer node. + return oaktimeline_capi::make_handle( + reinterpret_cast(markers)); } -int oaktimeline_marker_count(const OakTimelineMarkerList *list, - int *out_count) +void oaktimeline_marker_list_free(OakTimelineMarkerList *list) { - if (!list || !out_count) { + oaktimeline_capi::free_handle(list); +} + +int oaktimeline_marker_count(OakTimelineMarkerList list, int *out_count) +{ + if (!list.ctx || !out_count) { return OAKTIMELINE_E_INVALID; } *out_count = int(impl(list)->size()); return OAKTIMELINE_OK; } -int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, +int oaktimeline_marker_at(OakTimelineMarkerList list, int index, int *in_num, int *in_den, int *out_num, int *out_den, int *color, char *name_buf, int buf_size) { - if (!list) { + if (!list.ctx) { return OAKTIMELINE_E_INVALID; } if (index < 0 || index >= int(impl(list)->size())) { @@ -127,10 +124,10 @@ int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, } OakUndoCommand oaktimeline_marker_add_command( - OakTimelineMarkerList *list, int in_num, int in_den, int out_num, + OakTimelineMarkerList list, int in_num, int in_den, int out_num, int out_den, const char *name, int color) { - if (!list) { + if (!list.ctx) { return OakUndoCommand{}; } @@ -146,7 +143,7 @@ OakUndoCommand oaktimeline_marker_add_command( } OakUndoCommand oaktimeline_marker_remove_at_command( - OakTimelineMarkerList *list, int index) + OakTimelineMarkerList list, int index) { olive::TimelineMarker *m = marker_at(list, index); if (!m) { @@ -161,7 +158,7 @@ OakUndoCommand oaktimeline_marker_remove_at_command( } OakUndoCommand oaktimeline_marker_set_time_command( - OakTimelineMarkerList *list, int index, int in_num, int in_den, + OakTimelineMarkerList list, int index, int in_num, int in_den, int out_num, int out_den) { olive::TimelineMarker *m = marker_at(list, index); @@ -179,7 +176,7 @@ OakUndoCommand oaktimeline_marker_set_time_command( } OakUndoCommand oaktimeline_marker_set_props_command( - OakTimelineMarkerList *list, int index, int color, const char *name) + OakTimelineMarkerList list, int index, int color, const char *name) { olive::TimelineMarker *m = marker_at(list, index); if (!m || (color < 0 && !name)) { @@ -202,10 +199,10 @@ OakUndoCommand oaktimeline_marker_set_props_command( } } -int oaktimeline_marker_list_load(OakTimelineMarkerList *list, +int oaktimeline_marker_list_load(OakTimelineMarkerList list, OakXmlReader reader) { - if (!list || !reader.ctx) { + if (!list.ctx || !reader.ctx) { return OAKTIMELINE_E_INVALID; } @@ -222,10 +219,10 @@ int oaktimeline_marker_list_load(OakTimelineMarkerList *list, } } -int oaktimeline_marker_list_save(const OakTimelineMarkerList *list, +int oaktimeline_marker_list_save(OakTimelineMarkerList list, OakXmlWriter writer) { - if (!list || !writer.ctx) { + if (!list.ctx || !writer.ctx) { return OAKTIMELINE_E_INVALID; } diff --git a/src/timeline/c_api/timelinehandle.h b/src/timeline/c_api/timelinehandle.h new file mode 100644 index 000000000..9bd927553 --- /dev/null +++ b/src/timeline/c_api/timelinehandle.h @@ -0,0 +1,125 @@ +/*** + + Oak Video Editor - Non-Linear Video Editor + Copyright (C) 2026 Oak Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef OAK_TIMELINE_CAPI_TIMELINEHANDLE_H +#define OAK_TIMELINE_CAPI_TIMELINEHANDLE_H + +#include +#include + +#include "timeline/error.h" + +/** + * @brief Internal control block behind the oaktimeline value handles, + * shared between the c_api translation units. + * + * Both public handle types (OakTimelineMarkerList, OakTimelineWorkArea) + * are borrowed references into viewer nodes: the box never owns the + * underlying object, so release() only destroys the box. A single + * generic box and addref/release pair serves both families (identical + * ctx/addref/release/abi_version layout). + */ +struct OakTimelineBox { + void *object; + std::atomic refs; + + explicit OakTimelineBox(void *o) + : object(o) + , refs(1) + { + } +}; + +namespace oaktimeline_capi +{ + +inline void handle_addref(void *ctx) +{ + if (ctx) { + static_cast(ctx)->refs.fetch_add(1); + } +} + +inline void handle_release(void *ctx) +{ + if (!ctx) { + return; + } + OakTimelineBox *box = static_cast(ctx); + if (box->refs.fetch_sub(1) == 1) { + delete box; + } +} + +/** + * @brief Wrap a borrowed native object in a value handle with reference + * count 1. Returns an empty handle (ctx == nullptr) for a null + * object or on allocation failure. + */ +template +inline Handle make_handle(void *object) +{ + Handle handle = {}; + if (!object) { + return handle; + } + + OakTimelineBox *box = new (std::nothrow) OakTimelineBox(object); + if (!box) { + return handle; + } + + handle.ctx = box; + handle.addref = handle_addref; + handle.release = handle_release; + handle.abi_version = OAKTIMELINE_ABI_VERSION; + return handle; +} + +/** + * @brief Unwrap a value handle to the native C++ pointer (nullptr for + * an empty handle). + */ +template +inline T *to_native(Handle h) +{ + if (!h.ctx) { + return nullptr; + } + return static_cast(static_cast(h.ctx)->object); +} + +/** + * @brief Shared free() body: release the caller's reference (box only) + * and null out the handle. NULL and ctx == NULL are no-ops. + */ +template +inline void free_handle(Handle *h) +{ + if (!h || !h->ctx) { + return; + } + h->release(h->ctx); + h->ctx = nullptr; +} + +} // namespace oaktimeline_capi + +#endif // OAK_TIMELINE_CAPI_TIMELINEHANDLE_H diff --git a/src/timeline/c_api/workarea.cpp b/src/timeline/c_api/workarea.cpp index b901e3dba..4a1e87752 100644 --- a/src/timeline/c_api/workarea.cpp +++ b/src/timeline/c_api/workarea.cpp @@ -25,18 +25,14 @@ #include "../src/timelineundoworkarea.h" #include "../src/timelineworkarea.h" #include "../../undo/c_api/commandhandle.h" +#include "timelinehandle.h" namespace { -olive::TimelineWorkArea *impl(OakTimelineWorkArea *h) +olive::TimelineWorkArea *impl(OakTimelineWorkArea h) { - return reinterpret_cast(h); -} - -const olive::TimelineWorkArea *impl(const OakTimelineWorkArea *h) -{ - return reinterpret_cast(h); + return oaktimeline_capi::to_native(h); } OakUndoCommand wrap_command(olive::UndoCommand *command) @@ -54,24 +50,31 @@ olive::core::Rational rat(int64_t n, int64_t d) } // namespace -OakTimelineWorkArea *oaktimeline_workarea_of(OakNodeNode owner) +OakTimelineWorkArea oaktimeline_workarea_of(OakNodeNode owner) { if (!owner.ctx) { - return NULL; + return OakTimelineWorkArea{}; } OakNodeWorkArea *workarea = NULL; if (oaknode_node_get_work_area(owner, &workarea) != OAKNODE_OK) { - return NULL; + return OakTimelineWorkArea{}; } - return reinterpret_cast(workarea); + // Borrowed box: the work area stays owned by the viewer node. + return oaktimeline_capi::make_handle( + reinterpret_cast(workarea)); } -int oaktimeline_workarea_get(const OakTimelineWorkArea *w, int *in_num, +void oaktimeline_workarea_free(OakTimelineWorkArea *w) +{ + oaktimeline_capi::free_handle(w); +} + +int oaktimeline_workarea_get(OakTimelineWorkArea w, int *in_num, int *in_den, int *out_num, int *out_den, int *enabled) { - if (!w) { + if (!w.ctx) { return OAKTIMELINE_E_INVALID; } @@ -93,10 +96,10 @@ int oaktimeline_workarea_get(const OakTimelineWorkArea *w, int *in_num, return OAKTIMELINE_OK; } -int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, +int oaktimeline_workarea_set_range(OakTimelineWorkArea w, int in_num, int in_den, int out_num, int out_den) { - if (!w) { + if (!w.ctx) { return OAKTIMELINE_E_INVALID; } @@ -110,11 +113,11 @@ int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, } OakUndoCommand oaktimeline_workarea_set_range_command( - OakTimelineWorkArea *w, int in_num, int in_den, int out_num, + OakTimelineWorkArea w, int in_num, int in_den, int out_num, int out_den, int old_in_num, int old_in_den, int old_out_num, int old_out_den) { - if (!w) { + if (!w.ctx) { return OakUndoCommand{}; } @@ -130,9 +133,9 @@ OakUndoCommand oaktimeline_workarea_set_range_command( } OakUndoCommand oaktimeline_workarea_set_enabled_command( - OakTimelineWorkArea *w, int enabled) + OakTimelineWorkArea w, int enabled) { - if (!w) { + if (!w.ctx) { return OakUndoCommand{}; } @@ -158,9 +161,9 @@ int oaktimeline_workarea_reset(int *in_num, int *in_den, int *out_num, return OAKTIMELINE_OK; } -int oaktimeline_workarea_load(OakTimelineWorkArea *w, OakXmlReader reader) +int oaktimeline_workarea_load(OakTimelineWorkArea w, OakXmlReader reader) { - if (!w || !reader.ctx) { + if (!w.ctx || !reader.ctx) { return OAKTIMELINE_E_INVALID; } @@ -176,10 +179,10 @@ int oaktimeline_workarea_load(OakTimelineWorkArea *w, OakXmlReader reader) } } -int oaktimeline_workarea_save(const OakTimelineWorkArea *w, +int oaktimeline_workarea_save(OakTimelineWorkArea w, OakXmlWriter writer) { - if (!w || !writer.ctx) { + if (!w.ctx || !writer.ctx) { return OAKTIMELINE_E_INVALID; } diff --git a/src/timeline/tests/timeline_test.cpp b/src/timeline/tests/timeline_test.cpp index 5e84eeacf..2f656e304 100644 --- a/src/timeline/tests/timeline_test.cpp +++ b/src/timeline/tests/timeline_test.cpp @@ -65,19 +65,19 @@ protected: TEST_F(TimelineSequenceFixture, MarkerListOfReturnsList) { - EXPECT_NE(oaktimeline_marker_list_of(node_), nullptr); - EXPECT_EQ(oaktimeline_marker_list_of(OakNodeNode{}), nullptr); + EXPECT_NE(oaktimeline_marker_list_of(node_).ctx, nullptr); + EXPECT_EQ(oaktimeline_marker_list_of(OakNodeNode{}).ctx, nullptr); } TEST_F(TimelineSequenceFixture, MarkerAddCountAtRemove) { - OakTimelineMarkerList *list = oaktimeline_marker_list_of(node_); - ASSERT_NE(list, nullptr); + OakTimelineMarkerList list = oaktimeline_marker_list_of(node_); + ASSERT_NE(list.ctx, nullptr); int count = -1; EXPECT_EQ(oaktimeline_marker_count(list, &count), OAKTIMELINE_OK); EXPECT_EQ(count, 0); - EXPECT_EQ(oaktimeline_marker_count(nullptr, &count), + EXPECT_EQ(oaktimeline_marker_count(OakTimelineMarkerList{}, &count), OAKTIMELINE_E_INVALID); EXPECT_EQ(oaktimeline_marker_count(list, nullptr), OAKTIMELINE_E_INVALID); @@ -106,8 +106,9 @@ TEST_F(TimelineSequenceFixture, MarkerAddCountAtRemove) EXPECT_EQ(oaktimeline_marker_at(list, 9, &in_n, &in_d, &out_n, &out_d, &color, name, sizeof(name)), OAKTIMELINE_E_NOT_FOUND); - EXPECT_EQ(oaktimeline_marker_at(nullptr, 0, &in_n, &in_d, &out_n, - &out_d, &color, name, sizeof(name)), + EXPECT_EQ(oaktimeline_marker_at(OakTimelineMarkerList{}, 0, &in_n, + &in_d, &out_n, &out_d, &color, name, + sizeof(name)), OAKTIMELINE_E_INVALID); OakUndoCommand rm = oaktimeline_marker_remove_at_command(list, 0); @@ -123,8 +124,8 @@ TEST_F(TimelineSequenceFixture, MarkerAddCountAtRemove) TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) { - OakTimelineMarkerList *list = oaktimeline_marker_list_of(node_); - ASSERT_NE(list, nullptr); + OakTimelineMarkerList list = oaktimeline_marker_list_of(node_); + ASSERT_NE(list.ctx, nullptr); OakUndoCommand add = oaktimeline_marker_add_command(list, 0, 1, 1, 1, "a", 1); @@ -174,8 +175,8 @@ TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) TEST_F(TimelineSequenceFixture, MarkerListXmlRoundTrip) { - OakTimelineMarkerList *list = oaktimeline_marker_list_of(node_); - ASSERT_NE(list, nullptr); + OakTimelineMarkerList list = oaktimeline_marker_list_of(node_); + ASSERT_NE(list.ctx, nullptr); OakUndoCommand add = oaktimeline_marker_add_command(list, 1, 3, 2, 3, "xml", 4); @@ -203,9 +204,9 @@ TEST_F(TimelineSequenceFixture, MarkerListXmlRoundTrip) ASSERT_EQ(oaknode_project_add_node(project_, oaknode_sequence_as_node(seq2)), OAKNODE_OK); - OakTimelineMarkerList *list2 = + OakTimelineMarkerList list2 = oaktimeline_marker_list_of(oaknode_sequence_as_node(seq2)); - ASSERT_NE(list2, nullptr); + ASSERT_NE(list2.ctx, nullptr); OakXmlReader reader = oakcommon_xml_reader_init(xml); ASSERT_NE(reader.ctx, nullptr); @@ -228,7 +229,7 @@ TEST_F(TimelineSequenceFixture, MarkerListXmlRoundTrip) EXPECT_EQ(color, 4); EXPECT_STREQ(name, "xml"); - EXPECT_EQ(oaktimeline_marker_list_load(nullptr, reader), + EXPECT_EQ(oaktimeline_marker_list_load(OakTimelineMarkerList{}, reader), OAKTIMELINE_E_INVALID); } @@ -237,9 +238,9 @@ TEST_F(TimelineSequenceFixture, MarkerListXmlRoundTrip) TEST_F(TimelineSequenceFixture, WorkareaGetSetLive) { - OakTimelineWorkArea *w = oaktimeline_workarea_of(node_); - ASSERT_NE(w, nullptr); - EXPECT_EQ(oaktimeline_workarea_of(OakNodeNode{}), nullptr); + OakTimelineWorkArea w = oaktimeline_workarea_of(node_); + ASSERT_NE(w.ctx, nullptr); + EXPECT_EQ(oaktimeline_workarea_of(OakNodeNode{}).ctx, nullptr); int in_n = 0, in_d = 0, out_n = 0, out_d = 0, enabled = -1; EXPECT_EQ(oaktimeline_workarea_get(w, &in_n, &in_d, &out_n, &out_d, @@ -257,17 +258,18 @@ TEST_F(TimelineSequenceFixture, WorkareaGetSetLive) EXPECT_EQ(out_n, 3); EXPECT_EQ(out_d, 4); - EXPECT_EQ(oaktimeline_workarea_set_range(nullptr, 0, 1, 1, 1), + EXPECT_EQ(oaktimeline_workarea_set_range(OakTimelineWorkArea{}, 0, 1, 1, + 1), OAKTIMELINE_E_INVALID); - EXPECT_EQ(oaktimeline_workarea_get(nullptr, &in_n, &in_d, &out_n, - &out_d, &enabled), + EXPECT_EQ(oaktimeline_workarea_get(OakTimelineWorkArea{}, &in_n, &in_d, + &out_n, &out_d, &enabled), OAKTIMELINE_E_INVALID); } TEST_F(TimelineSequenceFixture, WorkareaUndoCommands) { - OakTimelineWorkArea *w = oaktimeline_workarea_of(node_); - ASSERT_NE(w, nullptr); + OakTimelineWorkArea w = oaktimeline_workarea_of(node_); + ASSERT_NE(w.ctx, nullptr); OakUndoCommand range_cmd = oaktimeline_workarea_set_range_command( w, 1, 2, 1, 1, 0, 1, 1, 1); @@ -304,11 +306,13 @@ TEST_F(TimelineSequenceFixture, WorkareaUndoCommands) EXPECT_EQ(enabled, 0); oakundo_command_free(&enable_cmd); - EXPECT_EQ(oaktimeline_workarea_set_range_command(nullptr, 0, 1, 1, 1, - 0, 1, 1, 1) + EXPECT_EQ(oaktimeline_workarea_set_range_command(OakTimelineWorkArea{}, + 0, 1, 1, 1, 0, 1, 1, 1) .ctx, nullptr); - EXPECT_EQ(oaktimeline_workarea_set_enabled_command(nullptr, 1).ctx, + EXPECT_EQ(oaktimeline_workarea_set_enabled_command(OakTimelineWorkArea{}, + 1) + .ctx, nullptr); } @@ -326,8 +330,8 @@ TEST_F(TimelineSequenceFixture, WorkareaResetSentinels) TEST_F(TimelineSequenceFixture, WorkareaXmlRoundTrip) { - OakTimelineWorkArea *w = oaktimeline_workarea_of(node_); - ASSERT_NE(w, nullptr); + OakTimelineWorkArea w = oaktimeline_workarea_of(node_); + ASSERT_NE(w.ctx, nullptr); EXPECT_EQ(oaktimeline_workarea_set_range(w, 1, 3, 2, 3), OAKTIMELINE_OK); @@ -350,9 +354,9 @@ TEST_F(TimelineSequenceFixture, WorkareaXmlRoundTrip) ASSERT_EQ(oaknode_project_add_node(project_, oaknode_sequence_as_node(seq2)), OAKNODE_OK); - OakTimelineWorkArea *w2 = + OakTimelineWorkArea w2 = oaktimeline_workarea_of(oaknode_sequence_as_node(seq2)); - ASSERT_NE(w2, nullptr); + ASSERT_NE(w2.ctx, nullptr); OakXmlReader reader = oakcommon_xml_reader_init(xml); ASSERT_NE(reader.ctx, nullptr);