Merge remote-tracking branch 'origin/main'
This commit is contained in:
+214
-28
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Oak Video Editor[](https://github.com/olive-editor/olive/actions?query=branch%3Amaster)
|
||||
# Oak Video Editor[](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.
|
||||

|
||||
|
||||
@@ -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
@@ -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=2–3 秒。
|
||||
- 并发限制(例如 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 纹理缓存。
|
||||
@@ -1,92 +0,0 @@
|
||||
# TODO
|
||||
|
||||
## Goal
|
||||
- Add an LRU prerender cache (2–3 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 = 2–3s).
|
||||
- Limit worker count (e.g., 2–3 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 (2–3 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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ×tamp)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 ¶ms,
|
||||
CancelAtom *cancelled) override;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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 ¶ms)
|
||||
ProxyManager::Proxy
|
||||
ProxyManager::GetOrStartProxy(const QString &cache_path,
|
||||
const QString &source_filename, int stream_index,
|
||||
const ProxyParams ¶ms)
|
||||
{
|
||||
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));
|
||||
|
||||
@@ -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 ¶ms);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()) {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
|
||||
+8
-8
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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 *> ¶ms)
|
||||
QHash<QString, QVariant> BuildDefaultValues(
|
||||
const std::map<std::string, OFX::Host::Param::Instance *> ¶ms)
|
||||
{
|
||||
QHash<QString, QVariant> defaults;
|
||||
for (const auto ¶m : 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 *) {}));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¶ms,
|
||||
const rational &length)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,7 @@ signals:
|
||||
void CloseRequested();
|
||||
void shown(Qt::FocusReason reason);
|
||||
void hidden();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief paintEvent
|
||||
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
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;
|
||||
|
||||
@@ -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 ¶ms)
|
||||
OFX::Host::ImageEffect::ClipDescriptor &desc,
|
||||
VideoParams ¶ms)
|
||||
: 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 ¶ms);
|
||||
#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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear)
|
||||
const VideoParams ¶ms, 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 ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms,
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -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 ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
const VideoParams ¶ms, 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 ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
void AllocateFromParams(const VideoParams ¶ms, const OfxRectI &bounds,
|
||||
const OfxRectI &rod, bool clear = true);
|
||||
void EnsureAllocatedFromParams(const VideoParams ¶ms,
|
||||
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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¶ms, const void *data,
|
||||
int linesize)
|
||||
const VideoParams ¶ms,
|
||||
const void *data, int linesize)
|
||||
{
|
||||
upload_to_texture_(handle_, &handle, ¶ms, 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;
|
||||
|
||||
@@ -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 +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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
#include "pluginjob.h"
|
||||
|
||||
namespace olive {
|
||||
namespace plugin {
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
} // plugin
|
||||
} // olive
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 ¶ms);
|
||||
@@ -46,9 +48,15 @@ int BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms);
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ¶ms)
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace plugin {
|
||||
namespace plugin
|
||||
{
|
||||
class PluginRenderer;
|
||||
}
|
||||
|
||||
|
||||
+209
-131
@@ -62,8 +62,8 @@ struct FootageInput {
|
||||
|
||||
class FootageInputCollector : public NodeTraverser {
|
||||
public:
|
||||
QVector<FootageInput> Collect(const RenderManager::RenderVideoParams ¶ms,
|
||||
CancelAtom *cancel)
|
||||
QVector<FootageInput>
|
||||
Collect(const RenderManager::RenderVideoParams ¶ms, 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 ¶ms,
|
||||
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 ¶ms)
|
||||
bool RenderWorkerPool::SubmitFrame(
|
||||
RenderTicketPtr ticket, const RenderManager::RenderVideoParams ¶ms)
|
||||
{
|
||||
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 ¶ms) const
|
||||
bool RenderWorkerPool::IsSupported(
|
||||
const RenderManager::RenderVideoParams ¶ms) 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
|
||||
|
||||
@@ -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 ¶ms,
|
||||
Job *job);
|
||||
const RenderManager::RenderVideoParams ¶ms, Job *job);
|
||||
bool WriteGraphSnapshot(Project *project, QString *path);
|
||||
bool IsSupported(const RenderManager::RenderVideoParams ¶ms) 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;
|
||||
|
||||
@@ -164,9 +164,11 @@ public:
|
||||
{
|
||||
frame_ = ptr;
|
||||
}
|
||||
AVFramePtr frame(){
|
||||
AVFramePtr frame()
|
||||
{
|
||||
return frame_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsRendererAlive() const
|
||||
{
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user