core: merge oak-common into oak-core
CI / Build & test (Linux) (push) Successful in 24m6s
CI / Build & test (Windows) (push) Successful in 31m14s

oak-common is gone; its modules (configstore, xmlutils, ocioutils,
oiioutils, colormath, colortransform, videoparams, ffmpegutils, ...)
now live in oak-core alongside the value types. The render value/GPU
types moved too: backend (wgpu context + DisplayRenderer), color
(ColorProcessor over ocio-rs), texture, frame, and the commonutil
config helpers.

Fix-ups to make the merged tree build and pass tests:

- oak-core Cargo.toml: wgpu back to 25 (the moved backend code is
  written against that API generation); add the toml/quick-xml/image
  deps oak-common carried.
- lib.rs: drop the duplicate 'pub mod error;'.
- error.rs: unified OAKCORE_* codes; restore Error::new() and
  From<OcioError> from oak-common's error type.
- backend.rs/color.rs: oak_core::/oak_render:: self-references
  rewritten to crate::; the shaderfx-dependent GPU effect test moved
  to oak-render's shaderfx tests (shaderfx depends on oak-node and
  cannot live in oak-core).
- oak-render's error module re-exports oak_core::error::{Error,
  Result}; the OAKRENDER_* codes stay as the public-code contract.
- oak-node jobs.rs: ColorProcessor imported from oak_core::color.
- Integration tests repointed at oak_core::{texture, frame, backend,
  color, colormath}.
- the display-ICC regression test treats an empty OAK_DISPLAY_ICC as
  unset, matching displayicc::env_override_icc.
