refactor(render): de-Qt oakrender and wrap it in a pure C ABI
- copy engine/render to src/render/src (sunk param types excluded), de-Qt in five parallel groups: core machinery (tickets/worker pool/ jobs), caches, color/texture, preview/IPC, GPU backends - replace Qt GL/Vulkan wrappers with native context abstractions (CGL/EGL/WGL, raw vulkan.h), QProcess with POSIX WorkerProcess, QJsonObject with a minimal NDJSON-compatible workerjson (wire protocol unchanged), QDataStream disk state with a byte-compatible BinaryStream - signals become single std::function callbacks or facade-triggered calls per the documented signal/slot strategy - pure C ABI in include/render + src/render/c_api (renderer/cache/ color/manager families, OAKRENDER_E_* codes), 37 gtest cases - bridge src/node/transition/render/* stubs to the real oakrender headers, closing the node<->render cycle: liboaknode links liboakrender, zero dangling symbols - docs: M7 implementation status + oakrender semantic-change notes
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
target_sources(oakrender PRIVATE
|
||||
renderer.cpp
|
||||
cache.cpp
|
||||
color.cpp
|
||||
manager.cpp
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
/***
|
||||
|
||||
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_EDITOR_RENDER_ALIVECOUNT_H
|
||||
#define OAK_EDITOR_RENDER_ALIVECOUNT_H
|
||||
|
||||
/**
|
||||
* @brief Shared live-object counter hooks (internal, not installed).
|
||||
*
|
||||
* The counter itself and the public oakrender_debug_alive_count() live in
|
||||
* the cache family (src/render/c_api/cache.cpp); these hooks have
|
||||
* external linkage so the other families' create/free functions can
|
||||
* participate. Mirrors src/node/c_api/alivecount.h.
|
||||
*/
|
||||
namespace oakrender_c_api
|
||||
{
|
||||
|
||||
void alive_inc();
|
||||
void alive_dec();
|
||||
|
||||
}
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_ALIVECOUNT_H
|
||||
@@ -0,0 +1,208 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/cache.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "framehashcache.h"
|
||||
#include "playbackcache.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::atomic<int> g_alive_count(0);
|
||||
|
||||
/**
|
||||
* @brief FrameHashCache with the protected PlaybackCache::validate()
|
||||
* exposed for the ABI. Adds no data members, so a handle created
|
||||
* as OakRenderCacheImpl reinterpret-casts safely both ways.
|
||||
*/
|
||||
class OakRenderCacheImpl : public olive::FrameHashCache {
|
||||
public:
|
||||
OakRenderCacheImpl()
|
||||
: olive::FrameHashCache(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
using olive::PlaybackCache::validate;
|
||||
};
|
||||
|
||||
OakRenderCacheImpl *impl(OakRenderCache *c)
|
||||
{
|
||||
return reinterpret_cast<OakRenderCacheImpl *>(c);
|
||||
}
|
||||
|
||||
const OakRenderCacheImpl *impl(const OakRenderCache *c)
|
||||
{
|
||||
return reinterpret_cast<const OakRenderCacheImpl *>(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert an int64 timestamp to a time using the cache's
|
||||
* timebase; timestamps are whole seconds when no valid timebase
|
||||
* is set.
|
||||
*/
|
||||
olive::Rational ts_to_time(const OakRenderCacheImpl *c, int64_t ts)
|
||||
{
|
||||
const olive::Rational &tb = c->get_timebase();
|
||||
if (tb.isNull()) {
|
||||
return olive::Rational::from_double(double(ts));
|
||||
}
|
||||
return olive::core::Timecode::timestamp_to_time(ts, tb);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace oakrender_c_api
|
||||
{
|
||||
|
||||
void alive_inc()
|
||||
{
|
||||
g_alive_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void alive_dec()
|
||||
{
|
||||
g_alive_count.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int oakrender_debug_alive_count(void)
|
||||
{
|
||||
return g_alive_count.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
OakRenderCache *oakrender_cache_create(void)
|
||||
{
|
||||
try {
|
||||
auto *c = new OakRenderCacheImpl();
|
||||
oakrender_c_api::alive_inc();
|
||||
return reinterpret_cast<OakRenderCache *>(c);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_cache_free(OakRenderCache *cache)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
delete impl(cache);
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oakrender_cache_set_timebase(OakRenderCache *cache, int num, int den)
|
||||
{
|
||||
if (!cache || 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)
|
||||
{
|
||||
if (!cache || !uuid) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
impl(cache)->set_uuid(uuid);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
void oakrender_cache_invalidate(OakRenderCache *cache, int64_t in_ts,
|
||||
int64_t out_ts)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
c->invalidate(
|
||||
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,
|
||||
int64_t out_ts)
|
||||
{
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
c->validate(
|
||||
olive::core::TimeRange(ts_to_time(c, in_ts), ts_to_time(c, out_ts)));
|
||||
}
|
||||
|
||||
int oakrender_cache_has_validated_ranges(const OakRenderCache *cache)
|
||||
{
|
||||
return cache && impl(cache)->has_validated_ranges() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oakrender_cache_indicator_height(void)
|
||||
{
|
||||
return olive::PlaybackCache::get_cache_indicator_height();
|
||||
}
|
||||
|
||||
int oakrender_frame_cache_load(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, int64_t ts,
|
||||
OakCodecFrame **out_frame)
|
||||
{
|
||||
if (!cache || !path || !uuid || !out_frame) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::FramePtr f =
|
||||
olive::FrameHashCache::load_cache_frame(path, uuid, ts);
|
||||
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;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_frame_cache_save(OakRenderCache *cache, const char *path,
|
||||
const char *uuid, const OakCodecFrame *frame)
|
||||
{
|
||||
if (!cache || !path || !uuid || !frame || !frame->ptr) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OakRenderCacheImpl *c = impl(cache);
|
||||
olive::Rational tb = c->get_timebase();
|
||||
if (tb.isNull()) {
|
||||
tb = olive::Rational(1, 1);
|
||||
}
|
||||
olive::FrameHashCache::save_cache_frame(path, uuid,
|
||||
frame->ptr->timestamp(), tb,
|
||||
frame->ptr);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/color.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "color/colormanager/colormanager.h"
|
||||
#include "filefunctions.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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;
|
||||
}
|
||||
if (direction != OAKRENDER_COLOR_DIRECTION_NORMAL &&
|
||||
direction != OAKRENDER_COLOR_DIRECTION_INVERSE) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Resolve role names (e.g. "scene_linear") to canonical colorspace
|
||||
// names, mirroring ColorProcessor's constructor.
|
||||
std::string src = src_space;
|
||||
if (config->hasRole(src_space)) {
|
||||
src = config->getCanonicalName(src_space);
|
||||
}
|
||||
|
||||
// OCIO failures are non-fatal (matching the C++ behavior): the
|
||||
// handle is still returned, but holds a null processor and
|
||||
// conversions pass through.
|
||||
ocio::ConstProcessorRcPtr processor;
|
||||
try {
|
||||
if (direction == OAKRENDER_COLOR_DIRECTION_NORMAL) {
|
||||
processor = config->getProcessor(src.c_str(), dst_transform);
|
||||
} else {
|
||||
processor = config->getProcessor(dst_transform, src.c_str());
|
||||
}
|
||||
} catch (ocio::Exception &) {
|
||||
processor = nullptr;
|
||||
}
|
||||
|
||||
auto *block = new OakColorProcessor;
|
||||
block->ptr = olive::ColorProcessor::create(processor);
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_color_processor_free(OakColorProcessor *processor)
|
||||
{
|
||||
if (!processor) {
|
||||
return;
|
||||
}
|
||||
delete processor;
|
||||
oakrender_c_api::alive_dec();
|
||||
}
|
||||
|
||||
int oakrender_color_processor_is_valid(const OakColorProcessor *processor)
|
||||
{
|
||||
return processor && processor->ptr && processor->ptr->get_processor() ? 1 :
|
||||
0;
|
||||
}
|
||||
|
||||
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) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::Color out =
|
||||
processor->ptr->convert_color(olive::Color(ir, ig, ib, ia));
|
||||
*out_r = out.red();
|
||||
*out_g = out.green();
|
||||
*out_b = out.blue();
|
||||
*out_a = out.alpha();
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- ColorManager statics ------------------------------------------------- */
|
||||
|
||||
int oakrender_color_manager_set_up_default_config(void)
|
||||
{
|
||||
try {
|
||||
olive::ColorManager::set_up_default_config();
|
||||
return olive::ColorManager::get_default_config() ? OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_color_manager_get_config(char *buf, int n)
|
||||
{
|
||||
try {
|
||||
const char *ocio_env = std::getenv("OCIO");
|
||||
if (ocio_env && *ocio_env) {
|
||||
return write_string(ocio_env, buf, n);
|
||||
}
|
||||
if (!olive::ColorManager::get_default_config()) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
// The default config is extracted next to the configuration
|
||||
// location (ColorManager::set_up_default_config()).
|
||||
return write_string(FileFunctions::get_configuration_location() +
|
||||
"/ocioconf/config.ocio",
|
||||
buf, n);
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_color_manager_display_transform(const char *display,
|
||||
const char *view, char *buf,
|
||||
int n)
|
||||
{
|
||||
if (!display || !*display || !view || !*view) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
|
||||
if (!config) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
|
||||
bool display_found = false;
|
||||
for (int i = 0; i < config->getNumDisplays(); i++) {
|
||||
if (display == std::string(config->getDisplay(i))) {
|
||||
display_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!display_found) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool view_found = false;
|
||||
for (int i = 0; i < config->getNumViews(display); i++) {
|
||||
if (view == std::string(config->getView(display, i))) {
|
||||
view_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!view_found) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Source = the config's reference colorspace (role lookup).
|
||||
ocio::ConstColorSpaceRcPtr ref_cs =
|
||||
config->getColorSpace(ocio::ROLE_REFERENCE);
|
||||
if (!ref_cs) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
|
||||
auto dvt = ocio::DisplayViewTransform::Create();
|
||||
dvt->setSrc(ref_cs->getName());
|
||||
dvt->setDisplay(display);
|
||||
dvt->setView(view);
|
||||
|
||||
ocio::ConstProcessorRcPtr processor = config->getProcessor(dvt);
|
||||
if (!processor) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
return write_string(processor->getCacheID(), buf, n);
|
||||
} catch (ocio::Exception &) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/***
|
||||
|
||||
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_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
#define OAK_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
|
||||
/**
|
||||
* @brief Control-block definitions behind the public opaque handles
|
||||
* (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().
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "colorprocessor.h"
|
||||
#include "texture.h"
|
||||
|
||||
struct OakRenderTexture {
|
||||
olive::TexturePtr ptr;
|
||||
std::atomic<int> refcount{ 1 };
|
||||
};
|
||||
|
||||
struct OakCodecFrame {
|
||||
olive::FramePtr ptr;
|
||||
std::atomic<int> refcount{ 1 };
|
||||
};
|
||||
|
||||
struct OakColorProcessor {
|
||||
olive::ColorProcessorPtr ptr;
|
||||
};
|
||||
|
||||
#endif //OAK_EDITOR_RENDER_INTERNALHANDLES_H
|
||||
@@ -0,0 +1,221 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/manager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "diskmanager.h"
|
||||
#include "output/viewer/viewer.h"
|
||||
#include "previewautocacher.h"
|
||||
#include "rendermanager.h"
|
||||
#include "renderticket.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
std::mutex g_requests_mutex;
|
||||
std::map<int64_t, olive::RenderTicketPtr> g_requests;
|
||||
std::atomic<int64_t> g_next_request_id(1);
|
||||
|
||||
} // namespace
|
||||
|
||||
int oakrender_manager_init(void)
|
||||
{
|
||||
if (olive::RenderManager::instance()) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
try {
|
||||
olive::RenderManager::create_instance();
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_manager_shutdown(void)
|
||||
{
|
||||
try {
|
||||
olive::RenderManager::destroy_instance();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oakrender_request_frame(OakNodeNode *viewer, int64_t ts,
|
||||
oakrender_frame_ready_fn cb, void *userdata)
|
||||
{
|
||||
if (!viewer || !cb) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
olive::RenderManager *manager = olive::RenderManager::instance();
|
||||
if (!manager) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
auto *v = dynamic_cast<olive::ViewerOutput *>(
|
||||
reinterpret_cast<olive::Node *>(viewer));
|
||||
if (!v) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
// ts is a frame number in the viewer's video timebase; fall back
|
||||
// to whole seconds when the viewer carries no valid timebase.
|
||||
olive::Rational tb = v->get_video_params().time_base();
|
||||
const olive::Rational time = tb.isNull() ?
|
||||
olive::Rational::from_double(double(ts)) :
|
||||
olive::core::Timecode::timestamp_to_time(ts, tb);
|
||||
|
||||
olive::RenderTicketPtr ticket =
|
||||
manager->get_cacher()->get_single_frame(v, time);
|
||||
if (!ticket) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
|
||||
const int64_t id =
|
||||
g_next_request_id.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
olive::RenderTicket *raw_ticket = ticket.get();
|
||||
ticket->set_finished_callback([ticket, cb, ts, userdata, id]() {
|
||||
OakCodecFrame *handle = nullptr;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
g_requests.erase(id);
|
||||
}
|
||||
cb(handle, ts, userdata);
|
||||
});
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
g_requests[id] = ticket;
|
||||
}
|
||||
(void) raw_ticket;
|
||||
return id;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_cancel_request(int64_t request_id)
|
||||
{
|
||||
olive::RenderTicketPtr ticket;
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(g_requests_mutex);
|
||||
auto it = g_requests.find(request_id);
|
||||
if (it == g_requests.end()) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
ticket = it->second;
|
||||
g_requests.erase(it);
|
||||
}
|
||||
ticket->cancel();
|
||||
if (olive::RenderManager::instance()) {
|
||||
olive::RenderManager::instance()->remove_ticket(ticket);
|
||||
}
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_set_cacher_multicam(OakNodeNode *multicam_or_NULL)
|
||||
{
|
||||
olive::RenderManager *manager = olive::RenderManager::instance();
|
||||
if (!manager) {
|
||||
return OAKRENDER_E_STATE;
|
||||
}
|
||||
manager->get_cacher()->set_multicam_node(
|
||||
reinterpret_cast<olive::MultiCamNode *>(multicam_or_NULL));
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
/* ---- Disk cache ----------------------------------------------------------- */
|
||||
|
||||
int oakrender_disk_cache_path(char *buf, int n)
|
||||
{
|
||||
try {
|
||||
return write_string(olive::DiskManager::get_default_disk_cache_path(),
|
||||
buf, n);
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oakrender_disk_cache_size(void)
|
||||
{
|
||||
try {
|
||||
olive::DiskManager *dm = olive::DiskManager::instance();
|
||||
if (!dm || !dm->get_default_cache_folder()) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
return dm->get_default_cache_folder()->get_consumption();
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_disk_cache_clear(void)
|
||||
{
|
||||
try {
|
||||
olive::DiskManager *dm = olive::DiskManager::instance();
|
||||
if (!dm) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
return dm->clear_disk_cache(
|
||||
olive::DiskManager::get_default_disk_cache_path()) ?
|
||||
OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "../../../include/render/renderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "alivecount.h"
|
||||
#include "internalhandles.h"
|
||||
|
||||
#include "backend/dynamicrenderer.h"
|
||||
#include "job/colortransformjob.h"
|
||||
#include "opengl/openglrenderer.h"
|
||||
#include "renderer.h"
|
||||
#include "rendermanager.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::VideoParams pod_to_cpp(const oakrender_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));
|
||||
vp.set_premultiplied_alpha(v.premultiplied_alpha != 0);
|
||||
return vp;
|
||||
}
|
||||
|
||||
oakrender_video_params cpp_to_pod(const olive::VideoParams &vp)
|
||||
{
|
||||
oakrender_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();
|
||||
p.video_type = 0;
|
||||
p.premultiplied_alpha = vp.premultiplied_alpha() ? 1 : 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
OakRenderTexture *tex(OakRenderTexture *h)
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
OakCodecFrame *frm(OakCodecFrame *h)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
olive::Matrix4x4 mat_from_float(const float *f)
|
||||
{
|
||||
olive::Matrix4x4 m;
|
||||
if (!f) {
|
||||
return m;
|
||||
}
|
||||
// All-zero means identity (R7-A §A.2 convention)
|
||||
bool all_zero = true;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (f[i] != 0.0f) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_zero) {
|
||||
return m;
|
||||
}
|
||||
// POD is column-major (QMatrix4x4 layout); Matrix4x4 stores [row][col]
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
m(row, col) = f[col * 4 + row];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
int write_string(const std::string &s, char *buf, int n)
|
||||
{
|
||||
const int required = int(s.size()) + 1;
|
||||
if (buf && n >= required) {
|
||||
std::memcpy(buf, s.c_str(), size_t(required));
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
// Requested backend recorded through oakrender_set_backend(); applied to
|
||||
// the RenderManager instance when one is created by the facade.
|
||||
std::string g_requested_backend = "opengl";
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---- Renderer lifecycle -------------------------------------------------- */
|
||||
|
||||
OakRenderRenderer *oakrender_display_renderer_create_dynamic(
|
||||
const char *backend_id)
|
||||
{
|
||||
if (!backend_id || !*backend_id) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
auto *r = new olive::DynamicRenderer(backend_id);
|
||||
if (!r->load()) {
|
||||
delete r;
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<OakRenderRenderer *>(r);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakRenderRenderer *oakrender_display_renderer_create_opengl(void)
|
||||
{
|
||||
try {
|
||||
return reinterpret_cast<OakRenderRenderer *>(new olive::OpenGLRenderer());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_renderer_init(OakRenderRenderer *renderer,
|
||||
void *gl_context)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
if (gl_context) {
|
||||
auto *ctx = static_cast<olive::OpenGLContext *>(gl_context);
|
||||
if (auto *gl = dynamic_cast<olive::OpenGLRenderer *>(r)) {
|
||||
gl->init(ctx);
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
if (auto *dyn = dynamic_cast<olive::DynamicRenderer *>(r)) {
|
||||
return dyn->init_with_open_gl_context(ctx) ? OAKRENDER_OK :
|
||||
OAKRENDER_E_FAILED;
|
||||
}
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return r->init() ? OAKRENDER_OK : OAKRENDER_E_FAILED;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void oakrender_display_renderer_destroy(OakRenderRenderer *renderer)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
r->destroy();
|
||||
delete r;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Renderer queries ---------------------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_is_open_gl(const OakRenderRenderer *renderer)
|
||||
{
|
||||
return renderer && ren(renderer)->is_open_gl() ? 1 : 0;
|
||||
}
|
||||
|
||||
int oakrender_display_renderer_is_vulkan(const OakRenderRenderer *renderer)
|
||||
{
|
||||
return renderer && ren(renderer)->is_vulkan() ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ---- Texture handle ------------------------------------------------------ */
|
||||
|
||||
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;
|
||||
}
|
||||
try {
|
||||
olive::TexturePtr t = r->create_texture(pod_to_cpp(*params), pixels,
|
||||
linesize);
|
||||
if (!t) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *block = new OakRenderTexture;
|
||||
block->ptr = std::move(t);
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakRenderTexture *oakrender_display_texture_retain(OakRenderTexture *texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_texture_upload(OakRenderTexture *texture,
|
||||
const void *pixels, int linesize)
|
||||
{
|
||||
if (!texture || !pixels || !tex(texture)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
tex(texture)->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 linesize)
|
||||
{
|
||||
if (!texture || !pixels || !tex(texture)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
tex(texture)->ptr->download(pixels, linesize);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_display_texture_get_params(const OakRenderTexture *texture,
|
||||
oakrender_video_params *out)
|
||||
{
|
||||
if (!texture || !out || !tex(const_cast<OakRenderTexture *>(texture))->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
*out = cpp_to_pod(tex(const_cast<OakRenderTexture *>(texture))->ptr->params());
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_display_texture_id(const OakRenderTexture *texture)
|
||||
{
|
||||
if (!texture || !tex(const_cast<OakRenderTexture *>(texture))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return tex(const_cast<OakRenderTexture *>(texture))->ptr->id().to_int();
|
||||
}
|
||||
|
||||
/* ---- Frame handle -------------------------------------------------------- */
|
||||
|
||||
OakCodecFrame *oakrender_codec_frame_create(void)
|
||||
{
|
||||
try {
|
||||
auto *block = new OakCodecFrame;
|
||||
block->ptr = olive::Frame::create();
|
||||
oakrender_c_api::alive_inc();
|
||||
return block;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OakCodecFrame *oakrender_codec_frame_retain(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_set_video_params(
|
||||
OakCodecFrame *frame, const oakrender_video_params *params)
|
||||
{
|
||||
if (!frame || !params || !frm(frame)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
frm(frame)->ptr->set_video_params(pod_to_cpp(*params));
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_get_params(const OakCodecFrame *frame,
|
||||
oakrender_video_params *out)
|
||||
{
|
||||
if (!frame || !out || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
*out = cpp_to_pod(
|
||||
frm(const_cast<OakCodecFrame *>(frame))->ptr->video_params());
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_allocate(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(frame)->ptr) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return frm(frame)->ptr->allocate() ? OAKRENDER_OK : OAKRENDER_E_FAILED;
|
||||
}
|
||||
|
||||
void *oakrender_codec_frame_data(OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(frame)->ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(frame)->ptr->data();
|
||||
}
|
||||
|
||||
const void *oakrender_codec_frame_const_data(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->ptr->const_data();
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_linesize_bytes(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->ptr->linesize_bytes();
|
||||
}
|
||||
|
||||
int oakrender_codec_frame_is_allocated(const OakCodecFrame *frame)
|
||||
{
|
||||
if (!frame || !frm(const_cast<OakCodecFrame *>(frame))->ptr) {
|
||||
return 0;
|
||||
}
|
||||
return frm(const_cast<OakCodecFrame *>(frame))->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)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !job) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
olive::ColorTransformJob ctj;
|
||||
if (job->processor) {
|
||||
ctj.set_color_processor(
|
||||
static_cast<const OakColorProcessor *>(job->processor)->ptr);
|
||||
}
|
||||
if (job->input_texture) {
|
||||
ctj.set_input_texture(
|
||||
static_cast<OakRenderTexture *>(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());
|
||||
} else {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Cross-backend texture download -------------------------------------- */
|
||||
|
||||
int oakrender_display_renderer_download_from_texture(
|
||||
OakRenderRenderer *renderer, int texture_id,
|
||||
const oakrender_video_params *params, void *dst_pixels, int linesize)
|
||||
{
|
||||
olive::Renderer *r = ren(renderer);
|
||||
if (!r || !params || !dst_pixels) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
try {
|
||||
r->download_from_texture(texture_id, pod_to_cpp(*params), dst_pixels,
|
||||
linesize);
|
||||
return OAKRENDER_OK;
|
||||
} catch (...) {
|
||||
return OAKRENDER_E_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Backend management -------------------------------------------------- */
|
||||
|
||||
int oakrender_backend_count(void)
|
||||
{
|
||||
// olive::RenderManager::Backend: k_open_gl, k_vulkan, k_multi_process,
|
||||
// k_dummy
|
||||
return 4;
|
||||
}
|
||||
|
||||
int oakrender_backend_id_at(int i, char *buf, int n)
|
||||
{
|
||||
if (i < 0 || i >= oakrender_backend_count()) {
|
||||
return OAKRENDER_E_NOT_FOUND;
|
||||
}
|
||||
return write_string(
|
||||
olive::RenderManager::backend_to_string(
|
||||
static_cast<olive::RenderManager::Backend>(i)),
|
||||
buf, n);
|
||||
}
|
||||
|
||||
int oakrender_set_backend(const char *backend_id)
|
||||
{
|
||||
if (!backend_id) {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
std::string lower = backend_id;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return char(std::tolower(c)); });
|
||||
if (lower != "opengl" && lower != "vulkan" && lower != "multiprocess" &&
|
||||
lower != "dummy") {
|
||||
return OAKRENDER_E_INVALID;
|
||||
}
|
||||
g_requested_backend = lower;
|
||||
return OAKRENDER_OK;
|
||||
}
|
||||
|
||||
int oakrender_current_backend(char *buf, int n)
|
||||
{
|
||||
if (olive::RenderManager::instance()) {
|
||||
return write_string(
|
||||
olive::RenderManager::backend_to_string(
|
||||
olive::RenderManager::instance()->backend()),
|
||||
buf, n);
|
||||
}
|
||||
return write_string(g_requested_backend, buf, n);
|
||||
}
|
||||
Reference in New Issue
Block a user