diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt
index e13af427d..6bfa5a43b 100644
--- a/app/panel/CMakeLists.txt
+++ b/app/panel/CMakeLists.txt
@@ -17,6 +17,7 @@
add_subdirectory(audiomonitor)
add_subdirectory(curve)
add_subdirectory(footageviewer)
+add_subdirectory(multicam)
add_subdirectory(node)
add_subdirectory(param)
add_subdirectory(pixelsampler)
diff --git a/app/panel/multicam/CMakeLists.txt b/app/panel/multicam/CMakeLists.txt
new file mode 100644
index 000000000..89062f21e
--- /dev/null
+++ b/app/panel/multicam/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2022 Olive 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ panel/multicam/multicampanel.h
+ panel/multicam/multicampanel.cpp
+ PARENT_SCOPE
+)
diff --git a/app/panel/multicam/multicampanel.cpp b/app/panel/multicam/multicampanel.cpp
new file mode 100644
index 000000000..97245437a
--- /dev/null
+++ b/app/panel/multicam/multicampanel.cpp
@@ -0,0 +1,26 @@
+#include "multicampanel.h"
+
+namespace olive {
+
+#define super PanelWidget
+
+MulticamPanel::MulticamPanel(ViewerPanelBase *sibling, QWidget *parent) :
+ super(QStringLiteral("MultiCamPanel"), parent)
+{
+ widget_ = new MulticamWidget();
+ SetWidgetWithPadding(widget_);
+
+ connect(sibling, &ViewerPanelBase::ColorManagerChanged, widget_, &MulticamWidget::ConnectColorManager);
+ widget_->ConnectCacher(static_cast(sibling->GetTimeBasedWidget())->GetCacher());
+
+ Retranslate();
+}
+
+void MulticamPanel::Retranslate()
+{
+ super::Retranslate();
+
+ SetTitle(tr("Multi-Cam"));
+}
+
+}
diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h
new file mode 100644
index 000000000..61a8efed7
--- /dev/null
+++ b/app/panel/multicam/multicampanel.h
@@ -0,0 +1,37 @@
+#ifndef MULTICAMPANEL_H
+#define MULTICAMPANEL_H
+
+#include "panel/viewer/viewerbase.h"
+#include "widget/multicam/multicamwidget.h"
+#include "widget/panel/panel.h"
+
+namespace olive {
+
+class MulticamPanel : public PanelWidget
+{
+ Q_OBJECT
+public:
+ MulticamPanel(ViewerPanelBase *sibling, QWidget* parent = nullptr);
+
+ void SetNode(MultiCamNode *n)
+ {
+ widget_->SetNode(n);
+ }
+
+public slots:
+ void SetTime(const rational &t)
+ {
+ widget_->SetTime(t);
+ }
+
+protected:
+ virtual void Retranslate() override;
+
+private:
+ MulticamWidget *widget_;
+
+};
+
+}
+
+#endif // MULTICAMPANEL_H
diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h
index dff33ccca..4101e6b09 100644
--- a/app/panel/timebased/timebased.h
+++ b/app/panel/timebased/timebased.h
@@ -106,6 +106,8 @@ public:
virtual void Paste() override;
+ TimeBasedWidget* GetTimeBasedWidget() const { return widget_; }
+
public slots:
void SetTimebase(const rational& timebase);
@@ -127,11 +129,6 @@ signals:
void ShuttleRightRequested();
protected:
- TimeBasedWidget* GetTimeBasedWidget() const
- {
- return widget_;
- }
-
void SetTimeBasedWidget(TimeBasedWidget* widget);
virtual void Retranslate() override;
diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp
index 5a9a5a21e..9a0de0898 100644
--- a/app/render/previewautocacher.cpp
+++ b/app/render/previewautocacher.cpp
@@ -62,6 +62,11 @@ PreviewAutoCacher::~PreviewAutoCacher()
}
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
+{
+ return GetSingleFrame(viewer_node_->GetConnectedTextureOutput(), t, dry);
+}
+
+RenderTicketPtr PreviewAutoCacher::GetSingleFrame(Node *n, const rational &t, bool dry)
{
// If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now
CancelQueuedSingleFrameRender();
@@ -71,6 +76,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
sfr->Start();
sfr->setProperty("time", QVariant::fromValue(t));
sfr->setProperty("dry", dry);
+ sfr->setProperty("node", Node::PtrToValue(n));
// Queue it and try to render
single_frame_render_ = sfr;
@@ -611,11 +617,19 @@ void PreviewAutoCacher::TryRender()
single_frame_render_ = nullptr;
// Check if already caching this
- RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(),
- t->property("time").value(),
- nullptr,
- t->property("dry").toBool());
- video_immediate_passthroughs_[watcher].append(t);
+ Node *n = Node::ValueToPtr(t->property("node"));
+ Node *copy = copy_map_.value(n);
+
+ if (copy) {
+ RenderTicketWatcher *watcher = RenderFrame(copy,
+ t->property("time").value(),
+ nullptr,
+ t->property("dry").toBool());
+ video_immediate_passthroughs_[watcher].append(t);
+ } else {
+ qWarning() << "Failed to find copied node for SFR ticket";
+ t->Finish();
+ }
}
if (!pause_renders_) {
diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h
index 05e253851..6f0fc6ac7 100644
--- a/app/render/previewautocacher.h
+++ b/app/render/previewautocacher.h
@@ -50,6 +50,7 @@ public:
virtual ~PreviewAutoCacher() override;
RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false);
+ RenderTicketPtr GetSingleFrame(Node *n, const rational& t, bool dry = false);
RenderTicketPtr GetRangeOfAudio(TimeRange range);
diff --git a/app/shaders/multicam.frag b/app/shaders/multicam.frag
new file mode 100644
index 000000000..f3230598f
--- /dev/null
+++ b/app/shaders/multicam.frag
@@ -0,0 +1,7 @@
+uniform int rows;
+uniform int cols;
+
+void main(void)
+{
+
+}
diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt
index 5f4ab85e6..ef967b7bd 100644
--- a/app/widget/CMakeLists.txt
+++ b/app/widget/CMakeLists.txt
@@ -30,6 +30,7 @@ add_subdirectory(handmovableview)
add_subdirectory(keyframeview)
add_subdirectory(manageddisplay)
add_subdirectory(menu)
+add_subdirectory(multicam)
add_subdirectory(nodecombobox)
add_subdirectory(nodeparamview)
add_subdirectory(nodetableview)
diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp
index 62afcb876..a5446162a 100644
--- a/app/widget/manageddisplay/manageddisplay.cpp
+++ b/app/widget/manageddisplay/manageddisplay.cpp
@@ -275,6 +275,14 @@ void ManagedDisplayWidget::SetInnerMouseTracking(bool e)
}
}
+VideoParams ManagedDisplayWidget::GetViewportParams() const
+{
+ int device_width = width() * devicePixelRatioF();
+ int device_height = height() * devicePixelRatioF();
+ VideoParams::Format device_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt());
+ return VideoParams(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
+}
+
void ManagedDisplayWidget::update()
{
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h
index 644ab0711..c26008e77 100644
--- a/app/widget/manageddisplay/manageddisplay.h
+++ b/app/widget/manageddisplay/manageddisplay.h
@@ -58,7 +58,8 @@ protected:
virtual void initializeGL() override
{
connect(context(), &QOpenGLContext::aboutToBeDestroyed,
- this, &ManagedDisplayWidgetOpenGL::OnDestroy);
+ this, &ManagedDisplayWidgetOpenGL::DestroyListener,
+ Qt::DirectConnection);
emit OnInit();
}
@@ -211,6 +212,8 @@ protected:
return wrapper_ ? wrapper_->rect() : QRect();
}
+ VideoParams GetViewportParams() const;
+
protected slots:
/**
* @brief Called whenever the internal rendering context has been created
diff --git a/app/widget/multicam/CMakeLists.txt b/app/widget/multicam/CMakeLists.txt
new file mode 100644
index 000000000..ab0ea24a3
--- /dev/null
+++ b/app/widget/multicam/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2022 Olive 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ widget/multicam/multicamwidget.cpp
+ widget/multicam/multicamwidget.h
+ PARENT_SCOPE
+)
diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp
new file mode 100644
index 000000000..6fb7fc19b
--- /dev/null
+++ b/app/widget/multicam/multicamwidget.cpp
@@ -0,0 +1,104 @@
+#include "multicamwidget.h"
+
+namespace olive {
+
+#define super ManagedDisplayWidget
+
+MulticamWidget::MulticamWidget(QWidget *parent) :
+ super{parent},
+ node_(nullptr),
+ cacher_(nullptr),
+ tex_(nullptr)
+{
+
+}
+
+void MulticamWidget::SetNode(MultiCamNode *n)
+{
+ if (node_ == n) {
+ return;
+ }
+
+ if (node_) {
+ // Disconnect
+ }
+
+ node_ = n;
+
+ if (node_) {
+ // Connect
+ }
+
+ SetTime(time_);
+}
+
+void MulticamWidget::SetTime(const rational &r)
+{
+ time_ = r;
+
+ if (node_) {
+ if (cacher_) {
+ int sources = node_->InputArraySize(node_->kSourcesInput);
+ watchers_.resize(sources);
+ textures_.resize(sources);
+
+ watchers_.fill(nullptr);
+ textures_.fill(nullptr);
+
+ for (int i=0; iGetConnectedOutput(node_->kSourcesInput, 0)) {
+ auto w = new RenderTicketWatcher(this);
+ connect(w, &RenderTicketWatcher::Finished, this, &MulticamWidget::RenderedFrame);
+ w->SetTicket(cacher_->GetSingleFrame(n, time_, false));
+ watchers_[i] = w;
+ }
+ }
+ }
+ }
+}
+
+void MulticamWidget::OnInit()
+{
+ super::OnInit();
+
+ pipeline_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multicam.frag"))));
+}
+
+void MulticamWidget::OnPaint()
+{
+ // Clear display surface
+ renderer()->ClearDestination();
+
+ if (tex_) {
+ ColorTransformJob job;
+ job.SetColorProcessor(color_service());
+ job.SetInputTexture(tex_);
+ job.SetInputAlphaAssociation(kAlphaNone);
+ renderer()->BlitColorManaged(job, GetViewportParams());
+ }
+}
+
+void MulticamWidget::OnDestroy()
+{
+ pipeline_.clear();
+ tex_ = nullptr;
+
+ super::OnDestroy();
+}
+
+void MulticamWidget::RenderedFrame()
+{
+ auto watcher = static_cast(sender());
+ if (watcher->HasResult()) {
+ int index = watchers_.indexOf(watcher);
+
+ if (index != -1) {
+ if ((textures_[index] = watcher->Get().value())) {
+ update();
+ }
+ }
+ }
+ delete watcher;
+}
+
+}
diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h
new file mode 100644
index 000000000..aa5bf05f0
--- /dev/null
+++ b/app/widget/multicam/multicamwidget.h
@@ -0,0 +1,59 @@
+#ifndef MULTICAMWIDGET_H
+#define MULTICAMWIDGET_H
+
+#include "node/input/multicam/multicamnode.h"
+#include "render/previewautocacher.h"
+#include "widget/manageddisplay/manageddisplay.h"
+
+namespace olive {
+
+class MulticamWidget : public ManagedDisplayWidget
+{
+ Q_OBJECT
+public:
+ explicit MulticamWidget(QWidget *parent = nullptr);
+
+ MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(MulticamWidget)
+
+ void SetNode(MultiCamNode *n);
+
+ void ConnectCacher(PreviewAutoCacher *c)
+ {
+ cacher_ = c;
+ }
+
+public slots:
+ void SetTime(const rational &r);
+
+protected slots:
+ virtual void OnInit() override;
+
+ virtual void OnPaint() override;
+
+ virtual void OnDestroy() override;
+
+signals:
+
+
+private:
+ MultiCamNode *node_;
+
+ QVariant pipeline_;
+
+ PreviewAutoCacher *cacher_;
+
+ rational time_;
+
+ TexturePtr tex_;
+
+ QVector watchers_;
+ QVector textures_;
+
+private slots:
+ void RenderedFrame();
+
+};
+
+}
+
+#endif // MULTICAMWIDGET_H
diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp
index a0ba2eaab..da4136a5f 100644
--- a/app/widget/scope/histogram/histogram.cpp
+++ b/app/widget/scope/histogram/histogram.cpp
@@ -47,10 +47,10 @@ void HistogramScope::OnInit()
void HistogramScope::OnDestroy()
{
- super::OnDestroy();
-
pipeline_secondary_.clear();
texture_row_sums_ = nullptr;
+
+ super::OnDestroy();
}
ShaderCode HistogramScope::GenerateShaderCode()
diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp
index bedcd986d..d758f0b42 100644
--- a/app/widget/scope/scopebase/scopebase.cpp
+++ b/app/widget/scope/scopebase/scopebase.cpp
@@ -52,9 +52,7 @@ void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline)
job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
- renderer()->Blit(pipeline, job, VideoParams(width(), height(),
- static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
- VideoParams::kInternalChannelCount));
+ renderer()->Blit(pipeline, job, GetViewportParams());
}
void ScopeBase::OnInit()
@@ -89,11 +87,11 @@ void ScopeBase::OnPaint()
void ScopeBase::OnDestroy()
{
- super::OnDestroy();
-
managed_tex_ = nullptr;
texture_ = nullptr;
pipeline_.clear();
+
+ super::OnDestroy();
}
}
diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp
index bb6902a64..2f8f46407 100644
--- a/app/widget/scope/waveform/waveform.cpp
+++ b/app/widget/scope/waveform/waveform.cpp
@@ -72,9 +72,7 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline)
job.Insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
- renderer()->Blit(pipeline, job, VideoParams(width(), height(),
- static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
- VideoParams::kInternalChannelCount));
+ renderer()->Blit(pipeline, job, GetViewportParams());
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h
index 4c3a18551..734132dda 100644
--- a/app/widget/viewer/viewer.h
+++ b/app/widget/viewer/viewer.h
@@ -100,6 +100,8 @@ public:
enable_audio_scrubbing_ = e;
}
+ PreviewAutoCacher *GetCacher() const { return auto_cacher_; }
+
public slots:
void Play(bool in_to_out_only);
diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp
index 23d41efb1..27ea8bad4 100644
--- a/app/widget/viewer/viewerdisplay.cpp
+++ b/app/widget/viewer/viewerdisplay.cpp
@@ -361,10 +361,7 @@ void ViewerDisplayWidget::OnPaint()
// We only draw if we have a pipeline
if (push_mode_ != kPushNull) {
// Draw texture through color transform
- int device_width = width() * devicePixelRatioF();
- int device_height = height() * devicePixelRatioF();
- VideoParams::Format device_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt());
- VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
+ VideoParams device_params = GetViewportParams();
if (push_mode_ == kPushBlank) {
if (blank_shader_.isNull()) {
diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp
index 5b76ed1b0..9a336951e 100644
--- a/app/window/mainwindow/mainwindow.cpp
+++ b/app/window/mainwindow/mainwindow.cpp
@@ -87,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent) :
param_panel_ = new ParamPanel(this);
curve_panel_ = new CurvePanel(this);
sequence_viewer_panel_ = new SequenceViewerPanel(this);
+ multicam_panel_ = new MulticamPanel(sequence_viewer_panel_, this);
pixel_sampler_panel_ = new PixelSamplerPanel(this);
AppendProjectPanel();
tool_panel_ = new ToolPanel(this);
@@ -107,10 +108,13 @@ MainWindow::MainWindow(QWidget *parent) :
// Connect time signals together
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime);
+ connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime);
connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime);
+ connect(param_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime);
connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime);
+ connect(curve_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime);
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged);
@@ -489,6 +493,20 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks)
if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
UpdateNodePanelContextFromTimelinePanel(panel);
+
+ bool found = false;
+ for (Block *b : blocks) {
+ if (ClipBlock *c = dynamic_cast(b)) {
+ if (MultiCamNode *m = dynamic_cast(c->GetConnectedOutput(c->kBufferIn))) {
+ multicam_panel_->SetNode(m);
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ multicam_panel_->SetNode(nullptr);
+ }
}
}
@@ -599,6 +617,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel()
connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime);
connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
+ connect(panel, &TimelinePanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime);
connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture);
connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject);
@@ -837,6 +856,10 @@ void MainWindow::SetDefaultLayout()
scope_panel_->setFloating(true);
addDockWidget(Qt::TopDockWidgetArea, scope_panel_);
+ multicam_panel_->hide();
+ multicam_panel_->setFloating(true);
+ addDockWidget(Qt::TopDockWidgetArea, multicam_panel_);
+
sequence_viewer_panel_->show();
addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_);
diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h
index 1f6504c6f..892d2ca54 100644
--- a/app/window/mainwindow/mainwindow.h
+++ b/app/window/mainwindow/mainwindow.h
@@ -25,6 +25,7 @@
#include "mainwindowlayoutinfo.h"
#include "node/project/project.h"
+#include "panel/multicam/multicampanel.h"
#include "panel/panelmanager.h"
#include "panel/audiomonitor/audiomonitor.h"
#include "panel/curve/curve.h"
@@ -163,6 +164,7 @@ private:
PixelSamplerPanel* pixel_sampler_panel_;
ScopePanel* scope_panel_;
QMap viewer_panels_;
+ MulticamPanel *multicam_panel_;
#ifdef Q_OS_WINDOWS
unsigned int taskbar_btn_id_;