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:
+48
-24
@@ -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 --------------------------------------------------------------- */
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ TEST(OakRenderFrameTest, CreateRetainFree)
|
||||
{
|
||||
const int alive_before = oakrender_debug_alive_count();
|
||||
|
||||
OakCodecFrame *frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
OakCodecFrame frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
|
||||
|
||||
EXPECT_EQ(oakrender_codec_frame_is_allocated(frame), 0);
|
||||
@@ -63,24 +63,26 @@ TEST(OakRenderFrameTest, CreateRetainFree)
|
||||
EXPECT_EQ(oakrender_codec_frame_linesize_bytes(frame), 0);
|
||||
|
||||
// retain returns the same handle and pairs with exactly one free
|
||||
EXPECT_EQ(oakrender_codec_frame_retain(frame), frame);
|
||||
OakCodecFrame retained = oakrender_codec_frame_retain(frame);
|
||||
EXPECT_EQ(retained.ctx, frame.ctx);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
|
||||
oakrender_codec_frame_free(frame);
|
||||
oakrender_codec_frame_free(&retained);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
|
||||
|
||||
oakrender_codec_frame_free(frame);
|
||||
oakrender_codec_frame_free(&frame);
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
|
||||
|
||||
// NULL-safe no-ops
|
||||
EXPECT_EQ(oakrender_codec_frame_retain(nullptr), nullptr);
|
||||
// Empty-handle / NULL no-ops
|
||||
EXPECT_EQ(oakrender_codec_frame_retain(OakCodecFrame{}).ctx, nullptr);
|
||||
oakrender_codec_frame_free(nullptr);
|
||||
oakrender_codec_frame_free(&frame); // already cleared
|
||||
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
|
||||
}
|
||||
|
||||
TEST(OakRenderFrameTest, SetGetParams)
|
||||
{
|
||||
OakCodecFrame *frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
OakCodecFrame frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame.ctx, nullptr);
|
||||
|
||||
const oakrender_video_params in = make_params();
|
||||
EXPECT_EQ(oakrender_codec_frame_set_video_params(frame, &in),
|
||||
@@ -92,30 +94,31 @@ TEST(OakRenderFrameTest, SetGetParams)
|
||||
oakrender_video_params out = {};
|
||||
EXPECT_EQ(oakrender_codec_frame_get_params(frame, &out), OAKRENDER_OK);
|
||||
|
||||
EXPECT_EQ(oakrender_codec_frame_set_video_params(nullptr, &in),
|
||||
EXPECT_EQ(oakrender_codec_frame_set_video_params(OakCodecFrame{}, &in),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_codec_frame_set_video_params(frame, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_codec_frame_get_params(nullptr, &out),
|
||||
EXPECT_EQ(oakrender_codec_frame_get_params(OakCodecFrame{}, &out),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_codec_frame_get_params(frame, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
|
||||
oakrender_codec_frame_free(frame);
|
||||
oakrender_codec_frame_free(&frame);
|
||||
}
|
||||
|
||||
TEST(OakRenderFrameTest, AllocateOnTransitionalFrameFails)
|
||||
{
|
||||
// codec/frame.h is still the transition stub (oakcodec, M5): its
|
||||
// allocate() reports failure, which the ABI surfaces as E_FAILED.
|
||||
OakCodecFrame *frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
OakCodecFrame frame = oakrender_codec_frame_create();
|
||||
ASSERT_NE(frame.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_codec_frame_allocate(frame), OAKRENDER_E_FAILED);
|
||||
EXPECT_EQ(oakrender_codec_frame_allocate(nullptr), OAKRENDER_E_INVALID);
|
||||
oakrender_codec_frame_free(frame);
|
||||
EXPECT_EQ(oakrender_codec_frame_allocate(OakCodecFrame{}),
|
||||
OAKRENDER_E_INVALID);
|
||||
oakrender_codec_frame_free(&frame);
|
||||
}
|
||||
|
||||
/* ---- Renderer / texture: NULL-argument error paths (no GL) ---------------- */
|
||||
/* ---- Renderer / texture: empty-handle error paths (no GL) ----------------- */
|
||||
|
||||
TEST(OakRenderDisplayTest, NullArgumentErrorPaths)
|
||||
{
|
||||
@@ -123,47 +126,50 @@ TEST(OakRenderDisplayTest, NullArgumentErrorPaths)
|
||||
oakrender_video_params out = {};
|
||||
char pixel[4] = {};
|
||||
|
||||
EXPECT_EQ(oakrender_display_texture_create(nullptr, ¶ms, nullptr, 0),
|
||||
EXPECT_EQ(oakrender_display_texture_create(OakRenderRenderer{}, ¶ms,
|
||||
nullptr, 0)
|
||||
.ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oakrender_display_texture_retain(OakRenderTexture{}).ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oakrender_display_texture_retain(nullptr), nullptr);
|
||||
oakrender_display_texture_free(nullptr);
|
||||
EXPECT_EQ(oakrender_display_texture_upload(nullptr, pixel, 4),
|
||||
EXPECT_EQ(oakrender_display_texture_upload(OakRenderTexture{}, pixel, 4),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_display_texture_download(nullptr, pixel, 4),
|
||||
EXPECT_EQ(oakrender_display_texture_download(OakRenderTexture{}, pixel, 4),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_display_texture_get_params(nullptr, &out),
|
||||
EXPECT_EQ(oakrender_display_texture_get_params(OakRenderTexture{}, &out),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_display_texture_id(nullptr), 0);
|
||||
EXPECT_EQ(oakrender_display_texture_id(OakRenderTexture{}), 0);
|
||||
|
||||
EXPECT_EQ(oakrender_display_renderer_init(nullptr, nullptr),
|
||||
EXPECT_EQ(oakrender_display_renderer_init(OakRenderRenderer{}, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_open_gl(nullptr), 0);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_vulkan(nullptr), 0);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_open_gl(OakRenderRenderer{}), 0);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_vulkan(OakRenderRenderer{}), 0);
|
||||
oakrender_display_renderer_destroy(nullptr);
|
||||
|
||||
oakrender_color_transform_job job = {};
|
||||
EXPECT_EQ(oakrender_display_renderer_blit_color_managed(nullptr, &job,
|
||||
nullptr, ¶ms),
|
||||
EXPECT_EQ(oakrender_display_renderer_blit_color_managed(
|
||||
OakRenderRenderer{}, &job, OakRenderTexture{}, ¶ms),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_display_renderer_download_from_texture(
|
||||
nullptr, 0, ¶ms, pixel, 4),
|
||||
OakRenderRenderer{}, 0, ¶ms, pixel, 4),
|
||||
OAKRENDER_E_INVALID);
|
||||
}
|
||||
|
||||
TEST(OakRenderDisplayTest, CreateDynamicRejectsBadInput)
|
||||
{
|
||||
EXPECT_EQ(oakrender_display_renderer_create_dynamic(nullptr), nullptr);
|
||||
EXPECT_EQ(oakrender_display_renderer_create_dynamic(""), nullptr);
|
||||
EXPECT_EQ(oakrender_display_renderer_create_dynamic(nullptr).ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_display_renderer_create_dynamic("").ctx, nullptr);
|
||||
}
|
||||
|
||||
TEST(OakRenderDisplayTest, CreateOpenGLRenderer)
|
||||
{
|
||||
// Construction must not require a GL context; init() would.
|
||||
OakRenderRenderer *r = oakrender_display_renderer_create_opengl();
|
||||
ASSERT_NE(r, nullptr);
|
||||
OakRenderRenderer r = oakrender_display_renderer_create_opengl();
|
||||
ASSERT_NE(r.ctx, nullptr);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_open_gl(r), 1);
|
||||
EXPECT_EQ(oakrender_display_renderer_is_vulkan(r), 0);
|
||||
oakrender_display_renderer_destroy(r);
|
||||
oakrender_display_renderer_destroy(&r);
|
||||
}
|
||||
|
||||
TEST(OakRenderDisplayTest, InitAndTextureLifecycle)
|
||||
|
||||
@@ -24,26 +24,28 @@
|
||||
|
||||
TEST(OakRenderTicket, NullAndInvalidArgs)
|
||||
{
|
||||
EXPECT_EQ(oakrender_ticket_render_frame(nullptr, nullptr, nullptr),
|
||||
EXPECT_EQ(oakrender_ticket_render_frame(nullptr, nullptr, nullptr).ctx,
|
||||
nullptr);
|
||||
|
||||
oakrender_video_ticket_params params = {};
|
||||
EXPECT_EQ(oakrender_ticket_render_frame(¶ms, nullptr, nullptr),
|
||||
EXPECT_EQ(oakrender_ticket_render_frame(¶ms, nullptr, nullptr).ctx,
|
||||
nullptr);
|
||||
|
||||
EXPECT_EQ(oakrender_ticket_is_finished(nullptr), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_wait(nullptr), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_cancel(nullptr), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_get_type(nullptr), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_get_time(nullptr, nullptr, nullptr),
|
||||
EXPECT_EQ(oakrender_ticket_is_finished(OakRenderTicket{}),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_wait(OakRenderTicket{}), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_cancel(OakRenderTicket{}), OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_get_type(OakRenderTicket{}),
|
||||
OAKRENDER_E_INVALID);
|
||||
EXPECT_EQ(oakrender_ticket_get_time(OakRenderTicket{}, nullptr, nullptr),
|
||||
OAKRENDER_E_INVALID);
|
||||
|
||||
OakCodecFrame *frame = nullptr;
|
||||
EXPECT_EQ(oakrender_ticket_get_frame(nullptr, &frame),
|
||||
OakCodecFrame frame = {};
|
||||
EXPECT_EQ(oakrender_ticket_get_frame(OakRenderTicket{}, &frame),
|
||||
OAKRENDER_E_INVALID);
|
||||
|
||||
OakSampleBuffer *samples = nullptr;
|
||||
EXPECT_EQ(oakrender_ticket_get_samples(nullptr, &samples),
|
||||
EXPECT_EQ(oakrender_ticket_get_samples(OakRenderTicket{}, &samples),
|
||||
OAKRENDER_E_INVALID);
|
||||
|
||||
oakrender_ticket_free(nullptr); // no-op
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -66,10 +66,10 @@ std::string encoder_error(OakEncoder encoder)
|
||||
*
|
||||
* Returns an empty OakFrame (ctx == NULL) on failure.
|
||||
*/
|
||||
OakFrame copy_frame_to_codec(OakCodecFrame *render_frame)
|
||||
OakFrame copy_frame_to_codec(OakCodecFrame render_frame)
|
||||
{
|
||||
OakFrame empty = {};
|
||||
if (!render_frame) {
|
||||
if (!render_frame.ctx) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
@@ -145,12 +145,10 @@ OakNodeSequence sequence_alias_of(OakNodeNode node)
|
||||
ExportTask::ExportTask(OakNodeNode viewer_node,
|
||||
OakNodeColorManager color_manager,
|
||||
const oakcodec_encoding_params ¶ms)
|
||||
: copier_(nullptr)
|
||||
, color_manager_({})
|
||||
: color_manager_({})
|
||||
, params_(params)
|
||||
, encoder_({})
|
||||
, subtitle_encoder_({})
|
||||
, color_processor_(nullptr)
|
||||
, frame_time_(0)
|
||||
, null_frame_streak_(0)
|
||||
, audio_time_(0)
|
||||
@@ -162,7 +160,7 @@ ExportTask::ExportTask(OakNodeNode viewer_node,
|
||||
oaknode_node_get_project(viewer_node, &source_project);
|
||||
|
||||
copier_ = oakrender_project_copier_create();
|
||||
if (copier_ && source_project.ctx) {
|
||||
if (copier_.ctx && source_project.ctx) {
|
||||
oakrender_project_copier_set_project(copier_, source_project);
|
||||
}
|
||||
oaknode_project_free(&source_project);
|
||||
@@ -203,11 +201,9 @@ ExportTask::~ExportTask()
|
||||
if (subtitle_encoder_.ctx) {
|
||||
oakcodec_encoder_free(&subtitle_encoder_);
|
||||
}
|
||||
if (color_processor_) {
|
||||
oakrender_color_processor_free(color_processor_);
|
||||
}
|
||||
oakrender_color_processor_free(&color_processor_);
|
||||
oaknode_colormanager_free(&color_manager_);
|
||||
oakrender_project_copier_free(copier_);
|
||||
oakrender_project_copier_free(&copier_);
|
||||
if (audio_params()) {
|
||||
oakcore_audioparams_free(audio_params());
|
||||
}
|
||||
@@ -411,13 +407,13 @@ bool ExportTask::run()
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ExportTask::frame_downloaded(OakCodecFrame *f, const Rational &time)
|
||||
bool ExportTask::frame_downloaded(OakCodecFrame f, const Rational &time)
|
||||
{
|
||||
// The worker pool finishes tickets without a result when no worker is
|
||||
// available (or every worker crashed). Grinding through the whole
|
||||
// timeline at several seconds per dead worker looks like a hang, so
|
||||
// fail the export after a short streak of missing frames.
|
||||
if (!f) {
|
||||
if (!f.ctx) {
|
||||
if (++null_frame_streak_ >= 8) {
|
||||
set_error("Render workers failed to deliver " +
|
||||
std::to_string(null_frame_streak_) +
|
||||
@@ -457,8 +453,8 @@ bool ExportTask::frame_downloaded(OakCodecFrame *f, const Rational &time)
|
||||
set_error(encoder_error(encoder_));
|
||||
return false;
|
||||
}
|
||||
if (it->second) {
|
||||
oakrender_codec_frame_free(it->second);
|
||||
if (it->second.ctx) {
|
||||
oakrender_codec_frame_free(&it->second);
|
||||
}
|
||||
time_map_.erase(it);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -71,7 +71,7 @@ RenderTask::~RenderTask()
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTask::on_ticket_finished(OakRenderTicket *ticket)
|
||||
void RenderTask::on_ticket_finished(OakRenderTicket ticket)
|
||||
{
|
||||
finished_mutex_.lock();
|
||||
finished_tickets_.push_back(ticket);
|
||||
@@ -112,16 +112,16 @@ bool RenderTask::start_video_ticket(OakNodeColorManager manager,
|
||||
params.force_color_transform = force.color_transform;
|
||||
params.cache = cache;
|
||||
|
||||
OakRenderTicket *ticket = oakrender_ticket_render_frame(
|
||||
OakRenderTicket ticket = oakrender_ticket_render_frame(
|
||||
¶ms,
|
||||
[](OakRenderTicket *t, void *userdata) {
|
||||
[](OakRenderTicket t, void *userdata) {
|
||||
static_cast<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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -59,26 +59,26 @@ TEST_F(OakTaskFixture, TaskLifecycleSync)
|
||||
{
|
||||
oaktask_task_free(nullptr); // no-op on NULL
|
||||
|
||||
OakTaskTask *t = oaktask_create_project_save(project_, nullptr, 0);
|
||||
ASSERT_NE(t, nullptr);
|
||||
OakTaskTask t = oaktask_create_project_save(project_, nullptr, 0);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
char title[128];
|
||||
EXPECT_GT(oaktask_task_title(t, title, sizeof(title)), 0);
|
||||
EXPECT_EQ(oaktask_task_title(nullptr, title, sizeof(title)),
|
||||
EXPECT_EQ(oaktask_task_title(OakTaskTask{}, title, sizeof(title)),
|
||||
OAKTASK_E_INVALID);
|
||||
|
||||
EXPECT_EQ(oaktask_task_is_finished(t), 0);
|
||||
EXPECT_EQ(oaktask_task_is_finished(nullptr), OAKTASK_E_INVALID);
|
||||
EXPECT_EQ(oaktask_task_is_finished(OakTaskTask{}), OAKTASK_E_INVALID);
|
||||
|
||||
// Save to an override path in the temp dir
|
||||
std::string path =
|
||||
(std::filesystem::temp_directory_path() / "oaktask_save_test.ove")
|
||||
.string();
|
||||
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
|
||||
t = oaktask_create_project_save(project_, path.c_str(), 0);
|
||||
ASSERT_NE(t, nullptr);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
EXPECT_EQ(oaktask_task_start_sync(t), 1);
|
||||
EXPECT_EQ(oaktask_task_is_finished(t), 1);
|
||||
@@ -87,7 +87,7 @@ TEST_F(OakTaskFixture, TaskLifecycleSync)
|
||||
char err[256];
|
||||
EXPECT_GE(oaktask_task_error(t, err, sizeof(err)), 0);
|
||||
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
EXPECT_EQ(oaktask_debug_alive_count(), 0);
|
||||
|
||||
std::filesystem::remove(path);
|
||||
@@ -95,9 +95,9 @@ TEST_F(OakTaskFixture, TaskLifecycleSync)
|
||||
|
||||
TEST_F(OakTaskFixture, LoadMissingFileFails)
|
||||
{
|
||||
OakTaskTask *t =
|
||||
OakTaskTask t =
|
||||
oaktask_create_project_load("/nonexistent/definitely-missing.ove");
|
||||
ASSERT_NE(t, nullptr);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
EXPECT_EQ(oaktask_task_start_sync(t), 0);
|
||||
|
||||
@@ -105,9 +105,9 @@ TEST_F(OakTaskFixture, LoadMissingFileFails)
|
||||
EXPECT_GT(oaktask_task_error(t, err, sizeof(err)), 0);
|
||||
|
||||
EXPECT_EQ(oaktask_load_take_project(t).ctx, nullptr);
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
|
||||
EXPECT_EQ(oaktask_create_project_load(nullptr), nullptr);
|
||||
EXPECT_EQ(oaktask_create_project_load(nullptr).ctx, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(OakTaskFixture, SaveLoadRoundTrip)
|
||||
@@ -116,14 +116,14 @@ TEST_F(OakTaskFixture, SaveLoadRoundTrip)
|
||||
(std::filesystem::temp_directory_path() / "oaktask_roundtrip.ove")
|
||||
.string();
|
||||
|
||||
OakTaskTask *save =
|
||||
OakTaskTask save =
|
||||
oaktask_create_project_save(project_, path.c_str(), 0);
|
||||
ASSERT_NE(save, nullptr);
|
||||
ASSERT_NE(save.ctx, nullptr);
|
||||
ASSERT_EQ(oaktask_task_start_sync(save), 1);
|
||||
oaktask_task_free(save);
|
||||
oaktask_task_free(&save);
|
||||
|
||||
OakTaskTask *load = oaktask_create_project_load(path.c_str());
|
||||
ASSERT_NE(load, nullptr);
|
||||
OakTaskTask load = oaktask_create_project_load(path.c_str());
|
||||
ASSERT_NE(load.ctx, nullptr);
|
||||
ASSERT_EQ(oaktask_task_start_sync(load), 1);
|
||||
|
||||
OakNodeProject loaded = oaktask_load_take_project(load);
|
||||
@@ -131,7 +131,7 @@ TEST_F(OakTaskFixture, SaveLoadRoundTrip)
|
||||
EXPECT_EQ(oaktask_load_take_project(load).ctx, nullptr);
|
||||
|
||||
oaknode_project_free(&loaded);
|
||||
oaktask_task_free(load);
|
||||
oaktask_task_free(&load);
|
||||
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
@@ -142,9 +142,9 @@ TEST_F(OakTaskFixture, ImportDemoFootage)
|
||||
ASSERT_NE(folder.ctx, nullptr);
|
||||
|
||||
const char *urls[] = { OAK_REPO_ROOT "/tests/demo.mp4" };
|
||||
OakTaskTask *t =
|
||||
OakTaskTask t =
|
||||
oaktask_create_project_import(folder, project_, urls, 1);
|
||||
ASSERT_NE(t, nullptr);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
ASSERT_EQ(oaktask_task_start_sync(t), 1);
|
||||
|
||||
@@ -181,12 +181,13 @@ TEST_F(OakTaskFixture, ImportDemoFootage)
|
||||
EXPECT_EQ(oaknode_folder_child_count(folder), 0);
|
||||
|
||||
oakundo_command_free(&cmd);
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
|
||||
EXPECT_EQ(
|
||||
oaktask_create_project_import(OakNodeFolder{}, project_, urls, 1),
|
||||
oaktask_create_project_import(OakNodeFolder{}, project_, urls, 1).ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oaktask_import_footage_count(nullptr), OAKTASK_E_INVALID);
|
||||
EXPECT_EQ(oaktask_import_footage_count(OakTaskTask{}),
|
||||
OAKTASK_E_INVALID);
|
||||
|
||||
EXPECT_EQ(oaktask_debug_alive_count(), 0);
|
||||
}
|
||||
@@ -203,7 +204,7 @@ TEST(OakTaskManager, InitShutdownAndCodecSubmitter)
|
||||
EXPECT_GE(oaktask_manager_count(), 0);
|
||||
oaktask_manager_delete_finished();
|
||||
|
||||
EXPECT_EQ(oaktask_manager_at(0), nullptr);
|
||||
EXPECT_EQ(oaktask_manager_at(0).ctx, nullptr);
|
||||
|
||||
oaktask_manager_shutdown();
|
||||
EXPECT_EQ(oakcodec_task_submit_is_registered(), 0);
|
||||
@@ -223,8 +224,8 @@ TEST(OakTaskManager, AsyncStartAndSubscribe)
|
||||
(std::filesystem::temp_directory_path() / "oaktask_async.ove")
|
||||
.string();
|
||||
|
||||
OakTaskTask *t = oaktask_create_project_save(project, path.c_str(), 0);
|
||||
ASSERT_NE(t, nullptr);
|
||||
OakTaskTask t = oaktask_create_project_save(project, path.c_str(), 0);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
struct Seen {
|
||||
int started;
|
||||
@@ -262,7 +263,7 @@ TEST(OakTaskManager, AsyncStartAndSubscribe)
|
||||
EXPECT_TRUE(std::filesystem::exists(path));
|
||||
std::filesystem::remove(path);
|
||||
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
oaknode_project_free(&project);
|
||||
oaktask_manager_shutdown();
|
||||
|
||||
@@ -324,15 +325,18 @@ TEST(OakTaskConform, SubmittedConformProducesPcm)
|
||||
|
||||
TEST(OakTaskRenderFamily, FactoryErrorPaths)
|
||||
{
|
||||
EXPECT_EQ(oaktask_create_precache(OakNodeFootage{}, 0, OakNodeSequence{}),
|
||||
nullptr);
|
||||
EXPECT_EQ(
|
||||
oaktask_create_precache(OakNodeFootage{}, 0, OakNodeSequence{}).ctx,
|
||||
nullptr);
|
||||
|
||||
oakcodec_encoding_params params = {};
|
||||
EXPECT_EQ(oaktask_create_export(OakNodeNode{}, OakNodeColorManager{},
|
||||
¶ms),
|
||||
¶ms)
|
||||
.ctx,
|
||||
nullptr);
|
||||
EXPECT_EQ(oaktask_create_export(OakNodeNode{}, OakNodeColorManager{},
|
||||
nullptr),
|
||||
nullptr)
|
||||
.ctx,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -358,15 +362,15 @@ TEST(OakTaskRenderFamily, ExportTaskConstruction)
|
||||
sizeof(params.filename) - 1);
|
||||
params.video_enabled = 1;
|
||||
|
||||
OakTaskTask *t = oaktask_create_export(
|
||||
OakTaskTask t = oaktask_create_export(
|
||||
oaknode_sequence_as_node(sequence), cm, ¶ms);
|
||||
ASSERT_NE(t, nullptr);
|
||||
ASSERT_NE(t.ctx, nullptr);
|
||||
|
||||
char title[128];
|
||||
EXPECT_GT(oaktask_task_title(t, title, sizeof(title)), 0);
|
||||
|
||||
// Running needs a render manager; not available in this binary
|
||||
oaktask_task_free(t);
|
||||
oaktask_task_free(&t);
|
||||
oaknode_colormanager_free(&cm);
|
||||
oaknode_project_free(&project);
|
||||
|
||||
@@ -407,29 +411,29 @@ TEST(OakTaskOTIO, SaveLoadRoundTrip)
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path, ec);
|
||||
|
||||
OakTaskTask *save =
|
||||
OakTaskTask save =
|
||||
oaktask_create_project_save_otio(project, path.c_str());
|
||||
ASSERT_NE(save, nullptr);
|
||||
ASSERT_NE(save.ctx, nullptr);
|
||||
ASSERT_EQ(oaktask_task_start_sync(save), 1);
|
||||
oaktask_task_free(save);
|
||||
oaktask_task_free(&save);
|
||||
|
||||
ASSERT_TRUE(std::filesystem::exists(path));
|
||||
|
||||
OakTaskTask *load = oaktask_create_project_load_otio(path.c_str());
|
||||
ASSERT_NE(load, nullptr);
|
||||
OakTaskTask load = oaktask_create_project_load_otio(path.c_str());
|
||||
ASSERT_NE(load.ctx, nullptr);
|
||||
ASSERT_EQ(oaktask_task_start_sync(load), 1);
|
||||
|
||||
OakNodeProject loaded = oaktask_load_otio_take_project(load);
|
||||
ASSERT_NE(loaded.ctx, nullptr);
|
||||
oaknode_project_free(&loaded);
|
||||
oaktask_task_free(load);
|
||||
oaktask_task_free(&load);
|
||||
|
||||
std::filesystem::remove(path, ec);
|
||||
oaknode_project_free(&project);
|
||||
|
||||
EXPECT_EQ(oaktask_debug_alive_count(), 0);
|
||||
|
||||
EXPECT_EQ(oaktask_create_project_load_otio(nullptr), nullptr);
|
||||
EXPECT_EQ(oaktask_create_project_save_otio(OakNodeProject{}, "x"),
|
||||
EXPECT_EQ(oaktask_create_project_load_otio(nullptr).ctx, nullptr);
|
||||
EXPECT_EQ(oaktask_create_project_save_otio(OakNodeProject{}, "x").ctx,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user