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
This commit is contained in:
2026-08-07 18:05:34 +08:00
parent 67176281d9
commit 0462842f8c
48 changed files with 1462 additions and 919 deletions
+48 -24
View File
@@ -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 --------------------------------------------------------------- */
-10
View File
@@ -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
+29 -18
View File
@@ -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 ------------------------------------------------- */
+26 -9
View File
@@ -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
}
+10
View File
@@ -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. */
+8 -8
View File
@@ -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) -------------------------------------- */
+117 -66
View File
@@ -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) ---------------------------------------- */
+37 -23
View File
@@ -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);
/**
+2
View File
@@ -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. */
+3 -2
View File
@@ -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);
+27 -26
View File
@@ -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;
+26 -15
View File
@@ -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. */
+2
View File
@@ -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. */
+33 -19
View File
@@ -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
+28 -11
View File
@@ -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
+14 -15
View File
@@ -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<std::mutex> 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)
+4 -4
View File
@@ -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;
+12 -12
View File
@@ -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)
+2 -2
View File
@@ -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);
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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;
+44 -42
View File
@@ -49,14 +49,9 @@ public:
using olive::PlaybackCache::validate;
};
OakRenderCacheImpl *impl(OakRenderCache *c)
OakRenderCacheImpl *impl(OakRenderCache c)
{
return reinterpret_cast<OakRenderCacheImpl *>(c);
}
const OakRenderCacheImpl *impl(const OakRenderCache *c)
{
return reinterpret_cast<const OakRenderCacheImpl *>(c);
return oakrender_c_api::to_native<OakRenderCacheImpl>(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<OakRenderCache *>(c);
return oakrender_c_api::make_handle<OakRenderCache>(
new OakRenderCacheImpl(), true,
&oakrender_c_api::delete_as<OakRenderCacheImpl>);
} 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<OakRenderCache>(
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<OakCodecFrame>(
frame_impl, true,
&oakrender_c_api::delete_as<OakCodecFrameImpl>);
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<OakCodecFrameImpl>(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;
}
+29 -26
View File
@@ -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<OakColorProcessor>(
impl, true, &oakrender_c_api::delete_as<OakColorProcessorImpl>);
} 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<OakColorProcessorImpl>(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<OakColorProcessorImpl>(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<OakColorProcessorImpl>(processor);
OakCodecFrameImpl *f =
oakrender_c_api::to_native<OakCodecFrameImpl>(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;
+15 -13
View File
@@ -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<olive::ProjectCopier *>(h);
return oakrender_c_api::to_native<olive::ProjectCopier>(h);
}
} // namespace
OakRenderProjectCopier *oakrender_project_copier_create(void)
OakRenderProjectCopier oakrender_project_copier_create(void)
{
try {
return reinterpret_cast<OakRenderProjectCopier *>(
new olive::ProjectCopier());
return oakrender_c_api::make_handle<OakRenderProjectCopier>(
new olive::ProjectCopier(), true,
&oakrender_c_api::delete_as<olive::ProjectCopier>);
} 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.
+167 -12
View File
@@ -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 <atomic>
#include <new>
#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<int> refcount{ 1 };
};
struct OakCodecFrame {
/**
* @brief Boxed object behind OakCodecFrame: a shared_ptr-managed CPU
* frame.
*/
struct OakCodecFrameImpl {
olive::FramePtr ptr;
std::atomic<int> 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<uint32_t> 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<OakRenderBox *>(ctx)->refs.fetch_add(1);
}
}
inline void handle_release(void *ctx)
{
if (!ctx) {
return;
}
OakRenderBox *box = static_cast<OakRenderBox *>(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 <typename Handle>
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 <typename T, typename Handle>
inline T *to_native(Handle h)
{
if (!h.ctx) {
return nullptr;
}
return static_cast<T *>(static_cast<OakRenderBox *>(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<const OakRenderBox *>(ctx)->object;
}
/**
* @brief Deleter callback stamping the concrete C++ type.
*/
template <typename T>
inline void delete_as(void *object)
{
delete static_cast<T *>(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 <typename Handle>
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
+11 -8
View File
@@ -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<olive::FramePtr>();
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<OakCodecFrame>(
impl, true,
&oakrender_c_api::delete_as<OakCodecFrameImpl>);
}
}
}
@@ -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<OakColorProcessorImpl>(p_or_NULL);
manager->get_cacher()->set_display_color_processor(p ? p->ptr : nullptr);
return OAKRENDER_OK;
}
+117 -113
View File
@@ -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<olive::Renderer>(h);
}
OakCodecFrame *frm(OakCodecFrame *h)
void renderer_delete(void *object)
{
return h;
}
olive::Renderer *ren(OakRenderRenderer *h)
{
return reinterpret_cast<olive::Renderer *>(h);
}
const olive::Renderer *ren(const OakRenderRenderer *h)
{
return reinterpret_cast<const olive::Renderer *>(h);
auto *r = static_cast<olive::Renderer *>(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<OakRenderRenderer *>(r);
return oakrender_c_api::make_handle<OakRenderRenderer>(
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<OakRenderRenderer *>(new olive::OpenGLRenderer());
return oakrender_c_api::make_handle<OakRenderRenderer>(
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<OakRenderTexture>(
impl, true, &oakrender_c_api::delete_as<OakRenderTextureImpl>);
} 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<OakRenderTextureImpl>(texture);
if (!t || !pixels || !t->ptr) {
return OAKRENDER_E_INVALID;
}
try {
tex(texture)->ptr->upload(const_cast<void *>(pixels), linesize);
t->ptr->upload(const_cast<void *>(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<OakRenderTextureImpl>(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<OakRenderTexture *>(texture))->ptr) {
OakRenderTextureImpl *t =
oakrender_c_api::to_native<OakRenderTextureImpl>(texture);
if (!t || !out || !t->ptr) {
return OAKRENDER_E_INVALID;
}
*out = cpp_to_pod(tex(const_cast<OakRenderTexture *>(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<OakRenderTexture *>(texture))->ptr) {
OakRenderTextureImpl *t =
oakrender_c_api::to_native<OakRenderTextureImpl>(texture);
if (!t || !t->ptr) {
return 0;
}
return tex(const_cast<OakRenderTexture *>(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<OakCodecFrame>(
impl, true, &oakrender_c_api::delete_as<OakCodecFrameImpl>);
} 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<OakCodecFrameImpl>(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<OakCodecFrame *>(frame))->ptr) {
OakCodecFrameImpl *f =
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
if (!f || !out || !f->ptr) {
return OAKRENDER_E_INVALID;
}
*out = cpp_to_pod(
frm(const_cast<OakCodecFrame *>(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<OakCodecFrameImpl>(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<OakCodecFrameImpl>(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<OakCodecFrame *>(frame))->ptr) {
OakCodecFrameImpl *f =
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
if (!f || !f->ptr) {
return nullptr;
}
return frm(const_cast<OakCodecFrame *>(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<OakCodecFrame *>(frame))->ptr) {
OakCodecFrameImpl *f =
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
if (!f || !f->ptr) {
return 0;
}
return frm(const_cast<OakCodecFrame *>(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<OakCodecFrame *>(frame))->ptr) {
OakCodecFrameImpl *f =
oakrender_c_api::to_native<OakCodecFrameImpl>(frame);
if (!f || !f->ptr) {
return 0;
}
return frm(const_cast<OakCodecFrame *>(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<const OakColorProcessor *>(job->processor)->ptr);
static_cast<OakColorProcessorImpl *>(
oakrender_c_api::box_object(job->processor))
->ptr);
}
if (job->input_texture) {
ctj.set_input_texture(
static_cast<OakRenderTexture *>(job->input_texture)->ptr);
static_cast<OakRenderTextureImpl *>(
oakrender_c_api::box_object(job->input_texture))
->ptr);
}
ctj.set_input_alpha_association(
static_cast<olive::AlphaAssociated>(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<OakRenderTextureImpl>(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);
+67 -57
View File
@@ -39,25 +39,41 @@ struct TicketHandle {
olive::core::TimeRange range;
};
TicketHandle *impl(OakRenderTicket *t)
TicketHandle *impl(OakRenderTicket t)
{
return reinterpret_cast<TicketHandle *>(t);
return oakrender_c_api::to_native<TicketHandle>(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<TicketHandle *>(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<OakRenderTicket *>(h);
return oakrender_c_api::make_handle<OakRenderTicket>(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<OakColorProcessorImpl>(
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<OakCodecFrame>(
frame_impl, true, &oakrender_c_api::delete_as<OakCodecFrameImpl>);
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)
+55 -46
View File
@@ -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);
}
+21 -18
View File
@@ -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)
+2 -2
View File
@@ -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);
}
+40 -34
View File
@@ -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, &params, nullptr, 0),
EXPECT_EQ(oakrender_display_texture_create(OakRenderRenderer{}, &params,
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, &params),
EXPECT_EQ(oakrender_display_renderer_blit_color_managed(
OakRenderRenderer{}, &job, OakRenderTexture{}, &params),
OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_display_renderer_download_from_texture(
nullptr, 0, &params, pixel, 4),
OakRenderRenderer{}, 0, &params, 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)
+12 -10
View File
@@ -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(&params, nullptr, nullptr),
EXPECT_EQ(oakrender_ticket_render_frame(&params, 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
+3 -3
View File
@@ -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);
}
+47 -45
View File
@@ -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<olive::ProjectImportTask *>(impl(t)->task);
return dynamic_cast<olive::ProjectImportTask *>(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<olive::ProjectLoadBaseTask *>(impl(t)->task);
return dynamic_cast<olive::ProjectLoadBaseTask *>(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<std::string> 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{};
}
}
+38 -35
View File
@@ -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) {
+54 -17
View File
@@ -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<bool> finished;
std::atomic<bool> succeeded;
std::atomic<uint32_t> refs;
};
inline std::atomic<int> &alive()
@@ -49,26 +56,56 @@ inline std::atomic<int> &alive()
return g_alive;
}
inline TaskHandle *impl(OakTaskTask *t)
inline void task_addref(void *ctx)
{
return reinterpret_cast<TaskHandle *>(t);
if (ctx) {
static_cast<TaskHandle *>(ctx)->refs.fetch_add(1);
}
}
inline const TaskHandle *impl(const OakTaskTask *t)
inline void task_release(void *ctx)
{
return reinterpret_cast<const TaskHandle *>(t);
if (!ctx) {
return;
}
TaskHandle *h = static_cast<TaskHandle *>(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<TaskHandle *>(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<OakTaskTask *>(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<OakTaskTask *>(h);
return make_task_handle(h);
}
inline int copy_string(const std::string &value, char *buf, int buf_size)
+10 -14
View File
@@ -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 &params)
: 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);
+6 -4
View File
@@ -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<Rational, OakCodecFrame *> time_map_;
std::map<Rational, OakCodecFrame> 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_;
+13 -6
View File
@@ -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<OakRenderCache *>(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<int64_t> flat(size_t(range_count) * 4);
oakrender_cache_get_invalidated_ranges(
reinterpret_cast<OakRenderCache *>(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
+1 -1
View File
@@ -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,
+15 -15
View File
@@ -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(
&params,
[](OakRenderTicket *t, void *userdata) {
[](OakRenderTicket t, void *userdata) {
static_cast<RenderTask *>(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<RenderTask *>(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;
+6 -6
View File
@@ -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<OakRenderTicket *> finished_tickets_;
std::vector<OakRenderTicket *> running_ticket_list_;
std::deque<OakRenderTicket> finished_tickets_;
std::vector<OakRenderTicket> running_ticket_list_;
int running_tickets_;
bool native_progress_signalling_;
+45 -41
View File
@@ -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{},
&params),
&params)
.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, &params);
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);
}
+29 -32
View File
@@ -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<olive::TimelineMarkerList *>(h);
}
const olive::TimelineMarkerList *impl(const OakTimelineMarkerList *h)
{
return reinterpret_cast<const olive::TimelineMarkerList *>(h);
}
OakTimelineMarkerList *wrap(olive::TimelineMarkerList *l)
{
return reinterpret_cast<OakTimelineMarkerList *>(l);
return oaktimeline_capi::to_native<olive::TimelineMarkerList>(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<OakTimelineMarkerList *>(markers);
// Borrowed box: the list stays owned by the viewer node.
return oaktimeline_capi::make_handle<OakTimelineMarkerList>(
reinterpret_cast<olive::TimelineMarkerList *>(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;
}
+125
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_TIMELINE_CAPI_TIMELINEHANDLE_H
#define OAK_TIMELINE_CAPI_TIMELINEHANDLE_H
#include <atomic>
#include <new>
#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<uint32_t> refs;
explicit OakTimelineBox(void *o)
: object(o)
, refs(1)
{
}
};
namespace oaktimeline_capi
{
inline void handle_addref(void *ctx)
{
if (ctx) {
static_cast<OakTimelineBox *>(ctx)->refs.fetch_add(1);
}
}
inline void handle_release(void *ctx)
{
if (!ctx) {
return;
}
OakTimelineBox *box = static_cast<OakTimelineBox *>(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 <typename Handle>
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 <typename T, typename Handle>
inline T *to_native(Handle h)
{
if (!h.ctx) {
return nullptr;
}
return static_cast<T *>(static_cast<OakTimelineBox *>(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 <typename Handle>
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
+26 -23
View File
@@ -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<olive::TimelineWorkArea *>(h);
}
const olive::TimelineWorkArea *impl(const OakTimelineWorkArea *h)
{
return reinterpret_cast<const olive::TimelineWorkArea *>(h);
return oaktimeline_capi::to_native<olive::TimelineWorkArea>(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<OakTimelineWorkArea *>(workarea);
// Borrowed box: the work area stays owned by the viewer node.
return oaktimeline_capi::make_handle<OakTimelineWorkArea>(
reinterpret_cast<olive::TimelineWorkArea *>(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;
}
+33 -29
View File
@@ -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);