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.
- Add shared UBO layout across vertex + fragment stages so vertex
uniforms like ove_mvpmat compile and bind correctly.
- Rewrite uniform extraction/injection to avoid corrupting the UBO
block and to keep explicit sampler bindings stable.
- Make the UBO and sampler descriptor bindings visible to both vertex
and fragment stages.
- Add layout locations for vertex varyings (ove_texcoord) and fragment
inputs so SPIR-V generation succeeds.
- Extend TransitionImageLayout() to cover all layout pairs used by
upload/download/clear/blit.
- Add subpass dependencies to the cached render pass and leave
destinations in SHADER_READ_ONLY_OPTIMAL after Blit().
- Move descriptor allocation before command recording and abort cleanly
if allocation fails; switch Blit() to the persistent linear sampler.
- Add a gtest that creates a Vulkan backend, uploads a red U8 RGBA
texture, blits through the default pass-through shader, and verifies
the downloaded pixel is red. This test passes on the current system.
ctest passes 4/4; DynamicRenderBackend.* passes 3/1 (Vulkan fallback
skipped because Vulkan is available).
- Remove unconditional VK_KHR_swapchain request; offscreen renderer needs
no device extensions, fixing failures on headless/CI setups.
- Enumerate physical devices properly and pick the first one with a
graphics queue family instead of blindly choosing device 0.
- Check vkEnumeratePhysicalDevices/vkBindImageMemory/vkBindBufferMemory
results.
- Validate FindMemoryType() result before allocation and log clear errors.
- Add a persistent linear sampler created in PostInit and destroy it in
DestroyInternal.
- Fix DestroyInternal() early-return leak: now tears down the instance
even if device creation failed.
ctest passes 4/4.
- dynamic plan: mark that RenderManager::backend() now syncs with
DynamicRenderer's actual loaded backend after internal fallback.
- manual test plan: add checks in 10.2/10.3 that RenderManager reports
the actual runtime backend (OpenGL after Vulkan fallback).
No code changes; ctest passes 4/4.
DynamicRenderer::Load() can internally fall back from Vulkan to
OpenGL when the Vulkan library is missing or is_available() fails.
Previously RenderManager::backend_ stayed at kVulkan, so callers
asking RenderManager::backend() got the wrong answer. After a
successful dynamic load, sync backend_ from
DynamicRenderer::backend_name() and log any fallback.
ctest still passes 4/4 in build-default-on.
- Add PickRenderableFormat / IsColorAttachmentSupported so 3-channel
Vulkan formats fall back to 4-channel when unsupported.
- Make oakvulkan target and C ABI check conditional on Vulkan_FOUND;
skip liboakvulkan build/dependencies when Vulkan headers/libs are
absent.
- Update Preferences tooltip to reflect Vulkan is an experimental
prototype that may fall back to OpenGL.
- Revise dynamic backend plan doc: phase 3/4/5 described as prototype
frameworks with runtime validation pending, and list recent Vulkan
fixes (init idempotency, descriptor/sampler lifetime, dynamic
viewport/scissor, render pass clear, format probing).
Both OAK_ENABLE_DYNAMIC_RENDER_BACKEND=ON and OFF configurations
build and pass ctest (4/4).
- Extract libolive-rendercore static library to minimize backend link boundary.
- Add DynamicRenderer adapter with C ABI (oakgl/oakvulkan shared libs).
- Make OAK_ENABLE_DYNAMIC_RENDER_BACKEND default ON with OpenGL fallback.
- Implement VulkanRenderer prototype (textures, shaders, UBO blit, readback).
- Add backend-neutral viewer readback path (offscreen -> QImage -> QPainter).
- Refactor PluginRenderer to be renderer-agnostic; OFX plugins fall back to CPU
path on non-OpenGL backends while preserving OpenGL render path.
- Add Renderer::AttachOutputTexture/DetachOutputTexture and C ABI forwards.
- Update docs/zh/render-backend-dynamic-plan.md for Phase 3/4/5.