cli/worker/tests: facade-only linkage and updated test suites
oak-cli and oak-render-worker use the C ABI exclusively (0 olive:: symbols). Google Test suites updated for the migrated APIs, new engine facade test coverage, CLI transcode verification.
This commit is contained in:
@@ -46,7 +46,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "codec/encoder.h"
|
#include "codec/encoder.h"
|
||||||
|
#include "oakengine/encoding.h"
|
||||||
#include "dialog/export/codec/h264section.h"
|
#include "dialog/export/codec/h264section.h"
|
||||||
#include "dialog/export/codec/imagesection.h"
|
#include "dialog/export/codec/imagesection.h"
|
||||||
#include "dialog/export/exportadvancedvideodialog.h"
|
#include "dialog/export/exportadvancedvideodialog.h"
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
#include "dialog/export/exportvideotab.h"
|
#include "dialog/export/exportvideotab.h"
|
||||||
#include "node/color/colormanager/colormanager.h"
|
#include "node/color/colormanager/colormanager.h"
|
||||||
#include "node/project.h"
|
#include "node/project.h"
|
||||||
|
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -195,7 +197,7 @@ TEST(DialogExportVideoTab, SetFormatPopulatesCodecs)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ExportVideoTab tab(project.color_manager());
|
olive::ExportVideoTab tab(oak_color_manager(project.color_manager()));
|
||||||
|
|
||||||
const QList<olive::ExportCodec::Codec> codecs =
|
const QList<olive::ExportCodec::Codec> codecs =
|
||||||
olive::ExportFormat::get_video_codecs(olive::ExportFormat::k_format_matroska);
|
olive::ExportFormat::get_video_codecs(olive::ExportFormat::k_format_matroska);
|
||||||
@@ -214,7 +216,7 @@ TEST(DialogExportVideoTab, CodecSelectsMatchingSection)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ExportVideoTab tab(project.color_manager());
|
olive::ExportVideoTab tab(oak_color_manager(project.color_manager()));
|
||||||
tab.set_format(olive::ExportFormat::k_format_matroska);
|
tab.set_format(olive::ExportFormat::k_format_matroska);
|
||||||
|
|
||||||
// First Matroska codec is H.264, which has a dedicated section
|
// First Matroska codec is H.264, which has a dedicated section
|
||||||
@@ -233,7 +235,7 @@ TEST(DialogExportVideoTab, ImageSequenceCheckboxRoundTrips)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ExportVideoTab tab(project.color_manager());
|
olive::ExportVideoTab tab(oak_color_manager(project.color_manager()));
|
||||||
tab.set_format(olive::ExportFormat::k_format_png);
|
tab.set_format(olive::ExportFormat::k_format_png);
|
||||||
tab.video_codec_changed();
|
tab.video_codec_changed();
|
||||||
|
|
||||||
@@ -249,7 +251,7 @@ TEST(DialogExportVideoTab, MaintainAspectTogglesScalingMethod)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ExportVideoTab tab(project.color_manager());
|
olive::ExportVideoTab tab(oak_color_manager(project.color_manager()));
|
||||||
|
|
||||||
tab.maintain_aspect_checkbox()->setChecked(true);
|
tab.maintain_aspect_checkbox()->setChecked(true);
|
||||||
EXPECT_FALSE(tab.scaling_method_combobox()->isEnabled());
|
EXPECT_FALSE(tab.scaling_method_combobox()->isEnabled());
|
||||||
@@ -313,7 +315,7 @@ TEST(DialogExportSavePreset, AcceptWritesPresetFile)
|
|||||||
{
|
{
|
||||||
StandardPathsTestModeGuard test_mode;
|
StandardPathsTestModeGuard test_mode;
|
||||||
|
|
||||||
olive::EncodingParams params;
|
OakEngineEncodingParams *params = oakengine_encoding_params_create();
|
||||||
|
|
||||||
olive::ExportSavePresetDialog dialog(params);
|
olive::ExportSavePresetDialog dialog(params);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "dialog/actionsearch/actionsearch.h"
|
#include "dialog/actionsearch/actionsearch.h"
|
||||||
#include "dialog/autorecovery/autorecoverydialog.h"
|
#include "dialog/autorecovery/autorecoverydialog.h"
|
||||||
#include "dialog/color/colordialog.h"
|
#include "dialog/color/colordialog.h"
|
||||||
|
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||||
#include "dialog/configbase/configdialogbase.h"
|
#include "dialog/configbase/configdialogbase.h"
|
||||||
#include "dialog/diskcache/diskcachedialog.h"
|
#include "dialog/diskcache/diskcachedialog.h"
|
||||||
#include "dialog/preferences/keysequenceeditor.h"
|
#include "dialog/preferences/keysequenceeditor.h"
|
||||||
@@ -43,7 +44,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
@@ -75,7 +76,7 @@ public:
|
|||||||
return validate_result;
|
return validate_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void accept(olive::MultiUndoCommand *) override
|
virtual void accept(void *) override
|
||||||
{
|
{
|
||||||
++accept_count;
|
++accept_count;
|
||||||
}
|
}
|
||||||
@@ -502,10 +503,10 @@ TEST(DialogTask, WrapsAndOwnsTask)
|
|||||||
auto *task = new DummyTask();
|
auto *task = new DummyTask();
|
||||||
QPointer<olive::Task> task_guard(task);
|
QPointer<olive::Task> task_guard(task);
|
||||||
|
|
||||||
auto *dialog = new olive::TaskDialog(task, QStringLiteral("Title"));
|
auto *dialog = new olive::TaskDialog(
|
||||||
|
reinterpret_cast<OakEngineTask*>(task), QStringLiteral("Title"));
|
||||||
|
|
||||||
EXPECT_EQ(dialog->get_task(), task);
|
EXPECT_EQ(dialog->get_task(), reinterpret_cast<OakEngineTask*>(task));
|
||||||
EXPECT_EQ(task->parent(), dialog);
|
|
||||||
|
|
||||||
// The dialog takes ownership of the task
|
// The dialog takes ownership of the task
|
||||||
delete dialog;
|
delete dialog;
|
||||||
@@ -520,7 +521,7 @@ TEST(DialogColor, SelectedColorRoundTrips)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ColorDialog dialog(project.color_manager(),
|
olive::ColorDialog dialog(oak_color_manager(project.color_manager()),
|
||||||
olive::Color(1.0f, 0.0f, 0.0f, 1.0f));
|
olive::Color(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
olive::ManagedColor selected = dialog.get_selected_color();
|
olive::ManagedColor selected = dialog.get_selected_color();
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ protected:
|
|||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: Core is process-wide (matches footage_test)
|
// Leaked intentionally: Core is process-wide (matches footage_test)
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footage::Value() resolves Project::cache_path(), which goes through
|
// Footage::Value() resolves Project::cache_path(), which goes through
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ protected:
|
|||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: Core is process-wide and DiskManager
|
// Leaked intentionally: Core is process-wide and DiskManager
|
||||||
// touches it (matches render_diskcache_test).
|
// touches it (matches render_diskcache_test).
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footage::Value() resolves Project::cache_path(), which goes through
|
// Footage::Value() resolves Project::cache_path(), which goes through
|
||||||
@@ -439,7 +439,10 @@ TEST_F(FootageTest, DataRolesForInvalidFootage)
|
|||||||
|
|
||||||
EXPECT_EQ(footage.data(olive::Node::tooltip).toString(),
|
EXPECT_EQ(footage.data(olive::Node::tooltip).toString(),
|
||||||
QStringLiteral("Invalid"));
|
QStringLiteral("Invalid"));
|
||||||
EXPECT_TRUE(footage.data(olive::Node::icon).canConvert<QIcon>());
|
// B1: engine icon sites return icon name strings (mapped to QIcon in the
|
||||||
|
// app layer via icon::from_name); invalid footage gets "error".
|
||||||
|
EXPECT_EQ(footage.data(olive::Node::icon).toString(),
|
||||||
|
QStringLiteral("error"));
|
||||||
|
|
||||||
// With no existing file behind the footage, the time roles fall through
|
// With no existing file behind the footage, the time roles fall through
|
||||||
// to the base class and stay invalid
|
// to the base class and stay invalid
|
||||||
@@ -477,14 +480,8 @@ TEST_F(FootageTest, TooltipDescribesEnabledStreams)
|
|||||||
|
|
||||||
TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
||||||
{
|
{
|
||||||
// The icon globals must be loaded for the returned icons to be
|
// B1: engine icon sites return icon name strings ("video", "audio",
|
||||||
// distinguishable (null icons all share the same cache key)
|
// "image", "subtitles", "error"); the QIcon mapping lives in the app layer.
|
||||||
olive::icon::load_all(QStringLiteral(":/style/olive-dark"));
|
|
||||||
ASSERT_FALSE(olive::icon::video.isNull());
|
|
||||||
ASSERT_FALSE(olive::icon::audio.isNull());
|
|
||||||
ASSERT_FALSE(olive::icon::image.isNull());
|
|
||||||
ASSERT_FALSE(olive::icon::subtitles.isNull());
|
|
||||||
ASSERT_FALSE(olive::icon::error.isNull());
|
|
||||||
|
|
||||||
// Footage::data(ICON) only inspects streams once the footage has been
|
// Footage::data(ICON) only inspects streams once the footage has been
|
||||||
// probed (total_stream_count_ is set by Reprobe), so each variant is
|
// probed (total_stream_count_ is set by Reprobe), so each variant is
|
||||||
@@ -507,8 +504,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
|
|
||||||
// Invalid footage gets the error icon
|
// Invalid footage gets the error icon
|
||||||
TestableFootage invalid;
|
TestableFootage invalid;
|
||||||
EXPECT_EQ(invalid.data(olive::Node::icon).value<QIcon>().cacheKey(),
|
EXPECT_EQ(invalid.data(olive::Node::icon).toString(),
|
||||||
olive::icon::error.cacheKey());
|
QStringLiteral("error"));
|
||||||
|
|
||||||
// Real video streams take priority over audio
|
// Real video streams take priority over audio
|
||||||
olive::FootageDescription video_audio(QStringLiteral("fakedecoder"));
|
olive::FootageDescription video_audio(QStringLiteral("fakedecoder"));
|
||||||
@@ -517,10 +514,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
video_audio.set_stream_count(2);
|
video_audio.set_stream_count(2);
|
||||||
TestableFootage *footage = probe(QStringLiteral("video-audio.mkv"), video_audio);
|
TestableFootage *footage = probe(QStringLiteral("video-audio.mkv"), video_audio);
|
||||||
ASSERT_NE(footage, nullptr);
|
ASSERT_NE(footage, nullptr);
|
||||||
const QIcon video_icon = footage->data(olive::Node::icon).value<QIcon>();
|
EXPECT_EQ(footage->data(olive::Node::icon).toString(),
|
||||||
EXPECT_EQ(video_icon.cacheKey(), olive::icon::video.cacheKey());
|
QStringLiteral("video"));
|
||||||
EXPECT_NE(video_icon.cacheKey(), olive::icon::audio.cacheKey());
|
|
||||||
EXPECT_NE(video_icon.cacheKey(), olive::icon::error.cacheKey());
|
|
||||||
|
|
||||||
// Audio still takes priority over a still image stream
|
// Audio still takes priority over a still image stream
|
||||||
olive::VideoParams still_stream = make_video_stream(0);
|
olive::VideoParams still_stream = make_video_stream(0);
|
||||||
@@ -532,10 +527,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
TestableFootage *still_and_audio =
|
TestableFootage *still_and_audio =
|
||||||
probe(QStringLiteral("still-audio.mkv"), still_audio);
|
probe(QStringLiteral("still-audio.mkv"), still_audio);
|
||||||
ASSERT_NE(still_and_audio, nullptr);
|
ASSERT_NE(still_and_audio, nullptr);
|
||||||
const QIcon still_audio_icon =
|
EXPECT_EQ(still_and_audio->data(olive::Node::icon).toString(),
|
||||||
still_and_audio->data(olive::Node::icon).value<QIcon>();
|
QStringLiteral("audio"));
|
||||||
EXPECT_EQ(still_audio_icon.cacheKey(), olive::icon::audio.cacheKey());
|
|
||||||
EXPECT_NE(still_audio_icon.cacheKey(), olive::icon::image.cacheKey());
|
|
||||||
|
|
||||||
// A still image without audio hits the image branch
|
// A still image without audio hits the image branch
|
||||||
olive::FootageDescription stills(QStringLiteral("fakedecoder"));
|
olive::FootageDescription stills(QStringLiteral("fakedecoder"));
|
||||||
@@ -543,8 +536,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
stills.set_stream_count(1);
|
stills.set_stream_count(1);
|
||||||
TestableFootage *still_only = probe(QStringLiteral("still.mkv"), stills);
|
TestableFootage *still_only = probe(QStringLiteral("still.mkv"), stills);
|
||||||
ASSERT_NE(still_only, nullptr);
|
ASSERT_NE(still_only, nullptr);
|
||||||
EXPECT_EQ(still_only->data(olive::Node::icon).value<QIcon>().cacheKey(),
|
EXPECT_EQ(still_only->data(olive::Node::icon).toString(),
|
||||||
olive::icon::image.cacheKey());
|
QStringLiteral("image"));
|
||||||
|
|
||||||
// Audio-only footage
|
// Audio-only footage
|
||||||
olive::FootageDescription audio(QStringLiteral("fakedecoder"));
|
olive::FootageDescription audio(QStringLiteral("fakedecoder"));
|
||||||
@@ -552,8 +545,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
audio.set_stream_count(1);
|
audio.set_stream_count(1);
|
||||||
TestableFootage *audio_only = probe(QStringLiteral("audio.mkv"), audio);
|
TestableFootage *audio_only = probe(QStringLiteral("audio.mkv"), audio);
|
||||||
ASSERT_NE(audio_only, nullptr);
|
ASSERT_NE(audio_only, nullptr);
|
||||||
EXPECT_EQ(audio_only->data(olive::Node::icon).value<QIcon>().cacheKey(),
|
EXPECT_EQ(audio_only->data(olive::Node::icon).toString(),
|
||||||
olive::icon::audio.cacheKey());
|
QStringLiteral("audio"));
|
||||||
|
|
||||||
// Subtitle-only footage
|
// Subtitle-only footage
|
||||||
olive::FootageDescription subs(QStringLiteral("fakedecoder"));
|
olive::FootageDescription subs(QStringLiteral("fakedecoder"));
|
||||||
@@ -561,8 +554,8 @@ TEST_F(FootageTest, IconReflectsPrioritizedStreamType)
|
|||||||
subs.set_stream_count(1);
|
subs.set_stream_count(1);
|
||||||
TestableFootage *subs_only = probe(QStringLiteral("subs.mkv"), subs);
|
TestableFootage *subs_only = probe(QStringLiteral("subs.mkv"), subs);
|
||||||
ASSERT_NE(subs_only, nullptr);
|
ASSERT_NE(subs_only, nullptr);
|
||||||
EXPECT_EQ(subs_only->data(olive::Node::icon).value<QIcon>().cacheKey(),
|
EXPECT_EQ(subs_only->data(olive::Node::icon).toString(),
|
||||||
olive::icon::subtitles.cacheKey());
|
QStringLiteral("subtitles"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FootageTest, ProxyChangesMarkProjectModifiedAndEmitSignal)
|
TEST_F(FootageTest, ProxyChangesMarkProjectModifiedAndEmitSignal)
|
||||||
|
|||||||
@@ -21,10 +21,12 @@
|
|||||||
#include "render/rendermanager.h"
|
#include "render/rendermanager.h"
|
||||||
#include "task/task.h"
|
#include "task/task.h"
|
||||||
#include "task/taskmanager.h"
|
#include "task/taskmanager.h"
|
||||||
|
#include "oakengine/task.h"
|
||||||
|
#include "engineeventbridge.h"
|
||||||
#include "widget/menu/menushared.h"
|
#include "widget/menu/menushared.h"
|
||||||
#include "window/mainwindow/mainstatusbar.h"
|
#include "window/mainwindow/mainstatusbar.h"
|
||||||
#include "window/mainwindow/mainwindow.h"
|
#include "window/mainwindow/mainwindow.h"
|
||||||
#include "node/project/serializer/mainwindowlayoutinfo.h"
|
#include "node/project/serializer/serializedlayoutinfo.h"
|
||||||
|
|
||||||
using namespace olive;
|
using namespace olive;
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ protected:
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(MainWindowLayoutInfo, AccessorsStoreAndRetrieve)
|
TEST(SerializedLayoutInfo, AccessorsStoreAndRetrieve)
|
||||||
{
|
{
|
||||||
Project project;
|
Project project;
|
||||||
project.initialize();
|
project.initialize();
|
||||||
@@ -58,48 +60,49 @@ TEST(MainWindowLayoutInfo, AccessorsStoreAndRetrieve)
|
|||||||
auto *viewer = new ViewerOutput();
|
auto *viewer = new ViewerOutput();
|
||||||
viewer->setParent(&project);
|
viewer->setParent(&project);
|
||||||
|
|
||||||
MainWindowLayoutInfo info;
|
SerializedLayoutInfo info;
|
||||||
EXPECT_TRUE(info.open_folders().empty());
|
EXPECT_TRUE(info.open_folders.empty());
|
||||||
EXPECT_TRUE(info.open_sequences().empty());
|
EXPECT_TRUE(info.open_sequences.empty());
|
||||||
EXPECT_TRUE(info.open_viewers().empty());
|
EXPECT_TRUE(info.open_viewers.empty());
|
||||||
EXPECT_TRUE(info.panel_data().empty());
|
EXPECT_TRUE(info.panel_data.empty());
|
||||||
EXPECT_TRUE(info.state().isEmpty());
|
EXPECT_TRUE(info.state.isEmpty());
|
||||||
|
|
||||||
info.add_folder(folder);
|
info.open_folders.push_back(folder);
|
||||||
info.add_sequence(sequence);
|
info.open_sequences.push_back(sequence);
|
||||||
info.add_viewer(viewer);
|
info.open_viewers.push_back(viewer);
|
||||||
|
|
||||||
ASSERT_EQ(info.open_folders().size(), 1);
|
ASSERT_EQ(info.open_folders.size(), 1);
|
||||||
EXPECT_EQ(info.open_folders().front(), folder);
|
EXPECT_EQ(info.open_folders.front(), folder);
|
||||||
ASSERT_EQ(info.open_sequences().size(), 1);
|
ASSERT_EQ(info.open_sequences.size(), 1);
|
||||||
EXPECT_EQ(info.open_sequences().front(), sequence);
|
EXPECT_EQ(info.open_sequences.front(), sequence);
|
||||||
ASSERT_EQ(info.open_viewers().size(), 1);
|
ASSERT_EQ(info.open_viewers.size(), 1);
|
||||||
EXPECT_EQ(info.open_viewers().front(), viewer);
|
EXPECT_EQ(info.open_viewers.front(), viewer);
|
||||||
|
|
||||||
PanelWidget::Info data;
|
PanelWidget::Info data;
|
||||||
data[QStringLiteral("key")] = QStringLiteral("value");
|
data[QStringLiteral("key")] = QStringLiteral("value");
|
||||||
info.set_panel_data(QStringLiteral("panel_a"), data);
|
info.panel_data[QStringLiteral("panel_a")] = data;
|
||||||
ASSERT_EQ(info.panel_data().size(), 1);
|
ASSERT_EQ(info.panel_data.size(), 1);
|
||||||
EXPECT_EQ(info.panel_data()
|
EXPECT_EQ(info.panel_data
|
||||||
.at(QStringLiteral("panel_a"))
|
.at(QStringLiteral("panel_a"))
|
||||||
.at(QStringLiteral("key")),
|
.at(QStringLiteral("key")),
|
||||||
QStringLiteral("value"));
|
QStringLiteral("value"));
|
||||||
|
|
||||||
// move_panel_data renames the entry
|
// renaming the entry moves the data
|
||||||
info.move_panel_data(QStringLiteral("panel_a"),
|
info.panel_data[QStringLiteral("panel_b")] =
|
||||||
QStringLiteral("panel_b"));
|
info.panel_data.at(QStringLiteral("panel_a"));
|
||||||
EXPECT_EQ(info.panel_data().count(QStringLiteral("panel_a")), 0);
|
info.panel_data.erase(QStringLiteral("panel_a"));
|
||||||
ASSERT_EQ(info.panel_data().count(QStringLiteral("panel_b")), 1);
|
EXPECT_EQ(info.panel_data.count(QStringLiteral("panel_a")), 0);
|
||||||
EXPECT_EQ(info.panel_data()
|
ASSERT_EQ(info.panel_data.count(QStringLiteral("panel_b")), 1);
|
||||||
|
EXPECT_EQ(info.panel_data
|
||||||
.at(QStringLiteral("panel_b"))
|
.at(QStringLiteral("panel_b"))
|
||||||
.at(QStringLiteral("key")),
|
.at(QStringLiteral("key")),
|
||||||
QStringLiteral("value"));
|
QStringLiteral("value"));
|
||||||
|
|
||||||
info.set_state(QByteArray("layout-state"));
|
info.state = QByteArray("layout-state");
|
||||||
EXPECT_EQ(info.state(), QByteArray("layout-state"));
|
EXPECT_EQ(info.state, QByteArray("layout-state"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MainWindowLayoutInfo, XmlRoundTripPreservesEverything)
|
TEST(SerializedLayoutInfo, XmlRoundTripPreservesEverything)
|
||||||
{
|
{
|
||||||
Project project;
|
Project project;
|
||||||
project.initialize();
|
project.initialize();
|
||||||
@@ -110,14 +113,14 @@ TEST(MainWindowLayoutInfo, XmlRoundTripPreservesEverything)
|
|||||||
auto *viewer = new ViewerOutput();
|
auto *viewer = new ViewerOutput();
|
||||||
viewer->setParent(&project);
|
viewer->setParent(&project);
|
||||||
|
|
||||||
MainWindowLayoutInfo info;
|
SerializedLayoutInfo info;
|
||||||
info.add_folder(folder);
|
info.open_folders.push_back(folder);
|
||||||
info.add_sequence(sequence);
|
info.open_sequences.push_back(sequence);
|
||||||
info.add_viewer(viewer);
|
info.open_viewers.push_back(viewer);
|
||||||
PanelWidget::Info data;
|
PanelWidget::Info data;
|
||||||
data[QStringLiteral("splitter")] = QStringLiteral("AAA=");
|
data[QStringLiteral("splitter")] = QStringLiteral("AAA=");
|
||||||
info.set_panel_data(QStringLiteral("TimelinePanel"), data);
|
info.panel_data[QStringLiteral("TimelinePanel")] = data;
|
||||||
info.set_state(QByteArray("binary\x01\x02state", 12));
|
info.state = QByteArray("binary\x01\x02state", 12);
|
||||||
|
|
||||||
QString xml;
|
QString xml;
|
||||||
QXmlStreamWriter writer(&xml);
|
QXmlStreamWriter writer(&xml);
|
||||||
@@ -136,32 +139,32 @@ TEST(MainWindowLayoutInfo, XmlRoundTripPreservesEverything)
|
|||||||
ASSERT_TRUE(reader.readNextStartElement());
|
ASSERT_TRUE(reader.readNextStartElement());
|
||||||
ASSERT_EQ(reader.name(), QStringLiteral("layout"));
|
ASSERT_EQ(reader.name(), QStringLiteral("layout"));
|
||||||
|
|
||||||
MainWindowLayoutInfo loaded = MainWindowLayoutInfo::from_xml(&reader, node_map);
|
SerializedLayoutInfo loaded = SerializedLayoutInfo::from_xml(&reader, node_map);
|
||||||
|
|
||||||
ASSERT_EQ(loaded.open_folders().size(), 1);
|
ASSERT_EQ(loaded.open_folders.size(), 1);
|
||||||
EXPECT_EQ(loaded.open_folders().front(), folder);
|
EXPECT_EQ(loaded.open_folders.front(), folder);
|
||||||
ASSERT_EQ(loaded.open_sequences().size(), 1);
|
ASSERT_EQ(loaded.open_sequences.size(), 1);
|
||||||
EXPECT_EQ(loaded.open_sequences().front(), sequence);
|
EXPECT_EQ(loaded.open_sequences.front(), sequence);
|
||||||
|
|
||||||
// Open viewers must survive the round trip too
|
// Open viewers must survive the round trip too
|
||||||
ASSERT_EQ(loaded.open_viewers().size(), 1);
|
ASSERT_EQ(loaded.open_viewers.size(), 1);
|
||||||
EXPECT_EQ(loaded.open_viewers().front(), viewer);
|
EXPECT_EQ(loaded.open_viewers.front(), viewer);
|
||||||
|
|
||||||
EXPECT_EQ(loaded.state(), info.state());
|
EXPECT_EQ(loaded.state, info.state);
|
||||||
|
|
||||||
ASSERT_EQ(loaded.panel_data().count(QStringLiteral("TimelinePanel")), 1);
|
ASSERT_EQ(loaded.panel_data.count(QStringLiteral("TimelinePanel")), 1);
|
||||||
EXPECT_EQ(loaded.panel_data()
|
EXPECT_EQ(loaded.panel_data
|
||||||
.at(QStringLiteral("TimelinePanel"))
|
.at(QStringLiteral("TimelinePanel"))
|
||||||
.at(QStringLiteral("splitter")),
|
.at(QStringLiteral("splitter")),
|
||||||
QStringLiteral("AAA="));
|
QStringLiteral("AAA="));
|
||||||
|
|
||||||
// No unknown nodes leak into the viewers list: it must contain exactly the
|
// No unknown nodes leak into the viewers list: it must contain exactly the
|
||||||
// viewer that was added, not a duplicate of the sequences list
|
// viewer that was added, not a duplicate of the sequences list
|
||||||
EXPECT_NE(loaded.open_viewers().front(),
|
EXPECT_NE(loaded.open_viewers.front(),
|
||||||
static_cast<ViewerOutput *>(sequence));
|
static_cast<ViewerOutput *>(sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MainWindowLayoutInfo, FromXmlSkipsUnknownElementsAndNodes)
|
TEST(SerializedLayoutInfo, FromXmlSkipsUnknownElementsAndNodes)
|
||||||
{
|
{
|
||||||
const QString xml = QStringLiteral(
|
const QString xml = QStringLiteral(
|
||||||
"<layout version=\"1\">"
|
"<layout version=\"1\">"
|
||||||
@@ -177,17 +180,17 @@ TEST(MainWindowLayoutInfo, FromXmlSkipsUnknownElementsAndNodes)
|
|||||||
ASSERT_TRUE(reader.readNextStartElement());
|
ASSERT_TRUE(reader.readNextStartElement());
|
||||||
ASSERT_EQ(reader.name(), QStringLiteral("layout"));
|
ASSERT_EQ(reader.name(), QStringLiteral("layout"));
|
||||||
|
|
||||||
MainWindowLayoutInfo info = MainWindowLayoutInfo::from_xml(&reader, {});
|
SerializedLayoutInfo info = SerializedLayoutInfo::from_xml(&reader, {});
|
||||||
|
|
||||||
// Unknown pointers resolve to null but are still listed
|
// Unknown pointers resolve to null but are still listed
|
||||||
ASSERT_EQ(info.open_folders().size(), 1);
|
ASSERT_EQ(info.open_folders.size(), 1);
|
||||||
EXPECT_EQ(info.open_folders().front(), nullptr);
|
EXPECT_EQ(info.open_folders.front(), nullptr);
|
||||||
ASSERT_EQ(info.open_sequences().size(), 1);
|
ASSERT_EQ(info.open_sequences.size(), 1);
|
||||||
EXPECT_EQ(info.open_sequences().front(), nullptr);
|
EXPECT_EQ(info.open_sequences.front(), nullptr);
|
||||||
ASSERT_EQ(info.open_viewers().size(), 1);
|
ASSERT_EQ(info.open_viewers.size(), 1);
|
||||||
EXPECT_EQ(info.open_viewers().front(), nullptr);
|
EXPECT_EQ(info.open_viewers.front(), nullptr);
|
||||||
EXPECT_EQ(info.state(), QByteArray("ABC"));
|
EXPECT_EQ(info.state, QByteArray("ABC"));
|
||||||
EXPECT_TRUE(info.panel_data().empty());
|
EXPECT_TRUE(info.panel_data.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MainWindowStatusBar, ConstructionDefaults)
|
TEST(MainWindowStatusBar, ConstructionDefaults)
|
||||||
@@ -202,29 +205,36 @@ TEST(MainWindowStatusBar, ConstructionDefaults)
|
|||||||
|
|
||||||
TEST(MainWindowStatusBar, ReflectsTaskManagerState)
|
TEST(MainWindowStatusBar, ReflectsTaskManagerState)
|
||||||
{
|
{
|
||||||
TaskManager manager;
|
// The status bar now uses the global TaskManager singleton via the facade.
|
||||||
|
if (!TaskManager::instance()) {
|
||||||
|
TaskManager::create_instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineEventBridge bridge;
|
||||||
MainStatusBar bar;
|
MainStatusBar bar;
|
||||||
bar.show();
|
bar.show();
|
||||||
auto *progress = bar.findChild<QProgressBar *>();
|
auto *progress = bar.findChild<QProgressBar *>();
|
||||||
ASSERT_NE(progress, nullptr);
|
ASSERT_NE(progress, nullptr);
|
||||||
|
|
||||||
bar.connect_task_manager(&manager);
|
bar.connect_task_manager(&bridge);
|
||||||
|
|
||||||
auto *task = new DummyTask();
|
auto *task = new DummyTask();
|
||||||
manager.add_task(task);
|
oakengine_task_manager_add(
|
||||||
|
reinterpret_cast<OakEngineTask *>(task));
|
||||||
|
|
||||||
// One running task shows its title and the progress bar
|
// One running task shows its title and the progress bar
|
||||||
EXPECT_EQ(bar.currentMessage(), QStringLiteral("Status Test Task"));
|
EXPECT_EQ(bar.currentMessage(), QStringLiteral("Status Test Task"));
|
||||||
EXPECT_TRUE(progress->isVisible());
|
EXPECT_TRUE(progress->isVisible());
|
||||||
|
|
||||||
// Progress signals are forwarded to the bar
|
// Progress signals are forwarded to the bar through the facade event bridge
|
||||||
emit task->progress_changed(0.5);
|
emit task->progress_changed(0.5);
|
||||||
EXPECT_EQ(progress->value(), 50);
|
QTRY_COMPARE_WITH_TIMEOUT(progress->value(), 50, 1000);
|
||||||
|
|
||||||
// When the task list empties, the bar hides and the message clears
|
// When the task list empties, the bar hides and the message clears
|
||||||
manager.cancel_task_and_wait(task);
|
oakengine_task_manager_cancel(
|
||||||
QTRY_COMPARE_WITH_TIMEOUT(manager.get_task_count(), 0, 2000);
|
reinterpret_cast<OakEngineTask *>(task));
|
||||||
|
// Wait for the deferred delete on the task (facade cancel removes it)
|
||||||
|
QTRY_COMPARE_WITH_TIMEOUT(oakengine_task_manager_count(), 0, 2000);
|
||||||
EXPECT_TRUE(bar.currentMessage().isEmpty());
|
EXPECT_TRUE(bar.currentMessage().isEmpty());
|
||||||
EXPECT_FALSE(progress->isVisible());
|
EXPECT_FALSE(progress->isVisible());
|
||||||
EXPECT_EQ(progress->value(), 0);
|
EXPECT_EQ(progress->value(), 0);
|
||||||
@@ -300,7 +310,7 @@ TEST(MainWindow, ConstructsOffscreenWithPanelsAndMenus)
|
|||||||
AudioManager::create_instance();
|
AudioManager::create_instance();
|
||||||
}
|
}
|
||||||
if (!Core::instance()) {
|
if (!Core::instance()) {
|
||||||
new Core(Core::CoreParams()); // intentionally leaked
|
new Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "node/node.h"
|
#include "node/node.h"
|
||||||
#include "node/project.h"
|
#include "node/project.h"
|
||||||
#include "node/project/folder/folder.h"
|
#include "node/project/folder/folder.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -774,9 +775,9 @@ TEST_F(NodeCoreTest, KeyframeAddAndRemovalEmitSignals)
|
|||||||
int added = 0;
|
int added = 0;
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
QObject::connect(node, &olive::Node::keyframe_added,
|
QObject::connect(node, &olive::Node::keyframe_added,
|
||||||
[&added](olive::NodeKeyframe *) { ++added; });
|
[&added](OakEngineKeyframe *) { ++added; });
|
||||||
QObject::connect(node, &olive::Node::keyframe_removed,
|
QObject::connect(node, &olive::Node::keyframe_removed,
|
||||||
[&removed](olive::NodeKeyframe *) { ++removed; });
|
[&removed](OakEngineKeyframe *) { ++removed; });
|
||||||
QVector<olive::TimeRange> changed_ranges;
|
QVector<olive::TimeRange> changed_ranges;
|
||||||
QObject::connect(node, &olive::Node::value_changed,
|
QObject::connect(node, &olive::Node::value_changed,
|
||||||
[&changed_ranges](const olive::NodeInput &,
|
[&changed_ranges](const olive::NodeInput &,
|
||||||
@@ -854,9 +855,9 @@ TEST_F(NodeCoreTest, KeyframeTimeChangeResortsTrackAndEmits)
|
|||||||
olive::NodeKeyframe *last_changed = nullptr;
|
olive::NodeKeyframe *last_changed = nullptr;
|
||||||
QObject::connect(node, &olive::Node::keyframe_time_changed,
|
QObject::connect(node, &olive::Node::keyframe_time_changed,
|
||||||
[&time_changed,
|
[&time_changed,
|
||||||
&last_changed](olive::NodeKeyframe *key) {
|
&last_changed](OakEngineKeyframe *key) {
|
||||||
++time_changed;
|
++time_changed;
|
||||||
last_changed = key;
|
last_changed = reinterpret_cast<olive::NodeKeyframe *>(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Moving the first keyframe past the second resorts the track
|
// Moving the first keyframe past the second resorts the track
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ olive::TexturePtr get_output_texture(const olive::NodeValueTable &table)
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ protected:
|
|||||||
// singleton, which itself touches Core (same pattern as
|
// singleton, which itself touches Core (same pattern as
|
||||||
// project_factory_test)
|
// project_factory_test)
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
ColorManager::set_up_default_config();
|
ColorManager::set_up_default_config();
|
||||||
|
|
||||||
if (!Core::instance()) {
|
if (!Core::instance()) {
|
||||||
new Core(Core::CoreParams()); // intentionally leaked
|
new Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
|
|
||||||
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
|
||||||
@@ -401,7 +401,7 @@ TEST_F(PanelTest, CurvePanelSetNodes)
|
|||||||
// A single node appears as one top-level item listing its keyframable
|
// A single node appears as one top-level item listing its keyframable
|
||||||
// inputs (MathNode has three: the base "enabled" input and parameters
|
// inputs (MathNode has three: the base "enabled" input and parameters
|
||||||
// A and B)
|
// A and B)
|
||||||
panel.set_node(math);
|
panel.set_node(reinterpret_cast<OakEngineNode *>(math));
|
||||||
ASSERT_EQ(tree->topLevelItemCount(), 1);
|
ASSERT_EQ(tree->topLevelItemCount(), 1);
|
||||||
EXPECT_EQ(tree->topLevelItem(0)->text(0), math->name());
|
EXPECT_EQ(tree->topLevelItem(0)->text(0), math->name());
|
||||||
EXPECT_EQ(tree->topLevelItem(0)->childCount(), 3);
|
EXPECT_EQ(tree->topLevelItem(0)->childCount(), 3);
|
||||||
@@ -447,12 +447,12 @@ TEST_F(PanelTest, ParamPanelForwardsViewSignals)
|
|||||||
ParamPanel panel;
|
ParamPanel panel;
|
||||||
|
|
||||||
QSignalSpy focused_spy(&panel, &ParamPanel::focused_node_changed);
|
QSignalSpy focused_spy(&panel, &ParamPanel::focused_node_changed);
|
||||||
emit panel.get_param_view()->focused_node_changed(math);
|
emit panel.get_param_view()->focused_node_changed(reinterpret_cast<OakEngineNode *>(math));
|
||||||
ASSERT_EQ(focused_spy.count(), 1);
|
ASSERT_EQ(focused_spy.count(), 1);
|
||||||
EXPECT_EQ(focused_spy.first().first().value<Node *>(), math);
|
EXPECT_EQ(focused_spy.first().first().value<OakEngineNode *>(), reinterpret_cast<OakEngineNode *>(math));
|
||||||
|
|
||||||
QSignalSpy selected_spy(&panel, &ParamPanel::selected_nodes_changed);
|
QSignalSpy selected_spy(&panel, &ParamPanel::selected_nodes_changed);
|
||||||
emit panel.get_param_view()->selected_nodes_changed({ { math, nullptr } });
|
emit panel.get_param_view()->selected_nodes_changed({ { reinterpret_cast<OakEngineNode *>(math), nullptr } });
|
||||||
EXPECT_EQ(selected_spy.count(), 1);
|
EXPECT_EQ(selected_spy.count(), 1);
|
||||||
|
|
||||||
QSignalSpy text_spy(&panel, &ParamPanel::request_viewer_to_start_editing_text);
|
QSignalSpy text_spy(&panel, &ParamPanel::request_viewer_to_start_editing_text);
|
||||||
@@ -620,7 +620,7 @@ TEST_F(PanelTest, FootageViewerPanelConstruction)
|
|||||||
|
|
||||||
panel.connect_viewer_node(viewer);
|
panel.connect_viewer_node(viewer);
|
||||||
ASSERT_EQ(panel.get_selected_footage().size(), 1);
|
ASSERT_EQ(panel.get_selected_footage().size(), 1);
|
||||||
EXPECT_EQ(panel.get_selected_footage().first(), viewer);
|
EXPECT_EQ(panel.get_selected_footage().first(), reinterpret_cast<OakEngineNode *>(viewer));
|
||||||
|
|
||||||
panel.disconnect_viewer_node();
|
panel.disconnect_viewer_node();
|
||||||
EXPECT_TRUE(panel.get_selected_footage().isEmpty());
|
EXPECT_TRUE(panel.get_selected_footage().isEmpty());
|
||||||
@@ -656,15 +656,15 @@ TEST_F(PanelTest, NodePanelForwardsViewSignals)
|
|||||||
panel.set_contexts({ project.root() });
|
panel.set_contexts({ project.root() });
|
||||||
|
|
||||||
QSignalSpy selected_spy(&panel, &NodePanel::nodes_selected);
|
QSignalSpy selected_spy(&panel, &NodePanel::nodes_selected);
|
||||||
emit panel.get_node_widget()->view()->nodes_selected({ math });
|
emit panel.get_node_widget()->view()->nodes_selected({ reinterpret_cast<OakEngineNode *>(math) });
|
||||||
ASSERT_EQ(selected_spy.count(), 1);
|
ASSERT_EQ(selected_spy.count(), 1);
|
||||||
|
|
||||||
QSignalSpy deselected_spy(&panel, &NodePanel::nodes_deselected);
|
QSignalSpy deselected_spy(&panel, &NodePanel::nodes_deselected);
|
||||||
emit panel.get_node_widget()->view()->nodes_deselected({ math });
|
emit panel.get_node_widget()->view()->nodes_deselected({ reinterpret_cast<OakEngineNode *>(math) });
|
||||||
EXPECT_EQ(deselected_spy.count(), 1);
|
EXPECT_EQ(deselected_spy.count(), 1);
|
||||||
|
|
||||||
QSignalSpy selection_spy(&panel, &NodePanel::node_selection_changed);
|
QSignalSpy selection_spy(&panel, &NodePanel::node_selection_changed);
|
||||||
emit panel.get_node_widget()->view()->node_selection_changed({ math });
|
emit panel.get_node_widget()->view()->node_selection_changed({ reinterpret_cast<OakEngineNode *>(math) });
|
||||||
EXPECT_EQ(selection_spy.count(), 1);
|
EXPECT_EQ(selection_spy.count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ void collect_leaf_actions(QMenu *menu, QList<QAction *> *leaves)
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ protected:
|
|||||||
// Leaked intentionally: Core is process-wide and DiskCacheFolder
|
// Leaked intentionally: Core is process-wide and DiskCacheFolder
|
||||||
// eviction calls Core::instance()->WarnCacheFull() (matches
|
// eviction calls Core::instance()->WarnCacheFull() (matches
|
||||||
// viewer_display_repro_test).
|
// viewer_display_repro_test).
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
#include "node/globals.h"
|
#include "node/globals.h"
|
||||||
#include "node/project.h"
|
#include "node/project.h"
|
||||||
#include "render/job/acceleratedjob.h"
|
#include "render/job/acceleratedjob.h"
|
||||||
#include "render/managedcolor.h"
|
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||||
#include "render/renderjobtracker.h"
|
#include "render/renderjobtracker.h"
|
||||||
#include "render/rendermanager.h"
|
#include "render/rendermanager.h"
|
||||||
#include "render/renderprocessor.h"
|
#include "render/renderprocessor.h"
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ protected:
|
|||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: Core is process-wide (matches
|
// Leaked intentionally: Core is process-wide (matches
|
||||||
// render_diskcache_test)
|
// render_diskcache_test)
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ protected:
|
|||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: matches render_diskcache_test, Core is
|
// Leaked intentionally: matches render_diskcache_test, Core is
|
||||||
// process-wide and eviction paths call Core::WarnCacheFull().
|
// process-wide and eviction paths call Core::WarnCacheFull().
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
@@ -658,7 +658,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
|
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ protected:
|
|||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: Core is process-wide (matches footage_probe_test)
|
// Leaked intentionally: Core is process-wide (matches footage_probe_test)
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ protected:
|
|||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
// Leaked intentionally: Core is process-wide (matches footage_probe_test)
|
// Leaked intentionally: Core is process-wide (matches footage_probe_test)
|
||||||
new olive::Core(olive::Core::CoreParams());
|
new olive::Core();
|
||||||
}
|
}
|
||||||
|
|
||||||
created_disk_manager_ = (olive::DiskManager::instance() == nullptr);
|
created_disk_manager_ = (olive::DiskManager::instance() == nullptr);
|
||||||
@@ -109,9 +109,10 @@ TEST_F(TaskProjectImportTest, ImportOfUnprobeableFileCollectsInvalidList)
|
|||||||
EXPECT_DOUBLE_EQ(last_progress, 1.0);
|
EXPECT_DOUBLE_EQ(last_progress, 1.0);
|
||||||
|
|
||||||
// The undo command exists but contains no children since nothing was added
|
// The undo command exists but contains no children since nothing was added
|
||||||
ASSERT_NE(task.get_command(), nullptr);
|
olive::MultiUndoCommand *cmd = task.take_command();
|
||||||
EXPECT_EQ(task.get_command()->child_count(), 0);
|
ASSERT_NE(cmd, nullptr);
|
||||||
delete task.get_command();
|
EXPECT_EQ(cmd->child_count(), 0);
|
||||||
|
delete cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TaskProjectImportTest, ImportOfImageFileAddsFootageThroughUndoCommand)
|
TEST_F(TaskProjectImportTest, ImportOfImageFileAddsFootageThroughUndoCommand)
|
||||||
@@ -135,18 +136,19 @@ TEST_F(TaskProjectImportTest, ImportOfImageFileAddsFootageThroughUndoCommand)
|
|||||||
// Nothing is in the folder until the command is redone
|
// Nothing is in the folder until the command is redone
|
||||||
EXPECT_TRUE(project_->root()->children().isEmpty());
|
EXPECT_TRUE(project_->root()->children().isEmpty());
|
||||||
|
|
||||||
ASSERT_NE(task.get_command(), nullptr);
|
olive::MultiUndoCommand *cmd = task.take_command();
|
||||||
task.get_command()->redo_now();
|
ASSERT_NE(cmd, nullptr);
|
||||||
|
cmd->redo_now();
|
||||||
|
|
||||||
ASSERT_EQ(project_->root()->children().size(), 1);
|
ASSERT_EQ(project_->root()->children().size(), 1);
|
||||||
EXPECT_EQ(project_->root()->children().first(),
|
EXPECT_EQ(project_->root()->children().first(),
|
||||||
static_cast<olive::Node *>(footage));
|
static_cast<olive::Node *>(footage));
|
||||||
EXPECT_TRUE(project_->nodes().contains(footage));
|
EXPECT_TRUE(project_->nodes().contains(footage));
|
||||||
|
|
||||||
task.get_command()->undo_now();
|
cmd->undo_now();
|
||||||
EXPECT_TRUE(project_->root()->children().isEmpty());
|
EXPECT_TRUE(project_->root()->children().isEmpty());
|
||||||
|
|
||||||
delete task.get_command();
|
delete cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TaskProjectImportTest, ImportOfDirectoryCreatesFolderHierarchy)
|
TEST_F(TaskProjectImportTest, ImportOfDirectoryCreatesFolderHierarchy)
|
||||||
@@ -171,8 +173,9 @@ TEST_F(TaskProjectImportTest, ImportOfDirectoryCreatesFolderHierarchy)
|
|||||||
EXPECT_FALSE(task.has_invalid_files());
|
EXPECT_FALSE(task.has_invalid_files());
|
||||||
EXPECT_EQ(task.get_imported_footage().size(), 2);
|
EXPECT_EQ(task.get_imported_footage().size(), 2);
|
||||||
|
|
||||||
ASSERT_NE(task.get_command(), nullptr);
|
olive::MultiUndoCommand *cmd = task.take_command();
|
||||||
task.get_command()->redo_now();
|
ASSERT_NE(cmd, nullptr);
|
||||||
|
cmd->redo_now();
|
||||||
|
|
||||||
// Importing a directory creates a folder named after it under the target
|
// Importing a directory creates a folder named after it under the target
|
||||||
const QVector<olive::Node *> &root_children = project_->root()->children();
|
const QVector<olive::Node *> &root_children = project_->root()->children();
|
||||||
@@ -191,10 +194,10 @@ TEST_F(TaskProjectImportTest, ImportOfDirectoryCreatesFolderHierarchy)
|
|||||||
ASSERT_EQ(sub_folders.size(), 1);
|
ASSERT_EQ(sub_folders.size(), 1);
|
||||||
EXPECT_EQ(sub_folders.first()->get_label(), QStringLiteral("sub"));
|
EXPECT_EQ(sub_folders.first()->get_label(), QStringLiteral("sub"));
|
||||||
|
|
||||||
task.get_command()->undo_now();
|
cmd->undo_now();
|
||||||
EXPECT_TRUE(project_->root()->children().isEmpty());
|
EXPECT_TRUE(project_->root()->children().isEmpty());
|
||||||
|
|
||||||
delete task.get_command();
|
delete cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TaskProjectImportTest, CancelledBeforeRunReturnsFalseAndDropsCommand)
|
TEST_F(TaskProjectImportTest, CancelledBeforeRunReturnsFalseAndDropsCommand)
|
||||||
@@ -206,7 +209,7 @@ TEST_F(TaskProjectImportTest, CancelledBeforeRunReturnsFalseAndDropsCommand)
|
|||||||
task.Cancel();
|
task.Cancel();
|
||||||
|
|
||||||
EXPECT_FALSE(task.start());
|
EXPECT_FALSE(task.start());
|
||||||
EXPECT_EQ(task.get_command(), nullptr);
|
EXPECT_EQ(task.take_command(), nullptr);
|
||||||
EXPECT_TRUE(task.get_imported_footage().isEmpty());
|
EXPECT_TRUE(task.get_imported_footage().isEmpty());
|
||||||
EXPECT_FALSE(task.has_invalid_files());
|
EXPECT_FALSE(task.has_invalid_files());
|
||||||
EXPECT_TRUE(project_->root()->children().isEmpty());
|
EXPECT_TRUE(project_->root()->children().isEmpty());
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Core::instance()) {
|
if (!Core::instance()) {
|
||||||
new Core(Core::CoreParams());
|
new Core();
|
||||||
}
|
}
|
||||||
AudioManager::create_instance();
|
AudioManager::create_instance();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "widget/filefield/filefield.h"
|
#include "widget/filefield/filefield.h"
|
||||||
#include "widget/focusablelineedit/focusablelineedit.h"
|
#include "widget/focusablelineedit/focusablelineedit.h"
|
||||||
#include "widget/handmovableview/handmovableview.h"
|
#include "widget/handmovableview/handmovableview.h"
|
||||||
|
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
#include "widget/nodevaluetree/nodevaluetree.h"
|
#include "widget/nodevaluetree/nodevaluetree.h"
|
||||||
#include "widget/path/pathwidget.h"
|
#include "widget/path/pathwidget.h"
|
||||||
@@ -52,7 +53,7 @@ namespace
|
|||||||
void ensure_core()
|
void ensure_core()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +642,7 @@ TEST(WidgetColorButton, SetColorRoundTrips)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ColorButton btn(project.color_manager());
|
olive::ColorButton btn(oak_color_manager(project.color_manager()));
|
||||||
EXPECT_FLOAT_EQ(btn.get_color().red(), 1.0f);
|
EXPECT_FLOAT_EQ(btn.get_color().red(), 1.0f);
|
||||||
EXPECT_FLOAT_EQ(btn.get_color().green(), 1.0f);
|
EXPECT_FLOAT_EQ(btn.get_color().green(), 1.0f);
|
||||||
EXPECT_FLOAT_EQ(btn.get_color().blue(), 1.0f);
|
EXPECT_FLOAT_EQ(btn.get_color().blue(), 1.0f);
|
||||||
@@ -716,7 +717,7 @@ TEST(WidgetColorSwatchChooser, ClickingSwatchEmitsItsColor)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ColorSwatchChooser chooser(project.color_manager());
|
olive::ColorSwatchChooser chooser(oak_color_manager(project.color_manager()));
|
||||||
const auto buttons = chooser.findChildren<olive::ColorButton *>();
|
const auto buttons = chooser.findChildren<olive::ColorButton *>();
|
||||||
EXPECT_EQ(buttons.size(), 32);
|
EXPECT_EQ(buttons.size(), 32);
|
||||||
|
|
||||||
@@ -746,7 +747,7 @@ TEST(WidgetColorSpaceChooser, InputRoundTripsAndEmits)
|
|||||||
ASSERT_GE(spaces.size(), 2);
|
ASSERT_GE(spaces.size(), 2);
|
||||||
|
|
||||||
// Input-only mode, as used by the export dialog
|
// Input-only mode, as used by the export dialog
|
||||||
olive::ColorSpaceChooser chooser(project.color_manager(), true, false);
|
olive::ColorSpaceChooser chooser(oak_color_manager(project.color_manager()), true, false);
|
||||||
EXPECT_FALSE(chooser.input().isEmpty());
|
EXPECT_FALSE(chooser.input().isEmpty());
|
||||||
|
|
||||||
QSignalSpy spy(&chooser,
|
QSignalSpy spy(&chooser,
|
||||||
@@ -773,7 +774,7 @@ TEST(WidgetColorSpaceChooser, FullModePopulatesDisplayFields)
|
|||||||
olive::ColorManager::set_up_default_config();
|
olive::ColorManager::set_up_default_config();
|
||||||
olive::Project project;
|
olive::Project project;
|
||||||
|
|
||||||
olive::ColorSpaceChooser chooser(project.color_manager());
|
olive::ColorSpaceChooser chooser(oak_color_manager(project.color_manager()));
|
||||||
EXPECT_FALSE(chooser.input().isEmpty());
|
EXPECT_FALSE(chooser.input().isEmpty());
|
||||||
EXPECT_FALSE(chooser.output().display().isEmpty());
|
EXPECT_FALSE(chooser.output().display().isEmpty());
|
||||||
EXPECT_FALSE(chooser.output().view().isEmpty());
|
EXPECT_FALSE(chooser.output().view().isEmpty());
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
@@ -253,8 +253,8 @@ TEST_F(WidgetPanelsTest, NodeTreeCheckboxesToggleEnableStateAndEmit)
|
|||||||
int node_emissions = 0;
|
int node_emissions = 0;
|
||||||
QObject::connect(&view, &NodeTreeView::node_enable_changed,
|
QObject::connect(&view, &NodeTreeView::node_enable_changed,
|
||||||
[&node_signal_node, &node_signal_enabled,
|
[&node_signal_node, &node_signal_enabled,
|
||||||
&node_emissions](Node *n, bool e) {
|
&node_emissions](OakEngineNode *n, bool e) {
|
||||||
node_signal_node = n;
|
node_signal_node = reinterpret_cast<Node *>(n);
|
||||||
node_signal_enabled = e;
|
node_signal_enabled = e;
|
||||||
++node_emissions;
|
++node_emissions;
|
||||||
});
|
});
|
||||||
@@ -431,7 +431,7 @@ TEST(TaskView, TaskLifecycleUpdatesItems)
|
|||||||
TaskView view(nullptr);
|
TaskView view(nullptr);
|
||||||
DummyTask task;
|
DummyTask task;
|
||||||
|
|
||||||
view.add_task(&task);
|
view.add_task(reinterpret_cast<OakEngineTask*>(&task));
|
||||||
|
|
||||||
auto *item = view.findChild<TaskViewItem *>();
|
auto *item = view.findChild<TaskViewItem *>();
|
||||||
ASSERT_NE(item, nullptr);
|
ASSERT_NE(item, nullptr);
|
||||||
@@ -453,16 +453,16 @@ TEST(TaskView, TaskLifecycleUpdatesItems)
|
|||||||
EXPECT_EQ(bar->value(), 50);
|
EXPECT_EQ(bar->value(), 50);
|
||||||
|
|
||||||
// The cancel button relays the task through TaskCancelled
|
// The cancel button relays the task through TaskCancelled
|
||||||
Task *cancelled = nullptr;
|
OakEngineTask *cancelled = nullptr;
|
||||||
QObject::connect(&view, &TaskView::task_cancelled,
|
QObject::connect(&view, &TaskView::task_cancelled,
|
||||||
[&cancelled](Task *t) { cancelled = t; });
|
[&cancelled](OakEngineTask *t) { cancelled = t; });
|
||||||
auto *cancel_button = item->findChild<QPushButton *>();
|
auto *cancel_button = item->findChild<QPushButton *>();
|
||||||
ASSERT_NE(cancel_button, nullptr);
|
ASSERT_NE(cancel_button, nullptr);
|
||||||
cancel_button->click();
|
cancel_button->click();
|
||||||
EXPECT_EQ(cancelled, &task);
|
EXPECT_EQ(cancelled, reinterpret_cast<OakEngineTask*>(&task));
|
||||||
|
|
||||||
// Failure swaps in the error label
|
// Failure swaps in the error label
|
||||||
view.task_failed(&task);
|
view.task_failed(reinterpret_cast<OakEngineTask*>(&task));
|
||||||
bool found_error = false;
|
bool found_error = false;
|
||||||
foreach (QLabel *label, item->findChildren<QLabel *>()) {
|
foreach (QLabel *label, item->findChildren<QLabel *>()) {
|
||||||
if (label->text().contains(QStringLiteral("boom"))) {
|
if (label->text().contains(QStringLiteral("boom"))) {
|
||||||
@@ -473,7 +473,7 @@ TEST(TaskView, TaskLifecycleUpdatesItems)
|
|||||||
EXPECT_TRUE(found_error);
|
EXPECT_TRUE(found_error);
|
||||||
|
|
||||||
// Removal deletes the item once deferred deletions are processed
|
// Removal deletes the item once deferred deletions are processed
|
||||||
view.remove_task(&task);
|
view.remove_task(reinterpret_cast<OakEngineTask*>(&task));
|
||||||
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
EXPECT_EQ(view.findChild<TaskViewItem *>(), nullptr);
|
EXPECT_EQ(view.findChild<TaskViewItem *>(), nullptr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace
|
|||||||
void ensure_app_singletons()
|
void ensure_app_singletons()
|
||||||
{
|
{
|
||||||
if (!olive::Core::instance()) {
|
if (!olive::Core::instance()) {
|
||||||
new olive::Core(olive::Core::CoreParams()); // intentionally leaked
|
new olive::Core(); // intentionally leaked
|
||||||
}
|
}
|
||||||
if (!olive::DiskManager::instance()) {
|
if (!olive::DiskManager::instance()) {
|
||||||
olive::DiskManager::create_instance();
|
olive::DiskManager::create_instance();
|
||||||
|
|||||||
+11
-21
@@ -17,36 +17,26 @@
|
|||||||
# Render worker process (oak-render-worker).
|
# Render worker process (oak-render-worker).
|
||||||
#
|
#
|
||||||
# The worker is a headless render process spawned by the editor through
|
# The worker is a headless render process spawned by the editor through
|
||||||
# RenderWorkerPool. It reuses the libolive-editor object library so it
|
# RenderWorkerPool. It is a thin shell over liboakengine's pure C ABI
|
||||||
# shares the exact same render/node/codec code as the editor. The link set
|
# (oakengine/worker.h): all render/node/codec logic lives inside the engine
|
||||||
# is currently the full OLIVE_LIBRARIES for simplicity; trimming UI-only
|
# library, so the worker executable imports no engine C++ symbols and only
|
||||||
# dependencies is a later-phase cleanup (render-process-isolation plan).
|
# sees the C headers under engine/include.
|
||||||
add_executable(olive-render-worker
|
add_executable(olive-render-worker
|
||||||
workermain.cpp
|
workermain.cpp
|
||||||
$<TARGET_OBJECTS:olive-version-obj>
|
$<TARGET_OBJECTS:olive-version-obj>
|
||||||
)
|
)
|
||||||
set_target_properties(olive-render-worker PROPERTIES OUTPUT_NAME "oak-render-worker")
|
set_target_properties(olive-render-worker PROPERTIES OUTPUT_NAME "oak-render-worker")
|
||||||
|
|
||||||
if (APPLE)
|
# $<LINK_ONLY:...> links liboakengine without inheriting its usage
|
||||||
target_sources(olive-render-worker PRIVATE workermain_mac.mm)
|
# requirements (the engine's C++ include roots); the worker is only allowed
|
||||||
target_link_libraries(olive-render-worker PRIVATE "-framework Cocoa")
|
# to see the public C API headers.
|
||||||
endif ()
|
target_link_libraries(olive-render-worker PRIVATE $<LINK_ONLY:oakengine>)
|
||||||
|
|
||||||
# CMAKE_INCLUDE_CURRENT_DIR only covers the app/ scope, so add the app
|
|
||||||
# include roots explicitly now that the worker lives outside it
|
|
||||||
target_include_directories(olive-render-worker PRIVATE
|
target_include_directories(olive-render-worker PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/app
|
${CMAKE_SOURCE_DIR}/engine/include)
|
||||||
${CMAKE_BINARY_DIR}/app
|
|
||||||
${CMAKE_SOURCE_DIR}/app/pluginSupport
|
|
||||||
${OLIVE_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
target_link_libraries(olive-render-worker PUBLIC OfxHost)
|
|
||||||
target_link_libraries(olive-render-worker PRIVATE ${OLIVE_LIBRARIES})
|
|
||||||
target_compile_options(olive-render-worker PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
|
||||||
target_compile_definitions(olive-render-worker PRIVATE ${OLIVE_DEFINITIONS})
|
|
||||||
|
|
||||||
|
# The worker needs the dynamic render backend plugins at runtime; build them
|
||||||
|
# first so the worker is never run against stale backends.
|
||||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||||
target_compile_definitions(olive-render-worker PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
|
||||||
add_dependencies(olive-render-worker oakgl)
|
add_dependencies(olive-render-worker oakgl)
|
||||||
if (TARGET oakvulkan)
|
if (TARGET oakvulkan)
|
||||||
add_dependencies(olive-render-worker oakvulkan)
|
add_dependencies(olive-render-worker oakvulkan)
|
||||||
|
|||||||
+6
-729
@@ -18,736 +18,13 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <cstdio>
|
#include "oakengine/worker.h"
|
||||||
#include <cstring>
|
|
||||||
#include <csignal>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QMatrix4x4>
|
|
||||||
#include <QOpenGLContext>
|
|
||||||
#include <QSurfaceFormat>
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
#include <execinfo.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
|
||||||
#include "config/config.h"
|
|
||||||
#include "coreengine.h"
|
|
||||||
#include "node/factory.h"
|
|
||||||
#include "node/input/multicam/multicamnode.h"
|
|
||||||
#include "node/project/serializer/serializer.h"
|
|
||||||
#include "render/diskmanager.h"
|
|
||||||
#include "render/framemanager.h"
|
|
||||||
#include "render/ipc/frameslotpool.h"
|
|
||||||
#include "render/ipc/ipcmessage.h"
|
|
||||||
#include "render/ipc/sharedmemoryregion.h"
|
|
||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
|
||||||
#include "render/backend/dynamicrenderer.h"
|
|
||||||
#endif
|
|
||||||
#include "render/opengl/openglrenderer.h"
|
|
||||||
#include "render/rendermanager.h"
|
|
||||||
#include "render/renderprocessor.h"
|
|
||||||
#include "render/colorprocessor.h"
|
|
||||||
#include "render/colortransform.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
void HideWorkerDockIcon();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
void print_backtrace(int sig)
|
|
||||||
{
|
|
||||||
void *array[50];
|
|
||||||
size_t size = backtrace(array, 50);
|
|
||||||
fprintf(stderr, "worker: caught signal %d, backtrace:\n", sig);
|
|
||||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
|
||||||
fflush(stderr);
|
|
||||||
_exit(128 + sig);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
constexpr int k_protocol_version = 1;
|
|
||||||
constexpr int k_default_width = 1920;
|
|
||||||
constexpr int k_default_height = 1080;
|
|
||||||
constexpr int k_default_frame_rate = 24;
|
|
||||||
|
|
||||||
void install_surface_format()
|
|
||||||
{
|
|
||||||
QSurfaceFormat format;
|
|
||||||
format.setVersion(3, 2);
|
|
||||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
|
||||||
format.setDepthBufferSize(24);
|
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_error(const QString &message)
|
|
||||||
{
|
|
||||||
const QByteArray line = QByteArray("worker: ") + message.toUtf8() + '\n';
|
|
||||||
fwrite(line.constData(), 1, size_t(line.size()), stderr);
|
|
||||||
fflush(stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject error_message(const QString &message, qint64 ticket_id = 0)
|
|
||||||
{
|
|
||||||
QJsonObject o;
|
|
||||||
o["type"] = olive::ipc::msgtype::k_error;
|
|
||||||
o["message"] = message;
|
|
||||||
if (ticket_id) {
|
|
||||||
o["ticket"] = double(ticket_id);
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenderWorker {
|
|
||||||
public:
|
|
||||||
RenderWorker(olive::Renderer *renderer, QFile *out)
|
|
||||||
: renderer_(renderer)
|
|
||||||
, out_(out)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~RenderWorker()
|
|
||||||
{
|
|
||||||
project_.reset();
|
|
||||||
olive::ProjectSerializer::destroy();
|
|
||||||
olive::DiskManager::destroy_instance();
|
|
||||||
olive::FrameManager::destroy_instance();
|
|
||||||
olive::NodeFactory::destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initialize_runtime()
|
|
||||||
{
|
|
||||||
// Create a minimal EngineCore instance so that code paths calling
|
|
||||||
// EngineCore::instance() (e.g. ViewerOutput::data for timecode display)
|
|
||||||
// do not dereference null. The worker has no UI, so the plain engine
|
|
||||||
// core is sufficient. The worker is short-lived; leaking this on exit
|
|
||||||
// is harmless.
|
|
||||||
if (!olive::EngineCore::instance()) {
|
|
||||||
new olive::EngineCore(olive::EngineCore::CoreParams());
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::Config::load();
|
|
||||||
olive::NodeFactory::initialize();
|
|
||||||
olive::ColorManager::set_up_default_config();
|
|
||||||
olive::FrameManager::create_instance();
|
|
||||||
olive::DiskManager::create_instance();
|
|
||||||
olive::ProjectSerializer::initialize();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool send_startup_handshake()
|
|
||||||
{
|
|
||||||
olive::ipc::HandshakeMsg hs;
|
|
||||||
hs.protocol_version = k_protocol_version;
|
|
||||||
hs.shm_key = QString();
|
|
||||||
hs.input_shm_key = QString();
|
|
||||||
hs.input_slots = 0;
|
|
||||||
hs.output_slots = 0;
|
|
||||||
hs.slot_data_bytes = 0;
|
|
||||||
hs.input_slot_data_bytes = 0;
|
|
||||||
|
|
||||||
QJsonObject handshake = hs.to_json();
|
|
||||||
QOpenGLContext *ctx = nullptr;
|
|
||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
|
||||||
if (auto *dynamic_renderer =
|
|
||||||
dynamic_cast<olive::DynamicRenderer *>(renderer_)) {
|
|
||||||
ctx = dynamic_renderer->open_gl_context();
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
ctx = static_cast<olive::OpenGLRenderer *>(renderer_)->context();
|
|
||||||
}
|
|
||||||
if (ctx) {
|
|
||||||
const QSurfaceFormat fmt = ctx->format();
|
|
||||||
handshake["gl_major"] = fmt.majorVersion();
|
|
||||||
handshake["gl_minor"] = fmt.minorVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
return write(handshake);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handle(const QJsonObject &message)
|
|
||||||
{
|
|
||||||
const QString type = message["type"].toString();
|
|
||||||
|
|
||||||
if (type == QLatin1String(olive::ipc::msgtype::k_handshake)) {
|
|
||||||
olive::ipc::HandshakeMsg hs;
|
|
||||||
if (!olive::ipc::HandshakeMsg::from_json(message, &hs)) {
|
|
||||||
return write(
|
|
||||||
error_message(QStringLiteral("invalid handshake message")));
|
|
||||||
}
|
|
||||||
return attach_output_pool(hs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == QLatin1String(olive::ipc::msgtype::k_load_graph)) {
|
|
||||||
olive::ipc::LoadGraphMsg load;
|
|
||||||
if (!olive::ipc::LoadGraphMsg::from_json(message, &load)) {
|
|
||||||
return write(
|
|
||||||
error_message(QStringLiteral("invalid load_graph message")));
|
|
||||||
}
|
|
||||||
return load_graph(load.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == QLatin1String(olive::ipc::msgtype::k_render_frame)) {
|
|
||||||
olive::ipc::RenderFrameMsg render;
|
|
||||||
if (!olive::ipc::RenderFrameMsg::from_json(message, &render)) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("invalid render_frame message")));
|
|
||||||
}
|
|
||||||
return render_frame(render);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == QLatin1String(olive::ipc::msgtype::k_cancel)) {
|
|
||||||
// Stage 5 wires cancellation into in-flight jobs. Stage 2 has only synchronous single-frame work.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == QLatin1String(olive::ipc::msgtype::k_shutdown)) {
|
|
||||||
shutdown_requested_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return write(
|
|
||||||
error_message(QStringLiteral("unknown message type: %1").arg(type)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shutdown_requested() const
|
|
||||||
{
|
|
||||||
return shutdown_requested_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool write(const QJsonObject &message)
|
|
||||||
{
|
|
||||||
const bool ok = olive::ipc::write_message(out_, message);
|
|
||||||
out_->flush();
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool attach_output_pool(const olive::ipc::HandshakeMsg &hs)
|
|
||||||
{
|
|
||||||
if (hs.protocol_version != k_protocol_version) {
|
|
||||||
return write(
|
|
||||||
error_message(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(error_message(QStringLiteral(
|
|
||||||
"handshake missing output shared-memory geometry")));
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t bytes = olive::ipc::FrameSlotPool::bytes_needed(
|
|
||||||
uint32_t(hs.output_slots), size_t(hs.slot_data_bytes));
|
|
||||||
if (!output_region_.open(hs.shm_key, bytes,
|
|
||||||
olive::ipc::SharedMemoryRegion::k_attach)) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("failed to attach shared memory: %1")
|
|
||||||
.arg(output_region_.error())));
|
|
||||||
}
|
|
||||||
|
|
||||||
output_pool_ = olive::ipc::FrameSlotPool::attach(output_region_.data());
|
|
||||||
if (!output_pool_->is_valid()) {
|
|
||||||
output_region_.close();
|
|
||||||
output_pool_.reset();
|
|
||||||
return write(error_message(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(error_message(QStringLiteral(
|
|
||||||
"handshake missing input shared-memory geometry")));
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t input_bytes = olive::ipc::FrameSlotPool::bytes_needed(
|
|
||||||
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::k_attach)) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("failed to attach input shared memory: %1")
|
|
||||||
.arg(input_region_.error())));
|
|
||||||
}
|
|
||||||
|
|
||||||
input_pool_ =
|
|
||||||
olive::ipc::FrameSlotPool::attach(input_region_.data());
|
|
||||||
if (!input_pool_->is_valid()) {
|
|
||||||
input_region_.close();
|
|
||||||
input_pool_.reset();
|
|
||||||
return write(error_message(QStringLiteral(
|
|
||||||
"input shared memory does not contain a frame slot pool")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_graph(const QString &path)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
QFileInfo fi(path);
|
|
||||||
if (!fi.exists()) {
|
|
||||||
log_error(
|
|
||||||
QStringLiteral("LoadGraph: graph file does not exist: %1")
|
|
||||||
.arg(path));
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("graph file does not exist: %1").arg(path)));
|
|
||||||
}
|
|
||||||
if (fi.size() == 0) {
|
|
||||||
log_error(QStringLiteral("LoadGraph: graph file is empty: %1")
|
|
||||||
.arg(path));
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("graph file is empty: %1").arg(path)));
|
|
||||||
}
|
|
||||||
log_error(
|
|
||||||
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::k_project);
|
|
||||||
if (result != olive::ProjectSerializer::k_success) {
|
|
||||||
return write(
|
|
||||||
error_message(QStringLiteral("failed to load graph %1: %2")
|
|
||||||
.arg(path, result.get_details())));
|
|
||||||
}
|
|
||||||
|
|
||||||
project_ = std::move(loaded);
|
|
||||||
node_by_token_.clear();
|
|
||||||
color_processor_cache_.clear();
|
|
||||||
|
|
||||||
const auto &data = result.get_load_data();
|
|
||||||
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) {
|
|
||||||
node_by_token_.insert(it.value().toString(), it.key());
|
|
||||||
node_by_token_.insert(it.value().toString(QUuid::WithoutBraces),
|
|
||||||
it.key());
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject ack;
|
|
||||||
ack["type"] = QStringLiteral("graph_loaded");
|
|
||||||
ack["nodes"] = node_by_token_.size();
|
|
||||||
return write(ack);
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::Node *find_node(const QString &token) const
|
|
||||||
{
|
|
||||||
if (olive::Node *node = node_by_token_.value(token, nullptr)) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
const quintptr ptr = token.toULongLong(&ok, 0);
|
|
||||||
if (ok) {
|
|
||||||
return node_by_token_.value(QString::number(ptr), nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool render_frame(const olive::ipc::RenderFrameMsg &message)
|
|
||||||
{
|
|
||||||
if (!project_) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("render_frame received before load_graph"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
if (!output_pool_ || !output_pool_->is_valid()) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral(
|
|
||||||
"render_frame received before output shm handshake"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::Node *node = find_node(message.node_uuid);
|
|
||||||
if (!node) {
|
|
||||||
return write(
|
|
||||||
error_message(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;
|
|
||||||
if (!requested_input_slots.isEmpty()) {
|
|
||||||
if (!input_pool_ || !input_pool_->is_valid()) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral(
|
|
||||||
"render_frame referenced input slot without input pool"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int requested_slot : requested_input_slots) {
|
|
||||||
if (requested_slot < 0 ||
|
|
||||||
requested_slot >= int(input_pool_->slot_count())) {
|
|
||||||
for (int slot : input_slots) {
|
|
||||||
input_pool_->release(uint32_t(slot));
|
|
||||||
}
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("input slot index out of range"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t consumed_slot = 0;
|
|
||||||
if (!input_pool_->consume(&consumed_slot)) {
|
|
||||||
for (int slot : input_slots) {
|
|
||||||
input_pool_->release(uint32_t(slot));
|
|
||||||
}
|
|
||||||
return write(
|
|
||||||
error_message(QStringLiteral("input slot was not ready"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
if (int(consumed_slot) != requested_slot) {
|
|
||||||
input_pool_->release(consumed_slot);
|
|
||||||
for (int slot : input_slots) {
|
|
||||||
input_pool_->release(uint32_t(slot));
|
|
||||||
}
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("input slot order mismatch"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
input_slots.append(int(consumed_slot));
|
|
||||||
|
|
||||||
const olive::ipc::FrameSlotMeta *meta =
|
|
||||||
input_pool_->meta(consumed_slot);
|
|
||||||
if (meta) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::VideoParams vparams(
|
|
||||||
message.width > 0 ? message.width : k_default_width,
|
|
||||||
message.height > 0 ? message.height : k_default_height,
|
|
||||||
olive::Rational(1, k_default_frame_rate),
|
|
||||||
message.format >= 0 ? olive::PixelFormat::Format(message.format) :
|
|
||||||
olive::PixelFormat::f32,
|
|
||||||
message.channel_count > 0 ? message.channel_count :
|
|
||||||
olive::VideoParams::k_rgba_channel_count);
|
|
||||||
|
|
||||||
olive::RenderTicketPtr ticket = std::make_shared<olive::RenderTicket>();
|
|
||||||
ticket->setProperty("node", olive::QtUtils::ptr_to_value(node));
|
|
||||||
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);
|
|
||||||
ticket->setProperty("usecache", false);
|
|
||||||
ticket->setProperty("channelcount", message.channel_count);
|
|
||||||
ticket->setProperty("mode", olive::RenderMode::Mode(message.mode));
|
|
||||||
ticket->setProperty("type", olive::RenderManager::k_type_video);
|
|
||||||
ticket->setProperty("colormanager", olive::QtUtils::ptr_to_value(
|
|
||||||
project_->color_manager()));
|
|
||||||
|
|
||||||
{
|
|
||||||
olive::ColorProcessorPtr color_output;
|
|
||||||
if (message.has_color_transform) {
|
|
||||||
QString cache_key = QStringLiteral("%1|%2|%3|%4")
|
|
||||||
.arg(message.color_is_display ? 1 : 0)
|
|
||||||
.arg(message.color_output,
|
|
||||||
message.color_view,
|
|
||||||
message.color_look);
|
|
||||||
auto it = color_processor_cache_.find(cache_key);
|
|
||||||
if (it != color_processor_cache_.end()) {
|
|
||||||
color_output = it.value();
|
|
||||||
} else {
|
|
||||||
olive::ColorTransform transform;
|
|
||||||
if (message.color_is_display) {
|
|
||||||
transform = olive::ColorTransform(message.color_output,
|
|
||||||
message.color_view,
|
|
||||||
message.color_look);
|
|
||||||
} else {
|
|
||||||
transform = olive::ColorTransform(message.color_output);
|
|
||||||
}
|
|
||||||
color_output = olive::ColorProcessor::create(
|
|
||||||
project_->color_manager(),
|
|
||||||
project_->color_manager()->get_reference_color_space(),
|
|
||||||
transform);
|
|
||||||
if (color_output) {
|
|
||||||
color_processor_cache_.insert(cache_key, color_output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ticket->setProperty("coloroutput",
|
|
||||||
QVariant::fromValue(color_output));
|
|
||||||
}
|
|
||||||
ticket->setProperty("vparam", QVariant::fromValue(vparams));
|
|
||||||
// The IPC render_frame message carries no audio parameters, but
|
|
||||||
// rendering a sequence that has audio content evaluates audio
|
|
||||||
// tracks with globals.aparams -- an empty AudioParams aborts
|
|
||||||
// (AudioParams::time_to_samples asserts is_valid). Use the render
|
|
||||||
// node's own audio parameters, mirroring the in-process render
|
|
||||||
// path (PreviewAutoCacher uses context->get_audio_params()).
|
|
||||||
olive::AudioParams aparam;
|
|
||||||
if (olive::ViewerOutput *viewer =
|
|
||||||
dynamic_cast<olive::ViewerOutput *>(node)) {
|
|
||||||
aparam = viewer->get_audio_params();
|
|
||||||
}
|
|
||||||
ticket->setProperty("aparam", QVariant::fromValue(aparam));
|
|
||||||
ticket->setProperty("return", olive::RenderManager::k_frame);
|
|
||||||
ticket->setProperty("cache", QString());
|
|
||||||
ticket->setProperty("cachetimebase",
|
|
||||||
QVariant::fromValue(olive::Rational(1)));
|
|
||||||
ticket->setProperty("cacheid", QVariant::fromValue(QUuid()));
|
|
||||||
ticket->setProperty("multicam", olive::QtUtils::ptr_to_value(
|
|
||||||
static_cast<void *>(nullptr)));
|
|
||||||
ticket->setProperty(
|
|
||||||
"ipc_input_pool",
|
|
||||||
// The engine reads this back as the internal implementation object
|
|
||||||
// (olive::engine::internal::ipc::FrameSlotPool), which is exactly
|
|
||||||
// what the C handle points at.
|
|
||||||
olive::QtUtils::ptr_to_value(input_pool_ ?
|
|
||||||
static_cast<void *>(
|
|
||||||
input_pool_->handle()) :
|
|
||||||
static_cast<void *>(nullptr)));
|
|
||||||
QVariantList input_slot_values;
|
|
||||||
for (int slot : input_slots) {
|
|
||||||
input_slot_values.append(slot);
|
|
||||||
}
|
|
||||||
ticket->setProperty("ipc_input_slots", input_slot_values);
|
|
||||||
ticket->setProperty("ipc_input_slot_cursor", 0);
|
|
||||||
ticket->setProperty("ipc_input_slot",
|
|
||||||
input_slots.isEmpty() ? -1 : input_slots.front());
|
|
||||||
|
|
||||||
ticket->start();
|
|
||||||
olive::RenderProcessor::process(ticket, renderer_, nullptr,
|
|
||||||
&shader_cache_);
|
|
||||||
for (int slot : input_slots) {
|
|
||||||
input_pool_->release(uint32_t(slot));
|
|
||||||
}
|
|
||||||
if (!ticket->has_result()) {
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("render produced no frame"), message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::FramePtr frame = ticket->get().value<olive::FramePtr>();
|
|
||||||
if (!frame || !frame->is_allocated()) {
|
|
||||||
return write(error_message(QStringLiteral("render result was empty"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t slot = 0;
|
|
||||||
if (!output_pool_->acquire(&slot)) {
|
|
||||||
return write(
|
|
||||||
error_message(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);
|
|
||||||
log_error(QString("Output frame size") + QString::number(data_size));
|
|
||||||
log_error(QString("Slot size") +
|
|
||||||
QString::number(output_pool_->slot_data_bytes()));
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("rendered frame does not fit output slot "),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(output_pool_->slot_data(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();
|
|
||||||
meta->time_den = frame->timestamp().denominator();
|
|
||||||
meta->width = frame->width();
|
|
||||||
meta->height = frame->height();
|
|
||||||
meta->format = int32_t(frame->format());
|
|
||||||
meta->channel_count = frame->channel_count();
|
|
||||||
meta->linesize = frame->linesize_bytes();
|
|
||||||
meta->data_size = data_size;
|
|
||||||
|
|
||||||
if (!output_pool_->publish(slot)) {
|
|
||||||
output_pool_->release(slot);
|
|
||||||
return write(error_message(
|
|
||||||
QStringLiteral("failed to publish output frame slot"),
|
|
||||||
message.ticket_id));
|
|
||||||
}
|
|
||||||
olive::ipc::FrameReadyMsg ready;
|
|
||||||
ready.ticket_id = message.ticket_id;
|
|
||||||
ready.output_slot = int(slot);
|
|
||||||
return write(ready.to_json());
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::Renderer *renderer_;
|
|
||||||
QFile *out_;
|
|
||||||
bool shutdown_requested_ = false;
|
|
||||||
std::unique_ptr<olive::Project> project_;
|
|
||||||
QHash<QString, olive::Node *> node_by_token_;
|
|
||||||
olive::ipc::SharedMemoryRegion output_region_;
|
|
||||||
std::optional<olive::ipc::FrameSlotPool> output_pool_;
|
|
||||||
olive::ipc::SharedMemoryRegion input_region_;
|
|
||||||
std::optional<olive::ipc::FrameSlotPool> input_pool_;
|
|
||||||
olive::ShaderCache shader_cache_;
|
|
||||||
QHash<QString, olive::ColorProcessorPtr> color_processor_cache_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
// The render worker is a thin shell: all runtime logic (Qt application setup,
|
||||||
|
// render backend initialization, startup handshake and the NDJSON control
|
||||||
|
// loop) lives inside liboakengine behind the pure C ABI, so this executable
|
||||||
|
// imports no engine C++ symbols.
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
return oakengine_worker_main(argc, argv);
|
||||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
|
||||||
install_surface_format();
|
|
||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
HideWorkerDockIcon();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QCoreApplication::setOrganizationName(QStringLiteral("oakvideoeditor.org"));
|
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("oak-render-worker"));
|
|
||||||
|
|
||||||
QString backend = QStringLiteral("opengl");
|
|
||||||
const QStringList args = app.arguments();
|
|
||||||
for (int i = 1; i < args.size(); ++i) {
|
|
||||||
if (args[i] == QStringLiteral("--backend") && i + 1 < args.size()) {
|
|
||||||
backend = args[i + 1].toLower();
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
std::signal(SIGSEGV, print_backtrace);
|
|
||||||
std::signal(SIGABRT, print_backtrace);
|
|
||||||
std::signal(SIGFPE, print_backtrace);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QFile in;
|
|
||||||
QFile out;
|
|
||||||
if (!in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered) ||
|
|
||||||
!out.open(stdout, QIODevice::WriteOnly | QIODevice::Unbuffered)) {
|
|
||||||
log_error(QStringLiteral("failed to open stdio control pipes"));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
olive::Renderer *renderer;
|
|
||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
|
||||||
auto *dynamic_renderer = new olive::DynamicRenderer(backend);
|
|
||||||
if (dynamic_renderer->init()) {
|
|
||||||
dynamic_renderer->post_init();
|
|
||||||
renderer = dynamic_renderer;
|
|
||||||
} else {
|
|
||||||
delete dynamic_renderer;
|
|
||||||
qWarning() << "Failed to initialize dynamic" << backend
|
|
||||||
<< "backend, falling back to direct OpenGL renderer";
|
|
||||||
renderer = new olive::OpenGLRenderer();
|
|
||||||
if (!renderer->init()) {
|
|
||||||
log_error(QStringLiteral("failed to initialize OpenGL renderer"));
|
|
||||||
delete renderer;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
renderer->post_init();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
renderer = new olive::OpenGLRenderer();
|
|
||||||
if (!renderer->Init()) {
|
|
||||||
LogError(QStringLiteral("failed to initialize OpenGL renderer"));
|
|
||||||
delete renderer;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
renderer->PostInit();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Validate the renderer. For OpenGL we check the GL context; for Vulkan we
|
|
||||||
// rely on Init()/PostInit() succeeding (there is no QOpenGLContext).
|
|
||||||
bool renderer_valid = true;
|
|
||||||
QOpenGLContext *ctx = nullptr;
|
|
||||||
if (backend == QStringLiteral("opengl")) {
|
|
||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
|
||||||
if (auto *loaded_renderer =
|
|
||||||
dynamic_cast<olive::DynamicRenderer *>(renderer)) {
|
|
||||||
ctx = loaded_renderer->open_gl_context();
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
ctx = static_cast<olive::OpenGLRenderer *>(renderer)->context();
|
|
||||||
}
|
|
||||||
if (!ctx || !ctx->isValid()) {
|
|
||||||
renderer_valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!renderer_valid) {
|
|
||||||
log_error(QStringLiteral("OpenGL context is not valid after init"));
|
|
||||||
renderer->destroy();
|
|
||||||
renderer->post_destroy();
|
|
||||||
delete renderer;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int exit_code = 0;
|
|
||||||
{
|
|
||||||
RenderWorker worker(renderer, &out);
|
|
||||||
if (!worker.initialize_runtime() || !worker.send_startup_handshake()) {
|
|
||||||
exit_code = 1;
|
|
||||||
} else {
|
|
||||||
QByteArray buffer;
|
|
||||||
while (!worker.shutdown_requested() && !in.atEnd()) {
|
|
||||||
const QByteArray chunk = in.readLine();
|
|
||||||
if (chunk.isEmpty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.append(chunk);
|
|
||||||
while (true) {
|
|
||||||
QJsonObject message;
|
|
||||||
bool ok = true;
|
|
||||||
if (!olive::ipc::read_message(&buffer, &message, &ok)) {
|
|
||||||
if (!ok) {
|
|
||||||
olive::ipc::write_message(
|
|
||||||
&out, error_message(QStringLiteral(
|
|
||||||
"malformed control message")));
|
|
||||||
out.flush();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!worker.handle(message)) {
|
|
||||||
exit_code = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer->destroy();
|
|
||||||
renderer->post_destroy();
|
|
||||||
delete renderer;
|
|
||||||
|
|
||||||
return exit_code;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Oak - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2026 Oak Team
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
***/
|
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
void HideWorkerDockIcon()
|
|
||||||
{
|
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user