- oakengine/export.h establishes the OAKENGINE_API visibility macros;
include/oakengine/ipc.h is the first pure-C surface (41 functions:
shm, frame slot pool, and the worker IPC messages as POD<->JSON
build/parse), implemented in engine/src/capi/
- the IPC implementations move to engine/src/oliveimpl (namespace
olive::engine::internal::ipc); engine/render/ipc/*.h are rebuilt as
same-name/same-API wrapper classes forwarding across the C boundary
- FrameSlotMeta is shared with the C header verbatim so the app/worker
wire format (v1) is bit-identical; static_asserts pin sizeof and
field offsets
- spscringbuffer.h moves to include/oakengine/ as an inline-only
header (no symbols, not ABI)
- new pure-C test oakengine_ipc_test (make_oakengine_test, no GL)
covers shm, frame pool, message round-trips and the layout asserts;
full gtest suite stays green (1986 tests)
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
- NodeFactory's menu creation moves to UI-side widget/menu/factorymenu
(the factory only exposes its node library read-only now)
- DiskManager's cache-settings dialog is created through a registered
std::function handler (registered by Core at startup)
- OlivePluginInstance creates progress UIs through a
PluginProgressReporter interface (Null fallback headless) and queries
the active viewer through a provider callback, both registered by Core
- factory.h, diskmanager and pluginSupport no longer reference any
widget//dialog//panel//window headers or classes
EngineCore (new app/coreengine.{h,cpp}) owns every engine-safe part of
the old Core singleton: CoreParams, lifecycle of the engine managers,
UndoStack, tool/snapping/timecode state, locale, autorecovery, recent
projects, footage filters, clipboard, project registry, type
declarations, and the proxy toggle. UI dependencies are inverted
through hooks instead: status-bar/cache-full signals and std::function
handlers for image-sequence confirmation, footage relink, OTIO import,
project save/close and layout load (same pattern as
Config::ErrorHandler).
Core (app/) now derives from EngineCore and keeps only UI behavior:
the main window, dialogs, panel heuristics, import/export flows and
project lifecycle presentation. Its public API is unchanged (all
inherited), and Core::instance() covariantly static_casts the engine
singleton. The render worker constructs EngineCore directly, making it
the first binary that no longer needs the UI side of Core.
~25 engine call sites move from core.h to coreengine.h; a dozen more
drop a vestigial core.h include (gaining direct includes for symbols
they were borrowing transitively). Full gtest suite green (1986 tests,
0 failures).
- slider DisplayType enums sink to node/sliderdisplaytype.h (canonical
engine home); FloatSlider/RationalSlider alias them for compatibility
- DropWithoutSequenceBehavior enum sinks to common/dropworkflowbehavior.h
- Config errors now go through a registered ErrorHandler hook instead of
QMessageBox with a MainWindow parent; the style default no longer
depends on the UI style manager
- MainWindowLayoutInfo moves to node/project/serializer/ and its panel
dependency is reduced to a plain std::map alias (PanelLayoutInfo),
breaking the engine -> PanelWidget -> KDDockWidgets chain
- ProjectImportErrorDialog moves from task/ to dialog/projectimport/
- remove confirmed-redundant UI includes and give project.h/import.h/
project.cpp the direct includes they were borrowing transitively
- project.h includes folder/sequence headers directly (it used both
types in its own API all along)
core was added with EXCLUDE_FROM_ALL, which also excluded its install
rules, so liboakcore.so never made it into packages. It now installs to
the platform libdir (/usr/lib on this machine, verified via DESTDIR).
Even statically linked runtime libraries must not leak symbols: the
linker version script now whitelists oakcore_* only. nm reports 188
oakcore_* exports and zero anything else.
liboakcore is now a shared library that exposes only a C ABI:
- every value class (Rational, TimeRange, Color, Bezier, AudioParams,
SampleBuffer) and the free-function groups (StringUtils, fraction
utils, Timecode) is wrapped in an opaque-handle C API under
core/include/olive/core/oakcore/ (init/copy/free + self-first
functions), implemented in core/src/capi/
- consumers keep the original C++ API unchanged through same-name
wrapper classes that hold the handle and forward across the C
boundary; original implementations moved to core/src/oliveimpl
(namespace olive::core::internal) and are hidden from export
- TimeRangeList/TimeRangeListFrameIterator are reimplemented inline
over the wrapper (iterators/containers don't cross C ABI)
- generic Value container stays internal (unused by consumers) and is
no longer part of the public umbrella header
- hidden visibility + OAKCORE_BUILD export macro; nm shows zero
olive::* symbols exported
- install into the platform's standard libdir (GNUInstallDirs);
Windows DLLs next to the executables, macOS into the app bundle
- TimelineWorkArea::in/out/length now return by value: the wrapped
TimeRange getters return values, and forwarding them through const
references dangled (found via RenderWorkerFootageTest crash)
- tests: 9 new pure C ABI test executables (oakcore_*_test) covering
every public C function; 4 stale legacy core tests removed (they
targeted a long-renamed API and were never built due to a malformed
option() that also kept OLIVECORE_BUILD_TESTS off)
- CI/CD: oakcore.dll staged for NSIS, liboakcore.so added to the
AppImage validation list, build-tree DLL copies on Windows
The load/save of the per-footage custom divider attribute was lost when
the proxy changes were re-applied during the three-way commit split;
ProxyManager.FootagePersistsCustomProxyParams caught it.
- oak-render-worker now builds from worker/ (own CMakeLists.txt) as a
peer of app/; RenderWorkerPool resolves the new build-tree location
- deduplicated the Linux install() rules for the worker
- worker-spawning tests resolve build/worker instead of build/app
- cd.yml: Windows staging copies the worker from its new output path
- media color primaries/transfer tags now flow from the FFmpeg probe
through VideoParams into Footage::get_colorspace_to_use(); precedence
is user override > media tags > project default
- export nclc tags derive from the output colorspace (PQ/HLG/BT.2020,
P3, sRGB, Rec.601, Rec.709) instead of hardcoded BT.709
- new OCIO Color Grading (Log) node (lift/gamma/gain) and White Balance
node (kelvin temperature + tint, HDR-safe)
- LUT whitelist extended to 9 OCIO-supported formats
- waveform scope gains an RGB parade mode (GPU and software paths)
- tests updated for the new colorspace precedence
- playback timer uses the audio output device as its master clock: the
PortAudio callback counts consumed frames (including underrun
zero-fill) so video cannot drift away from what is heard; wall clock
remains as fallback when no clocked output is running
- output clock compensates for device output latency; new Preferences >
Audio buffer size setting (0 = auto)
- SampleBuffer::speed() now uses linear interpolation instead of
nearest-neighbor sampling
- regression tests: audio-clock driven timer (fwd/rev/speed), wall
clock fallback, interpolation correctness
- FootageJob::should_use_proxy() centralizes the proxy decision; worker
pre-decode now honors the render mode so exports always decode the
original media (previously every frame was pre-decoded from proxies)
- global Tools > Use Proxy Media toggle with footage invalidation
- ffmpeg -progress parsing for real percentage feedback while generating
- divider mode (1/2, 1/4, 1/8 of source resolution) with UI, proxy
filename tags and per-footage persistence (pdivider)
- Media Offline warning slat rendered for missing footage
- regression tests: export isolation, relink invalidation, offline slat,
progress parsing, divider arguments
Fake tests rewritten to assert real behavior:
- audio_smoke: conversion tests now actually Convert() samples and verify
output; waveform length/summary assertions tightened to exact values
- plugin_format_conversion: RowBytes/U8ToU16/LoadImageFile now call real
production code (VideoParams::GetBytesPerPixel, sws scaler, OIIO decode
of tests/img.png with known pixel values)
- core_color: HSV round trip now verifies fromHsv(toHsv(c)) == c instead
of comparing toHsv against its own accessors
- core_bezier/node_inputimmediate: expected values replaced with
independently derived constants instead of re-running the code under test
- common_commandlineparser/common_debug/common_jobtime: capture
stdout/stderr/qDebug and assert actual output content
- proxy_manager: ProxyFinished test now drives a real proxy job instead of
emitting the signal itself; proxy_dialog/panel/proxy/preferences/timeruler
tests assert real widget state
- viewer_smoke/preview_autocacher/render_misc: zero-assertion tests given
observable-state assertions or removed where nothing is observable
Duplicates removed:
- plugin_smoke_test.cpp: 18 tests duplicated from plugin_paraminstance /
plugin_support_* / plugin_renderer_readback (751 -> 180 lines)
- module_smoke HumanStrings tests covered precisely by ui_humanstrings_test
- render_misc duplicate kDefaultInterpolation constant check
Removed by policy (skip allowed, never disabled):
- all DISABLED_ prefixes: re-enabled as real offscreen tests or deleted
- ffmpeg_decoder_hw: hardcoded personal path replaced with
OAK_TEST_HW_DECODE_FILE env var, GTEST_SKIP when unset
Also:
- config_test: restore Config defaults after run (cross-test pollution)
- render_worker_footage: drop /tmp debug-output scaffolding
- previewaudiodevice construction test asserts the real bugfix
- OpenGLRenderer: hold the viewer-owned QOpenGLContext in a QPointer so
DestroyInternal() safely skips it when the context has already been
destroyed by Qt's shared-context lifecycle. Fixes a SIGSEGV when the
full gtest suite ran MainWindow.ConstructsOffscreenWithPanelsAndMenus
after earlier viewer tests.
- PreviewAudioDevice: add SetParams() deriving bytes_per_frame from the
audio format (bytes per sample * channel count) instead of staying 0.
Enzo GD, administrator of the Olive Facebook user group, gave this
project generous promotional support in its early days. The shared body
text now carries a special-thanks line in both the About dialog and the
first-run Welcome dialog; zh_CN translation included.
- Runners: ubuntu-24.04, macos-15, windows-latest (free tier) instead
of WarpBuild 8x/12x instances
- openfx-misc parallelism adapts to nproc (GitHub runners have fewer
vCPUs than the WarpBuild instances)
- Schedule a daily 03:17 UTC run so ccache entries never hit GitHub's
7-day cache expiry and system dependencies stay current
WarpBuild's cache service is billed; GitHub's cache is free (10 GB
repo limit with LRU eviction, ample for ccache entries). All three
cache sites — ccache for the matrix and dynamic-backend jobs, plus the
macOS OpenTimelineIO build — now use actions/cache@v4, which also
removes the Windows-specific fallback step.
Point CCACHE_DIR and the cache action at a fixed workspace directory
instead; checkout cleans untracked files before the restore, so the
ordering is safe.
- Restore ccache with WarpBuilds/cache@v1 on all three OS and the
dynamic-backend job (per-OS keys with branch fallback), and wire it
up through CMAKE_C/CXX_COMPILER_LAUNCHER; builds print ccache -s so
hit rates are visible in CI logs
- Cache the macOS OpenTimelineIO build (keyed on v0.16.0) and skip the
rebuild on cache hit
- Install ccache via apt/brew/MSYS2 as needed; CCACHE_MAXSIZE=1G
- Runner sizes 16x/32x -> 8x (macOS 12x kept); openfx-misc make -j8 to
match
The test assumed msleep(100) measures close to 100 ms (expecting ~2
frames at 24fps vs ~6 at 60fps). On the loaded macOS CI runner the
24fps leg overslept ~2.5x, producing 6 frames at both rates. Measure
the actual interval per leg and compare against the expected frame
count with a 1-frame tolerance, and use highly distinct timebases
(1fps vs 240fps) for the rate-ordering check.
- Linux CD builds no longer disable Vulkan: the AppImage, deb, rpm and
Arch packages now build and ship liboakvulkan (the AppImage deploys
it via linuxdeploy --library so libvulkan is bundled too, and the
verify step checks both backend libraries); deb/rpm dependencies gain
libvulkan1/vulkan-loader; the Arch PKGBUILD gains vulkan-headers
- macOS CD installs vulkan-loader and exports VULKAN_SDK so
find_package(Vulkan) locates the Homebrew loader (previously the
Vulkan backend silently never built there)
- ffmpeg_bridge now installs to the standard lib directory with
/../lib RPATH instead of the non-standard ffmpeg_bridge/bin
layout (verified: editor, worker, oakgl and oakvulkan all resolve it)
- build guides (EN/ZH) document the macOS Vulkan backend dependencies
and the VULKAN_SDK variable
Version is no longer hardcoded: at configure time CMake uses the tag
name when HEAD is exactly on a tag (leading "v" stripped), otherwise
the first 8 hex digits of the commit hash. When git is unavailable
(e.g. source tarball) it falls back to the contents of version.txt,
which is now the single place to bump the release version.
Also remove the temporary qDebug() flood in the audio playback path
(ViewerWidget::QueueNextAudioBuffer / ReceivedAudioBufferForPlayback,
AudioManager::PushToOutput).
DirectoryIsValid() ignored try_to_create_if_not_exists and always
called mkpath(). On the Windows CI runner (which may create dirs at
the drive root) PathWidget validation both created bogus directories
and never flagged invalid paths; on Linux the mkpath just failed.
- filefunctions: only mkpath when try_to_create_if_not_exists is set
- TaskProjectLoadTest: Project::set_filename() stores native
separators on Windows, normalize before comparing paths
- DialogProjectProperties: use a nonexistent file inside a temp dir
instead of a fixed /definitely/... path that prior tests may have
partially created on a writable drive
QIcon::addFile() differs between Qt versions in whether entries for
nonexistent files keep the icon non-null (Ubuntu CI failure). Assert
on what matters: no usable pixmap or size is produced.
ColorManager::Init() (run by every Project construction) dereferenced
GetDefaultConfig() unconditionally. Any Project created before
SetUpDefaultConfig() crashed inside OCIO getCanonicalName on a null
config — the Windows CI SEGFAULT, where the suite order runs a
Project-creating test first. Reproduced locally by running
MainWindowLayoutInfo.AccessorsStoreAndRetrieve as the first suite.
The test forces GraphicsBackend=opengl and builds a full MainWindow
with QOpenGLWidget-based viewer panels. On headless platforms whose
QPA cannot provide a GL context (Ubuntu/Windows CI on offscreen), the
app's GL calls crash on a non-current context (SEGFAULT in CI on
both). Probe with QOpenGLContext+QOffscreenSurface and GTEST_SKIP when
GL is unusable; the test still runs on platforms with real GL.
- RenderManager: GPU-side members (context_, decoder_cache_,
shader_cache_, auto_cacher_, worker_pool_, decoder_clear_timer_) were
left uninitialized when the configured graphics backend is unknown
(e.g. dummy); ViewerWidget then dereferenced garbage and crashed.
Initialize them at declaration
- CurveView::SelectKeyframesOfInput ignored its reference parameter and
selected keyframes of every connected track; select only the
requested track's keyframes
- SeekableWidget::SeekToScenePoint dereferenced GetViewerNode()
unconditionally; skip the playhead update when no viewer is connected
- LoadOTIOTask: unknown root schema leaked the freshly allocated
project_ (delete + reset; OTIO is not enabled in local builds so this
file is compile-verified by inspection only)
Locked by new tests: RenderManagerDummyBackend,
TimeRuler.SeekToScenePointWithoutViewerIsNoOp,
CurveViewTest.SelectKeyframesOfInputSelectsOnlyRequestedTrack
format_, video_codec_, audio_codec_, subtitle_sidecar_fmt_ and
subtitles_codec_ were left indeterminate by the constructor; reading
them before the corresponding Enable* call was UB. Initialize to the
kFormatCount/kCodecCount invalid sentinels.
- all 19 panel classes: construction, titles, context/signal wiring,
save/load data round-trips
- MainWindowLayoutInfo XML round-trip, MainStatusBar, offscreen
MainWindow construction with standard panels and menus
- RatioDialog parsing (decimal and : / ; separators), validation
- MainWindowLayoutInfo: toXml() iterated open_sequences_ for the
<viewers> section and fromXml() never parsed it, so open footage
viewers were lost on layout save/load
- PanelWidget never set QObject::objectName, so
PanelManager::GetPanelWithName() always returned nullptr and every
caller (layout restore, NodeView param panel lookup) was dead code
- PanelManager::DestroyInstance() left instance_ dangling (UAF on any
later RegisterPanel), unlike the other singletons
- FrameRateComboBox: RepopulateList() left current index at -1 on first
fill, so GetFrameRate() returned 0/1 instead of the displayed first
entry until something called SetFrameRate()
- HandMovableView: default_drag_mode_ was never initialized; an early
Core::ToolChanged signal would setDragMode() with it (UB)
- SequencePreset::Save() wrote element "interlacing_" while Load() read
"interlacing", losing the interlacing mode on preset round-trips;
Save() now writes "interlacing" and Load() accepts both for backward
compatibility with existing preset files
- ExportFormatComboBox: current_ was never initialized (UB on GetFormat
before first user selection)
- ExportSubtitlesTab::SetSidecarEnabled() called setEnabled instead of
setChecked, so restored export params could never re-enable sidecar
subtitles
- h264section.h: static const int constants were odr-use unsafe (link
error when referenced); changed to static constexpr