Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-07-13 17:05:22 +08:00
606 changed files with 135087 additions and 138186 deletions
+214 -28
View File
@@ -14,7 +14,7 @@ jobs:
# Windows Installer (MSYS2)
# ------------------------------------------------------------------
windows:
runs-on: warp-windows-latest-x64-16x
runs-on: warp-windows-latest-x64-32x
steps:
- uses: actions/checkout@v4
with:
@@ -57,21 +57,29 @@ jobs:
- name: Deploy dependencies
shell: msys2 {0}
run: |
mkdir -p app/packaging/windows/nsis/olive-editor
cp build/app/olive-editor.exe app/packaging/windows/nsis/olive-editor/
windeployqt6 app/packaging/windows/nsis/olive-editor/olive-editor.exe
# Copy all non-Qt MSYS2 DLLs recursively
cd app/packaging/windows/nsis/olive-editor
for l in $(ntldd -R olive-editor.exe | grep -E 'mingw64|ucrt64|clang64' | sed 's/^[ \t]*//' | cut -d' ' -f3); do
mkdir -p app/packaging/windows/nsis/oak-editor
cp build/app/oak-editor.exe app/packaging/windows/nsis/oak-editor/
cp build/app/oak-render-worker.exe app/packaging/windows/nsis/oak-editor/
cp build/app/oakgl.dll app/packaging/windows/nsis/oak-editor/
if [ -f build/app/oakvulkan.dll ]; then
cp build/app/oakvulkan.dll app/packaging/windows/nsis/oak-editor/
fi
windeployqt6 app/packaging/windows/nsis/oak-editor/oak-editor.exe
# Copy all non-Qt MSYS2 DLLs recursively for every binary we ship
cd app/packaging/windows/nsis/oak-editor
for binary in oak-editor.exe oak-render-worker.exe oakgl.dll oakvulkan.dll; do
[ -f "$binary" ] || continue
for l in $(ntldd -R "$binary" | grep -E 'mingw64|ucrt64|clang64' | sed 's/^[ \t]*//' | cut -d' ' -f3); do
cp -v "$l" .
done
done
- name: Build installer
shell: msys2 {0}
run: |
cd app/packaging/windows/nsis
cp "${GITHUB_WORKSPACE}"/LICENSE .
makensis olive.nsi
makensis oak.nsi
mv setup.exe "${GITHUB_WORKSPACE}"/Oak-Video-Editor-Windows-x64.exe
- name: Upload artifact
@@ -122,21 +130,37 @@ jobs:
- name: Deploy Qt dependencies
run: |
export PATH="$(brew --prefix qt@6)/bin:$PATH"
macdeployqt build/app/Olive.app
macdeployqt build/app/Oak.app \
-verbose=2 \
-executable=build/app/Oak.app/Contents/MacOS/oak-render-worker
# Re-sign after macdeployqt (it breaks signatures when modifying libs)
codesign --force --deep --sign - build/app/Olive.app
codesign --force --deep --sign - build/app/Oak.app
- name: Verify macOS bundle plugins
run: |
ls -la build/app/Oak.app/Contents/PlugIns/platforms/ || (echo "Missing platform plugins!" && exit 1)
ls -la build/app/Oak.app/Contents/PlugIns/imageformats/ || true
echo "=== otool -L main binary ==="
otool -L build/app/Oak.app/Contents/MacOS/Oak | head -30
echo "=== otool -L worker ==="
otool -L build/app/Oak.app/Contents/MacOS/oak-render-worker | head -30
- name: Bundle non-Qt libraries
run: |
mkdir -p build/app/Olive.app/Contents/Frameworks
mkdir -p build/app/Oak.app/Contents/Frameworks
python3 << 'PYEOF'
import os, shutil, subprocess
APP = "build/app/Olive.app"
BINARY = f"{APP}/Contents/MacOS/Olive"
APP = "build/app/Oak.app"
MACOS_DIR = f"{APP}/Contents/MacOS"
DEST = f"{APP}/Contents/Frameworks"
os.makedirs(DEST, exist_ok=True)
QT_PREFIXES = ("Qt", "libQt")
def is_qt_lib(name):
return name.startswith(QT_PREFIXES)
def get_rpaths(binary):
out = subprocess.run(["otool", "-l", binary], capture_output=True, text=True).stdout
rpaths = []
@@ -178,42 +202,55 @@ jobs:
return p
return None
EXEC_RPATHS = get_rpaths(BINARY)
PROCESSED = set()
def process(target):
def process(target, exec_rpaths):
tdir = os.path.dirname(target)
print(f"Processing: {target}")
for dep in get_deps(target):
resolved = resolve(dep, EXEC_RPATHS if target == BINARY else [], tdir)
# Skip Qt frameworks and Qt dylibs; macdeployqt already deploys them.
dep_base = os.path.basename(dep)
if is_qt_lib(dep_base):
continue
resolved = resolve(dep, exec_rpaths, tdir)
if not resolved or not os.path.isfile(resolved):
continue
resolved = os.path.realpath(resolved)
if not (resolved.startswith("/opt/homebrew/") or resolved.startswith("/usr/local/") or resolved.startswith(os.path.realpath(os.getcwd()))):
continue
base = os.path.basename(resolved)
if is_qt_lib(base):
continue
if base in PROCESSED:
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
continue
PROCESSED.add(base)
dst = os.path.join(DEST, base)
if os.path.abspath(resolved) == os.path.abspath(dst):
# Already in Frameworks (likely copied by macdeployqt), just fix paths
subprocess.run(["install_name_tool", "-id", f"@rpath/{base}", dst], capture_output=True)
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
process(dst)
process(dst, exec_rpaths)
continue
print(f" Copying: {resolved} -> {dst}")
shutil.copy2(resolved, dst)
subprocess.run(["install_name_tool", "-id", f"@rpath/{base}", dst], capture_output=True)
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
process(dst)
process(dst, exec_rpaths)
process(BINARY)
binaries = [os.path.join(MACOS_DIR, f) for f in os.listdir(MACOS_DIR)
if os.path.isfile(os.path.join(MACOS_DIR, f))]
if not binaries:
raise RuntimeError(f"No binaries found in {MACOS_DIR}")
for rp in EXEC_RPATHS:
subprocess.run(["install_name_tool", "-delete_rpath", rp, BINARY], capture_output=True)
subprocess.run(["install_name_tool", "-add_rpath", "@executable_path/../Frameworks", BINARY], capture_output=True)
main_binary = os.path.join(MACOS_DIR, "Oak")
main_rpaths = get_rpaths(main_binary)
for binary in binaries:
process(binary, main_rpaths)
# Ensure every binary can find libraries in Frameworks
for rp in get_rpaths(binary):
subprocess.run(["install_name_tool", "-delete_rpath", rp, binary], capture_output=True)
subprocess.run(["install_name_tool", "-add_rpath", "@executable_path/../Frameworks", binary], capture_output=True)
subprocess.run(["codesign", "--force", "--deep", "--sign", "-", APP], check=True)
print("Done. Frameworks:")
@@ -224,16 +261,19 @@ jobs:
- name: Debug bundle contents
run: |
echo "=== otool -L ==="
otool -L build/app/Olive.app/Contents/MacOS/Olive
otool -L build/app/Oak.app/Contents/MacOS/Oak
echo "=== Frameworks dir ==="
ls -la build/app/Olive.app/Contents/Frameworks/ || echo "(empty)"
ls -la build/app/Oak.app/Contents/Frameworks/ || echo "(empty)"
echo "=== App bundle size ==="
du -sh build/app/Olive.app
du -sh build/app/Oak.app
- name: Create DMG
run: |
mkdir -p build/app/dmg-staging
cp -R build/app/Oak.app build/app/dmg-staging/
ln -s /Applications build/app/dmg-staging/Applications
hdiutil create \
-srcfolder build/app/Olive.app \
-srcfolder build/app/dmg-staging \
-volname "Oak Video Editor" \
-fs HFS+ \
-format UDZO \
@@ -317,11 +357,157 @@ jobs:
name: oak-linux-appimage
path: Oak_Video_Editor-*.AppImage
# ------------------------------------------------------------------
# Debian package (.deb)
# ------------------------------------------------------------------
deb:
runs-on: warp-ubuntu-latest-x64-16x
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
cmake ninja-build pkg-config \
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
portaudio19-dev libgl1-mesa-dev libxkbcommon-dev
- name: Build
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_QT6=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_Vulkan=ON
cmake --build build
- name: Create DEB package
run: |
cd build
cpack -G DEB
mv *.deb "${GITHUB_WORKSPACE}/"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: oak-deb-package
path: oak-video-editor_*.deb
# ------------------------------------------------------------------
# RPM package (.rpm)
# ------------------------------------------------------------------
rpm:
runs-on: warp-ubuntu-latest-x64-16x
container:
image: fedora:latest
steps:
- name: Install Git
run: dnf install -y git
- uses: actions/checkout@v4
with:
submodules: true
- name: Install dependencies
run: |
dnf install -y \
cmake ninja-build pkgconf-pkg-config \
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
ffmpeg-free-devel \
OpenImageIO-devel \
OpenColorIO-devel \
openexr-devel \
expat-devel \
portaudio-devel \
mesa-libGL-devel \
vulkan-headers \
vulkan-loader-devel \
libxkbcommon-devel \
gcc-c++ \
bzip2-devel \
rpm-build
- name: Build
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_QT6=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_Vulkan=ON
cmake --build build
- name: Create RPM package
run: |
cd build
cpack -G RPM
mv *.rpm "${GITHUB_WORKSPACE}/"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: oak-rpm-package
path: oak-video-editor-*.rpm
# ------------------------------------------------------------------
# Arch Linux package (.pkg.tar.zst)
# ------------------------------------------------------------------
archlinux:
runs-on: warp-ubuntu-latest-x64-16x
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Prepare source tarball and PKGBUILD
run: |
VERSION="${GITHUB_REF_NAME#v}"
# Arch pkgver cannot contain hyphens
PKGVER="${VERSION//-/_}"
TARBALL="oak-video-editor-${PKGVER}.tar.gz"
tar czf "/tmp/${TARBALL}" \
--transform "s,^,oak-video-editor-${PKGVER}/," \
--exclude='.git' \
--exclude='build' \
--exclude='*.tar.gz' \
.
mv "/tmp/${TARBALL}" "${TARBALL}"
sed "s/@VERSION@/${PKGVER}/g" app/packaging/arch/PKGBUILD.in > PKGBUILD
- name: Build package in Arch Linux container
run: |
docker run --rm \
-v "${PWD}:/build" \
-w /build \
archlinux:base-devel \
bash -c '
set -e
pacman-key --init
pacman -Syu --noconfirm
pacman -S --noconfirm --needed \
cmake ninja git pkgconf \
qt6-base qt6-tools ffmpeg openimageio opencolorio openexr expat portaudio \
mesa vulkan-icd-loader libxkbcommon fmt
useradd -m builder
chown -R builder /build
su builder -c "makepkg -s --noconfirm"
'
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: oak-arch-package
path: oak-video-editor-*.pkg.tar.zst
# ------------------------------------------------------------------
# Create Draft Release
# ------------------------------------------------------------------
release:
needs: [windows, macos, appimage]
needs: [windows, macos, appimage, deb, rpm, archlinux]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [warp-ubuntu-latest-x64-16x, warp-macos-15-arm64-12x, warp-windows-latest-x64-16x]
os: [warp-ubuntu-latest-x64-16x, warp-macos-15-arm64-12x, warp-windows-latest-x64-32x]
env:
CMAKE_BUILD_TYPE: Release
# Enable OFX integration tests that require external plugin bundles
+1 -1
View File
@@ -1,5 +1,5 @@
# CMake artifacts
/build*/
/cmake-build-*
build
# Doxygen
@@ -1,25 +0,0 @@
From 6b0ef44eb189411d36c739ccde8a081a3e62034a Mon Sep 17 00:00:00 2001
From: Mike Solar <iam@mikesolar.com>
Date: Mon, 24 Nov 2025 20:57:12 +0800
Subject: [PATCH] Fix include path for Window_p.h in qtcommon directory
---
src/qtcommon/Window_p.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/qtcommon/Window_p.h b/src/qtcommon/Window_p.h
index f29b209a5..d1d6b47ad 100644
--- a/src/qtcommon/Window_p.h
+++ b/src/qtcommon/Window_p.h
@@ -11,7 +11,7 @@
#pragma once
-#include "core/Window_p.h"
+#include "../core/Window_p.h"
#include "Screen_p.h"
#include <QPointer>
--
2.52.0
+30 -2
View File
@@ -17,8 +17,8 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(olive-editor VERSION 0.3.0 LANGUAGES CXX)
set(PROJECT_VERSION "0.3.0-alpha")
project(olive-editor VERSION 0.4.0 LANGUAGES CXX)
set(PROJECT_VERSION "0.4.0-alpha")
set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -DOFX_SUPPORTS_OPENGLRENDER)
@@ -337,3 +337,31 @@ if (BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# ------------------------------------------------------------------------------
# CPack / system package configuration
# ------------------------------------------------------------------------------
set(CPACK_PACKAGE_NAME "oak-video-editor")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_VENDOR "Oak Video Editor Team")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Oak - Non-linear video editor")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://oakvideoeditor.org")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
# Debian
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Oak Video Editor Team")
set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF)
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"libqt6core6, libqt6gui6, libqt6widgets6, libqt6opengl6, libqt6openglwidgets6, libqt6network6, libqt6concurrent6, libavcodec60, libavformat60, libavutil58, libswscale7, libswresample4, libavfilter9, libopenimageio2.4, libopencolorio2, libopenexr-3-1-30, libexpat1, libportaudio2, libgl1, libxkbcommon0")
# RPM
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
set(CPACK_RPM_PACKAGE_GROUP "Applications/Multimedia")
set(CPACK_RPM_PACKAGE_URL "https://oakvideoeditor.org")
set(CPACK_RPM_PACKAGE_REQUIRES
"qt6-qtbase >= 6.0, qt6-qtbase-gui >= 6.0, qt6-qttools, ffmpeg-libs >= 6.0, OpenImageIO >= 2.4, OpenColorIO >= 2.0, openexr >= 3.1, expat, portaudio, mesa-libGL, libxkbcommon")
include(CPack)
+7 -12
View File
@@ -1,12 +1,6 @@
# Contributing to Olive
# Contributing to Oak
Thank you for your interest in contributing to Olive!
## Reporting issues
Bug reports help to make the software more stable and usable.
Please read the pinned [issue #1175](https://github.com/olive-editor/olive/issues/1175)
for guidelines before you create a new issue.
Thank you for your interest in contributing to Oak Video Editor!
## Writing code
@@ -22,9 +16,11 @@ In order to keep the code as readable and maintainable as possible, code
submitted should abide by the following standards:
* The code style generally follows the
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
including, but not limited to:
* Indentation is 4 spaces wide, spaces only (no tabs)
[Linux Kernel Coding Style](https://www.kernel.org/doc/html/latest/process/coding-style.html)
with the following project-specific exceptions and notes:
* Indentation uses **tabs**, not spaces.
* Documentation comments should use **Javadoc-style** (`/** ... */`) where appropriate.
* The naming rules below are retained from the original Olive codebase:
* `lowercase_underscored_variable_names`
* `lowercase_underscored_functions()` or `SentenceCaseFunctions()`
* `class SentenceCaseClassesAndStructs {}`
@@ -33,4 +29,3 @@ submitted should abide by the following standards:
* `class_member_variables_` end with a `_`
* 100 column limit (where it doesn't impair readability)
* Unix line endings (only LF no CRLF)
* Javadoc documentation where appropriate
+5 -6
View File
@@ -1,10 +1,8 @@
# Oak Video Editor[![Build status](https://github.com/olive-editor/olive/workflows/CI/badge.svg?branch=master)](https://github.com/olive-editor/olive/actions?query=branch%3Amaster)
# Oak Video Editor[![CI](https://github.com/OakVideoEditorCommunity/oak/actions/workflows/ci.yml/badge.svg)](https://github.com/OakVideoEditorCommunity/oak/actions/workflows/ci.yml)
[中文](docs/zh/README.md)
Oak Video Editor is a free non-linear video editor for Windows, macOS, and Linux.
Unfortunately, the original author has not submitted code updates for over 7 months, and no public contact information (email or otherwise) is available to reach them directly.
This project is a community-maintained fork of Olive Video Editor.
![screen](https://olivevideoeditor.org/img/020-2.png)
@@ -12,9 +10,10 @@ This project is a community-maintained fork of Olive Video Editor.
**NOTE: Oak Video Editor is alpha software and is considered highly unstable. While we highly appreciate users testing and providing usage information, please use at your own risk.**
## Binaries
The original author compiled following binaries:
- [0.1.0 alpha](https://github.com/olive-editor/olive/releases/tag/0.1.0)
- [0.2.0 unstable development build](https://github.com/olive-editor/olive/releases/tag/0.2.0-nightly)
The binary can be downloaded here:
[v0.3.0](https://github.com/OakVideoEditorCommunity/oak/releases/tag/v0.3.0-alpha)
## Building from Source
-90
View File
@@ -1,90 +0,0 @@
# TODO
## 目标
- 实现 2–3 秒预渲染的 LRU 缓存和代理剪辑功能,并以“小步快跑”的方式在现有架构中逐步落地,确保每一步都可编译。
## 现有架构中的落点
- 播放/渲染调度:`app/render/renderprocessor.cpp`, `app/render/plugin/pluginrenderer.cpp`, `app/node/traverser.cpp`
- 插件节点输入/默认值:`app/node/plugins/Plugin.cpp`
- Clip 图像/纹理获取:`app/pluginSupport/OliveClip.cpp`, `app/pluginSupport/OliveClip.h`
- 节点与值系统:`app/node/node.h`, `app/node/node.cpp`, `app/node/value.h`
- 工程序列化:`app/node/project/serializer/*`
## LRU 缓存计划(代码改动 + 集成点)
### 步骤 1(可编译):新增缓存类型但不接入逻辑
- 新增缓存模块,例如 `app/render/cache/framecache.h/.cpp`
- 定义:
- `FrameCacheKey`(图哈希/版本、时间、参数、代理模式、渲染缩放)。
- `FrameCacheEntry`AVFrame 或 Texture + 元信息 + 字节数 + 最近访问时间)。
- `FrameCache` API`get(key)``put(key, entry)``invalidateByVersion(version)`
- 先只编译通过,不改变行为。
### 步骤 2(可编译):图版本号/失效机制
-`Node` 或渲染入口维护图版本号。
- 当参数变化、连线变化时递增。
- 渲染侧可读取版本号用于缓存失效。
### 步骤 3(小行为):仅缓存当前帧
-`renderprocessor.cpp` 播放路径上:
- 先查缓存,命中则直接显示。
- 未命中则正常渲染,并写入缓存。
- 缓存预算先设很小,风险低。
### 步骤 4(小行为):预渲染窗口
- 增加队列,渲染 [now, now+N]N=23 秒。
- 并发限制(例如 2–3 个任务),避免抢 UI。
- 优先级:当前帧 > 近未来。
- Seek 时取消/丢弃过期任务。
### 步骤 5(行为):LRU 淘汰
- 按内存预算/帧数上限淘汰最久未使用。
### 步骤 6(行为):CPU/GPU 策略
- 默认缓存 CPU 帧,播放时再上传 GPU。
- GPU 缓存可作为后续优化开关。
### 步骤 7(可观测性)
- 统计命中率、平均渲染耗时、掉帧。
- Debug 构建下输出日志。
## 代理剪辑计划(代码改动 + 集成点)
### 步骤 1(可编译):数据模型与序列化
- 在 clip 元数据里增加:
- `proxy_path``proxy_width``proxy_height``proxy_codec``proxy_fps`
-`app/node/project/serializer/*` 写入/读取。
### 步骤 2(小行为):代理选择策略
- 增加全局/每 clip 的代理模式:
- `Auto``ForceProxy``ForceOriginal`
- 在媒体解析层根据模式决定用原片还是代理。
### 步骤 3(行为):代理生成
- 新增后台转码任务(复用现有渲染/导出流程)。
- 生成完成后更新元数据。
### 步骤 4(行为):UI 接入
- 增加“生成代理”“重链接代理”入口。
- 在剪辑或预览上显示代理标识。
### 步骤 5(验证)
- 对比代理与原片的时间精度、音画同步。
- 导出默认使用原片。
## 小步快跑执行顺序(每步可编译)
1) 新增缓存模块/类型(不接入)。
2) 增加图版本号与失效接口。
3) 播放路径只缓存当前帧。
4) 预渲染 2–3 秒窗口 + 并发限制。
5) LRU 淘汰策略。
6) 图版本变更触发失效。
7) 统计与日志。
8) 代理元数据字段 + 序列化。
9) 代理选择策略(Auto/Force)。
10) 代理生成任务 + UI 入口。
## 待确认问题
- 缓存预算默认值(按硬件分级)。
- 代理文件默认存储路径。
- 是否做 GPU 纹理缓存。
-92
View File
@@ -1,92 +0,0 @@
# TODO
## Goal
- Add an LRU prerender cache (23 seconds ahead) and proxy clip support, implemented as incremental, compile-safe steps within the current architecture.
## Where the Changes Live (Current Architecture)
- Playback/render scheduling: `app/render/renderprocessor.cpp`, `app/render/plugin/pluginrenderer.cpp`, `app/node/traverser.cpp`
- Plugin node inputs/defaults: `app/node/plugins/Plugin.cpp`
- Clip image/texture fetch: `app/pluginSupport/OliveClip.cpp`, `app/pluginSupport/OliveClip.h`
- Node graph and values: `app/node/node.h`, `app/node/node.cpp`, `app/node/value.h`
- Project/serialization: `app/node/project/serializer/*`
## LRU Cache Plan (Code Changes + Integration Points)
### Step 1 (compile-safe): Introduce cache data types (no behavior yet)
- Add a small cache module, e.g. `app/render/cache/framecache.h/.cpp`.
- Define:
- `FrameCacheKey` (graph version/hash, time, params, proxy mode, render scale).
- `FrameCacheEntry` (AVFrame or Texture + metadata + byte size + last-used).
- `FrameCache` API: `get(key)`, `put(key, entry)`, `invalidateByVersion(version)`.
- Wire in a compile-only stub with no runtime usage.
### Step 2 (compile-safe): Define graph/version invalidation hook
- Add a lightweight “graph version” counter to `Node` or a render pipeline owner.
- Increment on param changes and graph edits.
- Expose a read-only version getter for the render pipeline.
### Step 3 (small behavior): Cache current frame only
- In `renderprocessor.cpp` playback path, check cache before rendering:
- If hit, present cached frame.
- If miss, render normally and `put` into cache.
- Keep budget small (few frames) to minimize risk.
### Step 4 (small behavior): Pre-render window scheduling
- Add a render queue for time range [now, now+N] (N = 23s).
- Limit worker count (e.g., 23 tasks) to avoid UI starvation.
- Prioritize current frame > near future.
- On seek, cancel or drop stale tasks.
### Step 5 (behavior): LRU eviction policy
- Enforce memory budget and frame count cap.
- Evict least-recently-used entries.
### Step 6 (behavior): GPU/CPU policy
- Cache CPU frames by default for safety.
- For GL outputs, upload from cached CPU frame when displayed.
- Optionally add GPU caching later behind a feature flag.
### Step 7 (observability)
- Add counters for hit rate, average render time, and drops.
- Log only in debug builds.
## Proxy Clip Plan (Code Changes + Integration Points)
### Step 1 (compile-safe): Data model + serialization
- Extend clip metadata with:
- `proxy_path`, `proxy_width`, `proxy_height`, `proxy_codec`, `proxy_fps`.
- Add read/write in `app/node/project/serializer/*`.
### Step 2 (small behavior): Proxy selection policy
- Add project-level and clip-level proxy mode:
- `Auto`, `ForceProxy`, `ForceOriginal`.
- Add a simple resolver in clip/media source code that picks proxy if enabled.
### Step 3 (behavior): Proxy generation pipeline
- Add a background task to build proxies (using existing render/export tasks).
- Store output path and metadata on success.
### Step 4 (behavior): UI wiring
- Add “Generate Proxy” action + proxy indicator.
- Add “Relink Proxy” dialog.
### Step 5 (validation)
- Compare proxy vs original for timing and sync.
- Ensure proxies are ignored for export unless explicitly enabled.
## Small-Step Implementation Plan (Each Step Builds)
1) Add cache module + types (no references).
2) Add graph version counter (increment on changes).
3) Wire cache lookup for current frame only.
4) Add prerender queue (23 seconds) with limited concurrency.
5) Add LRU eviction + memory budget.
6) Add cache invalidation on graph version change.
7) Add basic metrics/logging.
8) Add proxy metadata fields + serialization.
9) Add proxy selection policy (Auto/Force modes).
10) Add proxy generation task + UI entry points.
## Open Questions
- Default cache size per hardware tier.
- Where to store proxy files on disk.
- Whether to cache GPU textures or CPU frames only.
+34 -4
View File
@@ -155,6 +155,9 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
if (WIN32)
set_target_properties(oakgl PROPERTIES PREFIX "")
endif ()
install(TARGETS oakgl
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
@@ -181,6 +184,9 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
if (WIN32)
set_target_properties(oakvulkan PROPERTIES PREFIX "")
endif ()
install(TARGETS oakvulkan
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
@@ -241,8 +247,12 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
add_dependencies(olive-editor oakvulkan)
endif ()
endif ()
# Ensure the render worker is always built together with the editor on every platform.
add_dependencies(olive-editor olive-render-worker)
# Add render worker process (olive-render-worker).
set_target_properties(olive-editor PROPERTIES OUTPUT_NAME "oak-editor")
# Add render worker process (oak-render-worker).
# Reuses the libolive-editor object library so the worker shares the exact same render/node/codec
# code as the editor. It is a headless app that owns its own offscreen GL context. The link set is
# currently the full OLIVE_LIBRARIES for simplicity; trimming UI-only dependencies is a later-phase
@@ -252,6 +262,11 @@ add_executable(olive-render-worker
$<TARGET_OBJECTS:libolive-editor>
$<TARGET_OBJECTS:olive-version-obj>
)
set_target_properties(olive-render-worker PROPERTIES OUTPUT_NAME "oak-render-worker")
if (APPLE)
target_sources(olive-render-worker PRIVATE render/worker/workermain_mac.mm)
target_link_libraries(olive-render-worker PRIVATE "-framework Cocoa")
endif ()
target_include_directories(olive-render-worker PUBLIC pluginSupport)
target_link_libraries(olive-render-worker PUBLIC OfxHost)
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
@@ -280,7 +295,7 @@ if (WIN32)
elseif (APPLE)
# Set Mac application icon
set(OLIVE_ICON packaging/macos/olive.icns)
set(OLIVE_ICON packaging/macos/oak.icns)
target_sources(olive-editor PRIVATE ${OLIVE_ICON})
# Set Mac bundle properties
@@ -288,15 +303,30 @@ elseif(APPLE)
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/MacOSXBundleInfo.plist.in
MACOSX_BUNDLE_GUI_IDENTIFIER org.oakvideoeditor.Oak
MACOSX_BUNDLE_ICON_FILE olive.icns
MACOSX_BUNDLE_ICON_FILE oak.icns
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
MACOSX_BUNDLE_BUNDLE_NAME "Oak Video Editor"
MACOSX_BUNDLE_INFO_STRING "Oak Video Editor ${PROJECT_LONG_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "©2018-2021 Olive Studios LLC and others. Fork maintained by Oak Video Editor Team."
RESOURCE "${OLIVE_ICON}"
OUTPUT_NAME "Olive"
OUTPUT_NAME "Oak"
)
# Copy the render worker and dynamic render backends into the app bundle.
# They are looked up in QCoreApplication::applicationDirPath(), which on
# macOS points to Oak.app/Contents/MacOS.
add_custom_command(TARGET olive-editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-render-worker> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakgl> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
COMMENT "Copying oak-render-worker and render backends into Oak.app"
)
if (TARGET oakvulkan)
add_custom_command(TARGET olive-editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakvulkan> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
)
endif ()
elseif (UNIX)
# Set Linux-specific properties for application
install(TARGETS olive-editor RUNTIME DESTINATION bin)
+4 -3
View File
@@ -57,7 +57,8 @@ AudioLevelMeter::AnalyzeSampleBuffer(const core::SampleBuffer &samples)
square_sum += value * value;
}
const double mean_square = square_sum / static_cast<double>(sample_count);
const double mean_square =
square_sum / static_cast<double>(sample_count);
const double rms = std::sqrt(mean_square);
ChannelStats channel_stats;
@@ -74,8 +75,8 @@ AudioLevelMeter::AnalyzeSampleBuffer(const core::SampleBuffer &samples)
}
stats.silence = qFuzzyIsNull(stats.max_peak_linear);
stats.integrated_lufs = PowerToLufs(
total_square / static_cast<double>(total_samples));
stats.integrated_lufs =
PowerToLufs(total_square / static_cast<double>(total_samples));
return stats;
}
+4 -5
View File
@@ -28,8 +28,7 @@ AudioSynchronizer::Placement AudioSynchronizer::PlaceBySourceTime(
const core::rational &reference_timeline_in)
{
Placement placement;
if (!reference.has_source_start_time ||
!candidate.has_source_start_time ||
if (!reference.has_source_start_time || !candidate.has_source_start_time ||
reference.source_start_time.isNaN() ||
candidate.source_start_time.isNaN()) {
return placement;
@@ -55,9 +54,9 @@ AudioSynchronizer::Placement AudioSynchronizer::PlaceByWaveformOffset(
return placement;
}
placement.timeline_in =
reference_timeline_in +
core::rational::fromDouble(static_cast<double>(candidate_offset_samples) /
placement.timeline_in = reference_timeline_in +
core::rational::fromDouble(
static_cast<double>(candidate_offset_samples) /
static_cast<double>(sample_rate));
placement.valid = !placement.timeline_in.isNaN();
return placement;
+4 -4
View File
@@ -41,12 +41,12 @@ public:
bool valid = false;
};
static Placement PlaceBySourceTime(const SourceClip &reference,
const SourceClip &candidate,
static Placement
PlaceBySourceTime(const SourceClip &reference, const SourceClip &candidate,
const core::rational &reference_timeline_in);
static Placement PlaceByWaveformOffset(
const core::rational &reference_timeline_in,
static Placement
PlaceByWaveformOffset(const core::rational &reference_timeline_in,
int64_t candidate_offset_samples, int sample_rate);
};
+3 -3
View File
@@ -96,7 +96,8 @@ AudioWaveformSync::OffsetResult AudioWaveformSync::EstimateEnvelopeOffset(
int64_t best_lag = 0;
for (int64_t lag = -max_offset_windows; lag <= max_offset_windows; lag++) {
const int reference_start = static_cast<int>(std::max<int64_t>(0, -lag));
const int reference_start =
static_cast<int>(std::max<int64_t>(0, -lag));
const int candidate_start = static_cast<int>(std::max<int64_t>(0, lag));
const int overlap = std::min(reference.size() - reference_start,
candidate.size() - candidate_start);
@@ -142,8 +143,7 @@ AudioWaveformSync::OffsetResult AudioWaveformSync::EstimateEnvelopeOffset(
if (best_score > -2.0) {
result.valid = true;
result.confidence = std::max(0.0, best_score);
result.offset_samples =
best_lag * static_cast<int64_t>(window_samples);
result.offset_samples = best_lag * static_cast<int64_t>(window_samples);
}
return result;
+2 -2
View File
@@ -38,8 +38,8 @@ public:
bool valid = false;
};
static QVector<double>
ExtractRmsEnvelope(const core::SampleBuffer &samples, size_t window_samples);
static QVector<double> ExtractRmsEnvelope(const core::SampleBuffer &samples,
size_t window_samples);
static OffsetResult EstimateOffset(const core::SampleBuffer &reference,
const core::SampleBuffer &candidate,
+77 -60
View File
@@ -25,11 +25,11 @@ extern "C" {
#include <libavutil/pixdesc.h>
}
namespace olive {
namespace olive
{
static FramePtr CopyPackedAVFrameToFrame(const AVFramePtr &src,
PixelFormat format,
int channel_count,
PixelFormat format, int channel_count,
const rational &timestamp)
{
if (!src || !src->data[0]) {
@@ -48,8 +48,7 @@ static FramePtr CopyPackedAVFrameToFrame(const AVFramePtr &src,
VideoParams::GetBytesPerPixel(format, channel_count);
for (int y = 0; y < frame->height(); y++) {
memcpy(frame->data() + y * frame->linesize_bytes(),
src->data[0] + y * src->linesize[0],
size_t(row_bytes));
src->data[0] + y * src->linesize[0], size_t(row_bytes));
}
return frame;
@@ -97,7 +96,8 @@ namespace olive
QVariant Yuv2RgbShader;
QVariant DeinterlaceShader;
namespace {
namespace
{
constexpr int64_t kAnalyzeDurationUs = 5000000;
constexpr int64_t kProbeSizeBytes = 20000000;
@@ -133,8 +133,9 @@ void DiscardSubtitleStreams(AVFormatContext *ctx)
}
}
TimecodeMetadata::SourceTime ExtractSourceStartTime(
AVDictionary *metadata, const rational &timebase, int sample_rate)
TimecodeMetadata::SourceTime ExtractSourceStartTime(AVDictionary *metadata,
const rational &timebase,
int sample_rate)
{
if (!metadata) {
return TimecodeMetadata::SourceTime();
@@ -458,14 +459,15 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p)
AVFramePtr dest = CreateAVFramePtr();
dest->width = f->width;
dest->height = f->height;
dest->format = p.maximum_format == PixelFormat::U8
? AV_PIX_FMT_RGBA
: AV_PIX_FMT_RGBA64;
dest->format = p.maximum_format == PixelFormat::U8 ? AV_PIX_FMT_RGBA :
AV_PIX_FMT_RGBA64;
dest->color_range = f->color_range;
dest->colorspace = f->colorspace;
if (p.divider > 1) {
dest->width = VideoParams::GetScaledDimension(dest->width, p.divider);
dest->height = VideoParams::GetScaledDimension(dest->height, p.divider);
dest->width =
VideoParams::GetScaledDimension(dest->width, p.divider);
dest->height =
VideoParams::GetScaledDimension(dest->height, p.divider);
}
int r = av_frame_get_buffer(dest.get(), 0);
@@ -504,8 +506,8 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p)
// zero-initializes the destination, leaving alpha at 0. The color
// management shader later multiplies RGB by alpha, producing black.
// Ensure alpha is opaque for source formats that have no alpha.
const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(
static_cast<AVPixelFormat>(f->format));
const AVPixFmtDescriptor *src_desc =
av_pix_fmt_desc_get(static_cast<AVPixelFormat>(f->format));
if (src_desc && !(src_desc->flags & AV_PIX_FMT_FLAG_ALPHA)) {
const int bpc = (dest->format == AV_PIX_FMT_RGBA) ? 1 : 2;
const int stride = dest->linesize[0];
@@ -522,11 +524,10 @@ FramePtr FFmpegDecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p)
}
return CopyPackedAVFrameToFrame(dest,
dest->format == AV_PIX_FMT_RGBA
? PixelFormat::U8
: PixelFormat::U16,
VideoParams::kRGBAChannelCount,
p.time);
dest->format == AV_PIX_FMT_RGBA ?
PixelFormat::U8 :
PixelFormat::U16,
VideoParams::kRGBAChannelCount, p.time);
}
return nullptr;
@@ -580,7 +581,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
AVFormatContext *fmt_ctx = nullptr;
AVDictionary *format_opts = nullptr;
ApplyFormatOpenOptions(&format_opts);
error_code = avformat_open_input(&fmt_ctx, filename_c, nullptr, &format_opts);
error_code =
avformat_open_input(&fmt_ctx, filename_c, nullptr, &format_opts);
av_dict_free(&format_opts);
TuneFormatContext(fmt_ctx);
DiscardSubtitleStreams(fmt_ctx);
@@ -591,9 +593,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
avformat_find_stream_info(fmt_ctx, nullptr);
int64_t footage_duration = fmt_ctx->duration;
TimecodeMetadata::SourceTime source_start_time =
ExtractSourceStartTime(fmt_ctx->metadata, rational(1, AV_TIME_BASE),
0);
TimecodeMetadata::SourceTime source_start_time = ExtractSourceStartTime(
fmt_ctx->metadata, rational(1, AV_TIME_BASE), 0);
bool duration_guessed_from_bitrate =
(fmt_ctx->duration_estimation_method ==
@@ -629,12 +630,14 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone;
VideoParams::Interlacing interlacing =
VideoParams::kInterlaceNone;
AVRational pixel_aspect_ratio = { 1, 1 };
AVRational frame_rate = avstream->avg_frame_rate;
AVPixelFormat compatible_pix_fmt =
FFmpegUtils::GetCompatiblePixelFormat(
static_cast<AVPixelFormat>(avstream->codecpar->format));
static_cast<AVPixelFormat>(
avstream->codecpar->format));
bool image_is_still = false;
{
@@ -643,15 +646,17 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
goto cleanup;
AVCodecContext *avctx = instance.codec_ctx();
interlacing = FFmpegFieldOrderToOlive(avctx->field_order);
interlacing =
FFmpegFieldOrderToOlive(avctx->field_order);
if (instance.GetFrame(pkt, frame) >= 0) {
pixel_aspect_ratio =
av_guess_sample_aspect_ratio(instance.fmt_ctx(),
instance.avstream(), frame);
frame_rate =
av_guess_frame_rate(instance.fmt_ctx(),
instance.avstream(), frame);
av_guess_sample_aspect_ratio(
instance.fmt_ctx(), instance.avstream(),
frame);
frame_rate = av_guess_frame_rate(
instance.fmt_ctx(), instance.avstream(),
frame);
}
int ret = instance.GetFrame(pkt, frame);
@@ -660,7 +665,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
} else if (avstream->duration == AV_NOPTS_VALUE ||
duration_guessed_from_bitrate) {
int64_t last_ts = frame->best_effort_timestamp;
while (instance.GetFrame(pkt, frame) >= 0 &&
while (
instance.GetFrame(pkt, frame) >= 0 &&
(!cancelled || !cancelled->IsCancelled()))
last_ts = frame->best_effort_timestamp;
avstream->duration = last_ts;
@@ -677,26 +683,30 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
stream.set_stream_index(i);
stream.set_width(avstream->codecpar->width);
stream.set_height(avstream->codecpar->height);
stream.set_video_type(image_is_still ? VideoParams::kVideoTypeStill
: VideoParams::kVideoTypeVideo);
stream.set_format(GetNativePixelFormat(compatible_pix_fmt));
stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt));
stream.set_video_type(image_is_still ?
VideoParams::kVideoTypeStill :
VideoParams::kVideoTypeVideo);
stream.set_format(
GetNativePixelFormat(compatible_pix_fmt));
stream.set_channel_count(
GetNativeChannelCount(compatible_pix_fmt));
stream.set_interlacing(interlacing); // <-- 已正确填充
stream.set_pixel_aspect_ratio(pixel_aspect_ratio);
stream.set_frame_rate(frame_rate);
stream.set_start_time(avstream->start_time);
stream.set_time_base(avstream->time_base);
stream.set_duration(avstream->duration);
stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG
? VideoParams::kColorRangeFull
: VideoParams::kColorRangeLimited);
stream.set_color_range(
avstream->codecpar->color_range ==
AVCOL_RANGE_JPEG ?
VideoParams::kColorRangeFull :
VideoParams::kColorRangeLimited);
stream.set_premultiplied_alpha(false);
desc.AddVideoStream(stream);
image_is_still ? still_streams++ : video_streams++;
}
} else if (avstream->codecpar->codec_type ==
AVMEDIA_TYPE_AUDIO) {
// Create an audio stream object
@@ -1270,14 +1280,14 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time,
AVFramePtr FFmpegDecoder::TransferHardwareFrame(AVFramePtr f)
{
if (!instance_.hwaccel_enabled() ||
f->format != instance_.hw_pix_fmt()) {
if (!instance_.hwaccel_enabled() || f->format != instance_.hw_pix_fmt()) {
return f;
}
AVFrame *sw_frame = av_frame_alloc();
if (!sw_frame) {
qCritical() << "Failed to allocate software frame for hardware transfer";
qCritical()
<< "Failed to allocate software frame for hardware transfer";
return nullptr;
}
@@ -1291,7 +1301,8 @@ AVFramePtr FFmpegDecoder::TransferHardwareFrame(AVFramePtr f)
ret = av_frame_copy_props(sw_frame, f.get());
if (ret < 0) {
qWarning() << "Failed to copy frame properties during hardware transfer:"
qWarning()
<< "Failed to copy frame properties during hardware transfer:"
<< FFmpegError(ret);
}
@@ -1372,7 +1383,8 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
// Open file in a format context
AVDictionary *format_opts = nullptr;
ApplyFormatOpenOptions(&format_opts);
int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts);
int error_code =
avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts);
av_dict_free(&format_opts);
TuneFormatContext(fmt_ctx_);
DiscardSubtitleStreams(fmt_ctx_);
@@ -1437,13 +1449,14 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
error_code = avcodec_open2(codec_ctx_, codec, &opts_);
if (error_code == 0) {
hwaccel_enabled_ = true;
qDebug() << "Hardware decoding enabled for" << filename
<< "using" << av_hwdevice_get_type_name(hw_device_type_)
qDebug() << "Hardware decoding enabled for" << filename << "using"
<< av_hwdevice_get_type_name(hw_device_type_)
<< "pixel format" << av_get_pix_fmt_name(hw_pix_fmt_);
return true;
}
qWarning() << "Failed to open hardware codec, falling back to software decoding:";
qWarning()
<< "Failed to open hardware codec, falling back to software decoding:";
char buf[512];
av_strerror(error_code, buf, 512);
qWarning() << FFmpegError(error_code) << buf;
@@ -1454,11 +1467,13 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
codec_ctx_ = avcodec_alloc_context3(codec);
if (codec_ctx_ == nullptr) {
qCritical() << "Failed to allocate codec context for software fallback";
qCritical()
<< "Failed to allocate codec context for software fallback";
return false;
}
error_code = avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar);
error_code =
avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar);
if (error_code < 0) {
qCritical()
<< "Failed to copy parameters from AVStream to AVCodecContext";
@@ -1494,7 +1509,8 @@ AVHWDeviceType FFmpegDecoder::Instance::ChooseHardwareDevice()
}
}
#elif defined(Q_OS_WIN)
for (AVHWDeviceType type : { AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2,
for (AVHWDeviceType type :
{ AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_CUDA }) {
if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(type)) !=
AV_HWDEVICE_TYPE_NONE) {
@@ -1502,17 +1518,17 @@ AVHWDeviceType FFmpegDecoder::Instance::ChooseHardwareDevice()
}
}
#elif defined(Q_OS_MACOS)
if (av_hwdevice_find_type_by_name(
av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_VIDEOTOOLBOX)) !=
AV_HWDEVICE_TYPE_NONE) {
if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(
AV_HWDEVICE_TYPE_VIDEOTOOLBOX)) != AV_HWDEVICE_TYPE_NONE) {
return AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
}
#endif
return AV_HWDEVICE_TYPE_NONE;
}
AVPixelFormat FFmpegDecoder::Instance::GetHardwareFormat(
AVCodecContext *ctx, const AVPixelFormat *pix_fmts)
AVPixelFormat
FFmpegDecoder::Instance::GetHardwareFormat(AVCodecContext *ctx,
const AVPixelFormat *pix_fmts)
{
const Instance *inst = static_cast<const Instance *>(ctx->opaque);
for (const AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
@@ -1521,7 +1537,8 @@ AVPixelFormat FFmpegDecoder::Instance::GetHardwareFormat(
}
}
qWarning() << "Hardware pixel format not supported by decoder, using first software format";
qWarning()
<< "Hardware pixel format not supported by decoder, using first software format";
return pix_fmts[0];
}
@@ -1547,8 +1564,8 @@ bool FFmpegDecoder::Instance::InitHardwareAcceleration(const AVCodec *codec)
}
if (hw_pix_fmt_ == AV_PIX_FMT_NONE) {
qDebug() << "Codec" << codec->id
<< "does not support hardware device type"
qDebug()
<< "Codec" << codec->id << "does not support hardware device type"
<< av_hwdevice_get_type_name(device_type);
return false;
}
+2 -1
View File
@@ -74,7 +74,8 @@ protected:
virtual bool OpenInternal() override;
virtual TexturePtr
RetrieveVideoInternal(const RetrieveVideoParams &p) override;
virtual FramePtr RetrieveVideoFrameInternal(const RetrieveVideoParams &p) override;
virtual FramePtr
RetrieveVideoFrameInternal(const RetrieveVideoParams &p) override;
virtual bool ConformAudioInternal(const QVector<QString> &filenames,
const AudioParams &params,
CancelAtom *cancelled) override;
+2 -1
View File
@@ -185,7 +185,8 @@ FramePtr OIIODecoder::RetrieveVideoFrameInternal(const RetrieveVideoParams &p)
if (!frame->allocate()) {
return nullptr;
}
memcpy(frame->data(), buffer_.const_data(), size_t(buffer_.allocated_size()));
memcpy(frame->data(), buffer_.const_data(),
size_t(buffer_.allocated_size()));
return frame;
}
+2 -1
View File
@@ -51,7 +51,8 @@ protected:
virtual bool OpenInternal() override;
virtual TexturePtr
RetrieveVideoInternal(const RetrieveVideoParams &p) override;
virtual FramePtr RetrieveVideoFrameInternal(const RetrieveVideoParams &p) override;
virtual FramePtr
RetrieveVideoFrameInternal(const RetrieveVideoParams &p) override;
virtual void CloseInternal() override;
private:
+12 -14
View File
@@ -52,14 +52,12 @@ QString ProxyManager::GetProxyFilename(const QString &cache_path,
const QString proxy_dir = GetProxyDirectory(cache_path);
const QString extension =
params.extension.isEmpty() ? QStringLiteral("mp4") : params.extension;
const QString filename = QStringLiteral("%1-%2.%3x%4.v%5.%6")
.arg(FileFunctions::GetUniqueFileIdentifier(
source_filename),
QString::number(stream_index),
QString::number(params.width),
const QString filename =
QStringLiteral("%1-%2.%3x%4.v%5.%6")
.arg(FileFunctions::GetUniqueFileIdentifier(source_filename),
QString::number(stream_index), QString::number(params.width),
QString::number(params.height),
QString::number(params.version),
extension);
QString::number(params.version), extension);
return QDir(proxy_dir).filePath(filename);
}
@@ -119,9 +117,10 @@ ProxyManager::ProxyStateFromString(const QString &state)
return kProxyMissing;
}
ProxyManager::Proxy ProxyManager::GetOrStartProxy(
const QString &cache_path, const QString &source_filename,
int stream_index, const ProxyParams &params)
ProxyManager::Proxy
ProxyManager::GetOrStartProxy(const QString &cache_path,
const QString &source_filename, int stream_index,
const ProxyParams &params)
{
QMutexLocker locker(&mutex_);
@@ -145,10 +144,9 @@ ProxyManager::Proxy ProxyManager::GetOrStartProxy(
}
const QString working_filename = GetWorkingProxyFilename(filename);
ProxyTask *task = new ProxyTask(source_filename, stream_index, params,
working_filename);
connect(task, &Task::Finished, this,
&ProxyManager::ProxyTaskFinished);
ProxyTask *task =
new ProxyTask(source_filename, stream_index, params, working_filename);
connect(task, &Task::Finished, this, &ProxyManager::ProxyTaskFinished);
task->moveToThread(TaskManager::instance()->thread());
QMetaObject::invokeMethod(TaskManager::instance(), "AddTask",
Qt::QueuedConnection, Q_ARG(Task *, task));
+1 -2
View File
@@ -90,8 +90,7 @@ public:
static ProxyState ProxyStateFromString(const QString &state);
Proxy GetOrStartProxy(const QString &cache_path,
const QString &source_filename,
int stream_index,
const QString &source_filename, int stream_index,
const ProxyParams &params);
signals:
+9 -8
View File
@@ -28,8 +28,9 @@
namespace olive
{
TimecodeMetadata::SourceTime TimecodeMetadata::FromTimecodeString(
const QString &timecode, const core::rational &timebase)
TimecodeMetadata::SourceTime
TimecodeMetadata::FromTimecodeString(const QString &timecode,
const core::rational &timebase)
{
SourceTime result;
const QString trimmed = timecode.trimmed();
@@ -39,8 +40,8 @@ TimecodeMetadata::SourceTime TimecodeMetadata::FromTimecodeString(
bool ok = false;
const core::Timecode::Display display =
trimmed.contains(';') ? core::Timecode::kTimecodeDropFrame
: core::Timecode::kTimecodeNonDropFrame;
trimmed.contains(';') ? core::Timecode::kTimecodeDropFrame :
core::Timecode::kTimecodeNonDropFrame;
result.time = core::Timecode::timecode_to_time(trimmed.toStdString(),
timebase, display, &ok);
result.valid = ok;
@@ -50,8 +51,9 @@ TimecodeMetadata::SourceTime TimecodeMetadata::FromTimecodeString(
return result;
}
TimecodeMetadata::SourceTime TimecodeMetadata::FromBwfTimeReference(
const QString &time_reference, int sample_rate)
TimecodeMetadata::SourceTime
TimecodeMetadata::FromBwfTimeReference(const QString &time_reference,
int sample_rate)
{
SourceTime result;
if (sample_rate <= 0) {
@@ -73,8 +75,7 @@ TimecodeMetadata::SourceTime TimecodeMetadata::FromBwfTimeReference(
const qulonglong rational_limit =
static_cast<qulonglong>(std::numeric_limits<int>::max());
if (numerator <= rational_limit && denominator <= rational_limit) {
result.time =
core::rational(static_cast<int>(numerator),
result.time = core::rational(static_cast<int>(numerator),
static_cast<int>(denominator));
} else {
result.time = core::rational::fromDouble(
+3 -3
View File
@@ -73,10 +73,12 @@ public:
return plugin_cache_;
}
void setPluginCache(std::shared_ptr<OFX::Host::ImageEffect::PluginCache> cache)
void
setPluginCache(std::shared_ptr<OFX::Host::ImageEffect::PluginCache> cache)
{
plugin_cache_ = cache;
}
private:
static Current current;
olive::VideoParams currentVideoParams_;
@@ -85,6 +87,4 @@ private:
std::shared_ptr<OFX::Host::ImageEffect::PluginCache> plugin_cache_;
};
#endif //CURRENT_H
+2 -2
View File
@@ -341,8 +341,8 @@ void Config::Load()
// Config::Load() is called before Core (and therefore the main window)
// is constructed, so we cannot use Core::instance()->main_window() as
// the message box parent. Passing nullptr creates a top-level dialog.
QWidget *parent = Core::instance() ? Core::instance()->main_window()
: nullptr;
QWidget *parent = Core::instance() ? Core::instance()->main_window() :
nullptr;
QMessageBox::critical(
parent,
QCoreApplication::translate("Config", "Error loading settings"),
+18 -18
View File
@@ -80,7 +80,8 @@
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
namespace {
namespace
{
QStringList FootageVideoExtensions()
{
@@ -95,22 +96,21 @@ QStringList FootageVideoExtensions()
QStringList FootageAudioExtensions()
{
return QStringList{
QStringLiteral("wav"), QStringLiteral("mp3"), QStringLiteral("flac"),
QStringLiteral("aac"), QStringLiteral("ogg"), QStringLiteral("opus"),
QStringLiteral("m4a"), QStringLiteral("alac"), QStringLiteral("aif"),
QStringLiteral("aiff"), QStringLiteral("aifc"), QStringLiteral("wma")
};
return QStringList{ QStringLiteral("wav"), QStringLiteral("mp3"),
QStringLiteral("flac"), QStringLiteral("aac"),
QStringLiteral("ogg"), QStringLiteral("opus"),
QStringLiteral("m4a"), QStringLiteral("alac"),
QStringLiteral("aif"), QStringLiteral("aiff"),
QStringLiteral("aifc"), QStringLiteral("wma") };
}
QStringList FootageImageExtensions()
{
return QStringList{
QStringLiteral("png"), QStringLiteral("jpg"), QStringLiteral("jpeg"),
QStringLiteral("tif"), QStringLiteral("tiff"), QStringLiteral("bmp"),
QStringLiteral("gif"), QStringLiteral("exr"), QStringLiteral("dpx"),
QStringLiteral("webp")
};
return QStringList{ QStringLiteral("png"), QStringLiteral("jpg"),
QStringLiteral("jpeg"), QStringLiteral("tif"),
QStringLiteral("tiff"), QStringLiteral("bmp"),
QStringLiteral("gif"), QStringLiteral("exr"),
QStringLiteral("dpx"), QStringLiteral("webp") };
}
QString BuildFootageFilterGroup(const QString &label,
@@ -122,8 +122,8 @@ QString BuildFootageFilterGroup(const QString &label,
patterns.append(QStringLiteral("*.%1").arg(ext));
}
return QStringLiteral("%1 (%2)")
.arg(label, patterns.join(QLatin1Char(' ')));
return QStringLiteral("%1 (%2)").arg(label,
patterns.join(QLatin1Char(' ')));
}
QString BuildFootageFileDialogFilter()
@@ -457,9 +457,9 @@ void Core::DialogAboutShow()
void Core::DialogImportShow()
{
// Open dialog for user to select files
QStringList files = QFileDialog::getOpenFileNames(
main_window_, tr("Import footage..."), QString(),
FootageFileDialogFilter());
QStringList files =
QFileDialog::getOpenFileNames(main_window_, tr("Import footage..."),
QString(), FootageFileDialogFilter());
// Check if the user actually selected files to import
if (!files.isEmpty()) {
+4 -1
View File
@@ -22,6 +22,9 @@ add_executable(
$<TARGET_OBJECTS:olive-version-obj>
)
# Rename the generated binary to oak-crashhandler so it matches the Oak branding.
set_target_properties(olive-crashhandler PROPERTIES OUTPUT_NAME "oak-crashhandler")
# Disable console appearing on crash handler dialog
set_target_properties(olive-crashhandler PROPERTIES
WIN32_EXECUTABLE TRUE
@@ -52,7 +55,7 @@ set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}")
if (APPLE)
# Move crash handler executables inside Mac app bundle
add_custom_command(TARGET olive-crashhandler POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different olive-crashhandler $<TARGET_FILE_DIR:olive-editor>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-crashhandler> $<TARGET_FILE_DIR:olive-editor>
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} $<TARGET_FILE_DIR:olive-editor>
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} $<TARGET_FILE_DIR:olive-editor>
)
+8 -8
View File
@@ -54,8 +54,8 @@ CrashHandlerDialog::CrashHandlerDialog(const QString &report_path)
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(
tr("We're sorry, Oak Video Editor has crashed. Please help us fix it by "
layout->addWidget(new QLabel(tr(
"We're sorry, Oak Video Editor has crashed. Please help us fix it by "
"sending an error report.")));
QSplitter *splitter = new QSplitter(Qt::Vertical);
@@ -124,7 +124,7 @@ QString CrashHandlerDialog::GetSymbolPath()
#elif BUILDFLAG(IS_LINUX)
app_path.cdUp();
symbols_path =
app_path.filePath(QStringLiteral("share/olive-editor/symbols"));
app_path.filePath(QStringLiteral("share/oak-editor/symbols"));
#elif BUILDFLAG(IS_APPLE)
app_path.cdUp();
symbols_path = app_path.filePath(QStringLiteral("Resources/symbols"));
@@ -291,11 +291,11 @@ void CrashHandlerDialog::SendErrorReport()
QString symbol_bin_name;
#if BUILDFLAG(IS_WIN)
symbol_bin_name = QStringLiteral("olive-editor.pdb");
symbol_bin_name = QStringLiteral("oak-editor.pdb");
#elif BUILDFLAG(IS_APPLE)
symbol_bin_name = QStringLiteral("Olive");
symbol_bin_name = QStringLiteral("Oak");
#else
symbol_bin_name = QStringLiteral("olive-editor");
symbol_bin_name = QStringLiteral("oak-editor");
#endif
symbol_dir = QDir(symbol_dir.filePath(symbol_bin_name));
@@ -320,9 +320,9 @@ void CrashHandlerDialog::SendErrorReport()
// Create sym section
QString symbol_filename;
#if BUILDFLAG(IS_APPLE)
symbol_filename = QStringLiteral("Olive.sym");
symbol_filename = QStringLiteral("Oak.sym");
#else
symbol_filename = QStringLiteral("olive-editor.sym");
symbol_filename = QStringLiteral("oak-editor.sym");
#endif
QString symbol_full_path = symbol_dir.filePath(symbol_filename);
QHttpPart sym_part;
+2 -1
View File
@@ -66,7 +66,8 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent)
"<p>%3</p>" // Description
"<p>%4</p>" // Fork notice
"</body></html>")
.arg(QApplication::applicationName(),
.arg(
QApplication::applicationName(),
QApplication::applicationVersion(),
tr("Oak Video Editor is a free open source non-linear video editor. "
"This software is licensed under the GNU GPL Version 3."),
+2 -1
View File
@@ -79,5 +79,6 @@ while True:
break
text_file = open("patreon.h", "w", encoding="utf-8")
text_file.write("#ifndef PATREON_H\n#define PATREON_H\n\n#include <QStringList>\n\nQStringList patrons = {\n%s\n};\n\n#endif // PATREON_H\n" % name_list)
text_file.write(
"#ifndef PATREON_H\n#define PATREON_H\n\n#include <QStringList>\n\nQStringList patrons = {\n%s\n};\n\n#endif // PATREON_H\n" % name_list)
text_file.close()
-1
View File
@@ -26,7 +26,6 @@
#include <QLabel>
#include <QMessageBox>
namespace olive
{
-1
View File
@@ -24,7 +24,6 @@
#include <QGridLayout>
#include <QLabel>
namespace olive
{
+6 -3
View File
@@ -46,15 +46,18 @@ PreferencesDialog::PreferencesDialog(MainWindow *main_window, int start_tab)
AddTab(new PreferencesGeneralTab(), tr("General"));
AddTab(new PreferencesAppearanceTab(), tr("Appearance"));
AddTab(new PreferencesAudioTab(), tr("Audio"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryTimeline),
AddTab(
new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryTimeline),
tr("Timeline"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryPlayback),
AddTab(
new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryPlayback),
tr("Playback"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryProject),
tr("Project"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryNodes),
tr("Nodes"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryRendering),
AddTab(
new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryRendering),
tr("Rendering"));
AddTab(new PreferencesDiskTab(), tr("Disk"));
AddTab(new PreferencesKeyboardTab(main_window), tr("Keyboard"));
@@ -40,8 +40,7 @@ PreferencesBehaviorTab::PreferencesBehaviorTab(Category category)
AddItems({
{ tr("Auto-Seek to Imported Clips"),
QStringLiteral("EnableSeekToImport") },
{ tr("Edit Tool Also Seeks"),
QStringLiteral("EditToolAlsoSeeks") },
{ tr("Edit Tool Also Seeks"), QStringLiteral("EditToolAlsoSeeks") },
{ tr("Edit Tool Selects Links"),
QStringLiteral("EditToolSelectsLinks") },
{ tr("Enable Drag Files to Timeline"),
@@ -83,8 +82,7 @@ PreferencesBehaviorTab::PreferencesBehaviorTab(Category category)
});
break;
case kCategoryRendering:
{
case kCategoryRendering: {
QLabel *backend_label = new QLabel(tr("Graphics Backend"));
backend_label->setToolTip(
tr("Selects the graphics API Oak should request on next launch. "
@@ -100,11 +98,10 @@ PreferencesBehaviorTab::PreferencesBehaviorTab(Category category)
const QString current_backend =
OLIVE_CONFIG("GraphicsBackend").toString().toLower();
const int backend_index = graphics_backend_combobox_->findData(
current_backend.isEmpty() ? QStringLiteral("opengl")
: current_backend);
graphics_backend_combobox_->setCurrentIndex(backend_index >= 0
? backend_index
: 0);
current_backend.isEmpty() ? QStringLiteral("opengl") :
current_backend);
graphics_backend_combobox_->setCurrentIndex(
backend_index >= 0 ? backend_index : 0);
QHBoxLayout *backend_layout = new QHBoxLayout();
backend_layout->addWidget(backend_label);
@@ -49,7 +49,8 @@ public:
static QString BehaviorPrefTr(const char *text)
{
return QCoreApplication::translate("olive::PreferencesBehaviorTab", text);
return QCoreApplication::translate("olive::PreferencesBehaviorTab",
text);
}
private:
@@ -105,16 +105,14 @@ PreferencesDiskTab::PreferencesDiskTab()
proxy_width_slider_ = new IntegerSlider();
proxy_width_slider_->SetMinimum(160);
proxy_width_slider_->SetMaximum(4096);
proxy_width_slider_->SetValue(
OLIVE_CONFIG("ProxyWidth").value<int>());
proxy_width_slider_->SetValue(OLIVE_CONFIG("ProxyWidth").value<int>());
proxy_layout->addWidget(proxy_width_slider_, proxy_row, 1);
proxy_layout->addWidget(new QLabel(tr("Proxy Height:")), proxy_row, 2);
proxy_height_slider_ = new IntegerSlider();
proxy_height_slider_->SetMinimum(120);
proxy_height_slider_->SetMaximum(2160);
proxy_height_slider_->SetValue(
OLIVE_CONFIG("ProxyHeight").value<int>());
proxy_height_slider_->SetValue(OLIVE_CONFIG("ProxyHeight").value<int>());
proxy_layout->addWidget(proxy_height_slider_, proxy_row, 3);
proxy_row++;
@@ -123,28 +121,22 @@ PreferencesDiskTab::PreferencesDiskTab()
proxy_crf_slider_ = new IntegerSlider();
proxy_crf_slider_->SetMinimum(0);
proxy_crf_slider_->SetMaximum(51);
proxy_crf_slider_->SetValue(
OLIVE_CONFIG("ProxyCRF").value<int>());
proxy_crf_slider_->SetValue(OLIVE_CONFIG("ProxyCRF").value<int>());
proxy_layout->addWidget(proxy_crf_slider_, proxy_row, 1);
proxy_layout->addWidget(new QLabel(tr("Proxy Preset:")), proxy_row, 2);
proxy_preset_combo_ = new QComboBox();
const QStringList presets = {
QStringLiteral("ultrafast"),
QStringLiteral("superfast"),
QStringLiteral("veryfast"),
QStringLiteral("faster"),
QStringLiteral("fast"),
QStringLiteral("medium"),
QStringLiteral("slow"),
QStringLiteral("slower"),
QStringLiteral("ultrafast"), QStringLiteral("superfast"),
QStringLiteral("veryfast"), QStringLiteral("faster"),
QStringLiteral("fast"), QStringLiteral("medium"),
QStringLiteral("slow"), QStringLiteral("slower"),
QStringLiteral("veryslow"),
};
for (const QString &preset : presets) {
proxy_preset_combo_->addItem(preset);
}
proxy_preset_combo_->setCurrentText(
OLIVE_CONFIG("ProxyPreset").toString());
proxy_preset_combo_->setCurrentText(OLIVE_CONFIG("ProxyPreset").toString());
proxy_layout->addWidget(proxy_preset_combo_, proxy_row, 3);
outer_layout->addStretch();
@@ -185,8 +177,10 @@ void PreferencesDiskTab::Accept(MultiUndoCommand *command)
OLIVE_CONFIG("DiskCacheAhead") = QVariant::fromValue(
rational::fromDouble(cache_ahead_slider_->GetValue()));
OLIVE_CONFIG("ProxyWidth") = static_cast<int>(proxy_width_slider_->GetValue());
OLIVE_CONFIG("ProxyHeight") = static_cast<int>(proxy_height_slider_->GetValue());
OLIVE_CONFIG("ProxyWidth") =
static_cast<int>(proxy_width_slider_->GetValue());
OLIVE_CONFIG("ProxyHeight") =
static_cast<int>(proxy_height_slider_->GetValue());
OLIVE_CONFIG("ProxyCRF") = static_cast<int>(proxy_crf_slider_->GetValue());
OLIVE_CONFIG("ProxyPreset") = proxy_preset_combo_->currentText();
}
@@ -169,21 +169,25 @@ PreferencesGeneralTab::PreferencesGeneralTab()
}
{
QGroupBox *behavior_groupbox = new QGroupBox(PreferencesBehaviorTab::BehaviorPrefTr("Behavior"));
QGroupBox *behavior_groupbox =
new QGroupBox(PreferencesBehaviorTab::BehaviorPrefTr("Behavior"));
QVBoxLayout *behavior_layout = new QVBoxLayout(behavior_groupbox);
layout->addWidget(behavior_groupbox);
hover_focus_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Enable hover focus"));
hover_focus_ = new QCheckBox(
PreferencesBehaviorTab::BehaviorPrefTr("Enable hover focus"));
hover_focus_->setToolTip(PreferencesBehaviorTab::BehaviorPrefTr(
"Panels will be considered focused when the mouse cursor is over them without having to click them."));
hover_focus_->setChecked(OLIVE_CONFIG("HoverFocus").toBool());
behavior_layout->addWidget(hover_focus_);
slider_ladder_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Enable slider ladder"));
slider_ladder_ = new QCheckBox(
PreferencesBehaviorTab::BehaviorPrefTr("Enable slider ladder"));
slider_ladder_->setChecked(OLIVE_CONFIG("UseSliderLadders").toBool());
behavior_layout->addWidget(slider_ladder_);
scroll_zooms_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Scrolling zooms by default"));
scroll_zooms_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr(
"Scrolling zooms by default"));
scroll_zooms_->setToolTip(PreferencesBehaviorTab::BehaviorPrefTr(
"By default, scrolling will move the view around, and holding Ctrl/Cmd will make it zoom instead. "
"Enabling this will switch those, scrolling will zoom by default, and holding Ctrl/Cmd will move the view instead."));
@@ -23,7 +23,6 @@
#include <QPushButton>
#include <QVBoxLayout>
namespace olive
{
-1
View File
@@ -27,7 +27,6 @@
#include <QPushButton>
#include <QVBoxLayout>
namespace olive
{
+8 -8
View File
@@ -148,14 +148,15 @@ int decompress_project(const QString &project)
int main(int argc, char *argv[])
{
// Set up debug handler
qInstallMessageHandler(olive::DebugHandler);
// Ignore SIGPIPE so that writing to a render-worker process that has
// already crashed/closed does not terminate the main application. QProcess
// will report the failure through its normal error path instead.
#if !defined(_WIN32)
signal(SIGPIPE, SIG_IGN);
#endif
// Set application metadata
QCoreApplication::setOrganizationName("oakvideoeditor.org");
@@ -227,8 +228,7 @@ int main(int argc, char *argv[])
auto no_plugin = parser.AddOption(
{ QStringLiteral("-no-plugin") },
QCoreApplication::translate("main", "Don't load plugins")
);
QCoreApplication::translate("main", "Don't load plugins"));
// Qt options re-implemented (add to this as necessary)
//
@@ -350,13 +350,13 @@ int main(int argc, char *argv[])
olive::Config::Current()[QStringLiteral("GraphicsBackend")]
.toString()
.toLower();
qputenv("QSG_RHI_BACKEND",
graphics_backend == QStringLiteral("vulkan")
? QByteArrayLiteral("vulkan")
: QByteArrayLiteral("opengl"));
qputenv("QSG_RHI_BACKEND", graphics_backend == QStringLiteral("vulkan") ?
QByteArrayLiteral("vulkan") :
QByteArrayLiteral("opengl"));
if (auto *gui_app = qobject_cast<QGuiApplication *>(a.get())) {
gui_app->setWindowIcon(QIcon(QStringLiteral(":/graphics/oak-logo.png")));
gui_app->setWindowIcon(
QIcon(QStringLiteral(":/graphics/oak-logo.png")));
}
if (load_plugins) {
+10 -8
View File
@@ -37,7 +37,8 @@ const QString OCIOLutNode::kDirectionInput = QStringLiteral("lut_dir_in");
#define super OCIOBaseNode
namespace {
namespace
{
bool IsSupportedLutExtension(const QString &suffix)
{
@@ -47,7 +48,8 @@ bool IsSupportedLutExtension(const QString &suffix)
bool IsMainProcess()
{
return qobject_cast<QApplication *>(QCoreApplication::instance()) != nullptr;
return qobject_cast<QApplication *>(QCoreApplication::instance()) !=
nullptr;
}
int ReadDirectionInput(const Node *node)
@@ -245,9 +247,8 @@ bool OCIOLutNode::CreateProcessorFromInputs() const
ColorProcessorPtr processor;
try {
const bool forward =
static_cast<ColorProcessor::Direction>(direction) ==
ColorProcessor::kNormal;
const bool forward = static_cast<ColorProcessor::Direction>(
direction) == ColorProcessor::kNormal;
qDebug() << "OCIOLutNode: creating processor for" << path
<< "direction=" << direction
<< "ocio_dir=" << (forward ? "FORWARD" : "INVERSE")
@@ -256,10 +257,11 @@ bool OCIOLutNode::CreateProcessorFromInputs() const
OCIO::FileTransformRcPtr transform = OCIO::FileTransform::Create();
transform->setSrc(path.toUtf8().constData());
transform->setInterpolation(OCIO::INTERP_LINEAR);
transform->setDirection(
forward ? OCIO::TRANSFORM_DIR_FORWARD : OCIO::TRANSFORM_DIR_INVERSE);
transform->setDirection(forward ? OCIO::TRANSFORM_DIR_FORWARD :
OCIO::TRANSFORM_DIR_INVERSE);
processor = ColorProcessor::Create(manager()->GetConfig()->getProcessor(transform));
processor = ColorProcessor::Create(
manager()->GetConfig()->getProcessor(transform));
} catch (const std::exception &e) {
qWarning() << "OCIO LUT processor error:" << e.what();
processor = nullptr;
@@ -87,8 +87,7 @@ void ThreeWayColorNode::Retranslate()
SetInputName(kHighlightsAmountInput, tr("Highlights Amount"));
}
ShaderCode
ThreeWayColorNode::GetShaderCode(const ShaderRequest &request) const
ShaderCode ThreeWayColorNode::GetShaderCode(const ShaderRequest &request) const
{
Q_UNUSED(request)
return ShaderCode(
+4 -7
View File
@@ -94,7 +94,6 @@ void NodeFactory::Initialize()
}
RegisterPluginNodes();
}
void NodeFactory::Destroy()
@@ -151,8 +150,7 @@ Menu *NodeFactory::CreateMenu(QWidget *parent, bool create_none_item,
// Determine final destination (support secondary grouping)
Menu *destination = top_menu;
QString sub = n->SubCategory();
if (!sub.isEmpty() &&
n->Category().contains(Node::kCategoryOpenFX)) {
if (!sub.isEmpty() && n->Category().contains(Node::kCategoryOpenFX)) {
QList<QAction *> sub_actions = top_menu->actions();
foreach (QAction *action, sub_actions) {
if (action->menu() && action->menu()->title() == sub) {
@@ -249,16 +247,15 @@ void NodeFactory::RegisterPluginNodes()
continue;
}
const QString plugin_id = QString::fromStdString(
image_effect->getIdentifier());
const QString plugin_id =
QString::fromStdString(image_effect->getIdentifier());
if (existing_ids.contains(plugin_id)) {
continue;
}
const auto &contexts = image_effect->getContexts();
if (contexts.empty()) {
qWarning() << "Skipping OFX plugin with no contexts:"
<< plugin_id;
qWarning() << "Skipping OFX plugin with no contexts:" << plugin_id;
continue;
}
std::string context = kOfxImageEffectContextFilter;
+5 -2
View File
@@ -182,7 +182,10 @@ public:
* @brief Return a sub-category string for secondary grouping
* within the primary category (e.g. "Filter" under "OpenFX").
*/
virtual QString SubCategory() const { return QString(); }
virtual QString SubCategory() const
{
return QString();
}
/**
* @brief Return a description of this node's purpose (optional for subclassing, but recommended)
@@ -1144,8 +1147,8 @@ public:
{
return plugin_instance_ ? plugin_instance_->getPlugin() : nullptr;
}
protected:
protected:
// If set, this node owns a plugin instance.
OFX::Host::ImageEffect::Instance *plugin_instance_ = nullptr;
+4 -4
View File
@@ -581,12 +581,12 @@ void ViewerOutput::set_parameters_from_footage(
found_video_params = true;
}
SetVideoParams(VideoParams(
s.width(), s.height(), using_timebase,
SetVideoParams(
VideoParams(s.width(), s.height(), using_timebase,
static_cast<PixelFormat::Format>(
OLIVE_CONFIG("OfflinePixelFormat").toInt()),
VideoParams::kInternalChannelCount, s.pixel_aspect_ratio(),
s.interlacing(), 1));
VideoParams::kInternalChannelCount,
s.pixel_aspect_ratio(), s.interlacing(), 1));
if (found_video_params) {
break;
+43 -69
View File
@@ -32,7 +32,8 @@
#include <QVector2D>
#include <QVector3D>
namespace {
namespace
{
QHash<QString, QHash<QString, QVariant>> g_plugin_param_defaults;
static bool IsNormalisedCoordSystem(const OFX::Host::Param::Base *param)
@@ -61,8 +62,7 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param)
const std::string &ofxType = param->getType();
const auto &props = param->getProperties();
if (ofxType == kOfxParamTypeInteger ||
ofxType == kOfxParamTypeChoice) {
if (ofxType == kOfxParamTypeInteger || ofxType == kOfxParamTypeChoice) {
return props.getIntProperty(kOfxParamPropDefault);
}
if (ofxType == kOfxParamTypeBoolean) {
@@ -77,14 +77,12 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param)
}
return val;
}
if (ofxType == kOfxParamTypeString ||
ofxType == kOfxParamTypeStrChoice ||
if (ofxType == kOfxParamTypeString || ofxType == kOfxParamTypeStrChoice ||
ofxType == kOfxParamTypeCustom) {
return QString::fromStdString(
props.getStringProperty(kOfxParamPropDefault));
}
if (ofxType == kOfxParamTypeRGB ||
ofxType == kOfxParamTypeRGBA) {
if (ofxType == kOfxParamTypeRGB || ofxType == kOfxParamTypeRGBA) {
const int count = (ofxType == kOfxParamTypeRGBA) ? 4 : 3;
double values[4] = { 0.0, 0.0, 0.0, 1.0 };
props.getDoublePropertyN(kOfxParamPropDefault, values, count);
@@ -92,17 +90,15 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param)
return QVariant::fromValue(
olive::core::Color(values[0], values[1], values[2], alpha));
}
if (ofxType == kOfxParamTypeDouble2D ||
ofxType == kOfxParamTypeDouble3D ||
if (ofxType == kOfxParamTypeDouble2D || ofxType == kOfxParamTypeDouble3D ||
ofxType == kOfxParamTypeInteger2D ||
ofxType == kOfxParamTypeInteger3D) {
const bool is_double =
(ofxType == kOfxParamTypeDouble2D ||
const bool is_double = (ofxType == kOfxParamTypeDouble2D ||
ofxType == kOfxParamTypeDouble3D);
const int count = (ofxType == kOfxParamTypeDouble2D ||
ofxType == kOfxParamTypeInteger2D)
? 2
: 3;
ofxType == kOfxParamTypeInteger2D) ?
2 :
3;
if (is_double) {
double values[3] = { 0.0, 0.0, 0.0 };
props.getDoublePropertyN(kOfxParamPropDefault, values, count);
@@ -156,8 +152,7 @@ QString DeduceColorSemantic(const OFX::Host::Param::Base *param,
// Rule 1: explicit color keywords → color
static const QStringList kColorKeywords = {
QStringLiteral("color"), QStringLiteral("colour"),
QStringLiteral("fill"), QStringLiteral("tint"),
QStringLiteral("key")
QStringLiteral("fill"), QStringLiteral("tint"), QStringLiteral("key")
};
for (const QString &kw : kColorKeywords) {
if (label.contains(kw) || hint.contains(kw) || name.contains(kw)) {
@@ -227,14 +222,13 @@ QString DeduceColorSemantic(const OFX::Host::Param::Base *param,
return QStringLiteral("color");
}
QHash<QString, QVariant>
BuildDefaultValues(const std::map<std::string, OFX::Host::Param::Instance *> &params)
QHash<QString, QVariant> BuildDefaultValues(
const std::map<std::string, OFX::Host::Param::Instance *> &params)
{
QHash<QString, QVariant> defaults;
for (const auto &param : params) {
const std::string &ofxType = param.second->getType();
if (ofxType == kOfxParamTypeGroup ||
ofxType == kOfxParamTypePage ||
if (ofxType == kOfxParamTypeGroup || ofxType == kOfxParamTypePage ||
ofxType == kOfxParamTypePushButton) {
continue;
}
@@ -254,7 +248,8 @@ BuildDefaultValues(const std::map<std::string, OFX::Host::Param::Instance *> &pa
return defaults;
}
}
static QString ClipLabelForName(const std::string &name,
static QString
ClipLabelForName(const std::string &name,
const OFX::Host::ImageEffect::ClipDescriptor *desc)
{
if (name == kOfxImageEffectSimpleSourceClipName) {
@@ -278,8 +273,7 @@ static QString ClipLabelForName(const std::string &name,
return QString::fromStdString(name);
}
olive::plugin::PluginNode::PluginNode(
OFX::Host::ImageEffect::Instance *plugin)
olive::plugin::PluginNode::PluginNode(OFX::Host::ImageEffect::Instance *plugin)
{
plugin_instance_ = plugin;
@@ -300,8 +294,8 @@ olive::plugin::PluginNode::PluginNode(
QHash<QString, QString> page_for_param;
auto params = plugin_instance_->getParams();
const QString plugin_id = QString::fromStdString(
plugin_instance_->getPlugin()->getIdentifier());
const QString plugin_id =
QString::fromStdString(plugin_instance_->getPlugin()->getIdentifier());
auto defaults_iter = g_plugin_param_defaults.find(plugin_id);
if (defaults_iter == g_plugin_param_defaults.end()) {
g_plugin_param_defaults.insert(plugin_id, BuildDefaultValues(params));
@@ -337,7 +331,6 @@ olive::plugin::PluginNode::PluginNode(
}
for (auto param : params) {
NodeValue::Type type = NodeValue::kNone;
const std::string &ofxType = param.second->getType();
@@ -356,14 +349,14 @@ olive::plugin::PluginNode::PluginNode(
type = NodeValue::kCombo;
} else if (ofxType == kOfxParamTypeDouble2D ||
ofxType == kOfxParamTypeInteger2D) {
type = NodeValue::kVec2;}
else if (ofxType == kOfxParamTypeDouble3D ||
type = NodeValue::kVec2;
} else if (ofxType == kOfxParamTypeDouble3D ||
ofxType == kOfxParamTypeInteger3D) {
type = NodeValue::kVec3;
} else if (ofxType == kOfxParamTypeStrChoice) {
type = NodeValue::kStrCombo;
}else if (ofxType == kOfxParamTypeBytes
|| ofxType == kOfxParamTypeCustom) {
} else if (ofxType == kOfxParamTypeBytes ||
ofxType == kOfxParamTypeCustom) {
type = NodeValue::kBinary;
} else if (ofxType == kOfxParamTypePushButton) {
type = NodeValue::kPushButton;
@@ -374,7 +367,8 @@ olive::plugin::PluginNode::PluginNode(
type = NodeValue::kNone;
}
const QString input_id = QString::fromStdString(param.second->getName());
const QString input_id =
QString::fromStdString(param.second->getName());
if (input_id.isEmpty()) {
continue;
}
@@ -395,8 +389,7 @@ olive::plugin::PluginNode::PluginNode(
if (is_secret) {
SetInputFlag(input_id, kInputFlagHidden);
}
const QString label =
QString::fromStdString(param.second->getLabel());
const QString label = QString::fromStdString(param.second->getLabel());
if (!label.isEmpty()) {
SetInputName(input_id, label);
} else {
@@ -413,33 +406,22 @@ olive::plugin::PluginNode::PluginNode(
page_for_param.value(input_id));
}
if (type == NodeValue::kColor) {
QString semantic =
DeduceColorSemantic(param.second, group_labels);
SetInputProperty(input_id,
QStringLiteral("color_semantic"),
QString semantic = DeduceColorSemantic(param.second, group_labels);
SetInputProperty(input_id, QStringLiteral("color_semantic"),
semantic);
const int dim =
(ofxType == kOfxParamTypeRGBA) ? 4 : 3;
const int dim = (ofxType == kOfxParamTypeRGBA) ? 4 : 3;
double dmin[4] = { 0, 0, 0, 0 };
double dmax[4] = { 1, 1, 1, 1 };
props.getDoublePropertyN(kOfxParamPropDisplayMin,
dmin, dim);
props.getDoublePropertyN(kOfxParamPropDisplayMax,
dmax, dim);
SetInputProperty(input_id,
QStringLiteral("min"),
dmin[0]);
SetInputProperty(input_id,
QStringLiteral("max"),
dmax[0]);
props.getDoublePropertyN(kOfxParamPropDisplayMin, dmin, dim);
props.getDoublePropertyN(kOfxParamPropDisplayMax, dmax, dim);
SetInputProperty(input_id, QStringLiteral("min"), dmin[0]);
SetInputProperty(input_id, QStringLiteral("max"), dmax[0]);
const QString hint = QString::fromStdString(
param.second->getHint());
const QString hint =
QString::fromStdString(param.second->getHint());
if (!hint.isEmpty()) {
SetInputProperty(input_id,
QStringLiteral("tooltip"),
hint);
SetInputProperty(input_id, QStringLiteral("tooltip"), hint);
}
}
if (type == NodeValue::kCombo || type == NodeValue::kStrCombo) {
@@ -447,8 +429,7 @@ olive::plugin::PluginNode::PluginNode(
QStringList option_values;
const int label_count =
props.getDimension(kOfxParamPropChoiceOption);
const int value_count =
props.getDimension(kOfxParamPropChoiceEnum);
const int value_count = props.getDimension(kOfxParamPropChoiceEnum);
for (int i = 0; i < label_count; ++i) {
const std::string &label =
@@ -478,13 +459,11 @@ olive::plugin::PluginNode::PluginNode(
indices[i] = i;
}
std::stable_sort(indices.begin(), indices.end(),
[&](int a, int b) {
return props.getIntProperty(
kOfxParamPropChoiceOrder,
std::stable_sort(
indices.begin(), indices.end(), [&](int a, int b) {
return props.getIntProperty(kOfxParamPropChoiceOrder,
a) <
props.getIntProperty(
kOfxParamPropChoiceOrder,
props.getIntProperty(kOfxParamPropChoiceOrder,
b);
});
@@ -520,15 +499,13 @@ olive::plugin::PluginNode::PluginNode(
has_texture_input = true;
}
const QString source_id =
QString::fromUtf8(kOfxImageEffectSimpleSourceClipName);
if (HasInputWithID(source_id)) {
SetEffectInput(source_id);
} else if (HasInputWithID(kTextureInput)) {
SetEffectInput(kTextureInput);
}
else {
} else {
if (has_texture_input) {
AddInput(kTextureInput, NodeValue::kTexture);
SetInputName(kTextureInput, tr("Texture"));
@@ -545,7 +522,6 @@ QString olive::plugin::PluginNode::Name() const
.getProps()
.getStringProperty(kOfxPropLabel)
.data();
}
QVector<olive::Node::CategoryID> olive::plugin::PluginNode::Category() const
@@ -565,7 +541,6 @@ QString olive::plugin::PluginNode::Description() const
.getProps()
.getStringProperty(kOfxPropPluginDescription)
.data();
}
void olive::plugin::PluginNode::ProcessSamples(const NodeValueRow &values,
const SampleBuffer &input,
@@ -689,8 +664,7 @@ QString olive::plugin::PluginNode::id() const
}
auto *node = new PluginNode(instance);
if (auto *olive_instance =
dynamic_cast<OlivePluginInstance *>(instance)) {
if (auto *olive_instance = dynamic_cast<OlivePluginInstance *>(instance)) {
olive_instance->setNode(
std::shared_ptr<PluginNode>(node, [](PluginNode *) {}));
}
+2 -5
View File
@@ -34,7 +34,6 @@ public:
PluginNode(OFX::Host::ImageEffect::Instance *plugin);
~PluginNode() override;
QString Name() const override;
QString id() const override;
QVector<CategoryID> Category() const override;
@@ -58,8 +57,8 @@ public:
* corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior
* of the NodeParam objects will handle everything related to it automatically.
*/
void Value(const NodeValueRow &value,
const NodeGlobals &globals, NodeValueTable *table) const override;
void Value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
/**
* @brief If Value() pushes a ShaderJob, this is the function that will process them.
@@ -82,11 +81,9 @@ private:
public slots:
void pushButtonClicked(QString name);
};
}
}
#endif //PLUGIN_H
+5 -7
View File
@@ -121,9 +121,8 @@ SerializedData Project::Load(QXmlStreamReader *reader)
}
}
const QString path = bundle_path.isEmpty()
? file_path
: bundle_path;
const QString path = bundle_path.isEmpty() ? file_path :
bundle_path;
if (!path.isEmpty()) {
plugin_paths.insert(path);
}
@@ -250,8 +249,7 @@ void Project::Save(QXmlStreamWriter *writer) const
seen.insert(key);
QMap<QString, QString> attrs;
attrs.insert(QStringLiteral("id"),
QString::fromStdString(id));
attrs.insert(QStringLiteral("id"), QString::fromStdString(id));
attrs.insert(QStringLiteral("major"), QString::number(major));
attrs.insert(QStringLiteral("minor"), QString::number(minor));
if (!bundle_path.isEmpty()) {
@@ -269,8 +267,8 @@ void Project::Save(QXmlStreamWriter *writer) const
writer->writeStartElement(QStringLiteral("plugins"));
for (const auto &entry : plugins_to_save) {
writer->writeStartElement(QStringLiteral("plugin"));
for (auto it = entry.second.cbegin();
it != entry.second.cend(); ++it) {
for (auto it = entry.second.cbegin(); it != entry.second.cend();
++it) {
writer->writeAttribute(it.key(), it.value());
}
writer->writeEndElement();
+5 -9
View File
@@ -242,11 +242,8 @@ void Footage::set_proxy_enabled(bool enabled)
}
}
void Footage::SetProxy(const QString &path,
ProxyManager::ProxyState state,
int video_stream_index,
int preset_version,
bool enabled)
void Footage::SetProxy(const QString &path, ProxyManager::ProxyState state,
int video_stream_index, int preset_version, bool enabled)
{
qDebug() << "Footage::SetProxy:" << filename() << "enabled=" << enabled
<< "state=" << ProxyManager::ProxyStateToString(state)
@@ -598,11 +595,10 @@ void Footage::SaveCustom(QXmlStreamWriter *writer) const
if (!proxy_path_.isEmpty() || proxy_enabled_) {
writer->writeStartElement(QStringLiteral("proxy"));
writer->writeAttribute(QStringLiteral("enabled"),
proxy_enabled_ ? QStringLiteral("1")
: QStringLiteral("0"));
proxy_enabled_ ? QStringLiteral("1") :
QStringLiteral("0"));
writer->writeAttribute(QStringLiteral("state"),
ProxyManager::ProxyStateToString(
proxy_state_));
ProxyManager::ProxyStateToString(proxy_state_));
writer->writeAttribute(QStringLiteral("stream"),
QString::number(proxy_video_stream_index_));
writer->writeAttribute(QStringLiteral("preset"),
+2 -5
View File
@@ -200,11 +200,8 @@ public:
return proxy_state_;
}
void SetProxy(const QString &path,
ProxyManager::ProxyState state,
int video_stream_index,
int preset_version,
bool enabled);
void SetProxy(const QString &path, ProxyManager::ProxyState state,
int video_stream_index, int preset_version, bool enabled);
void ClearProxy();
@@ -78,8 +78,7 @@ bool FootageDescription::Load(const QString &filename)
const QStringList split =
reader.readElementText().split('/');
if (split.size() == 2) {
SetSourceStartTime(
rational(split.at(0).toInt(),
SetSourceStartTime(rational(split.at(0).toInt(),
split.at(1).toInt()),
source);
}
+4 -1
View File
@@ -96,7 +96,10 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project,
return inner_result;
}
} else {
return kFileError;
Result r(kFileError);
r.SetDetails(QStringLiteral("Unable to open '%1': %2")
.arg(filename, project_file.errorString()));
return r;
}
}
+5 -4
View File
@@ -502,19 +502,20 @@ void NodeTraverser::ResolveJobs(NodeValue &val)
}
val.set_value(tex);
}
else if (plugin::PluginJob* plugin_job=dynamic_cast<plugin::PluginJob*>(base_job)) {
} else if (plugin::PluginJob *plugin_job =
dynamic_cast<plugin::PluginJob *>(
base_job)) {
VideoParams tex_params = job_tex->params();
// Force internal working format (F32) for plugin processing,
// matching FootageJob/GenerateJob behavior.
tex_params.set_format(GetCacheVideoParams().format());
tex_params.set_channel_count(VideoParams::kRGBAChannelCount);
tex_params.set_channel_count(
VideoParams::kRGBAChannelCount);
TexturePtr tex = CreateTexture(tex_params);
ProcessPluginJob(job_tex, tex, val.source());
val.set_value(tex);
}
// Cache resolved value
+3 -1
View File
@@ -147,7 +147,9 @@ protected:
return SampleBuffer();
}
virtual TexturePtr ProcessPluginJob(TexturePtr texture, TexturePtr destination, const Node *node);
virtual TexturePtr ProcessPluginJob(TexturePtr texture,
TexturePtr destination,
const Node *node);
SampleBuffer CreateSampleBuffer(const AudioParams &params,
const rational &length)
{
+27
View File
@@ -0,0 +1,27 @@
# Maintainer: Oak Video Editor Team
pkgname=oak-video-editor
pkgver=@VERSION@
pkgrel=1
pkgdesc="Oak - Non-linear video editor"
arch=('x86_64')
url="https://oakvideoeditor.org"
license=('GPL3')
depends=('qt6-base' 'qt6-tools' 'ffmpeg' 'openimageio' 'opencolorio' 'openexr' 'expat' 'portaudio' 'mesa' 'vulkan-icd-loader' 'libxkbcommon' 'fmt')
makedepends=('cmake' 'ninja' 'git' 'pkgconf')
source=("${pkgname}-${pkgver}.tar.gz")
md5sums=('SKIP')
build() {
cd "${srcdir}/${pkgname}-${pkgver}"
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_QT6=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_Vulkan=ON
cmake --build build
}
package() {
cd "${srcdir}/${pkgname}-${pkgver}"
DESTDIR="${pkgdir}" cmake --install build
}
+4 -4
View File
@@ -20,16 +20,16 @@
APPDIR=$(readlink -f $(dirname "$0"))
# Custom AppRun that ensures the AppImage doesn't dismount before olive-crashhandler exits
# Custom AppRun that ensures the AppImage doesn't dismount before oak-crashhandler exits
# Run main program
"$APPDIR/usr/bin/olive-editor" "$@"
"$APPDIR/usr/bin/oak-editor" "$@"
# Wait arbitrary amount of time
sleep 5
# While olive-crashhandler exists, keep sleeping
while [[ $(ps -aux | grep olive-crashhandler | grep -v grep) ]]
# While oak-crashhandler exists, keep sleeping
while [[ $(ps -aux | grep oak-crashhandler | grep -v grep) ]]
do
sleep 5
done
@@ -28,8 +28,8 @@
<p xml:lang="id">Oak Video Editor adalah aplikasi edit video bersifat non-linier yang bebas dan gratis, bertujuan untuk memberikan alternatif yang lengkap untuk aplikasi edit video profesional. Ini adalah fork komunitas dari Olive Video Editor.</p>
<p xml:lang="fr">Oak Video Editor est un éditeur vidéo non linéaire libre visant à fournir une alternative complète aux logiciels de montage vidéo professionnels haut de gamme. Il s'agit d'un fork communautaire d'Olive Video Editor.</p>
</description>
<url type="homepage">https://github.com/olive-editor/olive</url>
<url type="bugtracker">https://github.com/olive-editor/olive/issues</url>
<url type="homepage">https://github.com/OakVideoEditorCommunity/oak</url>
<url type="bugtracker">https://github.com/OakVideoEditorCommunity/oak/issues</url>
<screenshots>
<screenshot type="default"><image>https://olivevideoeditor.org/img/screenshot.1600.jpg</image></screenshot>
</screenshots>
@@ -4,7 +4,7 @@ Comment=Professional open-source non-linear video editor
Comment[fr]=Éditeur vidéo non-linéaire open-source professionnel
Comment[it]=Programma di montaggio video professionale open-source
Comment[id]=Aplikasi edit video yang non-linier, profesional serta sumbernya terbuka.
Exec=olive-editor %f
Exec=oak-editor %f
Icon=org.oakvideoeditor.Oak
Terminal=false
Type=Application
@@ -4,9 +4,9 @@
!define MUI_UNICON "uninstall icon.ico"
!define APP_NAME "Oak Video Editor"
!define APP_TARGET "olive-editor"
!define APP_TARGET "oak-editor"
!define MUI_FINISHPAGE_RUN "$INSTDIR\olive-editor.exe"
!define MUI_FINISHPAGE_RUN "$INSTDIR\oak-editor.exe"
SetCompressor lzma
@@ -38,7 +38,15 @@ InstallDir "$PROGRAMFILES32\${APP_NAME}"
Section "Oak Video Editor"
SectionIn RO
SetOutPath $INSTDIR
File /r olive-editor\*
File /r oak-editor\*
# Render worker process must live next to the editor binary
File "oak-editor\oak-render-worker.exe"
# Render backends must also live next to the editor binary
File "oak-editor\oakgl.dll"
File /nonfatal "oak-editor\oakvulkan.dll"
WriteUninstaller "$INSTDIR\uninstall.exe"
# Install Visual C++ 2010 Redistributable
@@ -61,8 +69,8 @@ Section "Associate *.ove files with Oak Video Editor"
WriteRegStr HKCR ".ove" "" "OakEditor.OVEFile"
WriteRegStr HKCR ".ove" "Content Type" "application/vnd.olive-project"
WriteRegStr HKCR "OakEditor.OVEFile" "" "Oak project file"
WriteRegStr HKCR "OakEditor.OVEFile\DefaultIcon" "" "$INSTDIR\olive-editor.exe,1"
WriteRegStr HKCR "OakEditor.OVEFile\shell\open\command" "" "$\"$INSTDIR\olive-editor.exe$\" $\"%1$\""
WriteRegStr HKCR "OakEditor.OVEFile\DefaultIcon" "" "$INSTDIR\oak-editor.exe,1"
WriteRegStr HKCR "OakEditor.OVEFile\shell\open\command" "" "$\"$INSTDIR\oak-editor.exe$\" $\"%1$\""
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
SectionEnd
+1 -2
View File
@@ -131,8 +131,7 @@ void PanelWidget::changeEvent(QEvent *e)
if (e->type() == QEvent::WindowStateChange) {
if (isVisible() && !isMinimized()) {
emit shown(Qt::OtherFocusReason);
}
else {
} else {
emit hidden();
}
}
+1
View File
@@ -287,6 +287,7 @@ signals:
void CloseRequested();
void shown(Qt::FocusReason reason);
void hidden();
protected:
/**
* @brief paintEvent
+60 -68
View File
@@ -41,7 +41,8 @@ extern "C" {
#include <libswscale/swscale.h>
#include <libavutil/pixdesc.h>
}
namespace {
namespace
{
const std::string kBitDepthNoneStr(kOfxBitDepthNone);
const std::string kBitDepthByteStr(kOfxBitDepthByte);
const std::string kBitDepthShortStr(kOfxBitDepthShort);
@@ -117,16 +118,16 @@ static bool PackedDstInfo(AVPixelFormat fmt, int *channels,
}
}
static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture,
static olive::AVFramePtr
ReadbackTextureToFrame(olive::TexturePtr texture,
const olive::VideoParams &params)
{
if (!texture || texture->IsDummy() || !texture->renderer()) {
return nullptr;
}
AVPixelFormat pix_fmt =
olive::FFmpegUtils::GetFFmpegPixelFormat(params.format(),
params.channel_count());
AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(
params.format(), params.channel_count());
if (pix_fmt == AV_PIX_FMT_NONE) {
return nullptr;
}
@@ -145,15 +146,15 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture,
return nullptr;
}
const int linesize_pixels = BytesToPixels(frame->linesize[0], params);
texture->renderer()->DownloadFromTexture(texture->id(), params,
frame->data[0],
linesize_pixels);
texture->renderer()->DownloadFromTexture(
texture->id(), params, frame->data[0], linesize_pixels);
return frame;
}
olive::VideoParams rgba_params(
params.width(), params.height(), olive::core::PixelFormat::U8, 4,
params.pixel_aspect_ratio(), params.interlacing(), params.divider());
olive::VideoParams rgba_params(params.width(), params.height(),
olive::core::PixelFormat::U8, 4,
params.pixel_aspect_ratio(),
params.interlacing(), params.divider());
olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr();
rgba_frame->format = AV_PIX_FMT_RGBA;
@@ -165,9 +166,8 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture,
const int linesize_pixels =
BytesToPixels(rgba_frame->linesize[0], rgba_params);
texture->renderer()->DownloadFromTexture(texture->id(), rgba_params,
rgba_frame->data[0],
linesize_pixels);
texture->renderer()->DownloadFromTexture(
texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels);
olive::AVFramePtr dst = olive::CreateAVFramePtr();
dst->format = pix_fmt;
@@ -179,9 +179,8 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture,
SwsContext *sws_ctx = sws_getContext(
rgba_frame->width, rgba_frame->height,
static_cast<AVPixelFormat>(rgba_frame->format),
dst->width, dst->height, pix_fmt, SWS_POINT,
nullptr, nullptr, nullptr);
static_cast<AVPixelFormat>(rgba_frame->format), dst->width, dst->height,
pix_fmt, SWS_POINT, nullptr, nullptr, nullptr);
if (!sws_ctx) {
return rgba_frame;
}
@@ -218,9 +217,7 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
return nullptr;
}
auto clamp01 = [](float v) -> float {
return std::clamp(v, 0.0f, 1.0f);
};
auto clamp01 = [](float v) -> float { return std::clamp(v, 0.0f, 1.0f); };
for (int y = 0; y < src->height; ++y) {
const float *src_row = reinterpret_cast<const float *>(
@@ -242,17 +239,13 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
continue;
}
dst_row_u16[x * dst_channels + 0] =
static_cast<uint16_t>(
std::lround(clamp01(r) * 65535.0f));
static_cast<uint16_t>(std::lround(clamp01(r) * 65535.0f));
dst_row_u16[x * dst_channels + 1] =
static_cast<uint16_t>(
std::lround(clamp01(g) * 65535.0f));
static_cast<uint16_t>(std::lround(clamp01(g) * 65535.0f));
dst_row_u16[x * dst_channels + 2] =
static_cast<uint16_t>(
std::lround(clamp01(b) * 65535.0f));
static_cast<uint16_t>(std::lround(clamp01(b) * 65535.0f));
if (dst_channels == 4) {
dst_row_u16[x * dst_channels + 3] =
static_cast<uint16_t>(
dst_row_u16[x * dst_channels + 3] = static_cast<uint16_t>(
std::lround(clamp01(a) * 65535.0f));
}
}
@@ -270,18 +263,14 @@ static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src,
continue;
}
dst_row[x * dst_channels + 0] =
static_cast<uint8_t>(
std::lround(clamp01(r) * 255.0f));
static_cast<uint8_t>(std::lround(clamp01(r) * 255.0f));
dst_row[x * dst_channels + 1] =
static_cast<uint8_t>(
std::lround(clamp01(g) * 255.0f));
static_cast<uint8_t>(std::lround(clamp01(g) * 255.0f));
dst_row[x * dst_channels + 2] =
static_cast<uint8_t>(
std::lround(clamp01(b) * 255.0f));
static_cast<uint8_t>(std::lround(clamp01(b) * 255.0f));
if (dst_channels == 4) {
dst_row[x * dst_channels + 3] =
static_cast<uint8_t>(
std::lround(clamp01(a) * 255.0f));
static_cast<uint8_t>(std::lround(clamp01(a) * 255.0f));
}
}
}
@@ -427,7 +416,8 @@ olive::plugin::OliveClipInstance::getImage(OfxTime time,
if (name_ == "Output") {
if (!images_.contains(time)) {
// make a new ref counted image
images_.insert(time, new Image(*const_cast<OliveClipInstance *>(this),
images_.insert(time,
new Image(*const_cast<OliveClipInstance *>(this),
params_, bounds, rod, true));
}
@@ -504,7 +494,8 @@ olive::plugin::OliveClipInstance::getOutputImage(OfxTime time)
return image;
}
olive::VideoParams olive::plugin::OliveClipInstance::getPluginPreferredParams() const
olive::VideoParams
olive::plugin::OliveClipInstance::getPluginPreferredParams() const
{
VideoParams result = params_;
@@ -584,7 +575,10 @@ void olive::plugin::OliveClipInstance::setParams(const VideoParams &params)
setComponents(getUnmappedComponents());
}
void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTime time, bool readback_cpu){
void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture,
OfxTime time,
bool readback_cpu)
{
if (!texture) {
return;
}
@@ -622,9 +616,8 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
if (!frame || !frame->data[0]) {
frame = ReadbackTextureToFrame(texture, params_);
}
AVPixelFormat expected_fmt =
FFmpegUtils::GetFFmpegPixelFormat(params_.format(),
params_.channel_count());
AVPixelFormat expected_fmt = FFmpegUtils::GetFFmpegPixelFormat(
params_.format(), params_.channel_count());
if (expected_fmt == AV_PIX_FMT_NONE) {
return;
}
@@ -642,8 +635,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
false);
} else {
pruneImagesCache();
image = new Image(*this, params_, bounds,
regionOfDefinition, false);
image = new Image(*this, params_, bounds, regionOfDefinition, false);
image->EnsureAllocatedFromParams(params_, bounds, regionOfDefinition,
false);
images_.insert(time, image);
@@ -669,10 +661,12 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
int row_floats = frame->linesize[0] / static_cast<int>(sizeof(float));
bool has_nan = false;
for (int y = 0; y < params_.height() && !has_nan; ++y) {
for (int x = 0; x < params_.width() * params_.channel_count(); ++x) {
for (int x = 0; x < params_.width() * params_.channel_count();
++x) {
float v = fptr[y * row_floats + x];
if (std::isnan(v) || std::isinf(v)) {
qWarning() << "[PLUGIN] NaN/Inf detected in input frame at pixel ("
qWarning()
<< "[PLUGIN] NaN/Inf detected in input frame at pixel ("
<< x / params_.channel_count() << "," << y
<< ") channel=" << (x % params_.channel_count())
<< " value=" << v;
@@ -682,19 +676,19 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
}
}
if (has_nan) {
qWarning() << "[PLUGIN] Filling corrupted input frame with black to avoid CImg crash";
qWarning()
<< "[PLUGIN] Filling corrupted input frame with black to avoid CImg crash";
std::memset(dst, 0, image->row_bytes() * image->height());
return;
}
}
AVFramePtr src_frame = frame;
if (frame->format != expected_fmt ||
frame->width != params_.width() ||
if (frame->format != expected_fmt || frame->width != params_.width() ||
frame->height != params_.height()) {
if (PackedFloatChannels(static_cast<AVPixelFormat>(frame->format)) > 0) {
AVFramePtr converted =
ConvertPackedFloatFrame(frame, expected_fmt);
if (PackedFloatChannels(static_cast<AVPixelFormat>(frame->format)) >
0) {
AVFramePtr converted = ConvertPackedFloatFrame(frame, expected_fmt);
if (converted) {
src_frame = converted;
goto copy_pixels;
@@ -710,9 +704,8 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
SwsContext *sws_ctx = sws_getContext(
frame->width, frame->height,
static_cast<AVPixelFormat>(frame->format),
converted->width, converted->height,
static_cast<AVPixelFormat>(converted->format),
static_cast<AVPixelFormat>(frame->format), converted->width,
converted->height, static_cast<AVPixelFormat>(converted->format),
SWS_POINT, nullptr, nullptr, nullptr);
if (!sws_ctx) {
return;
@@ -727,12 +720,12 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi
copy_pixels:
int bytes_per_component = params_.format().byte_count();
int bytes_per_row = params_.width() * params_.channel_count() *
bytes_per_component;
int bytes_per_row =
params_.width() * params_.channel_count() * bytes_per_component;
int src_row_bytes = src_frame->linesize[0];
int dst_row_bytes = image->row_bytes();
int copy_bytes = std::min(bytes_per_row,
std::min(src_row_bytes, dst_row_bytes));
int copy_bytes =
std::min(bytes_per_row, std::min(src_row_bytes, dst_row_bytes));
int copy_height = std::min(image->height(), src_frame->height);
const uint8_t *src = src_frame->data[0];
@@ -754,7 +747,8 @@ copy_pixels:
}
}
if (has_nan) {
qWarning() << "[PLUGIN] NaN/Inf scrubbed from input frame data during copy";
qWarning()
<< "[PLUGIN] NaN/Inf scrubbed from input frame data during copy";
}
} else if (dst_row_bytes == src_row_bytes && src_row_bytes == copy_bytes) {
std::memcpy(dst, src, copy_bytes * copy_height);
@@ -764,8 +758,6 @@ copy_pixels:
copy_bytes);
}
}
}
void olive::plugin::OliveClipInstance::setOutputTexture(TexturePtr texture,
@@ -818,17 +810,17 @@ olive::plugin::OliveClipInstance::loadTexture(OfxTime time, const char *format,
bounds.x2 = std::min(bounds.x2, rod.x2);
bounds.y2 = std::min(bounds.y2, rod.y2);
const int bytes_per_row =
params_.width() * params_.channel_count() * params_.format().byte_count();
const int bytes_per_row = params_.width() * params_.channel_count() *
params_.format().byte_count();
const std::string &field = getFieldOrder();
const std::string unique_id = std::to_string(
reinterpret_cast<uintptr_t>(gl_texture.get())) + "_" +
const std::string unique_id =
std::to_string(reinterpret_cast<uintptr_t>(gl_texture.get())) + "_" +
std::to_string(static_cast<long long>(time));
const int texture_id = gl_texture->id().value<GLuint>();
OFX::Host::ImageEffect::Texture *texture =
new OFX::Host::ImageEffect::Texture(
*this, 1.0, 1.0, texture_id, GL_TEXTURE_2D, bounds, rod,
new OFX::Host::ImageEffect::Texture(*this, 1.0, 1.0, texture_id,
GL_TEXTURE_2D, bounds, rod,
bytes_per_row, field, unique_id);
texture->addReference();
return texture;
+10 -8
View File
@@ -38,7 +38,8 @@ namespace plugin
class OliveClipInstance : public OFX::Host::ImageEffect::ClipInstance {
public:
OliveClipInstance(OFX::Host::ImageEffect::Instance *effectInstance,
OFX::Host::ImageEffect::ClipDescriptor& desc,VideoParams &params)
OFX::Host::ImageEffect::ClipDescriptor &desc,
VideoParams &params)
: ClipInstance(effectInstance, desc)
, params_(params)
, defaultRegionOfDefinitions_{ 0, 0, 0, 0 }
@@ -56,21 +57,24 @@ public:
const std::string &getFieldOrder() const override;
bool getConnected() const override;
double getUnmappedFrameRate() const override;
void getUnmappedFrameRange(double &startFrame, double &endFrame) const override;
void getUnmappedFrameRange(double &startFrame,
double &endFrame) const override;
bool getContinuousSamples() const override;
OFX::Host::ImageEffect::Image* getImage(OfxTime time, const OfxRectD *optionalBounds) override;
OFX::Host::ImageEffect::Image *
getImage(OfxTime time, const OfxRectD *optionalBounds) override;
OfxRectD getRegionOfDefinition(OfxTime time) const override;
void setRegionOfDefinition(OfxRectD regionOfDefinition, OfxTime time);
void setDefaultRegionOfDefinition(OfxRectD regionOfDefinition);
void setParams(const VideoParams &params);
#ifdef OFX_SUPPORTS_OPENGLRENDER
OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time,
const char *format,
OFX::Host::ImageEffect::Texture *
loadTexture(OfxTime time, const char *format,
const OfxRectD *optionalBounds) override;
#endif
void setInputTexture(TexturePtr texture, OfxTime time, bool readback_cpu = true);
void setInputTexture(TexturePtr texture, OfxTime time,
bool readback_cpu = true);
void setOutputTexture(TexturePtr texture, OfxTime time);
// Get the plugin-preferred VideoParams based on base class _pixelDepth/_components
@@ -99,6 +103,4 @@ private:
}
}
#endif //OLIVECLIP_H
+26 -18
View File
@@ -37,14 +37,18 @@
using namespace OFX::Host;
using namespace olive::plugin;
namespace olive {
namespace plugin {
namespace olive
{
namespace plugin
{
class PluginNode;
}
}
namespace {
void AddPluginPath(OFX::Host::PluginCache *cache, const QString &path, bool recurse = true)
namespace
{
void AddPluginPath(OFX::Host::PluginCache *cache, const QString &path,
bool recurse = true)
{
if (!cache || path.isEmpty()) {
return;
@@ -80,7 +84,8 @@ void olive::plugin::loadPlugins(QString path)
host = std::make_shared<OliveHost>();
Current::getInstance().setPluginHost(host);
imageEffectPluginCache = std::make_shared<ImageEffect::PluginCache>(*host);
imageEffectPluginCache =
std::make_shared<ImageEffect::PluginCache>(*host);
Current::getInstance().setPluginCache(imageEffectPluginCache);
imageEffectPluginCache->registerInCache(
@@ -92,7 +97,8 @@ void olive::plugin::loadPlugins(QString path)
const QString home_path = QDir::homePath();
AddPluginPath(cache, QDir(home_path).filePath(".OFX/Plugins"));
AddPluginPath(cache, QDir(home_path).filePath(".local/share/OFX/Plugins"));
AddPluginPath(cache, QDir(home_path).filePath(".local/share/olive/ofx/Plugins"));
AddPluginPath(cache,
QDir(home_path).filePath(".local/share/olive/ofx/Plugins"));
const QString app_dir = QCoreApplication::applicationDirPath();
AddPluginPath(cache, QDir(app_dir).filePath("../OFX/Plugins"));
@@ -109,7 +115,6 @@ void olive::plugin::loadPlugins(QString path)
}
OliveHost::~OliveHost()
{
}
void OliveHost::destroyInstance(OFX::Host::ImageEffect::Instance *instance)
@@ -151,10 +156,11 @@ OliveHost::makeDescriptor(const std::string &bundlePath,
return desc;
}
ImageEffect::Instance* OliveHost::newInstance(void *clientData,
ImageEffect::ImageEffectPlugin* plugin,
ImageEffect::Instance *
OliveHost::newInstance(void *clientData, ImageEffect::ImageEffectPlugin *plugin,
ImageEffect::Descriptor &desc,
const std::string& context){
const std::string &context)
{
auto *instance = new OlivePluginInstance(
plugin, desc, context, Current::getInstance().interactive());
if (clientData) {
@@ -165,8 +171,9 @@ ImageEffect::Instance* OliveHost::newInstance(void *clientData,
instances_.append(std::shared_ptr<OlivePluginInstance>(instance));
return instance;
};
OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, const char *format,
va_list args){
OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id,
const char *format, va_list args)
{
if (!type || !format) {
return kOfxStatFailed;
}
@@ -178,8 +185,7 @@ OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, c
auto *app = qobject_cast<QApplication *>(QCoreApplication::instance());
if (!app) {
qWarning().noquote()
<< "OFX message:" << type << message;
qWarning().noquote() << "OFX message:" << type << message;
if (strcmp(type, kOfxMessageQuestion) == 0) {
return kOfxStatReplyNo;
}
@@ -187,8 +193,8 @@ OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, c
}
if (strcmp(type, kOfxMessageQuestion) == 0) {
auto ret = QMessageBox::question(nullptr, "", message,
QMessageBox::Ok, QMessageBox::Cancel);
auto ret = QMessageBox::question(nullptr, "", message, QMessageBox::Ok,
QMessageBox::Cancel);
return (ret == QMessageBox::Ok) ? kOfxStatReplyYes : kOfxStatReplyNo;
}
@@ -203,8 +209,10 @@ OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, c
return kOfxStatOK;
}
// TODO: Persistent messages shouldn't use pop-up window.
OfxStatus olive::plugin::OliveHost::setPersistentMessage(
const char *type, const char *id, const char *format, va_list args)
OfxStatus olive::plugin::OliveHost::setPersistentMessage(const char *type,
const char *id,
const char *format,
va_list args)
{
if (!type || !format) {
return kOfxStatFailed;
+32 -26
View File
@@ -43,7 +43,8 @@ namespace olive
{
namespace plugin
{
namespace {
namespace
{
const std::string kImageFieldNoneStr(kOfxImageFieldNone);
const std::string kImageFieldUpperStr(kOfxImageFieldUpper);
const std::string kImageFieldLowerStr(kOfxImageFieldLower);
@@ -62,7 +63,8 @@ QString FormatOfxMessage(const char *format, va_list args)
return QString::fromUtf8(buffer);
}
QByteArray dynamic_buffer(needed + 1, 0);
const int written = vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args);
const int written =
vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args);
if (written < 0) {
return QString();
}
@@ -136,7 +138,8 @@ ViewerOutput *GetActiveViewerOutput()
}
}
QList<TimelinePanel *> timelines = manager->GetPanelsOfType<TimelinePanel>();
QList<TimelinePanel *> timelines =
manager->GetPanelsOfType<TimelinePanel>();
for (TimelinePanel *panel : timelines) {
if (panel && panel->GetConnectedViewer()) {
return panel->GetConnectedViewer();
@@ -180,7 +183,8 @@ OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id,
if (is_question) {
const auto ret = QMessageBox::question(
nullptr, "", message, QMessageBox::Ok, QMessageBox::Cancel);
result = (ret == QMessageBox::Ok) ? kOfxStatReplyYes : kOfxStatReplyNo;
result = (ret == QMessageBox::Ok) ? kOfxStatReplyYes :
kOfxStatReplyNo;
} else {
QMessageBox::information(nullptr, "", message);
result = kOfxStatOK;
@@ -191,7 +195,8 @@ OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id,
show_message();
} else if (auto *app = QCoreApplication::instance()) {
if (is_question) {
QMetaObject::invokeMethod(app, show_message, Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(app, show_message,
Qt::BlockingQueuedConnection);
} else {
QMetaObject::invokeMethod(app, show_message, Qt::QueuedConnection);
}
@@ -201,8 +206,10 @@ OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id,
return result;
}
OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char *id,
const char *format, va_list args)
OfxStatus OlivePluginInstance::setPersistentMessage(const char *type,
const char *id,
const char *format,
va_list args)
{
const QString message = FormatOfxMessage(format, args);
if (message.isEmpty()) {
@@ -215,11 +222,13 @@ OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char
error_type = ErrorType::Error;
}
// A warning
else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) == 0) {
else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) ==
0) {
error_type = ErrorType::Warning;
}
// A simple information
else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) == 0) {
else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) ==
0) {
error_type = ErrorType::Message;
} else {
return kOfxStatFailed;
@@ -272,7 +281,8 @@ void OlivePluginInstance::getProjectSize(double &xSize, double &ySize) const
xSize = params_.width() * par;
ySize = params_.height();
}
void OlivePluginInstance::getProjectOffset(double &xOffset, double &yOffset) const
void OlivePluginInstance::getProjectOffset(double &xOffset,
double &yOffset) const
{
double par = params_.pixel_aspect_ratio().toDouble();
xOffset = params_.x() * par;
@@ -343,8 +353,7 @@ OlivePluginInstance::newParam(const std::string &name,
return new Double3DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeInteger3D) {
return new Integer3DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeCustom ||
type == kOfxParamTypeBytes) {
} else if (type == kOfxParamTypeCustom || type == kOfxParamTypeBytes) {
return new CustomInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeGroup) {
return new GroupInstance(desc, this);
@@ -366,8 +375,7 @@ OfxStatus OlivePluginInstance::editBegin(const std::string &name)
edit_param_count_ = 0;
if (!name.empty()) {
edit_first_label_ =
QCoreApplication::translate(
"OlivePluginInstance", "Change %1")
QCoreApplication::translate("OlivePluginInstance", "Change %1")
.arg(QString::fromStdString(name));
}
}
@@ -383,15 +391,14 @@ OfxStatus OlivePluginInstance::editEnd()
if (label.isEmpty()) {
if (edit_param_count_ <= 1 && !edit_first_label_.isEmpty()) {
label = edit_first_label_;
} else if (edit_param_count_ > 1 &&
!edit_first_label_.isEmpty()) {
label = QCoreApplication::translate(
"OlivePluginInstance", "%1 (+%2)")
} else if (edit_param_count_ > 1 && !edit_first_label_.isEmpty()) {
label = QCoreApplication::translate("OlivePluginInstance",
"%1 (+%2)")
.arg(edit_first_label_)
.arg(edit_param_count_ - 1);
} else {
label = QCoreApplication::translate(
"OlivePluginInstance", "Edit Parameters");
label = QCoreApplication::translate("OlivePluginInstance",
"Edit Parameters");
}
}
Core::instance()->undo_stack()->push(edit_command_, label);
@@ -450,9 +457,8 @@ void OlivePluginInstance::progressStart(const std::string &message,
progress_dialog_->deleteLater();
}
QString dialog_message = message.empty()
? QStringLiteral("Processing...")
: QString::fromStdString(message);
QString dialog_message = message.empty() ? QStringLiteral("Processing...") :
QString::fromStdString(message);
progress_dialog_ = new ::olive::ProgressDialog(
dialog_message, QStringLiteral("OpenFX"), nullptr);
@@ -547,11 +553,11 @@ void OlivePluginInstance::setCustomInArgs(const std::string &action,
OFX::Host::ImageEffect::ClipInstance *OlivePluginInstance::newClipInstance(
OFX::Host::ImageEffect::Instance *plugin,
OFX::Host::ImageEffect::ClipDescriptor *descriptor,
int index)
OFX::Host::ImageEffect::ClipDescriptor *descriptor, int index)
{
// Create a new clip instance
OliveClipInstance* clipInstance = new OliveClipInstance(plugin, *descriptor, params_);
OliveClipInstance *clipInstance =
new OliveClipInstance(plugin, *descriptor, params_);
// Initialize base class clip properties from VideoParams so that
// setupClipPreferencesArgs and plugin constructors (which may fetch
+23 -24
View File
@@ -33,7 +33,8 @@
#include <qcontainerfwd.h>
#include <qlist.h>
namespace olive {
namespace olive
{
inline bool IsGuiThread()
{
@@ -43,24 +44,19 @@ inline bool IsGuiThread()
return true;
}
class ProgressDialog;
namespace plugin {
namespace plugin
{
class PluginNode;
enum class ErrorType{
Error,
Warning,
Message
};
enum class ErrorType { Error, Warning, Message };
struct PersistentErrors {
ErrorType type;
QString message;
};
class OlivePluginInstance : public OFX::Host::ImageEffect::Instance {
public:
OlivePluginInstance(
OFX::Host::ImageEffect::ImageEffectPlugin* plugin,
OlivePluginInstance(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
OFX::Host::ImageEffect::Descriptor &desc,
const std::string& context,
bool interactive)
const std::string &context, bool interactive)
: OFX::Host::ImageEffect::Instance(plugin, desc, context, interactive)
{
}
@@ -78,7 +74,8 @@ public:
_outputFielding = instance._outputFielding;
_outputFrameRate = instance._outputFrameRate;
}
explicit OlivePluginInstance(Instance & instance):Instance(instance){};
explicit OlivePluginInstance(Instance &instance)
: Instance(instance) {};
~OlivePluginInstance() override;
const std::string &getDefaultOutputFielding() const override;
@@ -99,20 +96,16 @@ public:
{
return _created;
}
OFX::Host::ImageEffect::ClipInstance *newClipInstance(
OFX::Host::ImageEffect::Instance *plugin,
OFX::Host::ImageEffect::ClipInstance *
newClipInstance(OFX::Host::ImageEffect::Instance *plugin,
OFX::Host::ImageEffect::ClipDescriptor *descriptor,
int index) override;
OfxStatus vmessage(const char* type,
const char* id,
const char* format,
OfxStatus vmessage(const char *type, const char *id, const char *format,
va_list args) override;
OfxStatus setPersistentMessage(const char* type,
const char* id,
const char* format,
va_list args) override;
OfxStatus setPersistentMessage(const char *type, const char *id,
const char *format, va_list args) override;
OfxStatus clearPersistentMessage() override;
int persistentMessageCount() const
@@ -153,7 +146,9 @@ public:
/// make a parameter instance
///
/// Client host code needs to implement this
OFX::Host::Param::Instance* newParam(const std::string& name, OFX::Host::Param::Descriptor& Descriptor) override;
OFX::Host::Param::Instance *
newParam(const std::string &name,
OFX::Host::Param::Descriptor &Descriptor) override;
void SubmitUndoCommand(UndoCommand *command, const QString &label);
@@ -210,7 +205,6 @@ public:
void setCustomInArgs(const std::string &action,
OFX::Host::Property::Set &inArgs) override;
private:
QList<PersistentErrors> persistentErrors_;
VideoParams params_;
@@ -224,8 +218,13 @@ private:
bool progress_cancelled_ = false;
bool progress_active_ = false;
bool open_gl_enabled_ = false;
public:
std::mutex& mutex() { return mutex_; }
std::mutex &mutex()
{
return mutex_;
}
private:
std::mutex mutex_;
};
+16 -29
View File
@@ -23,8 +23,10 @@
#include <algorithm>
namespace olive {
namespace plugin {
namespace olive
{
namespace plugin
{
static const char *PixelDepthToOfx(core::PixelFormat format)
{
@@ -75,10 +77,8 @@ Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance)
}
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
bool clear)
const VideoParams &params, const OfxRectI &bounds,
const OfxRectI &rod, bool clear)
: OFX::Host::ImageEffect::Image(clip_instance)
, width_(0)
, height_(0)
@@ -97,24 +97,17 @@ Image::~Image()
}
void Image::AllocateFromParams(const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
const OfxRectI &bounds, const OfxRectI &rod,
bool clear)
{
Allocate(bounds.x2 - bounds.x1,
bounds.y2 - bounds.y1,
params.format(),
params.channel_count(),
params.premultiplied_alpha(),
bounds,
rod,
Allocate(bounds.x2 - bounds.x1, bounds.y2 - bounds.y1, params.format(),
params.channel_count(), params.premultiplied_alpha(), bounds, rod,
clear);
}
void Image::EnsureAllocatedFromParams(const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
bool clear)
const OfxRectI &rod, bool clear)
{
bool same = (width_ == bounds.x2 - bounds.x1) &&
(height_ == bounds.y2 - bounds.y1) &&
@@ -133,14 +126,9 @@ void Image::EnsureAllocatedFromParams(const VideoParams &params,
}
}
void Image::Allocate(int width,
int height,
core::PixelFormat format,
int channel_count,
bool premultiplied_alpha,
const OfxRectI &bounds,
const OfxRectI &rod,
bool clear)
void Image::Allocate(int width, int height, core::PixelFormat format,
int channel_count, bool premultiplied_alpha,
const OfxRectI &bounds, const OfxRectI &rod, bool clear)
{
width_ = width;
height_ = height;
@@ -174,11 +162,10 @@ void Image::Allocate(int width,
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y2, 3);
setStringProperty(kOfxImageEffectPropComponents,
ComponentsToOfx(channel_count_));
setStringProperty(kOfxImageEffectPropPixelDepth,
PixelDepthToOfx(format_));
setStringProperty(kOfxImageEffectPropPixelDepth, PixelDepthToOfx(format_));
setStringProperty(kOfxImageEffectPropPreMultiplication,
premultiplied_alpha_ ? kOfxImagePreMultiplied
: kOfxImageUnPreMultiplied);
premultiplied_alpha_ ? kOfxImagePreMultiplied :
kOfxImageUnPreMultiplied);
}
core::PixelFormat Image::pixel_format()
+11 -18
View File
@@ -37,12 +37,11 @@ class Image : public OFX::Host::ImageEffect::Image {
public:
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance);
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
bool clear = true);
const VideoParams &params, const OfxRectI &bounds,
const OfxRectI &rod, bool clear = true);
~Image();
uint8_t *data() {
uint8_t *data()
{
return (uint8_t *)getPointerProperty(kOfxImagePropData);
}
int width();
@@ -51,26 +50,20 @@ public:
bool premultiplied_alpha();
int channel_count();
void AllocateFromParams(const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
bool clear = true);
void AllocateFromParams(const VideoParams &params, const OfxRectI &bounds,
const OfxRectI &rod, bool clear = true);
void EnsureAllocatedFromParams(const VideoParams &params,
const OfxRectI &bounds,
const OfxRectI &rod,
const OfxRectI &bounds, const OfxRectI &rod,
bool clear = false);
void Allocate(int width,
int height,
core::PixelFormat format,
int channel_count,
bool premultiplied_alpha,
const OfxRectI &bounds,
const OfxRectI &rod,
void Allocate(int width, int height, core::PixelFormat format,
int channel_count, bool premultiplied_alpha,
const OfxRectI &bounds, const OfxRectI &rod,
bool clear = true);
int row_bytes() const
{
return row_bytes_;
}
protected:
std::vector<uint8_t> image_;
int width_;
+1 -3
View File
@@ -17,7 +17,6 @@
*
*/
#include "paraminstance.h"
#include "OlivePluginInstance.h"
@@ -35,8 +34,7 @@ void SubmitUndoCommand(const std::shared_ptr<PluginNode> &node,
if (node) {
auto *instance = node->getPluginInstance();
auto *olive_instance =
dynamic_cast<OlivePluginInstance *>(instance);
auto *olive_instance = dynamic_cast<OlivePluginInstance *>(instance);
if (olive_instance) {
olive_instance->SubmitUndoCommand(command, label);
return;
+111 -82
View File
@@ -34,14 +34,15 @@
#include "undo/undocommand.h"
#include "common/Current.h"
#include <iostream>
#include <mutex>
#include <qlogging.h>
namespace olive
{
namespace plugin
{
inline bool IsNormalisedCoordinateSystem(
const OFX::Host::Param::Descriptor &descriptor)
inline bool
IsNormalisedCoordinateSystem(const OFX::Host::Param::Descriptor &descriptor)
{
return descriptor.getDefaultCoordinateSystem() ==
kOfxParamCoordinatesNormalised;
@@ -83,8 +84,10 @@ class PushbuttonInstance : public OFX::Host::Param::PushbuttonInstance,
protected:
std::shared_ptr<PluginNode> node;
OFX::Host::Param::Descriptor *_descriptor;
public:
PushbuttonInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
PushbuttonInstance(std::shared_ptr<PluginNode> effect,
const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::PushbuttonInstance(descriptor, paramSet)
@@ -104,10 +107,13 @@ protected:
std::shared_ptr<PluginNode> _node;
OFX::Host::Param::Descriptor &_descriptor;
QString id;
mutable std::mutex no_node_mutex_;
bool has_value_ = false;
int value_ = 0;
public:
IntegerInstance(std::shared_ptr<PluginNode>node, OFX::Host::Param::Descriptor &descriptor,
IntegerInstance(std::shared_ptr<PluginNode> node,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::IntegerInstance(descriptor, paramSet)
, _node(node)
@@ -115,7 +121,8 @@ public:
, id(_descriptor.getName().c_str())
{
try {
value_ = _descriptor.getProperties().getIntProperty(kOfxParamPropDefault);
value_ = _descriptor.getProperties().getIntProperty(
kOfxParamPropDefault);
has_value_ = true;
} catch (...) {
value_ = 0;
@@ -129,6 +136,7 @@ public:
OfxStatus get(int &a)
{
if (!_node) {
std::lock_guard<std::mutex> lock(no_node_mutex_);
a = has_value_ ? value_ : 0;
return kOfxStatOK;
}
@@ -147,13 +155,15 @@ public:
OfxStatus get(OfxTime time, int &data)
{
if (!_node) {
std::lock_guard<std::mutex> lock(no_node_mutex_);
data = has_value_ ? value_ : 0;
return kOfxStatOK;
}
if (id.isEmpty()) {
return kOfxStatErrBadHandle;
}
QVariant variant=_node->GetValueAtTime(id, rational::fromDouble(time));
QVariant variant =
_node->GetValueAtTime(id, rational::fromDouble(time));
if (variant.canConvert<int>()) {
data = variant.toInt();
return kOfxStatOK;
@@ -164,6 +174,7 @@ public:
OfxStatus set(int data)
{
if (!_node) {
std::lock_guard<std::mutex> lock(no_node_mutex_);
value_ = data;
has_value_ = true;
return kOfxStatOK;
@@ -179,6 +190,7 @@ public:
OfxStatus set(OfxTime time, int data)
{
if (!_node) {
std::lock_guard<std::mutex> lock(no_node_mutex_);
value_ = data;
has_value_ = true;
return kOfxStatOK;
@@ -199,8 +211,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
double value_ = 0.0;
public:
DoubleInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
DoubleInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::DoubleInstance(descriptor, paramSet)
, node(effect)
@@ -208,7 +222,8 @@ public:
{
(void)name;
try {
value_ = _descriptor.getProperties().getDoubleProperty(kOfxParamPropDefault);
value_ = _descriptor.getProperties().getDoubleProperty(
kOfxParamPropDefault);
has_value_ = true;
} catch (...) {
value_ = 0.0;
@@ -225,7 +240,8 @@ public:
data = has_value_ ? value_ : 0.0;
return kOfxStatOK;
}
QVariant variant = node->GetStandardValue(_descriptor.getName().c_str());
QVariant variant =
node->GetStandardValue(_descriptor.getName().c_str());
if (variant.canConvert<double>()) {
data = variant.toDouble();
if (IsNormalisedCoordinateSystem(_descriptor)) {
@@ -244,8 +260,7 @@ public:
data = has_value_ ? value_ : 0.0;
return kOfxStatOK;
}
QVariant variant =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVariant variant = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time));
if (variant.canConvert<double>()) {
data = variant.toDouble();
@@ -293,8 +308,8 @@ public:
val = ToCanonical(val, xSize);
}
auto command = new MultiUndoCommand();
Node::SetValueAtTime(
NodeInput(node.get(), _descriptor.getName().c_str()),
Node::SetValueAtTime(NodeInput(node.get(),
_descriptor.getName().c_str()),
rational::fromDouble(time), val, 0, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
@@ -318,11 +333,13 @@ protected:
bool value_ = false;
bool DefaultValue() const
{
return _descriptor.getProperties()
.getIntProperty(kOfxParamPropDefault) != 0;
return _descriptor.getProperties().getIntProperty(
kOfxParamPropDefault) != 0;
}
public:
BooleanInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
BooleanInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::BooleanInstance(descriptor, paramSet)
, node(effect)
@@ -342,7 +359,8 @@ public:
data = has_value_ ? value_ : false;
return kOfxStatOK;
}
QVariant variant = node->GetStandardValue(_descriptor.getName().c_str());
QVariant variant =
node->GetStandardValue(_descriptor.getName().c_str());
if (variant.canConvert<bool>()) {
data = variant.toBool();
return kOfxStatOK;
@@ -356,11 +374,12 @@ public:
data = has_value_ ? value_ : false;
return kOfxStatOK;
}
QVariant variant =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVariant variant = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time));
if (variant.isNull()) {
qWarning().noquote()<<"Boolean get failed: Varient is null" << time << rational::fromDouble(time).toDouble();
qWarning().noquote()
<< "Boolean get failed: Varient is null" << time
<< rational::fromDouble(time).toDouble();
}
if (!variant.isValid()) {
qWarning().noquote()
@@ -411,8 +430,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
int value_ = 0;
public:
ChoiceInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
ChoiceInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::ChoiceInstance(descriptor, paramSet)
, node(effect)
@@ -420,7 +441,8 @@ public:
{
(void)name;
try {
value_ = _descriptor.getProperties().getIntProperty(kOfxParamPropDefault);
value_ = _descriptor.getProperties().getIntProperty(
kOfxParamPropDefault);
has_value_ = true;
} catch (...) {
value_ = 0;
@@ -437,7 +459,8 @@ public:
data = has_value_ ? value_ : 0;
return kOfxStatOK;
}
QVariant variant = node->GetStandardValue(_descriptor.getName().c_str());
QVariant variant =
node->GetStandardValue(_descriptor.getName().c_str());
if (variant.canConvert<int>()) {
data = variant.toInt();
return kOfxStatOK;
@@ -451,8 +474,7 @@ public:
data = has_value_ ? value_ : 0;
return kOfxStatOK;
}
QVariant variant =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVariant variant = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time));
if (variant.canConvert<int>()) {
data = variant.toInt();
@@ -498,8 +520,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
double value_[4] = { 0.0, 0.0, 0.0, 0.0 };
public:
RGBAInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
RGBAInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::RGBAInstance(descriptor, paramSet)
, node(effect)
@@ -588,20 +612,19 @@ public:
}
auto command = new MultiUndoCommand();
const QString name = _descriptor.getName().c_str();
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
r, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
g, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
b, 2, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
a, 3, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), r, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), g, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), b, 2, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), a, 3, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
}
};
class RGBInstance : public OFX::Host::Param::RGBInstance,
public NodeBoundParam {
protected:
@@ -609,8 +632,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
double value_[3] = { 0.0, 0.0, 0.0 };
public:
RGBInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
RGBInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::RGBInstance(descriptor, paramSet)
, node(effect)
@@ -693,12 +718,12 @@ public:
}
auto command = new MultiUndoCommand();
const QString name = _descriptor.getName().c_str();
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
r, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
g, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
b, 2, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), r, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), g, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), b, 2, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
}
@@ -711,8 +736,11 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
double value_[2] = { 0.0, 0.0 };
public:
Double2DInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
Double2DInstance(std::shared_ptr<PluginNode> effect,
const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::Double2DInstance(descriptor, paramSet)
, node(effect)
@@ -735,8 +763,7 @@ public:
}
return kOfxStatOK;
}
QVector2D vec =
node->GetStandardValue(_descriptor.getName().c_str())
QVector2D vec = node->GetStandardValue(_descriptor.getName().c_str())
.value<QVector2D>();
x = static_cast<double>(vec.x());
y = static_cast<double>(vec.y());
@@ -759,8 +786,7 @@ public:
}
return kOfxStatOK;
}
QVector2D vec =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVector2D vec = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time))
.value<QVector2D>();
x = static_cast<double>(vec.x());
@@ -812,10 +838,10 @@ public:
}
auto command = new MultiUndoCommand();
const QString name = _descriptor.getName().c_str();
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
xv, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
yv, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), xv, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), yv, 1, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
}
@@ -828,8 +854,11 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
int value_[2] = { 0, 0 };
public:
Integer2DInstance(std::shared_ptr<PluginNode> effect, const std::string& name, OFX::Host::Param::Descriptor& descriptor,
Integer2DInstance(std::shared_ptr<PluginNode> effect,
const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::Integer2DInstance(descriptor, paramSet)
, node(effect)
@@ -852,8 +881,7 @@ public:
}
return kOfxStatOK;
}
QVector2D vec =
node->GetStandardValue(_descriptor.getName().c_str())
QVector2D vec = node->GetStandardValue(_descriptor.getName().c_str())
.value<QVector2D>();
x = static_cast<int>(vec.x());
y = static_cast<int>(vec.y());
@@ -870,8 +898,7 @@ public:
}
return kOfxStatOK;
}
QVector2D vec =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVector2D vec = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time))
.value<QVector2D>();
x = static_cast<int>(vec.x());
@@ -903,10 +930,10 @@ public:
}
auto command = new MultiUndoCommand();
const QString name = _descriptor.getName().c_str();
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
x, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time),
y, 1, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), x, 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(), name),
rational::fromDouble(time), y, 1, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
}
@@ -919,8 +946,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
double value_[3] = { 0.0, 0.0, 0.0 };
public:
Double3DInstance(std::shared_ptr<PluginNode> effect, const std::string& name,
Double3DInstance(std::shared_ptr<PluginNode> effect,
const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::Double3DInstance(descriptor, paramSet)
@@ -945,8 +974,7 @@ public:
}
return kOfxStatOK;
}
QVector3D vec =
node->GetStandardValue(_descriptor.getName().c_str())
QVector3D vec = node->GetStandardValue(_descriptor.getName().c_str())
.value<QVector3D>();
x = static_cast<double>(vec.x());
y = static_cast<double>(vec.y());
@@ -972,8 +1000,7 @@ public:
}
return kOfxStatOK;
}
QVector3D vec =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVector3D vec = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time))
.value<QVector3D>();
x = static_cast<double>(vec.x());
@@ -1049,8 +1076,10 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
int value_[3] = { 0, 0, 0 };
public:
Integer3DInstance(std::shared_ptr<PluginNode> effect, const std::string& name,
Integer3DInstance(std::shared_ptr<PluginNode> effect,
const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
OFX::Host::Param::SetInstance *paramSet = nullptr)
: OFX::Host::Param::Integer3DInstance(descriptor, paramSet)
@@ -1075,8 +1104,7 @@ public:
}
return kOfxStatOK;
}
QVector3D vec =
node->GetStandardValue(_descriptor.getName().c_str())
QVector3D vec = node->GetStandardValue(_descriptor.getName().c_str())
.value<QVector3D>();
x = static_cast<int>(vec.x());
y = static_cast<int>(vec.y());
@@ -1095,8 +1123,7 @@ public:
}
return kOfxStatOK;
}
QVector3D vec =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVector3D vec = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time))
.value<QVector3D>();
x = static_cast<int>(vec.x());
@@ -1149,6 +1176,7 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
std::string value_;
public:
StringInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
@@ -1159,7 +1187,8 @@ public:
{
(void)name;
try {
value_ = _descriptor.getProperties().getStringProperty(kOfxParamPropDefault);
value_ = _descriptor.getProperties().getStringProperty(
kOfxParamPropDefault);
has_value_ = true;
} catch (...) {
value_.clear();
@@ -1176,7 +1205,8 @@ public:
data = has_value_ ? value_ : std::string();
return kOfxStatOK;
}
QVariant variant = node->GetStandardValue(_descriptor.getName().c_str());
QVariant variant =
node->GetStandardValue(_descriptor.getName().c_str());
if (variant.canConvert<QString>()) {
data = variant.toString().toStdString();
return kOfxStatOK;
@@ -1190,8 +1220,7 @@ public:
data = has_value_ ? value_ : std::string();
return kOfxStatOK;
}
QVariant variant =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVariant variant = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time));
if (variant.canConvert<QString>()) {
data = variant.toString().toStdString();
@@ -1223,9 +1252,10 @@ public:
return kOfxStatOK;
}
auto command = new MultiUndoCommand();
Node::SetValueAtTime(
NodeInput(node.get(), _descriptor.getName().c_str()),
rational::fromDouble(time), QString::fromUtf8(data), 0, command, true);
Node::SetValueAtTime(NodeInput(node.get(),
_descriptor.getName().c_str()),
rational::fromDouble(time),
QString::fromUtf8(data), 0, command, true);
SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor));
return kOfxStatOK;
}
@@ -1238,6 +1268,7 @@ protected:
OFX::Host::Param::Descriptor &_descriptor;
bool has_value_ = false;
std::string value_;
public:
CustomInstance(std::shared_ptr<PluginNode> effect, const std::string &name,
OFX::Host::Param::Descriptor &descriptor,
@@ -1258,7 +1289,8 @@ public:
data = has_value_ ? value_ : std::string();
return kOfxStatOK;
}
QVariant variant = node->GetStandardValue(_descriptor.getName().c_str());
QVariant variant =
node->GetStandardValue(_descriptor.getName().c_str());
if (variant.canConvert<QByteArray>()) {
data = variant.toByteArray().toStdString();
return kOfxStatOK;
@@ -1276,8 +1308,7 @@ public:
data = has_value_ ? value_ : std::string();
return kOfxStatOK;
}
QVariant variant =
node->GetValueAtTime(_descriptor.getName().c_str(),
QVariant variant = node->GetValueAtTime(_descriptor.getName().c_str(),
rational::fromDouble(time));
if (variant.canConvert<QByteArray>()) {
data = variant.toByteArray().toStdString();
@@ -1341,6 +1372,4 @@ public:
}
}
#endif // HOST_DEMO_PARAM_INSTANCE_H
+21 -16
View File
@@ -37,21 +37,24 @@ DynamicRenderer::~DynamicRenderer()
// system libGL/libvulkan loader is never mistaken for an Oak render backend.
QString DynamicRenderer::LibraryFilename() const
{
const QString base = backend_ == QStringLiteral("vulkan")
? QStringLiteral("oakvulkan")
: QStringLiteral("oakgl");
const QString base = backend_ == QStringLiteral("vulkan") ?
QStringLiteral("oakvulkan") :
QStringLiteral("oakgl");
#if defined(Q_OS_WIN)
const QString filename = base + QStringLiteral(".dll");
#elif defined(Q_OS_MAC)
const QString filename = QStringLiteral("lib") + base + QStringLiteral(".dylib");
const QString filename =
QStringLiteral("lib") + base + QStringLiteral(".dylib");
#else
const QString filename = QStringLiteral("lib") + base + QStringLiteral(".so");
const QString filename =
QStringLiteral("lib") + base + QStringLiteral(".so");
#endif
const QDir app_dir(QCoreApplication::applicationDirPath());
const QStringList candidates = {
app_dir.filePath(filename),
app_dir.filePath(QDir(QStringLiteral("render_backends")).filePath(filename)),
app_dir.filePath(
QDir(QStringLiteral("render_backends")).filePath(filename)),
app_dir.filePath(QDir(QStringLiteral("../lib")).filePath(filename)),
app_dir.filePath(QDir(QStringLiteral("../../lib")).filePath(filename)),
app_dir.filePath(QDir(QStringLiteral("../app")).filePath(filename)),
@@ -77,9 +80,9 @@ bool DynamicRenderer::Load()
library_.setFileName(LibraryFilename());
if (!library_.load()) {
if (backend_ == QStringLiteral("vulkan")) {
qWarning() << "Failed to load Vulkan render backend"
<< library_.fileName() << library_.errorString()
<< "falling back to OpenGL backend";
qWarning()
<< "Failed to load Vulkan render backend" << library_.fileName()
<< library_.errorString() << "falling back to OpenGL backend";
backend_ = QStringLiteral("opengl");
library_.setFileName(LibraryFilename());
}
@@ -128,7 +131,8 @@ bool DynamicRenderer::ResolveFunctions()
ResetFunctions();
#define RESOLVE(member, type, symbol) \
member = reinterpret_cast<type>(library_.resolve(symbol)); \
if (!member) return false
if (!member) \
return false
RESOLVE(create_, OakBackendCreateFn, "oak_renderer_create");
RESOLVE(destroy_, OakBackendDestroyFn, "oak_renderer_destroy");
@@ -281,8 +285,8 @@ void DynamicRenderer::DestroyNativeShader(QVariant shader)
// Uploads CPU pixel data into a backend texture through the dynamic ABI.
void DynamicRenderer::UploadToTexture(const QVariant &handle,
const VideoParams &params, const void *data,
int linesize)
const VideoParams &params,
const void *data, int linesize)
{
upload_to_texture_(handle_, &handle, &params, data, linesize);
}
@@ -313,9 +317,9 @@ Color DynamicRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
// null so callers can avoid GL-only paths.
QOpenGLContext *DynamicRenderer::OpenGLContext() const
{
return opengl_context_ && handle_
? static_cast<QOpenGLContext *>(opengl_context_(handle_))
: nullptr;
return opengl_context_ && handle_ ?
static_cast<QOpenGLContext *>(opengl_context_(handle_)) :
nullptr;
}
// Reports the effective backend after any load-time fallback has completed.
@@ -340,7 +344,8 @@ void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
// Allocates a backend-native texture and wraps its opaque handle in QVariant.
QVariant DynamicRenderer::CreateNativeTexture(int width, int height, int depth,
PixelFormat format, int channel_count,
PixelFormat format,
int channel_count,
const void *data, int linesize)
{
QVariant out;
+3 -3
View File
@@ -42,9 +42,9 @@ public:
// Runs backend post-init setup.
virtual void PostInit() override;
// Clears either a native texture destination or the backend output target.
virtual void ClearDestination(Texture *texture = nullptr,
double r = 0.0, double g = 0.0,
double b = 0.0, double a = 0.0) override;
virtual void ClearDestination(Texture *texture = nullptr, double r = 0.0,
double g = 0.0, double b = 0.0,
double a = 0.0) override;
// Creates a native shader through the dynamic backend.
virtual QVariant CreateNativeShader(ShaderCode code) override;
// Destroys a native shader through the dynamic backend.
+7 -7
View File
@@ -7,7 +7,8 @@
#ifdef _WIN32
#define OAK_RENDER_BACKEND_EXPORT extern "C" __declspec(dllexport)
#else
#define OAK_RENDER_BACKEND_EXPORT extern "C" __attribute__((visibility("default")))
#define OAK_RENDER_BACKEND_EXPORT \
extern "C" __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
@@ -69,11 +70,9 @@ typedef void (*OakBackendClearDestinationFn)(OakRenderBackendHandle handle,
void *texture, double r, double g,
double b, double a);
/* Creates a native texture and writes a QVariant-compatible handle. */
typedef void (*OakBackendCreateNativeTextureFn)(OakRenderBackendHandle handle,
int width, int height, int depth,
int format, int channel_count,
const void *data, int linesize,
void *out_variant);
typedef void (*OakBackendCreateNativeTextureFn)(
OakRenderBackendHandle handle, int width, int height, int depth, int format,
int channel_count, const void *data, int linesize, void *out_variant);
/* Destroys a native texture represented by a QVariant-compatible handle. */
typedef void (*OakBackendDestroyNativeTextureFn)(OakRenderBackendHandle handle,
const void *variant);
@@ -98,7 +97,8 @@ typedef void (*OakBackendDownloadFromTextureFn)(OakRenderBackendHandle handle,
typedef void (*OakBackendFlushFn)(OakRenderBackendHandle handle);
/* Reads one pixel from a texture. */
typedef void (*OakBackendGetPixelFromTextureFn)(OakRenderBackendHandle handle,
void *texture, const void *point,
void *texture,
const void *point,
void *out_color);
/* Executes a shader blit job. */
typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
+11 -8
View File
@@ -125,7 +125,8 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job,
OCIO::GpuShaderDesc::TextureType channel =
OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
#if OCIO_VERSION_MAJOR > 2 || (OCIO_VERSION_MAJOR == 2 && OCIO_VERSION_MINOR >= 3)
#if OCIO_VERSION_MAJOR > 2 || \
(OCIO_VERSION_MAJOR == 2 && OCIO_VERSION_MINOR >= 3)
OCIO::GpuShaderDesc::TextureDimensions dimensions =
OCIO::GpuShaderDesc::TEXTURE_2D;
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
@@ -149,12 +150,14 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job,
}
// Allocate 1D LUT
int lut_channels = (channel ==
OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ?
int lut_channels =
(channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ?
1 :
VideoParams::kRGBChannelCount;
VideoParams lut_params(width, height, PixelFormat::F32, lut_channels);
color_ctx.lut1d_textures[i].texture = CreateTexture(lut_params, values);
VideoParams lut_params(width, height, PixelFormat::F32,
lut_channels);
color_ctx.lut1d_textures[i].texture =
CreateTexture(lut_params, values);
color_ctx.lut1d_textures[i].name = sampler_name;
color_ctx.lut1d_textures[i].interpolation =
(interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest :
@@ -176,9 +179,9 @@ void Renderer::BlitColorManaged(const ColorTransformJob &color_job,
ShaderJob fallback_job;
fallback_job.Insert(QStringLiteral("ove_maintex"),
color_job.GetInputTexture());
fallback_job.Insert(
QStringLiteral("ove_mvpmat"),
NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
fallback_job.Insert(QStringLiteral("ove_mvpmat"),
NodeValue(NodeValue::kMatrix,
color_job.GetTransformMatrix()));
if (destination) {
BlitToTexture(GetDefaultShader(), fallback_job, destination,
+4 -2
View File
@@ -67,7 +67,8 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input,
} else {
auto group = OCIO::GroupTransform::Create();
const char *out_cs = OCIO::LookTransform::GetLooksResultColorSpace(
const char *out_cs =
OCIO::LookTransform::GetLooksResultColorSpace(
ocio_config, ocio_config->getCurrentContext(),
transform.look().toUtf8());
@@ -105,7 +106,8 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input,
ColorProcessor::ColorProcessor(OCIO::ConstProcessorRcPtr processor)
{
processor_ = processor;
cpu_processor_ = processor_ ? processor_->getDefaultCPUProcessor() : nullptr;
cpu_processor_ = processor_ ? processor_->getDefaultCPUProcessor() :
nullptr;
}
void ColorProcessor::ConvertFrame(Frame *f)
+12 -6
View File
@@ -44,9 +44,12 @@ size_t FrameSlotPool::BytesNeeded(uint32_t slot_count, size_t slot_data_bytes)
{
const uint32_t ring_cap = RingCapacity(slot_count);
size_t total = AlignUp(sizeof(Header), kAlign);
total += AlignUp(SpscRingBuffer::BytesNeeded(ring_cap), kAlign); // free ring
total += AlignUp(SpscRingBuffer::BytesNeeded(ring_cap), kAlign); // ready ring
total += AlignUp(sizeof(FrameSlotMeta) * slot_count, kAlign); // metadata array
total +=
AlignUp(SpscRingBuffer::BytesNeeded(ring_cap), kAlign); // free ring
total +=
AlignUp(SpscRingBuffer::BytesNeeded(ring_cap), kAlign); // ready ring
total +=
AlignUp(sizeof(FrameSlotMeta) * slot_count, kAlign); // metadata array
total += AlignUp(slot_data_bytes, kAlign) * slot_count; // pixel data blocks
return total;
}
@@ -111,9 +114,12 @@ FrameSlotPool FrameSlotPool::Attach(void *mem)
return pool;
}
pool.free_ring_ = SpscRingBuffer::Attach(pool.base_ + pool.header_->free_ring_offset);
pool.ready_ring_ = SpscRingBuffer::Attach(pool.base_ + pool.header_->ready_ring_offset);
pool.meta_ = reinterpret_cast<FrameSlotMeta *>(pool.base_ + pool.header_->meta_offset);
pool.free_ring_ =
SpscRingBuffer::Attach(pool.base_ + pool.header_->free_ring_offset);
pool.ready_ring_ =
SpscRingBuffer::Attach(pool.base_ + pool.header_->ready_ring_offset);
pool.meta_ = reinterpret_cast<FrameSlotMeta *>(pool.base_ +
pool.header_->meta_offset);
pool.data_ = pool.base_ + pool.header_->data_offset;
return pool;
+2 -2
View File
@@ -90,7 +90,8 @@ public:
* Initializes both rings, seeds the free ring with every slot index, and zeroes metadata.
* `mem` must provide at least BytesNeeded(slot_count, slot_data_bytes) bytes.
*/
static FrameSlotPool Create(void *mem, uint32_t slot_count, size_t slot_data_bytes);
static FrameSlotPool Create(void *mem, uint32_t slot_count,
size_t slot_data_bytes);
/**
* @brief Map an existing, already-initialized pool (peer side).
@@ -148,7 +149,6 @@ public:
FrameSlotPool() = default;
private:
struct Header {
uint32_t magic;
uint32_t slot_count;
+10 -5
View File
@@ -93,7 +93,8 @@ bool ReadMessage(QByteArray *buffer, QJsonObject *out, bool *ok = nullptr);
struct HandshakeMsg {
int protocol_version = 0;
QString shm_key; ///< Worker->main output shared-memory segment key.
QString input_shm_key; ///< Main->worker input shared-memory segment key (optional).
QString
input_shm_key; ///< Main->worker input shared-memory segment key (optional).
int input_slots = 0; ///< Number of main->worker input frame slots.
int output_slots = 0; ///< Number of worker->main output frame slots.
qint64 slot_data_bytes = 0; ///< Per-output-slot pixel block size.
@@ -104,8 +105,10 @@ struct HandshakeMsg {
};
struct RenderFrameMsg {
qint64 ticket_id = 0; ///< Correlates this request with the eventual frame_ready.
QString node_uuid; ///< Output/viewer node to render, by stable uuid in the loaded graph.
qint64 ticket_id =
0; ///< Correlates this request with the eventual frame_ready.
QString
node_uuid; ///< Output/viewer node to render, by stable uuid in the loaded graph.
qint64 time_num = 0;
qint64 time_den = 1;
int width = 0; ///< Forced output size (0 = use graph default).
@@ -113,8 +116,10 @@ struct RenderFrameMsg {
int format = -1; ///< Forced PixelFormat::Format (-1 = default/INVALID).
int channel_count = 0; ///< 0 = default.
int mode = 0; ///< RenderMode::Mode.
int input_slot = -1; ///< Optional main->worker decoded input slot for footage nodes.
QVector<int> input_slots; ///< Optional ordered decoded input slots for footage nodes.
int input_slot =
-1; ///< Optional main->worker decoded input slot for footage nodes.
QVector<int>
input_slots; ///< Optional ordered decoded input slots for footage nodes.
// Output color transform to apply before returning the frame. When empty,
// the worker returns the image in the project's reference space.
+13 -7
View File
@@ -75,16 +75,20 @@ bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode)
const std::wstring wname = mapping_name.toStdWString();
if (mode == kCreate) {
const DWORD size_high = static_cast<DWORD>((quint64(size) >> 32) & 0xFFFFFFFF);
const DWORD size_high =
static_cast<DWORD>((quint64(size) >> 32) & 0xFFFFFFFF);
const DWORD size_low = static_cast<DWORD>(quint64(size) & 0xFFFFFFFF);
handle_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
size_high, size_low, wname.c_str());
handle_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, size_high, size_low,
wname.c_str());
if (!handle_) {
error_ = QStringLiteral("CreateFileMapping failed: %1").arg(GetLastError());
error_ = QStringLiteral("CreateFileMapping failed: %1")
.arg(GetLastError());
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS) {
error_ = QStringLiteral("Shared memory key already exists: %1").arg(key);
error_ =
QStringLiteral("Shared memory key already exists: %1").arg(key);
CloseHandle(handle_);
handle_ = nullptr;
return false;
@@ -92,7 +96,8 @@ bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode)
} else {
handle_ = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wname.c_str());
if (!handle_) {
error_ = QStringLiteral("OpenFileMapping failed: %1").arg(GetLastError());
error_ =
QStringLiteral("OpenFileMapping failed: %1").arg(GetLastError());
return false;
}
}
@@ -165,7 +170,8 @@ bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode)
data_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
if (data_ == MAP_FAILED) {
error_ = QStringLiteral("mmap failed: %1").arg(QString::fromUtf8(strerror(errno)));
error_ = QStringLiteral("mmap failed: %1")
.arg(QString::fromUtf8(strerror(errno)));
data_ = nullptr;
::close(fd_);
fd_ = -1;
+2 -1
View File
@@ -174,7 +174,8 @@ private:
std::atomic<uint32_t> tail_;
uint32_t capacity_;
static_assert(sizeof(std::atomic<uint32_t>) == sizeof(uint32_t),
static_assert(
sizeof(std::atomic<uint32_t>) == sizeof(uint32_t),
"atomic<uint32_t> must be lock-free POD-sized for shared memory use");
};
+4 -2
View File
@@ -19,7 +19,9 @@
#include "pluginjob.h"
namespace olive {
namespace plugin {
namespace olive
{
namespace plugin
{
} // plugin
} // olive
+10 -5
View File
@@ -26,8 +26,10 @@
#include <any>
#include <chrono>
namespace olive {
namespace plugin {
namespace olive
{
namespace plugin
{
class PluginJob : public AcceleratedJob {
public:
@@ -47,15 +49,18 @@ public:
{
}
PluginNode *node() const {
PluginNode *node() const
{
return const_cast<PluginNode *>(node_);
}
OFX::Host::ImageEffect::Instance* pluginInstance() {
OFX::Host::ImageEffect::Instance *pluginInstance()
{
return const_cast<OFX::Host::ImageEffect::Instance *>(pluginInstance_);
}
double time_seconds() const {
double time_seconds() const
{
return time_seconds_;
}
+69 -50
View File
@@ -10,7 +10,8 @@
#include "render/texture.h"
#include "render/videoparams.h"
namespace {
namespace
{
class BackendOpenGLRenderer : public olive::OpenGLRenderer {
public:
@@ -39,29 +40,32 @@ const QVariant &VariantRef(const void *variant)
} // namespace
// Creates the backend object and returns it as an opaque C handle.
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle
oak_renderer_create(void *parent)
{
return new BackendOpenGLRenderer(static_cast<QObject *>(parent));
}
// Destroys the opaque backend object created by oak_renderer_create().
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy(OakRenderBackendHandle handle)
{
delete Renderer(handle);
}
// Reports static OpenGL backend capabilities to the adapter.
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
OAK_RENDER_BACKEND_EXPORT bool
oak_renderer_get_info(OakRenderBackendHandle handle,
OakRenderBackendInfo *out_info)
{
if (!handle || !out_info) {
return false;
}
out_info->abi_version = 1;
out_info->kind = OAK_RENDER_BACKEND_OPENGL;
out_info->capabilities = OAK_RENDER_BACKEND_CAP_TEXTURES |
OAK_RENDER_BACKEND_CAP_SHADERS | OAK_RENDER_BACKEND_CAP_BLIT |
OAK_RENDER_BACKEND_CAP_READBACK |
out_info->capabilities =
OAK_RENDER_BACKEND_CAP_TEXTURES | OAK_RENDER_BACKEND_CAP_SHADERS |
OAK_RENDER_BACKEND_CAP_BLIT | OAK_RENDER_BACKEND_CAP_READBACK |
OAK_RENDER_BACKEND_CAP_VIEWER_CONTEXT;
out_info->name = "opengl";
out_info->status = "available";
@@ -70,8 +74,8 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
// OpenGL availability is context-dependent, so object creation is the minimum
// availability signal for this backend.
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT bool
oak_renderer_is_available(OakRenderBackendHandle handle)
{
return handle != nullptr;
}
@@ -83,35 +87,37 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
}
// Initializes the backend against a caller-owned viewer OpenGL context.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
OakRenderBackendHandle handle, void *context)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_init_with_context(OakRenderBackendHandle handle, void *context)
{
Renderer(handle)->Init(static_cast<QOpenGLContext *>(context));
}
// Runs renderer post-initialization once the GL context is available.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_post_init(OakRenderBackendHandle handle)
{
Renderer(handle)->PostInit();
}
// Releases post-init OpenGL surface/context state.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_post_destroy(OakRenderBackendHandle handle)
{
Renderer(handle)->PostDestroy();
}
// Releases renderer-owned GL resources before object destruction.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_internal(OakRenderBackendHandle handle)
{
Renderer(handle)->DestroyInternal();
}
// Clears either the widget framebuffer or a texture destination.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
double a)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_clear_destination(OakRenderBackendHandle handle, void *texture,
double r, double g, double b, double a)
{
Renderer(handle)->ClearDestination(static_cast<olive::Texture *>(texture),
r, g, b, a);
@@ -122,51 +128,58 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
OakRenderBackendHandle handle, int width, int height, int depth, int format,
int channel_count, const void *data, int linesize, void *out_variant)
{
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeTexture(
width, height, depth, static_cast<olive::PixelFormat::Format>(format),
channel_count, data, linesize);
*static_cast<QVariant *>(out_variant) =
Renderer(handle)->CreateNativeTexture(
width, height, depth,
static_cast<olive::PixelFormat::Format>(format), channel_count,
data, linesize);
}
// Destroys an OpenGL texture represented by a QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
OakRenderBackendHandle handle, const void *variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_native_texture(OakRenderBackendHandle handle,
const void *variant)
{
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
}
// Compiles an OpenGL shader program and returns its QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_create_native_shader(OakRenderBackendHandle handle,
const void *shader_code, void *out_variant)
{
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeShader(
*static_cast<QVariant *>(out_variant) =
Renderer(handle)->CreateNativeShader(
*static_cast<const olive::ShaderCode *>(shader_code));
}
// Destroys an OpenGL shader program represented by a QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
OakRenderBackendHandle handle, const void *variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_native_shader(OakRenderBackendHandle handle,
const void *variant)
{
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
}
// Uploads CPU pixel data into an OpenGL texture.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_upload_to_texture(OakRenderBackendHandle handle,
const void *variant, const void *video_params,
const void *data, int linesize)
{
Renderer(handle)->UploadToTexture(
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
data, linesize);
VariantRef(variant),
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
}
// Reads an OpenGL texture back to CPU memory.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
void *data, int linesize)
OakRenderBackendHandle handle, const void *variant,
const void *video_params, void *data, int linesize)
{
Renderer(handle)->DownloadFromTexture(
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
data, linesize);
VariantRef(variant),
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
}
// Flushes/waits for pending OpenGL work as required by the renderer.
@@ -176,18 +189,23 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
}
// Reads one pixel from an OpenGL texture.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
OakRenderBackendHandle handle, void *texture, const void *point,
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_get_pixel_from_texture(OakRenderBackendHandle handle,
void *texture, const void *point,
void *out_color)
{
*static_cast<olive::Color *>(out_color) = Renderer(handle)->GetPixelFromTexture(
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
*static_cast<olive::Color *>(out_color) =
Renderer(handle)->GetPixelFromTexture(
static_cast<olive::Texture *>(texture),
*static_cast<const QPointF *>(point));
}
// Executes a shader blit through the wrapped C++ OpenGL renderer.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
OakRenderBackendHandle handle, const void *shader, void *job,
void *destination, const void *destination_params, bool clear_destination)
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(OakRenderBackendHandle handle,
const void *shader, void *job,
void *destination,
const void *destination_params,
bool clear_destination)
{
Renderer(handle)->Blit(
VariantRef(shader), *static_cast<olive::AcceleratedJob *>(job),
@@ -197,22 +215,23 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
}
// Exposes the wrapped OpenGL context for GL-specific integrations.
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void *
oak_renderer_opengl_context(OakRenderBackendHandle handle)
{
return Renderer(handle)->context();
}
// Binds an output texture for OFX OpenGL rendering.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
OakRenderBackendHandle handle, const void *texture_id)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_attach_output_texture(OakRenderBackendHandle handle,
const void *texture_id)
{
Renderer(handle)->AttachTextureAsDestination(VariantRef(texture_id));
}
// Detaches any OFX OpenGL output texture binding.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_detach_output_texture(OakRenderBackendHandle handle)
{
Renderer(handle)->DetachTextureAsDestination();
}
+49 -36
View File
@@ -265,8 +265,8 @@ void OpenGLRenderer::AttachTextureAsDestination(const QVariant &texture)
void OpenGLRenderer::DetachTextureAsDestination()
{
// QOpenGLWidget renders to a non-zero default FBO.
const GLuint default_fbo =
context_ ? context_->defaultFramebufferObject() : 0;
const GLuint default_fbo = context_ ? context_->defaultFramebufferObject() :
0;
functions_->glBindFramebuffer(GL_FRAMEBUFFER, default_fbo);
}
@@ -504,8 +504,8 @@ struct TextureToBind {
Texture::Interpolation interpolation;
};
void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destination,
VideoParams destination_params,
void OpenGLRenderer::Blit(QVariant s, AcceleratedJob &a_job,
Texture *destination, VideoParams destination_params,
bool clear_destination)
{
GL_PREAMBLE;
@@ -585,7 +585,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
case NodeValue::kColor: {
Color color = value.toColor();
functions_->glUniform4f(variable_location, color.red(),
color.green(), color.blue(), color.alpha());
color.green(), color.blue(),
color.alpha());
break;
}
case NodeValue::kBoolean:
@@ -595,7 +596,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
TexturePtr texture = value.toTexture();
// Set value to bound texture
functions_->glUniform1i(variable_location, textures_to_bind.size());
functions_->glUniform1i(variable_location,
textures_to_bind.size());
texture_index_map.insert(it.key(), textures_to_bind.size());
@@ -605,8 +607,10 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
// Set enable flag if shader wants it
GLuint tex_id = texture ? texture->id().value<GLuint>() : 0;
int enable_param_location = functions_->glGetUniformLocation(
shader,
QStringLiteral("%1_enabled").arg(it.key()).toUtf8().constData());
shader, QStringLiteral("%1_enabled")
.arg(it.key())
.toUtf8()
.constData());
if (enable_param_location > -1) {
functions_->glUniform1i(enable_param_location, tex_id > 0);
}
@@ -637,7 +641,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
functions_->glActiveTexture(GL_TEXTURE0 + i);
GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D :
GLenum target = (texture && texture->params().is_3d()) ?
GL_TEXTURE_3D :
GL_TEXTURE_2D;
functions_->glBindTexture(target, tex_id);
@@ -647,12 +652,12 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
if (texture->channel_count() == 1 &&
destination_params.channel_count() != 1) {
// Interpret this texture as a grayscale texture
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R,
GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G,
GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B,
GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_SWIZZLE_R, GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_SWIZZLE_G, GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_SWIZZLE_B, GL_RED);
}
}
}
@@ -683,7 +688,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
if (!job.GetVertexCoordinates().isEmpty()) {
Q_ASSERT(job.GetVertexCoordinates().size() == 18);
vert_vbo_.allocate(job.GetVertexCoordinates().constData(),
job.GetVertexCoordinates().size() * sizeof(float));
job.GetVertexCoordinates().size() *
sizeof(float));
} else {
vert_vbo_.allocate(blit_vertices.constData(),
blit_vertices.size() * sizeof(GLfloat));
@@ -707,12 +713,13 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
vert_vbo_.release();
}
GLint tex_location = functions_->glGetAttribLocation(shader, "a_texcoord");
GLint tex_location =
functions_->glGetAttribLocation(shader, "a_texcoord");
if (tex_location != -1) {
frag_vbo_.bind();
functions_->glEnableVertexAttribArray(tex_location);
functions_->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE,
0, nullptr);
functions_->glVertexAttribPointer(tex_location, 2, GL_FLOAT,
GL_FALSE, 0, nullptr);
frag_vbo_.release();
}
@@ -788,9 +795,9 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
// Blit this texture through this shader
{
PRINT_GL_ERRORS;
functions_->glDrawArrays(GL_TRIANGLES, 0, blit_vertices.size() / 3);
functions_->glDrawArrays(GL_TRIANGLES, 0,
blit_vertices.size() / 3);
}
}
if (destination) {
@@ -801,7 +808,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
// Release any textures we bound before
for (int i = textures_to_bind.size() - 1; i >= 0; i--) {
TexturePtr texture = textures_to_bind.at(i).texture;
GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D :
GLenum target = (texture && texture->params().is_3d()) ?
GL_TEXTURE_3D :
GL_TEXTURE_2D;
functions_->glActiveTexture(GL_TEXTURE0 + i);
functions_->glBindTexture(target, 0);
@@ -815,9 +823,8 @@ void OpenGLRenderer::Blit(QVariant s, AcceleratedJob& a_job, Texture *destinatio
vert_vbo_.destroy();
vao_.release();
vao_.destroy();
} catch (std::bad_cast e) {
}
catch (std::bad_cast e){}
}
GLint OpenGLRenderer::GetInternalFormat(PixelFormat format, int channel_layout)
@@ -965,11 +972,11 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
const int major = context_ ? context_->format().majorVersion() : 0;
const int minor = context_ ? context_->format().minorVersion() : 0;
const bool is_gles2 = is_gles && (major < 3);
const QString gles_preamble = is_gles2
? QStringLiteral("#version 100\n\n"
const QString gles_preamble =
is_gles2 ? QStringLiteral("#version 100\n\n"
"precision highp float;\n\n"
"#define frag_color gl_FragColor\n")
: QStringLiteral("#version 300 es\n\n"
"#define frag_color gl_FragColor\n") :
QStringLiteral("#version 300 es\n\n"
"precision highp float;\n\n");
const QString desktop_preamble =
// Use appropriate GL 3.2 shader header
@@ -991,7 +998,8 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
QString complete_code;
if (base_code.startsWith(QStringLiteral("#version"))) {
if (is_gles || !desktop_preamble.startsWith(QStringLiteral("#version"))) {
if (is_gles ||
!desktop_preamble.startsWith(QStringLiteral("#version"))) {
int newline = base_code.indexOf('\n');
if (newline >= 0) {
complete_code = shader_preamble + base_code.mid(newline + 1);
@@ -1007,17 +1015,21 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
if (is_gles2) {
if (type == GL_VERTEX_SHADER) {
complete_code.replace(QRegularExpression(QStringLiteral("\\bin\\b")),
complete_code.replace(
QRegularExpression(QStringLiteral("\\bin\\b")),
QStringLiteral("attribute"));
complete_code.replace(QRegularExpression(QStringLiteral("\\bout\\b")),
complete_code.replace(
QRegularExpression(QStringLiteral("\\bout\\b")),
QStringLiteral("varying"));
} else if (type == GL_FRAGMENT_SHADER) {
complete_code.replace(QRegularExpression(QStringLiteral("\\bin\\b")),
complete_code.replace(
QRegularExpression(QStringLiteral("\\bin\\b")),
QStringLiteral("varying"));
complete_code.replace(QRegularExpression(
QStringLiteral("\\bout\\s+vec4\\s+frag_color\\s*;")),
complete_code.replace(QRegularExpression(QStringLiteral(
"\\bout\\s+vec4\\s+frag_color\\s*;")),
QStringLiteral("// frag_color output"));
complete_code.replace(QRegularExpression(QStringLiteral("\\btexture\\b")),
complete_code.replace(
QRegularExpression(QStringLiteral("\\btexture\\b")),
QStringLiteral("texture2D"));
}
}
@@ -1058,7 +1070,8 @@ bool OpenGLRenderer::EnsureContextCurrent(const char *caller)
// paint code can receive textures produced by a render-thread OpenGL
// renderer, so guard here before makeCurrent() can crash inside Qt/GL.
if (context_->thread() != QThread::currentThread()) {
qWarning() << caller << "called from the wrong thread for this OpenGL context";
qWarning()
<< caller << "called from the wrong thread for this OpenGL context";
return false;
}
File diff suppressed because it is too large Load Diff
+13 -7
View File
@@ -31,8 +31,10 @@
namespace olive
{
namespace plugin{
namespace detail {
namespace plugin
{
namespace detail
{
// 作用:将字节行跨度转换为像素跨度,便于纹理读写。
// Purpose: Convert byte stride to pixel stride for texture I/O.
int BytesToPixels(int byte_linesize, const olive::VideoParams &params);
@@ -46,9 +48,15 @@ int BytesToPixels(int byte_linesize, const olive::VideoParams &params);
class PluginRenderer : public QObject {
Q_OBJECT
public:
explicit PluginRenderer(olive::Renderer *renderer, QObject *parent = nullptr)
: QObject(parent), renderer_(renderer) {}
virtual ~PluginRenderer() override {}
explicit PluginRenderer(olive::Renderer *renderer,
QObject *parent = nullptr)
: QObject(parent)
, renderer_(renderer)
{
}
virtual ~PluginRenderer() override
{
}
olive::Renderer *renderer() const
{
@@ -74,6 +82,4 @@ private:
}
}
#endif //PLUGINRENDERER_H
+6 -3
View File
@@ -561,7 +561,8 @@ void PreviewAutoCacher::TryRender()
video_immediate_passthroughs_[watcher].append(t);
}
} else {
qWarning() << "Failed to find copied node for SFR ticket, requeueing";
qWarning()
<< "Failed to find copied node for SFR ticket, requeueing";
single_frame_render_ = t;
if (!delayed_requeue_timer_.isActive()) {
delayed_requeue_timer_.start();
@@ -594,7 +595,8 @@ void PreviewAutoCacher::TryRender()
}
}
} else {
qWarning() << "Failed to find node copy for video job, retrying";
qWarning()
<< "Failed to find node copy for video job, retrying";
if (!delayed_requeue_timer_.isActive()) {
delayed_requeue_timer_.start();
}
@@ -636,7 +638,8 @@ void PreviewAutoCacher::TryRender()
RenderAudio(copy, d.context, use_range, d.cache);
} else {
qWarning() << "Failed to find node copy for audio job, retrying";
qWarning()
<< "Failed to find node copy for audio job, retrying";
pop = false;
if (!delayed_requeue_timer_.isActive()) {
delayed_requeue_timer_.start();
+7 -4
View File
@@ -265,7 +265,9 @@ void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy)
if (Footage *src_footage = dynamic_cast<Footage *>(node)) {
if (dynamic_cast<Footage *>(copy)) {
connect(src_footage, &Footage::ProxySettingsChanged, this,
[this, src_footage]() { SyncFootageProxySettings(src_footage); });
[this, src_footage]() {
SyncFootageProxySettings(src_footage);
});
SyncFootageProxySettings(src_footage);
}
}
@@ -283,10 +285,11 @@ void ProjectCopier::SyncFootageProxySettings(Footage *source)
return;
}
qDebug() << "ProjectCopier::SyncFootageProxySettings:" << source->filename()
qDebug()
<< "ProjectCopier::SyncFootageProxySettings:" << source->filename()
<< "enabled=" << source->proxy_enabled() << "->"
<< copy->proxy_enabled() << "state="
<< ProxyManager::ProxyStateToString(source->proxy_state());
<< copy->proxy_enabled()
<< "state=" << ProxyManager::ProxyStateToString(source->proxy_state());
copy->SetProxy(source->proxy_path(), source->proxy_state(),
source->proxy_video_stream_index(),
+7 -5
View File
@@ -34,7 +34,8 @@
#include "texture.h"
// Forward declarations to keep the render core header lightweight
namespace olive {
namespace olive
{
class ColorTransformJob;
class Node;
}
@@ -63,11 +64,10 @@ public:
{
Blit(shader, job, destination, destination->params(),
clear_destination);
}
void Blit(QVariant shader, olive::AcceleratedJob& job, olive::VideoParams params,
bool clear_destination = true)
void Blit(QVariant shader, olive::AcceleratedJob &job,
olive::VideoParams params, bool clear_destination = true)
{
Blit(shader, job, nullptr, params, clear_destination);
}
@@ -147,7 +147,9 @@ public:
*
* Default implementation is a no-op.
*/
virtual void DetachOutputTexture() {}
virtual void DetachOutputTexture()
{
}
protected:
virtual void Blit(QVariant shader, olive::AcceleratedJob &job,
+13 -8
View File
@@ -78,24 +78,26 @@ QString RenderManager::BackendToString(Backend backend)
}
RenderManager::RenderManager(QObject *parent)
: backend_(BackendFromString(
OLIVE_CONFIG("GraphicsBackend").toString()))
: backend_(BackendFromString(OLIVE_CONFIG("GraphicsBackend").toString()))
, requested_backend_(backend_)
, aggressive_gc_(0)
, worker_pool_(nullptr)
{
if (backend_ == kVulkan) {
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
qWarning() << "Vulkan backend requested but dynamic render backend is not enabled. Falling back to OpenGL.";
qWarning()
<< "Vulkan backend requested but dynamic render backend is not enabled. Falling back to OpenGL.";
backend_ = kOpenGL;
#endif
}
if (backend_ == kOpenGL || backend_ == kVulkan) {
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
auto *dynamic_renderer = new DynamicRenderer(BackendToString(requested_backend_));
auto *dynamic_renderer =
new DynamicRenderer(BackendToString(requested_backend_));
if (!dynamic_renderer->Load()) {
qWarning() << "Failed to load dynamic render backend" << BackendToString(requested_backend_)
qWarning() << "Failed to load dynamic render backend"
<< BackendToString(requested_backend_)
<< ", falling back to OpenGL";
delete dynamic_renderer;
backend_ = kOpenGL;
@@ -104,7 +106,8 @@ RenderManager::RenderManager(QObject *parent)
context_ = dynamic_renderer;
// DynamicRenderer may internally fall back (e.g. Vulkan -> OpenGL).
// Synchronize RenderManager's view of the actual runtime backend.
Backend actual_backend = BackendFromString(dynamic_renderer->backend_name());
Backend actual_backend =
BackendFromString(dynamic_renderer->backend_name());
if (actual_backend != backend_) {
qWarning() << "Dynamic render backend fell back from"
<< BackendToString(backend_) << "to"
@@ -218,10 +221,12 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams &params)
if (worker_params.return_type == ReturnType::kNull) {
dry_run_thread_->AddTicket(ticket);
} else if (worker_pool_ && worker_pool_->SubmitFrame(ticket, worker_params)) {
} else if (worker_pool_ &&
worker_pool_->SubmitFrame(ticket, worker_params)) {
return ticket;
} else {
qWarning() << "RenderManager: worker pool unavailable, finishing ticket "
qWarning()
<< "RenderManager: worker pool unavailable, finishing ticket "
"without result";
ticket->Finish();
}
+1 -1
View File
@@ -82,7 +82,7 @@ public:
/// Vulkan requested by the user. Falls back to OpenGL until VulkanRenderer is implemented.
kVulkan,
/// Video frames are rendered by an external olive-render-worker process.
/// Video frames are rendered by an external oak-render-worker process.
kMultiProcess,
/// No graphics rendering - used to test core threading logic
+42 -40
View File
@@ -28,7 +28,6 @@
#include <QVector3D>
#include <QVector4D>
#include "audio/audioprocessor.h"
#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
@@ -159,7 +158,6 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture,
QString::fromUtf8(output_color_transform->id()));
frame->set_video_params(display_params);
}
}
return frame;
@@ -416,7 +414,8 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
}
if (using_colorspace.isEmpty()) {
qWarning() << "RenderProcessor ProcessVideoFootage: no input colorspace available";
qWarning()
<< "RenderProcessor ProcessVideoFootage: no input colorspace available";
}
auto blit_color_managed = [&](const TexturePtr &unmanaged_texture,
@@ -427,8 +426,8 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
// We convert to our rendering pixel format, since that will always be float-based which
// is necessary for correct color conversion
ColorProcessorPtr processor = ColorProcessor::Create(
color_manager, using_colorspace,
ColorProcessorPtr processor =
ColorProcessor::Create(color_manager, using_colorspace,
color_manager->GetReferenceColorSpace());
ColorTransformJob job;
@@ -436,7 +435,8 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
job.SetInputTexture(unmanaged_texture);
if (texture_params.channel_count() != VideoParams::kRGBAChannelCount ||
texture_params.colorspace() == color_manager->GetReferenceColorSpace()) {
texture_params.colorspace() ==
color_manager->GetReferenceColorSpace()) {
job.SetInputAlphaAssociation(kAlphaNone);
} else if (texture_params.premultiplied_alpha()) {
job.SetInputAlphaAssociation(kAlphaAssociated);
@@ -450,12 +450,14 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
render_ctx_->Flush();
};
auto *input_pool =
QtUtils::ValueToPtr<ipc::FrameSlotPool>(ticket_->property("ipc_input_pool"));
auto *input_pool = QtUtils::ValueToPtr<ipc::FrameSlotPool>(
ticket_->property("ipc_input_pool"));
int input_slot = -1;
const QVariantList input_slots = ticket_->property("ipc_input_slots").toList();
const QVariantList input_slots =
ticket_->property("ipc_input_slots").toList();
if (!input_slots.isEmpty()) {
const QVariant cursor_value = ticket_->property("ipc_input_slot_cursor");
const QVariant cursor_value =
ticket_->property("ipc_input_slot_cursor");
const int cursor = cursor_value.isValid() ? cursor_value.toInt() : 0;
if (cursor >= 0 && cursor < input_slots.size()) {
input_slot = input_slots.at(cursor).toInt();
@@ -467,13 +469,15 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
}
if (render_ctx_ && input_pool && input_slot >= 0) {
if (input_slot >= int(input_pool->slot_count())) {
qWarning() << "RenderProcessor received out-of-range IPC input frame slot"
qWarning()
<< "RenderProcessor received out-of-range IPC input frame slot"
<< input_slot;
return;
}
const ipc::FrameSlotMeta *meta = input_pool->Meta(uint32_t(input_slot));
if (meta && meta->width > 0 && meta->height > 0 && meta->data_size > 0 &&
if (meta && meta->width > 0 && meta->height > 0 &&
meta->data_size > 0 &&
meta->data_size <= int(input_pool->slot_data_bytes())) {
VideoParams input_params = stream_data;
input_params.set_width(meta->width);
@@ -486,7 +490,6 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
input_params.set_depth(1);
}
// Prefer the colorspace that the main process used when decoding this
// frame. The FootageJob reconstructed in the worker may have stale or
// empty colorspace if the project snapshot was saved before stream
@@ -498,9 +501,9 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
}
const int bytes_per_pixel = input_params.GetBytesPerPixel();
const int linesize_pixels = bytes_per_pixel > 0
? meta->linesize / bytes_per_pixel
: input_params.effective_width();
const int linesize_pixels = bytes_per_pixel > 0 ?
meta->linesize / bytes_per_pixel :
input_params.effective_width();
const void *slot_data = input_pool->SlotData(uint32_t(input_slot));
TexturePtr unmanaged_texture = render_ctx_->CreateTexture(
@@ -509,12 +512,14 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
blit_color_managed(unmanaged_texture, input_params);
return;
}
qWarning() << "RenderProcessor received invalid IPC input frame slot" << input_slot;
qWarning() << "RenderProcessor received invalid IPC input frame slot"
<< input_slot;
return;
}
if (!decoder_cache_) {
qWarning() << "RenderProcessor has no decoder cache or IPC input frame for"
qWarning()
<< "RenderProcessor has no decoder cache or IPC input frame for"
<< stream->filename();
return;
}
@@ -523,15 +528,15 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
static_cast<RenderMode::Mode>(ticket_->property("mode").toInt()) ==
RenderMode::kOffline &&
stream->has_proxy() && QFileInfo::exists(stream->proxy_filename());
const QString decode_filename =
use_proxy ? stream->proxy_filename() : stream->filename();
const QString decoder_id =
use_proxy ? stream->proxy_decoder() : stream->decoder();
const int stream_index =
use_proxy ? stream->proxy_stream_index() : stream_data.stream_index();
const QString decode_filename = use_proxy ? stream->proxy_filename() :
stream->filename();
const QString decoder_id = use_proxy ? stream->proxy_decoder() :
stream->decoder();
const int stream_index = use_proxy ? stream->proxy_stream_index() :
stream_data.stream_index();
Decoder::CodecStream default_codec_stream(
decode_filename, stream_index, GetCurrentBlock());
Decoder::CodecStream default_codec_stream(decode_filename, stream_index,
GetCurrentBlock());
DecoderPtr decoder = nullptr;
@@ -553,8 +558,8 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
decode_filename, frame_number);
// Decoder will close automatically since it's a stream_ptr
decoder->Open(Decoder::CodecStream(
frame_filename, stream_index, GetCurrentBlock()));
decoder->Open(Decoder::CodecStream(frame_filename, stream_index,
GetCurrentBlock()));
}
break;
}
@@ -643,7 +648,8 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node,
locker.unlock();
// Run shader
render_ctx_->BlitToTexture(shader, const_cast<ShaderJob&>(*job), destination.get());
render_ctx_->BlitToTexture(shader, const_cast<ShaderJob &>(*job),
destination.get());
}
void RenderProcessor::ProcessSamples(SampleBuffer &destination,
@@ -720,8 +726,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
return destination;
}
auto *plugin_job =
dynamic_cast<plugin::PluginJob *>(texture->job());
auto *plugin_job = dynamic_cast<plugin::PluginJob *>(texture->job());
if (!plugin_job) {
return destination;
}
@@ -779,13 +784,8 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
}
}
plugin_renderer.RenderPlugin(
src,
*plugin_job,
destination,
destination->params(),
true,
false);
plugin_renderer.RenderPlugin(src, *plugin_job, destination,
destination->params(), true, false);
return destination;
}
@@ -797,7 +797,8 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob *val)
// Auto-detect and discard black/empty cached frames (macOS TBDR artifact)
bool all_black = true;
if (frame->data() && frame->allocated_size() > 0) {
const uint8_t *pixels = reinterpret_cast<const uint8_t *>(frame->data());
const uint8_t *pixels =
reinterpret_cast<const uint8_t *>(frame->data());
size_t alloc_size = static_cast<size_t>(frame->allocated_size());
size_t check_bytes = std::min(alloc_size, size_t(4096));
for (size_t i = 0; i < check_bytes; ++i) {
@@ -808,7 +809,8 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob *val)
}
}
if (all_black) {
qWarning() << "[CACHE] Discarding black cached frame:" << val->GetFilename()
qWarning() << "[CACHE] Discarding black cached frame:"
<< val->GetFilename()
<< "time=" << frame->timestamp().toDouble()
<< "size=" << frame->allocated_size();
QFile::remove(val->GetFilename());
+2 -1
View File
@@ -32,7 +32,8 @@
namespace olive
{
namespace plugin {
namespace plugin
{
class PluginRenderer;
}
+209 -131
View File
@@ -62,8 +62,8 @@ struct FootageInput {
class FootageInputCollector : public NodeTraverser {
public:
QVector<FootageInput> Collect(const RenderManager::RenderVideoParams &params,
CancelAtom *cancel)
QVector<FootageInput>
Collect(const RenderManager::RenderVideoParams &params, CancelAtom *cancel)
{
SetCancelPointer(cancel);
VideoParams cache_params = params.video_params;
@@ -75,16 +75,14 @@ public:
if (cache_params.interlacing() != VideoParams::kInterlaceNone) {
frame_length /= 2;
}
NodeValueTable table = GenerateTable(params.node,
TimeRange(params.time,
params.time + frame_length));
NodeValueTable table = GenerateTable(
params.node, TimeRange(params.time, params.time + frame_length));
NodeValue texture = table.Get(NodeValue::kTexture);
ResolveJobs(texture);
if (cache_params.interlacing() != VideoParams::kInterlaceNone) {
NodeValueTable second_table =
GenerateTable(params.node,
TimeRange(params.time + frame_length,
NodeValueTable second_table = GenerateTable(
params.node, TimeRange(params.time + frame_length,
params.time + frame_length * 2));
NodeValue second_texture = second_table.Get(NodeValue::kTexture);
ResolveJobs(second_texture);
@@ -94,8 +92,7 @@ public:
}
protected:
void ProcessVideoFootage(TexturePtr destination,
const FootageJob *stream,
void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream,
const rational &input_time) override
{
Q_UNUSED(destination)
@@ -140,8 +137,7 @@ DecoderPtr ResolveDecoderFromCache(DecoderCache *decoder_cache,
}
FramePtr DecodeInputFrame(DecoderCache *decoder_cache,
const FootageInput &input,
CancelAtom *cancel)
const FootageInput &input, CancelAtom *cancel)
{
VideoParams stream_data = input.job.video_params();
QString filename = input.job.filename();
@@ -162,19 +158,17 @@ FramePtr DecodeInputFrame(DecoderCache *decoder_cache,
case VideoParams::kVideoTypeVideo:
case VideoParams::kVideoTypeStill:
decoder = ResolveDecoderFromCache(
decoder_cache,
decoder_id,
decoder_cache, decoder_id,
Decoder::CodecStream(filename, stream_index, nullptr));
break;
case VideoParams::kVideoTypeImageSequence: {
const int64_t frame_number =
stream_data.get_time_in_timebase_units(input.time);
filename = Decoder::TransformImageSequenceFileName(filename, frame_number);
filename =
Decoder::TransformImageSequenceFileName(filename, frame_number);
decoder = Decoder::CreateFromID(decoder_id);
if (decoder &&
!decoder->Open(Decoder::CodecStream(filename,
stream_index,
nullptr))) {
if (decoder && !decoder->Open(Decoder::CodecStream(
filename, stream_index, nullptr))) {
decoder = nullptr;
}
break;
@@ -188,9 +182,9 @@ FramePtr DecodeInputFrame(DecoderCache *decoder_cache,
Decoder::RetrieveVideoParams retrieve;
retrieve.divider = stream_data.divider();
retrieve.maximum_format = PixelFormat::U16;
retrieve.time = stream_data.video_type() == VideoParams::kVideoTypeVideo
? input.time
: Decoder::kAnyTimecode;
retrieve.time = stream_data.video_type() == VideoParams::kVideoTypeVideo ?
input.time :
Decoder::kAnyTimecode;
retrieve.cancelled = cancel;
retrieve.force_range = stream_data.color_range();
retrieve.src_interlacing = stream_data.interlacing();
@@ -207,15 +201,13 @@ FramePtr DecodeInputFrame(DecoderCache *decoder_cache,
frame_params.set_colorspace(stream_data.colorspace());
frame->set_video_params(frame_params);
}
}
return frame;
}
bool DecodeInputFrames(DecoderCache *decoder_cache,
const RenderManager::RenderVideoParams &params,
CancelAtom *cancel,
QVector<FramePtr> *frames)
CancelAtom *cancel, QVector<FramePtr> *frames)
{
frames->clear();
@@ -241,9 +233,9 @@ bool DecodeInputFrames(DecoderCache *decoder_cache,
QString WorkerProgramPath()
{
#if defined(Q_OS_WIN)
const QString file = QStringLiteral("olive-render-worker.exe");
const QString file = QStringLiteral("oak-render-worker.exe");
#else
const QString file = QStringLiteral("olive-render-worker");
const QString file = QStringLiteral("oak-render-worker");
#endif
const QString app_dir = QCoreApplication::applicationDirPath();
@@ -271,7 +263,8 @@ bool WriteControlMessage(QProcess *process, const QJsonObject &obj)
return false;
}
const QByteArray line = QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
const QByteArray line =
QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
const qint64 written = process->write(line);
if (written != line.size()) {
return false;
@@ -285,7 +278,8 @@ void TryWriteControlMessage(QProcess *process, const QJsonObject &obj)
return;
}
const QByteArray line = QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
const QByteArray line =
QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
process->write(line);
}
@@ -315,12 +309,14 @@ bool IsProcessAlive(qint64 process_id)
}
#if defined(Q_OS_WIN)
HANDLE handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, DWORD(process_id));
HANDLE handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
DWORD(process_id));
if (!handle) {
return false;
}
DWORD exit_code = 0;
const bool alive = GetExitCodeProcess(handle, &exit_code) && exit_code == STILL_ACTIVE;
const bool alive = GetExitCodeProcess(handle, &exit_code) &&
exit_code == STILL_ACTIVE;
CloseHandle(handle);
return alive;
#else
@@ -334,11 +330,11 @@ QString WorkerProcessDetails(const QProcess *process)
return QStringLiteral("worker process unavailable");
}
const QString exit_status =
process->exitStatus() == QProcess::CrashExit
? QStringLiteral("crash")
: QStringLiteral("normal");
return QStringLiteral("state=%1 exit_status=%2 exit_code=%3 process_error=%4 error=\"%5\"")
const QString exit_status = process->exitStatus() == QProcess::CrashExit ?
QStringLiteral("crash") :
QStringLiteral("normal");
return QStringLiteral(
"state=%1 exit_status=%2 exit_code=%3 process_error=%4 error=\"%5\"")
.arg(int(process->state()))
.arg(exit_status)
.arg(process->exitCode())
@@ -355,7 +351,8 @@ bool ReadControlMessage(QProcess *process, QJsonObject *out, QString *error,
*error = QStringLiteral("worker exited before response: %1")
.arg(WorkerProcessDetails(process));
} else {
*error = QStringLiteral("timeout waiting for worker response: %1")
*error =
QStringLiteral("timeout waiting for worker response: %1")
.arg(WorkerProcessDetails(process));
}
}
@@ -372,7 +369,8 @@ bool ReadControlMessage(QProcess *process, QJsonObject *out, QString *error,
const QJsonDocument doc = QJsonDocument::fromJson(line, &parse_error);
if (parse_error.error != QJsonParseError::NoError || !doc.isObject()) {
if (error) {
*error = QStringLiteral("worker emitted malformed control JSON");
*error =
QStringLiteral("worker emitted malformed control JSON");
}
return false;
}
@@ -397,8 +395,7 @@ bool ReadControlMessage(QProcess *process, QJsonObject *out, QString *error,
} // namespace
RenderWorkerPool::RenderWorkerPool(DecoderCache *decoder_cache,
const QString &gpu_backend,
QObject *parent)
const QString &gpu_backend, QObject *parent)
: QThread(parent)
, decoder_cache_(decoder_cache)
, gpu_backend_(gpu_backend)
@@ -410,8 +407,8 @@ RenderWorkerPool::~RenderWorkerPool()
Shutdown();
}
bool RenderWorkerPool::SubmitFrame(RenderTicketPtr ticket,
const RenderManager::RenderVideoParams &params)
bool RenderWorkerPool::SubmitFrame(
RenderTicketPtr ticket, const RenderManager::RenderVideoParams &params)
{
Job job(ticket, params);
if (!PrepareJob(ticket, params, &job)) {
@@ -460,7 +457,7 @@ bool RenderWorkerPool::RemoveTicket(RenderTicketPtr ticket)
}
if (!queued_graph_path.isEmpty()) {
CleanupGraphFile(queued_graph_path);
ReleaseGraphPathRef(queued_graph_path);
return true;
}
@@ -469,8 +466,6 @@ bool RenderWorkerPool::RemoveTicket(RenderTicketPtr ticket)
void RenderWorkerPool::Shutdown()
{
QVector<QString> graph_paths_to_clean;
{
QMutexLocker locker(&mutex_);
stopping_ = true;
@@ -478,6 +473,7 @@ void RenderWorkerPool::Shutdown()
if (job.ticket) {
job.ticket->Cancel();
}
ReleaseGraphPathRefLocked(job.graph_path);
}
queue_.clear();
for (ActiveJob &active : active_jobs_) {
@@ -487,16 +483,12 @@ void RenderWorkerPool::Shutdown()
}
}
for (auto it = graph_cache_.begin(); it != graph_cache_.end(); ++it) {
graph_paths_to_clean.append(it->path);
SetGraphPathCachedLocked(it->path, false);
}
graph_cache_.clear();
wait_.wakeAll();
}
for (const QString &path : graph_paths_to_clean) {
CleanupGraphFile(path);
}
if (isRunning()) {
wait();
}
@@ -510,13 +502,13 @@ void RenderWorkerPool::run()
active_jobs_.resize(worker_count);
}
std::vector<std::vector<std::unique_ptr<PooledWorker>>> local_pools(worker_count);
std::vector<std::vector<std::unique_ptr<PooledWorker>>> local_pools(
worker_count);
std::vector<std::thread> workers;
workers.reserve(size_t(worker_count));
for (int i = 0; i < worker_count; i++) {
workers.emplace_back([this, i, &local_pools]() {
WorkerLoop(i, &local_pools[i]);
});
workers.emplace_back(
[this, i, &local_pools]() { WorkerLoop(i, &local_pools[i]); });
}
for (std::thread &worker : workers) {
@@ -534,8 +526,7 @@ void RenderWorkerPool::run()
}
void RenderWorkerPool::WorkerLoop(
int worker_index,
std::vector<std::unique_ptr<PooledWorker>> *local_pool)
int worker_index, std::vector<std::unique_ptr<PooledWorker>> *local_pool)
{
while (true) {
mutex_.lock();
@@ -552,6 +543,7 @@ void RenderWorkerPool::WorkerLoop(
mutex_.unlock();
ProcessJob(job, worker_index, local_pool);
ReleaseGraphPathRef(job.graph_path);
}
}
@@ -565,7 +557,8 @@ bool RenderWorkerPool::PrepareJob(RenderTicketPtr ticket,
Project *project = Project::GetProjectFromObject(params.node);
if (!project) {
qWarning() << "RenderWorkerPool could not resolve project for render node";
qWarning()
<< "RenderWorkerPool could not resolve project for render node";
return false;
}
@@ -585,13 +578,16 @@ bool RenderWorkerPool::PrepareJob(RenderTicketPtr ticket,
auto it = graph_cache_.find(project_uuid);
if (it != graph_cache_.end() && !project->is_modified()) {
graph_path = it->path;
//qDebug() << "RenderWorkerPool::PrepareJob: using cached graph snapshot"
// << graph_path;
AddGraphPathRefLocked(graph_path);
qDebug()
<< "RenderWorkerPool::PrepareJob: using cached graph snapshot"
<< graph_path;
} else {
if (it != graph_cache_.end()) {
qDebug() << "RenderWorkerPool::PrepareJob: graph stale, rewriting"
qDebug()
<< "RenderWorkerPool::PrepareJob: graph stale, rewriting"
<< project->is_modified();
CleanupGraphFile(it->path);
SetGraphPathCachedLocked(it->path, false);
graph_cache_.erase(it);
}
locker.unlock();
@@ -607,6 +603,8 @@ bool RenderWorkerPool::PrepareJob(RenderTicketPtr ticket,
}
locker.relock();
graph_cache_.insert(project_uuid, { graph_path });
SetGraphPathCachedLocked(graph_path, true);
AddGraphPathRefLocked(graph_path);
}
}
@@ -621,17 +619,26 @@ bool RenderWorkerPool::PrepareJob(RenderTicketPtr ticket,
bool RenderWorkerPool::WriteGraphSnapshot(Project *project, QString *path)
{
QTemporaryFile file(QDir::temp().filePath(QStringLiteral("oak-render-graph-XXXXXX.ove")));
// Keep snapshots in the system temp directory. The previous bug was not the
// temp location itself, but stale snapshots being deleted while queued jobs
// still referenced them.
const QString graph_dir = QDir::tempPath();
QTemporaryFile file(QDir(graph_dir).filePath(
QStringLiteral("oak-render-graph-XXXXXX.ove")));
file.setAutoRemove(false);
if (!file.open()) {
qWarning() << "RenderWorkerPool failed to create graph snapshot temp file"
qWarning()
<< "RenderWorkerPool failed to create graph snapshot temp file"
<< file.errorString();
return false;
}
QXmlStreamWriter writer(&file);
ProjectSerializer::SaveData data(ProjectSerializer::kProject, project, file.fileName());
const ProjectSerializer::Result result = ProjectSerializer::Save(&writer, data);
ProjectSerializer::SaveData data(ProjectSerializer::kProject, project,
file.fileName());
const ProjectSerializer::Result result =
ProjectSerializer::Save(&writer, data);
file.close();
if (result.code() != ProjectSerializer::kSuccess || writer.hasError()) {
@@ -641,11 +648,15 @@ bool RenderWorkerPool::WriteGraphSnapshot(Project *project, QString *path)
return false;
}
qDebug() << "RenderWorkerPool wrote graph snapshot" << file.fileName()
<< "size" << QFileInfo(file.fileName()).size();
*path = file.fileName();
return true;
}
bool RenderWorkerPool::IsSupported(const RenderManager::RenderVideoParams &params) const
bool RenderWorkerPool::IsSupported(
const RenderManager::RenderVideoParams &params) const
{
return params.node && params.return_type == RenderManager::kFrame &&
params.video_params.is_valid();
@@ -655,7 +666,8 @@ void RenderWorkerPool::ProcessJob(
const Job &job, int worker_index,
std::vector<std::unique_ptr<PooledWorker>> *local_pool)
{
const qint64 ticket_id = qint64(reinterpret_cast<quintptr>(job.ticket.get()));
const qint64 ticket_id =
qint64(reinterpret_cast<quintptr>(job.ticket.get()));
SetActiveWorker(worker_index, job.ticket, nullptr, ticket_id);
job.ticket->Start();
@@ -665,7 +677,8 @@ void RenderWorkerPool::ProcessJob(
return;
}
std::unique_ptr<PooledWorker> worker = AcquireWorker(local_pool, job.graph_path);
std::unique_ptr<PooledWorker> worker =
AcquireWorker(local_pool, job.graph_path);
if (!worker) {
qWarning() << "RenderWorkerPool failed to acquire worker for ticket"
<< ticket_id;
@@ -678,22 +691,24 @@ void RenderWorkerPool::ProcessJob(
if (attempt > 0) {
worker = AcquireWorker(local_pool, job.graph_path);
if (!worker) {
qWarning() << "RenderWorkerPool failed to acquire worker for retry"
qWarning()
<< "RenderWorkerPool failed to acquire worker for retry"
<< ticket_id;
break;
}
}
const JobResult result = ProcessJobAttempt(job, worker_index, attempt,
worker.get());
const qint64 worker_pid = worker && worker->process
? worker->process->processId()
: 0;
const JobResult result =
ProcessJobAttempt(job, worker_index, attempt, worker.get());
const qint64 worker_pid =
worker && worker->process ? worker->process->processId() : 0;
const bool process_state_running = worker && worker->process &&
worker->process->state() == QProcess::Running;
worker->process->state() ==
QProcess::Running;
const bool os_alive = worker_pid > 0 && IsProcessAlive(worker_pid);
const bool worker_healthy = process_state_running || os_alive;
const bool keep_alive = (result == JobResult::kFinished) && worker_healthy;
const bool keep_alive = (result == JobResult::kFinished) &&
worker_healthy;
ReturnWorker(local_pool, std::move(worker), keep_alive);
worker.reset();
@@ -726,11 +741,12 @@ void RenderWorkerPool::ProcessJob(
ClearActiveWorker(worker_index, 0);
}
RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
const Job &job, int worker_index, int attempt_index,
PooledWorker *worker)
RenderWorkerPool::JobResult
RenderWorkerPool::ProcessJobAttempt(const Job &job, int worker_index,
int attempt_index, PooledWorker *worker)
{
const qint64 ticket_id = qint64(reinterpret_cast<quintptr>(job.ticket.get()));
const qint64 ticket_id =
qint64(reinterpret_cast<quintptr>(job.ticket.get()));
if (job.ticket->IsCancelled()) {
return JobResult::kCancelled;
}
@@ -741,27 +757,25 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
const qint64 worker_process_id = worker->process->processId();
const int output_width = job.params.force_size.width() > 0
? job.params.force_size.width()
: job.params.video_params.effective_width();
const int output_height = job.params.force_size.height() > 0
? job.params.force_size.height()
: job.params.video_params.effective_height();
const int output_width = job.params.force_size.width() > 0 ?
job.params.force_size.width() :
job.params.video_params.effective_width();
const int output_height = job.params.force_size.height() > 0 ?
job.params.force_size.height() :
job.params.video_params.effective_height();
const PixelFormat::Format output_format =
job.params.force_format != PixelFormat::INVALID
? PixelFormat::Format(job.params.force_format)
: PixelFormat::F32;
const int output_channels = job.params.force_channel_count > 0
? job.params.force_channel_count
: VideoParams::kRGBAChannelCount;
const int output_linesize =
Frame::generate_linesize_bytes(output_width, output_format,
output_channels);
job.params.force_format != PixelFormat::INVALID ?
PixelFormat::Format(job.params.force_format) :
PixelFormat::F32;
const int output_channels = job.params.force_channel_count > 0 ?
job.params.force_channel_count :
VideoParams::kRGBAChannelCount;
const int output_linesize = Frame::generate_linesize_bytes(
output_width, output_format, output_channels);
const size_t estimated_output_slot_bytes =
size_t(output_linesize) * size_t(output_height);
const int f32_rgba_linesize =
Frame::generate_linesize_bytes(output_width, PixelFormat::F32,
VideoParams::kRGBAChannelCount);
const int f32_rgba_linesize = Frame::generate_linesize_bytes(
output_width, PixelFormat::F32, VideoParams::kRGBAChannelCount);
const size_t f32_rgba_slot_bytes =
size_t(f32_rgba_linesize) * size_t(output_height);
const size_t output_slot_bytes =
@@ -790,7 +804,8 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
if (!worker->output_region.Open(worker->output_shm_key,
output_region_bytes,
ipc::SharedMemoryRegion::kCreate)) {
qWarning() << "RenderWorkerPool failed to create output shared memory"
qWarning()
<< "RenderWorkerPool failed to create output shared memory"
<< worker->output_region.error();
return JobResult::kFatalFailure;
}
@@ -816,17 +831,19 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
ipc::SharedMemoryRegion::MakeKey(worker_process_id, 1) +
QStringLiteral("-in");
}
const size_t input_region_bytes =
ipc::FrameSlotPool::BytesNeeded(input_slot_count, input_slot_bytes);
const size_t input_region_bytes = ipc::FrameSlotPool::BytesNeeded(
input_slot_count, input_slot_bytes);
if (!worker->input_region.Open(worker->input_shm_key,
input_region_bytes,
ipc::SharedMemoryRegion::kCreate)) {
qWarning() << "RenderWorkerPool failed to create input shared memory"
qWarning()
<< "RenderWorkerPool failed to create input shared memory"
<< worker->input_region.error();
return JobResult::kFatalFailure;
}
worker->input_pool = ipc::FrameSlotPool::Create(
worker->input_region.data(), input_slot_count, input_slot_bytes);
worker->input_pool =
ipc::FrameSlotPool::Create(worker->input_region.data(),
input_slot_count, input_slot_bytes);
worker->input_slot_bytes = input_slot_bytes;
}
}
@@ -836,7 +853,8 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
if (input_slot_count > 0) {
for (const FramePtr &frame : job.input_frames) {
if (frame->allocated_size() > int(worker->input_slot_bytes)) {
qWarning() << "RenderWorkerPool decoded input frame exceeds slot size";
qWarning()
<< "RenderWorkerPool decoded input frame exceeds slot size";
return JobResult::kFatalFailure;
}
@@ -862,8 +880,8 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
const QString cs = frame->video_params().colorspace();
if (!cs.isEmpty()) {
const QByteArray cs_utf8 = cs.toUtf8();
const size_t copy_len = qMin(
static_cast<size_t>(cs_utf8.size()),
const size_t copy_len =
qMin(static_cast<size_t>(cs_utf8.size()),
sizeof(meta->colorspace) - 1);
memcpy(meta->colorspace, cs_utf8.constData(), copy_len);
meta->colorspace[copy_len] = '\0';
@@ -898,16 +916,16 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
handshake.input_slots = input_slots.size();
handshake.output_slots = int(kOutputSlots);
handshake.slot_data_bytes = qint64(output_slot_bytes);
handshake.input_slot_data_bytes = input_slots.isEmpty()
? 0
: qint64(input_slot_bytes);
handshake.input_slot_data_bytes =
input_slots.isEmpty() ? 0 : qint64(input_slot_bytes);
if (!WriteControlMessage(worker->process, handshake.ToJson())) {
if (!job.ticket->IsCancelled()) {
qWarning() << "RenderWorkerPool failed to send shared-memory handshake";
qWarning()
<< "RenderWorkerPool failed to send shared-memory handshake";
}
ClearActiveWorker(worker_index, worker_process_id);
return job.ticket->IsCancelled() ? JobResult::kCancelled
: JobResult::kRetryableFailure;
return job.ticket->IsCancelled() ? JobResult::kCancelled :
JobResult::kRetryableFailure;
}
if (worker->loaded_graph_path != job.graph_path) {
@@ -922,11 +940,10 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
<< error << worker->process->readAllStandardError();
}
ClearActiveWorker(worker_index, worker_process_id);
return job.ticket->IsCancelled() ? JobResult::kCancelled
: JobResult::kRetryableFailure;
return job.ticket->IsCancelled() ? JobResult::kCancelled :
JobResult::kRetryableFailure;
}
worker->loaded_graph_path = job.graph_path;
}
ipc::RenderFrameMsg render;
render.ticket_id = ticket_id;
@@ -953,8 +970,8 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
qWarning() << "RenderWorkerPool failed to send render_frame";
}
ClearActiveWorker(worker_index, worker_process_id);
return job.ticket->IsCancelled() ? JobResult::kCancelled
: JobResult::kRetryableFailure;
return job.ticket->IsCancelled() ? JobResult::kCancelled :
JobResult::kRetryableFailure;
}
QString error;
@@ -967,8 +984,8 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt(
<< error << worker->process->readAllStandardError();
}
ClearActiveWorker(worker_index, worker_process_id);
return job.ticket->IsCancelled() ? JobResult::kCancelled
: JobResult::kRetryableFailure;
return job.ticket->IsCancelled() ? JobResult::kCancelled :
JobResult::kRetryableFailure;
}
if (ipc::FrameReadyMsg::FromJson(response, &ready)) {
@@ -1061,8 +1078,8 @@ std::unique_ptr<RenderWorkerPool::PooledWorker> RenderWorkerPool::AcquireWorker(
local_pool->erase(local_pool->begin() + i);
continue;
}
const bool candidate_state_running =
candidate->process->state() == QProcess::Running;
const bool candidate_state_running = candidate->process->state() ==
QProcess::Running;
const bool candidate_os_alive =
IsProcessAlive(candidate->process->processId());
if (!candidate_state_running && !candidate_os_alive) {
@@ -1078,7 +1095,8 @@ std::unique_ptr<RenderWorkerPool::PooledWorker> RenderWorkerPool::AcquireWorker(
if (best_index < 0 ||
(!candidate->loaded_graph_path.isEmpty() &&
candidate->loaded_graph_path == graph_path &&
((*local_pool)[size_t(best_index)]->loaded_graph_path != graph_path))) {
((*local_pool)[size_t(best_index)]->loaded_graph_path !=
graph_path))) {
best_index = int(i);
}
++i;
@@ -1093,14 +1111,14 @@ std::unique_ptr<RenderWorkerPool::PooledWorker> RenderWorkerPool::AcquireWorker(
return worker;
}
// No idle worker available: start a new one.
auto *process = new QProcess();
process->setProgram(WorkerProgramPath());
process->setArguments({ QStringLiteral("--backend"), gpu_backend_ });
const QString worker_stderr_path = QDir(QDir::tempPath()).filePath(
QStringLiteral("oak-render-worker-%1-%2.stderr.log")
const QString worker_stderr_path =
QDir(QDir::tempPath())
.filePath(QStringLiteral("oak-render-worker-%1-%2.stderr.log")
.arg(QCoreApplication::applicationPid())
.arg(QDateTime::currentMSecsSinceEpoch()));
process->setStandardErrorFile(worker_stderr_path);
@@ -1133,8 +1151,7 @@ std::unique_ptr<RenderWorkerPool::PooledWorker> RenderWorkerPool::AcquireWorker(
void RenderWorkerPool::ReturnWorker(
std::vector<std::unique_ptr<PooledWorker>> *local_pool,
std::unique_ptr<PooledWorker> worker,
bool keep_alive)
std::unique_ptr<PooledWorker> worker, bool keep_alive)
{
if (!worker || !worker->process) {
return;
@@ -1190,9 +1207,11 @@ void RenderWorkerPool::ClearGraphCache()
{
QMutexLocker locker(&mutex_);
for (auto it = graph_cache_.begin(); it != graph_cache_.end(); ++it) {
CleanupGraphFile(it->path);
SetGraphPathCachedLocked(it->path, false);
}
graph_cache_.clear();
graph_path_ref_count_.clear();
cached_graph_paths_.clear();
}
void RenderWorkerPool::FinishWithFrame(RenderTicketPtr ticket,
@@ -1207,8 +1226,7 @@ void RenderWorkerPool::FinishWithFrame(RenderTicketPtr ticket,
}
VideoParams params(meta->width, meta->height,
PixelFormat::Format(meta->format),
meta->channel_count);
PixelFormat::Format(meta->format), meta->channel_count);
FramePtr frame = Frame::Create();
frame->set_timestamp(rational(int(meta->time_num), int(meta->time_den)));
frame->set_video_params(params);
@@ -1224,8 +1242,68 @@ void RenderWorkerPool::FinishWithFrame(RenderTicketPtr ticket,
void RenderWorkerPool::CleanupGraphFile(const QString &path)
{
if (!path.isEmpty()) {
qDebug() << "RenderWorkerPool cleaning up graph file" << path;
QFile::remove(path);
}
}
void RenderWorkerPool::AddGraphPathRef(const QString &path)
{
QMutexLocker locker(&mutex_);
AddGraphPathRefLocked(path);
}
void RenderWorkerPool::AddGraphPathRefLocked(const QString &path)
{
if (path.isEmpty()) {
return;
}
++graph_path_ref_count_[path];
}
void RenderWorkerPool::ReleaseGraphPathRef(const QString &path)
{
QMutexLocker locker(&mutex_);
ReleaseGraphPathRefLocked(path);
}
void RenderWorkerPool::ReleaseGraphPathRefLocked(const QString &path)
{
if (path.isEmpty()) {
return;
}
auto it = graph_path_ref_count_.find(path);
if (it == graph_path_ref_count_.end()) {
return;
}
if (--(*it) <= 0) {
graph_path_ref_count_.erase(it);
if (!cached_graph_paths_.contains(path)) {
CleanupGraphFile(path);
}
}
}
void RenderWorkerPool::SetGraphPathCached(const QString &path, bool cached)
{
QMutexLocker locker(&mutex_);
SetGraphPathCachedLocked(path, cached);
}
void RenderWorkerPool::SetGraphPathCachedLocked(const QString &path,
bool cached)
{
if (path.isEmpty()) {
return;
}
if (cached) {
cached_graph_paths_.insert(path);
} else {
cached_graph_paths_.remove(path);
if (!graph_path_ref_count_.contains(path)) {
CleanupGraphFile(path);
}
}
}
} // namespace olive
+15 -7
View File
@@ -23,6 +23,7 @@
#include <QHash>
#include <QMutex>
#include <QSet>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
@@ -113,8 +114,7 @@ private:
};
bool PrepareJob(RenderTicketPtr ticket,
const RenderManager::RenderVideoParams &params,
Job *job);
const RenderManager::RenderVideoParams &params, Job *job);
bool WriteGraphSnapshot(Project *project, QString *path);
bool IsSupported(const RenderManager::RenderVideoParams &params) const;
@@ -123,24 +123,30 @@ private:
void ProcessJob(const Job &job, int worker_index,
std::vector<std::unique_ptr<PooledWorker>> *local_pool);
JobResult ProcessJobAttempt(const Job &job, int worker_index,
int attempt_index,
PooledWorker *worker);
int attempt_index, PooledWorker *worker);
void FinishWithFrame(RenderTicketPtr ticket, const ipc::FrameSlotPool &pool,
uint32_t slot);
void CleanupGraphFile(const QString &path);
void AddGraphPathRef(const QString &path);
void AddGraphPathRefLocked(const QString &path);
void ReleaseGraphPathRef(const QString &path);
void ReleaseGraphPathRefLocked(const QString &path);
void SetGraphPathCached(const QString &path, bool cached);
void SetGraphPathCachedLocked(const QString &path, bool cached);
void CancelActiveProcess(qint64 process_id);
void SetActiveWorker(int worker_index, RenderTicketPtr ticket,
QProcess *worker, qint64 ticket_id);
void ClearActiveWorker(int worker_index, qint64 process_id);
int WorkerCount() const;
std::unique_ptr<PooledWorker> AcquireWorker(
std::vector<std::unique_ptr<PooledWorker>> *local_pool,
std::unique_ptr<PooledWorker>
AcquireWorker(std::vector<std::unique_ptr<PooledWorker>> *local_pool,
const QString &graph_path);
void ReturnWorker(std::vector<std::unique_ptr<PooledWorker>> *local_pool,
std::unique_ptr<PooledWorker> worker, bool keep_alive);
void ShutdownWorker(PooledWorker *worker);
void ShutdownLocalPool(std::vector<std::unique_ptr<PooledWorker>> *local_pool);
void
ShutdownLocalPool(std::vector<std::unique_ptr<PooledWorker>> *local_pool);
void ClearGraphCache();
DecoderCache *decoder_cache_;
@@ -151,6 +157,8 @@ private:
bool stopping_ = false;
QVector<ActiveJob> active_jobs_;
QHash<QUuid, CachedGraph> graph_cache_;
QHash<QString, int> graph_path_ref_count_;
QSet<QString> cached_graph_paths_;
static constexpr uint32_t kOutputSlots = 2;
static constexpr int kMaxAttempts = 2;
+3 -1
View File
@@ -164,9 +164,11 @@ public:
{
frame_ = ptr;
}
AVFramePtr frame(){
AVFramePtr frame()
{
return frame_;
}
private:
bool IsRendererAlive() const
{
+71 -53
View File
@@ -11,7 +11,8 @@
#include "render/videoparams.h"
#include "render/vulkan/vulkanrenderer.h"
namespace {
namespace
{
class BackendVulkanRenderer : public olive::VulkanRenderer {
public:
@@ -38,38 +39,42 @@ const QVariant &VariantRef(const void *variant)
} // namespace
// Creates the Vulkan backend object and returns it as an opaque C handle.
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle
oak_renderer_create(void *parent)
{
return new BackendVulkanRenderer(static_cast<QObject *>(parent));
}
// Destroys the opaque backend object created by oak_renderer_create().
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy(OakRenderBackendHandle handle)
{
delete Renderer(handle);
}
// Reports Vulkan backend capabilities and runtime availability status.
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
OAK_RENDER_BACKEND_EXPORT bool
oak_renderer_get_info(OakRenderBackendHandle handle,
OakRenderBackendInfo *out_info)
{
if (!handle || !out_info) {
return false;
}
out_info->abi_version = 1;
out_info->kind = OAK_RENDER_BACKEND_VULKAN;
out_info->capabilities = OAK_RENDER_BACKEND_CAP_TEXTURES |
OAK_RENDER_BACKEND_CAP_SHADERS | OAK_RENDER_BACKEND_CAP_BLIT |
OAK_RENDER_BACKEND_CAP_READBACK;
out_info->capabilities =
OAK_RENDER_BACKEND_CAP_TEXTURES | OAK_RENDER_BACKEND_CAP_SHADERS |
OAK_RENDER_BACKEND_CAP_BLIT | OAK_RENDER_BACKEND_CAP_READBACK;
out_info->name = "vulkan";
out_info->status = Renderer(handle)->IsAvailable() ? "available" : "unavailable";
out_info->status = Renderer(handle)->IsAvailable() ? "available" :
"unavailable";
return true;
}
// Probes runtime availability by trying Init() once; this lets missing ICDs or
// unusable drivers fall back before normal rendering starts.
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT bool
oak_renderer_is_available(OakRenderBackendHandle handle)
{
auto *r = Renderer(handle);
if (!r || r->IsAvailable()) {
@@ -89,38 +94,38 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
}
// Vulkan does not use a QOpenGLContext; the argument is accepted for ABI parity.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
OakRenderBackendHandle handle, void *context)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_init_with_context(OakRenderBackendHandle handle, void *context)
{
Q_UNUSED(context)
Renderer(handle)->Init();
}
// Creates reusable Vulkan resources after device initialization.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_post_init(OakRenderBackendHandle handle)
{
Renderer(handle)->PostInit();
}
// Reserved for API symmetry; Vulkan cleanup is handled by destroy_internal.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_post_destroy(OakRenderBackendHandle handle)
{
Renderer(handle)->PostDestroy();
}
// Releases all Vulkan resources owned by the renderer.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_internal(OakRenderBackendHandle handle)
{
Renderer(handle)->DestroyInternal();
}
// Clears a Vulkan texture destination.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
double a)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_clear_destination(OakRenderBackendHandle handle, void *texture,
double r, double g, double b, double a)
{
Renderer(handle)->ClearDestination(static_cast<olive::Texture *>(texture),
r, g, b, a);
@@ -131,51 +136,58 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
OakRenderBackendHandle handle, int width, int height, int depth, int format,
int channel_count, const void *data, int linesize, void *out_variant)
{
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeTexture(
width, height, depth, static_cast<olive::PixelFormat::Format>(format),
channel_count, data, linesize);
*static_cast<QVariant *>(out_variant) =
Renderer(handle)->CreateNativeTexture(
width, height, depth,
static_cast<olive::PixelFormat::Format>(format), channel_count,
data, linesize);
}
// Destroys a Vulkan texture represented by a QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
OakRenderBackendHandle handle, const void *variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_native_texture(OakRenderBackendHandle handle,
const void *variant)
{
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
}
// Compiles a Vulkan shader and returns its QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_create_native_shader(OakRenderBackendHandle handle,
const void *shader_code, void *out_variant)
{
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeShader(
*static_cast<QVariant *>(out_variant) =
Renderer(handle)->CreateNativeShader(
*static_cast<const olive::ShaderCode *>(shader_code));
}
// Destroys a Vulkan shader represented by a QVariant handle.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
OakRenderBackendHandle handle, const void *variant)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_destroy_native_shader(OakRenderBackendHandle handle,
const void *variant)
{
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
}
// Uploads CPU pixel data into a Vulkan texture.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_upload_to_texture(OakRenderBackendHandle handle,
const void *variant, const void *video_params,
const void *data, int linesize)
{
Renderer(handle)->UploadToTexture(
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
data, linesize);
VariantRef(variant),
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
}
// Downloads a Vulkan texture to CPU memory.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
void *data, int linesize)
OakRenderBackendHandle handle, const void *variant,
const void *video_params, void *data, int linesize)
{
Renderer(handle)->DownloadFromTexture(
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
data, linesize);
VariantRef(variant),
*static_cast<const olive::VideoParams *>(video_params), data, linesize);
}
// Waits for all queued Vulkan work to finish.
@@ -185,18 +197,23 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
}
// Reads one pixel from a Vulkan texture.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
OakRenderBackendHandle handle, void *texture, const void *point,
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_get_pixel_from_texture(OakRenderBackendHandle handle,
void *texture, const void *point,
void *out_color)
{
*static_cast<olive::Color *>(out_color) = Renderer(handle)->GetPixelFromTexture(
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
*static_cast<olive::Color *>(out_color) =
Renderer(handle)->GetPixelFromTexture(
static_cast<olive::Texture *>(texture),
*static_cast<const QPointF *>(point));
}
// Executes a shader blit through the wrapped Vulkan renderer.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
OakRenderBackendHandle handle, const void *shader, void *job,
void *destination, const void *destination_params, bool clear_destination)
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(OakRenderBackendHandle handle,
const void *shader, void *job,
void *destination,
const void *destination_params,
bool clear_destination)
{
Renderer(handle)->Blit(
VariantRef(shader), *static_cast<olive::AcceleratedJob *>(job),
@@ -206,16 +223,17 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
}
// Vulkan has no OpenGL context; return null so callers avoid GL-only paths.
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void *
oak_renderer_opengl_context(OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
return nullptr;
}
// OFX OpenGL output attachment is unsupported in Vulkan and intentionally no-op.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
OakRenderBackendHandle handle, const void *texture_id)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_attach_output_texture(OakRenderBackendHandle handle,
const void *texture_id)
{
Q_UNUSED(handle)
Q_UNUSED(texture_id)
@@ -223,8 +241,8 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
}
// OFX OpenGL output detachment is unsupported in Vulkan and intentionally no-op.
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
OakRenderBackendHandle handle)
OAK_RENDER_BACKEND_EXPORT void
oak_renderer_detach_output_texture(OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
// Vulkan does not support OFX OpenGL render output attachment.
File diff suppressed because it is too large Load Diff
+12 -8
View File
@@ -90,7 +90,8 @@ public:
protected:
// Runs one or more fullscreen shader passes into the destination texture.
virtual void Blit(QVariant shader, olive::AcceleratedJob &job,
olive::Texture *destination, VideoParams destination_params,
olive::Texture *destination,
VideoParams destination_params,
bool clear_destination) override;
// Creates a Vulkan image/view/memory bundle and optionally uploads initial
// pixel data.
@@ -172,8 +173,8 @@ private:
float GetFormatMaxAlpha(PixelFormat format) const;
// Repackages tightly packed pixels when the requested CPU channel count
// differs from the selected GPU format channel count.
void CopyPixelsWithChannelConversion(const void *src, void *dst,
int width, int height, int depth,
void CopyPixelsWithChannelConversion(const void *src, void *dst, int width,
int height, int depth,
int src_channels, int dst_channels,
PixelFormat format) const;
// Rounds a size up to the requested alignment.
@@ -187,11 +188,13 @@ private:
bool CompileGlslToSpv(const QString &glsl, VkShaderStageFlagBits stage,
QByteArray *out_spv);
// Rewrites an Oak GLSL shader into Vulkan-compatible GLSL.
QString ConvertGlslToVulkan(const QString &glsl, VkShaderStageFlagBits stage);
QString ConvertGlslToVulkan(const QString &glsl,
VkShaderStageFlagBits stage);
// Ensures a shader declares a Vulkan-compatible GLSL version.
QString EnsureGlslVersion450(const QString &glsl) const;
// Extracts uniforms and sampler names from GLSL declarations.
void ExtractUniforms(const QString &glsl, QVector<UniformInfo> *out_uniforms,
void ExtractUniforms(const QString &glsl,
QVector<UniformInfo> *out_uniforms,
QVector<QString> *out_samplers) const;
// Computes std140 offsets and total UBO size for extracted uniforms.
void ComputeUniformLayout(QVector<UniformInfo> *uniforms) const;
@@ -199,7 +202,8 @@ private:
QString BuildUboBlock(const QVector<UniformInfo> &uniforms) const;
// Rewrites standalone uniforms and samplers into explicit UBO/sampler
// bindings accepted by Vulkan GLSL.
QString RewriteShaderWithUbo(const QString &glsl,
QString
RewriteShaderWithUbo(const QString &glsl,
const QVector<UniformInfo> &all_uniforms,
const QHash<QString, int> &sampler_bindings) const;
// Returns std140 storage size for a supported GLSL type.
@@ -225,8 +229,8 @@ private:
void BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
const QVector<TextureBinding> &bindings,
const QByteArray &ubo_data,
const VideoParams &destination_params,
bool clear_destination, int iteration);
const VideoParams &destination_params, bool clear_destination,
int iteration);
VkInstance instance_ = VK_NULL_HANDLE;
VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE;
+140 -64
View File
@@ -24,6 +24,7 @@
#include <optional>
#include <QFile>
#include <QFileInfo>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QJsonObject>
@@ -51,6 +52,10 @@
#include "render/colorprocessor.h"
#include "render/colortransform.h"
#ifdef Q_OS_MACOS
void HideWorkerDockIcon();
#endif
namespace
{
@@ -105,7 +110,6 @@ public:
bool InitializeRuntime()
{
// Create a minimal Core instance so that code paths calling Core::instance()
// (e.g. ViewerOutput::data for timecode display) do not dereference null.
// The worker is short-lived; leaking this on exit is harmless.
@@ -136,7 +140,8 @@ public:
QJsonObject handshake = hs.ToJson();
QOpenGLContext *ctx = nullptr;
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
if (auto *dynamic_renderer = dynamic_cast<olive::DynamicRenderer *>(renderer_)) {
if (auto *dynamic_renderer =
dynamic_cast<olive::DynamicRenderer *>(renderer_)) {
ctx = dynamic_renderer->OpenGLContext();
} else
#endif
@@ -159,7 +164,8 @@ public:
if (type == QLatin1String(olive::ipc::msgtype::kHandshake)) {
olive::ipc::HandshakeMsg hs;
if (!olive::ipc::HandshakeMsg::FromJson(message, &hs)) {
return Write(ErrorMessage(QStringLiteral("invalid handshake message")));
return Write(
ErrorMessage(QStringLiteral("invalid handshake message")));
}
return AttachOutputPool(hs);
}
@@ -167,7 +173,8 @@ public:
if (type == QLatin1String(olive::ipc::msgtype::kLoadGraph)) {
olive::ipc::LoadGraphMsg load;
if (!olive::ipc::LoadGraphMsg::FromJson(message, &load)) {
return Write(ErrorMessage(QStringLiteral("invalid load_graph message")));
return Write(
ErrorMessage(QStringLiteral("invalid load_graph message")));
}
return LoadGraph(load.path);
}
@@ -175,7 +182,8 @@ public:
if (type == QLatin1String(olive::ipc::msgtype::kRenderFrame)) {
olive::ipc::RenderFrameMsg render;
if (!olive::ipc::RenderFrameMsg::FromJson(message, &render)) {
return Write(ErrorMessage(QStringLiteral("invalid render_frame message")));
return Write(ErrorMessage(
QStringLiteral("invalid render_frame message")));
}
return RenderFrame(render);
}
@@ -190,7 +198,8 @@ public:
return true;
}
return Write(ErrorMessage(QStringLiteral("unknown message type: %1").arg(type)));
return Write(
ErrorMessage(QStringLiteral("unknown message type: %1").arg(type)));
}
bool shutdown_requested() const
@@ -209,18 +218,23 @@ private:
bool AttachOutputPool(const olive::ipc::HandshakeMsg &hs)
{
if (hs.protocol_version != kProtocolVersion) {
return Write(ErrorMessage(QStringLiteral("unsupported protocol version %1")
return Write(
ErrorMessage(QStringLiteral("unsupported protocol version %1")
.arg(hs.protocol_version)));
}
if (hs.shm_key.isEmpty() || hs.output_slots <= 0 || hs.slot_data_bytes <= 0) {
return Write(ErrorMessage(QStringLiteral("handshake missing output shared-memory geometry")));
if (hs.shm_key.isEmpty() || hs.output_slots <= 0 ||
hs.slot_data_bytes <= 0) {
return Write(ErrorMessage(QStringLiteral(
"handshake missing output shared-memory geometry")));
}
const size_t bytes = olive::ipc::FrameSlotPool::BytesNeeded(
uint32_t(hs.output_slots), size_t(hs.slot_data_bytes));
if (!output_region_.Open(hs.shm_key, bytes, olive::ipc::SharedMemoryRegion::kAttach)) {
return Write(ErrorMessage(QStringLiteral("failed to attach shared memory: %1")
if (!output_region_.Open(hs.shm_key, bytes,
olive::ipc::SharedMemoryRegion::kAttach)) {
return Write(ErrorMessage(
QStringLiteral("failed to attach shared memory: %1")
.arg(output_region_.error())));
}
@@ -228,29 +242,34 @@ private:
if (!output_pool_->IsValid()) {
output_region_.Close();
output_pool_.reset();
return Write(ErrorMessage(QStringLiteral("shared memory does not contain a frame slot pool")));
return Write(ErrorMessage(QStringLiteral(
"shared memory does not contain a frame slot pool")));
}
input_pool_.reset();
input_region_.Close();
if (hs.input_slots > 0) {
if (hs.input_shm_key.isEmpty() || hs.input_slot_data_bytes <= 0) {
return Write(ErrorMessage(QStringLiteral("handshake missing input shared-memory geometry")));
return Write(ErrorMessage(QStringLiteral(
"handshake missing input shared-memory geometry")));
}
const size_t input_bytes = olive::ipc::FrameSlotPool::BytesNeeded(
uint32_t(hs.input_slots), size_t(hs.input_slot_data_bytes));
if (!input_region_.Open(hs.input_shm_key, input_bytes,
olive::ipc::SharedMemoryRegion::kAttach)) {
return Write(ErrorMessage(QStringLiteral("failed to attach input shared memory: %1")
return Write(ErrorMessage(
QStringLiteral("failed to attach input shared memory: %1")
.arg(input_region_.error())));
}
input_pool_ = olive::ipc::FrameSlotPool::Attach(input_region_.data());
input_pool_ =
olive::ipc::FrameSlotPool::Attach(input_region_.data());
if (!input_pool_->IsValid()) {
input_region_.Close();
input_pool_.reset();
return Write(ErrorMessage(QStringLiteral("input shared memory does not contain a frame slot pool")));
return Write(ErrorMessage(QStringLiteral(
"input shared memory does not contain a frame slot pool")));
}
}
@@ -259,15 +278,39 @@ private:
bool LoadGraph(const QString &path)
{
{
QFileInfo fi(path);
if (!fi.exists()) {
LogError(
QStringLiteral("LoadGraph: graph file does not exist: %1")
.arg(path));
return Write(ErrorMessage(
QStringLiteral("graph file does not exist: %1").arg(path)));
}
if (fi.size() == 0) {
LogError(QStringLiteral("LoadGraph: graph file is empty: %1")
.arg(path));
return Write(ErrorMessage(
QStringLiteral("graph file is empty: %1").arg(path)));
}
LogError(
QStringLiteral("LoadGraph: loading %1 (%2 bytes, readable=%3)")
.arg(path)
.arg(fi.size())
.arg(fi.isReadable()));
}
auto loaded = std::make_unique<olive::Project>();
// Do not call Initialize() here: project serializers expect a blank
// project (root_ == nullptr) and will set root themselves. Calling
// Initialize() first triggers Q_ASSERT(!root_) in Project::Load.
olive::ProjectSerializer::Result result =
olive::ProjectSerializer::Load(loaded.get(), path, olive::ProjectSerializer::kProject);
olive::ProjectSerializer::Load(loaded.get(), path,
olive::ProjectSerializer::kProject);
if (result != olive::ProjectSerializer::kSuccess) {
return Write(ErrorMessage(QStringLiteral("failed to load graph %1: %2")
return Write(
ErrorMessage(QStringLiteral("failed to load graph %1: %2")
.arg(path, result.GetDetails())));
}
@@ -276,12 +319,15 @@ private:
color_processor_cache_.clear();
const auto &data = result.GetLoadData();
for (auto it = data.node_ptrs.cbegin(); it != data.node_ptrs.cend(); ++it) {
for (auto it = data.node_ptrs.cbegin(); it != data.node_ptrs.cend();
++it) {
node_by_token_.insert(QString::number(it.key()), it.value());
}
for (auto it = data.node_uuids.cbegin(); it != data.node_uuids.cend(); ++it) {
for (auto it = data.node_uuids.cbegin(); it != data.node_uuids.cend();
++it) {
node_by_token_.insert(it.value().toString(), it.key());
node_by_token_.insert(it.value().toString(QUuid::WithoutBraces), it.key());
node_by_token_.insert(it.value().toString(QUuid::WithoutBraces),
it.key());
}
QJsonObject ack;
@@ -308,28 +354,35 @@ private:
bool RenderFrame(const olive::ipc::RenderFrameMsg &message)
{
if (!project_) {
return Write(ErrorMessage(QStringLiteral("render_frame received before load_graph"),
return Write(ErrorMessage(
QStringLiteral("render_frame received before load_graph"),
message.ticket_id));
}
if (!output_pool_ || !output_pool_->IsValid()) {
return Write(ErrorMessage(QStringLiteral("render_frame received before output shm handshake"),
return Write(ErrorMessage(
QStringLiteral(
"render_frame received before output shm handshake"),
message.ticket_id));
}
olive::Node *node = FindNode(message.node_uuid);
if (!node) {
return Write(ErrorMessage(QStringLiteral("render node not found: %1").arg(message.node_uuid),
return Write(
ErrorMessage(QStringLiteral("render node not found: %1")
.arg(message.node_uuid),
message.ticket_id));
}
QVector<int> input_slots;
const QVector<int> requested_input_slots =
message.input_slots.isEmpty() && message.input_slot >= 0
? QVector<int>{message.input_slot}
: message.input_slots;
message.input_slots.isEmpty() && message.input_slot >= 0 ?
QVector<int>{ message.input_slot } :
message.input_slots;
if (!requested_input_slots.isEmpty()) {
if (!input_pool_ || !input_pool_->IsValid()) {
return Write(ErrorMessage(QStringLiteral("render_frame referenced input slot without input pool"),
return Write(ErrorMessage(
QStringLiteral(
"render_frame referenced input slot without input pool"),
message.ticket_id));
}
@@ -339,7 +392,8 @@ private:
for (int slot : input_slots) {
input_pool_->Release(uint32_t(slot));
}
return Write(ErrorMessage(QStringLiteral("input slot index out of range"),
return Write(ErrorMessage(
QStringLiteral("input slot index out of range"),
message.ticket_id));
}
@@ -348,7 +402,8 @@ private:
for (int slot : input_slots) {
input_pool_->Release(uint32_t(slot));
}
return Write(ErrorMessage(QStringLiteral("input slot was not ready"),
return Write(
ErrorMessage(QStringLiteral("input slot was not ready"),
message.ticket_id));
}
if (int(consumed_slot) != requested_slot) {
@@ -356,7 +411,8 @@ private:
for (int slot : input_slots) {
input_pool_->Release(uint32_t(slot));
}
return Write(ErrorMessage(QStringLiteral("input slot order mismatch"),
return Write(ErrorMessage(
QStringLiteral("input slot order mismatch"),
message.ticket_id));
}
input_slots.append(int(consumed_slot));
@@ -368,37 +424,37 @@ private:
}
}
olive::VideoParams vparams(message.width > 0 ? message.width : kDefaultWidth,
olive::VideoParams vparams(
message.width > 0 ? message.width : kDefaultWidth,
message.height > 0 ? message.height : kDefaultHeight,
olive::rational(1, kDefaultFrameRate),
message.format >= 0
? olive::PixelFormat::Format(message.format)
: olive::PixelFormat::F32,
message.channel_count > 0
? message.channel_count
: olive::VideoParams::kRGBAChannelCount);
message.format >= 0 ? olive::PixelFormat::Format(message.format) :
olive::PixelFormat::F32,
message.channel_count > 0 ? message.channel_count :
olive::VideoParams::kRGBAChannelCount);
olive::RenderTicketPtr ticket = std::make_shared<olive::RenderTicket>();
ticket->setProperty("node", olive::QtUtils::PtrToValue(node));
ticket->setProperty("time", QVariant::fromValue(
olive::rational(int(message.time_num), int(message.time_den))));
ticket->setProperty("time",
QVariant::fromValue(olive::rational(
int(message.time_num), int(message.time_den))));
ticket->setProperty("size", QSize(message.width, message.height));
ticket->setProperty("matrix", QMatrix4x4());
ticket->setProperty("format",
message.format >= 0
? olive::PixelFormat::Format(message.format)
: olive::PixelFormat::INVALID);
message.format >= 0 ?
olive::PixelFormat::Format(message.format) :
olive::PixelFormat::INVALID);
ticket->setProperty("usecache", false);
ticket->setProperty("channelcount", message.channel_count);
ticket->setProperty("mode", olive::RenderMode::Mode(message.mode));
ticket->setProperty("type", olive::RenderManager::kTypeVideo);
ticket->setProperty("colormanager", olive::QtUtils::PtrToValue(project_->color_manager()));
ticket->setProperty("colormanager", olive::QtUtils::PtrToValue(
project_->color_manager()));
{
olive::ColorProcessorPtr color_output;
if (message.has_color_transform) {
QString cache_key =
QStringLiteral("%1|%2|%3|%4")
QString cache_key = QStringLiteral("%1|%2|%3|%4")
.arg(message.color_is_display ? 1 : 0)
.arg(message.color_output,
message.color_view,
@@ -428,16 +484,20 @@ private:
QVariant::fromValue(color_output));
}
ticket->setProperty("vparam", QVariant::fromValue(vparams));
ticket->setProperty("aparam", QVariant::fromValue(olive::AudioParams()));
ticket->setProperty("aparam",
QVariant::fromValue(olive::AudioParams()));
ticket->setProperty("return", olive::RenderManager::kFrame);
ticket->setProperty("cache", QString());
ticket->setProperty("cachetimebase", QVariant::fromValue(olive::rational(1)));
ticket->setProperty("cachetimebase",
QVariant::fromValue(olive::rational(1)));
ticket->setProperty("cacheid", QVariant::fromValue(QUuid()));
ticket->setProperty("multicam", olive::QtUtils::PtrToValue(static_cast<void *>(nullptr)));
ticket->setProperty("ipc_input_pool",
olive::QtUtils::PtrToValue(
input_pool_ ? static_cast<void *>(&*input_pool_)
: static_cast<void *>(nullptr)));
ticket->setProperty("multicam", olive::QtUtils::PtrToValue(
static_cast<void *>(nullptr)));
ticket->setProperty(
"ipc_input_pool",
olive::QtUtils::PtrToValue(input_pool_ ?
static_cast<void *>(&*input_pool_) :
static_cast<void *>(nullptr)));
QVariantList input_slot_values;
for (int slot : input_slots) {
input_slot_values.append(slot);
@@ -448,34 +508,42 @@ private:
input_slots.isEmpty() ? -1 : input_slots.front());
ticket->Start();
olive::RenderProcessor::Process(ticket, renderer_, nullptr, &shader_cache_);
olive::RenderProcessor::Process(ticket, renderer_, nullptr,
&shader_cache_);
for (int slot : input_slots) {
input_pool_->Release(uint32_t(slot));
}
if (!ticket->HasResult()) {
return Write(ErrorMessage(QStringLiteral("render produced no frame"), message.ticket_id));
return Write(ErrorMessage(
QStringLiteral("render produced no frame"), message.ticket_id));
}
olive::FramePtr frame = ticket->Get().value<olive::FramePtr>();
if (!frame || !frame->is_allocated()) {
return Write(ErrorMessage(QStringLiteral("render result was empty"), message.ticket_id));
return Write(ErrorMessage(QStringLiteral("render result was empty"),
message.ticket_id));
}
uint32_t slot = 0;
if (!output_pool_->Acquire(&slot)) {
return Write(ErrorMessage(QStringLiteral("no free output frame slot"), message.ticket_id));
return Write(
ErrorMessage(QStringLiteral("no free output frame slot"),
message.ticket_id));
}
const int data_size = frame->linesize_bytes() * frame->height();
if (data_size > int(output_pool_->slot_data_bytes())) {
output_pool_->Release(slot);
LogError(QString("Output frame size") + QString::number(data_size));
LogError(QString("Slot size")+QString::number(output_pool_->slot_data_bytes()));
return Write(ErrorMessage(QStringLiteral("rendered frame does not fit output slot "),
LogError(QString("Slot size") +
QString::number(output_pool_->slot_data_bytes()));
return Write(ErrorMessage(
QStringLiteral("rendered frame does not fit output slot "),
message.ticket_id));
}
std::memcpy(output_pool_->SlotData(slot), frame->const_data(), size_t(data_size));
std::memcpy(output_pool_->SlotData(slot), frame->const_data(),
size_t(data_size));
olive::ipc::FrameSlotMeta *meta = output_pool_->Meta(slot);
meta->id = message.ticket_id;
meta->time_num = frame->timestamp().numerator();
@@ -489,7 +557,8 @@ private:
if (!output_pool_->Publish(slot)) {
output_pool_->Release(slot);
return Write(ErrorMessage(QStringLiteral("failed to publish output frame slot"),
return Write(ErrorMessage(
QStringLiteral("failed to publish output frame slot"),
message.ticket_id));
}
olive::ipc::FrameReadyMsg ready;
@@ -520,8 +589,13 @@ int main(int argc, char *argv[])
InstallSurfaceFormat();
QGuiApplication app(argc, argv);
#ifdef Q_OS_MACOS
HideWorkerDockIcon();
#endif
QCoreApplication::setOrganizationName(QStringLiteral("oakvideoeditor.org"));
QCoreApplication::setApplicationName(QStringLiteral("olive-render-worker"));
QCoreApplication::setApplicationName(QStringLiteral("oak-render-worker"));
QString backend = QStringLiteral("opengl");
const QStringList args = app.arguments();
@@ -574,7 +648,8 @@ int main(int argc, char *argv[])
QOpenGLContext *ctx = nullptr;
if (backend == QStringLiteral("opengl")) {
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
if (auto *loaded_renderer = dynamic_cast<olive::DynamicRenderer *>(renderer)) {
if (auto *loaded_renderer =
dynamic_cast<olive::DynamicRenderer *>(renderer)) {
ctx = loaded_renderer->OpenGLContext();
} else
#endif
@@ -613,7 +688,8 @@ int main(int argc, char *argv[])
if (!olive::ipc::ReadMessage(&buffer, &message, &ok)) {
if (!ok) {
olive::ipc::WriteMessage(
&out, ErrorMessage(QStringLiteral("malformed control message")));
&out, ErrorMessage(QStringLiteral(
"malformed control message")));
out.flush();
continue;
}

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