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_;