diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 857057649..8841062c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ on: env: DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60 UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60 + CMAKE_ARGS: -DUSE_WERROR=ON -DBUILD_TESTS=ON jobs: linux: @@ -73,7 +74,8 @@ jobs: cmake .. -G "${{ matrix.cmake-gen }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" + -DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" \ + $CMAKE_ARGS - name: Build working-directory: build @@ -204,7 +206,8 @@ jobs: working-directory: ${{ runner.workspace }}/build run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \ - -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \ + $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build @@ -356,7 +359,8 @@ jobs: PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \ - -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" + -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" \ + $CMAKE_ARGS - name: Build working-directory: ${{ runner.workspace }}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index b46d11f6f..1206118dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,8 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR) project(olive-editor VERSION 0.2.0 LANGUAGES CXX) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) -option(BUILD_TESTS "Build unit tests" ON) -option(USE_WERROR "Error on compile warning" ON) +option(BUILD_TESTS "Build unit tests" OFF) +option(USE_WERROR "Error on compile warning" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index fd845737e..c98cb5c2a 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -43,6 +43,12 @@ Decoder::Decoder() : UpdateLastAccessed(); } +void Decoder::IncrementAccessTime(qint64 t) +{ + QMutexLocker locker(&mutex_); + last_accessed_ += t; +} + bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 08ccf6e32..665f21cc8 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -87,6 +87,8 @@ public: virtual bool SupportsVideo(){return false;} virtual bool SupportsAudio(){return false;} + void IncrementAccessTime(qint64 t); + class CodecStream { public: diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 2ab14eadb..c2dbe2488 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -513,6 +513,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can } + desc.SetStreamCount(fmt_ctx->nb_streams); + } // Free all memory diff --git a/app/codec/footagemeta.h b/app/codec/footagemeta.h deleted file mode 100644 index 4d23025f1..000000000 --- a/app/codec/footagemeta.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef FOOTAGEMETA_H -#define FOOTAGEMETA_H - -struct FootageData { - struct StreamData { - - }; -}; - -#endif // FOOTAGEMETA_H diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e9a84ca28..7c7dd8878 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -73,7 +73,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance bool stream_enabled = true; - for (int i=0; in->seek_subimage(i, 0); i++) { + int i; + for (i=0; in->seek_subimage(i, 0); i++) { OIIO::ImageSpec spec = in->spec(); VideoParams video_params = GetVideoParamsFromImageSpec(spec); @@ -104,6 +105,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance desc.AddVideoStream(video_params); } + desc.SetStreamCount(i); + // If we're here, we have a successful image open in->close(); diff --git a/app/core.cpp b/app/core.cpp index c9b701ead..d6989319a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -586,6 +586,8 @@ void Core::ImportTaskComplete(Task* task) } undo_stack_.pushIfHasChildren(command); + + main_window_->SelectFootage(import_task->GetImportedFootage()); } bool Core::ConfirmImageSequence(const QString& filename) diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp index 57851f424..2d08ceacd 100644 --- a/app/dialog/export/exportadvancedvideodialog.cpp +++ b/app/dialog/export/exportadvancedvideodialog.cpp @@ -35,7 +35,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList &pix_f pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0); yuv_color_range_combobox_ = new QComboBox(); - yuv_color_range_combobox_->addItems({tr("MPEG (16-235)"), tr("JPEG (0-255)")}); + yuv_color_range_combobox_->addItems({tr("Limited (16-235)"), tr("Full (0-255)")}); pixel_layout->addWidget(yuv_color_range_combobox_, row, 1); } diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 24d199811..c839c93f0 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -22,12 +22,9 @@ #include -#include "common/range.h" -#include "core.h" -#include "node/traverser.h" - namespace olive { +const QString TransformDistortNode::kParentInput = QStringLiteral("parent_in"); const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in"); const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in"); const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in"); @@ -36,6 +33,8 @@ const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interp TransformDistortNode::TransformDistortNode() { + AddInput(kParentInput, NodeValue::kMatrix); + AddInput(kAutoscaleInput, NodeValue::kCombo, 0); AddInput(kInterpolationInput, NodeValue::kCombo, 2); @@ -73,6 +72,7 @@ void TransformDistortNode::Retranslate() { super::Retranslate(); + SetInputName(kParentInput, tr("Parent")); SetInputName(kAutoscaleInput, tr("Auto-Scale")); SetInputName(kTextureInput, tr("Texture")); SetInputName(kInterpolationInput, tr("Interpolation")); @@ -84,12 +84,12 @@ void TransformDistortNode::Retranslate() void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Generate matrix - QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false); + QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false, value[kParentInput].toMatrix()); // Pop texture NodeValue texture_meta = value[kTextureInput]; - bool pushed_job = false; + QVariant job_to_push; // If we have a texture, generate a matrix and make it happen if (TexturePtr texture = texture_meta.toTexture()) { @@ -103,15 +103,17 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].toInt())); - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); - - pushed_job = true; + job_to_push = QVariant::fromValue(job); } } - if (!pushed_job) { + table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this); + + if (job_to_push.isNull()) { // Re-push whatever value we received table->Push(texture_meta); + } else { + table->Push(NodeValue::kTexture, job_to_push, this); } } @@ -129,7 +131,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou if (gizmo == anchor_gizmo_) { - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false, row[kParentInput].toMatrix()).toTransform().inverted(); } else if (IsAScaleGizmo(gizmo)) { @@ -171,7 +173,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } // Store current matrix - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true, row[kParentInput].toMatrix()).toTransform().inverted(); } else if (gizmo == rotation_gizmo_) { @@ -356,7 +358,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Fold values into a matrix for the rectangle QMatrix4x4 rectangle_matrix; rectangle_matrix.scale(sequence_half_res); - rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false), + rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, tex_offset, @@ -376,7 +378,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Draw anchor point QMatrix4x4 anchor_matrix; anchor_matrix.scale(sequence_half_res); - anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false), + anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()), sequence_res, tex_sz, tex_offset, @@ -402,7 +404,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { if (TexturePtr texture = row[kTextureInput].toTexture()) { - auto m = GenerateMatrix(row, false, false, false); + auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()); return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform(); } return super::GizmoTransformation(row, globals); diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index b6ae714b9..be17de561 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -84,6 +84,7 @@ public: virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override; + static const QString kParentInput; static const QString kTextureInput; static const QString kAutoscaleInput; static const QString kInterpolationInput; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 81e43c4ea..81dbdb8f6 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -90,11 +90,11 @@ void MatrixGenerator::Retranslate() void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { // Push matrix output - QMatrix4x4 mat = GenerateMatrix(value, false, false, false); + QMatrix4x4 mat = GenerateMatrix(value, false, false, false, QMatrix4x4()); table->Push(NodeValue::kMatrix, mat, this); } -QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const +QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const { QVector2D anchor; QVector2D position; @@ -116,17 +116,17 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignor value[kRotationInput].toDouble(), scale, value[kUniformScaleInput].toBool(), - anchor); + anchor, + mat); } QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, const float& rot, const QVector2D& scale, bool uniform_scale, - const QVector2D& anchor) + const QVector2D& anchor, + QMatrix4x4 mat) { - QMatrix4x4 mat; - // Position mat.translate(pos); diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 5d93cb51d..60ea1a035 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -53,12 +53,13 @@ public: static const QString kAnchorInput; protected: - QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const; + QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const; static QMatrix4x4 GenerateMatrix(const QVector2D &pos, const float &rot, const QVector2D &scale, bool uniform_scale, - const QVector2D &anchor); + const QVector2D &anchor, + QMatrix4x4 mat); virtual void InputValueChangedEvent(const QString& input, int element) override; diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 09b0ac3d5..8d26f4bd3 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -126,7 +126,7 @@ public: return InputArraySize(kSubtitleParamsInput); } - int GetTotalStreamCount() const + virtual int GetTotalStreamCount() const { return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount(); } diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index 3f8ffe700..4bcd7cabc 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -73,6 +73,21 @@ bool Folder::ChildExistsWithName(const QString &s) const return ChildExistsWithNameInternal(this, s); } +bool Folder::HasChildRecursive(Node *child) const +{ + for (Node *i : item_children_) { + if (i == child) { + return true; + } else if (Folder *f = dynamic_cast(i)) { + if (f->HasChildRecursive(child)) { + return true; + } + } + } + + return false; +} + int Folder::index_of_child_in_array(Node *item) const { int index_of_item = item_children_.indexOf(item); diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 7274960f1..22d25f110 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -65,6 +65,8 @@ public: bool ChildExistsWithName(const QString& s) const; + bool HasChildRecursive(Node *child) const; + int item_child_count() const { return item_children_.size(); diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 7b8870ee2..7c89f3496 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -44,7 +44,8 @@ Footage::Footage(const QString &filename) : ViewerOutput(false, false), timestamp_(0), valid_(false), - cancelled_(nullptr) + cancelled_(nullptr), + total_stream_count_(0) { SetCacheTextures(true); @@ -125,6 +126,9 @@ void Footage::Clear() // Clear decoder link decoder_.clear(); + // Clear total stream count + total_stream_count_ = 0; + // Reset ready state valid_ = false; } @@ -497,6 +501,8 @@ void Footage::Reprobe() SetStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)), i); } + total_stream_count_ = footage_info.GetStreamCount(); + SetValid(); } diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 8397726a2..7eb38f3c5 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -180,6 +180,8 @@ public: virtual qint64 creation_time() const override; virtual qint64 mod_time() const override; + virtual int GetTotalStreamCount() const override { return total_stream_count_; } + static const QString kFilenameInput; protected: @@ -224,6 +226,8 @@ private: CancelAtom *cancelled_; + int total_stream_count_; + private slots: void CheckFootage(); diff --git a/app/node/project/footage/footagedescription.cpp b/app/node/project/footage/footagedescription.cpp index 610db5c39..742372586 100644 --- a/app/node/project/footage/footagedescription.cpp +++ b/app/node/project/footage/footagedescription.cpp @@ -43,9 +43,11 @@ bool FootageDescription::Load(const QString &filename) // Default to first version of metadata (which wasn't versioned at all) unsigned version = 1; - XMLAttributeLoop((&reader), attr) { - if (attr.name() == QStringLiteral("version")) { - version = attr.value().toUInt(); + { + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("version")) { + version = attr.value().toUInt(); + } } } @@ -58,6 +60,14 @@ bool FootageDescription::Load(const QString &filename) if (reader.name() == QStringLiteral("decoder")) { decoder_ = reader.readElementText(); } else if (reader.name() == QStringLiteral("streams")) { + { + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("count")) { + total_stream_count_ = attr.value().toInt(); + } + } + } + while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("video")) { VideoParams vp; @@ -116,6 +126,8 @@ bool FootageDescription::Save(const QString &filename) const writer.writeStartElement(QStringLiteral("streams")); + writer.writeAttribute(QStringLiteral("count"), QString::number(total_stream_count_)); + foreach (const VideoParams& vp, video_streams_) { writer.writeStartElement(QStringLiteral("video")); vp.Save(&writer); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index 930a6e8be..c05849273 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -32,7 +32,8 @@ class FootageDescription { public: FootageDescription(const QString& decoder = QString()) : - decoder_(decoder) + decoder_(decoder), + total_stream_count_(0) { } @@ -118,6 +119,9 @@ public: return StreamIsVideo(index) || StreamIsAudio(index) || StreamIsSubtitle(index); } + int GetStreamCount() const { return total_stream_count_; } + void SetStreamCount(int s) { total_stream_count_ = s; } + bool Load(const QString& filename); bool Save(const QString& filename) const; @@ -138,7 +142,7 @@ public: } private: - static constexpr unsigned kFootageMetaVersion = 2; + static constexpr unsigned kFootageMetaVersion = 4; QString decoder_; @@ -148,6 +152,8 @@ private: QVector subtitle_streams_; + int total_stream_count_; + }; } diff --git a/app/panel/project/project.h b/app/panel/project/project.h index 39f6299ee..d4918b7b3 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -52,9 +52,9 @@ public: ProjectViewModel* model() const; - bool SelectItem(Node *n) + bool SelectItem(Node *n, bool deselect_all_first = true) { - return explorer_->SelectItem(n); + return explorer_->SelectItem(n, deselect_all_first); } virtual void SelectAll() override; diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 5078b963c..8c6f2a703 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -78,9 +78,9 @@ private: #define PRINT_GL_ERRORS ErrorPrinter __e(__FUNCTION__, functions_) #define GL_PREAMBLE \ - QMutexLocker __l(&global_opengl_mutex); + //QMutexLocker __l(&global_opengl_mutex); -QMutex global_opengl_mutex; +//QMutex global_opengl_mutex; OpenGLRenderer::OpenGLRenderer(QObject* parent) : Renderer(parent), @@ -107,7 +107,7 @@ void OpenGLRenderer::Init(QOpenGLContext *existing_ctx) bool OpenGLRenderer::Init() { - QMutexLocker locker(&global_opengl_mutex); + GL_PREAMBLE; if (context_) { qCritical() << "Can't initialize already initialized OpenGLRenderer"; @@ -177,7 +177,7 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub GL_PREAMBLE; if (texture) { - AttachTextureAsDestination(texture); + AttachTextureAsDestination(texture->id()); } ClearDestinationInternal(r, g, b, a); @@ -187,16 +187,11 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub } } -QVariant OpenGLRenderer::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) +QVariant OpenGLRenderer::CreateNativeTexture(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) { GL_PREAMBLE; - return CreateNativeTexture2DInternal(width, height, format, channel_count, data, linesize); -} - -QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - GL_PREAMBLE; + bool is_3d = depth > 1; // Generate new texture GLuint texture; @@ -205,23 +200,32 @@ QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + GLenum target_current = is_3d ? GL_TEXTURE_BINDING_3D : GL_TEXTURE_BINDING_2D; + GLenum target = is_3d ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex); + functions_->glGetIntegerv(target_current, ¤t_tex); - functions_->glBindTexture(GL_TEXTURE_3D, texture); + functions_->glBindTexture(target, texture); - context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count), - width, height, depth, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); + if (is_3d) { + context_->extraFunctions()->glTexImage3D(target, 0, GetInternalFormat(format, channel_count), + width, height, depth, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); + } else { + functions_->glTexImage2D(target, 0, GetInternalFormat(format, channel_count), + width, height, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); + } functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - functions_->glBindTexture(GL_TEXTURE_3D, current_tex); + functions_->glBindTexture(target, current_tex); return texture; } -void OpenGLRenderer::AttachTextureAsDestination(Texture* texture) +void OpenGLRenderer::AttachTextureAsDestination(const QVariant &texture) { PRINT_GL_ERRORS; @@ -229,7 +233,7 @@ void OpenGLRenderer::AttachTextureAsDestination(Texture* texture) functions_->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - texture->id().value(), + texture.value(), 0); } @@ -287,15 +291,16 @@ void OpenGLRenderer::DestroyNativeShader(QVariant shader) functions_->glDeleteProgram(program); } -void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int linesize) +void OpenGLRenderer::UploadToTexture(const QVariant &handle, const VideoParams &p, const void *data, int linesize) { GL_PREAMBLE; - GLuint t = texture->id().value(); - const VideoParams& p = texture->params(); + GLuint t = handle.value(); - GLenum tex_type = texture->type() == Texture::k2D ? GL_TEXTURE_2D : GL_TEXTURE_3D; - GLenum tex_binding = texture->type() == Texture::k2D ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D; + bool is_3d = p.is_3d(); + + GLenum tex_type = !is_3d ? GL_TEXTURE_2D : GL_TEXTURE_3D; + GLenum tex_binding = !is_3d ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D; // Store currently bound texture so it can be restored later GLint current_tex; @@ -308,7 +313,7 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin { PRINT_GL_ERRORS; - if (texture->type() == Texture::k2D) { + if (!is_3d) { functions_->glTexSubImage2D(tex_type, 0, 0, 0, p.effective_width(), p.effective_height(), GetPixelFormat(p.channel_count()), GetPixelType(p.format()), @@ -326,16 +331,14 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin functions_->glBindTexture(tex_type, current_tex); } -void OpenGLRenderer::DownloadFromTexture(Texture* texture, void *data, int linesize) +void OpenGLRenderer::DownloadFromTexture(const QVariant &id, const VideoParams &p, void *data, int linesize) { GL_PREAMBLE; - const VideoParams& p = texture->params(); - GLint current_tex; functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); - AttachTextureAsDestination(texture); + AttachTextureAsDestination(id); functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize); @@ -366,7 +369,7 @@ void OpenGLRenderer::Flush() Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) { - AttachTextureAsDestination(texture); + AttachTextureAsDestination(texture->id()); QByteArray data(VideoParams::GetBytesPerPixel(texture->format(), texture->channel_count()), Qt::Uninitialized); @@ -502,7 +505,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video functions_->glActiveTexture(GL_TEXTURE0 + i); - GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D; functions_->glBindTexture(target, tex_id); if (tex_id) { @@ -584,11 +587,11 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video TexturePtr output_tex, input_tex; if (real_iteration_count > 1) { // Create one texture to bounce off - output_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params); + output_tex = CreateTexture(destination_params); if (real_iteration_count > 2) { // Create a second texture bounce off - input_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params); + input_tex = CreateTexture(destination_params); } } @@ -604,7 +607,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // This is the last iteration, draw to the destination if (destination) { // If we have a destination texture, draw to it - AttachTextureAsDestination(destination); + AttachTextureAsDestination(destination->id()); } else if (iteration > 0) { // Otherwise, if we were iterating before, detach texture now DetachTextureAsDestination(); @@ -616,7 +619,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video } } else { // Always draw to output_tex, which gets swapped with input_tex every iteration - AttachTextureAsDestination(output_tex.get()); + AttachTextureAsDestination(output_tex->id()); } if (iteration > 0) { @@ -648,7 +651,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // Release any textures we bound before for (int i=textures_to_bind.size()-1; i>=0; i--) { TexturePtr texture = textures_to_bind.at(i).texture; - GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D; functions_->glActiveTexture(GL_TEXTURE0 + i); functions_->glBindTexture(target, 0); } @@ -788,38 +791,6 @@ void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, doub functions_->glClear(GL_COLOR_BUFFER_BIT); } -QVariant OpenGLRenderer::CreateNativeTexture2DInternal(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) -{ - GLuint texture; - functions_->glGenTextures(1, &texture); - texture_params_.insert(texture, {width, height, 1, format, channel_count}); - - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); - - GLint current_tex; - functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); - - functions_->glBindTexture(GL_TEXTURE_2D, texture); - - { - PRINT_GL_ERRORS; - functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count), - width, height, 0, GetPixelFormat(channel_count), - GetPixelType(format), data); - } - - functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - functions_->glBindTexture(GL_TEXTURE_2D, current_tex); - - return texture; -} - -QVariant OpenGLRenderer::CreateNativeTexture2DInternal(const VideoParams ¶ms, const void *data, int linesize) -{ - return CreateNativeTexture2DInternal(params.effective_width(), params.effective_height(), params.format(), params.channel_count(), data, linesize); -} - GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code) { static const QString shader_preamble = diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h index 2e51480b1..fe7f0098a 100644 --- a/app/render/opengl/openglrenderer.h +++ b/app/render/opengl/openglrenderer.h @@ -47,37 +47,35 @@ public: virtual void PostDestroy() override; -public slots: virtual void PostInit() override; - virtual void DestroyInternal() override; - virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) override; - virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; - - virtual void DestroyNativeTexture(QVariant texture) override; - virtual QVariant CreateNativeShader(olive::ShaderCode code) override; virtual void DestroyNativeShader(QVariant shader) override; - virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) override; + virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) override; - virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) override; + virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) override; virtual void Flush() override; virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) override; -protected slots: +protected: virtual void Blit(QVariant shader, olive::ShaderJob job, olive::Texture* destination, olive::VideoParams destination_params, bool clear_destination) override; + virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + + virtual void DestroyNativeTexture(QVariant texture) override; + + virtual void DestroyInternal() override; + private: static GLint GetInternalFormat(VideoParams::Format format, int channel_layout); @@ -85,7 +83,7 @@ private: static GLenum GetPixelFormat(int channel_count); - void AttachTextureAsDestination(olive::Texture* texture); + void AttachTextureAsDestination(const QVariant &texture); void DetachTextureAsDestination(); @@ -93,9 +91,6 @@ private: void ClearDestinationInternal(double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0); - QVariant CreateNativeTexture2DInternal(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0); - QVariant CreateNativeTexture2DInternal(const VideoParams ¶ms, const void* data = nullptr, int linesize = 0); - GLuint CompileShader(GLenum type, const QString &code); QOpenGLContext* context_; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index d5707c845..e876ddafa 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -61,7 +61,7 @@ PreviewAutoCacher::~PreviewAutoCacher() SetViewerNode(nullptr); } -RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry) { // If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now CancelQueuedSingleFrameRender(); @@ -70,6 +70,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) auto sfr = std::make_shared(); sfr->Start(); sfr->setProperty("time", QVariant::fromValue(t)); + sfr->setProperty("dry", dry); // Queue it and try to render single_frame_render_ = sfr; @@ -566,7 +567,8 @@ void PreviewAutoCacher::TryRender() if (single_frame_render_) { // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), - nullptr); + nullptr, + single_frame_render_->property("dry").toBool()); video_immediate_passthroughs_[watcher].append(single_frame_render_); single_frame_render_ = nullptr; @@ -583,7 +585,7 @@ void PreviewAutoCacher::TryRender() // We want this hash, if we're not already rendering, start render now if (!render_task) { // Don't render any hash more than once - RenderFrame(t, viewer_node_->video_frame_cache()); + RenderFrame(t, viewer_node_->video_frame_cache(), false); } emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); @@ -609,7 +611,7 @@ void PreviewAutoCacher::TryRender() } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache, bool dry) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); @@ -623,7 +625,8 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderMode::kOffline, cache, - RenderManager::kTexture)); + dry ? RenderManager::kNull : RenderManager::kTexture)); + return watcher; } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index c9343021f..0ce429673 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -49,7 +49,7 @@ public: virtual ~PreviewAutoCacher() override; - RenderTicketPtr GetSingleFrame(const rational& t); + RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false); RenderTicketPtr GetRangeOfAudio(TimeRange range); @@ -98,10 +98,10 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache); - RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache) + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache, bool dry); + RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache, bool dry) { - return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache); + return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache, dry); } RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms); diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index e174a4b35..85f3aed69 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -20,36 +20,62 @@ #include "renderer.h" +#include +#include #include -#include "common/ocioutils.h" - namespace olive { Renderer::Renderer(QObject *parent) : QObject(parent) { - -} - -TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, Texture::Type type, const void *data, int linesize) -{ - QVariant v; - - if (type == Texture::k3D) { - v = CreateNativeTexture3D(params.effective_width(), params.effective_height(), - params.effective_depth(), params.format(), params.channel_count(), data, linesize); - } else { - v = CreateNativeTexture2D(params.effective_width(), params.effective_height(), params.format(), - params.channel_count(), data, linesize); - } - - return CreateTextureFromNativeHandle(v, params, type); } TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, int linesize) { - return CreateTexture(params, Texture::k2D, data, linesize); + QVariant v; + + if (USE_TEXTURE_CACHE) { + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + if (it->width == params.effective_width() + && it->height == params.effective_height() + && it->depth == params.effective_depth() + && it->format == params.format() + && it->channel_count == params.channel_count()) { + v = it->handle; + texture_cache_.erase(it); + break; + } + } + } + + if (v.isNull()) { + v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(), + params.format(), params.channel_count(), data, linesize); + } else if (data) { + UploadToTexture(v, params, data, linesize); + } else { + this->Flush(); + } + + return CreateTextureFromNativeHandle(v, params); +} + +void Renderer::DestroyTexture(Texture *texture) +{ + if (USE_TEXTURE_CACHE) { + texture_cache_.push_back({texture->params().effective_width(), + texture->params().effective_height(), + texture->params().effective_depth(), + texture->params().format(), + texture->params().channel_count(), + texture->id(), + QDateTime::currentMSecsSinceEpoch()}); + + ClearOldTextures(); + } else { + DestroyNativeTexture(texture->id()); + } } TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms) @@ -95,16 +121,21 @@ void Renderer::Destroy() interlace_texture_.clear(); } + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) { + DestroyNativeTexture(it->handle); + } + texture_cache_.clear(); + DestroyInternal(); } -TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type) +TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms) { if (v.isNull()) { return nullptr; } - return std::make_shared(this, v, params, type); + return std::make_shared(this, v, params); } bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::ColorContext *ctx) @@ -175,8 +206,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } // Allocate 3D LUT - color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), - Texture::k3D, values); + color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), values); color_ctx.lut3d_textures[i].name = sampler_name; color_ctx.lut3d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; } @@ -206,9 +236,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } // Allocate 1D LUT - color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), - Texture::k2D, - values); + color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), values); color_ctx.lut1d_textures[i].name = sampler_name; color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; } @@ -219,6 +247,18 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col } } +void Renderer::ClearOldTextures() +{ + for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); ) { + if (it->accessed < QDateTime::currentMSecsSinceEpoch() - MAX_TEXTURE_LIFE) { + DestroyNativeTexture(it->handle); + it = texture_cache_.erase(it); + } else { + it++; + } + } +} + void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *destination, const VideoParams ¶ms) { ColorContext color_ctx; diff --git a/app/render/renderer.h b/app/render/renderer.h index 839be32cf..96141df4e 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -45,9 +45,10 @@ public: virtual bool Init() = 0; - TexturePtr CreateTexture(const VideoParams& params, Texture::Type type, const void* data = nullptr, int linesize = 0); TexturePtr CreateTexture(const VideoParams& params, const void *data = nullptr, int linesize = 0); + void DestroyTexture(Texture *texture); + void BlitToTexture(QVariant shader, olive::ShaderJob job, olive::Texture* destination, @@ -82,39 +83,34 @@ public: virtual void PostDestroy() = 0; -public slots: virtual void PostInit() = 0; - virtual void DestroyInternal() = 0; - virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) = 0; - virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; - virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; - - virtual void DestroyNativeTexture(QVariant texture) = 0; - virtual QVariant CreateNativeShader(olive::ShaderCode code) = 0; virtual void DestroyNativeShader(QVariant shader) = 0; - virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) = 0; + virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) = 0; - virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) = 0; + virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) = 0; virtual void Flush() = 0; virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) = 0; -protected slots: +protected: virtual void Blit(QVariant shader, olive::ShaderJob job, olive::Texture* destination, olive::VideoParams destination_params, bool clear_destination) = 0; -protected: - TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type = Texture::k2D); + virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; + + virtual void DestroyNativeTexture(QVariant texture) = 0; + + virtual void DestroyInternal() = 0; private: struct ColorContext { @@ -130,10 +126,29 @@ private: }; + TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms); + bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx); + void ClearOldTextures(); + QHash color_cache_; + struct CachedTexture + { + int width; + int height; + int depth; + VideoParams::Format format; + int channel_count; + QVariant handle; + qint64 accessed; + }; + + static const int MAX_TEXTURE_LIFE = 5000; + static const bool USE_TEXTURE_CACHE = true; + std::list texture_cache_; + QMutex color_cache_mutex_; QVariant default_shader_; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 202820be5..1df5590b3 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -35,6 +35,7 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; +const rational RenderManager::kDryRunInterval = rational(10); RenderManager::RenderManager(QObject *parent) : backend_(kOpenGL), @@ -52,9 +53,11 @@ RenderManager::RenderManager(QObject *parent) : if (context_) { video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this); + dry_run_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); video_thread_->start(QThread::IdlePriority); + dry_run_thread_->start(QThread::IdlePriority); audio_thread_->start(QThread::IdlePriority); } @@ -73,6 +76,9 @@ RenderManager::~RenderManager() video_thread_->quit(); video_thread_->wait(); + dry_run_thread_->quit(); + dry_run_thread_->wait(); + context_->PostDestroy(); delete context_; @@ -129,7 +135,11 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid())); } - video_thread_->AddTicket(ticket); + if (return_type == ReturnType::kNull) { + dry_run_thread_->AddTicket(ticket); + } else { + video_thread_->AddTicket(ticket); + } return ticket; } @@ -157,6 +167,8 @@ bool RenderManager::RemoveTicket(RenderTicketPtr ticket) return true; } else if (audio_thread_->RemoveTicket(ticket)) { return true; + } else if (dry_run_thread_->RemoveTicket(ticket)) { + return true; } else { return false; } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index a75c58aaf..891e2fffc 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -97,9 +97,12 @@ public: enum ReturnType { kTexture, - kFrame + kFrame, + kNull }; + static const rational kDryRunInterval; + /** * @brief Asynchronously generate a frame at a given time * @@ -168,6 +171,7 @@ private: QTimer *decoder_clear_timer_; RenderThread *video_thread_; + RenderThread *dry_run_thread_; RenderThread *audio_thread_; private slots: diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index c8617e095..0a6d78078 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -123,7 +123,7 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time render_ctx_->Flush(); - render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels()); + render_ctx_->DownloadFromTexture(texture->id(), texture->params(), frame->data(), frame->linesize_pixels()); } return frame; @@ -151,53 +151,57 @@ void RenderProcessor::Run() TexturePtr texture = GenerateTexture(time, frame_length); - if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) { - // Get next between frame and interlace it - TexturePtr top = texture; - TexturePtr bottom = GenerateTexture(time + frame_length, frame_length); - - if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) { - std::swap(top, bottom); - } - - texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams()); - } - - if (HeardCancel()) { - // Finish cancelled ticket with nothing since we can't guarantee the frame we generated - // is actually "complete + if (!render_ctx_) { ticket_->Finish(); } else { - RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt()); + if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) { + // Get next between frame and interlace it + TexturePtr top = texture; + TexturePtr bottom = GenerateTexture(time + frame_length, frame_length); - FramePtr frame; - QString cache = ticket_->property("cache").toString(); - - if (return_type == RenderManager::kFrame || !cache.isEmpty()) { - // Convert to CPU frame - frame = GenerateFrame(texture, time); - - // Save to cache if requested - if (!cache.isEmpty()) { - rational timebase = ticket_->property("cachetimebase").value(); - QUuid uuid = ticket_->property("cacheuuid").value(); - bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame); - ticket_->setProperty("cached", cache_result); + if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) { + std::swap(top, bottom); } + + texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams()); } - if (return_type == RenderManager::kTexture) { - // Return GPU texture - if (!texture) { - texture = render_ctx_->CreateTexture(GetCacheVideoParams()); - render_ctx_->ClearDestination(texture.get()); + if (HeardCancel()) { + // Finish cancelled ticket with nothing since we can't guarantee the frame we generated + // is actually "complete + ticket_->Finish(); + } else { + RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt()); + + FramePtr frame; + QString cache = ticket_->property("cache").toString(); + + if (return_type == RenderManager::kFrame || !cache.isEmpty()) { + // Convert to CPU frame + frame = GenerateFrame(texture, time); + + // Save to cache if requested + if (!cache.isEmpty()) { + rational timebase = ticket_->property("cachetimebase").value(); + QUuid uuid = ticket_->property("cacheuuid").value(); + bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame); + ticket_->setProperty("cached", cache_result); + } } - render_ctx_->Flush(); + if (return_type == RenderManager::kTexture) { + // Return GPU texture + if (!texture) { + texture = render_ctx_->CreateTexture(GetCacheVideoParams()); + render_ctx_->ClearDestination(texture.get()); + } - ticket_->Finish(QVariant::fromValue(texture)); - } else { - ticket_->Finish(QVariant::fromValue(frame)); + render_ctx_->Flush(); + + ticket_->Finish(QVariant::fromValue(texture)); + } else { + ticket_->Finish(QVariant::fromValue(frame)); + } } } break; @@ -265,6 +269,11 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c << "::" << stream.stream(); return nullptr; } + + if (!render_ctx_) { + // Assume dry run and increment access time + decoder.decoder->IncrementAccessTime(RenderManager::kDryRunInterval.toDouble() * 1000); + } } return decoder.decoder; @@ -404,10 +413,6 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) { - if (!render_ctx_) { - return; - } - if (ticket_->property("type").value() != RenderManager::kTypeVideo) { // Video cannot contribute to audio, so we do nothing here return; @@ -440,21 +445,23 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ break; case VideoParams::kVideoTypeImageSequence: { - // Since image sequences involve multiple files, we don't engage the decoder cache - decoder = Decoder::CreateFromID(decoder_id); + if (render_ctx_) { + // Since image sequences involve multiple files, we don't engage the decoder cache + decoder = Decoder::CreateFromID(decoder_id); - QString frame_filename; + QString frame_filename; - int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); - frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); + int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); + frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); - // Decoder will close automatically since it's a stream_ptr - decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); + // Decoder will close automatically since it's a stream_ptr + decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); + } break; } } - if (decoder) { + if (decoder && render_ctx_) { Decoder::RetrieveVideoParams p; p.divider = stream.video_params().divider(); p.maximum_format = destination->format(); diff --git a/app/render/texture.cpp b/app/render/texture.cpp index 33973ddaf..2c5c9fdea 100644 --- a/app/render/texture.cpp +++ b/app/render/texture.cpp @@ -29,21 +29,21 @@ const Texture::Interpolation Texture::kDefaultInterpolation = Texture::kMipmappe Texture::~Texture() { if (renderer_) { - renderer_->DestroyNativeTexture(id_); + renderer_->DestroyTexture(this); } } void Texture::Upload(void *data, int linesize) { if (renderer_) { - renderer_->UploadToTexture(this, data, linesize); + renderer_->UploadToTexture(this->id(), this->params(), data, linesize); } } void Texture::Download(void *data, int linesize) { if (renderer_) { - renderer_->DownloadFromTexture(this, data, linesize); + renderer_->DownloadFromTexture(this->id(), this->params(), data, linesize); } } diff --git a/app/render/texture.h b/app/render/texture.h index 612b3e2f2..6179878a8 100644 --- a/app/render/texture.h +++ b/app/render/texture.h @@ -32,11 +32,6 @@ class Renderer; class Texture { public: - enum Type { - k2D, - k3D - }; - enum Interpolation { kNearest, kLinear, @@ -50,19 +45,17 @@ public: */ Texture(const VideoParams& param) : renderer_(nullptr), - params_(param), - type_(k2D) + params_(param) { } /** * @brief Construct a real texture linked to a renderer backend */ - Texture(Renderer* renderer, const QVariant& native, const VideoParams& param, Type type) : + Texture(Renderer* renderer, const QVariant& native, const VideoParams& param) : renderer_(renderer), params_(param), - id_(native), - type_(type) + id_(native) { } @@ -117,11 +110,6 @@ public: return params_.pixel_aspect_ratio(); } - Type type() const - { - return type_; - } - Renderer* renderer() const { return renderer_; @@ -134,8 +122,6 @@ private: QVariant id_; - Type type_; - }; using TexturePtr = std::shared_ptr; diff --git a/app/render/videoparams.h b/app/render/videoparams.h index f3cedd28b..28d8c29fe 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -121,6 +121,8 @@ public: calculate_effective_size(); } + bool is_3d() const { return depth_ > 1; } + const rational& time_base() const { return time_base_; diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 923d0bdee..62afcb876 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -62,7 +62,11 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : attached_renderer_ = new OpenGLRenderer(this); // Create widget wrapper for OpenGL window +#ifdef USE_QOPENGLWINDOW wrapper_ = QWidget::createWindowContainer(static_cast(inner_widget_)); +#else + wrapper_ = inner_widget_; +#endif layout->addWidget(wrapper_); } else { inner_widget_ = nullptr; diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 89a952eb0..10e805d77 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -21,9 +21,15 @@ #ifndef MANAGEDDISPLAYOBJECT_H #define MANAGEDDISPLAYOBJECT_H +#define USE_QOPENGLWINDOW + #include #include +#ifdef USE_QOPENGLWINDOW #include +#else +#include +#endif #include "node/color/colormanager/colormanager.h" #include "render/renderer.h" @@ -31,7 +37,12 @@ namespace olive { -class ManagedDisplayWidgetOpenGL : public QOpenGLWindow +class ManagedDisplayWidgetOpenGL +#ifdef USE_QOPENGLWINDOW + : public QOpenGLWindow +#else + : public QOpenGLWidget +#endif { Q_OBJECT public: @@ -175,7 +186,12 @@ protected: void doneCurrent(); - QWindow* inner_widget() const +#ifdef USE_QOPENGLWINDOW + QWindow* +#else + QWidget* +#endif + inner_widget() const { return inner_widget_; } @@ -225,7 +241,11 @@ private: /** * @brief Main drawing surface abstraction */ +#ifdef USE_QOPENGLWINDOW QWindow* inner_widget_; +#else + QWidget* inner_widget_; +#endif QWidget *wrapper_; /** diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 05c2b5e38..1a3cc36a4 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -653,9 +653,11 @@ void ProjectExplorer::DeleteSelected() } } -bool ProjectExplorer::SelectItem(Node *n) +bool ProjectExplorer::SelectItem(Node *n, bool deselect_all_first) { - DeselectAll(); + if (deselect_all_first) { + DeselectAll(); + } QModelIndex index = model_.CreateIndexFromItem(n); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 086c2eab3..d69f1b8e3 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,7 +85,7 @@ public: void DeleteSelected(); - bool SelectItem(Node *n); + bool SelectItem(Node *n, bool deselect_all_first = true); public slots: void set_view_type(ProjectToolbar::ViewType type); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index a293545fb..033c50fa8 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -86,6 +86,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) : ruler_and_time_layout->addWidget(ruler()); + ruler()->setFocusPolicy(Qt::TabFocus); + QWidget::setTabOrder(ruler(), timecode_label_); + // Create list of TimelineViews - these MUST correspond to the ViewType enum view_splitter_ = new QSplitter(Qt::Vertical); diff --git a/app/widget/timelinewidget/undo/timelineundoripple.cpp b/app/widget/timelinewidget/undo/timelineundoripple.cpp index 3b7bdcac9..71550f64c 100644 --- a/app/widget/timelinewidget/undo/timelineundoripple.cpp +++ b/app/widget/timelinewidget/undo/timelineundoripple.cpp @@ -63,8 +63,15 @@ void TrackRippleRemoveAreaCommand::prepare() // If it's getting trimmed, determine if it's actually getting spliced if (first_block_is_out_trimmed && first_block_is_in_trimmed) { - // This block is getting spliced, so we'll handle that later - splice_split_command_ = new BlockSplitCommand(first_block, range_.in()); + if (dynamic_cast(first_block)) { + // As a rule, we don't split gaps, so we just treat it as a trim of the range requested + trim_out_ = {first_block, + first_block->length(), + first_block->length() - range_.length()}; + } else { + // This block is getting spliced, so we'll handle that later + splice_split_command_ = new BlockSplitCommand(first_block, range_.in()); + } } else { // It's just getting trimmed or removed, so we'll append that operation if (first_block_is_out_trimmed) { diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 2a9135780..c5635cc86 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -560,6 +560,35 @@ void ViewerWidget::ShowSubtitleProperties() } } +void ViewerWidget::DryRunFinished() +{ + RenderTicketWatcher *w = static_cast(sender()); + + if (dry_run_watchers_.contains(w)) { + RequestNextDryRun(); + } + + delete w; +} + +void ViewerWidget::RequestNextDryRun() +{ + if (IsPlaying()) { + rational next_time = Timecode::timestamp_to_time(dry_run_next_frame_, timebase()); + if (FrameExistsAtTime(next_time)) { + if (next_time > GetTime() + RenderManager::kDryRunInterval) { + QTimer::singleShot(timebase().toDouble() / playback_speed_, this, &ViewerWidget::RequestNextDryRun); + } else { + RenderTicketWatcher *watcher = new RenderTicketWatcher(this); + connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished); + watcher->SetTicket(auto_cacher_.GetSingleFrame(next_time, true)); + dry_run_next_frame_ += playback_speed_; + dry_run_watchers_.append(watcher); + } + } + } +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -828,6 +857,9 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) for (int i=0; i playback_devices_; bool prequeuing_video_; @@ -294,6 +295,8 @@ private: WaveformMode waveform_mode_; + QVector dry_run_watchers_; + private slots: void PlaybackTimerUpdate(); @@ -354,6 +357,10 @@ private slots: void ShowSubtitleProperties(); + void DryRunFinished(); + + void RequestNextDryRun(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 92f9bd119..1357af21a 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -456,84 +456,7 @@ void ViewerDisplayWidget::OnPaint() } // Extraordinarily basic subtitle renderer. Hoping to swap this out with libass at some point. - if (show_subtitles_ && subtitle_tracks_) { - const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); - - if (!subtitle_tracklist.empty()) { - QPainterPath path; - - QTransform transform = GenerateWorldTransform(); - QRect bounding_box = transform.mapRect(rect()); - - QFont f; - qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt(); - { - // Scale font size by transform - QTransform display_transform = GenerateDisplayTransform(); - font_sz *= display_transform.m11(); - } - f.setPointSizeF(font_sz); - - QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString(); - if (!family.isEmpty()) { - f.setFamily(family); - } - - f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); - - bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); - - QFontMetrics fm(f); - - for (int j=subtitle_tracklist.size()-1; j>=0; j--) { - Track *sub_track = subtitle_tracklist.at(j); - if (!sub_track->IsMuted()) { - if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { - // Split into lines - QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); - - for (int i=list.size()-1; i>=0; i--) { - int w = QtUtils::QFontMetricsWidth(fm, list.at(i)); - path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i)); - } - } - } - } - - bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool(); - - QPixmap *aa_pixmap; - QPainter *text_painter; - if (antialias) { - // QPainter only supports anti-aliasing in software, so to achieve it, we draw to a - // software buffer first and then draw that onto the hardware - aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height()); - aa_pixmap->fill(Qt::transparent); - text_painter = new QPainter(aa_pixmap); - } else { - // Just draw straight to the hardware - text_painter = new QPainter(paint_device()); - - // Offset path by however much is necessary - path.translate(bounding_box.x(), bounding_box.y()); - } - - text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16)); - text_painter->setBrush(Qt::white); - text_painter->setRenderHint(QPainter::Antialiasing); - - text_painter->drawPath(path); - - delete text_painter; - - if (antialias) { - // We just drew to a software buffer, now draw this image onto the hardware device - QPainter p(paint_device()); - p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap); - delete aa_pixmap; - } - } - } + DrawSubtitleTracks(); if (add_band_) { QPainter p(paint_device()); @@ -974,6 +897,93 @@ void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) } } +void ViewerDisplayWidget::DrawSubtitleTracks() +{ + if (!show_subtitles_ || !subtitle_tracks_) { + return; + } + + const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); + if (subtitle_tracklist.empty()) { + return; + } + + // Scale font size by transform + QTransform display_transform = GenerateDisplayTransform(); + qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt(); + font_sz *= display_transform.m11(); + if (qIsNaN(font_sz)) { + return; + } + + QPainterPath path; + + QTransform transform = GenerateWorldTransform(); + QRect bounding_box = transform.mapRect(rect()); + + QFont f; + f.setPointSizeF(font_sz); + + QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString(); + if (!family.isEmpty()) { + f.setFamily(family); + } + + f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt()); + + bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); + + QFontMetrics fm(f); + + for (int j=subtitle_tracklist.size()-1; j>=0; j--) { + Track *sub_track = subtitle_tracklist.at(j); + if (!sub_track->IsMuted()) { + if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { + // Split into lines + QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); + + for (int i=list.size()-1; i>=0; i--) { + int w = QtUtils::QFontMetricsWidth(fm, list.at(i)); + path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i)); + } + } + } + } + + bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool(); + + QPixmap *aa_pixmap; + QPainter *text_painter; + if (antialias) { + // QPainter only supports anti-aliasing in software, so to achieve it, we draw to a + // software buffer first and then draw that onto the hardware + aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height()); + aa_pixmap->fill(Qt::transparent); + text_painter = new QPainter(aa_pixmap); + } else { + // Just draw straight to the hardware + text_painter = new QPainter(paint_device()); + + // Offset path by however much is necessary + path.translate(bounding_box.x(), bounding_box.y()); + } + + text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16)); + text_painter->setBrush(Qt::white); + text_painter->setRenderHint(QPainter::Antialiasing); + + text_painter->drawPath(path); + + delete text_painter; + + if (antialias) { + // We just drew to a software buffer, now draw this image onto the hardware device + QPainter p(paint_device()); + p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap); + delete aa_pixmap; + } +} + void ViewerDisplayWidget::SetShowFPS(bool e) { show_fps_ = e; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index e88b845a3..430f11b54 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -263,6 +263,8 @@ private: void EmitColorAtCursor(QMouseEvent* e); + void DrawSubtitleTracks(); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index e7f017a69..39d943eea 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -410,6 +410,16 @@ void MainWindow::SetApplicationProgressValue(int value) #endif } +void MainWindow::SelectFootage(const QVector &e) +{ + for (ProjectPanel *p : project_panels_) { + SelectFootageForProjectPanel(e, p); + } + for (ProjectPanel *p : folder_panels_) { + SelectFootageForProjectPanel(e, p); + } +} + void MainWindow::closeEvent(QCloseEvent *e) { // Try to close all projects (this will return false if the user chooses not to close) @@ -759,6 +769,16 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) param_panel_->SetContexts(context); } +void MainWindow::SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p) +{ + p->DeselectAll(); + for (Footage *f : e) { + if (p->get_root()->HasChildRecursive(f)) { + p->SelectItem(f, false); + } + } +} + void MainWindow::FocusedPanelChanged(PanelWidget *panel) { // Update audio monitor panel diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 20498b684..1f6504c6f 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -92,6 +92,8 @@ public: */ void SetApplicationProgressValue(int value); + void SelectFootage(const QVector &e); + public slots: void ProjectOpen(Project *p); @@ -142,6 +144,8 @@ private: void UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel); + void SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p); + QByteArray premaximized_state_; // Standard panels