diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b1f3471a..337ca5784 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ endif() find_package(OpenGL REQUIRED) -find_package(OpenColorIO REQUIRED) +find_package(OpenColorIO 2.0.0 REQUIRED) find_package(OpenImageIO 1.6 REQUIRED) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a5177e705..7e7487ba5 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -41,6 +41,7 @@ add_subdirectory(project) add_subdirectory(render) add_subdirectory(shaders) add_subdirectory(task) +add_subdirectory(threading) add_subdirectory(timeline) add_subdirectory(tool) add_subdirectory(ui) diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt index b8b8e49a2..5b0976dc0 100644 --- a/app/audio/CMakeLists.txt +++ b/app/audio/CMakeLists.txt @@ -24,8 +24,6 @@ set(OLIVE_SOURCES audio/outputdeviceproxy.cpp audio/outputmanager.h audio/outputmanager.cpp - audio/sampleformat.h - audio/sampleformat.cpp audio/tempoprocessor.h audio/tempoprocessor.cpp PARENT_SCOPE diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index a0afa7cce..48daa0c9b 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -124,36 +124,8 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) format.setChannelCount(output_params_.channel_count()); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); - - switch (output_params_.format()) { - case SampleFormat::SAMPLE_FMT_U8: - format.setSampleSize(8); - format.setSampleType(QAudioFormat::UnSignedInt); - break; - case SampleFormat::SAMPLE_FMT_S16: - format.setSampleSize(16); - format.setSampleType(QAudioFormat::SignedInt); - break; - case SampleFormat::SAMPLE_FMT_S32: - format.setSampleSize(32); - format.setSampleType(QAudioFormat::SignedInt); - break; - case SampleFormat::SAMPLE_FMT_S64: - format.setSampleSize(64); - format.setSampleType(QAudioFormat::SignedInt); - break; - case SampleFormat::SAMPLE_FMT_FLT: - format.setSampleSize(32); - format.setSampleType(QAudioFormat::Float); - break; - case SampleFormat::SAMPLE_FMT_DBL: - format.setSampleSize(64); - format.setSampleType(QAudioFormat::Float); - break; - case SampleFormat::SAMPLE_FMT_COUNT: - case SampleFormat::SAMPLE_FMT_INVALID: - abort(); - } + format.setSampleSize(output_params_.bits_per_sample()); + format.setSampleType(AudioParams::GetQtSampleType(output_params_.format())); if (info.isFormatSupported(format)) { QMetaObject::invokeMethod(output_manager_, diff --git a/app/audio/sampleformat.cpp b/app/audio/sampleformat.cpp deleted file mode 100644 index f7af19c65..000000000 --- a/app/audio/sampleformat.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "sampleformat.h" - -#include "core.h" - -OLIVE_NAMESPACE_ENTER - -const SampleFormat::Format SampleFormat::kInternalFormat = SAMPLE_FMT_FLT; - -QString SampleFormat::GetSampleFormatName(const SampleFormat::Format &f) -{ - switch (f) { - case SAMPLE_FMT_U8: - return tr("Unsigned 8-bit"); - case SAMPLE_FMT_S16: - return tr("Signed 16-bit"); - case SAMPLE_FMT_S32: - return tr("Signed 32-bit"); - case SAMPLE_FMT_S64: - return tr("Signed 64-bit"); - case SAMPLE_FMT_FLT: - return tr("32-bit Float"); - case SAMPLE_FMT_DBL: - return tr("64-bit Float"); - case SAMPLE_FMT_COUNT: - case SAMPLE_FMT_INVALID: - break; - } - - return tr("Invalid"); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/audio/tempoprocessor.cpp b/app/audio/tempoprocessor.cpp index 2deda4f33..1cb56b468 100644 --- a/app/audio/tempoprocessor.cpp +++ b/app/audio/tempoprocessor.cpp @@ -28,7 +28,7 @@ extern "C" { #include -#include "codec/ffmpeg/ffmpegcommon.h" +#include "common/ffmpegutils.h" OLIVE_NAMESPACE_ENTER @@ -75,7 +75,7 @@ bool TempoProcessor::Open(const AudioParams ¶ms, const double& speed) 1, params_.sample_rate(), params_.sample_rate(), - FFmpegCommon::GetFFmpegSampleFormat(params_.format()), + FFmpegUtils::GetFFmpegSampleFormat(params_.format()), params.channel_layout()); // Create buffer and buffersink @@ -171,7 +171,7 @@ void TempoProcessor::Push(const char *data, int length) // Allocate a buffer for the number of samples we got src_frame->sample_rate = params_.sample_rate(); - src_frame->format = FFmpegCommon::GetFFmpegSampleFormat(params_.format()); + src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format()); src_frame->channel_layout = params_.channel_layout(); src_frame->nb_samples = params_.bytes_to_samples(length); src_frame->pts = timestamp_; diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index d4dc7108b..9841fbf4f 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -24,11 +24,11 @@ #include #include -#include "codec/ffmpeg/ffmpegcommon.h" #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/oiio/oiiodecoder.h" #include "codec/waveinput.h" #include "codec/waveoutput.h" +#include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "common/timecodefunctions.h" #ifdef USE_OTIO @@ -39,55 +39,160 @@ OLIVE_NAMESPACE_ENTER +QMutex Decoder::currently_conforming_mutex_; +QWaitCondition Decoder::currently_conforming_wait_cond_; +QVector Decoder::currently_conforming_; + Decoder::Decoder() : - open_(false), stream_(nullptr) { } -Decoder::Decoder(Stream *fs) : - open_(false), - stream_(fs) +bool Decoder::Open(StreamPtr fs) { + QMutexLocker locker(&mutex_); + + if (stream_) { + // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not. + if (stream_ == fs) { + return true; + } else { + qWarning() << "Tried to open a decoder that was already open with another stream"; + return false; + } + } else { + // Stream was not open, try opening it now + if (fs == nullptr) { + // Cannot open null stream + qCritical() << "Decoder attempted to open null stream"; + return false; + } + + if (fs->footage()->decoder() != id()) { + qCritical() << "Tried to open footage in incorrect decoder"; + return false; + } + + // Set stream + stream_ = fs; + + // Try open internal + if (OpenInternal()) { + return true; + } else { + // Unset stream + CloseInternal(); + stream_ = nullptr; + return false; + } + } } -StreamPtr Decoder::stream() const +FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r) { - return stream_; + QMutexLocker locker(&mutex_); + + if (!stream_) { + qCritical() << "Can't retrieve video on a closed decoder"; + return nullptr; + } + + if (!SupportsVideo()) { + qCritical() << "Decoder doesn't support video"; + return nullptr; + } + + if (stream_->type() != Stream::kVideo) { + qCritical() << "Tried to retrieve video from a non-video stream"; + return nullptr; + } + + return RetrieveVideoInternal(timecode, divider); } -void Decoder::set_stream(StreamPtr fs) +SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QAtomicInt *cancelled) { - Close(); + QMutexLocker locker(&mutex_); - stream_ = fs; + if (!stream_) { + qCritical() << "Can't retrieve audio on a closed decoder"; + return nullptr; + } + + if (!SupportsAudio()) { + qCritical() << "Decoder doesn't support audio"; + return nullptr; + } + + if (stream_->type() != Stream::kAudio) { + qCritical() << "Tried to retrieve audio from a non-audio stream"; + return nullptr; + } + + // Determine if we already have a conformed version + QString conform_filename = GetConformedFilename(params); + CurrentlyConforming want_conform = {stream_, params}; + + currently_conforming_mutex_.lock(); + + // Wait for conform to complete + while (currently_conforming_.contains(want_conform)) { + currently_conforming_wait_cond_.wait(¤tly_conforming_mutex_); + } + + // See if we got the conform + SampleBufferPtr buffer = RetrieveAudioFromConform(conform_filename, range); + + if (!buffer) { + // We'll need to conform this ourselves + currently_conforming_.append(want_conform); + currently_conforming_mutex_.unlock(); + + // We conform to a different filename until it's done to make it clear even across sessions + // whether this conform is ready or not + QString working_fn = conform_filename; + working_fn.append(QStringLiteral(".working")); + + if (ConformAudioInternal(working_fn, params, cancelled)) { + // Move file to standard conform name, making it clear this conform is ready for use + QFile::remove(conform_filename); + QFile::rename(working_fn, conform_filename); + + // Return audio as planned + buffer = RetrieveAudioFromConform(conform_filename, range); + } else { + // Failed + qCritical() << "Failed to conform audio"; + } + + currently_conforming_mutex_.lock(); + currently_conforming_.removeOne(want_conform); + currently_conforming_wait_cond_.wakeAll(); + } + + currently_conforming_mutex_.unlock(); + + return buffer; } -FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/) +void Decoder::Close() { - return nullptr; -} + QMutexLocker locker(&mutex_); -SampleBufferPtr Decoder::RetrieveAudio(const rational &/*timecode*/, const rational &/*length*/, const AudioParams &/*params*/) -{ - return nullptr; -} - -bool Decoder::SupportsVideo() -{ - return false; -} - -bool Decoder::SupportsAudio() -{ - return false; + if (stream_) { + CloseInternal(); + stream_ = nullptr; + } else { + qWarning() << "Tried to close a decoder that wasn't open"; + } } /* * DECODER STATIC PUBLIC MEMBERS */ -QVector ReceiveListOfAllDecoders() { +QVector ReceiveListOfAllDecoders() +{ QVector decoders; // The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last, @@ -98,7 +203,7 @@ QVector ReceiveListOfAllDecoders() { return decoders; } -FootagePtr Decoder::ProbeMedia(Project* project, const QString &filename, const QAtomicInt* cancelled) +FootagePtr Decoder::Probe(Project* project, const QString &filename, const QAtomicInt* cancelled) { // Check for a valid filename if (filename.isEmpty()) { @@ -184,37 +289,6 @@ QString Decoder::GetIndexFilename() return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index()))); } -bool Decoder::ConformAudio(const QAtomicInt *, const AudioParams& ) -{ - return false; -} - -bool Decoder::HasConformedVersion(const AudioParams ¶ms) -{ - if (stream()->type() != Stream::kAudio) { - return false; - } - - AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); - - if (audio_stream->has_conformed_version(params)) { - return true; - } - - // Get indexed WAV file - WaveInput input(GetIndexFilename()); - - bool index_already_matches = false; - - if (input.open()) { - index_already_matches = (input.params() == params); - - input.close(); - } - - return index_already_matches; -} - void Decoder::SignalProcessingProgress(const int64_t &ts) { if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) { @@ -267,4 +341,40 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename) return number_only.toLongLong(); } +FramePtr Decoder::RetrieveVideoInternal(const rational &timecode, const int ÷r) +{ + Q_UNUSED(timecode) + Q_UNUSED(divider) + return nullptr; +} + +bool Decoder::ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled) +{ + Q_UNUSED(filename) + Q_UNUSED(cancelled) + Q_UNUSED(params) + return false; +} + +SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filename, const TimeRange& range) +{ + WaveInput input(conform_filename); + + if (input.open()) { + const AudioParams& input_params = input.params(); + + // Read bytes from wav + QByteArray packed_data = input.read(input_params.time_to_bytes(range.in()), + input_params.time_to_bytes(range.length())); + input.close(); + + // Create sample buffer + SampleBufferPtr sample_buffer = SampleBuffer::CreateFromPackedData(input_params, packed_data); + + return sample_buffer; + } + + return nullptr; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/codec/decoder.h b/app/codec/decoder.h index a096d5b08..68ff8dbb5 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -27,6 +27,7 @@ extern "C" { #include #include +#include #include #include "codec/frame.h" @@ -68,117 +69,46 @@ public: Decoder(); - Decoder(Stream* fs); - - DISABLE_COPY_MOVE(Decoder) - + /** + * @brief Unique decoder ID + */ virtual QString id() = 0; - StreamPtr stream() const; - void set_stream(StreamPtr fs); + virtual bool SupportsVideo(){return false;} + virtual bool SupportsAudio(){return false;} /** - * @brief Probe a footage file and dump metadata about it + * @brief Open stream for decoding * - * When a Footage file is imported, we'll need to know whether Olive is equipped with a decoder for utilizing it - * and metadata should be retrieved about it if so. For this purpose, the Footage object is passed through all - * Probe() functions of available deocders until one returns TRUE. A FALSE return means the Decoder was unable to - * parse this file and the next should be tried. + * This function is thread safe. * - * Probe() differs from Open() since it focuses on a file as a whole rather than one particular stream. Probe() - * should be able to be run directly without calling Open() or Close() and should free its memory before returning. - * - * Probe() will never be called on an object that is also used for decoding. In other words, it will never be called - * alongside Open() or Close() externally, so Probe() can use variables that would otherwise be used for decoding - * without conflict. - * - * @param f - * - * A Footage object to probe. The Footage object will have a valid filename and will be empty prior to being sent - * to this function (i.e. Footage::Clear() will not have to be called). - * - * @return - * - * TRUE if the Decoder was able to decode this file. FALSE if not. This function should have filled the Footage - * object with metadata if it returns TRUE. Otherwise, the Footage object should be untouched. + * Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is + * already open and the stream == the stream provided. Returns FALSE if the stream couldn't + * be opened OR if already open and the stream is NOT the same. */ - virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const = 0; + bool Open(StreamPtr fs); /** - * @brief Open media/allocate memory + * @brief Retrieves a video frame from footage * - * Any file handles or memory allocation that needs to be done before this instance of a Decoder can return data - * should be done here. + * This function will always return a valid frame unless a fatal error occurs (in such case, + * nullptr will return). If the timecode is before the start of the footage, this function should + * return the first frame. Likewise, if it is after the timecode, this function should return the + * last frame. * - * @return - * - * TRUE if successful and ready to return data, FALSE if failed to open and unable to retrieve data. If the function - * fails, any memory allocated should be free'd before returning FALSE, possibly by calling Close(). + * This function is thread safe and can only run while the decoder is open. \see Open() */ - virtual bool Open() = 0; + FramePtr RetrieveVideo(const rational& timecode, const int& divider); /** - * @brief Retrieve video frame + * @brief Retrieve audio data from footage * - * The main function for retrieving video data from the Decoder. This function should always provide complete frame - * data (i.e. no partial frames) at the timecode provided. The Decoder should perform any steps required to retrieve - * a complete frame separate from the rest of the program, using any form of caching/indexing to keep this as - * performant as possible. + * This function will always return a sample buffer unless a fatal error occurs (in such case, + * nullptr will return). The SampleBuffer should always have enough audio for the range provided. * - * It's acceptable for this function to check whether the Decoder is open, and call Open() if not. If Open() returns - * false, this function should return nullptr. - * - * @param timecode - * - * The timecode (a rational in seconds) to retrieve the frame at. If there is not a frame at this precise location - * this should be corrected internally to the closest fit for the timecode. - * - * @return - * - * A FramePtr of valid data at this timecode or nullptr if there was nothing to retrieve at the provided timecode or - * the media could not be opened. + * This function is thread safe and can only run while the decoder is open. \see Open() */ - virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider); - - /** - * @brief Retrieve video frame - * - * The main function for retrieving audio data from the Decoder. This function should always provide complete frame - * data (i.e. no missing samples) at the timecode and length requested. The Decoder should perform any steps - * required to retrieve a complete frame separate from the rest of the program, using any form of caching/indexing - * to keep this as performant as possible. - * - * It's acceptable for this function to check whether the Decoder is open, and call Open() if not. If Open() returns - * false, this function should return nullptr. - * - * @param timecode - * - * The starting timecode (a rational in seconds) to retrieve the data at. - * - * @param length - * - * The total length of audio data to retrieve (a rational in seconds). - * - * @return - * - * A FramePtr of valid data at this timecode of the requested length or nullptr if there was nothing to retrieve at - * the provided timecode or the media could not be opened. - */ - virtual SampleBufferPtr RetrieveAudio(const rational& timecode, const rational& length, const AudioParams& params); - - virtual bool SupportsVideo(); - virtual bool SupportsAudio(); - - /** - * @brief Close media/deallocate memory - * - * Any file handles or memory allocations opened in Open() should be cleaned up here. - * - * As the main memory freeing function, it's good practice to call this in Open() if there's an error that prevents - * correct function before Open() returns. As such, Close() should be prepared for not all memory/file handles to - * have been opened successfully. - */ - virtual void Close() = 0; + SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QAtomicInt *cancelled); /** * @brief Try to probe a Footage file by passing it through all available Decoders @@ -199,7 +129,27 @@ public: * * TRUE if a Decoder was successfully able to parse and probe this file. FALSE if not. */ - static FootagePtr ProbeMedia(Project *project, const QString& filename, const QAtomicInt *cancelled); + static FootagePtr Probe(Project *project, const QString& filename, const QAtomicInt *cancelled); + + /** + * @brief Generate a Footage object from a file + * + * If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it + * will return nullptr. + * + * For sub-classes, this function should be effectively static. We can't do virtual static + * functions in C++, but it should hold and access no state during its run. + * + * This function is re-entrant. + */ + virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const = 0; + + /** + * @brief Closes media/deallocates memory + * + * This function is thread safe and can only run while the decoder is open. \see Open() + */ + void Close(); /** * @brief Create a Decoder instance using a Decoder ID @@ -210,42 +160,45 @@ public: */ static DecoderPtr CreateFromID(const QString& id); - /** - * @brief AUDIO ONLY: Produces a complete PCM extraction of the audio stream - * - * Internally, our render engine only deals with PCM since it provides the least headaches and - * modern computers have the processing power to do it. - * - * Resamples and converts the currently open audio to match the params. If the audio doesn't need - * conforming (e.g. audio params already match or a conformed match already exists), this function - * will return immediately. Otherwise it will block the calling thread until the conform is - * complete. This function should therefore only be called from a background render thread. - * - * All audio decoders must override this. It's not pure since video decoders don't need to use - * this, but default behavior will abort since it should never be called. - */ - virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioParams ¶ms); - - /** - * @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params - * already exists - */ - bool HasConformedVersion(const AudioParams& params); - static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number); static int GetImageSequenceDigitCount(const QString& filename); static int64_t GetImageSequenceIndex(const QString& filename); -signals: - /** - * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if - * available - */ - void IndexProgress(double); - protected: + /** + * @brief Internal open function + * + * Sub-classes must override this function. Function will already be mutexed, so there is no need + * to worry about thread safety. Also many other sanity checks will be done before this, so + * sub-classes only need to worry about their own opening functions. It is guaranteed that the + * decoder is not open yet and that the footage stream was from that sub-classes probe function. + * + * Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise, + * return FALSE. If this function returns false, Decoder will call CloseInternal to clean any + * memory allocated during OpenInternal. + */ + virtual bool OpenInternal() = 0; + + /** + * @brief Internal close function + * + * Sub-classes must override this function. Function should be able to safely clear all allocated + * memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called. + */ + virtual void CloseInternal() = 0; + + /** + * @brief Internal frame retrieval function + * + * Sub-classes must override this function IF they support video. Function is already mutexed + * so sub-classes don't need to worry about thread safety. + */ + virtual FramePtr RetrieveVideoInternal(const rational& timecode, const int& divider); + + virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled); + void SignalProcessingProgress(const int64_t& ts); /** @@ -255,11 +208,44 @@ protected: QString GetIndexFilename(); - bool open_; + struct CurrentlyConforming { + StreamPtr stream; + AudioParams params; + + bool operator==(const CurrentlyConforming& rhs) const + { + return this->stream == rhs.stream && this->params == rhs.params; + } + }; + + /** + * @brief Return currently open stream + * + * This function is NOT thread safe and should therefore only be called by thread safe functions. + */ + StreamPtr stream() const + { + return stream_; + } + + static QMutex currently_conforming_mutex_; + static QWaitCondition currently_conforming_wait_cond_; + static QVector currently_conforming_; + +signals: + /** + * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if + * available + */ + void IndexProgress(double); private: + SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range); + StreamPtr stream_; + QMutex mutex_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 0f648884e..b328e3135 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -232,7 +232,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params) { Q_UNUSED(id) - + return new FFmpegEncoder(params); } diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 4acf38bde..9cd9547a0 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -122,6 +122,11 @@ public: virtual void Close() = 0; + virtual VideoParams::Format GetDesiredPixelFormat() const + { + return VideoParams::kFormatInvalid; + } + private: EncodingParams params_; diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index 12267fe76..7c26e24bf 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -17,8 +17,6 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} codec/ffmpeg/avframeptr.h - codec/ffmpeg/ffmpegcommon.h - codec/ffmpeg/ffmpegcommon.cpp codec/ffmpeg/ffmpegdecoder.h codec/ffmpeg/ffmpegdecoder.cpp codec/ffmpeg/ffmpegencoder.h diff --git a/app/codec/ffmpeg/ffmpegcommon.cpp b/app/codec/ffmpeg/ffmpegcommon.cpp deleted file mode 100644 index f66804048..000000000 --- a/app/codec/ffmpeg/ffmpegcommon.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "ffmpegcommon.h" - -OLIVE_NAMESPACE_ENTER - -AVPixelFormat FFmpegCommon::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) -{ - AVPixelFormat possible_pix_fmts[] = { - AV_PIX_FMT_RGB24, - AV_PIX_FMT_RGBA, - AV_PIX_FMT_RGB48, - AV_PIX_FMT_RGBA64, - AV_PIX_FMT_NONE - }; - - return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, - pix_fmt, - 1, - nullptr); -} - -SampleFormat::Format FFmpegCommon::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) -{ - switch (smp_fmt) { - case AV_SAMPLE_FMT_U8: - return SampleFormat::SAMPLE_FMT_U8; - case AV_SAMPLE_FMT_S16: - return SampleFormat::SAMPLE_FMT_S16; - case AV_SAMPLE_FMT_S32: - return SampleFormat::SAMPLE_FMT_S32; - case AV_SAMPLE_FMT_S64: - return SampleFormat::SAMPLE_FMT_S64; - case AV_SAMPLE_FMT_FLT: - return SampleFormat::SAMPLE_FMT_FLT; - case AV_SAMPLE_FMT_DBL: - return SampleFormat::SAMPLE_FMT_DBL; - case AV_SAMPLE_FMT_U8P : - case AV_SAMPLE_FMT_S16P: - case AV_SAMPLE_FMT_S32P: - case AV_SAMPLE_FMT_S64P: - case AV_SAMPLE_FMT_FLTP: - case AV_SAMPLE_FMT_DBLP: - case AV_SAMPLE_FMT_NONE: - case AV_SAMPLE_FMT_NB: - break; - } - - return SampleFormat::SAMPLE_FMT_INVALID; -} - -AVSampleFormat FFmpegCommon::GetFFmpegSampleFormat(const SampleFormat::Format &smp_fmt) -{ - switch (smp_fmt) { - case SampleFormat::SAMPLE_FMT_U8: - return AV_SAMPLE_FMT_U8; - case SampleFormat::SAMPLE_FMT_S16: - return AV_SAMPLE_FMT_S16; - case SampleFormat::SAMPLE_FMT_S32: - return AV_SAMPLE_FMT_S32; - case SampleFormat::SAMPLE_FMT_S64: - return AV_SAMPLE_FMT_S64; - case SampleFormat::SAMPLE_FMT_FLT: - return AV_SAMPLE_FMT_FLT; - case SampleFormat::SAMPLE_FMT_DBL: - return AV_SAMPLE_FMT_DBL; - case SampleFormat::SAMPLE_FMT_INVALID: - case SampleFormat::SAMPLE_FMT_COUNT: - break; - } - - return AV_SAMPLE_FMT_NONE; -} - -AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_fmt) -{ - switch (pix_fmt) { - case PixelFormat::PIX_FMT_RGBA8: - return AV_PIX_FMT_RGBA; - case PixelFormat::PIX_FMT_RGBA16U: - return AV_PIX_FMT_RGBA64; - case PixelFormat::PIX_FMT_RGB8: - return AV_PIX_FMT_RGB24; - case PixelFormat::PIX_FMT_RGB16U: - return AV_PIX_FMT_RGB48; - case PixelFormat::PIX_FMT_RGBA16F: - case PixelFormat::PIX_FMT_RGBA32F: - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return AV_PIX_FMT_NONE; -} - -PixelFormat::Format FFmpegCommon::GetCompatiblePixelFormat(const PixelFormat::Format &pix_fmt) -{ - switch (pix_fmt) { - case PixelFormat::PIX_FMT_RGB8: - return PixelFormat::PIX_FMT_RGB8; - case PixelFormat::PIX_FMT_RGBA8: - return PixelFormat::PIX_FMT_RGBA8; - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGB32F: - return PixelFormat::PIX_FMT_RGB16U; - case PixelFormat::PIX_FMT_RGBA16U: - case PixelFormat::PIX_FMT_RGBA16F: - case PixelFormat::PIX_FMT_RGBA32F: - return PixelFormat::PIX_FMT_RGBA16U; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return PixelFormat::PIX_FMT_INVALID; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c5a0f3546..e6718ead5 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -38,95 +38,58 @@ extern "C" { #include "codec/waveinput.h" #include "common/define.h" +#include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "common/functiontimer.h" #include "common/timecodefunctions.h" -#include "ffmpegcommon.h" #include "render/framehashcache.h" #include "render/diskmanager.h" -#include "render/pixelformat.h" OLIVE_NAMESPACE_ENTER -QHash< Stream*, QList > FFmpegDecoder::instance_map_; -QMutex FFmpegDecoder::instance_map_lock_; -QHash< FFmpegDecoder::FFmpegFramePoolKey, FFmpegDecoder::FFmpegFramePoolValue > FFmpegDecoder::frame_pool_map_; - -// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make -// this a dynamic value somehow or a configurable value? -const int FFmpegDecoderInstance::kMaxFrameLife = 2000; - FFmpegDecoder::FFmpegDecoder() : scale_ctx_(nullptr), - scale_divider_(0) + scale_divider_(0), + pool_(QThread::idealThreadCount()*2), + is_working_(false), + cache_at_zero_(false), + cache_at_eof_(false) { } FFmpegDecoder::~FFmpegDecoder() { - Close(); + CloseInternal(); } -bool FFmpegDecoder::Open() +bool FFmpegDecoder::OpenInternal() { - if (open_) { + if (instance_.Open(stream()->footage()->filename().toUtf8(), stream()->index())) { + AVStream* s = instance_.avstream(); + + // Store one second in the source's timebase + second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); + + if (stream()->type() == Stream::kVideo) { + // Get an Olive compatible AVPixelFormat + ideal_pix_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(static_cast(s->codecpar->format)); + + // Determine which Olive native pixel format we retrieved + // Note that FFmpeg doesn't support float formats + native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_); + native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt_); + + if (native_pix_fmt_ == VideoParams::kFormatInvalid + || native_channel_count_ == 0) { + qDebug() << "Failed to find valid native pixel format for" << ideal_pix_fmt_; + return false; + } + } + return true; } - Q_ASSERT(stream()); - - // Convert QString to a C string - QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - - FFmpegDecoderInstance* our_instance = new FFmpegDecoderInstance(fn_bytes.constData(), stream()->index()); - - if (!our_instance->IsValid()) { - delete our_instance; - return false; - } - - if (stream()->type() == Stream::kVideo) { - // Get an Olive compatible AVPixelFormat - src_pix_fmt_ = static_cast(our_instance->stream()->codecpar->format); - ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(src_pix_fmt_); - - // Determine which Olive native pixel format we retrieved - // Note that FFmpeg doesn't support float formats - native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_); - - Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID); - } - - if (StreamUsesMultipleInstances(stream())) { - // Video optimizes with multiple instances that we can swap between - QMutexLocker map_locker(&instance_map_lock_); - - VideoStreamPtr vs = std::static_pointer_cast(stream()); - - FFmpegFramePoolKey key = {vs->width(), vs->height(), src_pix_fmt_}; - FFmpegFramePoolValue& frame_pool = frame_pool_map_[key]; - - if (!frame_pool.pool) { - // Frames are allocated as threads * threads, to scale from each thread sharing one set - // to all of them working individually - int thread_count = QThread::idealThreadCount(); - int max_memory_frame_count = thread_count * thread_count; - frame_pool.pool = new FFmpegFramePool(max_memory_frame_count); - } - frame_pool.handles++; - - our_instance->SetFramePool(frame_pool.pool); - - instance_map_[stream().get()].append(our_instance); - } else { - // Images, image sequences, and audio don't need an instance - delete our_instance; - } - - // All allocation succeeded so we set the state to open - open_ = true; - - return true; + return false; } FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) @@ -136,51 +99,64 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int & QString img_filename = stream()->footage()->filename(); + int64_t ts; + // If it's an image sequence, we'll probably need to transform the filename if (is->video_type() == VideoStream::kVideoTypeImageSequence) { - int64_t ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode); + ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode); img_filename = TransformImageSequenceFileName(stream()->footage()->filename(), ts); + } else { + ts = 0; } - FFmpegDecoderInstance i(img_filename.toUtf8(), stream()->index()); - AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); FramePtr output_frame = nullptr; + Instance i; + i.Open(img_filename.toUtf8(), stream()->index()); + int ret = i.GetFrame(pkt, frame); if (ret >= 0) { - output_frame = BuffersToNativeFrame(divider, - is->width(), - is->height(), - 0, - frame->data, - frame->linesize); + // Create frame to return + output_frame = Frame::Create(); + output_frame->set_video_params(VideoParams(frame->width, + frame->height, + native_pix_fmt_, + native_channel_count_, + std::static_pointer_cast(stream())->pixel_aspect_ratio(), + std::static_pointer_cast(stream())->interlacing(), + divider)); + output_frame->set_timestamp(timecode); + output_frame->allocate(); + + uint8_t* copy_data = reinterpret_cast(output_frame->data()); + int copy_linesize = output_frame->linesize_bytes(); + + FFmpegBufferToNativeBuffer(frame->data, frame->linesize, ©_data, ©_linesize); } else { qWarning() << "Failed to retrieve still image from decoder"; } + i.Close(); + av_frame_free(&frame); av_packet_free(&pkt); return output_frame; } -FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r) +FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const int ÷r) { - if (!open_) { - qWarning() << "Tried to retrieve video on a decoder that's still closed"; - return nullptr; - } - - if (stream()->type() != Stream::kVideo) { - return nullptr; - } - VideoStreamPtr vs = std::static_pointer_cast(stream()); + if (scale_divider_ != divider) { + FreeScaler(); + InitScaler(divider); + } + if (vs->video_type() == VideoStream::kVideoTypeStill || vs->video_type() == VideoStream::kVideoTypeImageSequence) { @@ -188,153 +164,39 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid } else { - FFmpegFramePool::ElementPtr return_frame = nullptr; - int64_t target_ts = vs->get_time_in_timebase_units(timecode); - FFmpegDecoderInstance* working_instance = nullptr; - int divided_width = VideoParams::GetScaledDimension(vs->width(), divider); int divided_height = VideoParams::GetScaledDimension(vs->height(), divider); - // Find instance - do { - QMutexLocker list_locker(&instance_map_lock_); + if (pool_.width() != divided_width || pool_.height() != divided_height) { + // Clear all instance queues + ClearFrameCache(); - const QList& instances = instance_map_.value(stream().get()); - - FFmpegFramePool* pool = frame_pool_map_.value({vs->width(), vs->height(), src_pix_fmt_}).pool; - - if (pool->width() != divided_width || pool->height() != divided_height) { - // Clear all instance queues - foreach (FFmpegDecoderInstance* i, instances) { - i->ClearFrameCache(); - } - - // Set new frame pool parameters - pool->SetParameters(divided_width, divided_height, src_pix_fmt_); - } - - QList non_ideal_contenders; - - foreach (FFmpegDecoderInstance* i, instances) { - - i->cache_lock()->lock(); - - if (i->CacheContainsTime(target_ts)) { - - // Found our instance, allow others to enter the list - - list_locker.unlock(); - - // Get the frame from this cache - return_frame = i->GetFrameFromCache(target_ts); - - // Got our frame, allow cache to continue - i->cache_lock()->unlock(); - break; - - } else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) { - - // Found our instance, allow others to enter the list - list_locker.unlock(); - - // If the instance is currently in use, enter into a loop of seeing from frames come up next in case one is ours - if (i->IsWorking()) { - - do { - // Allow instance to continue to the next frame - i->cache_wait_cond()->wait(i->cache_lock()); - - // See if the cache now contains this frame, if so we'll exit this loop - if (i->CacheContainsTime(target_ts)) { - - // Grab the frame - return_frame = i->GetFrameFromCache(target_ts); - - // We can release this worker now since we don't need it anymore - i->cache_lock()->unlock(); - - } else if (!i->IsWorking()) { - - // This instance finished and we didn't get our frame, we'll take it and continue it - working_instance = i; - break; - - } - } while (!return_frame); - - } else { - // Otherwise, we'll grab this instance and continue it ourselves - working_instance = i; - } - - break; - - } else if (i->IsWorking()) { - - // Ignore currently working instances - i->cache_lock()->unlock(); - - } else if (i->CacheIsEmpty()) { - - // Prioritize this cache over others (leaves this instance LOCKED in case we end up using it later) - non_ideal_contenders.prepend(i); - - } else { - - // De-prioritize this cache (leaves this instance LOCKED in case we end up using it later) - non_ideal_contenders.append(i); - - } - } - - // If we didn't find a suitable contender, grab the first non-suitable and roll with that - if (!return_frame && !working_instance && !non_ideal_contenders.isEmpty()) { - working_instance = non_ideal_contenders.takeFirst(); - } - - // For all instances we left locked but didn't end up using, lock them now - foreach (FFmpegDecoderInstance* unsuitable_instance, non_ideal_contenders) { - unsuitable_instance->cache_lock()->unlock(); - } - } while (!return_frame && !working_instance); - - if (!return_frame && working_instance) { - - // This instance SHOULD remain locked from our earlier loop, making this operation safe - working_instance->SetWorking(true); - - // Retrieve frame - return_frame = working_instance->RetrieveFrame(target_ts, divider, true); - - // Set working to false and wake any threads waiting - working_instance->cache_lock()->lock(); - working_instance->SetWorking(false); - working_instance->cache_wait_cond()->wakeAll(); - working_instance->cache_lock()->unlock(); + // Set new frame pool parameters + pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_); } + // Retrieve frame + FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(target_ts, divider); + // We found the frame, we'll return a copy if (return_frame) { - // Align buffer to data/linesize points that can be passed to sws_scale - uint8_t* input_data[4]; - int input_linesize[4]; + FramePtr copy = Frame::Create(); + copy->set_video_params(VideoParams(vs->width(), + vs->height(), + native_pix_fmt_, + native_channel_count_, + std::static_pointer_cast(stream())->pixel_aspect_ratio(), + std::static_pointer_cast(stream())->interlacing(), + divider)); + copy->set_timestamp(timecode); + copy->allocate(); - av_image_fill_arrays(input_data, - input_linesize, - reinterpret_cast(return_frame->data()), - src_pix_fmt_, - divided_width, - divided_height, - 1); + // This data will already match the frame + memcpy(copy->data(), return_frame->data(), copy->allocated_size()); - return BuffersToNativeFrame(divider, - vs->width(), - vs->height(), - timecode, - input_data, - input_linesize); + return copy; } } @@ -342,97 +204,13 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid return nullptr; } -SampleBufferPtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rational &length, const AudioParams ¶ms) +void FFmpegDecoder::CloseInternal() { - if (!open_) { - qWarning() << "Tried to retrieve audio on a decoder that's still closed"; - return nullptr; - } + ClearFrameCache(); - if (stream()->type() != Stream::kAudio) { - return nullptr; - } + instance_.Close(); - QString wav_fn = GetConformedFilename(params); - WaveInput input(wav_fn); - - if (input.open()) { - const AudioParams& input_params = input.params(); - - // Read bytes from wav - QByteArray packed_data = input.read(input_params.time_to_bytes(timecode), input_params.time_to_bytes(length)); - input.close(); - - // Create sample buffer - SampleBufferPtr sample_buffer = SampleBuffer::CreateFromPackedData(input_params, packed_data); - - return sample_buffer; - } - - qCritical() << "Failed to open cached file" << wav_fn; - - return nullptr; -} - -void FFmpegDecoder::Close() -{ - if (stream() && StreamUsesMultipleInstances(stream())) { - // Clear whichever instance is not in use and is least useful (there are only ever as many instances as there are - // threads so if this thread is closing, an instance MUST be inactive) - QMutexLocker l(&instance_map_lock_); - - QList list = instance_map_.value(stream().get()); - - if (!list.isEmpty()) { - // Rank the instances by least useful (the top one should be one that isn't working and isn't in use) - QList least_useful; - - foreach (FFmpegDecoderInstance* i, list) { - i->cache_lock()->lock(); - - if (i->IsWorking()) { - // Don't bother any currently working instances - i->cache_lock()->unlock(); - continue; - } - - if (i->CacheIsEmpty()) { - least_useful.prepend(i); - } else { - least_useful.append(i); - } - } - - // Remove the least useful from the list and re-insert it into the map - FFmpegDecoderInstance* least_useful_instance = least_useful.first(); - list.removeOne(least_useful_instance); - instance_map_.insert(stream().get(), list); - - // If there are no more instances, destroy frame pool - VideoStreamPtr vs = std::static_pointer_cast(stream()); - FFmpegFramePoolKey frame_pool_key = {vs->width(), vs->height(), src_pix_fmt_}; - FFmpegFramePoolValue& frame_pool_info = frame_pool_map_[frame_pool_key]; - frame_pool_info.handles--; - - if (frame_pool_info.handles == 0) { - delete frame_pool_info.pool; - frame_pool_map_.remove(frame_pool_key); - } - - // We're done with the list now, we can unlock it and allow others to use it - l.unlock(); - - // Unlock all the instances we locked - foreach (FFmpegDecoderInstance* i, least_useful) { - i->cache_lock()->unlock(); - } - - // Delete this least useful instance now that we've definitely taken ownership of it - least_useful_instance->deleteLater(); - } - } - - ClearResources(); + FreeScaler(); } QString FFmpegDecoder::id() @@ -440,16 +218,6 @@ QString FFmpegDecoder::id() return QStringLiteral("ffmpeg"); } -bool FFmpegDecoder::SupportsVideo() -{ - return true; -} - -bool FFmpegDecoder::SupportsAudio() -{ - return true; -} - FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const { // Variable for receiving errors from FFmpeg @@ -484,119 +252,134 @@ FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cance StreamPtr str; - if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && decoder) { + if (decoder + && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO + || avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) { - bool image_is_still = false; - rational pixel_aspect_ratio; - rational frame_rate; - VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone; + if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - { - // Read at least two frames to get more information about this video stream - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); + bool image_is_still = false; + rational pixel_aspect_ratio; + rational frame_rate; + VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone; { - FFmpegDecoderInstance instance(filename_c, i); + // Read at least two frames to get more information about this video stream + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); - // Read first frame and retrieve some metadata - if (instance.GetFrame(pkt, frame) >= 0) { - // Check if video is interlaced and what field dominance it has if so - if (frame->interlaced_frame) { - if (frame->top_field_first) { - interlacing = VideoParams::kInterlacedTopFirst; - } else { - interlacing = VideoParams::kInterlacedBottomFirst; + { + Instance instance; + instance.Open(filename.toUtf8(), avstream->index); + + // Read first frame and retrieve some metadata + if (instance.GetFrame(pkt, frame) >= 0) { + // Check if video is interlaced and what field dominance it has if so + if (frame->interlaced_frame) { + if (frame->top_field_first) { + interlacing = VideoParams::kInterlacedTopFirst; + } else { + interlacing = VideoParams::kInterlacedBottomFirst; + } } + + pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(), + instance.avstream(), + frame); + + frame_rate = av_guess_frame_rate(instance.fmt_ctx(), + instance.avstream(), + frame); } - pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(), - instance.stream(), - frame); + // Read second frame + int ret = instance.GetFrame(pkt, frame); - frame_rate = av_guess_frame_rate(instance.fmt_ctx(), - instance.stream(), - frame); - } + if (ret >= 0) { + // Check if we need a manual duration + if (avstream->duration == AV_NOPTS_VALUE) { + int64_t new_dur; - // Read second frame - int ret = instance.GetFrame(pkt, frame); + do { + new_dur = frame->pts; + } while (instance.GetFrame(pkt, frame) >= 0); - if (ret >= 0) { - // Check if we need a manual duration - if (avstream->duration == AV_NOPTS_VALUE) { - int64_t new_dur; - - do { - new_dur = frame->pts; - } while (instance.GetFrame(pkt, frame) >= 0); - - avstream->duration = new_dur; + avstream->duration = new_dur; + } + } else if (ret == AVERROR_EOF) { + // Video has only one frame in it, treat it like a still image + image_is_still = true; } - } else if (ret == AVERROR_EOF) { - // Video has only one frame in it, treat it like a still image - image_is_still = true; + + instance.Close(); } + + av_frame_free(&frame); + av_packet_free(&pkt); } - av_frame_free(&frame); - av_packet_free(&pkt); - } + VideoStreamPtr video_stream = std::make_shared(); - VideoStreamPtr video_stream = std::make_shared(); + if (image_is_still) { + video_stream->set_video_type(VideoStream::kVideoTypeStill); + } else { + video_stream->set_video_type(VideoStream::kVideoTypeVideo); + + video_stream->set_frame_rate(frame_rate); + video_stream->set_start_time(avstream->start_time); + } + + video_stream->set_width(avstream->codecpar->width); + video_stream->set_height(avstream->codecpar->height); + video_stream->set_interlacing(interlacing); + video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio); + + AVPixelFormat compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)); + video_stream->set_format(GetNativePixelFormat(compatible_pix_fmt)); + video_stream->set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); + + str = video_stream; - if (image_is_still) { - video_stream->set_video_type(VideoStream::kVideoTypeStill); } else { - video_stream->set_video_type(VideoStream::kVideoTypeVideo); - video_stream->set_frame_rate(frame_rate); - video_stream->set_start_time(avstream->start_time); + // Create an audio stream object + AudioStreamPtr audio_stream = std::make_shared(); + + uint64_t channel_layout = avstream->codecpar->channel_layout; + if (!channel_layout) { + channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); + } + + audio_stream->set_channel_layout(channel_layout); + audio_stream->set_channels(avstream->codecpar->channels); + audio_stream->set_sample_rate(avstream->codecpar->sample_rate); + + if (avstream->duration == AV_NOPTS_VALUE) { + // Loop through stream until we get the whole duration + Instance instance; + instance.Open(filename.toUtf8(), avstream->index); + + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + + int64_t new_dur; + + do { + new_dur = frame->pts; + } while (instance.GetFrame(pkt, frame) >= 0); + + avstream->duration = new_dur; + + av_frame_free(&frame); + av_packet_free(&pkt); + + instance.Close(); + } + + str = audio_stream; + } - video_stream->set_width(avstream->codecpar->width); - video_stream->set_height(avstream->codecpar->height); - video_stream->set_format(GetNativePixelFormat(FFmpegCommon::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)))); - video_stream->set_interlacing(interlacing); - video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio); - - str = video_stream; - - } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && decoder) { - - // Create an audio stream object - AudioStreamPtr audio_stream = std::make_shared(); - - uint64_t channel_layout = avstream->codecpar->channel_layout; - if (!channel_layout) { - channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); - } - - audio_stream->set_channel_layout(channel_layout); - audio_stream->set_channels(avstream->codecpar->channels); - audio_stream->set_sample_rate(avstream->codecpar->sample_rate); - - if (avstream->duration == AV_NOPTS_VALUE) { - // Loop through stream until we get the whole duration - FFmpegDecoderInstance instance(filename_c, i); - - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - - int64_t new_dur; - - do { - new_dur = frame->pts; - } while (instance.GetFrame(pkt, frame) >= 0); - - avstream->duration = new_dur; - - av_frame_free(&frame); - av_packet_free(&pkt); - } - - str = audio_stream; - } else { // This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file @@ -658,50 +441,23 @@ FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cance return footage; } -void FFmpegDecoder::FFmpegError(int error_code) +QString FFmpegDecoder::FFmpegError(int error_code) { char err[1024]; av_strerror(error_code, err, 1024); - - Error(QStringLiteral("Error decoding %1 - %2 %3").arg(stream()->footage()->filename(), - QString::number(error_code), - err)); + return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } -void FFmpegDecoder::Error(const QString &s) -{ - qWarning() << s; - - ClearResources(); -} - -bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams &p) +bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioParams ¶ms, const QAtomicInt *cancelled) { // Iterate through each audio frame and extract the PCM data AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); - // Check if we already have a conform of this type - QString conformed_fn = GetConformedFilename(p); - - if (QFileInfo::exists(conformed_fn)) { - - // If we have one, and we can open it correctly, we can use it as-is - WaveInput input(conformed_fn); - if (input.open()) { - audio_stream->append_conformed_version(p); - - input.close(); - - return true; - } - } - - // Conform doesn't exist, we'll have to produce one - FFmpegDecoderInstance index_instance(stream()->footage()->filename().toUtf8(), - stream()->index()); + // Seek to starting point + instance_.Seek(0); // Handle NULL channel layout - uint64_t channel_layout = ValidateChannelLayout(index_instance.stream()); + uint64_t channel_layout = ValidateChannelLayout(instance_.avstream()); if (!channel_layout) { qCritical() << "Failed to determine channel layout of audio file, could not conform"; return false; @@ -709,18 +465,18 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams // Create resampling context SwrContext* resampler = swr_alloc_set_opts(nullptr, - p.channel_layout(), - FFmpegCommon::GetFFmpegSampleFormat(p.format()), - p.sample_rate(), + params.channel_layout(), + FFmpegUtils::GetFFmpegSampleFormat(params.format()), + params.sample_rate(), channel_layout, - static_cast(index_instance.stream()->codecpar->format), - index_instance.stream()->codecpar->sample_rate, + static_cast(instance_.avstream()->codecpar->format), + instance_.avstream()->codecpar->sample_rate, 0, nullptr); swr_init(resampler); - WaveOutput wave_out(conformed_fn, p); + WaveOutput wave_out(filename, params); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); @@ -735,7 +491,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams break; } - ret = index_instance.GetFrame(pkt, frame); + ret = instance_.GetFrame(pkt, frame); if (ret < 0) { @@ -752,7 +508,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams // Allocate buffers int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); - char* data = new char[p.samples_to_bytes(nb_samples)]; + char* data = new char[params.samples_to_bytes(nb_samples)]; // Resample audio to our destination parameters nb_samples = swr_convert(resampler, @@ -769,7 +525,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams } // Write packed WAV data to the disk cache - wave_out.write(data, p.samples_to_bytes(nb_samples)); + wave_out.write(data, params.samples_to_bytes(nb_samples)); // If we allocated an output for the resampler, delete it here if (data != reinterpret_cast(frame->data[0])) { @@ -780,18 +536,6 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams } wave_out.close(); - - if (success) { - - // If our conform succeeded, add it - audio_stream->append_conformed_version(p); - - } else { - - // Audio index didn't complete, delete it - QFile(conformed_fn).remove(); - - } } else { qWarning() << "Failed to open WAVE output for indexing"; } @@ -804,19 +548,31 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams return success; } -PixelFormat::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt) +VideoParams::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_RGB24: - return PixelFormat::PIX_FMT_RGB8; case AV_PIX_FMT_RGBA: - return PixelFormat::PIX_FMT_RGBA8; + return VideoParams::kFormatUnsigned8; case AV_PIX_FMT_RGB48: - return PixelFormat::PIX_FMT_RGB16U; case AV_PIX_FMT_RGBA64: - return PixelFormat::PIX_FMT_RGBA16U; + return VideoParams::kFormatUnsigned16; default: - return PixelFormat::PIX_FMT_INVALID; + return VideoParams::kFormatInvalid; + } +} + +int FFmpegDecoder::GetNativeChannelCount(AVPixelFormat pix_fmt) +{ + switch (pix_fmt) { + case AV_PIX_FMT_RGB24: + case AV_PIX_FMT_RGB48: + return VideoParams::kRGBChannelCount; + case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_RGBA64: + return VideoParams::kRGBAChannelCount; + default: + return 0; } } @@ -829,116 +585,15 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) return av_get_default_channel_layout(stream->codecpar->channels); } -bool FFmpegDecoder::StreamUsesMultipleInstances(StreamPtr stream) +void FFmpegDecoder::FFmpegBufferToNativeBuffer(uint8_t **input_data, int *input_linesize, uint8_t** output_buffer, int* output_linesize) { - return stream->type() == Stream::kVideo - && std::static_pointer_cast(stream)->video_type() != VideoStream::kVideoTypeStill; -} - -FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, const rational& ts, uint8_t** input_data, int* input_linesize) -{ - if (divider != scale_divider_) { - FreeScaler(); - InitScaler(divider); - } - - // Create frame to return - FramePtr copy = Frame::Create(); - copy->set_video_params(VideoParams(width, - height, - native_pix_fmt_, - std::static_pointer_cast(stream())->pixel_aspect_ratio(), - std::static_pointer_cast(stream())->interlacing(), - divider)); - copy->set_timestamp(ts); - copy->allocate(); - - // Convert frame to RGB/A for the rest of the pipeline - uint8_t* output_data = reinterpret_cast(copy->data()); - int output_linesize = copy->linesize_bytes(); - sws_scale(scale_ctx_, input_data, input_linesize, 0, - VideoParams::GetScaledDimension(height, divider), - &output_data, - &output_linesize); - - return copy; -} - -int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame) -{ - bool eof = false; - - int ret; - - // Clear any previous frames - av_frame_unref(frame); - - while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) { - - // Find next packet in the correct stream index - do { - // Free buffer in packet if there is one - av_packet_unref(pkt); - - // Read packet from file - ret = av_read_frame(fmt_ctx_, pkt); - } while (pkt->stream_index != avstream_->index && ret >= 0); - - if (ret == AVERROR_EOF) { - // Don't break so that receive gets called again, but don't try to read again - eof = true; - - // Send a null packet to signal end of - avcodec_send_packet(codec_ctx_, nullptr); - } else if (ret < 0) { - // Handle other error by breaking loop and returning the code we received - break; - } else { - // Successful read, send the packet - ret = avcodec_send_packet(codec_ctx_, pkt); - - // We don't need the packet anymore, so free it - av_packet_unref(pkt); - - if (ret < 0) { - break; - } - } - } - - return ret; -} - -QMutex *FFmpegDecoderInstance::cache_lock() -{ - return &cache_lock_; -} - -QWaitCondition *FFmpegDecoderInstance::cache_wait_cond() -{ - return &cache_wait_cond_; -} - -bool FFmpegDecoderInstance::IsWorking() -{ - QMutexLocker locker(&is_working_mutex_); - return is_working_; -} - -void FFmpegDecoderInstance::SetWorking(bool working) -{ - QMutexLocker locker(&is_working_mutex_); - is_working_ = working; -} - -void FFmpegDecoderInstance::Seek(int64_t timestamp) -{ - avcodec_flush_buffers(codec_ctx_); - av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); + instance_.avstream()->codecpar->height, + output_buffer, + output_linesize); } /* OLD UNUSED CODE: Keeping this around in case the code proves useful @@ -998,63 +653,51 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) } */ -void FFmpegDecoderInstance::ClearFrameCache() +void FFmpegDecoder::ClearFrameCache() { cached_frames_.clear(); cache_at_eof_ = false; cache_at_zero_ = false; } -FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, int divider, bool cache_is_locked) +FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_ts, int divider) { - if (!cache_is_locked) { - cache_lock_.lock(); - } - - if (scale_divider_ != divider) { - FreeScaler(); - InitScaler(divider); - } - int64_t seek_ts = target_ts; bool still_seeking = false; - // CacheCouldContainTime uses cache_target_time_, so we'll temporarily set it to the last frame's TS - if (!cached_frames_.isEmpty()) { - cache_target_time_ = cached_frames_.last()->timestamp(); - } - // If the frame wasn't in the frame cache, see if this frame cache is too old to use - if (!CacheCouldContainTime(target_ts)) { + if (cached_frames_.isEmpty() + || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) { ClearFrameCache(); - Seek(seek_ts); + instance_.Seek(seek_ts); if (seek_ts == 0) { cache_at_zero_ = true; } still_seeking = true; + } else { + // Search cache for frame + FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts); + if (cached_frame) { + return cached_frame; + } } - cache_target_time_ = target_ts; - int ret; AVPacket* pkt = av_packet_alloc(); FFmpegFramePool::ElementPtr return_frame = nullptr; // Allocate a new frame - AVFrameWrapper working_frame; - - bool unlocked = false; + AVFrame* working_frame = av_frame_alloc(); while (true) { // Pull from the decoder - ret = GetFrame(pkt, working_frame.frame()); + ret = instance_.GetFrame(pkt, working_frame); // Handle any errors that aren't EOF (EOF is handled later on) if (ret < 0 && ret != AVERROR_EOF) { - cache_lock_.unlock(); qCritical() << "Failed to retrieve frame:" << ret; break; } @@ -1062,10 +705,10 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& if (still_seeking) { // Handle a failure to seek (occurs on some media) // We'll only be here if the frame cache was emptied earlier - if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame.frame()->pts > target_ts)) { + if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->pts > target_ts)) { seek_ts = qMax(static_cast(0), seek_ts - second_ts_); - Seek(seek_ts); + instance_.Seek(seek_ts); if (seek_ts == 0) { cache_at_zero_ = true; } @@ -1078,11 +721,6 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& } } - if (cache_is_locked) { - cache_is_locked = false; - } else if (unlocked) { - cache_lock_.lock(); - } if (ret == AVERROR_EOF) { @@ -1095,53 +733,29 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& return_frame = cached_frames_.last(); } - cache_wait_cond_.wakeAll(); - cache_lock_.unlock(); break; } else { - // Whatever it is, keep this frame in memory for the time being just in case - if (!frame_pool_) { - qCritical() << "Cannot retrieve video without a valid frame pool"; - cache_lock_.unlock(); - break; + // Cut down to thread count - 1 before we acquire a new frame + if (cached_frames_.size() == QThread::idealThreadCount()) { + RemoveFirstFrame(); } - // Cut down to thread count - 1 before we acquire a new frame - TruncateCacheRangeToFrames(QThread::idealThreadCount() -1); - - FFmpegFramePool::ElementPtr cached = frame_pool_->Get(); + FFmpegFramePool::ElementPtr cached = pool_.Get(); if (!cached) { qCritical() << "Frame pool failed to return a valid frame - out of memory?"; - cache_lock_.unlock(); break; } - { - uint8_t* scale_data[4]; - int scale_linesize[4]; - - av_image_fill_arrays(scale_data, - scale_linesize, - cached->data(), - static_cast(working_frame.frame()->format), - VideoParams::GetScaledDimension(working_frame.frame()->width, divider), - VideoParams::GetScaledDimension(working_frame.frame()->height, divider), - 1); - - sws_scale(scale_ctx_, - working_frame.frame()->data, - working_frame.frame()->linesize, - 0, - working_frame.frame()->height, - scale_data, - scale_linesize); - } + // Store in queue, converting to native format + uint8_t* destination_data = cached->data(); + int destination_linesize = Frame::generate_linesize_bytes(VideoParams::GetScaledDimension(instance_.avstream()->codecpar->width, divider), native_pix_fmt_, native_channel_count_); + FFmpegBufferToNativeBuffer(working_frame->data, working_frame->linesize, &destination_data, &destination_linesize); // Set timestamp so this frame can be identified later - cached->set_timestamp(working_frame.frame()->pts); + cached->set_timestamp(working_frame->pts); // Store frame before just in case FFmpegFramePool::ElementPtr previous; @@ -1154,10 +768,6 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& // Append this frame and signal to other threads that a new frame has arrived cached_frames_.append(cached); - cache_wait_cond_.wakeAll(); - cache_lock_.unlock(); - unlocked = true; - // If this is a valid frame, see if this or the frame before it are the one we need if (cached->timestamp() == target_ts) { return_frame = cached; @@ -1174,18 +784,12 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& } } + av_frame_free(&working_frame); av_packet_free(&pkt); return return_frame; } -void FFmpegDecoder::ClearResources() -{ - FreeScaler(); - - open_ = false; -} - void FFmpegDecoder::InitScaler(int divider) { VideoStream* vs = static_cast(stream().get()); @@ -1193,9 +797,9 @@ void FFmpegDecoder::InitScaler(int divider) int scaled_width = VideoParams::GetScaledDimension(vs->width(), divider); int scaled_height = VideoParams::GetScaledDimension(vs->height(), divider); - scale_ctx_ = sws_getContext(scaled_width, - scaled_height, - src_pix_fmt_, + scale_ctx_ = sws_getContext(vs->width(), + vs->height(), + static_cast(instance_.avstream()->codecpar->format), scaled_width, scaled_height, ideal_pix_fmt_, @@ -1221,40 +825,7 @@ void FFmpegDecoder::FreeScaler() } } -void FFmpegDecoderInstance::InitScaler(int divider) -{ - int scaled_width = VideoParams::GetScaledDimension(avstream_->codecpar->width, divider); - int scaled_height = VideoParams::GetScaledDimension(avstream_->codecpar->height, divider); - - scale_ctx_ = sws_getContext(avstream_->codecpar->width, - avstream_->codecpar->height, - static_cast(avstream_->codecpar->format), - scaled_width, - scaled_height, - static_cast(avstream_->codecpar->format), - SWS_FAST_BILINEAR, - nullptr, - nullptr, - nullptr); - - if (scale_ctx_) { - scale_divider_ = divider; - } else { - scale_divider_ = 0; - } -} - -void FFmpegDecoderInstance::FreeScaler() -{ - if (scale_ctx_) { - sws_freeContext(scale_ctx_); - scale_ctx_ = nullptr; - - scale_divider_ = 0; - } -} - -int64_t FFmpegDecoderInstance::RangeStart() const +/*int64_t FFmpegDecoder::RangeStart() const { if (cached_frames_.isEmpty()) { return AV_NOPTS_VALUE; @@ -1262,7 +833,7 @@ int64_t FFmpegDecoderInstance::RangeStart() const return cached_frames_.first()->timestamp(); } -int64_t FFmpegDecoderInstance::RangeEnd() const +int64_t FFmpegDecoder::RangeEnd() const { if (cached_frames_.isEmpty()) { return AV_NOPTS_VALUE; @@ -1270,7 +841,7 @@ int64_t FFmpegDecoderInstance::RangeEnd() const return cached_frames_.last()->timestamp(); } -bool FFmpegDecoderInstance::CacheContainsTime(const int64_t &t) const +bool FFmpegDecoder::CacheContainsTime(const int64_t &t) const { return !cached_frames_.isEmpty() && ((RangeStart() <= t && RangeEnd() >= t) @@ -1278,22 +849,22 @@ bool FFmpegDecoderInstance::CacheContainsTime(const int64_t &t) const || (cache_at_eof_ && t > cached_frames_.last()->timestamp())); } -bool FFmpegDecoderInstance::CacheWillContainTime(const int64_t &t) const +bool FFmpegDecoder::CacheWillContainTime(const int64_t &t) const { return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= cache_target_time_; } -bool FFmpegDecoderInstance::CacheCouldContainTime(const int64_t &t) const +bool FFmpegDecoder::CacheCouldContainTime(const int64_t &t) const { return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= (cache_target_time_ + 2*second_ts_); } -bool FFmpegDecoderInstance::CacheIsEmpty() const +bool FFmpegDecoder::CacheIsEmpty() const { return cached_frames_.isEmpty(); -} +}*/ -FFmpegFramePool::ElementPtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const +FFmpegFramePool::ElementPtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const { if (t < cached_frames_.first()->timestamp()) { @@ -1328,17 +899,14 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::GetFrameFromCache(const int64 return nullptr; } -void FFmpegDecoderInstance::RemoveFramesBefore(const qint64 &t) +/*void FFmpegDecoder::RemoveFramesBefore(const qint64 &t) { - // We keep one frame in memory as an identifier for what pts the decoder is up to - int min_frames = (MemoryPoolLimitReached() && !IsWorking()) ? 0 : 1; - - while (cached_frames_.size() > min_frames && cached_frames_.first()->last_accessed() < t) { + while (!cached_frames_.isEmpty() && cached_frames_.first()->last_accessed() < t) { RemoveFirstFrame(); } } -int FFmpegDecoderInstance::TruncateCacheRangeToTime(const qint64 &t) +int FFmpegDecoder::TruncateCacheRangeToTime(const qint64 &t) { int counter = 0; @@ -1351,7 +919,7 @@ int FFmpegDecoderInstance::TruncateCacheRangeToTime(const qint64 &t) return counter; } -int FFmpegDecoderInstance::TruncateCacheRangeToFrames(int nb_frames) +int FFmpegDecoder::TruncateCacheRangeToFrames(int nb_frames) { int counter = 0; @@ -1362,34 +930,30 @@ int FFmpegDecoderInstance::TruncateCacheRangeToFrames(int nb_frames) } return counter; -} +}*/ -void FFmpegDecoderInstance::RemoveFirstFrame() +void FFmpegDecoder::RemoveFirstFrame() { cached_frames_.removeFirst(); cache_at_zero_ = false; } -FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) : +FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), - opts_(nullptr), - scale_ctx_(nullptr), - scale_divider_(0), - frame_pool_(nullptr), - is_working_(false), - cache_at_zero_(false), - cache_at_eof_(false), - clear_timer_(nullptr) + opts_(nullptr) +{ +} + +bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index) { // Open file in a format context int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr); // Handle format context error if (error_code != 0) { - qCritical() << "Failed to open input:" << filename << error_code; - ClearResources(); - return; + qCritical() << "Failed to open input:" << filename << FFmpegError(error_code); + return false; } // Get stream information from format @@ -1397,9 +961,8 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in // Handle get stream information error if (error_code < 0) { - qCritical() << "Failed to find stream info:" << error_code; - ClearResources(); - return; + qCritical() << "Failed to find stream info:" << FFmpegError(error_code); + return false; } // Get reference to correct AVStream @@ -1410,17 +973,18 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in // Handle failure to find decoder if (codec == nullptr) { - qCritical() << "Failed to find appropriate decoder for this codec:" << filename << stream_index << avstream_->codecpar->codec_id; - ClearResources(); - return; + qCritical() << "Failed to find appropriate decoder for this codec:" + << filename + << stream_index + << avstream_->codecpar->codec_id; + return false; } // Allocate context for the decoder codec_ctx_ = avcodec_alloc_context3(codec); if (codec_ctx_ == nullptr) { qCritical() << "Failed to allocate codec context"; - ClearResources(); - return; + return false; } // Copy parameters from the AVStream to the AVCodecContext @@ -1429,8 +993,7 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in // Handle failure to copy parameters if (error_code < 0) { qCritical() << "Failed to copy parameters from AVStream to AVCodecContext"; - ClearResources(); - return; + return false; } // Set multithreading setting @@ -1447,50 +1010,14 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in char buf[50]; av_strerror(error_code, buf, 50); qCritical() << "Failed to open codec" << codec->id << error_code << buf; - ClearResources(); - return; + return false; } - // Create frame pool - if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // Start clear timer - clear_timer_ = new QTimer(); - clear_timer_->setInterval(kMaxFrameLife); - clear_timer_->moveToThread(qApp->thread()); - connect(clear_timer_, &QTimer::timeout, this, &FFmpegDecoderInstance::ClearTimerEvent, Qt::DirectConnection); - QMetaObject::invokeMethod(clear_timer_, "start", Qt::QueuedConnection); - } - - // Store one second in the source's timebase - second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base))); + return true; } -FFmpegDecoderInstance::~FFmpegDecoderInstance() +void FFmpegDecoder::Instance::Close() { - ClearResources(); -} - -bool FFmpegDecoderInstance::IsValid() const -{ - return codec_ctx_; -} - -void FFmpegDecoderInstance::SetFramePool(FFmpegFramePool *frame_pool) -{ - frame_pool_ = frame_pool; -} - -void FFmpegDecoderInstance::ClearResources() -{ - ClearFrameCache(); - - // Stop timer - if (clear_timer_) { - QMetaObject::invokeMethod(clear_timer_, "stop", Qt::QueuedConnection); - clear_timer_->deleteLater(); - clear_timer_ = nullptr; - } - if (opts_) { av_dict_free(&opts_); opts_ = nullptr; @@ -1505,20 +1032,57 @@ void FFmpegDecoderInstance::ClearResources() avformat_close_input(&fmt_ctx_); fmt_ctx_ = nullptr; } - - FreeScaler(); } -void FFmpegDecoderInstance::ClearTimerEvent() +int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) { - cache_lock()->lock(); - RemoveFramesBefore(QDateTime::currentMSecsSinceEpoch() - kMaxFrameLife); - cache_lock()->unlock(); + bool eof = false; + + int ret; + + // Clear any previous frames + av_frame_unref(frame); + + while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) { + + // Find next packet in the correct stream index + do { + // Free buffer in packet if there is one + av_packet_unref(pkt); + + // Read packet from file + ret = av_read_frame(fmt_ctx_, pkt); + } while (pkt->stream_index != avstream_->index && ret >= 0); + + if (ret == AVERROR_EOF) { + // Don't break so that receive gets called again, but don't try to read again + eof = true; + + // Send a null packet to signal end of + avcodec_send_packet(codec_ctx_, nullptr); + } else if (ret < 0) { + // Handle other error by breaking loop and returning the code we received + break; + } else { + // Successful read, send the packet + ret = avcodec_send_packet(codec_ctx_, pkt); + + // We don't need the packet anymore, so free it + av_packet_unref(pkt); + + if (ret < 0) { + break; + } + } + } + + return ret; } -uint qHash(const FFmpegDecoder::FFmpegFramePoolKey &r) +void FFmpegDecoder::Instance::Seek(int64_t timestamp) { - return ::qHash(r.width) ^ ::qHash(r.height) ^ ::qHash(r.format); + avcodec_flush_buffers(codec_ctx_); + av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); } OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index b2d91800f..1f820d746 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -32,7 +32,6 @@ extern "C" { #include #include -#include "audio/sampleformat.h" #include "avframeptr.h" #include "codec/decoder.h" #include "codec/waveoutput.h" @@ -41,99 +40,6 @@ extern "C" { OLIVE_NAMESPACE_ENTER -class FFmpegDecoderInstance : public QObject { - Q_OBJECT -public: - FFmpegDecoderInstance(const char* filename, int stream_index); - virtual ~FFmpegDecoderInstance(); - - DISABLE_COPY_MOVE(FFmpegDecoderInstance) - - bool IsValid() const; - - void SetFramePool(FFmpegFramePool* frame_pool); - - int64_t RangeStart() const; - int64_t RangeEnd() const; - bool CacheContainsTime(const int64_t& t) const; - bool CacheWillContainTime(const int64_t& t) const; - bool CacheCouldContainTime(const int64_t& t) const; - bool CacheIsEmpty() const; - FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const; - - void RemoveFramesBefore(const qint64& t); - int TruncateCacheRangeToTime(const qint64& t); - int TruncateCacheRangeToFrames(int nb_frames); - void RemoveFirstFrame(); - - AVFormatContext* fmt_ctx() const - { - return fmt_ctx_; - } - - AVStream* stream() const - { - return avstream_; - } - - void ClearFrameCache(); - - FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider, bool cache_is_locked); - - /** - * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) - * - * @return - * - * An FFmpeg error code, or >= 0 on success - */ - int GetFrame(AVPacket* pkt, AVFrame* frame); - - QMutex* cache_lock(); - QWaitCondition* cache_wait_cond(); - - bool IsWorking(); - void SetWorking(bool working); - -private: - void ClearResources(); - - void Seek(int64_t timestamp); - - void InitScaler(int divider); - void FreeScaler(); - - AVFormatContext* fmt_ctx_; - AVCodecContext* codec_ctx_; - AVStream* avstream_; - AVDictionary* opts_; - - SwsContext* scale_ctx_; - int scale_divider_; - - int64_t second_ts_; - - QWaitCondition cache_wait_cond_; - QMutex cache_lock_; - QList cached_frames_; - FFmpegFramePool* frame_pool_; - - int64_t cache_target_time_; - - bool is_working_; - QMutex is_working_mutex_; - - bool cache_at_zero_; - bool cache_at_eof_; - - QTimer* clear_timer_; - static const int kMaxFrameLife; - -private slots: - void ClearTimerEvent(); - -}; - /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder */ @@ -147,40 +53,62 @@ public: // Destructor virtual ~FFmpegDecoder() override; - virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override; - - virtual bool Open() override; - virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; - virtual SampleBufferPtr RetrieveAudio(const rational &timecode, const rational &length, const AudioParams& params) override; - virtual void Close() override; - virtual QString id() override; - virtual bool SupportsVideo() override; - virtual bool SupportsAudio() override; + virtual bool SupportsVideo() override{return true;} + virtual bool SupportsAudio() override{return true;} - virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioParams& p) override; + virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override; - struct FFmpegFramePoolKey { - int width; - int height; - AVPixelFormat format; - - bool operator==(const FFmpegFramePoolKey& k) const - { - return width == k.width && height == k.height && format == k.format; - } - }; +protected: + virtual bool OpenInternal() override; + virtual FramePtr RetrieveVideoInternal(const rational &timecode, const int& divider) override; + virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled) override; + virtual void CloseInternal() override; private: - /** - * @brief Handle an error - * - * Immediately closes the Decoder (freeing memory resources) and sends the string provided to the warning stream. - * As this function closes the Decoder, no further Decoder functions should be performed after this is called - * (unless the Decoder is opened again first). - */ - void Error(const QString& s); + class Instance + { + public: + Instance(); + + ~Instance() + { + Close(); + } + + bool Open(const char* filename, int stream_index); + + void Close(); + + /** + * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) + * + * @return + * + * An FFmpeg error code, or >= 0 on success + */ + int GetFrame(AVPacket* pkt, AVFrame* frame); + + void Seek(int64_t timestamp); + + AVFormatContext* fmt_ctx() const + { + return fmt_ctx_; + } + + AVStream* avstream() const + { + return avstream_; + } + + private: + AVFormatContext* fmt_ctx_; + AVCodecContext* codec_ctx_; + AVStream* avstream_; + AVDictionary* opts_; + + }; /** * @brief Handle an FFmpeg error code @@ -190,42 +118,50 @@ private: * * @param error_code */ - void FFmpegError(int error_code); - - void ClearResources(); + static QString FFmpegError(int error_code); void InitScaler(int divider); void FreeScaler(); FramePtr RetrieveStillImage(const rational& timecode, const int& divider); - static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt); + static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt); + static int GetNativeChannelCount(AVPixelFormat pix_fmt); static uint64_t ValidateChannelLayout(AVStream *stream); - static bool StreamUsesMultipleInstances(StreamPtr stream); + void FFmpegBufferToNativeBuffer(uint8_t** input_data, int* input_linesize, uint8_t **output_buffer, int *output_linesize); - FramePtr BuffersToNativeFrame(int divider, int width, int height, const rational &ts, uint8_t **input_data, int* input_linesize); + FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const; + + void ClearFrameCache(); + + FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider); + + void RemoveFirstFrame(); SwsContext* scale_ctx_; int scale_divider_; - AVPixelFormat src_pix_fmt_; AVPixelFormat ideal_pix_fmt_; - PixelFormat::Format native_pix_fmt_; + VideoParams::Format native_pix_fmt_; + int native_channel_count_; - struct FFmpegFramePoolValue { - FFmpegFramePool* pool = nullptr; - int handles = 0; - }; + FFmpegFramePool pool_; - static QHash< Stream*, QList > instance_map_; - static QHash< FFmpegFramePoolKey, FFmpegFramePoolValue > frame_pool_map_; - static QMutex instance_map_lock_; + int64_t second_ts_; + + QList cached_frames_; + + bool is_working_; + QMutex is_working_mutex_; + + bool cache_at_zero_; + bool cache_at_eof_; + + Instance instance_; }; -uint qHash(const FFmpegDecoder::FFmpegFramePoolKey& r); - OLIVE_NAMESPACE_EXIT #endif // FFMPEGDECODER_H diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index c31ac6b5a..3e95937db 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -26,8 +26,7 @@ extern "C" { #include -#include "ffmpegcommon.h" -#include "render/pixelformat.h" +#include "common/ffmpegutils.h" OLIVE_NAMESPACE_ENTER @@ -36,7 +35,8 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : fmt_ctx_(nullptr), video_stream_(nullptr), video_codec_ctx_(nullptr), - video_scale_ctx_(nullptr), + video_alpha_scale_ctx_(nullptr), + video_noalpha_scale_ctx_(nullptr), audio_stream_(nullptr), audio_codec_ctx_(nullptr), audio_resample_ctx_(nullptr), @@ -72,29 +72,49 @@ bool FFmpegEncoder::Open() } // This is the format we will expect frames received in Write() to be in - PixelFormat::Format native_pixel_fmt = params().video_params().format(); + VideoParams::Format native_pixel_fmt = params().video_params().format(); // This is the format we will need to convert the frame to for swscale to understand it - video_conversion_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(native_pixel_fmt); + video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt); // This is the equivalent pixel format above as an AVPixelFormat that swscale can understand - AVPixelFormat src_pix_fmt = FFmpegCommon::GetFFmpegPixelFormat(video_conversion_fmt_); + AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, + VideoParams::kRGBAChannelCount); + + AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_, + VideoParams::kRGBChannelCount); + + if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) { + Error(QStringLiteral("Failed to find suitable pixel format for this buffer")); + return false; + } // This is the pixel format the encoder wants to encode to AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt; // Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it // before encoding. Even if we don't, this may be useful for converting between linesizes, etc. - video_scale_ctx_ = sws_getContext(params().video_params().width(), - params().video_params().height(), - src_pix_fmt, - params().video_params().width(), - params().video_params().height(), - encoder_pix_fmt, - 0, - nullptr, - nullptr, - nullptr); + video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(), + params().video_params().height(), + src_alpha_pix_fmt, + params().video_params().width(), + params().video_params().height(), + encoder_pix_fmt, + 0, + nullptr, + nullptr, + nullptr); + + video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(), + params().video_params().height(), + src_noalpha_pix_fmt, + params().video_params().width(), + params().video_params().height(), + encoder_pix_fmt, + 0, + nullptr, + nullptr, + nullptr); } // Initialize an audio stream if it's enabled @@ -157,19 +177,22 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time) // We may need to convert this frame to a frame that swscale will understand if (frame->format() != video_conversion_fmt_) { - frame = PixelFormat::ConvertPixelFormat(frame, video_conversion_fmt_); + frame = frame->convert(video_conversion_fmt_); } // Use swscale context to convert formats/linesizes input_data = frame->const_data(); input_linesize = frame->linesize_bytes(); - error_code = sws_scale(video_scale_ctx_, + + error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_, reinterpret_cast(&input_data), &input_linesize, 0, frame->height(), encoded_frame->data, encoded_frame->linesize); + + if (error_code < 0) { FFmpegError("Failed to scale frame", error_code); goto fail; @@ -208,7 +231,7 @@ void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file) audio_codec_ctx_->sample_fmt, audio_codec_ctx_->sample_rate, static_cast(pcm_info.channel_layout()), - FFmpegCommon::GetFFmpegSampleFormat(pcm_info.format()), + FFmpegUtils::GetFFmpegSampleFormat(pcm_info.format()), pcm_info.sample_rate(), 0, nullptr); @@ -300,9 +323,14 @@ void FFmpegEncoder::Close() open_ = false; } - if (video_scale_ctx_) { - sws_freeContext(video_scale_ctx_); - video_scale_ctx_ = nullptr; + if (video_alpha_scale_ctx_) { + sws_freeContext(video_alpha_scale_ctx_); + video_alpha_scale_ctx_ = nullptr; + } + + if (video_noalpha_scale_ctx_) { + sws_freeContext(video_noalpha_scale_ctx_); + video_noalpha_scale_ctx_ = nullptr; } if (video_codec_ctx_) { diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 4efc76754..6b28fb62f 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -47,6 +47,11 @@ public: virtual void Close() override; + virtual VideoParams::Format GetDesiredPixelFormat() const override + { + return video_conversion_fmt_; + } + private: /** * @brief Handle an error @@ -80,8 +85,9 @@ private: AVStream* video_stream_; AVCodecContext* video_codec_ctx_; - SwsContext* video_scale_ctx_; - PixelFormat::Format video_conversion_fmt_; + SwsContext* video_alpha_scale_ctx_; + SwsContext* video_noalpha_scale_ctx_; + VideoParams::Format video_conversion_fmt_; AVStream* audio_stream_; AVCodecContext* audio_codec_ctx_; diff --git a/app/codec/ffmpeg/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp index cca05aed4..520f5d0ac 100644 --- a/app/codec/ffmpeg/ffmpegframepool.cpp +++ b/app/codec/ffmpeg/ffmpegframepool.cpp @@ -20,9 +20,7 @@ #include "ffmpegframepool.h" -extern "C" { -#include -} +#include "codec/frame.h" OLIVE_NAMESPACE_ENTER @@ -30,32 +28,24 @@ FFmpegFramePool::FFmpegFramePool(int element_count) : MemoryPool(element_count), width_(0), height_(0), - format_(AV_PIX_FMT_NONE) + format_(VideoParams::kFormatInvalid), + channel_count_(0) { } -void FFmpegFramePool::SetParameters(int width, int height, AVPixelFormat format) +void FFmpegFramePool::SetParameters(int width, int height, VideoParams::Format format, int channel_count) { Clear(); width_ = width; height_ = height; format_ = format; + channel_count_ = channel_count; } size_t FFmpegFramePool::GetElementSize() { - int buf_sz = av_image_get_buffer_size(static_cast(format_), - width_, - height_, - 1); - - if (buf_sz < 0) { - qDebug() << "Failed to find buffer size:" << buf_sz; - return 0; - } - - return buf_sz; + return Frame::generate_linesize_bytes(width_, format_, channel_count_) * height_; } OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h index 81a31312e..f97a948d0 100644 --- a/app/codec/ffmpeg/ffmpegframepool.h +++ b/app/codec/ffmpeg/ffmpegframepool.h @@ -22,7 +22,6 @@ #define FFMPEGFRAMEPOOL_H #include "common/memorypool.h" -#include "render/pixelformat.h" #include "render/videoparams.h" OLIVE_NAMESPACE_ENTER @@ -32,7 +31,7 @@ class FFmpegFramePool : public MemoryPool public: FFmpegFramePool(int element_count); - void SetParameters(int width, int height, AVPixelFormat format); + void SetParameters(int width, int height, VideoParams::Format format, int channel_count); const int& width() const { @@ -52,7 +51,9 @@ private: int height_; - AVPixelFormat format_; + VideoParams::Format format_; + + int channel_count_; }; diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index 8cd0a53bd..7d474a1c5 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -20,10 +20,13 @@ #include "frame.h" +#include #include #include #include +#include "common/oiioutils.h" + OLIVE_NAMESPACE_ENTER Frame::Frame() : @@ -45,33 +48,14 @@ void Frame::set_video_params(const VideoParams ¶ms) { params_ = params; - // Align linesize to 32 - linesize_ = qCeil(static_cast(width()) / 32.0) * 32; + linesize_ = generate_linesize_bytes(width(), params_.format(), params_.channel_count()); + linesize_pixels_ = linesize_ / params_.GetBytesPerPixel(); } -int Frame::linesize_pixels() const +int Frame::generate_linesize_bytes(int width, VideoParams::Format format, int channel_count) { - return linesize_; -} - -int Frame::linesize_bytes() const -{ - return linesize_pixels() * PixelFormat::BytesPerPixel(params_.format()); -} - -const int &Frame::width() const -{ - return params_.effective_width(); -} - -const int &Frame::height() const -{ - return params_.effective_height(); -} - -const PixelFormat::Format &Frame::format() const -{ - return params_.format(); + // Align to 32 bytes (not sure if this is necessary?) + return VideoParams::GetBytesPerPixel(format, channel_count) * ((width + 31) & ~31); } Color Frame::get_pixel(int x, int y) const @@ -80,11 +64,9 @@ Color Frame::get_pixel(int x, int y) const return Color(); } - int pixel_index = y * linesize_pixels() + x; + int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); - int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1); - - return Color(data_.data() + byte_offset, video_params().format()); + return Color(data_.data() + byte_offset, video_params().format(), video_params().channel_count()); } bool Frame::contains_pixel(int x, int y) const @@ -98,57 +80,53 @@ void Frame::set_pixel(int x, int y, const Color &c) return; } - int pixel_index = y * linesize_pixels() + x; + int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); - int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1); - - c.toData(data_.data() + byte_offset, video_params().format()); + c.toData(data_.data() + byte_offset, video_params().format(), video_params().channel_count()); } -const rational &Frame::timestamp() const -{ - return timestamp_; -} - -void Frame::set_timestamp(const rational ×tamp) -{ - timestamp_ = timestamp; -} - -char *Frame::data() -{ - return data_.data(); -} - -const char *Frame::const_data() const -{ - return data_.constData(); -} - -void Frame::allocate() +bool Frame::allocate() { // Assume this frame is intended to be a video frame if (!params_.is_valid()) { qWarning() << "Tried to allocate a frame with invalid parameters"; - return; + return false; } - data_.resize(PixelFormat::GetBufferSize(params_.format(), linesize_, params_.height())); + data_.resize(VideoParams::GetBufferSize(linesize_, height(), params_.format(), params_.channel_count())); + + return true; } -bool Frame::is_allocated() const +FramePtr Frame::convert(VideoParams::Format format) const { - return !data_.isEmpty(); -} + // Create new params with destination format + VideoParams params = params_; + params.set_format(format); -void Frame::destroy() -{ - data_.clear(); -} + // Create new frame + FramePtr converted = Frame::Create(); + converted->set_video_params(params); + converted->set_timestamp(timestamp_); + converted->allocate(); -int Frame::allocated_size() const -{ - return data_.size(); + // Do the conversion through OIIO for convenience + OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(), + channel_count(), + OIIOUtils::GetOIIOBaseTypeFromFormat(this->format()))); + + OIIOUtils::FrameToBuffer(this, &src); + + OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(), + channel_count(), + OIIOUtils::GetOIIOBaseTypeFromFormat(format))); + + if (dst.copy_pixels(src)) { + OIIOUtils::BufferToFrame(&dst, converted.get()); + return converted; + } else { + return nullptr; + } } OLIVE_NAMESPACE_EXIT diff --git a/app/codec/frame.h b/app/codec/frame.h index 7618ad622..29fbdaac1 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -26,7 +26,6 @@ #include "common/rational.h" #include "render/color.h" -#include "render/pixelformat.h" #include "render/videoparams.h" OLIVE_NAMESPACE_ENTER @@ -47,11 +46,37 @@ public: const VideoParams& video_params() const; void set_video_params(const VideoParams& params); - int linesize_pixels() const; - int linesize_bytes() const; - const int& width() const; - const int& height() const; - const PixelFormat::Format& format() const; + static int generate_linesize_bytes(int width, VideoParams::Format format, int channel_count); + + int linesize_pixels() const + { + return linesize_pixels_; + } + + int linesize_bytes() const + { + return linesize_; + } + + int width() const + { + return params_.effective_width(); + } + + int height() const + { + return params_.effective_height(); + } + + VideoParams::Format format() const + { + return params_.format(); + } + + int channel_count() const + { + return params_.channel_count(); + } Color get_pixel(int x, int y) const; bool contains_pixel(int x, int y) const; @@ -62,18 +87,31 @@ public: * * This timestamp is always a rational that will equate to the time in seconds. */ - const rational& timestamp() const; - void set_timestamp(const rational& timestamp); + const rational& timestamp() const + { + return timestamp_; + } + + void set_timestamp(const rational& timestamp) + { + timestamp_ = timestamp; + } /** * @brief Get the data buffer of this frame */ - char* data(); + char* data() + { + return data_.data(); + } /** * @brief Get the const data buffer of this frame */ - const char* const_data() const; + const char* const_data() const + { + return data_.constData(); + } /** * @brief Allocate memory buffer to store data based on parameters @@ -82,24 +120,35 @@ public: * * If a memory buffer has been previously allocated without destroying, this function will destroy it. */ - void allocate(); + bool allocate(); /** * @brief Return whether the frame is allocated or not */ - bool is_allocated() const; + bool is_allocated() const + { + return !data_.isEmpty(); + } /** * @brief Destroy a memory buffer allocated with allocate() */ - void destroy(); + void destroy() + { + data_.clear(); + } /** * @brief Returns the size of the array returned in data() in bytes * * Returns 0 if nothing is allocated. */ - int allocated_size() const; + int allocated_size() const + { + return data_.size(); + } + + FramePtr convert(VideoParams::Format format) const; private: VideoParams params_; @@ -110,6 +159,8 @@ private: int linesize_; + int linesize_pixels_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/codec/oiio/CMakeLists.txt b/app/codec/oiio/CMakeLists.txt index 19103a192..201fd3ee7 100644 --- a/app/codec/oiio/CMakeLists.txt +++ b/app/codec/oiio/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - codec/oiio/oiiodecoder.h codec/oiio/oiiodecoder.cpp + codec/oiio/oiiodecoder.h PARENT_SCOPE ) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 4ddbf0ca5..392951240 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -27,6 +27,7 @@ #include #include "common/define.h" +#include "common/oiioutils.h" #include "config/config.h" #include "core.h" @@ -40,6 +41,11 @@ OIIODecoder::OIIODecoder() : { } +OIIODecoder::~OIIODecoder() +{ + CloseInternal(); +} + QString OIIODecoder::id() { return QStringLiteral("oiio"); @@ -47,6 +53,10 @@ QString OIIODecoder::id() FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const { + Q_UNUSED(cancelled) + + // Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying + // to open a file that it can't if it's given one if (!FileTypeIsSupported(filename)) { return nullptr; } @@ -59,8 +69,9 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell return nullptr; } + // Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle + // it better if (!strcmp(in->format_name(), "FFmpeg movie")) { - // If this is FFmpeg via OIIO, fall-through to our native FFmpeg decoder return nullptr; } @@ -70,8 +81,9 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell image_stream->set_width(in->spec().width); image_stream->set_height(in->spec().height); - image_stream->set_format(GetFormatFromOIIOBasetype(in->spec())); - image_stream->set_pixel_aspect_ratio(GetPixelAspectRatioFromOIIO(in->spec())); + image_stream->set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(in->spec().format.basetype))); + image_stream->set_channel_count(in->spec().nchannels); + image_stream->set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec())); image_stream->set_video_type(VideoStream::kVideoTypeStill); // Images will always have just one stream @@ -95,45 +107,40 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell return footage; } -bool OIIODecoder::Open() +bool OIIODecoder::OpenInternal() { - Q_ASSERT(stream()); + // If we can open the filename provided, assume everything is working (even if this is an image + // sequence with potentially missing frame) + if (OpenImageHandler(stream()->footage()->filename())) { + VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - if (stream()->type() != Stream::kVideo) { - // Guard against non-video types - return false; + if (video_stream->video_type() == VideoStream::kVideoTypeStill) { + last_sequence_index_ = 0; + } else { + last_sequence_index_ = GetImageSequenceIndex(stream()->footage()->filename()); + } + + return true; } - - VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - - if (video_stream->video_type() == VideoStream::kVideoTypeVideo) { - // This decoder only handles kVideoTypeImageSequence and kVideoTypeStill - return false; - } - - if (video_stream->video_type() == VideoStream::kVideoTypeStill - && !OpenImageHandler(stream()->footage()->filename())) { - return false; - } - - open_ = true; - - return true; + return false; } -FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) +FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const int& divider) { - if (!open_) { - qWarning() << "Tried to retrieve video on a decoder that's still closed"; - return nullptr; - } - VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - if (video_stream->video_type() == VideoStream::kVideoTypeImageSequence) { - int64_t ts = video_stream->get_time_in_timebase_units(timecode); + int64_t sequence_index; - if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), ts))) { + if (video_stream->video_type() == VideoStream::kVideoTypeStill) { + sequence_index = 0; + } else { + sequence_index = video_stream->get_time_in_timebase_units(timecode); + } + + if (last_sequence_index_ != sequence_index) { + CloseImageHandle(); + + if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), sequence_index))) { return nullptr; } } @@ -143,14 +150,15 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider frame->set_video_params(VideoParams(buffer_->spec().width, buffer_->spec().height, pix_fmt_, - GetPixelAspectRatioFromOIIO(buffer_->spec()), + channel_count_, + OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()), VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us? divider)); frame->allocate(); if (divider == 1) { - BufferToFrame(buffer_, frame); + OIIOUtils::BufferToFrame(buffer_, frame.get()); } else { @@ -161,106 +169,18 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider qWarning() << "OIIO resize failed"; } - BufferToFrame(&dst, frame); + OIIOUtils::BufferToFrame(&dst, frame.get()); } - if (video_stream->video_type() == VideoStream::kVideoTypeImageSequence) { - CloseImageHandle(); - } - return frame; } -void OIIODecoder::Close() +void OIIODecoder::CloseInternal() { CloseImageHandle(); } -bool OIIODecoder::SupportsVideo() -{ - return true; -} - -void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf) -{ -#if OIIO_VERSION < 20112 - // - // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12 - // - // See more: https://github.com/OpenImageIO/oiio/pull/2487 - // - int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); - - for (int i=0;ispec().height;i++) { - memcpy( -#if OIIO_VERSION < 10903 - reinterpret_cast(buf->localpixels()) + i * width_in_bytes, -#else - reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), -#endif - frame->data() + i * frame->linesize_bytes(), - width_in_bytes); - } -#else - buf->set_pixels(OIIO::ROI(), - buf->spec().format, - frame->data(), - OIIO::AutoStride, - frame->linesize_bytes()); -#endif -} - -void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame) -{ -#if OIIO_VERSION < 20112 - // - // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12 - // - // See more: https://github.com/OpenImageIO/oiio/pull/2487 - // - int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); - - for (int i=0;ispec().height;i++) { - memcpy(frame->data() + i * frame->linesize_bytes(), -#if OIIO_VERSION < 10903 - reinterpret_cast(buf->localpixels()) + i * width_in_bytes, -#else - reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), -#endif - width_in_bytes); - } -#else - buf->get_pixels(OIIO::ROI(), - buf->spec().format, - frame->data(), - OIIO::AutoStride, - frame->linesize_bytes()); -#endif -} - -PixelFormat::Format OIIODecoder::GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec) -{ - bool has_alpha = (spec.nchannels == kRGBAChannels); - - if (spec.format == OIIO::TypeDesc::UINT8) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; - } else if (spec.format == OIIO::TypeDesc::UINT16) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; - } else if (spec.format == OIIO::TypeDesc::HALF) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; - } else if (spec.format == OIIO::TypeDesc::FLOAT) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; - } else { - return PixelFormat::PIX_FMT_INVALID; - } -} - -rational OIIODecoder::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec) -{ - return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1)); -} - bool OIIODecoder::FileTypeIsSupported(const QString& fn) { // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG) @@ -297,17 +217,23 @@ bool OIIODecoder::OpenImageHandler(const QString &fn) // Check if we can work with this pixel format const OIIO::ImageSpec& spec = image_->spec(); - is_rgba_ = (spec.nchannels == kRGBAChannels); + // Store channel count + channel_count_ = spec.nchannels; - pix_fmt_ = GetFormatFromOIIOBasetype(spec); + // We use RGBA frames because that tends to be the native format of GPUs + pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype)); - if (pix_fmt_ == PixelFormat::PIX_FMT_INVALID) { + if (pix_fmt_ == VideoParams::kFormatInvalid) { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; return false; } - // FIXME: Many OIIO pixel formats are not handled here - OIIO::TypeDesc type = PixelFormat::GetOIIOTypeDesc(pix_fmt_); + OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_); + + if (type == OIIO::TypeDesc::UNKNOWN) { + qCritical() << "Failed to determine appropriate OIIO basetype from native format"; + return false; + } #if OIIO_VERSION < 20100 buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type)); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 11e7638ab..f4b7e96d9 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -25,7 +25,6 @@ #include #include "codec/decoder.h" -#include "render/pixelformat.h" OLIVE_NAMESPACE_ENTER @@ -35,23 +34,18 @@ class OIIODecoder : public Decoder public: OIIODecoder(); + virtual ~OIIODecoder() override; + virtual QString id() override; + virtual bool SupportsVideo() override{return true;} + virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override; - virtual bool Open() override; - virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; - virtual void Close() override; - - virtual bool SupportsVideo() override; - - static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf); - - static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); - - static PixelFormat::Format GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec); - - static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec); +protected: + virtual bool OpenInternal() override; + virtual FramePtr RetrieveVideoInternal(const rational &timecode, const int& divider) override; + virtual void CloseInternal() override; private: #if OIIO_VERSION < 10903 @@ -66,9 +60,11 @@ private: void CloseImageHandle(); - PixelFormat::Format pix_fmt_; + int64_t last_sequence_index_; - bool is_rgba_; + VideoParams::Format pix_fmt_; + + int channel_count_; OIIO::ImageBuf* buffer_; diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index fdd25b825..8c8fdc3b9 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -38,6 +38,11 @@ SampleBufferPtr SampleBuffer::Create() return std::make_shared(); } +SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length) +{ + return CreateAllocated(audio_params, audio_params.time_to_samples(length)); +} + SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel) { SampleBufferPtr buffer = Create(); diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h index 891e9156d..eef51ff8a 100644 --- a/app/codec/samplebuffer.h +++ b/app/codec/samplebuffer.h @@ -46,6 +46,7 @@ public: virtual ~SampleBuffer(); static SampleBufferPtr Create(); + static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length); static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel); static SampleBufferPtr CreateFromPackedData(const AudioParams& audio_params, const QByteArray& bytes); diff --git a/app/codec/waveinput.cpp b/app/codec/waveinput.cpp index 275bea1f2..06fd5f2b6 100644 --- a/app/codec/waveinput.cpp +++ b/app/codec/waveinput.cpp @@ -108,27 +108,27 @@ bool WaveInput::open() uint16_t bits_per_sample; data_stream >> bits_per_sample; - SampleFormat::Format format; + AudioParams::Format format; switch (bits_per_sample) { case 8: - format = SampleFormat::SAMPLE_FMT_U8; + format = AudioParams::kFormatUnsigned8; break; case 16: - format = SampleFormat::SAMPLE_FMT_S16; + format = AudioParams::kFormatSigned16; break; case 32: if (data_is_float) { - format = SampleFormat::SAMPLE_FMT_FLT; + format = AudioParams::kFormatFloat32; } else { - format = SampleFormat::SAMPLE_FMT_S32; + format = AudioParams::kFormatSigned32; } break; case 64: if (data_is_float) { - format = SampleFormat::SAMPLE_FMT_DBL; + format = AudioParams::kFormatFloat64; } else { - format = SampleFormat::SAMPLE_FMT_S64; + format = AudioParams::kFormatSigned64; } break; default: diff --git a/app/codec/waveoutput.cpp b/app/codec/waveoutput.cpp index 844b3ad8f..648b8c4a8 100644 --- a/app/codec/waveoutput.cpp +++ b/app/codec/waveoutput.cpp @@ -20,6 +20,8 @@ #include "waveoutput.h" +#include "render/audioparams.h" + OLIVE_NAMESPACE_ENTER const int16_t kWAVIntegerFormat = 1; @@ -60,18 +62,18 @@ bool WaveOutput::open() // Type of format switch (params_.format()) { - case SampleFormat::SAMPLE_FMT_U8: - case SampleFormat::SAMPLE_FMT_S16: - case SampleFormat::SAMPLE_FMT_S32: - case SampleFormat::SAMPLE_FMT_S64: + case AudioParams::kFormatUnsigned8: + case AudioParams::kFormatSigned16: + case AudioParams::kFormatSigned32: + case AudioParams::kFormatSigned64: write_int(&file_, kWAVIntegerFormat); break; - case SampleFormat::SAMPLE_FMT_FLT: - case SampleFormat::SAMPLE_FMT_DBL: + case AudioParams::kFormatFloat32: + case AudioParams::kFormatFloat64: write_int(&file_, kWAVFloatFormat); break; - case SampleFormat::SAMPLE_FMT_INVALID: - case SampleFormat::SAMPLE_FMT_COUNT: + case AudioParams::kFormatInvalid: + case AudioParams::kFormatCount: qWarning() << "Invalid sample format for WAVE audio"; file_.close(); return false; diff --git a/app/codec/waveoutput.h b/app/codec/waveoutput.h index a42f103e0..450fe38bc 100644 --- a/app/codec/waveoutput.h +++ b/app/codec/waveoutput.h @@ -24,7 +24,6 @@ #include #include -#include "audio/sampleformat.h" #include "render/audioparams.h" OLIVE_NAMESPACE_ENTER diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 99521453c..06963187b 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -16,42 +16,49 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - common/bezier.h common/bezier.cpp + common/bezier.h common/cancelableobject.h common/channellayout.h common/clamp.h - common/commandlineparser.h common/commandlineparser.cpp - common/crashpadinterface.h + common/commandlineparser.h common/crashpadinterface.cpp + common/crashpadinterface.h common/crashpadutils.h - common/debug.h common/debug.cpp + common/debug.h common/define.h - common/filefunctions.h + common/ffmpegutils.cpp + common/ffmpegutils.h common/filefunctions.cpp - common/flipmodifiers.h + common/filefunctions.h common/flipmodifiers.cpp + common/flipmodifiers.h common/functiontimer.h common/lerp.h - common/memorypool.h common/memorypool.cpp - common/qtutils.h + common/memorypool.h + common/ocioutils.cpp + common/ocioutils.h + common/oiioutils.cpp + common/oiioutils.h common/qtutils.cpp + common/qtutils.h common/range.h - common/ratiodialog.h common/ratiodialog.cpp + common/ratiodialog.h common/rational.h common/rational.cpp - common/threadedobject.h + common/threadsafemap.h common/threadedobject.cpp - common/timecodefunctions.h + common/threadedobject.h common/timecodefunctions.cpp - common/timerange.h + common/timecodefunctions.h common/timerange.cpp + common/timerange.h common/tohex.h - common/xmlutils.h common/xmlutils.cpp + common/xmlutils.h PARENT_SCOPE ) diff --git a/app/common/cancelableobject.h b/app/common/cancelableobject.h index 19364a521..00d5254c9 100644 --- a/app/common/cancelableobject.h +++ b/app/common/cancelableobject.h @@ -34,14 +34,20 @@ public: { } - void Cancel() { + void Cancel() + { cancelled_ = true; + CancelEvent(); } - const QAtomicInt& IsCancelled() const { + const QAtomicInt& IsCancelled() const + { return cancelled_; } +protected: + virtual void CancelEvent(){} + private: QAtomicInt cancelled_; diff --git a/app/common/define.h b/app/common/define.h index aa968fcf7..0a29394a0 100644 --- a/app/common/define.h +++ b/app/common/define.h @@ -29,10 +29,6 @@ OLIVE_NAMESPACE_ENTER -const int kHSVChannels = 3; -const int kRGBChannels = 3; -const int kRGBAChannels = 4; - /// The minimum size an icon in ProjectExplorer can be const int kProjectIconSizeMinimum = 16; diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp new file mode 100644 index 000000000..9453bd851 --- /dev/null +++ b/app/common/ffmpegutils.cpp @@ -0,0 +1,141 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "common/ffmpegutils.h" + +OLIVE_NAMESPACE_ENTER + +AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) +{ + AVPixelFormat possible_pix_fmts[] = { + AV_PIX_FMT_RGB24, + AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGB48, + AV_PIX_FMT_RGBA64, + AV_PIX_FMT_NONE + }; + + return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, + pix_fmt, + 1, + nullptr); +} + +AudioParams::Format FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) +{ + switch (smp_fmt) { + case AV_SAMPLE_FMT_U8: + return AudioParams::kFormatUnsigned8; + case AV_SAMPLE_FMT_S16: + return AudioParams::kFormatSigned16; + case AV_SAMPLE_FMT_S32: + return AudioParams::kFormatSigned32; + case AV_SAMPLE_FMT_S64: + return AudioParams::kFormatSigned64; + case AV_SAMPLE_FMT_FLT: + return AudioParams::kFormatFloat32; + case AV_SAMPLE_FMT_DBL: + return AudioParams::kFormatFloat64; + case AV_SAMPLE_FMT_U8P : + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_S64P: + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_DBLP: + case AV_SAMPLE_FMT_NONE: + case AV_SAMPLE_FMT_NB: + break; + } + + return AudioParams::kFormatInvalid; +} + +AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt) +{ + switch (smp_fmt) { + case AudioParams::kFormatUnsigned8: + return AV_SAMPLE_FMT_U8; + case AudioParams::kFormatSigned16: + return AV_SAMPLE_FMT_S16; + case AudioParams::kFormatSigned32: + return AV_SAMPLE_FMT_S32; + case AudioParams::kFormatSigned64: + return AV_SAMPLE_FMT_S64; + case AudioParams::kFormatFloat32: + return AV_SAMPLE_FMT_FLT; + case AudioParams::kFormatFloat64: + return AV_SAMPLE_FMT_DBL; + case AudioParams::kFormatInvalid: + case AudioParams::kFormatCount: + break; + } + + return AV_SAMPLE_FMT_NONE; +} + +AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout) +{ + if (channel_layout == VideoParams::kRGBChannelCount) { + switch (pix_fmt) { + case VideoParams::kFormatUnsigned8: + return AV_PIX_FMT_RGB24; + case VideoParams::kFormatUnsigned16: + return AV_PIX_FMT_RGB48; + case VideoParams::kFormatFloat16: + case VideoParams::kFormatFloat32: + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + } else if (channel_layout == VideoParams::kRGBAChannelCount) { + switch (pix_fmt) { + case VideoParams::kFormatUnsigned8: + return AV_PIX_FMT_RGBA; + case VideoParams::kFormatUnsigned16: + return AV_PIX_FMT_RGBA64; + case VideoParams::kFormatFloat16: + case VideoParams::kFormatFloat32: + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + } + + return AV_PIX_FMT_NONE; +} + +VideoParams::Format FFmpegUtils::GetCompatiblePixelFormat(const VideoParams::Format &pix_fmt) +{ + switch (pix_fmt) { + case VideoParams::kFormatUnsigned8: + return VideoParams::kFormatUnsigned8; + case VideoParams::kFormatUnsigned16: + case VideoParams::kFormatFloat16: + case VideoParams::kFormatFloat32: + return VideoParams::kFormatUnsigned16; + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + + return VideoParams::kFormatInvalid; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegcommon.h b/app/common/ffmpegutils.h similarity index 77% rename from app/codec/ffmpeg/ffmpegcommon.h rename to app/common/ffmpegutils.h index 106452fad..6e9fb002a 100644 --- a/app/codec/ffmpeg/ffmpegcommon.h +++ b/app/common/ffmpegutils.h @@ -25,12 +25,12 @@ extern "C" { #include } -#include "audio/sampleformat.h" -#include "render/pixelformat.h" +#include "render/audioparams.h" +#include "render/videoparams.h" OLIVE_NAMESPACE_ENTER -class FFmpegCommon { +class FFmpegUtils { public: /** * @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss @@ -40,22 +40,22 @@ public: /** * @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss */ - static PixelFormat::Format GetCompatiblePixelFormat(const PixelFormat::Format& pix_fmt); + static VideoParams::Format GetCompatiblePixelFormat(const VideoParams::Format& pix_fmt); /** * @brief Returns an FFmpeg pixel format for a given native pixel format */ - static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat::Format& pix_fmt); + static AVPixelFormat GetFFmpegPixelFormat(const VideoParams::Format& pix_fmt, int channel_layout); /** * @brief Returns a native sample format type for a given AVSampleFormat */ - static SampleFormat::Format GetNativeSampleFormat(const AVSampleFormat& smp_fmt); + static AudioParams::Format GetNativeSampleFormat(const AVSampleFormat& smp_fmt); /** * @brief Returns an FFmpeg sample format type for a given native type */ - static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat::Format &smp_fmt); + static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt); }; OLIVE_NAMESPACE_EXIT diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index 23f3abf99..513d781ea 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -75,7 +75,7 @@ QString FileFunctions::GetTempFilePath() { QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)) .filePath(QCoreApplication::organizationName())) - .filePath(QCoreApplication::applicationName()); + .filePath(QCoreApplication::applicationName()); // Ensure it exists QDir(temp_path).mkpath("."); @@ -187,4 +187,57 @@ QString FileFunctions::EnsureFilenameExtension(QString fn, const QString &extens return fn; } +QString FileFunctions::ReadFileAsString(const QString &filename) +{ + QFile f(filename); + QString file_data; + if (f.open(QFile::ReadOnly | QFile::Text)) { + QTextStream text_stream(&f); + file_data = text_stream.readAll(); + f.close(); + } + return file_data; +} + +QString FileFunctions::GetSafeTemporaryFilename(const QString &original) +{ + int counter = 0; + + QFileInfo original_info(original); + QString basename = original_info.baseName(); + QString complete_suffix = original_info.completeSuffix(); + + // If we have a complete suffix, make sure there's a period in it + if (!complete_suffix.isEmpty()) { + complete_suffix.prepend('.'); + } + + QString temp_abs_path; + do { + temp_abs_path = original_info.dir().filePath( + QStringLiteral("%1.tmp%2%3").arg(basename, + QString::number(counter), + complete_suffix)); + counter++; + } while (QFileInfo::exists(temp_abs_path)); + + return temp_abs_path; +} + +bool FileFunctions::RenameFileAllowOverwrite(const QString &from, const QString &to) +{ + if (QFileInfo::exists(to) && !QFile::remove(to)) { + qCritical() << "Couldn't remove existing file" << to << "for overwrite"; + return false; + } + + // By this point, we can assume `to` either never existed or has now been deleted + if (!QFile::rename(from, to)) { + qCritical() << "Failed to rename file" << from << "to" << to; + return false; + } + + return true; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/common/filefunctions.h b/app/common/filefunctions.h index 87bcc6375..f19aac9b9 100644 --- a/app/common/filefunctions.h +++ b/app/common/filefunctions.h @@ -65,6 +65,25 @@ public: */ static QString EnsureFilenameExtension(QString fn, const QString& extension); + static QString ReadFileAsString(const QString& filename); + + /** + * @brief Returns a temporary filename that can be used while writing rather than the original + * + * If overwriting a file, it's safest to write to a new file first and then only replace it at + * the end so that if the program crashes or the user cancels the save half way through, the + * original file is still intact. + * + * This function returns a slight variant of the filename provided that's guaranteed to not exist + * and therefore won't overwrite anything important. + */ + static QString GetSafeTemporaryFilename(const QString& original); + + /** + * @brief Renames a file from `from` to `to`, deleting `to` if such a file already exists first + */ + static bool RenameFileAllowOverwrite(const QString& from, const QString& to); + }; diff --git a/app/audio/sampleformat.h b/app/common/ocioutils.cpp similarity index 57% rename from app/audio/sampleformat.h rename to app/common/ocioutils.cpp index 8b4dd5a9f..c5f182450 100644 --- a/app/audio/sampleformat.h +++ b/app/common/ocioutils.cpp @@ -18,41 +18,30 @@ ***/ -#ifndef SAMPLEFORMAT_H -#define SAMPLEFORMAT_H - -#include - -#include "common/define.h" -#include "render/rendermodes.h" +#include "ocioutils.h" OLIVE_NAMESPACE_ENTER -class SampleFormat : public QObject +OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(VideoParams::Format format) { - Q_OBJECT -public: - SampleFormat() = default; + switch (format) { + case VideoParams::kFormatUnsigned8: + return OCIO::BIT_DEPTH_UINT8; + case VideoParams::kFormatUnsigned16: + return OCIO::BIT_DEPTH_UINT16; + break; + case VideoParams::kFormatFloat16: + return OCIO::BIT_DEPTH_F16; + break; + case VideoParams::kFormatFloat32: + return OCIO::BIT_DEPTH_F32; + break; + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } - enum Format { - SAMPLE_FMT_INVALID = -1, - - SAMPLE_FMT_U8, - SAMPLE_FMT_S16, - SAMPLE_FMT_S32, - SAMPLE_FMT_S64, - SAMPLE_FMT_FLT, - SAMPLE_FMT_DBL, - - SAMPLE_FMT_COUNT - }; - - static const Format kInternalFormat; - - static QString GetSampleFormatName(const Format& f); - -}; + return OCIO::BIT_DEPTH_UNKNOWN; +} OLIVE_NAMESPACE_EXIT - -#endif // SAMPLEFORMAT_H diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/common/ocioutils.h similarity index 73% rename from app/render/backend/opengl/openglbackend.cpp rename to app/common/ocioutils.h index 60e81f914..edbaa9fef 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/common/ocioutils.h @@ -18,26 +18,22 @@ ***/ -#include "openglbackend.h" +#ifndef OCIOUTILS_H +#define OCIOUTILS_H -#include "openglworker.h" +#include +namespace OCIO = OpenColorIO_v2_0dev; + +#include "render/videoparams.h" OLIVE_NAMESPACE_ENTER -OpenGLBackend::OpenGLBackend(QObject* parent) : - RenderBackend(parent) +class OCIOUtils { - -} - -OpenGLBackend::~OpenGLBackend() -{ - Close(); -} - -RenderWorker *OpenGLBackend::CreateNewWorker() -{ - return new OpenGLWorker(this); -} +public: + static OCIO::BitDepth GetOCIOBitDepthFromPixelFormat(VideoParams::Format format); +}; OLIVE_NAMESPACE_EXIT + +#endif // OCIOUTILS_H diff --git a/app/common/oiioutils.cpp b/app/common/oiioutils.cpp new file mode 100644 index 000000000..8381ba904 --- /dev/null +++ b/app/common/oiioutils.cpp @@ -0,0 +1,120 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "oiioutils.h" + +OLIVE_NAMESPACE_ENTER + +void OIIOUtils::FrameToBuffer(const Frame* frame, OIIO::ImageBuf *buf) +{ +#if OIIO_VERSION < 20112 + // + // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12 + // + // See more: https://github.com/OpenImageIO/oiio/pull/2487 + // + int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); + + for (int i=0;ispec().height;i++) { + memcpy( +#if OIIO_VERSION < 10903 + reinterpret_cast(buf->localpixels()) + i * width_in_bytes, +#else + reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), +#endif + frame->data() + i * frame->linesize_bytes(), + width_in_bytes); + } +#else + buf->set_pixels(OIIO::ROI(), + buf->spec().format, + frame->const_data(), + OIIO::AutoStride, + frame->linesize_bytes()); +#endif +} + +void OIIOUtils::BufferToFrame(OIIO::ImageBuf *buf, Frame* frame) +{ +#if OIIO_VERSION < 20112 + // + // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12 + // + // See more: https://github.com/OpenImageIO/oiio/pull/2487 + // + int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); + + for (int i=0;ispec().height;i++) { + memcpy(frame->data() + i * frame->linesize_bytes(), +#if OIIO_VERSION < 10903 + reinterpret_cast(buf->localpixels()) + i * width_in_bytes, +#else + reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), +#endif + width_in_bytes); + } +#else + buf->get_pixels(OIIO::ROI(), + buf->spec().format, + frame->data(), + OIIO::AutoStride, + frame->linesize_bytes()); +#endif +} + +rational OIIOUtils::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec) +{ + return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1)); +} + +VideoParams::Format OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type) +{ + switch (type) { + case OIIO::TypeDesc::UNKNOWN: + case OIIO::TypeDesc::NONE: + break; + + case OIIO::TypeDesc::INT8: + case OIIO::TypeDesc::INT16: + case OIIO::TypeDesc::INT32: + case OIIO::TypeDesc::UINT32: + case OIIO::TypeDesc::INT64: + case OIIO::TypeDesc::UINT64: + case OIIO::TypeDesc::STRING: + case OIIO::TypeDesc::PTR: + case OIIO::TypeDesc::LASTBASE: + case OIIO::TypeDesc::DOUBLE: + qDebug() << "Tried to use unknown OIIO base type"; + break; + + case OIIO::TypeDesc::UINT8: + return VideoParams::kFormatUnsigned8; + case OIIO::TypeDesc::UINT16: + return VideoParams::kFormatUnsigned16; + case OIIO::TypeDesc::HALF: + return VideoParams::kFormatFloat16; + case OIIO::TypeDesc::FLOAT: + return VideoParams::kFormatFloat32; + } + + return VideoParams::kFormatInvalid; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/common/oiioutils.h b/app/common/oiioutils.h new file mode 100644 index 000000000..a9dca5baa --- /dev/null +++ b/app/common/oiioutils.h @@ -0,0 +1,65 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef OIIOUTILS_H +#define OIIOUTILS_H + +#include +#include + +#include "codec/frame.h" +#include "render/videoparams.h" + +OLIVE_NAMESPACE_ENTER + +class OIIOUtils { +public: + static OIIO::TypeDesc::BASETYPE GetOIIOBaseTypeFromFormat(VideoParams::Format format) + { + switch (format) { + case VideoParams::kFormatUnsigned8: + return OIIO::TypeDesc::UINT8; + case VideoParams::kFormatUnsigned16: + return OIIO::TypeDesc::UINT16; + case VideoParams::kFormatFloat16: + return OIIO::TypeDesc::HALF; + case VideoParams::kFormatFloat32: + return OIIO::TypeDesc::FLOAT; + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + + return OIIO::TypeDesc::UNKNOWN; + } + + static void FrameToBuffer(const Frame *frame, OIIO::ImageBuf* buf); + + static void BufferToFrame(OIIO::ImageBuf* buf, Frame* frame); + + static VideoParams::Format GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type); + + static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // OIIOUTILS_H diff --git a/app/common/threadsafemap.h b/app/common/threadsafemap.h new file mode 100644 index 000000000..0d4a6bc7e --- /dev/null +++ b/app/common/threadsafemap.h @@ -0,0 +1,27 @@ +#ifndef THREADSAFEMAP_H +#define THREADSAFEMAP_H + +#include +#include + +template +class ThreadSafeMap +{ +public: + ThreadSafeMap() = default; + + void insert(K key, V value) + { + mutex_.lock(); + map_.insert(key, value); + mutex_.unlock(); + } + +private: + QMutex mutex_; + + QMap map_; + +}; + +#endif // THREADSAFEMAP_H diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index d8b2a5b6b..639b5c030 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -20,6 +20,7 @@ #include "timerange.h" +#include #include OLIVE_NAMESPACE_ENTER @@ -77,11 +78,11 @@ bool TimeRange::operator!=(const TimeRange &r) const bool TimeRange::OverlapsWith(const TimeRange &a, bool in_inclusive, bool out_inclusive) const { - bool overlaps_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in()); + bool doesnt_overlap_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in()); - bool overlaps_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out()); + bool doesnt_overlap_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out()); - return !(overlaps_in || overlaps_out); + return !doesnt_overlap_in && !doesnt_overlap_out; } TimeRange TimeRange::Combined(const TimeRange &a) const @@ -148,6 +149,21 @@ const TimeRange &TimeRange::operator-=(const rational &rhs) return *this; } +std::list TimeRange::Split(const int &chunk_size) const +{ + std::list split_ranges; + + int start_time = qFloor(this->in().toDouble() / static_cast(chunk_size)) * chunk_size; + int end_time = qCeil(this->out().toDouble() / static_cast(chunk_size)) * chunk_size; + + for (int i=start_time; iin(), rational(i)), + qMin(this->out(), rational(i + chunk_size)))); + } + + return split_ranges; +} + void TimeRange::normalize() { // If `out` is earlier than `in`, swap them @@ -160,43 +176,45 @@ void TimeRange::normalize() length_ = out_ - in_; } -void TimeRangeList::InsertTimeRange(TimeRange range_to_add) +void TimeRangeList::insert(TimeRange range_to_add) { // See if list contains this range - if (ContainsTimeRange(range_to_add)) { + if (contains(range_to_add)) { return; } // Does not contain range, so we'll almost certainly be adding it in some way for (int i=0;isize(); for (int i=0;iremoveAt(i); + array_.removeAt(i); i--; sz--; } else if (compare.Contains(remove, false, false)) { // The remove range is within this element, only choice is to split the element into two - this->append(TimeRange(remove.out(), compare.out())); + TimeRange new_range(remove.out(), compare.out()); compare.set_out(remove.in()); + insert(new_range); + break; } else if (compare.in() < remove.in() && compare.out() > remove.in()) { // This element's out point overlaps the range's in, we'll trim it compare.set_out(remove.in()); @@ -207,10 +225,10 @@ void TimeRangeList::RemoveTimeRange(const TimeRange &remove) } } -bool TimeRangeList::ContainsTimeRange(const TimeRange &range, bool in_inclusive, bool out_inclusive) const +bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const { for (int i=0;i= range.out()) { // No intersect @@ -233,22 +284,13 @@ TimeRangeList TimeRangeList::Intersects(const TimeRange &range) const TimeRange cropped(qMax(range.in(), compare.in()), qMin(range.out(), compare.out())); - intersect_list.append(cropped); + intersect_list.insert(cropped); } } return intersect_list; } -void TimeRangeList::PrintTimeList() -{ - qDebug() << "TimeRangeList now contains:"; - - for (int i=0;i Split(const int &chunk_size) const; + private: void normalize(); @@ -65,25 +67,73 @@ private: }; -class TimeRangeList : public QList { +class TimeRangeList { public: TimeRangeList() = default; TimeRangeList(std::initializer_list r) : - QList(r) + array_(r) { } - void InsertTimeRange(TimeRange range_to_add); + void insert(TimeRange range_to_add); - void RemoveTimeRange(const TimeRange& remove); + void remove(const TimeRange& remove); - bool ContainsTimeRange(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const; + bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const; + + bool isEmpty() const + { + return array_.isEmpty(); + } + + void clear() + { + array_.clear(); + } + + int size() const + { + return array_.size(); + } + + void shift(const rational& diff); + + void trim_in(const rational& diff); + + void trim_out(const rational& diff); TimeRangeList Intersects(const TimeRange& range) const; + using const_iterator = QVector::const_iterator; + + const_iterator begin() const + { + return array_.constBegin(); + } + + const_iterator end() const + { + return array_.constEnd(); + } + + const TimeRange& first() const + { + return array_.first(); + } + + const TimeRange& last() const + { + return array_.last(); + } + + const QVector& internal_array() const + { + return array_; + } + private: - void PrintTimeList(); + QVector array_; }; @@ -92,6 +142,7 @@ uint qHash(const TimeRange& r, uint seed); OLIVE_NAMESPACE_EXIT QDebug operator<<(QDebug debug, const OLIVE_NAMESPACE::TimeRange& r); +QDebug operator<<(QDebug debug, const OLIVE_NAMESPACE::TimeRangeList& r); Q_DECLARE_METATYPE(OLIVE_NAMESPACE::TimeRange) diff --git a/app/config/config.cpp b/app/config/config.cpp index f2db463d7..2b317a51c 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -89,6 +89,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeParam::kBoolean, false); SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, ImportTool::kDWSAsk); SetEntryInternal(QStringLiteral("Loop"), NodeParam::kBoolean, false); + SetEntryInternal(QStringLiteral("SplitClipsCopyNodes"), NodeParam::kBoolean, true); SetEntryInternal(QStringLiteral("AutoCacheInterval"), NodeParam::kInt, 250); @@ -116,13 +117,10 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeParam::kInt, VideoParams::kInterlaceNone); SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeParam::kInt, 48000); SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeParam::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); - SetEntryInternal(QStringLiteral("DefaultSequencePreviewFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F); // Online/offline settings - SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA32F); - SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F); - SetEntryInternal(QStringLiteral("OnlineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOAccurate); - SetEntryInternal(QStringLiteral("OfflineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOFast); + SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeParam::kInt, VideoParams::kFormatFloat32); + SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeParam::kInt, VideoParams::kFormatFloat16); } void Config::Load() @@ -206,7 +204,10 @@ void Config::Load() void Config::Save() { - QFile config_file(GetConfigFilePath()); + QString real_filename = GetConfigFilePath(); + QString temp_filename = FileFunctions::GetSafeTemporaryFilename(real_filename); + + QFile config_file(temp_filename); if (!config_file.open(QFile::WriteOnly)) { QMessageBox::critical(Core::instance()->main_window(), @@ -233,10 +234,10 @@ void Config::Save() QString value = NodeInput::ValueToString(iterator.value().type, iterator.value().data, false); - writer.writeTextElement(iterator.key(), value); - if (iterator.value().type == NodeParam::kNone) { - qWarning() << "Config key" << iterator.key() << "had null type"; + qWarning() << "Config key" << iterator.key() << "had null type and was discarded"; + } else { + writer.writeTextElement(iterator.key(), value); } } @@ -245,6 +246,11 @@ void Config::Save() writer.writeEndDocument(); config_file.close(); + + if (!FileFunctions::RenameFileAllowOverwrite(temp_filename, real_filename)) { + qWarning() << QStringLiteral("Failed to overwrite \"%1\". Config has been saved as \"%2\" instead.") + .arg(real_filename, temp_filename); + } } QVariant Config::operator[](const QString &key) const diff --git a/app/core.cpp b/app/core.cpp index c070bac7e..60be123ed 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -49,11 +49,9 @@ #include "panel/panelmanager.h" #include "panel/project/project.h" #include "panel/viewer/viewer.h" -#include "render/backend/opengl/opengltexturecache.h" #include "render/colormanager.h" #include "render/diskmanager.h" -#include "render/pixelformat.h" -#include "render/shaderinfo.h" +#include "render/rendermanager.h" #ifdef USE_OTIO #include "task/project/loadotio/loadotio.h" #include "task/project/saveotio/saveotio.h" @@ -95,8 +93,6 @@ Core *Core::instance() void Core::DeclareTypesForQt() { qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -135,8 +131,8 @@ void Core::Start() // Initialize task manager TaskManager::CreateInstance(); - // Initialize OpenGL service - OpenGLProxy::CreateInstance(); + // Initialize RenderManager + RenderManager::CreateInstance(); // // Start application @@ -172,15 +168,13 @@ void Core::Stop() if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) { QTextStream ts(&recent_projects_file); - foreach (const QString& s, recent_projects_) { - ts << s << "\n"; - } + ts << recent_projects_.join('\n'); recent_projects_file.close(); } } - OpenGLProxy::DestroyInstance(); + RenderManager::DestroyInstance(); MenuShared::DestroyInstance(); @@ -192,11 +186,10 @@ void Core::Stop() DiskManager::DestroyInstance(); - PixelFormat::DestroyInstance(); - NodeFactory::Destroy(); delete main_window_; + main_window_ = nullptr; } MainWindow *Core::main_window() @@ -259,6 +252,7 @@ void Core::SetSelectedTransitionObject(const QString &obj) void Core::ClearOpenRecentList() { recent_projects_.clear(); + emit OpenRecentListChanged(); } void Core::CreateNewProject() @@ -648,9 +642,6 @@ void Core::StartGUI(bool full_screen) // Initialize disk service DiskManager::CreateInstance(); - // Initialize pixel service - PixelFormat::CreateInstance(); - // Connect the PanelFocusManager to the application's focus change signal connect(qApp, &QApplication::focusChanged, @@ -686,12 +677,15 @@ void Core::StartGUI(bool full_screen) if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) { QTextStream ts(&recent_projects_file); - while (!ts.atEnd()) { - recent_projects_.append(ts.readLine()); + QString s; + while (!(s = ts.readLine()).isEmpty()) { + recent_projects_.append(s); } recent_projects_file.close(); } + + emit OpenRecentListChanged(); } } @@ -961,6 +955,8 @@ void Core::PushRecentlyOpenedProject(const QString& s) } else { recent_projects_.prepend(s); } + + emit OpenRecentListChanged(); } void Core::OpenProjectInternal(const QString &filename) @@ -1038,7 +1034,7 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value; } -void Core::LabelNodes(const QList &nodes) const +void Core::LabelNodes(const QVector &nodes) const { if (nodes.isEmpty()) { return; @@ -1097,6 +1093,8 @@ void Core::OpenProjectFromRecentList(int index) tr("The project \"%1\" doesn't exist. Would you like to remove this file from the recent list?").arg(open_fn), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { recent_projects_.removeAt(index); + + emit OpenRecentListChanged(); } } diff --git a/app/core.h b/app/core.h index 8e3b45db1..62257a36a 100644 --- a/app/core.h +++ b/app/core.h @@ -233,7 +233,7 @@ public: /** * @brief Show a dialog to the user to rename a set of nodes */ - void LabelNodes(const QList& nodes) const; + void LabelNodes(const QVector &nodes) const; /** * @brief Create a new sequence named appropriately for the active project @@ -415,6 +415,11 @@ signals: */ void TimecodeDisplayChanged(Timecode::Display d); + /** + * @brief Signal emitted when a change is made to the open recent list + */ + void OpenRecentListChanged(); + private: /** * @brief Get the file filter than can be used with QFileDialog to open and save compatible projects diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 2d3250be9..ccc3df207 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -34,7 +34,6 @@ #include "dialog/task/task.h" #include "project/item/sequence/sequence.h" #include "project/project.h" -#include "render/pixelformat.h" #include "ui/icons/icons.h" OLIVE_NAMESPACE_ENTER @@ -179,6 +178,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height()); video_tab_->frame_rate_combobox()->SetFrameRate(viewer_node_->video_params().time_base().flipped()); video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(viewer_node_->video_params().pixel_aspect_ratio()); + video_tab_->pixel_format_field()->SetPixelFormat(static_cast(Config::Current()["OnlinePixelFormat"].toInt())); video_tab_->interlaced_combobox()->SetInterlaceMode(viewer_node_->video_params().interlacing()); audio_tab_->sample_rate_combobox()->SetSampleRate(viewer_node_->audio_params().sample_rate()); audio_tab_->channel_layout_combobox()->SetChannelLayout(viewer_node_->audio_params().channel_layout()); @@ -285,25 +285,36 @@ void ExportDialog::StartExport() } // Validate video resolution - if (video_enabled_->isChecked()) { - if (video_tab_->width_slider()->GetValue() % 2 != 0 - || video_tab_->height_slider()->GetValue() % 2 != 0) { - QMessageBox b(this); - b.setIcon(QMessageBox::Critical); - b.setWindowModality(Qt::WindowModal); - b.setWindowTitle(tr("Invalid parameters")); - b.setText(tr("Width and height must be multiples of 2.")); - b.exec(); - return; - } + if (video_enabled_->isChecked() + && video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264 + && (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) { + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Invalid Parameters")); + b.setText(tr("Width and height must be multiples of 2.")); + b.exec(); + return; } ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams()); TaskDialog* td = new TaskDialog(task, tr("Export"), this); - connect(td, &TaskDialog::TaskSucceeded, this, &QDialog::accept); + connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished); td->open(); } +void ExportDialog::ExportFinished() +{ + TaskDialog* td = static_cast(sender()); + + if (td->GetTask()->IsCancelled()) { + // If this task was cancelled, we stay open so the user can potentially queue another export + } else { + // Accept this dialog and close + this->accept(); + } +} + void ExportDialog::closeEvent(QCloseEvent *e) { preview_viewer_->ConnectViewerNode(nullptr); @@ -373,7 +384,7 @@ void ExportDialog::ResolutionChanged() new_width *= video_aspect_ratio_; // Align to even number and set - video_tab_->width_slider()->SetValue(AlignEvenNumber(new_width)); + video_tab_->width_slider()->SetValue(new_width); } else { @@ -384,7 +395,7 @@ void ExportDialog::ResolutionChanged() new_height /= video_aspect_ratio_; // Align to even number and set - video_tab_->height_slider()->SetValue(AlignEvenNumber(new_height)); + video_tab_->height_slider()->SetValue(new_height); } } @@ -414,24 +425,20 @@ void ExportDialog::SetDefaultFilename() filename_edit_->setText(file_location); } -int ExportDialog::AlignEvenNumber(double d) -{ - return qCeil(d * 0.5) * 2; -} - ExportParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue()), video_tab_->frame_rate_combobox()->GetFrameRate().flipped(), - PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline), + video_tab_->pixel_format_field()->GetPixelFormat(), + VideoParams::kInternalChannelCount, video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(), video_tab_->interlaced_combobox()->GetInterlaceMode(), 1); AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(), audio_tab_->channel_layout_combobox()->GetChannelLayout(), - SampleFormat::kInternalFormat); + AudioParams::kInternalFormat); ExportParams params; params.SetFilename(filename_edit_->text()); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 8313e59cf..d92eaba3e 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -49,8 +49,6 @@ private: void LoadPresets(); void SetDefaultFilename(); - static int AlignEvenNumber(double d); - ExportParams GenerateParams() const; ViewerOutput* viewer_node_; @@ -85,6 +83,8 @@ private slots: void StartExport(); + void ExportFinished(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 9d62c360f..617f67269 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -115,6 +115,13 @@ QWidget* ExportVideoTab::SetupResolutionSection() interlaced_combobox_ = new InterlacedComboBox(); layout->addWidget(interlaced_combobox_, row, 1); + row++; + + layout->addWidget(new QLabel(tr("Quality:")), row, 0); + + pixel_format_field_ = new PixelFormatComboBox(true); + layout->addWidget(pixel_format_field_, row, 1); + return resolution_group; } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index fe35d884b..ffbac3e19 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -111,6 +111,11 @@ public: return pixel_aspect_combobox_; } + PixelFormatComboBox* pixel_format_field() const + { + return pixel_format_field_; + } + const int& threads() const { return threads_; @@ -149,6 +154,7 @@ private: InterlacedComboBox* interlaced_combobox_; PixelAspectRatioComboBox* pixel_aspect_combobox_; + PixelFormatComboBox* pixel_format_field_; int threads_; diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp index 0aaca3a43..f6ebfcfcc 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -25,9 +25,8 @@ #include #include #include -#include -namespace OCIO = OCIO_NAMESPACE::v1; +#include "common/ocioutils.h" #include "core.h" #include "project/item/footage/footage.h" #include "project/project.h" @@ -78,11 +77,13 @@ VideoStreamProperties::VideoStreamProperties(VideoStreamPtr stream) : video_layout->addWidget(video_color_space_, row, 1); - row++; + if (stream->channel_count() == VideoParams::kRGBAChannelCount) { + row++; - video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); - video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha()); - video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); + video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); + video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha()); + video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); + } row++; diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 6713fe81b..3feb53035 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -99,6 +99,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() AddItem(tr("Auto-Scale By Default"), QStringLiteral("AutoscaleByDefault"), node_group); + AddItem(tr("Splitting Clips Copies Dependencies"), + QStringLiteral("SplitClipsCopyNodes"), + tr("Multiple clips can share the same nodes. Disable this to automatically share node " + "dependencies among clips when copying or splitting them."), + node_group); } void PreferencesBehaviorTab::Accept() diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 5a8713f5c..509a47e61 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -27,10 +27,9 @@ #include #include #include -#include -namespace OCIO = OCIO_NAMESPACE::v1; #include "common/filefunctions.h" +#include "common/ocioutils.h" #include "config/config.h" #include "core.h" #include "render/colormanager.h" diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 3006c2b73..0df85621e 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -107,13 +107,14 @@ void SequenceDialog::accept() parameter_tab_->GetSelectedVideoHeight(), parameter_tab_->GetSelectedVideoFrameRate().flipped(), parameter_tab_->GetSelectedPreviewFormat(), + VideoParams::kInternalChannelCount, parameter_tab_->GetSelectedVideoPixelAspect(), parameter_tab_->GetSelectedVideoInterlacingMode(), parameter_tab_->GetSelectedPreviewResolution()); AudioParams audio_params = AudioParams(parameter_tab_->GetSelectedAudioSampleRate(), parameter_tab_->GetSelectedAudioChannelLayout(), - SampleFormat::kInternalFormat); + AudioParams::kInternalFormat); if (make_undoable_) { diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 98689b16c..326df8b11 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -74,8 +74,8 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg preview_resolution_label_ = new QLabel(); preview_layout->addWidget(preview_resolution_label_, row, 2); row++; - preview_layout->addWidget(new QLabel(tr("Format:")), row, 0); - preview_format_field_ = new PixelFormatComboBox(true, true); + preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0); + preview_format_field_ = new PixelFormatComboBox(true); preview_layout->addWidget(preview_format_field_, row, 1, 1, 2); layout->addWidget(preview_group); @@ -133,7 +133,8 @@ void SequenceDialogParameterTab::UpdatePreviewResolutionLabel() { VideoParams test_param(video_width_field_->GetValue(), video_height_field_->GetValue(), - PixelFormat::PIX_FMT_INVALID, + VideoParams::kFormatInvalid, + VideoParams::kInternalChannelCount, rational(1), VideoParams::kInterlaceNone, preview_resolution_field_->currentData().toInt()); diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index 794abe505..d2dabdea1 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -58,7 +58,7 @@ public: return preview_resolution_field_->GetDivider(); } - PixelFormat::Format GetSelectedPreviewFormat() const + VideoParams::Format GetSelectedPreviewFormat() const { return preview_format_field_->GetPixelFormat(); } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index 18c9419db..1f25a29fb 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -30,6 +30,7 @@ #include #include "common/filefunctions.h" +#include "config/config.h" #include "node/input.h" #include "render/videoparams.h" #include "ui/icons/icons.h" @@ -41,8 +42,6 @@ const int kDataIsPreset = Qt::UserRole; const int kDataPresetIsCustomRole = Qt::UserRole + 1; const int kDataPresetDataRole = Qt::UserRole + 2; -const PixelFormat::Format kDefaultPreviewFormat = PixelFormat::PIX_FMT_RGBA16F; - SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) : QWidget(parent), PresetManager(this, QStringLiteral("sequencepresets")) @@ -100,6 +99,7 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name) QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider) { + VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt()); QTreeWidgetItem* parent = CreateFolder(name); AddStandardItem(parent, SequencePreset::Create(tr("%1 23.976 FPS").arg(name), width, @@ -110,7 +110,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); AddStandardItem(parent, SequencePreset::Create(tr("%1 25 FPS").arg(name), width, height, @@ -120,7 +120,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); AddStandardItem(parent, SequencePreset::Create(tr("%1 29.97 FPS").arg(name), width, height, @@ -130,7 +130,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); AddStandardItem(parent, SequencePreset::Create(tr("%1 50 FPS").arg(name), width, height, @@ -140,7 +140,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); AddStandardItem(parent, SequencePreset::Create(tr("%1 59.94 FPS").arg(name), width, height, @@ -150,12 +150,13 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); return parent; } QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider) { + VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt()); QTreeWidgetItem* parent = CreateFolder(name); preset_tree_->addTopLevelItem(parent); AddStandardItem(parent, SequencePreset::Create(tr("%1 Standard").arg(name), @@ -167,7 +168,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); AddStandardItem(parent, SequencePreset::Create(tr("%1 Widescreen").arg(name), width, height, @@ -177,7 +178,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na 48000, AV_CH_LAYOUT_STEREO, divider, - kDefaultPreviewFormat)); + default_format)); return parent; } diff --git a/app/dialog/sequence/sequencepreset.h b/app/dialog/sequence/sequencepreset.h index 6b134b8f1..59db546fd 100644 --- a/app/dialog/sequence/sequencepreset.h +++ b/app/dialog/sequence/sequencepreset.h @@ -26,7 +26,6 @@ #include "common/rational.h" #include "common/xmlutils.h" #include "dialog/sequence/presetmanager.h" -#include "render/pixelformat.h" #include "render/videoparams.h" OLIVE_NAMESPACE_ENTER @@ -44,7 +43,7 @@ public: int sample_rate, uint64_t channel_layout, int preview_divider, - PixelFormat::Format preview_format) : + VideoParams::Format preview_format) : width_(width), height_(height), frame_rate_(frame_rate), @@ -67,7 +66,7 @@ public: int sample_rate, uint64_t channel_layout, int preview_divider, - PixelFormat::Format preview_format) + VideoParams::Format preview_format) { return std::make_shared(name, width, height, frame_rate, pixel_aspect, interlacing, sample_rate, channel_layout, @@ -96,7 +95,7 @@ public: } else if (reader->name() == QStringLiteral("divider")) { preview_divider_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("format")) { - preview_format_ = static_cast(reader->readElementText().toInt()); + preview_format_ = static_cast(reader->readElementText().toInt()); } else { reader->skipCurrentElement(); } @@ -157,7 +156,7 @@ public: return preview_divider_; } - PixelFormat::Format preview_format() const + VideoParams::Format preview_format() const { return preview_format_; } @@ -171,7 +170,7 @@ private: int sample_rate_; uint64_t channel_layout_; int preview_divider_; - PixelFormat::Format preview_format_; + VideoParams::Format preview_format_; }; diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index dad795d02..7fbfd99ca 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -49,7 +49,7 @@ QString PanNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.pan"); } -QList PanNode::Category() const +QVector PanNode::Category() const { return {kCategoryChannels}; } @@ -69,7 +69,7 @@ NodeValueTable PanNode::Value(NodeValueDatabase &value) const NodeValueTable table = value.Merge(); if (job.HasSamples()) { - float pan_volume = job.GetValue(panning_input_).data().toFloat(); + float pan_volume = job.GetValue(panning_input_).data.toFloat(); if (panning_input_->is_static()) { if (!qIsNull(pan_volume) && job.samples()->audio_params().channel_count() == 2) { if (pan_volume > 0) { diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h index df23f9d0f..9ec58b110 100644 --- a/app/node/audio/pan/pan.h +++ b/app/node/audio/pan/pan.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual NodeValueTable Value(NodeValueDatabase &value) const override; diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 4b403118b..b6d48e764 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -49,7 +49,7 @@ QString VolumeNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.volume"); } -QList VolumeNode::Category() const +QVector VolumeNode::Category() const { return {kCategoryFilter}; } diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h index e408e3be7..c17656241 100644 --- a/app/node/audio/volume/volume.h +++ b/app/node/audio/volume/volume.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual NodeValueTable Value(NodeValueDatabase &value) const override; diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 68b9779bd..0289e11b2 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -58,7 +58,7 @@ Block::Block() : set_length_and_media_out(1); } -QList Block::Category() const +QVector Block::Category() const { return {kCategoryTimeline}; } @@ -241,9 +241,9 @@ void Block::SaveInternal(QXmlStreamWriter *writer) const } } -QList Block::GetInputsToHash() const +QVector Block::GetInputsToHash() const { - QList inputs = Node::GetInputsToHash(); + QVector inputs = Node::GetInputsToHash(); // Ignore these inputs inputs.removeOne(media_in_input_); diff --git a/app/node/block/block.h b/app/node/block/block.h index ca5c700ee..52beed7cb 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -43,7 +43,7 @@ public: virtual Type type() const = 0; - virtual QList Category() const override; + virtual QVector Category() const override; const rational& in() const; const rational& out() const; @@ -110,7 +110,7 @@ protected: virtual void SaveInternal(QXmlStreamWriter* writer) const override; - virtual QList GetInputsToHash() const override; + virtual QVector GetInputsToHash() const override; virtual void LengthChangedEvent(const rational& old_length, const rational& new_length, diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 2dfe7ace4..c85d48d3e 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -42,7 +42,7 @@ QString CrossDissolveTransition::id() const return QStringLiteral("org.olivevideoeditor.Olive.crossdissolve"); } -QList CrossDissolveTransition::Category() const +QVector CrossDissolveTransition::Category() const { return {kCategoryTransition}; } @@ -56,7 +56,7 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) cons { Q_UNUSED(shader_id) - return ShaderCode(Node::ReadFileAsString(":/shaders/crossdissolve.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString()); } void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.h b/app/node/block/transition/crossdissolve/crossdissolvetransition.h index e45d1de67..98daf4330 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.h +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; //virtual void Retranslate() override; diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index 816c75011..d75593158 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -43,7 +43,7 @@ QString DipToColorTransition::id() const return QStringLiteral("org.olivevideoeditor.Olive.diptocolor"); } -QList DipToColorTransition::Category() const +QVector DipToColorTransition::Category() const { return {kCategoryTransition}; } @@ -57,7 +57,7 @@ ShaderCode DipToColorTransition::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(Node::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); } void DipToColorTransition::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h index 2c19443e1..660b5fcfc 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.h +++ b/app/node/block/transition/diptocolor/diptocolortransition.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual ShaderCode GetShaderCode(const QString& shader_id) const override; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 3b339f536..5fc6a863a 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -161,15 +161,15 @@ void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &t { // Provides total transition progress from 0.0 (start) - 1.0 (end) job->InsertValue(QStringLiteral("ove_tprog_all"), - NodeValue(NodeParam::kFloat, GetTotalProgress(time), this)); + ShaderValue(GetTotalProgress(time), NodeParam::kFloat)); // Provides progress of out section from 1.0 (start) - 0.0 (end) job->InsertValue(QStringLiteral("ove_tprog_out"), - NodeValue(NodeParam::kFloat, GetOutProgress(time), this)); + ShaderValue(GetOutProgress(time), NodeParam::kFloat)); // Provides progress of in section from 0.0 (start) - 1.0 (end) job->InsertValue(QStringLiteral("ove_tprog_in"), - NodeValue(NodeParam::kFloat, GetInProgress(time), this)); + ShaderValue(GetInProgress(time), NodeParam::kFloat)); } void TransitionBlock::BlockConnected(NodeEdgePtr edge) diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 7950f77e9..332a31555 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -59,7 +59,7 @@ QString BlurFilterNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.blur"); } -QList BlurFilterNode::Category() const +QVector BlurFilterNode::Category() const { return {kCategoryFilter}; } @@ -83,7 +83,7 @@ void BlurFilterNode::Retranslate() ShaderCode BlurFilterNode::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(ReadFileAsString(":/shaders/blur.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/blur.frag"), QString()); } NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const @@ -100,19 +100,19 @@ NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const NodeValueTable table = value.Merge(); // If there's no texture, no need to run an operation - if (!job.GetValue(texture_input_).data().isNull()) { + if (!job.GetValue(texture_input_).data.isNull()) { // Check if radius > 0, and both "horiz" and/or "vert" are enabled - if ((job.GetValue(horiz_input_).data().toBool() || job.GetValue(vert_input_).data().toBool()) - && job.GetValue(radius_input_).data().toDouble() > 0.0) { + if ((job.GetValue(horiz_input_).data.toBool() || job.GetValue(vert_input_).data.toBool()) + && job.GetValue(radius_input_).data.toDouble() > 0.0) { // Set iteration count to 2 if we're blurring both horizontally and vertically - if (job.GetValue(horiz_input_).data().toBool() && job.GetValue(vert_input_).data().toBool()) { + if (job.GetValue(horiz_input_).data.toBool() && job.GetValue(vert_input_).data.toBool()) { job.SetIterations(2, texture_input_); } // If we're not repeating pixels, expect an alpha channel to appear - if (!job.GetValue(repeat_edge_pixels_input_).data().toBool()) { + if (!job.GetValue(repeat_edge_pixels_input_).data.toBool()) { job.SetAlphaChannelRequired(true); } @@ -120,7 +120,7 @@ NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const } else { // If we're not performing the blur job, just push the texture - table.Push(job.GetValue(texture_input_)); + table.Push(job.GetValue(texture_input_), this); } } diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h index 6f9100605..088363991 100644 --- a/app/node/filter/blur/blur.h +++ b/app/node/filter/blur/blur.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index d62c99b37..5606c19a8 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -63,7 +63,7 @@ QString StrokeFilterNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.stroke"); } -QList StrokeFilterNode::Category() const +QVector StrokeFilterNode::Category() const { return {kCategoryFilter}; } @@ -94,12 +94,12 @@ NodeValueTable StrokeFilterNode::Value(NodeValueDatabase &value) const NodeValueTable table = value.Merge(); - if (!job.GetValue(tex_input_).data().isNull()) { - if (job.GetValue(radius_input_).data().toDouble() > 0.0 - && job.GetValue(opacity_input_).data().toDouble() > 0.0) { + if (!job.GetValue(tex_input_).data.isNull()) { + if (job.GetValue(radius_input_).data.toDouble() > 0.0 + && job.GetValue(opacity_input_).data.toDouble() > 0.0) { table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); } else { - table.Push(job.GetValue(tex_input_)); + table.Push(job.GetValue(tex_input_), this); } } @@ -110,7 +110,7 @@ ShaderCode StrokeFilterNode::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(ReadFileAsString(":/shaders/stroke.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag"), QString()); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/filter/stroke/stroke.h b/app/node/filter/stroke/stroke.h index b9d1ac993..044f9ba95 100644 --- a/app/node/filter/stroke/stroke.h +++ b/app/node/filter/stroke/stroke.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index b35247276..836510a3f 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -72,7 +72,7 @@ QString MatrixGenerator::id() const return QStringLiteral("org.olivevideoeditor.Olive.transform"); } -QList MatrixGenerator::Category() const +QVector MatrixGenerator::Category() const { return {kCategoryGenerator, kCategoryMath}; } diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index d6418eda8..ff1ddddb6 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -39,7 +39,7 @@ public: virtual QString Name() const override; virtual QString ShortName() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 870488cc0..379eb659f 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -69,7 +69,7 @@ QString PolygonGenerator::id() const return QStringLiteral("org.olivevideoeditor.Olive.polygon"); } -QList PolygonGenerator::Category() const +QVector PolygonGenerator::Category() const { return {kCategoryGenerator}; } @@ -89,7 +89,7 @@ ShaderCode PolygonGenerator::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(Node::ReadFileAsString(":/shaders/polygon.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/polygon.frag"), QString()); } NodeValueTable PolygonGenerator::Value(NodeValueDatabase &value) const diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index 296177dea..d764ae7be 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -35,7 +35,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index abcf6491f..9309dcd69 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -48,7 +48,7 @@ QString SolidGenerator::id() const return QStringLiteral("org.olivevideoeditor.Olive.solidgenerator"); } -QList SolidGenerator::Category() const +QVector SolidGenerator::Category() const { return {kCategoryGenerator}; } @@ -77,7 +77,7 @@ ShaderCode SolidGenerator::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(ReadFileAsString(":/shaders/solid.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/solid.frag"), QString()); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index aa6fb5f79..101c8f15a 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/generator/text/text.cpp b/app/node/generator/text/text.cpp index de38f08bd..3c078cf34 100644 --- a/app/node/generator/text/text.cpp +++ b/app/node/generator/text/text.cpp @@ -72,7 +72,7 @@ QString TextGenerator::id() const return QStringLiteral("org.olivevideoeditor.Olive.textgenerator"); } -QList TextGenerator::Category() const +QVector TextGenerator::Category() const { return {kCategoryGenerator}; } @@ -104,7 +104,7 @@ NodeValueTable TextGenerator::Value(NodeValueDatabase &value) const NodeValueTable table = value.Merge(); - if (!job.GetValue(text_input_).data().toString().isEmpty()) { + if (!job.GetValue(text_input_).data.toString().isEmpty()) { table.Push(NodeParam::kGenerateJob, QVariant::fromValue(job), this); } @@ -124,14 +124,14 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const // Set default font QFont default_font; - default_font.setFamily(job.GetValue(font_input_).data().toString()); - default_font.setPointSizeF(job.GetValue(font_size_input_).data().toFloat()); + default_font.setFamily(job.GetValue(font_input_).data.toString()); + default_font.setPointSizeF(job.GetValue(font_size_input_).data.toFloat()); text_doc.setDefaultFont(default_font); // Center by default text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter)); - text_doc.setHtml(job.GetValue(text_input_).data().toString()); + text_doc.setHtml(job.GetValue(text_input_).data.toString()); // Align to 80% width because that's considered the "title safe" area int tenth_of_width = frame->video_params().width() / 10; @@ -144,7 +144,7 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const // Push 10% inwards to compensate for title safe area p.translate(tenth_of_width, 0); - TextVerticalAlign valign = static_cast(job.GetValue(valign_input_).data().toInt()); + TextVerticalAlign valign = static_cast(job.GetValue(valign_input_).data.toInt()); int doc_height = text_doc.size().height(); switch (valign) { @@ -165,7 +165,7 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const text_doc.drawContents(&p); // Transplant alpha channel to frame - Color rgb = job.GetValue(color_input_).data().value(); + Color rgb = job.GetValue(color_input_).data.value(); for (int x=0; xwidth(); x++) { for (int y=0; yheight(); y++) { uchar src_alpha = img.bits()[img.bytesPerLine() * y + x]; diff --git a/app/node/generator/text/text.h b/app/node/generator/text/text.h index e4a14b868..cb80fd035 100644 --- a/app/node/generator/text/text.h +++ b/app/node/generator/text/text.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/input.cpp b/app/node/input.cpp index de31c0934..d34e777b6 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -423,7 +423,7 @@ QVariant NodeInput::StringToValue(const DataType& data_type, const QString &stri } } -void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusive_only) const +void NodeInput::GetDependencies(QVector &list, bool traverse, bool exclusive_only) const { if (is_connected() && (get_connected_output()->edges().size() == 1 || !exclusive_only)) { @@ -433,7 +433,7 @@ void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusi list.append(connected); if (traverse) { - QList connected_inputs = connected->GetInputsIncludingArrays(); + QVector connected_inputs = connected->GetInputsIncludingArrays(); foreach (NodeInput* i, connected_inputs) { i->GetDependencies(list, traverse, exclusive_only); @@ -461,21 +461,21 @@ QVariant NodeInput::GetDefaultValueForTrack(int track) const return default_value_.at(track); } -QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const +QVector NodeInput::GetDependencies(bool traverse, bool exclusive_only) const { - QList list; + QVector list; GetDependencies(list, traverse, exclusive_only); return list; } -QList NodeInput::GetExclusiveDependencies() const +QVector NodeInput::GetExclusiveDependencies() const { return GetDependencies(true, true); } -QList NodeInput::GetImmediateDependencies() const +QVector NodeInput::GetImmediateDependencies() const { return GetDependencies(false, false); } diff --git a/app/node/input.h b/app/node/input.h index 2a81c6d40..c7296ad3e 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -282,17 +282,17 @@ public: static QVariant StringToValue(const DataType &data_type, const QString &string, bool value_is_a_key_track); - void GetDependencies(QList& list, bool traverse, bool exclusive_only) const; + void GetDependencies(QVector &list, bool traverse, bool exclusive_only) const; QVariant GetDefaultValue() const; QVariant GetDefaultValueForTrack(int track) const; - QList GetDependencies(bool traverse = true, bool exclusive_only = false) const; + QVector GetDependencies(bool traverse = true, bool exclusive_only = false) const; - QList GetExclusiveDependencies() const; + QVector GetExclusiveDependencies() const; - QList GetImmediateDependencies() const; + QVector GetImmediateDependencies() const; signals: void ValueChanged(const OLIVE_NAMESPACE::TimeRange& range); diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 8d5acd4b8..5469ef46c 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -35,7 +35,7 @@ MediaInput::MediaInput() : AddInput(footage_input_); } -QList MediaInput::Category() const +QVector MediaInput::Category() const { return {kCategoryInput}; } diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index 1ed9e0889..895a4ff66 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -38,7 +38,7 @@ public: virtual Stream::Type type() const = 0; - virtual QList Category() const override; + virtual QVector Category() const override; StreamPtr stream(); void SetStream(StreamPtr s); diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp index c02aeaf39..5507bc12c 100644 --- a/app/node/input/media/video/video.cpp +++ b/app/node/input/media/video/video.cpp @@ -27,7 +27,6 @@ #include "codec/ffmpeg/ffmpegdecoder.h" #include "core.h" #include "project/item/footage/footage.h" -#include "render/pixelformat.h" OLIVE_NAMESPACE_ENTER @@ -38,7 +37,7 @@ Node *VideoInput::copy() const Stream::Type VideoInput::type() const { - return Stream::kVideo; + return Stream::kVideo; } QString VideoInput::Name() const diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp index d4df5b32b..485e90173 100644 --- a/app/node/input/time/timeinput.cpp +++ b/app/node/input/time/timeinput.cpp @@ -41,7 +41,7 @@ QString TimeInput::id() const return QStringLiteral("org.olivevideoeditor.Olive.time"); } -QList TimeInput::Category() const +QVector TimeInput::Category() const { return {kCategoryInput}; } diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h index e79d1b333..d41492942 100644 --- a/app/node/input/time/timeinput.h +++ b/app/node/input/time/timeinput.h @@ -35,7 +35,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual NodeValueTable Value(NodeValueDatabase& value) const override; diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 9330d64d3..fc43f7010 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -55,7 +55,7 @@ QString MathNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.math"); } -QList MathNode::Category() const +QVector MathNode::Category() const { return {kCategoryMath}; } diff --git a/app/node/math/math/math.h b/app/node/math/math/math.h index 81e5111b1..97edcdf0b 100644 --- a/app/node/math/math/math.h +++ b/app/node/math/math/math.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 731a750da..e6b980c66 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -48,7 +48,7 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, NodeInp // No-op frag shader (can we return QString() instead?) operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in->id()); - vert = ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id()); + vert = FileFunctions::ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id()); } else { switch (op) { @@ -329,7 +329,7 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o float number = RetrieveNumber(number_val); SampleJob job(val_a.type() == NodeParam::kSamples ? val_a : val_b); - job.InsertValue(number_param, NodeValue(NodeParam::kFloat, number, this)); + job.InsertValue(number_param, ShaderValue(number, NodeParam::kFloat)); if (job.HasSamples()) { if (number_param->is_static()) { diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 092b69455..6b7ab17a6 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -46,7 +46,7 @@ QString MergeNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.merge"); } -QList MergeNode::Category() const +QVector MergeNode::Category() const { return {kCategoryMath}; } @@ -66,7 +66,7 @@ ShaderCode MergeNode::GetShaderCode(const QString &shader_id) const { Q_UNUSED(shader_id) - return ShaderCode(ReadFileAsString(":/shaders/alphaover.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag"), QString()); } NodeValueTable MergeNode::Value(NodeValueDatabase &value) const @@ -75,17 +75,18 @@ NodeValueTable MergeNode::Value(NodeValueDatabase &value) const job.InsertValue(base_in_, value); job.InsertValue(blend_in_, value); - // FIXME: Check if "blend" is RGB-only, in which case it's a no-op - NodeValueTable table = value.Merge(); - if (!job.GetValue(base_in_).data().isNull() || !job.GetValue(blend_in_).data().isNull()) { - if (job.GetValue(base_in_).data().isNull()) { - // We only have a blend texture, no need to alpha over - table.Push(job.GetValue(blend_in_)); - } else if (job.GetValue(blend_in_).data().isNull()) { + TexturePtr base_tex = job.GetValue(base_in_).data.value(); + TexturePtr blend_tex = job.GetValue(blend_in_).data.value(); + + if (base_tex || blend_tex) { + if (!base_tex || (blend_tex && blend_tex->channel_count() < VideoParams::kRGBAChannelCount)) { + // We only have a blend texture or the blend texture is RGB only, no need to alpha over + table.Push(job.GetValue(blend_in_), this); + } else if (!blend_tex) { // We only have a base texture, no need to alpha over - table.Push(job.GetValue(base_in_)); + table.Push(job.GetValue(base_in_), this); } else { // We have both textures, push the job table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h index 70f79369b..57182caaf 100644 --- a/app/node/math/merge/merge.h +++ b/app/node/math/merge/merge.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index c2f0d03ff..a8649ff64 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -48,7 +48,7 @@ QString TrigonometryNode::id() const return QStringLiteral("org.olivevideoeditor.Olive.trigonometry"); } -QList TrigonometryNode::Category() const +QVector TrigonometryNode::Category() const { return {kCategoryMath}; } diff --git a/app/node/math/trigonometry/trigonometry.h b/app/node/math/trigonometry/trigonometry.h index 9af68472f..b1524616a 100644 --- a/app/node/math/trigonometry/trigonometry.h +++ b/app/node/math/trigonometry/trigonometry.h @@ -34,7 +34,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; virtual void Retranslate() override; diff --git a/app/node/node.cpp b/app/node/node.cpp index 148c41ee5..0abc3e69d 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -29,6 +29,7 @@ #include "project/project.h" #include "project/item/footage/footage.h" #include "project/item/footage/videostream.h" +#include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER @@ -239,6 +240,66 @@ TimeRange Node::OutputTimeAdjustment(NodeInput *, const TimeRange &input_time) c return input_time; } +QVector Node::CopyDependencyGraph(const QVector &nodes, QUndoCommand* command) +{ + int nb_nodes = nodes.size(); + + QVector copies(nb_nodes); + + for (int i=0; icopy();; + + // Copy the values, but NOT the connections, since we'll be connecting to our own clones later + Node::CopyInputs(nodes.at(i), c, false); + + // Add to graph + NodeGraph* graph = static_cast(nodes.at(i)->parent()); + if (command) { + new NodeAddCommand(graph, c, command); + } else { + graph->AddNode(c); + } + + // Store in array at the same index as source + copies[i] = c; + } + + CopyDependencyGraph(nodes, copies, command); + + return copies; +} + +void Node::CopyDependencyGraph(const QVector &src, const QVector &dst, QUndoCommand *command) +{ + int nb_nodes = src.size(); + + for (int i=0; i inputs = src.at(i)->GetInputsIncludingArrays(); + + for (int j=0; jget_connected_node() == src.at(j)) { + // Found a connection + NodeOutput* copy_output = dst.at(j)->GetOutputWithID(input->get_connected_output()->id()); + NodeInput* copy_input = dst.at(i)->GetInputWithID(input->id()); + + if (command) { + new NodeEdgeAddCommand(copy_output, copy_input, command); + } else { + NodeParam::ConnectEdge(copy_output, copy_input); + } + } + } + } + } +} + void Node::SendInvalidateCache(const TimeRange &range, NodeInput *source) { // Loop through all parameters (there should be no children that are not NodeParams) @@ -265,24 +326,12 @@ void Node::SaveInternal(QXmlStreamWriter *) const { } -QList Node::GetInputsToHash() const +QVector Node::GetInputsToHash() const { return GetInputsIncludingArrays(); } -QString Node::ReadFileAsString(const QString &filename) -{ - QFile f(filename); - QString file_data; - if (f.open(QFile::ReadOnly | QFile::Text)) { - QTextStream text_stream(&f); - file_data = text_stream.readAll(); - f.close(); - } - return file_data; -} - -void GetInputsIncludingArraysInternal(NodeInputArray* array, QList& list) +void GetInputsIncludingArraysInternal(NodeInputArray* array, QVector& list) { foreach (NodeInput* input, array->sub_params()) { list.append(input); @@ -293,9 +342,9 @@ void GetInputsIncludingArraysInternal(NodeInputArray* array, QList& } } -QList Node::GetInputsIncludingArrays() const +QVector Node::GetInputsIncludingArrays() const { - QList inputs; + QVector inputs; foreach (NodeParam* param, params_) { if (param->type() == NodeParam::kInput) { @@ -312,7 +361,7 @@ QList Node::GetInputsIncludingArrays() const return inputs; } -QList Node::GetOutputs() const +QVector Node::GetOutputs() const { // The current design only uses one output per node. This function returns a list just in case that changes. return {output_}; @@ -359,7 +408,7 @@ void Node::Hash(QCryptographicHash &hash, const rational& time) const // Add this Node's ID hash.addData(id().toUtf8()); - QList inputs = GetInputsToHash(); + QVector inputs = GetInputsToHash(); foreach (NodeInput* input, inputs) { // For each input, try to hash its value @@ -428,8 +477,8 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections) { Q_ASSERT(source->id() == destination->id()); - const QList& src_param = source->params_; - const QList& dst_param = destination->params_; + const QVector& src_param = source->params_; + const QVector& dst_param = destination->params_; for (int i=0;i& Node::parameters() const +const QVector& Node::parameters() const { return params_; } @@ -490,9 +539,9 @@ int Node::IndexOfParameter(NodeParam *param) const * TRUE to recursively traverse each node for a complete dependency graph. FALSE to return only the immediate * dependencies. */ -QList Node::GetDependenciesInternal(bool traverse, bool exclusive_only) const { - QList inputs = GetInputsIncludingArrays(); - QList list; +QVector Node::GetDependenciesInternal(bool traverse, bool exclusive_only) const { + QVector inputs = GetInputsIncludingArrays(); + QVector list; foreach (NodeInput* i, inputs) { i->GetDependencies(list, traverse, exclusive_only); @@ -501,17 +550,17 @@ QList Node::GetDependenciesInternal(bool traverse, bool exclusive_only) c return list; } -QList Node::GetDependencies() const +QVector Node::GetDependencies() const { return GetDependenciesInternal(true, false); } -QList Node::GetExclusiveDependencies() const +QVector Node::GetExclusiveDependencies() const { return GetDependenciesInternal(true, true); } -QList Node::GetImmediateDependencies() const +QVector Node::GetImmediateDependencies() const { return GetDependenciesInternal(false, false); } @@ -535,7 +584,7 @@ void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const NodeInput *Node::GetInputWithID(const QString &id) const { - QList inputs = GetInputsIncludingArrays(); + QVector inputs = GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { if (i->id() == id) { @@ -560,7 +609,7 @@ NodeOutput *Node::GetOutputWithID(const QString &id) const bool Node::OutputsTo(Node *n, bool recursively) const { - QList outputs = GetOutputs(); + QVector outputs = GetOutputs(); foreach (NodeOutput* output, outputs) { foreach (NodeEdgePtr edge, output->edges()) { @@ -579,7 +628,7 @@ bool Node::OutputsTo(Node *n, bool recursively) const bool Node::OutputsTo(const QString &id, bool recursively) const { - QList outputs = GetOutputs(); + QVector outputs = GetOutputs(); foreach (NodeOutput* output, outputs) { foreach (NodeEdgePtr edge, output->edges()) { @@ -598,7 +647,7 @@ bool Node::OutputsTo(const QString &id, bool recursively) const bool Node::OutputsTo(NodeInput *input, bool recursively, bool include_arrays) const { - QList outputs = GetOutputs(); + QVector outputs = GetOutputs(); foreach (NodeOutput* output, outputs) { foreach (NodeEdgePtr edge, output->edges()) { @@ -620,7 +669,7 @@ bool Node::OutputsTo(NodeInput *input, bool recursively, bool include_arrays) co bool Node::InputsFrom(Node *n, bool recursively) const { - QList inputs = GetInputsIncludingArrays(); + QVector inputs = GetInputsIncludingArrays(); foreach (NodeInput* input, inputs) { foreach (NodeEdgePtr edge, input->edges()) { @@ -639,7 +688,7 @@ bool Node::InputsFrom(Node *n, bool recursively) const bool Node::InputsFrom(const QString &id, bool recursively) const { - QList inputs = GetInputsIncludingArrays(); + QVector inputs = GetInputsIncludingArrays(); foreach (NodeInput* input, inputs) { foreach (NodeEdgePtr edge, input->edges()) { @@ -661,7 +710,7 @@ int Node::GetRoutesTo(Node *n) const bool outputs_directly = false; int routes = 0; - QList outputs = GetOutputs(); + QVector outputs = GetOutputs(); foreach (NodeOutput* o, outputs) { foreach (NodeEdgePtr edge, o->edges()) { @@ -740,13 +789,13 @@ QString Node::GetCategoryName(const CategoryID &c) return tr("Uncategorized"); } -QList Node::TransformTimeTo(const TimeRange &time, Node *target, NodeParam::Type direction) +QVector Node::TransformTimeTo(const TimeRange &time, Node *target, NodeParam::Type direction) { - QList paths_found; + QVector paths_found; if (direction == NodeParam::kInput) { // Get list of all inputs - QList inputs = GetInputsIncludingArrays(); + QVector inputs = GetInputsIncludingArrays(); // If this input is connected, traverse it to see if we stumble across the specified `node` foreach (NodeInput* input, inputs) { @@ -767,7 +816,7 @@ QList Node::TransformTimeTo(const TimeRange &time, Node *target, Node } } else { // Get list of all outputs - QList outputs = GetOutputs(); + QVector outputs = GetOutputs(); // If this input is connected, traverse it to see if we stumble across the specified `node` foreach (NodeOutput* output, outputs) { diff --git a/app/node/node.h b/app/node/node.h index 5ed6826b7..b69dde82c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -36,7 +36,10 @@ #include "node/output.h" #include "node/value.h" #include "render/audioparams.h" -#include "render/shaderinfo.h" +#include "render/job/generatejob.h" +#include "render/job/samplejob.h" +#include "render/job/shaderjob.h" +#include "render/shadercode.h" OLIVE_NAMESPACE_ENTER @@ -129,7 +132,7 @@ public: * interpreted as an empty string category. This value should be run through a translator as its largely user * oriented. */ - virtual QList Category() const = 0; + virtual QVector Category() const = 0; /** * @brief Return a description of this node's purpose (optional for subclassing, but recommended) @@ -147,7 +150,7 @@ public: /** * @brief Return a list of NodeParams */ - const QList& parameters() const; + const QVector& parameters() const; /** * @brief Return the index of a parameter @@ -158,7 +161,7 @@ public: /** * @brief Return a list of all Nodes that this Node's inputs are connected to (does not include this Node) */ - QList GetDependencies() const; + QVector GetDependencies() const; /** * @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them @@ -166,12 +169,12 @@ public: * * Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node. */ - QList GetExclusiveDependencies() const; + QVector GetExclusiveDependencies() const; /** * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one) */ - QList GetImmediateDependencies() const; + QVector GetImmediateDependencies() const; /** * @brief Generate hardware accelerated code for this Node @@ -274,19 +277,19 @@ public: /** * @brief Transforms time from this node through the connections it takes to get to the specified node */ - QList TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction); + QVector TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction); /** * @brief Find nodes of a certain type that this Node takes inputs from */ template - QList FindInputNodes() const; + QVector FindInputNodes() const; template /** * @brief Find a node of a certain type that this Node outputs to */ - QList FindOutputNode(); + QVector FindOutputNode(); /** * @brief Convert a pointer to a value that can be sent between NodeParams @@ -343,6 +346,12 @@ public: */ static void CopyInputs(Node* source, Node* destination, bool include_connections = true); + /** + * @brief Clones a set of nodes and connects the new ones the way the old ones were + */ + static QVector CopyDependencyGraph(const QVector& nodes, QUndoCommand *command); + static void CopyDependencyGraph(const QVector& src, const QVector& dst, QUndoCommand *command); + /** * @brief Return whether this Node can be deleted or not */ @@ -404,11 +413,9 @@ public: void SetPosition(const QPointF& pos); - static QString ReadFileAsString(const QString& filename); + QVector GetInputsIncludingArrays() const; - QList GetInputsIncludingArrays() const; - - QList GetOutputs() const; + QVector GetOutputs() const; virtual bool HasGizmos() const; @@ -434,7 +441,7 @@ protected: virtual void SaveInternal(QXmlStreamWriter* writer) const; - virtual QList GetInputsToHash() const; + virtual QVector GetInputsToHash() const; protected slots: void InputChanged(const OLIVE_NAMESPACE::TimeRange &range); @@ -487,14 +494,14 @@ private: void DisconnectInput(NodeInput* input); template - static void FindInputNodeInternal(const Node* n, QList& list); + static void FindInputNodeInternal(const Node* n, QVector& list); template - static void FindOutputNodeInternal(const Node* n, QList& list); + static void FindOutputNodeInternal(const Node* n, QVector& list); - QList GetDependenciesInternal(bool traverse, bool exclusive_only) const; + QVector GetDependenciesInternal(bool traverse, bool exclusive_only) const; - QList params_; + QVector params_; /** * @brief Internal variable for whether this Node can be deleted or not @@ -519,9 +526,9 @@ private: }; template -void Node::FindInputNodeInternal(const Node* n, QList& list) +void Node::FindInputNodeInternal(const Node* n, QVector &list) { - QList inputs = n->GetInputsIncludingArrays(); + QVector inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* input, inputs) { if (input->is_connected()) { @@ -538,9 +545,9 @@ void Node::FindInputNodeInternal(const Node* n, QList& list) } template -QList Node::FindInputNodes() const +QVector Node::FindInputNodes() const { - QList list; + QVector list; FindInputNodeInternal(this, list); @@ -554,7 +561,7 @@ T* Node::ValueToPtr(const QVariant &ptr) } template -void Node::FindOutputNodeInternal(const Node* n, QList& list) { +void Node::FindOutputNodeInternal(const Node* n, QVector& list) { foreach (NodeEdgePtr edge, n->output()->edges()) { Node* connected = edge->input()->parentNode(); T* cast_test = dynamic_cast(connected); @@ -568,9 +575,9 @@ void Node::FindOutputNodeInternal(const Node* n, QList& list) { } template -QList Node::FindOutputNode() +QVector Node::FindOutputNode() { - QList list; + QVector list; FindOutputNodeInternal(this, list); diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 1cd8de5f9..09bb92615 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -85,7 +85,7 @@ QString TrackOutput::id() const return QStringLiteral("org.olivevideoeditor.Olive.track"); } -QList TrackOutput::Category() const +QVector TrackOutput::Category() const { return {kCategoryTimeline}; } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 6e32f1c9c..06296c513 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -45,7 +45,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; const double& GetTrackHeight() const; @@ -217,11 +217,6 @@ public: return waveform_; } - QMutex* waveform_lock() - { - return &waveform_lock_; - } - static const double kTrackHeightDefault; static const double kTrackHeightMinimum; static const double kTrackHeightInterval; @@ -297,7 +292,6 @@ private: bool locked_; AudioVisualWaveform waveform_; - QMutex waveform_lock_; private slots: void BlockConnected(NodeEdgePtr edge); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index ca99b0837..b2917546d 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -82,7 +82,7 @@ QString ViewerOutput::id() const return QStringLiteral("org.olivevideoeditor.Olive.vieweroutput"); } -QList ViewerOutput::Category() const +QVector ViewerOutput::Category() const { return {kCategoryOutput}; } @@ -102,7 +102,6 @@ void ViewerOutput::ShiftAudioCache(const rational &from, const rational &to) audio_playback_cache_.Shift(from, to); foreach (TrackOutput* track, track_lists_.at(Timeline::kTrackTypeAudio)->GetTracks()) { - QMutexLocker locker(track->waveform_lock()); track->waveform().Shift(from, to); } } @@ -164,6 +163,8 @@ void ViewerOutput::set_video_params(const VideoParams &video) } emit VideoParamsChanged(); + + video_frame_cache_.InvalidateAll(); } void ViewerOutput::set_audio_params(const AudioParams &audio) @@ -171,6 +172,9 @@ void ViewerOutput::set_audio_params(const AudioParams &audio) audio_params_ = audio; emit AudioParamsChanged(); + + // This will automatically InvalidateAll + audio_playback_cache_.SetParameters(audio_params()); } rational ViewerOutput::GetLength() diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 94185f2ac..3113c2eae 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -53,7 +53,7 @@ public: virtual QString Name() const override; virtual QString id() const override; - virtual QList Category() const override; + virtual QVector Category() const override; virtual QString Description() const override; void ShiftVideoCache(const rational& from, const rational& to); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 1994d8fd5..fd6593417 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -29,7 +29,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa NodeValueDatabase database; // We need to insert tables into the database for each input - QList inputs = node->GetInputsIncludingArrays(); + QVector inputs = node->GetInputsIncludingArrays(); foreach (NodeInput* input, inputs) { if (IsCancelled()) { diff --git a/app/node/value.cpp b/app/node/value.cpp index ef5ac05bd..fb337253e 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -22,26 +22,6 @@ OLIVE_NAMESPACE_ENTER -NodeValueTable& NodeValueDatabase::operator[](const QString &input_id) -{ - return tables_[input_id]; -} - -NodeValueTable& NodeValueDatabase::operator[](const NodeInput *input) -{ - return tables_[input->id()]; -} - -void NodeValueDatabase::Insert(const QString &key, const NodeValueTable &value) -{ - tables_.insert(key, value); -} - -void NodeValueDatabase::Insert(const NodeInput *key, const NodeValueTable &value) -{ - tables_.insert(key->id(), value); -} - NodeValueTable NodeValueDatabase::Merge() const { QHash copy = tables_; @@ -103,41 +83,6 @@ NodeValue NodeValueTable::TakeWithMeta(const NodeParam::DataType &type, const QS return NodeValue(); } -void NodeValueTable::Push(const NodeValue &value) -{ - values_.append(value); -} - -void NodeValueTable::Push(const NodeParam::DataType &type, const QVariant &data, const Node* from, const QString &tag) -{ - Push(NodeValue(type, data, from, tag)); -} - -void NodeValueTable::Prepend(const NodeValue &value) -{ - values_.prepend(value); -} - -void NodeValueTable::Prepend(const NodeParam::DataType &type, const QVariant &data, const Node* from, const QString &tag) -{ - Prepend(NodeValue(type, data, from, tag)); -} - -const NodeValue &NodeValueTable::at(int index) const -{ - return values_.at(index); -} - -NodeValue NodeValueTable::TakeAt(int index) -{ - return values_.takeAt(index); -} - -int NodeValueTable::Count() const -{ - return values_.size(); -} - bool NodeValueTable::Has(const NodeParam::DataType &type) const { for (int i=values_.size() - 1;i>=0;i--) { @@ -163,11 +108,6 @@ void NodeValueTable::Remove(const NodeValue &v) } } -bool NodeValueTable::isEmpty() const -{ - return values_.isEmpty(); -} - NodeValueTable NodeValueTable::Merge(QList tables) { diff --git a/app/node/value.h b/app/node/value.h index 448c1c54d..448291d9a 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -24,6 +24,7 @@ #include #include "input.h" +#include "render/shadervalue.h" OLIVE_NAMESPACE_ENTER @@ -72,17 +73,58 @@ public: NodeValue GetWithMeta(const NodeParam::DataType& type, const QString& tag = QString()) const; QVariant Take(const NodeParam::DataType& type, const QString& tag = QString()); NodeValue TakeWithMeta(const NodeParam::DataType& type, const QString& tag = QString()); - void Push(const NodeValue& value); - void Push(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()); - void Prepend(const NodeValue& value); - void Prepend(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()); - const NodeValue& at(int index) const; - NodeValue TakeAt(int index); - int Count() const; + + void Push(const NodeValue& value) + { + values_.append(value); + } + + void Push(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()) + { + Push(NodeValue(type, data, from, tag)); + } + + void Push(const ShaderValue &value, const Node *from) + { + Push(value.type, value.data, from, value.tag); + } + + void Prepend(const NodeValue& value) + { + values_.prepend(value); + } + + void Prepend(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()) + { + Prepend(NodeValue(type, data, from, tag)); + } + + void Prepend(const ShaderValue &value, const Node *from) + { + Prepend(value.type, value.data, from, value.tag); + } + + const NodeValue& at(int index) const + { + return values_.at(index); + } + NodeValue TakeAt(int index) + { + return values_.takeAt(index); + } + + int Count() const + { + return values_.size(); + } + bool Has(const NodeParam::DataType& type) const; void Remove(const NodeValue& v); - bool isEmpty() const; + bool isEmpty() const + { + return values_.isEmpty(); + } static NodeValueTable Merge(QList tables); @@ -98,11 +140,25 @@ class NodeValueDatabase public: NodeValueDatabase() = default; - NodeValueTable& operator[](const QString& input_id); - NodeValueTable& operator[](const NodeInput* input); + NodeValueTable& operator[](const QString& input_id) + { + return tables_[input_id]; + } - void Insert(const QString& key, const NodeValueTable &value); - void Insert(const NodeInput* key, const NodeValueTable& value); + NodeValueTable& operator[](const NodeInput* input) + { + return tables_[input->id()]; + } + + void Insert(const QString& key, const NodeValueTable &value) + { + tables_.insert(key, value); + } + + void Insert(const NodeInput* key, const NodeValueTable& value) + { + tables_.insert(key->id(), value); + } NodeValueTable Merge() const; diff --git a/app/panel/curve/curve.cpp b/app/panel/curve/curve.cpp index fd6e418ae..8625f111b 100644 --- a/app/panel/curve/curve.cpp +++ b/app/panel/curve/curve.cpp @@ -37,7 +37,7 @@ void CurvePanel::DeleteSelected() static_cast(GetTimeBasedWidget())->DeleteSelected(); } -void CurvePanel::SetNodes(const QList &nodes) +void CurvePanel::SetNodes(const QVector &nodes) { static_cast(GetTimeBasedWidget())->SetNodes(nodes); } diff --git a/app/panel/curve/curve.h b/app/panel/curve/curve.h index 375c42822..52903dbf0 100644 --- a/app/panel/curve/curve.h +++ b/app/panel/curve/curve.h @@ -35,7 +35,7 @@ public: virtual void DeleteSelected() override; public slots: - void SetNodes(const QList& nodes); + void SetNodes(const QVector &nodes); virtual void IncreaseTrackHeight() override; diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 8580b7c1e..cba87c52c 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -76,30 +76,30 @@ public: } public slots: - void Select(const QList& nodes) + void Select(const QVector& nodes) { node_view_->Select(nodes); } - void SelectWithDependencies(const QList& nodes) + void SelectWithDependencies(const QVector& nodes) { node_view_->SelectWithDependencies(nodes); } - void SelectBlocks(const QList& nodes) + void SelectBlocks(const QVector& nodes) { node_view_->SelectBlocks(nodes); } - void DeselectBlocks(const QList& nodes) + void DeselectBlocks(const QVector& nodes) { node_view_->DeselectBlocks(nodes); } signals: - void NodesSelected(const QList& nodes); + void NodesSelected(const QVector& nodes); - void NodesDeselected(const QList& nodes); + void NodesDeselected(const QVector& nodes); private: virtual void Retranslate() override diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 2b9df7f4c..68ae7525a 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -36,14 +36,14 @@ ParamPanel::ParamPanel(QWidget* parent) : Retranslate(); } -void ParamPanel::SelectNodes(const QList &nodes) +void ParamPanel::SelectNodes(const QVector &nodes) { static_cast(GetTimeBasedWidget())->SelectNodes(nodes); Retranslate(); } -void ParamPanel::DeselectNodes(const QList &nodes) +void ParamPanel::DeselectNodes(const QVector &nodes) { static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 3c4f15354..de7a67967 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -34,15 +34,15 @@ public: ParamPanel(QWidget* parent); public slots: - void SelectNodes(const QList& nodes); - void DeselectNodes(const QList& nodes); + void SelectNodes(const QVector& nodes); + void DeselectNodes(const QVector& nodes); virtual void DeleteSelected() override; signals: - void RequestSelectNode(const QList& target); + void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QList& nodes); + void NodeOrderChanged(const QVector& nodes); void FocusedNodeChanged(Node* n); diff --git a/app/panel/table/table.h b/app/panel/table/table.h index 85c6ea888..10e2bc338 100644 --- a/app/panel/table/table.h +++ b/app/panel/table/table.h @@ -33,12 +33,12 @@ public: NodeTablePanel(QWidget* parent); public slots: - void SelectNodes(const QList& nodes) + void SelectNodes(const QVector& nodes) { static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } - void DeselectNodes(const QList& nodes) + void DeselectNodes(const QVector& nodes) { static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index b05f7b8bc..30bab181a 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -91,9 +91,9 @@ protected: virtual void Retranslate() override; signals: - void BlocksSelected(const QList& selected_blocks); + void BlocksSelected(const QVector& selected_blocks); - void BlocksDeselected(const QList& deselected_blocks); + void BlocksDeselected(const QVector& deselected_blocks); }; diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp index f824c2ec1..946fe8942 100644 --- a/app/project/item/footage/audiostream.cpp +++ b/app/project/item/footage/audiostream.cpp @@ -66,38 +66,6 @@ void AudioStream::set_sample_rate(const int &sample_rate) sample_rate_ = sample_rate; } -bool AudioStream::try_start_conforming(const AudioParams ¶ms) -{ - QMutexLocker locker(proxy_access_lock()); - - if (!currently_conforming_.contains(params) - && !conformed_.contains(params)) { - currently_conforming_.append(params); - return true; - } - - return false; -} - -bool AudioStream::has_conformed_version(const AudioParams ¶ms) -{ - QMutexLocker locker(proxy_access_lock()); - - return conformed_.contains(params); -} - -void AudioStream::append_conformed_version(const AudioParams ¶ms) -{ - { - QMutexLocker locker(proxy_access_lock()); - - currently_conforming_.removeOne(params); - conformed_.append(params); - } - - emit ConformAppended(params); -} - QIcon AudioStream::icon() const { return icon::Audio; diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h index f85046839..a4990b385 100644 --- a/app/project/item/footage/audiostream.h +++ b/app/project/item/footage/audiostream.h @@ -49,10 +49,6 @@ public: const int& sample_rate() const; void set_sample_rate(const int& sample_rate); - bool try_start_conforming(const AudioParams& params); - bool has_conformed_version(const AudioParams& params); - void append_conformed_version(const AudioParams& params); - virtual QIcon icon() const override; protected: @@ -60,18 +56,11 @@ protected: virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override; -signals: - void ConformAppended(OLIVE_NAMESPACE::AudioParams params); - private: int channels_; uint64_t layout_; int sample_rate_; - QList conformed_; - - QList currently_conforming_; - }; using AudioStreamPtr = std::shared_ptr; diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index cd92604c6..a9fa04527 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -180,7 +180,6 @@ QIcon Footage::icon() QString Footage::duration() { // Find longest stream duration - StreamPtr longest_stream = nullptr; rational longest; @@ -200,18 +199,20 @@ QString Footage::duration() if (longest_stream->type() == Stream::kVideo) { VideoStreamPtr video_stream = std::static_pointer_cast(longest_stream); - int64_t duration = video_stream->duration(); - rational frame_rate_timebase = video_stream->frame_rate().flipped(); + if (video_stream->video_type() != VideoStream::kVideoTypeStill) { + int64_t duration = video_stream->duration(); + rational frame_rate_timebase = video_stream->frame_rate().flipped(); - if (video_stream->timebase() != frame_rate_timebase) { - // Convert from timebase to frame rate - rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase()); - duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase); + if (video_stream->timebase() != frame_rate_timebase) { + // Convert from timebase to frame rate + rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase()); + duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase); + } + + return Timecode::timestamp_to_timecode(duration, + frame_rate_timebase, + Core::instance()->GetTimecodeDisplay()); } - - return Timecode::timestamp_to_timecode(duration, - frame_rate_timebase, - Core::instance()->GetTimecodeDisplay()); } else if (longest_stream->type() == Stream::kAudio) { AudioStreamPtr audio_stream = std::static_pointer_cast(longest_stream); @@ -237,11 +238,13 @@ QString Footage::rate() return QString(); } - if (HasStreamsOfType(Stream::kVideo) - && std::static_pointer_cast(get_first_stream_of_type(Stream::kVideo))->video_type() != VideoStream::kVideoTypeStill) { + if (HasStreamsOfType(Stream::kVideo)) { // This is a video editor, prioritize video streams VideoStreamPtr video_stream = std::static_pointer_cast(get_first_stream_of_type(Stream::kVideo)); - return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream->frame_rate().toDouble()); + + if (video_stream->video_type() != VideoStream::kVideoTypeStill) { + return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream->frame_rate().toDouble()); + } } else if (HasStreamsOfType(Stream::kAudio)) { // No video streams, return audio AudioStreamPtr audio_stream = std::static_pointer_cast(streams_.first()); @@ -308,7 +311,7 @@ bool Footage::CompareFootageToItsFilename(FootagePtr footage) } else { // Footage may have changed and we'll have to re-probe it. It also may not have, in which // case nothing needs to change. - ItemPtr item = Decoder::ProbeMedia(footage->project(), footage->filename(), nullptr); + ItemPtr item = Decoder::Probe(footage->project(), footage->filename(), nullptr); if (item && item->type() == footage->type()) { // Item is the same type, that's a good sign. Let's look for any differences. diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 98cbfd1fc..9a0094e4f 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -104,7 +104,9 @@ void VideoStream::LoadCustomParameters(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("type")) { set_video_type(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("format")) { - set_format(static_cast(reader->readElementText().toInt())); + set_format(static_cast(reader->readElementText().toInt())); + } else if (reader->name() == QStringLiteral("channels")) { + set_channel_count(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("pixelaspect")) { set_pixel_aspect_ratio(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("framerate")) { @@ -126,6 +128,7 @@ void VideoStream::SaveCustomParameters(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_)); writer->writeTextElement(QStringLiteral("type"), QString::number(video_type_)); writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); + writer->writeTextElement(QStringLiteral("channels"), QString::number(channel_count_)); writer->writeTextElement(QStringLiteral("pixelaspect"), pixel_aspect_ratio_.toString()); writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString()); writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_)); @@ -159,12 +162,6 @@ void VideoStream::set_colorspace(const QString &color) emit ParametersChanged(); } -QString VideoStream::get_colorspace_match_string() const -{ - return QStringLiteral("%1:%2").arg(footage()->project()->color_manager()->GetConfigFilename(), - colorspace()); -} - void VideoStream::ColorConfigChanged() { ColorManager* color_manager = footage()->project()->color_manager(); diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index 7fe6ba773..59fb8956c 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -21,7 +21,6 @@ #ifndef VIDEOSTREAM_H #define VIDEOSTREAM_H -#include "render/pixelformat.h" #include "render/videoparams.h" #include "stream.h" @@ -74,24 +73,32 @@ public: height_ = height; } - const PixelFormat::Format& format() const + const VideoParams::Format& format() const { return format_; } - void set_format(const PixelFormat::Format& format) + void set_format(const VideoParams::Format& format) { format_ = format; } + int channel_count() const + { + return channel_count_; + } + + void set_channel_count(int c) + { + channel_count_ = c; + } + bool premultiplied_alpha() const; void set_premultiplied_alpha(bool e); const QString& colorspace(bool default_if_empty = true) const; void set_colorspace(const QString& color); - QString get_colorspace_match_string() const; - VideoParams::Interlacing interlacing() const { return interlacing_; @@ -155,7 +162,9 @@ private: VideoType video_type_; - PixelFormat::Format format_; + VideoParams::Format format_; + + int channel_count_; rational pixel_aspect_ratio_; diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index ab61093a1..8b7c4d534 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -21,6 +21,7 @@ #include "sequence.h" #include +#include #include "config/config.h" #include "common/channellayout.h" @@ -69,7 +70,7 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const int video_width = 0, video_height = 0, preview_div = 1; rational video_timebase, video_pixel_aspect; VideoParams::Interlacing video_interlacing = VideoParams::kInterlaceNone; - PixelFormat::Format preview_format = PixelFormat::PIX_FMT_INVALID; + VideoParams::Format preview_format = VideoParams::kFormatInvalid; while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { @@ -85,7 +86,7 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const } else if (reader->name() == QStringLiteral("divider")) { preview_div = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("format")) { - preview_format = static_cast(reader->readElementText().toInt()); + preview_format = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("pixelaspect")) { video_pixel_aspect = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("interlacing")) { @@ -96,11 +97,12 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const } set_video_params(VideoParams(video_width, video_height, video_timebase, preview_format, - video_pixel_aspect, video_interlacing, preview_div)); + VideoParams::kInternalChannelCount, video_pixel_aspect, + video_interlacing, preview_div)); } else if (reader->name() == QStringLiteral("audio")) { int rate = 0; uint64_t layout = 0; - SampleFormat::Format format = SampleFormat::SAMPLE_FMT_INVALID; + AudioParams::Format format = AudioParams::kFormatInvalid; while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("rate")) { @@ -108,7 +110,7 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const } else if (reader->name() == QStringLiteral("layout")) { layout = reader->readElementText().toULongLong(); } else if (reader->name() == QStringLiteral("format")) { - format = static_cast(reader->readElementText().toInt()); + format = static_cast(reader->readElementText().toInt()); } else { reader->skipCurrentElement(); } @@ -267,13 +269,14 @@ void Sequence::set_default_parameters() set_video_params(VideoParams(width, height, Config::Current()["DefaultSequenceFrameRate"].value(), - static_cast(Config::Current()["DefaultSequencePreviewFormat"].toInt()), + static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + VideoParams::kInternalChannelCount, Config::Current()["DefaultSequencePixelAspect"].value(), Config::Current()["DefaultSequenceInterlacing"].value(), VideoParams::generate_auto_divider(width, height))); set_audio_params(AudioParams(Config::Current()["DefaultSequenceAudioFrequency"].toInt(), Config::Current()["DefaultSequenceAudioLayout"].toULongLong(), - SampleFormat::kInternalFormat)); + AudioParams::kInternalFormat)); } void Sequence::set_parameters_from_footage(const QList footage) @@ -309,7 +312,8 @@ void Sequence::set_parameters_from_footage(const QList footage) set_video_params(VideoParams(vs->width(), vs->height(), using_timebase, - static_cast(Config::Current()["DefaultSequencePreviewFormat"].toInt()), + static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + VideoParams::kInternalChannelCount, vs->pixel_aspect_ratio(), vs->interlacing(), VideoParams::generate_auto_divider(vs->width(), vs->height()))); @@ -319,7 +323,7 @@ void Sequence::set_parameters_from_footage(const QList footage) case Stream::kAudio: if (!found_audio_params) { AudioStream* as = static_cast(s.get()); - set_audio_params(AudioParams(as->sample_rate(), as->channel_layout(), SampleFormat::kInternalFormat)); + set_audio_params(AudioParams(as->sample_rate(), as->channel_layout(), AudioParams::kInternalFormat)); found_audio_params = true; } break; diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 709a8c5e5..7f0bae3c7 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -14,35 +14,50 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(backend) +add_subdirectory(job) add_subdirectory(ocioconf) +add_subdirectory(opengl) set(OLIVE_SOURCES ${OLIVE_SOURCES} - render/audioparams.h render/audioparams.cpp - render/audioplaybackcache.h + render/audioparams.h render/audioplaybackcache.cpp - render/color.h + render/audioplaybackcache.h render/color.cpp - render/colormanager.h + render/color.h render/colormanager.cpp - render/colorprocessor.h + render/colormanager.h render/colorprocessor.cpp - render/diskmanager.h + render/colorprocessor.h + render/colorprocessorcache.h render/diskmanager.cpp - render/framehashcache.h + render/diskmanager.h render/framehashcache.cpp - render/managedcolor.h + render/framehashcache.h render/managedcolor.cpp - render/pixelformat.h - render/pixelformat.cpp - render/playbackcache.h + render/managedcolor.h render/playbackcache.cpp + render/playbackcache.h + render/previewautocacher.cpp + render/previewautocacher.h + render/renderer.cpp + render/renderer.h + render/rendercache.h + render/rendererthreadwrapper.cpp + render/rendererthreadwrapper.h + render/rendermanager.cpp + render/rendermanager.h render/rendermodes.h - render/shaderinfo.h - render/videoparams.h + render/renderprocessor.cpp + render/renderprocessor.h + render/shadercode.h + render/shadervalue.h + render/stillimagecache.h + render/texture.cpp + render/texture.h render/videoparams.cpp + render/videoparams.h PARENT_SCOPE ) diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index 9fb1ec2e1..24c308a33 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -49,6 +49,8 @@ const QVector AudioParams::kSupportedChannelLayouts = { AV_CH_LAYOUT_7POINT1 }; +const AudioParams::Format AudioParams::kInternalFormat = AudioParams::kFormatFloat32; + qint64 AudioParams::time_to_bytes(const double &time) const { Q_ASSERT(is_valid()); @@ -68,43 +70,63 @@ bool AudioParams::operator!=(const AudioParams &other) const return !(*this == other); } +QAudioFormat::SampleType AudioParams::GetQtSampleType(AudioParams::Format format) +{ + switch (format) { + case kFormatUnsigned8: + return QAudioFormat::UnSignedInt; + case kFormatSigned16: + case kFormatSigned32: + case kFormatSigned64: + return QAudioFormat::SignedInt; + case kFormatFloat32: + case kFormatFloat64: + return QAudioFormat::Float; + case kFormatInvalid: + case kFormatCount: + break; + } + + return QAudioFormat::Unknown; +} + qint64 AudioParams::time_to_bytes(const rational &time) const { return time_to_bytes(time.toDouble()); } -int AudioParams::time_to_samples(const double &time) const +qint64 AudioParams::time_to_samples(const double &time) const { Q_ASSERT(is_valid()); - return qFloor(time * sample_rate()); + return qRound64(time * sample_rate()); } -int AudioParams::time_to_samples(const rational &time) const +qint64 AudioParams::time_to_samples(const rational &time) const { return time_to_samples(time.toDouble()); } -int AudioParams::samples_to_bytes(const int &samples) const +qint64 AudioParams::samples_to_bytes(const qint64 &samples) const { Q_ASSERT(is_valid()); return samples * channel_count() * bytes_per_sample_per_channel(); } -rational AudioParams::samples_to_time(const int &samples) const +rational AudioParams::samples_to_time(const qint64 &samples) const { return rational(samples, sample_rate()); } -int AudioParams::bytes_to_samples(const int &bytes) const +qint64 AudioParams::bytes_to_samples(const qint64 &bytes) const { Q_ASSERT(is_valid()); return bytes / (channel_count() * bytes_per_sample_per_channel()); } -rational AudioParams::bytes_to_time(const int &bytes) const +rational AudioParams::bytes_to_time(const qint64 &bytes) const { Q_ASSERT(is_valid()); @@ -119,18 +141,18 @@ int AudioParams::channel_count() const int AudioParams::bytes_per_sample_per_channel() const { switch (format_) { - case SampleFormat::SAMPLE_FMT_U8: + case kFormatUnsigned8: return 1; - case SampleFormat::SAMPLE_FMT_S16: + case kFormatSigned16: return 2; - case SampleFormat::SAMPLE_FMT_S32: - case SampleFormat::SAMPLE_FMT_FLT: + case kFormatSigned32: + case kFormatFloat32: return 4; - case SampleFormat::SAMPLE_FMT_DBL: - case SampleFormat::SAMPLE_FMT_S64: + case kFormatSigned64: + case kFormatFloat64: return 8; - case SampleFormat::SAMPLE_FMT_INVALID: - case SampleFormat::SAMPLE_FMT_COUNT: + case kFormatInvalid: + case kFormatCount: break; } @@ -146,8 +168,8 @@ bool AudioParams::is_valid() const { return (sample_rate() > 0 && channel_layout() > 0 - && format_ != SampleFormat::SAMPLE_FMT_INVALID - && format_ != SampleFormat::SAMPLE_FMT_COUNT); + && format_ > kFormatInvalid + && format_ < kFormatCount); } QString AudioParams::SampleRateToString(const int &sample_rate) diff --git a/app/render/audioparams.h b/app/render/audioparams.h index 521218e0e..4ce4ccfbf 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -21,23 +21,51 @@ #ifndef AUDIOPARAMS_H #define AUDIOPARAMS_H +#include #include -#include "audio/sampleformat.h" #include "common/rational.h" OLIVE_NAMESPACE_ENTER class AudioParams { public: + enum Format { + /// Invalid + kFormatInvalid = -1, + + /// 8-bit unsigned integer + kFormatUnsigned8, + + /// 16-bit signed integer + kFormatSigned16, + + /// 32-bit signed integer + kFormatSigned32, + + /// 64-bit signed integer + kFormatSigned64, + + /// 32-bit float + kFormatFloat32, + + /// 64-bit float + kFormatFloat64, + + /// Total format count + kFormatCount + }; + + static const Format kInternalFormat; + AudioParams() : sample_rate_(0), channel_layout_(0), - format_(SampleFormat::SAMPLE_FMT_INVALID) + format_(kFormatInvalid) { } - AudioParams(const int& sample_rate, const uint64_t& channel_layout, const SampleFormat::Format& format) : + AudioParams(const int& sample_rate, const uint64_t& channel_layout, const Format& format) : sample_rate_(sample_rate), channel_layout_(channel_layout), format_(format) @@ -59,19 +87,19 @@ public: return rational(1, sample_rate()); } - const SampleFormat::Format &format() const + const Format &format() const { return format_; } qint64 time_to_bytes(const double& time) const; qint64 time_to_bytes(const rational& time) const; - int time_to_samples(const double& time) const; - int time_to_samples(const rational& time) const; - int samples_to_bytes(const int& samples) const; - rational samples_to_time(const int& samples) const; - int bytes_to_samples(const int &bytes) const; - rational bytes_to_time(const int &bytes) const; + qint64 time_to_samples(const double& time) const; + qint64 time_to_samples(const rational& time) const; + qint64 samples_to_bytes(const qint64& samples) const; + rational samples_to_time(const qint64& samples) const; + qint64 bytes_to_samples(const qint64 &bytes) const; + rational bytes_to_time(const qint64 &bytes) const; int channel_count() const; int bytes_per_sample_per_channel() const; int bits_per_sample() const; @@ -80,6 +108,8 @@ public: bool operator==(const AudioParams& other) const; bool operator!=(const AudioParams& other) const; + static QAudioFormat::SampleType GetQtSampleType(Format format); + static const QVector kSupportedChannelLayouts; static const QVector kSupportedSampleRates; @@ -98,7 +128,7 @@ private: uint64_t channel_layout_; - SampleFormat::Format format_; + Format format_; }; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index eda8ae905..a4997c035 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -127,7 +127,7 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample seg_file.close(); - ranges_we_validated.InsertTimeRange(TimeRange(this_write_in_point, this_write_out_point)); + ranges_we_validated.insert(TimeRange(this_write_in_point, this_write_out_point)); } else { qWarning() << "Failed to write PCM data to" << seg_file.fileName(); } @@ -157,7 +157,7 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range, qint64 job_time) void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time) { - if (from_in_time == to_in_time) { + if (from_in_time == to_in_time || GetLength().isNull()) { // Nothing to be done return; } diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt deleted file mode 100644 index f1a46cbd7..000000000 --- a/app/render/backend/opengl/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2019 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} - render/backend/opengl/openglbackend.h - render/backend/opengl/openglbackend.cpp - render/backend/opengl/openglcolorprocessor.h - render/backend/opengl/openglcolorprocessor.cpp - render/backend/opengl/openglframebuffer.h - render/backend/opengl/openglframebuffer.cpp - render/backend/opengl/openglproxy.h - render/backend/opengl/openglproxy.cpp - render/backend/opengl/openglrenderfunctions.h - render/backend/opengl/openglrenderfunctions.cpp - render/backend/opengl/openglshader.h - render/backend/opengl/openglshader.cpp - render/backend/opengl/opengltexture.h - render/backend/opengl/opengltexture.cpp - render/backend/opengl/opengltexturecache.h - render/backend/opengl/opengltexturecache.cpp - render/backend/opengl/openglworker.h - render/backend/opengl/openglworker.cpp - PARENT_SCOPE -) diff --git a/app/render/backend/opengl/openglcolorprocessor.cpp b/app/render/backend/opengl/openglcolorprocessor.cpp deleted file mode 100644 index bc0993f4f..000000000 --- a/app/render/backend/opengl/openglcolorprocessor.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglcolorprocessor.h" - -#include -#include - -#include "openglrenderfunctions.h" - -OLIVE_NAMESPACE_ENTER - -void OpenGLColorProcessor::Enable(QOpenGLContext *context, bool alpha_is_associated) -{ - if (IsEnabled()) { - return; - } - - context_ = context; - - pipeline_ = OpenGLShader::CreateOCIO(context_, - ocio_lut_, - GetProcessor(), - alpha_is_associated); - - connect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLColorProcessor::ClearTexture, Qt::DirectConnection); -} - -bool OpenGLColorProcessor::IsEnabled() const -{ - return ocio_lut_; -} - -OpenGLShaderPtr OpenGLColorProcessor::pipeline() const -{ - return pipeline_; -} - -void OpenGLColorProcessor::ProcessOpenGL(bool flipped, const QMatrix4x4& matrix) -{ - OpenGLRenderFunctions::OCIOBlit(pipeline_, ocio_lut_, flipped, matrix); -} - -void OpenGLColorProcessor::ClearTexture() -{ - if (IsEnabled()) { - // Clean up OCIO LUT texture and shader - context_->functions()->glDeleteTextures(1, &ocio_lut_); - - disconnect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLColorProcessor::ClearTexture); - - ocio_lut_ = 0; - pipeline_ = nullptr; - } -} - -OpenGLColorProcessor::OpenGLColorProcessor(ColorManager* config, const QString &source_space, const ColorTransform &dest_space) : - ColorProcessor(config, source_space, dest_space), - ocio_lut_(0) -{ -} - -OpenGLColorProcessor::~OpenGLColorProcessor() -{ - ClearTexture(); -} - -OpenGLColorProcessorPtr OpenGLColorProcessor::Create(ColorManager *config, const QString &source_space, const ColorTransform &dest_space) -{ - return std::make_shared(config, source_space, dest_space); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglcolorprocessor.h b/app/render/backend/opengl/openglcolorprocessor.h deleted file mode 100644 index bd8e6dc9c..000000000 --- a/app/render/backend/opengl/openglcolorprocessor.h +++ /dev/null @@ -1,69 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLCOLORPROCESSOR_H -#define OPENGLCOLORPROCESSOR_H - -#include "openglshader.h" -#include "render/colorprocessor.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLColorProcessor; -using OpenGLColorProcessorPtr = std::shared_ptr; - -class OpenGLColorProcessor : public QObject, public ColorProcessor -{ - Q_OBJECT -public: - OpenGLColorProcessor(ColorManager *config, - const QString& input, - const ColorTransform& dest); - - virtual ~OpenGLColorProcessor() override; - - static OpenGLColorProcessorPtr Create(ColorManager* config, - const QString& input, - const ColorTransform& dest); - - void Enable(QOpenGLContext* context, bool alpha_is_associated); - bool IsEnabled() const; - - OpenGLShaderPtr pipeline() const; - - void ProcessOpenGL(bool flipped = false, const QMatrix4x4& matrix = QMatrix4x4()); - -private: - QOpenGLContext* context_; - - GLuint ocio_lut_; - - OpenGLShaderPtr pipeline_; - -private slots: - void ClearTexture(); - -}; - -using OpenGLColorProcessorCache = QHash; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLCOLORPROCESSOR_H diff --git a/app/render/backend/opengl/openglframebuffer.cpp b/app/render/backend/opengl/openglframebuffer.cpp deleted file mode 100644 index e31ce50b3..000000000 --- a/app/render/backend/opengl/openglframebuffer.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglframebuffer.h" - -#include -#include - -OLIVE_NAMESPACE_ENTER - -OpenGLFramebuffer::OpenGLFramebuffer() : - context_(nullptr), - buffer_(0), - texture_(nullptr) -{ -} - -OpenGLFramebuffer::~OpenGLFramebuffer() -{ - Destroy(); -} - -void OpenGLFramebuffer::Create(QOpenGLContext *ctx) -{ - if (ctx == nullptr) { - qWarning() << "OpenGLFramebuffer::Create was passed an invalid context"; - return; - } - - // Free any previous framebuffer - Destroy(); - - context_ = ctx; - - connect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLFramebuffer::Destroy); - - // Create framebuffer object - context_->functions()->glGenFramebuffers(1, &buffer_); -} - -void OpenGLFramebuffer::Destroy() -{ - if (context_ != nullptr) { - disconnect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLFramebuffer::Destroy); - - context_->functions()->glDeleteFramebuffers(1, &buffer_); - - buffer_ = 0; - - context_ = nullptr; - } -} - -bool OpenGLFramebuffer::IsCreated() const -{ - return (buffer_ > 0); -} - -void OpenGLFramebuffer::Bind() -{ - if (context_ == nullptr) { - return; - } - context_->functions()->glBindFramebuffer(GL_FRAMEBUFFER, buffer_); -} - -void OpenGLFramebuffer::Release() -{ - if (context_ == nullptr) { - return; - } - context_->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void OpenGLFramebuffer::Attach(OpenGLTexture *texture, bool clear) -{ - if (context_ == nullptr) { - return; - } - - Detach(); - - texture_ = texture; - - QOpenGLFunctions* f = context_->functions(); - - // bind framebuffer for attaching - f->glBindFramebuffer(GL_FRAMEBUFFER, buffer_); - - context_->extraFunctions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_->texture(), 0 - ); - - if (clear) { - context_->functions()->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - context_->functions()->glClear(GL_COLOR_BUFFER_BIT); - } - - // release framebuffer - f->glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void OpenGLFramebuffer::Attach(OpenGLTexturePtr texture, bool clear) -{ - Attach(texture.get(), clear); -} - -void OpenGLFramebuffer::Detach() -{ - if (context_ == nullptr) { - return; - } - - if (texture_) { - QOpenGLFunctions* f = context_->functions(); - - // bind framebuffer for attaching - f->glBindFramebuffer(GL_FRAMEBUFFER, buffer_); - - context_->extraFunctions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 - ); - - // release framebuffer - f->glBindFramebuffer(GL_FRAMEBUFFER, 0); - - texture_ = nullptr; - } -} - -const GLuint &OpenGLFramebuffer::buffer() const -{ - return buffer_; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp deleted file mode 100644 index 1db21e0b3..000000000 --- a/app/render/backend/opengl/openglproxy.cpp +++ /dev/null @@ -1,567 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglproxy.h" - -#include - -#include "common/clamp.h" -#include "core.h" -#include "node/block/transition/transition.h" -#include "node/node.h" -#include "openglcolorprocessor.h" -#include "openglrenderfunctions.h" -#include "render/colormanager.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -OpenGLProxy* OpenGLProxy::instance_ = nullptr; - -OpenGLProxy::OpenGLProxy(QObject *parent) : - QObject(parent), - ctx_(nullptr), - functions_(nullptr) -{ - surface_.create(); -} - -OpenGLProxy::~OpenGLProxy() -{ - Close(); - - surface_.destroy(); -} - -void OpenGLProxy::CreateInstance() -{ - instance_ = new OpenGLProxy(); - - QThread* proxy_thread = new QThread(); - proxy_thread->start(QThread::IdlePriority); - instance_->moveToThread(proxy_thread); - - if (!instance_->Init()) { - DestroyInstance(); - } -} - -void OpenGLProxy::DestroyInstance() -{ - if (instance_) { - instance_->thread()->quit(); - instance_->thread()->wait(); - instance_->thread()->deleteLater(); - instance_->deleteLater(); - instance_ = nullptr; - } -} - -bool OpenGLProxy::Init() -{ - // Create context object - ctx_ = new QOpenGLContext(); - - // Create OpenGL context (automatically destroys any existing if there is one) - if (!ctx_->create()) { - qWarning() << "Failed to create OpenGL context in thread" << thread(); - return false; - } - - ctx_->moveToThread(this->thread()); - - // The rest of the initialization needs to occur in the other thread, so we signal for it to start - QMetaObject::invokeMethod(this, "FinishInit", Qt::QueuedConnection); - - return true; -} - -QVariant OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const VideoParams& params, const RenderMode::Mode& mode) -{ - VideoStreamPtr video_stream = std::static_pointer_cast(stream); - - // Set up OCIO context - QString colorspace_match = video_stream->get_colorspace_match_string(); - - OpenGLColorProcessorPtr color_processor = std::static_pointer_cast(color_cache_.value(colorspace_match)); - - if (!color_processor) { - color_processor = OpenGLColorProcessor::Create(video_stream->footage()->project()->color_manager(), - video_stream->colorspace(), - video_stream->footage()->project()->color_manager()->GetReferenceColorSpace()); - color_cache_.insert(colorspace_match, color_processor); - } - - ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(mode); - - // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU - if (ocio_method == ColorManager::kOCIOAccurate) { - bool has_alpha = PixelFormat::FormatHasAlphaChannel(frame->format()); - - // Convert frame to float for OCIO - frame = PixelFormat::ConvertPixelFormat(frame, - has_alpha - ? PixelFormat::PIX_FMT_RGBA32F - : PixelFormat::PIX_FMT_RGB32F); - - // If alpha is associated, disassociate for the color transform - if (has_alpha && video_stream->premultiplied_alpha()) { - ColorManager::DisassociateAlpha(frame); - } - - // Perform color transform - color_processor->ConvertFrame(frame); - - // Associate alpha - if (has_alpha) { - if (video_stream->premultiplied_alpha()) { - ColorManager::ReassociateAlpha(frame); - } else { - ColorManager::AssociateAlpha(frame); - } - } - } - - OpenGLTextureCache::ReferencePtr footage_tex_ref = texture_cache_.Get(ctx_, frame); - - if (ocio_method == ColorManager::kOCIOFast) { - if (!color_processor->IsEnabled()) { - color_processor->Enable(ctx_, video_stream->premultiplied_alpha()); - } - - VideoParams frame_params = frame->video_params(); - - PixelFormat::Format texture_fmt; - if (PixelFormat::FormatHasAlphaChannel(frame_params.format())) { - texture_fmt = PixelFormat::GetFormatWithAlphaChannel(params.format()); - } else { - texture_fmt = PixelFormat::GetFormatWithoutAlphaChannel(params.format()); - } - - VideoParams dest_params(frame_params.width(), - frame_params.height(), - texture_fmt, - frame_params.pixel_aspect_ratio(), - frame_params.interlacing(), - frame_params.divider()); - - // Create destination texture - OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_.Get(ctx_, dest_params); - - buffer_.Attach(associated_tex_ref->texture(), true); - buffer_.Bind(); - footage_tex_ref->texture()->Bind(); - - // Set viewport for texture size - functions_->glViewport(0, 0, associated_tex_ref->texture()->width(), associated_tex_ref->texture()->height()); - - // Blit old texture to new texture through OCIO shader - color_processor->ProcessOpenGL(); - - footage_tex_ref->texture()->Release(); - buffer_.Release(); - buffer_.Detach(); - - footage_tex_ref = associated_tex_ref; - } - - return QVariant::fromValue(footage_tex_ref); -} - -QVariant OpenGLProxy::PreCachedFrameToValue(FramePtr frame) -{ - return QVariant::fromValue(texture_cache_.Get(ctx_, frame)); -} - -OpenGLShaderPtr OpenGLProxy::ResolveShaderFromCache(const Node *node, const QString &shader_id) -{ - // Make a composite of the node ID and the shader ID (if applicable) - QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), shader_id); - OpenGLShaderPtr shader = shader_cache_.value(full_shader_id); - - if (!shader) { - // Since we have shader code, compile it now - ShaderCode code = node->GetShaderCode(shader_id); - QString vert_code = code.vert_code(); - QString frag_code = code.frag_code(); - - if (frag_code.isEmpty() && vert_code.isEmpty()) { - qWarning() << "No shader code found for" << node->id() << "- operation will be a no-op"; - } - - if (frag_code.isEmpty()) { - frag_code = OpenGLShader::CodeDefaultFragment(); - } - - if (vert_code.isEmpty()) { - vert_code = OpenGLShader::CodeDefaultVertex(); - } - - shader = OpenGLShader::Create(); - if (shader - && shader->create() - && shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code) - && shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code) - && shader->link()) { - shader_cache_.insert(full_shader_id, shader); - } else { - qWarning() << "Failed to compile shader for" << node->id(); - shader = nullptr; - } - } - - return shader; -} - -void OpenGLProxy::Close() -{ - shader_cache_.clear(); - buffer_.Destroy(); - copy_pipeline_ = nullptr; - functions_ = nullptr; - delete ctx_; - ctx_ = nullptr; -} - -QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, - const TimeRange &range, - const ShaderJob &job, - const VideoParams& params) -{ - // If this node is iterative, we'll pick up which input here - GLuint iterative_input = 0; - QList textures_to_bind; - bool input_textures_have_alpha = false; - - OpenGLShaderPtr shader = ResolveShaderFromCache(node, job.GetShaderID()); - - if (!shader) { - return QVariant(); - } - - shader->bind(); - - NodeValueMap::const_iterator it; - for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) { - // See if the shader has takes this parameter as an input - int variable_location = shader->uniformLocation(it.key()); - - if (variable_location == -1) { - continue; - } - - // See if this value corresponds to an input (NOTE: it may not and this may be null) - NodeInput* corresponding_input = node->GetInputWithID(it.key()); - - // This variable is used in the shader, let's set it - const QVariant& value = it.value().data(); - - NodeParam::DataType data_type = (it.value().type() != NodeParam::kNone) - ? it.value().type() - : corresponding_input->data_type(); - - switch (data_type) { - case NodeInput::kInt: - // kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to - // over/underflows if the number is large enough, but the likelihood of that is quite low. - shader->setUniformValue(variable_location, value.toInt()); - break; - case NodeInput::kFloat: - // kFloat technically specifies a double but as above, OpenGL doesn't support those. - shader->setUniformValue(variable_location, value.toFloat()); - break; - case NodeInput::kVec2: - if (corresponding_input && corresponding_input->IsArray()) { - QVector nv = value.value< QVector >(); - QVector a(nv.size()); - - for (int j=0;j(); - } - - shader->setUniformValueArray(variable_location, a.constData(), a.size()); - - int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key())); - if (count_location > -1) { - shader->setUniformValue(count_location, a.size()); - } - } else { - shader->setUniformValue(variable_location, value.value()); - } - break; - case NodeInput::kVec3: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kVec4: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kMatrix: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kCombo: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kColor: - { - Color color = value.value(); - - shader->setUniformValue(variable_location, color.red(), color.green(), color.blue(), color.alpha()); - break; - } - case NodeInput::kBoolean: - shader->setUniformValue(variable_location, value.toBool()); - break; - case NodeInput::kBuffer: - case NodeInput::kTexture: - { - OpenGLTextureCache::ReferencePtr texture = value.value(); - - if (texture) { - if (PixelFormat::FormatHasAlphaChannel(texture->texture()->format())) { - input_textures_have_alpha = true; - } - } - - // Set value to bound texture - shader->setUniformValue(variable_location, textures_to_bind.size()); - - // If this texture binding is the iterative input, set it here - if (corresponding_input && corresponding_input == job.GetIterativeInput()) { - iterative_input = textures_to_bind.size(); - } - - GLuint tex_id = texture ? texture->texture()->texture() : 0; - textures_to_bind.append(tex_id); - - // Set enable flag if shader wants it - int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(it.key())); - if (enable_param_location > -1) { - shader->setUniformValue(enable_param_location, - tex_id > 0); - } - - if (tex_id > 0) { - // Set texture resolution if shader wants it - int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(it.key())); - if (res_param_location > -1) { - int adjusted_width = texture->texture()->width() * texture->texture()->divider(); - - // Adjust virtual width by pixel aspect if necessary - if (texture->texture()->params().pixel_aspect_ratio() != 1 - || params.pixel_aspect_ratio() != 1) { - double relative_pixel_aspect = texture->texture()->params().pixel_aspect_ratio().toDouble() / params.pixel_aspect_ratio().toDouble(); - - adjusted_width = qRound(static_cast(adjusted_width) * relative_pixel_aspect); - } - - shader->setUniformValue(res_param_location, - adjusted_width, - static_cast(texture->texture()->height() * texture->texture()->divider())); - } - } - break; - } - case NodeInput::kSamples: - case NodeInput::kText: - case NodeInput::kRational: - case NodeInput::kFont: - case NodeInput::kFile: - case NodeInput::kDecimal: - case NodeInput::kNumber: - case NodeInput::kString: - case NodeInput::kVector: - case NodeInput::kShaderJob: - case NodeInput::kSampleJob: - case NodeInput::kGenerateJob: - case NodeInput::kFootage: - case NodeInput::kNone: - case NodeInput::kAny: - break; - } - } - - // Provide some standard args - shader->setUniformValue("ove_resolution", - static_cast(params.width()), - static_cast(params.height())); - - shader->release(); - - // Create the output textures - PixelFormat::Format output_format = (input_textures_have_alpha || job.GetAlphaChannelRequired()) - ? PixelFormat::GetFormatWithAlphaChannel(params.format()) - : PixelFormat::GetFormatWithoutAlphaChannel(params.format()); - VideoParams output_params(params.width(), - params.height(), - params.time_base(), - output_format, - params.pixel_aspect_ratio(), - params.interlacing(), - params.divider()); - - int real_iteration_count; - if (job.GetIterationCount() > 1 && job.GetIterativeInput()) { - real_iteration_count = job.GetIterationCount(); - } else { - real_iteration_count = 1; - } - - OpenGLTextureCache::ReferencePtr dst_refs[2]; - dst_refs[0] = texture_cache_.Get(ctx_, output_params); - - // If this node requires multiple iterations, get a texture for it too - if (real_iteration_count > 1) { - dst_refs[1] = texture_cache_.Get(ctx_, output_params); - } - - // Some nodes use multiple iterations for optimization - OpenGLTextureCache::ReferencePtr input_tex, output_tex; - - // Set up OpenGL parameters as necessary - functions_->glViewport(0, 0, params.effective_width(), params.effective_height()); - - // Bind all textures - for (int i=0; iglActiveTexture(GL_TEXTURE0 + i); - functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i)); - OpenGLRenderFunctions::PrepareToDraw(functions_); - } - - for (int iteration=0; iterationbind(); - shader->setUniformValue("ove_iteration", iteration); - shader->release(); - - // Replace iterative input - if (iteration == 0) { - output_tex = dst_refs[0]; - } else { - input_tex = dst_refs[(iteration+1)%2]; - output_tex = dst_refs[iteration%2]; - - functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); - functions_->glBindTexture(GL_TEXTURE_2D, input_tex->texture()->texture()); - OpenGLRenderFunctions::PrepareToDraw(functions_); - } - - buffer_.Attach(output_tex->texture(), true); - buffer_.Bind(); - - // Blit this texture through this shader - OpenGLRenderFunctions::Blit(shader); - - buffer_.Release(); - buffer_.Detach(); - } - - // Release any textures we bound before - for (int i=textures_to_bind.size()-1; i>=0; i--) { - functions_->glActiveTexture(GL_TEXTURE0 + i); - functions_->glBindTexture(GL_TEXTURE_2D, 0); - } - - return QVariant::fromValue(output_tex); -} - -void OpenGLProxy::TextureToBuffer(const QVariant& tex_in, - FramePtr frame, - const QMatrix4x4& matrix) -{ - OpenGLTextureCache::ReferencePtr texture = tex_in.value(); - - if (!texture) { - return; - } - - OpenGLTextureCache::ReferencePtr download_tex; - - if (!frame->is_allocated()) { - // If the frame isn't allocated, we'll assume that we're allocating it to the texture dimensions - frame->set_video_params(texture->texture()->params()); - frame->allocate(); - } - - functions_->glViewport(0, 0, frame->width(), frame->height()); - - if (frame->width() != texture->texture()->width() - || frame->height() != texture->texture()->height()) { - - // Resize the texture if necessary - OpenGLTextureCache::ReferencePtr resized = texture_cache_.Get(ctx_, frame->video_params()); - - buffer_.Attach(resized->texture(), true); - buffer_.Bind(); - - texture->texture()->Bind(); - - // Blit to this new texture - OpenGLRenderFunctions::Blit(copy_pipeline_, false, matrix); - - texture->texture()->Release(); - - buffer_.Release(); - buffer_.Detach(); - - download_tex = resized; - - } else { - - download_tex = texture; - - } - - buffer_.Attach(download_tex->texture()); - buffer_.Bind(); - - functions_->glPixelStorei(GL_PACK_ROW_LENGTH, frame->linesize_pixels()); - - functions_->glReadPixels(0, - 0, - frame->width(), - frame->height(), - OpenGLRenderFunctions::GetPixelFormat(frame->format()), - OpenGLRenderFunctions::GetPixelType(frame->format()), - frame->data()); - - functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0); - - buffer_.Release(); - buffer_.Detach(); -} - -void OpenGLProxy::FinishInit() -{ - // Make context current on that surface - if (!ctx_->makeCurrent(&surface_)) { - qWarning() << "Failed to makeCurrent() on offscreen surface in thread" << thread(); - return; - } - - // Store OpenGL functions instance - functions_ = ctx_->functions(); - functions_->glBlendFunc(GL_ONE, GL_ZERO); - - buffer_.Create(ctx_); - - copy_pipeline_ = OpenGLShader::CreateDefault(); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h deleted file mode 100644 index ff59f76b9..000000000 --- a/app/render/backend/opengl/openglproxy.h +++ /dev/null @@ -1,122 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLPROXY_H -#define OPENGLPROXY_H - -#include -#include - -#include "common/timerange.h" -#include "node/value.h" -#include "openglcolorprocessor.h" -#include "openglframebuffer.h" -#include "opengltexturecache.h" -#include "render/shaderinfo.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLProxy : public QObject -{ - Q_OBJECT -public: - OpenGLProxy(QObject* parent = nullptr); - - virtual ~OpenGLProxy() override; - - static void CreateInstance(); - - static void DestroyInstance(); - - static OpenGLProxy* instance() - { - return instance_; - } - - /** - * @brief Initialize OpenGL instance in whatever thread this object is a part of - * - * This function creates a context (shared with share_ctx provided in the constructor) as well as various other - * OpenGL thread-specific objects necessary for rendering. This function should only ever be called from the main - * thread (i.e. the thread where share_ctx is current on) but AFTER this object has been pushed to its thread with - * moveToThread(). If this function is called from a different thread, it could fail or even segfault on some - * platforms. - * - * The reason this function must be called in the main thread (rather than initializing asynchronously in a separate - * thread) is because different platforms have different rules about creating a share context with a context that - * is still "current" in another thread. While some implementations do allow this, Windows OpenGL (wgl) explicitly - * forbids it and other platforms/drivers will segfault attempting it. While we can obviously call "doneCurrent", I - * haven't found any reliable way to prevent the main thread from making it current again before initialization is - * complete other than blocking it entirely. - * - * To get around this, we create all share contexts in the main thread and then move them to the other thread - * afterwards (which is completely legal). While annoying, this gets around the issue listed above by both preventing - * the main thread from using the context during initialization and preventing more than one shared context being made - * at the same time (which may or may not actually make a difference). - */ - bool Init(); - - void Close(); - -public slots: - QVariant RunNodeAccelerated(const OLIVE_NAMESPACE::Node *node, - const OLIVE_NAMESPACE::TimeRange &range, - const OLIVE_NAMESPACE::ShaderJob &job, - const OLIVE_NAMESPACE::VideoParams ¶ms); - - void TextureToBuffer(const QVariant& texture, - OLIVE_NAMESPACE::FramePtr frame, - const QMatrix4x4& matrix); - - QVariant FrameToValue(OLIVE_NAMESPACE::FramePtr frame, - OLIVE_NAMESPACE::StreamPtr stream, - const OLIVE_NAMESPACE::VideoParams ¶ms, - const OLIVE_NAMESPACE::RenderMode::Mode &mode); - - QVariant PreCachedFrameToValue(OLIVE_NAMESPACE::FramePtr frame); - -private: - OpenGLShaderPtr ResolveShaderFromCache(const Node* node, const QString &shader_id); - - QOpenGLContext* ctx_; - QOffscreenSurface surface_; - - QOpenGLFunctions* functions_; - - OpenGLFramebuffer buffer_; - - OpenGLColorProcessorCache color_cache_; - - OpenGLShaderPtr copy_pipeline_; - - QHash shader_cache_; - - OpenGLTextureCache texture_cache_; - - static OpenGLProxy* instance_; - -private slots: - void FinishInit(); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLPROXY_H diff --git a/app/render/backend/opengl/openglrenderfunctions.cpp b/app/render/backend/opengl/openglrenderfunctions.cpp deleted file mode 100644 index fcdc36a64..000000000 --- a/app/render/backend/opengl/openglrenderfunctions.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglrenderfunctions.h" - -#include -#include -#include - -OLIVE_NAMESPACE_ENTER - -const QVector blit_vertices = { - -1.0f, -1.0f, 0.0f, - 1.0f, -1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - - -1.0f, -1.0f, 0.0f, - -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f -}; - -const QVector blit_texcoords = { - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - - 0.0f, 0.0f, - 0.0f, 1.0f, - 1.0f, 1.0f -}; - -const QVector flipped_blit_texcoords = { - 0.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 0.0f, - - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f -}; - -/** - * @brief Set up texture parameters and mipmap for drawing - * - * Internal function used just before drawing to allow mipmapped bilinear filtering when drawing textures small. - * - * @param f - * - * Currently active QOpenGLFunctions object (use context()->functions() if unsure). - */ -void OpenGLRenderFunctions::PrepareToDraw(QOpenGLFunctions* f) -{ - f->glGenerateMipmap(GL_TEXTURE_2D); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -} - -GLint OpenGLRenderFunctions::GetInternalFormat(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB8: - return GL_RGB8; - case PixelFormat::PIX_FMT_RGBA8: - return GL_RGBA8; - case PixelFormat::PIX_FMT_RGB16U: - return GL_RGB16; - case PixelFormat::PIX_FMT_RGBA16U: - return GL_RGBA16; - case PixelFormat::PIX_FMT_RGB16F: - return GL_RGB16F; - case PixelFormat::PIX_FMT_RGBA16F: - return GL_RGBA16F; - case PixelFormat::PIX_FMT_RGB32F: - return GL_RGB32F; - case PixelFormat::PIX_FMT_RGBA32F: - return GL_RGBA32F; - - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return GL_INVALID_VALUE; -} - -GLenum OpenGLRenderFunctions::GetPixelFormat(const PixelFormat::Format &format) -{ - if (PixelFormat::FormatHasAlphaChannel(format)) { - return GL_RGBA; - } else { - return GL_RGB; - } -} - -GLenum OpenGLRenderFunctions::GetPixelType(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - return GL_UNSIGNED_BYTE; - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - return GL_UNSIGNED_SHORT; - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - return GL_HALF_FLOAT; - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - return GL_FLOAT; - - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return GL_INVALID_VALUE; -} - -void OpenGLRenderFunctions::Blit(OpenGLShaderPtr pipeline, bool flipped, QMatrix4x4 matrix) -{ - Blit(pipeline.get(), flipped, matrix); -} - -void OpenGLRenderFunctions::Blit(OpenGLShader *pipeline, bool flipped, QMatrix4x4 matrix) -{ - Blit(pipeline, - GL_TRIANGLES, - blit_vertices, - flipped ? flipped_blit_texcoords : blit_texcoords, - matrix); -} - -void OpenGLRenderFunctions::Blit(OpenGLShader *pipeline, GLenum mode, const QVector &vert, const QVector &tex, QMatrix4x4 matrix) -{ - QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions(); - - PrepareToDraw(func); - - QOpenGLVertexArrayObject m_vao; - m_vao.create(); - m_vao.bind(); - - QOpenGLBuffer m_vbo; - m_vbo.create(); - m_vbo.bind(); - m_vbo.allocate(vert.constData(), vert.size() * sizeof(GLfloat)); - m_vbo.release(); - - QOpenGLBuffer m_vbo2; - m_vbo2.create(); - m_vbo2.bind(); - m_vbo2.allocate(tex.constData(), tex.size() * sizeof(GLfloat)); - m_vbo2.release(); - - pipeline->bind(); - - pipeline->setUniformValue("ove_mvpmat", matrix); - pipeline->setUniformValue("ove_maintex", 0); - - int vertex_location = pipeline->attributeLocation("a_position"); - m_vbo.bind(); - func->glEnableVertexAttribArray(vertex_location); - func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - m_vbo.release(); - - int tex_location = pipeline->attributeLocation("a_texcoord"); - m_vbo2.bind(); - func->glEnableVertexAttribArray(tex_location); - func->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - m_vbo2.release(); - - // (Size / 3) because we assume each GLfloat has an XYZ pair - func->glDrawArrays(mode, 0, blit_vertices.size() / 3); - - pipeline->release(); - - m_vbo2.destroy(); - m_vbo.destroy(); - m_vao.release(); - m_vao.destroy(); -} - -void OpenGLRenderFunctions::OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped, QMatrix4x4 matrix) -{ - OCIOBlit(pipeline.get(), lut, flipped, matrix); -} - -void OpenGLRenderFunctions::OCIOBlit(OpenGLShader *pipeline, - GLuint lut, - bool flipped, - QMatrix4x4 matrix) -{ - QOpenGLContext* ctx = QOpenGLContext::currentContext(); - QOpenGLExtraFunctions* xf = ctx->extraFunctions(); - - xf->glActiveTexture(GL_TEXTURE1); - xf->glBindTexture(GL_TEXTURE_3D, lut); - xf->glActiveTexture(GL_TEXTURE0); - - pipeline->bind(); - - pipeline->setUniformValue("ove_ociolut", 1); - - Blit(pipeline, flipped, matrix); - - pipeline->release(); - - xf->glActiveTexture(GL_TEXTURE1); - xf->glBindTexture(GL_TEXTURE_3D, 0); - xf->glActiveTexture(GL_TEXTURE0); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglrenderfunctions.h b/app/render/backend/opengl/openglrenderfunctions.h deleted file mode 100644 index 7e31987f7..000000000 --- a/app/render/backend/opengl/openglrenderfunctions.h +++ /dev/null @@ -1,70 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLFUNCTIONS_H -#define OPENGLFUNCTIONS_H - -#include -#include -#include - -#include "openglshader.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLRenderFunctions { -public: - /** - * @brief Draw texture on screen - * - * @param pipeline - * - * Shader to use for the texture drawing - * - * @param flipped - * - * Draw the texture vertically flipped (defaults to FALSE) - * - * @param matrix - * - * Transformation matrix to use when drawing (defaults to no transform) - */ - static void Blit(OpenGLShaderPtr pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); - static void Blit(OpenGLShader* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); - static void Blit(OpenGLShader* pipeline, GLenum mode, const QVector& vert, - const QVector& tex, QMatrix4x4 matrix = QMatrix4x4()); - - static void OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); - static void OCIOBlit(OpenGLShader* pipeline, GLuint lut, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); - - static void PrepareToDraw(QOpenGLFunctions* f); - - static GLint GetInternalFormat(const PixelFormat::Format& format); - - static GLenum GetPixelFormat(const PixelFormat::Format& format); - - static GLenum GetPixelType(const PixelFormat::Format& format); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLFUNCTIONS_H diff --git a/app/render/backend/opengl/openglshader.cpp b/app/render/backend/opengl/openglshader.cpp deleted file mode 100644 index 7e76f619a..000000000 --- a/app/render/backend/opengl/openglshader.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglshader.h" - -#include -OLIVE_NAMESPACE_ENTER - -OpenGLShaderPtr OpenGLShader::Create() -{ - return std::make_shared(); -} - -OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const QString &shader_code) -{ - OpenGLShaderPtr program = Create(); - - // Add shaders to program - program->addShaderFromSourceCode(QOpenGLShader::Vertex, CodeDefaultVertex()); - program->addShaderFromSourceCode(QOpenGLShader::Fragment, CodeDefaultFragment(function_name, shader_code)); - program->link(); - - return program; -} - -// copied from source code to OCIODisplay -const int OCIO_LUT3D_EDGE_SIZE = 64; - -// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE -const int OCIO_NUM_3D_ENTRIES = 3*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE; - -OpenGLShaderPtr OpenGLShader::CreateOCIO(QOpenGLContext* ctx, - GLuint& lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated) -{ - QOpenGLExtraFunctions* xf = ctx->extraFunctions(); - - // Set up shader description - OCIO::GpuShaderDesc shaderDesc; - const char* ocio_func_name = "OCIODisplay"; - shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); - shaderDesc.setFunctionName(ocio_func_name); - shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); - - // Compute LUT - std::vector ocio_lut_data(OCIO_NUM_3D_ENTRIES); - processor->getGpuLut3D(&ocio_lut_data[0], shaderDesc); - - // Create LUT texture - xf->glGenTextures(1, &lut_texture); - - // Bind LUT - xf->glActiveTexture(GL_TEXTURE1); - xf->glBindTexture(GL_TEXTURE_3D, lut_texture); - - // Set texture parameters - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - // Allocate storage for texture - xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F, - OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, - 0, GL_RGB, GL_FLOAT, &ocio_lut_data[0]); - - // Create OCIO shader code - QString shader_text; - - // Workaround since OCIO doesn't support the GLSL version we use - shader_text.append(QStringLiteral("#define texture2D texture\n" - "#define texture3D texture\n")); - - // Append OCIO shader code - shader_text.append(processor->getGpuShaderText(shaderDesc)); - - QString shader_call; - - // Enforce alpha association - if (alpha_is_associated) { - - // If alpha is already associated, we'll need to disassociate and reassociate - shader_text.append("\n"); - - QString disassociate_func_name = "disassoc"; - shader_text.append(CodeAlphaDisassociate(disassociate_func_name)); - - QString reassociate_func_name = "reassoc"; - shader_text.append(CodeAlphaReassociate(reassociate_func_name)); - - // Make OCIO call pass through disassociate and reassociate function - shader_call = QStringLiteral("%3(%1(%2(col), ove_ociolut));").arg(ocio_func_name, - disassociate_func_name, - reassociate_func_name); - - } else { - - // If alpha is not already associated, we can just associate after OCIO - - // Add associate function - QString associate_func_name = "assoc"; - shader_text.append(CodeAlphaAssociate(associate_func_name)); - - // Make OCIO call pass through associate function - shader_call = QStringLiteral("%2(%1(col, ove_ociolut));").arg(ocio_func_name, associate_func_name); - - } - - // Add process() function, which GetPipeline() will call if specified - QString process_function_name = "process"; - shader_text.append(QStringLiteral("\n" - "uniform sampler3D ove_ociolut;\n" - "\n" - "vec4 %2(vec4 col) {\n" - " return %1\n" - "}\n").arg(shader_call, process_function_name)); - - - // Get pipeline-based shader to inject OCIO shader into - OpenGLShaderPtr shader = OpenGLShader::CreateDefault(process_function_name, shader_text); - - // Release LUT - xf->glBindTexture(GL_TEXTURE_3D, 0); - - xf->glActiveTexture(GL_TEXTURE0); - - return shader; -} - -QString OpenGLShader::CodeDefaultFragment(QString function_name, const QString &shader_code) -{ - // Create shader header - QString frag_code = QStringLiteral("#version 150\n" - "\n" - "#ifdef GL_ES\n" - "precision highp int;\n" - "precision highp float;\n" - "#endif\n" - "\n" - "uniform sampler2D ove_maintex;\n" - "uniform vec2 ove_resolution;\n" - "uniform bool ove_deinterlace;\n" - "\n" - "in vec2 ove_texcoord;\n" - "\n" - "out vec4 fragColor;\n" - "\n"); - - // Check if additional code was passed to this function, add it here - if (!function_name.isEmpty() && !shader_code.isEmpty()) { - - // If additional code was passed, add it and reference it in main(). - // - // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. - // The texture coordinate can be acquired through `ove_texcoord`. - - frag_code.append(shader_code); - - } else { - - // No function to call - function_name = QString(); - - } - - // Our function_name arg will either resolve to the function added to this or to nothing, in - // which case they'll just be benign brackets. - frag_code.append(QStringLiteral("\n" - "void main() {\n" - " vec2 using_texcoord = ove_texcoord;\n" - " if (ove_deinterlace) {\n" - " // A very basic deinterlace that halves the vertical\n" - " // resolution and linearly interpolates the two fields\n" - " // by reading the texture coord between them.\n" - " float half_vert = round(ove_resolution.y / 2.0);\n" - " using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert;\n" - " }\n" - " vec4 color = %1(texture(ove_maintex, using_texcoord));\n" - " fragColor = color;\n" - "}\n").arg(function_name)); - - return frag_code; -} - -QString OpenGLShader::CodeDefaultVertex() -{ - // Generate vertex shader - return QStringLiteral("#version 150\n" - "\n" - "#ifdef GL_ES\n" - "precision highp int;\n" - "precision highp float;\n" - "#endif\n" - "\n" - "uniform mat4 ove_mvpmat;\n" - "\n" - "in vec4 a_position;\n" - "in vec2 a_texcoord;\n" - "\n" - "out vec2 ove_texcoord;\n" - "\n" - "void main() {\n" - " gl_Position = ove_mvpmat * a_position;\n" - " ove_texcoord = a_texcoord;\n" - "}\n"); -} - -QString OpenGLShader::CodeAlphaDisassociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " if (col.a > 0.0) {\n" - " return vec4(col.rgb / col.a, col.a);" - " }\n" - " return col;\n" - "}\n").arg(function_name); -} - -QString OpenGLShader::CodeAlphaReassociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " if (col.a > 0.0) {\n" - " return vec4(col.rgb * col.a, col.a);" - " }\n" - " return col;\n" - "}\n").arg(function_name); -} - -QString OpenGLShader::CodeAlphaAssociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " return vec4(col.rgb * col.a, col.a);\n" - "}\n").arg(function_name); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglshader.h b/app/render/backend/opengl/openglshader.h deleted file mode 100644 index 452dc3c3d..000000000 --- a/app/render/backend/opengl/openglshader.h +++ /dev/null @@ -1,65 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLSHADER_H -#define OPENGLSHADER_H - -#include -#include - -#include -namespace OCIO = OCIO_NAMESPACE::v1; - -#include "common/define.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLShader; -using OpenGLShaderPtr = std::shared_ptr; - -/** - * @brief A simple QOpenGLShaderProgram derivative with static functions for creating - */ -class OpenGLShader : public QOpenGLShaderProgram { -public: - OpenGLShader() = default; - - static OpenGLShaderPtr Create(); - - static OpenGLShaderPtr CreateDefault(const QString &function_name = QString(), - const QString &shader_code = QString()); - - static OpenGLShaderPtr CreateOCIO(QOpenGLContext* ctx, - GLuint& lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated); - - static QString CodeDefaultFragment(QString function_name = QString(), - const QString &shader_code = QString()); - static QString CodeDefaultVertex(); - static QString CodeAlphaDisassociate(const QString& function_name); - static QString CodeAlphaReassociate(const QString& function_name); - static QString CodeAlphaAssociate(const QString& function_name); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLSHADER_H diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp deleted file mode 100644 index 9954e863c..000000000 --- a/app/render/backend/opengl/opengltexture.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "opengltexture.h" - -#include -#include -#include - -#include "openglrenderfunctions.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -OpenGLTexture::OpenGLTexture() : - created_ctx_(nullptr), - texture_(0) -{ -} - -OpenGLTexture::~OpenGLTexture() -{ - Destroy(); -} - -bool OpenGLTexture::IsCreated() const -{ - return (texture_); -} - -void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoParams ¶ms, const void* data, int linesize) -{ - if (!ctx) { - qWarning() << "OpenGLTexture::Create was passed an invalid context"; - return; - } - - Destroy(); - - created_ctx_ = ctx; - params_ = params; - - connect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()), Qt::DirectConnection); - - // Create main texture - CreateInternal(created_ctx_, &texture_, data, linesize); -} - -void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoParams ¶ms) -{ - Create(ctx, params, nullptr, 0); -} - -void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame) -{ - Create(ctx, frame.get()); -} - -void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame) -{ - Create(ctx, frame->video_params(), frame->data(), frame->linesize_pixels()); -} - -void OpenGLTexture::Destroy() -{ - if (created_ctx_) { - disconnect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy())); - - created_ctx_->functions()->glDeleteTextures(1, &texture_); - texture_ = 0; - - created_ctx_ = nullptr; - } -} - -void OpenGLTexture::Bind() -{ - created_ctx_->functions()->glBindTexture(GL_TEXTURE_2D, texture_); -} - -void OpenGLTexture::Release() -{ - created_ctx_->functions()->glBindTexture(GL_TEXTURE_2D, 0); -} - -void OpenGLTexture::SetPixelAspectRatio(const rational &r) -{ - params_ = VideoParams(params_.width(), - params_.height(), - params_.time_base(), - params_.format(), - r, - params_.interlacing(), - params_.divider()); -} - -void OpenGLTexture::Upload(FramePtr frame) -{ - Upload(frame.get()); -} - -void OpenGLTexture::Upload(Frame *frame) -{ - Upload(frame->data(), frame->linesize_pixels()); -} - -void OpenGLTexture::Upload(const void *data, int linesize) -{ - if (!IsCreated()) { - qWarning() << "OpenGLTexture::Upload() called while it wasn't created"; - return; - } - - Bind(); - - created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); - - created_ctx_->functions()->glTexSubImage2D(GL_TEXTURE_2D, - 0, - 0, - 0, - width(), - height(), - OpenGLRenderFunctions::GetPixelFormat(format()), - OpenGLRenderFunctions::GetPixelType(format()), - data); - - created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - Release(); -} - -void OpenGLTexture::CreateInternal(QOpenGLContext* create_ctx, GLuint* tex, const void *data, int linesize) -{ - QOpenGLFunctions* f = create_ctx->functions(); - - // Create texture - f->glGenTextures(1, tex); - - // Verify texture - if (texture_ == 0) { - qWarning() << "OpenGL texture creation failed"; - return; - } - - // Bind texture - f->glBindTexture(GL_TEXTURE_2D, *tex); - - // Set linesize - f->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); - - // Allocate storage for texture - f->glTexImage2D(GL_TEXTURE_2D, - 0, - OpenGLRenderFunctions::GetInternalFormat(format()), - width(), - height(), - 0, - OpenGLRenderFunctions::GetPixelFormat(format()), - OpenGLRenderFunctions::GetPixelType(format()), - data); - - // Return linesize to default - f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - // Set texture filtering to bilinear - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Release texture - f->glBindTexture(GL_TEXTURE_2D, 0); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h deleted file mode 100644 index 301383da9..000000000 --- a/app/render/backend/opengl/opengltexture.h +++ /dev/null @@ -1,118 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLTEXTURE_H -#define OPENGLTEXTURE_H - -#include -#include - -#include "codec/frame.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -/** - * @brief A class wrapper around an OpenGL texture - */ -class OpenGLTexture : public QObject -{ - Q_OBJECT -public: - OpenGLTexture(); - virtual ~OpenGLTexture() override; - - DISABLE_COPY_MOVE(OpenGLTexture) - - void Create(QOpenGLContext* ctx, const VideoParams& params, const void *data, int linesize); - void Create(QOpenGLContext* ctx, const VideoParams& params); - void Create(QOpenGLContext* ctx, FramePtr frame); - void Create(QOpenGLContext* ctx, Frame* frame); - - bool IsCreated() const; - - void Bind(); - - void Release(); - - const VideoParams& params() const - { - return params_; - } - - const int& width() const - { - return params_.effective_width(); - } - - const int& height() const - { - return params_.effective_height(); - } - - const PixelFormat::Format &format() const - { - return params_.format(); - } - - const GLuint& texture() const - { - return texture_; - } - - const int& divider() const - { - return params_.divider(); - } - - /** - * @brief Changes the pixel aspect ratio metadata of this textuer - * - * This metadata is important for our render pipeline, but we don't need to do any re-allocation - * to set it like we do with other VideoParam changes, so we provide a function to change only - * the PAR here. - */ - void SetPixelAspectRatio(const rational& r); - - void Upload(FramePtr frame); - void Upload(Frame* frame); - void Upload(const void *data, int linesize); - -public slots: - void Destroy(); - -private: - void CreateInternal(QOpenGLContext *create_ctx, GLuint *tex, const void *data, int linesize); - - QOpenGLContext* created_ctx_; - - GLuint texture_; - - VideoParams params_; - -}; - -using OpenGLTexturePtr = std::shared_ptr; - -OLIVE_NAMESPACE_EXIT - -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTexturePtr) - -#endif // OPENGLTEXTURE_H diff --git a/app/render/backend/opengl/opengltexturecache.cpp b/app/render/backend/opengl/opengltexturecache.cpp deleted file mode 100644 index e00fc8f42..000000000 --- a/app/render/backend/opengl/opengltexturecache.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "opengltexturecache.h" - -OLIVE_NAMESPACE_ENTER - -OpenGLTextureCache::~OpenGLTextureCache() -{ - foreach (Reference* ref, existing_references_) { - ref->ParentKilled(); - } -} - -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, FramePtr frame) -{ - return Get(ctx, frame.get()); -} - -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, Frame *frame) -{ - return Get(ctx, frame->video_params(), frame->data(), frame->linesize_pixels()); -} - -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoParams ¶ms, const void *data, int linesize) -{ - OpenGLTexturePtr texture = nullptr; - - lock_.lock(); - - // Iterate through textures and see if we have one that matches these parameters - for (int i=0;iwidth() == params.effective_width() - && test->height() == params.effective_height() - && test->format() == params.format()) { - texture = test; - available_textures_.removeAt(i); - break; - } - } - - // If we didn't find a texture, we'll need to create one - if (!texture) { - texture = std::make_shared(); - texture->Create(ctx, params); - } - - texture->SetPixelAspectRatio(params.pixel_aspect_ratio()); - - ReferencePtr ref = std::make_shared(this, texture); - existing_references_.append(ref.get()); - - lock_.unlock(); - - if (data) { - texture->Upload(data, linesize); - } - - return ref; -} - -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoParams ¶ms) -{ - return Get(ctx, params, nullptr, 0); -} - -void OpenGLTextureCache::Relinquish(OpenGLTextureCache::Reference *ref) -{ - OpenGLTexturePtr tex = ref->texture(); - - lock_.lock(); - - existing_references_.removeOne(ref); - available_textures_.append(tex); - - lock_.unlock(); -} - -OpenGLTextureCache::Reference::Reference(OpenGLTextureCache *parent, OpenGLTexturePtr texture) : - parent_(parent), - texture_(texture) -{ -} - -OpenGLTextureCache::Reference::~Reference() -{ - if (parent_) { - parent_->Relinquish(this); - } -} - -OpenGLTexturePtr OpenGLTextureCache::Reference::texture() -{ - return texture_; -} - -void OpenGLTextureCache::Reference::ParentKilled() -{ - parent_ = nullptr; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/opengltexturecache.h b/app/render/backend/opengl/opengltexturecache.h deleted file mode 100644 index 276cdd150..000000000 --- a/app/render/backend/opengl/opengltexturecache.h +++ /dev/null @@ -1,80 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef OPENGLTEXTURECACHE_H -#define OPENGLTEXTURECACHE_H - -#include - -#include "openglframebuffer.h" -#include "opengltexture.h" -#include "render/videoparams.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLTextureCache -{ -public: - class Reference { - public: - Reference(OpenGLTextureCache* parent, OpenGLTexturePtr texture); - ~Reference(); - - DISABLE_COPY_MOVE(Reference) - - OpenGLTexturePtr texture(); - - void ParentKilled(); - - private: - OpenGLTextureCache* parent_; - - OpenGLTexturePtr texture_; - }; - - using ReferencePtr = std::shared_ptr; - - OpenGLTextureCache() = default; - - ~OpenGLTextureCache(); - - DISABLE_COPY_MOVE(OpenGLTextureCache) - - ReferencePtr Get(QOpenGLContext *ctx, FramePtr frame); - ReferencePtr Get(QOpenGLContext *ctx, Frame* frame); - ReferencePtr Get(QOpenGLContext *ctx, const VideoParams& params, const void *data, int linesize); - ReferencePtr Get(QOpenGLContext *ctx, const VideoParams& params); - -private: - void Relinquish(Reference* ref); - - QMutex lock_; - - QList available_textures_; - - QList existing_references_; - -}; - -OLIVE_NAMESPACE_EXIT - -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTextureCache::ReferencePtr) - -#endif // OPENGLTEXTURECACHE_H diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp deleted file mode 100644 index ec518ffc7..000000000 --- a/app/render/backend/opengl/openglworker.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "openglworker.h" - -OLIVE_NAMESPACE_ENTER - -OpenGLWorker::OpenGLWorker(RenderBackend *parent) : - RenderWorker(parent) -{ -} - -void OpenGLWorker::TextureToFrame(const QVariant &texture, FramePtr frame, const QMatrix4x4& mat) const -{ - QMetaObject::invokeMethod(OpenGLProxy::instance(), - "TextureToBuffer", - Qt::BlockingQueuedConnection, - Q_ARG(const QVariant&, texture), - OLIVE_NS_ARG(FramePtr, frame), - Q_ARG(const QMatrix4x4&, mat)); -} - -QVariant OpenGLWorker::FootageFrameToTexture(StreamPtr stream, FramePtr frame) const -{ - QVariant value; - - QMetaObject::invokeMethod(OpenGLProxy::instance(), - "FrameToValue", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, value), - OLIVE_NS_ARG(FramePtr, frame), - OLIVE_NS_ARG(StreamPtr, stream), - OLIVE_NS_CONST_ARG(VideoParams&, video_params()), - OLIVE_NS_CONST_ARG(RenderMode::Mode&, render_mode())); - - return value; -} - -QVariant OpenGLWorker::CachedFrameToTexture(FramePtr frame) const -{ - QVariant value; - - QMetaObject::invokeMethod(OpenGLProxy::instance(), - "PreCachedFrameToValue", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, value), - OLIVE_NS_ARG(FramePtr, frame)); - - return value; -} - -QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) -{ - QVariant value; - - QMetaObject::invokeMethod(OpenGLProxy::instance(), - "RunNodeAccelerated", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, value), - OLIVE_NS_CONST_ARG(Node*, node), - OLIVE_NS_CONST_ARG(TimeRange&, range), - OLIVE_NS_CONST_ARG(ShaderJob&, job), - OLIVE_NS_CONST_ARG(VideoParams&, video_params())); - - return value; -} - -bool OpenGLWorker::TextureHasAlpha(const QVariant &v) const -{ - return PixelFormat::FormatHasAlphaChannel(v.value()->texture()->format()); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp deleted file mode 100644 index 6e469e07e..000000000 --- a/app/render/backend/renderbackend.cpp +++ /dev/null @@ -1,888 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "renderbackend.h" - -#include -#include -#include - -#include "config/config.h" -#include "core.h" -#include "task/conform/conform.h" -#include "task/taskmanager.h" -#include "window/mainwindow/mainwindow.h" - -OLIVE_NAMESPACE_ENTER - -QVector RenderBackend::instances_; -QMutex RenderBackend::instance_lock_; -RenderBackend* RenderBackend::active_instance_ = nullptr; -QThreadPool RenderBackend::thread_pool_; - -RenderBackend::RenderBackend(QObject *parent) : - QObject(parent), - viewer_node_(nullptr), - video_force_download_resolution_(false), - autocache_enabled_(false), - autocache_paused_(false), - generate_audio_previews_(false), - render_mode_(RenderMode::kOnline), - autocache_has_changed_(false), - use_custom_autocache_range_(false), - ignore_next_mouse_button_(false) -{ - instance_lock_.lock(); - instances_.append(this); - instance_lock_.unlock(); - - // Set default autocache range - SetAutoCachePlayhead(rational()); -} - -RenderBackend::~RenderBackend() -{ - Close(); -} - -void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) -{ - if (viewer_node_ == viewer_node) { - return; - } - - ViewerOutput* old_viewer = viewer_node_; - if (!viewer_node) { - // If setting to null, set it here before we wait for jobs to finish to prevent WorkerFinished() - // from calling RunNextJob() again and preventing us from finishing - viewer_node_ = nullptr; - } - - if (old_viewer) { - // Cancel any remaining tickets - ClearQueue(); - - // Wait for any currently running jobs to finish - foreach (RenderTicketPtr ticket, running_tickets_) { - ticket->WaitForFinished(); - } - - // Clear autocache lists - { - // This can be cleared normally (hashes will be discarded and need to be calculated again) - autocache_hash_tasks_.clear(); - - // We need to wait for these since they work directly on the FrameHashCache. Most of the time - // this is fine, but not if the FrameHashCache gets deleted after this function. - foreach (QFutureWatcher* watcher, autocache_hash_process_tasks_) { - watcher->waitForFinished(); - } - autocache_hash_process_tasks_.clear(); - - // This can be cleared normally (frames will be discarded and need to be rendered again) - autocache_video_tasks_.clear(); - - // This can be cleared normally (PCM data will be discarded and need to be rendered again) - autocache_audio_tasks_.clear(); - - // We'll need to wait for these since they work directly on the FrameHashCache. Frames will - // be in the cache for later use. - { - QMap*, QByteArray>::const_iterator i; - for (i=autocache_video_download_tasks_.constBegin(); i!=autocache_video_download_tasks_.constEnd(); i++) { - i.key()->waitForFinished(); - } - autocache_video_download_tasks_.clear(); - } - - // No longer caching any hashes - autocache_currently_caching_hashes_.clear(); - } - - // Delete all of our copied nodes - foreach (Node* c, copy_map_) { - c->deleteLater(); - } - copy_map_.clear(); - copied_viewer_node_ = nullptr; - graph_update_queue_.clear(); - - // Disconnect signal (will be a no-op if the signal was never connected) - disconnect(old_viewer, - &ViewerOutput::GraphChangedFrom, - this, - &RenderBackend::NodeGraphChanged); - - disconnect(old_viewer->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &RenderBackend::AutoCacheVideoInvalidated); - - disconnect(old_viewer->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &RenderBackend::AutoCacheAudioInvalidated); - - foreach (const WorkerData& worker, workers_) { - worker.worker->ClearDecoders(); - } - } - - if (viewer_node) { - // If setting to non-null, set it now - viewer_node_ = viewer_node; - - // Copy graph - copied_viewer_node_ = static_cast(viewer_node_->copy()); - copy_map_.insert(viewer_node_, copied_viewer_node_); - - // We begin an operation and never end it which prevents the copy from unnecessarily - // invalidating its own cache - copied_viewer_node_->BeginOperation(); - - NodeGraphChanged(viewer_node_->texture_input()); - NodeGraphChanged(viewer_node_->samples_input()); - ProcessUpdateQueue(); - - if (autocache_enabled_) { - connect(viewer_node_, - &ViewerOutput::GraphChangedFrom, - this, - &RenderBackend::NodeGraphChanged); - - connect(viewer_node_->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &RenderBackend::AutoCacheVideoInvalidated); - - connect(viewer_node_->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &RenderBackend::AutoCacheAudioInvalidated); - } - } -} - -void RenderBackend::AutoCacheRange(const TimeRange &range) -{ - Q_ASSERT(autocache_enabled_); - - autocache_has_changed_ = true; - use_custom_autocache_range_ = true; - custom_autocache_range_ = range; - - AutoCacheRequeueFrames(); -} - -RenderTicketPtr RenderBackend::Hash(const QVector ×, bool prioritize) -{ - Q_ASSERT(viewer_node_); - - SetActiveInstance(); - - RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeHash, - QVariant::fromValue(times)); - - if (prioritize) { - render_queue_.push_front(ticket); - } else { - render_queue_.push_back(ticket); - } - - QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); - - return ticket; -} - -RenderTicketPtr RenderBackend::RenderFrame(const rational &time, bool prioritize, const QByteArray& hash) -{ - Q_ASSERT(viewer_node_); - - SetActiveInstance(); - - RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, - QVariant::fromValue(time)); - - ticket->setProperty("hash", hash); - - if (prioritize) { - render_queue_.push_front(ticket); - } else { - render_queue_.push_back(ticket); - } - - QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); - - return ticket; -} - -RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r, bool prioritize) -{ - Q_ASSERT(viewer_node_); - - SetActiveInstance(); - - RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeAudio, - QVariant::fromValue(r)); - - if (prioritize) { - render_queue_.push_front(ticket); - } else { - render_queue_.push_back(ticket); - } - - QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); - - return ticket; -} - -void RenderBackend::SetVideoParams(const VideoParams ¶ms) -{ - video_params_ = params; -} - -void RenderBackend::SetAudioParams(const AudioParams ¶ms) -{ - audio_params_ = params; -} - -void RenderBackend::IgnoreNextMouseButton() -{ - ignore_next_mouse_button_ = true; -} - -std::list RenderBackend::SplitRangeIntoChunks(const TimeRange &r) -{ - // FIXME: Magic number - const int chunk_size = 2; - - std::list split_ranges; - - int start_time = qFloor(r.in().toDouble() / static_cast(chunk_size)) * chunk_size; - int end_time = qCeil(r.out().toDouble() / static_cast(chunk_size)) * chunk_size; - - for (int i=start_time; iCancel(); - } - render_queue_.clear(); -} - -void RenderBackend::NodeGraphChanged(NodeInput *source) -{ - // We need to determine: - // - If we don't have this input, assume that it's coming soon and ignore it - // - If we do, is this input a child of another input we're already copying? - // - Or are any of the queued inputs children of this one? - - // First we need to find our copy of the input being queued - Node* our_copy_node = copy_map_.value(source->parentNode()); - - // If we don't have this node yet, assume it's coming in a later copy in which case it'll be - // copied then - if (!our_copy_node) { - // Assert that there are updates coming - Q_ASSERT(!graph_update_queue_.isEmpty()); - return; - } - - // If we're here, we must have this node. Determine if we're already copying a "parent" of this - for (int i=0; iIsArray() && static_cast(source)->sub_params().contains(queued_input)) - || queued_input->parentNode()->OutputsTo(source, true, true)) { - // In which case, we don't need to queue it and can queue our own - graph_update_queue_.removeAt(i); - disconnect(queued_input, &NodeInput::destroyed, this, &RenderBackend::QueuedInputRemoved); - i--; - } - - // Check if the source is a member of this array, in which case it'll be copied eventually anyway - if (queued_input->IsArray() - && static_cast(queued_input)->sub_params().contains(source)) { - return; - } - - // Check if this dependency graph is already queued - if (source->parentNode()->OutputsTo(queued_input, true, true)) { - // In which case, no further copy is necessary - return; - } - } - - graph_update_queue_.append(source); - connect(source, &NodeInput::destroyed, this, &RenderBackend::QueuedInputRemoved); -} - -void RenderBackend::Close() -{ - SetViewerNode(nullptr); - - for (int i=0;ideleteLater(); - } - workers_.clear(); -} - -void RenderBackend::RunNextJob() -{ - // If queue is empty, nothing to be done - if (render_queue_.empty()) { - - // If we're the active instance, unset it - instance_lock_.lock(); - if (active_instance_ == this) { - active_instance_ = nullptr; - } - instance_lock_.unlock(); - - return; - } - - // If we have a value update queued, check if all workers are available and proceed from there - if (autocache_enabled_ && !graph_update_queue_.isEmpty()) { - bool all_workers_available = true; - - foreach (const WorkerData& data, workers_) { - if (data.busy) { - all_workers_available = false; - break; - } - } - - if (all_workers_available) { - // Process queue - ProcessUpdateQueue(); - } else { - return; - } - } - - // If we have no workers allocated, allocate them now - if (workers_.isEmpty()) { - // Allocate workers here - workers_.resize(thread_pool_.maxThreadCount()); - - for (int i=0;iSetVideoParams(video_params_); - worker->SetAudioParams(audio_params_); - worker->SetForceDownloadResolution(video_force_download_resolution_); - worker->SetVideoDownloadMatrix(video_download_matrix_); - worker->SetRenderMode(render_mode_); - worker->SetPreviewGenerationEnabled(generate_audio_previews_); - worker->SetCopyMap(©_map_); - worker->SetCachePath(viewer_node_->video_frame_cache()->GetCacheDirectory()); - - // Move ticket from queue to running list - RenderTicketPtr ticket = render_queue_.front(); - render_queue_.pop_front(); - running_tickets_.push_back(ticket); - - // Create watcher to remove from running list - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::TicketFinished); - watcher->SetTicket(ticket); - - // Set job time to now - ticket->SetJobTime(); - - switch (ticket->GetType()) { - case RenderTicket::kTypeHash: - Q_ASSERT(video_params_.is_valid()); - - QtConcurrent::run(&thread_pool_, - worker, - &RenderWorker::Hash, - ticket, - copied_viewer_node_, - ticket->GetTime().value >()); - break; - case RenderTicket::kTypeVideo: - { - Q_ASSERT(video_params_.is_valid()); - - rational frame = ticket->GetTime().value(); - - QtConcurrent::run(&thread_pool_, - worker, - &RenderWorker::RenderFrame, - ticket, - copied_viewer_node_, - frame); - - QByteArray frame_hash = ticket->property("hash").toByteArray(); - if (!frame_hash.isEmpty()) { - autocache_currently_caching_hashes_.append(frame_hash); - } - break; - } - case RenderTicket::kTypeAudio: - Q_ASSERT(audio_params_.is_valid()); - - QtConcurrent::run(&thread_pool_, - worker, - &RenderWorker::RenderAudio, - ticket, - copied_viewer_node_, - ticket->GetTime().value()); - break; - } - - if (render_queue_.empty()) { - // No more jobs, can exit here - break; - } - } - } -} - -void RenderBackend::TicketFinished() -{ - RenderTicketPtr ticket = static_cast(sender())->GetTicket(); - delete sender(); - - running_tickets_.remove(ticket); -} - -void RenderBackend::WorkerGeneratedWaveform(RenderTicketPtr ticket, TrackOutput *track, AudioVisualWaveform samples, TimeRange range) -{ - QList valid_ranges = viewer_node_->audio_playback_cache()->GetValidRanges(range, - ticket->GetJobTime()); - if (!valid_ranges.isEmpty()) { - // Generate visual waveform in this background thread - track->waveform_lock()->lock(); - - track->waveform().set_channel_count(audio_params_.channel_count()); - - foreach (const TimeRange& r, valid_ranges) { - track->waveform().OverwriteSums(samples, r.in(), r.in() - range.in(), r.length()); - } - - track->waveform_lock()->unlock(); - - emit track->PreviewChanged(); - } -} - -void RenderBackend::AutoCacheVideoInvalidated(const TimeRange &range) -{ - ClearVideoQueue(); - - // Hash these frames since that should be relatively quick. - if (ignore_next_mouse_button_ || !(qApp->mouseButtons() & Qt::LeftButton)) { - ignore_next_mouse_button_ = false; - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - QVector frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange({range}); - autocache_hash_tasks_.insert(watcher, frames); - connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheHashesGenerated); - watcher->SetTicket(Hash(frames)); - } -} - -void RenderBackend::AutoCacheAudioInvalidated(const TimeRange &range) -{ - // Start a task to re-render the audio at this range - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - autocache_audio_tasks_.insert(watcher, range); - connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheAudioRendered); - watcher->SetTicket(RenderAudio(range, true)); -} - -void RenderBackend::SetHashes(FrameHashCache* cache, const QVector& times, const QVector& hashes, qint64 job_time) -{ - std::vector existing_hashes; - - for (int i=0; iCachePathName(hash)); - - if (hash_exists) { - existing_hashes.push_back(hash); - } - } - - QMetaObject::invokeMethod(cache, "SetHash", Qt::QueuedConnection, - OLIVE_NS_ARG(rational, time), - Q_ARG(QByteArray, hash), - Q_ARG(qint64, job_time), - Q_ARG(bool, hash_exists)); - } -} - -void RenderBackend::AutoCacheHashesGenerated() -{ - RenderTicketWatcher* watcher = static_cast(sender()); - - if (autocache_hash_tasks_.contains(watcher)) { - if (!watcher->WasCancelled()) { - QFutureWatcher* hw = new QFutureWatcher(); - connect(hw, &QFutureWatcher::finished, this, &RenderBackend::AutoCacheHashesProcessed); - autocache_hash_process_tasks_.append(hw); - hw->setFuture(QtConcurrent::run(this, - &RenderBackend::SetHashes, - viewer_node_->video_frame_cache(), - autocache_hash_tasks_.value(watcher), - watcher->Get().value >(), - watcher->GetTicket()->GetJobTime())); - } - - autocache_hash_tasks_.remove(watcher); - } - - delete watcher; -} - -void RenderBackend::AutoCacheHashesProcessed() -{ - QFutureWatcher* watcher = static_cast*>(sender()); - - if (autocache_hash_process_tasks_.contains(watcher)) { - autocache_hash_process_tasks_.removeOne(watcher); - - AutoCacheRequeueFrames(); - } - - delete watcher; -} - -void RenderBackend::AutoCacheAudioRendered() -{ - RenderTicketWatcher* watcher = static_cast(sender()); - - if (autocache_audio_tasks_.contains(watcher)) { - if (!watcher->WasCancelled()) { - viewer_node_->audio_playback_cache()->WritePCM(autocache_audio_tasks_.value(watcher), - watcher->Get().value(), - watcher->GetTicket()->GetJobTime()); - } - - autocache_audio_tasks_.remove(watcher); - } - - delete watcher; -} - -void RenderBackend::AutoCacheVideoRendered() -{ - RenderTicketWatcher* watcher = static_cast(sender()); - - if (autocache_video_tasks_.contains(watcher)) { - if (!watcher->WasCancelled()) { - const QByteArray& hash = autocache_video_tasks_.value(watcher); - - // Download frame in another thread - QFutureWatcher* w = new QFutureWatcher(); - autocache_video_download_tasks_.insert(w, hash); - connect(w, &QFutureWatcher::finished, this, &RenderBackend::AutoCacheVideoDownloaded); - w->setFuture(QtConcurrent::run(viewer_node_->video_frame_cache(), - &FrameHashCache::SaveCacheFrame, - hash, - watcher->Get().value())); - } - - autocache_video_tasks_.remove(watcher); - } - - delete watcher; -} - -void RenderBackend::AutoCacheVideoDownloaded() -{ - QFutureWatcher* watcher = static_cast*>(sender()); - - if (autocache_video_download_tasks_.contains(watcher)) { - if (!watcher->isCanceled()) { - if (watcher->result()) { - const QByteArray& hash = autocache_video_download_tasks_.value(watcher); - - autocache_currently_caching_hashes_.removeOne(hash); - - viewer_node_->video_frame_cache()->ValidateFramesWithHash(hash); - } else { - qCritical() << "Failed to download video frame"; - } - } - - autocache_video_download_tasks_.remove(watcher); - } - - delete watcher; -} - -void RenderBackend::QueuedInputRemoved() -{ - NodeInput* i = static_cast(sender()); - disconnect(i, &NodeInput::destroyed, this, &RenderBackend::QueuedInputRemoved); - graph_update_queue_.removeOne(i); -} - -//#define PRINT_UPDATE_QUEUE_INFO -void RenderBackend::ProcessUpdateQueue() -{ -#ifdef PRINT_UPDATE_QUEUE_INFO - qint64 t = QDateTime::currentMSecsSinceEpoch(); - qDebug() << "Processing update queue of" << graph_update_queue_.size() << "elements:"; -#endif - - while (!graph_update_queue_.isEmpty()) { - NodeInput* i = graph_update_queue_.takeFirst(); -#ifdef PRINT_UPDATE_QUEUE_INFO - qDebug() << " " << i->parentNode()->id() << i->id(); -#endif - disconnect(i, &NodeInput::destroyed, this, &RenderBackend::QueuedInputRemoved); - - CopyNodeInputValue(i); - } - -#ifdef PRINT_UPDATE_QUEUE_INFO - qDebug() << "Update queue took:" << (QDateTime::currentMSecsSinceEpoch() - t); -#endif -} - -void RenderBackend::WorkerFinished() -{ - RenderWorker* worker = static_cast(sender()); - - // Set busy state to false - for (int i=0;iparentNode()); - Q_ASSERT(our_copy_node); - NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); - - // Copy the standard/keyframe values between these two inputs - NodeInput::CopyValues(input, - our_copy, - false, - false); - - // Handle connections - if (input->is_connected() || our_copy->is_connected()) { - // If one of the inputs is connected, it's likely this change came from connecting or - // disconnecting whatever was connected to it - - // We start by removing all old dependencies from the map - QList old_deps = our_copy->GetExclusiveDependencies(); - foreach (Node* i, old_deps) { - copy_map_.take(copy_map_.key(i))->deleteLater(); - } - - // And clear any other edges - while (!our_copy->edges().isEmpty()) { - NodeParam::DisconnectEdge(our_copy->edges().first()); - } - - // Then we copy all node dependencies and connections (if there are any) - CopyNodeMakeConnection(input, our_copy); - } - - // Call on sub-elements too - if (input->IsArray()) { - foreach (NodeInput* i, static_cast(input)->sub_params()) { - CopyNodeInputValue(i); - } - } -} - -Node* RenderBackend::CopyNodeConnections(Node* src_node) -{ - // Check if this node is already in the map - Node* dst_node = copy_map_.value(src_node); - - // If not, create it now - if (!dst_node) { - dst_node = src_node->copy(); - - if (dst_node->IsTrack()) { - // Hack that ensures the track type is set since we don't bother copying the whole timeline - static_cast(dst_node)->set_track_type(static_cast(src_node)->track_type()); - } - - copy_map_.insert(src_node, dst_node); - } - - // Make sure its values are copied - Node::CopyInputs(src_node, dst_node, false); - - // Copy all connections - QList src_node_inputs = src_node->GetInputsIncludingArrays(); - QList dst_node_inputs = dst_node->GetInputsIncludingArrays(); - - for (int i=0;iis_connected()) { - Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); - - NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id()); - - NodeParam::ConnectEdge(corresponding_output, - dst_input); - } -} - -void RenderBackend::ClearQueueOfType(RenderTicket::Type type) -{ - std::list::iterator i = render_queue_.begin(); - - while (i != render_queue_.end()) { - if ((*i)->GetType() == type) { - (*i)->Cancel(); - i = render_queue_.erase(i); - } else { - i++; - } - } -} - -void RenderBackend::SetActiveInstance() -{ - QMutexLocker locker(&instance_lock_); - - if (active_instance_ != this) { - // Signal active instance to stop - QMetaObject::invokeMethod(active_instance_, "ClearVideoQueue", Qt::QueuedConnection); - - active_instance_ = this; - } -} - -void RenderBackend::AutoCacheRequeueFrames() -{ - if (viewer_node_ - && viewer_node_->video_frame_cache()->HasInvalidatedRanges() - && autocache_hash_tasks_.isEmpty() - && autocache_hash_process_tasks_.isEmpty() - && autocache_has_changed_ - && (!autocache_paused_ || use_custom_autocache_range_)) { - TimeRange using_range; - - if (use_custom_autocache_range_) { - using_range = custom_autocache_range_; - use_custom_autocache_range_ = false; - } else { - using_range = autocache_range_; - } - - QVector invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range); - - ClearVideoQueue(); - - // QMaps are automatically sorted by time which is always best for rendering - QList queued_hashes; - - foreach (const rational& t, invalidated_ranges) { - const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t); - - if (t >= using_range.in() - && t < using_range.out() - && !queued_hashes.contains(hash) - && !autocache_currently_caching_hashes_.contains(hash)) { - // Don't render any hash more than once - queued_hashes.append(hash); - - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheVideoRendered); - autocache_video_tasks_.insert(watcher, hash); - - watcher->SetTicket(RenderFrame(t, false, hash)); - } - } - - autocache_has_changed_ = false; - } -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h deleted file mode 100644 index c7e29e317..000000000 --- a/app/render/backend/renderbackend.h +++ /dev/null @@ -1,259 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef RENDERBACKEND_H -#define RENDERBACKEND_H - -#include - -#include "config/config.h" -#include "dialog/rendercancel/rendercancel.h" -#include "decodercache.h" -#include "node/graph.h" -#include "node/output/viewer/viewer.h" -#include "render/backend/colorprocessorcache.h" -#include "renderticket.h" -#include "renderticketwatcher.h" -#include "renderworker.h" - -OLIVE_NAMESPACE_ENTER - -class RenderBackend : public QObject -{ - Q_OBJECT -public: - RenderBackend(QObject* parent = nullptr); - - virtual ~RenderBackend() override; - - void Close(); - - ViewerOutput* GetViewerNode() const - { - return viewer_node_; - } - - void SetViewerNode(ViewerOutput* viewer_node); - - void SetAutoCacheEnabled(bool e) - { - autocache_enabled_ = e; - } - - bool IsAutoCachePaused() const - { - return autocache_paused_; - } - - void SetAutoCachePaused(bool paused) - { - autocache_paused_ = paused; - - if (autocache_paused_) { - // Pause the autocache - ClearVideoQueue(); - } else { - // Unpause the cache - AutoCacheRequeueFrames(); - } - } - - void AutoCacheRange(const TimeRange& range); - - void AutoCacheRequeueFrames(); - - void SetAutoCachePlayhead(const rational& playhead) - { - autocache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value(), - playhead + Config::Current()["DiskCacheAhead"].value()); - - autocache_has_changed_ = true; - use_custom_autocache_range_ = false; - - AutoCacheRequeueFrames(); - } - - void SetRenderMode(RenderMode::Mode e) - { - render_mode_ = e; - } - - void SetPreviewGenerationEnabled(bool e) - { - generate_audio_previews_ = e; - } - - void ProcessUpdateQueue(); - - /** - * @brief Asynchronously generate a hash at a given time - */ - RenderTicketPtr Hash(const QVector ×, bool prioritize = false); - - /** - * @brief Asynchronously generate a frame at a given time - */ - RenderTicketPtr RenderFrame(const rational& time, bool prioritize = false, const QByteArray& hash = QByteArray()); - - /** - * @brief Asynchronously generate a chunk of audio - */ - RenderTicketPtr RenderAudio(const TimeRange& r, bool prioritize = false); - - const VideoParams& GetVideoParams() const - { - return video_params_; - } - - const AudioParams& GetAudioParams() const - { - return audio_params_; - } - - void SetVideoParams(const VideoParams& params); - - void SetAudioParams(const AudioParams& params); - - void SetForceDownloadResolution(bool e) - { - video_force_download_resolution_ = e; - } - - void SetVideoDownloadMatrix(const QMatrix4x4& mat) - { - video_download_matrix_ = mat; - } - - void IgnoreNextMouseButton(); - - static std::list SplitRangeIntoChunks(const TimeRange& r); - -public slots: - void NodeGraphChanged(NodeInput *source); - - void ClearVideoQueue(); - - void ClearAudioQueue(); - - void ClearQueue(); - -signals: - -protected: - virtual RenderWorker* CreateNewWorker() = 0; - -private: - void CopyNodeInputValue(NodeInput* input); - Node *CopyNodeConnections(Node *src_node); - void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); - - void ClearQueueOfType(RenderTicket::Type type); - - void SetHashes(FrameHashCache* cache, const QVector& times, const QVector& hashes, qint64 job_time); - - ViewerOutput* viewer_node_; - - // VIDEO MEMBERS - VideoParams video_params_; - bool video_force_download_resolution_; - QMatrix4x4 video_download_matrix_; - - // AUDIO MEMBERS - AudioParams audio_params_; - - QList graph_update_queue_; - QHash copy_map_; - ViewerOutput* copied_viewer_node_; - - std::list render_queue_; - - std::list running_tickets_; - - struct WorkerData { - RenderWorker* worker; - bool busy; - }; - - QVector workers_; - - bool autocache_enabled_; - bool autocache_paused_; - - bool generate_audio_previews_; - - RenderMode::Mode render_mode_; - - TimeRange autocache_range_; - - bool autocache_has_changed_; - - bool use_custom_autocache_range_; - TimeRange custom_autocache_range_; - - static QVector instances_; - static QMutex instance_lock_; - static RenderBackend* active_instance_; - static QThreadPool thread_pool_; - void SetActiveInstance(); - - QMap > autocache_hash_tasks_; - - QList*> autocache_hash_process_tasks_; - - QMap autocache_audio_tasks_; - - QMap autocache_video_tasks_; - - QMap*, QByteArray> autocache_video_download_tasks_; - - QVector autocache_currently_caching_hashes_; - - bool ignore_next_mouse_button_; - -private slots: - void WorkerFinished(); - - void RunNextJob(); - - void TicketFinished(); - - void WorkerGeneratedWaveform(OLIVE_NAMESPACE::RenderTicketPtr ticket, OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange range); - - void AutoCacheVideoInvalidated(const OLIVE_NAMESPACE::TimeRange &range); - - void AutoCacheAudioInvalidated(const OLIVE_NAMESPACE::TimeRange &range); - - void AutoCacheHashesGenerated(); - - void AutoCacheHashesProcessed(); - - void AutoCacheAudioRendered(); - - void AutoCacheVideoRendered(); - - void AutoCacheVideoDownloaded(); - - void QueuedInputRemoved(); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // RENDERBACKEND_H diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp deleted file mode 100644 index 7c3e2bddd..000000000 --- a/app/render/backend/renderworker.cpp +++ /dev/null @@ -1,471 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "renderworker.h" - -#include -#include -#include - -#include "audio/audiovisualwaveform.h" -#include "common/functiontimer.h" -#include "config/config.h" -#include "node/block/clip/clip.h" -#include "task/conform/conform.h" - -OLIVE_NAMESPACE_ENTER - -// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make -// this a dynamic value somehow or a configurable value? -const int RenderWorker::kMaxDecoderLife = 6000; - -RenderWorker::RenderWorker(RenderBackend* parent) : - parent_(parent), - video_force_download_resolution_(false), - available_(true), - generate_audio_previews_(false), - render_mode_(RenderMode::kOnline) -{ - cleanup_timer_ = new QTimer(); - cleanup_timer_->setInterval(kMaxDecoderLife); - connect(cleanup_timer_, &QTimer::timeout, this, &RenderWorker::ClearOldDecoders, Qt::DirectConnection); - cleanup_timer_->moveToThread(qApp->thread()); - QMetaObject::invokeMethod(cleanup_timer_, "start", Qt::QueuedConnection); -} - -RenderWorker::~RenderWorker() -{ - QMetaObject::invokeMethod(cleanup_timer_, "stop", Qt::QueuedConnection); - cleanup_timer_->deleteLater(); -} - -void RenderWorker::Hash(RenderTicketPtr ticket, ViewerOutput *viewer, const QVector ×) -{ - ticket_ = ticket; - - QVector hashes(times.size()); - - for (int i=0;itexture_input()->get_connected_node(), - video_params_, - times.at(i)); - } - - ticket->Finish(QVariant::fromValue(hashes)); - - emit FinishedJob(); -} - -QByteArray RenderWorker::HashNode(const Node *n, const VideoParams ¶ms, const rational &time) -{ - QCryptographicHash hasher(QCryptographicHash::Sha1); - - // Embed video parameters into this hash - hasher.addData(reinterpret_cast(¶ms.effective_width()), sizeof(int)); - hasher.addData(reinterpret_cast(¶ms.effective_height()), sizeof(int)); - hasher.addData(reinterpret_cast(¶ms.format()), sizeof(PixelFormat::Format)); - - if (n) { - n->Hash(hasher, time); - } - - return hasher.result(); -} - -void RenderWorker::ClearOldDecoders() -{ - QMutexLocker locker(&decoder_lock_); - - QHash::iterator i = decoder_age_.begin(); - - while (i != decoder_age_.end()) { - if (i.value() < QDateTime::currentMSecsSinceEpoch() - kMaxDecoderLife) { - // This decoder is old, remove it - decoder_cache_.remove(i.key()); - - i = decoder_age_.erase(i); - } else { - i++; - } - } -} - -void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time) -{ - ticket_ = ticket; - - NodeValueTable table = ProcessInput(viewer->texture_input(), - TimeRange(time, time + video_params_.time_base())); - - QVariant texture = table.Get(NodeParam::kTexture); - - PixelFormat::Format output_format; - if (!texture.isNull() && TextureHasAlpha(texture)) { - output_format = PixelFormat::GetFormatWithAlphaChannel(video_params_.format()); - } else { - output_format = PixelFormat::GetFormatWithoutAlphaChannel(video_params_.format()); - } - - FramePtr frame = Frame::Create(); - frame->set_timestamp(time); - - if (video_force_download_resolution_ || texture.isNull()) { - // If we're setting the resolution ourselves or we're zeroing it out, allocate the frame now - frame->set_video_params(VideoParams(video_params_.width(), - video_params_.height(), - video_params_.time_base(), - output_format, - video_params_.pixel_aspect_ratio(), - video_params_.interlacing(), - video_params_.divider())); - frame->allocate(); - } - - if (texture.isNull()) { - // Blank frame out - memset(frame->data(), 0, frame->allocated_size()); - } else { - // Dump texture contents to frame - TextureToFrame(texture, frame, video_download_matrix_); - } - - ticket->Finish(QVariant::fromValue(frame)); - - emit FinishedJob(); -} - -void RenderWorker::RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange &range) -{ - ticket_ = ticket; - - NodeValueTable table = ProcessInput(viewer->samples_input(), range); - - QVariant samples = table.Get(NodeParam::kSamples); - - ticket->Finish(samples); - - emit FinishedJob(); -} - -void RenderWorker::ClearDecoders() -{ - decoder_cache_.clear(); -} - -NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) -{ - if (track->track_type() == Timeline::kTrackTypeAudio) { - - QList active_blocks = track->BlocksAtTimeRange(range); - - // All these blocks will need to output to a buffer so we create one here - SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_, - audio_params_.time_to_samples(range.length())); - block_range_buffer->fill(0); - - NodeValueTable merged_table; - - // Loop through active blocks retrieving their audio - foreach (Block* b, active_blocks) { - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); - - int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in()); - int max_dest_sz = audio_params_.time_to_samples(range_for_block.length()); - - // Destination buffer - NodeValueTable table = GenerateTable(b, range_for_block); - SampleBufferPtr samples_from_this_block = table.Take(NodeParam::kSamples).value(); - - if (!samples_from_this_block) { - // If we retrieved no samples from this block, do nothing - continue; - } - - // FIXME: Doesn't handle reversing - if (b->speed_input()->is_keyframing() || b->speed_input()->is_connected()) { - // FIXME: We'll need to calculate the speed hoo boy - } else { - double speed_value = b->speed_input()->get_standard_value().toDouble(); - - if (qIsNull(speed_value)) { - // Just silence, don't think there's any other practical application of 0 speed audio - samples_from_this_block->fill(0); - } else if (!qFuzzyCompare(speed_value, 1.0)) { - // Multiply time - samples_from_this_block->speed(speed_value); - } - } - - int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count()); - - // Copy samples into destination buffer - block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length); - - NodeValueTable::Merge({merged_table, table}); - } - - if (generate_audio_previews_) { - // Find original track object - TrackOutput* original_track = nullptr; - - // Have to do a manual loop since our track is const and QHash won't take it - QHash::const_iterator i; - for (i=copy_map_->constBegin(); i!=copy_map_->constEnd(); i++) { - if (i.value() == track) { - original_track = static_cast(i.key()); - break; - } - } - - if (original_track) { - // Generate a visual waveform and send it back to the main thread - AudioVisualWaveform visual_waveform; - visual_waveform.set_channel_count(audio_params_.channel_count()); - visual_waveform.OverwriteSamples(block_range_buffer, audio_params_.sample_rate()); - - emit WaveformGenerated(ticket_, original_track, visual_waveform, range); - } - } - - merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer), track); - - return merged_table; - - } else { - return NodeTraverser::GenerateBlockTable(track, range); - } -} - -QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob& job) -{ - if (!job.samples() || !job.samples()->is_allocated()) { - return QVariant(); - } - - SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count()); - NodeValueDatabase value_db; - - for (int i=0;isample_count();i++) { - // Calculate the exact rational time at this sample - double sample_to_second = static_cast(i) / static_cast(audio_params_.sample_rate()); - - rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second); - - // Update all non-sample and non-footage inputs - NodeValueMap::const_iterator j; - for (j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) { - NodeValueTable value; - NodeInput* corresponding_input = node->GetInputWithID(j.key()); - - if (corresponding_input) { - value = ProcessInput(corresponding_input, TimeRange(this_sample_time, this_sample_time)); - } else { - value.Push(j.value()); - } - - value_db.Insert(j.key(), value); - } - - AddGlobalsToDatabase(value_db, TimeRange(this_sample_time, this_sample_time)); - - node->ProcessSamples(value_db, - job.samples(), - output_buffer, - i); - } - - return QVariant::fromValue(output_buffer); -} - -QVariant RenderWorker::ProcessFrameGeneration(const Node* node, const GenerateJob &job) -{ - FramePtr frame = Frame::Create(); - - PixelFormat::Format output_fmt; - if (job.GetAlphaChannelRequired()) { - output_fmt = PixelFormat::GetFormatWithAlphaChannel(video_params_.format()); - } else { - output_fmt = PixelFormat::GetFormatWithoutAlphaChannel(video_params_.format()); - } - - frame->set_video_params(VideoParams(video_params_.width(), - video_params_.height(), - video_params_.time_base(), - output_fmt, - video_params_.pixel_aspect_ratio(), - video_params_.interlacing(), - video_params_.divider())); - frame->allocate(); - - node->GenerateFrame(frame, job); - - return CachedFrameToTexture(frame); -} - -QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time) -{ - if (render_mode_ == RenderMode::kOffline - && !cache_path_.isEmpty() - && node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { - QByteArray hash = HashNode(node, video_params(), time); - - FramePtr f = FrameHashCache::LoadCacheFrame(cache_path_, hash); - - if (f) { - // The cached frame won't load with the correct divider by default, so we enforce it here - f->set_video_params(VideoParams(f->width() * video_params_.divider(), - f->height() * video_params_.divider(), - f->video_params().time_base(), - f->video_params().format(), - f->video_params().pixel_aspect_ratio(), - f->video_params().interlacing(), - video_params_.divider())); - - return CachedFrameToTexture(f); - } - } - - return QVariant(); -} - -DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) -{ - // Access a map of Node inputs and decoder instances and retrieve a frame! - QMutexLocker locker(&decoder_lock_); - - DecoderPtr decoder = decoder_cache_.value(stream.get()); - - if (!decoder && stream) { - // Create a new Decoder here - decoder = Decoder::CreateFromID(stream->footage()->decoder()); - decoder->set_stream(stream); - - if (decoder->Open()) { - decoder_cache_.insert(stream.get(), decoder); - } else { - decoder = nullptr; - qWarning() << "Failed to open decoder for" << stream->footage()->filename() - << "::" << stream->index(); - } - } - - decoder_age_.insert(stream.get(), QDateTime::currentMSecsSinceEpoch()); - - return decoder; -} - -QVariant RenderWorker::ProcessVideoFootage(StreamPtr stream, const rational &input_time) -{ - VideoStreamPtr video_stream = std::static_pointer_cast(stream); - rational time_match = (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time; - QString colorspace_match = video_stream->get_colorspace_match_string(); - - QVariant value; - bool found_cache = false; - - if (still_image_cache_.contains(stream.get())) { - const CachedStill& cs = still_image_cache_[stream.get()]; - - if (cs.colorspace == colorspace_match - && cs.alpha_is_associated == video_stream->premultiplied_alpha() - && cs.divider == video_params_.divider() - && cs.time == time_match) { - value = cs.texture; - found_cache = true; - } else { - still_image_cache_.remove(stream.get()); - } - } - - if (!found_cache) { - - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - FramePtr frame = decoder->RetrieveVideo(input_time, - video_params().divider()); - - if (frame) { - // Return a texture from the derived class - value = FootageFrameToTexture(stream, frame); - - if (!value.isNull()) { - // Put this into the image cache instead - still_image_cache_.insert(stream.get(), {value, - colorspace_match, - video_stream->premultiplied_alpha(), - video_params_.divider(), - time_match}); - } - } - } - - } - - return value; -} - -QVariant RenderWorker::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) -{ - QVariant value; - - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - // See if we have a conformed version of this audio - if (!decoder->HasConformedVersion(audio_params())) { - - // If not, the audio needs to be conformed - // For online rendering/export, it's a waste of time to render the audio until we have - // all we need, so we try to handle the conform ourselves - AudioStreamPtr as = std::static_pointer_cast(stream); - - // Check if any other threads are conforming this audio - if (as->try_start_conforming(audio_params())) { - - // If not, conform it ourselves - decoder->ConformAudio(&IsCancelled(), audio_params()); - - } else { - - // If another thread is conforming already, hackily try to wait until it's done. - do { - QThread::msleep(1000); - } while (!as->has_conformed_version(audio_params()) && !IsCancelled()); - - } - - } - - if (decoder->HasConformedVersion(audio_params())) { - SampleBufferPtr frame = decoder->RetrieveAudio(input_time.in(), input_time.length(), - audio_params()); - - if (frame) { - value = QVariant::fromValue(frame); - } - } - } - - return value; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h deleted file mode 100644 index 84f9fbe94..000000000 --- a/app/render/backend/renderworker.h +++ /dev/null @@ -1,208 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef RENDERWORKER_H -#define RENDERWORKER_H - -#include - -#include "decodercache.h" -#include "node/traverser.h" -#include "node/output/viewer/viewer.h" -#include "renderticket.h" - -OLIVE_NAMESPACE_ENTER - -class RenderBackend; - -class RenderWorker : public QObject, public NodeTraverser -{ - Q_OBJECT -public: - RenderWorker(RenderBackend* parent); - - virtual ~RenderWorker() override; - - bool IsAvailable() const - { - return available_; - } - - void SetAvailable(bool a) - { - available_ = a; - } - - void SetVideoParams(const VideoParams& params) - { - video_params_ = params; - } - - void SetAudioParams(const AudioParams& params) - { - audio_params_ = params; - } - - void SetForceDownloadResolution(bool e) - { - video_force_download_resolution_ = e; - } - - void SetVideoDownloadMatrix(const QMatrix4x4& mat) - { - video_download_matrix_ = mat; - } - - void SetCopyMap(QHash* copy_map) - { - copy_map_ = copy_map; - } - - void SetRenderMode(const RenderMode::Mode& mode) - { - render_mode_ = mode; - } - - void SetPreviewGenerationEnabled(bool e) - { - generate_audio_previews_ = e; - } - - void SetCachePath(const QString& s) - { - cache_path_ = s; - } - - void Hash(RenderTicketPtr ticket, ViewerOutput* viewer, const QVector& times); - - /** - * @brief Render the frame at this time - * - * Produces a fully rendered frame from the connected viewer at this time. - * - * @return - * - * A frame corresponding to the set video parameters. If no nodes are active at the time, this - * function will still return a blank frame with the same parameters. If no viewer node is set, - * nullptr is returned. - */ - void RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time); - - void RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange& range); - - void ClearDecoders(); - -protected: - virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const = 0; - - virtual QVariant FootageFrameToTexture(StreamPtr stream, FramePtr frame) const = 0; - - virtual QVariant CachedFrameToTexture(FramePtr frame) const = 0; - - virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) override; - - virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time) override; - - virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) override; - - virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override; - - virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; - - virtual QVariant GetCachedFrame(const Node *node, const rational &time) override; - - virtual bool TextureHasAlpha(const QVariant& v) const = 0; - - const VideoParams& video_params() const - { - return video_params_; - } - - const AudioParams& audio_params() const - { - return audio_params_; - } - - const RenderMode::Mode& render_mode() const - { - return render_mode_; - } - -signals: - void AudioConformUnavailable(StreamPtr stream, TimeRange range, - rational stream_time, AudioParams params); - - void FinishedJob(); - - void WaveformGenerated(OLIVE_NAMESPACE::RenderTicketPtr ticket, OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange range); - -private: - DecoderPtr ResolveDecoderFromInput(StreamPtr stream); - - static QByteArray HashNode(const Node* n, const VideoParams& params, const rational& time); - - RenderBackend* parent_; - - RenderTicketPtr ticket_; - - VideoParams video_params_; - - AudioParams audio_params_; - - struct CachedStill { - QVariant texture; - QString colorspace; - bool alpha_is_associated; - int divider; - rational time; - }; - - QHash still_image_cache_; - - bool video_force_download_resolution_; - QMatrix4x4 video_download_matrix_; - - QMutex decoder_lock_; - DecoderCache decoder_cache_; - QHash decoder_age_; - - TimeRange audio_render_time_; - bool available_; - - bool generate_audio_previews_; - - QHash* copy_map_; - - RenderMode::Mode render_mode_; - - QTimer* cleanup_timer_; - - QString cache_path_; - - static const int kMaxDecoderLife; - -private slots: - void ClearOldDecoders(); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // RENDERWORKER_H diff --git a/app/render/color.cpp b/app/render/color.cpp index e8e4cbfca..55181d83f 100644 --- a/app/render/color.cpp +++ b/app/render/color.cpp @@ -20,54 +20,57 @@ #include "color.h" +#include + #include "common/clamp.h" +#include "common/oiioutils.h" OLIVE_NAMESPACE_ENTER -Color Color::fromHsv(const float &h, const float &s, const float &v) +Color Color::fromHsv(const double &h, const double &s, const double &v) { - float C = s * v; - float X = C * (1.0f - abs(fmod(h / 60.0f, 2.0f) - 1.0f)); - float m = v - C; - float Rs, Gs, Bs; + double C = s * v; + double X = C * (1.0 - abs(fmod(h / 60.0, 2.0) - 1.0)); + double m = v - C; + double Rs, Gs, Bs; - if(h >= 0.0f && h < 60.0f) { + if(h >= 0.0 && h < 60.0) { Rs = C; Gs = X; - Bs = 0.0f; + Bs = 0.0; } - else if(h >= 60.0f && h < 120.0f) { + else if(h >= 60.0 && h < 120.0) { Rs = X; Gs = C; - Bs = 0.0f; + Bs = 0.0; } - else if(h >= 120.0f && h < 180.0f) { - Rs = 0.0f; + else if(h >= 120.0 && h < 180.0) { + Rs = 0.0; Gs = C; Bs = X; } - else if(h >= 180.0f && h < 240.0f) { - Rs = 0.0f; + else if(h >= 180.0 && h < 240.0) { + Rs = 0.0; Gs = X; Bs = C; } - else if(h >= 240.0f && h < 300.0f) { + else if(h >= 240.0 && h < 300.0) { Rs = X; - Gs = 0.0f; + Gs = 0.0; Bs = C; } else { Rs = C; - Gs = 0.0f; + Gs = 0.0; Bs = X; } return Color(Rs + m, Gs + m, Bs + m); } -Color::Color(const char *data, const PixelFormat::Format &format) +Color::Color(const char *data, const VideoParams::Format &format, int ch_layout) { - *this = fromData(data, format); + *this = fromData(data, format, ch_layout); } Color::Color(const QColor &c) @@ -78,11 +81,11 @@ Color::Color(const QColor &c) set_alpha(c.alphaF()); } -void Color::toHsv(float *hue, float *sat, float *val) const +void Color::toHsv(double *hue, double *sat, double *val) const { - float fCMax = qMax(qMax(red(), green()), blue()); - float fCMin = qMin(qMin(red(), green()), blue()); - float fDelta = fCMax - fCMin; + double fCMax = qMax(qMax(red(), green()), blue()); + double fCMin = qMin(qMin(red(), green()), blue()); + double fDelta = fCMax - fCMin; if(fDelta > 0) { if(fCMax == red()) { @@ -111,31 +114,31 @@ void Color::toHsv(float *hue, float *sat, float *val) const } } -float Color::hsv_hue() const +double Color::hsv_hue() const { - float h, s, v; + double h, s, v; toHsv(&h, &s, &v); return h; } -float Color::hsv_saturation() const +double Color::hsv_saturation() const { - float h, s, v; + double h, s, v; toHsv(&h, &s, &v); return s; } -float Color::value() const +double Color::value() const { - float h, s, v; + double h, s, v; toHsv(&h, &s, &v); return v; } -void Color::toHsl(float *hue, float *sat, float *lightness) const +void Color::toHsl(double *hue, double *sat, double *lightness) const { - float fCMin = qMin(red(), qMin(green(), blue())); - float fCMax = qMax(red(), qMax(green(), blue())); + double fCMin = qMin(red(), qMin(green(), blue())); + double fCMax = qMax(red(), qMax(green(), blue())); *lightness = 0.5 * (fCMin + fCMax); @@ -173,49 +176,45 @@ void Color::toHsl(float *hue, float *sat, float *lightness) const } } -float Color::hsl_hue() const +double Color::hsl_hue() const { - float h, s, l; + double h, s, l; toHsl(&h, &s, &l); return h; } -float Color::hsl_saturation() const +double Color::hsl_saturation() const { - float h, s, l; + double h, s, l; toHsl(&h, &s, &l); return s; } -float Color::lightness() const +double Color::lightness() const { - float h, s, l; + double h, s, l; toHsl(&h, &s, &l); return l; } -void Color::toData(char *data, const PixelFormat::Format &format) const +void Color::toData(char *data, const VideoParams::Format &format, int ch_layout) const { - OIIO::convert_types(PixelFormat::GetOIIOTypeDesc(PixelFormat::PIX_FMT_RGB32F), - data_, - PixelFormat::GetOIIOTypeDesc(format), - data, - PixelFormat::FormatHasAlphaChannel(format) ? kRGBAChannels : kRGBChannels); + OIIO::convert_pixel_values(OIIO::TypeDesc::DOUBLE, + data_, + OIIOUtils::GetOIIOBaseTypeFromFormat(format), + data, + ch_layout); } -Color Color::fromData(const char *data, const PixelFormat::Format &format) +Color Color::fromData(const char *data, const VideoParams::Format &format, int ch_layout) { Color c; - OIIO::convert_types(PixelFormat::GetOIIOTypeDesc(format), - data, - PixelFormat::GetOIIOTypeDesc(PixelFormat::PIX_FMT_RGB32F), - c.data_, - PixelFormat::FormatHasAlphaChannel(format) ? kRGBAChannels : kRGBChannels); - - if (!PixelFormat::FormatHasAlphaChannel(format)) { - c.set_alpha(1.0f); - } + OIIO::convert_pixel_values(OIIOUtils::GetOIIOBaseTypeFromFormat(format), + data, + OIIO::TypeDesc::DOUBLE, + c.data_, + ch_layout); return c; } @@ -225,22 +224,22 @@ QColor Color::toQColor() const QColor c; // QColor only supports values from 0.0 to 1.0 and are only used for UI representations - c.setRedF(clamp(red(), 0.0f, 1.0f)); - c.setGreenF(clamp(green(), 0.0f, 1.0f)); - c.setBlueF(clamp(blue(), 0.0f, 1.0f)); - c.setAlphaF(clamp(alpha(), 0.0f, 1.0f)); + c.setRedF(clamp(red(), 0.0, 1.0)); + c.setGreenF(clamp(green(), 0.0, 1.0)); + c.setBlueF(clamp(blue(), 0.0, 1.0)); + c.setAlphaF(clamp(alpha(), 0.0, 1.0)); return c; } -float Color::GetRoughLuminance() const +double Color::GetRoughLuminance() const { - return (2*red()+blue()+3*green())/6.0f; + return (2*red()+blue()+3*green())/6.0; } const Color &Color::operator+=(const Color &rhs) { - for (int i=0;i #include "common/define.h" -#include "render/pixelformat.h" +#include "render/videoparams.h" OLIVE_NAMESPACE_ENTER /** - * @brief High precision 32-bit float based RGBA color value + * @brief High precision 64-bit float based RGBA color value */ class Color { public: Color() { - for (int i=0;igetDefaultLumaCoefs(rgb); } @@ -322,69 +307,6 @@ Color ColorManager::GetDefaultLumaCoefs() const return c; } -ColorManager::OCIOMethod ColorManager::GetOCIOMethodForMode(RenderMode::Mode mode) -{ - return static_cast(Core::GetPreferenceForRenderMode(mode, QStringLiteral("OCIOMethod")).toInt()); -} - -void ColorManager::SetOCIOMethodForMode(RenderMode::Mode mode, ColorManager::OCIOMethod method) -{ - Core::SetPreferenceForRenderMode(mode, QStringLiteral("OCIOMethod"), method); -} - -void ColorManager::AssociateAlphaPixFmtFilter(ColorManager::AlphaAction action, FramePtr f) -{ - if (!PixelFormat::FormatHasAlphaChannel(f->format())) { - // This frame has no alpha channel, do nothing - return; - } - - int pixel_count = f->width() * f->height() * kRGBAChannels; - - switch (static_cast(f->format())) { - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - qWarning() << "Alpha association functions received an invalid pixel format"; - break; - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - qWarning() << "Alpha association functions only works on float-based pixel formats at this time"; - break; - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - { - AssociateAlphaInternal(action, reinterpret_cast(f->data()), pixel_count); - break; - } - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - { - AssociateAlphaInternal(action, reinterpret_cast(f->data()), pixel_count); - break; - } - } -} - -template -void ColorManager::AssociateAlphaInternal(ColorManager::AlphaAction action, T *data, int pix_count) -{ - for (int i=0;i 0) { - for (int j=0;j - static void AssociateAlphaInternal(AlphaAction action, T* data, int pix_count); - QString config_filename_; QString default_input_color_space_; diff --git a/app/render/colorprocessor.cpp b/app/render/colorprocessor.cpp index 4b98a5133..cc249c977 100644 --- a/app/render/colorprocessor.cpp +++ b/app/render/colorprocessor.cpp @@ -21,6 +21,7 @@ #include "colorprocessor.h" #include "common/define.h" +#include "common/ocioutils.h" #include "colormanager.h" OLIVE_NAMESPACE_ENTER @@ -33,19 +34,35 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const const QString& view = (transform.view().isEmpty()) ? config->GetDefaultView(output) : transform.view(); - OCIO::DisplayTransformRcPtr display_transform = OCIO::DisplayTransform::Create(); + auto display_transform = OCIO::DisplayViewTransform::Create(); - display_transform->setInputColorSpaceName(input.toUtf8()); + display_transform->setSrc(input.toUtf8()); display_transform->setDisplay(output.toUtf8()); display_transform->setView(view.toUtf8()); - if (!transform.look().isEmpty()) { - display_transform->setLooksOverride(transform.look().toUtf8()); - display_transform->setLooksOverrideEnabled(true); - } - OCIO_SET_C_LOCALE_FOR_SCOPE; - processor_ = config->GetConfig()->getProcessor(display_transform); + + if (transform.look().isEmpty()) { + processor_ = config->GetConfig()->getProcessor(display_transform); + } else { + auto group = OCIO::GroupTransform::Create(); + + const char* out_cs = OCIO::LookTransform::GetLooksResultColorSpace(config->GetConfig(), + config->GetConfig()->getCurrentContext(), + transform.look().toUtf8()); + + auto lt = OCIO::LookTransform::Create(); + lt->setSrc(input.toUtf8()); + lt->setDst(out_cs); + lt->setLooks(transform.look().toUtf8()); + lt->setSkipColorSpaceConversion(false); + group->appendTransform(lt); + + display_transform->setSrc(out_cs); + group->appendTransform(display_transform); + + processor_ = config->GetConfig()->getProcessor(group); + } } else { @@ -54,25 +71,49 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const output.toUtf8()); } + + cpu_processor_ = processor_->getDefaultCPUProcessor(); + id_ = GenerateID(config, input, transform); } void ColorProcessor::ConvertFrame(Frame *f) { - OCIO::PackedImageDesc img(reinterpret_cast(f->data()), + OCIO::BitDepth ocio_bit_depth = OCIOUtils::GetOCIOBitDepthFromPixelFormat(f->format()); + + if (ocio_bit_depth == OCIO::BIT_DEPTH_UNKNOWN) { + qCritical() << "Tried to color convert frame with no format"; + return; + } + + OCIO::PackedImageDesc img(f->data(), f->width(), f->height(), - PixelFormat::ChannelCount(f->format()), + VideoParams::kRGBAChannelCount, + ocio_bit_depth, OCIO::AutoStride, OCIO::AutoStride, f->linesize_bytes()); - processor_->apply(img); + cpu_processor_->apply(img); } -Color ColorProcessor::ConvertColor(Color in) +Color ColorProcessor::ConvertColor(const Color& in) { - processor_->applyRGBA(in.data()); - return in; + // I've been bamboozled + float c[4] = {float(in.red()), float(in.green()), float(in.blue()), float(in.alpha())}; + + cpu_processor_->applyRGBA(c); + + return Color(c[0], c[1], c[2], c[3]); +} + +QString ColorProcessor::GenerateID(ColorManager *config, const QString &input, const ColorTransform &transform) +{ + return QStringLiteral("%1:%2:%3:%4:%5").arg(config->GetConfigFilename(), + input, + transform.display(), + transform.view(), + transform.look()); } ColorProcessorPtr ColorProcessor::Create(ColorManager *config, const QString& input, const ColorTransform &transform) diff --git a/app/render/colorprocessor.h b/app/render/colorprocessor.h index 6912cd66d..d5d4abc11 100644 --- a/app/render/colorprocessor.h +++ b/app/render/colorprocessor.h @@ -22,6 +22,7 @@ #define COLORPROCESSOR_H #include "codec/frame.h" +#include "common/ocioutils.h" #include "render/color.h" #include "render/colortransform.h" @@ -51,15 +52,28 @@ public: void ConvertFrame(FramePtr f); void ConvertFrame(Frame* f); - Color ConvertColor(Color in); + Color ConvertColor(const Color &in); + + const QString& id() const + { + return id_; + } + + static QString GenerateID(ColorManager* config, const QString& input, const ColorTransform& dest_space); private: OCIO::ConstProcessorRcPtr processor_; + OCIO::ConstCPUProcessorRcPtr cpu_processor_; + + QString id_; + }; -using ColorProcessorChain = QList; +using ColorProcessorChain = QVector; OLIVE_NAMESPACE_EXIT +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ColorProcessorPtr) + #endif // COLORPROCESSOR_H diff --git a/app/render/backend/colorprocessorcache.h b/app/render/colorprocessorcache.h similarity index 100% rename from app/render/backend/colorprocessorcache.h rename to app/render/colorprocessorcache.h diff --git a/app/render/colortransform.h b/app/render/colortransform.h index cd183dda3..e0aa67f83 100644 --- a/app/render/colortransform.h +++ b/app/render/colortransform.h @@ -21,12 +21,10 @@ #ifndef COLORTRANSFORM_H #define COLORTRANSFORM_H -#include -namespace OCIO = OCIO_NAMESPACE::v1; - #include #include "common/define.h" +#include "common/ocioutils.h" OLIVE_NAMESPACE_ENTER diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 387baa12e..b902f80eb 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -50,22 +50,16 @@ QByteArray FrameHashCache::GetHash(const rational &time) void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time, bool frame_exists) { - bool is_current = false; - for (int i=jobs_.size()-1; i>=0; i--) { const JobIdentifier& job = jobs_.at(i); if (job.range.Contains(time) - && job_time >= job.job_time) { - is_current = true; - break; + && job_time < job.job_time) { + // Hash here has changed since this frame started rendering, discard it + return; } } - if (!is_current) { - return; - } - time_hash_map_.insert(time, hash); TimeRange validated_range; @@ -82,15 +76,13 @@ void FrameHashCache::SetTimebase(const rational &tb) void FrameHashCache::ValidateFramesWithHash(const QByteArray &hash) { - QMap::const_iterator iterator; - const TimeRangeList& invalidated_ranges = GetInvalidatedRanges(); - for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { + for (auto iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { if (iterator.value() == hash) { TimeRange frame_range(iterator.key(), iterator.key() + timebase_); - if (invalidated_ranges.ContainsTimeRange(frame_range)) { + if (invalidated_ranges.contains(frame_range)) { Validate(frame_range); } } @@ -101,9 +93,7 @@ QList FrameHashCache::GetFramesWithHash(const QByteArray &hash) { QList times; - QMap::const_iterator iterator; - - for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { + for (auto iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { if (iterator.value() == hash) { times.append(iterator.key()); } @@ -116,7 +106,7 @@ QList FrameHashCache::TakeFramesWithHash(const QByteArray &hash) { QList times; - QMap::iterator iterator = time_hash_map_.begin(); + auto iterator = time_hash_map_.begin(); while (iterator != time_hash_map_.end()) { if (iterator.value() == hash) { @@ -167,7 +157,7 @@ QVector FrameHashCache::GetFrameListFromTimeRange(TimeRangeList range_ } times.append(snapped); - range_list.RemoveTimeRange(TimeRange(snapped, next)); + range_list.remove(TimeRange(snapped, next)); } return times; @@ -243,32 +233,27 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) int height = dw.max.y - dw.min.y + 1; bool has_alpha = file.header().channels().findChannel("A"); - PixelFormat::Format image_format; + VideoParams::Format image_format; if (pix_type == Imf::HALF) { - if (has_alpha) { - image_format = PixelFormat::PIX_FMT_RGBA16F; - } else { - image_format = PixelFormat::PIX_FMT_RGB16F; - } + image_format = VideoParams::kFormatFloat16; } else { - if (has_alpha) { - image_format = PixelFormat::PIX_FMT_RGBA32F; - } else { - image_format = PixelFormat::PIX_FMT_RGB32F; - } + image_format = VideoParams::kFormatFloat32; } + int channel_count = has_alpha ? VideoParams::kRGBAChannelCount : VideoParams::kRGBChannelCount; + frame = Frame::Create(); frame->set_video_params(VideoParams(width, height, image_format, + channel_count, rational::fromDouble(file.header().pixelAspectRatio()))); frame->allocate(); - int bpc = PixelFormat::BytesPerChannel(image_format); + int bpc = VideoParams::GetBytesPerChannel(image_format); - size_t xs = PixelFormat::ChannelCount(image_format) * bpc; + size_t xs = channel_count * bpc; size_t ys = frame->linesize_bytes(); Imf::FrameBuffer framebuffer; @@ -289,7 +274,7 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen) { if (newlen < old) { - QMap::iterator i = time_hash_map_.begin(); + auto i = time_hash_map_.begin(); while (i != time_hash_map_.end()) { if (i.key() >= newlen) { @@ -308,7 +293,7 @@ struct HashTimePair { void FrameHashCache::ShiftEvent(const rational &from, const rational &to) { - QMap::iterator i = time_hash_map_.begin(); + auto i = time_hash_map_.begin(); // POSITIVE if moving forward -> // NEGATIVE if moving backward <- @@ -359,10 +344,9 @@ void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash) } TimeRangeList ranges_to_invalidate; - QMap::const_iterator i; - for (i=time_hash_map_.constBegin(); i!=time_hash_map_.constEnd(); i++) { + for (auto i=time_hash_map_.constBegin(); i!=time_hash_map_.constEnd(); i++) { if (i.value() == hash) { - ranges_to_invalidate.InsertTimeRange(TimeRange(i.key(), i.key() + timebase_)); + ranges_to_invalidate.insert(TimeRange(i.key(), i.key() + timebase_)); } } @@ -406,13 +390,15 @@ QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArra bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) const { - Q_ASSERT(PixelFormat::FormatIsFloat(vparam.format())); + if (!VideoParams::FormatIsFloat(vparam.format())) { + qCritical() << "Tried to cache frame with non-float pixel format"; + return false; + } // Floating point types are stored in EXR Imf::PixelType pix_type; - if (vparam.format() == PixelFormat::PIX_FMT_RGB16F - || vparam.format() == PixelFormat::PIX_FMT_RGBA16F) { + if (vparam.format() == VideoParams::kFormatFloat16) { pix_type = Imf::HALF; } else { pix_type = Imf::FLOAT; @@ -423,7 +409,7 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V header.channels().insert("R", Imf::Channel(pix_type)); header.channels().insert("G", Imf::Channel(pix_type)); header.channels().insert("B", Imf::Channel(pix_type)); - if (PixelFormat::FormatHasAlphaChannel(vparam.format())) { + if (vparam.channel_count() == VideoParams::kRGBAChannelCount) { header.channels().insert("A", Imf::Channel(pix_type)); } @@ -433,16 +419,16 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V Imf::OutputFile out(filename.toUtf8(), header, 0); - int bpc = PixelFormat::BytesPerChannel(vparam.format()); + int bpc = VideoParams::GetBytesPerChannel(vparam.format()); - size_t xs = PixelFormat::ChannelCount(vparam.format()) * bpc; + size_t xs = vparam.channel_count() * bpc; size_t ys = linesize_bytes; Imf::FrameBuffer framebuffer; framebuffer.insert("R", Imf::Slice(pix_type, data, xs, ys)); framebuffer.insert("G", Imf::Slice(pix_type, data + bpc, xs, ys)); framebuffer.insert("B", Imf::Slice(pix_type, data + 2*bpc, xs, ys)); - if (PixelFormat::FormatHasAlphaChannel(vparam.format())) { + if (vparam.channel_count() == VideoParams::kRGBAChannelCount) { framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys)); } out.setFrameBuffer(framebuffer); diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 1d8360043..17009dabf 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -25,7 +25,7 @@ #include "common/rational.h" #include "common/timerange.h" -#include "render/pixelformat.h" +#include "codec/frame.h" #include "render/playbackcache.h" #include "render/videoparams.h" diff --git a/app/render/backend/CMakeLists.txt b/app/render/job/CMakeLists.txt similarity index 66% rename from app/render/backend/CMakeLists.txt rename to app/render/job/CMakeLists.txt index 79650bd7b..d71defccf 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/job/CMakeLists.txt @@ -14,19 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(opengl) - set(OLIVE_SOURCES ${OLIVE_SOURCES} - render/backend/colorprocessorcache.h - render/backend/decodercache.h - render/backend/renderbackend.h - render/backend/renderbackend.cpp - render/backend/renderticket.h - render/backend/renderticket.cpp - render/backend/renderticketwatcher.h - render/backend/renderticketwatcher.cpp - render/backend/renderworker.h - render/backend/renderworker.cpp + render/job/acceleratedjob.h + render/job/generatejob.h + render/job/samplejob.h + render/job/shaderjob.h PARENT_SCOPE ) diff --git a/app/render/job/acceleratedjob.h b/app/render/job/acceleratedjob.h new file mode 100644 index 000000000..71fb1a838 --- /dev/null +++ b/app/render/job/acceleratedjob.h @@ -0,0 +1,101 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef ACCELERATEDJOB_H +#define ACCELERATEDJOB_H + +#include "node/input.h" +#include "node/inputarray.h" +#include "render/shadervalue.h" +#include "node/value.h" + +OLIVE_NAMESPACE_ENTER + +class AcceleratedJob { +public: + AcceleratedJob() = default; + + ShaderValue GetValue(NodeInput* input) const + { + return value_map_.value(input->id()); + } + + ShaderValue GetValue(const QString& input) const + { + return value_map_.value(input); + } + + void InsertValue(NodeInput* input, NodeValueDatabase& value) + { + ShaderValue shader_val; + + shader_val.type = input->data_type(); + shader_val.array = input->IsArray(); + + if (input->IsArray()) { + NodeInputArray* array = static_cast(input); + QVector values(array->GetSize()); + + for (int j=0;jGetSize();j++) { + NodeInput* subparam = array->At(j); + + values[j] = value[subparam].Take(subparam->data_type()); + } + + shader_val.data = QVariant::fromValue(values); + } else { + NodeValue node_val = value[input].TakeWithMeta(input->data_type()); + shader_val.data = node_val.data(); + shader_val.tag = node_val.tag(); + } + + InsertValue(input->id(), shader_val); + } + + void InsertValue(const QString& input, const ShaderValue& value) + { + value_map_.insert(input, value); + } + + void InsertValue(NodeInput* input, const ShaderValue& value) + { + value_map_.insert(input->id(), value); + } + + void InsertValue(NodeInput* input, const NodeValue& value) + { + ShaderValue s(value.data(), value.type()); + s.tag = value.tag(); + value_map_.insert(input->id(), s); + } + + const NodeValueMap &GetValues() const + { + return value_map_; + } + +private: + NodeValueMap value_map_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // ACCELERATEDJOB_H diff --git a/app/render/job/generatejob.h b/app/render/job/generatejob.h new file mode 100644 index 000000000..e2ab45c72 --- /dev/null +++ b/app/render/job/generatejob.h @@ -0,0 +1,54 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef GENERATEJOB_H +#define GENERATEJOB_H + +#include "acceleratedjob.h" + +OLIVE_NAMESPACE_ENTER + +class GenerateJob : public AcceleratedJob { +public: + GenerateJob() + { + alpha_channel_required_ = false; + } + + bool GetAlphaChannelRequired() const + { + return alpha_channel_required_; + } + + void SetAlphaChannelRequired(bool e) + { + alpha_channel_required_ = e; + } + +private: + bool alpha_channel_required_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::GenerateJob) + +#endif // GENERATEJOB_H diff --git a/app/render/backend/opengl/openglframebuffer.h b/app/render/job/samplejob.h similarity index 54% rename from app/render/backend/opengl/openglframebuffer.h rename to app/render/job/samplejob.h index e7d8de9b4..f46e6a3eb 100644 --- a/app/render/backend/opengl/openglframebuffer.h +++ b/app/render/job/samplejob.h @@ -18,48 +18,48 @@ ***/ -#ifndef OPENGLFRAMEBUFFER_H -#define OPENGLFRAMEBUFFER_H +#ifndef SAMPLEJOB_H +#define SAMPLEJOB_H -#include - -#include "opengltexture.h" +#include "acceleratedjob.h" +#include "codec/samplebuffer.h" OLIVE_NAMESPACE_ENTER -class OpenGLFramebuffer : public QObject -{ - Q_OBJECT +class SampleJob : public AcceleratedJob { public: - OpenGLFramebuffer(); - virtual ~OpenGLFramebuffer() override; + SampleJob() + { + samples_ = nullptr; + } - void Create(QOpenGLContext *ctx); + SampleJob(const NodeValue& value) + { + samples_ = value.data().value(); + } - bool IsCreated() const; + SampleJob(NodeInput* from, NodeValueDatabase& db) + { + samples_ = db[from].Take(NodeParam::kSamples).value(); + } - void Bind(); + SampleBufferPtr samples() const + { + return samples_; + } - void Release(); - - void Attach(OpenGLTexture* texture, bool clear = false); - void Attach(OpenGLTexturePtr texture, bool clear = false); - - void Detach(); - - const GLuint& buffer() const; - -public slots: - void Destroy(); + bool HasSamples() const + { + return samples_ && samples_->is_allocated(); + } private: - QOpenGLContext* context_; + SampleBufferPtr samples_; - GLuint buffer_; - - OpenGLTexture* texture_; }; OLIVE_NAMESPACE_EXIT -#endif // OPENGLFRAMEBUFFER_H +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::SampleJob) + +#endif // SAMPLEJOB_H diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h new file mode 100644 index 000000000..72b8c9270 --- /dev/null +++ b/app/render/job/shaderjob.h @@ -0,0 +1,100 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef SHADERJOB_H +#define SHADERJOB_H + +#include + +#include "generatejob.h" +#include "render/texture.h" + +OLIVE_NAMESPACE_ENTER + +class ShaderJob : public GenerateJob { +public: + ShaderJob() + { + iterations_ = 1; + iterative_input_ = nullptr; + } + + const QString& GetShaderID() const + { + return shader_id_; + } + + void SetShaderID(const QString& id) + { + shader_id_ = id; + } + + void SetIterations(int iterations, NodeInput* iterative_input) + { + SetIterations(iterations, iterative_input->id()); + } + + void SetIterations(int iterations, const QString& iterative_input) + { + iterations_ = iterations; + iterative_input_ = iterative_input; + } + + int GetIterationCount() const + { + return iterations_; + } + + const QString& GetIterativeInput() const + { + return iterative_input_; + } + + Texture::Interpolation GetInterpolation(const QString& id) + { + return interpolation_.value(id, Texture::kDefaultInterpolation); + } + + void SetInterpolation(NodeInput* input, Texture::Interpolation interp) + { + interpolation_.insert(input->id(), interp); + } + + void SetInterpolation(const QString& id, Texture::Interpolation interp) + { + interpolation_.insert(id, interp); + } + +private: + QString shader_id_; + + int iterations_; + + QString iterative_input_; + + QHash interpolation_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ShaderJob) + +#endif // SHADERJOB_H diff --git a/app/render/managedcolor.cpp b/app/render/managedcolor.cpp index f4d86e197..042478cf2 100644 --- a/app/render/managedcolor.cpp +++ b/app/render/managedcolor.cpp @@ -26,13 +26,13 @@ ManagedColor::ManagedColor() { } -ManagedColor::ManagedColor(const float &r, const float &g, const float &b, const float &a) : +ManagedColor::ManagedColor(const double &r, const double &g, const double &b, const double &a) : Color(r, g, b, a) { } -ManagedColor::ManagedColor(const char *data, const PixelFormat::Format &format) : - Color(data, format) +ManagedColor::ManagedColor(const char *data, const VideoParams::Format &format, int channel_layout) : + Color(data, format, channel_layout) { } diff --git a/app/render/managedcolor.h b/app/render/managedcolor.h index c214f57b4..707218903 100644 --- a/app/render/managedcolor.h +++ b/app/render/managedcolor.h @@ -30,8 +30,8 @@ class ManagedColor : public Color { public: ManagedColor(); - ManagedColor(const float& r, const float& g, const float& b, const float& a = 1.0f); - ManagedColor(const char *data, const PixelFormat::Format &format); + ManagedColor(const double& r, const double& g, const double& b, const double& a = 1.0); + ManagedColor(const char *data, const VideoParams::Format &format, int channel_layout); ManagedColor(const Color& c); const QString& color_input() const; diff --git a/app/render/opengl/CMakeLists.txt b/app/render/opengl/CMakeLists.txt new file mode 100644 index 000000000..72662cb31 --- /dev/null +++ b/app/render/opengl/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 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} + render/opengl/openglrenderer.cpp + render/opengl/openglrenderer.h + PARENT_SCOPE +) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp new file mode 100644 index 000000000..a5578a569 --- /dev/null +++ b/app/render/opengl/openglrenderer.cpp @@ -0,0 +1,668 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "openglrenderer.h" + +#include +#include + +OLIVE_NAMESPACE_ENTER + +const QVector blit_vertices = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + + -1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f +}; + +const QVector blit_texcoords = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f +}; + +OpenGLRenderer::OpenGLRenderer(QObject* parent) : + Renderer(parent), + context_(nullptr) +{ +} + +OpenGLRenderer::~OpenGLRenderer() +{ + Destroy(); +} + +void OpenGLRenderer::Init(QOpenGLContext *existing_ctx) +{ + if (context_) { + qCritical() << "Can't initialize already initialized OpenGLRenderer"; + return; + } + + context_ = existing_ctx; +} + +bool OpenGLRenderer::Init() +{ + if (context_) { + qCritical() << "Can't initialize already initialized OpenGLRenderer"; + return false; + } + + surface_.create(); + + context_ = new QOpenGLContext(this); + if (!context_->create()) { + qCritical() << "Failed to create OpenGL context"; + return false; + } + + context_->moveToThread(this->thread()); + + return true; +} + +void OpenGLRenderer::PostInit() +{ + // Make context current on that surface + if (context_->parent() == this && !context_->makeCurrent(&surface_)) { + qCritical() << "Failed to makeCurrent() on offscreen surface in thread" << thread(); + return; + } + + functions_ = context_->functions(); + + // Store OpenGL functions instance + functions_->glBlendFunc(GL_ONE, GL_ZERO); + + // Set up framebuffer used for various things + functions_->glGenFramebuffers(1, &framebuffer_); +} + +void OpenGLRenderer::DestroyInternal() +{ + if (context_) { + // Delete framebuffer + functions_->glDeleteFramebuffers(1, &framebuffer_); + + // Delete context if it belongs to us + if (context_->parent() == this) { + delete context_; + } + context_ = nullptr; + + // Destroy surface if we created it + if (surface_.isValid()) { + surface_.destroy(); + } + } +} + +void OpenGLRenderer::ClearDestination(double r, double g, double b, double a) +{ + functions_->glClearColor(r, g, b, a); + functions_->glClear(GL_COLOR_BUFFER_BIT); +} + +QVariant OpenGLRenderer::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) +{ + GLuint texture; + functions_->glGenTextures(1, &texture); + + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); + + functions_->glBindTexture(GL_TEXTURE_2D, texture); + + 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::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) +{ + GLuint texture; + functions_->glGenTextures(1, &texture); + + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex); + + functions_->glBindTexture(GL_TEXTURE_3D, texture); + + context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count), + width, height, depth, 0, GetPixelFormat(channel_count), + GetPixelType(format), data); + + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + functions_->glBindTexture(GL_TEXTURE_3D, current_tex); + + return texture; +} + +void OpenGLRenderer::AttachTextureAsDestination(Texture* texture) +{ + functions_->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + functions_->glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + texture->id().value(), + 0); +} + +void OpenGLRenderer::DetachTextureAsDestination() +{ + functions_->glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void OpenGLRenderer::DestroyNativeTexture(QVariant texture) +{ + GLuint t = texture.value(); + functions_->glDeleteTextures(1, &t); +} + +QVariant OpenGLRenderer::CreateNativeShader(ShaderCode code) +{ + QOpenGLShaderProgram* program = new QOpenGLShaderProgram(context_); + + if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex, code.vert_code())) { + qCritical() << "Failed to add vertex code to shader"; + goto error; + } + + if (!program->addShaderFromSourceCode(QOpenGLShader::Fragment, code.frag_code())) { + qCritical() << "Failed to add fragment code to shader"; + goto error; + } + + if (!program->link()) { + qCritical() << "Failed to link shader"; + goto error; + } + + return Node::PtrToValue(program); + +error: + delete program; + return QVariant(); +} + +void OpenGLRenderer::DestroyNativeShader(QVariant shader) +{ + delete Node::ValueToPtr(shader); +} + +void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int linesize) +{ + GLuint t = texture->id().value(); + const VideoParams& p = texture->params(); + + // Store currently bound texture so it can be restored later + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); + + functions_->glBindTexture(GL_TEXTURE_2D, t); + + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize); + + functions_->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + p.effective_width(), p.effective_height(), + GetPixelFormat(p.channel_count()), GetPixelType(p.format()), + data); + + functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + functions_->glBindTexture(GL_TEXTURE_2D, current_tex); +} + +void OpenGLRenderer::DownloadFromTexture(Texture* texture, void *data, int linesize) +{ + const VideoParams& p = texture->params(); + + GLint current_tex; + functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); + + AttachTextureAsDestination(texture); + + functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize); + + functions_->glReadPixels(0, + 0, + p.width(), + p.height(), + GetPixelFormat(p.channel_count()), + GetPixelType(p.format()), + data); + + functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0); + + DetachTextureAsDestination(); + + functions_->glBindTexture(GL_TEXTURE_2D, current_tex); +} + +struct TextureToBind { + TexturePtr texture; + Texture::Interpolation interpolation; +}; + +void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, VideoParams destination_params) +{ + // If this node is iterative, we'll pick up which input here + QString iterative_name; + GLuint iterative_input = 0; + QVector textures_to_bind; + + QOpenGLShaderProgram* shader = Node::ValueToPtr(s); + + shader->bind(); + + NodeValueMap::const_iterator it; + for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) { + // See if the shader has takes this parameter as an input + int variable_location = shader->uniformLocation(it.key()); + + if (variable_location == -1) { + continue; + } + + // This variable is used in the shader, let's set it + const ShaderValue& value = it.value(); + + if (value.array) { + qWarning() << "FIXME: Array support is currently a stub"; + } + + switch (value.type) { + case NodeInput::kInt: + // kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to + // over/underflows if the number is large enough, but the likelihood of that is quite low. + shader->setUniformValue(variable_location, value.data.toInt()); + break; + case NodeInput::kFloat: + // kFloat technically specifies a double but as above, OpenGL doesn't support those. + shader->setUniformValue(variable_location, value.data.toFloat()); + break; + case NodeInput::kVec2: + shader->setUniformValue(variable_location, value.data.value()); + break; + case NodeInput::kVec3: + shader->setUniformValue(variable_location, value.data.value()); + break; + case NodeInput::kVec4: + shader->setUniformValue(variable_location, value.data.value()); + break; + case NodeInput::kMatrix: + shader->setUniformValue(variable_location, value.data.value()); + break; + case NodeInput::kCombo: + shader->setUniformValue(variable_location, value.data.value()); + break; + case NodeInput::kColor: + { + Color color = value.data.value(); + shader->setUniformValue(variable_location, + color.red(), color.green(), color.blue(), color.alpha()); + break; + } + case NodeInput::kBoolean: + shader->setUniformValue(variable_location, value.data.toBool()); + break; + case NodeInput::kBuffer: + case NodeInput::kTexture: + { + TexturePtr texture = value.data.value(); + + // Set value to bound texture + shader->setUniformValue(variable_location, textures_to_bind.size()); + + // If this texture binding is the iterative input, set it here + if (it.key() == job.GetIterativeInput()) { + iterative_input = textures_to_bind.size(); + iterative_name = it.key(); + } + + GLuint tex_id = texture ? texture->id().value() : 0; + textures_to_bind.append({texture, job.GetInterpolation(it.key())}); + + // Set enable flag if shader wants it + int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(it.key())); + if (enable_param_location > -1) { + shader->setUniformValue(enable_param_location, + tex_id > 0); + } + + if (tex_id > 0) { + // Set texture resolution if shader wants it + int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(it.key())); + if (res_param_location > -1) { + int virtual_width = texture->params().width(); + + // Adjust virtual width by pixel aspect if necessary + if (texture->params().pixel_aspect_ratio() != 1 + || destination_params.pixel_aspect_ratio() != 1) { + double relative_pixel_aspect = texture->params().pixel_aspect_ratio().toDouble() / destination_params.pixel_aspect_ratio().toDouble(); + + virtual_width = qRound(static_cast(virtual_width) * relative_pixel_aspect); + } + + shader->setUniformValue(res_param_location, + virtual_width, + static_cast(texture->height() * texture->divider())); + } + } + break; + } + case NodeInput::kSamples: + case NodeInput::kText: + case NodeInput::kRational: + case NodeInput::kFont: + case NodeInput::kFile: + case NodeInput::kDecimal: + case NodeInput::kNumber: + case NodeInput::kString: + case NodeInput::kVector: + case NodeInput::kShaderJob: + case NodeInput::kSampleJob: + case NodeInput::kGenerateJob: + case NodeInput::kFootage: + case NodeInput::kNone: + case NodeInput::kAny: + break; + } + } + + // Bind all textures + for (int i=0; iid().value() : 0; + + functions_->glActiveTexture(GL_TEXTURE0 + i); + + GLenum target = (texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + functions_->glBindTexture(target, tex_id); + + PrepareInputTexture(target, t.interpolation); + } + + // Set ove_resolution to the destination to the "logical" resolution of the destination + shader->setUniformValue("ove_resolution", + static_cast(destination_params.width()), + static_cast(destination_params.height())); + + // Ensure matrix is set, at least to identity + shader->setUniformValue("ove_mvpmat", + job.GetValue(QStringLiteral("ove_mvpmat")).data.value()); + + // Set the viewport to the "physical" resolution of the destination + functions_->glViewport(0, 0, + destination_params.effective_width(), + destination_params.effective_height()); + + // Bind vertex array object + QOpenGLVertexArrayObject vao_; + vao_.create(); + vao_.bind(); + + // Set buffers + QOpenGLBuffer vert_vbo_; + vert_vbo_.create(); + vert_vbo_.bind(); + vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat)); + vert_vbo_.release(); + + QOpenGLBuffer frag_vbo_; + frag_vbo_.create(); + frag_vbo_.bind(); + frag_vbo_.allocate(blit_texcoords.constData(), blit_texcoords.size() * sizeof(GLfloat)); + frag_vbo_.release(); + + int vertex_location = shader->attributeLocation("a_position"); + vert_vbo_.bind(); + functions_->glEnableVertexAttribArray(vertex_location); + functions_->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + vert_vbo_.release(); + + int tex_location = shader->attributeLocation("a_texcoord"); + frag_vbo_.bind(); + functions_->glEnableVertexAttribArray(tex_location); + functions_->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + frag_vbo_.release(); + + // Some shaders optimize through multiple iterations which requires ping-ponging textures + // - If there are only two iterations, we can just create one backend texture and then the + // destination can be the second + // - If there are more than two iterations, we need to ping pong back and forth between two + // textures. We can still use the destination as the last iteration, but we'll need textures + // for the iterative process. + int real_iteration_count; + if (job.GetIterationCount() > 1 && !job.GetIterativeInput().isEmpty()) { + real_iteration_count = job.GetIterationCount(); + } else { + real_iteration_count = 1; + } + + TexturePtr output_tex, input_tex; + if (real_iteration_count > 1) { + // Create one texture to bounce off + output_tex = CreateTexture(destination_params); + + if (real_iteration_count > 2) { + // Create a second texture bounce off + input_tex = CreateTexture(destination_params); + } + } + + for (int iteration=0; iterationsetUniformValue("ove_iteration", iteration); + + // Replace iterative input + if (iteration == real_iteration_count-1) { + // This is the last iteration, draw to the destination + if (destination) { + // If we have a destination texture, draw to it + AttachTextureAsDestination(destination); + } else if (iteration > 0) { + // Otherwise, if we were iterating before, detach texture now + DetachTextureAsDestination(); + } + + // Clear the destination, whatever it is + ClearDestination(); + } else { + // Always draw to output_tex, which gets swapped with input_tex every iteration + AttachTextureAsDestination(output_tex.get()); + } + + if (iteration > 0) { + // If this is not the first iteration, replace the iterative texture with the one we + // last drew + functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); + functions_->glBindTexture(GL_TEXTURE_2D, input_tex->id().value()); + + // At this time, we only support iterating 2D textures + PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_name)); + } + + // Swap so that the next iteration, the texture we draw now will be the input texture next + std::swap(output_tex, input_tex); + + // Blit this texture through this shader + functions_->glDrawArrays(GL_TRIANGLES, 0, blit_vertices.size() / 3); + } + + if (destination) { + // Reset framebuffer to default if we were drawing to a texture + DetachTextureAsDestination(); + } + + // Release any textures we bound before + for (int i=textures_to_bind.size()-1; i>=0; i--) { + GLenum target = (textures_to_bind.at(i).texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D; + functions_->glActiveTexture(GL_TEXTURE0 + i); + functions_->glBindTexture(target, 0); + } + + // Release shader + shader->release(); + + // Release vertex array object + frag_vbo_.destroy(); + vert_vbo_.destroy(); + vao_.release(); + vao_.destroy(); +} + +GLint OpenGLRenderer::GetInternalFormat(VideoParams::Format format, int channel_layout) +{ + switch (format) { + case VideoParams::kFormatUnsigned8: + switch (channel_layout) { + case 1: + return GL_R8; + case 2: + return GL_RG8; + case 3: + return GL_RGB8; + case 4: + return GL_RGBA8; + } + break; + case VideoParams::kFormatUnsigned16: + switch (channel_layout) { + case 1: + return GL_R16; + case 2: + return GL_RG16; + case 3: + return GL_RGB16; + case 4: + return GL_RGBA16; + } + break; + case VideoParams::kFormatFloat16: + switch (channel_layout) { + case 1: + return GL_R16F; + case 2: + return GL_RG16F; + case 3: + return GL_RGB16F; + case 4: + return GL_RGBA16F; + } + break; + case VideoParams::kFormatFloat32: + switch (channel_layout) { + case 1: + return GL_R32F; + case 2: + return GL_RG32F; + case 3: + return GL_RGB32F; + case 4: + return GL_RGBA32F; + } + break; + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + + return GL_INVALID_VALUE; +} + +GLenum OpenGLRenderer::GetPixelType(VideoParams::Format format) +{ + switch (format) { + case VideoParams::kFormatUnsigned8: + return GL_UNSIGNED_BYTE; + case VideoParams::kFormatUnsigned16: + return GL_UNSIGNED_SHORT; + case VideoParams::kFormatFloat16: + return GL_HALF_FLOAT; + case VideoParams::kFormatFloat32: + return GL_FLOAT; + + case VideoParams::kFormatInvalid: + case VideoParams::kFormatCount: + break; + } + + return GL_INVALID_VALUE; +} + +GLenum OpenGLRenderer::GetPixelFormat(int channel_count) +{ + switch (channel_count) { + case 1: + return GL_RED; + case 3: + return GL_RGB; + case 4: + return GL_RGBA; + default: + return GL_INVALID_VALUE; + } +} + +void OpenGLRenderer::PrepareInputTexture(GLenum target, Texture::Interpolation interp) +{ + switch (interp) { + case Texture::kNearest: + functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + case Texture::kLinear: + functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + case Texture::kMipmappedLinear: + functions_->glGenerateMipmap(target); + functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + } + + functions_->glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + functions_->glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h new file mode 100644 index 000000000..d212a111a --- /dev/null +++ b/app/render/opengl/openglrenderer.h @@ -0,0 +1,98 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef OPENGLCONTEXT_H +#define OPENGLCONTEXT_H + +#include +#include +#include +#include +#include +#include + +#include "render/renderer.h" + +OLIVE_NAMESPACE_ENTER + +class OpenGLRenderer : public Renderer +{ + Q_OBJECT +public: + OpenGLRenderer(QObject* parent = nullptr); + + virtual ~OpenGLRenderer() override; + + void Init(QOpenGLContext* existing_ctx); + + virtual bool Init() override; + +public slots: + virtual void PostInit() override; + + virtual void DestroyInternal() override; + + virtual void ClearDestination(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_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + virtual QVariant CreateNativeTexture3D(int width, int height, int depth, OLIVE_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + + virtual void DestroyNativeTexture(QVariant texture) override; + + virtual QVariant CreateNativeShader(OLIVE_NAMESPACE::ShaderCode code) override; + + virtual void DestroyNativeShader(QVariant shader) override; + + virtual void UploadToTexture(OLIVE_NAMESPACE::Texture* texture, const void* data, int linesize) override; + + virtual void DownloadFromTexture(OLIVE_NAMESPACE::Texture* texture, void* data, int linesize) override; + +protected slots: + virtual void Blit(QVariant shader, + OLIVE_NAMESPACE::ShaderJob job, + OLIVE_NAMESPACE::Texture* destination, + OLIVE_NAMESPACE::VideoParams destination_params) override; + +private: + static GLint GetInternalFormat(VideoParams::Format format, int channel_layout); + + static GLenum GetPixelType(VideoParams::Format format); + + static GLenum GetPixelFormat(int channel_count); + + void AttachTextureAsDestination(OLIVE_NAMESPACE::Texture* texture); + + void DetachTextureAsDestination(); + + void PrepareInputTexture(GLenum target, Texture::Interpolation interp); + + QOpenGLContext* context_; + + QOpenGLFunctions* functions_; + + QOffscreenSurface surface_; + + GLuint framebuffer_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // OPENGLCONTEXT_H diff --git a/app/render/pixelformat.cpp b/app/render/pixelformat.cpp deleted file mode 100644 index 01abf0aff..000000000 --- a/app/render/pixelformat.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#include "pixelformat.h" - -#include "OpenImageIO/imagebuf.h" -#include -#include -#include - -#include "codec/oiio/oiiodecoder.h" -#include "common/define.h" -#include "core.h" - -OLIVE_NAMESPACE_ENTER - -bool PixelFormat::FormatHasAlphaChannel(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGBA8: - case PixelFormat::PIX_FMT_RGBA16U: - case PixelFormat::PIX_FMT_RGBA16F: - case PixelFormat::PIX_FMT_RGBA32F: - return true; - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return false; -} - -bool PixelFormat::FormatIsFloat(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - return true; - - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return false; -} - -OIIO::TypeDesc::BASETYPE PixelFormat::GetOIIOTypeDesc(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - return OIIO::TypeDesc::UINT8; - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - return OIIO::TypeDesc::UINT16; - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - return OIIO::TypeDesc::HALF; - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - return OIIO::TypeDesc::FLOAT; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return OIIO::TypeDesc::UNKNOWN; -} - -QString PixelFormat::GetName(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - return tr("8-bit"); - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - return tr("16-bit Integer"); - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - return tr("Half-Float (16-bit)"); - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - return tr("Full-Float (32-bit)"); - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - return tr("Unknown (%1)").arg(format); -} - -PixelFormat* PixelFormat::instance_ = nullptr; - -void PixelFormat::CreateInstance() -{ - instance_ = new PixelFormat(); -} - -void PixelFormat::DestroyInstance() -{ - delete instance_; -} - -PixelFormat *PixelFormat::instance() -{ - return instance_; -} - -PixelFormat::Format PixelFormat::GetConfiguredFormatForMode(RenderMode::Mode mode) -{ - return static_cast(Core::GetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat")).toInt()); -} - -void PixelFormat::SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format) -{ - if (format != GetConfiguredFormatForMode(mode)) { - Core::SetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat"), format); - - emit FormatChanged(); - } -} - -PixelFormat::Format PixelFormat::OIIOFormatToOliveFormat(OIIO::TypeDesc desc, bool has_alpha) -{ - if (desc == OIIO::TypeDesc::UINT8) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; - } else if (desc == OIIO::TypeDesc::UINT16) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; - } else if (desc == OIIO::TypeDesc::HALF) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; - } else if (desc == OIIO::TypeDesc::FLOAT) { - return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; - } - - return PixelFormat::PIX_FMT_INVALID; -} - -PixelFormat::Format PixelFormat::GetFormatWithAlphaChannel(PixelFormat::Format f) -{ - switch (f) { - case PIX_FMT_INVALID: - case PIX_FMT_COUNT: - break; - case PIX_FMT_RGB8: - case PIX_FMT_RGBA8: - return PIX_FMT_RGBA8; - case PIX_FMT_RGB16U: - case PIX_FMT_RGBA16U: - return PIX_FMT_RGBA16U; - case PIX_FMT_RGB16F: - case PIX_FMT_RGBA16F: - return PIX_FMT_RGBA16F; - case PIX_FMT_RGB32F: - case PIX_FMT_RGBA32F: - return PIX_FMT_RGBA32F; - } - - return PIX_FMT_INVALID; -} - -PixelFormat::Format PixelFormat::GetFormatWithoutAlphaChannel(PixelFormat::Format f) -{ - switch (f) { - case PIX_FMT_INVALID: - case PIX_FMT_COUNT: - break; - case PIX_FMT_RGB8: - case PIX_FMT_RGBA8: - return PIX_FMT_RGB8; - case PIX_FMT_RGB16U: - case PIX_FMT_RGBA16U: - return PIX_FMT_RGB16U; - case PIX_FMT_RGB16F: - case PIX_FMT_RGBA16F: - return PIX_FMT_RGB16F; - case PIX_FMT_RGB32F: - case PIX_FMT_RGBA32F: - return PIX_FMT_RGB32F; - } - - return PIX_FMT_INVALID; -} - -int PixelFormat::GetBufferSize(const PixelFormat::Format &format, const int &width, const int &height) -{ - return BytesPerPixel(format) * width * height; -} - -int PixelFormat::BytesPerPixel(const PixelFormat::Format &format) -{ - return BytesPerChannel(format) * ChannelCount(format); -} - -int PixelFormat::BytesPerChannel(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - return 1; - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16U: - case PixelFormat::PIX_FMT_RGBA16F: - return 2; - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - return 4; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - qFatal("Invalid pixel format requested"); - - // qFatal will abort so we won't get here, but this suppresses compiler warnings - return 0; -} - -int PixelFormat::ChannelCount(const PixelFormat::Format &format) -{ - if (PixelFormat::FormatHasAlphaChannel(format)) { - return kRGBAChannels; - } else { - return kRGBChannels; - } -} - -FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Format &dest_format) -{ - if (frame->format() == dest_format) { - return frame; - } - - // Create a destination frame with the same parameters - FramePtr converted = Frame::Create(); - converted->set_video_params(VideoParams(frame->video_params().width(), - frame->video_params().height(), - dest_format)); - converted->set_timestamp(frame->timestamp()); - converted->allocate(); - - // Do the conversion through OIIO - create a buffer for the source image - OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(), - frame->height(), - ChannelCount(frame->format()), - GetOIIOTypeDesc(frame->format()))); - - // Set the pixels (this is necessary as opposed to an OIIO buffer wrapper since Frame has - // linesizes) - OIIODecoder::FrameToBuffer(frame, &src); - - // Create a destination OIIO buffer with our destination format - OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), - converted->height(), - ChannelCount(converted->format()), - GetOIIOTypeDesc(converted->format()))); - - if (dst.copy_pixels(src)) { - - // Convert our buffer back to a frame - OIIODecoder::BufferToFrame(&dst, converted); - - return converted; - } else { - return nullptr; - } -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/pixelformat.h b/app/render/pixelformat.h deleted file mode 100644 index cb7c53fae..000000000 --- a/app/render/pixelformat.h +++ /dev/null @@ -1,149 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef BITDEPTHS_H -#define BITDEPTHS_H - -#include -#include -#include -#include - -#include "render/rendermodes.h" - -OLIVE_NAMESPACE_ENTER - -class Frame; -using FramePtr = std::shared_ptr; - -class PixelFormat : public QObject -{ - Q_OBJECT -public: - /** - * @brief Olive's internal supported pixel formats. - */ - enum Format { - PIX_FMT_INVALID = -1, - - PIX_FMT_RGBA8, - PIX_FMT_RGBA16U, - PIX_FMT_RGBA16F, - PIX_FMT_RGBA32F, - - PIX_FMT_RGB8, - PIX_FMT_RGB16U, - PIX_FMT_RGB16F, - PIX_FMT_RGB32F, - - PIX_FMT_COUNT - }; - - static void CreateInstance(); - static void DestroyInstance(); - static PixelFormat* instance(); - - /** - * @brief Returns the configured pixel format for a given mode - */ - Format GetConfiguredFormatForMode(RenderMode::Mode mode); - void SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format); - - static Format OIIOFormatToOliveFormat(OIIO::TypeDesc desc, bool has_alpha); - - static Format GetFormatWithAlphaChannel(Format f); - static Format GetFormatWithoutAlphaChannel(Format f); - - /** - * @brief Returns the minimum buffer size (in bytes) necessary for a given format, width, and height. - * - * @param format - * - * The format of the data the buffer should contain. Must be a member of the olive::PixelFormat enum. - * - * @param width - * - * The width (in pixels) of the buffer. - * - * @param height - * - * The height (in pixels) of the buffer. - */ - static int GetBufferSize(const Format &format, const int& width, const int& height); - - /** - * @brief Returns the number of bytes per pixel for a certain format - * - * Different formats use different sizes of data for pixels. Use this function to determine how many bytes a pixel - * requires for a certain format. The number of bytes will always be a multiple of 4 since all formats use RGBA and - * are at least 1 bpc. - */ - static int BytesPerPixel(const Format &format); - - /** - * @brief Returns the number of bytes per channel for a certain format - */ - static int BytesPerChannel(const Format& format); - - /** - * @brief Return the number of channels in this format - */ - static int ChannelCount(const Format& format); - - /** - * @brief Convert a frame to a pixel format - * - * If the frame's pixel format == the destination format, this just returns `frame`. - */ - static FramePtr ConvertPixelFormat(FramePtr frame, const Format &dest_format); - - /** - * @brief Simple convenience function returning whether a pixel format has an alpha channel or not - */ - static bool FormatHasAlphaChannel(const Format& format); - - /** - * @brief Simple convenience function returning whether a pixel format is float-based or integer-based - */ - static bool FormatIsFloat(const Format& format); - - /** - * @brief Get corresponding OpenImageIO TypeDesc for a given pixel format - */ - static OIIO::TypeDesc::BASETYPE GetOIIOTypeDesc(const Format& format); - - /** - * @brief Get format name - */ - static QString GetName(const Format& format); - -signals: - void FormatChanged(); - -private: - PixelFormat() = default; - - static PixelFormat* instance_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // BITDEPTHS_H diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 1c6103e48..d3bc9cb63 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -31,9 +31,12 @@ OLIVE_NAMESPACE_ENTER void PlaybackCache::Invalidate(const TimeRange &r) { - Q_ASSERT(r.in() != r.out()); + if (r.in() == r.out()) { + qWarning() << "Tried to invalidate zero-length range"; + return; + } - invalidated_.InsertTimeRange(r); + invalidated_.insert(r); RemoveRangeFromJobs(r); qint64 job_time = QDateTime::currentMSecsSinceEpoch(); @@ -69,11 +72,11 @@ void PlaybackCache::SetLength(const rational &r) jobs_.clear(); } else if (r > length_) { // If new length is greater, simply extend the invalidated range for now - invalidated_.InsertTimeRange(range_diff); + invalidated_.insert(range_diff); jobs_.append({range_diff, QDateTime::currentMSecsSinceEpoch()}); } else { // If new length is smaller, removed hashes - invalidated_.RemoveTimeRange(range_diff); + invalidated_.remove(range_diff); RemoveRangeFromJobs(range_diff); } @@ -123,7 +126,7 @@ void PlaybackCache::Shift(const rational &from, const rational &to) void PlaybackCache::Validate(const TimeRange &r) { - invalidated_.RemoveTimeRange(r); + invalidated_.remove(r); emit Validated(r); } diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp new file mode 100644 index 000000000..9091b720b --- /dev/null +++ b/app/render/previewautocacher.cpp @@ -0,0 +1,774 @@ +#include "previewautocacher.h" + +#include +#include + +#include "project/item/sequence/sequence.h" +#include "project/project.h" +#include "render/rendermanager.h" +#include "render/renderprocessor.h" + +OLIVE_NAMESPACE_ENTER + +PreviewAutoCacher::PreviewAutoCacher() : + viewer_node_(nullptr), + paused_(false), + has_changed_(false), + use_custom_range_(false), + single_frame_render_(nullptr), + last_update_time_(0), + ignore_next_mouse_button_(false), + video_params_changed_(false), + audio_params_changed_(false), + color_manager_(nullptr) +{ + // Set default autocache range + SetPlayhead(rational()); +} + +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t) +{ + if (single_frame_render_) { + single_frame_render_->Cancel(); + } + + single_frame_render_ = std::make_shared(); + + single_frame_render_->setProperty("time", QVariant::fromValue(t)); + + // Copy because TryRender() might set this to null and we still want to return a handle to this + RenderTicketPtr copy = single_frame_render_; + + TryRender(); + + return copy; +} + +void PreviewAutoCacher::SetPaused(bool paused) +{ + paused_ = paused; + + if (paused_) { + // Pause the autocache + ClearVideoQueue(); + } else { + // Unpause the cache + RequeueFrames(); + } +} + +void PreviewAutoCacher::NodeGraphChanged(NodeInput *source) +{ + // We need to determine: + // - If we don't have this input, assume that it's coming soon and ignore it + // - If we do, is this input a child of another input we're already copying? + // - Or are any of the queued inputs children of this one? + + // First we need to find our copy of the input being queued + Node* our_copy_node = copy_map_.value(source->parentNode()); + + // If we don't have this node yet, assume it's coming in a later copy in which case it'll be + // copied then + if (!our_copy_node) { + // Assert that there are updates coming + Q_ASSERT(!graph_update_queue_.isEmpty()); + return; + } + + // If we're here, we must have this node. Determine if we're already copying a "parent" of this + for (int i=0; iIsArray() && static_cast(source)->sub_params().contains(queued_input)) + || queued_input->parentNode()->OutputsTo(source, true, true)) { + // In which case, we don't need to queue it and can queue our own + graph_update_queue_.removeAt(i); + disconnect(queued_input, &NodeInput::destroyed, this, &PreviewAutoCacher::QueuedInputRemoved); + i--; + } + + // Check if the source is a member of this array, in which case it'll be copied eventually anyway + if (queued_input->IsArray() + && static_cast(queued_input)->sub_params().contains(source)) { + return; + } + + // Check if this dependency graph is already queued + if (source->parentNode()->OutputsTo(queued_input, true, true)) { + // In which case, no further copy is necessary + return; + } + } + + graph_update_queue_.append(source); + connect(source, &NodeInput::destroyed, this, &PreviewAutoCacher::QueuedInputRemoved); +} + +void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×, qint64 job_time) +{ + std::vector existing_hashes; + + foreach (const rational& time, times) { + // See if hash already exists in disk cache + QByteArray hash = RenderManager::Hash(viewer->texture_input()->get_connected_node(), viewer->video_params(), time); + + // Check memory list since disk checking is slow + bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end()); + + if (!hash_exists) { + hash_exists = QFileInfo::exists(cache->CachePathName(hash)); + + if (hash_exists) { + existing_hashes.push_back(hash); + } + } + + // Set hash in FrameHashCache's thread rather than in ours to prevent race conditions + QMetaObject::invokeMethod(cache, "SetHash", Qt::QueuedConnection, + OLIVE_NS_ARG(rational, time), + Q_ARG(QByteArray, hash), + Q_ARG(qint64, job_time), + Q_ARG(bool, hash_exists)); + } +} + +void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) +{ + ClearQueue(false); + + // Hash these frames since that should be relatively quick. + if (ignore_next_mouse_button_ || !(qApp->mouseButtons() & Qt::LeftButton)) { + ignore_next_mouse_button_ = false; + + invalidated_video_.insert(range); + + TryRender(); + } +} + +void PreviewAutoCacher::AudioInvalidated(const TimeRange &range) +{ + ClearQueue(false); + + // Start jobs to re-render the audio at this range, split into 2 second chunks + invalidated_audio_.insert(range); + + TryRender(); +} + +void PreviewAutoCacher::HashesProcessed() +{ + QFutureWatcher* watcher = static_cast*>(sender()); + + if (hash_tasks_.contains(watcher)) { + hash_tasks_.removeOne(watcher); + + RequeueFrames(); + } + + // The cacher might be waiting for this job to finish + if (!graph_update_queue_.isEmpty()) { + TryRender(); + } + + delete watcher; +} + +void PreviewAutoCacher::AudioRendered() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (audio_tasks_.contains(watcher)) { + if (!watcher->WasCancelled()) { + viewer_node_->audio_playback_cache()->WritePCM(audio_tasks_.value(watcher), + watcher->Get().value(), + watcher->GetTicket()->GetJobTime()); + + // Retrieve visual waveforms + QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); + foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) { + // Find original track + TrackOutput* track = nullptr; + + for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { + if (it.value() == waveform_info.track) { + track = static_cast(it.key()); + break; + } + } + + if (track) { + QList valid_ranges = viewer_node_->audio_playback_cache()->GetValidRanges(waveform_info.range, + watcher->GetTicket()->GetJobTime()); + if (!valid_ranges.isEmpty()) { + // Generate visual waveform in this background thread + track->waveform().set_channel_count(viewer_node_->audio_params().channel_count()); + + foreach (const TimeRange& r, valid_ranges) { + track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); + } + + emit track->PreviewChanged(); + } + } + } + } + + audio_tasks_.remove(watcher); + } + + // The cacher might be waiting for this job to finish + if (!graph_update_queue_.isEmpty()) { + TryRender(); + } + + delete watcher; +} + +void PreviewAutoCacher::VideoRendered() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (video_tasks_.contains(watcher)) { + if (watcher->WasCancelled()) { + // We didn't get this hash + currently_caching_hashes_.removeOne(watcher->property("hash").toByteArray()); + } else { + const QByteArray& hash = video_tasks_.value(watcher); + + // Download frame in another thread + RenderTicketWatcher* w = new RenderTicketWatcher(); + video_download_tasks_.insert(w, hash); + connect(w, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoDownloaded); + w->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_node_->video_frame_cache(), + watcher->Get().value(), + hash, + true)); + } + + video_tasks_.remove(watcher); + } + + // The cacher might be waiting for this job to finish + if (!graph_update_queue_.isEmpty()) { + TryRender(); + } + + delete watcher; +} + +void PreviewAutoCacher::VideoDownloaded() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (video_download_tasks_.contains(watcher)) { + if (!watcher->WasCancelled()) { + if (watcher->Get().toBool()) { + const QByteArray& hash = video_download_tasks_.value(watcher); + + currently_caching_hashes_.removeOne(hash); + + viewer_node_->video_frame_cache()->ValidateFramesWithHash(hash); + } else { + qCritical() << "Failed to download video frame"; + } + } + + video_download_tasks_.remove(watcher); + } + + delete watcher; +} + +void PreviewAutoCacher::QueuedInputRemoved() +{ + NodeInput* i = static_cast(sender()); + disconnect(i, &NodeInput::destroyed, this, &PreviewAutoCacher::QueuedInputRemoved); + graph_update_queue_.removeOne(i); +} + +void PreviewAutoCacher::VideoParamsChanged() +{ + // In case the user is pressing the mouse at this exact moment + IgnoreNextMouseButton(); + + ClearVideoQueue(); + video_params_changed_ = true; + TryRender(); +} + +void PreviewAutoCacher::AudioParamsChanged() +{ + ClearAudioQueue(); + audio_params_changed_ = true; + TryRender(); +} + +void PreviewAutoCacher::SingleFrameFinished() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + RenderTicketPtr passthrough = watcher->property("passthrough").value(); + passthrough->Finish(watcher->GetTicket()->Get(), watcher->GetTicket()->WasCancelled()); + delete watcher; +} + +//#define PRINT_UPDATE_QUEUE_INFO +void PreviewAutoCacher::ProcessUpdateQueue() +{ +#ifdef PRINT_UPDATE_QUEUE_INFO + qint64 t = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "Processing update queue of" << graph_update_queue_.size() << "elements:"; +#endif + + while (!graph_update_queue_.isEmpty()) { + NodeInput* i = graph_update_queue_.takeFirst(); +#ifdef PRINT_UPDATE_QUEUE_INFO + qDebug() << " " << i->parentNode()->id() << i->id(); +#endif + disconnect(i, &NodeInput::destroyed, this, &PreviewAutoCacher::QueuedInputRemoved); + + CopyNodeInputValue(i); + } + +#ifdef PRINT_UPDATE_QUEUE_INFO + qDebug() << "Update queue took:" << (QDateTime::currentMSecsSinceEpoch() - t); +#endif + + last_update_time_ = QDateTime::currentMSecsSinceEpoch(); +} + +bool PreviewAutoCacher::HasActiveJobs() const +{ + return !hash_tasks_.isEmpty() + || !audio_tasks_.isEmpty() + || !video_tasks_.isEmpty(); +} + +void PreviewAutoCacher::SetPlayhead(const rational &playhead) +{ + cache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value(), + playhead + Config::Current()["DiskCacheAhead"].value()); + + has_changed_ = true; + use_custom_range_ = false; + + RequeueFrames(); +} + +void PreviewAutoCacher::ClearQueue(bool wait) +{ + ClearHashQueue(wait); + ClearVideoQueue(wait); + ClearAudioQueue(wait); +} + +void PreviewAutoCacher::ClearHashQueue(bool wait) +{ + auto copy = hash_tasks_; + + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + (*it)->cancel(); + } + if (wait) { + copy = hash_tasks_; + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + (*it)->waitForFinished(); + } + } +} + +void PreviewAutoCacher::ClearVideoQueue(bool wait) +{ + // Copy because tasks that cancel immediately will be automatically removed from the list + auto copy = video_tasks_; + + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->Cancel(); + } + if (wait) { + copy = video_tasks_; + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->WaitForFinished(); + } + } + + has_changed_ = true; + use_custom_range_ = false; +} + +void PreviewAutoCacher::ClearAudioQueue(bool wait) +{ + // Create a copy because otherwise + auto copy = audio_tasks_; + + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->Cancel(); + } + if (wait) { + copy = audio_tasks_; + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->WaitForFinished(); + } + } +} + +void PreviewAutoCacher::ClearVideoDownloadQueue(bool wait) +{ + // Create a copy because otherwise + auto copy = video_download_tasks_; + + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->Cancel(); + } + if (wait) { + copy = video_download_tasks_; + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->WaitForFinished(); + } + } +} + +void PreviewAutoCacher::CopyNodeInputValue(NodeInput *input) +{ + // Find our copy of this parameter + Node* our_copy_node = copy_map_.value(input->parentNode()); + Q_ASSERT(our_copy_node); + NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); + + // Copy the standard/keyframe values between these two inputs + NodeInput::CopyValues(input, + our_copy, + false, + false); + + // Handle connections + if (input->is_connected() || our_copy->is_connected()) { + // If one of the inputs is connected, it's likely this change came from connecting or + // disconnecting whatever was connected to it + + // We start by removing all old dependencies from the map + QVector old_deps = our_copy->GetExclusiveDependencies(); + foreach (Node* i, old_deps) { + copy_map_.take(copy_map_.key(i))->deleteLater(); + } + + // And clear any other edges + while (!our_copy->edges().isEmpty()) { + NodeParam::DisconnectEdge(our_copy->edges().first()); + } + + // Then we copy all node dependencies and connections (if there are any) + CopyNodeMakeConnection(input, our_copy); + } + + // Call on sub-elements too + if (input->IsArray()) { + foreach (NodeInput* i, static_cast(input)->sub_params()) { + CopyNodeInputValue(i); + } + } +} + +Node* PreviewAutoCacher::CopyNodeConnections(Node* src_node) +{ + // Check if this node is already in the map + Node* dst_node = copy_map_.value(src_node); + + // If not, create it now + if (!dst_node) { + dst_node = src_node->copy(); + + if (dst_node->IsTrack()) { + // Hack that ensures the track type is set since we don't bother copying the whole timeline + static_cast(dst_node)->set_track_type(static_cast(src_node)->track_type()); + } + + copy_map_.insert(src_node, dst_node); + } + + // Make sure its values are copied + Node::CopyInputs(src_node, dst_node, false); + + // Copy all connections + QVector src_node_inputs = src_node->GetInputsIncludingArrays(); + QVector dst_node_inputs = dst_node->GetInputsIncludingArrays(); + + for (int i=0;iis_connected()) { + Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); + + NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id()); + + NodeParam::ConnectEdge(corresponding_output, + dst_input); + } +} + +void PreviewAutoCacher::TryRender() +{ + if (!graph_update_queue_.isEmpty()) { + if (HasActiveJobs()) { + // Still waiting for jobs to finish + return; + } + + // No jobs are active, we can process the update queue + ProcessUpdateQueue(); + + if (video_params_changed_) { + copied_viewer_node_->set_video_params(viewer_node_->video_params()); + video_params_changed_ = false; + } + + if (audio_params_changed_) { + copied_viewer_node_->set_audio_params(viewer_node_->audio_params()); + audio_params_changed_ = false; + } + } + + // If we're here, we must be able to render + if (!invalidated_video_.isEmpty()) { + QVector frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange(invalidated_video_); + + QFutureWatcher* watcher = new QFutureWatcher(); + hash_tasks_.append(watcher); + connect(watcher, &QFutureWatcher::finished, this, &PreviewAutoCacher::HashesProcessed); + watcher->setFuture(QtConcurrent::run(&PreviewAutoCacher::GenerateHashes, + copied_viewer_node_, + viewer_node_->video_frame_cache(), + frames, + last_update_time_)); + + invalidated_video_.clear(); + } + + if (!invalidated_audio_.isEmpty()) { + foreach (const TimeRange& range, invalidated_audio_) { + std::list chunks = range.Split(2); + + foreach (const TimeRange& r, chunks) { + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); + audio_tasks_.insert(watcher, r); + watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, true)); + } + } + + invalidated_audio_.clear(); + } + + if (single_frame_render_) { + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + + watcher->setProperty("passthrough", QVariant::fromValue(single_frame_render_)); + + connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::SingleFrameFinished); + + single_frame_render_->Start(); + + watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_, + color_manager_, + single_frame_render_->property("time").value(), + RenderMode::kOffline, + viewer_node_->video_frame_cache(), + true)); + + single_frame_render_ = nullptr; + } +} + +void PreviewAutoCacher::RequeueFrames() +{ + if (viewer_node_ + && viewer_node_->video_frame_cache()->HasInvalidatedRanges() + && hash_tasks_.isEmpty() + && has_changed_ + && (!paused_ || use_custom_range_)) { + TimeRange using_range; + + if (use_custom_range_) { + using_range = custom_autocache_range_; + use_custom_range_ = false; + } else { + using_range = cache_range_; + } + + QVector invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range); + + ClearVideoQueue(); + + foreach (const rational& t, invalidated_ranges) { + const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t); + + if (t >= using_range.in() + && t < using_range.out() + && !currently_caching_hashes_.contains(hash)) { + // Don't render any hash more than once + currently_caching_hashes_.append(hash); + + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + watcher->setProperty("hash", hash); + connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); + video_tasks_.insert(watcher, hash); + watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_, + color_manager_, + t, RenderMode::kOffline, + viewer_node_->video_frame_cache(), + false)); + } + } + + has_changed_ = false; + } +} + +void PreviewAutoCacher::IgnoreNextMouseButton() +{ + ignore_next_mouse_button_ = true; +} + +void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) +{ + has_changed_ = true; + use_custom_range_ = true; + custom_autocache_range_ = range; + + RequeueFrames(); +} + +void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) +{ + if (viewer_node_ == viewer_node) { + return; + } + + if (viewer_node_) { + // Cancel any remaining tickets and wait for them to finish + ClearQueue(true); + + // Clear autocache lists + { + // We need to wait for these since they work directly on the FrameHashCache. Most of the time + // this is fine, but not if the FrameHashCache gets deleted after this function. + ClearHashQueue(true); + + // This can be cleared normally (frames will be discarded and need to be rendered again) + ClearVideoQueue(false); + + // This can be cleared normally (PCM data will be discarded and need to be rendered again) + ClearAudioQueue(false); + + // We'll need to wait for these since they work directly on the FrameHashCache. Frames will + // be in the cache for later use. + ClearVideoDownloadQueue(true); + + // No longer caching any hashes + currently_caching_hashes_.clear(); + } + + // Delete all of our copied nodes + foreach (Node* c, copy_map_) { + delete c; + } + copy_map_.clear(); + copied_viewer_node_ = nullptr; + graph_update_queue_.clear(); + + video_params_changed_ = false; + audio_params_changed_ = false; + + // Disconnect signal (will be a no-op if the signal was never connected) + disconnect(viewer_node_, + &ViewerOutput::GraphChangedFrom, + this, + &PreviewAutoCacher::NodeGraphChanged); + + disconnect(viewer_node_, + &ViewerOutput::VideoParamsChanged, + this, + &PreviewAutoCacher::VideoParamsChanged); + + disconnect(viewer_node_, + &ViewerOutput::AudioParamsChanged, + this, + &PreviewAutoCacher::AudioParamsChanged); + + disconnect(viewer_node_->video_frame_cache(), + &PlaybackCache::Invalidated, + this, + &PreviewAutoCacher::VideoInvalidated); + + disconnect(viewer_node_->audio_playback_cache(), + &PlaybackCache::Invalidated, + this, + &PreviewAutoCacher::AudioInvalidated); + } + + viewer_node_ = viewer_node; + + if (viewer_node_) { + // Copy graph + copied_viewer_node_ = static_cast(viewer_node_->copy()); + copy_map_.insert(viewer_node_, copied_viewer_node_); + + // Copy parameters + copied_viewer_node_->set_video_params(viewer_node_->video_params()); + copied_viewer_node_->set_audio_params(viewer_node_->audio_params()); + + // We begin an operation and never end it which prevents the copy from unnecessarily + // invalidating its own cache + copied_viewer_node_->BeginOperation(); + + NodeGraphChanged(viewer_node_->texture_input()); + NodeGraphChanged(viewer_node_->samples_input()); + ProcessUpdateQueue(); + + invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges(); + invalidated_audio_ = viewer_node_->audio_playback_cache()->GetInvalidatedRanges(); + + connect(viewer_node_, + &ViewerOutput::GraphChangedFrom, + this, + &PreviewAutoCacher::NodeGraphChanged); + + connect(viewer_node_, + &ViewerOutput::VideoParamsChanged, + this, + &PreviewAutoCacher::VideoParamsChanged); + + connect(viewer_node_, + &ViewerOutput::AudioParamsChanged, + this, + &PreviewAutoCacher::AudioParamsChanged); + + connect(viewer_node_->video_frame_cache(), + &PlaybackCache::Invalidated, + this, + &PreviewAutoCacher::VideoInvalidated); + + connect(viewer_node_->audio_playback_cache(), + &PlaybackCache::Invalidated, + this, + &PreviewAutoCacher::AudioInvalidated); + + TryRender(); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h new file mode 100644 index 000000000..663ee7bc2 --- /dev/null +++ b/app/render/previewautocacher.h @@ -0,0 +1,209 @@ +#ifndef AUTOCACHER_H +#define AUTOCACHER_H + +#include + +#include "config/config.h" +#include "node/node.h" +#include "node/output/viewer/viewer.h" +#include "render/colormanager.h" +#include "threading/threadticketwatcher.h" + +OLIVE_NAMESPACE_ENTER + +/** + * @brief Manager for dynamically caching a sequence in the background + * + * Intended to be used with a Viewer to dynamically cache parts of a sequence based on the playhead. + */ +class PreviewAutoCacher : public QObject +{ + Q_OBJECT +public: + PreviewAutoCacher(); + + RenderTicketPtr GetSingleFrame(const rational& t); + + /** + * @brief Set the viewer node to auto-cache + */ + void SetViewerNode(ViewerOutput *viewer_node); + + /** + * @brief If the mouse is held during the next cache invalidation, cache anyway + * + * By default, PreviewAutoCacher ignores invalidations that occur while the mouse is held down, + * assuming that if the mouse is held, the user is dragging something. If you know the mouse will + * be held during a certain action and want PreviewAutoCacher to cache anyway, call this before + * the cache invalidates. + */ + void IgnoreNextMouseButton(); + + /** + * @brief Returns whether the auto-cache is currently paused or not + */ + bool IsPaused() const + { + return paused_; + } + + /** + * @brief Sets whether the auto-cache is currently paused or not + * @param paused + * + * If TRUE, the cache queue is cleared (any frames currently being rendered will be processed as + * normal however). If FALSE, any uncached frames in the range will automatically be queued. + */ + void SetPaused(bool paused); + + /** + * @brief Force a certain range to be cached + * + * Usually, PreviewAutoCacher caches a user-defined range around the playhead, however there are + * times they may want certain non-playhead-related time ranges to be cached (i.e. entire sequence + * or in/out range), so that can be set here. + */ + void ForceCacheRange(const TimeRange& range); + + /** + * @brief Updates the range of frames to auto-cache + */ + void SetPlayhead(const rational& playhead); + + /** + * @brief Clears queue of running jobs + * + * Any jobs that haven't run yet are cancelled and will never run. Any jobs that are currently + * running are cancelled, but may not be finished by the time this function returns. If the + * jobs must be finished by the time this function returns, set `wait` to TRUE. + */ + void ClearQueue(bool wait = false); + + void ClearHashQueue(bool wait = false); + void ClearVideoQueue(bool wait = false); + void ClearAudioQueue(bool wait = false); + void ClearVideoDownloadQueue(bool wait = false); + + void SetColorManager(ColorManager* manager) + { + color_manager_ = manager; + } + +public slots: + /** + * @brief Main handler for when the NodeGraph changes + */ + void NodeGraphChanged(NodeInput *source); + +private: + static void GenerateHashes(ViewerOutput* viewer, FrameHashCache *cache, const QVector& times, qint64 job_time); + + void CopyNodeInputValue(NodeInput* input); + Node *CopyNodeConnections(Node *src_node); + void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); + + void TryRender(); + + /** + * @brief Generic function called whenever the frames to render need to be (re)queued + */ + void RequeueFrames(); + + /** + * @brief Process all changes to internal NodeGraph copy + * + * PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the + * RenderManager is not reading from it. This function is called when such an opportunity arises. + */ + void ProcessUpdateQueue(); + + bool HasActiveJobs() const; + + QList graph_update_queue_; + QHash copy_map_; + ViewerOutput* copied_viewer_node_; + + ViewerOutput* viewer_node_; + + bool paused_; + + TimeRange cache_range_; + + bool has_changed_; + + bool use_custom_range_; + TimeRange custom_autocache_range_; + + TimeRangeList invalidated_video_; + TimeRangeList invalidated_audio_; + + RenderTicketPtr single_frame_render_; + + QList*> hash_tasks_; + QMap audio_tasks_; + QMap video_tasks_; + QMap video_download_tasks_; + + QVector currently_caching_hashes_; + + qint64 last_update_time_; + + bool ignore_next_mouse_button_; + + bool video_params_changed_; + + bool audio_params_changed_; + + ColorManager* color_manager_; + +private slots: + /** + * @brief Handler for when the NodeGraph reports a video change over a certain time range + */ + void VideoInvalidated(const OLIVE_NAMESPACE::TimeRange &range); + + /** + * @brief Handler for when the NodeGraph reports a audio change over a certain time range + */ + void AudioInvalidated(const OLIVE_NAMESPACE::TimeRange &range); + + /** + * @brief Handler for when we have applied all the hashes to the FrameHashCache + */ + void HashesProcessed(); + + /** + * @brief Handler for when the RenderManager has returned rendered audio + */ + void AudioRendered(); + + /** + * @brief Handler for when the RenderManager has returned rendered video frames + */ + void VideoRendered(); + + /** + * @brief Handler for when we've saved a video frame to the cache + */ + void VideoDownloaded(); + + /** + * @brief Handler for when a NodeInput has been deleted so we clear it from the queue + * + * FIXME: This is hacky. It also might not be necessary anymore with recent changes to the + * node system, but I haven't tested yet. Either way, PreviewAutoCacher should probably + * be able to pick up on these sorts of things without such a slot. + */ + void QueuedInputRemoved(); + + void VideoParamsChanged(); + + void AudioParamsChanged(); + + void SingleFrameFinished(); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // AUTOCACHER_H diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/rendercache.h similarity index 66% rename from app/render/backend/opengl/openglbackend.h rename to app/render/rendercache.h index 7d88611f3..2c9745083 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/rendercache.h @@ -18,26 +18,31 @@ ***/ -#ifndef OPENGLBACKEND_H -#define OPENGLBACKEND_H +#ifndef RENDERCACHE_H +#define RENDERCACHE_H -#include "openglproxy.h" -#include "render/backend/renderbackend.h" +#include "codec/decoder.h" +#include "project/item/footage/stream.h" OLIVE_NAMESPACE_ENTER -class OpenGLBackend : public RenderBackend +template +class RenderCache : public QHash { public: - OpenGLBackend(QObject* parent = nullptr); + QMutex *mutex() + { + return &mutex_; + } - virtual ~OpenGLBackend() override; - -protected: - virtual RenderWorker* CreateNewWorker() override; +private: + QMutex mutex_; }; +using DecoderCache = RenderCache; +using ShaderCache = RenderCache; + OLIVE_NAMESPACE_EXIT -#endif // OPENGLBACKEND_H +#endif // RENDERCACHE_H diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp new file mode 100644 index 000000000..e3aad5fd4 --- /dev/null +++ b/app/render/renderer.cpp @@ -0,0 +1,278 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "renderer.h" + +#include + +#include "common/ocioutils.h" + +OLIVE_NAMESPACE_ENTER + +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); + } + + if (v.isNull()) { + return nullptr; + } + + return std::make_shared(this, v, params, type); +} + +TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, int linesize) +{ + return CreateTexture(params, Texture::k2D, data, linesize); +} + +void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture *destination, const QMatrix4x4 &matrix) +{ + BlitColorManagedInternal(color_processor, source, source_is_premultiplied, destination, destination->params(), matrix); +} + +void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, const QMatrix4x4& matrix) +{ + BlitColorManagedInternal(color_processor, source, source_is_premultiplied, nullptr, params, matrix); +} + +void Renderer::Destroy() +{ + color_cache_.clear(); + + DestroyInternal(); +} + +bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::ColorContext *ctx) +{ + ColorContext& color_ctx = *ctx; + + if (color_cache_.contains(color_processor->id())) { + color_ctx = color_cache_.value(color_processor->id()); + return true; + } else { + // Create shader description + const char* ocio_func_name = "OCIODisplay"; + auto shader_desc = OCIO::GpuShaderDesc::CreateShaderDesc(); + shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); + shader_desc->setFunctionName(ocio_func_name); + shader_desc->setResourcePrefix("ocio_"); + + // Generate shader + color_processor->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc); + + QString shader_frag; + shader_frag.append(QStringLiteral("#version 150\n" + "\n" + "#ifdef GL_ES\n" + "precision highp int;\n" + "precision highp float;\n" + "#endif\n" + "\n" + "// Main texture input\n" + "uniform sampler2D ove_maintex;\n" + "uniform int ove_maintex_alpha;\n" + "\n" + "// Macros defining `ove_maintex_alpha` state\n" + "// Matches `AlphaAssociated` C++ enum\n" + "#define ALPHA_NONE 0\n" + "#define ALPHA_UNASSOC 1\n" + "#define ALPHA_ASSOC 2\n" + "\n" + "// Macros so OCIO's shaders work on this GLSL version\n" + "#define texture2D texture\n" + "#define texture3D texture\n" + "\n" + "// Main texture coordinate\n" + "in vec2 ove_texcoord;\n" + "\n" + "// Texture output\n" + "out vec4 fragColor;\n")); + shader_frag.append(shader_desc->getShaderText()); + shader_frag.append(QStringLiteral("\n" + "// Alpha association functions\n" + "vec4 assoc(vec4 c) {\n" + " return vec4(c.rgb * c.a, c.a);\n" + "}\n" + "\n" + "vec4 reassoc(vec4 c) {\n" + " return (c.a == 0.0) ? c : assoc(c);\n" + "}\n" + "\n" + "vec4 deassoc(vec4 c) {\n" + " return (c.a == 0.0) ? c : vec4(c.rgb / c.a, c.a);\n" + "}\n" + "\n" + "void main() {\n" + " vec4 col = texture(ove_maintex, ove_texcoord);\n" + "\n" + " // If alpha is associated, de-associate now\n" + " if (ove_maintex_alpha == ALPHA_ASSOC) {\n" + " col = deassoc(col);\n" + " }\n" + "\n" + " // Perform color conversion\n" + " col = %1(col);\n" + "\n" + " // Associate or re-associate here\n" + " if (ove_maintex_alpha == ALPHA_ASSOC) {\n" + " col = reassoc(col);\n" + " } else if (ove_maintex_alpha == ALPHA_UNASSOC) {\n" + " col = assoc(col);\n" + " }\n" + "\n" + " fragColor = col;\n" + "}\n").arg(ocio_func_name)); + + // Try to compile shader + color_ctx.compiled_shader = CreateNativeShader(ShaderCode(shader_frag, + FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.vert")))); + + if (color_ctx.compiled_shader.isNull()) { + return false; + } + + color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures()); + for (unsigned int i=0; igetNum3DTextures(); i++) { + const char* tex_name = nullptr; + const char* sampler_name = nullptr; + unsigned int edge_len = 0; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + + shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len, interpolation); + + if (!tex_name || !*tex_name + || !sampler_name || !*sampler_name + || !edge_len) { + qCritical() << "3D LUT texture data is corrupted"; + return false; + } + + const float* values = nullptr; + shader_desc->get3DTextureValues(i, values); + if (!values) { + qCritical() << "3D LUT texture values are missing"; + return false; + } + + // 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].name = sampler_name; + color_ctx.lut3d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; + } + + color_ctx.lut1d_textures.resize(shader_desc->getNumTextures()); + for (unsigned int i=0; igetNumTextures(); i++) { + const char* tex_name = nullptr; + const char* sampler_name = nullptr; + unsigned int width = 0, height = 0; + OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + + shader_desc->getTexture(i, tex_name, sampler_name, width, height, channel, interpolation); + + if (!tex_name || !*tex_name + || !sampler_name || !*sampler_name + || !width) { + qCritical() << "1D LUT texture data is corrupted"; + return false; + } + + const float* values = nullptr; + shader_desc->getTextureValues(i, values); + if (!values) { + qCritical() << "1D LUT texture values are missing"; + return false; + } + + // 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].name = sampler_name; + color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; + } + + color_cache_.insert(color_processor->id(), color_ctx); + + return true; + } +} + +void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, + bool source_is_premultiplied, Texture *destination, + VideoParams params, const QMatrix4x4& matrix) +{ + ColorContext color_ctx; + if (!GetColorContext(color_processor, &color_ctx)) { + return; + } + + ShaderJob job; + + job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(source), NodeParam::kTexture)); + job.InsertValue(QStringLiteral("ove_mvpmat"), ShaderValue(matrix, NodeParam::kMatrix)); + + AlphaAssociated associated; + if (source->channel_count() == VideoParams::kRGBAChannelCount) { + if (source_is_premultiplied) { + // De-assoc/re-assoc required for color management + associated = kAlphaAssociated; + } else { + // Just assoc at the end + associated = kAlphaUnassociated; + } + } else { + // No assoc/deassoc required + associated = kAlphaNone; + } + job.InsertValue(QStringLiteral("ove_maintex_alpha"), ShaderValue(associated, NodeParam::kInt)); + + foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) { + job.InsertValue(l.name, ShaderValue(QVariant::fromValue(l.texture), NodeParam::kTexture)); + job.SetInterpolation(l.name, l.interpolation); + } + foreach (const ColorContext::LUT& l, color_ctx.lut1d_textures) { + job.InsertValue(l.name, ShaderValue(QVariant::fromValue(l.texture), NodeParam::kTexture)); + job.SetInterpolation(l.name, l.interpolation); + } + + if (destination) { + BlitToTexture(color_ctx.compiled_shader, job, destination); + } else { + Blit(color_ctx.compiled_shader, job, params); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/renderer.h b/app/render/renderer.h new file mode 100644 index 000000000..99bcf6374 --- /dev/null +++ b/app/render/renderer.h @@ -0,0 +1,126 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef RENDERCONTEXT_H +#define RENDERCONTEXT_H + +#include +#include + +#include "common/define.h" +#include "common/timerange.h" +#include "node/node.h" +#include "render/colorprocessor.h" +#include "render/videoparams.h" +#include "texture.h" + +OLIVE_NAMESPACE_ENTER + +class ShaderJob; + +class Renderer : public QObject +{ + Q_OBJECT +public: + Renderer(QObject* parent = nullptr); + + 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 BlitToTexture(QVariant shader, + OLIVE_NAMESPACE::ShaderJob job, + OLIVE_NAMESPACE::Texture* destination) + { + Blit(shader, job, destination, destination->params()); + } + + void Blit(QVariant shader, + OLIVE_NAMESPACE::ShaderJob job, + OLIVE_NAMESPACE::VideoParams params) + { + Blit(shader, job, nullptr, params); + } + + void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture* destination, const QMatrix4x4& matrix = QMatrix4x4()); + void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, const QMatrix4x4& matrix = QMatrix4x4()); + + void Destroy(); + +public slots: + virtual void PostInit() = 0; + + virtual void DestroyInternal() = 0; + + virtual void ClearDestination(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_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; + virtual QVariant CreateNativeTexture3D(int width, int height, int depth, OLIVE_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0; + + virtual void DestroyNativeTexture(QVariant texture) = 0; + + virtual QVariant CreateNativeShader(OLIVE_NAMESPACE::ShaderCode code) = 0; + + virtual void DestroyNativeShader(QVariant shader) = 0; + + virtual void UploadToTexture(OLIVE_NAMESPACE::Texture* texture, const void* data, int linesize) = 0; + + virtual void DownloadFromTexture(OLIVE_NAMESPACE::Texture* texture, void* data, int linesize) = 0; + +protected slots: + virtual void Blit(QVariant shader, + OLIVE_NAMESPACE::ShaderJob job, + OLIVE_NAMESPACE::Texture* destination, + OLIVE_NAMESPACE::VideoParams destination_params) = 0; + +private: + struct ColorContext { + struct LUT { + TexturePtr texture; + Texture::Interpolation interpolation; + QString name; + }; + + QVariant compiled_shader; + QVector lut3d_textures; + QVector lut1d_textures; + + }; + + enum AlphaAssociated { + kAlphaNone, + kAlphaUnassociated, + kAlphaAssociated + }; + + bool GetColorContext(ColorProcessorPtr color_processor, ColorContext* ctx); + + void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, + bool source_is_premultiplied, + Texture* destination, VideoParams params, const QMatrix4x4 &matrix); + + QHash color_cache_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // RENDERCONTEXT_H diff --git a/app/render/rendererthreadwrapper.cpp b/app/render/rendererthreadwrapper.cpp new file mode 100644 index 000000000..483304575 --- /dev/null +++ b/app/render/rendererthreadwrapper.cpp @@ -0,0 +1,160 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "rendererthreadwrapper.h" + +OLIVE_NAMESPACE_ENTER + +RendererThreadWrapper::RendererThreadWrapper(Renderer *inner, QObject *parent) : + Renderer(parent), + inner_(inner), + thread_(nullptr) +{ +} + +bool RendererThreadWrapper::Init() +{ + // Init context in main thread + if (!inner_->Init()) { + return false; + } + + // Create thread + thread_ = new QThread(this); + thread_->start(QThread::IdlePriority); + + // Move context to thread + inner_->moveToThread(thread_); + + // Queue post-init in new thread + QMetaObject::invokeMethod(inner_, "PostInit", Qt::BlockingQueuedConnection); + + return true; +} + +void RendererThreadWrapper::PostInit() +{ + // Do nothing +} + +void RendererThreadWrapper::DestroyInternal() +{ + if (thread_) { + QMetaObject::invokeMethod(inner_, "DestroyInternal", Qt::BlockingQueuedConnection); + inner_ = nullptr; + + thread_->quit(); + thread_->wait(); + delete thread_; + thread_ = nullptr; + } +} + +void RendererThreadWrapper::ClearDestination(double r, double g, double b, double a) +{ + QMetaObject::invokeMethod(inner_, "ClearDestination", Qt::BlockingQueuedConnection, + Q_ARG(double, r), + Q_ARG(double, g), + Q_ARG(double, b), + Q_ARG(double, a)); +} + +QVariant RendererThreadWrapper::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize) +{ + QVariant v; + + QMetaObject::invokeMethod(inner_, "CreateNativeTexture2D", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, v), + Q_ARG(int, width), + Q_ARG(int, height), + OLIVE_NS_ARG(VideoParams::Format, format), + Q_ARG(int, channel_count), + Q_ARG(const void*, data), + Q_ARG(int, linesize)); + + return v; +} + +QVariant RendererThreadWrapper::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize) +{ + QVariant v; + + QMetaObject::invokeMethod(inner_, "CreateNativeTexture3D", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, v), + Q_ARG(int, width), + Q_ARG(int, height), + Q_ARG(int, depth), + OLIVE_NS_ARG(VideoParams::Format, format), + Q_ARG(int, channel_count), + Q_ARG(const void*, data), + Q_ARG(int, linesize)); + + return v; +} + +void RendererThreadWrapper::DestroyNativeTexture(QVariant texture) +{ + QMetaObject::invokeMethod(inner_, "DestroyNativeTexture", Qt::BlockingQueuedConnection, + Q_ARG(QVariant, texture)); +} + +QVariant RendererThreadWrapper::CreateNativeShader(ShaderCode code) +{ + QVariant v; + + QMetaObject::invokeMethod(inner_, "CreateNativeShader", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, v), + OLIVE_NS_ARG(ShaderCode, code)); + + return v; +} + +void RendererThreadWrapper::DestroyNativeShader(QVariant shader) +{ + QMetaObject::invokeMethod(inner_, "DestroyNativeShader", Qt::BlockingQueuedConnection, + Q_ARG(QVariant, shader)); +} + +void RendererThreadWrapper::UploadToTexture(Texture *texture, const void *data, int linesize) +{ + QMetaObject::invokeMethod(inner_, "UploadToTexture", Qt::BlockingQueuedConnection, + OLIVE_NS_ARG(Texture*, texture), + Q_ARG(const void*, data), + Q_ARG(int, linesize)); +} + +void RendererThreadWrapper::DownloadFromTexture(Texture *texture, void *data, int linesize) +{ + QMetaObject::invokeMethod(inner_, "DownloadFromTexture", Qt::BlockingQueuedConnection, + OLIVE_NS_ARG(Texture*, texture), + Q_ARG(void*, data), + Q_ARG(int, linesize)); +} + +void RendererThreadWrapper::Blit(QVariant shader, ShaderJob job, Texture *destination, VideoParams destination_params) +{ + QMetaObject::invokeMethod(inner_, "Blit", Qt::BlockingQueuedConnection, + Q_ARG(QVariant, shader), + OLIVE_NS_ARG(ShaderJob, job), + OLIVE_NS_ARG(Texture*, destination), + OLIVE_NS_ARG(VideoParams, destination_params)); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/rendererthreadwrapper.h b/app/render/rendererthreadwrapper.h new file mode 100644 index 000000000..1c682161a --- /dev/null +++ b/app/render/rendererthreadwrapper.h @@ -0,0 +1,78 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef RENDERCONTEXTTHREADWRAPPER_H +#define RENDERCONTEXTTHREADWRAPPER_H + +#include + +#include "renderer.h" + +OLIVE_NAMESPACE_ENTER + +class RendererThreadWrapper : public Renderer +{ +public: + RendererThreadWrapper(Renderer* inner, QObject* parent = nullptr); + + virtual ~RendererThreadWrapper() override + { + Destroy(); + delete inner_; + } + + virtual bool Init() override; + +public slots: + virtual void PostInit() override; + + virtual void DestroyInternal() override; + + virtual void ClearDestination(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_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + virtual QVariant CreateNativeTexture3D(int width, int height, int depth, OLIVE_NAMESPACE::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override; + + virtual void DestroyNativeTexture(QVariant texture) override; + + virtual QVariant CreateNativeShader(OLIVE_NAMESPACE::ShaderCode code) override; + + virtual void DestroyNativeShader(QVariant shader) override; + + virtual void UploadToTexture(OLIVE_NAMESPACE::Texture* texture, const void* data, int linesize) override; + + virtual void DownloadFromTexture(OLIVE_NAMESPACE::Texture* texture, void* data, int linesize) override; + +protected slots: + virtual void Blit(QVariant shader, + OLIVE_NAMESPACE::ShaderJob job, + OLIVE_NAMESPACE::Texture* destination, + OLIVE_NAMESPACE::VideoParams destination_params) override; + +private: + Renderer* inner_; + + QThread* thread_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // RENDERCONTEXTTHREADWRAPPER_H diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp new file mode 100644 index 000000000..ba84737b0 --- /dev/null +++ b/app/render/rendermanager.cpp @@ -0,0 +1,202 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "rendermanager.h" + +#include +#include +#include +#include + +#include "config/config.h" +#include "core.h" +#include "render/opengl/openglrenderer.h" +#include "render/rendererthreadwrapper.h" +#include "renderprocessor.h" +#include "task/conform/conform.h" +#include "task/taskmanager.h" +#include "window/mainwindow/mainwindow.h" + +OLIVE_NAMESPACE_ENTER + +RenderManager* RenderManager::instance_ = nullptr; + +RenderManager::RenderManager(QObject *parent) : + ThreadPool(QThread::IdlePriority, 0, parent), + backend_(kOpenGL) +{ + Renderer* graphics_renderer = nullptr; + + if (backend_ == kOpenGL) { + graphics_renderer = new OpenGLRenderer(); + } + + if (graphics_renderer) { + context_ = new RendererThreadWrapper(graphics_renderer, this); + context_->Init(); + context_->PostInit(); + + still_cache_ = new StillImageCache(); + decoder_cache_ = new DecoderCache(); + shader_cache_ = new ShaderCache(); + default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString())); + } else { + qCritical() << "Tried to initialize unknown graphics backend"; + context_ = nullptr; + still_cache_ = nullptr; + decoder_cache_ = nullptr; + } +} + +RenderManager::~RenderManager() +{ + if (context_) { + context_->DestroyNativeShader(default_shader_); + + delete shader_cache_; + delete decoder_cache_; + delete still_cache_; + + context_->Destroy(); + delete context_; + } +} + +QByteArray RenderManager::Hash(const Node *n, const VideoParams ¶ms, const rational &time) +{ + QCryptographicHash hasher(QCryptographicHash::Sha1); + + // Embed video parameters into this hash + int width = params.effective_width(); + int height = params.effective_height(); + VideoParams::Format format = params.format(); + + hasher.addData(reinterpret_cast(&width), sizeof(int)); + hasher.addData(reinterpret_cast(&height), sizeof(int)); + hasher.addData(reinterpret_cast(&format), sizeof(VideoParams::Format)); + + if (n) { + n->Hash(hasher, time); + } + + return hasher.result(); +} + +RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + const rational& time, RenderMode::Mode mode, + FrameHashCache* cache, bool prioritize) +{ + return RenderFrame(viewer, + color_manager, + time, + mode, + viewer->video_params(), + viewer->audio_params(), + QSize(0, 0), + QMatrix4x4(), + VideoParams::kFormatInvalid, + nullptr, + cache, + prioritize); +} + +RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + const rational& time, RenderMode::Mode mode, + const VideoParams &video_params, const AudioParams &audio_params, + const QSize& force_size, + const QMatrix4x4& force_matrix, VideoParams::Format force_format, + ColorProcessorPtr force_color_output, + FrameHashCache* cache, bool prioritize) +{ + // Create ticket + RenderTicketPtr ticket = std::make_shared(); + + ticket->setProperty("viewer", Node::PtrToValue(viewer)); + ticket->setProperty("time", QVariant::fromValue(time)); + ticket->setProperty("size", force_size); + ticket->setProperty("matrix", force_matrix); + ticket->setProperty("format", force_format); + ticket->setProperty("mode", mode); + ticket->setProperty("type", kTypeVideo); + ticket->setProperty("colormanager", Node::PtrToValue(color_manager)); + ticket->setProperty("coloroutput", QVariant::fromValue(force_color_output)); + ticket->setProperty("vparam", QVariant::fromValue(video_params)); + ticket->setProperty("aparam", QVariant::fromValue(audio_params)); + + if (cache) { + ticket->setProperty("cache", cache->GetCacheDirectory()); + } + + // Queue appending the ticket and running the next job on our thread to make this function thread-safe + QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, + OLIVE_NS_ARG(RenderTicketPtr, ticket), + Q_ARG(bool, prioritize)); + + return ticket; +} + +RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize) +{ + return RenderAudio(viewer, r, viewer->audio_params(), generate_waveforms, prioritize); +} + +RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, const AudioParams ¶ms, bool generate_waveforms, bool prioritize) +{ + // Create ticket + RenderTicketPtr ticket = std::make_shared(); + + ticket->setProperty("viewer", Node::PtrToValue(viewer)); + ticket->setProperty("time", QVariant::fromValue(r)); + ticket->setProperty("type", kTypeAudio); + ticket->setProperty("waveforms", generate_waveforms); + ticket->setProperty("aparam", QVariant::fromValue(params)); + + // Queue appending the ticket and running the next job on our thread to make this function thread-safe + QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, + OLIVE_NS_ARG(RenderTicketPtr, ticket), + Q_ARG(bool, prioritize)); + + return ticket; +} + +RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr frame, const QByteArray &hash, bool prioritize) +{ + // Create ticket + RenderTicketPtr ticket = std::make_shared(); + + ticket->setProperty("cache", Node::PtrToValue(cache)); + ticket->setProperty("frame", QVariant::fromValue(frame)); + ticket->setProperty("hash", hash); + ticket->setProperty("type", kTypeVideoDownload); + + // Queue appending the ticket and running the next job on our thread to make this function thread-safe + QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, + OLIVE_NS_ARG(RenderTicketPtr, ticket), + Q_ARG(bool, prioritize)); + + return ticket; +} + +void RenderManager::RunTicket(RenderTicketPtr ticket) const +{ + RenderProcessor::Process(ticket, context_, still_cache_, decoder_cache_, shader_cache_, default_shader_); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h new file mode 100644 index 000000000..a1083dc70 --- /dev/null +++ b/app/render/rendermanager.h @@ -0,0 +1,149 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef RENDERBACKEND_H +#define RENDERBACKEND_H + +#include + +#include "config/config.h" +#include "colorprocessorcache.h" +#include "dialog/rendercancel/rendercancel.h" +#include "node/graph.h" +#include "node/output/viewer/viewer.h" +#include "node/traverser.h" +#include "render/renderer.h" +#include "rendercache.h" +#include "stillimagecache.h" +#include "threading/threadpool.h" + +OLIVE_NAMESPACE_ENTER + +class RenderManager : public ThreadPool +{ + Q_OBJECT +public: + enum Backend { + /// Graphics acceleration provided by OpenGL + kOpenGL, + + /// No graphics rendering - used to test core threading logic + kDummy + }; + + static void CreateInstance() + { + instance_ = new RenderManager(); + } + + static void DestroyInstance() + { + delete instance_; + instance_ = nullptr; + } + + static RenderManager* instance() + { + return instance_; + } + + /** + * @brief Generate a unique identifier for a certain node at a certain time + */ + static QByteArray Hash(const Node *n, const VideoParams ¶ms, const rational &time); + + /** + * @brief Asynchronously generate a frame at a given time + * + * The ticket from this function will return a FramePtr - the rendered frame in reference color + * space. + * + * Setting `prioritize` to TRUE puts this ticket at the top of the queue. Leaving it as FALSE + * appends it to the bottom. + * + * This function is thread-safe. + */ + RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + const rational& time, RenderMode::Mode mode, + FrameHashCache* cache = nullptr, bool prioritize = false); + RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + const rational& time, RenderMode::Mode mode, + const VideoParams& video_params, const AudioParams& audio_params, + const QSize& force_size, + const QMatrix4x4& force_matrix, VideoParams::Format force_format, + ColorProcessorPtr force_color_output, + FrameHashCache* cache = nullptr, bool prioritize = false); + + /** + * @brief Asynchronously generate a chunk of audio + * + * The ticket from this function will return a SampleBufferPtr - the rendered audio. + * + * Setting `prioritize` to TRUE puts this ticket at the top of the queue. Leaving it as FALSE + * appends it to the bottom. + * + * This function is thread-safe. + */ + RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, const AudioParams& params, bool generate_waveforms, bool prioritize = false); + RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize = false); + + RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, bool prioritize = false); + + virtual void RunTicket(RenderTicketPtr ticket) const override; + + enum TicketType { + kTypeVideo, + kTypeAudio, + kTypeVideoDownload + }; + + Backend backend() const + { + return backend_; + } + +signals: + +private: + RenderManager(QObject* parent = nullptr); + + virtual ~RenderManager() override; + + static RenderManager* instance_; + + Renderer* context_; + + Backend backend_; + + StillImageCache* still_cache_; + + DecoderCache* decoder_cache_; + + ShaderCache* shader_cache_; + + QVariant default_shader_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::RenderManager::TicketType) + +#endif // RENDERBACKEND_H diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp new file mode 100644 index 000000000..74a6ee56b --- /dev/null +++ b/app/render/renderprocessor.cpp @@ -0,0 +1,515 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "renderprocessor.h" + +#include +#include +#include + +#include "project/project.h" +#include "rendermanager.h" + +OLIVE_NAMESPACE_ENTER + +RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache *shader_cache, QVariant default_shader) : + ticket_(ticket), + render_ctx_(render_ctx), + still_image_cache_(still_image_cache), + decoder_cache_(decoder_cache), + shader_cache_(shader_cache), + default_shader_(default_shader) +{ +} + +void RenderProcessor::Run() +{ + // Depending on the render ticket type, start a job + RenderManager::TicketType type = ticket_->property("type").value(); + + ticket_->Start(); + + switch (type) { + case RenderManager::kTypeVideo: + { + ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); + const VideoParams& video_params = ticket_->property("vparam").value(); + rational time = ticket_->property("time").value(); + + NodeValueTable table = ProcessInput(viewer->texture_input(), + TimeRange(time, time + video_params.time_base())); + + TexturePtr texture = table.Get(NodeParam::kTexture).value(); + + // Set up output frame parameters + VideoParams frame_params = ticket_->property("vparam").value(); + + QSize frame_size = ticket_->property("size").value(); + if (!frame_size.isNull()) { + frame_params.set_width(frame_size.width()); + frame_params.set_height(frame_size.height()); + } + + VideoParams::Format frame_format = static_cast(ticket_->property("format").toInt()); + if (frame_format != VideoParams::kFormatInvalid) { + frame_params.set_format(frame_format); + } + + if (texture) { + frame_params.set_channel_count(texture->channel_count()); + } + + FramePtr frame = Frame::Create(); + frame->set_timestamp(time); + frame->set_video_params(frame_params); + frame->allocate(); + + if (!texture) { + // Blank frame out + memset(frame->data(), 0, frame->allocated_size()); + } else { + // Dump texture contents to frame + ColorProcessorPtr output_color_transform = ticket_->property("coloroutput").value(); + const VideoParams& tex_params = texture->params(); + + if (tex_params.effective_width() != frame_params.effective_width() + || tex_params.effective_height() != frame_params.effective_height() + || tex_params.format() != frame_params.format() + || output_color_transform) { + TexturePtr blit_tex = render_ctx_->CreateTexture(frame_params); + + QMatrix4x4 matrix = ticket_->property("matrix").value(); + + if (output_color_transform) { + // Yes color transform, blit color managed + render_ctx_->BlitColorManaged(output_color_transform, texture, true, blit_tex.get(), matrix); + } else { + // No color transform, just blit + ShaderJob job; + job.InsertValue(QStringLiteral("ove_maintex"), {QVariant::fromValue(texture), NodeParam::kTexture}); + job.InsertValue(QStringLiteral("ove_mvpmat"), {matrix, NodeParam::kMatrix}); + + render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get()); + } + + // Replace texture that we're going to download in the next step + texture = blit_tex; + } + + render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels()); + } + + ticket_->Finish(QVariant::fromValue(frame), IsCancelled()); + break; + } + case RenderManager::kTypeAudio: + { + ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); + TimeRange time = ticket_->property("time").value(); + + NodeValueTable table = ProcessInput(viewer->samples_input(), time); + + ticket_->Finish(table.Get(NodeParam::kSamples), IsCancelled()); + break; + } + case RenderManager::kTypeVideoDownload: + { + FrameHashCache* cache = Node::ValueToPtr(ticket_->property("cache")); + FramePtr frame = ticket_->property("frame").value(); + QByteArray hash = ticket_->property("hash").toByteArray(); + + ticket_->Finish(cache->SaveCacheFrame(hash, frame), false); + break; + } + default: + // Fail + ticket_->Cancel(); + } +} + +DecoderPtr RenderProcessor::ResolveDecoderFromInput(StreamPtr stream) +{ + if (!stream) { + qWarning() << "Attempted to resolve the decoder of a null stream"; + return nullptr; + } + + QMutexLocker locker(decoder_cache_->mutex()); + + DecoderPtr decoder = decoder_cache_->value(stream.get()); + + if (!decoder) { + // No decoder + decoder = Decoder::CreateFromID(stream->footage()->decoder()); + + if (decoder->Open(stream)) { + decoder_cache_->insert(stream.get(), decoder); + } else { + qWarning() << "Failed to open decoder for" << stream->footage()->filename() + << "::" << stream->index(); + return nullptr; + } + } + + return decoder; +} + +void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache *still_image_cache, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader) +{ + RenderProcessor p(ticket, render_ctx, still_image_cache, decoder_cache, shader_cache, default_shader); + p.Run(); +} + +NodeValueTable RenderProcessor::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) +{ + if (track->track_type() == Timeline::kTrackTypeAudio) { + + const AudioParams& audio_params = ticket_->property("aparam").value(); + + QList active_blocks = track->BlocksAtTimeRange(range); + + // All these blocks will need to output to a buffer so we create one here + SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params, + audio_params.time_to_samples(range.length())); + block_range_buffer->fill(0); + + NodeValueTable merged_table; + + // Loop through active blocks retrieving their audio + foreach (Block* b, active_blocks) { + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + int destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in()); + int max_dest_sz = audio_params.time_to_samples(range_for_block.length()); + + // Destination buffer + NodeValueTable table = GenerateTable(b, range_for_block); + SampleBufferPtr samples_from_this_block = table.Take(NodeParam::kSamples).value(); + + if (!samples_from_this_block) { + // If we retrieved no samples from this block, do nothing + continue; + } + + // FIXME: Doesn't handle reversing + if (b->speed_input()->is_keyframing() || b->speed_input()->is_connected()) { + // FIXME: We'll need to calculate the speed hoo boy + } else { + double speed_value = b->speed_input()->get_standard_value().toDouble(); + + if (qIsNull(speed_value)) { + // Just silence, don't think there's any other practical application of 0 speed audio + samples_from_this_block->fill(0); + } else if (!qFuzzyCompare(speed_value, 1.0)) { + // Multiply time + samples_from_this_block->speed(speed_value); + } + } + + int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count()); + + // Copy samples into destination buffer + block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length); + + NodeValueTable::Merge({merged_table, table}); + } + + if (ticket_->property("waveforms").toBool()) { + // Generate a visual waveform and send it back to the main thread + AudioVisualWaveform visual_waveform; + visual_waveform.set_channel_count(audio_params.channel_count()); + visual_waveform.OverwriteSamples(block_range_buffer, audio_params.sample_rate()); + + RenderedWaveform waveform_info = {track, visual_waveform, range}; + QVector waveform_list = ticket_->property("waveforms").value< QVector >(); + waveform_list.append(waveform_info); + ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list)); + } + + merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer), track); + + return merged_table; + + } else { + return NodeTraverser::GenerateBlockTable(track, range); + } +} + +QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational &input_time) +{ + TexturePtr value = nullptr; + + // Check the still frame cache. On large frames such as high resolution still images, uploading + // and color managing them for every frame is a waste of time, so we implement a small cache here + // to optimize such a situation + VideoStreamPtr video_stream = std::static_pointer_cast(stream); + const VideoParams& video_params = ticket_->property("vparam").value(); + + ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); + + StillImageCache::EntryPtr want_entry = std::make_shared( + nullptr, + stream, + ColorProcessor::GenerateID(color_manager, video_stream->colorspace(), color_manager->GetReferenceColorSpace()), + video_stream->premultiplied_alpha(), + video_params.divider(), + (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time, + true); + + bool found_existing = false; + + still_image_cache_->mutex()->lock(); + + foreach (StillImageCache::EntryPtr e, still_image_cache_->entries()) { + if (StillImageCache::CompareEntryMetadata(want_entry, e)) { + // Found an exact match of the texture we want in the cache. See if it's working or if it's + // ready. + want_entry = e; + found_existing = true; + + while (want_entry->working) { + still_image_cache_->wait_cond()->wait(still_image_cache_->mutex()); + } + + value = want_entry->texture; + break; + } + } + + if (value) { + // Found the texture, we can release the cache now + still_image_cache_->mutex()->unlock(); + } else { + // Wasn't in still image cache, so we'll have to retrieve it from the decoder + + // Let other processors know we're getting this texture (want_entry's `working` field is + // already set to true in the initializer above) + if (!found_existing) { + still_image_cache_->PushEntry(want_entry); + } + + still_image_cache_->mutex()->unlock(); + + DecoderPtr decoder = ResolveDecoderFromInput(stream); + + if (decoder) { + FramePtr frame = decoder->RetrieveVideo(input_time, + video_params.divider()); + + if (frame) { + // Return a texture from the derived class + TexturePtr unmanaged_texture = render_ctx_->CreateTexture(frame->video_params(), + frame->data(), + frame->linesize_pixels()); + + // We convert to our rendering pixel format, since that will always be float-based which + // is necessary for correct color conversion + VideoParams managed_params = frame->video_params(); + managed_params.set_format(video_params.format()); + value = render_ctx_->CreateTexture(managed_params); + + //qDebug() << "FIXME: Accessing video_stream->colorspace() and video_stream->premultiplied_alpha() may cause race conditions"; + + ColorProcessorPtr processor = ColorProcessor::Create(color_manager, + video_stream->colorspace(), + color_manager->GetReferenceColorSpace()); + + render_ctx_->BlitColorManaged(processor, unmanaged_texture, + video_stream->premultiplied_alpha(), + value.get()); + + still_image_cache_->mutex()->lock(); + + // Put this into the image cache instead + want_entry->texture = value; + want_entry->working = false; + + still_image_cache_->wait_cond()->wakeAll(); + + still_image_cache_->mutex()->unlock(); + } + } + } + + return QVariant::fromValue(value); +} + +QVariant RenderProcessor::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) +{ + QVariant value; + + DecoderPtr decoder = ResolveDecoderFromInput(stream); + + if (decoder) { + const AudioParams& audio_params = ticket_->property("aparam").value(); + + SampleBufferPtr frame = decoder->RetrieveAudio(input_time, audio_params, &IsCancelled()); + + if (frame) { + value = QVariant::fromValue(frame); + } + } + + return value; +} + +QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +{ + Q_UNUSED(range) + + QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), job.GetShaderID()); + + QMutexLocker locker(shader_cache_->mutex()); + + QVariant shader = shader_cache_->value(full_shader_id); + + if (shader.isNull()) { + // Since we have shader code, compile it now + shader = render_ctx_->CreateNativeShader(node->GetShaderCode(job.GetShaderID())); + + if (shader.isNull()) { + // Couldn't find or build the shader required + return QVariant(); + } + } + + VideoParams tex_params = ticket_->property("vparam").value(); + + bool input_textures_have_alpha = false; + for (auto it=job.GetValues().cbegin(); it!=job.GetValues().cend(); it++) { + if (it.value().type == NodeParam::kTexture) { + TexturePtr tex = it.value().data.value(); + if (tex && tex->channel_count() == VideoParams::kRGBAChannelCount) { + input_textures_have_alpha = true; + break; + } + } + } + + if (input_textures_have_alpha || job.GetAlphaChannelRequired()) { + tex_params.set_channel_count(VideoParams::kRGBAChannelCount); + } else { + tex_params.set_channel_count(VideoParams::kRGBChannelCount); + } + + TexturePtr destination = render_ctx_->CreateTexture(tex_params); + + // Run shader + render_ctx_->BlitToTexture(shader, job, destination.get()); + + return QVariant::fromValue(destination); +} + +QVariant RenderProcessor::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) +{ + if (!job.samples() || !job.samples()->is_allocated()) { + return QVariant(); + } + + SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count()); + NodeValueDatabase value_db; + + const AudioParams& audio_params = ticket_->property("aparam").value(); + + for (int i=0;isample_count();i++) { + // Calculate the exact rational time at this sample + double sample_to_second = static_cast(i) / static_cast(audio_params.sample_rate()); + + rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second); + + // Update all non-sample and non-footage inputs + NodeValueMap::const_iterator j; + for (j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) { + NodeValueTable value; + NodeInput* corresponding_input = node->GetInputWithID(j.key()); + + if (corresponding_input) { + value = ProcessInput(corresponding_input, TimeRange(this_sample_time, this_sample_time)); + } else { + value.Push(j.value(), node); + } + + value_db.Insert(j.key(), value); + } + + AddGlobalsToDatabase(value_db, TimeRange(this_sample_time, this_sample_time)); + + node->ProcessSamples(value_db, + job.samples(), + output_buffer, + i); + } + + return QVariant::fromValue(output_buffer); +} + +QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const GenerateJob &job) +{ + FramePtr frame = Frame::Create(); + + VideoParams frame_params = ticket_->property("vparam").value(); + if (job.GetAlphaChannelRequired()) { + frame_params.set_channel_count(VideoParams::kRGBAChannelCount); + } else { + frame_params.set_channel_count(VideoParams::kRGBChannelCount); + } + + frame->set_video_params(frame_params); + frame->allocate(); + + node->GenerateFrame(frame, job); + + TexturePtr texture = render_ctx_->CreateTexture(frame->video_params(), + frame->data(), + frame->linesize_pixels()); + + return QVariant::fromValue(texture); +} + +QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time) +{ + if (!ticket_->property("cache").toString().isEmpty() + && node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { + const VideoParams& video_params = ticket_->property("vparam").value(); + + QByteArray hash = RenderManager::Hash(node, video_params, time); + + FramePtr f = FrameHashCache::LoadCacheFrame(ticket_->property("cache").toString(), hash); + + if (f) { + // The cached frame won't load with the correct divider by default, so we enforce it here + VideoParams p = f->video_params(); + + p.set_width(f->width() * video_params.divider()); + p.set_height(f->height() * video_params.divider()); + p.set_divider(video_params.divider()); + + f->set_video_params(p); + + TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels()); + return QVariant::fromValue(texture); + } + } + + return QVariant(); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h new file mode 100644 index 000000000..68627a6c2 --- /dev/null +++ b/app/render/renderprocessor.h @@ -0,0 +1,83 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef RENDERPROCESSOR_H +#define RENDERPROCESSOR_H + +#include "node/traverser.h" +#include "render/renderer.h" +#include "rendercache.h" +#include "stillimagecache.h" +#include "threading/threadticket.h" + +OLIVE_NAMESPACE_ENTER + +class RenderProcessor : public NodeTraverser +{ +public: + static void Process(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + + struct RenderedWaveform { + const TrackOutput* track; + AudioVisualWaveform waveform; + TimeRange range; + }; + +protected: + virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) override; + + virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time) override; + + virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) override; + + virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; + + virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override; + + virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; + + virtual QVariant GetCachedFrame(const Node *node, const rational &time) override; + +private: + RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + + void Run(); + + DecoderPtr ResolveDecoderFromInput(StreamPtr stream); + + RenderTicketPtr ticket_; + + Renderer* render_ctx_; + + StillImageCache* still_image_cache_; + + DecoderCache* decoder_cache_; + + ShaderCache* shader_cache_; + + QVariant default_shader_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::RenderProcessor::RenderedWaveform) + +#endif // RENDERPROCESSOR_H diff --git a/app/render/backend/opengl/openglworker.h b/app/render/shadercode.h similarity index 52% rename from app/render/backend/opengl/openglworker.h rename to app/render/shadercode.h index 75eed65f8..59d5a59de 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/shadercode.h @@ -18,32 +18,45 @@ ***/ -#ifndef OPENGLWORKER_H -#define OPENGLWORKER_H +#ifndef SHADERCODE_H +#define SHADERCODE_H -#include "openglproxy.h" -#include "render/backend/renderworker.h" +#include "common/filefunctions.h" OLIVE_NAMESPACE_ENTER -class OpenGLWorker : public RenderWorker -{ +class ShaderCode { public: - OpenGLWorker(RenderBackend* parent); + ShaderCode(const QString& frag_code, const QString& vert_code) : + frag_code_(frag_code), + vert_code_(vert_code) + { + if (frag_code_.isEmpty()) { + frag_code_ = FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.frag")); + } -protected: - virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const override; + if (vert_code_.isEmpty()) { + vert_code_ = FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.vert")); + } + } - virtual QVariant FootageFrameToTexture(StreamPtr stream, FramePtr frame) const override; + const QString& frag_code() const + { + return frag_code_; + } - virtual QVariant CachedFrameToTexture(FramePtr frame) const override; + const QString& vert_code() const + { + return vert_code_; + } - virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; +private: + QString frag_code_; - virtual bool TextureHasAlpha(const QVariant& v) const override; + QString vert_code_; }; OLIVE_NAMESPACE_EXIT -#endif // OPENGLWORKER_H +#endif // SHADERCODE_H diff --git a/app/render/shaderinfo.h b/app/render/shaderinfo.h deleted file mode 100644 index 1fa685e2b..000000000 --- a/app/render/shaderinfo.h +++ /dev/null @@ -1,193 +0,0 @@ -#ifndef SHADERINFO_H -#define SHADERINFO_H - -#include "codec/samplebuffer.h" -#include "node/input.h" -#include "node/inputarray.h" -#include "node/value.h" - -OLIVE_NAMESPACE_ENTER - -using NodeValueMap = QHash; - -class AcceleratedJob { -public: - AcceleratedJob() = default; - - NodeValue GetValue(NodeInput* input) const - { - return value_map_.value(input->id()); - } - - NodeValue GetValue(const QString& input) const - { - return value_map_.value(input); - } - - void InsertValue(NodeInput* input, NodeValueDatabase& value) - { - if (input->IsArray()) { - NodeInputArray* array = static_cast(input); - QVector values(array->GetSize()); - - for (int j=0;jGetSize();j++) { - NodeInput* subparam = array->At(j); - - values[j] = value[subparam].TakeWithMeta(subparam->data_type()); - } - - InsertValue(input->id(), NodeValue(NodeParam::kVec2, QVariant::fromValue(values), input->parentNode())); - } else { - InsertValue(input->id(), value[input].TakeWithMeta(input->data_type())); - } - } - - void InsertValue(const QString& input, const NodeValue& value) - { - value_map_.insert(input, value); - } - - void InsertValue(NodeInput* input, const NodeValue& value) - { - value_map_.insert(input->id(), value); - } - - const NodeValueMap &GetValues() const - { - return value_map_; - } - -private: - NodeValueMap value_map_; - -}; - -class SampleJob : public AcceleratedJob { -public: - SampleJob() - { - samples_ = nullptr; - } - - SampleJob(const NodeValue& value) - { - samples_ = value.data().value(); - } - - SampleJob(NodeInput* from, NodeValueDatabase& db) - { - samples_ = db[from].Take(NodeParam::kSamples).value(); - } - - SampleBufferPtr samples() const - { - return samples_; - } - - bool HasSamples() const - { - return samples_ && samples_->is_allocated(); - } - -private: - SampleBufferPtr samples_; - -}; - -class GenerateJob : public AcceleratedJob { -public: - GenerateJob() - { - alpha_channel_required_ = false; - } - - bool GetAlphaChannelRequired() const - { - return alpha_channel_required_; - } - - void SetAlphaChannelRequired(bool e) - { - alpha_channel_required_ = e; - } - -private: - bool alpha_channel_required_; - -}; - -class ShaderJob : public GenerateJob { -public: - ShaderJob() - { - iterations_ = 1; - iterative_input_ = nullptr; - } - - const QString& GetShaderID() const - { - return id_; - } - - void SetShaderID(const QString& id) - { - id_ = id; - } - - void SetIterations(int iterations, NodeInput* iterative_input) - { - iterations_ = iterations; - iterative_input_ = iterative_input; - } - - int GetIterationCount() const - { - return iterations_; - } - - NodeInput* GetIterativeInput() const - { - return iterative_input_; - } - -private: - QString id_; - - int iterations_; - - NodeInput* iterative_input_; - -}; - -class ShaderCode { -public: - ShaderCode(const QString& frag_code, const QString& vert_code) : - frag_code_(frag_code), - vert_code_(vert_code) - { - } - - const QString& frag_code() const - { - return frag_code_; - } - - const QString& vert_code() const - { - return vert_code_; - } - -private: - QString frag_code_; - - QString vert_code_; - -}; - -OLIVE_NAMESPACE_EXIT - -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ShaderJob) -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::SampleJob) -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::GenerateJob) - -#endif // SHADERINFO_H diff --git a/app/render/shadervalue.h b/app/render/shadervalue.h new file mode 100644 index 000000000..dd8412b30 --- /dev/null +++ b/app/render/shadervalue.h @@ -0,0 +1,55 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef SHADERVALUE_H +#define SHADERVALUE_H + +#include "node/param.h" + +OLIVE_NAMESPACE_ENTER + +struct ShaderValue +{ + ShaderValue() + { + type = NodeParam::kNone; + array = false; + } + + ShaderValue(QVariant data_in, NodeParam::DataType type_in, bool array_in = false) + { + data = data_in; + type = type_in; + array = array_in; + } + + NodeParam::DataType type; + QVariant data; + bool array; + + QString tag; + +}; + +using NodeValueMap = QHash; + +OLIVE_NAMESPACE_EXIT + +#endif // SHADERVALUE_H diff --git a/app/render/stillimagecache.h b/app/render/stillimagecache.h new file mode 100644 index 000000000..f5b4336e7 --- /dev/null +++ b/app/render/stillimagecache.h @@ -0,0 +1,83 @@ +#ifndef STILLIMAGECACHE_H +#define STILLIMAGECACHE_H + +#include +#include + +#include "common/rational.h" +#include "project/item/footage/stream.h" +#include "render/texture.h" + +OLIVE_NAMESPACE_ENTER + +class StillImageCache +{ +public: + struct Entry { + Entry(TexturePtr t, StreamPtr s, const QString& cs, bool a, int d, const rational& i, bool w) + { + texture = t; + stream = s; + colorspace = cs; + alpha_is_associated = a; + divider = d; + time = i; + working = w; + } + + TexturePtr texture; + StreamPtr stream; + QString colorspace; + bool alpha_is_associated; + int divider; + rational time; + bool working; + }; + + using EntryPtr = std::shared_ptr; + + QMutex* mutex() + { + return &mutex_; + } + + QWaitCondition* wait_cond() + { + return &wait_cond_; + } + + const QVector& entries() const + { + return entries_; + } + + static bool CompareEntryMetadata(EntryPtr a, EntryPtr b) + { + return (a->stream == b->stream + && a->colorspace == b->colorspace + && a->alpha_is_associated == b->alpha_is_associated + && a->divider == b->divider + && a->time == b->time); + } + + void PushEntry(EntryPtr e) + { + entries_.prepend(e); + + if (entries_.size() > 8) { + entries_.removeLast(); + } + } + +private: + QMutex mutex_; + + QWaitCondition wait_cond_; + + QVector entries_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // STILLIMAGECACHE_H diff --git a/app/render/backend/decodercache.h b/app/render/texture.cpp similarity index 71% rename from app/render/backend/decodercache.h rename to app/render/texture.cpp index 6e0890f43..66bd9dc77 100644 --- a/app/render/backend/decodercache.h +++ b/app/render/texture.cpp @@ -18,16 +18,22 @@ ***/ -#ifndef DECODERCACHE_H -#define DECODERCACHE_H +#include "texture.h" -#include "codec/decoder.h" -#include "project/item/footage/stream.h" +#include "renderer.h" OLIVE_NAMESPACE_ENTER -using DecoderCache = QHash; +const Texture::Interpolation Texture::kDefaultInterpolation = Texture::kMipmappedLinear; + +Texture::~Texture() +{ + renderer_->DestroyNativeTexture(id_); +} + +void Texture::Upload(void *data, int linesize) +{ + renderer_->UploadToTexture(this, data, linesize); +} OLIVE_NAMESPACE_EXIT - -#endif // DECODERCACHE_H diff --git a/app/render/texture.h b/app/render/texture.h new file mode 100644 index 000000000..b5f34843a --- /dev/null +++ b/app/render/texture.h @@ -0,0 +1,122 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef RENDERTEXTURE_H +#define RENDERTEXTURE_H + +#include + +#include "render/videoparams.h" + +OLIVE_NAMESPACE_ENTER + +class Renderer; + +class Texture +{ +public: + enum Type { + k2D, + k3D + }; + + enum Interpolation { + kNearest, + kLinear, + kMipmappedLinear + }; + + static const Interpolation kDefaultInterpolation; + + Texture(Renderer* renderer, const QVariant& native, const VideoParams& param, Type type) : + renderer_(renderer), + params_(param), + id_(native), + type_(type) + { + } + + ~Texture(); + + QVariant id() const + { + return id_; + } + + const VideoParams& params() const + { + return params_; + } + + void Upload(void* data, int linesize); + + int width() const + { + return params_.effective_width(); + } + + int height() const + { + return params_.effective_height(); + } + + VideoParams::Format format() const + { + return params_.format(); + } + + int channel_count() const + { + return params_.channel_count(); + } + + int divider() const + { + return params_.divider(); + } + + const rational& pixel_aspect_ratio() const + { + return params_.pixel_aspect_ratio(); + } + + Type type() const + { + return type_; + } + +private: + Renderer* renderer_; + + VideoParams params_; + + QVariant id_; + + Type type_; + +}; + +using TexturePtr = std::shared_ptr; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::TexturePtr) + +#endif // RENDERTEXTURE_H diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 00167c63a..0f127014a 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -26,6 +26,8 @@ OLIVE_NAMESPACE_ENTER +const int VideoParams::kInternalChannelCount = kRGBAChannelCount; + const rational VideoParams::kPixelAspectSquare(1); const rational VideoParams::kPixelAspectNTSCStandard(8, 9); const rational VideoParams::kPixelAspectNTSCWidescreen(32, 27); @@ -62,15 +64,19 @@ const QVector VideoParams::kStandardPixelAspects = { VideoParams::VideoParams() : width_(0), height_(0), - format_(PixelFormat::PIX_FMT_INVALID), + depth_(0), + format_(kFormatInvalid), + channel_count_(0), interlacing_(Interlacing::kInterlaceNone) { } -VideoParams::VideoParams(const int &width, const int &height, const PixelFormat::Format &format, const rational& pixel_aspect_ratio, const Interlacing &interlacing, const int& divider) : +VideoParams::VideoParams(int width, int height, Format format, int nb_channels, const rational& pixel_aspect_ratio, Interlacing interlacing, int divider) : width_(width), height_(height), + depth_(0), format_(format), + channel_count_(nb_channels), pixel_aspect_ratio_(pixel_aspect_ratio), interlacing_(interlacing), divider_(divider) @@ -79,11 +85,27 @@ VideoParams::VideoParams(const int &width, const int &height, const PixelFormat: validate_pixel_aspect_ratio(); } -VideoParams::VideoParams(const int &width, const int &height, const rational &time_base, const PixelFormat::Format &format, const rational& pixel_aspect_ratio, const Interlacing &interlacing, const int ÷r) : +VideoParams::VideoParams(int width, int height, int depth, Format format, int nb_channels, const rational &pixel_aspect_ratio, VideoParams::Interlacing interlacing, int divider) : width_(width), height_(height), + depth_(depth), + format_(format), + channel_count_(nb_channels), + pixel_aspect_ratio_(pixel_aspect_ratio), + interlacing_(interlacing), + divider_(divider) +{ + calculate_effective_size(); + validate_pixel_aspect_ratio(); +} + +VideoParams::VideoParams(int width, int height, const rational &time_base, Format format, int nb_channels, const rational& pixel_aspect_ratio, Interlacing interlacing, int divider) : + width_(width), + height_(height), + depth_(0), time_base_(time_base), format_(format), + channel_count_(nb_channels), pixel_aspect_ratio_(pixel_aspect_ratio), interlacing_(interlacing), divider_(divider) @@ -143,10 +165,69 @@ bool VideoParams::operator!=(const VideoParams &rhs) const return !(*this == rhs); } +int VideoParams::GetBytesPerChannel(VideoParams::Format format) +{ + switch (format) { + case kFormatInvalid: + case kFormatCount: + break; + case kFormatUnsigned8: + return 1; + case kFormatUnsigned16: + case kFormatFloat16: + return 2; + case kFormatFloat32: + return 4; + } + + return 0; +} + +int VideoParams::GetBytesPerPixel(VideoParams::Format format, int channels) +{ + return GetBytesPerChannel(format) * channels; +} + +bool VideoParams::FormatIsFloat(VideoParams::Format format) +{ + switch (format) { + case kFormatFloat16: + case kFormatFloat32: + return true; + case kFormatUnsigned8: + case kFormatUnsigned16: + case kFormatInvalid: + case kFormatCount: + break; + } + + return false; +} + +QString VideoParams::GetFormatName(VideoParams::Format format) +{ + switch (format) { + case kFormatUnsigned8: + return QCoreApplication::translate("VideoParams", "8-bit"); + case kFormatUnsigned16: + return QCoreApplication::translate("VideoParams", "16-bit Integer"); + case kFormatFloat16: + return QCoreApplication::translate("VideoParams", "Half-Float (16-bit)"); + case kFormatFloat32: + return QCoreApplication::translate("VideoParams", "Full-Float (32-bit)"); + case kFormatInvalid: + case kFormatCount: + break; + } + + return QCoreApplication::translate("VideoParams", "Unknown (0x%1)").arg(format, 0, 16); +} + void VideoParams::calculate_effective_size() { effective_width_ = GetScaledDimension(width(), divider_); effective_height_ = GetScaledDimension(height(), divider_); + effective_depth_ = GetScaledDimension(depth(), divider_); } void VideoParams::validate_pixel_aspect_ratio() @@ -161,8 +242,8 @@ bool VideoParams::is_valid() const return (width() > 0 && height() > 0 && !pixel_aspect_ratio_.isNull() - && format_ != PixelFormat::PIX_FMT_INVALID - && format_ != PixelFormat::PIX_FMT_COUNT); + && format_ > kFormatInvalid && format_ < kFormatCount + && channel_count_ > 0); } QString VideoParams::FrameRateToString(const rational &frame_rate) @@ -196,7 +277,7 @@ QString VideoParams::FormatPixelAspectRatioString(const QString &format, const r int VideoParams::GetScaledDimension(int dim, int divider) { - return qCeil(dim / divider * 0.5) * 2; + return dim / divider; } OLIVE_NAMESPACE_EXIT diff --git a/app/render/videoparams.h b/app/render/videoparams.h index fa24e2027..7cd4ef7c1 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -22,13 +22,35 @@ #define VIDEOPARAMS_H #include "common/rational.h" -#include "pixelformat.h" #include "rendermodes.h" OLIVE_NAMESPACE_ENTER class VideoParams { public: + enum Format { + /// Invalid or no format + kFormatInvalid = -1, + + /// 8-bit unsigned integer + kFormatUnsigned8, + + /// 16-bit unsigned integer + kFormatUnsigned16, + + /// 16-bit half float + kFormatFloat16, + + /// 32-bit full float + kFormatFloat32, + + /// 64-bit double float - disabled since very, very few libs support 64-bit buffers + //kFormatFloat64, + + /// Total format count + kFormatCount + }; + enum Interlacing { kInterlaceNone, kInterlacedTopFirst, @@ -36,58 +58,128 @@ public: }; VideoParams(); - VideoParams(const int& width, const int& height, const PixelFormat::Format& format, + VideoParams(int width, int height, Format format, int nb_channels, const rational& pixel_aspect_ratio = 1, - const Interlacing& interlacing = kInterlaceNone, const int& divider = 1); - VideoParams(const int& width, const int& height, const rational& time_base, - const PixelFormat::Format& format, const rational& pixel_aspect_ratio = 1, - const Interlacing& interlacing = kInterlaceNone, const int& divider = 1); + Interlacing interlacing = kInterlaceNone, int divider = 1); + VideoParams(int width, int height, int depth, + Format format, int nb_channels, + const rational& pixel_aspect_ratio = 1, + Interlacing interlacing = kInterlaceNone, int divider = 1); + VideoParams(int width, int height, const rational& time_base, + Format format, int nb_channels, + const rational& pixel_aspect_ratio = 1, + Interlacing interlacing = kInterlaceNone, int divider = 1); - const int& width() const + int width() const { return width_; } - const int& height() const + void set_width(int width) + { + width_ = width; + calculate_effective_size(); + } + + int height() const { return height_; } + void set_height(int height) + { + height_ = height; + calculate_effective_size(); + } + + int depth() const + { + return depth_; + } + + void set_depth(int depth) + { + depth_ = depth; + calculate_effective_size(); + } + const rational& time_base() const { return time_base_; } - const int& divider() const + void set_time_base(const rational& r) + { + time_base_ = r; + } + + int divider() const { return divider_; } - const int& effective_width() const + void set_divider(int d) + { + divider_ = d; + calculate_effective_size(); + } + + int effective_width() const { return effective_width_; } - const int& effective_height() const + int effective_height() const { return effective_height_; } - const PixelFormat::Format& format() const + int effective_depth() const + { + return effective_depth_; + } + + Format format() const { return format_; } + void set_format(Format f) + { + format_ = f; + } + + int channel_count() const + { + return channel_count_; + } + + void set_channel_count(int c) + { + channel_count_ = c; + } + const rational& pixel_aspect_ratio() const { return pixel_aspect_ratio_; } + void set_pixel_aspect_ratio(const rational& r) + { + pixel_aspect_ratio_ = r; + validate_pixel_aspect_ratio(); + } + Interlacing interlacing() const { return interlacing_; } + void set_interlacing(Interlacing i) + { + interlacing_ = i; + } + static int generate_auto_divider(qint64 width, qint64 height); bool is_valid() const; @@ -95,6 +187,33 @@ public: bool operator==(const VideoParams& rhs) const; bool operator!=(const VideoParams& rhs) const; + static int GetBytesPerChannel(Format format); + int GetBytesPerChannel() const + { + return GetBytesPerChannel(format_); + } + + static int GetBytesPerPixel(Format format, int channels); + int GetBytesPerPixel() const + { + return GetBytesPerPixel(format_, channel_count_); + } + + static int GetBufferSize(int width, int height, Format format, int channels) + { + return width * height * GetBytesPerPixel(format, channels); + } + int GetBufferSize() const + { + return GetBufferSize(width_, height_, format_, channel_count_); + } + + static bool FormatIsFloat(Format format); + + static QString GetFormatName(Format format); + + static const int kInternalChannelCount; + static const rational kPixelAspectSquare; static const rational kPixelAspectNTSCStandard; static const rational kPixelAspectNTSCWidescreen; @@ -106,6 +225,10 @@ public: static const QVector kStandardPixelAspects; static const QVector kSupportedDividers; + static const int kHSVChannelCount = 3; + static const int kRGBChannelCount = 3; + static const int kRGBAChannelCount = 4; + /** * @brief Convert rational frame rate (i.e. flipped timebase) to a user-friendly string */ @@ -123,9 +246,12 @@ private: int width_; int height_; + int depth_; rational time_base_; - PixelFormat::Format format_; + Format format_; + + int channel_count_; rational pixel_aspect_ratio_; @@ -134,6 +260,7 @@ private: int divider_; int effective_width_; int effective_height_; + int effective_depth_; }; OLIVE_NAMESPACE_EXIT diff --git a/app/shaders/default.frag b/app/shaders/default.frag new file mode 100644 index 000000000..181773f5c --- /dev/null +++ b/app/shaders/default.frag @@ -0,0 +1,20 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +// Input texture +uniform sampler2D ove_maintex; + +// Input texture coordinate +in vec2 ove_texcoord; + +// Output color +out vec4 fragColor; + +void main() { + vec4 color = texture(ove_maintex, ove_texcoord); + fragColor = color; +} diff --git a/app/shaders/default.vert b/app/shaders/default.vert new file mode 100644 index 000000000..2569ec9f2 --- /dev/null +++ b/app/shaders/default.vert @@ -0,0 +1,18 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +uniform mat4 ove_mvpmat; + +in vec4 a_position; +in vec2 a_texcoord; + +out vec2 ove_texcoord; + +void main() { + gl_Position = ove_mvpmat * a_position; + ove_texcoord = a_texcoord; +} \ No newline at end of file diff --git a/app/shaders/deinterlace.frag b/app/shaders/deinterlace.frag new file mode 100644 index 000000000..bda9c732f --- /dev/null +++ b/app/shaders/deinterlace.frag @@ -0,0 +1,26 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +uniform sampler2D ove_maintex; +uniform vec2 ove_resolution; + +in vec2 ove_texcoord; + +out vec4 fragColor; + +void main() { + vec2 using_texcoord = ove_texcoord; + + // A very basic deinterlace that halves the vertical + // resolution and linearly interpolates the two fields + // by reading the texture coord between them. + float half_vert = round(ove_resolution.y / 2.0); + using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert; + + vec4 color = %1(texture(ove_maintex, using_texcoord)); + fragColor = color; +} diff --git a/app/shaders/rgbhistogram.frag b/app/shaders/rgbhistogram.frag index 435949138..a593caaed 100644 --- a/app/shaders/rgbhistogram.frag +++ b/app/shaders/rgbhistogram.frag @@ -1,9 +1,7 @@ #version 150 uniform sampler2D ove_maintex; -uniform vec2 ove_resolution; -uniform vec2 ove_viewport; - +uniform vec2 viewport; uniform float histogram_scale; in vec2 ove_texcoord; @@ -11,7 +9,7 @@ in vec2 ove_texcoord; out vec4 fragColor; void main(void) { - float histogram_width = ceil(histogram_scale * ove_viewport.y); + float histogram_width = ceil(histogram_scale * viewport.y); float quantisation = 1.0 / (histogram_width - 1.0); vec3 cur_col = vec3(0.0); vec3 sum = vec3(0.0); diff --git a/app/shaders/rgbhistogram.vert b/app/shaders/rgbhistogram.vert index 92536144c..a8e53d253 100644 --- a/app/shaders/rgbhistogram.vert +++ b/app/shaders/rgbhistogram.vert @@ -1,7 +1,6 @@ #version 150 uniform float histogram_scale; -uniform vec2 ove_resolution; in vec4 a_position; in vec2 a_texcoord; @@ -26,4 +25,4 @@ void main() { gl_Position = transform * a_position; ove_texcoord = a_texcoord; -} \ No newline at end of file +} diff --git a/app/shaders/rgbhistogram_secondary.frag b/app/shaders/rgbhistogram_secondary.frag index 474db6b74..0bb8ac75a 100644 --- a/app/shaders/rgbhistogram_secondary.frag +++ b/app/shaders/rgbhistogram_secondary.frag @@ -1,8 +1,7 @@ #version 150 uniform sampler2D ove_maintex; -uniform vec2 ove_resolution; -uniform vec2 ove_viewport; +uniform vec2 viewport; uniform float histogram_scale; uniform float histogram_power; @@ -13,11 +12,11 @@ out vec4 fragColor; void main(void) { vec3 col = vec3(0.0); - float histogram_height = ceil(ove_viewport.y * histogram_scale); + float histogram_height = ceil(viewport.y * histogram_scale); vec3 histogram_ratio = vec3(0.0); vec3 sum = vec3(0.0); float ratio = 0.0; - vec3 total_pixels = vec3(ceil(ove_viewport.x * ove_resolution.y * + vec3 total_pixels = vec3(ceil(viewport.x * viewport.y * histogram_scale)); for (int i = 0; i < histogram_height; i++) { diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index e568b37cd..38b41a1f5 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -1,8 +1,8 @@ #version 150 uniform sampler2D ove_maintex; -uniform vec2 ove_resolution; -uniform vec2 ove_viewport; + +uniform vec2 viewport; uniform vec3 luma_coeffs; uniform float waveform_scale; @@ -12,7 +12,7 @@ in vec2 ove_texcoord; out vec4 fragColor; void main(void) { - float waveform_height = ceil(waveform_scale * ove_viewport.y); + float waveform_height = ceil(waveform_scale * viewport.y); float quantisation = 1.0 / (waveform_height - 1.0); float intensity = 0.10; vec4 col = vec4(0.0); diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index fa131be8c..4ddc322f7 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -33,7 +33,10 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioParams& params) : bool ConformTask::Run() { - if (stream_->footage()->decoder().isEmpty()) { + // Conforming is done by the renderer now, but I would like to use something like this just to + // show progress + + /*if (stream_->footage()->decoder().isEmpty()) { SetError(tr("Failed to find decoder to conform audio stream")); return false; } else { @@ -49,7 +52,9 @@ bool ConformTask::Run() } else { return true; } - } + }*/ + + return true; } OLIVE_NAMESPACE_EXIT diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 28215e856..1f5d4df38 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -33,16 +33,22 @@ ExportTask::ExportTask(ViewerOutput* viewer_node, params_(params) { SetTitle(tr("Exporting \"%1\"").arg(viewer_node->media_name())); - - // Render highest quality - backend()->SetRenderMode(RenderMode::kOnline); } bool ExportTask::Run() { TimeRange range; + // For safety, if we're overwriting, we save to a temporary filename and then only overwrite it + // at the end + QString real_filename = params_.filename(); + if (QFileInfo::exists(params_.filename())) { + // Generate a filename that definitely doesn't exist + params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename)); + } + encoder_ = Encoder::CreateFromID(params_.encoder(), params_); + if (!encoder_) { SetError(tr("Failed to create encoder")); return false; @@ -64,20 +70,26 @@ bool ExportTask::Run() frame_time_ = Timecode::time_to_timestamp(range.in(), viewer()->video_params().time_base()); + QSize video_force_size; + QMatrix4x4 video_force_matrix; + if (params_.video_enabled()) { - // Ensure renderer always provides the same resolution - backend()->SetForceDownloadResolution(true); - // If a transformation matrix is applied to this video, create it here - if (params_.video_scaling_method() != ExportParams::kStretch) { - QMatrix4x4 mat = ExportParams::GenerateMatrix(params_.video_scaling_method(), - viewer()->video_params().width(), - viewer()->video_params().height(), - params_.video_params().width(), - params_.video_params().height()); + if (viewer()->video_params().width() != params_.video_params().width() + || params_.video_params().height() != params_.video_params().height()) { + video_force_size = QSize(params_.video_params().width(), params_.video_params().height()); - backend()->SetVideoDownloadMatrix(mat); + if (params_.video_scaling_method() != ExportParams::kStretch) { + video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(), + viewer()->video_params().width(), + viewer()->video_params().height(), + params_.video_params().width(), + params_.video_params().height()); + } + } else { + // Disables forcing size in the renderer + video_force_size = QSize(0, 0); } // Create color processor @@ -94,15 +106,17 @@ bool ExportTask::Run() TimeRangeList video_range, audio_range; if (params_.video_enabled()) { - video_range.append(range); + video_range = {range}; } if (params_.audio_enabled()) { - audio_range.append(range); + audio_range = {range}; audio_data_.SetLength(range.length()); } - Render(video_range, audio_range, false); + Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr, + video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(), + color_processor_); bool success = true; @@ -113,49 +127,28 @@ bool ExportTask::Run() encoder_->Close(); - encoder_->deleteLater(); + delete encoder_; + + // If cancelled, delete the file we made, which is always a file we created since we write to a + // temp file during the actual encoding process + if (IsCancelled()) { + QFile::remove(params_.filename()); + } else if (params_.filename() != real_filename) { + // If we were writing to a temp file, overwrite now + if (!FileFunctions::RenameFileAllowOverwrite(params_.filename(), real_filename)) { + SetError(tr("Failed to overwrite \"%1\". Export has been saved as \"%2\" instead.") + .arg(real_filename, params_.filename())); + success = false; + } + } return success; } -void FrameColorConvert(ColorProcessorPtr processor, FramePtr frame) -{ - // OCIO conversion requires a frame in 32F format - if (frame->format() != PixelFormat::PIX_FMT_RGBA32F - && frame->format() != PixelFormat::PIX_FMT_RGB32F) { - PixelFormat::Format dst; - - if (PixelFormat::FormatHasAlphaChannel(frame->format())) { - dst = PixelFormat::PIX_FMT_RGBA32F; - } else { - dst = PixelFormat::PIX_FMT_RGB32F; - } - - frame = PixelFormat::ConvertPixelFormat(frame, dst); - } - - // Color conversion must be done with unassociated alpha, and the pipeline is always associated - ColorManager::DisassociateAlpha(frame); - - // Convert color space - processor->ConvertFrame(frame); - - // Re-associate alpha - ColorManager::ReassociateAlpha(frame); -} - -QFuture ExportTask::DownloadFrame(FramePtr frame, const QByteArray &hash) -{ - rendered_frame_.insert(hash, frame); - - return QtConcurrent::run(FrameColorConvert, color_processor_, frame); -} - -void ExportTask::FrameDownloaded(const QByteArray &hash, const std::list ×, qint64 job_time) +void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector ×, qint64 job_time) { Q_UNUSED(job_time) - - FramePtr f = rendered_frame_.value(hash); + Q_UNUSED(hash) foreach (const rational& t, times) { time_map_.insert(t, f); @@ -174,7 +167,6 @@ void ExportTask::FrameDownloaded(const QByteArray &hash, const std::listWriteFrame(time_map_.take(real_time), real_time); frame_time_++; - } } diff --git a/app/task/export/export.h b/app/task/export/export.h index 58e392ad5..4829f02c3 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -38,15 +38,16 @@ public: protected: virtual bool Run() override; - virtual QFuture DownloadFrame(FramePtr frame, const QByteArray &hash) override; - - virtual void FrameDownloaded(const QByteArray& hash, const std::list& times, qint64 job_time) override; + virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times, qint64 job_time) override; virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override; -private: - QHash rendered_frame_; + virtual bool TwoStepFrameRendering() const override + { + return false; + } +private: QHash time_map_; ColorManager* color_manager_; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index e707a6949..5d96f5978 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -20,6 +20,8 @@ #include "precachetask.h" +#include "project/project.h" + OLIVE_NAMESPACE_ENTER PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) : @@ -29,9 +31,6 @@ PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) : viewer()->set_video_params(sequence->video_params()); viewer()->set_audio_params(sequence->audio_params()); - // Render fastest quality - backend()->SetRenderMode(RenderMode::kOffline); - video_node_ = new VideoInput(); video_node_->SetStream(footage); @@ -39,13 +38,11 @@ PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) : SetTitle(tr("Pre-caching %1:%2").arg(footage->footage()->filename(), QString::number(footage->index()))); - - backend()->NodeGraphChanged(viewer()->texture_input()); - backend()->ProcessUpdateQueue(); } PreCacheTask::~PreCacheTask() { + // We created this viewer node ourselves, so now we should delete it delete viewer(); delete video_node_; } @@ -66,23 +63,21 @@ bool PreCacheTask::Run() } */ - Render(video_range, TimeRangeList(), true); - - download_threads_.waitForDone(); + Render(footage_->footage()->project()->color_manager(), + video_range, + TimeRangeList(), + RenderMode::kOnline, + viewer()->video_frame_cache()); return true; } -QFuture PreCacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash) -{ - return QtConcurrent::run(&download_threads_, viewer()->video_frame_cache(), &FrameHashCache::SaveCacheFrame, hash, frame); -} - -void PreCacheTask::FrameDownloaded(const QByteArray &hash, const std::list ×, qint64 job_time) +void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const QVector ×, qint64 job_time) { // Do nothing. Pre-cache essentially just creates more frames in the cache, it doesn't need to do // anything else. + Q_UNUSED(frame) Q_UNUSED(hash) Q_UNUSED(times) Q_UNUSED(job_time) diff --git a/app/task/precache/precachetask.h b/app/task/precache/precachetask.h index 090fedd44..960d2fa02 100644 --- a/app/task/precache/precachetask.h +++ b/app/task/precache/precachetask.h @@ -38,9 +38,7 @@ public: protected: virtual bool Run() override; - virtual QFuture DownloadFrame(FramePtr frame, const QByteArray &hash) override; - - virtual void FrameDownloaded(const QByteArray& hash, const std::list& times, qint64 job_time) override; + virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times, qint64 job_time) override; virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override; @@ -49,8 +47,6 @@ private: VideoInput* video_node_; - QThreadPool download_threads_; - }; OLIVE_NAMESPACE_EXIT diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 5a94e2d38..ab30be115 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -110,8 +110,8 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte } else { - FootagePtr item = Decoder::ProbeMedia(model_->project(), file_info.absoluteFilePath(), - &IsCancelled()); + FootagePtr item = Decoder::Probe(model_->project(), file_info.absoluteFilePath(), + &IsCancelled()); if (item) { // See if this footage is an image sequence diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index 78799e389..8efcc91aa 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -166,7 +166,7 @@ bool LoadOTIOTask::Run() if (imported_footage.contains(footage_url)) { probed_item = imported_footage.value(footage_url); } else { - probed_item = Decoder::ProbeMedia(project_.get(), footage_url, &IsCancelled()); + probed_item = Decoder::Probe(project_.get(), footage_url, &IsCancelled()); imported_footage.insert(footage_url, probed_item); project_->root()->add_child(probed_item); } diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index 4ee1b1fc0..a196d38f7 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -38,7 +38,7 @@ ProjectSaveTask::ProjectSaveTask(ProjectPtr project) : bool ProjectSaveTask::Run() { // File to temporarily save to (ensures we can't half-write the user's main file and crash) - QString temp_save = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("tempsv")); + QString temp_save = FileFunctions::GetSafeTemporaryFilename(project_->filename()); QFile project_file(temp_save); @@ -74,16 +74,15 @@ bool ProjectSaveTask::Run() } // Save was successful, we can now rewrite the original file - QFile original(project_->filename()); - if ((!original.exists() || original.remove()) - && QFile::copy(temp_save, project_->filename())) { + if (FileFunctions::RenameFileAllowOverwrite(temp_save, project_->filename())) { return true; } else { - SetError(tr("Failed to write to \"%1\".").arg(project_->filename())); + SetError(tr("Failed to overwrite \"%1\". Project has been saved as \"%2\" instead.") + .arg(project_->filename(), temp_save)); return false; } } else { - SetError(tr("Failed to open file \"%1\" for writing.").arg(project_->filename())); + SetError(tr("Failed to open temporary file \"%1\" for writing.").arg(temp_save)); return false; } } diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index e1c94ef19..acc70b71f 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -133,7 +133,7 @@ opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(TrackOutput *track) otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(), block->length().toRationalTime())); - QList media_nodes = block->FindInputNodes(); + QVector media_nodes = block->FindInputNodes(); if (!media_nodes.isEmpty()) { auto media_ref = new opentimelineio::v1_0::ExternalReference(media_nodes.first()->stream()->footage()->filename().toStdString()); otio_clip->set_media_reference(media_ref); diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index bd813ac19..65821d777 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -21,223 +21,221 @@ #include "render.h" #include "common/timecodefunctions.h" +#include "render/rendermanager.h" OLIVE_NAMESPACE_ENTER -RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) +RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) : + viewer_(viewer), + video_params_(vparams), + audio_params_(aparams), + running_tickets_(0) { - backend_ = new OpenGLBackend(); - backend_->SetViewerNode(viewer); - backend_->SetVideoParams(vparams); - backend_->SetAudioParams(aparams); } RenderTask::~RenderTask() { - delete backend_; } -struct TimeHashFuturePair { - rational time; - RenderTicketPtr hash_future; -}; - -struct HashTimePair { - rational time; - QByteArray hash; -}; - -struct HashFrameFuturePair { - QByteArray hash; - RenderTicketPtr frame_future; -}; - -struct RangeSampleFuturePair { - TimeRange range; - RenderTicketPtr sample_future; -}; - -struct HashDownloadFuturePair { - QByteArray hash; - QFuture download_future; - qint64 job_time; -}; - -void RenderTask::Render(const TimeRangeList& video_range, +bool RenderTask::Render(ColorManager* manager, + const TimeRangeList& video_range, const TimeRangeList &audio_range, - bool use_disk_cache) + RenderMode::Mode mode, + FrameHashCache* cache, const QSize &force_size, + const QMatrix4x4 &force_matrix, VideoParams::Format force_format, + ColorProcessorPtr force_color_output) { + // Run watchers in another thread so they can accept signals even while this thread is blocked + QThread watcher_thread; + watcher_thread.start(); + double progress_counter = 0; double total_length = 0; double video_frame_sz = video_params().time_base().toDouble(); - std::list audio_queue; - std::list audio_lookup_table; - if (!audio_range.isEmpty()) { - foreach (const TimeRange& r, audio_range) { - total_length += r.length().toDouble(); + // Store real time before any rendering takes place + qint64 job_time = QDateTime::currentMSecsSinceEpoch(); - std::list ranges = RenderBackend::SplitRangeIntoChunks(r); - audio_queue.insert(audio_queue.end(), ranges.begin(), ranges.end()); - } + // Queue audio jobs + foreach (const TimeRange& r, audio_range) { + // Don't count audio progress, since it's generally a lot faster than video and is weighted at + // 50%, which makes the progress bar look weird to the uninitiated + //total_length += r.length().toDouble(); + + IncrementRunningTickets(); + + RenderTicketWatcher* watcher = CreateWatcher(&watcher_thread); + watcher->setProperty("range", QVariant::fromValue(r)); + watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_, r, audio_params_, false)); } - std::list render_lookup_table; - QVector times; - QVector hashes; - std::list frame_queue; - qint64 hash_job_time = 0; + // Look up hashes + QMap > time_map; if (!video_range.isEmpty()) { - times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range); + // Get list of discrete frames from range + QVector times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range); + QVector hashes(times.size()); + // Add to "total progress" total_length += video_frame_sz * times.size(); - RenderTicketPtr hash_future = backend_->Hash(times); - hashes = hash_future->Get().value >(); - hash_job_time = hash_future->GetJobTime(); + // Generate hashes + for (int i=0; iWasCancelled()) { - for (int i=0;iHash(viewer(), video_params_, times.at(i)); + } + + // Filter out duplicates + for (int i=0; isetProperty("hash", hash); + + IncrementRunningTickets(); + + watcher->SetTicket(RenderManager::instance()->RenderFrame(viewer_, manager, times.at(i), + mode, video_params_, audio_params_, + force_size, force_matrix, + force_format, force_color_output, + cache)); } } } - // Start downloading frames that have finished - std::list download_futures; + finished_watcher_mutex_.lock(); - // Iterators - std::list::iterator i; - std::list::iterator j; - std::list::iterator k; + while (!IsCancelled()) { + while (!finished_watchers_.empty() && !IsCancelled()) { + RenderTicketWatcher* watcher = finished_watchers_.front(); + finished_watchers_.pop_front(); - std::list running_hashes; - std::list existing_hashes; + finished_watcher_mutex_.unlock(); - while (!IsCancelled() - && (!render_lookup_table.empty() - || !frame_queue.empty() - || !audio_queue.empty() - || !download_futures.empty() - || !audio_lookup_table.empty())) { + // Analyze watcher here + RenderManager::TicketType ticket_type = watcher->GetTicket()->property("type").value(); - while (!IsCancelled() && !frame_queue.empty()) { + if (ticket_type == RenderManager::kTypeAudio) { - // Pop another frame off the frame queue - const HashTimePair& p = frame_queue.front(); + TimeRange range = watcher->property("range").value(); - // Check if we're already rendering this hash - bool rendering_hash = (std::find(running_hashes.begin(), running_hashes.end(), p.hash) != running_hashes.end()); + AudioDownloaded(range, + watcher->Get().value(), + job_time); - // Skip this hash if we're already rendering it - if (!rendering_hash) { - // Check if this frame already exists (has already been rendered previously or during this job) - bool hash_exists = false; + // Don't count audio progress, since it's generally a lot faster than video and is weighted at + // 50%, which makes the progress bar look weird to the uninitiated + //progress_counter += range.length().toDouble(); + //emit ProgressChanged(progress_counter / total_length); - if (use_disk_cache) { - // Check if this hash is in our "existing hashes" list - hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end()); + } else if (ticket_type == RenderManager::kTypeVideo && TwoStepFrameRendering()) { - // If not, check if it's in the filesystem - if (!hash_exists) { - hash_exists = QFileInfo::exists(viewer()->video_frame_cache()->CachePathName(p.hash)); + DownloadFrame(&watcher_thread, + watcher->Get().value(), + watcher->property("hash").toByteArray()); - // If so, add it to the list so we don't have to check the filesystem again later - if (hash_exists) { - existing_hashes.push_back(p.hash); - } - } - - if (hash_exists) { - // Already exists, no need to render it again - FrameDownloaded(p.hash, {p.time}, hash_job_time); - progress_counter += video_frame_sz; - emit ProgressChanged(progress_counter / total_length); - } - } - - // If no existing disk cache was found, queue it now - if (!hash_exists) { - render_lookup_table.push_back({p.hash, backend_->RenderFrame(p.time)}); - running_hashes.push_back(p.hash); - } - } - - // Remove first element - frame_queue.pop_front(); - } - - while (!IsCancelled() && !audio_queue.empty()) { - audio_lookup_table.push_back({audio_queue.front(), backend_->RenderAudio(audio_queue.front())}); - audio_queue.pop_front(); - } - - i = render_lookup_table.begin(); - - while (!IsCancelled() && i != render_lookup_table.end()) { - if (i->frame_future->IsFinished()) { - if (!i->frame_future->WasCancelled()) { - FramePtr f = i->frame_future->Get().value(); - - // Start multithreaded download here - download_futures.push_back({i->hash, DownloadFrame(f, i->hash), i->frame_future->GetJobTime()}); - } - - i = render_lookup_table.erase(i); - } else { - i++; - } - } - - j = download_futures.begin(); - - while (!IsCancelled() && j != download_futures.end()) { - if (j->download_future.isFinished()) { - // Place it in the cache - std::list times_with_hash; - - for (int hash_index=0;hash_indexhash) { - times_with_hash.push_back(times.at(hash_index)); - } - } - - FrameDownloaded(j->hash, times_with_hash, j->job_time); - - existing_hashes.push_back(j->hash); - - // Signal process - progress_counter += times_with_hash.size() * video_frame_sz; + progress_counter += video_frame_sz * 0.5; emit ProgressChanged(progress_counter / total_length); - j = download_futures.erase(j); - } else { - j++; - } - } - k = audio_lookup_table.begin(); + // Assume single-step video or video download ticket + QByteArray rendered_hash = watcher->property("hash").toByteArray(); + FrameDownloaded(watcher->Get().value(), rendered_hash, time_map.value(rendered_hash), job_time); - while (!IsCancelled() && k != audio_lookup_table.end()) { - if (k->sample_future->IsFinished()) { - AudioDownloaded(k->range, - k->sample_future->Get().value(), - k->sample_future->GetJobTime()); + double progress_to_add = video_frame_sz; + if (TwoStepFrameRendering()) { + progress_to_add *= 0.5; + } + progress_counter += progress_to_add; - progress_counter += k->range.length().toDouble(); emit ProgressChanged(progress_counter / total_length); - k = audio_lookup_table.erase(k); - } else { - k++; } + + delete watcher; + running_watchers_.removeOne(watcher); + + finished_watcher_mutex_.lock(); + } + + if (IsCancelled()) { + break; + } + + // Run out of finished watchers. If we still have running tickets, wait for the next one to finish. + if (running_tickets_ > 0) { + finished_watcher_wait_cond_.wait(&finished_watcher_mutex_); + } else { + // No more running tickets or finished tickets, wem ust be + break; } } - // `Close` will block until all jobs are done making a safe deletion - backend_->Close(); + finished_watcher_mutex_.unlock(); + + if (IsCancelled()) { + // Cancel every watcher we created + foreach (RenderTicketWatcher* watcher, running_watchers_) { + disconnect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone); + watcher->Cancel(); + } + } + + watcher_thread.quit(); + watcher_thread.wait(); + + return true; +} + +void RenderTask::DownloadFrame(QThread *thread, FramePtr frame, const QByteArray &hash) +{ + RenderTicketWatcher* watcher = CreateWatcher(thread); + + watcher->setProperty("hash", hash); + + IncrementRunningTickets(); + + watcher->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_->video_frame_cache(), + frame, + hash)); +} + +RenderTicketWatcher *RenderTask::CreateWatcher(QThread *thread) +{ + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + watcher->moveToThread(thread); + connect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone, Qt::DirectConnection); + running_watchers_.append(watcher); + return watcher; +} + +void RenderTask::IncrementRunningTickets() +{ + finished_watcher_mutex_.lock(); + running_tickets_++; + finished_watcher_mutex_.unlock(); +} + +void RenderTask::TicketDone(RenderTicketWatcher* watcher) +{ + finished_watcher_mutex_.lock(); + finished_watchers_.push_back(watcher); + finished_watcher_wait_cond_.wakeAll(); + running_tickets_--; + finished_watcher_mutex_.unlock(); } OLIVE_NAMESPACE_EXIT diff --git a/app/task/render/render.h b/app/task/render/render.h index 8571c0894..428e4e243 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -24,51 +24,81 @@ #include #include "node/output/viewer/viewer.h" -#include "render/backend/opengl/openglbackend.h" +#include "render/colormanager.h" #include "task/task.h" +#include "threading/threadticket.h" +#include "threading/threadticketwatcher.h" OLIVE_NAMESPACE_ENTER class RenderTask : public Task { + Q_OBJECT public: RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams); virtual ~RenderTask() override; protected: - void Render(const TimeRangeList &video_range, - const TimeRangeList &audio_range, - bool use_disk_cache); + bool Render(ColorManager *manager, const TimeRangeList &video_range, + const TimeRangeList &audio_range, RenderMode::Mode mode, + FrameHashCache *cache, const QSize& force_size = QSize(0, 0), + const QMatrix4x4& force_matrix = QMatrix4x4(), + VideoParams::Format force_format = VideoParams::kFormatInvalid, + ColorProcessorPtr force_color_output = nullptr); - virtual QFuture DownloadFrame(FramePtr frame, const QByteArray &hash) = 0; + virtual void DownloadFrame(QThread* thread, FramePtr frame, const QByteArray &hash); - virtual void FrameDownloaded(const QByteArray& hash, const std::list& times, qint64 job_time) = 0; + virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times, qint64 job_time) = 0; virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0; ViewerOutput* viewer() const { - return backend_->GetViewerNode(); + return viewer_; } - VideoParams video_params() const + const VideoParams& video_params() const { - return backend_->GetVideoParams(); + return video_params_; } - AudioParams audio_params() const + const AudioParams& audio_params() const { - return backend_->GetAudioParams(); + return audio_params_; } - RenderBackend* backend() + virtual void CancelEvent() override { - return backend_; + finished_watcher_mutex_.lock(); + finished_watcher_wait_cond_.wakeAll(); + finished_watcher_mutex_.unlock(); + } + + virtual bool TwoStepFrameRendering() const + { + return true; } private: - RenderBackend* backend_; + RenderTicketWatcher* CreateWatcher(QThread *thread); + + void IncrementRunningTickets(); + + ViewerOutput* viewer_; + + VideoParams video_params_; + + AudioParams audio_params_; + + QVector running_watchers_; + std::list finished_watchers_; + int running_tickets_; + QMutex finished_watcher_mutex_; + QWaitCondition finished_watcher_wait_cond_; + +private slots: + void TicketDone(RenderTicketWatcher *watcher); }; diff --git a/app/threading/CMakeLists.txt b/app/threading/CMakeLists.txt new file mode 100644 index 000000000..b7169048c --- /dev/null +++ b/app/threading/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 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} + threading/threadticket.cpp + threading/threadticket.h + threading/threadticketwatcher.cpp + threading/threadticketwatcher.h + threading/threadpool.cpp + threading/threadpool.h + PARENT_SCOPE +) diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp new file mode 100644 index 000000000..9e7f26206 --- /dev/null +++ b/app/threading/threadpool.cpp @@ -0,0 +1,138 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#include "threadpool.h" + +OLIVE_NAMESPACE_ENTER + +ThreadPool::ThreadPool(QThread::Priority priority, int threads, QObject *parent) : + QObject(parent) +{ + all_threads_.resize(threads ? threads : QThread::idealThreadCount()); + + // Create threads + for (int i=0; istart(priority); + } +} + +ThreadPool::~ThreadPool() +{ + foreach (ThreadPoolThread* thread, all_threads_) { + thread->Cancel(); + thread->wait(); + delete thread; + } +} + +void ThreadPool::AddTicket(RenderTicketPtr ticket, bool prioritize) +{ + if (prioritize) { + ticket_queue_.push_front(ticket); + } else { + ticket_queue_.push_back(ticket); + } + + RunNext(); +} + +void ThreadPool::RunNext() +{ + while (!ticket_queue_.empty() && !available_threads_.empty()) { + // Run function + RenderTicketPtr ticket = ticket_queue_.front(); + ticket_queue_.pop_front(); + + if (!ticket->WasCancelled()) { + ThreadPoolThread* thread = available_threads_.front(); + available_threads_.pop_front(); + + // Run the ticket in the thread, which actually just calls our virtual function RunTicket + thread->RunTicket(ticket); + } + } +} + +void ThreadPool::ThreadDone() +{ + ThreadPoolThread* thread = static_cast(sender()); + + available_threads_.push_back(thread); + + RunNext(); +} + +ThreadPoolThread::ThreadPoolThread(ThreadPool *parent) +{ + pool_ = parent; + + // Ensures mutex is definitely locked by the time the thread is running + mutex_.lock(); +} + +ThreadPoolThread::~ThreadPoolThread() +{ + mutex_.unlock(); +} + +void ThreadPoolThread::RunTicket(RenderTicketPtr ticket) +{ + mutex_.lock(); + ticket_ = ticket; + wait_cond_.wakeAll(); + mutex_.unlock(); +} + +void ThreadPoolThread::run() +{ + while (true) { + wait_cond_.wait(&mutex_); + + if (ticket_) { + pool_->RunTicket(ticket_); + ticket_ = nullptr; + } + + if (IsCancelled()) { + break; + } else { + emit Done(); + } + } +} + +void ThreadPoolThread::CancelEvent() +{ + wait_cond_.wakeAll(); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h new file mode 100644 index 000000000..4c9f8d3af --- /dev/null +++ b/app/threading/threadpool.h @@ -0,0 +1,93 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef THREADPOOL_H +#define THREADPOOL_H + +#include + +#include "common/cancelableobject.h" +#include "threading/threadticket.h" + +OLIVE_NAMESPACE_ENTER + +class ThreadPoolThread; + +class ThreadPool : public QObject +{ + Q_OBJECT +public: + ThreadPool(QThread::Priority priority = QThread::InheritPriority, int threads = 0, QObject* parent = nullptr); + + virtual ~ThreadPool() override; + + RenderTicketPtr Queue(); + + virtual void RunTicket(RenderTicketPtr ticket) const = 0; + +public slots: + void AddTicket(OLIVE_NAMESPACE::RenderTicketPtr ticket, bool prioritize = false); + +private: + void RunNext(); + + QVector all_threads_; + + std::list available_threads_; + + std::list ticket_queue_; + +private slots: + void ThreadDone(); + +}; + +class ThreadPoolThread : public QThread, public CancelableObject +{ + Q_OBJECT +public: + ThreadPoolThread(ThreadPool* parent); + + virtual ~ThreadPoolThread() override; + + void RunTicket(RenderTicketPtr ticket); + +protected: + virtual void run() override; + + virtual void CancelEvent() override; + +signals: + void Done(); + +private: + ThreadPool* pool_; + + RenderTicketPtr ticket_; + + QMutex mutex_; + + QWaitCondition wait_cond_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // THREADPOOL_H diff --git a/app/render/backend/renderticket.cpp b/app/threading/threadticket.cpp similarity index 60% rename from app/render/backend/renderticket.cpp rename to app/threading/threadticket.cpp index 027683cd1..e87b1c57e 100644 --- a/app/render/backend/renderticket.cpp +++ b/app/threading/threadticket.cpp @@ -18,17 +18,16 @@ ***/ -#include "renderticket.h" +#include "threadticket.h" OLIVE_NAMESPACE_ENTER -RenderTicket::RenderTicket(Type type, const QVariant &time) : +RenderTicket::RenderTicket() : + started_(false), finished_(false), - cancelled_(false), - time_(time), - type_(type), - job_time_(0) + cancelled_(false) { + SetJobTime(); } void RenderTicket::WaitForFinished() @@ -42,12 +41,10 @@ void RenderTicket::WaitForFinished() QVariant RenderTicket::Get() { - QMutexLocker locker(&lock_); - - if (!finished_) { - wait_.wait(&lock_); - } + WaitForFinished(); + // We don't have to mutex around this because there is no way to write to `result_` after + // the ticket has finished and the above function blocks the calling thread until it is finished return result_; } @@ -73,32 +70,55 @@ bool RenderTicket::WasCancelled() return cancelled_; } -void RenderTicket::Finish(QVariant result) +void RenderTicket::Start() { QMutexLocker locker(&lock_); - finished_ = true; - result_ = result; + if (!started_ && !finished_) { + started_ = true; + } +} - wait_.wakeAll(); +void RenderTicket::Finish(QVariant result, bool cancelled) +{ + QMutexLocker locker(&lock_); - locker.unlock(); + if (!started_) { + qWarning() << "Tried to finish a ticket that hadn't started"; + } else if (finished_) { + // Do nothing + return; + } else { + finished_ = true; + cancelled_ = cancelled; - emit Finished(); + result_ = result; + + wait_.wakeAll(); + + locker.unlock(); + + emit Finished(); + } } void RenderTicket::Cancel() { QMutexLocker locker(&lock_); - finished_ = true; - cancelled_ = true; + if (!finished_) { + cancelled_ = true; - wait_.wakeAll(); + if (!started_) { + finished_ = true; - locker.unlock(); + wait_.wakeAll(); - emit Finished(); + locker.unlock(); + + emit Finished(); + } + } } OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderticket.h b/app/threading/threadticket.h similarity index 84% rename from app/render/backend/renderticket.h rename to app/threading/threadticket.h index f4fca0c6b..7bb644640 100644 --- a/app/render/backend/renderticket.h +++ b/app/threading/threadticket.h @@ -28,6 +28,7 @@ #include "codec/frame.h" #include "codec/samplebuffer.h" #include "common/timerange.h" +#include "node/output/viewer/viewer.h" OLIVE_NAMESPACE_ENTER @@ -35,13 +36,7 @@ class RenderTicket : public QObject { Q_OBJECT public: - enum Type { - kTypeHash, - kTypeVideo, - kTypeAudio - }; - - RenderTicket(Type type, const QVariant& time); + RenderTicket(); qint64 GetJobTime() const { @@ -53,16 +48,6 @@ public: job_time_ = QDateTime::currentMSecsSinceEpoch(); } - const QVariant& GetTime() const - { - return time_; - } - - Type GetType() const - { - return type_; - } - void WaitForFinished(); QVariant Get(); @@ -76,7 +61,9 @@ public: return &lock_; } - void Finish(QVariant result); + void Start(); + + void Finish(QVariant result, bool cancelled); void Cancel(); @@ -84,6 +71,8 @@ signals: void Finished(); private: + bool started_; + bool finished_; bool cancelled_; @@ -94,10 +83,6 @@ private: QWaitCondition wait_; - QVariant time_; - - Type type_; - qint64 job_time_; }; diff --git a/app/render/backend/renderticketwatcher.cpp b/app/threading/threadticketwatcher.cpp similarity index 77% rename from app/render/backend/renderticketwatcher.cpp rename to app/threading/threadticketwatcher.cpp index d5860311d..247e9b69a 100644 --- a/app/render/backend/renderticketwatcher.cpp +++ b/app/threading/threadticketwatcher.cpp @@ -18,7 +18,7 @@ ***/ -#include "renderticketwatcher.h" +#include "threadticketwatcher.h" OLIVE_NAMESPACE_ENTER @@ -30,8 +30,15 @@ RenderTicketWatcher::RenderTicketWatcher(QObject *parent) : void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket) { - // Ensure that a ticket has NOT already been set and that this ticket is NOT NULL - Q_ASSERT(!ticket_ && ticket); + if (ticket_) { + qCritical() << "Tried to set a ticket on a RenderTicketWatcher twice"; + return; + } + + if (!ticket) { + qCritical() << "Tried to set a null ticket on a RenderTicketWatcher"; + return; + } ticket_ = ticket; @@ -39,9 +46,9 @@ void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket) if (ticket_->IsFinished(false)) { locker.unlock(); - emit Finished(); + emit Finished(this); } else { - connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::Finished); + connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::TicketFinished); } } @@ -79,4 +86,16 @@ QVariant RenderTicketWatcher::Get() } } +void RenderTicketWatcher::Cancel() +{ + if (ticket_) { + ticket_->Cancel(); + } +} + +void RenderTicketWatcher::TicketFinished() +{ + emit Finished(this); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderticketwatcher.h b/app/threading/threadticketwatcher.h similarity index 91% rename from app/render/backend/renderticketwatcher.h rename to app/threading/threadticketwatcher.h index fba301a3b..7684f64b6 100644 --- a/app/render/backend/renderticketwatcher.h +++ b/app/threading/threadticketwatcher.h @@ -21,7 +21,7 @@ #ifndef RENDERTICKETWATCHER_H #define RENDERTICKETWATCHER_H -#include "renderticket.h" +#include "threadticket.h" OLIVE_NAMESPACE_ENTER @@ -38,6 +38,8 @@ public: void SetTicket(RenderTicketPtr ticket); + void Cancel(); + bool WasCancelled(); bool IsFinished(); @@ -47,9 +49,11 @@ public: QVariant Get(); signals: - void Finished(); + void Finished(RenderTicketWatcher* watcher); private: + void TicketFinished(); + RenderTicketPtr ticket_; }; diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 5ee6f031d..3aa43c929 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -72,11 +72,12 @@ TimelineMarkerList::~TimelineMarkerList() qDeleteAll(markers_); } -void TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name) +TimelineMarker* TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name) { TimelineMarker* m = new TimelineMarker(time, name); markers_.append(m); emit MarkerAdded(m); + return m; } void TimelineMarkerList::RemoveMarker(TimelineMarker *marker) diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 09d005c51..16a79abec 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -61,7 +61,7 @@ public: virtual ~TimelineMarkerList() override; - void AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString()); + TimelineMarker *AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString()); void RemoveMarker(TimelineMarker* marker); diff --git a/app/widget/colorwheel/colorgradientwidget.cpp b/app/widget/colorwheel/colorgradientwidget.cpp index 82883eb69..e7b0579d9 100644 --- a/app/widget/colorwheel/colorgradientwidget.cpp +++ b/app/widget/colorwheel/colorgradientwidget.cpp @@ -76,7 +76,7 @@ void ColorGradientWidget::paintEvent(QPaintEvent *e) p.setPen(QPen(GetUISelectorColor(), qMax(1, selector_radius / 2))); p.setBrush(Qt::NoBrush); - float clamped_val = clamp(val_, 0.0f, 1.0f); + double clamped_val = clamp(val_, 0.0, 1.0); if (orientation_ == Qt::Horizontal) { p.drawRect(qRound(width() * (1.0 - clamped_val)) - selector_radius, 0, selector_radius * 2, height() - 1); @@ -87,7 +87,7 @@ void ColorGradientWidget::paintEvent(QPaintEvent *e) void ColorGradientWidget::SelectedColorChangedEvent(const Color &c, bool external) { - float hue, sat; + double hue, sat; c.toHsv(&hue, &sat, &val_); diff --git a/app/widget/colorwheel/colorgradientwidget.h b/app/widget/colorwheel/colorgradientwidget.h index 725b662fe..26f162912 100644 --- a/app/widget/colorwheel/colorgradientwidget.h +++ b/app/widget/colorwheel/colorgradientwidget.h @@ -50,7 +50,7 @@ private: Color end_; - float val_; + double val_; }; diff --git a/app/widget/colorwheel/colorswatchwidget.cpp b/app/widget/colorwheel/colorswatchwidget.cpp index f243e46e2..be6722a83 100644 --- a/app/widget/colorwheel/colorswatchwidget.cpp +++ b/app/widget/colorwheel/colorswatchwidget.cpp @@ -22,8 +22,6 @@ #include -#include "render/backend/opengl/openglrenderfunctions.h" - OLIVE_NAMESPACE_ENTER ColorSwatchWidget::ColorSwatchWidget(QWidget *parent) : diff --git a/app/widget/colorwheel/colorswatchwidget.h b/app/widget/colorwheel/colorswatchwidget.h index de8c1a828..dc0a43f20 100644 --- a/app/widget/colorwheel/colorswatchwidget.h +++ b/app/widget/colorwheel/colorswatchwidget.h @@ -23,7 +23,6 @@ #include -#include "render/backend/opengl/openglshader.h" #include "render/color.h" #include "render/colorprocessor.h" diff --git a/app/widget/colorwheel/colorwheelwidget.cpp b/app/widget/colorwheel/colorwheelwidget.cpp index 4edf774f3..f5e5e2a32 100644 --- a/app/widget/colorwheel/colorwheelwidget.cpp +++ b/app/widget/colorwheel/colorwheelwidget.cpp @@ -121,7 +121,7 @@ void ColorWheelWidget::SelectedColorChangedEvent(const Color &c, bool external) { if (external) { force_redraw_ = true; - val_ = clamp(c.value(), 0.0f, 1.0f); + val_ = clamp(c.value(), 0.0, 1.0); } } @@ -159,7 +159,7 @@ Color ColorWheelWidget::GetColorFromTriangle(const ColorWheelWidget::Triangle &t QPoint ColorWheelWidget::GetCoordsFromColor(const Color &c) const { - float hue, sat, val; + double hue, sat, val; c.toHsv(&hue, &sat, &val); qreal hypotenuse = sat * GetRadius(); diff --git a/app/widget/colorwheel/colorwheelwidget.h b/app/widget/colorwheel/colorwheelwidget.h index 17c739886..78ddb26c6 100644 --- a/app/widget/colorwheel/colorwheelwidget.h +++ b/app/widget/colorwheel/colorwheelwidget.h @@ -24,7 +24,6 @@ #include #include "colorswatchwidget.h" -#include "render/backend/opengl/openglshader.h" #include "render/color.h" OLIVE_NAMESPACE_ENTER diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index a9b43d4a9..c1ece5146 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -89,7 +89,7 @@ void CurveView::ConnectInput(NodeInput *input) void CurveView::DisconnectNode(Node *node) { - QList inputs = node->GetInputsIncludingArrays(); + QVector inputs = node->GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { DisconnectInput(i); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 41d2a88ef..7a80a8dfc 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -131,7 +131,7 @@ void CurveWidget::DeleteSelected() view_->DeleteSelected(); } -void CurveWidget::SetNodes(const QList &nodes) +void CurveWidget::SetNodes(const QVector &nodes) { tree_view_->SetNodes(nodes); @@ -216,7 +216,7 @@ void CurveWidget::UpdateBridgeTime(const int64_t ×tamp) void CurveWidget::ConnectNode(Node *n) { - QList inputs = n->GetInputsIncludingArrays(); + QVector inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { if (tree_view_->IsInputEnabled(i)) { diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 253ca6a71..530da8ac2 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -49,7 +49,7 @@ public: void DeleteSelected(); public slots: - void SetNodes(const QList& nodes); + void SetNodes(const QVector &nodes); protected: virtual void TimeChangedEvent(const int64_t &) override; @@ -85,9 +85,7 @@ private: NodeParamViewKeyframeControl* key_control_; - QList checkboxes_; - - QList nodes_; + QVector nodes_; private slots: void SelectionChanged(); diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index b53a0c236..80c27fd91 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -76,7 +76,7 @@ void KeyframeViewBase::DeleteSelected() void KeyframeViewBase::RemoveKeyframesOfNode(Node *n) { - QList inputs = n->GetInputsIncludingArrays(); + QVector inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { RemoveKeyframesOfInput(i); diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 30574363a..ce1528f7b 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -20,21 +20,59 @@ #include "manageddisplay.h" +#include #include +#include "render/opengl/openglrenderer.h" +#include "render/rendermanager.h" + OLIVE_NAMESPACE_ENTER ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : - QOpenGLWidget(parent), + QWidget(parent), color_manager_(nullptr), color_service_(nullptr) { setContextMenuPolicy(Qt::CustomContextMenu); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + // Create OpenGL widget + inner_widget_ = new ManagedDisplayWidgetOpenGL(); + connect(static_cast(inner_widget_), + &ManagedDisplayWidgetOpenGL::OnInit, + this, &ManagedDisplayWidget::OnInit, Qt::DirectConnection); + connect(static_cast(inner_widget_), + &ManagedDisplayWidgetOpenGL::OnDestroy, + this, &ManagedDisplayWidget::OnDestroy, Qt::DirectConnection); + connect(static_cast(inner_widget_), + &ManagedDisplayWidgetOpenGL::OnPaint, + this, &ManagedDisplayWidget::OnPaint, Qt::DirectConnection); + connect(static_cast(inner_widget_), + &ManagedDisplayWidgetOpenGL::frameSwapped, + this, &ManagedDisplayWidget::frameSwapped, Qt::DirectConnection); + + // Create OpenGL renderer + attached_renderer_ = new OpenGLRenderer(this); + } else { + inner_widget_ = nullptr; + } + + layout->addWidget(inner_widget_); } ManagedDisplayWidget::~ManagedDisplayWidget() { - ContextCleanup(); + OnDestroy(); + + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + disconnect(static_cast(inner_widget_), + &ManagedDisplayWidgetOpenGL::OnDestroy, + this, &ManagedDisplayWidget::OnDestroy); + } } void ManagedDisplayWidget::ConnectColorManager(ColorManager *color_manager) @@ -102,20 +140,11 @@ void ManagedDisplayWidget::ColorConfigChanged() SetColorTransform(color_manager_->GetCompliantColorSpace(color_transform_, true)); } -OpenGLColorProcessorPtr ManagedDisplayWidget::color_service() +ColorProcessorPtr ManagedDisplayWidget::color_service() { return color_service_; } -void ManagedDisplayWidget::ContextCleanup() -{ - makeCurrent(); - - color_service_ = nullptr; - - doneCurrent(); -} - void ManagedDisplayWidget::ShowDefaultContextMenu() { Menu m(this); @@ -172,22 +201,27 @@ void ManagedDisplayWidget::MenuColorspaceSelect(QAction *action) SetColorTransform(color_manager()->GetCompliantColorSpace(ColorTransform(action->data().toString()))); } -void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform) +void ManagedDisplayWidget::OnDestroy() { - makeCurrent(); - - color_transform_ = transform; - SetupColorProcessor(); - ColorProcessorChangedEvent(); - - doneCurrent(); + attached_renderer_->Destroy(); } -void ManagedDisplayWidget::initializeGL() +void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform) { + color_transform_ = transform; + SetupColorProcessor(); - connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ManagedDisplayWidget::ContextCleanup, Qt::DirectConnection); + ColorProcessorChangedEvent(); +} + +void ManagedDisplayWidget::OnInit() +{ + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + QOpenGLContext* context = static_cast(inner_widget_)->context(); + static_cast(attached_renderer_)->Init(context); + static_cast(attached_renderer_)->PostInit(); + } } void ManagedDisplayWidget::EnableDefaultContextMenu() @@ -200,6 +234,27 @@ void ManagedDisplayWidget::ColorProcessorChangedEvent() update(); } +void ManagedDisplayWidget::makeCurrent() +{ + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + static_cast(inner_widget_)->makeCurrent(); + } +} + +void ManagedDisplayWidget::doneCurrent() +{ + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + static_cast(inner_widget_)->doneCurrent(); + } +} + +void ManagedDisplayWidget::update() +{ + if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { + static_cast(inner_widget_)->update(); + } +} + Menu* ManagedDisplayWidget::GetDisplayMenu(QMenu* parent, bool auto_connect) { QStringList displays = color_manager()->ListAvailableDisplays(); @@ -269,37 +324,25 @@ Menu* ManagedDisplayWidget::GetLookMenu(QMenu* parent, bool auto_connect) void ManagedDisplayWidget::SetupColorProcessor() { - if (!context()) { - return; - } - color_service_ = nullptr; if (color_manager_) { // (Re)create color processor - try { - - color_service_ = OpenGLColorProcessor::Create(color_manager_, - color_manager_->GetReferenceColorSpace(), - color_transform_); - - color_service_->Enable(context(), true); - + color_service_ = ColorProcessor::Create(color_manager_, + color_manager_->GetReferenceColorSpace(), + color_transform_); } catch (OCIO::Exception& e) { - QMessageBox::critical(this, tr("OpenColorIO Error"), tr("Failed to set color configuration: %1").arg(e.what()), QMessageBox::Ok); - } - } else { color_service_ = nullptr; } - emit ColorProcessorChanged(std::static_pointer_cast(color_service_)); + emit ColorProcessorChanged(color_service_); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index c03d5ea71..dc10913dd 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -21,15 +21,58 @@ #ifndef MANAGEDDISPLAYOBJECT_H #define MANAGEDDISPLAYOBJECT_H +#include #include -#include "render/backend/opengl/openglcolorprocessor.h" #include "render/colormanager.h" +#include "render/renderer.h" #include "widget/menu/menu.h" OLIVE_NAMESPACE_ENTER -class ManagedDisplayWidget : public QOpenGLWidget +class ManagedDisplayWidgetOpenGL : public QOpenGLWidget +{ + Q_OBJECT +public: + ManagedDisplayWidgetOpenGL(QWidget* parent = nullptr) : + QOpenGLWidget(parent) + { + } + +signals: + void OnInit(); + + void OnPaint(); + + void OnDestroy(); + +protected: + virtual void initializeGL() override + { + connect(context(), &QOpenGLContext::aboutToBeDestroyed, + this, &ManagedDisplayWidgetOpenGL::OnDestroy); + + emit OnInit(); + } + + virtual void paintGL() override + { + emit OnPaint(); + } + +private slots: + void DestroyListener() + { + makeCurrent(); + + emit OnDestroy(); + + doneCurrent(); + } + +}; + +class ManagedDisplayWidget : public QWidget { Q_OBJECT public: @@ -72,6 +115,11 @@ public: */ Menu* GetLookMenu(QMenu* parent, bool auto_connect = true); + /** + * @brief Passes update signal through to inner widget + */ + void update(); + public slots: /** * @brief Replaces the color transform with a new one @@ -94,16 +142,13 @@ signals: */ void ColorManagerChanged(ColorManager* color_manager); + void frameSwapped(); + protected: /** * @brief Provides access to the color processor (nullptr if none is set) */ - OpenGLColorProcessorPtr color_service(); - - /** - * @brief Override when setting up OpenGL context - */ - virtual void initializeGL() override; + ColorProcessorPtr color_service(); /** * @brief Enables a context menu that allows simple access to the DVL pipeline @@ -117,6 +162,36 @@ protected: */ virtual void ColorProcessorChangedEvent(); + Renderer* renderer() const + { + return attached_renderer_; + } + + void makeCurrent(); + + void doneCurrent(); + + QWidget* inner_widget() const + { + return inner_widget_; + } + +protected slots: + /** + * @brief Called whenever the internal rendering context has been created + */ + virtual void OnInit(); + + /** + * @brief Called while the internal rendering context is being rendered + */ + virtual void OnPaint() = 0; + + /** + * @brief Called just before the internal rendering context is destroyed + */ + virtual void OnDestroy(); + private: /** * @brief Call this if this user has selected a different display/view/look to recreate the processor @@ -128,6 +203,16 @@ private: */ void ClearOCIOLutTexture(); + /** + * @brief Main drawing surface abstraction + */ + QWidget* inner_widget_; + + /** + * @brief Renderer abstraction + */ + Renderer* attached_renderer_; + /** * @brief Connected color manager */ @@ -136,7 +221,7 @@ private: /** * @brief Color management service */ - OpenGLColorProcessorPtr color_service_; + ColorProcessorPtr color_service_; /** * @brief Internal color transform storage @@ -149,11 +234,6 @@ private slots: */ void ColorConfigChanged(); - /** - * @brief Cleans up resources if context is about to be destroyed - */ - void ContextCleanup(); - /** * @brief The default context menu shown */ diff --git a/app/widget/nodecopypaste/nodecopypaste.cpp b/app/widget/nodecopypaste/nodecopypaste.cpp index 914e9f32c..a35db606f 100644 --- a/app/widget/nodecopypaste/nodecopypaste.cpp +++ b/app/widget/nodecopypaste/nodecopypaste.cpp @@ -29,7 +29,7 @@ OLIVE_NAMESPACE_ENTER -void NodeCopyPasteWidget::CopyNodesToClipboard(const QList &nodes, void *userdata) +void NodeCopyPasteWidget::CopyNodesToClipboard(const QVector &nodes, void *userdata) { QString copy_str; @@ -56,17 +56,17 @@ void NodeCopyPasteWidget::CopyNodesToClipboard(const QList &nodes, void Core::CopyStringToClipboard(copy_str); } -QList NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUndoCommand* command, void *userdata) +QVector NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUndoCommand* command, void *userdata) { QString clipboard = Core::PasteStringFromClipboard(); if (clipboard.isEmpty()) { - return QList(); + return QVector(); } QXmlStreamReader reader(clipboard); - QList pasted_nodes; + QVector pasted_nodes; XMLNodeData xml_node_data; while (XMLReadNextStartElement(&reader)) { @@ -100,7 +100,7 @@ QList NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUnd if (pasted_nodes.isEmpty()) { // If we passed through the whole string and there were no nodes, it must not be data for us after all - return QList(); + return QVector(); } // If we have some nodes AND the XML data was malformed, the user should probably know @@ -116,7 +116,7 @@ QList NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUnd QCoreApplication::translate("NodeCopyPasteWidget", "Failed to paste nodes: %1").arg(reader.errorString()), QMessageBox::Ok); - return QList(); + return QVector(); } // Add all nodes to graph diff --git a/app/widget/nodecopypaste/nodecopypaste.h b/app/widget/nodecopypaste/nodecopypaste.h index 9791ae99f..0691692ef 100644 --- a/app/widget/nodecopypaste/nodecopypaste.h +++ b/app/widget/nodecopypaste/nodecopypaste.h @@ -35,9 +35,9 @@ public: NodeCopyPasteWidget() = default; protected: - void CopyNodesToClipboard(const QList& nodes, void* userdata = nullptr); + void CopyNodesToClipboard(const QVector &nodes, void* userdata = nullptr); - QList PasteNodesFromClipboard(Sequence *graph, QUndoCommand *command, void* userdata = nullptr); + QVector PasteNodesFromClipboard(Sequence *graph, QUndoCommand *command, void* userdata = nullptr); virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b07031ea6..5cdc32452 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -135,7 +135,7 @@ NodeParamView::NodeParamView(QWidget *parent) : &NodeParamView::FocusChanged); } -void NodeParamView::SelectNodes(const QList &nodes) +void NodeParamView::SelectNodes(const QVector &nodes) { active_nodes_.append(nodes); @@ -185,7 +185,7 @@ void NodeParamView::SelectNodes(const QList &nodes) } } -void NodeParamView::DeselectNodes(const QList &nodes) +void NodeParamView::DeselectNodes(const QVector &nodes) { // Remove item from map and delete the widget bool changes_made = false; @@ -283,8 +283,8 @@ void NodeParamView::QueueKeyframePositionUpdate() void NodeParamView::SignalNodeOrder() { // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) - QList nodes; - QList item_ys; + QVector nodes; + QVector item_ys; for (auto it=items_.cbegin(); it!=items_.cend(); it++) { int item_y = it.value()->pos().y(); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index f9fee45db..a8e43b0bb 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -60,8 +60,8 @@ class NodeParamView : public TimeBasedWidget public: NodeParamView(QWidget* parent = nullptr); - void SelectNodes(const QList& nodes); - void DeselectNodes(const QList& nodes); + void SelectNodes(const QVector &nodes); + void DeselectNodes(const QVector& nodes); const QMap& GetItemMap() const { @@ -75,9 +75,9 @@ public: signals: void InputDoubleClicked(NodeInput* input); - void RequestSelectNode(const QList& target); + void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QList& nodes); + void NodeOrderChanged(const QVector& nodes); void FocusedNodeChanged(Node* n); @@ -113,9 +113,9 @@ private: // docking windows QMainWindow* param_widget_area_; - QList pinned_nodes_; + QVector pinned_nodes_; - QList active_nodes_; + QVector active_nodes_; QMap node_expanded_state_; diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 2bc8972fa..4731ef782 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -91,7 +91,7 @@ signals: void InputDoubleClicked(NodeInput* input); - void RequestSelectNode(const QList& node); + void RequestSelectNode(const QVector& node); private: void UpdateUIForEdgeConnection(NodeInput* input); @@ -161,7 +161,7 @@ signals: void InputDoubleClicked(NodeInput* input); - void RequestSelectNode(const QList& node); + void RequestSelectNode(const QVector& node); void PinToggled(bool e); diff --git a/app/widget/nodeparamview/nodeparamviewrichtext.cpp b/app/widget/nodeparamview/nodeparamviewrichtext.cpp index 476c7cc76..9f30be45d 100644 --- a/app/widget/nodeparamview/nodeparamviewrichtext.cpp +++ b/app/widget/nodeparamview/nodeparamviewrichtext.cpp @@ -34,19 +34,20 @@ NodeParamViewRichText::NodeParamViewRichText(QWidget *parent) : QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); - line_edit_ = new QLineEdit(); - connect(line_edit_, &QLineEdit::textEdited, this, &NodeParamViewRichText::textEdited); + line_edit_ = new QTextEdit(); + connect(line_edit_, &QTextEdit::textChanged, this, &NodeParamViewRichText::InnerWidgetTextChanged); layout->addWidget(line_edit_); QPushButton* edit_btn = new QPushButton(); edit_btn->setIcon(icon::ToolEdit); + edit_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); layout->addWidget(edit_btn); connect(edit_btn, &QPushButton::clicked, this, &NodeParamViewRichText::ShowRichTextDialog); } void NodeParamViewRichText::ShowRichTextDialog() { - RichTextDialog d(line_edit_->text(), this); + RichTextDialog d(this->text(), this); if (d.exec() == QDialog::Accepted) { QString s = d.text(); @@ -55,4 +56,9 @@ void NodeParamViewRichText::ShowRichTextDialog() } } +void NodeParamViewRichText::InnerWidgetTextChanged() +{ + emit textEdited(this->text()); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewrichtext.h b/app/widget/nodeparamview/nodeparamviewrichtext.h index 974a742b4..9f09576f3 100644 --- a/app/widget/nodeparamview/nodeparamviewrichtext.h +++ b/app/widget/nodeparamview/nodeparamviewrichtext.h @@ -21,7 +21,7 @@ #ifndef NODEPARAMVIEWRICHTEXT_H #define NODEPARAMVIEWRICHTEXT_H -#include +#include #include #include "common/define.h" @@ -36,31 +36,42 @@ public: QString text() const { - return line_edit_->text(); + return line_edit_->toPlainText().replace('\n', QStringLiteral("
")); } public slots: - void setText(const QString &s) + void setText(QString s) { - line_edit_->setText(s); + line_edit_->blockSignals(true); + line_edit_->setPlainText(s.replace(QStringLiteral("
"), QStringLiteral("\n"))); + line_edit_->blockSignals(false); } void setTextPreservingCursor(const QString &s) { - int cursor_pos = line_edit_->cursorPosition(); - line_edit_->setText(s); - line_edit_->setCursorPosition(cursor_pos); + // Save cursor position + int cursor_pos = line_edit_->textCursor().position(); + + // Set text + this->setText(s); + + // Get new text cursor + QTextCursor c = line_edit_->textCursor(); + c.setPosition(cursor_pos); + line_edit_->setTextCursor(c); } signals: void textEdited(const QString &); private: - QLineEdit* line_edit_; + QTextEdit* line_edit_; private slots: void ShowRichTextDialog(); + void InnerWidgetTextChanged(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetabletraverser.cpp b/app/widget/nodetableview/nodetabletraverser.cpp index 156409224..69df8e142 100644 --- a/app/widget/nodetableview/nodetabletraverser.cpp +++ b/app/widget/nodetableview/nodetabletraverser.cpp @@ -30,6 +30,7 @@ QVariant NodeTableTraverser::ProcessVideoFootage(StreamPtr stream, const rationa video_stream->height(), video_stream->timebase(), video_stream->format(), + video_stream->channel_count(), video_stream->pixel_aspect_ratio())); } @@ -39,7 +40,7 @@ QVariant NodeTableTraverser::ProcessAudioFootage(StreamPtr stream, const TimeRan return QVariant::fromValue(AudioParams(audio_stream->sample_rate(), audio_stream->channel_layout(), - SampleFormat::kInternalFormat)); + AudioParams::kInternalFormat)); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp index 7696ace3b..80f6b8fd4 100644 --- a/app/widget/nodetableview/nodetableview.cpp +++ b/app/widget/nodetableview/nodetableview.cpp @@ -40,7 +40,7 @@ NodeTableView::NodeTableView(QWidget* parent) : tr("A/W")}); } -void NodeTableView::SelectNodes(const QList &nodes) +void NodeTableView::SelectNodes(const QVector &nodes) { foreach (Node* n, nodes) { QTreeWidgetItem* top_item = new QTreeWidgetItem(); @@ -53,7 +53,7 @@ void NodeTableView::SelectNodes(const QList &nodes) SetTime(last_time_); } -void NodeTableView::DeselectNodes(const QList &nodes) +void NodeTableView::DeselectNodes(const QVector &nodes) { foreach (Node* n, nodes) { delete top_level_item_map_.take(n); @@ -145,10 +145,7 @@ void NodeTableView::SetTime(const rational &time) case NodeParam::kTexture: { // NodeTableTraverser puts video params in here - VideoParams p = value.data().value(); - int channel_count = PixelFormat::ChannelCount(p.format()); - - for (int k=0;ksetItemWidget(sub_item, 2 + k, new QCheckBox()); } break; diff --git a/app/widget/nodetableview/nodetableview.h b/app/widget/nodetableview/nodetableview.h index 7aa6e3eeb..de5d920d3 100644 --- a/app/widget/nodetableview/nodetableview.h +++ b/app/widget/nodetableview/nodetableview.h @@ -32,9 +32,9 @@ class NodeTableView : public QTreeWidget public: NodeTableView(QWidget* parent = nullptr); - void SelectNodes(const QList& nodes); + void SelectNodes(const QVector &nodes); - void DeselectNodes(const QList& nodes); + void DeselectNodes(const QVector& nodes); void SetTime(const rational& time); diff --git a/app/widget/nodetableview/nodetablewidget.h b/app/widget/nodetableview/nodetablewidget.h index 1eeecbfcb..2b499b629 100644 --- a/app/widget/nodetableview/nodetablewidget.h +++ b/app/widget/nodetableview/nodetablewidget.h @@ -31,12 +31,12 @@ class NodeTableWidget : public TimeBasedWidget public: NodeTableWidget(QWidget* parent = nullptr); - void SelectNodes(const QList& nodes) + void SelectNodes(const QVector& nodes) { view_->SelectNodes(nodes); } - void DeselectNodes(const QList& nodes) + void DeselectNodes(const QVector& nodes) { view_->DeselectNodes(nodes); } diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index b02a707d9..324dd78c8 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -21,7 +21,7 @@ bool NodeTreeView::IsInputEnabled(NodeInput *i) const return !disabled_inputs_.contains(i); } -void NodeTreeView::SetNodes(const QList &nodes) +void NodeTreeView::SetNodes(const QVector &nodes) { nodes_ = nodes; @@ -34,7 +34,7 @@ void NodeTreeView::SetNodes(const QList &nodes) node_item->setData(0, kItemType, kItemTypeNode); node_item->setData(0, kItemPointer, reinterpret_cast(n)); - QList inputs = n->GetInputsIncludingArrays(); + QVector inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { if (only_show_keyframable_ && !i->is_keyframable()) { continue; diff --git a/app/widget/nodetreeview/nodetreeview.h b/app/widget/nodetreeview/nodetreeview.h index 2b221a68c..65d87bbda 100644 --- a/app/widget/nodetreeview/nodetreeview.h +++ b/app/widget/nodetreeview/nodetreeview.h @@ -23,7 +23,7 @@ public: } public slots: - void SetNodes(const QList& nodes); + void SetNodes(const QVector &nodes); signals: void NodeEnableChanged(Node* n, bool e); @@ -44,11 +44,11 @@ private: static const int kItemType = Qt::UserRole; static const int kItemPointer = Qt::UserRole + 1; - QList nodes_; + QVector nodes_; - QList disabled_nodes_; + QVector disabled_nodes_; - QList disabled_inputs_; + QVector disabled_inputs_; bool only_show_keyframable_; diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d3cfdd3b9..bfc2472aa 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -115,7 +115,7 @@ void NodeView::DeleteSelected() QUndoCommand* command = new QUndoCommand(); { - QList selected_edges = scene_.GetSelectedEdges(); + QVector selected_edges = scene_.GetSelectedEdges(); foreach (NodeEdge* edge, selected_edges) { new NodeEdgeRemoveCommand(edge->output(), edge->input(), command); @@ -124,7 +124,7 @@ void NodeView::DeleteSelected() } { - QList selected_nodes = scene_.GetSelectedNodes(); + QVector selected_nodes = scene_.GetSelectedNodes(); // Ensure no nodes are "undeletable" for (int i=0;i &nodes) +void NodeView::Select(const QVector &nodes) { if (!graph_) { return; @@ -188,7 +188,7 @@ void NodeView::Select(const QList &nodes) SceneSelectionChangedSlot(); } -void NodeView::SelectWithDependencies(QList nodes) +void NodeView::SelectWithDependencies(QVector nodes) { if (!graph_) { return; @@ -202,7 +202,7 @@ void NodeView::SelectWithDependencies(QList nodes) Select(nodes); } -void NodeView::SelectBlocks(const QList &blocks) +void NodeView::SelectBlocks(const QVector &blocks) { if (!graph_) { return; @@ -213,7 +213,7 @@ void NodeView::SelectBlocks(const QList &blocks) QueueSelectBlocksInternal(); } -void NodeView::DeselectBlocks(const QList &blocks) +void NodeView::DeselectBlocks(const QVector &blocks) { if (!graph_) { return; @@ -240,7 +240,7 @@ void NodeView::CopySelected(bool cut) return; } - QList selected = scene_.GetSelectedNodes(); + QVector selected = scene_.GetSelectedNodes(); if (selected.isEmpty()) { return; @@ -261,7 +261,7 @@ void NodeView::Paste() QUndoCommand* command = new QUndoCommand(); - QList pasted_nodes = PasteNodesFromClipboard(static_cast(graph_), command); + QVector pasted_nodes = PasteNodesFromClipboard(static_cast(graph_), command); Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -280,7 +280,7 @@ void NodeView::Duplicate() return; } - QList selected = scene_.GetSelectedNodes(); + QVector selected = scene_.GetSelectedNodes(); if (selected.isEmpty()) { return; @@ -288,43 +288,11 @@ void NodeView::Duplicate() QUndoCommand* command = new QUndoCommand(); - QList duplicated_nodes; - - foreach (Node* n, selected) { - Node* copy = n->copy(); - - Node::CopyInputs(n, copy, false); - - duplicated_nodes.append(copy); - - new NodeAddCommand(graph_, copy, command); - } - - for (int i=0;ioutput()->edges()) { - if (edge->input()->parentNode() == dst) { - new NodeEdgeAddCommand(duplicated_nodes.at(i)->output(), - duplicated_nodes.at(j)->GetInputWithID(edge->input()->id()), - command); - } - } - } - } + QVector duplicated_nodes = Node::CopyDependencyGraph(selected, command); Core::instance()->undo_stack()->pushIfHasChildren(command); - if (!duplicated_nodes.isEmpty()) { - AttachNodesToCursor(duplicated_nodes); - } + AttachNodesToCursor(duplicated_nodes); } void NodeView::ItemsChanged() @@ -488,10 +456,10 @@ void NodeView::wheelEvent(QWheelEvent *event) void NodeView::SceneSelectionChangedSlot() { - QList current_selection = scene_.GetSelectedNodes(); + QVector current_selection = scene_.GetSelectedNodes(); - QList selected; - QList deselected; + QVector selected; + QVector deselected; // Determine which nodes are newly selected if (selected_nodes_.isEmpty()) { @@ -540,7 +508,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); - QList selected = scene_.GetSelectedItems(); + QVector selected = scene_.GetSelectedItems(); if (itemAt(pos) && !selected.isEmpty()) { @@ -636,7 +604,7 @@ void NodeView::ContextMenuSetDirection(QAction *action) void NodeView::AutoPositionDescendents() { - QList selected = scene_.GetSelectedNodes(); + QVector selected = scene_.GetSelectedNodes(); foreach (Node* n, selected) { scene_.ReorganizeFrom(n); @@ -665,18 +633,18 @@ void NodeView::ContextMenuFilterChanged(QAction *action) } } -void NodeView::AttachNodesToCursor(const QList &nodes) +void NodeView::AttachNodesToCursor(const QVector &nodes) { - QList items; + QVector items(nodes.size()); - foreach (Node* p, nodes) { - items.append(scene_.NodeToUIObject(p)); + for (int i=0; i& items) +void NodeView::AttachItemsToCursor(const QVector& items) { DetachItemsFromCursor(); @@ -731,7 +699,7 @@ void NodeView::UpdateBlockFilter() bool first = true; QPointF last_bottom_right; - QList currently_visible; + QVector currently_visible; foreach (Block* b, selected_blocks_) { // Auto-position this node's dependencies @@ -741,7 +709,7 @@ void NodeView::UpdateBlockFilter() QPointF node_pos = b->GetPosition(); QRectF anchor(node_pos, node_pos); - QList deps = b->GetDependencies(); + QVector deps = b->GetDependencies(); foreach (Node* d, deps) { QPointF dep_pos = d->GetPosition(); @@ -779,8 +747,7 @@ void NodeView::UpdateBlockFilter() // ...then add its associations deps.append(temporary_association_map_[b]); - QHash >::const_iterator i; - for (i=association_map_.begin(); i!=association_map_.end(); i++) { + for (auto i=association_map_.begin(); i!=association_map_.end(); i++) { if (i.value().contains(b)) { deps.append(i.key()); } @@ -835,7 +802,7 @@ void NodeView::SelectBlocksInternal() UpdateBlockFilter(); } - QList nodes; + QVector nodes; nodes.reserve(selected_blocks_.size()); foreach (Block* b, selected_blocks_) { @@ -888,7 +855,7 @@ void NodeView::GraphEdgeAdded(NodeEdgePtr edge) Node* input_node = edge->input()->parentNode(); if (input_node->OutputsTo(static_cast(graph_)->viewer_output(), true)) { - QHash >::const_iterator i = association_map_.begin(); + auto i = association_map_.begin(); while (i != association_map_.end()) { if (input_node->InputsFrom(i.key(), true)) { @@ -917,14 +884,14 @@ void NodeView::GraphEdgeRemoved(NodeEdgePtr edge) } } - QList disconnected_nodes; + QVector disconnected_nodes; disconnected_nodes.append(output_node); disconnected_nodes.append(output_node->GetDependencies()); if (output_node->OutputsTo(static_cast(graph_)->viewer_output(), true)) { // Check if this disconnected node still has a path to the viewer somewhere else foreach (Block* b, selected_blocks_) { - QList& temp_assocs = temporary_association_map_[b]; + QVector& temp_assocs = temporary_association_map_[b]; temp_assocs.append(disconnected_nodes); } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 5bcf7799e..d05c8f899 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -58,22 +58,22 @@ public: void SelectAll(); void DeselectAll(); - void Select(const QList& nodes); - void SelectWithDependencies(QList nodes); + void Select(const QVector& nodes); + void SelectWithDependencies(QVector nodes); void CopySelected(bool cut); void Paste(); void Duplicate(); - void SelectBlocks(const QList& blocks); + void SelectBlocks(const QVector& blocks); - void DeselectBlocks(const QList& blocks); + void DeselectBlocks(const QVector& blocks); signals: - void NodesSelected(const QList& nodes); + void NodesSelected(const QVector& nodes); - void NodesDeselected(const QList& nodes); + void NodesDeselected(const QVector& nodes); protected: virtual void keyPressEvent(QKeyEvent *event) override; @@ -85,9 +85,9 @@ protected: virtual void wheelEvent(QWheelEvent* event) override; private: - void AttachNodesToCursor(const QList& nodes); + void AttachNodesToCursor(const QVector &nodes); - void AttachItemsToCursor(const QList& items); + void AttachItemsToCursor(const QVector &items); void DetachItemsFromCursor(); @@ -121,13 +121,13 @@ private: NodeViewScene scene_; - QList selected_nodes_; + QVector selected_nodes_; - QList selected_blocks_; + QVector selected_blocks_; - QHash > association_map_; + QHash > association_map_; - QHash > temporary_association_map_; + QHash > temporary_association_map_; enum FilterMode { kFilterShowAll, diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index a36657b7c..1ae231b5d 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -120,10 +120,10 @@ void NodeViewScene::SetGraph(NodeGraph *graph) graph_ = graph; } -QList NodeViewScene::GetSelectedNodes() const +QVector NodeViewScene::GetSelectedNodes() const { QHash::const_iterator iterator; - QList selected; + QVector selected; for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { if (iterator.value()->isSelected()) { @@ -134,10 +134,10 @@ QList NodeViewScene::GetSelectedNodes() const return selected; } -QList NodeViewScene::GetSelectedItems() const +QVector NodeViewScene::GetSelectedItems() const { QHash::const_iterator iterator; - QList selected; + QVector selected; for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { if (iterator.value()->isSelected()) { @@ -148,9 +148,9 @@ QList NodeViewScene::GetSelectedItems() const return selected; } -QList NodeViewScene::GetSelectedEdges() const +QVector NodeViewScene::GetSelectedEdges() const { - QList edges; + QVector edges; QHash::const_iterator i; @@ -228,7 +228,7 @@ void NodeViewScene::RemoveEdge(NodeEdgePtr edge) int NodeViewScene::DetermineWeight(Node *n) { - QList inputs = n->GetImmediateDependencies(); + QVector inputs = n->GetImmediateDependencies(); int weight = 0; @@ -253,7 +253,7 @@ NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const void NodeViewScene::ReorganizeFrom(Node* n) { - QList immediates = n->GetImmediateDependencies(); + QVector immediates = n->GetImmediateDependencies(); if (immediates.isEmpty()) { // Nothing to do diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 1cb3d1a6c..87128bb3b 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -63,9 +63,9 @@ public: void SetGraph(NodeGraph* graph); - QList GetSelectedNodes() const; - QList GetSelectedItems() const; - QList GetSelectedEdges() const; + QVector GetSelectedNodes() const; + QVector GetSelectedItems() const; + QVector GetSelectedEdges() const; const QHash& item_map() const; const QHash& edge_map() const; diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 032cd0bcd..7e5d35170 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -131,7 +131,7 @@ Project *NodeAddCommand::GetRelevantProject() const return static_cast(graph_)->project(); } -NodeRemoveCommand::NodeRemoveCommand(NodeGraph *graph, const QList &nodes, QUndoCommand *parent) : +NodeRemoveCommand::NodeRemoveCommand(NodeGraph *graph, const QVector &nodes, QUndoCommand *parent) : UndoCommand(parent), graph_(graph), nodes_(nodes) @@ -197,7 +197,7 @@ Project *NodeRemoveCommand::GetRelevantProject() const NodeRemoveWithExclusiveDeps::NodeRemoveWithExclusiveDeps(NodeGraph *graph, Node *node, QUndoCommand *parent) : UndoCommand(parent) { - QList node_and_its_deps; + QVector node_and_its_deps; node_and_its_deps.append(node); node_and_its_deps.append(node->GetExclusiveDependencies()); diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 6b2854e38..9fc95d717 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -98,7 +98,7 @@ private: class NodeRemoveCommand : public UndoCommand { public: NodeRemoveCommand(NodeGraph* graph, - const QList& nodes, + const QVector& nodes, QUndoCommand* parent = nullptr); virtual Project* GetRelevantProject() const override; @@ -111,9 +111,9 @@ private: QObject memory_manager_; NodeGraph* graph_; - QList nodes_; - QList edges_; - QList block_unlink_commands_; + QVector nodes_; + QVector edges_; + QVector block_unlink_commands_; }; class NodeRemoveWithExclusiveDeps : public UndoCommand { diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 8a57628c9..e0bdf6ab7 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -637,12 +637,12 @@ void ProjectExplorer::DeleteSelected() if (msgbox.clickedButton() == delete_clip_btn) { // Delete any blocks that use this footage - QList blocks_to_remove; + QVector blocks_to_remove; foreach (Sequence* s, used_in_sequences) { foreach (TrackOutput* track, s->viewer_output()->GetTracks()) { foreach (Block* b, track->Blocks()) { - QList deps = b->GetDependencies(); + QVector deps = b->GetDependencies(); foreach (MediaInput* i, footage_nodes) { if (deps.contains(i)) { diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index e89918c06..f292ccc29 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -22,10 +22,10 @@ #include #include +#include #include "common/qtutils.h" #include "node/node.h" -#include "render/backend/opengl/openglrenderfunctions.h" OLIVE_NAMESPACE_ENTER @@ -36,74 +36,33 @@ HistogramScope::HistogramScope(QWidget* parent) : HistogramScope::~HistogramScope() { - CleanUp(); - - if (context()) { - disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, - &HistogramScope::CleanUp); - } + OnDestroy(); } -void HistogramScope::initializeGL() +void HistogramScope::OnInit() { - ScopeBase::initializeGL(); + ScopeBase::OnInit(); - pipeline_secondary_ = CreateSecondaryShader(); - - connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, - &HistogramScope::CleanUp, Qt::DirectConnection); + ShaderCode secondary_code(FileFunctions::ReadFileAsString(":/shaders/rgbhistogram_secondary.frag"), + FileFunctions::ReadFileAsString(":/shaders/rgbhistogram.vert")); + pipeline_secondary_ = renderer()->CreateNativeShader(secondary_code); } -void HistogramScope::AssertAdditionalTextures() +void HistogramScope::OnDestroy() { - if (!texture_row_sums_.IsCreated() - || texture_row_sums_.width() != width() - || texture_row_sums_.height() != height()) { - texture_row_sums_.Destroy(); - texture_row_sums_.Create(context(), VideoParams(width(), - height(), managed_tex().format())); - } + ScopeBase::OnDestroy(); + + pipeline_secondary_.clear(); + texture_row_sums_ = nullptr; } -void HistogramScope::CleanUp() +ShaderCode HistogramScope::GenerateShaderCode() { - makeCurrent(); - - pipeline_secondary_ = nullptr; - texture_row_sums_.Destroy(); - - doneCurrent(); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgbhistogram.frag"), + FileFunctions::ReadFileAsString(":/shaders/default.vert")); } -OpenGLShaderPtr HistogramScope::CreateShader() -{ - OpenGLShaderPtr pipeline = OpenGLShader::Create(); - - pipeline->create(); - pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex, - OpenGLShader::CodeDefaultVertex()); - pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment, - Node::ReadFileAsString(":/shaders/rgbhistogram.frag")); - pipeline->link(); - - return pipeline; -} - -OpenGLShaderPtr HistogramScope::CreateSecondaryShader() -{ - OpenGLShaderPtr shader = OpenGLShader::Create(); - - shader->create(); - shader->addShaderFromSourceCode(QOpenGLShader::Vertex, - Node::ReadFileAsString(":/shaders/rgbhistogram.vert")); - shader->addShaderFromSourceCode(QOpenGLShader::Fragment, - Node::ReadFileAsString(":/shaders/rgbhistogram_secondary.frag")); - shader->link(); - - return shader; -} - -void HistogramScope::DrawScope() +void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) { float histogram_scale = 0.80f; // This value is eyeballed for usefulness. Until we have a geometry @@ -112,43 +71,30 @@ void HistogramScope::DrawScope() float histogram_base = 2.5f; float histogram_power = 1.0f / histogram_base; - pipeline()->bind(); - pipeline()->setUniformValue("ove_resolution", managed_tex().width(), - managed_tex().height()); - pipeline()->setUniformValue("ove_viewport", width(), height()); - pipeline()->setUniformValue("histogram_scale", histogram_scale); - pipeline()->release(); + ShaderJob shader_job; - AssertAdditionalTextures(); + shader_job.InsertValue(QStringLiteral("viewport"), ShaderValue(QVector2D(width(), height()), NodeParam::kVec2)); + shader_job.InsertValue(QStringLiteral("histogram_scale"), ShaderValue(histogram_scale, NodeParam::kFloat)); + shader_job.InsertValue(QStringLiteral("histogram_power"), ShaderValue(histogram_power, NodeParam::kFloat)); - framebuffer().Attach(&texture_row_sums_, true); - framebuffer().Bind(); + if (!texture_row_sums_ + || texture_row_sums_->width() != this->width() + || texture_row_sums_->height() != this->height()) { + texture_row_sums_ = renderer()->CreateTexture(VideoParams(width(), height(), + managed_tex->format(), + managed_tex->channel_count())); + } - managed_tex().Bind(); + // Draw managed texture to a sums texture + shader_job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(managed_tex), NodeParam::kTexture)); + renderer()->BlitToTexture(pipeline, shader_job, texture_row_sums_.get()); - OpenGLRenderFunctions::Blit(pipeline()); - - managed_tex().Release(); - - framebuffer().Release(); - framebuffer().Detach(); - - pipeline_secondary_->bind(); - pipeline_secondary_->setUniformValue("ove_resolution", - texture_row_sums_.width(), texture_row_sums_.height()); - pipeline_secondary_->setUniformValue("ove_viewport", width(), height()); - pipeline_secondary_->setUniformValue("histogram_scale", histogram_scale); - pipeline_secondary_->setUniformValue("histogram_power", histogram_power); - pipeline_secondary_->release(); - - texture_row_sums_.Bind(); - - OpenGLRenderFunctions::Blit(pipeline_secondary_); - - texture_row_sums_.Release(); + // Draw sums into a histogram + shader_job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(texture_row_sums_), NodeParam::kTexture)); + renderer()->Blit(pipeline_secondary_, shader_job, texture_row_sums_->params()); // Draw line overlays - QPainter p(this); + QPainter p(inner_widget()); QFont font = p.font(); font.setPixelSize(10); QFontMetrics font_metrics = QFontMetrics(font); @@ -173,28 +119,28 @@ void HistogramScope::DrawScope() float histogram_dim_x = ceil((width() - 1.0) * histogram_scale); float histogram_dim_y = ceil((height() - 1.0) * histogram_scale); float histogram_start_dim_x = - ((width() - 1.0) - histogram_dim_x) / 2.0f; + ((width() - 1.0) - histogram_dim_x) / 2.0f; float histogram_start_dim_y = - ((height() - 1.0) - histogram_dim_y) / 2.0f; + ((height() - 1.0) - histogram_dim_y) / 2.0f; float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x; // for (int i=0; i <= histogram_steps; i++) { for(std::vector::iterator it = histogram_increments.begin(); - it != histogram_increments.end(); it++) { + it != histogram_increments.end(); it++) { histogram_lines[it - histogram_increments.begin()].setLine( - histogram_start_dim_x, - (histogram_dim_y * pow(1.0 - *it, histogram_base)) + - histogram_start_dim_y, - histogram_end_dim_x, - (histogram_dim_y * pow(1.0 - *it, histogram_base)) + - histogram_start_dim_y); - label = QString::number( - *it * 100, 'f', 1) + "%"; - font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; + histogram_start_dim_x, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + histogram_start_dim_y, + histogram_end_dim_x, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + histogram_start_dim_y); + label = QString::number( + *it * 100, 'f', 1) + "%"; + font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; - p.drawText( - histogram_start_dim_x - font_x_offset, - (histogram_dim_y * pow(1.0 - *it, histogram_base)) + + p.drawText( + histogram_start_dim_x - font_x_offset, + (histogram_dim_y * pow(1.0 - *it, histogram_base)) + histogram_start_dim_y + font_y_offset, label); } p.drawLines(histogram_lines); diff --git a/app/widget/scope/histogram/histogram.h b/app/widget/scope/histogram/histogram.h index 70751355f..30895f93d 100644 --- a/app/widget/scope/histogram/histogram.h +++ b/app/widget/scope/histogram/histogram.h @@ -33,22 +33,21 @@ public: virtual ~HistogramScope() override; +protected slots: + virtual void OnInit() override; + + virtual void OnDestroy() override; + protected: - virtual void initializeGL() override; + virtual ShaderCode GenerateShaderCode() override; + QVariant CreateSecondaryShader(); - virtual OpenGLShaderPtr CreateShader() override; - OpenGLShaderPtr CreateSecondaryShader(); - - void AssertAdditionalTextures(); - - virtual void DrawScope() override; + virtual void DrawScope(TexturePtr managed_tex, QVariant pipeline) override; private: - OpenGLShaderPtr pipeline_secondary_; - OpenGLTexture texture_row_sums_; + QVariant pipeline_secondary_; + TexturePtr texture_row_sums_; -private slots: - void CleanUp(); }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp index e3b65d48c..ebeb1c7a7 100644 --- a/app/widget/scope/scopebase/scopebase.cpp +++ b/app/widget/scope/scopebase/scopebase.cpp @@ -20,7 +20,7 @@ #include "scopebase.h" -#include "render/backend/opengl/openglrenderfunctions.h" +#include "config/config.h" OLIVE_NAMESPACE_ENTER @@ -33,11 +33,7 @@ ScopeBase::ScopeBase(QWidget* parent) : ScopeBase::~ScopeBase() { - CleanUp(); - - if (context()) { - disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ScopeBase::CleanUp); - } + OnDestroy(); } void ScopeBase::SetBuffer(Frame *frame) @@ -54,18 +50,15 @@ void ScopeBase::showEvent(QShowEvent* e) UploadTextureFromBuffer(); } -OpenGLShaderPtr ScopeBase::CreateShader() +void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline) { - return OpenGLShader::CreateDefault(); -} + ShaderJob job; -void ScopeBase::DrawScope() -{ - managed_tex().Bind(); + job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(managed_tex), NodeParam::kTexture)); - OpenGLRenderFunctions::Blit(pipeline()); - - managed_tex().Release(); + renderer()->Blit(pipeline, job, VideoParams(width(), height(), + static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + VideoParams::kInternalChannelCount)); } void ScopeBase::UploadTextureFromBuffer() @@ -77,17 +70,18 @@ void ScopeBase::UploadTextureFromBuffer() if (buffer_) { makeCurrent(); - if (!texture_.IsCreated() - || texture_.width() != buffer_->width() - || texture_.height() != buffer_->height() - || texture_.format() != buffer_->format()) { - texture_.Destroy(); - managed_tex_.Destroy(); + if (!texture_ + || texture_->width() != buffer_->width() + || texture_->height() != buffer_->height() + || texture_->format() != buffer_->format()) { + texture_ = nullptr; + managed_tex_ = nullptr; - texture_.Create(context(), buffer_); - managed_tex_.Create(context(), buffer_->video_params()); + texture_ = renderer()->CreateTexture(buffer_->video_params(), + buffer_->data(), buffer_->linesize_pixels()); + managed_tex_ = renderer()->CreateTexture(buffer_->video_params()); } else { - texture_.Upload(buffer_); + texture_->Upload(buffer_->data(), buffer_->linesize_pixels()); } doneCurrent(); @@ -96,58 +90,35 @@ void ScopeBase::UploadTextureFromBuffer() update(); } -void ScopeBase::CleanUp() +void ScopeBase::OnInit() { - makeCurrent(); - - pipeline_ = nullptr; - texture_.Destroy(); - managed_tex_.Destroy(); - framebuffer_.Destroy(); - - doneCurrent(); -} - -void ScopeBase::initializeGL() -{ - ManagedDisplayWidget::initializeGL(); - - pipeline_ = CreateShader(); - - framebuffer_.Create(context()); - - connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ScopeBase::CleanUp, Qt::DirectConnection); + ManagedDisplayWidget::OnInit(); UploadTextureFromBuffer(); + + pipeline_ = renderer()->CreateNativeShader(GenerateShaderCode()); } -void ScopeBase::paintGL() +void ScopeBase::OnPaint() { - QOpenGLFunctions* f = context()->functions(); + // Clear display surface + renderer()->ClearDestination(); - f->glClearColor(0, 0, 0, 0); - f->glClear(GL_COLOR_BUFFER_BIT); - - if (buffer_ && pipeline() && texture_.IsCreated()) { + if (buffer_) { // Convert reference frame to display space - framebuffer_.Attach(&managed_tex_); - framebuffer_.Bind(); + renderer()->BlitColorManaged(color_service(), texture_, true, managed_tex_.get()); - texture_.Bind(); - - f->glViewport(0, 0, texture_.width(), texture_.height()); - - color_service()->ProcessOpenGL(); - - texture_.Release(); - - framebuffer_.Release(); - framebuffer_.Detach(); - - f->glViewport(0, 0, width(), height()); - - DrawScope(); + DrawScope(managed_tex_, pipeline_); } } +void ScopeBase::OnDestroy() +{ + ManagedDisplayWidget::OnDestroy(); + + managed_tex_ = nullptr; + texture_ = nullptr; + pipeline_.clear(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/scopebase/scopebase.h b/app/widget/scope/scopebase/scopebase.h index 41e2edefc..2bcd415c5 100644 --- a/app/widget/scope/scopebase/scopebase.h +++ b/app/widget/scope/scopebase/scopebase.h @@ -22,10 +22,7 @@ #define SCOPEBASE_H #include "codec/frame.h" -#include "render/backend/opengl/openglcolorprocessor.h" -#include "render/backend/opengl/openglframebuffer.h" -#include "render/backend/opengl/openglshader.h" -#include "render/backend/opengl/opengltexture.h" +#include "render/colorprocessor.h" #include "widget/manageddisplay/manageddisplay.h" OLIVE_NAMESPACE_ENTER @@ -40,48 +37,36 @@ public: public slots: void SetBuffer(Frame* frame); +protected slots: + virtual void OnInit() override; + + virtual void OnPaint() override; + + virtual void OnDestroy() override; + protected: - virtual void initializeGL() override; - - virtual void paintGL() override; - virtual void showEvent(QShowEvent* e) override; - virtual OpenGLShaderPtr CreateShader(); + virtual ShaderCode GenerateShaderCode() = 0; - virtual void DrawScope(); - - OpenGLShaderPtr pipeline() - { - return pipeline_; - } - - OpenGLTexture& managed_tex() - { - return managed_tex_; - } - - OpenGLFramebuffer& framebuffer() - { - return framebuffer_; - } + /** + * @brief Draw function + * + * Override this if your sub-class scope needs extra drawing. + */ + virtual void DrawScope(TexturePtr managed_tex, QVariant pipeline); private: void UploadTextureFromBuffer(); - OpenGLShaderPtr pipeline_; + QVariant pipeline_; - OpenGLTexture texture_; + TexturePtr texture_; - OpenGLTexture managed_tex_; - - OpenGLFramebuffer framebuffer_; + TexturePtr managed_tex_; Frame* buffer_; -private slots: - void CleanUp(); - }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index ff4f1296b..ac4964624 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -24,10 +24,12 @@ #include #include #include +#include +#include #include "common/qtutils.h" +#include "config/config.h" #include "node/node.h" -#include "render/backend/opengl/openglrenderfunctions.h" OLIVE_NAMESPACE_ENTER @@ -36,53 +38,57 @@ WaveformScope::WaveformScope(QWidget* parent) : { } -OpenGLShaderPtr WaveformScope::CreateShader() +WaveformScope::~WaveformScope() { - OpenGLShaderPtr pipeline = OpenGLShader::Create(); - - pipeline->create(); - pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex, - Node::ReadFileAsString(":/shaders/rgbwaveform.vert")); - pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment, - Node::ReadFileAsString(":/shaders/rgbwaveform.frag")); - pipeline->link(); - - return pipeline; + OnDestroy(); } -void WaveformScope::DrawScope() +ShaderCode WaveformScope::GenerateShaderCode() +{ + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgbwaveform.frag"), + FileFunctions::ReadFileAsString(":/shaders/rgbwaveform.vert")); +} + +void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) { float waveform_scale = 0.80f; // Draw waveform through shader - pipeline()->bind(); - pipeline()->setUniformValue("ove_resolution", managed_tex().width(), managed_tex().height()); - pipeline()->setUniformValue("ove_viewport", width(), height()); - GLfloat luma[3] = {0.0, 0.0, 0.0}; - color_manager()->GetDefaultLumaCoefs(luma); - pipeline()->setUniformValue("luma_coeffs", luma[0], luma[1], luma[2]); + ShaderJob job; + + // Set viewport size + job.InsertValue(QStringLiteral("viewport"), + ShaderValue(QVector2D(width(), height()), NodeParam::kVec2)); + + // Set luma coefficients + double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; + color_manager()->GetDefaultLumaCoefs(luma_coeffs); + job.InsertValue(QStringLiteral("luma_coeffs"), + ShaderValue(QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]), NodeParam::kVec3)); + // Scale of the waveform relative to the viewport surface. - pipeline()->setUniformValue("waveform_scale", waveform_scale); + job.InsertValue(QStringLiteral("waveform_scale"), + ShaderValue(waveform_scale, NodeParam::kFloat)); - pipeline()->release(); + // Insert source texture + job.InsertValue(QStringLiteral("ove_maintex"), + ShaderValue(QVariant::fromValue(managed_tex), NodeParam::kTexture)); - managed_tex().Bind(); - - OpenGLRenderFunctions::Blit(pipeline()); - - managed_tex().Release(); + renderer()->Blit(pipeline, job, VideoParams(width(), height(), + static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + VideoParams::kInternalChannelCount)); float waveform_dim_x = ceil((width() - 1.0) * waveform_scale); float waveform_dim_y = ceil((height() - 1.0) * waveform_scale); float waveform_start_dim_x = - ((width() - 1.0) - waveform_dim_x) / 2.0f; + ((width() - 1.0) - waveform_dim_x) / 2.0f; float waveform_start_dim_y = - ((height() - 1.0) - waveform_dim_y) / 2.0f; + ((height() - 1.0) - waveform_dim_y) / 2.0f; float waveform_end_dim_x = (width() - 1.0) - waveform_start_dim_x; // Draw line overlays - QPainter p(this); + QPainter p(inner_widget()); QFont font; font.setPixelSize(10); QFontMetrics font_metrics = QFontMetrics(font); @@ -100,18 +106,19 @@ void WaveformScope::DrawScope() for (int i=0; i <= ire_steps; i++) { ire_lines[i].setLine( - waveform_start_dim_x, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y, - waveform_end_dim_x, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); - label = QString::number(1.0 - (i * ire_increment), 'f', 1); - font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; + waveform_start_dim_x, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y, + waveform_end_dim_x, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); + label = QString::number(1.0 - (i * ire_increment), 'f', 1); + font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; - p.drawText( - waveform_start_dim_x - font_x_offset, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y + font_y_offset, - label); + p.drawText( + waveform_start_dim_x - font_x_offset, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y + font_y_offset, + label); } + p.drawLines(ire_lines); } diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index 04a464e52..687d5b038 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -31,10 +31,12 @@ class WaveformScope : public ScopeBase public: WaveformScope(QWidget* parent = nullptr); -protected: - virtual OpenGLShaderPtr CreateShader() override; + virtual ~WaveformScope() override; - virtual void DrawScope() override; +protected: + virtual ShaderCode GenerateShaderCode() override; + + virtual void DrawScope(TexturePtr managed_tex, QVariant pipeline) override; }; diff --git a/app/widget/standardcombos/pixelformatcombobox.h b/app/widget/standardcombos/pixelformatcombobox.h index 48638a14b..7fc29dc94 100644 --- a/app/widget/standardcombos/pixelformatcombobox.h +++ b/app/widget/standardcombos/pixelformatcombobox.h @@ -23,7 +23,7 @@ #include -#include "render/pixelformat.h" +#include "render/videoparams.h" OLIVE_NAMESPACE_ENTER @@ -31,26 +31,25 @@ class PixelFormatComboBox : public QComboBox { Q_OBJECT public: - PixelFormatComboBox(bool alpha_only, bool float_only, QWidget* parent = nullptr) : + PixelFormatComboBox(bool float_only, QWidget* parent = nullptr) : QComboBox(parent) { // Set up preview formats - for (int i=0;i(i); + for (int i=0;i(i); - if ((!alpha_only || PixelFormat::FormatHasAlphaChannel(pix_fmt)) - && (!float_only || PixelFormat::FormatIsFloat(pix_fmt))) { - this->addItem(PixelFormat::GetName(pix_fmt), pix_fmt); + if (!float_only || VideoParams::FormatIsFloat(pix_fmt)) { + this->addItem(VideoParams::GetFormatName(pix_fmt), pix_fmt); } } } - PixelFormat::Format GetPixelFormat() const + VideoParams::Format GetPixelFormat() const { - return static_cast(this->currentData().toInt()); + return static_cast(this->currentData().toInt()); } - void SetPixelFormat(PixelFormat::Format fmt) + void SetPixelFormat(VideoParams::Format fmt) { for (int i=0; icount(); i++) { if (this->itemData(i).toInt() == fmt) { diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index 5c83206c6..b839d64aa 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -458,7 +458,8 @@ void TimeBasedWidget::SetMarker() } if (ok) { - points_->markers()->AddMarker(TimeRange(GetTime(), GetTime()), marker_name); + Core::instance()->undo_stack()->push(new MarkerAddCommand(static_cast(GetConnectedNode()->parent())->project(), + points_->markers(), TimeRange(GetTime(), GetTime()), marker_name)); } } @@ -517,4 +518,27 @@ void TimeBasedWidget::GoToOut() } } +TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name) : + project_(project), + marker_list_(marker_list), + range_(range), + name_(name) +{ +} + +Project *TimeBasedWidget::MarkerAddCommand::GetRelevantProject() const +{ + return project_; +} + +void TimeBasedWidget::MarkerAddCommand::redo_internal() +{ + added_marker_ = marker_list_->AddMarker(range_, name_); +} + +void TimeBasedWidget::MarkerAddCommand::undo_internal() +{ + marker_list_->RemoveMarker(added_marker_); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h index 008514d26..c31970189 100644 --- a/app/widget/timebased/timebased.h +++ b/app/widget/timebased/timebased.h @@ -139,6 +139,27 @@ signals: void TimebaseChanged(const rational&); private: + class MarkerAddCommand : public UndoCommand + { + public: + MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo_internal() override; + virtual void undo_internal() override; + + private: + Project* project_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + + TimelineMarker* added_marker_; + + }; + /** * @brief Set either in or out point to the current playhead * diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 4a3a6b759..bc48f0172 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -362,7 +362,7 @@ rational TimelineWidget::GetToolTipTimebase() const void TimelineWidget::SelectAll() { - QList newly_selected_blocks; + QVector newly_selected_blocks; for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) { if (!selected_blocks_.contains(it.key())) { @@ -414,7 +414,7 @@ void TimelineWidget::SplitAtPlayhead() rational playhead_time = Timecode::timestamp_to_time(GetTimestamp(), timebase()); - QList selected_blocks = GetSelectedBlocks(); + QVector selected_blocks = GetSelectedBlocks(); // Prioritize blocks that are selected and overlap the playhead QVector blocks_to_split; @@ -459,7 +459,7 @@ void TimelineWidget::SplitAtPlayhead() } } -void TimelineWidget::ReplaceBlocksWithGaps(const QList &blocks, +void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, bool remove_from_graph, QUndoCommand *command) { @@ -482,8 +482,8 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QList &blocks, void TimelineWidget::DeleteSelected(bool ripple) { - QList selected_list = GetSelectedBlocks(); - QList blocks_to_delete; + QVector selected_list = GetSelectedBlocks(); + QVector blocks_to_delete; foreach (TimelineViewBlockItem* item, selected_list) { Block* b = item->block(); @@ -498,8 +498,8 @@ void TimelineWidget::DeleteSelected(bool ripple) QUndoCommand* command = new QUndoCommand(); - QList clips_to_delete; - QList transitions_to_delete; + QVector clips_to_delete; + QVector transitions_to_delete; foreach (Block* b, blocks_to_delete) { if (b->type() == Block::kClip) { @@ -531,7 +531,7 @@ void TimelineWidget::DeleteSelected(bool ripple) TimeRangeList range_list; foreach (Block* b, blocks_to_delete) { - range_list.InsertTimeRange(TimeRange(b->in(), b->out())); + range_list.insert(TimeRange(b->in(), b->out())); } new TimelineRippleDeleteGapsAtRegionsCommand(GetConnectedNode(), range_list, command); @@ -580,9 +580,9 @@ void TimelineWidget::OverwriteFootageAtPlayhead(const QList &footage) void TimelineWidget::ToggleLinksOnSelected() { - QList sel = GetSelectedBlocks(); + QVector sel = GetSelectedBlocks(); - QList blocks; + QVector blocks; bool link = true; foreach (TimelineViewBlockItem* item, sel) { @@ -612,20 +612,20 @@ void TimelineWidget::CopySelected(bool cut) return; } - QList selected = GetSelectedBlocks(); + QVector selected = GetSelectedBlocks(); if (selected.isEmpty()) { return; } - QList selected_nodes; + QVector selected_nodes; foreach (TimelineViewBlockItem* item, selected) { Node* block = item->block(); selected_nodes.append(block); - QList deps = block->GetDependencies(); + QVector deps = block->GetDependencies(); foreach (Node* d, deps) { if (!selected_nodes.contains(d)) { @@ -649,8 +649,8 @@ void TimelineWidget::Paste(bool insert) QUndoCommand* command = new QUndoCommand(); - QList paste_data; - QList pasted = PasteNodesFromClipboard(static_cast(GetConnectedNode()->parent()), command, &paste_data); + QVector paste_data; + QVector pasted = PasteNodesFromClipboard(static_cast(GetConnectedNode()->parent()), command, &paste_data); rational paste_start = GetTime(); @@ -731,7 +731,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) void TimelineWidget::ToggleSelectedEnabled() { - QList items = GetSelectedBlocks(); + QVector items = GetSelectedBlocks(); if (items.isEmpty()) { return; @@ -748,12 +748,12 @@ void TimelineWidget::ToggleSelectedEnabled() Core::instance()->undo_stack()->pushIfHasChildren(command); } -QList TimelineWidget::GetSelectedBlocks() +QVector TimelineWidget::GetSelectedBlocks() { - QList list; + QVector list(selected_blocks_.size()); - foreach (Block* b, selected_blocks_) { - list.append(block_items_.value(b)); + for (int i=0; i &blocks) { - QList delete_items; - delete_items.reserve(blocks.size()); - - QList deselect_blocks; + QVector deselect_blocks; foreach (Block* b, blocks) { // Disconnect all signals @@ -940,8 +937,6 @@ void TimelineWidget::RemoveBlock(const QList &blocks) // through emit BlocksDeselected(deselect_blocks); } - - qDeleteAll(delete_items); } void TimelineWidget::AddTrack(TrackOutput *track, Timeline::TrackType type) @@ -1051,7 +1046,7 @@ void TimelineWidget::ShowContextMenu() { Menu menu(this); - QList selected = GetSelectedBlocks(); + QVector selected = GetSelectedBlocks(); if (!selected.isEmpty()) { MenuShared::instance()->AddItemsForEditMenu(&menu, true); @@ -1060,8 +1055,8 @@ void TimelineWidget::ShowContextMenu() QAction* properties_action = menu.addAction(tr("Properties")); connect(properties_action, &QAction::triggered, this, [this](){ - QList block_items = GetSelectedBlocks(); - QList nodes; + QVector block_items = GetSelectedBlocks(); + QVector nodes; foreach (TimelineViewBlockItem* i, block_items) { nodes.append(i->block()); @@ -1210,7 +1205,7 @@ const QRect& TimelineWidget::GetRubberBandGeometry() const return rubberband_.geometry(); } -void TimelineWidget::SignalSelectedBlocks(QList input, bool filter) +void TimelineWidget::SignalSelectedBlocks(QVector input, bool filter) { if (input.isEmpty()) { return; @@ -1233,7 +1228,7 @@ void TimelineWidget::SignalSelectedBlocks(QList input, bool filter) emit BlocksSelected(input); } -void TimelineWidget::SignalDeselectedBlocks(const QList &deselected_blocks) +void TimelineWidget::SignalDeselectedBlocks(const QVector &deselected_blocks) { if (deselected_blocks.isEmpty()) { return; @@ -1512,7 +1507,7 @@ void TimelineWidget::EndRubberBandSelect() void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &track) { - selections_[track].InsertTimeRange(time); + selections_[track].insert(time); UpdateViewports(track.type()); } @@ -1524,7 +1519,7 @@ void TimelineWidget::AddSelection(TimelineViewBlockItem *item) void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference &track) { - selections_[track].RemoveTimeRange(time); + selections_[track].remove(time); UpdateViewports(track.type()); } diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index c1be47a75..568f42949 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -89,7 +89,7 @@ public: void ToggleSelectedEnabled(); - QList GetSelectedBlocks(); + QVector GetSelectedBlocks(); virtual bool SnapPoint(QList start_times, rational *movement, int snap_points = kSnapAll) override; @@ -99,7 +99,7 @@ public: void RestoreSplitterState(const QByteArray& state); - static void ReplaceBlocksWithGaps(const QList& blocks, bool remove_from_graph, QUndoCommand* command); + static void ReplaceBlocksWithGaps(const QVector &blocks, bool remove_from_graph, QUndoCommand* command); /** * @brief Retrieve the QGraphicsItem at a particular scene position @@ -185,12 +185,12 @@ public: * this is preferable and should only be set to FALSE if the list is guaranteed not to contain * already selected blocks (and therefore filtering can be skipped to save time). */ - void SignalSelectedBlocks(QList selected_blocks, bool filter = true); + void SignalSelectedBlocks(QVector selected_blocks, bool filter = true); /** * @brief Track blocks that have been newly deselected */ - void SignalDeselectedBlocks(const QList& deselected_blocks); + void SignalDeselectedBlocks(const QVector &deselected_blocks); /** * @brief Convenience function to deselect all blocks and signal them @@ -198,9 +198,9 @@ public: void SignalDeselectedAllBlocks(); signals: - void BlocksSelected(const QList& selected_blocks); + void BlocksSelected(const QVector& selected_blocks); - void BlocksDeselected(const QList& deselected_blocks); + void BlocksDeselected(const QVector& deselected_blocks); protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -237,7 +237,7 @@ private: QRubberBand rubberband_; TimelineWidgetSelections rubberband_old_selections_; - QList rubberband_now_selected_; + QVector rubberband_now_selected_; TimelineWidgetSelections selections_; @@ -257,7 +257,7 @@ private: TimeSlider* timecode_label_; - QList selected_blocks_; + QVector selected_blocks_; int deferred_scroll_value_; diff --git a/app/widget/timelinewidget/timelinewidgetselections.cpp b/app/widget/timelinewidget/timelinewidgetselections.cpp index 951b80946..938f2e16a 100644 --- a/app/widget/timelinewidget/timelinewidgetselections.cpp +++ b/app/widget/timelinewidget/timelinewidgetselections.cpp @@ -25,9 +25,7 @@ OLIVE_NAMESPACE_ENTER void TimelineWidgetSelections::ShiftTime(const rational &diff) { for (auto it=this->begin(); it!=this->end(); it++) { - for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { - (*it2) += diff; - } + it.value().shift(diff); } } @@ -59,18 +57,14 @@ void TimelineWidgetSelections::ShiftTracks(Timeline::TrackType type, int diff) void TimelineWidgetSelections::TrimIn(const rational &diff) { for (auto it=this->begin(); it!=this->end(); it++) { - for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { - (*it2).set_in((*it2).in() + diff); - } + it.value().trim_in(diff); } } void TimelineWidgetSelections::TrimOut(const rational &diff) { for (auto it=this->begin(); it!=this->end(); it++) { - for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { - (*it2).set_out((*it2).out() + diff); - } + it.value().trim_out(diff); } } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index a2c77b7d4..a1552839e 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -397,8 +397,8 @@ void ImportTool::DropGhosts(bool insert) QVector block_items(parent()->GetGhostItems().size()); - // Check if we're inserting - if (insert) { + // Check if we're inserting (only valid if we're not creating this sequence ourselves) + if (insert && !open_sequence) { InsertGapsAtGhostDestination(command); } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 7f4f8e772..d1a6a257b 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -79,7 +79,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) if (parent()->IsBlockSelected(clicked_item_->block())) { // Collect item deselections - QList deselected_blocks; + QVector deselected_blocks; // If shift is held, deselect it if (event->GetModifiers() & Qt::ShiftModifier) { @@ -89,7 +89,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) // If not holding alt, deselect all links as well if (!(event->GetModifiers() & Qt::AltModifier)) { parent()->SetBlockLinksSelected(clicked_item_->block(), false); - deselected_blocks.append(clicked_item_->block()->linked_clips().toList()); + deselected_blocks.append(clicked_item_->block()->linked_clips()); } } @@ -107,7 +107,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) if (selectable_item) { // Collect item selections - QList selected_blocks; + QVector selected_blocks; // Select this item parent()->AddSelection(clicked_item_); @@ -116,7 +116,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) // If not holding alt, select all links as well if (!(event->GetModifiers() & Qt::AltModifier)) { parent()->SetBlockLinksSelected(clicked_item_->block(), true); - selected_blocks.append(clicked_item_->block()->linked_clips().toList()); + selected_blocks.append(clicked_item_->block()->linked_clips()); } parent()->SignalSelectedBlocks(selected_blocks); @@ -219,13 +219,13 @@ void SetGhostToSlideMode(TimelineViewGhostItem* g) } void PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item, - Timeline::MovementMode trim_mode, - bool dont_roll_trims, - bool allow_nongap_rolling, - bool slide_instead_of_moving) + Timeline::MovementMode trim_mode, + bool dont_roll_trims, + bool allow_nongap_rolling, + bool slide_instead_of_moving) { // Get list of selected blocks - QList clips = parent()->GetSelectedBlocks(); + QVector clips = parent()->GetSelectedBlocks(); if (trim_mode == Timeline::kMove) { @@ -590,10 +590,10 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // If we're not duplicating, "remove" the clips and replace them with gaps if (!duplicate_clips) { - QList blocks_to_delete; + QVector blocks_to_delete(blocks_moving.size()); - foreach (const GhostBlockPair& p, blocks_moving) { - blocks_to_delete.append(p.block); + for (int i=0; iReplaceBlocksWithGaps(blocks_to_delete, false, command); @@ -610,13 +610,23 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) if (duplicate_clips) { // Duplicate rather than move - Node* copy = block->copy(); + Node* copy; - new NodeAddCommand(static_cast(block->parent()), - copy, - command); + if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) { + QVector nodes_to_clone; + nodes_to_clone.append(block); + nodes_to_clone.append(block->GetDependencies()); + QVector duplicated = Node::CopyDependencyGraph(nodes_to_clone, command); + copy = duplicated.first(); + } else { + copy = block->copy(); - new NodeCopyInputsCommand(block, copy, true, command); + new NodeAddCommand(static_cast(block->parent()), + copy, + command); + + new NodeCopyInputsCommand(block, copy, true, command); + } // Place the copy instead of the original block block = static_cast(copy); @@ -724,7 +734,7 @@ Timeline::MovementMode PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem * } void PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode) + Timeline::MovementMode trim_mode) { InitiateDragInternal(clicked_item, trim_mode, false, false, false); } @@ -795,8 +805,8 @@ void PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::Movem } bool PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, - const QList& items, - const Timeline::MovementMode& mode) + const QVector& items, + const Timeline::MovementMode& mode) { foreach (TimelineViewBlockItem* compare, items) { if (clip->Track() == compare->Track() @@ -811,9 +821,9 @@ bool PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, } bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, - const TrackReference& track, - Timeline::MovementMode movement, - const QList& selected_items) + const TrackReference& track, + Timeline::MovementMode movement, + const QList& selected_items) { // Assume block is a clip and see if it has any transitions TransitionBlock* transitions[2]; diff --git a/app/widget/timelinewidget/tool/pointer.h b/app/widget/timelinewidget/tool/pointer.h index 31ec8b455..3d42fbb54 100644 --- a/app/widget/timelinewidget/tool/pointer.h +++ b/app/widget/timelinewidget/tool/pointer.h @@ -100,7 +100,7 @@ private: void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode); bool IsClipTrimmable(TimelineViewBlockItem* clip, - const QList& items, + const QVector &items, const Timeline::MovementMode& mode); void ProcessGhostsForSliding(); diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 7f968e29e..36f81e8e1 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -20,6 +20,7 @@ #include "undo.h" +#include "config/config.h" #include "core.h" #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" @@ -209,10 +210,21 @@ void TrackRippleRemoveAreaCommand::redo_internal() if (splice_) { // Split the block here - trim_in_ = static_cast(trim_out_->copy()); + splice_split_command_ = new QUndoCommand(); - static_cast(track_->parent())->AddNode(trim_in_); - Node::CopyInputs(trim_out_, trim_in_); + if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) { + QVector nodes_to_clone; + nodes_to_clone.append(trim_out_); + nodes_to_clone.append(trim_out_->GetDependencies()); + QVector duplicated = Node::CopyDependencyGraph(nodes_to_clone, splice_split_command_); + trim_in_ = static_cast(duplicated.first()); + } else { + trim_in_ = static_cast(trim_out_->copy()); + new NodeAddCommand(static_cast(track_->parent()), trim_in_, splice_split_command_); + new NodeCopyInputsCommand(trim_out_, trim_in_, true, splice_split_command_); + } + + splice_split_command_->redo(); trim_out_old_length_ = trim_out_->length(); trim_out_->set_length_and_media_out(in_ - trim_out_->in()); @@ -292,7 +304,8 @@ void TrackRippleRemoveAreaCommand::undo_internal() track_->RippleRemoveBlock(trim_in_); trim_out_->set_length_and_media_out(trim_out_old_length_); - TakeNodeFromParentGraph(trim_in_, &memory_manager_); + splice_split_command_->undo(); + delete splice_split_command_; } else { @@ -428,8 +441,33 @@ void BlockSplitCommand::redo_internal() { track_->BeginOperation(); - static_cast(block_->parent())->AddNode(new_block_); - Node::CopyInputs(block_, new_block_); + NodeGraph* graph = static_cast(block_->parent()); + + add_command_ = new QUndoCommand(); + new NodeAddCommand(graph, new_block_, add_command_); + new NodeCopyInputsCommand(block_, new_block_, true, add_command_); + + if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) { + + QVector src_nodes; + QVector dst_nodes; + + src_nodes.append(block_); + src_nodes.append(block_->GetDependencies()); + + dst_nodes.resize(src_nodes.size()); + dst_nodes[0] = new_block_; + for (int i=1; icopy(); + new NodeAddCommand(graph, dst_nodes[i], add_command_); + Node::CopyInputs(src_nodes[i], dst_nodes[i], false); + } + + Node::CopyDependencyGraph(src_nodes, dst_nodes, add_command_); + + } + + add_command_->redo(); rational new_part_length = block_->length() - (point_ - block_->in()); @@ -451,16 +489,18 @@ void BlockSplitCommand::undo_internal() { track_->BeginOperation(); - block_->set_length_and_media_out(old_length_); - track_->RippleRemoveBlock(new_block_); - - TakeNodeFromParentGraph(new_block_, &memory_manager_); - foreach (NodeInput* transition, transitions_to_move_) { NodeParam::DisconnectEdge(new_block_->output(), transition); NodeParam::ConnectEdge(block_->output(), transition); } + block_->set_length_and_media_out(old_length_); + track_->RippleRemoveBlock(new_block_); + + add_command_->undo(); + new_block_->setParent(&memory_manager_); + delete add_command_; + track_->EndOperation(); } @@ -765,7 +805,7 @@ void BlockUnlinkAllCommand::undo_internal() unlinked_.clear(); } -BlockLinkManyCommand::BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand *parent) : +BlockLinkManyCommand::BlockLinkManyCommand(const QVector blocks, bool link, QUndoCommand *parent) : UndoCommand(parent), blocks_(blocks) { diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h index 220f4133d..b8be979b2 100644 --- a/app/widget/timelinewidget/undo/undo.h +++ b/app/widget/timelinewidget/undo/undo.h @@ -192,6 +192,7 @@ protected: rational out_; bool splice_; + QUndoCommand* splice_split_command_; Block* trim_out_; Block* trim_in_; @@ -329,17 +330,18 @@ protected: private: TrackOutput* track_; Block* block_; + Block* new_block_; rational new_length_; rational old_length_; rational point_; - Block* new_block_; - QList transitions_to_move_; QObject memory_manager_; + QUndoCommand* add_command_; + }; class TrackSplitAtTimeCommand : public UndoCommand { @@ -471,12 +473,12 @@ private: class BlockLinkManyCommand : public UndoCommand { public: - BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand* parent = nullptr); + BlockLinkManyCommand(const QVector blocks, bool link, QUndoCommand* parent = nullptr); virtual Project* GetRelevantProject() const override; private: - QList blocks_; + QVector blocks_; }; diff --git a/app/widget/timelinewidget/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index b42978e26..883937a2d 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -246,7 +246,7 @@ void TimelineViewBase::UpdateSceneRect() bounding_rect.setLeft(0); // Ensure the scene is always the full length of the timeline with a gap at the end to work with - bounding_rect.setRight(TimeToScene(end_time_) + width() / 2); + bounding_rect.setRight(TimeToScene(end_time_) + width()); // Any further rect processing from derivatives can be done here SceneRectUpdateEvent(bounding_rect); diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 9c3a6ceba..7b98b3630 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -94,8 +94,6 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI painter->setPen(QColor(64, 64, 64)); TrackOutput* track = TrackOutput::TrackFromBlock(block_); if (track) { - QMutexLocker locker(track->waveform_lock()); - AudioVisualWaveform::DrawWaveform(painter, rect().toRect(), this->GetScale(), diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 9c066acf1..13108b3cd 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -51,6 +51,8 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) if (timeline_points_) { disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); + disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); + disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); } timeline_points_ = points; @@ -58,6 +60,8 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) if (timeline_points_) { connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); } update(); diff --git a/app/widget/timetarget/timetarget.cpp b/app/widget/timetarget/timetarget.cpp index 51ec506d3..73f29746a 100644 --- a/app/widget/timetarget/timetarget.cpp +++ b/app/widget/timetarget/timetarget.cpp @@ -60,7 +60,7 @@ TimeRange TimeTargetObject::GetAdjustedTime(Node* from, Node* to, const TimeRang return r; } - QList adjusted = from->TransformTimeTo(r, to, direction); + QVector adjusted = from->TransformTimeTo(r, to, direction); if (adjusted.isEmpty()) { return r; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 5d6bdca82..d2b584722 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -37,7 +37,7 @@ #include "config/config.h" #include "project/item/sequence/sequence.h" #include "project/project.h" -#include "render/pixelformat.h" +#include "render/rendermanager.h" #include "task/taskmanager.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" @@ -108,19 +108,10 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // FIXME: Magic number SetScale(48.0); - // Start background renderer - renderer_ = new OpenGLBackend(this); - renderer_->SetAutoCacheEnabled(true); - renderer_->SetRenderMode(RenderMode::kOffline); - renderer_->SetPreviewGenerationEnabled(true); - // Ensures that seeking on the waveform view updates the time as expected connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::TimeChangedFromWaveform); connect(waveform_view_, &AudioWaveformView::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); - // Ensures renderer is updated if the global pixel format is changed - connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererVideoParameters); - connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate); SetAutoMaxScrollBar(true); @@ -167,7 +158,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) } if (!pause_autocache_during_playback_ || !IsPlaying()) { - renderer_->SetAutoCachePlayhead(time_set); + auto_cacher_.SetPlayhead(time_set); } display_widget_->SetTime(time_set); @@ -192,8 +183,6 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) ruler()->SetPlaybackCache(n->video_frame_cache()); - n->audio_playback_cache()->SetParameters(n->audio_params()); - SetViewerResolution(n->video_params().width(), n->video_params().height()); SetViewerPixelAspect(n->video_params().pixel_aspect_ratio()); last_length_ = rational(); @@ -209,6 +198,8 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) using_manager = nullptr; } + auto_cacher_.SetColorManager(using_manager); + display_widget_->ConnectColorManager(using_manager); foreach (ViewerWindow* window, windows_) { window->display_widget()->ConnectColorManager(using_manager); @@ -251,6 +242,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) foreach (ViewerWindow* window, windows_) { window->display_widget()->DisconnectColorManager(); } + auto_cacher_.SetColorManager(nullptr); waveform_view_->SetViewer(nullptr); waveform_view_->ConnectTimelinePoints(nullptr); @@ -261,7 +253,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n) { - renderer_->SetViewerNode(n); + auto_cacher_.SetViewerNode(n); } void ViewerWidget::ScaleChangedEvent(const double &s) @@ -355,18 +347,18 @@ void ViewerWidget::ForceUpdate() void ViewerWidget::SetAutoCacheEnabled(bool e) { - renderer_->SetAutoCachePaused(!e); + auto_cacher_.SetPaused(!e); } void ViewerWidget::CacheEntireSequence() { - renderer_->AutoCacheRange(TimeRange(rational(), GetConnectedNode()->video_frame_cache()->GetLength())); + auto_cacher_.ForceCacheRange(TimeRange(rational(), GetConnectedNode()->video_frame_cache()->GetLength())); } void ViewerWidget::CacheSequenceInOut() { if (GetConnectedTimelinePoints() && GetConnectedTimelinePoints()->workarea()->enabled()) { - renderer_->AutoCacheRange(GetConnectedTimelinePoints()->workarea()->range()); + auto_cacher_.ForceCacheRange(GetConnectedTimelinePoints()->workarea()->range()); } else { QMessageBox::warning(this, tr("Error"), @@ -396,7 +388,8 @@ FramePtr ViewerWidget::DecodeCachedImage(const QString &fn, const rational& time void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const { - ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time))); + ticket->Start(); + ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time)), false); } bool ViewerWidget::ShouldForceWaveform() const @@ -472,7 +465,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) // Kindly tell all viewers to stop caching if (pause_autocache_during_playback_) { foreach (ViewerWidget* viewer, instances_) { - viewer->renderer_->ClearVideoQueue(); + viewer->auto_cacher_.ClearVideoQueue(); } } @@ -619,11 +612,6 @@ void ViewerWidget::RequestNextFrameForQueue() watcher->SetTicket(GetFrame(next_time, false)); } -PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const -{ - return PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline); -} - RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queue) { QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t); @@ -633,14 +621,14 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queu if (cached_hash.isEmpty() || !QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job if (clear_render_queue) { - renderer_->ClearVideoQueue(); + auto_cacher_.ClearVideoQueue(); } - return renderer_->RenderFrame(t, true); + return auto_cacher_.GetSingleFrame(t); } else { // Frame has been cached, grab the frame - RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, - QVariant::fromValue(t)); + RenderTicketPtr ticket = std::make_shared(); + ticket->setProperty("time", QVariant::fromValue(t)); QtConcurrent::run(this, &ViewerWidget::DecodeCachedImage, ticket, cache_fn, t); return ticket; @@ -896,7 +884,7 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) // Auto-cache QAction* autocache_action = cache_menu->addAction(tr("Auto-Cache")); autocache_action->setCheckable(true); - autocache_action->setChecked(!renderer_->IsAutoCachePaused()); + autocache_action->setChecked(!auto_cacher_.IsPaused()); connect(autocache_action, &QAction::triggered, this, &ViewerWidget::SetAutoCacheEnabled); cache_menu->addSeparator(); @@ -981,7 +969,7 @@ void ViewerWidget::Pause() { PauseInternal(); - renderer_->SetAutoCachePlayhead(GetTime()); + auto_cacher_.SetPlayhead(GetTime()); } void ViewerWidget::ShuttleLeft() @@ -1162,25 +1150,14 @@ void ViewerWidget::InterlacingChangedSlot(VideoParams::Interlacing interlacing) void ViewerWidget::UpdateRendererVideoParameters() { - renderer_->ClearVideoQueue(); - - renderer_->SetVideoParams(GetConnectedNode()->video_params()); - - // In case the user is pressing the mouse at this exact moment - renderer_->IgnoreNextMouseButton(); - - GetConnectedNode()->video_frame_cache()->InvalidateAll(); - display_widget_->SetVideoParams(GetConnectedNode()->video_params()); + foreach (ViewerWindow* window, windows_) { + window->display_widget()->SetVideoParams(GetConnectedNode()->video_params()); + } } void ViewerWidget::UpdateRendererAudioParameters() { - renderer_->ClearAudioQueue(); - - renderer_->SetAudioParams(GetConnectedNode()->audio_params()); - - GetConnectedNode()->audio_playback_cache()->InvalidateAll(); } void ViewerWidget::SetZoomFromMenu(QAction *action) diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 06b096d7d..838dc4f4f 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -32,8 +32,8 @@ #include "common/rational.h" #include "node/output/viewer/viewer.h" #include "panel/scope/scope.h" -#include "render/backend/opengl/openglbackend.h" -#include "render/backend/renderticketwatcher.h" +#include "render/previewautocacher.h" +#include "threading/threadticketwatcher.h" #include "viewerdisplay.h" #include "viewerplaybacktimer.h" #include "viewerqueue.h" @@ -82,11 +82,6 @@ public: */ void SetFullScreen(QScreen* screen = nullptr); - RenderBackend* renderer() const - { - return renderer_; - } - ColorManager* color_manager() const { return display_widget_->color_manager(); @@ -196,8 +191,6 @@ private: void RequestNextFrameForQueue(); - PixelFormat::Format GetCurrentPixelFormat() const; - RenderTicketPtr GetFrame(const rational& t, bool clear_render_queue); void FinishPlayPreprocess(); @@ -246,8 +239,6 @@ private: ViewerQueue playback_queue_; int64_t playback_queue_next_frame_; - RenderBackend* renderer_; - bool prequeuing_; QList nonqueue_watchers_; @@ -256,6 +247,8 @@ private: int prequeue_length_; + PreviewAutoCacher auto_cacher_; + static QVector instances_; private slots: diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 983f422e1..68fe1d505 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -31,11 +31,9 @@ #include "common/define.h" #include "common/functiontimer.h" -#include "gizmotraverser.h" -#include "render/backend/opengl/openglrenderfunctions.h" -#include "render/backend/opengl/openglshader.h" -#include "render/pixelformat.h" +#include "config/config.h" #include "core.h" +#include "gizmotraverser.h" OLIVE_NAMESPACE_ENTER @@ -56,7 +54,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : ViewerDisplayWidget::~ViewerDisplayWidget() { - ContextCleanup(); + OnDestroy(); } void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat) @@ -86,7 +84,7 @@ QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrixFlippedYTranslation() { QMatrix4x4 mat = combined_matrix_; - mat.data()[13] *= -1.0f; + mat.scale(1, -1, 1); return mat; } @@ -104,13 +102,14 @@ void ViewerDisplayWidget::SetImage(FramePtr in_buffer) if (last_loaded_buffer_) { makeCurrent(); - if (!texture_.IsCreated() - || texture_.width() != in_buffer->width() - || texture_.height() != in_buffer->height() - || texture_.format() != in_buffer->format()) { - texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), in_buffer->linesize_pixels()); + if (!texture_ + || texture_->width() != in_buffer->width() + || texture_->height() != in_buffer->height() + || texture_->format() != in_buffer->format() + || texture_->channel_count() != in_buffer->channel_count()) { + texture_ = renderer()->CreateTexture(in_buffer->video_params(), in_buffer->data(), in_buffer->linesize_pixels()); } else { - texture_.Upload(in_buffer); + texture_->Upload(in_buffer->data(), in_buffer->linesize_pixels()); } doneCurrent(); @@ -205,7 +204,7 @@ void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) emit DragStarted(); } - QOpenGLWidget::mousePressEvent(event); + ManagedDisplayWidget::mousePressEvent(event); } } @@ -252,7 +251,7 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) } else { // Default behavior - QOpenGLWidget::mouseMoveEvent(event); + ManagedDisplayWidget::mouseMoveEvent(event); } } @@ -275,50 +274,34 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) } else { // Default behavior - QOpenGLWidget::mouseReleaseEvent(event); + ManagedDisplayWidget::mouseReleaseEvent(event); } } -QMatrix4x4 ViewerDisplayWidget::GetMatrixTranslate() +void ViewerDisplayWidget::OnInit() { - return translate_matrix_; + ManagedDisplayWidget::OnInit(); } -void ViewerDisplayWidget::initializeGL() +void ViewerDisplayWidget::OnPaint() { - ManagedDisplayWidget::initializeGL(); - - connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ViewerDisplayWidget::ContextCleanup, Qt::DirectConnection); -} - -void ViewerDisplayWidget::paintGL() -{ - // Get functions attached to this context (they will already be initialized) - QOpenGLFunctions* f = context()->functions(); - // Clear background to empty - f->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - f->glClear(GL_COLOR_BUFFER_BIT); + renderer()->ClearDestination(); // We only draw if we have a pipeline if (last_loaded_buffer_ && color_service()) { - // Bind retrieved texture - f->glBindTexture(GL_TEXTURE_2D, texture_.texture()); - - // Set some parameters - color_service()->pipeline()->bind(); - color_service()->pipeline()->setUniformValue("ove_resolution", texture_.width(), texture_.height()); - color_service()->pipeline()->setUniformValue("ove_deinterlace", deinterlace_); - color_service()->pipeline()->release(); - - // Blit using the color service - color_service()->ProcessOpenGL(true, GetCompleteMatrixFlippedYTranslation()); - - // Release retrieved texture - f->glBindTexture(GL_TEXTURE_2D, 0); + if (deinterlace_) { + qDebug() << "FIXME: Deinterlacing is currently broken, we're working on this..."; + //color_service()->pipeline()->setUniformValue("ove_resolution", texture_.width(), texture_.height()); + //color_service()->pipeline()->setUniformValue("ove_deinterlace", deinterlace_); + } + // Draw texture through color transform + renderer()->BlitColorManaged(color_service(), texture_, true, + VideoParams(width(), height(), static_cast(Config::Current()["OfflinePixelFormat"].toInt()), VideoParams::kInternalChannelCount), + GetCompleteMatrixFlippedYTranslation()); } QTransform world_transform = GenerateWorldTransform(); @@ -331,14 +314,14 @@ void ViewerDisplayWidget::paintGL() gizmo_db_ = gt.GenerateDatabase(gizmos_, TimeRange(node_time, node_time)); - QPainter p(this); + QPainter p(inner_widget()); p.setWorldTransform(world_transform); gizmos_->DrawGizmos(gizmo_db_, &p, QVector2D(GetTexturePosition(size())), size()); } // Draw action/title safe areas if (safe_margin_.is_enabled()) { - QPainter p(this); + QPainter p(inner_widget()); p.setWorldTransform(world_transform); p.setPen(Qt::lightGray); @@ -371,6 +354,18 @@ void ViewerDisplayWidget::paintGL() } } +void ViewerDisplayWidget::OnDestroy() +{ + ManagedDisplayWidget::OnDestroy(); + + texture_ = nullptr; +} + +QMatrix4x4 ViewerDisplayWidget::GetMatrixTranslate() +{ + return translate_matrix_; +} + QPointF ViewerDisplayWidget::GetTexturePosition(const QPoint &screen_pos) { return GetTexturePosition(screen_pos.x(), screen_pos.y()); @@ -426,13 +421,4 @@ QTransform ViewerDisplayWidget::GenerateWorldTransform() return world; } -void ViewerDisplayWidget::ContextCleanup() -{ - makeCurrent(); - - texture_.Destroy(); - - doneCurrent(); -} - OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 0f8f6866f..a8d8b3ade 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -22,12 +22,9 @@ #define VIEWERGLWIDGET_H #include +#include #include "node/node.h" -#include "render/backend/opengl/openglcolorprocessor.h" -#include "render/backend/opengl/openglframebuffer.h" -#include "render/backend/opengl/openglshader.h" -#include "render/backend/opengl/opengltexture.h" #include "render/color.h" #include "render/colormanager.h" #include "tool/tool.h" @@ -181,19 +178,22 @@ protected: */ virtual void mouseReleaseEvent(QMouseEvent* event) override; +protected: /** * @brief Initialize function to set up the OpenGL context upon its construction * * Currently primarily used to regenerate the pipeline shader used for drawing. */ - virtual void initializeGL() override; + virtual void OnInit() override; /** * @brief Paint function to display the texture (received in SetTexture()) on screen. * * Simple OpenGL drawing function for painting the texture on screen. Standardized around OpenGL ES 3.2 Core. */ - virtual void paintGL() override; + virtual void OnPaint() override; + + virtual void OnDestroy() override; private: QPointF GetTexturePosition(const QPoint& screen_pos); @@ -211,7 +211,7 @@ private: /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ - OpenGLTexture texture_; + TexturePtr texture_; /** * @brief Translation only matrix (defaults to identity). @@ -250,12 +250,6 @@ private: bool deinterlace_; -private slots: - /** - * @brief Slot to connect just before the OpenGL context is destroyed to clean up resources - */ - void ContextCleanup(); - }; OLIVE_NAMESPACE_EXIT diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index b33a3e604..1bdbe9219 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -50,8 +50,7 @@ MainMenu::MainMenu(MainWindow *parent) : file_new_menu_ = new Menu(file_menu_); MenuShared::instance()->AddItemsForNewMenu(file_new_menu_); file_open_item_ = file_menu_->AddItem("openproj", Core::instance(), &Core::OpenProject, "Ctrl+O"); - file_open_recent_menu_ = new Menu(file_menu_, this, &MainMenu::PopulateOpenRecent); - connect(file_open_recent_menu_, &Menu::aboutToHide, this, &MainMenu::CloseOpenRecentMenu); + file_open_recent_menu_ = new Menu(file_menu_); file_open_recent_separator_ = file_open_recent_menu_->addSeparator(); file_open_recent_clear_item_ = file_open_recent_menu_->AddItem("clearopenrecent", Core::instance(), &Core::ClearOpenRecentList); file_save_item_ = file_menu_->AddItem("saveproj", Core::instance(), &Core::SaveActiveProject, "Ctrl+S"); @@ -255,6 +254,9 @@ MainMenu::MainMenu(MainWindow *parent) : help_menu_->addSeparator(); help_about_item_ = help_menu_->AddItem("about", Core::instance(), &Core::DialogAboutShow); + connect(Core::instance(), &Core::OpenRecentListChanged, this, &MainMenu::RepopulateOpenRecent); + PopulateOpenRecent(); + Retranslate(); } @@ -398,6 +400,12 @@ void MainMenu::PopulateOpenRecent() } } +void MainMenu::RepopulateOpenRecent() +{ + CloseOpenRecentMenu(); + PopulateOpenRecent(); +} + void MainMenu::CloseOpenRecentMenu() { while (file_open_recent_menu_->actions().first() != file_open_recent_separator_) { diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index 622303900..3bfb691fe 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -96,6 +96,8 @@ private slots: */ void PopulateOpenRecent(); + void RepopulateOpenRecent(); + /** * @brief Clears open recent items when menu closes */ diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index a071b3c39..440795363 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -25,6 +25,10 @@ #include #include +#ifdef Q_OS_LINUX +#include +#endif + #include "mainmenu.h" #include "mainstatusbar.h"