R7: pure C ABI display boundary + engine visibility closure
R7-A (Qwen 3.8 Max): oakengine/display.h rewritten to the POD contract from r7-pure-abi-plan.md - oak_video_params everywhere, opaque texture/frame handles with retain/free protocol (engine-heap control blocks), OakSharedBuffer refcounted wrapper for the QVariant playback path. All TexturePtr/FramePtr gone from app (47 sites). R7-B (Qwen 3.8 Max): liboakengine.so exports 3486 -> 19 C++ symbols (version script oakengine.ver: oakengine_* plus the documented oakgl/oakvulkan dlopen plugin ABI). oakengine-obj OBJECT library feeds both the shared lib and the test binaries (-rdynamic so dlopen'd backends resolve engine objects). Fix (Kimi K3): producer/consumer type mismatch - viewerdisplay unpacks OakSharedBufferPtr but viewer.cpp pushed raw void* handles, so no frame ever reached the display widget (all 5 vulkan viewer tests timed out with 'never received a texture'). Producers now wrap with oak_make_shared_frame / oak_make_shared_texture(retain). Verified: build 0 errors, ctest 45/45, nm U _ZN5olive = 0 in oak-editor/oak-render-worker/oak-cli, 19 exported C++ symbols in liboakengine.so (all documented plugin ABI).
This commit is contained in:
+41
-9
@@ -46,25 +46,31 @@ add_subdirectory(ui)
|
||||
add_subdirectory(undo)
|
||||
add_subdirectory(src/capi)
|
||||
|
||||
add_library(oakengine SHARED
|
||||
# Object library: compiled once, consumed by both the shared library (with
|
||||
# version-script restrictions) and internal test executables (unrestricted).
|
||||
add_library(oakengine-obj OBJECT
|
||||
${OLIVE_SOURCES}
|
||||
${OLIVE_RESOURCES}
|
||||
)
|
||||
|
||||
add_library(oakengine SHARED $<TARGET_OBJECTS:oakengine-obj>)
|
||||
|
||||
# macOS: hides the render worker's dock icon; called from the worker main in
|
||||
# src/capi/worker.cpp (declared there as a plain C++ symbol).
|
||||
if (APPLE)
|
||||
target_sources(oakengine PRIVATE src/worker_dockicon_mac.mm)
|
||||
target_sources(oakengine-obj PRIVATE src/worker_dockicon_mac.mm)
|
||||
target_link_libraries(oakengine PRIVATE "-framework Cocoa")
|
||||
endif ()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(pluginSupport)
|
||||
|
||||
target_compile_features(oakengine PUBLIC cxx_std_23)
|
||||
target_compile_features(oakengine-obj PUBLIC cxx_std_23)
|
||||
set_target_properties(oakengine-obj PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
set_target_properties(oakengine PROPERTIES
|
||||
OUTPUT_NAME oakengine
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
# Consumers resolve engine headers ("node/...", "render/...", "coreengine.h",
|
||||
@@ -76,7 +82,7 @@ set_target_properties(oakengine PROPERTIES
|
||||
# cannot work here: CMAKE_INCLUDE_CURRENT_DIR puts the engine root ahead of
|
||||
# every target-level directory, so the prefixed paths are what keep internal
|
||||
# sources from ever picking up a consumer wrapper header by accident.
|
||||
target_include_directories(oakengine
|
||||
target_include_directories(oakengine-obj
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
@@ -85,10 +91,28 @@ target_include_directories(oakengine
|
||||
${OLIVE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(oakengine-obj PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
||||
target_link_libraries(oakengine PRIVATE oakengine-obj)
|
||||
# Propagate engine header paths to consumers of the shared library
|
||||
# (the OBJECT lib's PUBLIC includes don't transit through a PRIVATE link).
|
||||
target_include_directories(oakengine
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
# Consumers also need the third-party link deps (Qt, FFmpeg, etc.)
|
||||
target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
||||
# OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport)
|
||||
target_compile_definitions(oakengine PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
||||
target_compile_options(oakengine PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
target_compile_definitions(oakengine-obj PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
||||
target_compile_options(oakengine-obj PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
# Version script: only oakengine_* + render-backend plugin ABI are exported;
|
||||
# all other C++ internals are hidden (local: *).
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_options(oakengine PRIVATE
|
||||
"LINKER:--version-script,${CMAKE_CURRENT_SOURCE_DIR}/oakengine.ver")
|
||||
set_target_properties(oakengine PROPERTIES LINK_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/oakengine.ver)
|
||||
endif ()
|
||||
|
||||
# Install into the platform's standard library directory (/usr/lib,
|
||||
# /usr/lib64 or the Debian multiarch path); Windows DLLs go next to the
|
||||
@@ -112,7 +136,7 @@ endif ()
|
||||
# dlopen; they share this engine library instead of embedding a static
|
||||
# render-core subset.
|
||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
target_compile_definitions(oakengine PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
target_compile_definitions(oakengine-obj PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
|
||||
foreach (target olivecore kddockwidgets)
|
||||
if (TARGET ${target})
|
||||
@@ -225,10 +249,18 @@ if (BUILD_TESTS)
|
||||
tests/${name}.cpp
|
||||
$<TARGET_OBJECTS:olive-version-obj>
|
||||
)
|
||||
target_link_libraries(${name} PRIVATE oakengine)
|
||||
# Link the object library directly to bypass the version-script
|
||||
# restrictions on liboakengine.so (tests are internal consumers).
|
||||
target_link_libraries(${name} PRIVATE oakengine-obj)
|
||||
target_include_directories(${name} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
# -rdynamic exports the test executable's symbols so that dlopen'd
|
||||
# render backend plugins (oakgl/oakvulkan) can resolve the embedded
|
||||
# engine objects (Renderer base class, etc.).
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_options(${name} PRIVATE "LINKER:--export-dynamic")
|
||||
endif ()
|
||||
add_test(${name} ${name})
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
target_sources(oakengine PRIVATE
|
||||
target_sources(oakengine-obj PRIVATE
|
||||
cancelableobject.h
|
||||
commandlineparser.cpp
|
||||
commandlineparser.h
|
||||
|
||||
+243
-117
@@ -21,8 +21,11 @@
|
||||
#ifndef OAKENGINE_DISPLAY_H
|
||||
#define OAKENGINE_DISPLAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "export.h"
|
||||
#include "init.h"
|
||||
#include "videoparams.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -30,155 +33,278 @@ extern "C" {
|
||||
|
||||
/**
|
||||
* @file display.h
|
||||
* @brief C ABI for the GPU display renderer used by viewer/scope widgets
|
||||
* @brief Pure C ABI for the GPU display renderer used by viewer/scope widgets
|
||||
*
|
||||
* This family wraps the engine's interactive display renderer
|
||||
* (olive::Renderer and its OpenGLRenderer/DynamicRenderer implementations,
|
||||
* engine/render/renderer.h) plus the GPU texture (olive::Texture) and the
|
||||
* CPU frame buffer (olive::Frame) that viewer/scope widgets use to move
|
||||
* pixels between the CPU and the GPU.
|
||||
* Ownership protocol: textures and frames are opaque handles pointing to
|
||||
* engine-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 all functions
|
||||
* and yields a no-op / zero result.
|
||||
*
|
||||
* It is distinct from the sequence-rendering facade in oakengine/renderer.h
|
||||
* (OakEngineRenderer), which pulls finished CPU frames out of the async
|
||||
* render pipeline. This family drives the *on-screen* paint path instead:
|
||||
* a widget creates a renderer, initializes it with the widget's GL context,
|
||||
* uploads/downloads textures, and blits color-managed images each paint.
|
||||
*
|
||||
* Conventions (matching the other facade families):
|
||||
* - All object pointers are opaque. `renderer` is an olive::Renderer*,
|
||||
* `texture` an olive::Texture*, `frame` an olive::Frame*.
|
||||
* - `out_texture` / `out_frame` are pointers to caller-owned
|
||||
* olive::TexturePtr / olive::FramePtr (std::shared_ptr) storage; the
|
||||
* callee assigns a newly created smart pointer into them, releasing any
|
||||
* previously held object. This keeps shared-pointer ownership/deleter
|
||||
* bookkeeping entirely on the engine side.
|
||||
* - `video_params` is a `const olive::VideoParams*`; `color_job` is a
|
||||
* `const olive::ColorTransformJob*`. These are passed as opaque pointers
|
||||
* because they are C++ types; both the caller (app) and the callee
|
||||
* (engine) are compiled as C++ against the same headers.
|
||||
* - `gl_context` is a `QOpenGLContext*` or NULL.
|
||||
* - `parent` is the owning `QObject*` (the display widget); the created
|
||||
* renderer is a QObject child of it and is destroyed by Qt ownership.
|
||||
* Do NOT call oakengine_display_renderer_destroy() and then also rely on
|
||||
* Qt deletion of the same renderer's GPU resources -- destroy() releases
|
||||
* GPU state, Qt deletion releases the object.
|
||||
* Conventions:
|
||||
* - `renderer` is an opaque renderer handle (olive::Renderer* internally).
|
||||
* - `texture` is an OakEngineDisplayTexture handle (refcounted).
|
||||
* - `frame` is an OakEngineCodecFrame handle (refcounted).
|
||||
* - Video parameters use the oak_video_params POD (oakengine/videoparams.h).
|
||||
* - Color jobs use the oak_color_transform_job POD defined below.
|
||||
* - `gl_context` is a QOpenGLContext* or NULL.
|
||||
* - `parent` is the owning QObject* (the display widget).
|
||||
*/
|
||||
|
||||
/* ---- Display renderer lifecycle ---------------------------------------- */
|
||||
/* ---- oak_color_transform_job POD ---------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a dynamic-backend renderer (olive::DynamicRenderer) for
|
||||
* `backend_name` and load() it.
|
||||
*
|
||||
* @return The renderer (olive::Renderer*), or NULL if the backend library
|
||||
* could not be loaded (the failed renderer is deleted internally and
|
||||
* the caller should fall back to
|
||||
* oakengine_display_renderer_create_opengl()). NULL is also returned
|
||||
* when the engine was built without dynamic-backend support.
|
||||
* @brief Flattened POD of the engine's ColorTransformJob for the display blit
|
||||
* path. Fields map 1:1 to ColorTransformJob members used by viewer/scope.
|
||||
*/
|
||||
typedef struct oak_color_transform_job {
|
||||
const void *processor; /**< OakEngineColorProcessor* (borrowed). */
|
||||
void *input_texture; /**< Texture handle (borrowed, not retained). */
|
||||
int input_alpha_association; /**< 0=none, 1=associated. */
|
||||
int clear_destination; /**< 0/1. */
|
||||
int force_opaque; /**< 0/1. */
|
||||
float matrix[16]; /**< QMatrix4x4 (column-major, identity if all 0). */
|
||||
float crop_matrix[16]; /**< QMatrix4x4 (column-major). */
|
||||
} oak_color_transform_job;
|
||||
|
||||
/* ---- Display renderer lifecycle ----------------------------------------- */
|
||||
|
||||
OAKENGINE_API void *
|
||||
oakengine_display_renderer_create_dynamic(const char *backend_name,
|
||||
void *parent);
|
||||
|
||||
/**
|
||||
* @brief Create the built-in OpenGL renderer (olive::OpenGLRenderer).
|
||||
*
|
||||
* @return The renderer (olive::Renderer*), never NULL.
|
||||
*/
|
||||
OAKENGINE_API void *oakengine_display_renderer_create_opengl(void *parent);
|
||||
|
||||
/**
|
||||
* @brief Initialize a display renderer and run its post-init step.
|
||||
*
|
||||
* If `gl_context` is non-NULL the OpenGL/dynamic path is taken (the renderer
|
||||
* is initialized against the widget's shared QOpenGLContext); otherwise the
|
||||
* backend-neutral path (Renderer::init()/post_init()) is used.
|
||||
*
|
||||
* @return OAKENGINE_OK on success, OAKENGINE_E_INVALID for a NULL renderer.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_init(void *renderer,
|
||||
void *gl_context);
|
||||
|
||||
/**
|
||||
* @brief Release a display renderer's GPU resources (Renderer::destroy()
|
||||
* followed by post_destroy()). The renderer object itself remains owned by
|
||||
* its Qt parent.
|
||||
*/
|
||||
OAKENGINE_API void oakengine_display_renderer_destroy(void *renderer);
|
||||
|
||||
/* ---- Texture creation and pixel transfer -------------------------------- */
|
||||
/* ---- Renderer queries --------------------------------------------------- */
|
||||
|
||||
OAKENGINE_API int oakengine_display_renderer_is_open_gl(const void *renderer);
|
||||
OAKENGINE_API int oakengine_display_renderer_is_vulkan(const void *renderer);
|
||||
|
||||
/* ---- Texture handle (opaque, refcounted) -------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Create a GPU texture on `renderer` (Renderer::create_texture()).
|
||||
*
|
||||
* @param renderer olive::Renderer*.
|
||||
* @param video_params const olive::VideoParams* describing the texture.
|
||||
* @param pixels Initial pixel data, or NULL for an empty texture.
|
||||
* @param linesize Line stride of `pixels` (ignored when NULL).
|
||||
* @param out_texture Pointer to an olive::TexturePtr to receive the result.
|
||||
* @brief Create a GPU texture on `renderer`.
|
||||
* @return New texture handle (refcount=1), or NULL on failure.
|
||||
*/
|
||||
OAKENGINE_API void
|
||||
oakengine_display_renderer_create_texture(void *renderer,
|
||||
const void *video_params,
|
||||
const void *pixels, int linesize,
|
||||
void *out_texture);
|
||||
OAKENGINE_API void *oakengine_display_texture_create(
|
||||
void *renderer, const oak_video_params *params,
|
||||
const void *pixels, int linesize);
|
||||
|
||||
/** @brief Increment refcount, return same handle. NULL-safe. */
|
||||
OAKENGINE_API void *oakengine_display_texture_retain(void *texture);
|
||||
|
||||
/** @brief Decrement refcount; frees at zero. NULL-safe. */
|
||||
OAKENGINE_API void oakengine_display_texture_free(void *texture);
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_upload(
|
||||
void *texture, const void *pixels, int linesize);
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_download(
|
||||
void *texture, void *pixels, int linesize);
|
||||
|
||||
/* ---- Texture queries ---------------------------------------------------- */
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_get_params(
|
||||
const void *texture, oak_video_params *out);
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_id(const void *texture);
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_is_dummy(const void *texture);
|
||||
|
||||
/** @brief The renderer that owns this texture (borrowed, do NOT free). */
|
||||
OAKENGINE_API void *oakengine_display_texture_renderer(const void *texture);
|
||||
|
||||
OAKENGINE_API int oakengine_display_texture_width(const void *texture);
|
||||
OAKENGINE_API int oakengine_display_texture_height(const void *texture);
|
||||
OAKENGINE_API int oakengine_display_texture_format(const void *texture);
|
||||
OAKENGINE_API int oakengine_display_texture_channel_count(const void *texture);
|
||||
|
||||
/**
|
||||
* @brief Blit a color-managed image (Renderer::blit_color_managed()).
|
||||
*
|
||||
* @param renderer olive::Renderer*.
|
||||
* @param color_job const olive::ColorTransformJob*.
|
||||
* @param dst_texture Destination olive::Texture*, or NULL to blit to the
|
||||
* current output destination.
|
||||
* @param video_params const olive::VideoParams* for the destination, or NULL
|
||||
* to use dst_texture's own parameters (in which case
|
||||
* dst_texture must be non-NULL).
|
||||
* @brief Compare two texture handles' video params for equality.
|
||||
* @return 1 if equal, 0 otherwise (NULL handles compare unequal).
|
||||
*/
|
||||
OAKENGINE_API void
|
||||
oakengine_display_renderer_blit_color_managed(void *renderer,
|
||||
const void *color_job,
|
||||
void *dst_texture,
|
||||
const void *video_params);
|
||||
OAKENGINE_API int oakengine_display_texture_params_equal(
|
||||
const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* @brief Upload CPU pixels into a GPU texture (Texture::upload()).
|
||||
*/
|
||||
OAKENGINE_API void oakengine_display_texture_upload(void *texture,
|
||||
void *pixels, int linesize);
|
||||
/* ---- Frame handle (opaque, refcounted) ---------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Download GPU texture pixels into CPU memory (Texture::download()).
|
||||
*/
|
||||
OAKENGINE_API void oakengine_display_texture_download(void *texture,
|
||||
void *pixels,
|
||||
int linesize);
|
||||
/** @brief Create an empty CPU frame. Returns handle (refcount=1). */
|
||||
OAKENGINE_API void *oakengine_codec_frame_create(void);
|
||||
|
||||
/* ---- CPU frame buffer --------------------------------------------------- */
|
||||
/** @brief Increment refcount, return same handle. NULL-safe. */
|
||||
OAKENGINE_API void *oakengine_codec_frame_retain(void *frame);
|
||||
|
||||
/**
|
||||
* @brief Create an empty CPU frame (olive::Frame::create()).
|
||||
*
|
||||
* @param out_frame Pointer to an olive::FramePtr to receive the new frame.
|
||||
*/
|
||||
OAKENGINE_API void oakengine_codec_frame_create(void *out_frame);
|
||||
/** @brief Decrement refcount; frees at zero. NULL-safe. */
|
||||
OAKENGINE_API void oakengine_codec_frame_free(void *frame);
|
||||
|
||||
/**
|
||||
* @brief Set a frame's video parameters (Frame::set_video_params()).
|
||||
*
|
||||
* @param frame olive::Frame*.
|
||||
* @param video_params const olive::VideoParams*.
|
||||
*/
|
||||
OAKENGINE_API void oakengine_codec_frame_set_video_params(void *frame,
|
||||
const void
|
||||
*video_params);
|
||||
OAKENGINE_API int oakengine_codec_frame_set_video_params(
|
||||
void *frame, const oak_video_params *params);
|
||||
|
||||
OAKENGINE_API int oakengine_codec_frame_get_params(
|
||||
const void *frame, oak_video_params *out);
|
||||
|
||||
/**
|
||||
* @brief Allocate the frame's pixel buffer (Frame::allocate()).
|
||||
*
|
||||
* @return 1 on success, 0 on failure or NULL frame.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_codec_frame_allocate(void *frame);
|
||||
|
||||
/** @brief Borrowed pixel data pointer (valid until free). */
|
||||
OAKENGINE_API void *oakengine_codec_frame_data(void *frame);
|
||||
|
||||
/** @brief Borrowed const pixel data pointer. */
|
||||
OAKENGINE_API const void *oakengine_codec_frame_const_data(const void *frame);
|
||||
|
||||
/** @brief Line stride in pixels. */
|
||||
OAKENGINE_API int oakengine_codec_frame_linesize(const void *frame);
|
||||
|
||||
/** @brief Line stride in bytes. */
|
||||
OAKENGINE_API int oakengine_codec_frame_linesize_bytes(const void *frame);
|
||||
|
||||
/* ---- Frame queries ------------------------------------------------------ */
|
||||
|
||||
OAKENGINE_API int oakengine_codec_frame_width(const void *frame);
|
||||
OAKENGINE_API int oakengine_codec_frame_height(const void *frame);
|
||||
OAKENGINE_API int oakengine_codec_frame_format(const void *frame);
|
||||
OAKENGINE_API int oakengine_codec_frame_channel_count(const void *frame);
|
||||
OAKENGINE_API int oakengine_codec_frame_is_allocated(const void *frame);
|
||||
|
||||
/* ---- Color-managed blit ------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Blit a color-managed image through the OCIO pipeline.
|
||||
*
|
||||
* @param renderer Renderer handle.
|
||||
* @param job POD color transform job (processor + input texture + flags).
|
||||
* @param dst_texture Destination texture handle, or NULL for screen.
|
||||
* @param params Destination video params, or NULL to use dst_texture's.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_color_managed(
|
||||
void *renderer, const oak_color_transform_job *job,
|
||||
void *dst_texture, const oak_video_params *params);
|
||||
|
||||
/* ---- Cross-backend texture download ------------------------------------- */
|
||||
|
||||
OAKENGINE_API int oakengine_display_renderer_download_from_texture(
|
||||
void *renderer, int texture_id, const oak_video_params *params,
|
||||
void *dst_pixels, int linesize);
|
||||
|
||||
/* ---- Shader management -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Compile a native shader from GLSL source.
|
||||
* @param frag_src Fragment shader source (required).
|
||||
* @param vert_src Vertex shader source, or NULL for engine default.
|
||||
* @return Shader pipeline handle (QVariant*), or NULL on failure.
|
||||
* Free with oakengine_display_renderer_destroy_shader().
|
||||
*/
|
||||
OAKENGINE_API void *oakengine_display_renderer_create_shader(
|
||||
void *renderer, const char *frag_src, const char *vert_src);
|
||||
|
||||
/**
|
||||
* @brief Create the engine's default blank shader (no custom source).
|
||||
*/
|
||||
OAKENGINE_API void *oakengine_display_renderer_create_blank_shader(
|
||||
void *renderer);
|
||||
|
||||
OAKENGINE_API void oakengine_display_renderer_destroy_shader(
|
||||
void *renderer, void *shader);
|
||||
|
||||
/* ---- Shader blit operations --------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Blit a single texture through a shader to the screen.
|
||||
* Used by scope draw_scope() path.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_shader(
|
||||
void *renderer, void *shader, void *texture,
|
||||
const oak_video_params *viewport_params);
|
||||
|
||||
/**
|
||||
* @brief Blit a single texture through a shader to a destination texture.
|
||||
* Used by histogram blit_to_texture path.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_shader_to_texture(
|
||||
void *renderer, void *shader, void *texture, void *dst_texture);
|
||||
|
||||
/**
|
||||
* @brief Blit with a vec2 uniform + texture to a destination texture.
|
||||
* Used by deinterlace path (resolution_in uniform).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_shader_vec2_to_texture(
|
||||
void *renderer, void *shader, void *texture,
|
||||
const char *vec2_name, float vec2_x, float vec2_y,
|
||||
void *dst_texture);
|
||||
|
||||
/**
|
||||
* @brief Blit the blank shader with MVP + crop matrices.
|
||||
* Used by draw_blank().
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_blank(
|
||||
void *renderer, void *shader,
|
||||
const float *mvp_matrix, const float *crop_matrix,
|
||||
const oak_video_params *params);
|
||||
|
||||
/**
|
||||
* @brief Blit multiple named textures through a shader to a dst texture.
|
||||
* Used by multicam compositing.
|
||||
*
|
||||
* @param names Array of `count` UTF-8 uniform names.
|
||||
* @param textures Array of `count` texture handles.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_shader_multi(
|
||||
void *renderer, void *shader,
|
||||
const char *const *names, void *const *textures, int count,
|
||||
void *dst_texture);
|
||||
|
||||
/* ---- Generic shader blit with named uniforms ----------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Named uniform descriptor for scope shader blits.
|
||||
*/
|
||||
typedef struct oak_shader_uniform {
|
||||
const char *name; /**< Uniform name (UTF-8). */
|
||||
int type; /**< 0=float, 1=vec2, 2=int/bool, 3=vec3. */
|
||||
float values[4]; /**< float: [0]; vec2: [0],[1]; vec3: [0],[1],[2]; int: [0]. */
|
||||
} oak_shader_uniform;
|
||||
|
||||
/**
|
||||
* @brief Blit a single texture through a shader with arbitrary named uniforms.
|
||||
*
|
||||
* @param renderer Renderer handle.
|
||||
* @param shader Shader pipeline handle.
|
||||
* @param texture Main input texture (bound as "ove_maintex").
|
||||
* @param uniforms Array of extra uniforms (may be NULL).
|
||||
* @param uniform_count Number of entries in `uniforms`.
|
||||
* @param dst_texture Destination texture, or NULL for screen.
|
||||
* @param params Viewport params (used when dst_texture is NULL).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_blit_shader_uniforms(
|
||||
void *renderer, void *shader, void *texture,
|
||||
const oak_shader_uniform *uniforms, int uniform_count,
|
||||
void *dst_texture, const oak_video_params *params);
|
||||
|
||||
/* ---- Renderer clear ----------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Clear the current destination.
|
||||
* @param mask NULL for default, or a string mask (unused currently).
|
||||
* @param r,g,b Clear color (0.0-1.0).
|
||||
*/
|
||||
OAKENGINE_API void oakengine_display_renderer_clear(
|
||||
void *renderer, double r, double g, double b);
|
||||
|
||||
/* ---- Pixel readback ----------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Read a single pixel from a texture (Renderer::get_pixel_from_texture).
|
||||
* @param x,y Pixel coordinates.
|
||||
* @param out_rgba Output 4 doubles (RGBA).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_display_renderer_get_pixel(
|
||||
void *renderer, void *texture, int x, int y, double *out_rgba);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
{
|
||||
global:
|
||||
oakengine_*;
|
||||
/* Render backend plugin ABI (oakgl / oakvulkan dlopen) */
|
||||
_ZN5olive8Renderer*;
|
||||
_ZTVN5olive8RendererE;
|
||||
_ZN5olive11VideoParams19get_bytes_per_pixelE*;
|
||||
_ZN5olive11VideoParams21get_bytes_per_channelE*;
|
||||
_ZN5olive13FileFunctions19read_file_as_stringE*;
|
||||
_ZN5olive7Texture23k_default_interpolationE;
|
||||
/* App residual C++ dependency (to be migrated to C ABI) */
|
||||
_ZNK5olive6Folder17has_child_recursiveE*;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
target_sources(oakengine PRIVATE
|
||||
target_sources(oakengine-obj PRIVATE
|
||||
olivehost.h
|
||||
olivehost.cpp
|
||||
oliveplugininstance.h
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "pluginSupport/oliveclip.h"
|
||||
#include "pluginSupport/olivehost.h"
|
||||
#include "oliveimpl/render/ipc/frameslotpool.h"
|
||||
#include "src/capi/displayinternal.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -367,7 +368,7 @@ NodeValueDatabase RenderProcessor::generate_database(const Node *node,
|
||||
if (QtUtils::value_to_ptr<MultiCamNode>(ticket_->property("multicam")) ==
|
||||
multicam) {
|
||||
int sz = multicam->get_source_count();
|
||||
QVector<TexturePtr> multicam_tex(sz);
|
||||
QVector<void *> multicam_tex(sz);
|
||||
for (int i = 0; i < sz; i++) {
|
||||
NodeValueTable t =
|
||||
generate_table(multicam->get_connected_render_output(
|
||||
@@ -377,7 +378,9 @@ NodeValueDatabase RenderProcessor::generate_database(const Node *node,
|
||||
multicam, multicam->k_sources_input, i, &t, range);
|
||||
resolve_jobs(val);
|
||||
|
||||
multicam_tex[i] = val.to_texture();
|
||||
TexturePtr tp = val.to_texture();
|
||||
// Store as opaque retained handle for the C ABI app layer
|
||||
multicam_tex[i] = oakengine_internal_wrap_texture(tp);
|
||||
}
|
||||
ticket_->setProperty("multicam_output",
|
||||
QVariant::fromValue(multicam_tex));
|
||||
|
||||
+654
-53
@@ -19,22 +19,118 @@
|
||||
***/
|
||||
|
||||
#include "oakengine/display.h"
|
||||
#include "displayinternal.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include <QObject>
|
||||
#include <QOpenGLContext>
|
||||
#include <QMatrix4x4>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colorinternal.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
#include "render/renderer.h"
|
||||
#include "render/shadercode.h"
|
||||
#include "render/texture.h"
|
||||
#include "node/value.h"
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
#include "render/backend/dynamicrenderer.h"
|
||||
#endif
|
||||
|
||||
/* ---- Internal control blocks -------------------------------------------- */
|
||||
|
||||
struct OakEngineDisplayTexture {
|
||||
olive::TexturePtr ptr;
|
||||
std::atomic<int> refcount{1};
|
||||
};
|
||||
|
||||
struct OakEngineCodecFrame {
|
||||
olive::FramePtr ptr;
|
||||
std::atomic<int> refcount{1};
|
||||
};
|
||||
|
||||
/* ---- Internal helpers --------------------------------------------------- */
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::VideoParams pod_to_cpp(const oak_video_params &v)
|
||||
{
|
||||
olive::VideoParams vp(
|
||||
v.width, v.height, olive::Rational(v.time_base_num, v.time_base_den),
|
||||
static_cast<olive::PixelFormat::Format>(v.format),
|
||||
olive::VideoParams::k_internal_channel_count,
|
||||
olive::Rational(v.pixel_aspect_num, v.pixel_aspect_den),
|
||||
static_cast<olive::VideoParams::Interlacing>(v.interlacing),
|
||||
v.divider > 0 ? v.divider : 1);
|
||||
vp.set_color_range(static_cast<olive::VideoParams::ColorRange>(v.color_range));
|
||||
return vp;
|
||||
}
|
||||
|
||||
oak_video_params cpp_to_pod(const olive::VideoParams &vp)
|
||||
{
|
||||
oak_video_params p = {};
|
||||
p.width = vp.width();
|
||||
p.height = vp.height();
|
||||
p.time_base_num = vp.time_base().numerator();
|
||||
p.time_base_den = vp.time_base().denominator();
|
||||
p.format = static_cast<int>(vp.format());
|
||||
p.pixel_aspect_num = vp.pixel_aspect_ratio().numerator();
|
||||
p.pixel_aspect_den = vp.pixel_aspect_ratio().denominator();
|
||||
p.interlacing = static_cast<int>(vp.interlacing());
|
||||
p.color_range = static_cast<int>(vp.color_range());
|
||||
p.divider = vp.divider();
|
||||
return p;
|
||||
}
|
||||
|
||||
OakEngineDisplayTexture *tex(void *h)
|
||||
{
|
||||
return static_cast<OakEngineDisplayTexture *>(h);
|
||||
}
|
||||
|
||||
OakEngineCodecFrame *frm(void *h)
|
||||
{
|
||||
return static_cast<OakEngineCodecFrame *>(h);
|
||||
}
|
||||
|
||||
olive::Renderer *ren(void *h)
|
||||
{
|
||||
return static_cast<olive::Renderer *>(h);
|
||||
}
|
||||
|
||||
QMatrix4x4 mat_from_float(const float *f)
|
||||
{
|
||||
if (!f) {
|
||||
return QMatrix4x4();
|
||||
}
|
||||
// Check if all zeros → identity
|
||||
bool all_zero = true;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (f[i] != 0.0f) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_zero) {
|
||||
return QMatrix4x4();
|
||||
}
|
||||
return QMatrix4x4(f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
/* ---- Renderer lifecycle ------------------------------------------------- */
|
||||
|
||||
void *oakengine_display_renderer_create_dynamic(const char *backend_name,
|
||||
void *parent)
|
||||
{
|
||||
@@ -45,8 +141,6 @@ void *oakengine_display_renderer_create_dynamic(const char *backend_name,
|
||||
if (dyn->load()) {
|
||||
return dyn;
|
||||
}
|
||||
// Backend library failed to load: drop it so the caller can fall back
|
||||
// to the built-in OpenGL renderer.
|
||||
delete dyn;
|
||||
return nullptr;
|
||||
#else
|
||||
@@ -63,7 +157,7 @@ void *oakengine_display_renderer_create_opengl(void *parent)
|
||||
|
||||
int oakengine_display_renderer_init(void *renderer, void *gl_context)
|
||||
{
|
||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
@@ -90,7 +184,7 @@ int oakengine_display_renderer_init(void *renderer, void *gl_context)
|
||||
|
||||
void oakengine_display_renderer_destroy(void *renderer)
|
||||
{
|
||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
@@ -98,85 +192,592 @@ void oakengine_display_renderer_destroy(void *renderer)
|
||||
r->post_destroy();
|
||||
}
|
||||
|
||||
void oakengine_display_renderer_create_texture(void *renderer,
|
||||
const void *video_params,
|
||||
const void *pixels, int linesize,
|
||||
void *out_texture)
|
||||
/* ---- Renderer queries --------------------------------------------------- */
|
||||
|
||||
int oakengine_display_renderer_is_open_gl(const void *renderer)
|
||||
{
|
||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
||||
if (!r || !video_params || !out_texture) {
|
||||
return;
|
||||
}
|
||||
const olive::VideoParams ¶ms =
|
||||
*static_cast<const olive::VideoParams *>(video_params);
|
||||
*static_cast<olive::TexturePtr *>(out_texture) =
|
||||
r->create_texture(params, pixels, linesize);
|
||||
olive::Renderer *r = ren(const_cast<void *>(renderer));
|
||||
return r ? r->is_open_gl() : 0;
|
||||
}
|
||||
|
||||
void oakengine_display_renderer_blit_color_managed(void *renderer,
|
||||
const void *color_job,
|
||||
void *dst_texture,
|
||||
const void *video_params)
|
||||
int oakengine_display_renderer_is_vulkan(const void *renderer)
|
||||
{
|
||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
||||
if (!r || !color_job) {
|
||||
olive::Renderer *r = ren(const_cast<void *>(renderer));
|
||||
return r ? r->is_vulkan() : 0;
|
||||
}
|
||||
|
||||
/* ---- Texture handle ----------------------------------------------------- */
|
||||
|
||||
void *oakengine_display_texture_create(void *renderer,
|
||||
const oak_video_params *params,
|
||||
const void *pixels, int linesize)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !params) {
|
||||
return nullptr;
|
||||
}
|
||||
olive::VideoParams vp = pod_to_cpp(*params);
|
||||
olive::TexturePtr tp = r->create_texture(vp, pixels, linesize);
|
||||
if (!tp) {
|
||||
return nullptr;
|
||||
}
|
||||
return new OakEngineDisplayTexture{tp, {1}};
|
||||
}
|
||||
|
||||
void *oakengine_display_texture_retain(void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
}
|
||||
tex(texture)->refcount.fetch_add(1, std::memory_order_relaxed);
|
||||
return texture;
|
||||
}
|
||||
|
||||
void oakengine_display_texture_free(void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
const olive::ColorTransformJob &job =
|
||||
*static_cast<const olive::ColorTransformJob *>(color_job);
|
||||
olive::Texture *dst = static_cast<olive::Texture *>(dst_texture);
|
||||
if (video_params) {
|
||||
r->blit_color_managed(
|
||||
job, dst, *static_cast<const olive::VideoParams *>(video_params));
|
||||
} else if (dst) {
|
||||
r->blit_color_managed(job, dst, dst->params());
|
||||
if (tex(texture)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete tex(texture);
|
||||
}
|
||||
}
|
||||
|
||||
void oakengine_display_texture_upload(void *texture, void *pixels, int linesize)
|
||||
int oakengine_display_texture_upload(void *texture, const void *pixels,
|
||||
int linesize)
|
||||
{
|
||||
olive::Texture *t = static_cast<olive::Texture *>(texture);
|
||||
if (!t) {
|
||||
return;
|
||||
if (!texture || !pixels) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
t->upload(pixels, linesize);
|
||||
tex(texture)->ptr->upload(const_cast<void *>(pixels), linesize);
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
void oakengine_display_texture_download(void *texture, void *pixels,
|
||||
int linesize)
|
||||
int oakengine_display_texture_download(void *texture, void *pixels,
|
||||
int linesize)
|
||||
{
|
||||
olive::Texture *t = static_cast<olive::Texture *>(texture);
|
||||
if (!t) {
|
||||
return;
|
||||
if (!texture || !pixels) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
t->download(pixels, linesize);
|
||||
tex(texture)->ptr->download(pixels, linesize);
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
void oakengine_codec_frame_create(void *out_frame)
|
||||
/* ---- Texture queries ---------------------------------------------------- */
|
||||
|
||||
int oakengine_display_texture_get_params(const void *texture,
|
||||
oak_video_params *out)
|
||||
{
|
||||
if (!out_frame) {
|
||||
return;
|
||||
if (!texture || !out) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
*static_cast<olive::FramePtr *>(out_frame) = olive::Frame::create();
|
||||
*out = cpp_to_pod(tex(const_cast<void *>(texture))->ptr->params());
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
void oakengine_codec_frame_set_video_params(void *frame,
|
||||
const void *video_params)
|
||||
int oakengine_display_texture_id(const void *texture)
|
||||
{
|
||||
olive::Frame *f = static_cast<olive::Frame *>(frame);
|
||||
if (!f || !video_params) {
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->id().toInt();
|
||||
}
|
||||
|
||||
int oakengine_display_texture_is_dummy(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return 1;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->is_dummy() ? 1 : 0;
|
||||
}
|
||||
|
||||
void *oakengine_display_texture_renderer(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->renderer();
|
||||
}
|
||||
|
||||
int oakengine_display_texture_width(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->width();
|
||||
}
|
||||
|
||||
int oakengine_display_texture_height(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->height();
|
||||
}
|
||||
|
||||
int oakengine_display_texture_format(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(tex(const_cast<void *>(texture))->ptr->format());
|
||||
}
|
||||
|
||||
int oakengine_display_texture_channel_count(const void *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<void *>(texture))->ptr->channel_count();
|
||||
}
|
||||
|
||||
int oakengine_display_texture_params_equal(const void *a, const void *b)
|
||||
{
|
||||
if (!a || !b) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<void *>(a))->ptr->params() ==
|
||||
tex(const_cast<void *>(b))->ptr->params();
|
||||
}
|
||||
|
||||
/* ---- Frame handle ------------------------------------------------------- */
|
||||
|
||||
void *oakengine_codec_frame_create(void)
|
||||
{
|
||||
return new OakEngineCodecFrame{olive::Frame::create(), {1}};
|
||||
}
|
||||
|
||||
void *oakengine_codec_frame_retain(void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
frm(frame)->refcount.fetch_add(1, std::memory_order_relaxed);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void oakengine_codec_frame_free(void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
f->set_video_params(*static_cast<const olive::VideoParams *>(video_params));
|
||||
if (frm(frame)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
delete frm(frame);
|
||||
}
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_set_video_params(void *frame,
|
||||
const oak_video_params *params)
|
||||
{
|
||||
if (!frame || !params) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
frm(frame)->ptr->set_video_params(pod_to_cpp(*params));
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_get_params(const void *frame, oak_video_params *out)
|
||||
{
|
||||
if (!frame || !out) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
*out = cpp_to_pod(frm(const_cast<void *>(frame))->ptr->video_params());
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_allocate(void *frame)
|
||||
{
|
||||
olive::Frame *f = static_cast<olive::Frame *>(frame);
|
||||
if (!f) {
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return f->allocate() ? 1 : 0;
|
||||
return frm(frame)->ptr->allocate() ? 1 : 0;
|
||||
}
|
||||
|
||||
void *oakengine_codec_frame_data(void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(frame)->ptr->data();
|
||||
}
|
||||
|
||||
const void *oakengine_codec_frame_const_data(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->const_data();
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_linesize(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->linesize_pixels();
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_linesize_bytes(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->linesize_bytes();
|
||||
}
|
||||
|
||||
/* ---- Frame queries ------------------------------------------------------ */
|
||||
|
||||
int oakengine_codec_frame_width(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->width();
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_height(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->height();
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_format(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(frm(const_cast<void *>(frame))->ptr->format());
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_channel_count(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->channel_count();
|
||||
}
|
||||
|
||||
int oakengine_codec_frame_is_allocated(const void *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<void *>(frame))->ptr->is_allocated() ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ---- Color-managed blit ------------------------------------------------- */
|
||||
|
||||
int oakengine_display_renderer_blit_color_managed(
|
||||
void *renderer, const oak_color_transform_job *job,
|
||||
void *dst_texture, const oak_video_params *params)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !job) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
|
||||
olive::ColorTransformJob ctj;
|
||||
if (job->processor) {
|
||||
ctj.set_color_processor(
|
||||
static_cast<const OakEngineColorProcessor *>(job->processor)->ptr);
|
||||
}
|
||||
if (job->input_texture) {
|
||||
ctj.set_input_texture(tex(job->input_texture)->ptr);
|
||||
}
|
||||
ctj.set_input_alpha_association(
|
||||
static_cast<olive::AlphaAssociated>(job->input_alpha_association));
|
||||
ctj.set_clear_destination_enabled(job->clear_destination != 0);
|
||||
ctj.set_force_opaque(job->force_opaque != 0);
|
||||
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;
|
||||
|
||||
if (params) {
|
||||
r->blit_color_managed(ctj, dst, pod_to_cpp(*params));
|
||||
} else if (dst) {
|
||||
r->blit_color_managed(ctj, dst, dst->params());
|
||||
}
|
||||
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
/* ---- Cross-backend texture download ------------------------------------- */
|
||||
|
||||
int oakengine_display_renderer_download_from_texture(
|
||||
void *renderer, int texture_id, const oak_video_params *params,
|
||||
void *dst_pixels, int linesize)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !params || !dst_pixels) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
r->download_from_texture(texture_id, pod_to_cpp(*params), dst_pixels,
|
||||
linesize);
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
/* ---- Shader management -------------------------------------------------- */
|
||||
|
||||
void *oakengine_display_renderer_create_shader(void *renderer,
|
||||
const char *frag_src,
|
||||
const char *vert_src)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !frag_src) {
|
||||
return nullptr;
|
||||
}
|
||||
olive::ShaderCode code(QString::fromUtf8(frag_src),
|
||||
vert_src ? QString::fromUtf8(vert_src) : QString());
|
||||
QVariant *v = new QVariant(r->create_native_shader(code));
|
||||
return v;
|
||||
}
|
||||
|
||||
void *oakengine_display_renderer_create_blank_shader(void *renderer)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return nullptr;
|
||||
}
|
||||
QVariant *v = new QVariant(r->create_native_shader(olive::ShaderCode()));
|
||||
return v;
|
||||
}
|
||||
|
||||
void oakengine_display_renderer_destroy_shader(void *renderer, void *shader)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader) {
|
||||
return;
|
||||
}
|
||||
QVariant *v = static_cast<QVariant *>(shader);
|
||||
r->destroy_native_shader(*v);
|
||||
delete v;
|
||||
}
|
||||
|
||||
/* ---- Shader blit operations --------------------------------------------- */
|
||||
|
||||
int oakengine_display_renderer_blit_shader(void *renderer, void *shader,
|
||||
void *texture,
|
||||
const oak_video_params *viewport_params)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !texture || !viewport_params) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
job.insert(QStringLiteral("ove_maintex"),
|
||||
olive::NodeValue(olive::NodeValue::k_texture,
|
||||
QVariant::fromValue(tex(texture)->ptr)));
|
||||
r->blit(*sv, job, pod_to_cpp(*viewport_params));
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_display_renderer_blit_shader_to_texture(
|
||||
void *renderer, void *shader, void *texture, void *dst_texture)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !texture || !dst_texture) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
job.insert(QStringLiteral("ove_maintex"),
|
||||
olive::NodeValue(olive::NodeValue::k_texture,
|
||||
QVariant::fromValue(tex(texture)->ptr)));
|
||||
r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get());
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_display_renderer_blit_shader_vec2_to_texture(
|
||||
void *renderer, void *shader, void *texture,
|
||||
const char *vec2_name, float vec2_x, float vec2_y,
|
||||
void *dst_texture)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !texture || !dst_texture) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
job.insert(QStringLiteral("ove_maintex"),
|
||||
olive::NodeValue(olive::NodeValue::k_texture,
|
||||
QVariant::fromValue(tex(texture)->ptr)));
|
||||
if (vec2_name) {
|
||||
job.insert(QString::fromUtf8(vec2_name),
|
||||
olive::NodeValue(olive::NodeValue::k_vec2,
|
||||
QVector2D(vec2_x, vec2_y)));
|
||||
}
|
||||
r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get());
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_display_renderer_blit_blank(
|
||||
void *renderer, void *shader,
|
||||
const float *mvp_matrix, const float *crop_matrix,
|
||||
const oak_video_params *params)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !params) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
job.insert(QStringLiteral("ove_mvpmat"),
|
||||
olive::NodeValue(olive::NodeValue::k_matrix,
|
||||
mat_from_float(mvp_matrix)));
|
||||
job.insert(QStringLiteral("ove_cropmatrix"),
|
||||
olive::NodeValue(olive::NodeValue::k_matrix,
|
||||
mat_from_float(crop_matrix)));
|
||||
r->blit(*sv, job, pod_to_cpp(*params), false);
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_display_renderer_blit_shader_multi(
|
||||
void *renderer, void *shader,
|
||||
const char *const *names, void *const *textures, int count,
|
||||
void *dst_texture)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !names || !textures || count <= 0 || !dst_texture) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (names[i] && textures[i]) {
|
||||
job.insert(QString::fromUtf8(names[i]),
|
||||
olive::NodeValue(olive::NodeValue::k_texture,
|
||||
QVariant::fromValue(tex(textures[i])->ptr)));
|
||||
}
|
||||
}
|
||||
r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get());
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_display_renderer_blit_shader_uniforms(
|
||||
void *renderer, void *shader, void *texture,
|
||||
const oak_shader_uniform *uniforms, int uniform_count,
|
||||
void *dst_texture, const oak_video_params *params)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !shader || !texture) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
QVariant *sv = static_cast<QVariant *>(shader);
|
||||
olive::ShaderJob job;
|
||||
job.insert(QStringLiteral("ove_maintex"),
|
||||
olive::NodeValue(olive::NodeValue::k_texture,
|
||||
QVariant::fromValue(tex(texture)->ptr)));
|
||||
for (int i = 0; i < uniform_count; i++) {
|
||||
if (!uniforms[i].name) {
|
||||
continue;
|
||||
}
|
||||
QString name = QString::fromUtf8(uniforms[i].name);
|
||||
switch (uniforms[i].type) {
|
||||
case 0: // float
|
||||
job.insert(name, olive::NodeValue(olive::NodeValue::k_float,
|
||||
uniforms[i].values[0]));
|
||||
break;
|
||||
case 1: // vec2
|
||||
job.insert(name, olive::NodeValue(olive::NodeValue::k_vec2,
|
||||
QVector2D(uniforms[i].values[0],
|
||||
uniforms[i].values[1])));
|
||||
break;
|
||||
case 2: // int/bool
|
||||
job.insert(name, olive::NodeValue(olive::NodeValue::k_boolean,
|
||||
static_cast<int>(uniforms[i].values[0]) != 0));
|
||||
break;
|
||||
case 3: // vec3
|
||||
job.insert(name, olive::NodeValue(olive::NodeValue::k_vec3,
|
||||
QVector3D(uniforms[i].values[0],
|
||||
uniforms[i].values[1],
|
||||
uniforms[i].values[2])));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dst_texture) {
|
||||
r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get());
|
||||
} else if (params) {
|
||||
r->blit(*sv, job, pod_to_cpp(*params));
|
||||
} else {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
/* ---- Renderer clear ----------------------------------------------------- */
|
||||
|
||||
void oakengine_display_renderer_clear(void *renderer, double r, double g,
|
||||
double b)
|
||||
{
|
||||
olive::Renderer *rn = ren(renderer);
|
||||
if (!rn) {
|
||||
return;
|
||||
}
|
||||
rn->clear_destination(nullptr, r, g, b);
|
||||
}
|
||||
|
||||
/* ---- Pixel readback ----------------------------------------------------- */
|
||||
|
||||
int oakengine_display_renderer_get_pixel(void *renderer, void *texture,
|
||||
int x, int y, double *out_rgba)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !texture || !out_rgba) {
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
olive::Color c = r->get_pixel_from_texture(tex(texture)->ptr.get(),
|
||||
QPoint(x, y));
|
||||
out_rgba[0] = c.red();
|
||||
out_rgba[1] = c.green();
|
||||
out_rgba[2] = c.blue();
|
||||
out_rgba[3] = c.alpha();
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
/* ---- Internal C++ helpers (not exported via C ABI) ---------------------- */
|
||||
|
||||
void *oakengine_internal_wrap_texture(const olive::TexturePtr &tp)
|
||||
{
|
||||
if (!tp) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *blk = new OakEngineDisplayTexture;
|
||||
blk->ptr = tp;
|
||||
blk->refcount.store(1);
|
||||
return blk;
|
||||
}
|
||||
|
||||
void *oakengine_internal_wrap_frame(const olive::FramePtr &fp)
|
||||
{
|
||||
if (!fp) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *blk = new OakEngineCodecFrame;
|
||||
blk->ptr = fp;
|
||||
blk->refcount.store(1);
|
||||
return blk;
|
||||
}
|
||||
|
||||
olive::TexturePtr oakengine_internal_unwrap_texture(void *handle)
|
||||
{
|
||||
if (!handle) {
|
||||
return nullptr;
|
||||
}
|
||||
return tex(handle)->ptr;
|
||||
}
|
||||
|
||||
olive::FramePtr oakengine_internal_unwrap_frame(void *handle)
|
||||
{
|
||||
if (!handle) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(handle)->ptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/***
|
||||
|
||||
Oak - 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 OAKENGINE_DISPLAYINTERNAL_H
|
||||
#define OAKENGINE_DISPLAYINTERNAL_H
|
||||
|
||||
// Internal (not installed) helpers for bridging engine-internal TexturePtr /
|
||||
// FramePtr to the opaque C ABI handles defined in capi/display.cpp.
|
||||
|
||||
#include "render/texture.h"
|
||||
#include "codec/frame.h"
|
||||
|
||||
/**
|
||||
* @brief Wrap an existing TexturePtr into a refcounted ABI handle (refcount=1).
|
||||
* Returns nullptr if tp is null.
|
||||
*/
|
||||
void *oakengine_internal_wrap_texture(const olive::TexturePtr &tp);
|
||||
|
||||
/**
|
||||
* @brief Wrap an existing FramePtr into a refcounted ABI handle (refcount=1).
|
||||
* Returns nullptr if fp is null.
|
||||
*/
|
||||
void *oakengine_internal_wrap_frame(const olive::FramePtr &fp);
|
||||
|
||||
/**
|
||||
* @brief Unwrap an ABI texture handle to the underlying TexturePtr.
|
||||
* Returns empty TexturePtr if handle is null.
|
||||
*/
|
||||
olive::TexturePtr oakengine_internal_unwrap_texture(void *handle);
|
||||
|
||||
/**
|
||||
* @brief Unwrap an ABI frame handle to the underlying FramePtr.
|
||||
* Returns empty FramePtr if handle is null.
|
||||
*/
|
||||
olive::FramePtr oakengine_internal_unwrap_frame(void *handle);
|
||||
|
||||
#endif // OAKENGINE_DISPLAYINTERNAL_H
|
||||
Reference in New Issue
Block a user