Ubuntu (FFmpeg 7.0) build failure: the public FB_PIX_FMT_* values were
hardcoded to AVPixelFormat enum values, but those shift between FFmpeg
releases (new formats are inserted mid-enum: X2RGB10 in 7.0,
RGBF16/GRAYF16 later), so the static_asserts in internal.h failed and
RGBF16LE/GRAYF16LE did not exist at all.
The FB pixel format values are now fixed identifiers translated inside
the library by pixel format name (stable across releases):
- fb::PixFmtToAV/PixFmtFromAV resolve the static table by name;
formats a decoder produces that have no static identifier (hardware
downloads like nv12/p010le/p210le) receive process-local dynamic ids
>= 1000 so they keep round-tripping through the API (scaler etc.)
instead of collapsing to NONE and crashing sws.
- All crossing points translated: scaler, frame get/set format,
decoder/probe stream info, encoder config and write_video_frame,
fb_pix_fmt_* utilities, fb_find_best_pix_fmt_of_list (which also
reinterpret_cast the caller's list; now translated element-wise and
properly NONE-terminated).
- nv12 and p010le get static identifiers as the common hardware
download formats.
- Hardware frame detection no longer compares pixel formats (breaks
across format spaces); it uses the existing fb_frame_is_hw().
macOS test failures (FFmpegBridgeEncoder.WritePngVideoAndProbeBack,
WritePcmAudioAndProbeBack): config.filename held a dangling pointer to
a temporary QByteArray (UB; happened to work on Linux). Keep the
QByteArray alive for the encoder's lifetime.
Windows (MSYS2 UCRT64) build failure: timecodefunctions.h used int64_t
without including <cstdint> (previously pulled in transitively).
Full gtest suite: 584 passed.
ClipBlock::kBufferIn is declared as NodeValue::kNone with no value
hint, so when a footage node is connected straight to a clip's buffer
input (no effect node in between), the traverser has no type to look
up in the footage's value table and falls back to its last entry. A
footage pushes its video texture first and its audio samples last, so
a video clip ended up fed with audio samples, produced no texture and
the preview went silently black. With any node in between, the table
only carries the passthrough texture, which is why the bug only showed
on direct connections.
Make Node::GetValueHintForInput virtual and override it in ClipBlock
to prefer the value type matching the clip's track (kTexture on video
tracks, kSamples on audio tracks).
Regression tests cover the exact application render path
(PreviewAutoCacher -> RenderManager -> RenderWorkerPool ->
oak-render-worker) for both the direct and indirect cases, the hint
following the track type, and viewer display widgets. The direct-case
test reproduces the black frame without the fix and passes with it.
Full suite: 584 passed.
- Replace direct FFmpeg usage in the test suite (channel layout masks,
pixel/sample format constants, AVFrame field access, sws_scale) with
the bridge equivalents and the olive::AVFrame adapter
- Drop CoreRational.ToAVRational (API removed with core's FFmpeg
dependency) and the GetSwsColorspaceFromAVColorSpace tests (helper
moved inside the bridge)
- New ffmpeg_bridge_test.cpp exercises the C API directly: constants vs
core, error strings, pixel format utilities, frames/packets, scaler,
resampler, audio graph tempo processing, probe/decoder round-trip on
tests/demo.mp4 (including hw-frame transfer), SRT subtitle reading,
and encoder end-to-end tests (PNG video and PCM audio probed back)
Replace every direct FFmpeg call in the editor and the render worker
with the pure C ffmpeg_bridge API, wrapped in thin C++ adapters that
preserve the original interfaces:
- avframeptr.h: olive::AVFrame adapter around FBFrame handles
- ffmpegutils: format conversion helpers on FB_* constants; the int
overload is renamed GetCompatibleBridgePixelFormat to avoid a silent
overload-resolution trap with the PixelFormat enum
- ffmpegdecoder/ffmpegencoder: rewritten as handle-based adapters over
FBDecoder/FBProbe/FBEncoder/FBScaler/FBResampler
- audioprocessor: FBAudioGraph push/pull adapter
- pluginrenderer/OliveClip: sws/pixdesc usage converted to FBScaler and
fb_pix_fmt_* queries
- AudioParams/channel layouts are plain uint64_t masks everywhere
Build integration: the root project no longer links FFMPEG directly;
only ffmpeg_bridge does. Binaries resolve the bridge library at runtime
via @loader_path inside the macOS app bundle (copied there post-build)
and via $ORIGIN/../ffmpeg_bridge/bin on Linux; on Windows the DLL is
installed next to the executables, so packages on all three platforms
ship the bridge library.
ffmpeg_bridge gains the extra API the adapters need:
fb_frame_make_writable, fb_decoder_get_format_duration,
fb_resampler_convert_frame, FB_PIX_FMT_YUV440P, SRT validation in
fb_probe_read_subtitle_stream, packed/planar fixes in
fb_encoder_write_audio, and a component-size fix in
fb_pix_fmt_component_size.
The library lives outside of app/ and is the only component that includes
FFmpeg headers or links FFmpeg libraries. All objects (frames, packets,
decoder/encoder instances, scalers, resamplers, audio filter graphs) are
identified by opaque handles and never leave the library.
Public surface (include/ffmpeg_bridge/ffmpeg_bridge.h):
- FBFrame/FBPacket: AVFrame/AVPacket wrappers with field accessors
- FBDecoder: demux+decode instance with hwaccel fallback logic
- FBProbe: file probing, per-stream details, subtitle reading
- FBScaler/FBResampler/FBAudioGraph: swscale/swresample/avfilter wrappers
- FBEncoder: full export encoder (video filter graph, audio resampling,
subtitles) ported from FFmpegEncoder
- FB_* constants mirror AV_* values, verified by static_asserts against
the real FFmpeg headers inside the library
Two small bugs in the ported encoder were fixed: the resampler is now
properly freed with swr_free() (was re-initialized with swr_init() and
leaked), and codec option dictionaries are freed after avcodec_open2().
- rational: store num/den natively instead of AVRational; math operators
re-implemented natively (ported av_reduce/av_d2q/av_cmp_q semantics,
verified bit-exact against FFmpeg)
- AudioParams: replace AVChannelLayout member with a plain uint64_t mask
(new render/channellayout.h constants mirror AV_CH_LAYOUT_* values)
- Timecode: native rescale (av_rescale_q/av_rescale_q_rnd equivalents)
with 128-bit intermediate precision
- core no longer finds or links FFMPEG::avutil
Part of the FFmpeg isolation effort: all FFmpeg access is being moved
behind a dedicated shared library (ffmpeg_bridge).
- On Linux, automatically prefer PipeWire/JACK/PulseAudio over ALSA
even when a saved ALSA device name exists in config.
- Restore Footage length after deserialization so worker snapshots
have valid stream lengths and video playback can advance.
- Guard ResolveDecoderFromInput and ProcessAudioFootage against a
null decoder cache to prevent worker crashes on direct Footage ->
ViewerOutput connections.
- Keep Linux signal backtrace handler in the render worker.
- Add Oak project SVG logos.
- Strip the dense RenderWorkerPool debug messages about cached/stale graph
snapshots and cleanup to reduce log spam.
- Add qDebug logging and status-bar feedback to
TimelineWidget::SynchronizeSelectedClipsByWaveform() so we can see why the
sync action does nothing (e.g. not enough cached clips or no usable offset).
- Add FixChannelLayout() helper to AudioProcessor::Open() to fall back to a
default native channel layout when the input/output layout is unspecified,
custom, or has a zero mask. This prevents FFmpeg's abuffer/aformat filters
from rejecting 'channel_layouts=0x0' on Linux.
- Log audio processor open parameters, viewer audio queue state, and
AudioManager::PushToOutput device/stream status to help diagnose silent
playback on Linux.
- Add explicit QTabBar::tab styles to olive-dark/style.css so inactive
KDDockWidgets tabs render with a dark background and white text instead
of falling back to the native macOS light tab appearance.
- Update macOS CD to stage Oak.app alongside an Applications symlink
before creating the DMG, matching standard macOS installer disk images.
The custom Python bundling script was copying Qt framework binaries as
flat dylibs (e.g. Contents/Frameworks/QtCore) on top of the frameworks
already deployed by macdeployqt (Contents/Frameworks/QtCore.framework).
This caused duplicate Objective-C class definitions and crashes in
QAction construction.
- Skip any dependency whose basename starts with 'Qt' or 'libQt'.
- Process every binary in Contents/MacOS (main app, worker, backends).
- Fix YAML duplicate 'run:' key from the previous commit.
- Add -verbose=2 to macdeployqt so deployment issues are visible in CI logs.
- Pass -executable for oak-render-worker so its rpaths are also fixed.
- Add a verification step that fails the build if platform plugins are missing.
- Print otool -L for both main binary and worker to aid debugging.
Oak only uses KDDockWidgets' QtWidgets frontend. Building the QtQuick
frontend caused duplicate QML module registration when QtQuick.framework
initialized, resulting in a SIGABRT in QQmlMetaType::qmlInsertModuleRegistration.
- Set KDDockWidgets_FRONTENDS to qtwidgets in ext/CMakeLists.txt.
- Remove the -qmldir flags from macOS CD deploy since QML plugins are no
longer needed.
The .ts files had been corrupted by an earlier formatting run that
inserted spaces into XML tags (e.g. '<?xml version = "1.0" ... ?>'),
breaking lrelease parsing. Restore all files under app/ts/ from the last
good revision before the corruption.
Run clang-format over all project C/C++ source and header files
(.c, .cpp, .cc, .h, .hpp, .hh, .m, .mm) under app/ and tests/.
Non-source files (svg, qm, qrc, etc.) were not touched.
- Reference Linux Kernel Coding Style instead of Google C++ Style Guide.
- Require tabs for indentation.
- Keep Javadoc-style documentation comments.
- Preserve the original naming rules section.
- Update project name to Oak Video Editor.
- Rename macOS bundle output from Olive.app to Oak.app.
- Rename editor binary to oak-editor and render worker to oak-render-worker.
- Rename crash handler output to oak-crashhandler.
- Update worker lookup, NSIS installer, Linux desktop/AppRun, crashhandler
symbol paths, and documentation for the new binary names.
- Update CD workflow paths and add -qmldir flags to macdeployqt so QtQuick
/ QML plugins required by KDDockWidgets are bundled, fixing the launch
crash on macOS.
- Update user-facing GitHub URLs to OakVideoEditorCommunity/oak.
MinGW CMake defaults to prefixing shared libraries with 'lib', producing liboakgl.dll which the dynamic backend loader cannot find. Set PREFIX empty on Windows so the output names match the runtime names oakgl.dll / oakvulkan.dll.
Install git in the Fedora container before actions/checkout so the checkout step can clone the repository normally instead of falling back to the REST API.
- Arch: create the source tarball in /tmp to avoid file changed as we read it and exclude existing tarballs from the archive.
- RPM: run the Fedora build inside docker instead of a job container so actions/checkout is not hampered by a missing Git in the base image.
- Add CPack configuration in CMakeLists.txt for .deb and .rpm generation
with declared runtime dependencies.
- Add Arch Linux PKGBUILD template under app/packaging/arch/ and build it
inside the official archlinux:base-devel Docker container.
- Extend CD workflow with deb, rpm and archlinux jobs and attach the
resulting packages to the draft release.
- English and Chinese build.md now list macOS as a fully supported
platform and point to the dedicated macOS build guide.
- Chinese build.md Fedora dependency list synced with English: use
ffmpeg-free-devel and add bzip2-devel.
- RenderWorkerFootageTest: skip when the dynamic render backend is not
built; we cannot verify backend availability without it, and the tests
fail on headless/GPU-less CI runners.
- NodeProject.FilenameAndNameUpdate: use a plain filename instead of
/tmp/... so the expected name does not depend on POSIX temp paths or
Windows backslash normalization.
Proxy generation modifies the project, causing graph snapshots to be
rewritten. The old implementation deleted the previous snapshot
immediately, even though queued render jobs still held its path. The
worker then failed to load the deleted file.
Add per-path reference counting:
- Increment when a job is created with a snapshot path.
- Decrement when the job is processed, cancelled or removed.
- Only delete a snapshot when its reference count reaches zero and it
is no longer the cached snapshot for the project.
Also keep snapshots in the system temp directory as before, since the
location itself was not the bug.
The worker process on macOS could not see the graph snapshot when it
was placed in /var/folders/.../T (likely sandbox/working-directory
visibility). Write snapshots to a 'render-graphs' subdirectory of the
application's persistent data location instead, which both the editor
and the worker can access on all platforms.
- Report QFile::errorString() when ProjectSerializer::Load() fails to open
the graph file, so the worker log shows why instead of an empty detail.
- In olive-render-worker LoadGraph(), check file existence, size and
readability before delegating to the serializer and log the attempt.
- Update CI badge URL to OakVideoEditorCommunity in README.
- Remove outdated TODO files, vcpkg.json, patch file and stale log.
- Clean up obsolete docs and move ofx-pluginrenderer-functions-zh.md
into docs/zh/.
- Make olive-editor depend on olive-render-worker on all platforms so the
worker is always built with the main app.
- Copy olive-render-worker and dynamic render backends (oakgl, oakvulkan)
into the macOS app bundle next to the executable.
- Include oakgl.dll and oakvulkan.dll in the Windows NSIS installer.
- Fix PluginSmokeThread.ConcurrentParamAccess CI failure by adding a mutex
around IntegerInstance's no-node fallback storage and serializing the
test's set/get pair.
- Hide the render worker's Dock icon on macOS via
NSApplicationActivationPolicyProhibited.
KDDockWidgets may need an event-loop iteration to finish opening or
tabifying a dock widget before raise() can switch to it. Defer raise(),
activateWindow(), and setFocus() via QMetaObject::invokeMethod with
Qt::QueuedConnection after show().
Call setAsCurrentTab() and activateWindow() on the ParamPanel dock widget
in ShowSelectedNodeInParamEditor so the parameter editor becomes the active
tab/window when invoked from the node view context menu or Shift+P shortcut.
In NodeParamView::SetSelectedNodes, after locating the target node item,
expand and raise its containing NodeParamViewContext dock widget so the
active tab/folded section switches to the one that actually holds the node.
- Ensure right-clicking a node that isn't currently selected makes it the
sole selection before showing the context menu, so 'Show in Parameter Editor'
operates on the clicked node.
- Emit all selected node/context pairs when jumping to the parameter editor.
- Guard dragging_items_ iteration in mouseReleaseEvent against deleted items
by snapshotting keys as QPointer and skipping null entries.
- Move Behavior-General options (hover focus, slider ladder, scroll zooms)
into the General preferences tab.
- Move Behavior-Audio option (audio scrubbing) into the Audio preferences tab.
- Promote remaining Behavior categories (Timeline, Playback, Project, Nodes,
Rendering) to top-level sidebar entries without the 'Behavior - ' prefix.
- Update Chinese translations for the new sidebar titles and fill unfinished
Behavior tab strings.
NodeView:
- Remove double-click jump-to-parameter-editor behavior; keep expand/collapse.
- Add right-click context menu item 'Show in Parameter Editor'.
- Add Shift+P shortcut bound to the same action.
Render:
- Fix crash in PreviewAutoCacher::ClearSingleFrameRenders when proxy playback
causes a render ticket to finish synchronously before the watcher pointer is
returned. Defer the watcher Finished signal via queued connection so the
caller can safely register the watcher before it is deleted.
Behavior preferences used a nested QTreeWidget with groups like General,
Audio, Timeline, etc. Replace it with one tab per group in the left sidebar
so users can jump directly to a category without expanding a secondary menu.
Also wire double-clicking a node in the node graph to emit a selection
signal with its context, causing the parameter panel to scroll to that
node's parameters (in addition to showing/raising the panel as before).
ColorProcessor creation was done for every exported frame and the input
reference space was passed as a role string (e.g. "scene_linear"). If
getProcessor() rejected the role name the worker would crash, the pool
would retry the frame, and export throughput would drop to near zero with
minimal CPU/GPU usage.
- Canonicalize role names to colorspace names in ColorProcessor so
"scene_linear" resolves to the config's actual colorspace.
- Wrap processor creation in a try/catch and initialize cpu_processor_ to
nullptr on failure instead of dereferencing a null processor.
- Cache created ColorProcessor objects in the worker keyed by transform
so OCIO processor/shader setup is only paid once per export.
When no explicit color transform was set, ManagedDisplayWidget fell back to
the project's default input colorspace. For scene-referred reference spaces
like ACEScg / Linear this produced a raw, greenish image on the viewer
instead of a monitor-ready picture. Default to the OCIO config's default
display and view so the viewer looks correct out of the box, while still
honoring any Color Space / Display / View choice the user makes from the
context menu.
Expand docs/project-file-reference.md to cover the full .ove format
implemented in the current codebase, including:
- Root document and project container structure
- Project data: uuid, plugins, nodes, settings
- Detailed Node serialization (inputs, keyframes, connections, hints,
context, caches)
- Node-specific custom data: Footage proxy/sourcestarttime, ViewerOutput
workarea/markers, Track height, NodeGroup passthroughs
- VideoParams and AudioParams field lists
- MainWindowLayoutInfo layout block
- Partial documents (markers, keyframes, nodes/timeline)
- Versioning and OpenFX notes
All sections are provided in English with Chinese translations.
The export color transform was passed to RenderTask as a ColorProcessor,
but the worker IPC render_frame message never carried it. The worker
always set the ticket's coloroutput to null, so frames were returned in
the project's reference space and encoded without the chosen output
transform (e.g. Rec.709 / sRGB), causing the exported video to look
wrongly tinted.
Serialize the ColorTransform through the render_frame control message
and reconstruct the ColorProcessor on the worker side before rendering.
Also expose ColorTransform as a Qt metatype so it can be stored in a
ticket QVariant.
The Project Explorer previously only had an old Pre-Cache submenu that
required choosing a sequence. Replace it with the same Proxy menu that
the timeline uses:
- Generate Proxy: batch-generates proxies for all selected footage items
that have an enabled video stream, using ProxyManager and the current
proxy settings.
- Use Proxy: checkable toggle that enables/disables proxy playback for the
selected footage.
- Reveal Proxy / Delete Proxy: consistent with the timeline context menu.
Also make Footage::SaveCustom() write the <proxy> element whenever
proxy_enabled_ is true, even if proxy_path_ is empty, and load it back
in LoadCustom(). This preserves the user's Use Proxy preference across
project saves so it is restored on the next open.
Translation files updated with lupdate; zh_CN and zh_TW translations for
the new Project Explorer strings (and the matching timeline strings) are
filled in.
When a render-worker process crashed or exited while the worker-pool
thread was writing a control message (e.g. right after an audio-sync and
drag operation triggered a new render), the main process received SIGPIPE
and terminated at WriteControlMessage().
- Ignore SIGPIPE in main() so QProcess can report the broken pipe through
its normal error path instead of killing the application.
- Harden WriteControlMessage() / TryWriteControlMessage() to check the
process state and the number of bytes written before waiting.
TaskDialog::closeEvent() was calling deleteLater() immediately when the
user closed the window. The ExportTask was a child QObject of the dialog,
so it got destroyed while its Run() method was still executing in the
worker thread, leading to a use-after-free on encoder_->Close().
Defer deletion until the QFutureWatcher reports that the task has actually
finished. A new task_finished_ flag tracks this so closeEvent only deletes
when it is safe, and TaskFinished() closes the dialog (which now deletes
instead of calling close() again while the task is still alive).
The Waveform, Vectorscope and Histogram panels were black when running on
the Vulkan / backend-neutral render path because ScopeBase::OnPaint()
returned early with a TODO. The viewer emits GPU textures that live in the
viewer's own renderer; on Vulkan each ManagedDisplayWidget has a separate
device, so those textures cannot be sampled directly.
Fix by:
- Adding a backend-neutral branch in ScopeBase::OnPaint() that downloads the
reference texture (cross-renderer if needed), color-manages it into a U8
offscreen texture, downloads it to a QImage, and draws via QPainter.
- Adding a pure virtual DrawScopeSoftware() to ScopeBase and implementing it
for WaveformScope, VectorscopeScope and HistogramScope.
- Fixing the managed_tex_up_to_date_ flag that was never set to true in the
OpenGL path, which caused the color-managed scope texture to be recreated
on every paint.
All gtest suites pass.
- Default ViewerOutput::autocache_input_video_ to true so new sequences
and viewer outputs use the frame cache without requiring the user to
manually enable Auto-Cache.
- Update the DefaultSequenceAutoCache2 config default to true for
consistency.
- The playback backup timer was using timebase_dbl() in seconds as its
QTimer interval, which truncated to 0 ms for normal frame rates. This
caused the timer to fire continuously and starve the event loop when
the video display was hidden, e.g. in Show Waveform Only mode, freezing
the UI and stopping the playhead. Multiply by 1000 to use milliseconds.
- If PlayInternal has neither video nor audio to prequeue, call
FinishPlayPreprocess immediately so the backup timer starts and the
playhead advances even in waveform-only mode with no audio.
- AudioWaveformView now uses the video frame rate as its timebase
(falling back to the default sequence frame rate), preventing the
enormous scene rect that froze the UI when it previously used the
audio sample rate.
- Call UpdateSceneRect() after changing the waveform timebase so the
QGraphicsScene bounds are recomputed immediately.
- Refresh the waveform view's viewer/timebase whenever the waveform
visibility mode changes, ensuring the correct timebase is applied
even if the node was connected earlier with a different setting.
- Use a divider of 1 (full resolution) for new sequences and for
footage-derived viewer parameters instead of the auto-downscaling
heuristic, so the viewer defaults to full-res preview.
- In AudioWaveformView, base the view timebase on the video frame rate
when video is present; fall back to the audio sample rate only for
audio-only sources. Using the audio sample rate as the view timebase
created an enormous scene rect (time * sample_rate * scale), which
froze the waveform view and stalled playback updates when showing
the audio waveform.
Renderer / preview:
- Switch the preview/display readback format from F16 to packed
10-bit RGBA (PixelFormat::U10) to halve GPU->CPU/IPC bandwidth
while keeping 10-bit panel precision.
- Add U10 support to VideoParams, FFmpeg/OIIO/OCIO utility mappings,
OpenGL (GL_RGB10_A2), Vulkan (VK_FORMAT_A2B10G10R10_UNORM_PACK32),
and plugin bit-depth lookups.
- Update preview autocacher comment to reflect the new behavior.
OCIO LUT tests:
- Add four E2E-style ColorLutNode gtests that drive a SolidGenerator
-> OCIOLutNode graph through NodeTraverser and compare resulting
pixels on the CPU. They cover forward/inverse transforms and verify
that switching LUT direction and LUT file updates both the processor
and the output pixels.
Cleanup:
- Remove a leftover SolidGenerator::Value debug fprintf.
- Capture render worker stderr in RenderWorkerFootageTest for better
diagnostics.
Add temporary qDebug logging to Footage, ProjectCopier,
RenderWorkerPool, and TimelineWidget to trace why Use Proxy cannot be
toggled and why proxy footage may still be used after disabling.
Run lupdate to pull the latest source strings (including new proxy
settings/menu text) into all .ts files. Translations for the new strings
are marked unfinished and can be filled in by translators.
- Extend ProxyManager::ProxyParams with crf and preset, and update
ProxyTask/ProxyParamsEqual to use them.
- Add global config keys ProxyWidth, ProxyHeight, ProxyCRF, ProxyPreset.
- Add a Proxy Settings group to Preferences -> Disk with width/height/CRF/preset
controls.
- Read proxy settings from config when generating proxies from the timeline.
- Add Tools -> Proxy Settings... menu entry that opens Preferences on the Disk tab.
- Add ConfigDialogBase::SetCurrentTab() so PreferencesDialog can start on a
specific tab.
Proxy state (enabled/path/state/etc.) is stored as Footage member data,
not as a Node input, so ProjectCopier did not synchronize it to the
internal copy project used by worker processes. This caused toggling
Use Proxy in the UI to have no effect on rendered output.
Add a Footage::ProxySettingsChanged signal and have ProjectCopier sync
the proxy state to the copied Footage node, marking the copy project
modified so RenderWorkerPool writes a fresh graph snapshot.
ffmpeg could not infer the container format because ProxyManager writes
proxies to a temporary '.mp4.working' file. Explicitly pass '-f mp4' (or
the configured proxy extension) so ffmpeg knows the output format
regardless of the temporary extension.
- RenderWorkerPool::DecodeInputFrame now uses the proxy filename/decoder/stream
from FootageJob when a proxy is attached, so generated proxies are actually
used during render.
- Add qDebug/qWarning logging to TimelineWidget::GenerateProxiesForSelectedClips
and ProxyTask::Run to diagnose why Generate Proxy appears to do nothing.
- Introduce ReadDirectionInput() helper that accepts both integer and
string ('Forward'/'Inverse') representations of the direction combo.
- Log every processor creation with the raw direction value and the OCIO
transform direction being used, along with whether it is main or worker.
- This helps determine why Forward/Inverse switching reportedly has no
effect in the viewer/worker.
Tests still pass.
EnsureProcessor() was returning early whenever a processor existed and
processor_dirty_ was false, without checking whether the file or direction
input had changed. As a result, switching Forward/Inverse in the main GUI
re-used the old processor and produced no visible difference.
Now EnsureProcessor() also compares the current inputs against the values
used to create last_processor_, so direction/file changes always trigger a
regeneration before the color transform job is emitted.
- Override OCIOLutNode::Value() to ensure the processor is created/updated
before the color transform job is emitted.
- In the worker process, mark the processor dirty on input/config changes
instead of creating it synchronously during LoadGraph, which blocked the
main process waiting for the graph load acknowledgement.
- Keep eager processor generation in the main GUI process so the viewer
cache is invalidated immediately when the LUT file or direction changes.
- Mark the ProjectCopier's internal render-proxy project as modified when
its update queue is processed, and reset the flag after RenderWorkerPool
writes a new graph snapshot. This fixes the worker loading a stale graph
snapshot after switching cube files or direction, which caused the old
LUT effect to persist.
All existing tests pass.
Temporarily disable the last_path_/last_direction_ reuse check to rule
out stale processor reuse as the cause of LUT changes not appearing in
the viewer.
Reversing the order ensures that any render tasks that finish after the
cancel request will find the cache already invalidated, preventing stale
LUT-processed frames from being written back and shown in the viewer.
Checking applicationName was not reliable. The worker process uses
QGuiApplication while the main process uses QApplication, so use
qobject_cast<QApplication*> to determine whether it is safe to access
RenderManager/PreviewAutoCacher. This prevents the worker from crashing
when loading or switching LUT files.
The LUT processor generation was calling RenderManager::GetCacher()
during project load in the render worker, but the worker has no
RenderManager/PreviewAutoCacher. This caused a SEGV and made the main
process hang waiting for the worker. Only cancel/invalidate caches in
the main GUI process.
After synchronously generating the new LUT processor, cancel any
running background video cache jobs before invalidating the cache.
This prevents the preview autocacher from re-rendering the entire
timeline, which was causing the UI to freeze, while still updating
the current visible frame with the new LUT.
Background generation caused the UI to freeze indefinitely when
switching LUT files, likely due to a deadlock between the worker
process, the preview autocacher, and the asynchronous set_processor
path. Synchronous generation is fast enough for typical 33^3 .cube
files and keeps the cache invalidation logic simple and safe.
The passthrough fix in OCIOBaseNode::Value() is retained so the
viewer shows the input frame while a processor is being created.
InvalidateAll() on the OCIO LUT node caused the preview autocacher to
re-render the entire timeline, freezing the UI when switching LUT files.
Instead, let the new processor be used naturally on the next render
request (scrubbing/playback).
InvalidateAll() on the OCIO LUT node triggered the worker's
PreviewAutoCacher to re-cache the entire timeline, causing the main
process to freeze while waiting for frames. Only invalidate in the
main GUI process so the viewer refreshes; the worker will naturally
use the new processor on its next render request.
- Pass through input texture when the LUT processor is not ready yet,
preventing black frames while the processor is being generated.
- Serialize processor generation with a single in-flight task to avoid
concurrent OCIO lock contention that could freeze the UI.
- Invalidate cached frames after the async processor is set so the viewer
refreshes automatically without requiring the playhead to be moved.
- Add OAK_DISABLE_HWACCEL environment variable to force software decoding.
- Add FFmpegDecoderHW regression test for H.264 4:2:2 10-bit decoding.
- Document that Vulkan now passes an end-to-end upload/blit/download test
on real hardware.
- List recently landed fixes: shared vertex/fragment UBO, descriptor set
layout, render pass dependencies, image layout transitions, framebuffer
and sampler caching, grayscale swizzle, texture-enable uniforms.
- Add remaining gaps: iterative/pin-pong multi-pass Blit, 1/3-channel
upload/download alignment, and full viewer/proxy/export smoke tests.
- Cache a VkFramebuffer per texture to avoid creating/destroying one for
every Blit() call.
- Create and cache both linear and nearest samplers; honor
Texture::Interpolation in descriptor writes.
- Add single-channel image-view component swizzle (R -> RGB, A = 1) to
match OpenGL grayscale behavior.
- Set texture-enable uniforms (NAME_enabled) when shaders declare them.
- Guard Blit() with null destination and emit a clear warning instead of
recording an invalid render pass.
ctest passes 4/4 in both dynamic-backend ON and OFF builds.