This commit is contained in:
2026-09-03 17:42:20 +08:00
parent 49fed365a4
commit 4babbf5de8
150 changed files with 3052 additions and 4030 deletions
Generated
+5 -19
View File
@@ -4158,9 +4158,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.33"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
dependencies = [
"serde_core",
"value-bag",
@@ -4732,7 +4732,6 @@ dependencies = [
"log",
"oak-audio",
"oak-codec",
"oak-common",
"oak-core",
"oak-node",
"oak-plugin",
@@ -4753,7 +4752,6 @@ dependencies = [
"cpal",
"ffmpeg-next",
"oak-codec",
"oak-common",
"oak-core",
"oak-ffmpeg-link",
"thiserror 2.0.20",
@@ -4765,7 +4763,6 @@ version = "0.5.0"
dependencies = [
"clap",
"oak-codec",
"oak-common",
"oak-core",
"oak-node",
"oak-render",
@@ -4778,29 +4775,24 @@ name = "oak-codec"
version = "0.5.0"
dependencies = [
"ffmpeg-next",
"oak-common",
"oak-core",
"oak-ffmpeg-link",
"thiserror 2.0.20",
]
[[package]]
name = "oak-common"
name = "oak-core"
version = "0.5.0"
dependencies = [
"image",
"log",
"oak-core",
"ocio-rs",
"quick-xml 0.41.0",
"thiserror 2.0.20",
"toml 0.8.23",
"wgpu 25.0.2",
]
[[package]]
name = "oak-core"
version = "0.5.0"
[[package]]
name = "oak-ffmpeg-link"
version = "0.5.0"
@@ -4810,7 +4802,6 @@ name = "oak-node"
version = "0.5.0"
dependencies = [
"oak-codec",
"oak-common",
"oak-core",
"oak-undo",
"thiserror 2.0.20",
@@ -4846,7 +4837,6 @@ dependencies = [
"libc",
"naga 25.0.1",
"oak-codec",
"oak-common",
"oak-core",
"oak-node",
"ocio-rs",
@@ -4861,7 +4851,6 @@ name = "oak-storage"
version = "0.5.0"
dependencies = [
"chrono",
"oak-common",
"oak-core",
"oak-node",
"oak-otio",
@@ -4878,7 +4867,6 @@ name = "oak-task"
version = "0.5.0"
dependencies = [
"oak-codec",
"oak-common",
"oak-core",
"oak-node",
"oak-otio",
@@ -4891,7 +4879,6 @@ dependencies = [
name = "oak-timeline"
version = "0.5.0"
dependencies = [
"oak-common",
"oak-core",
"oak-node",
"oak-undo",
@@ -4911,7 +4898,6 @@ version = "0.5.0"
dependencies = [
"libc",
"oak-codec",
"oak-common",
"oak-core",
"oak-node",
"oak-plugin",
@@ -7566,7 +7552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand 2.5.0",
"getrandom 0.3.4",
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
-1
View File
@@ -76,7 +76,6 @@ log = "0.4"
# dylib, no C ABI, no build.rs link step, no host shims.
oak-audio = { path = "../oak-audio" }
oak-codec = { path = "../oak-codec" }
oak-common = { path = "../oak-common" }
oak-core = { path = "../oak-core" }
oak-node = { path = "../oak-node" }
oak-plugin = { path = "../oak-plugin" }
+1 -1
View File
@@ -481,7 +481,7 @@ pub(crate) fn shortcuts_test_lock() -> &'static Mutex<()> {
/// The path of the custom-shortcuts file: `<config>/shortcuts`, exactly like
/// the C++ `MainWindow::get_custom_shortcuts_file`.
pub fn custom_shortcuts_path() -> String {
let dir = oak_common::filefunctions::FileFunctions::new()
let dir = oak_core::filefunctions::FileFunctions::new()
.get_configuration_location()
.unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().into_owned());
format!("{}/shortcuts", dir.trim_end_matches('/'))
+24 -24
View File
@@ -42,13 +42,13 @@ use std::time::Duration;
use crate::oakui::component::menu::{self, Menu, MenuBar, MenuBarEntry, MenuBarEvent, MenuItem};
use crate::oakui::component::text_input::install_text_input_bindings;
use gpui::dock::{
DockArea, DockEvent, DockLayout, DropTarget, DropZone, NodePath, PanelHandle, PanelId,
PanelRegistry,
DockArea, DockEvent, DockLayout, DropTarget, DropZone, NodePath, PanelHandle, PanelId,
PanelRegistry,
};
use gpui::timeline::{ClipData, ClipId, Frame, FrameRange, TimelineEvent, TimelineView, TrackData};
use gpui::{
colors::DefaultColors, div, prelude::*, px, size, App, AsyncWindowContext, Bounds, Context,
Entity, PathPromptOptions, Render, Window, WindowBounds, WindowOptions,
colors::DefaultColors, div, prelude::*, px, size, App, AsyncWindowContext, Bounds, Context,
Entity, PathPromptOptions, Render, Window, WindowBounds, WindowOptions,
};
use gpui_widgets::audio_meter::{AudioLevelMeter, MeterOrientation};
use gpui_widgets::dialog::progress::{progress_dialog, ProgressContent};
@@ -58,7 +58,7 @@ use gpui_widgets::viewer::PlaybackClock;
use crate::actions::{ActionId, TimelineToolExt, Tool};
use crate::dialogs::{
DropSequenceChoice, ExportDialogContent, PreferencesDialogContent, SequenceFormatSeed,
DropSequenceChoice, ExportDialogContent, PreferencesDialogContent, SequenceFormatSeed,
};
use crate::oakui::{AppEngine, ExportSession, MockEngine, Monitor, RealEngine};
use crate::panels::commands as panel_commands;
@@ -78,9 +78,9 @@ use crate::panels::timeline::{FootageDropNeedsSequence, TimelinePanel};
// registry is the single source; these keep the test call sites readable).
#[cfg(test)]
pub(crate) mod menu_ids {
use crate::actions::ActionId;
use crate::actions::ActionId;
pub const NEW_PROJECT: usize = ActionId::NewProject.menu_id();
pub const NEW_PROJECT: usize = ActionId::NewProject.menu_id();
pub const OPEN_PROJECT: usize = ActionId::OpenProject.menu_id();
pub const EXPORT_PROJECT: usize = ActionId::SaveProject.menu_id();
pub const CLOSE: usize = ActionId::CloseProject.menu_id();
@@ -1035,8 +1035,8 @@ impl<E: AppEngine> OakApp<E> {
/// preferences, tools, transport fallbacks — and the placeholder
/// `println!` for the actions not wired yet.
fn handle_global_action(&mut self, action: ActionId, cx: &mut Context<Self>) {
use crate::actions::ActionId as A;
match action {
use crate::actions::ActionId as A;
match action {
// --- File ------------------------------------------------------
A::NewProject => self.open_new_project(cx),
A::OpenProject => self.open_file_dialog(FileAction::Open, cx),
@@ -1746,8 +1746,8 @@ impl<E: AppEngine> OakApp<E> {
/// close the dialog on success; the mutating actions reload the list;
/// failures land in the dialog's status line.
fn on_manager_event(&mut self, event: &crate::manager::ManagerEvent, cx: &mut Context<Self>) {
use crate::manager::ManagerEvent as E;
match event {
use crate::manager::ManagerEvent as E;
match event {
E::Create => {
let name = crate::i18n::tr("manager.new.default_name").to_string();
let result = self
@@ -3392,7 +3392,7 @@ impl MenuState {
loop_playback: false,
show_all: false,
full_screen: false,
use_proxy_media: oak_common::configstore::ConfigStore::instance().get_bool(
use_proxy_media: oak_core::configstore::ConfigStore::instance().get_bool(
None,
"UseProxyMedia",
1,
@@ -3420,10 +3420,10 @@ fn menu_item(action: ActionId) -> MenuItem {
/// the menu bar after a language switch repaints it in the new language;
/// `state` drives the dynamic checkmarks (theme, tool, snapping, loop, …).
fn make_menus(state: MenuState) -> Vec<MenuBarEntry> {
use crate::actions::ActionId as A;
use crate::i18n::tr;
use crate::actions::ActionId as A;
use crate::i18n::tr;
let theme_submenu = Menu::new(vec![
let theme_submenu = Menu::new(vec![
menu_item(A::ThemeDark).with_checked(state.dark),
menu_item(A::ThemeLight).with_checked(!state.dark),
]);
@@ -3754,9 +3754,9 @@ fn run_with<E: AppEngine>(args: AppArgs) {
// choice through the process environment. Read by gpui_wgpu as
// OAK_DISPLAY_BIT_DEPTH ("8" opts into the 8-bit pair; anything
// else requests 10-bit).
let bit_depth = oak_render::backend::DisplayBitDepth::from_config_string(
let bit_depth = oak_core::backend::DisplayBitDepth::from_config_string(
&crate::oakui::real::config_get_string(
oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH,
oak_core::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH,
),
);
// SAFETY: single-threaded startup, before any window exists.
@@ -3830,14 +3830,14 @@ fn run_with<E: AppEngine>(args: AppArgs) {
#[cfg(test)]
mod tests {
use super::*;
use crate::oakui::EngineGateway as _;
use gpui::timeline::TimelineDataSource as _;
use gpui::{
px, size, AnyWindowHandle, ExternalPaths, FileDropEvent, TestAppContext, VisualTestContext,
};
use super::*;
use crate::oakui::EngineGateway as _;
use gpui::timeline::TimelineDataSource as _;
use gpui::{
px, size, AnyWindowHandle, ExternalPaths, FileDropEvent, TestAppContext, VisualTestContext,
};
/// The 视图/View menu carries a 语言/Language submenu whose items are
/// The 视图/View menu carries a 语言/Language submenu whose items are
/// labeled in their own language and whose checkmark follows the active
/// language — and the whole menu bar flips language with `i18n`.
#[test]
+32 -32
View File
@@ -21,7 +21,7 @@
//! Each view owns its widgets and emits nothing itself — the host
//! (`crate::app::OakApp`) reads the state (format / path) when a dialog
//! button is clicked, and the preferences view writes its choices straight
//! into the oakcommon config store on selection. Theme/language changes
//! into the oak_core config store on selection. Theme/language changes
//! additionally emit a [`PreferencesEvent`] so the host can re-apply the
//! shell chrome immediately.
@@ -36,28 +36,28 @@ use gpui::colors::DefaultColors;
use gpui::prelude::*;
use gpui::timeline::FrameRate;
use gpui::{
div, px, App, ClickEvent, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, Keystroke, PathPromptOptions, Render, SharedString, Window,
div, px, App, ClickEvent, Context, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, Keystroke, PathPromptOptions, Render, SharedString, Window,
};
use gpui_elements::editable_text::{EditableTextState, StringStorage, TextChanged};
use crate::actions::ActionId;
use crate::i18n;
use crate::oakui::real::{
audio_input_device, audio_input_devices, audio_output_device, audio_output_devices,
config_get_bool, config_get_int, config_get_string, config_set_bool, config_set_int,
config_set_string, encoding_formats, proxy_dividers, renderer_backends, set_audio_input_device,
set_audio_output_device, set_theme_dark, theme_is_dark, CONFIG_KEY_DEFAULT_TRANSITION_SEC,
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PG_URL,
CONFIG_KEY_PREVIEW_WINDOW, CONFIG_KEY_PROXY_DIVIDER, CONFIG_KEY_RENDERER_BACKEND,
CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_STORAGE_BACKEND, CONFIG_KEY_USE_PROXY,
DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC,
EXPORT_FORMAT_MP4,
audio_input_device, audio_input_devices, audio_output_device, audio_output_devices,
config_get_bool, config_get_int, config_get_string, config_set_bool, config_set_int,
config_set_string, encoding_formats, proxy_dividers, renderer_backends, set_audio_input_device,
set_audio_output_device, set_theme_dark, theme_is_dark, CONFIG_KEY_DEFAULT_TRANSITION_SEC,
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PG_URL,
CONFIG_KEY_PREVIEW_WINDOW, CONFIG_KEY_PROXY_DIVIDER, CONFIG_KEY_RENDERER_BACKEND,
CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_STORAGE_BACKEND, CONFIG_KEY_USE_PROXY,
DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC,
EXPORT_FORMAT_MP4,
};
// The `DisplayBitDepth` config key lives with the format mapping it
// drives (oak-render's backend); the preferences dropdown and the
// window-layer consumer share the same key.
use oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH;
use oak_core::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH;
// ---------------------------------------------------------------------------
// Preferences
@@ -373,13 +373,13 @@ impl PreferencesContent {
})
.detach();
// --- 色彩 Color: display ICC color management -----------------------
// On by default: the viewer frames are transformed through the
// display's ICC profile (system profile, or a custom file below).
// A mode change re-evaluates the platform display policy and
// retags the windows immediately (no restart).
use crate::oakui::displaycolor::{CONFIG_KEY_COLOR_MODE, CONFIG_KEY_CUSTOM_ICC};
let display_icc = cx.new(|cx| {
// --- 色彩 Color: display ICC color management -----------------------
// On by default: the viewer frames are transformed through the
// display's ICC profile (system profile, or a custom file below).
// A mode change re-evaluates the platform display policy and
// retags the windows immediately (no restart).
use crate::oakui::displaycolor::{CONFIG_KEY_COLOR_MODE, CONFIG_KEY_CUSTOM_ICC};
let display_icc = cx.new(|cx| {
let mode = config_get_string(CONFIG_KEY_COLOR_MODE);
CheckBox::new(
13,
@@ -1685,8 +1685,8 @@ fn divider_label(divider: i32) -> String {
/// The display string of a proxy lifecycle state.
fn proxy_state_label(state: crate::oakui::engine::ProxyMediaState) -> String {
use crate::oakui::engine::ProxyMediaState;
match state {
use crate::oakui::engine::ProxyMediaState;
match state {
ProxyMediaState::Missing => i18n::tr("proxydialog.state.missing"),
ProxyMediaState::Generating => i18n::tr("proxydialog.state.generating"),
ProxyMediaState::Ready => i18n::tr("proxydialog.state.ready"),
@@ -1918,20 +1918,20 @@ impl<E: crate::oakui::engine::AppEngine> ProjectPropertiesContent<E> {
let (working, gamut, transfer) = engine.read(cx).project_color_settings();
working_space.update(cx, |combo, cx| {
combo.set_selected(
Some(oak_common::colormath::WorkingColorSpace::from_setting(&working) as usize),
cx,
Some(oak_core::colormath::WorkingColorSpace::from_setting(&working) as usize),
cx,
)
});
output_gamut.update(cx, |combo, cx| {
combo.set_selected(
Some(oak_common::colormath::OutputGamut::from_setting(&gamut) as usize),
cx,
Some(oak_core::colormath::OutputGamut::from_setting(&gamut) as usize),
cx,
)
});
output_transfer.update(cx, |combo, cx| {
combo.set_selected(
Some(oak_common::colormath::OutputTransfer::from_setting(&transfer) as usize),
cx,
Some(oak_core::colormath::OutputTransfer::from_setting(&transfer) as usize),
cx,
)
});
@@ -2015,8 +2015,8 @@ impl<E: crate::oakui::engine::AppEngine> ProjectPropertiesContent<E> {
/// The color pipeline settings currently selected in the combos, as
/// the canonical persisted strings.
fn color_settings(&self, cx: &App) -> (String, String, String) {
use oak_common::colormath::{OutputGamut, OutputTransfer, WorkingColorSpace};
let working = self
use oak_core::colormath::{OutputGamut, OutputTransfer, WorkingColorSpace};
let working = self
.working_space
.read(cx)
.selected()
@@ -4289,8 +4289,8 @@ impl Render for NewProjectContent {
#[cfg(test)]
mod tests {
use super::*;
fn keystroke(key: &str) -> Keystroke {
use super::*;
fn keystroke(key: &str) -> Keystroke {
gpui::Keystroke::parse(key).unwrap()
}
+1 -1
View File
@@ -25,7 +25,7 @@
//! directly (M14 R3: project open/save through the oaknode serializer,
//! timeline edits through the oaktimeline edit commands on the oakundo
//! global stack, the oakrender ticket arena for the viewers, the oaktask
//! export path, the oakcommon config store, and the oakstorage
//! export path, the oak_core config store, and the oakstorage
//! write-through library — no `liboakengine` dylib, no C ABI).
//!
//! # Layout
+10 -10
View File
@@ -52,7 +52,7 @@
//! ## The content space
//!
//! The chain starts from the project's output spec
//! ([`oak_render::color::pipeline_output_spec`]): sRGB content runs
//! ([`oak_core::color::pipeline_output_spec`]): sRGB content runs
//! through the named sRGB/Rec.709 space of the active OCIO config, and
//! non-sRGB content (P3/BT.2020 gamuts, PQ/HLG transfers) is converted to
//! CIE XYZ (D65, unit luminance) first and flows through the ICC's
@@ -82,9 +82,9 @@
use std::sync::{Arc, LazyLock, Mutex};
use std::time::{Duration, Instant};
use oak_common::colormath::{output_spec_to_xyz_d65, OutputColorSpec, OutputGamut, OutputTransfer};
use oak_common::configstore::ConfigStore;
use oak_render::color::{pipeline_output_spec, ColorProcessor};
use oak_core::colormath::{output_spec_to_xyz_d65, OutputColorSpec, OutputGamut, OutputTransfer};
use oak_core::configstore::ConfigStore;
use oak_core::color::{pipeline_output_spec, ColorProcessor};
/// Config key: the display color management mode ("icc" / "off"). The
/// preference only applies where the platform policy allows self-management
@@ -188,7 +188,7 @@ pub fn display_policy() -> DisplayPolicy {
// sRGB; a non-sRGB project output can't be honored through it — warn
// once so the degradation is visible in the log.
#[cfg(target_os = "windows")]
if oak_common::displayicc::windows_acm_active() {
if oak_core::displayicc::windows_acm_active() {
if pipeline_output_spec() != OutputColorSpec::default() {
static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
WARNED.get_or_init(|| {
@@ -280,7 +280,7 @@ fn current_monitor_fingerprint(window: &gpui::Window, cx: &gpui::App) -> Option<
#[cfg(target_os = "windows")]
{
let id = u64::from(window.display(cx)?.id());
return oak_common::displayicc::windows_monitor_fingerprint(id);
return oak_core::displayicc::windows_monitor_fingerprint(id);
}
#[cfg(target_os = "linux")]
{
@@ -288,7 +288,7 @@ fn current_monitor_fingerprint(window: &gpui::Window, cx: &gpui::App) -> Option<
let center = window.bounds().center();
let x = f64::from(center.x) * f64::from(window.scale_factor());
let y = f64::from(center.y) * f64::from(window.scale_factor());
return oak_common::displayicc::x11_monitor_fingerprint_at(x, y);
return oak_core::displayicc::x11_monitor_fingerprint_at(x, y);
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
@@ -414,9 +414,9 @@ fn current() -> Option<State> {
/// inside `system_display_icc_for`); an unknown one (empty or malformed)
/// falls back to the main-display profile — the pre-multi-monitor behavior.
fn monitor_icc_path(monitor: &str) -> Option<String> {
match oak_common::displayicc::monitor_ref_from_fingerprint(monitor) {
Some(monitor) => oak_common::displayicc::system_display_icc_for(&monitor),
None => oak_common::displayicc::system_display_icc(),
match oak_core::displayicc::monitor_ref_from_fingerprint(monitor) {
Some(monitor) => oak_core::displayicc::system_display_icc_for(&monitor),
None => oak_core::displayicc::system_display_icc(),
}
}
+12 -12
View File
@@ -829,7 +829,7 @@ pub trait AppEngine:
/// The global "Use Proxy Media" switch (the C++ `UseProxyMedia`
/// config; preview-only — exports always decode the original media).
fn use_proxy_media(&self) -> bool {
oak_common::configstore::ConfigStore::instance()
oak_core::configstore::ConfigStore::instance()
.get_bool(None, "UseProxyMedia", 1)
!= 0
}
@@ -838,7 +838,7 @@ pub trait AppEngine:
/// footage's rendered frames (the C++ toggles the config and
/// re-renders; the preview path reads the switch on every montage).
fn set_use_proxy_media(&mut self, enabled: bool, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
"UseProxyMedia",
if enabled { "true" } else { "false" },
@@ -851,7 +851,7 @@ pub trait AppEngine:
/// size, 2/4/8 = progressively smaller preview renders for machines
/// that cannot keep up. Preview-only; exports always render native.
fn playback_divider(&self) -> i64 {
oak_common::configstore::ConfigStore::instance()
oak_core::configstore::ConfigStore::instance()
.get_int(None, "PlaybackDivider", 1)
.clamp(1, 8) as i64
}
@@ -859,7 +859,7 @@ pub trait AppEngine:
/// Sets the playback resolution divider and invalidates the rendered
/// frames so the next pull re-renders at the new geometry.
fn set_playback_divider(&mut self, divider: i64, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
"PlaybackDivider",
&divider.clamp(1, 8).to_string(),
@@ -877,7 +877,7 @@ pub trait AppEngine:
/// Whether playback stops at the last frame instead of looping (the C++
/// viewer `Stop on Last` toggle / `StopOnLastFrame` config).
fn stop_on_last(&self) -> bool {
oak_common::configstore::ConfigStore::instance()
oak_core::configstore::ConfigStore::instance()
.get_bool(None, "StopOnLastFrame", 0)
!= 0
}
@@ -885,7 +885,7 @@ pub trait AppEngine:
/// Sets the `StopOnLastFrame` config (the next tick past the end either
/// pauses at the last frame or wraps around).
fn set_stop_on_last(&mut self, enabled: bool, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
"StopOnLastFrame",
if enabled { "true" } else { "false" },
@@ -897,14 +897,14 @@ pub trait AppEngine:
/// value (`0` automatic / `1` only / `2` both), clamped to the valid
/// range.
fn waveform_mode(&self) -> i32 {
oak_common::configstore::ConfigStore::instance()
oak_core::configstore::ConfigStore::instance()
.get_int(None, "ViewerWaveformMode", 0)
.clamp(0, 2)
}
/// Sets the `ViewerWaveformMode` config value (clamped to `0..=2`).
fn set_waveform_mode(&mut self, mode: i32, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
"ViewerWaveformMode",
&mode.clamp(0, 2).to_string(),
@@ -972,15 +972,15 @@ pub trait AppEngine:
/// The project's color pipeline settings:
/// `(working colorspace, output gamut, output transfer)` as the
/// persisted setting strings (see `oak_common::colormath`). The
/// persisted setting strings (see `oak_core::colormath`). The
/// working colorspace is the pipeline's scene space (ACEScg by
/// default, not hard-coded sRGB); the output pair is the delivery
/// target for export and presentation.
fn project_color_settings(&self) -> (String, String, String) {
(
oak_common::colormath::WorkingColorSpace::default().as_setting().to_string(),
oak_common::colormath::OutputGamut::default().as_setting().to_string(),
oak_common::colormath::OutputTransfer::default().as_setting().to_string(),
oak_core::colormath::WorkingColorSpace::default().as_setting().to_string(),
oak_core::colormath::OutputGamut::default().as_setting().to_string(),
oak_core::colormath::OutputTransfer::default().as_setting().to_string(),
)
}
+1 -1
View File
@@ -2450,7 +2450,7 @@ pub fn remove_node(p: &ProjectRef, node: NodeId) -> Result<(), String> {
// ---------------------------------------------------------------------------
/// A process-wide test lock: the app's tests share the oakundo global
/// stack, the oakcommon config store and the codec decode sessions, so any
/// stack, the oak_core config store and the codec decode sessions, so any
/// test touching them serializes on this lock.
#[cfg(test)]
pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
+21 -21
View File
@@ -43,19 +43,19 @@ use std::sync::{Arc, Mutex};
use std::time::Instant;
use gpui::effect_stack::{
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
};
use gpui::node_graph::{
EdgeData, EdgeId, NodeData, NodeGraphDataSource, NodeGraphEvent, NodeId, PortData,
PortDataType, PortId, PortKind,
EdgeData, EdgeId, NodeData, NodeGraphDataSource, NodeGraphEvent, NodeId, PortData,
PortDataType, PortId, PortKind,
};
use gpui::timeline::{
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
TrackData, TrackKind, TrimEdge,
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
TrackData, TrackKind, TrimEdge,
};
use gpui::{
hsla, point, prelude::*, px, App, Context, Entity, Hsla, Pixels, Point, RenderImage,
SharedString,
hsla, point, prelude::*, px, App, Context, Entity, Hsla, Pixels, Point, RenderImage,
SharedString,
};
use gpui_widgets::audio_meter::AudioMeterDataSource;
use gpui_widgets::project_explorer::{ProjectDataSource, ProjectEntry};
@@ -67,8 +67,8 @@ use oak_node::track::TrackType;
use oak_timeline::util::{block_clip_create, track_append_block};
use super::engine::{
AppEngine, EngineGateway, ExportEvent, ExportSession, LibraryProject, Monitor, MulticamState,
Project, ScopeData, Sequence, VideoFormat, WizardFootage, WizardSyncOffset,
AppEngine, EngineGateway, ExportEvent, ExportSession, LibraryProject, Monitor, MulticamState,
Project, ScopeData, Sequence, VideoFormat, WizardFootage, WizardSyncOffset,
};
use super::graphops;
use super::transport::TransportState;
@@ -2060,7 +2060,7 @@ impl AppEngine for MockEngine {
let trimmed = path.trim().to_string();
// Validate like the real engine (a bogus path keeps the dialog open).
if !trimmed.is_empty() {
oak_render::color::set_up_default_config_from(Some(&trimmed))
oak_core::color::set_up_default_config_from(Some(&trimmed))
.map_err(|e| e.to_string())?;
}
self.ocio_config = trimmed;
@@ -2582,11 +2582,11 @@ impl DemoMulticamGraph {
/// tracks are built directly in the graph (no `Add Track` undo entries —
/// the demo's initial state is not a user edit).
fn build() -> Self {
use oak_node::node::NodeCore;
use oak_node::sequence::SequenceBehavior;
use oak_node::track::{TrackBehavior, TrackListBehavior};
use oak_node::node::NodeCore;
use oak_node::sequence::SequenceBehavior;
use oak_node::track::{TrackBehavior, TrackListBehavior};
let project = graphops::create_project();
let project = graphops::create_project();
let sequence = graphops::create_sequence(&project, "Multicam Demo");
// A video track list with four tracks, wired into the sequence.
{
@@ -2863,10 +2863,10 @@ impl MockEngine {
#[cfg(test)]
mod tests {
use super::*;
use gpui::TestAppContext;
use super::*;
use gpui::TestAppContext;
fn demo_engine(app: &mut gpui::App) -> Entity<MockEngine> {
fn demo_engine(app: &mut gpui::App) -> Entity<MockEngine> {
app.new(|cx| MockEngine::demo(cx))
}
@@ -2932,8 +2932,8 @@ mod tests {
/// The stop-on-last tick pauses on the final frame instead of wrapping.
#[test]
fn clock_tick_stops_on_the_last_frame_when_asked() {
use std::time::{Duration, Instant};
let mut clock = MockClock::new(FrameRate::new(30, 1));
use std::time::{Duration, Instant};
let mut clock = MockClock::new(FrameRate::new(30, 1));
clock.play();
// 10 s at 30 fps = 300 frames into a 5-frame sequence: wrapped 60×.
clock.started = Some((Instant::now() - Duration::from_secs(10), Frame(0)));
@@ -2953,8 +2953,8 @@ mod tests {
/// Without stop-on-last the tick wraps modulo the sequence length.
#[test]
fn clock_tick_loops_when_not_stopping() {
use std::time::{Duration, Instant};
let mut clock = MockClock::new(FrameRate::new(30, 1));
use std::time::{Duration, Instant};
let mut clock = MockClock::new(FrameRate::new(30, 1));
clock.play();
clock.started = Some((Instant::now() - Duration::from_secs(10), Frame(0)));
+1 -1
View File
@@ -29,7 +29,7 @@
//! * [`real`] — [`RealEngine`](real::RealEngine) and
//! [`RealClock`](real::RealClock), the real engine. M14 R3: it calls the
//! oak* module crates' Rust APIs directly (oaknode / oaktimeline /
//! oakundo / oakrender / oaktask / oakcodec / oakaudio / oakcommon /
//! oakundo / oakrender / oaktask / oakcodec / oakaudio / oak_core /
//! oakstorage — no `liboakengine` dylib, no C ABI) behind the same
//! [`EngineGateway`](engine::EngineGateway) seam the mock implements.
//! * [`graphops`] / [`effectchain`] / [`renderops`] — the app's assembly
+55 -55
View File
@@ -44,7 +44,7 @@
//! listener and cancel atom.
//! * **Config** — the preferences (renderer backend, language, theme,
//! cache dir, proxy policy, snapshot interval, default transition,
//! audio devices) round-trip through the oakcommon config store; the
//! audio devices) round-trip through the oak_core config store; the
//! audio device selection additionally applies live through oakaudio's
//! manager.
//! * **Storage** — the write-through library binds every opened project
@@ -66,12 +66,12 @@ use std::sync::{Arc, Mutex};
use std::time::Instant;
use gpui::effect_stack::{
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
EffectCardKind, EffectData, EffectId, EffectStackDataSource, EffectStackEvent,
};
use gpui::node_graph::{NodeGraphDataSource, NodeGraphEvent};
use gpui::timeline::{
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
TrackData, TrackHeaderEvent, TrackKind, TrimEdge,
ClipData, ClipId, Frame, FrameRange, FrameRate, Marker, TimelineDataSource, TimelineEvent,
TrackData, TrackHeaderEvent, TrackKind, TrimEdge,
};
use gpui::{prelude::*, px, App, Context, Entity, Hsla, Pixels, RenderImage, SharedString};
use gpui_widgets::audio_meter::AudioMeterDataSource;
@@ -86,8 +86,8 @@ use oak_timeline::handle::CHandle;
use oak_timeline::util::NodeRef;
use super::engine::{
AppEngine, EngineGateway, ExportSession, LibraryProject, Monitor, MulticamState, Project,
ScopeData, Sequence, SequenceParameters, VideoFormat, WizardFootage, WizardSyncOffset,
AppEngine, EngineGateway, ExportSession, LibraryProject, Monitor, MulticamState, Project,
ScopeData, Sequence, SequenceParameters, VideoFormat, WizardFootage, WizardSyncOffset,
};
use super::frames::{bgra_bytes_to_render_image, f32_rgba_to_bgra_image, synthetic_frame_samples};
use super::graphops::{self, ProjectRef};
@@ -714,10 +714,10 @@ fn rendered_to_owned_image(rendered: &super::renderops::RenderedFrame) -> Option
/// The app-side output node for F32 frames (working colorspace → the
/// project's output colorspace); pass-through in the legacy working space.
fn apply_output_node_f32(samples: &mut [f32]) {
oak_common::colormath::working_to_display_target(
samples,
oak_render::color::pipeline_working_space(),
oak_render::color::pipeline_output_spec(),
oak_core::colormath::working_to_display_target(
samples,
oak_core::color::pipeline_working_space(),
oak_core::color::pipeline_output_spec(),
);
}
@@ -2378,8 +2378,8 @@ impl RealEngine {
/// mapped onto the widget's badge enum; folders and footage without
/// proxy state get none.
fn proxy_badge_of(&self, id: u64) -> Option<gpui_widgets::project_explorer::ProxyBadge> {
use gpui_widgets::project_explorer::ProxyBadge;
let project = self.project.as_ref()?;
use gpui_widgets::project_explorer::ProxyBadge;
let project = self.project.as_ref()?;
let node = graphops::id_of(id)?;
let guard = graphops::lock(project);
let f = graphops::footage_behavior(&guard.graph, node)?;
@@ -2547,7 +2547,7 @@ impl RealEngine {
fn proxy_cache_path() -> String {
let configured = config_get_string(CONFIG_KEY_DISK_CACHE_PATH);
if configured.trim().is_empty() {
oak_common::filefunctions::default_disk_cache_path()
oak_core::filefunctions::default_disk_cache_path()
} else {
configured
}
@@ -2706,8 +2706,8 @@ impl RealEngine {
f: &oak_node::footage::FootageBehavior,
node: NodeId,
) -> super::engine::ProxyMediaState {
use super::engine::ProxyMediaState;
if self.proxy_runs.iter().any(|run| run.footage == node) {
use super::engine::ProxyMediaState;
if self.proxy_runs.iter().any(|run| run.footage == node) {
return ProxyMediaState::Generating;
}
if f.proxy.is_empty() {
@@ -2733,9 +2733,9 @@ impl RealEngine {
/// every clip is re-placed so its source head lines up with the
/// reference's at the anchor (one multi-undo).
fn sync_clips_by_source_time_internal(&mut self, clips: &[ClipId]) {
use oak_audio::synchronizer::{place_by_source_time, SourceClip};
use oak_audio::synchronizer::{place_by_source_time, SourceClip};
let Some(project) = self.project.clone() else {
let Some(project) = self.project.clone() else {
return;
};
@@ -2857,12 +2857,12 @@ impl RealEngine {
/// offset triggers a rate search whose winner also rescales the clip
/// speed (one multi-undo).
fn sync_clips_by_waveform_internal(&mut self, clips: &[ClipId], allow_speed: bool) {
use oak_audio::synchronizer::place_by_waveform_offset;
use oak_audio::waveformsync::{
estimate_envelope_offset_valid, estimate_stretch_and_offset,
};
use oak_audio::synchronizer::place_by_waveform_offset;
use oak_audio::waveformsync::{
estimate_envelope_offset_valid, estimate_stretch_and_offset,
};
let Some(cache) = self.waveform_cache() else {
let Some(cache) = self.waveform_cache() else {
return;
};
let Some(project) = self.project.clone() else {
@@ -3157,7 +3157,7 @@ impl RealEngine {
// the render workers pick them up at graph-load time.
{
let guard = graphops::lock(&project);
oak_render::color::set_pipeline_color_settings(
oak_core::color::set_pipeline_color_settings(
guard.working_color_space(),
guard.output_color_spec(),
);
@@ -3263,12 +3263,12 @@ impl RealEngine {
.cloned()
});
let applied = match stored.as_deref() {
None => oak_render::color::set_up_default_config(),
Some(path) => oak_render::color::set_up_default_config_from(Some(path)),
None => oak_core::color::set_up_default_config(),
Some(path) => oak_core::color::set_up_default_config_from(Some(path)),
};
if let Err(e) = applied {
println!("[real engine] project OCIO config apply failed: {e}");
let _ = oak_render::color::set_up_default_config();
let _ = oak_core::color::set_up_default_config();
}
super::displaycolor::invalidate();
}
@@ -5422,7 +5422,7 @@ impl AppEngine for RealEngine {
}
fn set_use_proxy_media(&mut self, enabled: bool, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
CONFIG_KEY_USE_PROXY,
if enabled { "true" } else { "false" },
@@ -5436,7 +5436,7 @@ impl AppEngine for RealEngine {
/// Resolution ▸` menu): the preview geometry changes, so every cached
/// and in-flight preview frame is stale.
fn set_playback_divider(&mut self, divider: i64, cx: &mut Context<Self>) {
oak_common::configstore::ConfigStore::instance().set(
oak_core::configstore::ConfigStore::instance().set(
None,
"PlaybackDivider",
&divider.clamp(1, 8).to_string(),
@@ -5471,9 +5471,9 @@ impl AppEngine for RealEngine {
// refuses an invalid config the same way). Applying is the
// process-wide color config reload plus a full frame invalidation.
if trimmed.is_empty() {
oak_render::color::set_up_default_config().map_err(|e| e.to_string())?;
oak_core::color::set_up_default_config().map_err(|e| e.to_string())?;
} else {
oak_render::color::set_up_default_config_from(Some(&trimmed))
oak_core::color::set_up_default_config_from(Some(&trimmed))
.map_err(|e| e.to_string())?;
}
{
@@ -5531,9 +5531,9 @@ impl AppEngine for RealEngine {
fn project_color_settings(&self) -> (String, String, String) {
let Some(project) = self.project_ref() else {
return (
oak_common::colormath::WorkingColorSpace::default().as_setting().to_string(),
oak_common::colormath::OutputGamut::default().as_setting().to_string(),
oak_common::colormath::OutputTransfer::default().as_setting().to_string(),
oak_core::colormath::WorkingColorSpace::default().as_setting().to_string(),
oak_core::colormath::OutputGamut::default().as_setting().to_string(),
oak_core::colormath::OutputTransfer::default().as_setting().to_string(),
);
};
let guard = graphops::lock(project);
@@ -5547,16 +5547,16 @@ impl AppEngine for RealEngine {
};
(
get(
oak_node::project::SETTING_WORKING_COLOR_SPACE,
oak_common::colormath::WorkingColorSpace::default().as_setting(),
oak_node::project::SETTING_WORKING_COLOR_SPACE,
oak_core::colormath::WorkingColorSpace::default().as_setting(),
),
get(
oak_node::project::SETTING_OUTPUT_GAMUT,
oak_common::colormath::OutputGamut::default().as_setting(),
oak_node::project::SETTING_OUTPUT_GAMUT,
oak_core::colormath::OutputGamut::default().as_setting(),
),
get(
oak_node::project::SETTING_OUTPUT_TRANSFER,
oak_common::colormath::OutputTransfer::default().as_setting(),
oak_node::project::SETTING_OUTPUT_TRANSFER,
oak_core::colormath::OutputTransfer::default().as_setting(),
),
)
}
@@ -5572,8 +5572,8 @@ impl AppEngine for RealEngine {
return;
};
// Normalize through the parsers so only canonical values persist.
let working = oak_common::colormath::WorkingColorSpace::from_setting(&working);
let spec = oak_common::colormath::OutputColorSpec::from_settings(&gamut, &transfer);
let working = oak_core::colormath::WorkingColorSpace::from_setting(&working);
let spec = oak_core::colormath::OutputColorSpec::from_settings(&gamut, &transfer);
{
let mut guard = graphops::lock(&project);
guard.settings.insert(
@@ -5592,7 +5592,7 @@ impl AppEngine for RealEngine {
}
// The app-side transforms read the process global; the workers pick
// the new settings up with the next graph upload.
oak_render::color::set_pipeline_color_settings(working, spec);
oak_core::color::set_pipeline_color_settings(working, spec);
// The workers derive their pipeline colors from the uploaded project
// snapshot; a settings change alone does not bump the undo-stack
// revision (the manager dedups re-uploads on it), so push an explicit
@@ -7124,7 +7124,7 @@ pub fn encoding_formats() -> Vec<(i32, String, String)> {
pub const EXPORT_FORMAT_MP4: i32 = 2;
// ---------------------------------------------------------------------------
// Config (preferences) — the oakcommon config store directly
// Config (preferences) — the oak_core config store directly
// ---------------------------------------------------------------------------
/// The config key selecting the renderer backend (worker `create_renderer`
@@ -7134,7 +7134,7 @@ pub const CONFIG_KEY_RENDERER_BACKEND: &str = "GraphicsBackend";
/// defaults to dark when the key is absent).
pub const CONFIG_KEY_THEME: &str = "Theme";
/// The config key overriding the disk cache directory (empty = the
/// platform default `<config dir>/mediacache`; honored by oakcommon's
/// platform default `<config dir>/mediacache`; honored by oak_core's
/// `default_disk_cache_path`, so oakrender/oaknode caches follow it).
pub const CONFIG_KEY_DISK_CACHE_PATH: &str = "DiskCachePath";
/// The config key toggling proxy media use (`UseProxyMedia`, bool).
@@ -7169,8 +7169,8 @@ pub const DEFAULT_SNAPSHOT_INTERVAL_SEC: i64 = 600;
pub const DEFAULT_TRANSITION_SEC: &str = "0.5";
/// The process-wide config store.
fn config_store() -> &'static oak_common::configstore::ConfigStore {
oak_common::configstore::ConfigStore::instance()
fn config_store() -> &'static oak_core::configstore::ConfigStore {
oak_core::configstore::ConfigStore::instance()
}
/// Loads the persisted configuration from disk (once at startup, before
@@ -7358,11 +7358,11 @@ pub fn library_list() -> Result<Vec<LibraryProject>, String> {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::mpsc as std_mpsc;
use std::time::Duration;
use super::*;
use std::sync::mpsc as std_mpsc;
use std::time::Duration;
/// Serializes the media/FFmpeg-heavy tests (the codec library is not
/// Serializes the media/FFmpeg-heavy tests (the codec library is not
/// thread-safe against concurrent decode sessions) and shares the
/// process-global undo stack with the other app test modules.
fn media_lock() -> std::sync::MutexGuard<'static, ()> {
@@ -7740,11 +7740,11 @@ mod tests {
fn process_backend_preview_path_is_zero_copy() {
let _media = media_lock();
let _worker = WorkerBinGuard::set();
use oak_render::manager::{RenderBackendChoice, RenderManager};
use oak_render::procpool::{
main_heap_frame_copies, reset_main_heap_frame_copies, DispatcherConfig,
};
RenderManager::shutdown();
use oak_render::manager::{RenderBackendChoice, RenderManager};
use oak_render::procpool::{
main_heap_frame_copies, reset_main_heap_frame_copies, DispatcherConfig,
};
RenderManager::shutdown();
let config = DispatcherConfig {
worker_bin: Some(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+12 -12
View File
@@ -29,12 +29,12 @@
use std::sync::mpsc;
use gpui::RenderImage;
use oak_core::texture::Texture;
use oak_core::{PixelFormat, Rational, TimeRange};
use oak_node::id::NodeId;
use oak_node::track::TrackType;
use oak_render::manager::RenderManager;
use oak_render::procpool::ShmFrameRef;
use oak_render::texture::Texture;
use oak_render::ticket::{AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams};
use super::engine::{ExportEvent, ExportSession};
@@ -84,7 +84,7 @@ pub fn ensure_render_manager() -> bool {
/// `UseProxyMedia` config switch (C++ `Tools > Use Proxy Media`; the
/// export path never consults it — exports always decode the original).
pub fn use_proxy_media() -> bool {
oak_common::configstore::ConfigStore::instance().get_bool(None, "UseProxyMedia", 1) != 0
oak_core::configstore::ConfigStore::instance().get_bool(None, "UseProxyMedia", 1) != 0
}
/// The preview media of a footage node with the three-level proxy switch
@@ -606,10 +606,10 @@ impl RenderedFrame {
/// the project's output colorspace, in place on tightly packed samples.
/// Pass-through in the legacy sRGB working space.
fn apply_output_node_f32(samples: &mut [f32]) {
oak_common::colormath::working_to_display_target(
samples,
oak_render::color::pipeline_working_space(),
oak_render::color::pipeline_output_spec(),
oak_core::colormath::working_to_display_target(
samples,
oak_core::color::pipeline_working_space(),
oak_core::color::pipeline_output_spec(),
);
}
@@ -1437,9 +1437,9 @@ mod tests {
// pixels; disabling restores them), not the color pipeline. Pin the
// legacy sRGB pass-through so the pixel-value assertions hold
// regardless of the ACEScg default.
oak_render::color::set_pipeline_color_settings(
oak_common::colormath::WorkingColorSpace::SrgbLegacy,
oak_common::colormath::OutputColorSpec::default(),
oak_core::color::set_pipeline_color_settings(
oak_core::colormath::WorkingColorSpace::SrgbLegacy,
oak_core::colormath::OutputColorSpec::default(),
);
oak_undo::global::clear().unwrap();
let media =
@@ -1697,7 +1697,7 @@ mod tests {
let gdata;
let goff;
{
let oak_render::texture::Texture::Cpu(ref gf) = &graph_frame else {
let oak_core::texture::Texture::Cpu(ref gf) = &graph_frame else {
panic!("graph render produced a non-CPU frame");
};
grow = gf.linesize_bytes();
@@ -1805,7 +1805,7 @@ mod tests {
oak_core::PixelFormat::F32,
)
.expect("graph render");
let oak_render::texture::Texture::Cpu(ref gf) = texture else {
let oak_core::texture::Texture::Cpu(ref gf) = texture else {
panic!("non-CPU frame");
};
let stride = gf.linesize_bytes();
@@ -2103,7 +2103,7 @@ mod tests {
// Force the global proxy switch on and restore it afterwards (the
// config store is process-global; the serialization lock held
// above is the same one the other app test modules use).
let store = oak_common::configstore::ConfigStore::instance();
let store = oak_core::configstore::ConfigStore::instance();
let old = store.get(None, "UseProxyMedia").unwrap_or_default();
store.set_bool(None, "UseProxyMedia", 1);
+2 -2
View File
@@ -78,8 +78,8 @@
| C++ | Rust 落点 |
|---|---|
| `output_buffer_size()` | `config::output_buffer_size``oakcommon_config_get_int(nullptr,"AudioOutputBufferSize",0)` |
| `device_name(is_output_device)` | `config::device_name``oakcommon_config_get` 两阶段;key = "AudioOutput"/"AudioInput" |
| `output_buffer_size()` | `config::output_buffer_size``oak_core_config_get_int(nullptr,"AudioOutputBufferSize",0)` |
| `device_name(is_output_device)` | `config::device_name``oak_core_config_get` 两阶段;key = "AudioOutput"/"AudioInput" |
## 9. 刻意不迁移(drop
+2 -2
View File
@@ -305,7 +305,7 @@ name = "oakaudio"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oak_core",
"oakcore-rs",
]
@@ -318,7 +318,7 @@ dependencies = [
]
[[package]]
name = "oakcommon"
name = "oak_core"
version = "0.1.0"
dependencies = [
"image",
-1
View File
@@ -11,7 +11,6 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
oak-ffmpeg-link = { path = "../oak-ffmpeg-link" }
oak-core = { path = "../oak-core" }
oak-common = { path = "../oak-common" }
oak-codec = { path = "../oak-codec" }
# Real resample/channel-convert/time-stretch filter graph. The C++
# ffmpeg_bridge library existed only to absorb FFmpeg API churn; the Rust
+2 -2
View File
@@ -47,8 +47,8 @@ functions) — frozen, implemented verbatim by `src/ffi.rs`.
oakcodec encoder C ABI (`bridge::codec`) and waveform extraction
decodes through the oakcodec decoder C ABI — exactly as the C++
does. No direct ffmpeg_bridge use in the record path.
7. **Config via oakcommon.** Device names and the output buffer size
read through `bridge::common` (`oakcommon_config_*`), preserving the
7. **Config via oak_core.** Device names and the output buffer size
read through `bridge::common` (`oak_core_config_*`), preserving the
`audio_config` namespace semantics as a `config.rs` free-function
module.
+3 -3
View File
@@ -15,12 +15,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! The `audio_config` namespace from `src/audio/src/configbridge.*`:
//! audio-specific configuration read through the oakcommon C ABI.
//! audio-specific configuration read through the oak_core C ABI.
use std::error::Error;
use std::ffi::CString;
use std::str::FromStr;
use oak_common::configstore::*;
use oak_core::configstore::*;
/// PortAudio output buffer size in frames; 0 = let PortAudio choose.
///
/// `// CPP-PARITY: src/audio/src/configbridge.cpp:30`
@@ -44,7 +44,7 @@ pub fn device_name(is_output_device: bool) -> Result<String, Box<dyn Error>> {
let store = ConfigStore::instance();
let size = i32::from_str(store.get(None, key)?.as_str())?;
if size <= 1 {
// Absent (OAKCOMMON_E_NOT_FOUND) or empty
// Absent (oak_core_E_NOT_FOUND) or empty
return Err(Box::new(crate::error::Error::NotFound));
}
let mut buf = vec![0u8; size as usize];
+1 -1
View File
@@ -22,7 +22,7 @@
//! `include/audio/manager.h`). An empty handle reports `OAKAUDIO_E_STATE`.
//!
//! Recording goes through the oakcodec encoder C ABI ([`crate::bridge`]);
//! device/config lookups go through oakcommon.
//! device/config lookups go through oak_core.
use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, Ordering};
+1 -1
View File
@@ -77,7 +77,7 @@ impl Default for ProcessorInner {
}
/// Map an oakcore [`SampleFormat`] to the equivalent ffmpeg [`Sample`].
/// Replaces `FFmpegUtils::get_ffmpeg_sample_format` crossing the oakcommon
/// Replaces `FFmpegUtils::get_ffmpeg_sample_format` crossing the oak_core
/// C ABI (`// CPP-PARITY: src/common/src/ffmpegutils.cpp:83`).
fn to_ffmpeg_sample_format(fmt: SampleFormat) -> Sample {
match fmt {
+2 -2
View File
@@ -879,7 +879,7 @@ dependencies = [
]
[[package]]
name = "oakcommon"
name = "oak_core"
version = "0.1.0"
dependencies = [
"image",
@@ -900,7 +900,7 @@ dependencies = [
"libc",
"oakaudio",
"oakcodec",
"oakcommon",
"oak_core",
"oakcore-rs",
"oaknode",
"oakplugin",
+1 -2
View File
@@ -32,9 +32,8 @@ clap = { version = "4", features = ["derive"] }
# direct Rust call into the oak* rlibs (oaknode for projects/footage,
# oaktimeline for the track/clip commands, oakrender for the ticket arena,
# oakcodec for the export formats/codecs, oaktask for the export task,
# oakcommon/oakcore-rs for the shared value types). No liboakengine dylib,
# oak_core/oakcore-rs for the shared value types). No liboakengine dylib,
# no C ABI, no build.rs link step, no host shims.
oak-common = { path = "../oak-common" }
oak-core = { path = "../oak-core" }
oak-node = { path = "../oak-node" }
oak-timeline = { path = "../oak-timeline" }
+1 -1
View File
@@ -20,7 +20,7 @@ cargo test # unit + integration tests
The crate is **self-contained** (M14 R2): it links the oak* module rlibs
directly (`oaknode`, `oaktimeline`, `oakcodec`, `oakrender`, `oaktask`,
`oakcommon`) — no `liboakengine` dylib, no C ABI, no build.rs link step.
`oak_core`) — no `liboakengine` dylib, no C ABI, no build.rs link step.
`cargo test -p oak-cli` stands alone.
## Subcommands
+6 -6
View File
@@ -17,7 +17,7 @@
//! Module-native engine helpers (M14 R2).
//!
//! oak-cli links the oak* module rlibs directly (oaknode / oaktimeline /
//! oakcodec / oakrender / oaktask / oakcommon) instead of the built
//! oakcodec / oakrender / oaktask / oak_core) instead of the built
//! liboakengine dylib's C ABI. This module is the CLI's own assembly
//! layer: it reproduces the facade operations the subcommands need —
//! project load/create, footage probe, sequence + clip assembly, montage
@@ -43,14 +43,14 @@ use oak_node::project::Project;
use oak_node::sequence::SequenceBehavior;
use oak_node::track::{TrackBehavior, TrackListBehavior, TrackType};
use oak_node::value::VideoParams;
use oak_timeline::undogeneral::TimelineAddTrackCommand;
use oak_timeline::undopointer::TrackPlaceBlockCommand;
use oak_timeline::util::NodeRef;
use oak_render::manager::RenderManager;
use oak_render::procpool::bgra8_to_rgba8;
use oak_render::ticket::{
AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams,
AudioTicketParams, MontageClip, TicketPayload, VideoTicketParams,
};
use oak_timeline::undogeneral::TimelineAddTrackCommand;
use oak_timeline::undopointer::TrackPlaceBlockCommand;
use oak_timeline::util::NodeRef;
/// The shared project reference (the modules' domain project handle).
pub type ProjectRef = Arc<Mutex<Project>>;
@@ -663,7 +663,7 @@ pub fn render_frame(
m.tickets.wait(id).map_err(|e| e.to_string())?;
let result = m.tickets.result(id).ok_or_else(|| "render ticket produced no result".to_string())?;
match &result {
Ok(TicketPayload::Video(oak_render::texture::Texture::Cpu(frame))) => {
Ok(TicketPayload::Video(oak_core::texture::Texture::Cpu(frame))) => {
Ok(RenderedFrame {
width: frame.width,
height: frame.height,
-1
View File
@@ -9,7 +9,6 @@ license = "GPL-3.0-or-later"
crate-type = ["staticlib", "rlib"]
[dependencies]
oak-common = { path = "../oak-common" }
oak-ffmpeg-link = { path = "../oak-ffmpeg-link" }
oak-core = { path = "../oak-core" }
# Real media decode/encode. The C++ ffmpeg_bridge library existed only to
+4 -4
View File
@@ -42,15 +42,15 @@ and never blocks.
virtual chain.
3. **`Frame` owns its params by value.** `olive::Frame` wraps an
`OakVideoParams` handle plus a `Vec<u8>` pixel buffer. In Rust the
params are held as an `oakcommon::videoparams::VideoParams` value
(single-lib unification dropped the refcounted oakcommon handle);
params are held as an `oak_core::videoparams::VideoParams` value
(single-lib unification dropped the refcounted oak_core handle);
the buffer is a plain `Vec<u8>`.
4. **No adapter layer.** Codec calls the other module crates directly
(`oakcommon`, `oakcore-rs`, `oakffmpeg-link`), keeping the 2026-08
(`oak_core`, `oakcore-rs`, `oakffmpeg-link`), keeping the 2026-08
decision recorded in NOTES.md §6. Only genuinely repeated
conversions survive as small module-local helpers.
5. **XML stays on the C++ side.** `EncodingParams::load/save` use
oakcommon's C++ `XmlStreamWriter/Reader` classes
oak_core's C++ `XmlStreamWriter/Reader` classes
(`src/common/src/xmlutils.h`), exactly as oaknode/oakrender do —
the one C++-to-C++ coupling the bridge cannot cover (NOTES.md §7).
6. **Threading.** `FrameManager` keeps its background GC thread behind
+2 -2
View File
@@ -22,7 +22,7 @@
//! `Unavailable`. Deterministic per-channel filenames derive from the
//! source + target audio params.
use oak_common::filefunctions::FileFunctions;
use oak_core::filefunctions::FileFunctions;
use std::path::Path;
/// Conform state of one audio stream.
@@ -184,7 +184,7 @@ fn conform_filenames(
out
}
/// `oakcommon_filefunctions_get_unique_file_identifier` wrapper.
/// `oak_core_filefunctions_get_unique_file_identifier` wrapper.
fn unique_file_identifier(filename: &str) -> String {
FileFunctions::new()
.get_unique_file_identifier(filename)
+2 -2
View File
@@ -26,7 +26,7 @@
use std::path::Path;
use std::sync::{Arc, Mutex, OnceLock};
use oak_common::cancelatom::CancelAtom;
use oak_core::cancelatom::CancelAtom;
use oak_core::{Rational, TimeRange};
use crate::footagedescription::FootageDescription;
@@ -113,7 +113,7 @@ pub enum RenderMode {
/// "Don't force a color range" sentinel for
/// [`RetrieveVideoParams::force_range`] (the actual ranges are the
/// `OAKCOMMON_COLOR_RANGE_*` values).
/// `oak_core_COLOR_RANGE_*` values).
pub const K_COLOR_RANGE_DEFAULT: i32 = -1;
/// `Decoder::RetrieveVideoParams` — what a video retrieve call needs.
+3 -3
View File
@@ -269,10 +269,10 @@ impl EncodingParams {
}
}
/// Load from a compact XML preset string (oakcommon C++ XmlStreamReader).
/// Load from a compact XML preset string (oak_core C++ XmlStreamReader).
///
/// # CPP-PARITY
/// `EncodingParams::load` — uses oakcommon's C++ `XmlStreamReader`
/// `EncodingParams::load` — uses oak_core's C++ `XmlStreamReader`
/// (`src/common/src/xmlutils.h`), a C++-to-C++ coupling the bridge
/// cannot cover (NOTES.md §7). Preserves the load_v1 bug of not
/// assigning `custom_range`.
@@ -524,7 +524,7 @@ impl EncodingParams {
// ---------------------------------------------------------------------------
// Minimal XML helpers for the round-trip `load`/`save_to_string`.
//
// CPP-PARITY: the C++ `load`/`save_to_string` go through oakcommon's
// CPP-PARITY: the C++ `load`/`save_to_string` go through oak_core's
// `XmlStreamReader`/`XmlStreamWriter` (a C++-to-C++ coupling the Rust bridge
// cannot cover, NOTES.md §7). Rather than returning `Err`, this port keeps a
// minimal but faithful round-trip for the fields representable without the
+17 -17
View File
@@ -56,10 +56,10 @@ use ffmpeg::software::{resampling, scaling};
use ffmpeg::{ChannelLayout, Dictionary, Error as FfmpegError, Rational as FfRational};
use ffmpeg_next as ffmpeg;
use oak_common::cancelatom::CancelAtom;
use oak_common::colormath::YuvMatrix;
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_common::videoparams::{Interlacing, VideoParams, VideoType};
use oak_core::cancelatom::CancelAtom;
use oak_core::colormath::YuvMatrix;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::videoparams::{Interlacing, VideoParams, VideoType};
use oak_core::{PixelFormat, Rational, SampleFormat, TimeRange};
use crate::audioparams::AudioParams;
@@ -69,10 +69,10 @@ use crate::encodingparams::EncodingParams;
use crate::footagedescription::{FootageDescription, StreamEntry};
use crate::frame::Frame;
/// `OAKCOMMON_COLOR_RANGE_FULL`.
const OAKCOMMON_COLOR_RANGE_FULL: i32 = 1;
/// `OAKCOMMON_COLOR_RANGE_LIMITED`.
const OAKCOMMON_COLOR_RANGE_LIMITED: i32 = 0;
/// `oak_core_COLOR_RANGE_FULL`.
const oak_core_COLOR_RANGE_FULL: i32 = 1;
/// `oak_core_COLOR_RANGE_LIMITED`.
const oak_core_COLOR_RANGE_LIMITED: i32 = 0;
/// `AVCOL_RANGE_JPEG` (full range; AVCOL_RANGE_MPEG = 1 is limited).
const AVCOL_RANGE_JPEG: i32 = 2;
/// swscale colorspace ids (`SWS_CS_*`, libswscale/swscale.h).
@@ -372,9 +372,9 @@ impl Decoder for FFmpegDecoder {
params.set_color_primaries(color_meta.color_primaries);
params.set_color_transfer(color_meta.color_trc);
params.set_color_range(if color_meta.full_range {
oak_common::videoparams::ColorRange::Full
oak_core::videoparams::ColorRange::Full
} else {
oak_common::videoparams::ColorRange::Limited
oak_core::videoparams::ColorRange::Limited
});
}
Ok(Arc::new(frame))
@@ -1099,9 +1099,9 @@ impl DecoderState {
// frame's own metadata (YUVJ sources are full range). The old path
// forced MPEG/limited for everything, crushing full-range screen
// captures and JPEG-derived footage.
let full_range = if force_range == OAKCOMMON_COLOR_RANGE_FULL {
let full_range = if force_range == oak_core_COLOR_RANGE_FULL {
true
} else if force_range == OAKCOMMON_COLOR_RANGE_LIMITED {
} else if force_range == oak_core_COLOR_RANGE_LIMITED {
false
} else {
yuvj_full || raw_range == AVCOL_RANGE_JPEG
@@ -1949,7 +1949,7 @@ fn convert_rgba8_to_f32(data: &[u8], w: u32, h: u32, stride: usize) -> Vec<u8> {
/// colorspace tables and full ranges on both sides, so no matrix and no
/// range recompression was applied. 10/12-bit sources arrive left-shifted
/// to 16-bit (code << 6 / code << 4) — exactly the code-value scale
/// [`oak_common::colormath::yuv444p16_to_rgb_f32`] expects.
/// [`oak_core::colormath::yuv444p16_to_rgb_f32`] expects.
fn convert_yuv444p16_to_rgba_f32(
out: &ffmpeg::frame::Video,
w: u32,
@@ -1958,7 +1958,7 @@ fn convert_yuv444p16_to_rgba_f32(
full_range: bool,
) -> Vec<u8> {
let mut rgba = vec![0.0f32; (w as usize) * (h as usize) * 4];
oak_common::colormath::yuv444p16_to_rgb_f32(
oak_core::colormath::yuv444p16_to_rgb_f32(
out.data(0),
out.stride(0),
out.data(1),
@@ -2111,9 +2111,9 @@ fn probe_file(filename: &str, cancelled: Option<&CancelAtom>) -> Option<FootageD
vp.set_color_primaries((*raw).color_primaries as i32);
vp.set_color_transfer((*raw).color_trc as i32);
vp.set_color_range(if (*raw).color_range as i32 == AVCOL_RANGE_JPEG {
oak_common::videoparams::ColorRange::Full
oak_core::videoparams::ColorRange::Full
} else {
oak_common::videoparams::ColorRange::Limited
oak_core::videoparams::ColorRange::Limited
});
}
desc.push_stream(StreamEntry::Video(vp));
@@ -3165,7 +3165,7 @@ mod tests {
#[test]
fn yuv_matrix_mapping_is_strict() {
use oak_common::colormath::YuvMatrix;
use oak_core::colormath::YuvMatrix;
assert_eq!(yuv_matrix_for(AVCOL_SPC_BT709, 1920, 1080), YuvMatrix::Bt709);
assert_eq!(yuv_matrix_for(AVCOL_SPC_BT470BG, 640, 480), YuvMatrix::Bt601);
assert_eq!(yuv_matrix_for(AVCOL_SPC_SMPTE170M, 1920, 1080), YuvMatrix::Bt601);
+11 -11
View File
@@ -18,13 +18,13 @@
//!
//! Mirrors `src/codec/src/footagedescription.h`. A value type describing
//! the streams a `Decoder::probe()` found in a file. Video and subtitle
//! streams are stored as oakcommon by-value handles; audio streams as
//! streams are stored as oak_core by-value handles; audio streams as
//! `oak_core::TimeRangeList`/raw audio params. The original's
//! `Track::Type` mapping and XML load/save are intentionally not reproduced
//! (NOTES.md §4) — use [`FootageDescription::stream_is_video`] etc.
use oak_common::subtitleparams::SubtitleParams;
use oak_common::videoparams::VideoParams;
use oak_core::subtitleparams::SubtitleParams;
use oak_core::videoparams::VideoParams;
use oak_core::{Rational, TimeRange};
use crate::audioparams::AudioParams;
@@ -214,14 +214,14 @@ mod tests {
fn video_params(index: i32) -> VideoParams {
let mut vp = VideoParams::new_basic(
1920,
1080,
oak_common::ocioutils::PixelFormat::from_code(0),
4,
1,
1,
0,
1,
1920,
1080,
oak_core::ocioutils::PixelFormat::from_code(0),
4,
1,
1,
0,
1,
);
vp.set_stream_index(index);
vp
+5 -5
View File
@@ -16,13 +16,13 @@
//! `olive::Frame` — a CPU pixel buffer plus a [`VideoParams`] value.
//!
//! Mirrors `src/codec/src/frame.h`. The params are held as an oakcommon
//! Mirrors `src/codec/src/frame.h`. The params are held as an oak_core
//! [`VideoParams`] value (single-lib unification; the former refcounted
//! oakcommon handle is gone, so copies are plain clones); the pixel data
//! oak_core handle is gone, so copies are plain clones); the pixel data
//! itself is a plain `Vec<u8>`. Line-size and pixel-format math lives
//! here.
use oak_common::videoparams::VideoParams;
use oak_core::videoparams::VideoParams;
use oak_core::{PixelFormat, Rational};
/// Number of channels in the internal RGBA pipeline layout
@@ -331,7 +331,7 @@ impl Frame {
#[cfg(test)]
mod tests {
use super::*;
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
fn frame(w: i32, h: i32) -> Frame {
let params = VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1);
@@ -422,7 +422,7 @@ mod tests {
#[cfg(test)]
mod tests_extra {
use super::*;
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
fn frame(w: i32, h: i32) -> Frame {
let params = VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1);
+2 -2
View File
@@ -27,7 +27,7 @@ use std::sync::{Arc, Mutex, OnceLock};
use std::thread;
use std::time::Duration;
use oak_common::videoparams::VideoParams;
use oak_core::videoparams::VideoParams;
use crate::frame::Frame;
/// `olive::FrameManager`: singleton frame pool with background GC.
@@ -148,7 +148,7 @@ fn frame_matches(frame: &Frame, params: &VideoParams) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
fn test_params(w: i32, h: i32) -> VideoParams {
VideoParams::new_basic(w, h, OakPixelFormat::from_code(0), 4, 1, 1, 0, 1)
+1 -1
View File
@@ -106,7 +106,7 @@ pub fn hardware_decoding_enabled() -> bool {
if let Ok(v) = std::env::var("OAK_HWACCEL") {
return v != "0";
}
match oak_common::configstore::ConfigStore::instance()
match oak_core::configstore::ConfigStore::instance()
.get(None, CONFIG_KEY_HARDWARE_DECODING)
{
Ok(value) => value != "false",
+10 -10
View File
@@ -18,7 +18,7 @@
//!
//! Mirrors `src/codec/src/oiio/{oiiodecoder,oiioencoder}.{h,cpp}`. OIIO
//! frame conversion goes through the local
//! [`crate::oiioframebridge`] helpers plus oakcommon's OIIO mapping
//! [`crate::oiioframebridge`] helpers plus oak_core's OIIO mapping
//! functions.
//!
//! The OIIO dylib (`liboakoiio`) is not linked into this build, so every
@@ -58,9 +58,9 @@ impl Decoder for OIIODecoder {
}
fn probe(
&self,
_filename: &str,
_cancelled: Option<&oak_common::cancelatom::CancelAtom>,
&self,
_filename: &str,
_cancelled: Option<&oak_core::cancelatom::CancelAtom>,
) -> Option<crate::footagedescription::FootageDescription> {
// Probing is a dylib operation; without it we cannot report anything.
None
@@ -113,12 +113,12 @@ impl Decoder for OIIODecoder {
}
fn conform_audio(
&self,
_output_filenames: &[String],
_sample_rate: i32,
_channel_layout: u64,
_sample_format: i32,
_cancelled: Option<&oak_common::cancelatom::CancelAtom>,
&self,
_output_filenames: &[String],
_sample_rate: i32,
_channel_layout: u64,
_sample_format: i32,
_cancelled: Option<&oak_core::cancelatom::CancelAtom>,
) -> crate::error::Result<()> {
Err(crate::error::Error::Failed(Self::NOT_AVAILABLE.to_string()))
}
+4 -4
View File
@@ -18,8 +18,8 @@
//! frame <-> pixel-buffer conversion.
//!
//! Mirrors `src/codec/src/oiioframebridge.{h,cpp}`. These are internal C++
//! functions that moved into codec from oakcommon (NOTES.md §oakcommon侧修复);
//! oakcommon keeps its OIIO mapping functions; the frame conversion itself
//! functions that moved into codec from oak_core (NOTES.md §oak_core侧修复);
//! oak_core keeps its OIIO mapping functions; the frame conversion itself
//! lives here.
//!
//! The C++ bridge copies pixels through the live OpenImageIO `ImageBuf`
@@ -30,8 +30,8 @@
//! timestamp and time base alongside the raw pixel rows, so a buffer can be
//! turned back into an equivalent [`Frame`] without any external state.
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_common::videoparams::VideoParams;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::videoparams::VideoParams;
use crate::frame::Frame;
use oak_core::Rational;
use std::ffi::c_int;
+10 -10
View File
@@ -19,12 +19,12 @@
//! Mirrors `src/codec/src/proxymanager.h`. Stateless (NOTES.md): actual
//! transcodes are delegated to the global task submit callback
//! ([`crate::task`]); with no registrar, `get_or_start` reports the proxy
//! as missing. `proxy_params_from_config` reads the oakcommon config store
//! as missing. `proxy_params_from_config` reads the oak_core config store
//! with the compiled-in defaults as fallback (1280x720 / divider 1 / crf 23
//! / "mp4" / "veryfast" / audio included).
use oak_common::configstore::ConfigStore;
use oak_common::filefunctions::FileFunctions;
use oak_core::configstore::ConfigStore;
use oak_core::filefunctions::FileFunctions;
use std::path::Path;
/// Proxy state of a proxy file on disk.
@@ -127,13 +127,13 @@ impl ProxyManager {
ProxyParams::default()
}
/// Proxy parameters read from the oakcommon config, with the compiled-in
/// Proxy parameters read from the oak_core config, with the compiled-in
/// defaults as fallback.
///
/// # CPP-PARITY
/// `src/codec/src/proxymanager.h` `proxy_params_from_config` — reads
/// ProxyWidth/ProxyHeight/ProxyDivider/ProxyCRF/ProxyPreset/
/// ProxyIncludeAudio via `oakcommon_config_*`.
/// ProxyIncludeAudio via `oak_core_config_*`.
pub fn proxy_params_from_config() -> ProxyParams {
let mut p = ProxyParams::default();
p.width = config_get_int("ProxyWidth", p.width);
@@ -387,17 +387,17 @@ fn cstr_slice(a: &[u8; 32]) -> &str {
std::str::from_utf8(&a[..end]).unwrap_or("")
}
/// `oakcommon_config_get_int` wrapper (null group).
/// `oak_core_config_get_int` wrapper (null group).
fn config_get_int(key: &str, default: i32) -> i32 {
ConfigStore::instance().get_int(None, key, default)
}
/// `oakcommon_config_get_bool` wrapper (null group).
/// `oak_core_config_get_bool` wrapper (null group).
fn config_get_bool(key: &str, default: i32) -> i32 {
ConfigStore::instance().get_bool(None, key, default)
}
/// `oakcommon_config_get` string read; `None` when the stored value is
/// `oak_core_config_get` string read; `None` when the stored value is
/// empty or absent.
fn config_get_str(key: &str) -> Option<String> {
match ConfigStore::instance().get(None, key) {
@@ -406,14 +406,14 @@ fn config_get_str(key: &str) -> Option<String> {
}
}
/// `oakcommon_filefunctions_get_unique_file_identifier` wrapper.
/// `oak_core_filefunctions_get_unique_file_identifier` wrapper.
fn unique_file_identifier(filename: &str) -> String {
FileFunctions::new()
.get_unique_file_identifier(filename)
.unwrap_or_default()
}
/// `oakcommon_filefunctions_get_application_path` read.
/// `oak_core_filefunctions_get_application_path` read.
fn application_path() -> String {
FileFunctions::new()
.get_application_path()
+3 -3
View File
@@ -26,8 +26,8 @@
//! compiled without `#[cfg(test)]` and cannot resolve those symbols; see
//! `tests/ffi_contract_test.rs`).
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_common::videoparams::VideoParams;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::videoparams::VideoParams;
use crate::decoder::{
CodecStream, Decoder, RenderMode, RetrieveAudioStatus, RetrieveVideoParams,
K_COLOR_RANGE_DEFAULT,
@@ -503,7 +503,7 @@ static HW_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn hardware_decode_matches_software_decode() {
let _guard = HW_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let config = oak_common::configstore::ConfigStore::instance();
let config = oak_core::configstore::ConfigStore::instance();
let key = crate::hwdecode::CONFIG_KEY_HARDWARE_DECODING;
let decode_at = |time: i64| -> (Option<String>, Arc<Frame>) {
+2 -2
View File
@@ -37,8 +37,8 @@
use std::path::Path;
use oak_common::ocioutils::PixelFormat as OakPixelFormat;
use oak_common::videoparams::VideoParams;
use oak_core::ocioutils::PixelFormat as OakPixelFormat;
use oak_core::videoparams::VideoParams;
use oak_core::{PixelFormat, Rational, SampleFormat};
use crate::encodingparams::EncodingParams;
File diff suppressed because one or more lines are too long
-31
View File
@@ -1,31 +0,0 @@
# 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/>.
#
# `ocio-sys` (the FFI layer behind `ocio-rs`) does not probe the system for
# OpenColorIO on its own: with no configuration it builds a *stub* bridge
# whose calls all fail. These environment variables make it link the real
# Homebrew OpenColorIO dylib:
#
# OCIO_RS_ENABLE_REAL - opt into the real (non-stub) bridge
# OCIO_INSTALL_DIR - prefix whose include/ and lib/ hold OpenColorIO
# OCIO_RS_LINK - Homebrew ships a dylib, so link dynamically
#
# See README.md "Build & test" for the bundled alternative.
[env]
OCIO_RS_ENABLE_REAL = "1"
OCIO_INSTALL_DIR = "/opt/homebrew"
OCIO_RS_LINK = "dynamic"
-332
View File
@@ -1,332 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "fax"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"tiff",
]
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "oakcommon"
version = "0.1.0"
dependencies = [
"image",
"log",
"oakcore-rs",
"ocio-rs",
"quick-xml",
]
[[package]]
name = "oakcore-rs"
version = "0.1.0"
[[package]]
name = "ocio-rs"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3492534019b59e29dba06014f907dd12824537ed4d293d4108c4bfc669de7fd"
dependencies = [
"ocio-sys",
"thiserror",
]
[[package]]
name = "ocio-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63251d72d848de5eda39d59cd6490260cf031738ebd518ea37d76b5aae614ec"
dependencies = [
"cc",
"cmake",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiff"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
"fax",
"flate2",
"half",
"quick-error",
"weezl",
"zune-jpeg",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "zerocopy"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zune-core"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
-39
View File
@@ -1,39 +0,0 @@
[package]
name = "oak-common"
version.workspace = true
edition = "2021"
description = "Oak Video Editor shared utilities (Rust)"
license = "GPL-3.0-or-later"
[lib]
crate-type = ["staticlib", "rlib"]
[features]
# Compile the in-crate ffmpeg_bridge mock (fb_find_best_pix_fmt_of_list stub)
# so the integration tests can link without libffmpeg_bridge. Without this
# flag, `cargo test --lib` still works (the stub is active under `cfg(test)`),
# but the C ABI wrapper tests in tests/ffi_ffmpegutils.rs need it. This
# mirrors the `test-stubs` convention of oakplugin / oaktimeline.
test-stubs = []
[dependencies]
oak-core = { path = "../oak-core" }
quick-xml = "0.41.0"
log = "0.4"
# TOML persistence for the application config (configstore.rs). Already in
# Cargo.lock as a transitive dependency (0.8.23); promoted to a direct one.
toml = "0.8"
# OpenColorIO bindings (crates.io `ocio-rs`, BSD-3-Clause). ocioutils.rs maps
# PixelFormat to the real `ocio_rs::BitDepth` enum and wraps
# `ocio_rs::Config`/`CPUProcessor` for config loading and RGBA transforms.
# Rationale registered in README.md. The `bundled` feature builds the vendored
# OpenColorIO from source — all platforms build the SAME OCIO version (distro
# packages are too old for the bridge's API floor, e.g. Ubuntu 24.04's 2.1).
# An explicit OCIO_INSTALL_DIR still wins over the vendored build when set.
ocio-rs = { version = "0.2", features = ["bundled"] }
# Pure-Rust image I/O (crates.io `image`, MIT OR Apache-2.0); default features
# off, TIFF enabled — the only format current callers need. oiioutils.rs
# derives per-channel bit depths from its color-type tables and does float
# image I/O through it. See README.md.
image = { version = "0.25", default-features = false, features = ["tiff"] }
thiserror = "2"
-104
View File
@@ -1,104 +0,0 @@
# oakcommon Rust crate
> Status: **implemented**. All `include/common/*.h` contracts are
> implemented in Rust and covered by unit + C ABI integration tests
> (see [Testing](#testing)).
## Scope
Replaces the C++ oakcommon module (`src/common/src`): config store,
command-line parser, XML stream reader/writer, file functions,
debug/logging, ffmpeg/OCIO/OIIO utility queries, video/subtitle
params, color transform, misc utilities. Pure leaf module — depends
only on `oakcore-rs`, `quick-xml`, `log`, `ocio-rs`, `image`, and
system libraries.
Public contract: `include/common/*.h` (18 headers) — frozen,
implemented verbatim by `src/ffi.rs`.
## Third-party crates
| Crate | Version | License | Status |
|---|---|---|---|
| `quick-xml` | 0.41 | MIT | adopted — XML reader/writer (`xmlutils.rs`) |
| `log` | 0.4 | MIT / Apache-2.0 | adopted — logging facade (`debug.rs`); the stderr sink is retained as the always-available backend |
| `ocio-rs` | 0.2.1 | BSD-3-Clause | adopted — real OpenColorIO access for `ocioutils.rs` (`OcioConfig`/`OcioProcessor`, `BitDepth` mapping). Pulled in via `ocio-sys` built with `OCIO_RS_ENABLE_REAL=1` against the Homebrew OCIO install (see `.cargo/config.toml`) |
| `image` | 0.25 | MIT / Apache-2.0 | adopted — per-channel bit-depth tables and 32-bit float TIFF I/O for `oiioutils.rs` (`image_color_type_for`/`bits_per_channel`, `F32Image`). Default features off, `tiff` only |
| `oakcore-rs` | path | GPL-3.0 | adopted — `Rational::from_double` (the C++ `Rational::from_double` port of FFmpeg's `av_d2q`) for `get_pixel_aspect_ratio`; a hand-written port kept in the leaf crate instead of pulling in `ffmpeg-next` |
| `serde_json` | — | MIT / Apache-2.0 | **evaluated, not adopted** — the ConfigStore format is INI (QSettings-style `key=value` with `[group]` sections, `%g` doubles), not JSON; switching would break C++/Rust file interop |
| `pico-args` / `clap` | — | MIT / Apache-2.0 | **evaluated, not adopted**`commandlineparser.rs` must keep exact C++ quirks (case-insensitive names, first-match-wins, last-value-wins, argv[0] skipping, truncating getter copies, borrowed C ABI handles) that a generic parser cannot express without changing the C ABI shape |
## Architectural decisions
1. **Leaf module discipline**: no `bridge/` to other oak modules.
FFmpeg is reached through `ffmpeg_bridge`'s C ABI (narrow
`extern "C"` blocks in `ffmpegutils.rs`); OCIO and OIIO access is
pure Rust via the crates.io bindings listed in the table above
(`ocioutils.rs`, `oiioutils.rs`).
2. **`olive::Variant` disappears**: it exists in C++ only because
QVariant left a hole. Rust modules use closed enums; nothing in
common needs it. `variant.{h,cpp}` (C++) is retired when all
consumers are Rust.
3. **XML**: `XmlStreamReader/Writer` keep the C++ streaming API shape
(the C ABI is built on it), implemented over quick-xml — behavior
(attribute order, error semantics) pinned by tests against the C++
oracle.
4. **Config**: the ConfigStore is **INI-backed** (QSettings-style
`key=value` with `[group]` sections, `;`/`#` comments, `%g`
double formatting), keeping the exact C++ file format and lookup
order (user config → app defaults). It is *not* JSON — an earlier
draft described it as JSON-backed, which was wrong; that claim was
removed from this document.
5. **Logging**: `debug.rs` provides the leveled logger
(qWarning/qDebug/qCritical/qInfo replacement) with a printf-style C
ABI. Every record is written to stderr (the C++ `stderr_sink`
parity) and additionally forwarded to the `log` crate's global
logger when the host has installed one. oakcommon never installs a
global logger itself — the C ABI is loaded into hosts that set
their own, and `log::set_logger` can only be called once per
process.
6. **OCIO / OIIO / FFmpeg**: `ocioutils.rs` talks to real OpenColorIO
through the crates.io `ocio-rs` bindings (`OcioConfig`/`OcioProcessor`,
`BitDepth` enum — no hand-written constant tables); `oiioutils.rs`
derives its OIIO base-type mapping from the `image` crate's color-type
tables (HALF pinned from the frozen OIIO table — `image` has no f16
sample type) and converts aspect ratios with
`oakcore_rs::Rational::from_double` — a hand-written port of FFmpeg's
`av_d2q` matching the C++ `Rational::from_double` exactly, kept in the
leaf crate rather than adding `ffmpeg-next`/`ffmpeg-sys-next` (narrow
extern C discipline). 32-bit float image I/O (`F32Image`) is pure
`image`. All adopted crates are registered in the table above.
## Layout
```
src/
lib.rs crate doc + module map
error.rs error codes (include/common/error.h)
handle.rs refcounted-handle scaffolding
configstore.rs INI config (include/common/config.h)
commandlineparser.rs
xmlutils.rs streaming XML reader/writer
filefunctions.rs file/dir helpers
debug.rs leveled logging
ffmpegutils.rs pixfmt/samplefmt mapping (via ffmpeg_bridge C ABI)
ocioutils.rs OCIO queries
oiioutils.rs OIIO queries
videoparams.rs VideoParams plain data + queries
subtitleparams.rs SubtitleParams
colortransform.rs ColorTransform plain data
miscutils.rs misc (loop mode, drop behavior, power, current…)
ffi.rs export layer (one submodule per public header)
tests/ contract tests per module
```
## Testing
`cargo test --release --features test-stubs` runs the full suite:
unit tests, C ABI contract tests, and the integration tests (incl.
`tests/ffi_ffmpegutils.rs`). The `test-stubs` feature substitutes the
in-crate ffmpeg_bridge mock (`fb_find_best_pix_fmt_of_list` stub, see
`src/ffmpegutils.rs`) so the C ABI tests link without
libffmpeg_bridge; without the feature that symbol is imported from
`ffmpeg_bridge` at link time. This mirrors the `test-stubs`
convention of oakplugin / oaktimeline.
-189
View File
@@ -1,189 +0,0 @@
// 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/>.
//! Error codes, mirroring `include/common/error.h`; project-wide
//! -MMCCCC scheme (module 01), pass-through untranslated.
use thiserror::Error;
/// Success.
pub const OAKCOMMON_OK: i32 = 0;
/// Empty handle or invalid argument.
pub const OAKCOMMON_E_INVALID: i32 = -10001;
/// Call not valid in the current state.
pub const OAKCOMMON_E_STATE: i32 = -10002;
/// The underlying operation failed.
pub const OAKCOMMON_E_FAILED: i32 = -10003;
/// Index out of range / entry not found.
pub const OAKCOMMON_E_NOT_FOUND: i32 = -10004;
/// Allocation failed.
pub const OAKCOMMON_E_NOMEM: i32 = -10005;
/// Crate-internal result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Crate-internal error.
#[derive(Debug, Error)]
pub enum Error {
/// Empty handle or invalid argument.
#[error("common: empty handle or invalid argument")]
Invalid,
/// Wrong state.
#[error("common: call not valid in current state")]
State,
/// Operation failed (context string is log-only).
#[error("common: operation failed: {0}")]
Failed(String),
/// Not found.
#[error("common: entry not found")]
NotFound,
/// Out of memory.
#[error("common: allocation failed")]
NoMem,
}
impl Error {
/// Map to the public error code.
pub fn code(&self) -> i32 {
match self {
Error::Invalid => OAKCOMMON_E_INVALID,
Error::State => OAKCOMMON_E_STATE,
Error::Failed(_) => OAKCOMMON_E_FAILED,
Error::NotFound => OAKCOMMON_E_NOT_FOUND,
Error::NoMem => OAKCOMMON_E_NOMEM,
}
}
/// Create a [`Error::Failed`] carrying a context message (log-only).
///
/// Convenience constructor used by the OCIO/`image` wrappers in
/// `ocioutils.rs` / `oiioutils.rs` when an underlying library call fails.
pub fn new(message: impl Into<String>) -> Self {
Error::Failed(message.into())
}
}
impl From<ocio_rs::OcioError> for Error {
fn from(e: ocio_rs::OcioError) -> Self {
// The Display impl of `OcioError` always produces a non-empty message
// (every variant carries text or a fixed phrase); it becomes the
// log-only context of `Error::Failed`.
Error::Failed(e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_codes_match_header_values() {
// Load-bearing values from include/common/error.h (module 01).
assert_eq!(OAKCOMMON_OK, 0);
assert_eq!(OAKCOMMON_E_INVALID, -10001);
assert_eq!(OAKCOMMON_E_STATE, -10002);
assert_eq!(OAKCOMMON_E_FAILED, -10003);
assert_eq!(OAKCOMMON_E_NOT_FOUND, -10004);
assert_eq!(OAKCOMMON_E_NOMEM, -10005);
}
#[test]
fn error_code_maps_each_variant() {
assert_eq!(Error::Invalid.code(), OAKCOMMON_E_INVALID);
assert_eq!(Error::State.code(), OAKCOMMON_E_STATE);
assert_eq!(Error::Failed("boom".to_string()).code(), OAKCOMMON_E_FAILED);
assert_eq!(Error::NotFound.code(), OAKCOMMON_E_NOT_FOUND);
assert_eq!(Error::NoMem.code(), OAKCOMMON_E_NOMEM);
}
#[test]
fn error_codes_are_all_distinct() {
let codes = [
Error::Invalid.code(),
Error::State.code(),
Error::Failed(String::new()).code(),
Error::NotFound.code(),
Error::NoMem.code(),
];
for (i, a) in codes.iter().enumerate() {
for b in &codes[i + 1..] {
assert_ne!(a, b);
}
// Errors are strictly negative; OK stays zero.
assert!(*a < 0);
}
}
#[test]
fn failed_message_is_preserved_in_debug() {
// The context string is log-only but must survive to the log.
let e = Error::Failed("context info".to_string());
let dbg = format!("{e:?}");
assert!(dbg.contains("context info"));
}
#[test]
fn result_alias_round_trips_ok_and_err() {
let ok: Result<i32> = Ok(7);
assert_eq!(ok.unwrap(), 7);
let err: Result<i32> = Err(Error::NotFound);
assert_eq!(err.unwrap_err().code(), OAKCOMMON_E_NOT_FOUND);
}
#[test]
fn new_creates_failed_with_message() {
let e = Error::new("context info");
assert!(matches!(e, Error::Failed(_)));
assert_eq!(e.code(), OAKCOMMON_E_FAILED);
assert!(format!("{e:?}").contains("context info"));
}
#[test]
fn ocio_error_converts_to_failed() {
let e = Error::from(ocio_rs::OcioError::InvalidInput(
"bad colorspace".to_string(),
));
assert!(matches!(e, Error::Failed(_)));
assert_eq!(e.code(), OAKCOMMON_E_FAILED);
assert!(format!("{e:?}").contains("bad colorspace"));
}
#[test]
fn display_is_non_empty_for_each_variant() {
let variants = [
Error::Invalid,
Error::State,
Error::Failed("context".to_string()),
Error::NotFound,
Error::NoMem,
];
for e in &variants {
assert!(!e.to_string().is_empty());
}
}
#[test]
fn failed_display_includes_context() {
let e = Error::Failed("context info".to_string());
assert!(e.to_string().contains("context info"));
}
#[test]
fn error_is_object_safe() {
let e: Box<dyn std::error::Error> = Box::new(Error::NoMem);
assert!(!e.to_string().is_empty());
}
}
-63
View File
@@ -1,63 +0,0 @@
// 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/>.
//! # oakcommon — shared utilities (Rust). Leaf module.
//!
//! Implements `include/common/*.h` verbatim. See README.md.
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
pub mod cancelatom;
pub mod colormath;
pub mod colortransform;
pub mod commandlineparser;
pub mod configstore;
pub mod debug;
pub mod displayicc;
pub mod error;
pub mod ffmpegutils;
pub mod filefunctions;
pub mod miscutils;
pub mod ocioutils;
pub mod oiioutils;
pub mod qtutils;
pub mod subtitleparams;
pub mod videoparams;
pub mod xmlutils;
/// Test-only helpers shared across unit-test modules.
///
/// Several domain test modules (e.g. `configstore`, `filefunctions`) mutate
/// process-global state — notably the `OAK_CONFIG_DIR` environment variable
/// and shared temp paths — while exercising configuration-location logic.
/// Rust runs tests in parallel, so all such tests must serialize on a single
/// process-wide lock to avoid racing each other across module boundaries.
#[cfg(test)]
#[doc(hidden)]
pub mod test_support {
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
/// Process-wide lock guarding tests that mutate global config/env state.
///
/// Hold this for the duration of any test (or helper) that sets/removes
/// `OAK_CONFIG_DIR` or touches the shared configuration temp path.
pub fn env_lock() -> &'static Mutex<()> {
&ENV_LOCK
}
}
-113
View File
@@ -1,113 +0,0 @@
// 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/>.
//! C ABI contract tests. These assert the load-bearing constants, enum
//! discriminants, and handle layout that the C headers rely on. They do
//! NOT call any crate function, only public constants/types: these checks
//! are compile-time/constant-only and document the ABI surface that must
//! not drift from the headers.
use std::mem::{align_of, size_of};
use oak_common::error::{
OAKCOMMON_E_FAILED, OAKCOMMON_E_INVALID, OAKCOMMON_E_NOMEM, OAKCOMMON_E_NOT_FOUND,
OAKCOMMON_E_STATE, OAKCOMMON_OK,
};
use oak_common::ffmpegutils::{RGBA_CHANNEL_COUNT, RGB_CHANNEL_COUNT};
use oak_common::miscutils::{DropWorkflowBehavior, LoopMode, DECIBEL_MINIMUM};
use oak_common::ocioutils::PixelFormat;
use oak_common::videoparams::{ColorRange, Interlacing, VideoType};
/// Error codes must match `include/common/error.h`.
#[test]
fn error_codes_match_header() {
assert_eq!(OAKCOMMON_OK, 0);
assert_eq!(OAKCOMMON_E_INVALID, -10001);
assert_eq!(OAKCOMMON_E_STATE, -10002);
assert_eq!(OAKCOMMON_E_FAILED, -10003);
assert_eq!(OAKCOMMON_E_NOT_FOUND, -10004);
assert_eq!(OAKCOMMON_E_NOMEM, -10005);
}
/// Handle ABI version must match `include/common/handle.h`.
/// The handle struct must be a plain `{ctx, addref, release, abi_version}`
/// `#[repr(C)]` record: 3 pointers + a u32, padded to pointer alignment.
/// Pixel-format codes must match `olive::core::PixelFormat`.
#[test]
fn pixel_format_discriminants() {
assert_eq!(PixelFormat::Invalid as i32, -1);
assert_eq!(PixelFormat::U8 as i32, 0);
assert_eq!(PixelFormat::U10 as i32, 1);
assert_eq!(PixelFormat::U16 as i32, 2);
assert_eq!(PixelFormat::F16 as i32, 3);
assert_eq!(PixelFormat::F32 as i32, 4);
assert_eq!(PixelFormat::Count as i32, 5);
}
/// Decibel minimum must match `include/common/miscutils.h`.
#[test]
fn decibel_minimum() {
assert_eq!(DECIBEL_MINIMUM, -200.0);
}
/// Channel-count constants must match `include/common/ffmpegutils.h`.
#[test]
fn channel_count_constants() {
assert_eq!(RGB_CHANNEL_COUNT, 3);
assert_eq!(RGBA_CHANNEL_COUNT, 4);
}
/// Loop-mode codes must match `include/common/loopmode.h`.
#[test]
fn loop_mode_discriminants() {
assert_eq!(LoopMode::Off as i32, 0);
assert_eq!(LoopMode::Loop as i32, 1);
assert_eq!(LoopMode::Clamp as i32, 2);
}
/// Drop-workflow behavior codes must match `include/common/dropworkflowbehavior.h`.
#[test]
fn drop_workflow_behavior_discriminants() {
assert_eq!(DropWorkflowBehavior::Ask as i32, 0);
assert_eq!(DropWorkflowBehavior::Auto as i32, 1);
assert_eq!(DropWorkflowBehavior::Manual as i32, 2);
assert_eq!(DropWorkflowBehavior::Disable as i32, 3);
}
/// Interlacing codes must match `include/common/videoparams.h`.
#[test]
fn interlacing_discriminants() {
assert_eq!(Interlacing::None as i32, 0);
assert_eq!(Interlacing::TopFirst as i32, 1);
assert_eq!(Interlacing::BottomFirst as i32, 2);
}
/// Video-type codes must match `include/common/videoparams.h`.
#[test]
fn video_type_discriminants() {
assert_eq!(VideoType::Video as i32, 0);
assert_eq!(VideoType::Still as i32, 1);
assert_eq!(VideoType::ImageSequence as i32, 2);
}
/// Color-range codes must match `include/common/videoparams.h`.
#[test]
fn color_range_discriminants() {
assert_eq!(ColorRange::Limited as i32, 0);
assert_eq!(ColorRange::Full as i32, 1);
}
-189
View File
@@ -1,189 +0,0 @@
// 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/>.
//! Real-library smoke tests for the OCIO/image-backed utilities.
//!
//! These exercise the real OpenColorIO library through `ocio-rs`/`ocio-sys`
//! (compiled with `OCIO_RS_ENABLE_REAL=1`, see `.cargo/config.toml`). Each
//! OCIO test loads the project's own config at
//! `engine/render/ocioconf/config.ocio` and sets the `OCIO` environment
//! variable first (the library reads it at config-load time). The image tests
//! round-trip a small float TIFF through a temp file via the `image` crate.
use oak_common::error::Error;
use oak_common::ocioutils::OcioConfig;
use oak_common::oiioutils::{read_image_f32, write_image_f32};
/// Path to the project's OCIO config, relative to this crate's manifest dir.
fn config_path() -> String {
let manifest = env!("CARGO_MANIFEST_DIR");
// crates/oakcommon -> repository root
format!("{}/../../engine/render/ocioconf/config.ocio", manifest)
}
/// The `OCIO` env var is consumed by the library at config-load time; setting
/// it here keeps the test hermetic regardless of the host environment.
fn set_ocio_env() -> String {
let path = config_path();
std::env::set_var("OCIO", &path);
path
}
#[test]
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
fn ocio_load_and_list_colorspaces() {
let path = set_ocio_env();
let config = OcioConfig::from_file(&path).expect("config.ocio should load");
let count = config.colorspace_count().expect("count should work");
assert!(count > 0, "config should define at least one color space");
let colorspaces = config.colorspaces().expect("listing should work");
assert_eq!(colorspaces.len(), count as usize);
assert!(colorspaces.contains(&"Linear".to_string()));
assert!(colorspaces.contains(&"sRGB OETF".to_string()));
eprintln!("color spaces ({}): {:?}", colorspaces.len(), colorspaces);
}
#[test]
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
fn ocio_roles_and_canonical_names() {
let path = set_ocio_env();
let config = OcioConfig::from_file(&path).unwrap();
let roles = config.roles().unwrap();
assert!(!roles.is_empty(), "config should define roles");
eprintln!("roles: {:?}", roles);
assert!(
config.has_role("scene_linear").unwrap(),
"scene_linear role should exist"
);
assert!(config.has_role("default").unwrap());
assert!(!config.has_role("no_such_role").unwrap());
// scene_linear maps to the Linear color space.
assert_eq!(config.canonical_name("scene_linear").unwrap(), "Linear");
// Role/name equivalence: role name resolves to its canonical color space.
assert_eq!(config.canonical_name("Linear").unwrap(), "Linear");
}
#[test]
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
fn ocio_displays_and_views() {
let path = set_ocio_env();
let config = OcioConfig::from_file(&path).unwrap();
let display = config.default_display().unwrap();
assert_eq!(display, "sRGB");
let view = config.default_view(&display).unwrap();
assert_eq!(view, "sRGB OETF");
}
#[test]
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
fn ocio_processor_apply_rgba() {
let path = set_ocio_env();
let config = OcioConfig::from_file(&path).unwrap();
// Linear -> sRGB OETF: a mid-gray 0.18 (a common linear display-referred
// midpoint) should map well above itself and stay finite.
let processor = config.processor("Linear", "sRGB OETF").unwrap();
let mut px = [0.18f32, 0.18f32, 0.18f32, 1.0f32];
processor.apply_rgba(&mut px).unwrap();
assert!(
px[0] > 0.18f32,
"sRGB OETF should lift 0.18 linear, got {}",
px[0]
);
assert!(
px[0] < 1.0f32 + 1e-6,
"sRGB OETF output should be <= 1.0, got {}",
px[0]
);
assert!(px.iter().all(|v| v.is_finite()));
// Display-referred path: scene_linear -> default sRGB view.
let processor = config
.display_processor("scene_linear", "sRGB", "sRGB OETF")
.unwrap();
let mut px = [0.18f32, 0.18f32, 0.18f32, 1.0f32];
processor.apply_rgba(&mut px).unwrap();
assert!(px.iter().all(|v| v.is_finite()));
assert!(
px[0] > 0.18f32,
"display processor should also lift 0.18, got {}",
px[0]
);
}
#[test]
#[ignore = "requires real OpenColorIO (OCIO_RS_ENABLE_REAL=1) and engine/render/ocioconf/config.ocio (removed with the C++ engine tree); run with -- --ignored when both are available"]
fn ocio_error_paths() {
let path = set_ocio_env();
// Nonexistent config file.
let err = OcioConfig::from_file("/nonexistent/oakcommon-real-ocio.ocio").unwrap_err();
eprintln!("nonexistent config error: {err:?}");
assert!(matches!(err, Error::Failed(_)));
let config = OcioConfig::from_file(&path).unwrap();
// Unknown destination color space.
let err = config
.processor("Linear", "No Such Color Space")
.unwrap_err();
eprintln!("unknown colorspace error: {err:?}");
assert!(matches!(err, Error::Failed(_)));
// Unknown display/view.
let err = config
.display_processor("Linear", "No Such Display", "No View")
.unwrap_err();
eprintln!("unknown display error: {err:?}");
assert!(matches!(err, Error::Failed(_)));
}
#[test]
fn image_f32_round_trip() {
let dir = std::env::temp_dir().join("oakcommon-real-ocio");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("roundtrip.tif");
let path_str = path.to_str().unwrap().to_string();
// 2x2 RGBA float image.
let w = 2;
let h = 2;
let c = 4;
let pixels: Vec<f32> = vec![
0.0, 0.25, 0.5, 1.0, 0.75, 0.5, 0.25, 1.0, 1.0, 0.0, 0.5, 0.0, 0.125, 0.625, 0.875, 1.0,
];
write_image_f32(&path_str, w, h, c, &pixels).expect("write should succeed");
let img = read_image_f32(&path_str).expect("read should succeed");
assert_eq!(img.width, w);
assert_eq!(img.height, h);
assert_eq!(img.channels, c);
assert_eq!(img.pixels.len(), (w * h * c) as usize);
for (i, (a, b)) in img.pixels.iter().zip(pixels.iter()).enumerate() {
let diff = (a - b).abs();
assert!(diff < 1e-6, "pixel {i}: wrote {b}, read back {a}");
}
std::fs::remove_file(&path).ok();
}
+14
View File
@@ -9,3 +9,17 @@ license = "GPL-3.0-or-later"
crate-type = ["rlib"]
[dependencies]
thiserror = "2.0.20"
ocio-rs = { version = "0.2", features = ["bundled"] }
# wgpu: portable GPU backend — same major as oak-render's shaderfx/naga
# generation (25); the moved backend/color/texture/frame code is written
# against this API.
wgpu = "25"
log = "0.4.34"
# TOML persistence for the application config (configstore.rs).
toml = "0.8"
# XML helpers (xmlutils.rs).
quick-xml = "0.41.0"
# Pure-Rust image I/O for oiioutils.rs (TIFF only — the only format current
# callers need).
image = { version = "0.25", default-features = false, features = ["tiff"] }
@@ -37,17 +37,17 @@ use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use oak_core::PixelFormat;
use crate::PixelFormat;
use crate::error::{Error, Result};
use crate::frame::VideoParamsPod;
use crate::texture::{Frame, Texture};
use crate::error::{Error, Result};
/// Backend selection preference (mapped onto wgpu backends).
///
/// The choice is user-visible: the settings panel exposes a renderer
/// dropdown (Auto/Metal/Vulkan/OpenGL/CPU) persisted through the
/// oakcommon config C ABI under the "GraphicsBackend" key
/// oak_core config C ABI under the "GraphicsBackend" key
/// (C++ parity: `RenderManager::backend_from_string` config round-trip).
/// "auto" resolves Metal → Vulkan → GL → CPU at runtime.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -91,7 +91,7 @@ impl BackendKind {
}
}
/// Read the user's persisted choice through the oakcommon config C ABI
/// Read the user's persisted choice through the oak_core config C ABI
/// ("GraphicsBackend"). `OAK_RENDER_BACKEND` overrides the config
/// (tests / headless environments).
pub fn from_user_config() -> BackendKind {
@@ -179,7 +179,7 @@ impl DisplayBitDepth {
}
}
/// Read the user's persisted choice through the oakcommon config C ABI
/// Read the user's persisted choice through the oak_core config C ABI
/// ("DisplayBitDepth").
pub fn from_user_config() -> DisplayBitDepth {
let configured =
@@ -238,10 +238,10 @@ pub trait GpuContextLike: Send + Sync {
fn download(&self, token: u64) -> Result<Frame>;
/// Blit texture → texture (plain copy; color-managed deferred).
fn blit(
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
) -> Result<()>;
}
@@ -531,10 +531,10 @@ impl GpuContext {
/// of this pass (see README §4), so a `Some` processor returns
/// `Error::Failed` and the plain-copy WGSL pipeline is used for `None`.
pub fn blit(
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
) -> Result<()> {
if processor.is_some() {
return Err(Error::Failed(
@@ -830,7 +830,7 @@ impl GpuContext {
/// Run one effect pass: fragment-shade `dst` from `textures[0]` (the
/// main input) plus any extra input textures, with `uniforms` as the
/// packed std140 block (see [`crate::shaderfx::pack_uniforms`]).
/// packed std140 block (see `oak_render::shaderfx::pack_uniforms`).
pub fn run_shader_pass(
&self,
program: &ShaderProgram,
@@ -1000,10 +1000,10 @@ impl GpuContextLike for GpuContext {
}
fn blit(
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
&self,
src: u64,
dst: u64,
processor: Option<&crate::color::ColorProcessor>,
) -> Result<()> {
self.blit(src, dst, processor)
}
@@ -1049,11 +1049,11 @@ fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
// Minimal futures executor (wgpu brings futures-core transitively; a tiny
// block_on is enough for the immediately-ready adapter/device futures).
mod futures_executor {
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn noop_raw_waker() -> RawWaker {
fn noop_raw_waker() -> RawWaker {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker {
noop_raw_waker()
@@ -1250,10 +1250,10 @@ impl DisplayRenderer {
/// GPU path: plain-copy WGSL blit; a color processor on the GPU path is
/// deferred (`Error::Failed`, see [`GpuContext::blit`]).
pub fn blit_color_managed(
&self,
src: Option<&Texture>,
dst: &mut Texture,
processor: Option<&crate::color::ColorProcessor>,
&self,
src: Option<&Texture>,
dst: &mut Texture,
processor: Option<&crate::color::ColorProcessor>,
) -> Result<()> {
match (src, dst) {
(
@@ -1342,9 +1342,9 @@ pub fn frame_from_pixels_for_upload(
#[cfg(test)]
mod tests {
use super::*;
use super::*;
#[test]
#[test]
fn backend_string_roundtrip() {
for (s, kind) in [
("auto", BackendKind::Auto),
@@ -1496,78 +1496,6 @@ mod tests {
ctx.destroy_texture(dst);
}
/// End-to-end effect pass: a translated node shader (gain multiply)
/// runs through `compile_shader_pass`/`run_shader_pass` and the
/// readback matches the expected pixels exactly.
#[test]
fn gpu_effect_pass_runs_translated_shader() {
let Some(ctx) = any_gpu() else {
eprintln!("no adapter; skipping effect pass");
return;
};
let glsl = r#"
uniform sampler2D tex_in;
uniform float gain_in;
in vec2 ove_texcoord;
out vec4 frag_color;
void main() {
frag_color = texture(tex_in, ove_texcoord) * gain_in;
}
"#;
let translated = crate::shaderfx::translate(glsl).unwrap();
let program = ctx
.compile_shader_pass(
"test-gain",
&translated.wgsl,
translated.textures.len() as u32,
!translated.uniforms.is_empty(),
false,
)
.unwrap();
let mut row = oak_node::value::NodeValueRow::new();
row.insert("gain_in".into(), oak_node::value::NodeValue::Float(0.5));
let uniforms = crate::shaderfx::pack_uniforms(&translated, &row);
let w = 4;
let h = 2;
let src = ctx.create_texture(w, h).unwrap();
let dst = ctx.create_texture(w, h).unwrap();
let mut frame = Frame::new();
let mut pod = VideoParamsPod::default();
pod.width = w;
pod.height = h;
frame.set_video_params(pod);
frame.allocate();
// Distinct values per pixel (F32 RGBA): 0.2/0.4/0.6/1.0 shifted
// per pixel, so a UV mixup would be visible.
for px in 0..(w * h) as usize {
for c in 0..4 {
let v = 0.2 + 0.1 * (px + c) as f32;
frame.data[(px * 4 + c) * 4..(px * 4 + c) * 4 + 4]
.copy_from_slice(&v.to_le_bytes());
}
}
ctx.upload(src, &frame).unwrap();
ctx.run_shader_pass(&program, &uniforms, &[src], dst).unwrap();
let out = ctx.download(dst).unwrap();
for px in 0..(w * h) as usize {
for c in 0..4 {
let at = (px * 4 + c) * 4;
let got = f32::from_le_bytes(out.data[at..at + 4].try_into().unwrap());
let want = (0.2 + 0.1 * (px + c) as f32) * 0.5;
assert!(
(got - want).abs() < 1e-6,
"px {px} ch {c}: got {got}, want {want}"
);
}
}
ctx.destroy_texture(src);
ctx.destroy_texture(dst);
}
#[test]
fn gpu_missing_texture_errors() {
let Some(ctx) = any_gpu() else {
@@ -1669,9 +1597,9 @@ void main() {
assert_eq!(df.data[4], 0x22);
// Pass-through processor is a no-op.
r.blit_color_managed(
Some(&src),
&mut dst,
Some(&crate::color::ColorProcessor::pass_through()),
Some(&src),
&mut dst,
Some(&crate::color::ColorProcessor::pass_through()),
)
.unwrap();
// Size mismatch rejected.
@@ -24,7 +24,7 @@
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use oak_core::PixelFormat;
use crate::PixelFormat;
use crate::error::{Error, Result};
use crate::texture::Frame;
@@ -199,7 +199,7 @@ impl ColorProcessor {
/// starts from an OCIO named space (sRGB and friends); P3/BT.2020
/// targets have no named space in the builtin configs, so the caller
/// linearizes and gamut-maps to XYZ itself
/// (`oak_common::colormath::output_spec_to_xyz_d65`) and this chain only
/// (`crate::colormath::output_spec_to_xyz_d65`) and this chain only
/// needs the ICC half: it runs the existing builder with the config's
/// `cie_xyz_d65_interchange` role as the source space (XYZ → linear
/// Rec.709, whose inverse the builder's leg 2 immediately undoes — a
@@ -427,27 +427,27 @@ fn bytemuck_f32_slice(data: &mut [u8]) -> Option<&mut [f32]> {
/// project-properties commit). Render and export paths read it — a single
/// project is open at a time, so a process global is the same shape as the
/// OCIO default config above.
static PIPELINE_COLOR: LazyLock<Mutex<(oak_common::colormath::WorkingColorSpace, oak_common::colormath::OutputColorSpec)>> =
static PIPELINE_COLOR: LazyLock<Mutex<(crate::colormath::WorkingColorSpace, crate::colormath::OutputColorSpec)>> =
LazyLock::new(|| Mutex::new((
oak_common::colormath::WorkingColorSpace::default(),
oak_common::colormath::OutputColorSpec::default(),
crate::colormath::WorkingColorSpace::default(),
crate::colormath::OutputColorSpec::default(),
)));
/// Set the pipeline color settings (working space + output spec).
pub fn set_pipeline_color_settings(
working: oak_common::colormath::WorkingColorSpace,
output: oak_common::colormath::OutputColorSpec,
working: crate::colormath::WorkingColorSpace,
output: crate::colormath::OutputColorSpec,
) {
*PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()) = (working, output);
}
/// The pipeline working colorspace.
pub fn pipeline_working_space() -> oak_common::colormath::WorkingColorSpace {
pub fn pipeline_working_space() -> crate::colormath::WorkingColorSpace {
PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()).0
}
/// The pipeline output/delivery spec.
pub fn pipeline_output_spec() -> oak_common::colormath::OutputColorSpec {
pub fn pipeline_output_spec() -> crate::colormath::OutputColorSpec {
PIPELINE_COLOR.lock().unwrap_or_else(|e| e.into_inner()).1
}
@@ -457,8 +457,8 @@ pub fn pipeline_output_spec() -> oak_common::colormath::OutputColorSpec {
/// sRGB in the legacy pass-through mode).
pub fn pipeline_working_ofx_name() -> &'static str {
match pipeline_working_space() {
oak_common::colormath::WorkingColorSpace::AcesCg => "ACEScg",
oak_common::colormath::WorkingColorSpace::SrgbLegacy => "sRGB",
crate::colormath::WorkingColorSpace::AcesCg => "ACEScg",
crate::colormath::WorkingColorSpace::SrgbLegacy => "sRGB",
}
}
@@ -804,7 +804,7 @@ mod tests {
f.format = PixelFormat::U8;
assert_eq!(
valid.convert_frame(&mut f).unwrap_err().code(),
crate::error::OAKRENDER_E_INVALID
crate::error::OAKCORE_E_INVALID
);
}
}
@@ -967,7 +967,7 @@ mod tests {
/// The non-sRGB project output gamut display path: content converted to
/// CIE XYZ (D65, unit luminance) by
/// `oak_common::colormath::output_spec_to_xyz_d65` must flow through the
/// `crate::colormath::output_spec_to_xyz_d65` must flow through the
/// display ICC. Builds by running the classic builder with the config's
/// `cie_xyz_d65_interchange` role as the source space — the feasibility
/// question this test answers is whether OCIO accepts the role name as a
@@ -1005,10 +1005,10 @@ mod tests {
// the same encoded values — legs 1+2 (XYZ→lin709→XYZ) are the inverse
// round trip of the classic chain's lin709→XYZ leg, so both must land
// on the same device values.
let spec = oak_common::colormath::OutputColorSpec::default();
let spec = crate::colormath::OutputColorSpec::default();
let encoded = [0.5f32, 0.5, 0.5, 1.0];
let mut xyz_in = encoded;
oak_common::colormath::output_spec_to_xyz_d65(&mut xyz_in, spec);
crate::colormath::output_spec_to_xyz_d65(&mut xyz_in, spec);
let mut via_xyz = xyz_in;
let _ = p.convert_f32_rgba(&mut via_xyz, 1);
let srgb = ColorProcessor::create_display_icc("sRGB Encoded Rec.709 (sRGB)", icc)
@@ -1034,17 +1034,19 @@ mod tests {
/// The exact chain the viewers use (BGRA8, display-class ICC from
/// `OAK_DISPLAY_ICC`): a mid-grey frame must NOT collapse to black —
/// the viewer-black-screen regression guard. Skipped without the env
/// var (point it at the display profile under investigation).
/// var (point it at the display profile under investigation); an empty
/// value is treated as unset, same as `displayicc::env_override_icc`.
#[test]
fn display_icc_bgra8_never_outputs_black() {
let _lock = config_lock();
if set_up_default_config().is_err() {
return;
}
let Ok(icc) = std::env::var("OAK_DISPLAY_ICC") else {
eprintln!("OAK_DISPLAY_ICC unset; skipping");
let icc = std::env::var("OAK_DISPLAY_ICC").unwrap_or_default();
if icc.is_empty() {
eprintln!("OAK_DISPLAY_ICC unset or empty; skipping");
return;
};
}
let p = ColorProcessor::create_display_icc_bgra8("sRGB Encoded Rec.709 (sRGB)", &icc)
.expect("handle always returned");
assert!(p.is_valid(), "BGRA8 ICC processor builds from {icc}");
@@ -22,7 +22,7 @@
//! as a refcounted handle ([`OakColorTransform`] in `crate::ffi`); this
//! module owns the plain-data description behind the handle.
//!
//! The C++-only functions `oakcommon_colortransform_init_from_native` /
//! The C++-only functions `oak_core_colortransform_init_from_native` /
//! `get_native` take or return `olive::ColorTransform` and cannot be
//! expressed from Rust; they are served by the C++ adapter layer, not here.
@@ -14,32 +14,31 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! oakcommon helpers (config, file functions) — direct Rust calls into
//! the oakcommon crate (single-lib unification; the former
//! `bridge/common.rs`). The configuration-location and disk-cache
//! helpers delegate to oakcommon's own implementation.
//! Config / file-function helpers — thin wrappers over this crate's
//! `configstore` and `filefunctions` (the former `bridge/common.rs`,
//! moved here from oak-render with the backend/color merge).
use std::sync::Mutex;
/// Read a config string via the domain store
/// (`ConfigStore::get(group, key)`); `None` when missing or empty.
pub fn config_get_string(group: Option<&str>, key: &str) -> Option<String> {
oak_common::configstore::ConfigStore::instance()
crate::configstore::ConfigStore::instance()
.get(group, key)
.ok()
.filter(|s| !s.is_empty())
}
/// `oakcommon_config_get_int(group, key, default)`.
/// `oak_core_config_get_int(group, key, default)`.
pub fn config_get_int(group: Option<&str>, key: &str, default: i32) -> i32 {
oak_common::configstore::ConfigStore::instance().get_int(group, key, default)
crate::configstore::ConfigStore::instance().get_int(group, key, default)
}
/// The configuration directory — oakcommon's implementation
/// The configuration directory — `filefunctions`' implementation
/// (`FileFunctions::get_configuration_location`, honoring `OAK_CONFIG_DIR`
/// and the platform fallbacks).
pub fn configuration_location() -> String {
oak_common::filefunctions::FileFunctions::new()
crate::filefunctions::FileFunctions::new()
.get_configuration_location()
.unwrap_or_default()
}
@@ -51,7 +50,7 @@ pub static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
/// The default disk cache directory (C++ `DiskManager::
/// get_default_disk_cache_path`): `<configuration_location>/mediacache`.
pub fn default_disk_cache_path() -> String {
oak_common::filefunctions::default_disk_cache_path()
crate::filefunctions::default_disk_cache_path()
}
#[cfg(test)]
@@ -33,11 +33,11 @@ use std::sync::{Mutex, OnceLock};
use crate::error::{Error, Result};
/// Error-handler callback for user-visible config errors
/// (`OakCommonConfigErrorHandler`). Called with title, message, and the
/// (`oak_coreConfigErrorHandler`). Called with title, message, and the
/// registered userdata.
pub type ErrorHandler = Option<unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void)>;
/// Entry types (`OakCommonConfigEntryType`).
/// Entry types (`oak_coreConfigEntryType`).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EntryType {
/// No entry / null type.
@@ -377,7 +377,7 @@ impl ConfigStore {
Ok(())
}
/// Set a string entry. Mirrors `oakcommon_config_set` (`config.cpp:102`):
/// Set a string entry. Mirrors `oak_core_config_set` (`config.cpp:102`):
/// a new key is created as a string; setting an existing typed entry
/// parses the string into its declared type, and an unparseable value
/// leaves the entry unchanged.
@@ -400,7 +400,7 @@ impl ConfigStore {
}
/// Read an entry as a string (two-stage getter semantics: formatted
/// for numeric/bool entries). Mirrors `oakcommon_config_get`
/// for numeric/bool entries). Mirrors `oak_core_config_get`
/// (`config.cpp:134`).
pub fn get(&self, group: Option<&str>, key: &str) -> Result<String> {
if key.is_empty() {
@@ -492,7 +492,7 @@ impl ConfigStore {
self.set_entry(join_key(group, key), ConfigValue::Double(v));
}
/// Entry type of a key, or `NotFound`. Mirrors `oakcommon_config_entry_type`
/// Entry type of a key, or `NotFound`. Mirrors `oak_core_config_entry_type`
/// (`config.cpp:270`).
pub fn entry_type(&self, group: Option<&str>, key: &str) -> Result<EntryType> {
if key.is_empty() {
@@ -505,7 +505,7 @@ impl ConfigStore {
}
/// Register (or clear, with a null handler) the error handler. Mirrors
/// the domain half of `oakcommon_config_set_error_handler` (`config.cpp:288`).
/// the domain half of `oak_core_config_set_error_handler` (`config.cpp:288`).
pub fn set_error_handler(&self, handler: ErrorHandler, userdata: *mut c_void) -> Result<()> {
let ptr = handler.map_or(std::ptr::null_mut(), |h| h as *mut c_void);
self.error_handler.store(ptr, Ordering::Release);
@@ -887,7 +887,7 @@ mod tests {
fn with_temp_config<T>(f: impl FnOnce(&Path) -> T) -> T {
let _guard = test_lock().lock().unwrap();
let dir =
std::env::temp_dir().join(format!("oakcommon_configstore_test_{}", std::process::id()));
std::env::temp_dir().join(format!("oak_core_configstore_test_{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
std::env::set_var("OAK_CONFIG_DIR", &dir);
let result = f(&dir);
@@ -1363,7 +1363,7 @@ CustomKey=hello
let res = s.load();
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
assert!(res.is_err());
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
let reported = REPORTED.lock().unwrap().clone();
assert_eq!(reported.len(), 1);
@@ -1716,7 +1716,7 @@ FlatAfterEmptySection=ok
let res = s.load();
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
assert!(res.is_err());
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
assert_eq!(REPORTED.lock().unwrap().len(), 1);
});
}
@@ -1735,7 +1735,7 @@ FlatAfterEmptySection=ok
let res = s.load();
s.set_error_handler(None, std::ptr::null_mut()).unwrap();
assert!(res.is_err());
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
let reported = REPORTED.lock().unwrap().clone();
assert_eq!(reported.len(), 1);
assert_eq!(reported[0].0, "Error loading settings");
@@ -1751,7 +1751,7 @@ FlatAfterEmptySection=ok
fn test_save_failure_reports_error() {
let _g = test_lock().lock().unwrap();
let dir =
std::env::temp_dir().join(format!("oakcommon_configstore_test_{}", std::process::id()));
std::env::temp_dir().join(format!("oak_core_configstore_test_{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
// Point OAK_CONFIG_DIR at a regular FILE so writing
// "<dir>/config.toml.tmp" fails (create_dir_all on it is a silent
@@ -1772,7 +1772,7 @@ FlatAfterEmptySection=ok
let _ = std::fs::remove_dir_all(&dir);
assert!(res.is_err());
assert_eq!(res.unwrap_err().code(), crate::error::OAKCOMMON_E_FAILED);
assert_eq!(res.unwrap_err().code(), crate::error::OAKCORE_E_FAILED);
let reported = REPORTED.lock().unwrap().clone();
assert_eq!(reported.len(), 1);
assert_eq!(reported[0].0, "Error saving settings");
@@ -21,7 +21,7 @@
//! Built on the `log` facade crate (crates.io `log`, MIT/Apache-2.0):
//! filtering is `log::set_max_level` and the stderr sink is a `log::Log`
//! implementation installed on first use. No hand-rolled filter state.
//! The printf-style C ABI (`oakcommon_log`) is implemented in
//! The printf-style C ABI (`oak_core_log`) is implemented in
//! `crate::ffi` over this module's [`log`] helper.
use std::io::Write;
+138
View File
@@ -0,0 +1,138 @@
// 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/>.
//! Error codes, mirroring `include/render/error.h` verbatim; project-wide
//! -MMCCCC scheme (module registry in include/common/error.h), pass-through untranslated.
/// Success.
pub const OAKCORE_OK: i32 = 0;
/// Null handle or invalid argument.
pub const OAKCORE_E_INVALID: i32 = -10001;
/// Call not valid in the current state.
pub const OAKCORE_E_STATE: i32 = -10002;
/// The underlying operation failed.
pub const OAKCORE_E_FAILED: i32 = -10003;
/// Index out of range / entry not found.
pub const OAKCORE_E_NOT_FOUND: i32 = -10004;
/// Allocation failed.
pub const OAKCORE_E_NOMEM: i32 = -10005;
/// Crate-internal result type.
pub type Result<T> = std::result::Result<T, Error>;
/// Crate-internal error.
#[derive(Clone, Debug, thiserror::Error)]
pub enum Error {
/// Null handle or invalid argument.
#[error("render: invalid argument")]
Invalid,
/// Wrong state.
#[error("render: invalid state")]
State,
/// Operation failed (context string is log-only).
#[error("render: operation failed: {0}")]
Failed(String),
/// Not found.
#[error("render: not found")]
NotFound,
/// Out of memory.
#[error("render: out of memory")]
NoMem,
}
impl Error {
/// Map to the public error code.
pub fn code(&self) -> i32 {
match self {
Error::Invalid => OAKCORE_E_INVALID,
Error::State => OAKCORE_E_STATE,
Error::Failed(_) => OAKCORE_E_FAILED,
Error::NotFound => OAKCORE_E_NOT_FOUND,
Error::NoMem => OAKCORE_E_NOMEM,
}
}
/// Create a [`Error::Failed`] carrying a context message (log-only).
///
/// Convenience constructor used by the OCIO/`image` wrappers in
/// `ocioutils.rs` / `oiioutils.rs` when an underlying library call fails.
pub fn new(message: impl Into<String>) -> Self {
Error::Failed(message.into())
}
}
impl From<ocio_rs::OcioError> for Error {
fn from(e: ocio_rs::OcioError) -> Self {
// The Display impl of `OcioError` always produces a non-empty message
// (every variant carries text or a fixed phrase); it becomes the
// log-only context of `Error::Failed`.
Error::Failed(e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Every variant must produce a non-empty `Display` message; the
/// `Failed` variant must surface its context string.
#[test]
fn display_is_non_empty() {
for msg in [
Error::Invalid.to_string(),
Error::State.to_string(),
Error::Failed("context".into()).to_string(),
Error::NotFound.to_string(),
Error::NoMem.to_string(),
] {
assert!(!msg.trim().is_empty(), "empty Display message");
}
assert!(
Error::Failed("context".into())
.to_string()
.contains("context")
);
}
/// `Error` must be usable behind a trait object.
#[test]
fn error_is_object_safe() {
let errs: Vec<Box<dyn std::error::Error>> = vec![
Box::new(Error::Invalid),
Box::new(Error::State),
Box::new(Error::Failed("context".into())),
Box::new(Error::NotFound),
Box::new(Error::NoMem),
];
assert_eq!(errs.len(), 5);
}
/// No variant wraps a downstream error, so `source()` stays `None`.
#[test]
fn source_is_none() {
assert!(std::error::Error::source(&Error::Failed("context".into())).is_none());
}
/// The `code()` mapping must be unchanged by the `Error` trait impl.
#[test]
fn code_mapping_unchanged() {
assert_eq!(Error::Invalid.code(), OAKCORE_E_INVALID);
assert_eq!(Error::State.code(), OAKCORE_E_STATE);
assert_eq!(Error::Failed("context".into()).code(), OAKCORE_E_FAILED);
assert_eq!(Error::NotFound.code(), OAKCORE_E_NOT_FOUND);
assert_eq!(Error::NoMem.code(), OAKCORE_E_NOMEM);
}
}
@@ -20,7 +20,8 @@
//! The C ABI frame functions (`oakrender_codec_frame_*`) marshal this
//! type; the FFI layer stores [`Frame`] values in `OakCodecFrame` handles.
use oak_core::{PixelFormat, Rational};
use crate::PixelFormat;
use crate::Rational;
/// Mirror of the `oakrender_video_params` POD (include/render/renderer.h,
/// field order and semantics verbatim). Stored inside [`crate::texture::Frame`]
@@ -75,7 +76,7 @@ impl Default for VideoParamsPod {
impl VideoParamsPod {
/// The default render size used when a ticket carries no force size and
/// the output node's video params cannot be queried (oakcommon bridge
/// the output node's video params cannot be queried (oak_core bridge
/// pending).
pub const DEFAULT_WIDTH: i32 = 1920;
/// See [`VideoParamsPod::DEFAULT_WIDTH`].
@@ -116,9 +117,10 @@ impl VideoParamsPod {
#[cfg(test)]
mod tests {
use super::*;
use crate::PixelFormat;
use super::*;
#[test]
#[test]
fn pod_defaults() {
let p = VideoParamsPod::default();
assert_eq!(p.format, PixelFormat::F32 as i32);
+46
View File
@@ -21,12 +21,58 @@
#![warn(missing_docs)]
pub mod cancelatom;
pub mod colormath;
pub mod colortransform;
pub mod commandlineparser;
pub mod commonutil;
pub mod configstore;
pub mod debug;
pub mod displayicc;
pub mod error;
pub mod ffmpegutils;
pub mod filefunctions;
pub mod miscutils;
pub mod ocioutils;
pub mod oiioutils;
pub mod qtutils;
pub mod subtitleparams;
pub mod videoparams;
pub mod xmlutils;
/// Test-only helpers shared across unit-test modules.
///
/// Several domain test modules (e.g. `configstore`, `filefunctions`) mutate
/// process-global state — notably the `OAK_CONFIG_DIR` environment variable
/// and shared temp paths — while exercising configuration-location logic.
/// Rust runs tests in parallel, so all such tests must serialize on a single
/// process-wide lock to avoid racing each other across module boundaries.
#[cfg(test)]
#[doc(hidden)]
pub mod test_support {
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
/// Process-wide lock guarding tests that mutate global config/env state.
///
/// Hold this for the duration of any test (or helper) that sets/removes
/// `OAK_CONFIG_DIR` or touches the shared configuration temp path.
pub fn env_lock() -> &'static Mutex<()> {
&ENV_LOCK
}
}
mod rational;
mod samplefmt;
mod timerange;
/// Shared ABI value-handle type (see [`handle::CHandle`]).
pub mod handle;
pub mod color;
pub mod texture;
pub mod frame;
pub mod backend;
pub use handle::CHandle;
pub use rational::Rational;
@@ -32,7 +32,7 @@
use crate::error::{Error, Result};
use crate::ocioutils::PixelFormat;
use image::{ExtendedColorType, ImageBuffer, Rgb, Rgba};
use oak_core::Rational;
use crate::Rational;
/// OIIO base type codes, matching `OIIO::TypeDesc::BASETYPE`.
///
@@ -104,7 +104,7 @@ impl OIIOUtils {
/// known-but-unmappable types (INT8/INT16/INT32/UINT32/INT64/UINT64/
/// STRING/PTR/DOUBLE/LASTBASE) print to stderr in C++ and return
/// `invalid`; here they all fall to `Ok(PixelFormat::Invalid)`. The
/// `base_type < 0` error mirrors the `oakcommon_oiioutils_get_format_from_oiio_basetype`
/// `base_type < 0` error mirrors the `oak_core_oiioutils_get_format_from_oiio_basetype`
/// c_api guard; the `>= LASTBASE` upper-bound guard is likewise a c_api
/// concern and is not replicated in the domain function.
pub fn get_format_from_oiio_basetype(&self, base_type: i32) -> Result<PixelFormat> {
@@ -514,7 +514,7 @@ mod tests {
}
fn temp_tiff_path(name: &str) -> (std::path::PathBuf, String) {
let dir = std::env::temp_dir().join("oakcommon-oiioutils");
let dir = std::env::temp_dir().join("oak_core-oiioutils");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
let path_str = path.to_str().unwrap().to_string();
@@ -598,7 +598,7 @@ mod tests {
#[test]
fn image_f32_read_missing_file_errors() {
let err = read_image_f32("/nonexistent/oakcommon-oiioutils.tif").unwrap_err();
let err = read_image_f32("/nonexistent/oak_core-oiioutils.tif").unwrap_err();
assert!(matches!(err, Error::Failed(_)));
}
}
@@ -211,7 +211,7 @@ mod tests {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"oakcommon_qtutils_{}_{}_{}.tmp",
"oak_core_qtutils_{}_{}_{}.tmp",
std::process::id(),
tag,
n
@@ -1155,14 +1155,14 @@ mod tests {
(7, 3),
(i32::MAX, 1),
] {
let r = oak_core::Rational::new(n as i64, d as i64);
let r = crate::Rational::new(n as i64, d as i64);
assert_eq!(
rational_reduce(n, d),
(r.numerator() as i32, r.denominator() as i32)
);
}
for s in ["1/2", "7", "4/2", "junk", "a/b", "1/2/3", ""] {
let r = oak_core::Rational::from_string(s);
let r = crate::Rational::from_string(s);
assert_eq!(
rational_from_string(s),
(r.numerator() as i32, r.denominator() as i32),
@@ -16,10 +16,10 @@
//! Textures and CPU frames.
use crate::Rational;
use crate::PixelFormat;
use std::sync::Arc;
use oak_core::{PixelFormat, Rational};
use crate::backend::{BackendKind, GpuContextLike};
use crate::error::Result;
use crate::frame::VideoParamsPod;
@@ -545,7 +545,7 @@ impl VideoParams {
/// Load parameters from an XML fragment.
pub fn load_xml(&mut self, xml: &str) -> Result<()> {
// Mirrors oakcommon_videoparams_load_xml + VideoParams::load():
// Mirrors oak_core_videoparams_load_xml + VideoParams::load():
// parse, fail on error, position on the root element, then consume
// its children.
let events = parse_xml(xml).ok_or_else(|| Error::Failed("XML parse error".to_string()))?;
@@ -613,7 +613,7 @@ impl VideoParams {
/// Save parameters to an XML fragment.
pub fn save_xml(&self) -> Result<String> {
// Mirrors oakcommon_videoparams_save_xml: a `<videoparams>` root with
// Mirrors oak_core_videoparams_save_xml: a `<videoparams>` root with
// the exact child order of VideoParams::save(). The writer emits no
// whitespace; each child is `<name>text</name>`.
let mut out = String::new();
@@ -2289,14 +2289,14 @@ mod tests {
(7, 3),
(100, 10),
] {
let r = oak_core::Rational::new(n as i64, d as i64);
let r = crate::Rational::new(n as i64, d as i64);
assert_eq!(
make_rational(n, d),
(r.numerator() as i32, r.denominator() as i32)
);
}
for s in ["1/2", "7", "4/2", "junk", "a/b", "1/2/3", "-6/3"] {
let r = oak_core::Rational::from_string(s);
let r = crate::Rational::from_string(s);
assert_eq!(
rational_from_string(s),
(r.numerator() as i32, r.denominator() as i32),
@@ -2320,9 +2320,9 @@ mod tests {
1,
);
vp.set_start_time(11);
let tb = oak_core::Rational::new(1001, 30000);
let tb = crate::Rational::new(1001, 30000);
for (n, d) in [(1i32, 1i32), (1, 2), (24000, 1001), (-3, 1), (0, 1)] {
let expected = tb.time_to_timestamp(oak_core::Rational::new(n as i64, d as i64)) + 11;
let expected = tb.time_to_timestamp(crate::Rational::new(n as i64, d as i64)) + 11;
assert_eq!(vp.time_in_timebase_units(n, d), Some(expected));
}
}
+2 -2
View File
@@ -309,7 +309,7 @@ dependencies = [
]
[[package]]
name = "oakcommon"
name = "oak_core"
version = "0.1.0"
dependencies = [
"image",
@@ -328,7 +328,7 @@ name = "oaknode"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oak_core",
"oakcore-rs",
"oakundo",
]
-1
View File
@@ -10,7 +10,6 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
oak-core = { path = "../oak-core" }
oak-common = { path = "../oak-common" }
oak-undo = { path = "../oak-undo" }
oak-codec = { path = "../oak-codec" }
thiserror = "2"
+2 -2
View File
@@ -44,8 +44,8 @@ frozen, implemented verbatim by `src/ffi.rs`.
4. **Undo.** Commands are created through the oakundo C ABI
(`bridge::undo`); the C++ `UndoCommand` subclass hierarchy becomes
vtable commands whose userdata is a Rust closure.
5. **Serialization.** XML read/write goes through the oakcommon C ABI
(`bridge::common`) until oakcommon itself is rewritten.
5. **Serialization.** XML read/write goes through the oak_core C ABI
(`bridge::common`) until oak_core itself is rewritten.
6. **Threading.** The C++ code relied on Qt's event thread +
`called_on_owner_thread()` assertions. Rust replaces this with
`Mutex<Graph>` interior mutability plus explicit
+11 -11
View File
@@ -772,15 +772,15 @@ mod tests {
use oak_codec::footagedescription::{FootageDescription, StreamEntry};
fn video_entry(stream_index: i32, duration: i64) -> StreamEntry {
let mut vp = oak_common::videoparams::VideoParams::new_basic(
1920,
1080,
oak_common::ocioutils::PixelFormat::F32,
4,
1,
1,
0,
1,
let mut vp = oak_core::videoparams::VideoParams::new_basic(
1920,
1080,
oak_core::ocioutils::PixelFormat::F32,
4,
1,
1,
0,
1,
);
vp.set_stream_index(stream_index);
vp.set_frame_rate(30000, 1001);
@@ -826,7 +826,7 @@ mod tests {
assert_eq!(v.width, 1920);
assert_eq!(v.height, 1080);
assert_eq!(v.frame_rate, oak_core::Rational::new(30000, 1001));
assert_eq!(v.pixel_format, oak_common::ocioutils::PixelFormat::F32.code());
assert_eq!(v.pixel_format, oak_core::ocioutils::PixelFormat::F32.code());
// 300000 ticks at 1/30000 = 10 seconds.
assert_eq!(video.duration, oak_core::Rational::new(10, 1));
}
@@ -835,7 +835,7 @@ mod tests {
fn conversion_skips_subtitles_and_unusable_durations() {
let mut desc = FootageDescription::new("ffmpeg");
desc.push_stream(video_entry(0, i64::MIN)); // AV_NOPTS_VALUE
desc.push_stream(StreamEntry::Subtitle(oak_common::subtitleparams::SubtitleParams::new()));
desc.push_stream(StreamEntry::Subtitle(oak_core::subtitleparams::SubtitleParams::new()));
let streams = streams_from_description(&desc);
assert_eq!(streams.len(), 1, "the subtitle stream is skipped");
@@ -110,7 +110,7 @@ impl DisplayTransformNode {
fn generate_processor(&mut self, core: &mut NodeCore) {
let _ = core;
// The C++ wraps the color manager, builds a display transform
// (`oakcommon_colortransform_init_display`) for the selected
// (`oak_core_colortransform_init_display`) for the selected
// display/view, resolves the reference color space and creates
// the processor via `oakrender_color_processor_create_transform`,
// storing it with OcioBase::set_processor. Without a manager (the
+16 -1
View File
@@ -20,11 +20,21 @@
//! ([`crate::traverser::RenderHooks::resolve`]).
//! `// CPP-PARITY: app/render/job/footagejob.h, shaderjob.h`.
use oak_core::color::ColorProcessor;
use oak_core::Rational;
use crate::id::NodeId;
use crate::nodes::plugin::PluginJobPayload;
use crate::value::NodeValueRow;
/// Job types
pub enum Job{
FootageJob(FootageJobPayload),
ShaderJob(ShaderJobPayload),
PluginJob(PluginJobPayload),
ColorTransformJob
}
/// C++ `FootageJob` payload: the decode request a footage node emits at
/// its output instead of a texture. The render hooks decode it at the
/// request time and replace it with the resulting frame.
@@ -67,6 +77,11 @@ pub struct ShaderJobPayload {
pub iterative_input: String,
}
pub struct ColorTransformJobPayload{
pub color_processor: ColorProcessor,
}
impl Default for FootageJobPayload {
fn default() -> Self {
FootageJobPayload {
+8 -8
View File
@@ -140,29 +140,29 @@ impl Project {
/// The pipeline working colorspace (project property; ACEScg when the
/// setting is absent).
pub fn working_color_space(&self) -> oak_common::colormath::WorkingColorSpace {
oak_common::colormath::WorkingColorSpace::from_setting(
pub fn working_color_space(&self) -> oak_core::colormath::WorkingColorSpace {
oak_core::colormath::WorkingColorSpace::from_setting(
self.settings.get(SETTING_WORKING_COLOR_SPACE).map(String::as_str).unwrap_or(""),
)
}
/// The output/delivery colorspace (project property; sRGB when the
/// settings are absent).
pub fn output_color_spec(&self) -> oak_common::colormath::OutputColorSpec {
oak_common::colormath::OutputColorSpec::from_settings(
pub fn output_color_spec(&self) -> oak_core::colormath::OutputColorSpec {
oak_core::colormath::OutputColorSpec::from_settings(
self.settings.get(SETTING_OUTPUT_GAMUT).map(String::as_str).unwrap_or(""),
self.settings.get(SETTING_OUTPUT_TRANSFER).map(String::as_str).unwrap_or(""),
)
}
/// Set the pipeline working colorspace property.
pub fn set_working_color_space(&mut self, space: oak_common::colormath::WorkingColorSpace) {
pub fn set_working_color_space(&mut self, space: oak_core::colormath::WorkingColorSpace) {
self.settings
.insert(SETTING_WORKING_COLOR_SPACE.to_string(), space.as_setting().to_string());
}
/// Set the output/delivery colorspace properties.
pub fn set_output_color_spec(&mut self, spec: oak_common::colormath::OutputColorSpec) {
pub fn set_output_color_spec(&mut self, spec: oak_core::colormath::OutputColorSpec) {
self.settings
.insert(SETTING_OUTPUT_GAMUT.to_string(), spec.gamut.as_setting().to_string());
self.settings
@@ -357,8 +357,8 @@ impl Project {
_ => {}
}
// Default location: the shared disk-cache directory (single-lib:
// lives in oakcommon, used by oaknode and oakrender alike).
oak_common::filefunctions::default_disk_cache_path()
// lives in oak_core, used by oaknode and oakrender alike).
oak_core::filefunctions::default_disk_cache_path()
}
/// Copy all settings from `src` into `self` (C++
+2 -2
View File
@@ -123,9 +123,9 @@ impl SequenceBehavior {
/// Apply the default video/audio parameters (C++
/// `ViewerOutput::set_default_parameters()`; the config lookups read
/// the oakcommon config store directly).
/// the oak_core config store directly).
pub fn set_default_parameters(&mut self) {
let config = oak_common::configstore::ConfigStore::instance();
let config = oak_core::configstore::ConfigStore::instance();
let width = config.get_int(None, "DefaultSequenceWidth", 1920);
let height = config.get_int(None, "DefaultSequenceHeight", 1080);
let sample_rate = config.get_int(None, "DefaultSequenceAudioFrequency", 48000);
+9 -9
View File
@@ -16,7 +16,7 @@
//! Project (de)serialization: the C++ `ProjectSerializer` family.
//!
//! XML I/O goes through oakcommon's [`XmlReader`]/[`XmlWriter`] (direct
//! XML I/O goes through oak_core's [`XmlReader`]/[`XmlWriter`] (direct
//! Rust calls, single-lib unification). The XML shape mirrors
//! the C++ `Node::save`/`Project::save` writers (`// CPP-PARITY:
//! src/node/src/node.cpp:node::save`, `// CPP-PARITY:
@@ -32,7 +32,7 @@
use std::sync::{Arc, Mutex};
use oak_common::xmlutils::{XmlReader, XmlWriter};
use oak_core::xmlutils::{XmlReader, XmlWriter};
use oak_core::Rational;
use crate::graph::Graph;
@@ -43,7 +43,7 @@ use crate::project::{NodeRef, Project};
use crate::value::{NodeValue, ValueType};
/// Minimal XML reader surface the serializer needs (implemented over
/// oakcommon's `xmlutils`).
/// oak_core's `xmlutils`).
pub trait XmlRead {
/// Advance to the next start element; false at end/close.
fn next_start_element(&mut self) -> bool;
@@ -73,17 +73,17 @@ pub trait XmlWrite {
fn characters(&mut self, _text: &str) {}
}
/// Reader over oakcommon's [`XmlReader`].
/// Reader over oak_core's [`XmlReader`].
pub struct XmlReaderBridge {
/// The oakcommon reader.
/// The oak_core reader.
reader: XmlReader,
/// Current element name (cached).
name: String,
}
/// Writer over oakcommon's [`XmlWriter`].
/// Writer over oak_core's [`XmlWriter`].
pub struct XmlWriterBridge {
/// The oakcommon writer.
/// The oak_core writer.
writer: XmlWriter,
}
@@ -277,7 +277,7 @@ pub fn parse_node_ref(text: &str) -> Option<NodeId> {
pub fn save(project: &Project) -> crate::error::Result<String> {
use crate::error::Error;
let mut writer = XmlWriterBridge::new().ok_or(Error::Failed(
"oakcommon XML writer unavailable".to_string(),
"oak_core XML writer unavailable".to_string(),
))?;
writer.start_element("project");
@@ -510,7 +510,7 @@ pub fn load_with_id_map(
) -> crate::error::Result<(Arc<Mutex<Project>>, std::collections::HashMap<u64, NodeId>)> {
use crate::error::Error;
let mut reader = XmlReaderBridge::new(xml).ok_or(Error::Failed(
"oakcommon XML reader unavailable".to_string(),
"oak_core XML reader unavailable".to_string(),
))?;
// Detect the root element.
+1 -1
View File
@@ -570,7 +570,7 @@ fn lerp_arr4(a: &[f64; 4], b: &[f64; 4], t: f64) -> [f64; 4] {
]
}
/// Video parameters (plain data; mirrors oakcommon `VideoParams` C++
/// Video parameters (plain data; mirrors oak_core `VideoParams` C++
/// fields — the C ABI marshals field-by-field).
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct VideoParams {
+1 -1
View File
@@ -117,7 +117,7 @@ Runtime dependencies (crates.io):
order.
- `quick-xml` 0.41 — streaming XML codec for the FCPXML layer
(`src/fcpxml.rs`). Same major version the other Rust modules use
(`crates/oakcommon/Cargo.toml`).
(`crates/oak_core/Cargo.toml`).
- `oakcore-rs` (path: `../../oakcore-rs`) — shared `Rational` value type
(used by the `Rational::from_double` port and for exact FCPXML
rational-time conversion); same path dependency the other bindings use.
+3 -3
View File
@@ -756,7 +756,7 @@ dependencies = [
]
[[package]]
name = "oakcommon"
name = "oak_core"
version = "0.1.0"
dependencies = [
"image",
@@ -775,7 +775,7 @@ name = "oaknode"
version = "0.1.0"
dependencies = [
"oakcodec",
"oakcommon",
"oak_core",
"oakcore-rs",
"oakundo",
]
@@ -795,7 +795,7 @@ dependencies = [
name = "oakrender"
version = "0.1.0"
dependencies = [
"oakcommon",
"oak_core",
"oakcore-rs",
"ocio-rs",
"wgpu",
+1 -1
View File
@@ -16,7 +16,7 @@ crate-type = ["staticlib", "rlib"]
# 模块桥改直接 Rust 调用(单库化,见 docs/zh/plans/riir/single-lib.md);
# 只允许零依赖起步。确需引入的 crate 必须在 README 登记理由。
oak-core = { path = "../oak-core" }
oak-undo = { path = "../oak-undo" }
oak-undo = { path = "../oak-undo" }
oak-node = { path = "../oak-node" }
oak-render = { path = "../oak-render" }
# Error deriveDisplay + std::error::Error,见 src/error.rs);理由已登记 README。
+6 -6
View File
@@ -24,7 +24,7 @@
//! clip handle 即 `&props`)。
//!
//! 单库化后 oakrender 的 ffi 已删除:帧访问走
//! [`oak_render::texture::Texture::to_frame`] 值路径(GPU 纹理经后端
//! [`oak_core::texture::Texture::to_frame`] 值路径(GPU 纹理经后端
//! 下载、CPU 纹理克隆),帧释放随值 drop 自动发生(原
//! `texture_get_frame`/`frame_free` 句柄调用面随桩删除)。
@@ -89,7 +89,7 @@ impl ClipInstance {
props.set_one(
crate::host::PROP_CLIP_COLOURSPACE,
crate::property::Value::String(
std::ffi::CString::new(oak_render::color::pipeline_working_ofx_name()).unwrap(),
std::ffi::CString::new(oak_core::color::pipeline_working_ofx_name()).unwrap(),
),
);
}
@@ -192,8 +192,8 @@ impl ClipInstance {
scale: RenderScale,
region: Option<OfxRectD>,
) -> crate::error::Result<crate::image::Image> {
use crate::render::PIXEL_FORMAT_F32;
use crate::error::Error;
use crate::render::PIXEL_FORMAT_F32;
let _ = (time, scale);
if region.is_some() {
@@ -333,7 +333,7 @@ impl ClipInstance {
/// 输出纹理由 oakrender 侧创建并经 [`Self::set_output_texture`]
/// 挂入——本函数取该纹理的 CPU 帧(GPU 纹理经后端下载,写回后
/// 对 `Texture::Gpu` 再经
/// [`oak_render::backend::GpuContextLike::upload`] 上传),按帧
/// [`oak_core::backend::GpuContextLike::upload`] 上传),按帧
/// 参数校验 F32 与尺寸后整帧拷贝图像像素(全链路 F32;C++
/// pluginrenderer 的 `readback/wrap` 路径第 1 期以 CPU 拷贝表达,
/// GL 走 [`crate::render`] 的 `// [P2]`)。未挂输出纹理
@@ -343,8 +343,8 @@ impl ClipInstance {
&self,
image: &crate::image::Image,
) -> crate::error::Result<crate::render::Texture> {
use crate::render::{texture_get_frame, PIXEL_FORMAT_F32};
use crate::error::Error;
use crate::render::{texture_get_frame, PIXEL_FORMAT_F32};
let texture = self
.output_texture
@@ -396,7 +396,7 @@ impl ClipInstance {
/// 本 clip 的时间域(clipGetFrameRange)。
///
/// `// TODO(value-model)`:输入范围经 oakrender 帧的时间基推导
/// time_base)——随 clip 迁移到 `oak_render::texture::Texture`
/// time_base)——随 clip 迁移到 `oak_core::texture::Texture`
/// 值模型落地。
pub fn frame_range(&self) -> crate::error::Result<OfxRangeD> {
let _ = OfxRangeD::default();
+23 -23
View File
@@ -48,7 +48,7 @@ use oak_node::factory::{DynNodeConstructor, DynamicNodeMeta};
use oak_node::input::{flags as input_flags, Input};
use oak_node::node::{Category, NodeBehavior, NodeCore};
use oak_node::nodes::plugin::{
PluginInstanceHandle, PluginNode, SOURCE_CLIP, TEXTURE_INPUT,
PluginInstanceHandle, PluginNode, SOURCE_CLIP, TEXTURE_INPUT,
};
use oak_node::value::{NodeValue, ValueType};
@@ -832,10 +832,10 @@ fn set_text_param(inst: &Instance, key: &str, text: &str) {
/// executor 槽实现:JobSpec::Plugin → render_driver::render_frame。
fn execute_plugin_job(
req: &oak_render::eval::PluginJobRequest<'_>,
) -> oak_render::error::Result<oak_render::texture::Texture> {
use oak_render::error::Error;
) -> oak_render::error::Result<oak_core::texture::Texture> {
use oak_render::error::Error;
let oak_render::eval::JobSpec::Plugin {
let oak_render::eval::JobSpec::Plugin {
instance,
time,
effect_input_id,
@@ -880,7 +880,7 @@ fn execute_plugin_job(
)?;
let job = crate::render_driver::RenderJob {
time: *time,
dst: oak_render::texture::Texture::wrap_frame(dst_frame),
dst: oak_core::texture::Texture::wrap_frame(dst_frame),
src: Some(req.src.clone()),
effect_input_id: effect_input_id.clone(),
inputs: inputs.clone(),
@@ -962,9 +962,9 @@ fn shared_plugin_instance(identifier: &str) -> Option<u64> {
#[cfg(test)]
mod tests {
use super::*;
use super::*;
/// Records what the mock plugin entry saw, so the push-button test can
/// Records what the mock plugin entry saw, so the push-button test can
/// assert the kOfxActionInstanceChanged routing and its inArgs.
static PUSH_ENTRY_CALLS: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
@@ -1174,10 +1174,10 @@ mod tests {
};
let out = execute_plugin_job(&oak_render::eval::PluginJobRequest {
spec: &spec,
src: oak_render::texture::Texture::wrap_frame(frame),
src: oak_core::texture::Texture::wrap_frame(frame),
})
.expect("the GL-capable render succeeds (no MissingHostFeature purple)");
let oak_render::texture::Texture::Cpu(out_frame) = &out else {
let oak_core::texture::Texture::Cpu(out_frame) = &out else {
panic!("a CPU frame comes back");
};
let mut px = [0f32; 4];
@@ -1191,14 +1191,14 @@ mod tests {
/// 构造一个只含 push-button 参数的最小实例(直接登记进注册表)。
fn instance_with_push_button() -> u64 {
use std::ffi::{c_char, c_void};
use std::sync::atomic::AtomicU32;
use crate::descriptor::EffectDescriptor;
use crate::handle::RefBox;
use crate::host::Plugin;
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
use crate::descriptor::EffectDescriptor;
use crate::handle::RefBox;
use crate::host::Plugin;
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
use std::ffi::{c_char, c_void};
use std::sync::atomic::AtomicU32;
unsafe extern "C" fn dummy_entry(
unsafe extern "C" fn dummy_entry(
action: *const c_char,
_: *const c_void,
in_args: *mut c_void,
@@ -1289,14 +1289,14 @@ mod tests {
/// 构造一个带 parametric 参数(维度 2、自定义 range、双维 UI 颜色
/// 已配置)与一个 String 参数的实例(直接登记进注册表)。
fn instance_with_parametric() -> u64 {
use std::ffi::{c_char, c_void};
use std::sync::atomic::AtomicU32;
use crate::descriptor::EffectDescriptor;
use crate::handle::RefBox;
use crate::host::Plugin;
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
use crate::descriptor::EffectDescriptor;
use crate::handle::RefBox;
use crate::host::Plugin;
use crate::param::{ParamDef, ParamInstance, ParamSetInstance};
use std::ffi::{c_char, c_void};
use std::sync::atomic::AtomicU32;
unsafe extern "C" fn dummy_entry(
unsafe extern "C" fn dummy_entry(
_: *const c_char,
_: *const c_void,
_: *mut c_void,
+30 -30
View File
@@ -17,19 +17,19 @@
//! oakrender 桥(single-lib unification):纹理/帧值类型与渲染调用面。
//!
//! oakrender 的 C ABI 已删除(单库化):纹理是
//! [`oak_render::texture::Texture`]value enum,无句柄),CPU 帧是
//! [`oak_render::texture::Frame`]。本 crate 的 render 驱动与 GL suite
//! [`oak_core::texture::Texture`]value enum,无句柄),CPU 帧是
//! [`oak_core::texture::Frame`]。本 crate 的 render 驱动与 GL suite
//! 直接持值类型:
//!
//! - [`Texture`] = [`oak_render::texture::Texture`](值别名;
//! - [`Texture`] = [`oak_core::texture::Texture`](值别名;
//! clone 即引用语义,drop 自动释放后端 token——原 `texture_free`/
//! `frame_free` 调用面随值模型删除);
//! - [`Frame`] = [`oak_render::texture::Frame`](值别名);
//! - [`Renderer`] = `Arc<dyn oak_render::backend::GpuContextLike>`
//! - [`Frame`] = [`oak_core::texture::Frame`](值别名);
//! - [`Renderer`] = `Arc<dyn oak_core::backend::GpuContextLike>`
//! (渲染器即 oakrender 后端上下文,facade 经
//! [`oak_render::backend::GpuContext::create`] 创建);
//! [`oak_core::backend::GpuContext::create`] 创建);
//! - [`VideoParams`] 直接别名 oakrender 的
//! [`oak_render::frame::VideoParamsPod`](同布局 POD);
//! [`oak_core::frame::VideoParamsPod`](同布局 POD);
//! - 像素格式常量直接别名 [`oak_core::PixelFormat`]。
//!
//! 保留桩(GPU 相关、wgpu 模型无直接 Rust 等价物):
@@ -42,7 +42,7 @@
/// `oakrender_video_params` POD — single-lib unification: aliases the
/// oakrender crate's struct (identical layout;
/// include/render/renderer.h:78).
pub type VideoParams = oak_render::frame::VideoParamsPod;
pub type VideoParams = oak_core::frame::VideoParamsPod;
/// olive::PixelFormat::Format 的 f32 值。
pub const PIXEL_FORMAT_F32: i32 = oak_core::PixelFormat::F32 as i32;
@@ -51,13 +51,13 @@ pub const PIXEL_FORMAT_U8: i32 = oak_core::PixelFormat::U8 as i32;
/// oakrender 渲染器(后端上下文;Arc 共享,GPU 纹理据此 upload/
/// download/blit——无需独立渲染器句柄)。
pub type Renderer = std::sync::Arc<dyn oak_render::backend::GpuContextLike>;
pub type Renderer = std::sync::Arc<dyn oak_core::backend::GpuContextLike>;
/// oakrender 纹理(值型;GPU 或 CPU 包装)。
pub type Texture = oak_render::texture::Texture;
pub type Texture = oak_core::texture::Texture;
/// oakrender CPU 帧(值型)。
pub type Frame = oak_render::texture::Frame;
pub type Frame = oak_core::texture::Frame;
// ---- 桥调用面(值型实现;原 CHandle 桩随单库化重写)----------------------
@@ -138,10 +138,10 @@ pub fn texture_id(_texture: &Texture) -> i32 {
0
}
/// 渲染器是否为 OpenGL 后端([`oak_render::backend::BackendKind::Gl`]
/// 渲染器是否为 OpenGL 后端([`oak_core::backend::BackendKind::Gl`]
/// 原 `renderer_is_open_gl` 的句柄形态改为后端上下文 kind 查询)。
pub fn renderer_is_open_gl(renderer: &Renderer) -> bool {
renderer.kind() == oak_render::backend::BackendKind::Gl
renderer.kind() == oak_core::backend::BackendKind::Gl
}
/// A GL-kind marker context for [`RenderJob::renderer`]. The field's only
@@ -154,22 +154,22 @@ pub fn renderer_is_open_gl(renderer: &Renderer) -> bool {
/// oak-worker, where gl_bridge creates its own offscreen context.
pub struct GlKindMarker;
impl oak_render::backend::GpuContextLike for GlKindMarker {
fn kind(&self) -> oak_render::backend::BackendKind {
oak_render::backend::BackendKind::Gl
impl oak_core::backend::GpuContextLike for GlKindMarker {
fn kind(&self) -> oak_core::backend::BackendKind {
oak_core::backend::BackendKind::Gl
}
fn destroy_texture(&self, _token: u64) {}
fn upload(&self, _token: u64, _frame: &oak_render::texture::Frame) -> oak_render::error::Result<()> {
fn upload(&self, _token: u64, _frame: &oak_core::texture::Frame) -> oak_render::error::Result<()> {
Ok(())
}
fn download(&self, _token: u64) -> oak_render::error::Result<oak_render::texture::Frame> {
Ok(oak_render::texture::Frame::new())
fn download(&self, _token: u64) -> oak_render::error::Result<oak_core::texture::Frame> {
Ok(oak_core::texture::Frame::new())
}
fn blit(
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_render::color::ColorProcessor>,
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_core::color::ColorProcessor>,
) -> oak_render::error::Result<()> {
Ok(())
}
@@ -181,9 +181,9 @@ mod tests {
/// 测试渲染器:最小 GpuContextLike 假实现(无 GPU 适配器需求)。
struct FakeGpu;
impl oak_render::backend::GpuContextLike for FakeGpu {
fn kind(&self) -> oak_render::backend::BackendKind {
oak_render::backend::BackendKind::Cpu
impl oak_core::backend::GpuContextLike for FakeGpu {
fn kind(&self) -> oak_core::backend::BackendKind {
oak_core::backend::BackendKind::Cpu
}
fn destroy_texture(&self, _token: u64) {}
fn upload(&self, _token: u64, _frame: &Frame) -> oak_render::error::Result<()> {
@@ -193,10 +193,10 @@ mod tests {
Ok(Frame::new())
}
fn blit(
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_render::color::ColorProcessor>,
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_core::color::ColorProcessor>,
) -> oak_render::error::Result<()> {
Ok(())
}
+1 -1
View File
@@ -269,7 +269,7 @@ unsafe extern "C" fn clip_get_property_set(clip: *mut c_void, out: *mut *mut c_v
/// HS:2003-2049`getImage` 失败 → Failed)。
///
/// `// TODO(clip)`fetch_image 待 clip 迁移到
/// `oak_render::texture::Texture` 值模型(当前帧访问为本地桩)。
/// `oak_core::texture::Texture` 值模型(当前帧访问为本地桩)。
unsafe extern "C" fn clip_get_image(
clip: *mut c_void,
time: c_double,
+11 -11
View File
@@ -334,26 +334,26 @@ mod tests {
assert!(gl_ctx().is_none());
// 最小 GpuContextLike 假实现(无 GPU 适配器需求)。
struct FakeGpu;
impl oak_render::backend::GpuContextLike for FakeGpu {
fn kind(&self) -> oak_render::backend::BackendKind {
oak_render::backend::BackendKind::Cpu
impl oak_core::backend::GpuContextLike for FakeGpu {
fn kind(&self) -> oak_core::backend::BackendKind {
oak_core::backend::BackendKind::Cpu
}
fn destroy_texture(&self, _token: u64) {}
fn upload(
&self,
_token: u64,
_frame: &oak_render::texture::Frame,
_frame: &oak_core::texture::Frame,
) -> oak_render::error::Result<()> {
Ok(())
}
fn download(&self, _token: u64) -> oak_render::error::Result<oak_render::texture::Frame> {
Ok(oak_render::texture::Frame::new())
fn download(&self, _token: u64) -> oak_render::error::Result<oak_core::texture::Frame> {
Ok(oak_core::texture::Frame::new())
}
fn blit(
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_render::color::ColorProcessor>,
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_core::color::ColorProcessor>,
) -> oak_render::error::Result<()> {
Ok(())
}
@@ -369,7 +369,7 @@ mod tests {
let got = gl_ctx().unwrap();
assert_eq!(
got.renderer.kind(),
oak_render::backend::BackendKind::Cpu
oak_core::backend::BackendKind::Cpu
);
assert!(got.output_texture.is_dummy());
assert_eq!(got.gl_pixel_depth, "OfxBitDepthFloat");
+1 -1
View File
@@ -20,7 +20,7 @@
//! 运行时装配成 oak-test-plugin.ofx.bundle):filter 上下文、
//! Double 参数 gain、双 clipSource/Output)。插件未构建时相关
//! 用例经 [`skip`] 提前返回。
//! 单库化后像素路径经 oakrender 值模型(`oak_render::texture::Texture`
//! 单库化后像素路径经 oakrender 值模型(`oak_core::texture::Texture`
//! 驱动;渲染 goldens 待该迁移落地。
use std::path::PathBuf;
+3 -3
View File
@@ -42,8 +42,8 @@ use std::sync::Arc;
use oak_core::{PixelFormat, Rational};
use oak_plugin::host::Host;
use oak_plugin::render::{Renderer, Texture};
use oak_render::backend::{BackendKind, GpuContextLike};
use oak_render::texture::Frame;
use oak_core::backend::{BackendKind, GpuContextLike};
use oak_core::texture::Frame;
const GL_PLUGIN_ID: &str = "org.oak.test-plugin.gl";
@@ -67,7 +67,7 @@ impl GpuContextLike for FakeGlRenderer {
&self,
_src: u64,
_dst: u64,
_processor: Option<&oak_render::color::ColorProcessor>,
_processor: Option<&oak_core::color::ColorProcessor>,
) -> oak_render::error::Result<()> {
Ok(())
}
+1 -1
View File
@@ -34,7 +34,7 @@ use oak_node::node::{NodeBehavior, NodeCore};
use oak_node::traverser::{EvalRequest, Traverser};
use oak_node::value::{NodeValue, ValueType};
use oak_plugin::host::Host;
use oak_render::texture::Texture;
use oak_core::texture::Texture;
const PLUGIN_ID: &str = "org.oak.test-plugin";
const IDENTITY_ID: &str = "org.oak.test-plugin.identity";
+2 -2
View File
@@ -623,7 +623,7 @@ dependencies = [
]
[[package]]
name = "oakcommon"
name = "oak_core"
version = "0.1.0"
dependencies = [
"image",
@@ -641,7 +641,7 @@ version = "0.1.0"
name = "oakrender"
version = "0.1.0"
dependencies = [
"oakcommon",
"oak_core",
"oakcore-rs",
"ocio-rs",
"wgpu",
-1
View File
@@ -10,7 +10,6 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
oak-core = { path = "../oak-core" }
oak-common = { path = "../oak-common" }
# Direct Rust calls (single-lib unification): the decode bridge calls
# oakcodec's ffi, the node bridge calls oaknode's ffi. Both directions
# are acyclic (oakcodec → oakcore-rs/oak-ffmpeg-link; oaknode → oakcodec).
+15 -11
View File
@@ -62,12 +62,10 @@ frozen, implemented verbatim by `src/ffi.rs`.
```
src/
lib.rs crate doc + module map
error.rs error codes (mirrors include/render/error.h)
error.rs re-exports oak_core::error (the OAKRENDER_* codes stay as
the public-code contract)
handle.rs refcounted-handle scaffolding (facade entry points only)
texture.rs Texture value type (wraps backend textures / CPU frames)
frame.rs VideoParamsPod + Frame helpers
cache.rs PlaybackCache / FrameHashCache family + C++-parity disk state
color.rs ColorProcessor over ocio-rs + default config + LUT library
manager.rs RenderManager singleton + lifecycle + disk cache
ticket.rs Ticket arena, params, exactly-once completion delivery
worker.rs JobDispatch seam + thread-free InlineDispatcher (audio
@@ -84,22 +82,28 @@ src/
grow-on-demand segment geometry (S3)
autocacher.rs PreviewAutoCacher
eval.rs RenderHooks impl: the CPU evaluation seam
backend.rs wgpu device/queue/texture management + DisplayRenderer
shaderfx.rs effect GLSL→WGSL translation (naga) + std140 uniform
packing + the effect runner
copier.rs Render-side project copy client (bridge::node)
cancelatom.rs the cancellation primitive
bridge/ C ABI imports: node.rs, common.rs, codec.rs (dlsym-resolved)
ffi.rs include/render/*.h export layer
tests/ contract + golden tests (common/ has shared helpers)
```
The value/GPU types — `backend.rs` (wgpu device/queue/texture management
+ DisplayRenderer), `color.rs` (ColorProcessor over ocio-rs + default
config + LUT library), `texture.rs`, `frame.rs` and the `commonutil.rs`
config helpers — moved to `oak-core` in the oak-common/oak-core merge;
this crate uses them as `oak_core::*`.
## Hard rules
1. `CHandle` only appears at the facade boundary: the crate's internal
calls pass Rust types directly; `handle::make_owned`/`get`/`get_mut`
are the facade entry points the oakengine stubs call.
2. No `unsafe` outside `backend.rs` (GPU FFI), `bridge/`, and the M15
process-isolation transport (`ipc.rs` / `procpool.rs`: POSIX shm +
SPSC rings; every block carries its own SAFETY comment).
2. No `unsafe` outside `handle.rs`, the evaluation seam (`eval.rs`), and
the M15 process-isolation transport (`ipc.rs` / `procpool.rs`: POSIX
shm + SPSC rings; every block carries its own SAFETY comment). GPU
unsafe lives in `oak-core`'s `backend.rs`.
3. F32 + ACEScg pipeline invariants are asserted in tests, not in
comments (see tests/pipeline_test.rs).
@@ -118,7 +122,7 @@ tests/ contract + golden tests (common/ has shared helpers)
`Error::Failed` for a processor; the CPU path applies the processor
in float. `oakrender_color_processor_create_transform` resolves the
destination transform against the default config's reference role
until the oakcommon color-transform bridge lands.
until the oak_core color-transform bridge lands.
- **Worker process isolation** — landed in M15: `procpool.rs`
(`ProcessDispatcher`) + `scheduler.rs` + `ipc.rs` drive real
oak-worker processes (spawn, handshake, batched renders into
+2 -2
View File
@@ -297,9 +297,9 @@ mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::frame::VideoParamsPod;
use crate::texture::{Frame, Texture};
use crate::worker::{InlineDispatcher, JobDispatch};
use oak_core::frame::VideoParamsPod;
use oak_core::texture::{Frame, Texture};
fn frame_producer() -> crate::ticket::Producer {
Arc::new(|_, _| {
+1 -1
View File
@@ -261,7 +261,7 @@ pub struct PlaybackCache {
impl PlaybackCache {
/// New cache for `owner` (C++ `PlaybackCache(parent)`).
pub fn new(kind: CacheKind, owner: OwnerIdentity) -> Self {
let disk_dir = crate::commonutil::default_disk_cache_path();
let disk_dir = oak_core::commonutil::default_disk_cache_path();
Self {
kind,
owner,
+3 -3
View File
@@ -18,9 +18,9 @@
//! flag shared between a render/encode caller and its worker.
//!
//! Single-lib unification (see `docs/zh/plans/riir/single-lib.md`): the
//! implementation moved to oakcommon; this module re-exports it so the
//! implementation moved to oak_core; this module re-exports it so the
//! render ffi's `oakrender_cancelatom_*` exports (and their C-ABI
//! consumers, e.g. oakcodec) keep working unchanged.
/// Shared cancellation atom (oakcommon).
pub use oak_common::cancelatom::CancelAtom;
/// Shared cancellation atom (oak_core).
pub use oak_core::cancelatom::CancelAtom;

Some files were not shown because too many files have changed in this diff Show More