diff --git a/.gitmodules b/.gitmodules index 46b51ee955..2bbb3fe858 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "ext/core"] - path = ext/core - url = https://github.com/olive-editor/core [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets url = https://github.com/olive-editor/KDDockWidgets.git diff --git a/CMakeLists.txt b/CMakeLists.txt index db83ef6be2..04ccc1e0bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,7 +101,7 @@ list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES}) # Link Olive list(APPEND OLIVE_LIBRARIES olivecore) -list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ext/core/include) +list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/lib/olive/include) # Link Qt set(QT_LIBRARIES @@ -168,7 +168,7 @@ list(APPEND OLIVE_LIBRARIES ) # Link FFmpeg -find_package(FFMPEG 3.0 REQUIRED +find_package(FFMPEG 5.0 REQUIRED COMPONENTS avutil avcodec @@ -257,6 +257,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext) add_subdirectory(ext) +add_subdirectory(lib) add_subdirectory(app) if (BUILD_TESTS) diff --git a/app/audio/audioprocessor.cpp b/app/audio/audioprocessor.cpp index cddf5aed90..701c3e8b02 100644 --- a/app/audio/audioprocessor.cpp +++ b/app/audio/audioprocessor.cpp @@ -34,7 +34,6 @@ namespace olive { AudioProcessor::AudioProcessor() { filter_graph_ = nullptr; - in_frame_ = nullptr; out_frame_ = nullptr; } @@ -61,12 +60,12 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double // Set up audio buffer args char filter_args[200]; - snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64, + snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=%s", 1, from.sample_rate(), from.sample_rate(), from_fmt_, - from.channel_layout()); + from.channel_layout().toString().toUtf8().constData()); int r; @@ -120,10 +119,10 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double || (to.format().is_planar() && create_tempo)) { // Tempo processor automatically converts to packed, // so if the desired output is planar, it'll need // to be converted - snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64, + snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=%s", av_get_sample_fmt_name(to_fmt_), to.sample_rate(), - to.channel_layout()); + to.channel_layout().toString().toUtf8().constData()); AVFilterContext *c; r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), "fmt", filter_args, nullptr, filter_graph_); @@ -165,19 +164,6 @@ bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double return false; } - in_frame_ = av_frame_alloc(); - if (in_frame_) { - in_frame_->sample_rate = from.sample_rate(); - in_frame_->format = from_fmt_; - in_frame_->channel_layout = from.channel_layout(); - in_frame_->channels = from.channel_count(); - in_frame_->pts = 0; - } else { - qCritical() << "Failed to allocate input frame"; - Close(); - return false; - } - out_frame_ = av_frame_alloc(); if (!out_frame_) { qCritical() << "Failed to allocate output frame"; @@ -200,11 +186,6 @@ void AudioProcessor::Close() buffersink_ctx_ = nullptr; } - if (in_frame_) { - av_frame_free(&in_frame_); - in_frame_ = nullptr; - } - if (out_frame_) { av_frame_free(&out_frame_); out_frame_ = nullptr; @@ -221,14 +202,32 @@ int AudioProcessor::Convert(float **in, int nb_in_samples, AudioProcessor::Buffe int r = 0; if (in && nb_in_samples) { - // Set frame parameters - in_frame_->nb_samples = nb_in_samples; + // Pass pointers to AVFilter through an AVFrame + AVFrame *in_frame = av_frame_alloc(); + if (!in_frame) { + qCritical() << "Failed to allocate input frame"; + return -1; + } + + in_frame->sample_rate = from_.sample_rate(); + in_frame->format = from_fmt_; + from_.channel_layout().exportTo(&in_frame->ch_layout); + in_frame->pts = 0; + + in_frame->nb_samples = nb_in_samples; + for (int i=0; idata[i] = reinterpret_cast(in[i]); + in_frame->linesize[i] = from_.samples_to_bytes(nb_in_samples); + } + + r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + for (int i=0; idata[i] = reinterpret_cast(in[i]); - in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples); + in_frame->data[i] = nullptr; + in_frame->linesize[i] = 0; } - r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, AV_BUFFERSRC_FLAG_KEEP_REF); + av_frame_free(&in_frame); if (r < 0) { qCritical() << "Failed to add frame to buffersrc:" << r; return r; diff --git a/app/audio/audioprocessor.h b/app/audio/audioprocessor.h index a92e7627f1..0e730fd1eb 100644 --- a/app/audio/audioprocessor.h +++ b/app/audio/audioprocessor.h @@ -22,7 +22,6 @@ #define AUDIOPROCESSOR_H #include -#include #include extern "C" { @@ -30,11 +29,10 @@ extern "C" { } #include "common/define.h" +#include "core.h" namespace olive { -using namespace core; - class AudioProcessor { public: @@ -73,8 +71,6 @@ class AudioProcessor AudioParams to_; AVSampleFormat to_fmt_; - AVFrame *in_frame_; - AVFrame *out_frame_; }; diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 0558eb600c..fef2001f62 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -24,6 +24,7 @@ #include #include "config/config.h" +#include "util/cpuoptimize.h" namespace olive { diff --git a/app/audio/audiovisualwaveform.h b/app/audio/audiovisualwaveform.h index a75e46a2dd..1240df5b11 100644 --- a/app/audio/audiovisualwaveform.h +++ b/app/audio/audiovisualwaveform.h @@ -21,13 +21,12 @@ #ifndef SUMSAMPLES_H #define SUMSAMPLES_H -#include #include #include -namespace olive { +#include "render/samplebuffer.h" -using namespace core; +namespace olive { /** * @brief A buffer of data used to store a visual representation of audio diff --git a/app/codec/conformmanager.cpp b/app/codec/conformmanager.cpp index 70d012a941..8a9330d941 100644 --- a/app/codec/conformmanager.cpp +++ b/app/codec/conformmanager.cpp @@ -2,6 +2,7 @@ #include +#include "common/filefunctions.h" #include "task/taskmanager.h" namespace olive { @@ -66,7 +67,7 @@ QVector ConformManager::GetConformedFilename(const QString &cache_path, QString::number(stream.stream()), QString::number(params.sample_rate()), QString::number(params.format()), - QString::number(params.channel_layout()), + params.channel_layout().toString(), QString::number(i)); filenames[i] = QDir(cache_path).filePath(index_fn); diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 185a964bb0..f907286bbf 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -123,7 +123,7 @@ TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) return cached_texture_; } -Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) +Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const rational &time, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); @@ -139,18 +139,24 @@ Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const Ti return kInvalid; } - // Get conform state from ConformManager - ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, params, (mode == RenderMode::kOnline)); - if (conform.state == ConformManager::kConformGenerating) { - // If we need the task, it's available in `conform.task` - return kWaitingForConform; - } + const bool use_conforms = false; + + if (use_conforms) { + // Get conform state from ConformManager + ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, dest.audio_params(), (mode == RenderMode::kOnline)); + if (conform.state == ConformManager::kConformGenerating) { + // If we need the task, it's available in `conform.task` + return kWaitingForConform; + } - // See if we got the conform - if (RetrieveAudioFromConform(dest, conform.filenames, range, loop_mode, params)) { - return kOK; + // See if we got the conform + if (RetrieveAudioFromConform(dest, conform.filenames, time, loop_mode)) { + return kOK; + } else { + return kUnknownError; + } } else { - return kUnknownError; + return RetrieveAudioInternal(dest, time); } } @@ -280,14 +286,16 @@ bool Decoder::ConformAudioInternal(const QVector &filenames, const Audi return false; } -bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, rational time, LoopMode loop_mode) { + const AudioParams &input_params = sample_buffer.audio_params(); + PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { // Offset range by audio start offset - range -= GetAudioStartOffset(); + time -= GetAudioStartOffset(); - qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); + qint64 read_index = input_params.time_to_bytes(time) / input_params.channel_count(); qint64 write_index = 0; const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index de258296d1..42fae0ded7 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -192,7 +192,7 @@ class Decoder : public QObject * * This function is thread safe and can only run while the decoder is open. \see Open() */ - RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode); + RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const rational& time, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode); /** * @brief Determine the last time this decoder instance was used in any way @@ -272,6 +272,11 @@ class Decoder : public QObject */ virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p); + virtual RetrieveAudioStatus RetrieveAudioInternal(SampleBuffer &dest, const rational &time) + { + return kOK; + } + virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled); void SignalProcessingProgress(int64_t ts, int64_t duration); @@ -288,6 +293,8 @@ class Decoder : public QObject virtual rational GetAudioStartOffset() const { return 0; } + virtual int GetAudioSampleRate() const { return 0; } + signals: /** * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if @@ -298,7 +305,7 @@ class Decoder : public QObject private: void UpdateLastAccessed(); - bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, rational time, LoopMode loop_mode); CodecStream stream_; diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 810cdcd1bb..56fa88ffc8 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -22,6 +22,7 @@ #include +#include "common/filefunctions.h" #include "common/xmlutils.h" #include "ffmpeg/ffmpegencoder.h" #include "oiio/oiioencoder.h" @@ -201,8 +202,8 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); - writer->writeTextElement(QStringLiteral("customrangein"), QString::fromStdString(custom_range_.in().toString())); - writer->writeTextElement(QStringLiteral("customrangeout"), QString::fromStdString(custom_range_.out().toString())); + writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString()); + writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString()); writer->writeStartElement(QStringLiteral("video")); @@ -213,8 +214,8 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width())); writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height())); writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format())); - writer->writeTextElement(QStringLiteral("pixelaspect"), QString::fromStdString(video_params_.pixel_aspect_ratio().toString())); - writer->writeTextElement(QStringLiteral("timebase"), QString::fromStdString(video_params_.time_base().toString())); + writer->writeTextElement(QStringLiteral("pixelaspect"), video_params_.pixel_aspect_ratio().toString()); + writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString()); writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider())); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_)); writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_min_bit_rate_)); @@ -256,8 +257,8 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const if (audio_enabled_) { writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_)); writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate())); - writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout())); - writer->writeTextElement(QStringLiteral("format"), QString::fromStdString(audio_params_.format().to_string())); + writer->writeTextElement(QStringLiteral("channellayout"), audio_params_.channel_layout().toString()); + writer->writeTextElement(QStringLiteral("format"), audio_params_.format().to_string()); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(audio_bit_rate_)); } @@ -380,9 +381,9 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("range")) { has_custom_range_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("customrangein")) { - custom_range_in = rational::fromString(reader->readElementText().toStdString()); + custom_range_in = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("customrangeout")) { - custom_range_out = rational::fromString(reader->readElementText().toStdString()); + custom_range_out = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("video")) { XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("enabled")) { @@ -400,9 +401,9 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("format")) { video_params_.set_format(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("pixelaspect")) { - video_params_.set_pixel_aspect_ratio(rational::fromString(reader->readElementText().toStdString())); + video_params_.set_pixel_aspect_ratio(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("timebase")) { - video_params_.set_time_base(rational::fromString(reader->readElementText().toStdString())); + video_params_.set_time_base(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("divider")) { video_params_.set_divider(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("bitrate")) { @@ -469,9 +470,9 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("samplerate")) { audio_params_.set_sample_rate(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("channellayout")) { - audio_params_.set_channel_layout(reader->readElementText().toULongLong()); + audio_params_.set_channel_layout(AudioChannelLayout::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("format")) { - audio_params_.set_format(SampleFormat::from_string(reader->readElementText().toStdString())); + audio_params_.set_format(SampleFormat::from_string(reader->readElementText())); } else if (reader->name() == QStringLiteral("bitrate")) { audio_bit_rate_ = reader->readElementText().toLongLong(); } else { diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 65702a36f2..2f6757d6b5 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -229,7 +229,7 @@ class Encoder : public QObject public slots: virtual bool Open() = 0; - virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) = 0; + virtual bool WriteFrame(olive::FramePtr frame, rational time) = 0; virtual bool WriteAudio(const olive::SampleBuffer &audio) = 0; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0; diff --git a/app/codec/exportformat.h b/app/codec/exportformat.h index 1ede4ab499..b397fa31bc 100644 --- a/app/codec/exportformat.h +++ b/app/codec/exportformat.h @@ -26,6 +26,7 @@ #include "common/define.h" #include "exportcodec.h" +#include "render/sampleformat.h" namespace olive { diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 938ff3f221..1eb5c2e428 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -52,6 +52,7 @@ QVariant DeinterlaceShader; FFmpegDecoder::FFmpegDecoder() : sws_ctx_(nullptr), + swr_ctx_(nullptr), working_packet_(nullptr), cache_at_zero_(false), cache_at_eof_(false) @@ -167,17 +168,17 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVi TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->linesize[2] / px_size); ShaderJob job; - job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); - job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); - job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); - job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); - job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG)); + job.Insert(QStringLiteral("y_channel"), y_plane); + job.Insert(QStringLiteral("u_channel"), u_plane); + job.Insert(QStringLiteral("v_channel"), v_plane); + job.Insert(QStringLiteral("bits_per_pixel"), bits_per_pixel); + job.Insert(QStringLiteral("full_range"), hw_in->color_range == AVCOL_RANGE_JPEG); const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace)); - job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0)); - job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0)); - job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0)); - job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0)); + job.Insert(QStringLiteral("yuv_crv"), yuv_coeffs[0]/65536.0); + job.Insert(QStringLiteral("yuv_cgu"), yuv_coeffs[2]/65536.0); + job.Insert(QStringLiteral("yuv_cgv"), yuv_coeffs[3]/65536.0); + job.Insert(QStringLiteral("yuv_cbu"), yuv_coeffs[1]/65536.0); tex = p.renderer->CreateTexture(vp); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); @@ -208,7 +209,7 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVi // Flip frame rate so it can be used as a timebase frame_rate_tb.flip(); - int64_t req = Timecode::time_to_timestamp(p.time + rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE), frame_rate_tb); + int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb) + av_rescale_q(instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb.toAVRational()); int64_t frm = Timecode::rescale_timestamp(original->pts, instance_.avstream()->time_base, frame_rate_tb); bool first = (req == frm); @@ -219,9 +220,9 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVi TexturePtr deinterlaced = p.renderer->CreateTexture(tex->params()); ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, tex)); - job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); - job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, original->height)); + job.Insert(QStringLiteral("ove_maintex"), tex); + job.Insert(QStringLiteral("interlacing"), interlacing); + job.Insert(QStringLiteral("pixel_height"), original->height); p.renderer->BlitToTexture(DeinterlaceShader, job, deinterlaced.get(), false); @@ -270,6 +271,8 @@ void FFmpegDecoder::CloseInternal() ClearFrameCache(); FreeScaler(); + FreeResampler(); + instance_.Close(); } @@ -285,6 +288,11 @@ rational FFmpegDecoder::GetAudioStartOffset() const } } +int FFmpegDecoder::GetAudioSampleRate() const +{ + return instance_.avstream()->codecpar->sample_rate; +} + QString FFmpegDecoder::id() const { return QStringLiteral("ffmpeg"); @@ -439,10 +447,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // Create an audio stream object - uint64_t channel_layout = avstream->codecpar->channel_layout; - if (!channel_layout) { - channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); - } + AudioChannelLayout channel_layout = avstream->codecpar->ch_layout; if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) { // Loop through stream until we get the whole duration @@ -550,29 +555,23 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, cons // Seek to starting point instance_.Seek(0); - // Handle NULL channel layout - uint64_t channel_layout = ValidateChannelLayout(instance_.avstream()); - if (!channel_layout) { - qCritical() << "Failed to determine channel layout of audio file, could not conform"; - return false; - } - // Create resampling context - SwrContext* resampler = swr_alloc_set_opts(nullptr, - params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(params.format()), - params.sample_rate(), - channel_layout, - static_cast(instance_.avstream()->codecpar->format), - instance_.avstream()->codecpar->sample_rate, - 0, - nullptr); + int ret; + SwrContext* resampler; + ret = swr_alloc_set_opts2(&resampler, + params.channel_layout().ref(), + FFmpegUtils::GetFFmpegSampleFormat(params.format()), + params.sample_rate(), + &instance_.avstream()->codecpar->ch_layout, + static_cast(instance_.avstream()->codecpar->format), + instance_.avstream()->codecpar->sample_rate, + 0, + nullptr); swr_init(resampler); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); - int ret; bool success = false; @@ -661,6 +660,99 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, cons return success; } +Decoder::RetrieveAudioStatus FFmpegDecoder::RetrieveAudioInternal(SampleBuffer &dest, const rational& time) +{ + // Calculate our target sample + const AudioParams ¶ms = dest.audio_params(); + AVRational dst_tb = params.sample_rate_as_time_base().toAVRational(); + + // Calculate stream's timestamp at this time + int64_t ts = Timecode::time_to_timestamp(time, dst_tb, Timecode::kRound); + + // Seek to correct part of the audio if necessary + if (swr_time_ == AV_NOPTS_VALUE + || ts < swr_time_ + || ts >= swr_time_ + params.sample_rate()) { + if (swr_time_ == AV_NOPTS_VALUE) { + qDebug() << "seeking because of no swr time"; + } else { + qDebug() << "seeking" << this->stream().filename() << "because" << ts << "is <" << swr_time_ << "or >=" << (swr_time_ + params.sample_rate()); + } + int64_t seek_to = av_rescale_q(ts, dst_tb, instance_.avstream()->time_base); + seek_to = std::max(int64_t(0), seek_to - second_ts_); + instance_.Seek(seek_to); + FreeResampler(); + } + + // Ensure swr_ctx is initialized + Decoder::RetrieveAudioStatus resampler_status = ValidateResampler(params); + if (resampler_status != kOK) { + return resampler_status; + } + + // Copy data continuously to output, resampling if necessary + AVFramePtr decoded = CreateAVFramePtr(); + int dst_bpc = params.format().byte_count(); + auto dst_ptrs_vector = dest.to_raw_ptrs(); + uint8_t **dst_ptrs = reinterpret_cast(dst_ptrs_vector.data()); + int64_t dst_offset = std::max(int64_t(0), -ts); + for (size_t j = dst_offset; j < dest.sample_count(); ) { + // Read frame, if a frame wasn't allocated by an earlier seek + int ret = instance_.GetFrame(working_packet_, decoded.get()); + uint8_t **in_data = decoded->data; + if (ret < 0) { + if (ret != AVERROR_EOF) { + FFmpegError(ret); + return kUnknownError; + } else { + in_data = nullptr; + } + } + + // Fill in SwrContext's current time if it doesn't yet exist + if (swr_time_ == AV_NOPTS_VALUE) { + swr_time_ = av_rescale_q(decoded->pts, instance_.avstream()->time_base, dst_tb); + } + + // Determine if any samples need to be skipped + int64_t skip = (ts + j) - swr_time_; + if (skip > 0) { + swr_drop_output(swr_ctx_, skip); + swr_time_ += skip; + } + + // Push data to swresample + int copied = swr_convert(swr_ctx_, + dst_ptrs, dest.sample_count() - j, + const_cast(in_data), decoded->nb_samples); + if (copied < 0) { + // Encountered an error, handle it + if (copied != AVERROR_EOF) { + FFmpegError(ret); + return kUnknownError; + } else { + break; + } + } + + // Increment SwrContext sample count + swr_time_ += copied; + + // If reached EOF and SwrContext is fully flushed, exit now + if (ret == AVERROR_EOF && !in_data && copied == 0) { + break; + } + + // Limit + j += copied; + for (size_t i=0; icodecpar->channel_layout) { - return stream->codecpar->channel_layout; - } - - return av_get_default_channel_layout(stream->codecpar->channels); -} - const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing) { if (interlacing == VideoParams::kInterlacedTopFirst) { @@ -830,10 +913,6 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancel { int64_t target_ts = Timecode::time_to_timestamp(time, instance_.avstream()->time_base); - if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) { - target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base); - } - const int64_t min_seek = 0; int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; @@ -967,6 +1046,15 @@ void FFmpegDecoder::FreeScaler() } } +void FFmpegDecoder::FreeResampler() +{ + if (swr_ctx_) { + swr_free(&swr_ctx_); + swr_ctx_ = nullptr; + } + swr_time_ = AV_NOPTS_VALUE; +} + AVFramePtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const { if (t < cached_frames_.front()->pts) { @@ -1008,6 +1096,56 @@ void FFmpegDecoder::RemoveFirstFrame() cache_at_zero_ = false; } +Decoder::RetrieveAudioStatus FFmpegDecoder::ValidateResampler(const AudioParams &output) +{ + auto src_fmt_native = FFmpegUtils::GetNativeSampleFormat(static_cast(instance_.avstream()->codecpar->format)); + + // We need a resampler. Check if one already exists. + if (swr_ctx_ + && swr_irate_ == instance_.avstream()->codecpar->sample_rate + && swr_ichannels_ == instance_.avstream()->codecpar->ch_layout + && swr_iformat_ == src_fmt_native + && swr_orate_ == output.sample_rate() + && swr_ochannels_ == output.channel_layout() + && swr_oformat_ == output.format()) { + // Resampler is valid, continue + } else { + // Resampler must be (re)created + FreeResampler(); + + int r; + + r = swr_alloc_set_opts2(&swr_ctx_, + output.channel_layout().ref(), + FFmpegUtils::GetFFmpegSampleFormat(output.format()), + output.sample_rate(), + &instance_.avstream()->codecpar->ch_layout, + static_cast(instance_.avstream()->codecpar->format), + instance_.avstream()->codecpar->sample_rate, + 0, nullptr); + + if (!swr_ctx_) { + qCritical() << "Failed to allocate SwrContext"; + return kUnknownError; + } + + r = swr_init(swr_ctx_); + if (r < 0) { + FFmpegError(r); + return kUnknownError; + } + + swr_irate_ = instance_.avstream()->codecpar->sample_rate; + swr_ichannels_ = instance_.avstream()->codecpar->ch_layout; + swr_iformat_ = src_fmt_native; + swr_orate_ = output.sample_rate(); + swr_ochannels_ = output.channel_layout(); + swr_oformat_ = output.format(); + } + + return kOK; +} + int FFmpegDecoder::MaximumQueueSize() { // Fairly arbitrary size. This used to need to be the number of current threads to ensure any diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index bc37233748..f8b3c9ec8e 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -65,9 +65,11 @@ class FFmpegDecoder : public Decoder virtual bool OpenInternal() override; virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; + virtual RetrieveAudioStatus RetrieveAudioInternal(SampleBuffer &dest, const rational& time) override; virtual void CloseInternal() override; virtual rational GetAudioStartOffset() const override; + virtual int GetAudioSampleRate() const override; private: class Instance @@ -136,10 +138,11 @@ class FFmpegDecoder : public Decoder void FreeScaler(); + void FreeResampler(); + static PixelFormat GetNativePixelFormat(AVPixelFormat pix_fmt); - static int GetNativeChannelCount(AVPixelFormat pix_fmt); - static uint64_t ValidateChannelLayout(AVStream *stream); + static int GetNativeChannelCount(AVPixelFormat pix_fmt); static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing); @@ -157,6 +160,11 @@ class FFmpegDecoder : public Decoder void RemoveFirstFrame(); + Decoder::RetrieveAudioStatus ValidateResampler(const AudioParams &output); + + int64_t ts_to_samples(int64_t t) const; + int64_t samples_to_ts(int64_t s) const; + static int MaximumQueueSize(); SwsContext *sws_ctx_; @@ -169,6 +177,15 @@ class FFmpegDecoder : public Decoder AVColorRange sws_colrange_; AVColorSpace sws_colspace_; + SwrContext *swr_ctx_; + int swr_irate_; + AudioChannelLayout swr_ichannels_; + SampleFormat swr_iformat_; + int swr_orate_; + AudioChannelLayout swr_ochannels_; + SampleFormat swr_oformat_; + int64_t swr_time_; + AVPacket *working_packet_; int64_t second_ts_; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index f5ca225a4d..5089c7482c 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -334,7 +334,7 @@ bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, const uint8_ int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400; uint8_t** output_data = nullptr; int output_linesize; - av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->channels, + av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->ch_layout.nb_channels, output_sample_count, static_cast(audio_stream_->codecpar->format), 0); // Perform conversion @@ -349,7 +349,7 @@ bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, const uint8_ av_samples_copy(audio_frame_->data, output_data, audio_frame_offset_, i, copy_length, - audio_frame_->channels, static_cast(audio_frame_->format)); + audio_frame_->ch_layout.nb_channels, static_cast(audio_frame_->format)); audio_frame_offset_ += copy_length; i += copy_length; @@ -690,8 +690,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV // Assume audio stream codec_ctx->sample_rate = params().audio_params().sample_rate(); - codec_ctx->channel_layout = params().audio_params().channel_layout(); - codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout); + params().audio_params().channel_layout().exportTo(codec_ctx->ch_layout); codec_ctx->sample_fmt = FFmpegUtils::GetFFmpegSampleFormat(params().audio_params().format()); codec_ctx->time_base = {1, codec_ctx->sample_rate}; @@ -829,22 +828,24 @@ bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio) } // Create resample context - audio_resample_ctx_ = swr_alloc_set_opts(nullptr, - static_cast(audio_codec_ctx_->channel_layout), - audio_codec_ctx_->sample_fmt, - audio_codec_ctx_->sample_rate, - static_cast(audio.channel_layout()), - FFmpegUtils::GetFFmpegSampleFormat(audio.format()), - audio.sample_rate(), - 0, - nullptr); - if (!audio_resample_ctx_) { + int err; + err = swr_alloc_set_opts2(&audio_resample_ctx_, + &audio_codec_ctx_->ch_layout, + audio_codec_ctx_->sample_fmt, + audio_codec_ctx_->sample_rate, + audio.channel_layout().ref(), + FFmpegUtils::GetFFmpegSampleFormat(audio.format()), + audio.sample_rate(), + 0, + nullptr); + if (err < 0) { + FFmpegError(tr("Failed to create resampling context"), err); return false; } - int err = swr_init(audio_resample_ctx_); + err = swr_init(audio_resample_ctx_); if (err < 0) { - FFmpegError(tr("Failed to create resampling context"), err); + FFmpegError(tr("Failed to initialize resampling context"), err); return false; } @@ -865,7 +866,7 @@ bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio) return false; } - audio_frame_->channel_layout = audio_codec_ctx_->channel_layout; + audio_frame_->ch_layout = audio_codec_ctx_->ch_layout; audio_frame_->format = audio_codec_ctx_->sample_fmt; audio_frame_->nb_samples = audio_max_samples_; diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 3c1ec49ed1..c095cc5560 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -45,7 +45,7 @@ class FFmpegEncoder : public Encoder virtual bool Open() override; - virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) override; + virtual bool WriteFrame(olive::FramePtr frame, rational time) override; virtual bool WriteAudio(const olive::SampleBuffer &audio) override; diff --git a/app/codec/frame.h b/app/codec/frame.h index 30a6ebe86c..221060644f 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -22,11 +22,11 @@ #define FRAME_H #include -#include #include #include "common/define.h" #include "render/videoparams.h" +#include "util/color.h" namespace olive { diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e3efc41086..82f9ae6dd6 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -134,10 +134,10 @@ TexturePtr OIIODecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) if (p.divider == 1) { // Just upload straight to the buffer - image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); + image_->read_image(0, 0, 0, -1, oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); } else { OIIO::ImageBuf buf(image_->spec()); - image_->read_image(image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride()); + image_->read_image(0, 0, 0, -1, image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride()); // Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here) int px_sz = vp.GetBytesPerPixel(); diff --git a/app/codec/oiio/oiioencoder.h b/app/codec/oiio/oiioencoder.h index 778a914747..7a6268c5da 100644 --- a/app/codec/oiio/oiioencoder.h +++ b/app/codec/oiio/oiioencoder.h @@ -34,7 +34,7 @@ class OIIOEncoder : public Encoder public slots: virtual bool Open() override; - virtual bool WriteFrame(olive::FramePtr frame, olive::core::rational time) override; + virtual bool WriteFrame(olive::FramePtr frame, rational time) override; virtual bool WriteAudio(const SampleBuffer &audio) override; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override; diff --git a/app/codec/planarfiledevice.h b/app/codec/planarfiledevice.h index feaf2c6475..d0327ab4fd 100644 --- a/app/codec/planarfiledevice.h +++ b/app/codec/planarfiledevice.h @@ -21,13 +21,12 @@ #ifndef PLANARFILEDEVICE_H #define PLANARFILEDEVICE_H -#include #include #include -namespace olive { +#include "core.h" -using namespace core; +namespace olive { class PlanarFileDevice : public QObject { diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index 81ae18b4f3..06800019fb 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -20,6 +20,8 @@ #include "common/ffmpegutils.h" +#include "render/videoparams.h" + namespace olive { AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, PixelFormat maximum) diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 89252fd53d..81e85e239e 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -27,14 +27,13 @@ extern "C" { #include } -#include +#include -#include "render/videoparams.h" +#include "render/pixelformat.h" +#include "render/sampleformat.h" namespace olive { -using namespace core; - class FFmpegUtils { public: /** diff --git a/app/common/html.cpp b/app/common/html.cpp index 74cc805f91..bcc802aa3a 100644 --- a/app/common/html.cpp +++ b/app/common/html.cpp @@ -248,7 +248,7 @@ void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt) } if (fmt.foreground().style() != Qt::NoBrush) { - const QColor &color = fmt.foreground().color(); + QColor color = fmt.foreground().color(); QString cs; if (color.alpha() == 255) { diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index 95ca7b7125..a21f18668f 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -182,7 +182,7 @@ void QtUtils::SetComboBoxData(QComboBox *cb, const QString &data) } } -QColor QtUtils::toQColor(const core::Color &i) +QColor QtUtils::toQColor(const Color &i) { QColor c; @@ -195,19 +195,19 @@ QColor QtUtils::toQColor(const core::Color &i) return c; } -namespace core { - -uint qHash(const core::rational &r, uint seed) +uint qHash(const rational &r, uint seed) { return ::qHash(r.toDouble(), seed); } -uint qHash(const core::TimeRange &r, uint seed) +uint qHash(const TimeRange &r, uint seed) { return qHash(r.in(), seed) ^ qHash(r.out(), seed); } - +uint qHash(const AudioParams &r, uint seed) +{ + return qHash(r.sample_rate(), seed) ^ qHash(r.channel_layout(), seed) ^ qHash(r.format().to_string(), seed); } } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index c4eeef0272..a3a0f0279c 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -21,7 +21,6 @@ #ifndef QTVERSIONABSTRACTION_H #define QTVERSIONABSTRACTION_H -#include #include #include #include @@ -29,6 +28,12 @@ #include #include +#include "render/samplebuffer.h" +#include "util/bezier.h" +#include "util/color.h" +#include "util/rational.h" +#include "util/timerange.h" + namespace olive { class QtUtils { @@ -74,7 +79,7 @@ class QtUtils { return nullptr; } - static QColor toQColor(const core::Color &c); + static QColor toQColor(const Color &c); /** * @brief Convert a pointer to a value that can be sent between NodeParams @@ -95,20 +100,18 @@ class QtUtils { }; -namespace core { - -uint qHash(const core::rational& r, uint seed = 0); -uint qHash(const core::TimeRange& r, uint seed = 0); - -} +uint qHash(const rational &r, uint seed = 0); +uint qHash(const TimeRange &r, uint seed = 0); +uint qHash(const AudioParams& r, uint seed = 0); } -Q_DECLARE_METATYPE(olive::core::rational); -Q_DECLARE_METATYPE(olive::core::Color); -Q_DECLARE_METATYPE(olive::core::TimeRange); -Q_DECLARE_METATYPE(olive::core::Bezier); -Q_DECLARE_METATYPE(olive::core::AudioParams); -Q_DECLARE_METATYPE(olive::core::SampleBuffer); +Q_DECLARE_METATYPE(olive::AudioChannelLayout); +Q_DECLARE_METATYPE(olive::AudioParams); +Q_DECLARE_METATYPE(olive::Bezier); +Q_DECLARE_METATYPE(olive::Color); +Q_DECLARE_METATYPE(olive::SampleBuffer); +Q_DECLARE_METATYPE(olive::TimeRange); +Q_DECLARE_METATYPE(olive::rational); #endif // QTVERSIONABSTRACTION_H diff --git a/app/config/config.cpp b/app/config/config.cpp index 336b1eeb49..dac3d2c249 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -46,11 +46,6 @@ Config::Config() SetDefaults(); } -void Config::SetEntryInternal(const QString &key, NodeValue::Type type, const QVariant &data) -{ - config_map_[key] = {type, data}; -} - QString Config::GetConfigFilePath() { return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("config.xml")); @@ -64,110 +59,114 @@ Config &Config::Current() void Config::SetDefaults() { config_map_.clear(); - SetEntryInternal(QStringLiteral("Style"), NodeValue::kText, StyleManager::kDefaultStyle); - SetEntryInternal(QStringLiteral("TimecodeDisplay"), NodeValue::kInt, Timecode::kTimecodeDropFrame); - SetEntryInternal(QStringLiteral("DefaultStillLength"), NodeValue::kRational, QVariant::fromValue(rational(2))); - SetEntryInternal(QStringLiteral("HoverFocus"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("AudioScrubbing"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("AutorecoveryEnabled"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("AutorecoveryInterval"), NodeValue::kInt, 1); - SetEntryInternal(QStringLiteral("AutorecoveryMaximum"), NodeValue::kInt, 20); - SetEntryInternal(QStringLiteral("DiskCacheSaveInterval"), NodeValue::kInt, 10000); - SetEntryInternal(QStringLiteral("Language"), NodeValue::kText, QString()); - SetEntryInternal(QStringLiteral("ScrollZooms"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("EnableSeekToImport"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("EditToolAlsoSeeks"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("EditToolSelectsLinks"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("EnableDragFilesToTimeline"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("InvertTimelineScrollAxes"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("PasteSeeks"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("SeekAlsoSelects"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("AutoSeekToBeginning"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("DropFileOnMediaToReplace"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("AddDefaultEffectsToClips"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("AutoscaleByDefault"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("Autoscroll"), NodeValue::kInt, AutoScroll::kPage); - SetEntryInternal(QStringLiteral("AutoSelectDivider"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeValue::kInt, ImportTool::kDWSAsk); - SetEntryInternal(QStringLiteral("Loop"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("SplitClipsCopyNodes"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("UseGradients"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("AutoMergeTracks"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("UseSliderLadders"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("ShowWelcomeDialog"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("ShowClipWhileDragging"), NodeValue::kBoolean, true); - SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false); - - SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailInOut); - SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); - - SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); - SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); - SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); - - SetEntryInternal(QStringLiteral("DefaultSubtitleSize"), NodeValue::kInt, 48); - SetEntryInternal(QStringLiteral("DefaultSubtitleFamily"), NodeValue::kText, QString()); - SetEntryInternal(QStringLiteral("DefaultSubtitleWeight"), NodeValue::kInt, QFont::Bold); - SetEntryInternal(QStringLiteral("AntialiasSubtitles"), NodeValue::kBoolean, true); - - SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); - - SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed); - SetEntryInternal(QStringLiteral("CatColor1"), NodeValue::kInt, ColorCoding::kMaroon); - SetEntryInternal(QStringLiteral("CatColor2"), NodeValue::kInt, ColorCoding::kOrange); - SetEntryInternal(QStringLiteral("CatColor3"), NodeValue::kInt, ColorCoding::kBrown); - SetEntryInternal(QStringLiteral("CatColor4"), NodeValue::kInt, ColorCoding::kYellow); - SetEntryInternal(QStringLiteral("CatColor5"), NodeValue::kInt, ColorCoding::kOlive); - SetEntryInternal(QStringLiteral("CatColor6"), NodeValue::kInt, ColorCoding::kLime); - SetEntryInternal(QStringLiteral("CatColor7"), NodeValue::kInt, ColorCoding::kGreen); - SetEntryInternal(QStringLiteral("CatColor8"), NodeValue::kInt, ColorCoding::kCyan); - SetEntryInternal(QStringLiteral("CatColor9"), NodeValue::kInt, ColorCoding::kTeal); - SetEntryInternal(QStringLiteral("CatColor10"), NodeValue::kInt, ColorCoding::kBlue); - SetEntryInternal(QStringLiteral("CatColor11"), NodeValue::kInt, ColorCoding::kNavy); - - SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); - SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); - - SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000); - SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); - SetEntryInternal(QStringLiteral("AudioOutputSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); - - SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV); - SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM); - SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000); - SetEntryInternal(QStringLiteral("AudioRecordingChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); - SetEntryInternal(QStringLiteral("AudioRecordingSampleFormat"), NodeValue::kText, QString::fromStdString(SampleFormat(SampleFormat::S16).to_string())); - SetEntryInternal(QStringLiteral("AudioRecordingBitRate"), NodeValue::kInt, 320); - - SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeValue::kRational, QVariant::fromValue(rational(0))); - SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeValue::kRational, QVariant::fromValue(rational(60))); - - SetEntryInternal(QStringLiteral("DefaultSequenceWidth"), NodeValue::kInt, 1920); - SetEntryInternal(QStringLiteral("DefaultSequenceHeight"), NodeValue::kInt, 1080); - SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeValue::kRational, QVariant::fromValue(rational(1))); - SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeValue::kRational, QVariant::fromValue(rational(1001, 30000))); - SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeValue::kInt, VideoParams::kInterlaceNone); - SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache2"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeValue::kInt, 48000); - SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); + + OLIVE_CONFIG("Style") = StyleManager::kDefaultStyle; + OLIVE_CONFIG("TimecodeDisplay") = Timecode::kTimecodeDropFrame; + OLIVE_CONFIG("DefaultStillLength") = rational(2); + OLIVE_CONFIG("HoverFocus") = false; + OLIVE_CONFIG("AudioScrubbing") = true; + OLIVE_CONFIG("AutorecoveryEnabled") = true; + OLIVE_CONFIG("AutorecoveryInterval") = 1; + OLIVE_CONFIG("AutorecoveryMaximum") = 20; + OLIVE_CONFIG("DiskCacheSaveInterval") = 10000; + OLIVE_CONFIG("Language") = QString(); + OLIVE_CONFIG("ScrollZooms") = false; + OLIVE_CONFIG("EnableSeekToImport") = false; + OLIVE_CONFIG("EditToolAlsoSeeks") = false; + OLIVE_CONFIG("EditToolSelectsLinks") = false; + OLIVE_CONFIG("EnableDragFilesToTimeline") = true; + OLIVE_CONFIG("InvertTimelineScrollAxes") = true; + OLIVE_CONFIG("SelectAlsoSeeks") = false; + OLIVE_CONFIG("PasteSeeks") = true; + OLIVE_CONFIG("SeekAlsoSelects") = false; + OLIVE_CONFIG("SetNameWithMarker") = false; + OLIVE_CONFIG("AutoSeekToBeginning") = true; + OLIVE_CONFIG("DropFileOnMediaToReplace") = false; + OLIVE_CONFIG("AddDefaultEffectsToClips") = true; + OLIVE_CONFIG("AutoscaleByDefault") = false; + OLIVE_CONFIG("Autoscroll") = AutoScroll::kPage; + OLIVE_CONFIG("AutoSelectDivider") = true; + OLIVE_CONFIG("SetNameWithMarker") = false; + OLIVE_CONFIG("RectifiedWaveforms") = true; + OLIVE_CONFIG("DropWithoutSequenceBehavior") = ImportTool::kDWSAsk; + OLIVE_CONFIG("Loop") = false; + OLIVE_CONFIG("SplitClipsCopyNodes") = true; + OLIVE_CONFIG("UseGradients") = true; + OLIVE_CONFIG("AutoMergeTracks") = true; + OLIVE_CONFIG("UseSliderLadders") = true; + OLIVE_CONFIG("ShowWelcomeDialog") = true; + OLIVE_CONFIG("ShowClipWhileDragging") = true; + OLIVE_CONFIG("StopPlaybackOnLastFrame") = false; + OLIVE_CONFIG("UseLegacyColorInInputTab") = false; + OLIVE_CONFIG("ReassocLinToNonLin") = false; + OLIVE_CONFIG("PreviewNonFloatDontAskAgain") = false; + OLIVE_CONFIG("UseGLFinish") = false; + + OLIVE_CONFIG("TimelineThumbnailMode") = Timeline::kThumbnailInOut; + OLIVE_CONFIG("TimelineWaveformMode") = Timeline::kWaveformsEnabled; + + OLIVE_CONFIG("DefaultVideoTransition") = QStringLiteral("org.olivevideoeditor.Olive.crossdissolve"); + OLIVE_CONFIG("DefaultAudioTransition") = QStringLiteral("org.olivevideoeditor.Olive.crossdissolve"); + OLIVE_CONFIG("DefaultTransitionLength") = rational(1); + + OLIVE_CONFIG("DefaultSubtitleSize") = 48; + OLIVE_CONFIG("DefaultSubtitleFamily") = QString(); + OLIVE_CONFIG("DefaultSubtitleWeight") = QFont::Bold; + OLIVE_CONFIG("AntialiasSubtitles") = true; + + OLIVE_CONFIG("AutoCacheDelay") = 1000; + + OLIVE_CONFIG("CatColor0") = ColorCoding::kRed; + OLIVE_CONFIG("CatColor1") = ColorCoding::kMaroon; + OLIVE_CONFIG("CatColor2") = ColorCoding::kOrange; + OLIVE_CONFIG("CatColor3") = ColorCoding::kBrown; + OLIVE_CONFIG("CatColor4") = ColorCoding::kYellow; + OLIVE_CONFIG("CatColor5") = ColorCoding::kOlive; + OLIVE_CONFIG("CatColor6") = ColorCoding::kLime; + OLIVE_CONFIG("CatColor7") = ColorCoding::kGreen; + OLIVE_CONFIG("CatColor8") = ColorCoding::kCyan; + OLIVE_CONFIG("CatColor9") = ColorCoding::kTeal; + OLIVE_CONFIG("CatColor10") = ColorCoding::kBlue; + OLIVE_CONFIG("CatColor11") = ColorCoding::kNavy; + + OLIVE_CONFIG("AudioOutput") = QString(); + OLIVE_CONFIG("AudioInput") = QString(); + + OLIVE_CONFIG("AudioOutputSampleRate") = 48000; + OLIVE_CONFIG("AudioOutputChannelLayout") = AudioChannelLayout::STEREO.toString(); + OLIVE_CONFIG("AudioOutputSampleFormat") = SampleFormat(SampleFormat::S16).to_string(); + + OLIVE_CONFIG("AudioRecordingFormat") = ExportFormat::kFormatWAV; + OLIVE_CONFIG("AudioRecordingCodec") = ExportCodec::kCodecPCM; + OLIVE_CONFIG("AudioRecordingSampleRate") = 48000; + OLIVE_CONFIG("AudioRecordingChannelLayout") = AudioChannelLayout::STEREO.toString(); + OLIVE_CONFIG("AudioRecordingSampleFormat") = SampleFormat(SampleFormat::S16).to_string(); + OLIVE_CONFIG("AudioRecordingBitRate") = 320; + + OLIVE_CONFIG("DiskCacheBehind") = rational(0); + OLIVE_CONFIG("DiskCacheAhead") = rational(60); + + OLIVE_CONFIG("DefaultSequenceWidth") = 1920; + OLIVE_CONFIG("DefaultSequenceHeight") = 1080; + OLIVE_CONFIG("DefaultSequencePixelAspect") = rational(1); + OLIVE_CONFIG("DefaultSequenceFrameRate") = rational(1001, 30000); + OLIVE_CONFIG("DefaultSequenceInterlacing") = VideoParams::kInterlaceNone; + OLIVE_CONFIG("DefaultSequenceAutoCache2") = false; + OLIVE_CONFIG("DefaultSequenceAudioFrequency") = 48000; + OLIVE_CONFIG("DefaultSequenceAudioLayout") = AudioChannelLayout::STEREO.toString(); // Online/offline settings - SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, PixelFormat::F32); - SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, PixelFormat::F16); + OLIVE_CONFIG("OnlinePixelFormat") = PixelFormat::F32; + OLIVE_CONFIG("OfflinePixelFormat") = PixelFormat::F16; - SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kLime); + OLIVE_CONFIG("MarkerColor") = ColorCoding::kLime; } void Config::Load() { + // Reset to defaults + current_config_.SetDefaults(); + QFile config_file(GetConfigFilePath()); if (!config_file.exists()) { @@ -179,9 +178,6 @@ void Config::Load() return; } - // Reset to defaults - current_config_.SetDefaults(); - QXmlStreamReader reader(&config_file); QString config_version; @@ -221,9 +217,10 @@ void Config::Load() qDebug() << " CONFIG: Closest match was" << match.toDouble(); - current_config_[key] = QVariant::fromValue(match.flipped()); + current_config_[key] = match.flipped(); } else { - current_config_[key] = NodeValue::StringToValue(current_config_.GetConfigEntryType(key), value, false); + value_t &v = current_config_[key]; + v = value_t(value).converted(v.type()); } } @@ -271,17 +268,8 @@ void Config::Save() // Anything after the hyphen is considered "unimportant" information writer.writeTextElement("Version", QCoreApplication::applicationVersion().split('-').first()); - QMapIterator iterator(current_config_.config_map_); - while (iterator.hasNext()) { - iterator.next(); - - QString value = NodeValue::ValueToString(iterator.value().type, iterator.value().data, false); - - if (iterator.value().type == NodeValue::kNone) { - qWarning() << "Config key" << iterator.key() << "had null type and was discarded"; - } else { - writer.writeTextElement(iterator.key(), value); - } + for (auto it = current_config_.config_map_.cbegin(); it != current_config_.config_map_.cend(); it++) { + writer.writeTextElement(it.key(), it.value().converted(TYPE_STRING).toString()); } writer.writeEndElement(); // Configuration @@ -296,19 +284,14 @@ void Config::Save() } } -QVariant Config::operator[](const QString &key) const -{ - return config_map_[key].data; -} - -QVariant &Config::operator[](const QString &key) +value_t Config::operator[](const QString &key) const { - return config_map_[key].data; + return config_map_[key]; } -NodeValue::Type Config::GetConfigEntryType(const QString &key) const +value_t &Config::operator[](const QString &key) { - return config_map_[key].type; + return config_map_[key]; } } diff --git a/app/config/config.h b/app/config/config.h index 4179858dc1..97419f301d 100644 --- a/app/config/config.h +++ b/app/config/config.h @@ -32,7 +32,8 @@ namespace olive { #define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)] #define OLIVE_CONFIG_STR(x) Config::Current()[x] -class Config { +class Config +{ public: static Config& Current(); @@ -42,23 +43,14 @@ class Config { static void Save(); - QVariant operator[](const QString&) const; + value_t operator[](const QString&) const; - QVariant& operator[](const QString&); - - NodeValue::Type GetConfigEntryType(const QString& key) const; + value_t &operator[](const QString&); private: Config(); - struct ConfigEntry { - NodeValue::Type type; - QVariant data; - }; - - void SetEntryInternal(const QString& key, NodeValue::Type type, const QVariant& data); - - QMap config_map_; + QMap config_map_; static Config current_config_; diff --git a/app/core.cpp b/app/core.cpp index bdd794e73d..3e0b8c2e4f 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -107,17 +107,14 @@ Core *Core::instance() void Core::DeclareTypesForQt() { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -127,6 +124,9 @@ void Core::DeclareTypesForQt() void Core::Start() { + // Register value conversions + value_t::registerDefaultConverters(); + // Load application config Config::Load(); @@ -979,7 +979,7 @@ void Core::SaveAutorecovery() realname_file.close(); } - int64_t max_recoveries_per_file = OLIVE_CONFIG("AutorecoveryMaximum").toLongLong(); + int64_t max_recoveries_per_file = OLIVE_CONFIG("AutorecoveryMaximum").toInt(); // Since we write an extra file, increment total allowed files by 1 max_recoveries_per_file++; diff --git a/app/core.h b/app/core.h index 10018e7bae..f9ecf00675 100644 --- a/app/core.h +++ b/app/core.h @@ -21,12 +21,12 @@ #ifndef CORE_H #define CORE_H -#include #include #include #include #include +#include "core.h" #include "node/project/footage/footage.h" #include "node/project.h" #include "node/project/sequence/sequence.h" diff --git a/app/dialog/autorecovery/autorecoverydialog.cpp b/app/dialog/autorecovery/autorecoverydialog.cpp index 1e4354ed1c..b656bbf826 100644 --- a/app/dialog/autorecovery/autorecoverydialog.cpp +++ b/app/dialog/autorecovery/autorecoverydialog.cpp @@ -27,6 +27,7 @@ #include #include +#include "common/filefunctions.h" #include "core.h" namespace olive { diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index c5cfa2a170..bb73972238 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -31,6 +31,7 @@ #include #include "common/digit.h" +#include "common/filefunctions.h" #include "common/qtutils.h" #include "dialog/task/task.h" #include "exportsavepresetdialog.h" diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 211f070cb7..7fc4a7bdbb 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -91,7 +91,7 @@ PreferencesAudioTab::PreferencesAudioTab() output_param_layout->addWidget(new QLabel(tr("Channel Layout:")), output_row, 0); output_ch_layout_combo_ = new ChannelLayoutComboBox(); - output_ch_layout_combo_->SetChannelLayout(OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong()); + output_ch_layout_combo_->SetChannelLayout(AudioChannelLayout::fromString(OLIVE_CONFIG("AudioOutputChannelLayout").toString())); output_param_layout->addWidget(output_ch_layout_combo_, output_row, 1); output_row++; @@ -100,7 +100,7 @@ PreferencesAudioTab::PreferencesAudioTab() output_fmt_combo_ = new SampleFormatComboBox(); output_fmt_combo_->SetPackedFormats(); - output_fmt_combo_->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioOutputSampleFormat").toString().toStdString())); + output_fmt_combo_->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioOutputSampleFormat").toString())); output_param_layout->addWidget(output_fmt_combo_, output_row, 1); } } @@ -140,9 +140,9 @@ PreferencesAudioTab::PreferencesAudioTab() record_options_->SetFormat(record_format_combo_->GetFormat()); record_options_->SetCodec(static_cast(OLIVE_CONFIG("AudioRecordingCodec").toInt())); record_options_->sample_rate_combobox()->SetSampleRate(OLIVE_CONFIG("AudioRecordingSampleRate").toInt()); - record_options_->channel_layout_combobox()->SetChannelLayout(OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong()); + record_options_->channel_layout_combobox()->SetChannelLayout(AudioChannelLayout::fromString(OLIVE_CONFIG("AudioRecordingChannelLayout").toString())); record_options_->bit_rate_slider()->SetValue(OLIVE_CONFIG("AudioRecordingBitRate").toInt()); - record_options_->sample_format_combobox()->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioRecordingSampleFormat").toString().toStdString())); + record_options_->sample_format_combobox()->SetSampleFormat(SampleFormat::from_string(OLIVE_CONFIG("AudioRecordingSampleFormat").toString())); recording_layout->addWidget(record_options_); connect(record_format_combo_, &ExportFormatComboBox::FormatChanged, record_options_, &ExportAudioTab::SetFormat); @@ -181,15 +181,15 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command) AudioManager::instance()->SetInputDevice(input_device); OLIVE_CONFIG("AudioOutputSampleRate") = output_rate_combo_->GetSampleRate(); - OLIVE_CONFIG("AudioOutputChannelLayout") = QVariant::fromValue(output_ch_layout_combo_->GetChannelLayout()); - OLIVE_CONFIG("AudioOutputSampleFormat") = QString::fromStdString(output_fmt_combo_->GetSampleFormat().to_string()); + OLIVE_CONFIG("AudioOutputChannelLayout") = output_ch_layout_combo_->GetChannelLayout().toString(); + OLIVE_CONFIG("AudioOutputSampleFormat") = output_fmt_combo_->GetSampleFormat().to_string(); OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat(); OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec(); OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate(); - OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout()); - OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue()); - OLIVE_CONFIG("AudioRecordingSampleFormat") = QString::fromStdString(record_options_->sample_format_combobox()->GetSampleFormat().to_string()); + OLIVE_CONFIG("AudioRecordingChannelLayout") = record_options_->channel_layout_combobox()->GetChannelLayout().toString(); + OLIVE_CONFIG("AudioRecordingBitRate") = record_options_->bit_rate_slider()->GetValue(); + OLIVE_CONFIG("AudioRecordingSampleFormat") = record_options_->sample_format_combobox()->GetSampleFormat().to_string(); emit AudioManager::instance()->OutputParamsChanged(); } diff --git a/app/dialog/preferences/tabs/preferencesdisktab.cpp b/app/dialog/preferences/tabs/preferencesdisktab.cpp index 8aea10aab1..301e4ce7d3 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.cpp +++ b/app/dialog/preferences/tabs/preferencesdisktab.cpp @@ -115,8 +115,8 @@ void PreferencesDiskTab::Accept(MultiUndoCommand *command) default_disk_cache_folder_->SetPath(disk_cache_location_->text()); } - OLIVE_CONFIG("DiskCacheBehind") = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue())); - OLIVE_CONFIG("DiskCacheAhead") = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue())); + OLIVE_CONFIG("DiskCacheBehind") = rational::fromDouble(cache_behind_slider_->GetValue()); + OLIVE_CONFIG("DiskCacheAhead") = rational::fromDouble(cache_ahead_slider_->GetValue()); } } diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp index 765e91dfeb..a3474227dd 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp @@ -130,7 +130,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autorecovery_interval_->SetMinimum(1); autorecovery_interval_->SetMaximum(60); autorecovery_interval_->SetFormat(QT_TRANSLATE_N_NOOP("olive::SliderBase", "%n minute(s)"), true); - autorecovery_interval_->SetValue(OLIVE_CONFIG("AutorecoveryInterval").toLongLong()); + autorecovery_interval_->SetValue(OLIVE_CONFIG("AutorecoveryInterval").toInt()); autorecovery_layout->addWidget(autorecovery_interval_, row, 1); row++; @@ -140,7 +140,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autorecovery_maximum_ = new IntegerSlider(); autorecovery_maximum_->SetMinimum(1); autorecovery_maximum_->SetMaximum(1000); - autorecovery_maximum_->SetValue(OLIVE_CONFIG("AutorecoveryMaximum").toLongLong()); + autorecovery_maximum_->SetValue(OLIVE_CONFIG("AutorecoveryMaximum").toInt()); autorecovery_layout->addWidget(autorecovery_maximum_, row, 1); row++; @@ -159,9 +159,9 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command) OLIVE_CONFIG("RectifiedWaveforms") = rectified_waveforms_->isChecked(); - OLIVE_CONFIG("Autoscroll") = autoscroll_method_->currentData(); + OLIVE_CONFIG("Autoscroll") = autoscroll_method_->currentData().toInt(); - OLIVE_CONFIG("DefaultStillLength") = QVariant::fromValue(default_still_length_->GetValue()); + OLIVE_CONFIG("DefaultStillLength") = default_still_length_->GetValue(); QString set_language = language_combobox_->currentData().toString(); if (QLocale::system().name() == set_language) { @@ -176,8 +176,8 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command) } OLIVE_CONFIG("AutorecoveryEnabled") = autorecovery_enabled_->isChecked(); - OLIVE_CONFIG("AutorecoveryInterval") = QVariant::fromValue(autorecovery_interval_->GetValue()); - OLIVE_CONFIG("AutorecoveryMaximum") = QVariant::fromValue(autorecovery_maximum_->GetValue()); + OLIVE_CONFIG("AutorecoveryInterval") = autorecovery_interval_->GetValue(); + OLIVE_CONFIG("AutorecoveryMaximum") = autorecovery_maximum_->GetValue(); Core::instance()->SetAutorecoveryInterval(autorecovery_interval_->GetValue()); } diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index e059e9ac99..873a06cba8 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -29,6 +29,7 @@ #include #include "common/filefunctions.h" +#include "common/qtutils.h" #include "node/color/colormanager/colormanager.h" #include "render/diskmanager.h" diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index d6a109f1c4..d3c7180351 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -172,11 +172,11 @@ void SequenceDialog::SetAsDefaultClicked() // Maybe replace with Preset system OLIVE_CONFIG("DefaultSequenceWidth") = parameter_tab_->GetSelectedVideoWidth(); OLIVE_CONFIG("DefaultSequenceHeight") = parameter_tab_->GetSelectedVideoHeight(); - OLIVE_CONFIG("DefaultSequencePixelAspect") = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect()); - OLIVE_CONFIG("DefaultSequenceFrameRate") = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped()); + OLIVE_CONFIG("DefaultSequencePixelAspect") = parameter_tab_->GetSelectedVideoPixelAspect(); + OLIVE_CONFIG("DefaultSequenceFrameRate") = parameter_tab_->GetSelectedVideoFrameRate().flipped(); OLIVE_CONFIG("DefaultSequenceInterlacing") = parameter_tab_->GetSelectedVideoInterlacingMode(); OLIVE_CONFIG("DefaultSequenceAudioFrequency") = parameter_tab_->GetSelectedAudioSampleRate(); - OLIVE_CONFIG("DefaultSequenceAudioLayout") = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout()); + OLIVE_CONFIG("DefaultSequenceAudioLayout") = parameter_tab_->GetSelectedAudioChannelLayout().toString(); } } diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index 0efef7e7c6..887af3e472 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -49,7 +49,7 @@ class SequenceDialogParameterTab : public QWidget return audio_sample_rate_field_->GetSampleRate(); } - uint64_t GetSelectedAudioChannelLayout() const + AudioChannelLayout GetSelectedAudioChannelLayout() const { return audio_channels_field_->GetChannelLayout(); } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index b027066c1a..ea064e7a07 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -109,7 +109,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -120,7 +120,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -131,7 +131,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -142,7 +142,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -153,7 +153,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na VideoParams::kPixelAspectSquare, VideoParams::kInterlaceNone, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -173,7 +173,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na standard_par, VideoParams::kInterlacedBottomFirst, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); @@ -184,7 +184,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na wide_par, VideoParams::kInterlacedBottomFirst, 48000, - AV_CH_LAYOUT_STEREO, + AudioChannelLayout::STEREO, divider, default_format, default_autocache)); diff --git a/app/dialog/sequence/sequencepreset.h b/app/dialog/sequence/sequencepreset.h index 2a2f5699b8..932333a659 100644 --- a/app/dialog/sequence/sequencepreset.h +++ b/app/dialog/sequence/sequencepreset.h @@ -21,7 +21,6 @@ #ifndef SEQUENCEPARAM_H #define SEQUENCEPARAM_H -#include #include #include "common/xmlutils.h" @@ -41,7 +40,7 @@ class SequencePreset : public Preset { const rational& pixel_aspect, VideoParams::Interlacing interlacing, int sample_rate, - uint64_t channel_layout, + const AudioChannelLayout &channel_layout, int preview_divider, PixelFormat preview_format, bool preview_autocache) : @@ -69,15 +68,15 @@ class SequencePreset : public Preset { } else if (reader->name() == QStringLiteral("height")) { height_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("framerate")) { - frame_rate_ = rational::fromString(reader->readElementText().toStdString()); + frame_rate_ = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("pixelaspect")) { - pixel_aspect_ = rational::fromString(reader->readElementText().toStdString()); + pixel_aspect_ = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("interlacing")) { interlacing_ = static_cast(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("samplerate")) { sample_rate_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("chlayout")) { - channel_layout_ = reader->readElementText().toULongLong(); + channel_layout_ = AudioChannelLayout::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("divider")) { preview_divider_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("format")) { @@ -95,11 +94,11 @@ class SequencePreset : public Preset { writer->writeTextElement(QStringLiteral("name"), GetName()); writer->writeTextElement(QStringLiteral("width"), QString::number(width_)); writer->writeTextElement(QStringLiteral("height"), QString::number(height_)); - writer->writeTextElement(QStringLiteral("framerate"), QString::fromStdString(frame_rate_.toString())); - writer->writeTextElement(QStringLiteral("pixelaspect"), QString::fromStdString(pixel_aspect_.toString())); + writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString()); + writer->writeTextElement(QStringLiteral("pixelaspect"), pixel_aspect_.toString()); writer->writeTextElement(QStringLiteral("interlacing_"), QString::number(interlacing_)); writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_)); - writer->writeTextElement(QStringLiteral("chlayout"), QString::number(channel_layout_)); + writer->writeTextElement(QStringLiteral("chlayout"), channel_layout_.toString()); writer->writeTextElement(QStringLiteral("divider"), QString::number(preview_divider_)); writer->writeTextElement(QStringLiteral("format"), QString::number(preview_format_)); writer->writeTextElement(QStringLiteral("autocache"), QString::number(preview_autocache_)); @@ -135,7 +134,7 @@ class SequencePreset : public Preset { return sample_rate_; } - uint64_t channel_layout() const + const AudioChannelLayout &channel_layout() const { return channel_layout_; } @@ -162,7 +161,7 @@ class SequencePreset : public Preset { rational pixel_aspect_; VideoParams::Interlacing interlacing_; int sample_rate_; - uint64_t channel_layout_; + AudioChannelLayout channel_layout_; int preview_divider_; PixelFormat preview_format_; bool preview_autocache_; diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index ef1978c21d..ca075085e6 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -50,12 +50,8 @@ set(OLIVE_SOURCES node/param.h node/project.cpp node/project.h - node/splitvalue.h - node/traverser.cpp - node/traverser.h + node/swizzlemap.h node/value.cpp node/value.h - node/valuedatabase.cpp - node/valuedatabase.h PARENT_SCOPE ) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 5f4ecebf1d..b2d7947e85 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -31,9 +31,9 @@ const QString PanNode::kPanningInput = QStringLiteral("panning_in"); PanNode::PanNode() { - AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); + AddInput(kSamplesInput, TYPE_SAMPLES, kInputFlagNotKeyframable); - AddInput(kPanningInput, NodeValue::kFloat, 0.0); + AddInput(kPanningInput, TYPE_DOUBLE, 0.0); SetInputProperty(kPanningInput, QStringLiteral("min"), -1.0); SetInputProperty(kPanningInput, QStringLiteral("max"), 1.0); SetInputProperty(kPanningInput, QStringLiteral("view"), FloatSlider::kPercentage); @@ -62,49 +62,63 @@ QString PanNode::Description() const return tr("Adjust the stereo panning of an audio source."); } -void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +void PanNode::ProcessSamples(const void *context, const SampleJob &job, SampleBuffer &output) { - // Create a sample job - SampleBuffer samples = value[kSamplesInput].toSamples(); - if (samples.is_allocated()) { - // This node is only compatible with stereo audio - if (samples.audio_params().channel_count() == 2) { - // If the input is static, we can just do it now which will be faster - if (IsInputStatic(kPanningInput)) { - float pan_volume = value[kPanningInput].toDouble(); - if (!qIsNull(pan_volume)) { - if (pan_volume > 0) { - samples.transform_volume_for_channel(0, 1.0f - pan_volume); - } else { - samples.transform_volume_for_channel(1, 1.0f + pan_volume); - } - } + const PanNode *n = static_cast(context); + const ValueParams &p = job.value_params(); + + const SampleBuffer input = job.Get(PanNode::kSamplesInput).toSamples(); + if (!input.is_allocated()) { + return; + } - table->Push(NodeValue(NodeValue::kSamples, samples, this)); - } else { - // Requires job - table->Push(NodeValue::kSamples, SampleJob(globals.time(), kSamplesInput, value), this); + // This node is only compatible with stereo audio + if (job.audio_params().channel_count() == 2) { + if (n->IsInputStatic(kPanningInput)) { + float pan_volume = n->GetInputValue(p, kPanningInput).toDouble(); + if (!qIsNull(pan_volume)) { + if (pan_volume > 0) { + SampleBuffer::transform_volume_for_channel(0, 1.0f - pan_volume, &input, &output); + output.set(1, input.data(1), input.sample_count()); + } else { + output.set(0, input.data(0), input.sample_count()); + SampleBuffer::transform_volume_for_channel(1, 1.0f + pan_volume, &input, &output); + } } } else { - // Pass right through - table->Push(value[kSamplesInput]); + for (size_t index = 0; index < output.sample_count(); index++) { + rational this_sample_time = p.time().in() + rational(index, job.audio_params().sample_rate()); + TimeRange this_sample_range(this_sample_time, this_sample_time + job.audio_params().sample_rate_as_time_base()); + auto pan_val = n->GetInputValue(p.time_transformed(this_sample_range), kPanningInput).toDouble(); + + if (pan_val > 0) { + output.data(0)[index] = input.data(0)[index] * (1.0F - pan_val); + output.data(1)[index] = input.data(1)[index]; + } else if (pan_val < 0) { + output.data(0)[index] = input.data(0)[index]; + output.data(1)[index] = input.data(1)[index] * (1.0F - qAbs(pan_val)); + } + } } } } -void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const +value_t PanNode::Value(const ValueParams &p) const { - float pan_val = values[kPanningInput].toDouble(); + // Create a sample job + value_t samples_original = GetInputValue(p, kSamplesInput); - for (int i=0;i 0) { - output.data(0)[index] *= (1.0F - pan_val); - } else if (pan_val < 0) { - output.data(1)[index] *= (1.0F - qAbs(pan_val)); + job.Insert(kSamplesInput, GetInputValue(p, kSamplesInput)); + job.set_function(ProcessSamples, this); + + return job; } + + return samples_original; } void PanNode::Retranslate() diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h index bc1be92f9b..de84990173 100644 --- a/app/node/audio/pan/pan.h +++ b/app/node/audio/pan/pan.h @@ -38,9 +38,7 @@ class PanNode : public Node virtual QVector Category() const override; virtual QString Description() const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; + virtual value_t Value(const ValueParams &p) const override; virtual void Retranslate() override; @@ -48,6 +46,8 @@ class PanNode : public Node static const QString kPanningInput; private: + static void ProcessSamples(const void *context, const SampleJob &job, SampleBuffer &output); + NodeInput* samples_input_; NodeInput* panning_input_; diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 229295d98e..fa6418f4e2 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -20,6 +20,7 @@ #include "volume.h" +#include "node/math/math/math.h" #include "widget/slider/floatslider.h" namespace olive { @@ -27,13 +28,13 @@ namespace olive { const QString VolumeNode::kSamplesInput = QStringLiteral("samples_in"); const QString VolumeNode::kVolumeInput = QStringLiteral("volume_in"); -#define super MathNodeBase +#define super Node VolumeNode::VolumeNode() { - AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); + AddInput(kSamplesInput, TYPE_SAMPLES, kInputFlagNotKeyframable); - AddInput(kVolumeInput, NodeValue::kFloat, 1.0); + AddInput(kVolumeInput, TYPE_DOUBLE, 1.0); SetInputProperty(kVolumeInput, QStringLiteral("min"), 0.0); SetInputProperty(kVolumeInput, QStringLiteral("view"), FloatSlider::kDecibel); @@ -61,33 +62,17 @@ QString VolumeNode::Description() const return tr("Adjusts the volume of an audio source."); } -void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t VolumeNode::Value(const ValueParams &p) const { - // Create a sample job - SampleBuffer buffer = value[kSamplesInput].toSamples(); - - if (buffer.is_allocated()) { - // If the input is static, we can just do it now which will be faster - if (IsInputStatic(kVolumeInput)) { - auto volume = value[kVolumeInput].toDouble(); - - if (!qFuzzyCompare(volume, 1.0)) { - buffer.transform_volume(volume); - } - - table->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this); - } else { - // Requires job - SampleJob job(globals.time(), kSamplesInput, value); - job.Insert(kVolumeInput, value); - table->Push(NodeValue::kSamples, QVariant::fromValue(job), this); - } - } -} + SampleJob job(p); -void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const -{ - return ProcessSamplesInternal(values, kOpMultiply, kSamplesInput, kVolumeInput, input, output, index); + job.Insert(QStringLiteral("samples"), GetInputValue(p, kSamplesInput)); + job.Insert(QStringLiteral("number"), kVolumeInput); + job.Insert(QStringLiteral("operation"), MathNode::kOpMultiply); + + job.set_function(MathNode::ProcessSamplesDouble, this); + + return job; } void VolumeNode::Retranslate() diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h index ba22598214..01eeba9fea 100644 --- a/app/node/audio/volume/volume.h +++ b/app/node/audio/volume/volume.h @@ -21,11 +21,11 @@ #ifndef VOLUMENODE_H #define VOLUMENODE_H -#include "node/math/math/mathbase.h" +#include "node/node.h" namespace olive { -class VolumeNode : public MathNodeBase +class VolumeNode : public Node { Q_OBJECT public: @@ -38,9 +38,7 @@ class VolumeNode : public MathNodeBase virtual QVector Category() const override; virtual QString Description() const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; + virtual value_t Value(const ValueParams &p) const override; virtual void Retranslate() override; diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 98c1bedd36..88bae696ff 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -22,6 +22,7 @@ #include +#include "common/qtutils.h" #include "node/inputdragger.h" #include "node/output/track/track.h" #include "transition/transition.h" @@ -38,8 +39,8 @@ Block::Block() : next_(nullptr), track_(nullptr) { - AddInput(kLengthInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagHidden)); - SetInputProperty(kLengthInput, QStringLiteral("min"), QVariant::fromValue(rational(0, 1))); + AddInput(kLengthInput, TYPE_RATIONAL, kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagHidden); + SetInputProperty(kLengthInput, QStringLiteral("min"), rational(0, 1)); SetInputProperty(kLengthInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true); @@ -78,18 +79,6 @@ void Block::set_length_and_media_in(const rational &length) set_length_internal(length); } -bool Block::is_enabled() const -{ - return GetStandardValue(kEnabledInput).toBool(); -} - -void Block::set_enabled(bool e) -{ - SetStandardValue(kEnabledInput, e); - - emit EnabledChanged(); -} - void Block::InputValueChangedEvent(const QString &input, int element) { super::InputValueChangedEvent(input, element); @@ -103,7 +92,7 @@ void Block::InputValueChangedEvent(const QString &input, int element) void Block::set_length_internal(const rational &length) { - SetStandardValue(kLengthInput, QVariant::fromValue(length)); + SetStandardValue(kLengthInput, length); } void Block::Retranslate() diff --git a/app/node/block/block.h b/app/node/block/block.h index 3b0c7e4e9f..375831f2c9 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -99,9 +99,6 @@ class Block : public Node emit TrackChanged(track_); } - bool is_enabled() const; - void set_enabled(bool e); - virtual void Retranslate() override; virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index d30ec6ac2a..57b655aaa3 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -45,26 +45,27 @@ ClipBlock::ClipBlock() : out_transition_(nullptr), connected_viewer_(nullptr) { - AddInput(kMediaInInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kMediaInInput, TYPE_RATIONAL, kInputFlagNotConnectable | kInputFlagNotKeyframable); SetInputProperty(kMediaInInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kMediaInInput, QStringLiteral("viewlock"), true); - AddInput(kSpeedInput, NodeValue::kFloat, 1.0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kSpeedInput, TYPE_DOUBLE, 1.0, kInputFlagNotConnectable | kInputFlagNotKeyframable); SetInputProperty(kSpeedInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kSpeedInput, QStringLiteral("min"), 0.0); - AddInput(kReverseInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kReverseInput, TYPE_BOOL, false, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kMaintainAudioPitchInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kMaintainAudioPitchInput, TYPE_BOOL, false, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kAutoCacheInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kAutoCacheInput, TYPE_BOOL, false, kInputFlagNotConnectable | kInputFlagNotKeyframable); - PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); - //SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); + PrependInput(kBufferIn, kInputFlagNotKeyframable); + AddAcceptableTypeForInput(kBufferIn, TYPE_TEXTURE); + AddAcceptableTypeForInput(kBufferIn, TYPE_SAMPLES); SetEffectInput(kBufferIn); - AddInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kLoopModeInput, TYPE_COMBO, 0, kInputFlagNotConnectable | kInputFlagNotKeyframable); } QString ClipBlock::Name() const @@ -130,7 +131,7 @@ rational ClipBlock::media_in() const void ClipBlock::set_media_in(const rational &media_in) { - SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); + SetStandardValue(kMediaInInput, media_in); RequestInvalidatedFromConnected(); } @@ -407,11 +408,12 @@ void ClipBlock::LinkChangeEvent() } } -void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *output) +void ClipBlock::InputConnectedEvent(const QString &input, int element, const NodeOutput &o) { - super::InputConnectedEvent(input, element, output); + super::InputConnectedEvent(input, element, o); if (input == kBufferIn) { + Node *output = o.node(); connect(output->thumbnail_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); connect(output->waveform_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); connect(output->video_frame_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); @@ -423,11 +425,12 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out } } -void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node *output) +void ClipBlock::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &o) { - super::InputDisconnectedEvent(input, element, output); + super::InputDisconnectedEvent(input, element, o); if (input == kBufferIn) { + Node *output = o.node(); disconnect(output->thumbnail_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); disconnect(output->waveform_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); disconnect(output->video_frame_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); @@ -484,17 +487,9 @@ TimeRange ClipBlock::OutputTimeAdjustment(const QString& input, int element, con return super::OutputTimeAdjustment(input, element, input_time); } -void ClipBlock::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ClipBlock::Value(const ValueParams &p) const { - Q_UNUSED(globals) - - // We discard most values here except for the buffer we received - NodeValue data = value[kBufferIn]; - - table->Clear(); - if (data.type() != NodeValue::kNone) { - table->Push(data); - } + return GetInputValue(p.loop_mode_edited(this->loop_mode()), kBufferIn); } void ClipBlock::Retranslate() diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 19987c7fb7..945be96c17 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -72,7 +72,7 @@ class ClipBlock : public Block virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; virtual void Retranslate() override; @@ -212,9 +212,9 @@ class ClipBlock : public Block protected: virtual void LinkChangeEvent() override; - virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput &output) override; - virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput &output) override; virtual void InputValueChangedEvent(const QString& input, int element) override; diff --git a/app/node/block/subtitle/subtitle.cpp b/app/node/block/subtitle/subtitle.cpp index b645a15951..66333479b9 100644 --- a/app/node/block/subtitle/subtitle.cpp +++ b/app/node/block/subtitle/subtitle.cpp @@ -28,7 +28,7 @@ const QString SubtitleBlock::kTextIn = QStringLiteral("text_in"); SubtitleBlock::SubtitleBlock() { - AddInput(kTextIn, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kTextIn, TYPE_STRING, kInputFlagNotConnectable | kInputFlagNotKeyframable); SetInputFlag(kBufferIn, kInputFlagHidden); SetInputFlag(kLengthInput, kInputFlagHidden); diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 28264a4469..23e47ddd3c 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -46,25 +46,34 @@ QString CrossDissolveTransition::Description() const return tr("Smoothly transition between two clips."); } -ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request) const +ShaderCode CrossDissolveTransition::GetShaderCode(const QString &id) { - Q_UNUSED(request) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag")); +} - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString()); +void CrossDissolveTransition::ShaderJobEvent(const ValueParams &p, ShaderJob *job) const +{ + job->set_function(GetShaderCode); } -void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const +void CrossDissolveTransition::ProcessSamples(const void *context, const SampleJob &job, SampleBuffer &out_samples) { + const CrossDissolveTransition *t = static_cast(context); + SampleBuffer from_samples = job.Get(TransitionBlock::kOutBlockInput).toSamples(); + SampleBuffer to_samples = job.Get(TransitionBlock::kInBlockInput).toSamples(); + + double time_in = job.value_params().time().in().toDouble(); + for (size_t i=0; iGetTotalProgress(this_sample_time); for (int j=0; jTransformCurve(1.0 - progress); } } @@ -73,11 +82,16 @@ void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, c size_t remain = (out_samples.sample_count() - to_samples.sample_count()); if (i >= remain) { qint64 in_index = i - remain; - out_samples.data(j)[i] += to_samples.data(j)[in_index] * TransformCurve(progress); + out_samples.data(j)[i] += to_samples.data(j)[in_index] * t->TransformCurve(progress); } } } } } +void CrossDissolveTransition::SampleJobEvent(const ValueParams &p, SampleJob *job) const +{ + job->set_function(ProcessSamples, this); +} + } diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.h b/app/node/block/transition/crossdissolve/crossdissolvetransition.h index 7b35970023..90106b271e 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.h +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.h @@ -38,12 +38,14 @@ class CrossDissolveTransition : public TransitionBlock virtual QVector Category() const override; virtual QString Description() const override; - //virtual void Retranslate() override; +protected: + virtual void ShaderJobEvent(const ValueParams &p, ShaderJob *job) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void SampleJobEvent(const ValueParams &p, SampleJob *job) const override; -protected: - virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const override; +private: + static ShaderCode GetShaderCode(const QString &id); + static void ProcessSamples(const void *context, const SampleJob &job, SampleBuffer &out_samples); }; diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index 42d04dd949..6f37bdd803 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -28,7 +28,7 @@ const QString DipToColorTransition::kColorInput = QStringLiteral("color_in"); DipToColorTransition::DipToColorTransition() { - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0, 0, 0))); + AddInput(kColorInput, TYPE_COLOR, Color(0, 0, 0)); } QString DipToColorTransition::Name() const @@ -51,11 +51,9 @@ QString DipToColorTransition::Description() const return tr("Transition between clips by dipping to a color."); } -ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) const +ShaderCode DipToColorTransition::GetShaderCode(const QString &id) { - Q_UNUSED(request) - - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag")); } void DipToColorTransition::Retranslate() @@ -65,9 +63,10 @@ void DipToColorTransition::Retranslate() SetInputName(kColorInput, tr("Color")); } -void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const +void DipToColorTransition::ShaderJobEvent(const ValueParams &p, ShaderJob *job) const { - job->Insert(kColorInput, value); + job->Insert(kColorInput, GetInputValue(p, kColorInput)); + job->set_function(GetShaderCode); } } diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h index 8c84134ebd..af15840f29 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.h +++ b/app/node/block/transition/diptocolor/diptocolortransition.h @@ -38,14 +38,15 @@ class DipToColorTransition : public TransitionBlock virtual QVector Category() const override; virtual QString Description() const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Retranslate() override; static const QString kColorInput; protected: - virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const override; + virtual void ShaderJobEvent(const ValueParams &p, ShaderJob *job) const override; + +private: + static ShaderCode GetShaderCode(const QString &id); }; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 9209a14001..62eaba82b4 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -37,13 +37,13 @@ TransitionBlock::TransitionBlock() : connected_out_block_(nullptr), connected_in_block_(nullptr) { - AddInput(kOutBlockInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + AddInput(kOutBlockInput, kInputFlagNotKeyframable); - AddInput(kInBlockInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + AddInput(kInBlockInput, kInputFlagNotKeyframable); - AddInput(kCurveInput, NodeValue::kCombo, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kCurveInput, TYPE_COMBO, kInputFlagNotKeyframable | kInputFlagNotConnectable); - AddInput(kCenterInput, NodeValue::kRational, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kCenterInput, TYPE_RATIONAL, kInputFlagNotKeyframable | kInputFlagNotConnectable); SetInputProperty(kCenterInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kCenterInput, QStringLiteral("viewlock"), true); @@ -92,7 +92,7 @@ rational TransitionBlock::offset_center() const void TransitionBlock::set_offset_center(const rational &r) { - SetStandardValue(kCenterInput, QVariant::fromValue(r)); + SetStandardValue(kCenterInput, r); } void TransitionBlock::set_offsets_and_length(const rational &in_offset, const rational &out_offset) @@ -145,80 +145,59 @@ double TransitionBlock::GetInternalTransitionTime(const double &time) const void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &time) const { // Provides total transition progress from 0.0 (start) - 1.0 (end) - job->Insert(QStringLiteral("ove_tprog_all"), - NodeValue(NodeValue::kFloat, GetTotalProgress(time), this)); + job->Insert(QStringLiteral("ove_tprog_all"), GetTotalProgress(time)); // Provides progress of out section from 1.0 (start) - 0.0 (end) - job->Insert(QStringLiteral("ove_tprog_out"), - NodeValue(NodeValue::kFloat, GetOutProgress(time), this)); + job->Insert(QStringLiteral("ove_tprog_out"), GetOutProgress(time)); // Provides progress of in section from 0.0 (start) - 1.0 (end) - job->Insert(QStringLiteral("ove_tprog_in"), - NodeValue(NodeValue::kFloat, GetInProgress(time), this)); + job->Insert(QStringLiteral("ove_tprog_in"), GetInProgress(time)); } -void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TransitionBlock::Value(const ValueParams &p) const { - NodeValue out_buffer = value[kOutBlockInput]; - NodeValue in_buffer = value[kInBlockInput]; - NodeValue::Type data_type = (out_buffer.type() != NodeValue::kNone) ? out_buffer.type() : in_buffer.type(); + value_t out_buffer = GetInputValue(p, kOutBlockInput); + value_t in_buffer = GetInputValue(p, kInBlockInput); - NodeValue::Type job_type = NodeValue::kNone; - QVariant push_job; + type_t data_type = (out_buffer.type() != TYPE_NONE) ? out_buffer.type() : in_buffer.type(); - if (data_type == NodeValue::kTexture) { + if (data_type == TYPE_TEXTURE) { // This must be a visual transition ShaderJob job; - if (out_buffer.type() != NodeValue::kNone) { + if (out_buffer.type() != TYPE_NONE) { job.Insert(kOutBlockInput, out_buffer); } else { - job.Insert(kOutBlockInput, NodeValue(NodeValue::kTexture, nullptr)); + job.Insert(kOutBlockInput, TexturePtr(nullptr)); } - if (in_buffer.type() != NodeValue::kNone) { + if (in_buffer.type() != TYPE_NONE) { job.Insert(kInBlockInput, in_buffer); } else { - job.Insert(kInBlockInput, NodeValue(NodeValue::kTexture, nullptr)); + job.Insert(kInBlockInput, TexturePtr(nullptr)); } - job.Insert(kCurveInput, value); + job.Insert(kCurveInput, GetInputValue(p, kCurveInput)); - double time = globals.time().in().toDouble(); + double time = p.time().in().toDouble(); InsertTransitionTimes(&job, time); - ShaderJobEvent(value, &job); + ShaderJobEvent(p, &job); - job_type = NodeValue::kTexture; - push_job = QVariant::fromValue(Texture::Job(globals.vparams(), job)); - } else if (data_type == NodeValue::kSamples) { + return Texture::Job(p.vparams(), job); + } else if (data_type == TYPE_SAMPLES) { // This must be an audio transition - SampleBuffer from_samples = out_buffer.toSamples(); - SampleBuffer to_samples = in_buffer.toSamples(); + SampleJob job(p); - if (from_samples.is_allocated() || to_samples.is_allocated()) { - double time_in = globals.time().in().toDouble(); - double time_out = globals.time().out().toDouble(); + job.Insert(kOutBlockInput, out_buffer); + job.Insert(kInBlockInput, in_buffer); - const AudioParams& params = (from_samples.is_allocated()) ? from_samples.audio_params() : to_samples.audio_params(); + SampleJobEvent(p, &job); - SampleBuffer out_samples; - - if (params.is_valid()) { - int nb_samples = params.time_to_samples(time_out - time_in); - - out_samples = SampleBuffer(params, nb_samples); - SampleJobEvent(from_samples, to_samples, out_samples, time_in); - } - - job_type = NodeValue::kSamples; - push_job = QVariant::fromValue(out_samples); - } + return job; } - if (!push_job.isNull()) { - table->Push(job_type, push_job, this); - } + return value_t(); } void TransitionBlock::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options) @@ -251,24 +230,24 @@ double TransitionBlock::TransformCurve(double linear) const return linear; } -void TransitionBlock::InputConnectedEvent(const QString &input, int element, Node *output) +void TransitionBlock::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { Q_UNUSED(element) if (input == kOutBlockInput) { // If node is not a block, this will just be null - if ((connected_out_block_ = dynamic_cast(output))) { + if ((connected_out_block_ = dynamic_cast(output.node()))) { connected_out_block_->set_out_transition(this); } } else if (input == kInBlockInput) { // If node is not a block, this will just be null - if ((connected_in_block_ = dynamic_cast(output))) { + if ((connected_in_block_ = dynamic_cast(output.node()))) { connected_in_block_->set_in_transition(this); } } } -void TransitionBlock::InputDisconnectedEvent(const QString &input, int element, Node *output) +void TransitionBlock::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { Q_UNUSED(element) Q_UNUSED(output) diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index d16a195bdc..3b457a2481 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -63,7 +63,9 @@ class TransitionBlock : public Block double GetOutProgress(const double &time) const; double GetInProgress(const double &time) const; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + double TransformCurve(double linear) const; + + virtual value_t Value(const ValueParams &globals) const override; virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override; @@ -73,15 +75,13 @@ class TransitionBlock : public Block static const QString kCenterInput; protected: - virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const {} + virtual void ShaderJobEvent(const ValueParams &p, ShaderJob *job) const {} - virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const {} - - double TransformCurve(double linear) const; + virtual void SampleJobEvent(const ValueParams &p, SampleJob *job) const {} - virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput &output) override; - virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput &output) override; virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; diff --git a/app/node/color/displaytransform/displaytransform.cpp b/app/node/color/displaytransform/displaytransform.cpp index 6bf2116c3f..a23ee75812 100644 --- a/app/node/color/displaytransform/displaytransform.cpp +++ b/app/node/color/displaytransform/displaytransform.cpp @@ -32,11 +32,11 @@ const QString DisplayTransformNode::kDirectionInput = QStringLiteral("dir_in"); DisplayTransformNode::DisplayTransformNode() { - AddInput(kDisplayInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kDisplayInput, TYPE_COMBO, 0, kInputFlagNotKeyframable | kInputFlagNotConnectable); - AddInput(kViewInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kViewInput, TYPE_COMBO, 0, kInputFlagNotKeyframable | kInputFlagNotConnectable); - AddInput(kDirectionInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kDirectionInput, TYPE_COMBO, 0, kInputFlagNotKeyframable | kInputFlagNotConnectable); } QString DisplayTransformNode::Name() const diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp index 015f1db979..101af326d2 100644 --- a/app/node/color/ociobase/ociobase.cpp +++ b/app/node/color/ociobase/ociobase.cpp @@ -31,7 +31,7 @@ OCIOBaseNode::OCIOBaseNode() : manager_(nullptr), processor_(nullptr) { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); SetEffectInput(kTextureInput); @@ -53,9 +53,9 @@ void OCIOBaseNode::RemovedFromGraphEvent(Project *p) } } -void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t OCIOBaseNode::Value(const ValueParams &p) const { - auto tex_met = value[kTextureInput]; + auto tex_met = GetInputValue(p, kTextureInput); TexturePtr t = tex_met.toTexture(); if (t && processor_) { ColorTransformJob job; @@ -63,8 +63,10 @@ void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, job.SetColorProcessor(processor_); job.SetInputTexture(tex_met); - table->Push(NodeValue::kTexture, t->toJob(job), this); + return t->toJob(job); } + + return tex_met; } } diff --git a/app/node/color/ociobase/ociobase.h b/app/node/color/ociobase/ociobase.h index c476a69fef..60e451491a 100644 --- a/app/node/color/ociobase/ociobase.h +++ b/app/node/color/ociobase/ociobase.h @@ -35,7 +35,7 @@ class OCIOBaseNode : public Node virtual void AddedToGraphEvent(Project *p) override; virtual void RemovedFromGraphEvent(Project *p) override; - virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp index 6b88ab7ce1..20ec92588f 100644 --- a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp +++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp @@ -43,36 +43,36 @@ const QString OCIOGradingTransformLinearNode::kClampWhiteInput = QStringLiteral( OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode() { - AddInput(kContrastInput, NodeValue::kVec4, QVector4D{1.0, 1.0, 1.0, 1.0}); + AddInput(kContrastInput, TYPE_VEC4, QVector4D{1.0, 1.0, 1.0, 1.0}); // Minimum based on OCIO::GradingPrimary::validate SetInputProperty(kContrastInput, QStringLiteral("min"), QVector4D{0.01f, 0.01f, 0.01f, 0.01f}); SetInputProperty(kContrastInput, QStringLiteral("base"), 0.01); SetVec4InputColors(kContrastInput); - AddInput(kOffsetInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0}); + AddInput(kOffsetInput, TYPE_VEC4, QVector4D{0.0, 0.0, 0.0, 0.0}); SetInputProperty(kOffsetInput, QStringLiteral("base"), 0.01); SetVec4InputColors(kOffsetInput); - AddInput(kExposureInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0}); + AddInput(kExposureInput, TYPE_VEC4, QVector4D{0.0, 0.0, 0.0, 0.0}); SetInputProperty(kExposureInput, QStringLiteral("base"), 0.01); SetVec4InputColors(kExposureInput); - AddInput(kSaturationInput, NodeValue::kFloat, 1.0); + AddInput(kSaturationInput, TYPE_DOUBLE, 1.0); SetInputProperty(kSaturationInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kSaturationInput, QStringLiteral("min"), 0.0); - AddInput(kPivotInput, NodeValue::kFloat, 0.18); // Default listed in OCIO::GradingPrimary + AddInput(kPivotInput, TYPE_DOUBLE, 0.18); // Default listed in OCIO::GradingPrimary SetInputProperty(kPivotInput, QStringLiteral("base"), 0.01); - AddInput(kClampBlackEnableInput, NodeValue::kBoolean, false); + AddInput(kClampBlackEnableInput, TYPE_BOOL, false); - AddInput(kClampBlackInput, NodeValue::kFloat, 0.0); + AddInput(kClampBlackInput, TYPE_DOUBLE, 0.0); SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool()); SetInputProperty(kClampBlackInput, QStringLiteral("base"), 0.01); - AddInput(kClampWhiteEnableInput, NodeValue::kBoolean, false); + AddInput(kClampWhiteEnableInput, TYPE_BOOL, false); - AddInput(kClampWhiteInput, NodeValue::kFloat, 1.0); + AddInput(kClampWhiteInput, TYPE_DOUBLE, 1.0); SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool()); SetInputProperty(kClampWhiteInput, QStringLiteral("base"), 0.01); @@ -153,14 +153,16 @@ void OCIOGradingTransformLinearNode::GenerateProcessor() } } -void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t OCIOGradingTransformLinearNode::Value(const ValueParams &p) const { - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { if (processor()) { - ColorTransformJob job(value); + ColorTransformJob job = CreateColorTransformJob(p); job.SetColorProcessor(processor()); - job.SetInputTexture(value[kTextureInput]); + job.SetInputTexture(tex_meta); const int MASTER_CHANNEL = 0; const int RED_CHANNEL = 1; @@ -171,35 +173,37 @@ void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const Node // Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API. // Therefore, this code has been duplicated from OCIO here: // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157 - QVector4D offset = value[kOffsetInput].toVec4(); + QVector4D offset = GetInputValue(p, kOffsetInput).toVec4(); offset[RED_CHANNEL] += offset[MASTER_CHANNEL]; offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL]; offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL]; - job.Insert(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL]))); + job.Insert(kOffsetInput, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL])); - QVector4D exposure = value[kExposureInput].toVec4(); + QVector4D exposure = GetInputValue(p, kExposureInput).toVec4(); exposure[RED_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[RED_CHANNEL]); exposure[GREEN_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[GREEN_CHANNEL]); exposure[BLUE_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[BLUE_CHANNEL]); - job.Insert(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL]))); + job.Insert(kExposureInput, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL])); - QVector4D contrast = value[kContrastInput].toVec4(); + QVector4D contrast = GetInputValue(p, kContrastInput).toVec4(); contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL]; contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL]; contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL]; - job.Insert(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL]))); + job.Insert(kContrastInput, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL])); - if (!value[kClampBlackEnableInput].toBool()) { - job.Insert(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack())); + if (!GetInputValue(p, kClampBlackEnableInput).toBool()) { + job.Insert(kClampBlackInput, OCIO::GradingPrimary::NoClampBlack()); } - if (!value[kClampWhiteEnableInput].toBool()) { - job.Insert(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite())); + if (!GetInputValue(p, kClampWhiteEnableInput).toBool()) { + job.Insert(kClampWhiteInput, OCIO::GradingPrimary::NoClampWhite()); } - table->Push(NodeValue::kTexture, tex->toJob(job), this); + return tex->toJob(job); } } + + return tex_meta; } void OCIOGradingTransformLinearNode::ConfigChanged() diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h index 72a7692088..d3fd302492 100644 --- a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h +++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h @@ -43,7 +43,7 @@ class OCIOGradingTransformLinearNode : public OCIOBaseNode virtual void InputValueChangedEvent(const QString &input, int element) override; void GenerateProcessor(); - virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kContrastInput; static const QString kOffsetInput; diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 3f37e39947..e72f6141ea 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -37,12 +37,12 @@ const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspect CornerPinDistortNode::CornerPinDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - AddInput(kPerspectiveInput, NodeValue::kBoolean, true); - AddInput(kTopLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); - AddInput(kTopRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); - AddInput(kBottomRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); - AddInput(kBottomLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); + AddInput(kPerspectiveInput, TYPE_BOOL, true); + AddInput(kTopLeftInput, TYPE_VEC2, QVector2D(0.0, 0.0)); + AddInput(kTopRightInput, TYPE_VEC2, QVector2D(0.0, 0.0)); + AddInput(kBottomRightInput, TYPE_VEC2, QVector2D(0.0, 0.0)); + AddInput(kBottomLeftInput, TYPE_VEC2, QVector2D(0.0, 0.0)); // Initiate gizmos gizmo_whole_rect_ = AddDraggableGizmo(); @@ -67,27 +67,35 @@ void CornerPinDistortNode::Retranslate() SetInputName(kBottomLeftInput, tr("Bottom Left")); } -void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode CornerPinDistortNode::GetShaderCode(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")), + FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert"))); +} + +value_t CornerPinDistortNode::Value(const ValueParams &p) const { // If no texture do nothing - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { // In the special case that all sliders are in their default position just // push the texture. - if (!(value[kTopLeftInput].toVec2().isNull() - && value[kTopRightInput].toVec2().isNull() && - value[kBottomRightInput].toVec2().isNull() && - value[kBottomLeftInput].toVec2().isNull())) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + if (!(GetInputValue(p, kTopLeftInput).toVec2().isNull() + && GetInputValue(p, kTopRightInput).toVec2().isNull() && + GetInputValue(p, kBottomRightInput).toVec2().isNull() && + GetInputValue(p, kBottomLeftInput).toVec2().isNull())) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); // Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the // vertex coordinates. const QVector2D &resolution = tex->virtual_resolution(); QVector2D half_resolution = resolution * 0.5; - QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); - QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D top_left = QVector2D(ValueToPixel(0, p, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D top_right = QVector2D(ValueToPixel(1, p, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D bottom_right = QVector2D(ValueToPixel(2, p, resolution)) / half_resolution - QVector2D(1.0, 1.0); + QVector2D bottom_left = QVector2D(ValueToPixel(3, p, resolution)) / half_resolution - QVector2D(1.0, 1.0); // Override default vertex coordinates. QVector adjusted_vertices = {top_left.x(), top_left.y(), 0.0f, @@ -99,22 +107,14 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g bottom_right.x(), bottom_right.y(), 0.0f}; job.SetVertexCoordinates(adjusted_vertices); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else { - table->Push(value[kTextureInput]); + return tex->toJob(job); } } -} - -ShaderCode CornerPinDistortNode::GetShaderCode(const ShaderRequest &request) const -{ - Q_UNUSED(request) - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")), - FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert"))); + return tex_meta; } -QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const +QPointF CornerPinDistortNode::ValueToPixel(int value, const ValueParams& p, const QVector2D &resolution) const { Q_ASSERT(value >= 0 && value <= 3); @@ -122,16 +122,16 @@ QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, c switch (value) { case 0: // Top left - v = row[kTopLeftInput].toVec2(); + v = GetInputValue(p, kTopLeftInput).toVec2(); return QPointF(v.x(), v.y()); case 1: // Top right - v = row[kTopRightInput].toVec2(); + v = GetInputValue(p, kTopRightInput).toVec2(); return QPointF(resolution.x() + v.x(), v.y()); case 2: // Bottom right - v = row[kBottomRightInput].toVec2(); + v = GetInputValue(p, kBottomRightInput).toVec2(); return QPointF(resolution.x() + v.x(), resolution.y() + v.y()); case 3: //Bottom left - v = row[kBottomLeftInput].toVec2(); + v = GetInputValue(p, kBottomLeftInput).toVec2(); return QPointF(v.x(), v.y() + resolution.y()); default: // We should never get here return QPointF(); @@ -143,20 +143,20 @@ void CornerPinDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM DraggableGizmo *gizmo = static_cast(sender()); if (gizmo != gizmo_whole_rect_) { - gizmo->GetDraggers()[0].Drag(gizmo->GetDraggers()[0].GetStartValue().toDouble() + x); - gizmo->GetDraggers()[1].Drag(gizmo->GetDraggers()[1].GetStartValue().toDouble() + y); + gizmo->GetDraggers()[0].Drag(gizmo->GetDraggers()[0].GetStartValue().value() + x); + gizmo->GetDraggers()[1].Drag(gizmo->GetDraggers()[1].GetStartValue().value() + y); } } -void CornerPinDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void CornerPinDistortNode::UpdateGizmoPositions(const ValueParams &p) { - if (TexturePtr tex = row[kTextureInput].toTexture()) { + if (TexturePtr tex = GetInputValue(p, kTextureInput).toTexture()) { const QVector2D &resolution = tex->virtual_resolution(); - QPointF top_left = ValueToPixel(0, row, resolution); - QPointF top_right = ValueToPixel(1, row, resolution); - QPointF bottom_right = ValueToPixel(2, row, resolution); - QPointF bottom_left = ValueToPixel(3, row, resolution); + QPointF top_left = ValueToPixel(0, p, resolution); + QPointF top_right = ValueToPixel(1, p, resolution); + QPointF bottom_right = ValueToPixel(2, p, resolution); + QPointF bottom_left = ValueToPixel(3, p, resolution); // Add the correct offset to each slider SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0)); diff --git a/app/node/distort/cornerpin/cornerpindistortnode.h b/app/node/distort/cornerpin/cornerpindistortnode.h index 39dda53d88..5273fbbd7e 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.h +++ b/app/node/distort/cornerpin/cornerpindistortnode.h @@ -59,17 +59,15 @@ class CornerPinDistortNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; /** * @brief Convenience function - converts the 2D slider values from being * an offset to the actual pixel value. */ - QPointF ValueToPixel(int value, const NodeValueRow &row, const QVector2D &resolution) const; + QPointF ValueToPixel(int value, const ValueParams &p, const QVector2D &resolution) const; static const QString kTextureInput; static const QString kPerspectiveInput; @@ -82,6 +80,8 @@ protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: + static ShaderCode GetShaderCode(const QString &id); + // Gizmo variables static const int kGizmoCornerCount = 4; PointGizmo *gizmo_resize_handle_[kGizmoCornerCount]; diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index da278f8800..18baaebf0b 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -37,14 +37,14 @@ const QString CropDistortNode::kFeatherInput = QStringLiteral("feather_in"); CropDistortNode::CropDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); CreateCropSideInput(kLeftInput); CreateCropSideInput(kTopInput); CreateCropSideInput(kRightInput); CreateCropSideInput(kBottomInput); - AddInput(kFeatherInput, NodeValue::kFloat, 0.0); + AddInput(kFeatherInput, TYPE_DOUBLE, 0.0); SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0); // Initiate gizmos @@ -75,41 +75,40 @@ void CropDistortNode::Retranslate() SetInputName(kFeatherInput, tr("Feather")); } -void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode CropDistortNode::GetShaderCode(const QString &id) { - ShaderJob job; - job.Insert(value); + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag"))); +} + +value_t CropDistortNode::Value(const ValueParams &p) const +{ + value_t tex_meta = GetInputValue(p, kTextureInput); - if (TexturePtr texture = job.Get(kTextureInput).toTexture()) { - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture->params().width(), texture->params().height()), this)); + if (TexturePtr texture = tex_meta.toTexture()) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.Insert(QStringLiteral("resolution_in"), QVector2D(texture->params().width(), texture->params().height())); if (!qIsNull(job.Get(kLeftInput).toDouble()) || !qIsNull(job.Get(kRightInput).toDouble()) || !qIsNull(job.Get(kTopInput).toDouble()) || !qIsNull(job.Get(kBottomInput).toDouble())) { - table->Push(NodeValue::kTexture, texture->toJob(job), this); - } else { - table->Push(job.Get(kTextureInput)); + return texture->toJob(job); } } -} -ShaderCode CropDistortNode::GetShaderCode(const ShaderRequest &request) const -{ - Q_UNUSED(request) - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag"))); + return tex_meta; } -void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void CropDistortNode::UpdateGizmoPositions(const ValueParams &p) { - if (TexturePtr tex = row[kTextureInput].toTexture()) { + if (TexturePtr tex = GetInputValue(p, kTextureInput).toTexture()) { const QVector2D &resolution = tex->virtual_resolution(); temp_resolution_ = resolution; - double left_pt = resolution.x() * row[kLeftInput].toDouble(); - double top_pt = resolution.y() * row[kTopInput].toDouble(); - double right_pt = resolution.x() * (1.0 - row[kRightInput].toDouble()); - double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].toDouble()); + double left_pt = resolution.x() * GetInputValue(p, kLeftInput).toDouble(); + double top_pt = resolution.y() * GetInputValue(p, kTopInput).toDouble(); + double right_pt = resolution.x() * (1.0 - GetInputValue(p, kRightInput).toDouble()); + double bottom_pt = resolution.y() * (1.0 - GetInputValue(p, kBottomInput).toDouble()); double center_x_pt = mid(left_pt, right_pt); double center_y_pt = mid(top_pt, bottom_pt); @@ -136,7 +135,7 @@ void CropDistortNode::GizmoDragMove(double x_diff, double y_diff, const Qt::Keyb for (int j=0; jGetDraggers().size(); j++) { NodeInputDragger& i = gizmo->GetDraggers()[j]; - double s = i.GetStartValue().toDouble(); + double s = i.GetStartValue().value(); if (i.GetInput().input().input() == kLeftInput) { i.Drag(s + x_diff); } else if (i.GetInput().input().input() == kTopInput) { @@ -151,7 +150,7 @@ void CropDistortNode::GizmoDragMove(double x_diff, double y_diff, const Qt::Keyb void CropDistortNode::CreateCropSideInput(const QString &id) { - AddInput(id, NodeValue::kFloat, 0.0); + AddInput(id, TYPE_DOUBLE, 0.0); SetInputProperty(id, QStringLiteral("min"), 0.0); SetInputProperty(id, QStringLiteral("max"), 1.0); SetInputProperty(id, QStringLiteral("view"), FloatSlider::kPercentage); diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index ebbab1946e..156e6462ef 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -60,11 +60,9 @@ class CropDistortNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; static const QString kTextureInput; static const QString kLeftInput; @@ -77,6 +75,8 @@ protected slots: virtual void GizmoDragMove(double delta_x, double delta_y, const Qt::KeyboardModifiers &modifiers) override; private: + static ShaderCode GetShaderCode(const QString &id); + void CreateCropSideInput(const QString& id); // Gizmo variables diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index 1d8e360347..d3f0b4efe1 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -30,11 +30,11 @@ const QString FlipDistortNode::kVerticalInput = QStringLiteral("vert_in"); FlipDistortNode::FlipDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kHorizontalInput, NodeValue::kBoolean, false); + AddInput(kHorizontalInput, TYPE_BOOL, false); - AddInput(kVerticalInput, NodeValue::kBoolean, false); + AddInput(kVerticalInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -69,24 +69,24 @@ void FlipDistortNode::Retranslate() SetInputName(kVerticalInput, tr("Vertical")); } -ShaderCode FlipDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode FlipDistortNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/flip.frag")); } -void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t FlipDistortNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t v = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = v.toTexture()) { // Only run shader if at least one of flip or flop are selected - if (value[kHorizontalInput].toBool() || value[kVerticalInput].toBool()) { - table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this); - } else { - // If we're not flipping or flopping just push the texture - table->Push(value[kTextureInput]); + if (GetInputValue(p, kHorizontalInput).toBool() || GetInputValue(p, kVerticalInput).toBool()) { + return tex->toJob(CreateShaderJob(p, GetShaderCode)); } } + + return v; } } diff --git a/app/node/distort/flip/flipdistortnode.h b/app/node/distort/flip/flipdistortnode.h index 1c9dcf727c..e6fc238145 100644 --- a/app/node/distort/flip/flipdistortnode.h +++ b/app/node/distort/flip/flipdistortnode.h @@ -40,13 +40,15 @@ class FlipDistortNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &globals) const override; static const QString kTextureInput; static const QString kHorizontalInput; static const QString kVerticalInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/distort/mask/mask.cpp b/app/node/distort/mask/mask.cpp index 71e789e4e9..9852e74462 100644 --- a/app/node/distort/mask/mask.cpp +++ b/app/node/distort/mask/mask.cpp @@ -34,23 +34,25 @@ MaskDistortNode::MaskDistortNode() // Mask should always be (1.0, 1.0, 1.0) for multiply to work correctly SetInputFlag(kColorInput, kInputFlagHidden); - AddInput(kInvertInput, NodeValue::kBoolean, false); + AddInput(kInvertInput, TYPE_BOOL, false); - AddInput(kFeatherInput, NodeValue::kFloat, 0.0); + AddInput(kFeatherInput, TYPE_DOUBLE, 0.0); SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0); } -ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode GetInvertShader(const QString &id) { - if (request.id == QStringLiteral("mrg")) { - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multiply.frag"))); - } else if (request.id == QStringLiteral("feather")) { - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag"))); - } else if (request.id == QStringLiteral("invert")) { - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/invertrgba.frag"))); - } else { - return super::GetShaderCode(request); - } + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/invertrgba.frag"))); +} + +ShaderCode GetFeatherShader(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag"))); +} + +ShaderCode GetMergeShader(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multiply.frag"))); } void MaskDistortNode::Retranslate() @@ -62,49 +64,49 @@ void MaskDistortNode::Retranslate() SetInputName(kFeatherInput, tr("Feather")); } -void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t MaskDistortNode::Value(const ValueParams &p) const { - TexturePtr texture = value[kBaseInput].toTexture(); + TexturePtr texture = GetInputValue(p, kBaseInput).toTexture(); - VideoParams job_params = texture ? texture->params() : globals.vparams(); - NodeValue job(NodeValue::kTexture, Texture::Job(job_params, GetGenerateJob(value, job_params)), this); + VideoParams job_params = texture ? texture->params() : p.vparams(); + value_t job = Texture::Job(job_params, GetGenerateJob(p, job_params)); - if (value[kInvertInput].toBool()) { + if (GetInputValue(p, kInvertInput).toBool()) { ShaderJob invert; - invert.SetShaderID(QStringLiteral("invert")); + invert.set_function(GetInvertShader); invert.Insert(QStringLiteral("tex_in"), job); - job.set_value(Texture::Job(job_params, invert)); + job = Texture::Job(job_params, invert); } if (texture) { // Push as merge node ShaderJob merge; - merge.SetShaderID(QStringLiteral("mrg")); - merge.Insert(QStringLiteral("tex_a"), value[kBaseInput]); + merge.set_function(GetMergeShader); + merge.Insert(QStringLiteral("tex_a"), GetInputValue(p, kBaseInput)); - if (value[kFeatherInput].toDouble() > 0.0) { + if (GetInputValue(p, kFeatherInput).toDouble() > 0.0) { // Nest a blur shader in there too ShaderJob feather; - feather.SetShaderID(QStringLiteral("feather")); + feather.set_function(GetFeatherShader); feather.Insert(BlurFilterNode::kTextureInput, job); - feather.Insert(BlurFilterNode::kMethodInput, NodeValue(NodeValue::kInt, int(BlurFilterNode::kGaussian), this)); - feather.Insert(BlurFilterNode::kHorizInput, NodeValue(NodeValue::kBoolean, true, this)); - feather.Insert(BlurFilterNode::kVertInput, NodeValue(NodeValue::kBoolean, true, this)); - feather.Insert(BlurFilterNode::kRepeatEdgePixelsInput, NodeValue(NodeValue::kBoolean, true, this)); - feather.Insert(BlurFilterNode::kRadiusInput, NodeValue(NodeValue::kFloat, value[kFeatherInput].toDouble(), this)); + feather.Insert(BlurFilterNode::kMethodInput, int(BlurFilterNode::kGaussian)); + feather.Insert(BlurFilterNode::kHorizInput, true); + feather.Insert(BlurFilterNode::kVertInput, true); + feather.Insert(BlurFilterNode::kRepeatEdgePixelsInput, true); + feather.Insert(BlurFilterNode::kRadiusInput, GetInputValue(p, kFeatherInput).toDouble()); feather.SetIterations(2, BlurFilterNode::kTextureInput); - feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, texture ? texture->virtual_resolution() : globals.square_resolution(), this)); + feather.Insert(QStringLiteral("resolution_in"), texture ? texture->virtual_resolution() : p.square_resolution()); - merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, Texture::Job(job_params, feather), this)); + merge.Insert(QStringLiteral("tex_b"), Texture::Job(job_params, feather)); } else { merge.Insert(QStringLiteral("tex_b"), job); } - table->Push(NodeValue::kTexture, Texture::Job(job_params, merge), this); + return Texture::Job(job_params, merge); } else { - table->Push(job); + return job; } } diff --git a/app/node/distort/mask/mask.h b/app/node/distort/mask/mask.h index e5e44c2c99..4fd7f4d822 100644 --- a/app/node/distort/mask/mask.h +++ b/app/node/distort/mask/mask.h @@ -53,11 +53,9 @@ class MaskDistortNode : public PolygonGenerator return tr("Apply a polygonal mask."); } - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kInvertInput; static const QString kFeatherInput; diff --git a/app/node/distort/ripple/rippledistortnode.cpp b/app/node/distort/ripple/rippledistortnode.cpp index 2e8a1f1b49..a939faf41f 100644 --- a/app/node/distort/ripple/rippledistortnode.cpp +++ b/app/node/distort/ripple/rippledistortnode.cpp @@ -33,16 +33,16 @@ const QString RippleDistortNode::kStretchInput = QStringLiteral("stretch_in"); RippleDistortNode::RippleDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kEvolutionInput, NodeValue::kFloat, 0); - AddInput(kIntensityInput, NodeValue::kFloat, 100); + AddInput(kEvolutionInput, TYPE_DOUBLE, 0.0); + AddInput(kIntensityInput, TYPE_DOUBLE, 100.0); - AddInput(kFrequencyInput, NodeValue::kFloat, 1); + AddInput(kFrequencyInput, TYPE_DOUBLE, 1.0); SetInputProperty(kFrequencyInput, QStringLiteral("base"), 0.01); - AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); - AddInput(kStretchInput, NodeValue::kBoolean, false); + AddInput(kPositionInput, TYPE_VEC2, QVector2D(0, 0)); + AddInput(kStretchInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -86,33 +86,47 @@ void RippleDistortNode::Retranslate() SetInputName(kStretchInput, tr("Stretch")); } -ShaderCode RippleDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode RippleDistortNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/ripple.frag")); } -void RippleDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t RippleDistortNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t texture = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = texture.toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(value[kIntensityInput].toDouble())) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); - table->Push(NodeValue::kTexture, tex->toJob(job), this); + value_t intensity = GetInputValue(p, kIntensityInput); + + if (!qIsNull(intensity.toDouble())) { + ShaderJob job; + + job.set_function(GetShaderCode); + job.Insert(kTextureInput, texture); + job.Insert(kEvolutionInput, GetInputValue(p, kEvolutionInput)); + job.Insert(kIntensityInput, intensity); + job.Insert(kFrequencyInput, GetInputValue(p, kFrequencyInput)); + job.Insert(kPositionInput, GetInputValue(p, kPositionInput)); + job.Insert(kStretchInput, GetInputValue(p, kStretchInput)); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); + + return tex->toJob(job); } else { // If we're not flipping or flopping just push the texture - table->Push(value[kTextureInput]); + return texture; } } + + return value_t(); } -void RippleDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void RippleDistortNode::UpdateGizmoPositions(const ValueParams &p) { - if (TexturePtr tex = row[kTextureInput].toTexture()) { + if (TexturePtr tex = GetInputValue(p, kTextureInput).toTexture()) { QPointF half_res(tex->virtual_resolution().x()/2, tex->virtual_resolution().y()/2); - gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); + gizmo_->SetPoint(half_res + GetInputValue(p, kPositionInput).toVec2().toPointF()); } } @@ -121,8 +135,8 @@ void RippleDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModi NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } } diff --git a/app/node/distort/ripple/rippledistortnode.h b/app/node/distort/ripple/rippledistortnode.h index 9f05e06a89..43841fd984 100644 --- a/app/node/distort/ripple/rippledistortnode.h +++ b/app/node/distort/ripple/rippledistortnode.h @@ -41,10 +41,9 @@ class RippleDistortNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; static const QString kTextureInput; static const QString kEvolutionInput; @@ -57,6 +56,8 @@ protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: + static ShaderCode GetShaderCode(const QString &id); + PointGizmo *gizmo_; }; diff --git a/app/node/distort/swirl/swirldistortnode.cpp b/app/node/distort/swirl/swirldistortnode.cpp index eb91bc27a7..b7854a8562 100644 --- a/app/node/distort/swirl/swirldistortnode.cpp +++ b/app/node/distort/swirl/swirldistortnode.cpp @@ -31,15 +31,15 @@ const QString SwirlDistortNode::kPositionInput = QStringLiteral("pos_in"); SwirlDistortNode::SwirlDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kRadiusInput, NodeValue::kFloat, 200); - SetInputProperty(kRadiusInput, QStringLiteral("min"), 0); + AddInput(kRadiusInput, TYPE_DOUBLE, 200.0); + SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); - AddInput(kAngleInput, NodeValue::kFloat, 10); + AddInput(kAngleInput, TYPE_DOUBLE, 10.0); SetInputProperty(kAngleInput, QStringLiteral("base"), 0.1); - AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); + AddInput(kPositionInput, TYPE_VEC2, QVector2D(0, 0)); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -81,33 +81,33 @@ void SwirlDistortNode::Retranslate() SetInputName(kPositionInput, tr("Position")); } -ShaderCode SwirlDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode SwirlDistortNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/swirl.frag")); } -void SwirlDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t SwirlDistortNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(value[kAngleInput].toDouble()) && !qIsNull(value[kRadiusInput].toDouble())) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else { - // If we're not flipping or flopping just push the texture - table->Push(value[kTextureInput]); + if (!qIsNull(GetInputValue(p, kAngleInput).toDouble()) && !qIsNull(GetInputValue(p, kRadiusInput).toDouble())) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); + return tex->toJob(job); } } + + return tex_meta; } -void SwirlDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void SwirlDistortNode::UpdateGizmoPositions(const ValueParams &p) { - QPointF half_res(globals.square_resolution().x()/2, globals.square_resolution().y()/2); + QPointF half_res(p.square_resolution().x()/2, p.square_resolution().y()/2); - gizmo_->SetPoint(half_res + row[kPositionInput].toVec2().toPointF()); + gizmo_->SetPoint(half_res + GetInputValue(p, kPositionInput).toVec2().toPointF()); } void SwirlDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) @@ -115,8 +115,8 @@ void SwirlDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModif NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } } diff --git a/app/node/distort/swirl/swirldistortnode.h b/app/node/distort/swirl/swirldistortnode.h index 744704ddc9..9e3bed93bd 100644 --- a/app/node/distort/swirl/swirldistortnode.h +++ b/app/node/distort/swirl/swirldistortnode.h @@ -41,10 +41,9 @@ class SwirlDistortNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; static const QString kTextureInput; static const QString kRadiusInput; @@ -55,6 +54,8 @@ protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: + static ShaderCode GetShaderCode(const QString &id); + PointGizmo *gizmo_; }; diff --git a/app/node/distort/tile/tiledistortnode.cpp b/app/node/distort/tile/tiledistortnode.cpp index 864428a7ad..0c55fa6964 100644 --- a/app/node/distort/tile/tiledistortnode.cpp +++ b/app/node/distort/tile/tiledistortnode.cpp @@ -35,18 +35,18 @@ const QString TileDistortNode::kMirrorYInput = QStringLiteral("mirrory_in"); TileDistortNode::TileDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kScaleInput, NodeValue::kFloat, 0.5); - SetInputProperty(kScaleInput, QStringLiteral("min"), 0); + AddInput(kScaleInput, TYPE_DOUBLE, 0.5); + SetInputProperty(kScaleInput, QStringLiteral("min"), 0.0); SetInputProperty(kScaleInput, QStringLiteral("view"), FloatSlider::kPercentage); - AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); + AddInput(kPositionInput, TYPE_VEC2, QVector2D(0, 0)); - AddInput(kAnchorInput, NodeValue::kCombo, kMiddleCenter); + AddInput(kAnchorInput, TYPE_COMBO, kMiddleCenter); - AddInput(kMirrorXInput, NodeValue::kBoolean, false); - AddInput(kMirrorYInput, NodeValue::kBoolean, false); + AddInput(kMirrorXInput, TYPE_BOOL, false); + AddInput(kMirrorYInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -102,37 +102,37 @@ void TileDistortNode::Retranslate() }); } -ShaderCode TileDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode TileDistortNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/tile.frag")); } -void TileDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TileDistortNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t texture = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = texture.toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qFuzzyCompare(value[kScaleInput].toDouble(), 1.0)) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else { - // If we're not flipping or flopping just push the texture - table->Push(value[kTextureInput]); + if (!qFuzzyCompare(GetInputValue(p, kScaleInput).toDouble(), 1.0)) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); + return tex->toJob(job); } } + + return texture; } -void TileDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void TileDistortNode::UpdateGizmoPositions(const ValueParams &p) { - if (TexturePtr tex = row[kTextureInput].toTexture()) { + if (TexturePtr tex = GetInputValue(p, kTextureInput).toTexture()) { QPointF res = tex->virtual_resolution().toPointF(); - QPointF pos = row[kPositionInput].toVec2().toPointF(); + QPointF pos = GetInputValue(p, kPositionInput).toVec2().toPointF(); qreal x = pos.x(); qreal y = pos.y(); - Anchor a = static_cast(row[kAnchorInput].toInt()); + Anchor a = static_cast(GetInputValue(p, kAnchorInput).toInt()); if (a == kTopLeft || a == kTopCenter || a == kTopRight) { // Do nothing } else if (a == kMiddleLeft || a == kMiddleCenter || a == kMiddleRight) { @@ -157,8 +157,8 @@ void TileDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifi NodeInputDragger &x_drag = gizmo_->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo_->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } } diff --git a/app/node/distort/tile/tiledistortnode.h b/app/node/distort/tile/tiledistortnode.h index 44d9b9c1e7..4d9a84a58d 100644 --- a/app/node/distort/tile/tiledistortnode.h +++ b/app/node/distort/tile/tiledistortnode.h @@ -41,10 +41,9 @@ class TileDistortNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; static const QString kTextureInput; static const QString kScaleInput; @@ -57,6 +56,8 @@ protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: + static ShaderCode GetShaderCode(const QString &id); + enum Anchor { kTopLeft, kTopCenter, diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 01d9e1c454..3d1d3026e4 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -28,18 +28,19 @@ const QString TransformDistortNode::kParentInput = QStringLiteral("parent_in"); const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in"); const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in"); const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in"); +const QString TransformDistortNode::kMatrixOutput = QStringLiteral("matrix_out"); #define super MatrixGenerator TransformDistortNode::TransformDistortNode() { - AddInput(kParentInput, NodeValue::kMatrix); + AddInput(kParentInput, TYPE_MATRIX); - AddInput(kAutoscaleInput, NodeValue::kCombo, 0); + AddInput(kAutoscaleInput, TYPE_COMBO, 0); - AddInput(kInterpolationInput, NodeValue::kCombo, 2); + AddInput(kInterpolationInput, TYPE_COMBO, 2); - PrependInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + PrependInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); // Initiate gizmos rotation_gizmo_ = AddDraggableGizmo(); @@ -66,6 +67,11 @@ TransformDistortNode::TransformDistortNode() SetFlag(kVideoEffect); SetEffectInput(kTextureInput); + + AddOutput(kMatrixOutput, TYPE_MATRIX); + + // Undo MatrixGenerator deprecation flag for derivative + SetFlag(kDontShowInCreateMenu, false); } void TransformDistortNode::Retranslate() @@ -79,71 +85,74 @@ void TransformDistortNode::Retranslate() SetComboBoxStrings(kAutoscaleInput, {tr("None"), tr("Fit"), tr("Fill"), tr("Stretch")}); SetComboBoxStrings(kInterpolationInput, {tr("Nearest Neighbor"), tr("Bilinear"), tr("Mipmapped Bilinear")}); + + SetOutputName(kMatrixOutput, tr("Matrix")); } -void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode TransformDistortNode::GetShaderCode(const QString &id) { - // Generate matrix - QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false, value[kParentInput].toMatrix()); - - // Pop texture - NodeValue texture_meta = value[kTextureInput]; - - TexturePtr job_to_push = nullptr; - - // If we have a texture, generate a matrix and make it happen - if (TexturePtr texture = texture_meta.toTexture()) { - // Adjust our matrix by the resolutions involved - QMatrix4x4 real_matrix = GenerateAutoScaledMatrix(generated_matrix, value, globals, texture->params()); + // Returns default frag and vert shader + return ShaderCode(); +} - if (!real_matrix.isIdentity()) { - // The matrix will transform things - ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), texture_meta); - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); - job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].toInt())); +value_t TransformDistortNode::Value(const ValueParams &p) const +{ + // Generate matrix + QMatrix4x4 generated_matrix = GenerateMatrix(p, false, false, false, GetInputValue(p, kParentInput).toMatrix()); - // Use global resolution rather than texture resolution because this may result in a size change - job_to_push = Texture::Job(globals.vparams(), job); + if (p.output() == kMatrixOutput) { + return generated_matrix; + } else { + // Pop texture + value_t texture_meta = GetInputValue(p, kTextureInput); + + TexturePtr job_to_push = nullptr; + + // If we have a texture, generate a matrix and make it happen + if (TexturePtr texture = texture_meta.toTexture()) { + // Adjust our matrix by the resolutions involved + QMatrix4x4 real_matrix = GenerateAutoScaledMatrix(generated_matrix, p, texture->params()); + + if (!real_matrix.isIdentity()) { + // The matrix will transform things + ShaderJob job; + job.Insert(QStringLiteral("ove_maintex"), texture_meta); + job.Insert(QStringLiteral("ove_mvpmat"), real_matrix); + job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(GetInputValue(p, kInterpolationInput).toInt())); + job.set_function(GetShaderCode); + + // Use global resolution rather than texture resolution because this may result in a size change + job_to_push = Texture::Job(p.vparams(), job); + } } - } - - table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this); - if (!job_to_push) { - // Re-push whatever value we received - table->Push(texture_meta); - } else { - table->Push(NodeValue::kTexture, job_to_push, this); + if (!job_to_push) { + // Re-push whatever value we received + return texture_meta; + } else { + return job_to_push; + } } } -ShaderCode TransformDistortNode::GetShaderCode(const ShaderRequest &request) const -{ - Q_UNUSED(request); - - // Returns default frag and vert shader - return ShaderCode(); -} - -void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, double y, const rational &time) +void TransformDistortNode::GizmoDragStart(const ValueParams &p, double x, double y, const rational &time) { DraggableGizmo *gizmo = static_cast(sender()); if (gizmo == anchor_gizmo_) { - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false, row[kParentInput].toMatrix()).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(p, true, true, false, GetInputValue(p, kParentInput).toMatrix()).toTransform().inverted(); } else if (IsAScaleGizmo(gizmo)) { // Dragging scale handle - TexturePtr tex = row[kTextureInput].toTexture(); + TexturePtr tex = GetInputValue(p, kTextureInput).toTexture(); if (!tex) { return; } - gizmo_scale_uniform_ = row[kUniformScaleInput].toBool(); - gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); + gizmo_scale_uniform_ = GetInputValue(p, kUniformScaleInput).toBool(); + gizmo_anchor_pt_ = (GetInputValue(p, kAnchorInput).toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight] || gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) { @@ -157,7 +166,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou // Store texture size VideoParams texture_params = tex->params(); QVector2D texture_sz(texture_params.square_pixel_width(), texture_params.height()); - gizmo_scale_anchor_ = row[kAnchorInput].toVec2() + texture_sz/2; + gizmo_scale_anchor_ = GetInputValue(p, kAnchorInput).toVec2() + texture_sz/2; if (gizmo == point_gizmo_[kGizmoScaleTopRight] || gizmo == point_gizmo_[kGizmoScaleBottomRight] @@ -174,11 +183,11 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } // Store current matrix - gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true, row[kParentInput].toMatrix()).toTransform().inverted(); + gizmo_inverted_transform_ = GenerateMatrix(p, true, true, true, GetInputValue(p, kParentInput).toMatrix()).toTransform().inverted(); } else if (gizmo == rotation_gizmo_) { - gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); + gizmo_anchor_pt_ = (GetInputValue(p, kAnchorInput).toVec2() + gizmo->GetGlobals().nonsquare_resolution()/2).toPointF(); gizmo_start_angle_ = std::atan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x()); gizmo_last_angle_ = gizmo_start_angle_; gizmo_last_alt_angle_ = std::atan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y()); @@ -197,8 +206,8 @@ void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } else if (gizmo == anchor_gizmo_) { @@ -209,10 +218,10 @@ void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM QPointF inverted_movement(gizmo_inverted_transform_.map(QPointF(x, y))); - x_anchor_drag.Drag(x_anchor_drag.GetStartValue().toDouble() + inverted_movement.x()); - y_anchor_drag.Drag(y_anchor_drag.GetStartValue().toDouble() + inverted_movement.y()); - x_pos_drag.Drag(x_pos_drag.GetStartValue().toDouble() + x); - y_pos_drag.Drag(y_pos_drag.GetStartValue().toDouble() + y); + x_anchor_drag.Drag(x_anchor_drag.GetStartValue().value() + inverted_movement.x()); + y_anchor_drag.Drag(y_anchor_drag.GetStartValue().value() + inverted_movement.y()); + x_pos_drag.Drag(x_pos_drag.GetStartValue().value() + x); + y_pos_drag.Drag(y_pos_drag.GetStartValue().value() + y); } else if (gizmo == rotation_gizmo_) { @@ -249,7 +258,7 @@ void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958; NodeInputDragger &d = gizmo->GetDraggers()[0]; - d.Drag(d.GetStartValue().toDouble() + rotation_difference); + d.Drag(d.GetStartValue().value() + rotation_difference); } else if (IsAScaleGizmo(gizmo)) { @@ -336,15 +345,15 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat return adjusted_matrix; } -void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void TransformDistortNode::UpdateGizmoPositions(const ValueParams &p) { - TexturePtr tex = row[kTextureInput].toTexture(); + TexturePtr tex = GetInputValue(p, kTextureInput).toTexture(); if (!tex) { return; } // Get the sequence resolution - const QVector2D &sequence_res = globals.nonsquare_resolution(); + const QVector2D &sequence_res = p.nonsquare_resolution(); QVector2D sequence_half_res = sequence_res * 0.5; QPointF sequence_half_res_pt = sequence_half_res.toPointF(); @@ -354,12 +363,12 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N QVector2D tex_offset = tex_params.offset(); // Retrieve autoscale value - AutoScaleType autoscale = static_cast(row[kAutoscaleInput].toInt()); + AutoScaleType autoscale = static_cast(GetInputValue(p, kAutoscaleInput).toInt()); // Fold values into a matrix for the rectangle QMatrix4x4 rectangle_matrix; rectangle_matrix.scale(sequence_half_res.x(), sequence_half_res.y()); - rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()), + rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(p, false, false, false, GetInputValue(p, kParentInput).toMatrix()), sequence_res, tex_sz, tex_offset, @@ -379,7 +388,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N // Draw anchor point QMatrix4x4 anchor_matrix; anchor_matrix.scale(sequence_half_res.x(), sequence_half_res.y()); - anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()), + anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(p, true, false, false, GetInputValue(p, kParentInput).toMatrix()), sequence_res, tex_sz, tex_offset, @@ -402,14 +411,14 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N SetInputProperty(kAnchorInput, QStringLiteral("offset"), tex_sz * 0.5); } -QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const +QTransform TransformDistortNode::GizmoTransformation(const ValueParams &p) const { - if (TexturePtr texture = row[kTextureInput].toTexture()) { + if (TexturePtr texture = GetInputValue(p, kTextureInput).toTexture()) { //auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()); - auto m = GenerateMatrix(row, false, false, false, QMatrix4x4()); - return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform(); + auto m = GenerateMatrix(p, false, false, false, QMatrix4x4()); + return GenerateAutoScaledMatrix(m, p, texture->params()).toTransform(); } - return super::GizmoTransformation(row, globals); + return super::GizmoTransformation(p); } QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat) @@ -417,11 +426,11 @@ QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF return mat.map(QPointF(x, y)) + half_res; } -QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const +QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const ValueParams &p, const VideoParams& texture_params) const { - const QVector2D &sequence_res = globals.nonsquare_resolution(); + const QVector2D &sequence_res = p.nonsquare_resolution(); QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height()); - AutoScaleType autoscale = static_cast(value[kAutoscaleInput].toInt()); + AutoScaleType autoscale = static_cast(GetInputValue(p, kAutoscaleInput).toInt()); return AdjustMatrixByResolutions(generated_matrix, sequence_res, diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index be17de561e..11c51e1cec 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -64,9 +64,7 @@ class TransformDistortNode : public MatrixGenerator virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual value_t Value(const ValueParams &p) const override; enum AutoScaleType { kAutoScaleNone, @@ -81,23 +79,26 @@ class TransformDistortNode : public MatrixGenerator const QVector2D& offset, AutoScaleType autoscale_type = kAutoScaleNone); - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; - virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; + virtual QTransform GizmoTransformation(const ValueParams &p) const override; static const QString kParentInput; static const QString kTextureInput; static const QString kAutoscaleInput; static const QString kInterpolationInput; + static const QString kMatrixOutput; protected slots: - virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time) override; + virtual void GizmoDragStart(const olive::ValueParams &p, double x, double y, const olive::rational &time) override; virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: static QPointF CreateScalePoint(double x, double y, const QPointF& half_res, const QMatrix4x4& mat); - QMatrix4x4 GenerateAutoScaledMatrix(const QMatrix4x4 &generated_matrix, const NodeValueRow &db, const NodeGlobals &globals, const VideoParams &texture_params) const; + static ShaderCode GetShaderCode(const QString &id); + + QMatrix4x4 GenerateAutoScaledMatrix(const QMatrix4x4 &generated_matrix, const ValueParams &globals, const VideoParams &texture_params) const; bool IsAScaleGizmo(NodeGizmo *g) const; diff --git a/app/node/distort/wave/wavedistortnode.cpp b/app/node/distort/wave/wavedistortnode.cpp index 88171d6f23..3d974376bc 100644 --- a/app/node/distort/wave/wavedistortnode.cpp +++ b/app/node/distort/wave/wavedistortnode.cpp @@ -32,13 +32,13 @@ const QString WaveDistortNode::kVerticalInput = QStringLiteral("vertical_in"); WaveDistortNode::WaveDistortNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kFrequencyInput, NodeValue::kFloat, 10); - AddInput(kIntensityInput, NodeValue::kFloat, 10); - AddInput(kEvolutionInput, NodeValue::kFloat, 0); + AddInput(kFrequencyInput, TYPE_DOUBLE, 10.0); + AddInput(kIntensityInput, TYPE_DOUBLE, 10.0); + AddInput(kEvolutionInput, TYPE_DOUBLE, 0.0); - AddInput(kVerticalInput, NodeValue::kCombo, false); + AddInput(kVerticalInput, TYPE_COMBO, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -76,25 +76,24 @@ void WaveDistortNode::Retranslate() SetComboBoxStrings(kVerticalInput, {tr("Horizontal"), tr("Vertical")}); } -ShaderCode WaveDistortNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode WaveDistortNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/wave.frag")); } -void WaveDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t WaveDistortNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr texture = value[kTextureInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr texture = tex_meta.toTexture()) { // Only run shader if at least one of flip or flop are selected - if (!qIsNull(value[kIntensityInput].toDouble())) { - table->Push(NodeValue::kTexture, Texture::Job(texture->params(), ShaderJob(value)), this); - } else { - // If we're not flipping or flopping just push the texture - table->Push(value[kTextureInput]); + if (!qIsNull(GetInputValue(p, kIntensityInput).toDouble())) { + return Texture::Job(texture->params(), CreateShaderJob(p, GetShaderCode)); } } + return tex_meta; } } diff --git a/app/node/distort/wave/wavedistortnode.h b/app/node/distort/wave/wavedistortnode.h index aa253dc0e8..b7b1b8aa91 100644 --- a/app/node/distort/wave/wavedistortnode.h +++ b/app/node/distort/wave/wavedistortnode.h @@ -40,8 +40,7 @@ class WaveDistortNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &globals) const override; static const QString kTextureInput; static const QString kFrequencyInput; @@ -49,6 +48,9 @@ class WaveDistortNode : public Node static const QString kEvolutionInput; static const QString kVerticalInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index 4b7e7ce67d..73348593e4 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -12,15 +12,9 @@ const QString OpacityEffect::kValueInput = QStringLiteral("opacity_in"); OpacityEffect::OpacityEffect() { - MathNode *math = new MathNode(); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - math->SetOperation(MathNode::kOpMultiply); - - SetNodePositionInContext(math, QPointF(0, 0)); - - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - - AddInput(kValueInput, NodeValue::kFloat, 1.0); + AddInput(kValueInput, TYPE_DOUBLE, 1.0); SetInputProperty(kValueInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kValueInput, QStringLiteral("min"), 0.0); SetInputProperty(kValueInput, QStringLiteral("max"), 1.0); @@ -37,30 +31,35 @@ void OpacityEffect::Retranslate() SetInputName(kValueInput, tr("Opacity")); } -ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const +ShaderCode GetRGBShaderCode(const QString &id) { - if (request.id == QStringLiteral("rgbmult")) { - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity_rgb.frag")); - } else { - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); - } + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity_rgb.frag")); +} + +ShaderCode GetNumberShaderCode(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); } -void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t OpacityEffect::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { - if (TexturePtr opacity_tex = value[kValueInput].toTexture()) { - ShaderJob job(value); + value_t texture = GetInputValue(p, kTextureInput); + value_t value = GetInputValue(p, kValueInput); + + if (TexturePtr tex = texture.toTexture()) { + TexturePtr opacity_tex; + + if (value.get(&opacity_tex)) { + ShaderJob job = CreateShaderJob(p, GetRGBShaderCode); job.SetShaderID(QStringLiteral("rgbmult")); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) { - table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this); - } else { - // 1.0 float is a no-op, so just push the texture - table->Push(value[kTextureInput]); + return tex->toJob(job); + } else if (!qFuzzyCompare(value.toDouble(), 1.0)) { + return tex->toJob(CreateShaderJob(p, GetNumberShaderCode)); } } + + return texture; } } diff --git a/app/node/effect/opacity/opacityeffect.h b/app/node/effect/opacity/opacityeffect.h index 36e284274e..f057d61fcd 100644 --- a/app/node/effect/opacity/opacityeffect.h +++ b/app/node/effect/opacity/opacityeffect.h @@ -34,8 +34,7 @@ class OpacityEffect : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; static const QString kValueInput; diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 1839d8f080..e8a6f83d1c 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -53,6 +53,7 @@ #include "generator/text/textv1.h" #include "generator/text/textv2.h" #include "generator/text/textv3.h" +#include "generator/tone/tonegenerator.h" #include "input/multicam/multicamnode.h" #include "input/time/timeinput.h" #include "input/value/valuenode.h" @@ -309,6 +310,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new RippleDistortNode(); case kMulticamNode: return new MultiCamNode(); + case kToneGenerator: + return new ToneGenerator(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index fbf3535514..2cf99ade6d 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -81,6 +81,7 @@ class NodeFactory kTileDistort, kSwirlDistort, kMulticamNode, + kToneGenerator, // Count value kInternalNodeCount diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 8d0b68bd35..40c65a1128 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -37,34 +37,34 @@ const QString BlurFilterNode::kRadialCenterInput = QStringLiteral("radial_center BlurFilterNode::BlurFilterNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - Method default_method = kGaussian; + const Method default_method = kGaussian; - AddInput(kMethodInput, NodeValue::kCombo, default_method, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + AddInput(kMethodInput, TYPE_COMBO, default_method, kInputFlagNotKeyframable | kInputFlagNotConnectable); - AddInput(kRadiusInput, NodeValue::kFloat, 10.0); + AddInput(kRadiusInput, TYPE_DOUBLE, 10.0); SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); { // Box and gaussian only - AddInput(kHorizInput, NodeValue::kBoolean, true); - AddInput(kVertInput, NodeValue::kBoolean, true); + AddInput(kHorizInput, TYPE_BOOL, true); + AddInput(kVertInput, TYPE_BOOL, true); } { // Directional only - AddInput(kDirectionalDegreesInput, NodeValue::kFloat, 0.0); + AddInput(kDirectionalDegreesInput, TYPE_DOUBLE, 0.0); } { // Radial only - AddInput(kRadialCenterInput, NodeValue::kVec2, QVector2D(0, 0)); + AddInput(kRadialCenterInput, TYPE_VEC2, QVector2D(0, 0)); } UpdateInputs(default_method); - AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true); + AddInput(kRepeatEdgePixelsInput, TYPE_BOOL, true); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -111,30 +111,30 @@ void BlurFilterNode::Retranslate() SetInputName(kRadialCenterInput, tr("Center")); } -ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode BlurFilterNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/blur.frag")); } -void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t BlurFilterNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { - Method method = static_cast(value[kMethodInput].toInt()); + value_t tex_meta = GetInputValue(p, kTextureInput); + if (TexturePtr tex = tex_meta.toTexture()) { + Method method = static_cast(GetInputValue(p, kMethodInput).toInt()); bool can_push_job = true; int iterations = 1; // Check if radius is > 0 - if (value[kRadiusInput].toDouble() > 0.0) { + if (GetInputValue(p, kRadiusInput).toDouble() > 0.0) { // Method-specific considerations switch (method) { case kBox: case kGaussian: { - bool horiz = value[kHorizInput].toBool(); - bool vert = value[kVertInput].toBool(); + bool horiz = GetInputValue(p, kHorizInput).toBool(); + bool vert = GetInputValue(p, kVertInput).toBool(); if (!horiz && !vert) { // Disable job if horiz and vert are unchecked @@ -154,27 +154,27 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals } if (can_push_job) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + ShaderJob job = CreateShaderJob(p, GetShaderCode); + + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); job.SetIterations(iterations, kTextureInput); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else { - // If we're not performing the blur job, just push the texture - table->Push(value[kTextureInput]); - } + return tex->toJob(job); + } } + + return tex_meta; } -void BlurFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void BlurFilterNode::UpdateGizmoPositions(const ValueParams &p) { - if (TexturePtr tex = row[kTextureInput].toTexture()) { - if (row[kMethodInput].toInt() == kRadial) { + if (TexturePtr tex = GetInputValue(p, kTextureInput).toTexture()) { + if (GetInputValue(p, kMethodInput).toInt() == kRadial) { const QVector2D &sequence_res = tex->virtual_resolution(); QVector2D sequence_half_res = sequence_res * 0.5; radial_center_gizmo_->SetVisible(true); - radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].toVec2().toPointF()); + radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + GetInputValue(p, kRadialCenterInput).toVec2().toPointF()); SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res); } else{ @@ -192,8 +192,8 @@ void BlurFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifie NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } } diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h index 11ff669530..cc7ebedd9d 100644 --- a/app/node/filter/blur/blur.h +++ b/app/node/filter/blur/blur.h @@ -48,15 +48,14 @@ class BlurFilterNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &globals) const override; Method GetMethod() const { return static_cast(GetStandardValue(kMethodInput).toInt()); } - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; static const QString kTextureInput; static const QString kMethodInput; @@ -76,6 +75,8 @@ protected slots: virtual void InputValueChangedEvent(const QString& input, int element) override; private: + static ShaderCode GetShaderCode(const QString &id); + void UpdateInputs(Method method); PointGizmo *radial_center_gizmo_; diff --git a/app/node/filter/dropshadow/dropshadowfilter.cpp b/app/node/filter/dropshadow/dropshadowfilter.cpp index fead73b096..3a8449e56f 100644 --- a/app/node/filter/dropshadow/dropshadowfilter.cpp +++ b/app/node/filter/dropshadow/dropshadowfilter.cpp @@ -36,22 +36,22 @@ const QString DropShadowFilter::kFastInput = QStringLiteral("fast_in"); DropShadowFilter::DropShadowFilter() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0, 0.0, 0.0))); + AddInput(kColorInput, TYPE_COLOR, Color(0.0, 0.0, 0.0)); - AddInput(kDistanceInput, NodeValue::kFloat, 10.0); + AddInput(kDistanceInput, TYPE_DOUBLE, 10.0); - AddInput(kAngleInput, NodeValue::kFloat, 135.0); + AddInput(kAngleInput, TYPE_DOUBLE, 135.0); - AddInput(kSoftnessInput, NodeValue::kFloat, 10.0); + AddInput(kSoftnessInput, TYPE_DOUBLE, 10.0); SetInputProperty(kSoftnessInput, QStringLiteral("min"), 0.0); - AddInput(kOpacityInput, NodeValue::kFloat, 1.0); + AddInput(kOpacityInput, TYPE_DOUBLE, 1.0); SetInputProperty(kOpacityInput, QStringLiteral("min"), 0.0); SetInputProperty(kOpacityInput, QStringLiteral("view"), FloatSlider::kPercentage); - AddInput(kFastInput, NodeValue::kBoolean, false); + AddInput(kFastInput, TYPE_BOOL, false); SetEffectInput(kTextureInput); SetFlag(kVideoEffect); @@ -70,28 +70,31 @@ void DropShadowFilter::Retranslate() SetInputName(kFastInput, tr("Faster (Lower Quality)")); } -ShaderCode DropShadowFilter::GetShaderCode(const ShaderRequest &request) const +ShaderCode DropShadowFilter::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/dropshadow.frag")); } -void DropShadowFilter::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t DropShadowFilter::Value(const ValueParams &p) const { - if (TexturePtr tex = value[kTextureInput].toTexture()) { - ShaderJob job(value); + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); QString iterative = QStringLiteral("previous_iteration_in"); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); - job.Insert(iterative, value[kTextureInput]); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); + job.Insert(iterative, tex_meta); - if (!qIsNull(value[kSoftnessInput].toDouble())) { + if (!qIsNull(GetInputValue(p, kSoftnessInput).toDouble())) { job.SetIterations(3, iterative); } - table->Push(NodeValue::kTexture, tex->toJob(job), this); + return tex->toJob(job); } + + return value_t(); } } diff --git a/app/node/filter/dropshadow/dropshadowfilter.h b/app/node/filter/dropshadow/dropshadowfilter.h index 5e820a2809..75189301f6 100644 --- a/app/node/filter/dropshadow/dropshadowfilter.h +++ b/app/node/filter/dropshadow/dropshadowfilter.h @@ -40,8 +40,7 @@ class DropShadowFilter : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; static const QString kColorInput; @@ -51,6 +50,9 @@ class DropShadowFilter : public Node static const QString kOpacityInput; static const QString kFastInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index 63966320ef..6f7161ca95 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -30,12 +30,12 @@ const QString MosaicFilterNode::kVertInput = QStringLiteral("vert_in"); MosaicFilterNode::MosaicFilterNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kHorizInput, NodeValue::kFloat, 32.0); + AddInput(kHorizInput, TYPE_DOUBLE, 32.0); SetInputProperty(kHorizInput, QStringLiteral("min"), 1.0); - AddInput(kVertInput, NodeValue::kFloat, 18.0); + AddInput(kVertInput, TYPE_DOUBLE, 18.0); SetInputProperty(kVertInput, QStringLiteral("min"), 1.0); SetFlag(kVideoEffect); @@ -51,29 +51,29 @@ void MosaicFilterNode::Retranslate() SetInputName(kVertInput, tr("Vertical")); } -void MosaicFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode MosaicFilterNode::GetShaderCode(const QString &id) { - if (TexturePtr texture = value[kTextureInput].toTexture()) { + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/mosaic.frag")); +} + +value_t MosaicFilterNode::Value(const ValueParams &p) const +{ + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr texture = tex_meta.toTexture()) { if (texture - && value[kHorizInput].toInt() != texture->width() - && value[kVertInput].toInt() != texture->height()) { - ShaderJob job(value); + && std::floor(GetInputValue(p, kHorizInput).toDouble()) != texture->width() + && std::floor(GetInputValue(p, kVertInput).toDouble()) != texture->height()) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); // Mipmapping makes this look weird, so we just use bilinear for finding the color of each block job.SetInterpolation(kTextureInput, Texture::kLinear); - table->Push(NodeValue::kTexture, texture->toJob(job), this); - } else { - table->Push(value[kTextureInput]); + return texture->toJob(job); } } -} - -ShaderCode MosaicFilterNode::GetShaderCode(const ShaderRequest &request) const -{ - Q_UNUSED(request) - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/mosaic.frag")); + return tex_meta; } } diff --git a/app/node/filter/mosaic/mosaicfilternode.h b/app/node/filter/mosaic/mosaicfilternode.h index 9f962a0cbd..8617c3f523 100644 --- a/app/node/filter/mosaic/mosaicfilternode.h +++ b/app/node/filter/mosaic/mosaicfilternode.h @@ -55,13 +55,15 @@ class MosaicFilterNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; static const QString kHorizInput; static const QString kVertInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index 3f49cd4c4f..0f15207b65 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -34,19 +34,19 @@ const QString StrokeFilterNode::kInnerInput = QStringLiteral("inner_in"); StrokeFilterNode::StrokeFilterNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 1.0f, 1.0f, 1.0f))); + AddInput(kColorInput, TYPE_COLOR, Color(1.0f, 1.0f, 1.0f, 1.0f)); - AddInput(kRadiusInput, NodeValue::kFloat, 10.0); + AddInput(kRadiusInput, TYPE_DOUBLE, 10.0); SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); - AddInput(kOpacityInput, NodeValue::kFloat, 1.0f); + AddInput(kOpacityInput, TYPE_DOUBLE, 1.0f); SetInputProperty(kOpacityInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kOpacityInput, QStringLiteral("min"), 0.0f); SetInputProperty(kOpacityInput, QStringLiteral("max"), 1.0f); - AddInput(kInnerInput, NodeValue::kBoolean, false); + AddInput(kInnerInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -83,25 +83,25 @@ void StrokeFilterNode::Retranslate() SetInputName(kInnerInput, tr("Inner")); } -void StrokeFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode StrokeFilterNode::GetShaderCode(const QString &id) { - if (TexturePtr tex = value[kTextureInput].toTexture()) { - if (value[kRadiusInput].toDouble() > 0.0 - && value[kOpacityInput].toDouble() > 0.0) { - ShaderJob job(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); - table->Push(NodeValue::kTexture, tex->toJob(job), this); - } else { - table->Push(value[kTextureInput]); - } - } + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag")); } -ShaderCode StrokeFilterNode::GetShaderCode(const ShaderRequest &request) const +value_t StrokeFilterNode::Value(const ValueParams &p) const { - Q_UNUSED(request) + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { + if (GetInputValue(p, kRadiusInput).toDouble() > 0.0 + && GetInputValue(p, kOpacityInput).toDouble() > 0.0) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.Insert(QStringLiteral("resolution_in"), tex->virtual_resolution()); + return tex->toJob(job); + } + } - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag")); + return tex_meta; } } diff --git a/app/node/filter/stroke/stroke.h b/app/node/filter/stroke/stroke.h index 4ec07c0e94..872b34e19d 100644 --- a/app/node/filter/stroke/stroke.h +++ b/app/node/filter/stroke/stroke.h @@ -40,8 +40,7 @@ class StrokeFilterNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; static const QString kColorInput; @@ -49,6 +48,9 @@ class StrokeFilterNode : public Node static const QString kOpacityInput; static const QString kInnerInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/generator/CMakeLists.txt b/app/node/generator/CMakeLists.txt index 424c3a0d4e..304a2c253f 100644 --- a/app/node/generator/CMakeLists.txt +++ b/app/node/generator/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(polygon) add_subdirectory(shape) add_subdirectory(solid) add_subdirectory(text) +add_subdirectory(tone) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 0d8a112d43..43fd117704 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -37,18 +37,21 @@ const QString MatrixGenerator::kAnchorInput = QStringLiteral("anchor_in"); MatrixGenerator::MatrixGenerator() { - AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); + AddInput(kPositionInput, TYPE_VEC2, QVector2D(0.0, 0.0)); - AddInput(kRotationInput, NodeValue::kFloat, 0.0); + AddInput(kRotationInput, TYPE_DOUBLE, 0.0); - AddInput(kScaleInput, NodeValue::kVec2, QVector2D(1.0f, 1.0f)); + AddInput(kScaleInput, TYPE_VEC2, QVector2D(1.0f, 1.0f)); SetInputProperty(kScaleInput, QStringLiteral("min"), QVector2D(0, 0)); SetInputProperty(kScaleInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kScaleInput, QStringLiteral("disable1"), true); - AddInput(kUniformScaleInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kUniformScaleInput, TYPE_BOOL, true, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kAnchorInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); + AddInput(kAnchorInput, TYPE_VEC2, QVector2D(0.0, 0.0)); + + // Deprecated: Use Transform instead, which now outputs a matrix too + SetFlag(kDontShowInCreateMenu); } QString MatrixGenerator::Name() const @@ -87,35 +90,34 @@ void MatrixGenerator::Retranslate() SetInputName(kAnchorInput, tr("Anchor Point")); } -void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t MatrixGenerator::Value(const ValueParams &p) const { // Push matrix output - QMatrix4x4 mat = GenerateMatrix(value, false, false, false, QMatrix4x4()); - table->Push(NodeValue::kMatrix, mat, this); + return GenerateMatrix(p, false, false, false, QMatrix4x4()); } -QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const +QMatrix4x4 MatrixGenerator::GenerateMatrix(const ValueParams &p, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const { QVector2D anchor; QVector2D position; QVector2D scale; if (!ignore_anchor) { - anchor = value[kAnchorInput].toVec2(); + anchor = GetInputValue(p, kAnchorInput).toVec2(); } if (!ignore_scale) { - scale = value[kScaleInput].toVec2(); + scale = GetInputValue(p, kScaleInput).toVec2(); } if (!ignore_position) { - position = value[kPositionInput].toVec2(); + position = GetInputValue(p, kPositionInput).toVec2(); } return GenerateMatrix(position, - value[kRotationInput].toDouble(), + GetInputValue(p, kRotationInput).toDouble(), scale, - value[kUniformScaleInput].toBool(), + GetInputValue(p, kUniformScaleInput).toBool(), anchor, mat); } diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 60ea1a0355..d4dff2eb66 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -44,7 +44,7 @@ class MatrixGenerator : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kPositionInput; static const QString kRotationInput; @@ -53,7 +53,7 @@ class MatrixGenerator : public Node static const QString kAnchorInput; protected: - QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const; + QMatrix4x4 GenerateMatrix(const ValueParams &p, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const; static QMatrix4x4 GenerateMatrix(const QVector2D &pos, const float &rot, const QVector2D &scale, diff --git a/app/node/generator/noise/noise.cpp b/app/node/generator/noise/noise.cpp index 8d9f70c24a..2fd1f99764 100644 --- a/app/node/generator/noise/noise.cpp +++ b/app/node/generator/noise/noise.cpp @@ -32,13 +32,13 @@ const QString NoiseGeneratorNode::kStrengthInput = QStringLiteral("strength_in") NoiseGeneratorNode::NoiseGeneratorNode() { - AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kBaseIn, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kStrengthInput, NodeValue::kFloat, 0.2); + AddInput(kStrengthInput, TYPE_DOUBLE, 0.2); SetInputProperty(kStrengthInput, QStringLiteral("view"), FloatSlider::kPercentage); - SetInputProperty(kStrengthInput, QStringLiteral("min"), 0); + SetInputProperty(kStrengthInput, QStringLiteral("min"), 0.0); - AddInput(kColorInput, NodeValue::kBoolean, false); + AddInput(kColorInput, TYPE_BOOL, false); SetEffectInput(kBaseIn); SetFlag(kVideoEffect); @@ -73,20 +73,19 @@ void NoiseGeneratorNode::Retranslate() SetInputName(kColorInput, tr("Color")); } -ShaderCode NoiseGeneratorNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode NoiseGeneratorNode::GetShaderCode(const QString &id) { return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/noise.frag")); } -void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t NoiseGeneratorNode::Value(const ValueParams &p) const { - ShaderJob job(value); + ShaderJob job = CreateShaderJob(p, GetShaderCode); - job.Insert(value); - job.Insert(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this)); + job.Insert(QStringLiteral("time_in"), p.time().in().toDouble()); - TexturePtr base = value[kBaseIn].toTexture(); + TexturePtr base = GetInputValue(p, kBaseIn).toTexture(); - table->Push(NodeValue::kTexture, Texture::Job(base ? base->params() : globals.vparams(), job), this); + return Texture::Job(base ? base->params() : p.vparams(), job); } } diff --git a/app/node/generator/noise/noise.h b/app/node/generator/noise/noise.h index c44526ab77..84f6ad2293 100644 --- a/app/node/generator/noise/noise.h +++ b/app/node/generator/noise/noise.h @@ -39,13 +39,15 @@ class NoiseGeneratorNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kBaseIn; static const QString kColorInput; static const QString kStrengthInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } // namespace olive diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index e05c3da14a..eb7d818f21 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -23,6 +23,8 @@ #include #include +#include "widget/nodeparamview/paramwidget/bezierparamwidget.h" + namespace olive { const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in"); @@ -32,19 +34,19 @@ const QString PolygonGenerator::kColorInput = QStringLiteral("color_in"); PolygonGenerator::PolygonGenerator() { - AddInput(kPointsInput, NodeValue::kBezier, QVector2D(0, 0), InputFlags(kInputFlagArray)); + AddInput(kPointsInput, TYPE_DOUBLE, size_t(6), value_t(), kInputFlagArray); - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 1.0, 1.0))); + AddInput(kColorInput, TYPE_COLOR, Color(1.0, 1.0, 1.0)); - const int kMiddleX = 135; - const int kMiddleY = 45; - const int kBottomX = 90; - const int kBottomY = 120; - const int kTopY = 135; + const double kMiddleX = 135; + const double kMiddleY = 45; + const double kBottomX = 90; + const double kBottomY = 120; + const double kTopY = 135; // The Default Pentagon(tm) InputArrayResize(kPointsInput, 5); - SetSplitStandardValueOnTrack(kPointsInput, 0, 0, 0); + SetSplitStandardValueOnTrack(kPointsInput, 0, 0.0, 0); SetSplitStandardValueOnTrack(kPointsInput, 1, -kTopY, 0); SetSplitStandardValueOnTrack(kPointsInput, 0, kMiddleX, 1); SetSplitStandardValueOnTrack(kPointsInput, 1, -kMiddleY, 1); @@ -87,27 +89,7 @@ void PolygonGenerator::Retranslate() SetInputName(kColorInput, tr("Color")); } -ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value, const VideoParams ¶ms) const -{ - VideoParams p = params; - p.set_format(PixelFormat::U8); - auto job = Texture::Job(p, GenerateJob(value)); - - // Conversion to RGB - ShaderJob rgb; - rgb.SetShaderID(QStringLiteral("rgb")); - rgb.Insert(QStringLiteral("texture_in"), NodeValue(NodeValue::kTexture, job, this)); - rgb.Insert(QStringLiteral("color_in"), value[kColorInput]); - - return rgb; -} - -void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - PushMergableJob(value, Texture::Job(globals.vparams(), GetGenerateJob(value, globals.vparams())), table); -} - -void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) const +void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) { // This could probably be more optimized, but for now we use Qt to draw to a QImage. // QImages only support integer pixels and we use float pixels, so what we do here is draw onto @@ -118,7 +100,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con auto points = job.Get(kPointsInput).toArray(); - QPainterPath path = GeneratePath(points, InputArraySize(kPointsInput)); + QPainterPath path = GeneratePath(points, points.size()); QPainter p(&img); double par = frame->video_params().pixel_aspect_ratio().toDouble(); @@ -130,6 +112,36 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con p.drawPath(path); } +ShaderCode PolygonGenerator::GetShaderCode(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgb.frag")); +} + +Bezier PolygonGenerator::GetBezier(const value_t &v) +{ + return Bezier(v.value(0), v.value(1), v.value(2), v.value(3), v.value(4), v.value(5)); +} + +ShaderJob PolygonGenerator::GetGenerateJob(const ValueParams &p, const VideoParams ¶ms) const +{ + VideoParams vp = params; + vp.set_format(PixelFormat::U8); + auto job = Texture::Job(vp, CreateGenerateJob(p, GenerateFrame)); + + // Conversion to RGB + ShaderJob rgb; + rgb.set_function(GetShaderCode); + rgb.Insert(QStringLiteral("texture_in"), job); + rgb.Insert(QStringLiteral("color_in"), GetInputValue(p, kColorInput)); + + return rgb; +} + +value_t PolygonGenerator::Value(const ValueParams &p) const +{ + return GetMergableJob(p, Texture::Job(p.vparams(), GetGenerateJob(p, p.vparams()))); +} + template NodeGizmo *PolygonGenerator::CreateAppropriateGizmo() { @@ -164,18 +176,18 @@ void PolygonGenerator::ValidateGizmoVectorSize(QVector &vec, int new_sz) } } -void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void PolygonGenerator::UpdateGizmoPositions(const ValueParams &p) { QVector2D res; - if (TexturePtr tex = row[kBaseInput].toTexture()) { + if (TexturePtr tex = GetInputValue(p, kBaseInput).toTexture()) { res = tex->virtual_resolution(); } else { - res = globals.square_resolution(); + res = p.square_resolution(); } Imath::V2d half_res(res.x()/2, res.y()/2); - auto points = row[kPointsInput].toArray(); + auto points = GetInputValue(p, kPointsInput).toArray(); int current_pos_sz = gizmo_position_handles_.size(); @@ -200,10 +212,9 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG bez_gizmo2->SetSmaller(true); } - int pts_sz = InputArraySize(kPointsInput); if (!points.empty()) { - for (int i=0; iSetPath(GeneratePath(points, pts_sz).translated(QPointF(half_res.x, half_res.y))); + poly_gizmo_->SetPath(GeneratePath(points, points.size()).translated(QPointF(half_res.x, half_res.y))); } -ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const +AbstractParamWidget *PolygonGenerator::GetCustomWidget(const QString &input) const { - if (request.id == QStringLiteral("rgb")) { - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgb.frag")); + if (input == kPointsInput) { + return new BezierParamWidget(); } else { - return super::GetShaderCode(request); + return super::GetCustomWidget(input); } } @@ -239,8 +250,8 @@ void PolygonGenerator::GizmoDragMove(double x, double y, const Qt::KeyboardModif } else { NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } } @@ -258,15 +269,15 @@ QPainterPath PolygonGenerator::GeneratePath(const NodeValueArray &points, int si QPainterPath path; if (!points.empty()) { - const Bezier &first_pt = points.at(0).toBezier(); + const Bezier &first_pt = GetBezier(points.at(0)); Imath::V2d v = first_pt.to_vec(); path.moveTo(QPointF(v.x, v.y)); for (int i=1; i void ValidateGizmoVectorSize(QVector &vec, int new_sz); diff --git a/app/node/generator/shape/generatorwithmerge.cpp b/app/node/generator/shape/generatorwithmerge.cpp index 6832e3e6ea..d45ae6b931 100644 --- a/app/node/generator/shape/generatorwithmerge.cpp +++ b/app/node/generator/shape/generatorwithmerge.cpp @@ -30,7 +30,7 @@ const QString GeneratorWithMerge::kBaseInput = QStringLiteral("base_in"); GeneratorWithMerge::GeneratorWithMerge() { - AddInput(kBaseInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kBaseInput, TYPE_TEXTURE, kInputFlagNotKeyframable); SetEffectInput(kBaseInput); SetFlag(kVideoEffect); } @@ -42,29 +42,28 @@ void GeneratorWithMerge::Retranslate() SetInputName(kBaseInput, tr("Base")); } -ShaderCode GeneratorWithMerge::GetShaderCode(const ShaderRequest &request) const +ShaderCode GeneratorWithMerge::GetShaderCode(const QString &id) { - if (request.id == QStringLiteral("mrg")) { - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag")); - } - - return ShaderCode(); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag")); } -void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, TexturePtr job, NodeValueTable *table) const +value_t GeneratorWithMerge::GetMergableJob(const ValueParams &p, TexturePtr job) const { - if (TexturePtr base = value[kBaseInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kBaseInput); + + if (TexturePtr base = tex_meta.toTexture()) { // Push as merge node ShaderJob merge; merge.SetShaderID(QStringLiteral("mrg")); - merge.Insert(MergeNode::kBaseIn, value[kBaseInput]); - merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this)); + merge.set_function(GetShaderCode); + merge.Insert(MergeNode::kBaseIn, tex_meta); + merge.Insert(MergeNode::kBlendIn, job); - table->Push(NodeValue::kTexture, base->toJob(merge), this); + return base->toJob(merge); } else { // Just push generate job - table->Push(NodeValue::kTexture, job, this); + return job; } } diff --git a/app/node/generator/shape/generatorwithmerge.h b/app/node/generator/shape/generatorwithmerge.h index cc462a5597..1ecff49fe4 100644 --- a/app/node/generator/shape/generatorwithmerge.h +++ b/app/node/generator/shape/generatorwithmerge.h @@ -33,12 +33,13 @@ class GeneratorWithMerge : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - static const QString kBaseInput; protected: - void PushMergableJob(const NodeValueRow &value, TexturePtr job, NodeValueTable *table) const; + value_t GetMergableJob(const ValueParams &p, TexturePtr job) const; + +private: + static ShaderCode GetShaderCode(const QString &id); }; diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 4c866e2796..504be7194b 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -29,9 +29,9 @@ QString ShapeNode::kRadiusInput = QStringLiteral("radius_in"); ShapeNode::ShapeNode() { - PrependInput(kTypeInput, NodeValue::kCombo); + PrependInput(kTypeInput, TYPE_COMBO); - AddInput(kRadiusInput, NodeValue::kFloat, 20.0); + AddInput(kRadiusInput, TYPE_DOUBLE, 20.0); SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); } @@ -66,25 +66,20 @@ void ShapeNode::Retranslate() SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse"), tr("Rounded Rectangle")}); } -ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode ShapeNode::GetShaderCode(const QString &id) { - if (request.id == QStringLiteral("shape")) { - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag"))); - } else { - return super::GetShaderCode(request); - } + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag"))); } -void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ShapeNode::Value(const ValueParams &p) const { - TexturePtr base = value[kBaseInput].toTexture(); + TexturePtr base = GetInputValue(p, kBaseInput).toTexture(); - ShaderJob job(value); + ShaderJob job = CreateShaderJob(p, GetShaderCode); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, base ? base->virtual_resolution() : globals.square_resolution(), this)); - job.SetShaderID(QStringLiteral("shape")); + job.Insert(QStringLiteral("resolution_in"), base ? base->virtual_resolution() : p.square_resolution()); - PushMergableJob(value, Texture::Job(base ? base->params() : globals.vparams(), job), table); + return GetMergableJob(p, Texture::Job(base ? base->params() : p.vparams(), job)); } void ShapeNode::InputValueChangedEvent(const QString &input, int element) diff --git a/app/node/generator/shape/shapenode.h b/app/node/generator/shape/shapenode.h index 285fc5bb5a..51516731f6 100644 --- a/app/node/generator/shape/shapenode.h +++ b/app/node/generator/shape/shapenode.h @@ -46,8 +46,7 @@ class ShapeNode : public ShapeNodeBase virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static QString kTypeInput; static QString kRadiusInput; @@ -55,6 +54,9 @@ class ShapeNode : public ShapeNodeBase protected: virtual void InputValueChangedEvent(const QString &input, int element) override; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index f57034ca75..aff091faa2 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -37,12 +37,12 @@ const QString ShapeNodeBase::kColorInput = QStringLiteral("color_in"); ShapeNodeBase::ShapeNodeBase(bool create_color_input) { - AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); - AddInput(kSizeInput, NodeValue::kVec2, QVector2D(100, 100)); + AddInput(kPositionInput, TYPE_VEC2, QVector2D(0, 0)); + AddInput(kSizeInput, TYPE_VEC2, QVector2D(100, 100)); SetInputProperty(kSizeInput, QStringLiteral("min"), QVector2D(0, 0)); if (create_color_input) { - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0))); + AddInput(kColorInput, TYPE_COLOR, Color(1.0, 0.0, 0.0, 1.0)); } // Initiate gizmos @@ -73,15 +73,15 @@ void ShapeNodeBase::Retranslate() } } -void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +void ShapeNodeBase::UpdateGizmoPositions(const ValueParams &p) { // Use offsets to make the appearance of values that start in the top left, even though we // really anchor around the center - QVector2D center_pt = globals.square_resolution() * 0.5; + QVector2D center_pt = p.square_resolution() * 0.5; SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt); - QVector2D pos = row[kPositionInput].toVec2(); - QVector2D sz = row[kSizeInput].toVec2(); + QVector2D pos = GetInputValue(p, kPositionInput).toVec2(); + QVector2D sz = GetInputValue(p, kSizeInput).toVec2(); QVector2D half_sz = sz * 0.5; double left_pt = pos.x() + center_pt.x() - half_sz.x(); @@ -126,8 +126,8 @@ void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifier NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; if (gizmo == poly_gizmo_) { - x_drag.Drag(x_drag.GetStartValue().toDouble() + x); - y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + x_drag.Drag(x_drag.GetStartValue().value() + x); + y_drag.Drag(y_drag.GetStartValue().value() + y); } else { bool from_center = modifiers & Qt::AltModifier; bool keep_ratio = modifiers & Qt::ShiftModifier; @@ -135,8 +135,8 @@ void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifier NodeInputDragger &w_drag = gizmo->GetDraggers()[2]; NodeInputDragger &h_drag = gizmo->GetDraggers()[3]; - QVector2D gizmo_sz_start(w_drag.GetStartValue().toDouble(), h_drag.GetStartValue().toDouble()); - QVector2D gizmo_pos_start(x_drag.GetStartValue().toDouble(), y_drag.GetStartValue().toDouble()); + QVector2D gizmo_sz_start(w_drag.GetStartValue().value(), h_drag.GetStartValue().value()); + QVector2D gizmo_pos_start(x_drag.GetStartValue().value(), y_drag.GetStartValue().value()); QVector2D gizmo_half_res = gizmo->GetGlobals().square_resolution()/2; QVector2D adjusted_pt(x, y); QVector2D new_size; @@ -147,7 +147,7 @@ void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifier double original_ratio; if (keep_ratio) { - original_ratio = w_drag.GetStartValue().toDouble() / h_drag.GetStartValue().toDouble(); + original_ratio = w_drag.GetStartValue().value() / h_drag.GetStartValue().value(); } // Calculate new size @@ -241,10 +241,10 @@ void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifier new_pos = GenerateGizmoAnchor(gizmo_pos_start, gizmo_sz_start, gizmo) + using_size / 2; } - x_drag.Drag(new_pos.x()); - y_drag.Drag(new_pos.y()); - w_drag.Drag(new_size.x()); - h_drag.Drag(new_size.y()); + x_drag.Drag(double(new_pos.x())); + y_drag.Drag(double(new_pos.y())); + w_drag.Drag(double(new_size.x())); + h_drag.Drag(double(new_size.y())); } } diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index fd0f6658a9..a5c2cd9a17 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -37,7 +37,7 @@ class ShapeNodeBase : public GeneratorWithMerge virtual void Retranslate() override; - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; void SetRect(QRectF rect, const VideoParams &sequence_res, MultiUndoCommand *command); diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 4674ab8932..c6ad8da3ae 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -29,7 +29,7 @@ const QString SolidGenerator::kColorInput = QStringLiteral("color_in"); SolidGenerator::SolidGenerator() { // Default to a color that isn't black - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 0.0f, 0.0f, 1.0f))); + AddInput(kColorInput, TYPE_COLOR, Color(1.0f, 0.0f, 0.0f, 1.0f)); } QString SolidGenerator::Name() const @@ -59,16 +59,14 @@ void SolidGenerator::Retranslate() SetInputName(kColorInput, tr("Color")); } -void SolidGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +ShaderCode SolidGenerator::GetShaderCode(const QString &id) { - table->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), ShaderJob(value)), this); + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/solid.frag")); } -ShaderCode SolidGenerator::GetShaderCode(const ShaderRequest &request) const +value_t SolidGenerator::Value(const ValueParams &p) const { - Q_UNUSED(request) - - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/solid.frag")); + return Texture::Job(p.vparams(), CreateShaderJob(p, GetShaderCode)); } } diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 97013a13ab..714dfd3c4b 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -40,11 +40,13 @@ class SolidGenerator : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kColorInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index ae08389c60..f7f74d461a 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -42,17 +42,17 @@ const QString TextGeneratorV1::kFontSizeInput = QStringLiteral("font_size_in"); TextGeneratorV1::TextGeneratorV1() { - AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); + AddInput(kTextInput, TYPE_STRING, tr("Sample Text")); - AddInput(kHtmlInput, NodeValue::kBoolean, false); + AddInput(kHtmlInput, TYPE_BOOL, false); - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 1.0f, 1.0))); + AddInput(kColorInput, TYPE_COLOR, Color(1.0f, 1.0f, 1.0)); - AddInput(kVAlignInput, NodeValue::kCombo, 1); + AddInput(kVAlignInput, TYPE_COMBO, 1); - AddInput(kFontInput, NodeValue::kFont); + AddInput(kFontInput, TYPE_FONT); - AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f); + AddInput(kFontSizeInput, TYPE_DOUBLE, 72.0f); SetFlag(kDontShowInCreateMenu); } @@ -90,14 +90,7 @@ void TextGeneratorV1::Retranslate() SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")}); } -void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - if (!value[kTextInput].toString().isEmpty()) { - table->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), GenerateJob(value)), this); - } -} - -void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) const +void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) { // This could probably be more optimized, but for now we use Qt to draw to a QImage. // QImages only support integer pixels and we use float pixels, so what we do here is draw onto @@ -170,4 +163,13 @@ void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) cons } } +value_t TextGeneratorV1::Value(const ValueParams &p) const +{ + if (!GetInputValue(p, kTextInput).toString().isEmpty()) { + return Texture::Job(p.vparams(), CreateGenerateJob(p, GenerateFrame)); + } + + return value_t(); +} + } diff --git a/app/node/generator/text/textv1.h b/app/node/generator/text/textv1.h index e45b9bc07f..8936408349 100644 --- a/app/node/generator/text/textv1.h +++ b/app/node/generator/text/textv1.h @@ -40,9 +40,7 @@ class TextGeneratorV1 : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextInput; static const QString kHtmlInput; @@ -51,6 +49,9 @@ class TextGeneratorV1 : public Node static const QString kFontInput; static const QString kFontSizeInput; +private: + static void GenerateFrame(FramePtr frame, const GenerateJob& job); + }; } diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index 3472bb6015..54aa375170 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -20,11 +20,12 @@ #include "textv2.h" -#include #include #include #include +#include "util/cpuoptimize.h" + namespace olive { #define super ShapeNodeBase @@ -43,17 +44,17 @@ const QString TextGeneratorV2::kFontSizeInput = QStringLiteral("font_size_in"); TextGeneratorV2::TextGeneratorV2() { - AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); + AddInput(kTextInput, TYPE_STRING, tr("Sample Text")); - AddInput(kHtmlInput, NodeValue::kBoolean, false); + AddInput(kHtmlInput, TYPE_BOOL, false); - AddInput(kVAlignInput, NodeValue::kCombo, kVerticalAlignTop); + AddInput(kVAlignInput, TYPE_COMBO, kVerticalAlignTop); - AddInput(kFontInput, NodeValue::kFont); + AddInput(kFontInput, TYPE_FONT); - AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f); + AddInput(kFontSizeInput, TYPE_DOUBLE, 72.0f); - SetStandardValue(kColorInput, QVariant::fromValue(Color(1.0f, 1.0f, 1.0))); + SetStandardValue(kColorInput, Color(1.0f, 1.0f, 1.0)); SetStandardValue(kSizeInput, QVector2D(400, 300)); SetFlag(kDontShowInCreateMenu); @@ -91,17 +92,7 @@ void TextGeneratorV2::Retranslate() SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")}); } -void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - if (!value[kTextInput].toString().isEmpty()) { - GenerateJob job(value); - auto text_params = globals.vparams(); - text_params.set_format(PixelFormat::F32); - table->Push(NodeValue::kTexture, Texture::Job(text_params, job), this); - } -} - -void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) const +void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) { // This could probably be more optimized, but for now we use Qt to draw to a QImage. // QImages only support integer pixels and we use float pixels, so what we do here is draw onto @@ -195,4 +186,16 @@ void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) cons } } +value_t TextGeneratorV2::Value(const ValueParams &p) const +{ + if (!GetInputValue(p, kTextInput).toString().isEmpty()) { + GenerateJob job = CreateGenerateJob(p, GenerateFrame); + auto text_params = p.vparams(); + text_params.set_format(PixelFormat::F32); + return Texture::Job(text_params, job); + } + + return value_t(); +} + } diff --git a/app/node/generator/text/textv2.h b/app/node/generator/text/textv2.h index c3879692e0..3358d1278a 100644 --- a/app/node/generator/text/textv2.h +++ b/app/node/generator/text/textv2.h @@ -40,9 +40,7 @@ class TextGeneratorV2 : public ShapeNodeBase virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextInput; static const QString kHtmlInput; @@ -50,6 +48,9 @@ class TextGeneratorV2 : public ShapeNodeBase static const QString kFontInput; static const QString kFontSizeInput; +private: + static void GenerateFrame(FramePtr frame, const GenerateJob& job); + }; } diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index 986e2f7544..0cfeb27e21 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -48,16 +48,16 @@ TextGeneratorV3::TextGeneratorV3() : ShapeNodeBase(false), dont_emit_valign_(false) { - AddInput(kTextInput, NodeValue::kText, QStringLiteral("

%1

").arg(tr("Sample Text"))); + AddInput(kTextInput, TYPE_STRING, QStringLiteral("

%1

").arg(tr("Sample Text"))); SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true); SetStandardValue(kSizeInput, QVector2D(400, 300)); - AddInput(kVerticalAlignmentInput, NodeValue::kCombo, InputFlags(kInputFlagHidden | kInputFlagStatic)); + AddInput(kVerticalAlignmentInput, TYPE_COMBO, kInputFlagHidden | kInputFlagStatic); - AddInput(kUseArgsInput, NodeValue::kBoolean, true, InputFlags(kInputFlagHidden | kInputFlagStatic)); + AddInput(kUseArgsInput, TYPE_BOOL, true, kInputFlagHidden | kInputFlagStatic); - AddInput(kArgsInput, NodeValue::kText, InputFlags(kInputFlagArray)); + AddInput(kArgsInput, TYPE_STRING, kInputFlagArray); SetInputProperty(kArgsInput, QStringLiteral("arraystart"), 1); text_gizmo_ = new TextGizmo(this); @@ -96,40 +96,7 @@ void TextGeneratorV3::Retranslate() SetInputName(kArgsInput, tr("Arguments")); } -void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - QString text = value[kTextInput].toString(); - - if (value[kUseArgsInput].toBool()) { - auto args = value[kArgsInput].toArray(); - if (!args.empty()) { - QStringList list; - list.reserve(args.size()); - for (size_t i=0; iparams() : globals.vparams(); - text_params.set_format(PixelFormat::U8); - text_params.set_colorspace(project()->color_manager()->GetDefaultInputColorSpace()); - - GenerateJob job(value); - job.Insert(kTextInput, NodeValue(NodeValue::kText, text)); - - PushMergableJob(value, Texture::Job(text_params, job), table); - } else if (value[kBaseInput].toTexture()) { - table->Push(value[kBaseInput]); - } -} - -void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) const +void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) { QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied); img.fill(Qt::transparent); @@ -157,14 +124,14 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons p.translate(frame->video_params().width()/2, frame->video_params().height()/2); p.setClipRect(0, 0, size.x(), size.y()); - switch (static_cast(job.Get(kVerticalAlignmentInput).toInt())) { - case kVAlignTop: + switch (static_cast(job.Get(kVerticalAlignmentInput).toInt())) { + case TextGeneratorV3::kVAlignTop: // Do nothing break; - case kVAlignMiddle: + case TextGeneratorV3::kVAlignMiddle: p.translate(0, size.y()/2-text_doc.size().height()/2); break; - case kVAlignBottom: + case TextGeneratorV3::kVAlignBottom: p.translate(0, size.y()-text_doc.size().height()); break; } @@ -176,13 +143,48 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons text_doc.documentLayout()->draw(&p, ctx); } -void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +value_t TextGeneratorV3::Value(const ValueParams &p) const +{ + QString text = GetInputValue(p, kTextInput).toString(); + + if (GetInputValue(p, kUseArgsInput).toBool()) { + auto args = GetInputValue(p, kArgsInput).toArray(); + if (!args.empty()) { + QStringList list; + list.reserve(args.size()); + for (size_t i=0; iparams() : p.vparams(); + text_params.set_format(PixelFormat::U8); + text_params.set_colorspace(project()->color_manager()->GetDefaultInputColorSpace()); + + GenerateJob job = CreateGenerateJob(p, GenerateFrame); + job.Insert(kTextInput, text); + + return GetMergableJob(p, Texture::Job(text_params, job)); + } + + return base_val; +} + +void TextGeneratorV3::UpdateGizmoPositions(const ValueParams &p) { - super::UpdateGizmoPositions(row, globals); + super::UpdateGizmoPositions(p); QRectF rect = poly_gizmo()->GetPolygon().boundingRect(); text_gizmo_->SetRect(rect); - text_gizmo_->SetHtml(row[kTextInput].toString()); + text_gizmo_->SetHtml(GetInputValue(p, kTextInput).toString()); } Qt::Alignment TextGeneratorV3::GetQtAlignmentFromOurs(VerticalAlignment v) diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h index b7d6a5674f..d74d08d5eb 100644 --- a/app/node/generator/text/textv3.h +++ b/app/node/generator/text/textv3.h @@ -41,11 +41,9 @@ class TextGeneratorV3 : public ShapeNodeBase virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; - virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; - - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual void UpdateGizmoPositions(const ValueParams &p) override; enum VerticalAlignment { @@ -73,6 +71,8 @@ class TextGeneratorV3 : public ShapeNodeBase virtual void InputValueChangedEvent(const QString &input, int element) override; private: + static void GenerateFrame(FramePtr frame, const GenerateJob& job); + TextGizmo *text_gizmo_; bool dont_emit_valign_; diff --git a/app/panel/table/CMakeLists.txt b/app/node/generator/tone/CMakeLists.txt similarity index 90% rename from app/panel/table/CMakeLists.txt rename to app/node/generator/tone/CMakeLists.txt index 9afcbf2faf..d20fe5032b 100644 --- a/app/panel/table/CMakeLists.txt +++ b/app/node/generator/tone/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - panel/table/table.h - panel/table/table.cpp + node/generator/tone/tonegenerator.cpp + node/generator/tone/tonegenerator.h PARENT_SCOPE ) diff --git a/app/node/generator/tone/tonegenerator.cpp b/app/node/generator/tone/tonegenerator.cpp new file mode 100644 index 0000000000..bfd242f2a3 --- /dev/null +++ b/app/node/generator/tone/tonegenerator.cpp @@ -0,0 +1,93 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "tonegenerator.h" + +#include + +#include "widget/slider/floatslider.h" + +namespace olive { + +const QString ToneGenerator::kFrequencyInput = QStringLiteral("freq_in"); + +#define super Node + +ToneGenerator::ToneGenerator() +{ + AddInput(kFrequencyInput, TYPE_DOUBLE, 1000.0, kInputFlagStatic); +} + +QString ToneGenerator::Name() const +{ + return tr("Tone"); +} + +QString ToneGenerator::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.tonegenerator"); +} + +QVector ToneGenerator::Category() const +{ + return {kCategoryGenerator}; +} + +QString ToneGenerator::Description() const +{ + return tr("Generates an audio tone."); +} + +void ToneGenerator::Retranslate() +{ + super::Retranslate(); + + SetInputName(kFrequencyInput, tr("Frequency")); +} + +void ProcessSamples(const void *context, const SampleJob &job, SampleBuffer &output) +{ + const double freq = job.Get(ToneGenerator::kFrequencyInput).toDouble(); + + const ValueParams &vp = job.value_params(); + const double sample_interval = vp.aparams().sample_rate_as_time_base().toDouble(); + double time = vp.time().in().toDouble(); + + for (size_t i = 0; i < output.sample_count(); i++) { + static const double PI = 3.14159265358979323846264; + double val = std::sin(time * 2 * PI * freq); + + for (int j = 0; j < output.channel_count(); j++) { + output.data(j)[i] = val; + } + + time += sample_interval; + } +} + +value_t ToneGenerator::Value(const ValueParams &p) const +{ + SampleJob job(p); + job.Insert(kFrequencyInput, GetStandardValue(kFrequencyInput)); + job.set_function(ProcessSamples, this); + return job; +} + +} diff --git a/app/panel/table/table.h b/app/node/generator/tone/tonegenerator.h similarity index 57% rename from app/panel/table/table.h rename to app/node/generator/tone/tonegenerator.h index cbdc2da737..36f32b6e12 100644 --- a/app/panel/table/table.h +++ b/app/node/generator/tone/tonegenerator.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,36 +18,33 @@ ***/ -#ifndef NODETABLEPANEL_H -#define NODETABLEPANEL_H +#ifndef TONEGENERATOR_H +#define TONEGENERATOR_H -#include "panel/timebased/timebased.h" -#include "widget/nodetableview/nodetablewidget.h" +#include "node/node.h" namespace olive { -class NodeTablePanel : public TimeBasedPanel +class ToneGenerator : public Node { - Q_OBJECT public: - NodeTablePanel(); + ToneGenerator(); -public slots: - void SelectNodes(const QVector& nodes) - { - static_cast(GetTimeBasedWidget())->SelectNodes(nodes); - } + NODE_DEFAULT_FUNCTIONS(ToneGenerator) - void DeselectNodes(const QVector& nodes) - { - static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); - } + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; -private: virtual void Retranslate() override; + virtual value_t Value(const ValueParams &p) const override; + + static const QString kFrequencyInput; + }; } -#endif // NODETABLEPANEL_H +#endif // TONEGENERATOR_H diff --git a/app/node/gizmo/draggable.cpp b/app/node/gizmo/draggable.cpp index 1c988d4d59..1a0a541127 100644 --- a/app/node/gizmo/draggable.cpp +++ b/app/node/gizmo/draggable.cpp @@ -28,13 +28,13 @@ DraggableGizmo::DraggableGizmo(QObject *parent) { } -void DraggableGizmo::DragStart(const NodeValueRow &row, double abs_x, double abs_y, const rational &time) +void DraggableGizmo::DragStart(const ValueParams &p, double abs_x, double abs_y, const rational &time) { for (int i=0; icontains(node)) { + const QHash &map = cache_->value(node); + if (map.contains(p)) { + out = map.value(p); + return true; + } + } + } + + return false; +} + +void ValueParams::insert_cached_value(const Node *node, const ValueParams &p, const value_t &in) const +{ + if (cache_) { + (*cache_)[node][p] = in; + } +} + +bool ValueParams::operator==(const ValueParams &p) const +{ + return this->video_params_ == p.video_params_ + && this->audio_params_ == p.audio_params_ + && this->time_ == p.time_ + && this->loop_mode_ == p.loop_mode_ + && this->output_ == p.output_; +} + +uint qHash(const ValueParams &p, uint seed) +{ + return qHash(p.vparams(), seed) ^ qHash(p.aparams(), seed) ^ qHash(p.time(), seed) ^ qHash(int(p.loop_mode()), seed) ^ qHash(p.output(), seed); +} + } diff --git a/app/node/globals.h b/app/node/globals.h index 85540e5043..b1c9302e19 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -23,26 +23,39 @@ #include +#include "node/value.h" +#include "render/cancelatom.h" #include "render/loopmode.h" +#include "render/audioparams.h" #include "render/videoparams.h" +#include "util/timerange.h" namespace olive { -class NodeGlobals +class ValueParams { public: - NodeGlobals(){} + using Cache = QHash>; - NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time, LoopMode loop_mode) : + ValueParams() + { + cache_ = nullptr; + cancel_atom_ = nullptr; + } + + ValueParams(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time, const QString &output, LoopMode loop_mode, CancelAtom *cancel, Cache *cache) : video_params_(vparam), audio_params_(aparam), time_(time), - loop_mode_(loop_mode) + loop_mode_(loop_mode), + output_(output), + cancel_atom_(cancel), + cache_(cache) { } - NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const rational &time, LoopMode loop_mode) : - NodeGlobals(vparam, aparam, TimeRange(time, time + vparam.frame_rate_as_time_base()), loop_mode) + ValueParams(const VideoParams &vparam, const AudioParams &aparam, const rational &time, const QString &output, LoopMode loop_mode, CancelAtom *cancel, Cache *cache) : + ValueParams(vparam, aparam, TimeRange(time, time + vparam.frame_rate_as_time_base()), output, loop_mode, cancel, cache) { } @@ -52,15 +65,35 @@ class NodeGlobals const VideoParams &vparams() const { return video_params_; } const TimeRange &time() const { return time_; } LoopMode loop_mode() const { return loop_mode_; } + const QString &output() const { return output_; } + + CancelAtom *cancel_atom() const { return cancel_atom_; } + bool is_cancelled() const { return cancel_atom_ && cancel_atom_->IsCancelled(); } + + ValueParams time_transformed(const TimeRange &time) const; + ValueParams output_edited(const QString &output) const; + ValueParams loop_mode_edited(const LoopMode &lm) const; + ValueParams with_cache(Cache *cache) const; + + bool get_cached_value(const Node *node, const ValueParams &p, value_t &out) const; + void insert_cached_value(const Node *node, const ValueParams &p, const value_t &in) const; + + bool operator==(const ValueParams &p) const; private: VideoParams video_params_; AudioParams audio_params_; TimeRange time_; LoopMode loop_mode_; + QString output_; + + CancelAtom *cancel_atom_; + Cache *cache_; }; +uint qHash(const ValueParams &p, uint seed = 0); + } #endif // NODEGLOBALS_H diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index ac8de38a92..7e4fba9593 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -83,11 +83,11 @@ bool NodeGroup::LoadCustom(QXmlStreamReader *reader, SerializedData *data) } else if (reader->name() == QStringLiteral("name")) { link.custom_name = reader->readElementText(); } else if (reader->name() == QStringLiteral("flags")) { - link.custom_flags = InputFlags(reader->readElementText().toULongLong()); + link.custom_flags = InputFlag(reader->readElementText().toULongLong()); } else if (reader->name() == QStringLiteral("type")) { - link.data_type = NodeValue::GetDataTypeFromName(reader->readElementText()); + link.data_type = type_t::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("default")) { - link.default_val = NodeValue::StringToValue(link.data_type, reader->readElementText(), false); + link.default_val = value_t(reader->readElementText()).converted(link.data_type); } else if (reader->name() == QStringLiteral("properties")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("property")) { @@ -152,10 +152,10 @@ void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("flags"), QString::number((GetInputFlags(input) & ~ip.second.GetFlags()).value())); - NodeValue::Type data_type = GetInputDataType(input); - writer->writeTextElement(QStringLiteral("type"), NodeValue::GetDataTypeName(data_type)); + type_t data_type = GetInputDataType(input); + writer->writeTextElement(QStringLiteral("type"), data_type.toString()); - writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, GetDefaultValue(input), false)); + writer->writeTextElement(QStringLiteral("default"), GetDefaultValue(input).toSerializedString()); writer->writeStartElement(QStringLiteral("properties")); auto p = GetInputProperties(input); @@ -185,7 +185,7 @@ void NodeGroup::PostLoadEvent(SerializedData *data) l.group->AddInputPassthrough(resolved, l.passthrough_id); - l.group->SetInputFlag(l.passthrough_id, InputFlag(l.custom_flags.value())); + l.group->SetInputFlag(l.passthrough_id, l.custom_flags); if (!l.custom_name.isEmpty()) { l.group->SetInputName(l.passthrough_id, l.custom_name); diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 1ea80d5ae5..3d911ada3a 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -13,13 +13,13 @@ const QString MultiCamNode::kSequenceTypeInput = QStringLiteral("sequence_type_i MultiCamNode::MultiCamNode() { - AddInput(kCurrentInput, NodeValue::kCombo, InputFlags(kInputFlagStatic)); + AddInput(kCurrentInput, TYPE_COMBO, kInputFlagStatic); - AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(kSourcesInput, kInputFlagNotKeyframable | kInputFlagArray); SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); - AddInput(kSequenceInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); - AddInput(kSequenceTypeInput, NodeValue::kCombo, InputFlags(kInputFlagStatic | kInputFlagHidden)); + AddInput(kSequenceInput, kInputFlagNotKeyframable); + AddInput(kSequenceTypeInput, TYPE_COMBO, kInputFlagStatic | kInputFlagHidden); sequence_ = nullptr; } @@ -44,72 +44,136 @@ QString MultiCamNode::Description() const return tr("Allows easy switching between multiple sources."); } -Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const +QString dblToGlsl(double d) { - if (input == kSourcesInput) { - int src = GetCurrentSource(); - if (src >= 0 && src < GetSourceCount()) { - Node::ActiveElements a; - a.add(src); - return a; - } else { - return ActiveElements::kNoElements; - } - } else { - return super::GetActiveElementsAtTime(input, r); - } + return QString::number(d, 'f'); } -void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +QString GenerateShaderCode(int rows, int cols) { - NodeValueArray arr = value[kSourcesInput].toArray(); - if (!arr.empty()) { - table->Push(arr.begin()->second); + int multiplier = std::max(cols, rows); + + QStringList shader; + + shader.append(QStringLiteral("in vec2 ove_texcoord;")); + shader.append(QStringLiteral("out vec4 frag_color;")); + + for (int x=0;x 0) { + shader.append(QStringLiteral(" else")); + } + if (x == cols-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier)))); + } + + for (int y=0;y 0) { + shader.append(QStringLiteral(" else")); + } + if (y == rows-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier)))); + } + QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x)); + shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg( + dblToGlsl( - double(x)/double(multiplier)), + dblToGlsl(multiplier), + dblToGlsl( - double(y)/double(multiplier)), + dblToGlsl(multiplier) + )); + shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input)); + shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input)); + shader.append(QStringLiteral(" } else {")); + shader.append(QStringLiteral(" discard;")); + shader.append(QStringLiteral(" }")); + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral("}")); + + return shader.join('\n'); } -Node *MultiCamNode::GetConnectedRenderOutput(const QString &input, int element) const +ShaderCode MultiCamNode::GetShaderCode(const QString &id) { - if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) { - return GetTrackList()->GetTrackAt(element); - } else { - return Node::GetConnectedRenderOutput(input, element); - } + auto l = id.split(':'); + int rows = l.at(0).toInt(); + int cols = l.at(1).toInt(); + + return ShaderCode(GenerateShaderCode(rows, cols)); } -bool MultiCamNode::IsInputConnectedForRender(const QString &input, int element) const +value_t MultiCamNode::Value(const ValueParams &p) const { - if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) { - return true; + if (p.output() == QStringLiteral("all")) { + // Switcher mode: output a collage of all sources + int sources = GetSourceCount(); + int rows, cols; + MultiCamNode::GetRowsAndColumns(sources, &rows, &cols); + + ShaderJob job; + job.SetShaderID(QStringLiteral("%1:%2").arg(QString::number(rows), QString::number(cols))); + job.set_function(GetShaderCode); + + for (int i=0; i MultiCamNode::IgnoreInputsForRendering() const +void MultiCamNode::IndexToRowCols(int index, int total_rows, int total_cols, int *row, int *col) { - return {kSequenceInput}; + Q_UNUSED(total_rows) + + *col = index%total_cols; + *row = index/total_cols; } -void MultiCamNode::InputConnectedEvent(const QString &input, int element, Node *output) +void MultiCamNode::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kSequenceInput) { - if (Sequence *s = dynamic_cast(output)) { + if (Sequence *s = dynamic_cast(output.node())) { SetInputFlag(kSequenceTypeInput, kInputFlagHidden, false); sequence_ = s; } } } -void MultiCamNode::InputDisconnectedEvent(const QString &input, int element, Node *output) +void MultiCamNode::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kSequenceInput) { SetInputFlag(kSequenceTypeInput, kInputFlagHidden, true); @@ -117,6 +181,15 @@ void MultiCamNode::InputDisconnectedEvent(const QString &input, int element, Nod } } +NodeOutput MultiCamNode::GetSourceNode(int source) const +{ + if (sequence_) { + return NodeOutput(GetTrackList()->GetTrackAt(source)); + } else { + return GetConnectedOutput2(kSourcesInput, source); + } +} + TrackList *MultiCamNode::GetTrackList() const { return sequence_->track_list(static_cast(GetStandardValue(kSequenceTypeInput).toInt())); @@ -137,8 +210,9 @@ void MultiCamNode::Retranslate() names.reserve(name_count); for (int i=0; iName(); + NodeOutput o = GetSourceNode(i); + if (o.IsValid()) { + src_name = o.node()->Name(); } names.append(tr("%1: %2").arg(QString::number(i+1), src_name)); } diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index ead53c63c2..bc67558626 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -21,9 +21,7 @@ class MultiCamNode : public Node virtual QVector Category() const override; virtual QString Description() const override; - virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; - - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; virtual void Retranslate() override; @@ -57,16 +55,15 @@ class MultiCamNode : public Node return col + row * total_cols; } - virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const override; - virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const override; - - virtual QVector IgnoreInputsForRendering() const override; - protected: - virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; - virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override; + virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; private: + static ShaderCode GetShaderCode(const QString &id); + + NodeOutput GetSourceNode(int source) const; + TrackList *GetTrackList() const; Sequence *sequence_; diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp index 80624ad1d3..059d3f7084 100644 --- a/app/node/input/time/timeinput.cpp +++ b/app/node/input/time/timeinput.cpp @@ -48,13 +48,9 @@ QString TimeInput::Description() const return tr("Generates the time (in seconds) at this frame."); } -void TimeInput::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TimeInput::Value(const ValueParams &p) const { - table->Push(NodeValue::kFloat, - globals.time().in().toDouble(), - this, - false, - QStringLiteral("time")); + return p.time().in().toDouble(); } } diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h index a00d0d2c66..7a1e6de3c9 100644 --- a/app/node/input/time/timeinput.h +++ b/app/node/input/time/timeinput.h @@ -38,7 +38,7 @@ class TimeInput : public Node virtual QVector Category() const override; virtual QString Description() const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; }; diff --git a/app/node/input/value/valuenode.cpp b/app/node/input/value/valuenode.cpp index 5520d2cf9f..b12a461b59 100644 --- a/app/node/input/value/valuenode.cpp +++ b/app/node/input/value/valuenode.cpp @@ -24,27 +24,27 @@ namespace olive { const QString ValueNode::kTypeInput = QStringLiteral("type_in"); const QString ValueNode::kValueInput = QStringLiteral("value_in"); -const QVector ValueNode::kSupportedTypes = { - NodeValue::kFloat, - NodeValue::kInt, - NodeValue::kRational, - NodeValue::kVec2, - NodeValue::kVec3, - NodeValue::kVec4, - NodeValue::kColor, - NodeValue::kText, - NodeValue::kMatrix, - NodeValue::kFont, - NodeValue::kBoolean, -}; #define super Node +const QVector ValueNode::kSupportedTypes = { + TYPE_DOUBLE, + TYPE_RATIONAL, + TYPE_VEC2, + TYPE_VEC3, + TYPE_VEC4, + TYPE_COLOR, + TYPE_STRING, + TYPE_MATRIX, + TYPE_FONT, + TYPE_BOOL +}; + ValueNode::ValueNode() { - AddInput(kTypeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kTypeInput, TYPE_COMBO, 0, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kValueInput, kSupportedTypes.first(), QVariant(), InputFlags(kInputFlagNotConnectable)); + AddInput(kValueInput, TYPE_DOUBLE, kInputFlagNotConnectable); } void ValueNode::Retranslate() @@ -56,27 +56,61 @@ void ValueNode::Retranslate() QStringList type_names; type_names.reserve(kSupportedTypes.size()); - foreach (NodeValue::Type type, kSupportedTypes) { - type_names.append(NodeValue::GetPrettyDataTypeName(type)); + for (const type_t &type : kSupportedTypes) { + type_names.append(GetPrettyTypeName(type)); } + SetComboBoxStrings(kTypeInput, type_names); } -void ValueNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ValueNode::Value(const ValueParams &p) const { - Q_UNUSED(globals) + Q_UNUSED(p) // Ensure value is pushed onto the table - table->Push(value[kValueInput]); + return GetInputValue(p, kValueInput); } void ValueNode::InputValueChangedEvent(const QString &input, int element) { if (input == kTypeInput) { - SetInputDataType(kValueInput, kSupportedTypes.at(GetStandardValue(kTypeInput).toInt())); + int64_t k = GetStandardValue(kTypeInput).toInt(); + + const type_t &t = kSupportedTypes.at(k); + + SetInputDataType(kValueInput, t); } super::InputValueChangedEvent(input, element); } +QString ValueNode::GetPrettyTypeName(const type_t &id) +{ + if (id == TYPE_DOUBLE) { + return tr("Double"); + } else if (id == TYPE_INTEGER) { + return tr("Integer"); + } else if (id == TYPE_RATIONAL) { + return tr("Rational"); + } else if (id == TYPE_VEC2) { + return tr("Vector 2D"); + } else if (id == TYPE_VEC3) { + return tr("Vector 3D"); + } else if (id == TYPE_VEC4) { + return tr("Vector 4D"); + } else if (id == TYPE_COLOR) { + return tr("Color"); + } else if (id == TYPE_STRING) { + return tr("Text"); + } else if (id == TYPE_MATRIX) { + return tr("Matrix"); + } else if (id == TYPE_FONT) { + return tr("Font"); + } else if (id == TYPE_BOOL) { + return tr("Boolean"); + } + + return QString(); +} + } diff --git a/app/node/input/value/valuenode.h b/app/node/input/value/valuenode.h index f45bf8914f..1c00b6234c 100644 --- a/app/node/input/value/valuenode.h +++ b/app/node/input/value/valuenode.h @@ -58,13 +58,15 @@ class ValueNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; protected: virtual void InputValueChangedEvent(const QString &input, int element) override; private: - static const QVector kSupportedTypes; + static QString GetPrettyTypeName(const type_t &id); + + static const QVector kSupportedTypes; }; diff --git a/app/node/inputdragger.cpp b/app/node/inputdragger.cpp index 5832d0097b..c9cb7c36aa 100644 --- a/app/node/inputdragger.cpp +++ b/app/node/inputdragger.cpp @@ -67,7 +67,7 @@ void NodeInputDragger::Start(const NodeKeyframeTrackReference &input, const rati created_keys_.append(dragging_key_); if (create_key_on_all_tracks) { - int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input.input().node()->GetInputDataType(input.input().input())); + int nb_tracks = input.input().node()->GetNumberOfKeyframeTracks(input.input().input()); for (int i=0; iHasInputProperty(input, QStringLiteral("min"))) { // Assumes the value is a double of some kind - double min = node->GetInputProperty(input, QStringLiteral("min")).toDouble(); - double v = value.toDouble(); + double min = node->GetInputProperty(input, QStringLiteral("min")).converted(TYPE_DOUBLE).toDouble(); + double v = value.converted(type, TYPE_DOUBLE).value(); if (v < min) { - value = min; + value = value_t::component_t(min).converted(TYPE_DOUBLE, type); } } if (node->HasInputProperty(input, QStringLiteral("max"))) { - double max = node->GetInputProperty(input, QStringLiteral("max")).toDouble(); - double v = value.toDouble(); + double max = node->GetInputProperty(input, QStringLiteral("max")).converted(TYPE_DOUBLE).toDouble(); + double v = value.converted(type, TYPE_DOUBLE).value(); if (v > max) { - value = max; + value = value_t::component_t(max).converted(TYPE_DOUBLE, type); } } diff --git a/app/node/inputdragger.h b/app/node/inputdragger.h index 7bdab14720..7e1ba54c6d 100644 --- a/app/node/inputdragger.h +++ b/app/node/inputdragger.h @@ -36,7 +36,7 @@ class NodeInputDragger void Start(const NodeKeyframeTrackReference& input, const rational& time, bool create_key_on_all_tracks = true); - void Drag(QVariant value); + void Drag(value_t::component_t value); void End(MultiUndoCommand *command); @@ -45,7 +45,7 @@ class NodeInputDragger return input_being_dragged; } - const QVariant &GetStartValue() const + const value_t::component_t &GetStartValue() const { return start_value_; } @@ -65,9 +65,9 @@ class NodeInputDragger rational time_; - QVariant start_value_; + value_t::component_t start_value_; - QVariant end_value_; + value_t::component_t end_value_; NodeKeyframe* dragging_key_; QVector created_keys_; diff --git a/app/node/inputimmediate.cpp b/app/node/inputimmediate.cpp index 7821b7bc80..f5c2ef2e94 100644 --- a/app/node/inputimmediate.cpp +++ b/app/node/inputimmediate.cpp @@ -25,21 +25,26 @@ namespace olive { -NodeInputImmediate::NodeInputImmediate(NodeValue::Type type, const SplitValue &default_val) : - default_value_(default_val), +NodeInputImmediate::NodeInputImmediate() : keyframing_(false) { - set_data_type(type); } -void NodeInputImmediate::set_standard_value_on_track(const QVariant &value, int track) +void NodeInputImmediate::set_standard_value_on_track(const value_t::component_t &value, size_t track) { - standard_value_.replace(track, value); + if (track >= standard_value_.size()) { + standard_value_.resize(track + 1); + } + standard_value_[track] = value; } -void NodeInputImmediate::set_split_standard_value(const SplitValue &value) +void NodeInputImmediate::set_split_standard_value(const value_t &value) { - for (int i=0; i NodeInputImmediate::get_keyframe_at_time(const rational & return keys; } -NodeKeyframe* NodeInputImmediate::get_keyframe_at_time_on_track(const rational &time, int track) const +NodeKeyframe* NodeInputImmediate::get_keyframe_at_time_on_track(const rational &time, size_t track) const { if (!is_using_standard_value(track)) { foreach (NodeKeyframe* key, keyframe_tracks_.at(track)) { @@ -174,16 +179,6 @@ bool NodeInputImmediate::has_keyframe_at_time(const rational &time) const return false; } -void NodeInputImmediate::set_data_type(NodeValue::Type type) -{ - int track_size = NodeValue::get_number_of_keyframe_tracks(type); - - keyframe_tracks_.resize(track_size); - standard_value_.resize(track_size); - - set_split_standard_value(default_value_); -} - NodeKeyframe *NodeInputImmediate::get_earliest_keyframe() const { NodeKeyframe* earliest = nullptr; @@ -222,6 +217,10 @@ NodeKeyframe *NodeInputImmediate::get_latest_keyframe() const void NodeInputImmediate::insert_keyframe(NodeKeyframe* key) { + if (key->track() >= keyframe_tracks_.size()) { + keyframe_tracks_.resize(key->track() + 1); + } + NodeKeyframeTrack& key_track = keyframe_tracks_[key->track()]; int insert_index = key_track.size(); diff --git a/app/node/inputimmediate.h b/app/node/inputimmediate.h index 06c3fad068..4c2bf3abb3 100644 --- a/app/node/inputimmediate.h +++ b/app/node/inputimmediate.h @@ -24,7 +24,6 @@ #include "common/xmlutils.h" #include "node/keyframe.h" #include "node/value.h" -#include "splitvalue.h" namespace olive { @@ -33,7 +32,7 @@ class NodeInput; class NodeInputImmediate { public: - NodeInputImmediate(NodeValue::Type type, const SplitValue& default_val); + NodeInputImmediate(); /** * @brief Internal insert function, automatically does an insertion sort based on the keyframe's time @@ -47,19 +46,22 @@ class NodeInputImmediate /** * @brief Get non-keyframed value split into components (the way it's stored) */ - const SplitValue& get_split_standard_value() const + const std::vector &get_standard_value() const { return standard_value_; } - const QVariant& get_split_standard_value_on_track(int track) const + value_t::component_t get_split_standard_value_on_track(size_t track) const { - return standard_value_.at(track); + if (track < standard_value_.size()) { + return standard_value_.at(track); + } + return value_t::component_t(); } - void set_standard_value_on_track(const QVariant &value, int track); + void set_standard_value_on_track(const value_t::component_t &value, size_t track); - void set_split_standard_value(const SplitValue& value); + void set_split_standard_value(const value_t& value); /** * @brief Retrieve a list of keyframe objects for all tracks at a given time @@ -75,7 +77,7 @@ class NodeInputImmediate * * The keyframe object at this time or nullptr if there isn't one or if is_keyframing() is false. */ - NodeKeyframe* get_keyframe_at_time_on_track(const rational& time, int track) const; + NodeKeyframe* get_keyframe_at_time_on_track(const rational& time, size_t track) const; /** * @brief Gets the closest keyframe to a time @@ -147,21 +149,14 @@ class NodeInputImmediate bool is_using_standard_value(int track) const { - return (!is_keyframing() || keyframe_tracks_.at(track).isEmpty()); + return (!is_keyframing() || track >= keyframe_tracks_.size() || keyframe_tracks_.at(track).isEmpty()); } - void set_data_type(NodeValue::Type type); - private: /** * @brief Non-keyframed value */ - SplitValue standard_value_; - - /** - * @brief Default value - */ - SplitValue default_value_; + std::vector standard_value_; /** * @brief Internal keyframe array diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index d329add422..a793039ca7 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -26,7 +26,7 @@ namespace olive { const NodeKeyframe::Type NodeKeyframe::kDefaultType = kLinear; -NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, Type type, int track, int element, const QString &input, QObject *parent) : +NodeKeyframe::NodeKeyframe(const rational &time, const value_t::component_t &value, Type type, int track, int element, const QString &input, QObject *parent) : time_(time), value_(value), type_(type), @@ -80,12 +80,12 @@ void NodeKeyframe::set_time(const rational &time) emit TimeChanged(time_); } -const QVariant &NodeKeyframe::value() const +const value_t::component_t &NodeKeyframe::value() const { return value_; } -void NodeKeyframe::set_value(const QVariant &value) +void NodeKeyframe::set_value(const value_t::component_t &value) { value_ = value; emit ValueChanged(value_); @@ -210,7 +210,7 @@ bool NodeKeyframe::has_sibling_at_time(const rational &t) const return k && k != this; } -bool NodeKeyframe::load(QXmlStreamReader *reader, NodeValue::Type data_type) +bool NodeKeyframe::load(QXmlStreamReader *reader, type_t data_type) { QString key_input; QPointF key_in_handle; @@ -220,7 +220,7 @@ bool NodeKeyframe::load(QXmlStreamReader *reader, NodeValue::Type data_type) if (attr.name() == QStringLiteral("input")) { key_input = attr.value().toString(); } else if (attr.name() == QStringLiteral("time")) { - this->set_time(rational::fromString(attr.value().toString().toStdString())); + this->set_time(rational::fromString(attr.value().toString())); } else if (attr.name() == QStringLiteral("type")) { this->set_type_no_bezier_adj(static_cast(attr.value().toInt())); } else if (attr.name() == QStringLiteral("inhandlex")) { @@ -234,7 +234,7 @@ bool NodeKeyframe::load(QXmlStreamReader *reader, NodeValue::Type data_type) } } - this->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); + this->set_value(value_t::component_t::fromSerializedString(data_type, reader->readElementText())); this->set_bezier_control_in(key_in_handle); this->set_bezier_control_out(key_out_handle); @@ -242,17 +242,17 @@ bool NodeKeyframe::load(QXmlStreamReader *reader, NodeValue::Type data_type) return true; } -void NodeKeyframe::save(QXmlStreamWriter *writer, NodeValue::Type data_type) const +void NodeKeyframe::save(QXmlStreamWriter *writer, type_t data_type) const { writer->writeAttribute(QStringLiteral("input"), this->input()); - writer->writeAttribute(QStringLiteral("time"), QString::fromStdString(this->time().toString())); + writer->writeAttribute(QStringLiteral("time"), this->time().toString()); writer->writeAttribute(QStringLiteral("type"), QString::number(this->type())); writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(this->bezier_control_in().x())); writer->writeAttribute(QStringLiteral("inhandley"), QString::number(this->bezier_control_in().y())); writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(this->bezier_control_out().x())); writer->writeAttribute(QStringLiteral("outhandley"), QString::number(this->bezier_control_out().y())); - writer->writeCharacters(NodeValue::ValueToString(data_type, this->value(), true)); + writer->writeCharacters(this->value().toSerializedString(data_type)); } } diff --git a/app/node/keyframe.h b/app/node/keyframe.h index c59f3ffa42..7c5f0b3c3f 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -61,7 +61,7 @@ class NodeKeyframe : public QObject /** * @brief NodeKeyframe Constructor */ - NodeKeyframe(const rational& time, const QVariant& value, Type type, int track, int element, const QString& input, QObject* parent = nullptr); + NodeKeyframe(const rational& time, const value_t::component_t& value, Type type, int track, int element, const QString& input, QObject* parent = nullptr); NodeKeyframe(); virtual ~NodeKeyframe() override; @@ -88,8 +88,8 @@ class NodeKeyframe : public QObject /** * @brief The value of this keyframe (i.e. the value to use at this keyframe's time) */ - const QVariant& value() const; - void set_value(const QVariant &value); + const value_t::component_t& value() const; + void set_value(const value_t::component_t &value); /** * @brief The method of interpolation to use with this keyframe @@ -165,8 +165,8 @@ class NodeKeyframe : public QObject bool has_sibling_at_time(const rational &t) const; - bool load(QXmlStreamReader *reader, NodeValue::Type data_type); - void save(QXmlStreamWriter *writer, NodeValue::Type data_type) const; + bool load(QXmlStreamReader *reader, type_t data_type); + void save(QXmlStreamWriter *writer, type_t data_type) const; signals: /** @@ -177,7 +177,7 @@ class NodeKeyframe : public QObject /** * @brief Signal emitted when this keyframe's value is changed */ - void ValueChanged(const QVariant& value); + void ValueChanged(const value_t::component_t &value); /** * @brief Signal emitted when this keyframe's value is changed @@ -197,7 +197,7 @@ class NodeKeyframe : public QObject private: rational time_; - QVariant value_; + value_t::component_t value_; Type type_; diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp index cfbea289b8..f086045091 100644 --- a/app/node/keying/chromakey/chromakey.cpp +++ b/app/node/keying/chromakey/chromakey.cpp @@ -34,13 +34,13 @@ const QString ChromaKeyNode::kHighlightsInput = QStringLiteral("highlights_in"); ChromaKeyNode::ChromaKeyNode() { - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0f, 1.0f, 0.0f, 1.0f))); + AddInput(kColorInput, TYPE_COLOR, Color(0.0f, 1.0f, 0.0f, 1.0f)); - AddInput(kLowerToleranceInput, NodeValue::kFloat, 5.0); + AddInput(kLowerToleranceInput, TYPE_DOUBLE, 5.0); SetInputProperty(kLowerToleranceInput, QStringLiteral("min"), 0.0); SetInputProperty(kLowerToleranceInput, QStringLiteral("base"), 0.1); - AddInput(kUpperToleranceInput, NodeValue::kFloat, 25.0); + AddInput(kUpperToleranceInput, TYPE_DOUBLE, 25.0); SetInputProperty(kUpperToleranceInput, QStringLiteral("base"), 0.1); // FIXME: Temporarily disabled. This will break if "lower tolerance" is keyframed or connected to @@ -48,21 +48,21 @@ ChromaKeyNode::ChromaKeyNode() // we can look into re-enabling this. //SetInputProperty(kUpperToleranceInput, QStringLiteral("min"), GetStandardValue(kLowerToleranceInput).toDouble()); - AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kGarbageMatteInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kCoreMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kCoreMatteInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kHighlightsInput, NodeValue::kFloat, 100.0f); + AddInput(kHighlightsInput, TYPE_DOUBLE, 100.0f); SetInputProperty(kHighlightsInput, QStringLiteral("min"), 0.0); SetInputProperty(kHighlightsInput, QStringLiteral("base"), 0.1); - AddInput(kShadowsInput, NodeValue::kFloat, 100.0f); + AddInput(kShadowsInput, TYPE_DOUBLE, 100.0f); SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.1); - AddInput(kInvertInput, NodeValue::kBoolean, false); + AddInput(kInvertInput, TYPE_BOOL, false); - AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); + AddInput(kMaskOnlyInput, TYPE_BOOL, false); } QString ChromaKeyNode::Name() const @@ -113,9 +113,9 @@ void ChromaKeyNode::InputValueChangedEvent(const QString &input, int element) GenerateProcessor(); } -ShaderCode ChromaKeyNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode GetColorTransformCode(const QString &stub) { - return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/chromakey.frag")).arg(request.stub)); + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/chromakey.frag")).arg(stub)); } void ChromaKeyNode::GenerateProcessor() @@ -130,20 +130,24 @@ void ChromaKeyNode::GenerateProcessor() } } -void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ChromaKeyNode::Value(const ValueParams &p) const { - if (TexturePtr tex = value[kTextureInput].toTexture()) { + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { if (processor()) { - ColorTransformJob job(value); + ColorTransformJob job = CreateColorTransformJob(p); job.SetColorProcessor(processor()); - job.SetInputTexture(value[kTextureInput]); - job.SetNeedsCustomShader(this); + job.SetInputTexture(GetInputValue(p, kTextureInput)); + job.SetCustomShaderFunction(GetColorTransformCode); job.SetFunctionName(QStringLiteral("SceneLinearToCIEXYZ_d65")); - table->Push(NodeValue::kTexture, tex->toJob(job), this); + return tex->toJob(job); } } + + return tex_meta; } void ChromaKeyNode::ConfigChanged() diff --git a/app/node/keying/chromakey/chromakey.h b/app/node/keying/chromakey/chromakey.h index 5082dc32f5..14249b02a9 100644 --- a/app/node/keying/chromakey/chromakey.h +++ b/app/node/keying/chromakey/chromakey.h @@ -36,8 +36,7 @@ class ChromaKeyNode : public OCIOBaseNode { virtual void InputValueChangedEvent(const QString& input, int element) override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; + virtual value_t Value(const ValueParams &p) const override; virtual void ConfigChanged() override; diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index 40ff98ccfe..8933262086 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -29,23 +29,23 @@ const QString ColorDifferenceKeyNode::kMaskOnlyInput = QStringLiteral("mask_only ColorDifferenceKeyNode::ColorDifferenceKeyNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kGarbageMatteInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kCoreMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kCoreMatteInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kColorInput, NodeValue::kCombo, 0); + AddInput(kColorInput, TYPE_COMBO, 0); - AddInput(kHighlightsInput, NodeValue::kFloat, 1.0f); + AddInput(kHighlightsInput, TYPE_DOUBLE, 1.0f); SetInputProperty(kHighlightsInput, QStringLiteral("min"), 0.0); SetInputProperty(kHighlightsInput, QStringLiteral("base"), 0.01); - AddInput(kShadowsInput, NodeValue::kFloat, 1.0f); + AddInput(kShadowsInput, TYPE_DOUBLE, 1.0f); SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.01); - AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); + AddInput(kMaskOnlyInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -85,20 +85,22 @@ void ColorDifferenceKeyNode::Retranslate() SetInputName(kMaskOnlyInput, tr("Show Mask Only")); } -ShaderCode ColorDifferenceKeyNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode ColorDifferenceKeyNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/colordifferencekey.frag")); } -void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ColorDifferenceKeyNode::Value(const ValueParams &p) const { // If there's no texture, no need to run an operation - if (TexturePtr tex = value[kTextureInput].toTexture()) { - ShaderJob job; - job.Insert(value); - table->Push(NodeValue::kTexture, tex->toJob(job), this); + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); + return tex->toJob(job); } + + return tex_meta; } } // namespace olive diff --git a/app/node/keying/colordifferencekey/colordifferencekey.h b/app/node/keying/colordifferencekey/colordifferencekey.h index 31cb106a6e..100da677d8 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.h +++ b/app/node/keying/colordifferencekey/colordifferencekey.h @@ -33,8 +33,7 @@ class ColorDifferenceKeyNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTextureInput; static const QString kGarbageMatteInput; @@ -44,6 +43,9 @@ class ColorDifferenceKeyNode : public Node { static const QString kHighlightsInput; static const QString kMaskOnlyInput; +private: + static ShaderCode GetShaderCode(const QString &id); + }; } // namespace olive diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index 267e3d3c42..328f1345cb 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -28,13 +28,13 @@ const QString DespillNode::kPreserveLuminanceInput = QStringLiteral("preserve_lu DespillNode::DespillNode() { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kColorInput, NodeValue::kCombo, 0); + AddInput(kColorInput, TYPE_COMBO, 0); - AddInput(kMethodInput, NodeValue::kCombo, 0); + AddInput(kMethodInput, TYPE_COMBO, 0); - AddInput(kPreserveLuminanceInput, NodeValue::kBoolean, false); + AddInput(kPreserveLuminanceInput, TYPE_BOOL, false); SetFlag(kVideoEffect); SetEffectInput(kTextureInput); @@ -75,25 +75,27 @@ void DespillNode::Retranslate() SetInputName(kPreserveLuminanceInput, tr("Preserve Luminance")); } -ShaderCode DespillNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(request) +ShaderCode DespillNode::GetShaderCode(const QString &id) +{ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/despill.frag")); } -void DespillNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - job.Insert(value); +value_t DespillNode::Value(const ValueParams &p) const +{ + value_t tex_meta = GetInputValue(p, kTextureInput); + + if (TexturePtr tex = tex_meta.toTexture()) { + ShaderJob job = CreateShaderJob(p, GetShaderCode); - // Set luma coefficients - double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; - project()->color_manager()->GetDefaultLumaCoefs(luma_coeffs); - job.Insert(QStringLiteral("luma_coeffs"), - NodeValue(NodeValue::kVec3, QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]))); + // Set luma coefficients + double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; + project()->color_manager()->GetDefaultLumaCoefs(luma_coeffs); + job.Insert(QStringLiteral("luma_coeffs"), QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2])); - // If there's no texture, no need to run an operation - if (TexturePtr tex = job.Get(kTextureInput).toTexture()) { - table->Push(NodeValue::kTexture, tex->toJob(job), this); + return tex->toJob(job); } + + return tex_meta; } diff --git a/app/node/keying/despill/despill.h b/app/node/keying/despill/despill.h index 9364024272..249f02c921 100644 --- a/app/node/keying/despill/despill.h +++ b/app/node/keying/despill/despill.h @@ -34,14 +34,15 @@ class DespillNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; + virtual value_t Value(const ValueParams &globals) const override; static const QString kTextureInput; static const QString kColorInput; static const QString kMethodInput; static const QString kPreserveLuminanceInput; +private: + static ShaderCode GetShaderCode(const QString &id); }; diff --git a/app/node/math/math/CMakeLists.txt b/app/node/math/math/CMakeLists.txt index 8837a6e9d4..5f3a7c4df9 100644 --- a/app/node/math/math/CMakeLists.txt +++ b/app/node/math/math/CMakeLists.txt @@ -16,9 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - node/math/math/math.h node/math/math/math.cpp - node/math/math/mathbase.h - node/math/math/mathbase.cpp + node/math/math/math.h + node/math/math/mathfunctions.cpp + node/math/math/mathfunctions.h PARENT_SCOPE ) diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 82b07e6e87..08a7190687 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -20,6 +20,9 @@ #include "math.h" +#include "mathfunctions.h" +#include "util/cpuoptimize.h" + namespace olive { const QString MathNode::kMethodIn = QStringLiteral("method_in"); @@ -27,28 +30,40 @@ const QString MathNode::kParamAIn = QStringLiteral("param_a_in"); const QString MathNode::kParamBIn = QStringLiteral("param_b_in"); const QString MathNode::kParamCIn = QStringLiteral("param_c_in"); -#define super MathNodeBase +#define super Node + +std::map> > > MathNode::operations_; MathNode::MathNode() { - AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kMethodIn, TYPE_COMBO, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kParamAIn, NodeValue::kFloat, 0.0); + AddInput(kParamAIn, TYPE_DOUBLE, 0.0, kInputFlagDontAutoConvert); SetInputProperty(kParamAIn, QStringLiteral("decimalplaces"), 8); SetInputProperty(kParamAIn, QStringLiteral("autotrim"), true); - AddInput(kParamBIn, NodeValue::kFloat, 0.0); + AddInput(kParamBIn, TYPE_DOUBLE, 0.0, kInputFlagDontAutoConvert); SetInputProperty(kParamBIn, QStringLiteral("decimalplaces"), 8); SetInputProperty(kParamBIn, QStringLiteral("autotrim"), true); + + AddInput(kParamCIn, TYPE_DOUBLE, 0.0, kInputFlagDontAutoConvert); + SetInputProperty(kParamCIn, QStringLiteral("decimalplaces"), 8); + SetInputProperty(kParamCIn, QStringLiteral("autotrim"), true); + + if (operations_.empty()) { + PopulateOperations(); + } + + UpdateInputVisibility(); } QString MathNode::Name() const { // Default to naming after the operation if (parent()) { - QString op_name = GetOperationName(GetOperation()); - if (!op_name.isEmpty()) { - return op_name; + QString name = GetOperationName(GetOperation()); + if (!name.isEmpty()) { + return name; } } @@ -75,51 +90,608 @@ void MathNode::Retranslate() super::Retranslate(); SetInputName(kMethodIn, tr("Method")); + SetInputName(kParamAIn, tr("Value")); - SetInputName(kParamBIn, tr("Value")); + if (GetOperation() == kOpClamp) { + SetInputName(kParamBIn, tr("Minimum")); + SetInputName(kParamCIn, tr("Maximum")); + } else { + SetInputName(kParamBIn, tr("Value")); + SetInputName(kParamCIn, tr("Value")); + } - QStringList operations = {GetOperationName(kOpAdd), - GetOperationName(kOpSubtract), - GetOperationName(kOpMultiply), - GetOperationName(kOpDivide), - QString(), - GetOperationName(kOpPower)}; + QStringList operations = { + GetOperationName(kOpAdd), + GetOperationName(kOpSubtract), + GetOperationName(kOpMultiply), + GetOperationName(kOpDivide), + GetOperationName(kOpModulo), + QString(), + GetOperationName(kOpPower), + QString(), + GetOperationName(kOpSine), + GetOperationName(kOpCosine), + GetOperationName(kOpTangent), + QString(), + GetOperationName(kOpArcSine), + GetOperationName(kOpArcCosine), + GetOperationName(kOpArcTangent), + QString(), + GetOperationName(kOpHypSine), + GetOperationName(kOpHypCosine), + GetOperationName(kOpHypTangent), + QString(), + GetOperationName(kOpMin), + GetOperationName(kOpMax), + GetOperationName(kOpClamp), + QString(), + GetOperationName(kOpFloor), + GetOperationName(kOpCeil), + GetOperationName(kOpRound), + GetOperationName(kOpAbs), + }; SetComboBoxStrings(kMethodIn, operations); } -ShaderCode MathNode::GetShaderCode(const ShaderRequest &request) const +void MathNode::ProcessSamplesSamples(const void *context, const SampleJob &job, SampleBuffer &mixed_samples) { - return GetShaderCodeInternal(request.id, kParamAIn, kParamBIn); + const SampleBuffer samples_a = job.Get(QStringLiteral("a")).toSamples(); + const SampleBuffer samples_b = job.Get(QStringLiteral("b")).toSamples(); + const MathNode::Operation operation = static_cast(job.Get(QStringLiteral("operation")).toInt()); + + if (!samples_a.is_allocated() || !samples_b.is_allocated()) { + return; + } + + size_t max_samples = qMax(samples_a.sample_count(), samples_b.sample_count()); + size_t min_samples = qMin(samples_a.sample_count(), samples_b.sample_count()); + + for (int i=0;i min_samples) { + size_t remainder = max_samples - min_samples; + + const SampleBuffer &larger_buffer = (max_samples == samples_a.sample_count()) ? samples_a : samples_b; + + for (int i=0;i(GetStandardValue(kMethodIn).toInt()); +} + +void MathNode::UpdateInputVisibility() +{ + int count = GetNumberOfOperands(GetOperation()); + + SetInputFlag(kParamAIn, kInputFlagHidden, 0 >= count); + SetInputFlag(kParamBIn, kInputFlagHidden, 1 >= count); + SetInputFlag(kParamCIn, kInputFlagHidden, 2 >= count); + + Retranslate(); +} + +void MathNode::ProcessSamplesDouble(const void *context, const SampleJob &job, SampleBuffer &output) +{ + const Node *n = static_cast(context); + + const ValueParams &p = job.value_params(); + const SampleBuffer input = job.Get(QStringLiteral("samples")).toSamples(); + const QString number_in = job.Get(QStringLiteral("number")).toString(); + const Operation operation = static_cast(job.Get(QStringLiteral("operation")).toInt()); + + if (!input.is_allocated()) { return; } - return ValueInternal(GetOperation(), - calc.GetMostLikelyPairing(), - kParamAIn, - calc.GetMostLikelyValueA(), - kParamBIn, - calc.GetMostLikelyValueB(), - globals, - table); + if (n->IsInputStatic(number_in)) { + auto f = n->GetStandardValue(number_in).toDouble(); + + for (int i=0;i values(input.sample_count()); + + for (size_t i = 0; i < values.size(); i++) { + rational this_sample_time = p.time().in() + rational(i, input.audio_params().sample_rate()); + TimeRange this_sample_range(this_sample_time, this_sample_time + input.audio_params().sample_rate_as_time_base()); + auto v = n->GetInputValue(p.time_transformed(this_sample_range), number_in).toDouble(); + values[i] = v; + } + + for (int i=0;i(j.at(0).toInt()); + type_t atype = type_t::fromString(j.at(1)); + type_t btype = type_t::fromString(j.at(2)); + type_t ctype = type_t::fromString(j.at(3)); + + QString line; + + switch (op) { + case kOpAdd: line = QStringLiteral("%1 + %2"); break; + case kOpSubtract: line = QStringLiteral("%1 - %2"); break; + case kOpMultiply: line = QStringLiteral("%1 * %2"); break; + case kOpDivide: line = QStringLiteral("%1 / %2"); break; + case kOpModulo: line = QStringLiteral("mod(%1, %2)"); break; + case kOpPower: + case kOpMin: + case kOpMax: + if (op == kOpPower) { + line = QStringLiteral("pow"); + } else if (op == kOpMin) { + line = QStringLiteral("min"); + } else if (op == kOpMax) { + line = QStringLiteral("max"); + } + + if (atype == TYPE_DOUBLE) { + // The "number" in this operation has to be declared a vec4 + line.append("(%2, vec4(%1))"); + } else if (btype == TYPE_DOUBLE) { + line.append("(%1, vec4(%2))"); + } else { + line.append("(%1, %2)"); + } + break; + case kOpSine: line = QStringLiteral("sin(%1)"); break; + case kOpCosine: line = QStringLiteral("cos(%1)"); break; + case kOpTangent: line = QStringLiteral("tan(%1)"); break; + case kOpArcSine: line = QStringLiteral("asin(%1)"); break; + case kOpArcCosine: line = QStringLiteral("acos(%1)"); break; + case kOpArcTangent: line = QStringLiteral("atan(%1)"); break; + case kOpHypSine: line = QStringLiteral("sinh(%1)"); break; + case kOpHypCosine: line = QStringLiteral("cosh(%1)"); break; + case kOpHypTangent: line = QStringLiteral("tanh(%1)"); break; + case kOpFloor: line = QStringLiteral("floor(%1)"); break; + case kOpCeil: line = QStringLiteral("ceil(%1)"); break; + case kOpRound: line = QStringLiteral("round(%1)"); break; + case kOpAbs: line = QStringLiteral("abs(%1)"); break; + case kOpClamp: line = QStringLiteral("clamp(%1, %2, %3)"); break; + break; + } + + line = line.arg(GetShaderVariableCall(kParamAIn, atype), + GetShaderVariableCall(kParamBIn, btype), + GetShaderVariableCall(kParamCIn, ctype)); + + static const QString stub = + QStringLiteral("uniform %1 %4;\n" + "uniform %2 %5;\n" + "uniform %3 %6;\n" + "\n" + "in vec2 ove_texcoord;\n" + "out vec4 frag_color;\n" + "\n" + "void main(void) {\n" + " vec4 c = %7;\n" + " c.a = clamp(c.a, 0.0, 1.0);\n" // Ensure alpha is between 0.0 and 1.0 + " frag_color = c;\n" + "}\n"); + + return stub.arg(GetShaderUniformType(atype), + GetShaderUniformType(btype), + GetShaderUniformType(ctype), + kParamAIn, + kParamBIn, + kParamCIn, + line); +} + +void NormalizeNumber(value_t &in) +{ + if (in.type() == TYPE_RATIONAL || in.type() == TYPE_INTEGER) { + in = in.converted(TYPE_DOUBLE); + } +} + +value_t MathNode::Value(const ValueParams &p) const +{ + // Auto-detect what values to operate with + auto aval = GetInputValue(p, kParamAIn); + auto bval = GetInputValue(p, kParamBIn); + auto cval = GetInputValue(p, kParamCIn); + + Operation operation = static_cast(GetInputValue(p, kMethodIn).toInt()); + int count = GetNumberOfOperands(operation); + + // Null values if we aren't listening so the function-lookup is more reliable + if (count < 1) { aval = value_t(); } + if (count < 2) { bval = value_t(); } + if (count < 3) { cval = value_t(); } + + // Treat integers and rationals as doubles + NormalizeNumber(aval); + NormalizeNumber(bval); + NormalizeNumber(cval); + + if (aval.type() == TYPE_SAMPLES || bval.type() == TYPE_SAMPLES) { + if (aval.type() == TYPE_SAMPLES && bval.type() == TYPE_SAMPLES) { + SampleJob job(p); + + job.Insert(QStringLiteral("a"), aval); + job.Insert(QStringLiteral("b"), bval); + job.Insert(QStringLiteral("operation"), int(operation)); + + job.set_function(ProcessSamplesSamples, this); + + return job; + } else if (aval.type() == TYPE_DOUBLE || bval.type() == TYPE_DOUBLE) { + SampleJob job(p); + + job.Insert(QStringLiteral("samples"), aval.type() == TYPE_SAMPLES ? aval : bval); + job.Insert(QStringLiteral("number"), aval.type() == TYPE_SAMPLES ? kParamBIn : kParamAIn); + job.Insert(QStringLiteral("operation"), int(operation)); + + job.set_function(ProcessSamplesDouble, this); + + return job; + } + } else if (aval.type() == TYPE_TEXTURE || bval.type() == TYPE_TEXTURE) { + // Produce shader job + ShaderJob job = CreateShaderJob(p, GetShaderCode); + job.SetShaderID(QStringLiteral("%1:%2:%3:%4").arg(QString::number(operation), aval.type().toString(), bval.type().toString(), cval.type().toString())); + return Texture::Job(aval.type() == TYPE_TEXTURE ? aval.toTexture()->params() : bval.toTexture()->params(), job); + } else { + // Operation can be done entirely here + operation_t func = operations_[operation][aval.type()][bval.type()][cval.type()]; + if (func) { + return func(aval, bval, cval); + } + } + + return aval; +} + +QString MathNode::GetOperationName(Operation o) +{ + switch (o) { + case kOpAdd: return tr("Add"); + case kOpSubtract: return tr("Subtract"); + case kOpMultiply: return tr("Multiply"); + case kOpDivide: return tr("Divide"); + case kOpModulo: return tr("Modulo"); + case kOpPower: return tr("Power"); + case kOpSine: return tr("Sine"); + case kOpCosine: return tr("Cosine"); + case kOpTangent: return tr("Tangent"); + case kOpArcSine: return tr("Inverse Sine"); + case kOpArcCosine: return tr("Inverse Cosine"); + case kOpArcTangent: return tr("Inverse Tangent"); + case kOpHypSine: return tr("Hyperbolic Sine"); + case kOpHypCosine: return tr("Hyperbolic Cosine"); + case kOpHypTangent: return tr("Hyperbolic Tangent"); + case kOpMin: return tr("Minimum"); + case kOpMax: return tr("Maximum"); + case kOpClamp: return tr("Clamp"); + case kOpFloor: return tr("Floor"); + case kOpCeil: return tr("Ceil"); + case kOpRound: return tr("Round"); + case kOpAbs: return tr("Absolute"); + } + + return QString(); +} + +void MathNode::PopulateOperations() +{ + operations_[kOpAdd][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::AddDoubleDouble; + operations_[kOpSubtract][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::SubtractDoubleDouble; + operations_[kOpMultiply][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::MultiplyDoubleDouble; + operations_[kOpDivide][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::DivideDoubleDouble; + operations_[kOpModulo][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::ModuloDoubleDouble; + operations_[kOpPower][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::PowerDoubleDouble; + + operations_[kOpSine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::SineDouble; + operations_[kOpCosine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::CosineDouble; + operations_[kOpTangent][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::TangentDouble; + + operations_[kOpArcSine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::ArcSineDouble; + operations_[kOpArcCosine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::ArcCosineDouble; + operations_[kOpArcTangent][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::ArcTangentDouble; + + operations_[kOpHypSine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::HypSineDouble; + operations_[kOpHypCosine][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::HypCosineDouble; + operations_[kOpHypTangent][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::HypTangentDouble; + + operations_[kOpMin][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::MinDoubleDouble; + operations_[kOpMax][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_NONE] = Math::MaxDoubleDouble; + operations_[kOpClamp][TYPE_DOUBLE][TYPE_DOUBLE][TYPE_DOUBLE] = Math::ClampDoubleDoubleDouble; + + operations_[kOpFloor][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::FloorDouble; + operations_[kOpCeil][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::CeilDouble; + operations_[kOpRound][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::RoundDouble; + operations_[kOpAbs][TYPE_DOUBLE][TYPE_NONE][TYPE_NONE] = Math::AbsDouble; + + operations_[kOpAdd][TYPE_MATRIX][TYPE_MATRIX][TYPE_NONE] = Math::AddMatrixMatrix; + operations_[kOpSubtract][TYPE_MATRIX][TYPE_MATRIX][TYPE_NONE] = Math::SubtractMatrixMatrix; + operations_[kOpMultiply][TYPE_MATRIX][TYPE_MATRIX][TYPE_NONE] = Math::MultiplyMatrixMatrix; } } diff --git a/app/node/math/math/math.h b/app/node/math/math/math.h index 1e2f1ffcc4..49d7c1f6bc 100644 --- a/app/node/math/math/math.h +++ b/app/node/math/math/math.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -21,16 +21,47 @@ #ifndef MATHNODE_H #define MATHNODE_H -#include "mathbase.h" +#include "node/node.h" namespace olive { -class MathNode : public MathNodeBase +class MathNode : public Node { Q_OBJECT public: MathNode(); + enum Operation { + kOpAdd, + kOpSubtract, + kOpMultiply, + kOpDivide, + kOpModulo, + + kOpPower, + + kOpSine, + kOpCosine, + kOpTangent, + + kOpArcSine, + kOpArcCosine, + kOpArcTangent, + + kOpHypSine, + kOpHypCosine, + kOpHypTangent, + + kOpMin, + kOpMax, + kOpClamp, + + kOpFloor, + kOpCeil, + kOpRound, + kOpAbs + }; + NODE_DEFAULT_FUNCTIONS(MathNode) virtual QString Name() const override; @@ -40,27 +71,40 @@ class MathNode : public MathNodeBase virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - - Operation GetOperation() const - { - return static_cast(GetStandardValue(kMethodIn).toInt()); - } + virtual value_t Value(const ValueParams &p) const override; - void SetOperation(Operation o) - { - SetStandardValue(kMethodIn, o); - } + static QString GetOperationName(Operation o); - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - - virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; + static ShaderCode GetShaderCode(const QString &id); static const QString kMethodIn; static const QString kParamAIn; static const QString kParamBIn; static const QString kParamCIn; + static void ProcessSamplesDouble(const void *context, const SampleJob &job, SampleBuffer &mixed_samples); + +protected: + virtual void InputValueChangedEvent(const QString& input, int element) override; + +private: + typedef value_t (*operation_t)(const value_t &a, const value_t &b, const value_t &c); + + static std::map> > > operations_; + + static void PopulateOperations(); + + static void ProcessSamplesSamples(const void *context, const SampleJob &job, SampleBuffer &mixed_samples); + + static void OperateSampleNumber(Operation operation, const float *input, float *output, float b, size_t start, size_t end); + static void OperateSampleSample(Operation operation, const float *input, float *output, const float *input2, size_t start, size_t end); + + static int GetNumberOfOperands(Operation o); + + Operation GetOperation() const; + + void UpdateInputVisibility(); + }; } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp deleted file mode 100644 index af8ded1e37..0000000000 --- a/app/node/math/math/mathbase.cpp +++ /dev/null @@ -1,690 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "math.h" - -#include -#include - -#include "common/tohex.h" -#include "node/distort/transform/transformdistortnode.h" - -namespace olive { - -ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const QString& param_a_in, const QString& param_b_in) const -{ - QStringList code_id = shader_id.split('.'); - - Operation op = static_cast(code_id.at(0).toInt()); - Pairing pairing = static_cast(code_id.at(1).toInt()); - NodeValue::Type type_a = static_cast(code_id.at(2).toInt()); - NodeValue::Type type_b = static_cast(code_id.at(3).toInt()); - - QString operation, frag, vert; - - if (pairing == kPairTextureMatrix && op == kOpMultiply) { - - // Override the operation for this operation since we multiply texture COORDS by the matrix rather than - const QString& tex_in = (type_a == NodeValue::kTexture) ? param_a_in : param_b_in; - const QString& mat_in = (type_a == NodeValue::kTexture) ? param_b_in : param_a_in; - - // No-op frag shader (can we return QString() instead?) - operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in); - - vert = QStringLiteral("uniform mat4 %1;\n" - "\n" - "in vec4 a_position;\n" - "in vec2 a_texcoord;\n" - "\n" - "out vec2 ove_texcoord;\n" - "\n" - "void main() {\n" - " gl_Position = %1 * a_position;\n" - " ove_texcoord = a_texcoord;\n" - "}\n").arg(mat_in); - - } else { - switch (op) { - case kOpAdd: - operation = QStringLiteral("%1 + %2"); - break; - case kOpSubtract: - operation = QStringLiteral("%1 - %2"); - break; - case kOpMultiply: - operation = QStringLiteral("%1 * %2"); - break; - case kOpDivide: - operation = QStringLiteral("%1 / %2"); - break; - case kOpPower: - if (pairing == kPairTextureNumber) { - // The "number" in this operation has to be declared a vec4 - if (NodeValue::type_is_numeric(type_a)) { - operation = QStringLiteral("pow(%2, vec4(%1))"); - } else { - operation = QStringLiteral("pow(%1, vec4(%2))"); - } - } else { - operation = QStringLiteral("pow(%1, %2)"); - } - break; - } - - operation = operation.arg(GetShaderVariableCall(param_a_in, type_a), - GetShaderVariableCall(param_b_in, type_b)); - } - - frag = QStringLiteral("uniform %1 %3;\n" - "uniform %2 %4;\n" - "\n" - "in vec2 ove_texcoord;\n" - "out vec4 frag_color;\n" - "\n" - "void main(void) {\n" - " vec4 c = %5;\n" - " c.a = clamp(c.a, 0.0, 1.0);\n" // Ensure alpha is between 0.0 and 1.0 - " frag_color = c;\n" - "}\n").arg(GetShaderUniformType(type_a), - GetShaderUniformType(type_b), - param_a_in, - param_b_in, - operation); - - return ShaderCode(frag, vert); -} - -QString MathNodeBase::GetShaderUniformType(const olive::NodeValue::Type &type) -{ - switch (type) { - case NodeValue::kTexture: - return QStringLiteral("sampler2D"); - case NodeValue::kColor: - return QStringLiteral("vec4"); - case NodeValue::kMatrix: - return QStringLiteral("mat4"); - default: - return QStringLiteral("float"); - } -} - -QString MathNodeBase::GetShaderVariableCall(const QString &input_id, const NodeValue::Type &type, const QString& coord_op) -{ - if (type == NodeValue::kTexture) { - return QStringLiteral("texture(%1, ove_texcoord%2)").arg(input_id, coord_op); - } - - return input_id; -} - -QVector4D MathNodeBase::RetrieveVector(const NodeValue &val) -{ - // QVariant doesn't know that QVector*D can convert themselves so we do it here - switch (val.type()) { - case NodeValue::kVec2: - return QVector4D(val.toVec2()); - case NodeValue::kVec3: - return QVector4D(val.toVec3()); - case NodeValue::kVec4: - default: - return val.toVec4(); - } -} - -void MathNodeBase::PushVector(NodeValueTable *output, olive::NodeValue::Type type, const QVector4D &vec) const -{ - switch (type) { - case NodeValue::kVec2: - output->Push(type, QVector2D(vec), this); - break; - case NodeValue::kVec3: - output->Push(type, QVector3D(vec), this); - break; - case NodeValue::kVec4: - output->Push(type, vec, this); - break; - default: - break; - } -} - -QString MathNodeBase::GetOperationName(Operation o) -{ - switch (o) { - case kOpAdd: return tr("Add"); - case kOpSubtract: return tr("Subtract"); - case kOpMultiply: return tr("Multiply"); - case kOpDivide: return tr("Divide"); - case kOpPower: return tr("Power"); - } - - return QString(); -} - -void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end) -{ - for (int j=start;jPush(NodeValue::kRational, - QVariant::fromValue(PerformAddSubMultDiv(operation, val_a.toRational(), val_b.toRational())), - this); - } else { - output->Push(NodeValue::kFloat, - PerformAll(operation, RetrieveNumber(val_a), RetrieveNumber(val_b)), - this); - } - break; - } - - case kPairVecVec: - { - // We convert all vectors to QVector4D just for simplicity and exploit the fact that kVec4 is higher than kVec2 in - // the enum to find the largest data type - PushVector(output, - qMax(val_a.type(), val_b.type()), - PerformAddSubMultDiv(operation, RetrieveVector(val_a), RetrieveVector(val_b))); - break; - } - - case kPairMatrixVec: - { - QMatrix4x4 matrix = (val_a.type() == NodeValue::kMatrix) ? val_a.toMatrix() : val_b.toMatrix(); - QVector4D vec = (val_a.type() == NodeValue::kMatrix) ? RetrieveVector(val_b) : RetrieveVector(val_a); - - // Only valid operation is multiply - PushVector(output, - qMax(val_a.type(), val_b.type()), - PerformMult(operation, vec, matrix)); - break; - } - - case kPairVecNumber: - { - QVector4D vec = (NodeValue::type_is_vector(val_a.type()) ? RetrieveVector(val_a) : RetrieveVector(val_b)); - float number = RetrieveNumber((val_a.type() & NodeValue::kMatrix) ? val_b : val_a); - - // Only multiply and divide are valid operations - PushVector(output, val_a.type(), PerformMultDiv(operation, vec, number)); - break; - } - - case kPairMatrixMatrix: - { - QMatrix4x4 mat_a = val_a.toMatrix(); - QMatrix4x4 mat_b = val_b.toMatrix(); - output->Push(NodeValue::kMatrix, PerformAddSubMult(operation, mat_a, mat_b), this); - break; - } - - case kPairColorColor: - { - Color col_a = val_a.toColor(); - Color col_b = val_b.toColor(); - - // Only add and subtract are valid operations - output->Push(NodeValue::kColor, QVariant::fromValue(PerformAddSub(operation, col_a, col_b)), this); - break; - } - - - case kPairNumberColor: - { - Color col = (val_a.type() == NodeValue::kColor) ? val_a.toColor() : val_b.toColor(); - float num = (val_a.type() == NodeValue::kColor) ? val_b.toDouble() : val_a.toDouble(); - - // Only multiply and divide are valid operations - output->Push(NodeValue::kColor, QVariant::fromValue(PerformMult(operation, col, num)), this); - break; - } - - case kPairSampleSample: - { - SampleBuffer samples_a = val_a.toSamples(); - SampleBuffer samples_b = val_b.toSamples(); - - size_t max_samples = qMax(samples_a.sample_count(), samples_b.sample_count()); - size_t min_samples = qMin(samples_a.sample_count(), samples_b.sample_count()); - - SampleBuffer mixed_samples = SampleBuffer(samples_a.audio_params(), max_samples); - - for (int i=0;i(operation, samples_a.data(i)[j], samples_b.data(i)[j]); - } - } - - if (max_samples > min_samples) { - // Fill in remainder space with 0s - size_t remainder = max_samples - min_samples; - - const SampleBuffer &larger_buffer = (max_samples == samples_a.sample_count()) ? samples_a : samples_b; - - for (int i=0;iPush(NodeValue::kSamples, QVariant::fromValue(mixed_samples), this); - break; - } - - case kPairTextureColor: - case kPairTextureNumber: - case kPairTextureTexture: - case kPairTextureMatrix: - { - ShaderJob job; - job.SetShaderID(QStringLiteral("%1.%2.%3.%4").arg(QString::number(operation), - QString::number(pairing), - QString::number(val_a.type()), - QString::number(val_b.type()))); - - job.Insert(param_a_in, val_a); - job.Insert(param_b_in, val_b); - - bool operation_is_noop = false; - - const NodeValue& number_val = val_a.type() == NodeValue::kTexture ? val_b : val_a; - const NodeValue& texture_val = val_a.type() == NodeValue::kTexture ? val_a : val_b; - TexturePtr texture = texture_val.toTexture(); - - if (!texture) { - operation_is_noop = true; - } else if (pairing == kPairTextureNumber) { - if (NumberIsNoOp(operation, RetrieveNumber(number_val))) { - operation_is_noop = true; - } - } else if (pairing == kPairTextureMatrix) { - // Only allow matrix multiplication - const QVector2D &sequence_res = globals.nonsquare_resolution(); - QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height()); - - QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.toMatrix(), - sequence_res, - texture->params().offset(), - texture_res); - - if (operation != kOpMultiply || adjusted_matrix.isIdentity()) { - operation_is_noop = true; - } else { - // Replace with adjusted matrix - job.Insert(val_a.type() == NodeValue::kTexture ? param_b_in : param_a_in, - NodeValue(NodeValue::kMatrix, adjusted_matrix, this)); - } - } - - if (operation_is_noop) { - // Just push texture as-is - output->Push(texture_val); - } else { - // Push shader job - output->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), job), this); - } - break; - } - - case kPairSampleNumber: - { - // Queue a sample job - const NodeValue& number_val = val_a.type() == NodeValue::kSamples ? val_b : val_a; - const QString& number_param = val_a.type() == NodeValue::kSamples ? param_b_in : param_a_in; - - float number = RetrieveNumber(number_val); - - SampleBuffer buffer = val_a.type() == NodeValue::kSamples ? val_a.toSamples() : val_b.toSamples(); - - if (buffer.is_allocated()) { - if (IsInputStatic(number_param)) { - if (!NumberIsNoOp(operation, number)) { - for (int i=0;iPush(NodeValue::kSamples, QVariant::fromValue(buffer), this); - } else { - SampleJob job(globals.time(), val_a.type() == NodeValue::kSamples ? val_a : val_b); - job.Insert(number_param, NodeValue(NodeValue::kFloat, number, this)); - output->Push(NodeValue::kSamples, QVariant::fromValue(job), this); - } - } - break; - } - - case kPairNone: - case kPairCount: - break; - } -} - -void MathNodeBase::ProcessSamplesInternal(const NodeValueRow &values, MathNodeBase::Operation operation, const QString ¶m_a_in, const QString ¶m_b_in, const olive::SampleBuffer &input, olive::SampleBuffer &output, int index) const -{ - // This function is only used for sample+number pairing - NodeValue number_val = values[param_a_in]; - - if (number_val.type() == NodeValue::kNone) { - number_val = values[param_b_in]; - - if (number_val.type() == NodeValue::kNone) { - return; - } - } - - float number_flt = RetrieveNumber(number_val); - - for (int i=0;i(operation, input.data(i)[index], number_flt); - } -} - -float MathNodeBase::RetrieveNumber(const NodeValue &val) -{ - if (val.type() == NodeValue::kRational) { - return val.toRational().toDouble(); - } else { - return val.toDouble(); - } -} - -bool MathNodeBase::NumberIsNoOp(const MathNodeBase::Operation &op, const float &number) -{ - switch (op) { - case kOpAdd: - case kOpSubtract: - if (qIsNull(number)) { - return true; - } - break; - case kOpMultiply: - case kOpDivide: - case kOpPower: - if (qFuzzyCompare(number, 1.0f)) { - return true; - } - break; - } - - return false; -} - -MathNodeBase::PairingCalculator::PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b) -{ - QVector pair_likelihood_a = GetPairLikelihood(table_a); - QVector pair_likelihood_b = GetPairLikelihood(table_b); - - int weight_a = qMax(0, table_b.Count() - table_a.Count()); - int weight_b = qMax(0, table_a.Count() - table_b.Count()); - - QVector likelihoods(kPairCount); - - for (int i=0;i -1) { - if (most_likely_pairing_ == kPairNone - || likelihoods.at(i) > likelihoods.at(most_likely_pairing_)) { - most_likely_pairing_ = static_cast(i); - } - } - } - - if (most_likely_pairing_ != kPairNone) { - most_likely_value_a_ = table_a.at(pair_likelihood_a.at(most_likely_pairing_)); - most_likely_value_b_ = table_b.at(pair_likelihood_b.at(most_likely_pairing_)); - } -} - -QVector MathNodeBase::PairingCalculator::GetPairLikelihood(const NodeValueTable &table) -{ - QVector likelihood(kPairCount, -1); - - for (int i=0;i kPairNone && most_likely_pairing_ < kPairCount); -} - -MathNodeBase::Pairing MathNodeBase::PairingCalculator::GetMostLikelyPairing() const -{ - return most_likely_pairing_; -} - -const NodeValue &MathNodeBase::PairingCalculator::GetMostLikelyValueA() const -{ - return most_likely_value_a_; -} - -const NodeValue &MathNodeBase::PairingCalculator::GetMostLikelyValueB() const -{ - return most_likely_value_b_; -} - -template -T MathNodeBase::PerformAll(Operation operation, T a, U b) -{ - switch (operation) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpPower: - return std::pow(a, b); - } - - return a; -} - -template -T MathNodeBase::PerformMultDiv(Operation operation, T a, U b) -{ - switch (operation) { - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpAdd: - case kOpSubtract: - case kOpPower: - break; - } - - return a; -} - -template -T MathNodeBase::PerformAddSub(Operation operation, T a, U b) -{ - switch (operation) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNodeBase::PerformMult(Operation operation, T a, U b) -{ - switch (operation) { - case kOpMultiply: - return a * b; - case kOpAdd: - case kOpSubtract: - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNodeBase::PerformAddSubMult(Operation operation, T a, U b) -{ - switch (operation) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNodeBase::PerformAddSubMultDiv(Operation operation, T a, U b) -{ - switch (operation) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpPower: - break; - } - - return a; -} - -} diff --git a/app/node/math/math/mathbase.h b/app/node/math/math/mathbase.h deleted file mode 100644 index 674f4d85a4..0000000000 --- a/app/node/math/math/mathbase.h +++ /dev/null @@ -1,132 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef MATHNODEBASE_H -#define MATHNODEBASE_H - -#include "node/node.h" - -namespace olive { - -class MathNodeBase : public Node -{ -public: - MathNodeBase() = default; - - enum Operation { - kOpAdd, - kOpSubtract, - kOpMultiply, - kOpDivide, - kOpPower - }; - - static QString GetOperationName(Operation o); - -protected: - enum Pairing { - kPairNone = -1, - - kPairNumberNumber, - kPairVecVec, - kPairMatrixMatrix, - kPairColorColor, - kPairTextureTexture, - - kPairVecNumber, - kPairMatrixVec, - kPairNumberColor, - kPairTextureNumber, - kPairTextureColor, - kPairTextureMatrix, - kPairSampleSample, - kPairSampleNumber, - - kPairCount - }; - - class PairingCalculator { - public: - PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b); - - bool FoundMostLikelyPairing() const; - Pairing GetMostLikelyPairing() const; - - const NodeValue& GetMostLikelyValueA() const; - const NodeValue& GetMostLikelyValueB() const; - - private: - static QVector GetPairLikelihood(const NodeValueTable& table); - - Pairing most_likely_pairing_; - - NodeValue most_likely_value_a_; - - NodeValue most_likely_value_b_; - - }; - - template - static T PerformAll(Operation operation, T a, U b); - - template - static T PerformMultDiv(Operation operation, T a, U b); - - template - static T PerformAddSub(Operation operation, T a, U b); - - template - static T PerformMult(Operation operation, T a, U b); - - template - static T PerformAddSubMult(Operation operation, T a, U b); - - template - static T PerformAddSubMultDiv(Operation operation, T a, U b); - - static void PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end); - -#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) - static void PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end); -#endif - - static QString GetShaderUniformType(const NodeValue::Type& type); - - static QString GetShaderVariableCall(const QString& input_id, const NodeValue::Type& type, const QString &coord_op = QString()); - - static QVector4D RetrieveVector(const NodeValue& val); - - static float RetrieveNumber(const NodeValue& val); - - static bool NumberIsNoOp(const Operation& op, const float& number); - - ShaderCode GetShaderCodeInternal(const QString &shader_id, const QString ¶m_a_in, const QString ¶m_b_in) const; - - void PushVector(NodeValueTable* output, NodeValue::Type type, const QVector4D& vec) const; - - void ValueInternal(Operation operation, Pairing pairing, const QString& param_a_in, const NodeValue &val_a, const QString& param_b_in, const NodeValue& val_b, const NodeGlobals &globals, NodeValueTable *output) const; - - void ProcessSamplesInternal(const NodeValueRow &values, Operation operation, const QString& param_a_in, const QString& param_b_in, const SampleBuffer &input, SampleBuffer &output, int index) const; - -}; - -} - -#endif // MATHNODEBASE_H diff --git a/app/node/math/math/mathfunctions.cpp b/app/node/math/math/mathfunctions.cpp new file mode 100644 index 0000000000..eb6421049b --- /dev/null +++ b/app/node/math/math/mathfunctions.cpp @@ -0,0 +1,250 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "mathfunctions.h" + +namespace olive { + +value_t Math::AddDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) + b.value(i); + } + return v; +} + +value_t Math::SubtractDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) - b.value(i); + } + return v; +} + +value_t Math::MultiplyDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) * b.value(i); + } + return v; +} + +value_t Math::DivideDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) / b.value(i); + } + return v; +} + +value_t Math::ModuloDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::fmod(a.value(i), b.value(i)); + } + return v; +} + +value_t Math::PowerDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::pow(a.value(i), b.value(i)); + } + return v; +} + +value_t Math::SineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::sin(a.value(i)); + } + return v; +} + +value_t Math::CosineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::cos(a.value(i)); + } + return v; +} + +value_t Math::TangentDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::tan(a.value(i)); + } + return v; +} + +value_t Math::ArcSineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::asin(a.value(i)); + } + return v; +} + +value_t Math::ArcCosineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::acos(a.value(i)); + } + return v; +} + +value_t Math::ArcTangentDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::atan(a.value(i)); + } + return v; +} + +value_t Math::HypSineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::sinh(a.value(i)); + } + return v; +} + +value_t Math::HypCosineDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::cosh(a.value(i)); + } + return v; +} + +value_t Math::HypTangentDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::tanh(a.value(i)); + } + return v; +} + +value_t Math::MinDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::min(a.value(i), b.value(i)); + } + return v; +} + +value_t Math::MaxDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::max(a.value(i), b.value(i)); + } + return v; +} + +value_t Math::ClampDoubleDoubleDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::clamp(a.value(i), b.value(i), c.value(i)); + } + return v; +} + +value_t Math::FloorDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::floor(a.value(i)); + } + return v; +} + +value_t Math::CeilDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::ceil(a.value(i)); + } + return v; +} + +value_t Math::RoundDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::round(a.value(i)); + } + return v; +} + +value_t Math::AbsDouble(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_DOUBLE, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::abs(a.value(i)); + } + return v; +} + +value_t Math::AddMatrixMatrix(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_MATRIX, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) + b.value(i); + } + return v; +} + +value_t Math::SubtractMatrixMatrix(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_MATRIX, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) - b.value(i); + } + return v; +} + +value_t Math::MultiplyMatrixMatrix(const value_t &a, const value_t &b, const value_t &c) +{ + value_t v(TYPE_MATRIX, std::max(a.size(), b.size())); + for (size_t i = 0; i < v.size(); i++) { + v[i] = a.value(i) * b.value(i); + } + return v; +} + +} diff --git a/app/node/math/math/mathfunctions.h b/app/node/math/math/mathfunctions.h new file mode 100644 index 0000000000..958533a6e9 --- /dev/null +++ b/app/node/math/math/mathfunctions.h @@ -0,0 +1,70 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 MATHFUNCTIONS_H +#define MATHFUNCTIONS_H + +#include "node/value.h" + +namespace olive { + +class Math +{ +public: + static value_t AddDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t SubtractDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t MultiplyDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t DivideDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t ModuloDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t PowerDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t SineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t CosineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t TangentDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t ArcSineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t ArcCosineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t ArcTangentDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t HypSineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t HypCosineDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t HypTangentDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t MinDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t MaxDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t ClampDoubleDoubleDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t FloorDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t CeilDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t RoundDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t AbsDouble(const value_t &a, const value_t &b, const value_t &c); + + static value_t AddMatrixMatrix(const value_t &a, const value_t &b, const value_t &c); + static value_t SubtractMatrixMatrix(const value_t &a, const value_t &b, const value_t &c); + static value_t MultiplyMatrixMatrix(const value_t &a, const value_t &b, const value_t &c); + + static value_t MultiplySamplesDouble(const value_t &a, const value_t &b, const value_t &c); + static value_t MultiplyDoubleSamples(const value_t &a, const value_t &b, const value_t &c); + +}; + +} + +#endif // MATHFUNCTIONS_H diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index e95e0ff99f..c14f2e257a 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -20,8 +20,6 @@ #include "merge.h" -#include "node/traverser.h" - namespace olive { const QString MergeNode::kBaseIn = QStringLiteral("base_in"); @@ -31,9 +29,9 @@ const QString MergeNode::kBlendIn = QStringLiteral("blend_in"); MergeNode::MergeNode() { - AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kBaseIn, TYPE_TEXTURE, kInputFlagNotKeyframable); - AddInput(kBlendIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + AddInput(kBlendIn, TYPE_TEXTURE, kInputFlagNotKeyframable); SetFlag(kDontShowInParamView); } @@ -67,30 +65,32 @@ void MergeNode::Retranslate() SetInputName(kBlendIn, tr("Blend")); } -ShaderCode MergeNode::GetShaderCode(const ShaderRequest &request) const +ShaderCode MergeNode::GetShaderCode(const QString &id) { - Q_UNUSED(request) - return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag")); } -void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t MergeNode::Value(const ValueParams &p) const { + value_t base_val = GetInputValue(p, kBaseIn); + value_t blend_val = GetInputValue(p, kBlendIn); - TexturePtr base_tex = value[kBaseIn].toTexture(); - TexturePtr blend_tex = value[kBlendIn].toTexture(); + TexturePtr base_tex = base_val.toTexture(); + TexturePtr blend_tex = blend_val.toTexture(); 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(value[kBlendIn]); + return blend_val; } else if (!blend_tex) { // We only have a base texture, no need to alpha over - table->Push(value[kBaseIn]); + return base_val; } else { - table->Push(NodeValue::kTexture, base_tex->toJob(ShaderJob(value)), this); + return base_tex->toJob(CreateShaderJob(p, GetShaderCode)); } } + + return value_t(); } } diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h index 52dd5c097f..b0f64bd22e 100644 --- a/app/node/math/merge/merge.h +++ b/app/node/math/merge/merge.h @@ -40,13 +40,14 @@ class MergeNode : public Node virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kBaseIn; static const QString kBlendIn; private: + static ShaderCode GetShaderCode(const QString &id); + NodeInput* base_in_; NodeInput* blend_in_; diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index eb85571574..437eb8d34c 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -29,9 +29,12 @@ const QString TrigonometryNode::kXIn = QStringLiteral("x_in"); TrigonometryNode::TrigonometryNode() { - AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kMethodIn, TYPE_COMBO, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kXIn, NodeValue::kFloat, 0.0); + AddInput(kXIn, TYPE_DOUBLE, 0.0); + + // Deprecated: Use Math instead, which implements trig functions now too + SetFlag(kDontShowInCreateMenu); } QString TrigonometryNode::Name() const @@ -77,9 +80,9 @@ void TrigonometryNode::Retranslate() SetInputName(kXIn, tr("Value")); } -void TrigonometryNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TrigonometryNode::Value(const ValueParams &p) const { - double x = value[kXIn].toDouble(); + double x = GetInputValue(p, kXIn).toDouble(); switch (static_cast(GetStandardValue(kMethodIn).toInt())) { case kOpSine: @@ -111,7 +114,7 @@ void TrigonometryNode::Value(const NodeValueRow &value, const NodeGlobals &globa break; } - table->Push(NodeValue::kFloat, x, this); + return x; } } diff --git a/app/node/math/trigonometry/trigonometry.h b/app/node/math/trigonometry/trigonometry.h index 21b82a6780..74118c9c11 100644 --- a/app/node/math/trigonometry/trigonometry.h +++ b/app/node/math/trigonometry/trigonometry.h @@ -40,7 +40,7 @@ class TrigonometryNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kMethodIn; static const QString kXIn; diff --git a/app/node/node.cpp b/app/node/node.cpp index c392f783af..1a368ad950 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -26,10 +26,10 @@ #include #include "common/lerp.h" -#include "core.h" +#include "common/qtutils.h" #include "config/config.h" +#include "core.h" #include "node/group/group.h" -#include "node/project/serializer/typeserializer.h" #include "nodeundo.h" #include "project.h" #include "serializeddata.h" @@ -48,7 +48,8 @@ Node::Node() : flags_(kNone), caches_enabled_(true) { - AddInput(kEnabledInput, NodeValue::kBoolean, true); + AddInput(kEnabledInput, TYPE_BOOL, true); + AddOutput(QString()); video_cache_ = new FrameHashCache(this); thumbnail_cache_ = new ThumbnailCache(this); @@ -101,6 +102,7 @@ QString Node::Description() const void Node::Retranslate() { SetInputName(kEnabledInput, tr("Enabled")); + SetOutputName(QString(), tr("Default")); } QVariant Node::data(const DataType &d) const @@ -184,25 +186,53 @@ QBrush Node::brush(qreal top, qreal bottom) const } } -void Node::ConnectEdge(Node *output, const NodeInput &input) +bool Node::ConnectionExistsNodeOnly(const Node *node, const NodeInput &input) +{ + for (auto it = node->output_connections_.cbegin(); it != node->output_connections_.cend(); it++) { + if (it->first.node() == node && it->second == input) { + return true; + } + } + + return false; +} + +bool Node::ConnectionExists(const NodeOutput &output, const NodeInput &input) +{ + for (auto it = output.node()->output_connections_.cbegin(); it != output.node()->output_connections_.cend(); it++) { + if (it->first == output && it->second == input) { + return true; + } + } + + return false; +} + +void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input, int64_t index) { // Ensure graph is the same - Q_ASSERT(input.node()->parent() == output->parent()); + Q_ASSERT(input.node()->parent() == output.node()->parent()); - // Ensure a connection isn't getting overwritten - Q_ASSERT(input.node()->input_connections().find(input) == input.node()->input_connections().end()); + // Ensure connection doesn't already exist + Q_ASSERT(!ConnectionExists(output, input)); // Insert connection on both sides - input.node()->input_connections_[input] = output; - output->output_connections_.push_back(std::pair({output, input})); + auto conn = std::pair({output, input}); + Connections &ic = input.node()->input_connections_; + if (index == -1) { + ic.push_back(conn); + } else { + ic.insert(ic.begin() + index, conn); + } + output.node()->output_connections_.push_back(conn); // Call internal events input.node()->InputConnectedEvent(input.input(), input.element(), output); - output->OutputConnectedEvent(input); + output.node()->OutputConnectedEvent(input); // Emit signals emit input.node()->InputConnected(output, input); - emit output->OutputConnected(output, input); + emit output.node()->OutputConnected(output, input); // Invalidate all if this node isn't ignoring this input if (!(input.node()->GetInputFlags(input.input()) & kInputFlagIgnoreInvalidations)) { @@ -210,27 +240,29 @@ void Node::ConnectEdge(Node *output, const NodeInput &input) } } -void Node::DisconnectEdge(Node *output, const NodeInput &input) +void Node::DisconnectEdge(const NodeOutput &output, const NodeInput &input) { // Ensure graph is the same - Q_ASSERT(input.node()->parent() == output->parent()); + Q_ASSERT(input.node()->parent() == output.node()->parent()); // Ensure connection exists - Q_ASSERT(input.node()->input_connections().at(input) == output); + Q_ASSERT(ConnectionExists(output, input)); // Remove connection from both sides - InputConnections& inputs = input.node()->input_connections_; - inputs.erase(inputs.find(input)); + auto conn = std::pair({output, input}); - OutputConnections& outputs = output->output_connections_; - outputs.erase(std::find(outputs.begin(), outputs.end(), std::pair({output, input}))); + Connections& inputs = input.node()->input_connections_; + inputs.erase(std::find(inputs.begin(), inputs.end(), conn)); + + Connections& outputs = output.node()->output_connections_; + outputs.erase(std::find(outputs.begin(), outputs.end(), conn)); // Call internal events input.node()->InputDisconnectedEvent(input.input(), input.element(), output); - output->OutputDisconnectedEvent(input); + output.node()->OutputDisconnectedEvent(input); emit input.node()->InputDisconnected(output, input); - emit output->OutputDisconnected(output, input); + emit output.node()->OutputDisconnected(output, input); if (!(input.node()->GetInputFlags(input.input()) & kInputFlagIgnoreInvalidations)) { input.node()->InvalidateAll(input.input(), input.element()); @@ -257,6 +289,16 @@ QString Node::GetInputName(const QString &id) const } } +QString Node::GetOutputName(const QString &id) const +{ + for (auto it = outputs_.constBegin(); it != outputs_.constEnd(); it++) { + if (it->id == id) { + return it->name; + } + } + return QString(); +} + bool Node::IsInputHidden(const QString &input) const { return (GetInputFlags(input) & kInputFlagHidden); @@ -307,15 +349,29 @@ bool Node::IsInputConnected(const QString &input, int element) const return GetConnectedOutput(input, element); } -Node *Node::GetConnectedOutput(const QString &input, int element) const +NodeOutput Node::GetConnectedOutput2(const QString &input, int element) const { - for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { - if (it->first.input() == input && it->first.element() == element) { - return it->second; + // NOTE: Only returns the first output connected to this input, there may be more than one + for (auto it = input_connections_.cbegin(); it != input_connections_.cend(); it++) { + if (it->second.input() == input && it->second.element() == element) { + return it->first; } } - return nullptr; + return NodeOutput(); +} + +std::vector Node::GetConnectedOutputs(const QString &input, int element) const +{ + std::vector l; + + for (auto it = input_connections_.cbegin(); it != input_connections_.cend(); it++) { + if (it->second.input() == input && it->second.element() == element) { + l.push_back(it->first); + } + } + + return l; } bool Node::IsUsingStandardValue(const QString &input, int track, int element) const @@ -330,31 +386,28 @@ bool Node::IsUsingStandardValue(const QString &input, int track, int element) co } } -NodeValue::Type Node::GetInputDataType(const QString &id) const +type_t Node::GetInputDataType(const QString &id) const { const Input* i = GetInternalInputData(id); if (i) { - return i->type; + if (i->types.empty()) { + return TYPE_NONE; + } else { + return i->types.front(); + } } else { ReportInvalidInput("get data type of", id, -1); - return NodeValue::kNone; + return TYPE_NONE; } } -void Node::SetInputDataType(const QString &id, const NodeValue::Type &type) +void Node::SetInputDataType(const QString &id, const type_t &type, size_t channels) { Input* input_meta = GetInternalInputData(id); if (input_meta) { - input_meta->type = type; - - int array_sz = InputArraySize(id); - for (int i=-1; iset_data_type(type); - } - - emit InputDataTypeChanged(id, type); + SetInputDataTypeInternal(input_meta, id, type, channels); } else { ReportInvalidInput("set data type of", id, -1); } @@ -372,7 +425,7 @@ bool Node::HasInputProperty(const QString &id, const QString &name) const } } -QHash Node::GetInputProperties(const QString &id) const +QHash Node::GetInputProperties(const QString &id) const { const Input* i = GetInternalInputData(id); @@ -380,11 +433,11 @@ QHash Node::GetInputProperties(const QString &id) const return i->properties; } else { ReportInvalidInput("get property table of", id, -1); - return QHash(); + return QHash(); } } -QVariant Node::GetInputProperty(const QString &id, const QString &name) const +value_t Node::GetInputProperty(const QString &id, const QString &name) const { const Input* i = GetInternalInputData(id); @@ -392,37 +445,43 @@ QVariant Node::GetInputProperty(const QString &id, const QString &name) const return i->properties.value(name); } else { ReportInvalidInput("get property of", id, -1); - return QVariant(); + return value_t(); } } -void Node::SetInputProperty(const QString &id, const QString &name, const QVariant &value) +void Node::SetInputProperty(const QString &id, const QString &name, const value_t &value) { Input* i = GetInternalInputData(id); if (i) { - i->properties.insert(name, value); - - emit InputPropertyChanged(id, name, value); + SetInputPropertyInternal(i, id, name, value); } else { ReportInvalidInput("set property of", id, -1); } } -SplitValue Node::GetSplitValueAtTime(const QString &input, const rational &time, int element) const +value_t Node::GetValueAtTime(const QString &input, const rational &time, int element) const { - SplitValue vals; + const Input* in = GetInternalInputData(input); + if (!in) { + ReportInvalidInput("get value at time of", input, element); + return value_t(); + } - int nb_tracks = GetNumberOfKeyframeTracks(input); + value_t v(in->types.empty() ? TYPE_NONE : in->types.front(), in->channel_count); - for (int i=0;iid_map.size() && in->id_map.at(i) != type_t()) { + c.set_id(in->id_map.at(i)); + } } - return vals; + return v; } -QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const +value_t::component_t Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const { if (!IsUsingStandardValue(input, track, element)) { const NodeKeyframeTrack& key_track = GetKeyframeTracks(input, element).at(track); @@ -437,7 +496,7 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & return key_track.last()->value(); } - NodeValue::Type type = GetInputDataType(input); + type_t type = GetInputDataType(input); // If we're here, the time must be somewhere in between the keyframes NodeKeyframe *before = nullptr, *after = nullptr; @@ -460,9 +519,11 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & } } + bool can_be_interpolated = (type == TYPE_INTEGER || type == TYPE_DOUBLE || type == TYPE_RATIONAL); + if (before) { if (before->time() == time - || ((!NodeValue::type_can_be_interpolated(type) || before->type() == NodeKeyframe::kHold) && after->time() > time)) { + || ((!can_be_interpolated || before->type() == NodeKeyframe::kHold) && after->time() > time)) { // Time == keyframe time, so value is precise return before->value(); @@ -476,12 +537,15 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & // We must interpolate between these keyframes double before_val, after_val, interpolated; - if (type == NodeValue::kRational) { + if (type == TYPE_RATIONAL) { before_val = before->value().value().toDouble(); after_val = after->value().value().toDouble(); + } else if (type == TYPE_INTEGER) { + before_val = before->value().value(); + after_val = after->value().value(); } else { - before_val = before->value().toDouble(); - after_val = after->value().toDouble(); + before_val = before->value().value(); + after_val = after->value().value(); } if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { @@ -519,8 +583,10 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & interpolated = lerp(before_val, after_val, period_progress); } - if (type == NodeValue::kRational) { - return QVariant::fromValue(rational::fromDouble(interpolated)); + if (type == TYPE_RATIONAL) { + return rational::fromDouble(interpolated); + } else if (type == TYPE_INTEGER) { + return int64_t(std::round(interpolated)); } else { return interpolated; } @@ -533,14 +599,7 @@ QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational & return GetSplitStandardValueOnTrack(input, track, element); } -QVariant Node::GetDefaultValue(const QString &input) const -{ - NodeValue::Type type = GetInputDataType(input); - - return NodeValue::combine_track_values_into_normal_value(type, GetSplitDefaultValue(input)); -} - -SplitValue Node::GetSplitDefaultValue(const QString &input) const +value_t Node::GetDefaultValue(const QString &input) const { const Input* i = GetInternalInputData(input); @@ -548,28 +607,21 @@ SplitValue Node::GetSplitDefaultValue(const QString &input) const return i->default_value; } else { ReportInvalidInput("retrieve default value of", input, -1); - return SplitValue(); + return value_t(); } } -QVariant Node::GetSplitDefaultValueOnTrack(const QString &input, int track) const +value_t::component_t Node::GetSplitDefaultValueOnTrack(const QString &input, size_t track) const { - SplitValue val = GetSplitDefaultValue(input); + value_t val = GetDefaultValue(input); if (track < val.size()) { return val.at(track); } else { - return QVariant(); + return value_t::component_t(); } } -void Node::SetDefaultValue(const QString &input, const QVariant &val) -{ - NodeValue::Type type = GetInputDataType(input); - - SetSplitDefaultValue(input, NodeValue::split_normal_value_into_track_values(type, val)); -} - -void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val) +void Node::SetDefaultValue(const QString &input, const value_t &val) { Input* i = GetInternalInputData(input); @@ -580,7 +632,7 @@ void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val) } } -void Node::SetSplitDefaultValueOnTrack(const QString &input, const QVariant &val, int track) +void Node::SetSplitDefaultValueOnTrack(const QString &input, const value_t::component_t &val, size_t track) { Input* i = GetInternalInputData(input); @@ -634,9 +686,15 @@ NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input, } } -int Node::GetNumberOfKeyframeTracks(const QString &id) const +size_t Node::GetNumberOfKeyframeTracks(const QString &id) const { - return NodeValue::get_number_of_keyframe_tracks(GetInputDataType(id)); + const Input* i = GetInternalInputData(id); + if (i) { + return i->channel_count; + } else { + ReportInvalidInput("get number of keyframe tracks of", id, -1); + return 1; + } } NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const @@ -701,29 +759,23 @@ bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int elemen QStringList Node::GetComboBoxStrings(const QString &id) const { - return GetInputProperty(id, QStringLiteral("combo_str")).toStringList(); -} - -QVariant Node::GetStandardValue(const QString &id, int element) const -{ - NodeValue::Type type = GetInputDataType(id); - - return NodeValue::combine_track_values_into_normal_value(type, GetSplitStandardValue(id, element)); + return GetInputProperty(id, QStringLiteral("combo_str")).value(); } -SplitValue Node::GetSplitStandardValue(const QString &id, int element) const +value_t Node::GetStandardValue(const QString &id, int element) const { + const Input *input = GetInternalInputData(id); NodeInputImmediate* imm = GetImmediate(id, element); if (imm) { - return imm->get_split_standard_value(); + return value_t(input->types.empty() ? TYPE_NONE : input->types.front(), imm->get_standard_value()); } else { ReportInvalidInput("get standard value of", id, element); - return SplitValue(); + return value_t(); } } -QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int element) const +value_t::component_t Node::GetSplitStandardValueOnTrack(const QString &input, int track, int element) const { NodeInputImmediate* imm = GetImmediate(input, element); @@ -731,25 +783,18 @@ QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int return imm->get_split_standard_value_on_track(track); } else { ReportInvalidInput("get standard value of", input, element); - return QVariant(); + return value_t::component_t(); } } -void Node::SetStandardValue(const QString &id, const QVariant &value, int element) -{ - NodeValue::Type type = GetInputDataType(id); - - SetSplitStandardValue(id, NodeValue::split_normal_value_into_track_values(type, value), element); -} - -void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int element) +void Node::SetStandardValue(const QString &id, const value_t &value, int element) { NodeInputImmediate* imm = GetImmediate(id, element); if (imm) { imm->set_split_standard_value(value); - for (int i=0; ifirst.input() == id && it->first.element() >= index) { + if (it->second.input() == id && it->second.element() >= index) { // Disconnect this and reconnect it one element down - NodeInput new_edge = it->first; + NodeInput new_edge = it->second; new_edge.set_element(new_edge.element() + 1); - DisconnectEdge(it->second, it->first); - ConnectEdge(it->second, new_edge); + DisconnectEdge(it->first, it->second); + ConnectEdge(it->first, new_edge); } } @@ -826,17 +871,17 @@ void Node::InputArrayRemove(const QString &id, int index) ArrayResizeInternal(id, InputArraySize(id) - 1); // Move connections up - InputConnections copied_edges = input_connections(); + Connections copied_edges = input_connections(); for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) { - if (it->first.input() == id && it->first.element() >= index) { + if (it->second.input() == id && it->second.element() >= index) { // Disconnect this and reconnect it one element up if it's not the element being removed - DisconnectEdge(it->second, it->first); + DisconnectEdge(it->first, it->second); - if (it->first.element() > index) { - NodeInput new_edge = it->first; + if (it->second.element() > index) { + NodeInput new_edge = it->second; new_edge.set_element(new_edge.element() - 1); - ConnectEdge(it->second, new_edge); + ConnectEdge(it->first, new_edge); } } } @@ -864,6 +909,148 @@ int Node::InputArraySize(const QString &id) const } } +value_t Node::GetInputValue(const ValueParams &g, const QString &input, int element, bool autoconversion) const +{ + if (!g.is_cancelled()) { + std::vector outputs = GetConnectedOutputs(input, element); + if (!outputs.empty()) { + Node::ValueHint vh = this->GetValueHintForInput(input, element); + + // Perform swizzle if requested + const SwizzleMap &swizzle = vh.swizzle(); + value_t v; + + if (swizzle.empty()) { + // Pass along first value + v = GetFakeConnectedValue(g, outputs.front(), input, element, autoconversion); + } else { + // Swizzle various + std::vector vals(outputs.size()); + + // Retrieve used values + for (auto it = swizzle.cbegin(); it != swizzle.cend(); it++) { + size_t output_index = it->second.output(); + + if (output_index < outputs.size()) { + value_t &vi = vals[output_index]; + + if (!vi.isValid()) { + vi = GetFakeConnectedValue(g, outputs.at(output_index), input, element, autoconversion); + } + } + } + + // Perform swizzle + for (auto it = swizzle.cbegin(); it != swizzle.cend(); it++) { + const SwizzleMap::From &from = it->second; + size_t to = it->first; + + if (from.output() < vals.size()) { + const value_t &vi = vals.at(from.output()); + + if (from.element() < vi.size()) { + if (to >= v.size()) { + v.resize(to + 1); + } + + if (v.type() == TYPE_NONE) { + v.set_type(vi.type()); + } + + v[to] = vi[from.element()]; + } + } + } + } + + return v; + } else { + TimeRange adjusted_time = InputTimeAdjustment(input, element, g.time(), true); + + if (element == -1 && InputIsArray(input)) { + NodeValueArray array(InputArraySize(input)); + + for (size_t i = 0; i < array.size(); i++) { + array[i] = GetValueAtTime(input, adjusted_time.in(), i); + } + + return array; + } else { + return GetValueAtTime(input, adjusted_time.in(), element); + } + } + } + + return value_t(); +} + +void ConvertDoubleToSamples(const void *context, const SampleJob &input, SampleBuffer &output) +{ + const Node *n = static_cast(context); + + const QString param = input.Get(QStringLiteral("input")).toString(); + const int element = input.Get(QStringLiteral("element")).toInt(); + const TimeRange &time = input.value_params().time(); + + for (size_t i = 0; i < output.sample_count(); i++) { + TimeRange this_sample_time = time + output.audio_params().sample_rate_as_time_base() * i; + value_t v = n->GetInputValue(input.value_params().time_transformed(this_sample_time), param, element, false); + for (int j = 0; j < output.channel_count(); j++) { + output.data(j)[i] = v.toDouble(); + } + } +} + +value_t Node::GetFakeConnectedValue(const ValueParams &g, NodeOutput output, const QString &input, int element, bool autoconversion) const +{ + if (!g.is_cancelled()) { + TimeRange adjusted_time = InputTimeAdjustment(input, element, g.time(), true); + + while (output.IsValid()) { + if (output.node()->is_enabled()) { + ValueParams connp = g.time_transformed(adjusted_time).output_edited(output.output()); + + // Find cached value with this + value_t v; + if (!g.get_cached_value(output.node(), connp, v)) { + v = output.node()->Value(connp); + g.insert_cached_value(output.node(), connp, v); + } + + // Perform conversion if necessary + type_t expected_type = this->GetInputDataType(input); + if (autoconversion && expected_type != TYPE_NONE && expected_type != v.type() && !(this->GetInputFlags(input) & kInputFlagDontAutoConvert)) { + if (v.type() == TYPE_DOUBLE && expected_type == TYPE_SAMPLES) { + // Create a job to generate audio from numbers + SampleJob job(g); + + job.Insert(QStringLiteral("input"), input); + job.Insert(QStringLiteral("element"), element); + + job.set_function(ConvertDoubleToSamples, this); + + v = job; + } else { + bool ok; + v = v.converted(expected_type, &ok); + if (!ok) { + // Return null value instead of converted value because node is probably not set up to + // handle this type (unless kInputFlagDontAutoConvert is specified of course) + v = value_t(); + } + } + } + + return v; + } else { + output = output.node()->GetConnectedOutput2(output.node()->GetEffectInput()); + } + } + } + + return value_t(); +} + void Node::SetValueHintForInput(const QString &input, const ValueHint &hint, int element) { value_hints_.insert({input, element}, hint); @@ -873,11 +1060,27 @@ void Node::SetValueHintForInput(const QString &input, const ValueHint &hint, int InvalidateAll(input, element); } +AbstractParamWidget *Node::GetCustomWidget(const QString &input) const +{ + return nullptr; +} + const NodeKeyframeTrack &Node::GetTrackFromKeyframe(NodeKeyframe *key) const { return GetImmediate(key->input(), key->element())->keyframe_tracks().at(key->track()); } +int64_t Node::GetInputConnectionIndex(const NodeOutput &output, const NodeInput &in) const +{ + for (size_t i = 0; i < input_connections_.size(); i++) { + if (input_connections_.at(i).first == output) { + return i; + } + } + + return -1; +} + NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const { if (element == -1) { @@ -893,7 +1096,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const return nullptr; } -InputFlags Node::GetInputFlags(const QString &input) const +InputFlag Node::GetInputFlags(const QString &input) const { const Input* i = GetInternalInputData(input); @@ -901,7 +1104,7 @@ InputFlags Node::GetInputFlags(const QString &input) const return i->flags; } else { ReportInvalidInput("retrieve flags of", input, -1); - return InputFlags(kInputFlagNormal); + return kInputFlagNormal; } } @@ -921,14 +1124,6 @@ void Node::SetInputFlag(const QString &input, InputFlag f, bool on) } } -void Node::Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const -{ - // Do nothing - Q_UNUSED(value) - Q_UNUSED(globals) - Q_UNUSED(table) -} - void Node::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options) { Q_UNUSED(from) @@ -1002,12 +1197,12 @@ void Node::CopyDependencyGraph(const QVector &src, const QVector for (auto it=src_node->input_connections_.cbegin(); it!=src_node->input_connections_.cend(); it++) { // Determine if the connected node is in our src list - int connection_index = src.indexOf(it->second); + int connection_index = src.indexOf(it->first.node()); if (connection_index > -1) { // Find the equivalent node in the dst list - Node *copied_output = dst.at(connection_index); - NodeInput copied_input = NodeInput(dst_node, it->first.input(), it->first.element()); + NodeOutput copied_output = NodeOutput(dst.at(connection_index), it->first.output()); + NodeInput copied_input = NodeInput(dst_node, it->second.input(), it->second.element()); if (command) { command->add_child(new NodeEdgeAddCommand(copied_output, copied_input)); @@ -1069,24 +1264,28 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre // Go through input connections and copy if non-item and connect if item for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) { - NodeInput input = it->first; - Node* connected = it->second; + NodeInput input = it->second; + NodeOutput output = it->first; + //Node* connected = it->first.node(); Node* connected_copy; - if (connected->IsItem()) { + if (output.node()->IsItem()) { // This is an item and we avoid copying those and just connect to them directly - connected_copy = connected; + connected_copy = output.node(); } else { // Non-item, we want to clone this too - connected_copy = created.value(connected, nullptr); + connected_copy = created.value(output.node(), nullptr); if (!connected_copy) { - connected_copy = CopyNodeAndDependencyGraphMinusItemsInternal(created, connected, command); + connected_copy = CopyNodeAndDependencyGraphMinusItemsInternal(created, output.node(), command); } } NodeInput copied_input = input; copied_input.set_node(copy); - command->add_child(new NodeEdgeAddCommand(connected_copy, copied_input)); + + NodeOutput copied_output(connected_copy, output.output()); + + command->add_child(new NodeEdgeAddCommand(copied_output, copied_input)); command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element()))); } @@ -1125,7 +1324,7 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) void Node::SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options) { - for (const OutputConnection& conn : output_connections_) { + for (const Connection& conn : output_connections_) { // Send clear cache signal to the Node const NodeInput& in = conn.second; @@ -1196,8 +1395,6 @@ bool Node::Load(QXmlStreamReader *reader, SerializedData *data) } } - Q_UNUSED(version) - while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("input")) { LoadInput(reader, data); @@ -1232,17 +1429,29 @@ bool Node::Load(QXmlStreamReader *reader, SerializedData *data) } } - QString output_node_id; + QString output_node_id, output_param; while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("output")) { - output_node_id = reader->readElementText(); + if (version < 2) { + output_node_id = reader->readElementText(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + output_node_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("param")) { + output_param = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + } } else { reader->skipCurrentElement(); } } - data->desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong()}); + data->desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong(), output_param}); } else { reader->skipCurrentElement(); } @@ -1320,7 +1529,7 @@ bool Node::Load(QXmlStreamReader *reader, SerializedData *data) void Node::Save(QXmlStreamWriter *writer) const { - writer->writeAttribute(QStringLiteral("version"), QString::number(1)); + writer->writeAttribute(QStringLiteral("version"), QString::number(2)); writer->writeAttribute(QStringLiteral("id"), this->id()); writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); @@ -1353,10 +1562,13 @@ void Node::Save(QXmlStreamWriter *writer) const for (auto it=this->input_connections().cbegin(); it!=this->input_connections().cend(); it++) { writer->writeStartElement(QStringLiteral("connection")); - writer->writeAttribute(QStringLiteral("input"), it->first.input()); - writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); + writer->writeAttribute(QStringLiteral("input"), it->second.input()); + writer->writeAttribute(QStringLiteral("element"), QString::number(it->second.element())); - writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); + writer->writeStartElement(QStringLiteral("output")); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(it->first.node()))); + writer->writeTextElement(QStringLiteral("param"), it->first.output()); + writer->writeEndElement(); // output writer->writeEndElement(); // connection } @@ -1524,12 +1736,12 @@ void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const bool Node::LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data) { - NodeValue::Type data_type = this->GetInputDataType(input); + type_t data_type = this->GetInputDataType(input); // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see // if this ends up being an issue in the future. - if (data_type == NodeValue::kSubtitleParams) { + if (data_type == ViewerOutput::TYPE_SPARAM) { reader->skipCurrentElement(); return true; } @@ -1541,20 +1753,21 @@ bool Node::LoadImmediate(QXmlStreamReader *reader, const QString &input, int ele while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; + value_t::component_t value_on_track; - if (data_type == NodeValue::kVideoParams) { + if (data_type == ViewerOutput::TYPE_VPARAM) { VideoParams vp; vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap = TypeSerializer::LoadAudioParams(reader); - value_on_track = QVariant::fromValue(ap); + value_on_track = vp; + } else if (data_type == ViewerOutput::TYPE_APARAM) { + AudioParams ap; + ap.load(reader); + value_on_track = ap; } else { QString value_text = reader->readElementText(); if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, true); + value_on_track = value_t::component_t(value_text).converted(TYPE_STRING, data_type); } } @@ -1618,20 +1831,25 @@ void Node::SaveImmediate(QXmlStreamWriter *writer, const QString &input, int ele writer->writeTextElement(QStringLiteral("keyframing"), QString::number(is_keyframing)); } - NodeValue::Type data_type = this->GetInputDataType(input); + type_t data_type = this->GetInputDataType(input); // Write standard value writer->writeStartElement(QStringLiteral("standard")); - foreach (const QVariant& v, this->GetSplitStandardValue(input, element)) { + value_t value = this->GetStandardValue(input, element); + for (size_t i = 0; i < value.size(); i++) { + const value_t::component_t &v = value.at(i); + writer->writeStartElement(QStringLiteral("track")); - if (data_type == NodeValue::kVideoParams) { + // FIXME: We now have converters for custom types, so this should be handled with those instead. + // It'll probably require bumping the project version though... + if (data_type == ViewerOutput::TYPE_VPARAM) { v.value().Save(writer); - } else if (data_type == NodeValue::kAudioParams) { - TypeSerializer::SaveAudioParams(writer, v.value()); + } else if (data_type == ViewerOutput::TYPE_APARAM) { + v.value().save(writer); } else { - writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); + writer->writeCharacters(v.toSerializedString(data_type)); } writer->writeEndElement(); // track @@ -1660,7 +1878,7 @@ void Node::SaveImmediate(QXmlStreamWriter *writer, const QString &input, int ele writer->writeEndElement(); // keyframes } - if (data_type == NodeValue::kColor) { + if (this->HasInputProperty(input, QStringLiteral("col_input"))) { // Save color management information writer->writeTextElement(QStringLiteral("csinput"), this->GetInputProperty(input, QStringLiteral("col_input")).toString()); writer->writeTextElement(QStringLiteral("csdisplay"), this->GetInputProperty(input, QStringLiteral("col_display")).toString()); @@ -1669,30 +1887,64 @@ void Node::SaveImmediate(QXmlStreamWriter *writer, const QString &input, int ele } } -void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index) +void Node::SetInputDataTypeInternal(Node::Input *i, const QString &id, type_t type, size_t channel_count) +{ + QString subtype; + std::vector id_map; + + type = ResolveSpecialType(type, channel_count, subtype, id_map); + + i->types.clear(); + if (type != TYPE_NONE) { + i->types.push_back(type); + } + + i->channel_count = channel_count; + i->id_map = id_map; + + if (!subtype.isEmpty()) { + SetInputPropertyInternal(i, id, QStringLiteral("subtype"), subtype); + } + + emit InputDataTypeChanged(id, type); +} + +void Node::SetInputPropertyInternal(Input *i, const QString &id, const QString &name, const value_t &value) +{ + i->properties.insert(name, value); + + emit InputPropertyChanged(id, name, value); +} + +void Node::InsertInput(const QString &id, type_t type, size_t channel_count, const value_t &default_value, InputFlag flags, int index) { if (id.isEmpty()) { qWarning() << "Rejected adding input with an empty ID on node" << this->id(); return; } - if (HasParamWithID(id)) { - qWarning() << "Failed to add input to node" << this->id() << "- param with ID" << id << "already exists"; + if (HasInputWithID(id)) { + qWarning() << "Failed to add input to node" << this->id() << "- ID" << id << "already exists"; return; } Node::Input i; - i.type = type; - i.default_value = NodeValue::split_normal_value_into_track_values(type, default_value); + i.default_value = default_value; i.flags = flags; i.array_size = 0; + SetInputDataTypeInternal(&i, id, type, channel_count); + input_ids_.insert(index, id); input_data_.insert(index, i); if (!standard_immediates_.value(id, nullptr)) { - standard_immediates_.insert(id, CreateImmediate(id)); + NodeInputImmediate *imm = CreateImmediate(id); + standard_immediates_.insert(id, imm); + if (default_value.isValid()) { + imm->set_split_standard_value(default_value); + } } emit InputAdded(id); @@ -1713,6 +1965,58 @@ void Node::RemoveInput(const QString &id) emit InputRemoved(id); } +void Node::AddOutput(const QString &id, const type_t &type) +{ + for (auto it = outputs_.constBegin(); it != outputs_.constEnd(); it++) { + if (it->id == id) { + qWarning() << "Failed to add output to node" << this->id() << "- ID" << id << "already exists"; + return; + } + } + + Output o; + o.id = id; + o.type = type; + outputs_.append(o); + + emit OutputAdded(id); +} + +void Node::RemoveOutput(const QString &id) +{ + for (auto it = outputs_.begin(); it != outputs_.end(); it++) { + if (it->id == id) { + outputs_.erase(it); + break; + } + } + + emit OutputRemoved(id); +} + +void Node::AddAcceptableTypeForInput(const QString &id, type_t type) +{ + Input* i = GetInternalInputData(id); + + if (i) { + i->types.push_back(type); + } else { + ReportInvalidInput("add acceptable type for", id, -1); + } +} + +std::vector Node::GetAcceptableTypesForInput(const QString &id) const +{ + const Input* i = GetInternalInputData(id); + + if (i) { + return i->types; + } else { + ReportInvalidInput("get acceptable types for", id, -1); + return std::vector(); + } +} + void Node::ReportInvalidInput(const char *attempted_action, const QString& id, int element) const { qWarning() << "Failed to" << attempted_action << "parameter" << id << "element" << element @@ -1724,29 +2028,31 @@ NodeInputImmediate *Node::CreateImmediate(const QString &input) const Input* i = GetInternalInputData(input); if (i) { - return new NodeInputImmediate(i->type, i->default_value); + return new NodeInputImmediate(); } else { ReportInvalidInput("create immediate", input, -1); return nullptr; } } -void Node::ArrayResizeInternal(const QString &id, int size) +void Node::ArrayResizeInternal(const QString &id, size_t size) { - Input* imm = GetInternalInputData(id); + Input* data = GetInternalInputData(id); - if (!imm) { + if (!data) { ReportInvalidInput("set array size", id, -1); return; } - if (imm->array_size != size) { + if (data->array_size != size) { // Update array size - if (imm->array_size < size) { + if (data->array_size < size) { // Size is larger, create any immediates that don't exist QVector& subinputs = array_immediates_[id]; - for (int i=subinputs.size(); iset_split_standard_value(data->default_value); + subinputs.append(imm); } // Note that we do not delete any immediates when decreasing size since the user might still @@ -1754,21 +2060,21 @@ void Node::ArrayResizeInternal(const QString &id, int size) // equal subinputs_.size() } - int old_sz = imm->array_size; - imm->array_size = size; + int old_sz = data->array_size; + data->array_size = size; emit InputArraySizeChanged(id, old_sz, size); ParameterValueChanged(id, -1, TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } } -QString Node::GetConnectCommandString(Node *output, const NodeInput &input) +QString Node::GetConnectCommandString(const NodeOutput &output, const NodeInput &input) { - return tr("Connected %1 to %2 - %3").arg(output->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName()); + return tr("Connected %1 to %2 - %3").arg(output.node()->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName()); } -QString Node::GetDisconnectCommandString(Node *output, const NodeInput &input) +QString Node::GetDisconnectCommandString(const NodeOutput &output, const NodeInput &input) { - return tr("Disconnected %1 from %2 - %3").arg(output->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName()); + return tr("Disconnected %1 from %2 - %3").arg(output.node()->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName()); } int Node::GetInternalInputArraySize(const QString &input) @@ -1779,10 +2085,10 @@ int Node::GetInternalInputArraySize(const QString &input) void FindWaysNodeArrivesHereRecursively(const Node *output, const Node *input, QVector &v) { for (auto it=input->input_connections().cbegin(); it!=input->input_connections().cend(); it++) { - if (it->second == output) { - v.append(it->first); + if (it->first.node() == output) { + v.append(it->second); } else { - FindWaysNodeArrivesHereRecursively(output, it->second, v); + FindWaysNodeArrivesHereRecursively(output, it->first.node(), v); } } } @@ -1809,6 +2115,16 @@ void Node::SetInputName(const QString &id, const QString &name) } } +void Node::SetOutputName(const QString &id, const QString &name) +{ + for (auto it = outputs_.begin(); it != outputs_.end(); it++) { + if (it->id == id) { + it->name = name; + break; + } + } +} + const QString &Node::GetLabel() const { return label_; @@ -1885,12 +2201,12 @@ void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool incl if (include_connections) { // Copy all connections for (auto it=src->input_connections().cbegin(); it!=src->input_connections().cend(); it++) { - if (!traverse_arrays && it->first.element() != -1) { + if (!traverse_arrays && it->second.element() != -1) { continue; } - auto conn_output = it->second; - NodeInput conn_input(dst, input, it->first.element()); + auto conn_output = it->first; + NodeInput conn_input(dst, input, it->second.element()); if (command) { command->add_child(new NodeEdgeAddCommand(conn_output, conn_input)); @@ -1911,11 +2227,11 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, NodeInput dst_input(dst, input, dst_element); // Copy standard value - SplitValue standard = src->GetSplitStandardValue(input, src_element); + value_t standard = src->GetStandardValue(input, src_element); if (command) { command->add_child(new NodeParamSetSplitStandardValueCommand(dst_input, standard)); } else { - dst->SetSplitStandardValue(input, standard, dst_element); + dst->SetStandardValue(input, standard, dst_element); } // Copy keyframes @@ -1968,7 +2284,7 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, void GetDependenciesRecursively(QVector& list, const Node* node, bool traverse, bool exclusive_only) { for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node* connected_node = it->second; + Node* connected_node = it->first.node(); if (!exclusive_only || !connected_node->IsItem()) { if (!list.contains(connected_node)) { @@ -2014,25 +2330,10 @@ QVector Node::GetImmediateDependencies() const return GetDependenciesInternal(false, false); } -ShaderCode Node::GetShaderCode(const ShaderRequest &request) const -{ - return ShaderCode(QString(), QString()); -} - -void Node::ProcessSamples(const NodeValueRow &, const SampleBuffer &, SampleBuffer &, int) const -{ -} - -void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const -{ - Q_UNUSED(frame) - Q_UNUSED(job) -} - bool Node::InputsFrom(Node *n, bool recursively) const { for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { - Node *connected = it->second; + Node *connected = it->first.node(); if (connected == n) { return true; @@ -2047,7 +2348,7 @@ bool Node::InputsFrom(Node *n, bool recursively) const bool Node::InputsFrom(const QString &id, bool recursively) const { for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) { - Node *connected = it->second; + Node *connected = it->first.node(); if (connected->id() == id) { return true; @@ -2062,13 +2363,13 @@ bool Node::InputsFrom(const QString &id, bool recursively) const void Node::DisconnectAll() { // Disconnect inputs (copy map since internal map will change as we disconnect) - InputConnections copy = input_connections_; + Connections copy = input_connections_; for (auto it=copy.cbegin(); it!=copy.cend(); it++) { - DisconnectEdge(it->second, it->first); + DisconnectEdge(it->first, it->second); } while (!output_connections_.empty()) { - OutputConnection conn = output_connections_.back(); + Connection conn = output_connections_.back(); DisconnectEdge(conn.first, conn.second); } } @@ -2194,7 +2495,43 @@ void Node::ClearElement(const QString& input, int index) SetInputIsKeyframing(input, false, index); } - SetSplitStandardValue(input, GetSplitDefaultValue(input), index); + SetStandardValue(input, GetDefaultValue(input), index); +} + +type_t Node::ResolveSpecialType(type_t type, size_t &channel_count, QString &subtype, std::vector &id_map) +{ + if (type == TYPE_VEC2) { + channel_count = 2; + id_map = value_t::XYZW_IDS; + return TYPE_DOUBLE; + } else if (type == TYPE_VEC3) { + channel_count = 3; + id_map = value_t::XYZW_IDS; + return TYPE_DOUBLE; + } else if (type == TYPE_VEC4) { + channel_count = 4; + id_map = value_t::XYZW_IDS; + return TYPE_DOUBLE; + } else if (type == TYPE_COLOR) { + channel_count = 4; + subtype = QStringLiteral("color"); + id_map = value_t::RGBA_IDS; + return TYPE_DOUBLE; + } else if (type == TYPE_BOOL) { + subtype = QStringLiteral("bool"); + return TYPE_INTEGER; + } else if (type == TYPE_COMBO) { + subtype = QStringLiteral("combo"); + return TYPE_INTEGER; + } else if (type == TYPE_FONT) { + subtype = QStringLiteral("font"); + return TYPE_STRING; + } else if (type == TYPE_FILE) { + subtype = QStringLiteral("file"); + return TYPE_STRING; + } + + return type; } void Node::InputValueChangedEvent(const QString &input, int element) @@ -2203,14 +2540,14 @@ void Node::InputValueChangedEvent(const QString &input, int element) Q_UNUSED(element) } -void Node::InputConnectedEvent(const QString &input, int element, Node *output) +void Node::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { Q_UNUSED(input) Q_UNUSED(element) Q_UNUSED(output) } -void Node::InputDisconnectedEvent(const QString &input, int element, Node *output) +void Node::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { Q_UNUSED(input) Q_UNUSED(element) @@ -2235,7 +2572,12 @@ void Node::childEvent(QChildEvent *event) NodeInput i(this, key->input(), key->element()); if (event->type() == QEvent::ChildAdded) { - GetImmediate(key->input(), key->element())->insert_keyframe(key); + NodeInputImmediate *imm = GetImmediate(key->input(), key->element()); + int old_sz = imm->keyframe_tracks().size(); + imm->insert_keyframe(key); + for (int j = old_sz; j < imm->keyframe_tracks().size(); j++) { + emit KeyframeTrackAdded(key->input(), key->element(), j); + } connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange); connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange); @@ -2353,7 +2695,7 @@ void Node::InvalidateFromKeyframeTypeChanged() emit KeyframeTypeChanged(key); } -void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) +void Node::SetValueAtTime(const NodeInput &input, const rational &time, const value_t::component_t &value, size_t track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) { if (input.IsKeyframing()) { rational node_time = time; @@ -2364,9 +2706,9 @@ void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QV command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value)); } else { // No existing key, create a new one - int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input.node()->GetInputDataType(input.input())); - for (int i=0; iGetNumberOfKeyframeTracks(input.input()); + for (size_t i=0; i &vec, Node *from, Node *to, int &path_index) { - for (auto it=from->output_connections().cbegin(); it!=from->output_connections().cend(); it++) { - const NodeInput &next = it->second; - - vec.push_back(next); + for (auto it = to->input_connections().cbegin(); it != to->input_connections().cend(); it++) { + vec.push_front(it->second); - if (next.node() == to) { + if (it->first.node() == from) { // Found a path! Determine if it's the index we want if (path_index == 0) { // It is! @@ -2409,11 +2749,11 @@ bool FindPathInternal(std::list &vec, Node *from, Node *to, int &path } } - if (FindPathInternal(vec, next.node(), to, path_index)) { + if (FindPathInternal(vec, from, it->first.node(), path_index)) { return true; } - vec.pop_back(); + vec.pop_front(); } return false; @@ -2438,20 +2778,11 @@ bool Node::ValueHint::load(QXmlStreamReader *reader) Q_UNUSED(version) while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - QVector types; - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - types.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - this->set_type(types); - } else if (reader->name() == QStringLiteral("index")) { - this->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("tag")) { - this->set_tag(reader->readElementText()); + if (reader->name() == QStringLiteral("tag")) { + QString output = reader->readElementText(); + qDebug() << "FIXME: need to propagate output" << output; + } else if (reader->name() == QStringLiteral("swizzle")) { + this->swizzle_.load(reader); } else { reader->skipCurrentElement(); } @@ -2462,19 +2793,15 @@ bool Node::ValueHint::load(QXmlStreamReader *reader) void Node::ValueHint::save(QXmlStreamWriter *writer) const { - writer->writeAttribute(QStringLiteral("version"), QString::number(1)); - - writer->writeStartElement(QStringLiteral("types")); + writer->writeAttribute(QStringLiteral("version"), QString::number(2)); - for (auto it=this->types().cbegin(); it!=this->types().cend(); it++) { - writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); - } - - writer->writeEndElement(); // types + if (!this->swizzle_.empty()) { + writer->writeStartElement(QStringLiteral("swizzle")); - writer->writeTextElement(QStringLiteral("index"), QString::number(this->index())); + this->swizzle_.save(writer); - writer->writeTextElement(QStringLiteral("tag"), this->tag()); + writer->writeEndElement(); // swizzle + } } bool Node::Position::load(QXmlStreamReader *reader) diff --git a/app/node/node.h b/app/node/node.h index 6058122dee..75db996be0 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -35,14 +35,15 @@ #include "node/keyframe.h" #include "node/inputimmediate.h" #include "node/param.h" +#include "node/swizzlemap.h" #include "render/audioplaybackcache.h" #include "render/audiowaveformcache.h" #include "render/framehashcache.h" +#include "render/job/colortransformjob.h" #include "render/job/generatejob.h" #include "render/job/samplejob.h" #include "render/job/shaderjob.h" -#include "render/shadercode.h" -#include "splitvalue.h" +#include "widget/nodeparamview/paramwidget/abstractparamwidget.h" namespace olive { @@ -105,6 +106,15 @@ class Node : public QObject kIsItem = 0x10 }; + static constexpr type_t TYPE_VEC2 = "vec2"; + static constexpr type_t TYPE_VEC3 = "vec3"; + static constexpr type_t TYPE_VEC4 = "vec4"; + static constexpr type_t TYPE_BOOL = "bool"; + static constexpr type_t TYPE_COLOR = "color"; + static constexpr type_t TYPE_COMBO = "combo"; + static constexpr type_t TYPE_FONT = "font"; + static constexpr type_t TYPE_FILE = "file"; + struct ContextPair { Node *node; Node *context; @@ -200,79 +210,23 @@ class Node : public QObject virtual QVariant data(const DataType &d) const; - const QVector& inputs() const - { - return input_ids_; - } + const QVector& inputs() const { return input_ids_; } - virtual QVector IgnoreInputsForRendering() const + struct Output { - return QVector(); - } - - class ActiveElements - { - public: - enum Mode { - kAllElements, - kSpecified, - kNoElements - }; - - ActiveElements(Mode m = kAllElements) - { - mode_ = m; - } - - Mode mode() const { return mode_; } - std::list elements() const { return elements_; } - - void add(int e) - { - elements_.push_back(e); - mode_ = kSpecified; - } - - private: - Mode mode_; - std::list elements_; - + QString id; + QString name; + type_t type; }; + const QVector& outputs() const { return outputs_; } - virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const - { - return ActiveElements::kAllElements; - } - - bool HasInputWithID(const QString& id) const - { - return input_ids_.contains(id); - } - - bool HasParamWithID(const QString& id) const - { - return HasInputWithID(id); - } - - FrameHashCache* video_frame_cache() const - { - return video_cache_; - } - - ThumbnailCache* thumbnail_cache() const - { - return thumbnail_cache_; - } - - AudioPlaybackCache* audio_playback_cache() const - { - return audio_cache_; - } + bool HasInputWithID(const QString& id) const { return input_ids_.contains(id); } - AudioWaveformCache* waveform_cache() const - { - return waveform_cache_; - } + // Node caches + FrameHashCache* video_frame_cache() const { return video_cache_; } + ThumbnailCache* thumbnail_cache() const { return thumbnail_cache_; } + AudioPlaybackCache* audio_playback_cache() const { return audio_cache_; } + AudioWaveformCache* waveform_cache() const { return waveform_cache_; } virtual TimeRange GetVideoCacheRange() const { return TimeRange(); } virtual TimeRange GetAudioCacheRange() const { return TimeRange(); } @@ -384,9 +338,13 @@ class Node : public QObject } } - static void ConnectEdge(Node *output, const NodeInput& input); + static bool ConnectionExistsNodeOnly(const Node *node, const NodeInput& input); + + static bool ConnectionExists(const NodeOutput &output, const NodeInput& input); - static void DisconnectEdge(Node *output, const NodeInput& input); + static void ConnectEdge(const NodeOutput &output, const NodeInput& input, int64_t index = -1); + + static void DisconnectEdge(const NodeOutput &output, const NodeInput& input); void CopyCacheUuidsFrom(Node *n); @@ -395,7 +353,10 @@ class Node : public QObject virtual QString GetInputName(const QString& id) const; + QString GetOutputName(const QString &id) const; + void SetInputName(const QString& id, const QString& name); + void SetOutputName(const QString &id, const QString &name); bool IsInputHidden(const QString& input) const; bool IsInputConnectable(const QString& input) const; @@ -419,15 +380,6 @@ class Node : public QObject return IsInputConnected(input.input(), input.element()); } - virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const - { - return IsInputConnected(input, element); - } - bool IsInputConnectedForRender(const NodeInput& input) const - { - return IsInputConnectedForRender(input.input(), input.element()); - } - bool IsInputStatic(const QString& input, int element = -1) const { return !IsInputConnected(input, element) && !IsInputKeyframing(input, element); @@ -438,70 +390,63 @@ class Node : public QObject return IsInputStatic(input.input(), input.element()); } - Node *GetConnectedOutput(const QString& input, int element = -1) const; + NodeOutput GetConnectedOutput2(const QString& input, int element = -1) const; + NodeOutput GetConnectedOutput2(const NodeInput& input) const + { + return GetConnectedOutput2(input.input(), input.element()); + } - Node *GetConnectedOutput(const NodeInput& input) const + Node *GetConnectedOutput(const QString& input, int element = -1) const { - return GetConnectedOutput(input.input(), input.element()); + return GetConnectedOutput2(input, element).node(); } - virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const + Node *GetConnectedOutput(const NodeInput& input) const { - return GetConnectedOutput(input, element); + return GetConnectedOutput(input.input(), input.element()); } - Node *GetConnectedRenderOutput(const NodeInput& input) const + std::vector GetConnectedOutputs(const QString &input, int element = -1) const; + std::vector GetConnectedOutputs(const NodeInput &input) const { - return GetConnectedRenderOutput(input.input(), input.element()); + return GetConnectedOutputs(input.input(), input.element()); } + bool is_enabled() const { return GetStandardValue(kEnabledInput).toBool(); } + void set_enabled(bool e) { SetStandardValue(kEnabledInput, e); } + bool IsUsingStandardValue(const QString& input, int track, int element = -1) const; - NodeValue::Type GetInputDataType(const QString& id) const; - void SetInputDataType(const QString& id, const NodeValue::Type& type); + type_t GetInputDataType(const QString& id) const; + void SetInputDataType(const QString& id, const type_t& type, size_t channels = 1); bool HasInputProperty(const QString& id, const QString& name) const; - QHash GetInputProperties(const QString& id) const; - QVariant GetInputProperty(const QString& id, const QString& name) const; - void SetInputProperty(const QString& id, const QString& name, const QVariant& value); + QHash GetInputProperties(const QString& id) const; + value_t GetInputProperty(const QString& id, const QString& name) const; + void SetInputProperty(const QString& id, const QString& name, const value_t &value); - QVariant GetValueAtTime(const QString& input, const rational& time, int element = -1) const - { - NodeValue::Type type = GetInputDataType(input); - - return NodeValue::combine_track_values_into_normal_value(type, GetSplitValueAtTime(input, time, element)); - } - - QVariant GetValueAtTime(const NodeInput& input, const rational& time) + value_t GetValueAtTime(const QString& input, const rational& time, int element = -1) const; + value_t GetValueAtTime(const NodeInput& input, const rational& time) { return GetValueAtTime(input.input(), time, input.element()); } - SplitValue GetSplitValueAtTime(const QString& input, const rational& time, int element = -1) const; - - SplitValue GetSplitValueAtTime(const NodeInput& input, const rational& time) - { - return GetSplitValueAtTime(input.input(), time, input.element()); - } - - QVariant GetSplitValueAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const; - QVariant GetSplitValueAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const + value_t::component_t GetSplitValueAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const; + value_t::component_t GetSplitValueAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const { return GetSplitValueAtTimeOnTrack(input.input(), time, track, input.element()); } - QVariant GetSplitValueAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const + value_t::component_t GetSplitValueAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const { return GetSplitValueAtTimeOnTrack(input.input(), time, input.track()); } - QVariant GetDefaultValue(const QString& input) const; - SplitValue GetSplitDefaultValue(const QString& input) const; - QVariant GetSplitDefaultValueOnTrack(const QString& input, int track) const; + value_t GetDefaultValue(const QString& input) const; + value_t::component_t GetSplitDefaultValueOnTrack(const QString& input, size_t track) const; - void SetDefaultValue(const QString& input, const QVariant &val); - void SetSplitDefaultValue(const QString& input, const SplitValue &val); - void SetSplitDefaultValueOnTrack(const QString& input, const QVariant &val, int track); + void SetDefaultValue(const QString& input, const value_t &val); + void SetSplitDefaultValueOnTrack(const QString& input, const value_t::component_t &val, size_t track); const QVector& GetKeyframeTracks(const QString& input, int element) const; const QVector& GetKeyframeTracks(const NodeInput& input) const @@ -538,7 +483,7 @@ class Node : public QObject return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, input.track()); } - int GetNumberOfKeyframeTracks(const QString& id) const; + size_t GetNumberOfKeyframeTracks(const QString& id) const; int GetNumberOfKeyframeTracks(const NodeInput& id) const { return GetNumberOfKeyframeTracks(id.input()); @@ -576,38 +521,26 @@ class Node : public QObject QStringList GetComboBoxStrings(const QString& id) const; - QVariant GetStandardValue(const QString& id, int element = -1) const; - QVariant GetStandardValue(const NodeInput& id) const + value_t GetStandardValue(const QString& id, int element = -1) const; + value_t GetStandardValue(const NodeInput& id) const { return GetStandardValue(id.input(), id.element()); } - SplitValue GetSplitStandardValue(const QString& id, int element = -1) const; - SplitValue GetSplitStandardValue(const NodeInput& id) const - { - return GetSplitStandardValue(id.input(), id.element()); - } - - QVariant GetSplitStandardValueOnTrack(const QString& input, int track, int element = -1) const; - QVariant GetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id) const + value_t::component_t GetSplitStandardValueOnTrack(const QString& input, int track, int element = -1) const; + value_t::component_t GetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id) const { return GetSplitStandardValueOnTrack(id.input().input(), id.track(), id.input().element()); } - void SetStandardValue(const QString& id, const QVariant& value, int element = -1); - void SetStandardValue(const NodeInput& id, const QVariant& value) + void SetStandardValue(const QString& id, const value_t& value, int element = -1); + void SetStandardValue(const NodeInput& id, const value_t& value) { SetStandardValue(id.input(), value, id.element()); } - void SetSplitStandardValue(const QString& id, const SplitValue& value, int element = -1); - void SetSplitStandardValue(const NodeInput& id, const SplitValue& value) - { - SetSplitStandardValue(id.input(), value, id.element()); - } - - void SetSplitStandardValueOnTrack(const QString& id, int track, const QVariant& value, int element = -1); - void SetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id, const QVariant& value) + void SetSplitStandardValueOnTrack(const QString& id, int track, const value_t::component_t& value, int element = -1); + void SetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id, const value_t::component_t& value) { SetSplitStandardValueOnTrack(id.input().input(), id.track(), value, id.input().element()); } @@ -635,6 +568,9 @@ class Node : public QObject int InputArraySize(const QString& id) const; + value_t GetInputValue(const ValueParams &g, const QString &input, int element = -1, bool autoconversion = true) const; + value_t GetFakeConnectedValue(const ValueParams &g, NodeOutput output, const QString &input, int element = -1, bool autoconversion = true) const; + NodeInputImmediate* GetImmediate(const QString& input, int element) const; NodeInput GetEffectInput() @@ -647,48 +583,19 @@ class Node : public QObject return effect_input_; } - class ValueHint { + class ValueHint + { public: - explicit ValueHint(const QVector &types = QVector(), int index = -1, const QString &tag = QString()) : - type_(types), - index_(index), - tag_(tag) - { - } + ValueHint() = default; - explicit ValueHint(const QVector &types, const QString &tag) : - type_(types), - index_(-1), - tag_(tag) - { - } - - explicit ValueHint(int index) : - index_(index) - { - } - - explicit ValueHint(const QString &tag) : - index_(-1), - tag_(tag) - { - } - - const QVector &types() const { return type_; } - const int &index() const { return index_; } - const QString& tag() const { return tag_; } - - void set_type(const QVector &type) { type_ = type; } - void set_index(const int &index) { index_ = index; } - void set_tag(const QString &tag) { tag_ = tag; } + const SwizzleMap &swizzle() const { return swizzle_; } + void set_swizzle(const SwizzleMap &m) { swizzle_ = m; } bool load(QXmlStreamReader *reader); void save(QXmlStreamWriter *writer) const; private: - QVector type_; - int index_; - QString tag_; + SwizzleMap swizzle_; }; @@ -704,9 +611,12 @@ class Node : public QObject void SetValueHintForInput(const QString &input, const ValueHint &hint, int element = -1); + virtual AbstractParamWidget *GetCustomWidget(const QString &input) const; + const NodeKeyframeTrack& GetTrackFromKeyframe(NodeKeyframe* key) const; - using InputConnections = std::map; + using Connection = std::pair; + using Connections = std::vector; /** * @brief Return map of input connections @@ -714,13 +624,12 @@ class Node : public QObject * Inputs can only have one connection, so the key is the input connected and the value is the * output that it's connected to. */ - const InputConnections& input_connections() const + const Connections& input_connections() const { return input_connections_; } - using OutputConnection = std::pair; - using OutputConnections = std::vector; + int64_t GetInputConnectionIndex(const NodeOutput &output, const NodeInput &i) const; /** * @brief Return list of output connections @@ -728,7 +637,7 @@ class Node : public QObject * An output can connect an infinite amount of inputs, so in this map, the key is the output and * the value is a vector of inputs. */ - const OutputConnections& output_connections() const + const Connections& output_connections() const { return output_connections_; } @@ -751,42 +660,6 @@ class Node : public QObject */ QVector GetImmediateDependencies() const; - struct ShaderRequest - { - ShaderRequest(const QString &shader_id) - { - id = shader_id; - } - - ShaderRequest(const QString &shader_id, const QString &shader_stub) - { - id = shader_id; - stub = shader_stub; - } - - QString id; - QString stub; - }; - - /** - * @brief Generate hardware accelerated code for this Node - */ - virtual ShaderCode GetShaderCode(const ShaderRequest &request) const; - - /** - * @brief If Value() pushes a ShaderJob, this is the function that will process them. - */ - virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const; - - /** - * @brief If Value() pushes a GenerateJob, override this function for the image to create - * - * @param frame - * - * The destination buffer. It will already be allocated and ready for writing to. - */ - virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const; - /** * @brief Returns whether this node ever receives an input from a particular node instance */ @@ -906,7 +779,7 @@ class Node : public QObject * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior * of the NodeParam objects will handle everything related to it automatically. */ - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const; + virtual value_t Value(const ValueParams &p) const {return value_t();} bool HasGizmos() const { @@ -918,9 +791,9 @@ class Node : public QObject return gizmos_; } - virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { return QTransform(); } + virtual QTransform GizmoTransformation(const ValueParams &p) const { return QTransform(); } - virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals){} + virtual void UpdateGizmoPositions(const ValueParams &p){} const QString& GetLabel() const; void SetLabel(const QString& s); @@ -962,59 +835,91 @@ class Node : public QObject folder_ = folder; } - InputFlags GetInputFlags(const QString& input) const; + InputFlag GetInputFlags(const QString& input) const; void SetInputFlag(const QString &input, InputFlag f, bool on = true); virtual void LoadFinishedEvent(){} virtual void ConnectedToPreviewEvent(){} - static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); + static void SetValueAtTime(const NodeInput &input, const rational &time, const value_t::component_t &value, size_t track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); /** * @brief Find path starting at `from` that outputs to arrive at `to` */ static std::list FindPath(Node *from, Node *to, int path_index); - void ArrayResizeInternal(const QString& id, int size); + void ArrayResizeInternal(const QString& id, size_t size); virtual void AddedToGraphEvent(Project *p){} virtual void RemovedFromGraphEvent(Project *p){} - static QString GetConnectCommandString(Node *output, const NodeInput &input); - static QString GetDisconnectCommandString(Node *output, const NodeInput &input); + static QString GetConnectCommandString(const NodeOutput &output, const NodeInput &input); + static QString GetDisconnectCommandString(const NodeOutput &output, const NodeInput &input); + + std::vector GetAcceptableTypesForInput(const QString &id) const; static const QString kEnabledInput; protected: - void InsertInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags, int index); + void InsertInput(const QString& id, type_t type, size_t channel_count, const value_t& default_value, InputFlag flags, int index); + void InsertInput(const QString& id, type_t type, const value_t& default_value, InputFlag flags, int index) + { + return InsertInput(id, type, 1, default_value, flags, index); + } - void PrependInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal)) + void PrependInput(const QString& id, type_t type, size_t channel_count, const value_t& default_value, InputFlag flags = kInputFlagNormal) { - InsertInput(id, type, default_value, flags, 0); + InsertInput(id, type, channel_count, default_value, flags, 0); } - void PrependInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal)) + void PrependInput(const QString& id, type_t type, const value_t& default_value, InputFlag flags = kInputFlagNormal) { - PrependInput(id, type, QVariant(), flags); + InsertInput(id, type, 1, default_value, flags, 0); } - void AddInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal)) + void PrependInput(const QString& id, type_t type, InputFlag flags = kInputFlagNormal) { - InsertInput(id, type, default_value, flags, input_ids_.size()); + PrependInput(id, type, value_t(), flags); } - void AddInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal)) + void PrependInput(const QString& id, InputFlag flags = kInputFlagNormal) { - AddInput(id, type, QVariant(), flags); + PrependInput(id, TYPE_NONE, value_t(), flags); + } + + void AddInput(const QString& id, type_t type, size_t channel_count, const value_t& default_value, InputFlag flags = kInputFlagNormal) + { + InsertInput(id, type, channel_count, default_value, flags, input_ids_.size()); + } + + void AddInput(const QString& id, type_t type, const value_t& default_value, InputFlag flags = kInputFlagNormal) + { + return AddInput(id, type, 1, default_value, flags); + } + + void AddInput(const QString& id, type_t type, InputFlag flags = kInputFlagNormal) + { + AddInput(id, type, value_t(), flags); + } + + void AddInput(const QString& id, InputFlag flags = kInputFlagNormal) + { + AddInput(id, TYPE_NONE, value_t(), flags); } void RemoveInput(const QString& id); + void AddOutput(const QString &id, const type_t &type = TYPE_NONE); + + void RemoveOutput(const QString &id); + void SetComboBoxStrings(const QString& id, const QStringList& strings) { - SetInputProperty(id, QStringLiteral("combo_str"), strings); + SetInputProperty(id, QStringLiteral("combo_str"), value_t("strl", strings)); } + void AddAcceptableTypeForInput(const QString &id, type_t type); + void SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options); enum GizmoScaleHandles { @@ -1033,9 +938,9 @@ class Node : public QObject virtual void InputValueChangedEvent(const QString& input, int element); - virtual void InputConnectedEvent(const QString& input, int element, Node *output); + virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput &output); - virtual void InputDisconnectedEvent(const QString& input, int element, Node *output); + virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput &output); virtual void OutputConnectedEvent(const NodeInput& input); @@ -1080,8 +985,27 @@ class Node : public QObject return AddDraggableGizmo(refs, behavior); } + ShaderJob CreateShaderJob(const ValueParams &p, ShaderJob::GetShaderCodeFunction_t f) const + { + ShaderJob j = CreateJob(p); + j.set_function(f); + return j; + } + + GenerateJob CreateGenerateJob(const ValueParams &p, GenerateJob::GenerateFrameFunction_t f) const + { + GenerateJob j = CreateJob(p); + j.set_function(f); + return j; + } + + ColorTransformJob CreateColorTransformJob(const ValueParams &p) const + { + return CreateJob(p); + } + protected slots: - virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::core::rational &time){} + virtual void GizmoDragStart(const olive::ValueParams &p, double x, double y, const rational &time){} virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers){} @@ -1095,17 +1019,17 @@ protected slots: void ValueChanged(const NodeInput& input, const TimeRange& range); - void InputConnected(Node *output, const NodeInput& input); + void InputConnected(const NodeOutput &output, const NodeInput& input); - void InputDisconnected(Node *output, const NodeInput& input); + void InputDisconnected(const NodeOutput &output, const NodeInput& input); - void OutputConnected(Node *output, const NodeInput& input); + void OutputConnected(const NodeOutput &output, const NodeInput& input); - void OutputDisconnected(Node *output, const NodeInput& input); + void OutputDisconnected(const NodeOutput &output, const NodeInput& input); void InputValueHintChanged(const NodeInput& input); - void InputPropertyChanged(const QString& input, const QString& key, const QVariant& value); + void InputPropertyChanged(const QString& input, const QString& key, const value_t& value); void LinksChanged(); @@ -1123,13 +1047,19 @@ protected slots: void KeyframeEnableChanged(const NodeInput& input, bool enabled); + void KeyframeTrackAdded(const QString &input, int element, int track); + void InputAdded(const QString& id); void InputRemoved(const QString& id); + void OutputAdded(const QString &id); + + void OutputRemoved(const QString &id); + void InputNameChanged(const QString& id, const QString& name); - void InputDataTypeChanged(const QString& id, NodeValue::Type type); + void InputDataTypeChanged(const QString& id, type_t type); void AddedToGraph(Project* graph); @@ -1141,16 +1071,18 @@ protected slots: void NodeRemovedFromContext(Node *node); - void InputFlagsChanged(const QString &input, const InputFlags &flags); + void InputFlagsChanged(const QString &input, const InputFlag &flags); private: struct Input { - NodeValue::Type type; - InputFlags flags; - SplitValue default_value; - QHash properties; + std::vector types; + InputFlag flags; + value_t default_value; + QHash properties; QString human_name; - int array_size; + size_t array_size; + size_t channel_count; + std::vector id_map; }; NodeInputImmediate* CreateImmediate(const QString& input); @@ -1182,6 +1114,9 @@ protected slots: } } + void SetInputDataTypeInternal(Node::Input *i, const QString &id, type_t type, size_t channel_count); + void SetInputPropertyInternal(Node::Input *i, const QString &id, const QString& name, const value_t &value); + void ReportInvalidInput(const char* attempted_action, const QString &id, int element) const; static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap &created, Node *node, MultiUndoCommand *command); @@ -1200,10 +1135,20 @@ protected slots: template static void FindInputNodeInternal(const Node* n, QVector& list, int maximum); + template + T CreateJob(const ValueParams &p) const + { + T job; + for (const QString &input : inputs()) { + job.Insert(input, GetInputValue(p, input)); + } + return job; + } + QVector GetDependenciesInternal(bool traverse, bool exclusive_only) const; - void ParameterValueChanged(const QString &input, int element, const olive::core::TimeRange &range); - void ParameterValueChanged(const NodeInput& input, const olive::core::TimeRange &range) + void ParameterValueChanged(const QString &input, int element, const TimeRange &range); + void ParameterValueChanged(const NodeInput& input, const TimeRange &range) { ParameterValueChanged(input.input(), input.element(), range); } @@ -1220,6 +1165,8 @@ protected slots: void ClearElement(const QString &input, int index); + type_t ResolveSpecialType(type_t type, size_t &channel_count, QString &subtype, std::vector &id_map); + /** * @brief Custom user label for node */ @@ -1237,14 +1184,15 @@ protected slots: QVector input_ids_; QVector input_data_; + QVector outputs_; QMap standard_immediates_; QMap > array_immediates_; - InputConnections input_connections_; + Connections input_connections_; - OutputConnections output_connections_; + Connections output_connections_; Folder* folder_; @@ -1328,7 +1276,7 @@ template void Node::FindInputNodeInternal(const Node* n, QVector &list, int maximum) { for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) { - FindInputNodesConnectedToInputInternal(it->first, list, maximum); + FindInputNodesConnectedToInputInternal(it->second, list, maximum); } } diff --git a/app/node/nodeundo.cpp b/app/node/nodeundo.cpp index f83f2666dc..cd49bdfd92 100644 --- a/app/node/nodeundo.cpp +++ b/app/node/nodeundo.cpp @@ -106,53 +106,36 @@ void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *no commands_.append(new NodeSetPositionCommand(node_, context_, pos)); for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *output = it->second; + Node *output = it->first.node(); if (context_->ContextContainsNode(output)) { move_recursively(output, diff); } } } -NodeEdgeAddCommand::NodeEdgeAddCommand(Node *output, const NodeInput &input) : +NodeEdgeAddCommand::NodeEdgeAddCommand(const NodeOutput &output, const NodeInput &input, int64_t index) : output_(output), input_(input), - remove_command_(nullptr) + index_(index) { } -NodeEdgeAddCommand::~NodeEdgeAddCommand() -{ - delete remove_command_; -} - void NodeEdgeAddCommand::redo() { - if (input_.IsConnected()) { - if (!remove_command_) { - remove_command_ = new NodeEdgeRemoveCommand(input_.GetConnectedOutput(), input_); - } - - remove_command_->redo_now(); - } - - Node::ConnectEdge(output_, input_); + Node::ConnectEdge(output_, input_, index_); } void NodeEdgeAddCommand::undo() { Node::DisconnectEdge(output_, input_); - - if (remove_command_) { - remove_command_->undo_now(); - } } Project *NodeEdgeAddCommand::GetRelevantProject() const { - return output_->project(); + return output_.node()->project(); } -NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(Node *output, const NodeInput &input) : +NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(const NodeOutput &output, const NodeInput &input) : output_(output), input_(input) { @@ -170,7 +153,7 @@ void NodeEdgeRemoveCommand::undo() Project *NodeEdgeRemoveCommand::GetRelevantProject() const { - return output_->project(); + return output_.node()->project(); } NodeAddCommand::NodeAddCommand(Project *graph, Node *node) : @@ -212,10 +195,10 @@ void NodeRemoveAndDisconnectCommand::prepare() // Disconnect everything for (auto it=node_->input_connections().cbegin(); it!=node_->input_connections().cend(); it++) { - command_->add_child(new NodeEdgeRemoveCommand(it->second, it->first)); + command_->add_child(new NodeEdgeRemoveCommand(it->first, it->second)); } - for (const Node::OutputConnection& conn : node_->output_connections()) { + for (const Node::Connection& conn : node_->output_connections()) { command_->add_child(new NodeEdgeRemoveCommand(conn.first, conn.second)); } @@ -284,8 +267,8 @@ void NodeViewDeleteCommand::AddNode(Node *node, Node *context) nodes_.append(p); for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (context->ContextContainsNode(it->second)) { - AddEdge(it->second, it->first); + if (context->ContextContainsNode(it->first.node())) { + AddEdge(it->first, it->second); } } @@ -296,9 +279,9 @@ void NodeViewDeleteCommand::AddNode(Node *node, Node *context) } } -void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input) +void NodeViewDeleteCommand::AddEdge(const NodeOutput &output, const NodeInput &input) { - foreach (const Node::OutputConnection &edge, edges_) { + foreach (const Node::Connection &edge, edges_) { if (edge.first == output && edge.second == input) { return; } @@ -325,7 +308,7 @@ Project *NodeViewDeleteCommand::GetRelevantProject() const } if (!edges_.isEmpty()) { - return edges_.first().first->project(); + return edges_.first().first.node()->project(); } return nullptr; @@ -333,7 +316,7 @@ Project *NodeViewDeleteCommand::GetRelevantProject() const void NodeViewDeleteCommand::redo() { - foreach (const Node::OutputConnection &edge, edges_) { + foreach (const Node::Connection &edge, edges_) { Node::DisconnectEdge(edge.first, edge.second); } @@ -398,14 +381,14 @@ void NodeParamSetKeyframingCommand::undo() input_.node()->SetInputIsKeyframing(input_, old_setting_); } -NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant& value) : +NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const value_t::component_t& value) : key_(key), old_value_(key_->value()), new_value_(value) { } -NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant &new_value, const QVariant &old_value) : +NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const value_t::component_t &new_value, const value_t::component_t &old_value) : key_(key), old_value_(old_value), new_value_(new_value) @@ -502,14 +485,14 @@ void NodeParamSetKeyframeTimeCommand::undo() key_->set_time(old_time_); } -NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const QVariant &value) : +NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const value_t::component_t &value) : ref_(input), - old_value_(ref_.input().node()->GetStandardValue(ref_.input())), + old_value_(ref_.input().node()->GetSplitStandardValueOnTrack(ref_)), new_value_(value) { } -NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const QVariant &new_value, const QVariant &old_value) : +NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const value_t::component_t &new_value, const value_t::component_t &old_value) : ref_(input), old_value_(old_value), new_value_(new_value) diff --git a/app/node/nodeundo.h b/app/node/nodeundo.h index 6df91caac4..79bf27973f 100644 --- a/app/node/nodeundo.h +++ b/app/node/nodeundo.h @@ -189,16 +189,14 @@ class NodeArrayResizeCommand : public UndoCommand if (old_size_ > size_) { // Decreasing in size, disconnect any extraneous edges - for (int i=size_; iinput_connections().at(input); - - removed_connections_[input] = output; + for (auto it = node_->input_connections().cbegin(); it != node_->input_connections().cend(); it++) { + const NodeOutput &output = it->first; + const NodeInput &input = it->second; + if (input.input() == input_ && input.element() >= size_) { + removed_connections_.push_back({output, input}); Node::DisconnectEdge(output, input); - } catch (std::out_of_range&) {} + } } } @@ -208,7 +206,7 @@ class NodeArrayResizeCommand : public UndoCommand virtual void undo() override { for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) { - Node::ConnectEdge(it->second, it->first); + Node::ConnectEdge(it->first, it->second); } removed_connections_.clear(); @@ -221,7 +219,7 @@ class NodeArrayResizeCommand : public UndoCommand int size_; int old_size_; - Node::InputConnections removed_connections_; + Node::Connections removed_connections_; }; @@ -244,7 +242,7 @@ class NodeArrayRemoveCommand : public UndoCommand if (node_->IsInputKeyframable(input_)) { is_keyframing_ = node_->IsInputKeyframing(input_, index_); } - standard_value_ = node_->GetSplitStandardValue(input_, index_); + standard_value_ = node_->GetStandardValue(input_, index_); keyframes_ = node_->GetKeyframeTracks(input_, index_); node_->GetImmediate(input_, index_)->delete_all_keyframes(&memory_manager_); @@ -261,7 +259,7 @@ class NodeArrayRemoveCommand : public UndoCommand key->setParent(node_); } } - node_->SetSplitStandardValue(input_, standard_value_, index_); + node_->SetStandardValue(input_, standard_value_, index_); if (node_->IsInputKeyframable(input_)) { node_->SetInputIsKeyframing(input_, is_keyframing_, index_); @@ -273,7 +271,7 @@ class NodeArrayRemoveCommand : public UndoCommand QString input_; int index_; - SplitValue standard_value_; + value_t standard_value_; bool is_keyframing_; QVector keyframes_; QObject memory_manager_; @@ -287,7 +285,7 @@ class NodeArrayRemoveCommand : public UndoCommand */ class NodeEdgeRemoveCommand : public UndoCommand { public: - NodeEdgeRemoveCommand(Node *output, const NodeInput& input); + NodeEdgeRemoveCommand(const NodeOutput &output, const NodeInput& input); virtual Project* GetRelevantProject() const override; @@ -296,7 +294,7 @@ class NodeEdgeRemoveCommand : public UndoCommand { virtual void undo() override; private: - Node *output_; + NodeOutput output_; NodeInput input_; }; @@ -308,9 +306,7 @@ class NodeEdgeRemoveCommand : public UndoCommand { */ class NodeEdgeAddCommand : public UndoCommand { public: - NodeEdgeAddCommand(Node *output, const NodeInput& input); - - virtual ~NodeEdgeAddCommand() override; + NodeEdgeAddCommand(const NodeOutput &output, const NodeInput& input, int64_t index = -1); virtual Project* GetRelevantProject() const override; @@ -319,10 +315,9 @@ class NodeEdgeAddCommand : public UndoCommand { virtual void undo() override; private: - Node *output_; + NodeOutput output_; NodeInput input_; - - NodeEdgeRemoveCommand* remove_command_; + int64_t index_; }; @@ -604,7 +599,7 @@ class NodeViewDeleteCommand : public UndoCommand void AddNode(Node *node, Node *context); - void AddEdge(Node *output, const NodeInput &input); + void AddEdge(const NodeOutput &output, const NodeInput &input); bool ContainsNode(Node *node, Node *context); @@ -618,7 +613,7 @@ class NodeViewDeleteCommand : public UndoCommand private: QVector nodes_; - QVector edges_; + QVector edges_; struct RemovedNode { Node *node; @@ -714,8 +709,8 @@ class NodeParamSetKeyframeTimeCommand : public UndoCommand class NodeParamSetKeyframeValueCommand : public UndoCommand { public: - NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant& value); - NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant& new_value, const QVariant& old_value); + NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const value_t::component_t &value); + NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const value_t::component_t &new_value, const value_t::component_t &old_value); virtual Project* GetRelevantProject() const override; @@ -726,16 +721,16 @@ class NodeParamSetKeyframeValueCommand : public UndoCommand private: NodeKeyframe* key_; - QVariant old_value_; - QVariant new_value_; + value_t::component_t old_value_; + value_t::component_t new_value_; }; class NodeParamSetStandardValueCommand : public UndoCommand { public: - NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const QVariant& value); - NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const QVariant& new_value, const QVariant& old_value); + NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const value_t::component_t& value); + NodeParamSetStandardValueCommand(const NodeKeyframeTrackReference& input, const value_t::component_t& new_value, const value_t::component_t& old_value); virtual Project* GetRelevantProject() const override; @@ -746,22 +741,22 @@ class NodeParamSetStandardValueCommand : public UndoCommand private: NodeKeyframeTrackReference ref_; - QVariant old_value_; - QVariant new_value_; + value_t::component_t old_value_; + value_t::component_t new_value_; }; class NodeParamSetSplitStandardValueCommand : public UndoCommand { public: - NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& new_value, const SplitValue& old_value) : + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const value_t& new_value, const value_t& old_value) : ref_(input), old_value_(old_value), new_value_(new_value) {} - NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& value) : - NodeParamSetSplitStandardValueCommand(input, value, input.node()->GetSplitStandardValue(input.input())) + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const value_t& value) : + NodeParamSetSplitStandardValueCommand(input, value, input.node()->GetStandardValue(input.input())) {} virtual Project* GetRelevantProject() const override @@ -772,19 +767,19 @@ class NodeParamSetSplitStandardValueCommand : public UndoCommand protected: virtual void redo() override { - ref_.node()->SetSplitStandardValue(ref_.input(), new_value_, ref_.element()); + ref_.node()->SetStandardValue(ref_.input(), new_value_, ref_.element()); } virtual void undo() override { - ref_.node()->SetSplitStandardValue(ref_.input(), old_value_, ref_.element()); + ref_.node()->SetStandardValue(ref_.input(), old_value_, ref_.element()); } private: NodeInput ref_; - SplitValue old_value_; - SplitValue new_value_; + value_t old_value_; + value_t new_value_; }; diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 0126bbc716..67031c7437 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -50,11 +50,11 @@ Track::Track() : arraymap_invalid_(false), ignore_arraymap_set_(false) { - AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden | kInputFlagIgnoreInvalidations)); + AddInput(kBlockInput, kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden | kInputFlagIgnoreInvalidations); - AddInput(kMutedInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kMutedInput, TYPE_BOOL, false, kInputFlagNotConnectable | kInputFlagNotKeyframable); - AddInput(kArrayMapInput, NodeValue::kBinary, InputFlags(kInputFlagStatic | kInputFlagHidden | kInputFlagIgnoreInvalidations)); + AddInput(kArrayMapInput, TYPE_BINARY, kInputFlagStatic | kInputFlagHidden | kInputFlagIgnoreInvalidations); // Set default height track_height_ = kTrackHeightDefault; @@ -95,18 +95,118 @@ QVector Track::Category() const QString Track::Description() const { - return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of " - "a Sequence."); + return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of a Sequence."); } -Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const +void ProcessAudio(const void *context, const SampleJob &job, SampleBuffer &block_range_buffer) { - if (input == kBlockInput) { - if (IsMuted() || blocks_.empty() || r.in() >= track_length() || r.out() <= 0) { - return ActiveElements::kNoElements; - } else { - int start = GetBlockIndexAtTime(r.in()); - int end = GetBlockIndexAtTime(r.out()); + const QVector *blocks = static_cast *>(context); + + const ValueParams &p = job.value_params(); + const TimeRange &range = job.value_params().time(); + + // All these blocks will need to output to a buffer so we create one here + block_range_buffer.silence(); + + for (auto it = job.GetValues().cbegin(); it != job.GetValues().cend(); it++) { + Block *b = blocks->at(it.key().toInt()); + + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + qint64 source_offset = 0; + qint64 destination_offset = p.aparams().time_to_samples(range_for_block.in() - range.in()); + qint64 max_dest_sz = p.aparams().time_to_samples(range_for_block.length()); + + // Destination buffer + SampleBuffer samples_from_this_block = it.value().toSamples(); + + if (samples_from_this_block.is_allocated()) { + // If this is a clip, we might have extra speed/reverse information + if (ClipBlock *clip_cast = dynamic_cast(b)) { + double speed_value = clip_cast->speed(); + bool reversed = clip_cast->reverse(); + + if (qIsNull(speed_value)) { + // Just silence, don't think there's any other practical application of 0 speed audio + samples_from_this_block.silence(); + } else if (!qFuzzyCompare(speed_value, 1.0)) { + if (clip_cast->maintain_audio_pitch()) { + AudioProcessor processor; + + if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { + AudioProcessor::Buffer out; + + // FIXME: This is not the best way to do this, the TempoProcessor works best + // when it's given a continuous stream of audio, which is challenging + // in our current "modular" audio system. This should still work reasonably + // well on export (assuming audio is all generated at once on export), but + // users may hear clicks and pops in the audio during preview due to this + // approach. + int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); + + if (r < 0) { + qCritical() << "Failed to change tempo of audio:" << r; + } else { + processor.Flush(); + + processor.Convert(nullptr, 0, &out); + + if (!out.empty()) { + int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); + + if (nb_samples) { + SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); + + for (int i=0; i 0) { + if (this->type() == Track::kVideo) { + // Just pass straight through + int index = GetBlockIndexAtTime(p.time().in()); + if (index != -1) { + Block *b = blocks_.at(index); + + if (b->is_enabled() && (dynamic_cast(b) || dynamic_cast(b))) { + return GetInputValue(p, kBlockInput, GetArrayIndexFromCacheIndex(index)); + } + } + } else if (this->type() == Track::kAudio) { + // Audio + const TimeRange &range = p.time(); + + int start = GetBlockIndexAtTime(range.in()); + int end = GetBlockIndexAtTime(range.out()); if (start == -1) { start = 0; @@ -115,41 +215,26 @@ Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const end = blocks_.size()-1; } - if (blocks_.at(end)->in() == r.out()) { + if (blocks_.at(end)->in() == range.out()) { end--; } - ActiveElements a; + SampleJob job(p); + + job.set_function(ProcessAudio, &blocks_); + for (int i=start; i<=end; i++) { Block *b = blocks_.at(i); if (b->is_enabled() && (dynamic_cast(b) || dynamic_cast(b))) { - a.add(GetArrayIndexFromCacheIndex(i)); + job.Insert(QString::number(i), GetInputValue(p, kBlockInput, GetArrayIndexFromCacheIndex(i))); } } - if (a.elements().empty()) { - return ActiveElements::kNoElements; - } else { - return a; - } + return job; } - } else { - return super::GetActiveElementsAtTime(input, r); } -} -void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - if (this->type() == Track::kVideo) { - // Just pass straight through - NodeValueArray a = value[kBlockInput].toArray(); - if (!a.empty()) { - table->Push(a.begin()->second); - } - } else if (this->type() == Track::kAudio) { - // Audio - ProcessAudioTrack(value, globals, table); - } + return value_t(); } TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const @@ -456,7 +541,7 @@ void Track::RippleRemoveBlock(Block *block) blocks_.removeAt(index); block_array_indexes_.removeAt(index); - Node::DisconnectEdge(block, NodeInput(this, kBlockInput, array_index)); + Node::DisconnectEdge(NodeOutput(block), NodeInput(this, kBlockInput, array_index)); empty_inputs_.push_back(array_index); disconnect(block, &Block::LengthChanged, this, &Track::BlockLengthChanged); @@ -492,8 +577,8 @@ void Track::ReplaceBlock(Block *old, Block *replace) int cache_index = blocks_.indexOf(old); int index_of_old_block = GetArrayIndexFromCacheIndex(cache_index); - DisconnectEdge(old, NodeInput(this, kBlockInput, index_of_old_block)); - ConnectEdge(replace, NodeInput(this, kBlockInput, index_of_old_block)); + DisconnectEdge(NodeOutput(old), NodeInput(this, kBlockInput, index_of_old_block)); + ConnectEdge(NodeOutput(replace), NodeInput(this, kBlockInput, index_of_old_block)); blocks_.replace(cache_index, replace); disconnect(old, &Block::LengthChanged, this, &Track::BlockLengthChanged); connect(replace, &Block::LengthChanged, this, &Track::BlockLengthChanged); @@ -556,7 +641,7 @@ void Track::SetLocked(bool e) locked_ = e; } -void Track::InputConnectedEvent(const QString &input, int element, Node *node) +void Track::InputConnectedEvent(const QString &input, int element, const NodeOutput &node) { if (arraymap_invalid_ && input == kBlockInput && element >= 0) { RefreshBlockCacheFromArrayMap(); @@ -625,112 +710,19 @@ int Track::GetBlockIndexAtTime(const rational &time) const return -1; } -void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - const TimeRange &range = globals.time(); - - // All these blocks will need to output to a buffer so we create one here - SampleBuffer block_range_buffer(globals.aparams(), range.length()); - block_range_buffer.silence(); - - // Loop through active blocks retrieving their audio - NodeValueArray arr = value[kBlockInput].toArray(); - - for (auto it=arr.cbegin(); it!=arr.cend(); it++) { - Block *b = blocks_.at(GetCacheIndexFromArrayIndex(it->first)); - - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); - - qint64 source_offset = 0; - qint64 destination_offset = globals.aparams().time_to_samples(range_for_block.in() - range.in()); - qint64 max_dest_sz = globals.aparams().time_to_samples(range_for_block.length()); - - // Destination buffer - SampleBuffer samples_from_this_block = it->second.toSamples(); - - if (samples_from_this_block.is_allocated()) { - // If this is a clip, we might have extra speed/reverse information - if (ClipBlock *clip_cast = dynamic_cast(b)) { - double speed_value = clip_cast->speed(); - bool reversed = clip_cast->reverse(); - - if (qIsNull(speed_value)) { - // Just silence, don't think there's any other practical application of 0 speed audio - samples_from_this_block.silence(); - } else if (!qFuzzyCompare(speed_value, 1.0)) { - if (clip_cast->maintain_audio_pitch()) { - AudioProcessor processor; - - if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { - AudioProcessor::Buffer out; - - // FIXME: This is not the best way to do this, the TempoProcessor works best - // when it's given a continuous stream of audio, which is challenging - // in our current "modular" audio system. This should still work reasonably - // well on export (assuming audio is all generated at once on export), but - // users may hear clicks and pops in the audio during preview due to this - // approach. - int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); - - if (r < 0) { - qCritical() << "Failed to change tempo of audio:" << r; - } else { - processor.Flush(); - - processor.Convert(nullptr, 0, &out); - - if (!out.empty()) { - int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); - - if (nb_samples) { - SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); - - for (int i=0; iPush(NodeValue::kSamples, QVariant::fromValue(block_range_buffer), this); -} - int Track::ConnectBlock(Block *b) { if (!empty_inputs_.empty()) { int index = empty_inputs_.front(); empty_inputs_.pop_front(); - Node::ConnectEdge(b, NodeInput(this, kBlockInput, index)); + Node::ConnectEdge(NodeOutput(b), NodeInput(this, kBlockInput, index)); return index; } else { int old_sz = InputArraySize(kBlockInput); InputArrayAppend(kBlockInput); - Node::ConnectEdge(b, NodeInput(this, kBlockInput, old_sz)); + Node::ConnectEdge(NodeOutput(b), NodeInput(this, kBlockInput, old_sz)); return old_sz; } } @@ -748,7 +740,7 @@ void Track::RefreshBlockCacheFromArrayMap() } // Disconnecting any existing blocks - for (Block *b : blocks_) { + for (Block *b : qAsConst(blocks_)) { Q_ASSERT(b->track() == this); b->set_track(nullptr); b->set_previous(nullptr); @@ -758,7 +750,7 @@ void Track::RefreshBlockCacheFromArrayMap() disconnect(b, &Block::LengthChanged, this, &Track::BlockLengthChanged); } - QByteArray bytes = GetStandardValue(kArrayMapInput).toByteArray(); + QByteArray bytes = GetStandardValue(kArrayMapInput).toBinary(); block_array_indexes_.resize(bytes.size() / sizeof(uint32_t)); memcpy(block_array_indexes_.data(), bytes.data(), bytes.size()); blocks_.clear(); diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 4d00b485b2..ff8b920b71 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -54,8 +54,7 @@ class Track : public Node virtual QVector Category() const override; virtual QString Description() const override; - virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override; @@ -448,7 +447,7 @@ public slots: void BlocksRefreshed(); protected: - virtual void InputConnectedEvent(const QString& input, int element, Node *node) override; + virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput &node) override; virtual void InputValueChangedEvent(const QString& input, int element) override; private: @@ -460,12 +459,14 @@ public slots: int GetBlockIndexAtTime(const rational &time) const; - void ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const; + value_t ProcessAudioTrack(const ValueParams &p) const; int ConnectBlock(Block *b); void UpdateArrayMap(); + TimeRange TransformTimeForBlock(const TimeRange &input_time, int index); + TimeRangeList block_length_pending_invalidations_; QVector blocks_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index adada2aa50..4c577492c4 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -22,7 +22,6 @@ #include "config/config.h" #include "core.h" -#include "node/traverser.h" namespace olive { @@ -44,20 +43,20 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream autocache_input_audio_(false), waveform_requests_enabled_(false) { - AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + AddInput(kVideoParamsInput, TYPE_VPARAM, kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden); - AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + AddInput(kAudioParamsInput, TYPE_APARAM, kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden); - AddInput(kSubtitleParamsInput, NodeValue::kSubtitleParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + AddInput(kSubtitleParamsInput, TYPE_SPARAM, kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden); if (create_buffer_inputs) { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); + AddInput(kTextureInput, TYPE_TEXTURE, kInputFlagNotKeyframable); + AddInput(kSamplesInput, TYPE_SAMPLES, kInputFlagNotKeyframable); } if (create_default_streams) { - AddStream(Track::kVideo, QVariant()); - AddStream(Track::kAudio, QVariant()); + AddStream(Track::kVideo, value_t(TYPE_VPARAM, VideoParams())); + AddStream(Track::kAudio, value_t(TYPE_APARAM, AudioParams())); set_default_parameters(); } @@ -65,6 +64,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream workarea_ = new TimelineWorkArea(this); markers_ = new TimelineMarkerList(this); + + connect(this, &ViewerOutput::InputArraySizeChanged, this, &ViewerOutput::ArraySizeChanged); } QString ViewerOutput::Name() const @@ -118,7 +119,7 @@ QVariant ViewerOutput::data(const DataType &d) const if (!using_timebase.isNull()) { // Return time transformed to timecode - return QString::fromStdString(Timecode::time_to_timecode(GetLength(), using_timebase, using_display)); + return Timecode::time_to_timecode(GetLength(), using_timebase, using_display); } break; } @@ -216,12 +217,12 @@ void ViewerOutput::set_default_parameters() static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount, OLIVE_CONFIG("DefaultSequencePixelAspect").value(), - OLIVE_CONFIG("DefaultSequenceInterlacing").value(), + static_cast(OLIVE_CONFIG("DefaultSequenceInterlacing").toInt()), VideoParams::generate_auto_divider(width, height) )); SetAudioParams(AudioParams( OLIVE_CONFIG("DefaultSequenceAudioFrequency").toInt(), - OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(), + AudioChannelLayout::fromString(OLIVE_CONFIG("DefaultSequenceAudioLayout").toString()), kDefaultSampleFormat )); } @@ -305,6 +306,20 @@ void ViewerOutput::Retranslate() if (HasInputWithID(kSamplesInput)) { SetInputName(kSamplesInput, tr("Samples")); } + + { + int vsc = GetVideoStreamCount(); + for (int i = 0; i < vsc; i++) { + SetOutputName(Track::Reference(Track::kVideo, i).ToString(), tr("Video %1").arg(i + 1)); + } + } + + { + int asc = GetAudioStreamCount(); + for (int i = 0; i < asc; i++) { + SetOutputName(Track::Reference(Track::kAudio, i).ToString(), tr("Audio %1").arg(i + 1)); + } + } } void ViewerOutput::VerifyLength() @@ -329,23 +344,23 @@ void ViewerOutput::SetPlayhead(const rational &t) emit PlayheadChanged(t); } -void ViewerOutput::InputConnectedEvent(const QString &input, int element, Node *output) +void ViewerOutput::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kTextureInput) { emit TextureInputChanged(); } else if (input == kSamplesInput) { - connect(output->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); + connect(output.node()->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputConnectedEvent(input, element, output); } -void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, Node *output) +void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kTextureInput) { emit TextureInputChanged(); } else if (input == kSamplesInput) { - disconnect(output->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); + disconnect(output.node()->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputDisconnectedEvent(input, element, output); @@ -353,25 +368,15 @@ void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, Nod rational ViewerOutput::VerifyLengthInternal(Track::Type type) const { - NodeTraverser traverser; - switch (type) { case Track::kVideo: - if (IsInputConnected(kTextureInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); - rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); - if (!r.isNaN()) { - return r; - } + if (ViewerOutput *v = dynamic_cast(GetConnectedOutput(kTextureInput))) { + return v->GetVideoLength(); } break; case Track::kAudio: - if (IsInputConnected(kSamplesInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); - rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); - if (!r.isNaN()) { - return r; - } + if (ViewerOutput *v = dynamic_cast(GetConnectedOutput(kSamplesInput))) { + return v->GetAudioLength(); } break; case Track::kNone: @@ -416,18 +421,18 @@ void ViewerOutput::SetWaveformEnabled(bool e) } } -void ViewerOutput::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t ViewerOutput::Value(const ValueParams &p) const { - if (HasInputWithID(kTextureInput)) { - NodeValue repush = value[kTextureInput]; - repush.set_tag(Track::Reference(Track::kVideo, 0).ToString()); - table->Push(repush); + Track::Reference ref = Track::Reference::FromString(p.output()); + + if (ref.type() == Track::kVideo && HasInputWithID(kTextureInput)) { + return GetInputValue(p, kTextureInput); } - if (HasInputWithID(kSamplesInput)) { - NodeValue repush = value[kSamplesInput]; - repush.set_tag(Track::Reference(Track::kAudio, 0).ToString()); - table->Push(value[kSamplesInput]); + if (ref.type() == Track::kAudio && HasInputWithID(kSamplesInput)) { + return GetInputValue(p, kSamplesInput); } + + return value_t(); } bool ViewerOutput::LoadCustom(QXmlStreamReader *reader, SerializedData *data) @@ -560,12 +565,12 @@ void ViewerOutput::set_parameters_from_footage(const QVector foo } } -int ViewerOutput::AddStream(Track::Type type, const QVariant& value) +int ViewerOutput::AddStream(Track::Type type, const value_t &value) { return SetStream(type, value, -1); } -int ViewerOutput::SetStream(Track::Type type, const QVariant &value, int index_in) +int ViewerOutput::SetStream(Track::Type type, const value_t &value, int index_in) { QString id; @@ -591,6 +596,22 @@ int ViewerOutput::SetStream(Track::Type type, const QVariant &value, int index_i return index; } +void ViewerOutput::ArraySizeChanged(const QString &id, int old_size, int new_size) +{ + if (id == kVideoParamsInput || id == kAudioParamsInput) { + Track::Type type = (id == kVideoParamsInput) ? Track::kVideo : Track::kAudio; + type_t data_type = (id == kVideoParamsInput) ? TYPE_TEXTURE : TYPE_SAMPLES; + + for (int i = old_size; i < new_size; i++) { + AddOutput(Track::Reference(type, i).ToString(), data_type); + } + + for (int i = new_size; i < old_size; i++) { + RemoveOutput(Track::Reference(type, i).ToString()); + } + } +} + QVector ViewerOutput::GetEnabledVideoStreams() const { QVector streams; diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 0ad696614d..fcfe0140a5 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -26,6 +26,7 @@ #include "node/output/track/track.h" #include "render/audioplaybackcache.h" #include "render/framehashcache.h" +#include "render/sampleformat.h" #include "render/subtitleparams.h" #include "render/videoparams.h" #include "timeline/timelinemarker.h" @@ -44,6 +45,10 @@ class ViewerOutput : public Node { Q_OBJECT public: + static constexpr type_t TYPE_VPARAM = "vparam"; + static constexpr type_t TYPE_APARAM = "aparam"; + static constexpr type_t TYPE_SPARAM = "sparam"; + ViewerOutput(bool create_buffer_inputs = true, bool create_default_streams = true); NODE_DEFAULT_FUNCTIONS(ViewerOutput) @@ -98,17 +103,17 @@ class ViewerOutput : public Node void SetVideoParams(const VideoParams &video, int index = 0) { - SetStandardValue(kVideoParamsInput, QVariant::fromValue(video), index); + SetStandardValue(kVideoParamsInput, value_t(TYPE_VPARAM, video), index); } void SetAudioParams(const AudioParams &audio, int index = 0) { - SetStandardValue(kAudioParamsInput, QVariant::fromValue(audio), index); + SetStandardValue(kAudioParamsInput, value_t(TYPE_APARAM, audio), index); } void SetSubtitleParams(const SubtitleParams &subs, int index = 0) { - SetStandardValue(kSubtitleParamsInput, QVariant::fromValue(subs), index); + SetStandardValue(kSubtitleParamsInput, value_t(TYPE_SPARAM, subs), index); } int GetVideoStreamCount() const @@ -186,7 +191,7 @@ class ViewerOutput : public Node bool IsVideoAutoCacheEnabled() const { qDebug() << "sequence ac is a stub"; return false; } void SetVideoAutoCacheEnabled(bool e) { qDebug() << "sequence ac is a stub"; } - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; const EncodingParams &GetLastUsedEncodingParams() const { return last_used_encoding_params_; } void SetLastUsedEncodingParams(const EncodingParams &p) { last_used_encoding_params_ = p; } @@ -231,16 +236,16 @@ public slots: void SetPlayhead(const rational &t); protected: - virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override; - virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; virtual rational VerifyLengthInternal(Track::Type type) const; virtual void InputValueChangedEvent(const QString& input, int element) override; - int AddStream(Track::Type type, const QVariant &value); - int SetStream(Track::Type type, const QVariant &value, int index); + int AddStream(Track::Type type, const value_t &value); + int SetStream(Track::Type type, const value_t &value, int index); private: rational last_length_; @@ -263,6 +268,9 @@ public slots: rational playhead_; +private slots: + void ArraySizeChanged(const QString &id, int old_size, int new_size); + }; } diff --git a/app/node/param.cpp b/app/node/param.cpp index 456648f501..5a14b5ca49 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -69,12 +69,12 @@ bool NodeInput::IsArray() const } } -InputFlags NodeInput::GetFlags() const +InputFlag NodeInput::GetFlags() const { if (IsValid()) { return node_->GetInputFlags(input_); } else { - return InputFlags(kInputFlagNormal); + return kInputFlagNormal; } } @@ -96,21 +96,39 @@ Node *NodeInput::GetConnectedOutput() const } } -NodeValue::Type NodeInput::GetDataType() const +NodeOutput NodeInput::GetConnectedOutput2() const +{ + if (IsValid()) { + return node_->GetConnectedOutput2(*this); + } else { + return NodeOutput(); + } +} + +type_t NodeInput::GetDataType() const { if (IsValid()) { return node_->GetInputDataType(input_); } else { - return NodeValue::kNone; + return TYPE_NONE; } } -QVariant NodeInput::GetDefaultValue() const +value_t NodeInput::GetDefaultValue() const { if (IsValid()) { return node_->GetDefaultValue(input_); } else { - return QVariant(); + return value_t(); + } +} + +size_t NodeInput::GetChannelCount() const +{ + if (IsValid()) { + return node_->GetNumberOfKeyframeTracks(input_); + } else { + return 0; } } @@ -123,30 +141,30 @@ QStringList NodeInput::GetComboBoxStrings() const } } -QVariant NodeInput::GetProperty(const QString &key) const +value_t NodeInput::GetProperty(const QString &key) const { if (IsValid()) { return node_->GetInputProperty(input_, key); } else { - return QVariant(); + return value_t(); } } -QHash NodeInput::GetProperties() const +QHash NodeInput::GetProperties() const { if (IsValid()) { return node_->GetInputProperties(input_); } else { - return QHash(); + return QHash(); } } -QVariant NodeInput::GetValueAtTime(const rational &time) const +value_t NodeInput::GetValueAtTime(const rational &time) const { if (IsValid()) { return node_->GetValueAtTime(*this, time); } else { - return QVariant(); + return value_t(); } } @@ -159,12 +177,12 @@ NodeKeyframe* NodeInput::GetKeyframeAtTimeOnTrack(const rational &time, int trac } } -QVariant NodeInput::GetSplitDefaultValueForTrack(int track) const +value_t::component_t NodeInput::GetSplitDefaultValueForTrack(int track) const { if (IsValid()) { return node_->GetSplitDefaultValueOnTrack(input_, track); } else { - return QVariant(); + return value_t::component_t(); } } @@ -192,4 +210,12 @@ uint qHash(const NodeInputPair &i) return qHash(i.node) & qHash(i.input); } +QString NodeOutput::GetName() const +{ + if (node_) { + return node_->GetOutputName(output_); + } + return QString(); +} + } diff --git a/app/node/param.h b/app/node/param.h index 60a90e33da..9896a2adec 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -30,138 +30,120 @@ namespace olive { class Node; class NodeKeyframe; -enum InputFlag : uint64_t { - /// By default, inputs are keyframable, connectable, and NOT arrays - kInputFlagNormal = 0x0, - kInputFlagArray = 0x1, - kInputFlagNotKeyframable = 0x2, - kInputFlagNotConnectable = 0x4, - kInputFlagHidden = 0x8, - kInputFlagIgnoreInvalidations = 0x10, - - kInputFlagStatic = kInputFlagNotKeyframable | kInputFlagNotConnectable -}; - -class InputFlags { +class InputFlag +{ public: - explicit InputFlags() - { - f_ = kInputFlagNormal; - } + explicit InputFlag(uint64_t f = 0) noexcept { f_ = f; } - explicit InputFlags(uint64_t flags) + InputFlag &operator|=(const InputFlag &i) { - f_ = flags; - } + f_ |= i.f_; + return *this; + }; - InputFlags operator|(const InputFlags &f) const + InputFlag operator|(const InputFlag &rhs) const { - InputFlags i = *this; - i |= f; - return i; - } + InputFlag f = *this; + f.f_ |= rhs.f_; + return f; + }; - InputFlags operator|(const InputFlag &f) const + InputFlag &operator&=(const InputFlag &i) { - InputFlags i = *this; - i |= f; - return i; + f_ &= i.f_; + return *this; } - InputFlags operator|(const uint64_t &f) const + InputFlag operator&(const InputFlag &rhs) const { - InputFlags i = *this; - i |= f; - return i; + InputFlag f = *this; + f.f_ &= rhs.f_; + return f; } - InputFlags &operator|=(const InputFlags &f) - { - f_ |= f.f_; - return *this; - } + operator bool() const { return f_; } + bool operator!() const { return !f_; } - InputFlags &operator|=(const InputFlag &f) + InputFlag operator~() const { - f_ |= f; - return *this; + return InputFlag(~f_); } - InputFlags &operator|=(const uint64_t &f) - { - f_ |= f; - return *this; - } + const uint64_t &value() const { return f_; } - InputFlags operator&(const InputFlags &f) const - { - InputFlags i = *this; - i &= f; - return i; - } +private: + uint64_t f_; - InputFlags operator&(const InputFlag &f) const - { - InputFlags i = *this; - i &= f; - return i; - } +}; - InputFlags operator&(const uint64_t &f) const - { - InputFlags i = *this; - i &= f; - return i; - } +static const InputFlag kInputFlagNormal = InputFlag(0x0); +static const InputFlag kInputFlagArray = InputFlag(0x1); +static const InputFlag kInputFlagNotKeyframable = InputFlag(0x2); +static const InputFlag kInputFlagNotConnectable = InputFlag(0x4); +static const InputFlag kInputFlagHidden = InputFlag(0x8); +static const InputFlag kInputFlagIgnoreInvalidations = InputFlag(0x10); +static const InputFlag kInputFlagDontAutoConvert = InputFlag(0x20); +static const InputFlag kInputFlagStatic = kInputFlagNotKeyframable | kInputFlagNotConnectable; - InputFlags &operator&=(const InputFlags &f) +struct NodeInputPair +{ + bool operator==(const NodeInputPair& rhs) const { - f_ &= f.f_; - return *this; + return node == rhs.node && input == rhs.input; } - InputFlags &operator&=(const InputFlag &f) + Node* node; + QString input; +}; + +class NodeOutput +{ +public: + NodeOutput() { - f_ &= f; - return *this; + node_ = nullptr; } - InputFlags &operator&=(const uint64_t &f) + explicit NodeOutput(Node *node, const QString &output = QString()) { - f_ &= f; - return *this; + node_ = node; + output_ = output; } - InputFlags operator~() const + bool operator==(const NodeOutput &rhs) const { - InputFlags i = *this; - i.f_ = ~i.f_; - return i; + return node_ == rhs.node_ && output_ == rhs.output_; } - inline operator bool() const + bool operator!=(const NodeOutput& rhs) const { - return f_; + return !(*this == rhs); } - inline const uint64_t &value() const + bool operator<(const NodeOutput& rhs) const { - return f_; + if (node_ != rhs.node_) { + return node_ < rhs.node_; + } + + return output_ < rhs.output_; } -private: - uint64_t f_; + QString GetName() const; -}; + Node *node() const { return node_; } + const QString &output() const { return output_; } + void set_node(Node *n) { node_ = n; } + void set_output(const QString &o) { output_ = o; } -struct NodeInputPair { - bool operator==(const NodeInputPair& rhs) const - { - return node == rhs.node && input == rhs.input; - } + void Reset() { *this = NodeOutput(); } + + bool IsValid() const { return node_; } + +private: + Node *node_; + QString output_; - Node* node; - QString input; }; /** @@ -256,26 +238,29 @@ class NodeInput bool IsArray() const; - InputFlags GetFlags() const; + InputFlag GetFlags() const; QString GetInputName() const; Node *GetConnectedOutput() const; + NodeOutput GetConnectedOutput2() const; + + type_t GetDataType() const; - NodeValue::Type GetDataType() const; + value_t GetDefaultValue() const; - QVariant GetDefaultValue() const; + size_t GetChannelCount() const; QStringList GetComboBoxStrings() const; - QVariant GetProperty(const QString& key) const; - QHash GetProperties() const; + value_t GetProperty(const QString& key) const; + QHash GetProperties() const; - QVariant GetValueAtTime(const rational& time) const; + value_t GetValueAtTime(const rational& time) const; NodeKeyframe *GetKeyframeAtTimeOnTrack(const rational& time, int track) const; - QVariant GetSplitDefaultValueForTrack(int track) const; + value_t::component_t GetSplitDefaultValueForTrack(int track) const; int GetArraySize() const; @@ -366,6 +351,7 @@ uint qHash(const NodeKeyframeTrackReference& i); } Q_DECLARE_METATYPE(olive::NodeInput) +Q_DECLARE_METATYPE(olive::NodeOutput) Q_DECLARE_METATYPE(olive::NodeKeyframeTrackReference) #endif // NODEPARAM_H diff --git a/app/node/project.cpp b/app/node/project.cpp index 9e24aacd02..409ad450f4 100644 --- a/app/node/project.cpp +++ b/app/node/project.cpp @@ -234,8 +234,8 @@ void Project::childEvent(QChildEvent *event) // Emit input connections for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (nodes().contains(it->second)) { - emit InputConnected(it->second, it->first); + if (nodes().contains(it->first.node())) { + emit InputConnected(it->first, it->second); } } diff --git a/app/node/project.h b/app/node/project.h index c2ef72f1fb..a18696a17a 100644 --- a/app/node/project.h +++ b/app/node/project.h @@ -176,9 +176,9 @@ class Project : public QObject */ void NodeRemoved(Node* node); - void InputConnected(Node *output, const NodeInput& input); + void InputConnected(const NodeOutput &output, const NodeInput& input); - void InputDisconnected(Node *output, const NodeInput& input); + void InputDisconnected(const NodeOutput &output, const NodeInput& input); void ValueChanged(const NodeInput& input); diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index 801f6cf7b7..a3168c51aa 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -36,7 +36,7 @@ Folder::Folder() { SetFlag(kIsItem); - AddInput(kChildInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable)); + AddInput(kChildInput, kInputFlagArray | kInputFlagNotKeyframable); } QVariant Folder::data(const DataType &d) const @@ -103,10 +103,10 @@ int Folder::index_of_child_in_array(Node *item) const return item_element_index_.at(index_of_item); } -void Folder::InputConnectedEvent(const QString &input, int element, Node *output) +void Folder::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kChildInput && element != -1) { - Node* item = output; + Node* item = output.node(); // The insert index is always our "count" because we only support appending in our internal // model. For sorting/organizing, a QSortFilterProxyModel is used instead. @@ -118,10 +118,10 @@ void Folder::InputConnectedEvent(const QString &input, int element, Node *output } } -void Folder::InputDisconnectedEvent(const QString &input, int element, Node *output) +void Folder::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { if (input == kChildInput && element != -1) { - Node* item = output; + Node* item = output.node(); int child_index = item_children_.indexOf(item); emit BeginRemoveItem(item, child_index); @@ -147,12 +147,12 @@ void FolderAddChild::redo() { int array_index = folder_->InputArraySize(Folder::kChildInput); folder_->InputArrayAppend(Folder::kChildInput); - Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index)); + Node::ConnectEdge(NodeOutput(child_), NodeInput(folder_, Folder::kChildInput, array_index)); } void FolderAddChild::undo() { - Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1)); + Node::DisconnectEdge(NodeOutput(child_), NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1)); folder_->InputArrayRemoveLast(Folder::kChildInput); } @@ -163,7 +163,7 @@ void Folder::RemoveElementCommand::redo() if (remove_index_ != -1) { NodeInput connected_input(folder_, Folder::kChildInput, remove_index_); subcommand_ = new MultiUndoCommand(); - subcommand_->add_child(new NodeEdgeRemoveCommand(folder_->GetConnectedOutput(connected_input), connected_input)); + subcommand_->add_child(new NodeEdgeRemoveCommand(NodeOutput(folder_->GetConnectedOutput(connected_input)), connected_input)); subcommand_->add_child(new NodeArrayRemoveCommand(folder_, Folder::kChildInput, remove_index_)); } } diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 7ed5040272..681026cd49 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -166,15 +166,15 @@ class Folder : public Node void EndRemoveItem(); protected: - virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput &output) override; - virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput &output) override; private: template static void ListOutputsOfTypeInternal(const Folder* n, QVector& list, bool recursive) { - foreach (const Node::OutputConnection& c, n->output_connections()) { + foreach (const Node::Connection& c, n->output_connections()) { Node* connected = c.second.node(); T* cast_test = dynamic_cast(connected); diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 1ff8cdce04..c151d65741 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -48,7 +48,7 @@ Footage::Footage(const QString &filename) : { SetFlag(kIsItem); - PrependInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + PrependInput(kFilenameInput, TYPE_FILE, kInputFlagNotConnectable | kInputFlagNotKeyframable); Clear(); @@ -231,34 +231,30 @@ QString Footage::DescribeSubtitleStream(const SubtitleParams ¶ms) .arg(QString::number(params.stream_index())); } -void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t Footage::Value(const ValueParams &p) const { - Q_UNUSED(globals) + Track::Reference ref = Track::Reference::FromString(p.output()); // Pop filename from table - QString file = value[kFilenameInput].toString(); + QString file = GetInputValue(p, kFilenameInput).toString(); // If the file exists and the reference is valid, push a footage job to the renderer if (QFileInfo::exists(file)) { - // Push length - table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, QStringLiteral("length")); + FootageJob job(p.time(), decoder_, filename(), ref.type(), GetLength(), p.loop_mode()); - // Push each stream as a footage job - for (int i=0; i= 0 && ref.index() < GetVideoStreamCount()) { VideoParams vp = GetVideoParams(ref.index()); // Ensure the colorspace is valid and not empty vp.set_colorspace(GetColorspaceToUse(vp)); // Adjust footage job's divider - if (globals.vparams().divider() > 1) { + if (p.vparams().divider() > 1) { // Use a divider appropriate for this target resolution - int calculated = VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height()); - vp.set_divider(std::min(calculated, globals.vparams().divider())); + int calculated = VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), p.vparams().effective_width(), p.vparams().effective_height()); + vp.set_divider(std::min(calculated, p.vparams().divider())); } else { // Render everything at full res vp.set_divider(1); @@ -266,16 +262,26 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV job.set_video_params(vp); - table->Push(NodeValue::kTexture, Texture::Job(vp, job), this, ref.ToString()); - } else if (ref.type() == Track::kAudio) { + return Texture::Job(vp, job); + } + break; + case Track::kAudio: + if (ref.index() >= 0 && ref.index() < GetAudioStreamCount()) { AudioParams ap = GetAudioParams(ref.index()); job.set_audio_params(ap); job.set_cache_path(project()->cache_path()); - table->Push(NodeValue::kSamples, QVariant::fromValue(job), this, ref.ToString()); + return AudioJob::Create(ap, job); } + break; + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; } } + + return value_t(); } QString Footage::GetStreamTypeName(Track::Type type) @@ -532,17 +538,17 @@ void Footage::Reprobe() } } - SetStream(Track::kVideo, QVariant::fromValue(video_stream), i); + SetStream(Track::kVideo, value_t(TYPE_VPARAM, video_stream), i); } InputArrayResize(kAudioParamsInput, footage_info.GetAudioStreams().size()); for (int i=0; i #include #include @@ -30,6 +29,7 @@ #include "node/output/viewer/viewer.h" #include "render/cancelatom.h" #include "render/videoparams.h" +#include "util/rational.h" namespace olive { @@ -157,7 +157,7 @@ class Footage : public ViewerOutput static QString DescribeAudioStream(const AudioParams& params); static QString DescribeSubtitleStream(const SubtitleParams& params); - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static QString GetStreamTypeName(Track::Type type); diff --git a/app/node/project/footage/footagedescription.cpp b/app/node/project/footage/footagedescription.cpp index cc878d1c92..537747fb2e 100644 --- a/app/node/project/footage/footagedescription.cpp +++ b/app/node/project/footage/footagedescription.cpp @@ -25,7 +25,6 @@ #include #include "common/xmlutils.h" -#include "node/project/serializer/typeserializer.h" namespace olive { @@ -75,7 +74,8 @@ bool FootageDescription::Load(const QString &filename) vp.Load(&reader); AddVideoStream(vp); } else if (reader.name() == QStringLiteral("audio")) { - AudioParams ap = TypeSerializer::LoadAudioParams(&reader); + AudioParams ap; + ap.load(&reader); AddAudioStream(ap); } else if (reader.name() == QStringLiteral("subtitle")) { SubtitleParams sp; @@ -136,7 +136,7 @@ bool FootageDescription::Save(const QString &filename) const foreach (const AudioParams& ap, audio_streams_) { writer.writeStartElement(QStringLiteral("audio")); - TypeSerializer::SaveAudioParams(&writer, ap); + ap.save(&writer); writer.writeEndElement(); // audio } diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index c460d32c07..ef96ef21cc 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -43,7 +43,7 @@ Sequence::Sequence() // Create track input QString track_input_id = kTrackInputFormat.arg(i); - AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden | kInputFlagIgnoreInvalidations)); + AddInput(track_input_id, kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden | kInputFlagIgnoreInvalidations); TrackList* list = new TrackList(this, static_cast(i), track_input_id); track_lists_.replace(i, list); @@ -150,12 +150,12 @@ rational Sequence::VerifyLengthInternal(Track::Type type) const return 0; } -void Sequence::InputConnectedEvent(const QString &input, int element, Node *output) +void Sequence::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) { foreach (TrackList* list, track_lists_) { if (list->track_input() == input) { // Return because we found our input - list->TrackConnected(output, element); + list->TrackConnected(output.node(), element); return; } } @@ -163,12 +163,12 @@ void Sequence::InputConnectedEvent(const QString &input, int element, Node *outp super::InputConnectedEvent(input, element, output); } -void Sequence::InputDisconnectedEvent(const QString &input, int element, Node *output) +void Sequence::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) { foreach (TrackList* list, track_lists_) { if (list->track_input() == input) { // Return because we found our input - list->TrackDisconnected(output, element); + list->TrackDisconnected(output.node(), element); return; } } diff --git a/app/node/project/sequence/sequence.h b/app/node/project/sequence/sequence.h index 42b10702a4..2faed58aeb 100644 --- a/app/node/project/sequence/sequence.h +++ b/app/node/project/sequence/sequence.h @@ -88,9 +88,9 @@ class Sequence : public ViewerOutput static const QString kTrackInputFormat; protected: - virtual void InputConnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override; - virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override; + virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; virtual rational VerifyLengthInternal(Track::Type type) const override; diff --git a/app/node/project/serializer/CMakeLists.txt b/app/node/project/serializer/CMakeLists.txt index df529037c2..e67b8b3d7f 100644 --- a/app/node/project/serializer/CMakeLists.txt +++ b/app/node/project/serializer/CMakeLists.txt @@ -31,10 +31,5 @@ set(OLIVE_SOURCES node/project/serializer/serializer220403.h node/project/serializer/serializer230220.cpp node/project/serializer/serializer230220.h - - - node/project/serializer/typeserializer.cpp - node/project/serializer/typeserializer.h - PARENT_SCOPE ) diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 7c5bf9e5b6..ff2d7cb0d3 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -25,7 +25,6 @@ #include "common/define.h" #include "node/project.h" -#include "typeserializer.h" namespace olive { @@ -82,7 +81,7 @@ class ProjectSerializer QVector nodes; - Node::OutputConnections promised_connections; + Node::Connections promised_connections; }; diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index a4a3854503..bc8aabbb91 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -449,7 +449,7 @@ void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node { Q_UNUSED(xml_node_data) - NodeValue::Type data_type = node->GetInputDataType(input); + type_t data_type = node->GetInputDataType(input); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("standard")) { @@ -462,20 +462,21 @@ void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; + value_t::component_t value_on_track; - if (data_type == NodeValue::kVideoParams) { + if (data_type == ViewerOutput::TYPE_VPARAM) { VideoParams vp; vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap = TypeSerializer::LoadAudioParams(reader); - value_on_track = QVariant::fromValue(ap); + value_on_track = vp; + } else if (data_type == ViewerOutput::TYPE_APARAM) { + AudioParams ap; + ap.load(reader); + value_on_track = ap; } else { QString value_text = reader->readElementText(); if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, true); + value_on_track = value_t::component_t(value_text).toSerializedString(data_type); } } @@ -506,7 +507,7 @@ void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node QString key_input; rational key_time; NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; + value_t::component_t key_value; QPointF key_in_handle; QPointF key_out_handle; @@ -518,7 +519,7 @@ void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node if (attr.name() == QStringLiteral("input")) { key_input = attr.value().toString(); } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString().toStdString()); + key_time = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("type")) { key_type = static_cast(attr.value().toInt()); } else if (attr.name() == QStringLiteral("inhandlex")) { @@ -532,7 +533,7 @@ void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node } } - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + key_value = value_t::component_t::fromSerializedString(data_type, reader->readElementText()); NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); key->set_bezier_control_in(key_in_handle); @@ -597,11 +598,7 @@ void ProjectSerializer210528::PostConnect(const XMLNodeData &xml_node_data) cons foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { // Use output param as hint tag since we grandfathered those in - Node::ValueHint hint(con.output_param); - - Node::ConnectEdge(out, con.input); - - con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element()); + Node::ConnectEdge(NodeOutput(out, con.output_param), con.input); } } @@ -715,9 +712,9 @@ void ProjectSerializer210528::LoadWorkArea(QXmlStreamReader *reader, TimelineWor if (attr.name() == QStringLiteral("enabled")) { workarea->set_enabled(attr.value() != QStringLiteral("0")); } else if (attr.name() == QStringLiteral("in")) { - range_in = rational::fromString(attr.value().toString().toStdString()); + range_in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString().toStdString()); + range_out = rational::fromString(attr.value().toString()); } } @@ -741,9 +738,9 @@ void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineM if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } } @@ -756,27 +753,14 @@ void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer210528::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const { - QVector types; - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - types.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("index")) { - hint->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("tag")) { - hint->set_tag(reader->readElementText()); + if (reader->name() == QStringLiteral("tag")) { + QString output = reader->readElementText(); + qDebug() << "FIXME: need to propagate output" << output; } else { reader->skipCurrentElement(); } } - - hint->set_type(types); } } diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 197f97b769..dc7a834d09 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -446,7 +446,7 @@ void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node { Q_UNUSED(xml_node_data) - NodeValue::Type data_type = node->GetInputDataType(input); + type_t data_type = node->GetInputDataType(input); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("standard")) { @@ -459,20 +459,21 @@ void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; + value_t::component_t value_on_track; - if (data_type == NodeValue::kVideoParams) { + if (data_type == ViewerOutput::TYPE_VPARAM) { VideoParams vp; vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap = TypeSerializer::LoadAudioParams(reader); - value_on_track = QVariant::fromValue(ap); + value_on_track = vp; + } else if (data_type == ViewerOutput::TYPE_APARAM) { + AudioParams ap; + ap.load(reader); + value_on_track = ap; } else { QString value_text = reader->readElementText(); if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, true); + value_on_track = value_t::component_t(value_text).toSerializedString(data_type); } } @@ -503,7 +504,7 @@ void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node QString key_input; rational key_time; NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; + value_t::component_t key_value; QPointF key_in_handle; QPointF key_out_handle; @@ -515,7 +516,7 @@ void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node if (attr.name() == QStringLiteral("input")) { key_input = attr.value().toString(); } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString().toStdString()); + key_time = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("type")) { key_type = static_cast(attr.value().toInt()); } else if (attr.name() == QStringLiteral("inhandlex")) { @@ -529,7 +530,7 @@ void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node } } - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + key_value = value_t::component_t::fromSerializedString(data_type, reader->readElementText()); NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); key->set_bezier_control_in(key_in_handle); @@ -593,7 +594,7 @@ void ProjectSerializer210907::PostConnect(const XMLNodeData &xml_node_data) cons { foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { - Node::ConnectEdge(out, con.input); + Node::ConnectEdge(NodeOutput(out), con.input); } } @@ -707,9 +708,9 @@ void ProjectSerializer210907::LoadWorkArea(QXmlStreamReader *reader, TimelineWor if (attr.name() == QStringLiteral("enabled")) { workarea->set_enabled(attr.value() != QStringLiteral("0")); } else if (attr.name() == QStringLiteral("in")) { - range_in = rational::fromString(attr.value().toString().toStdString()); + range_in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString().toStdString()); + range_out = rational::fromString(attr.value().toString()); } } @@ -733,9 +734,9 @@ void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineM if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } } @@ -748,27 +749,14 @@ void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer210907::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const { - QVector types; - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - types.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("index")) { - hint->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("tag")) { - hint->set_tag(reader->readElementText()); + if (reader->name() == QStringLiteral("tag")) { + QString output = reader->readElementText(); + qDebug() << "FIXME: need to propagate output" << output; } else { reader->skipCurrentElement(); } } - - hint->set_type(types); } } diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index a9ccf3d0a0..f68f3891bc 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -496,7 +496,7 @@ void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node { Q_UNUSED(xml_node_data) - NodeValue::Type data_type = node->GetInputDataType(input); + type_t data_type = node->GetInputDataType(input); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("standard")) { @@ -509,20 +509,21 @@ void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; + value_t::component_t value_on_track; - if (data_type == NodeValue::kVideoParams) { + if (data_type == ViewerOutput::TYPE_VPARAM) { VideoParams vp; vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap = TypeSerializer::LoadAudioParams(reader); - value_on_track = QVariant::fromValue(ap); + value_on_track = vp; + } else if (data_type == ViewerOutput::TYPE_APARAM) { + AudioParams ap; + ap.load(reader); + value_on_track = ap; } else { QString value_text = reader->readElementText(); if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, true); + value_on_track = value_t::component_t(value_text).toSerializedString(data_type); } } @@ -553,7 +554,7 @@ void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node QString key_input; rational key_time; NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; + value_t::component_t key_value; QPointF key_in_handle; QPointF key_out_handle; @@ -565,7 +566,7 @@ void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node if (attr.name() == QStringLiteral("input")) { key_input = attr.value().toString(); } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString().toStdString()); + key_time = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("type")) { key_type = static_cast(attr.value().toInt()); } else if (attr.name() == QStringLiteral("inhandlex")) { @@ -579,7 +580,7 @@ void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node } } - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + key_value = value_t::component_t::fromSerializedString(data_type, reader->readElementText()); NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); key->set_bezier_control_in(key_in_handle); @@ -643,7 +644,7 @@ void ProjectSerializer211228::PostConnect(const XMLNodeData &xml_node_data) cons { foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { - Node::ConnectEdge(out, con.input); + Node::ConnectEdge(NodeOutput(out), con.input); } } @@ -757,9 +758,9 @@ void ProjectSerializer211228::LoadWorkArea(QXmlStreamReader *reader, TimelineWor if (attr.name() == QStringLiteral("enabled")) { workarea->set_enabled(attr.value() != QStringLiteral("0")); } else if (attr.name() == QStringLiteral("in")) { - range_in = rational::fromString(attr.value().toString().toStdString()); + range_in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString().toStdString()); + range_out = rational::fromString(attr.value().toString()); } } @@ -783,9 +784,9 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } } @@ -798,27 +799,14 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer211228::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const { - QVector types; - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - types.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("index")) { - hint->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("tag")) { - hint->set_tag(reader->readElementText()); + if (reader->name() == QStringLiteral("tag")) { + QString output = reader->readElementText(); + qDebug() << "FIXME: need to propagate output" << output; } else { reader->skipCurrentElement(); } } - - hint->set_type(types); } } diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 0ee37bc434..caf6eb4702 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -653,12 +653,12 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node { Q_UNUSED(xml_node_data) - NodeValue::Type data_type = node->GetInputDataType(input); + type_t data_type = node->GetInputDataType(input); // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see // if this ends up being an issue in the future. - if (data_type == NodeValue::kSubtitleParams) { + if (data_type == ViewerOutput::TYPE_SPARAM) { reader->skipCurrentElement(); return; } @@ -674,20 +674,21 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; + value_t::component_t value_on_track; - if (data_type == NodeValue::kVideoParams) { + if (data_type == ViewerOutput::TYPE_VPARAM) { VideoParams vp; vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap = TypeSerializer::LoadAudioParams(reader); - value_on_track = QVariant::fromValue(ap); + value_on_track = vp; + } else if (data_type == ViewerOutput::TYPE_APARAM) { + AudioParams ap; + ap.load(reader); + value_on_track = ap; } else { QString value_text = reader->readElementText(); if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, true); + value_on_track = value_t::component_t::fromSerializedString(data_type, value_text); } } @@ -746,7 +747,7 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node } } -void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const +void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, type_t data_type) const { QString key_input; QPointF key_in_handle; @@ -760,7 +761,7 @@ void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyfram if (attr.name() == QStringLiteral("input")) { key_input = attr.value().toString(); } else if (attr.name() == QStringLiteral("time")) { - key->set_time(rational::fromString(attr.value().toString().toStdString())); + key->set_time(rational::fromString(attr.value().toString())); } else if (attr.name() == QStringLiteral("type")) { key->set_type_no_bezier_adj(static_cast(attr.value().toInt())); } else if (attr.name() == QStringLiteral("inhandlex")) { @@ -774,7 +775,7 @@ void ProjectSerializer220403::LoadKeyframe(QXmlStreamReader *reader, NodeKeyfram } } - key->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); + key->set_value(value_t::component_t::fromSerializedString(data_type, reader->readElementText())); key->set_bezier_control_in(key_in_handle); key->set_bezier_control_out(key_out_handle); @@ -815,7 +816,7 @@ void ProjectSerializer220403::PostConnect(const XMLNodeData &xml_node_data) cons { foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { - Node::ConnectEdge(out, con.input); + Node::ConnectEdge(NodeOutput(out), con.input); } } @@ -904,11 +905,11 @@ void ProjectSerializer220403::LoadNodeCustom(QXmlStreamReader *reader, Node *nod } else if (reader->name() == QStringLiteral("name")) { link.custom_name = reader->readElementText(); } else if (reader->name() == QStringLiteral("flags")) { - link.custom_flags = InputFlags(reader->readElementText().toULongLong()); + link.custom_flags = InputFlag(reader->readElementText().toULongLong()); } else if (reader->name() == QStringLiteral("type")) { - link.data_type = NodeValue::GetDataTypeFromName(reader->readElementText()); + link.data_type = type_t::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("default")) { - link.default_val = NodeValue::StringToValue(link.data_type, reader->readElementText(), false); + link.default_val = value_t::fromSerializedString(link.data_type, reader->readElementText()); } else if (reader->name() == QStringLiteral("properties")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("property")) { @@ -975,9 +976,9 @@ void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarke if (attr.name() == QStringLiteral("name")) { marker->set_name(attr.value().toString()); } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("color")) { marker->set_color(attr.value().toInt()); } @@ -998,9 +999,9 @@ void ProjectSerializer220403::LoadWorkArea(QXmlStreamReader *reader, TimelineWor if (attr.name() == QStringLiteral("enabled")) { workarea->set_enabled(attr.value() != QStringLiteral("0")); } else if (attr.name() == QStringLiteral("in")) { - range_in = rational::fromString(attr.value().toString().toStdString()); + range_in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString().toStdString()); + range_out = rational::fromString(attr.value().toString()); } } @@ -1027,27 +1028,14 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer220403::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const { - QVector types; - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - types.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("index")) { - hint->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("tag")) { - hint->set_tag(reader->readElementText()); + if (reader->name() == QStringLiteral("tag")) { + QString output = reader->readElementText(); + qDebug() << "FIXME: need to propagate output" << output; } else { reader->skipCurrentElement(); } } - - hint->set_type(types); } } diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index 74c6ccad7b..21d0f48964 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -57,10 +57,10 @@ class ProjectSerializer220403 : public ProjectSerializer QString input_id; int input_element; QString custom_name; - InputFlags custom_flags; - NodeValue::Type data_type; - QVariant default_val; - QHash custom_properties; + InputFlag custom_flags; + type_t data_type; + value_t default_val; + QHash custom_properties; }; QHash node_ptrs; @@ -82,7 +82,7 @@ class ProjectSerializer220403 : public ProjectSerializer void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; - void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const; + void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, type_t data_type) const; bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; diff --git a/app/node/project/serializer/serializer230220.cpp b/app/node/project/serializer/serializer230220.cpp index 06e140ad68..86374e9723 100644 --- a/app/node/project/serializer/serializer230220.cpp +++ b/app/node/project/serializer/serializer230220.cpp @@ -283,7 +283,7 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project if (Node *si = skipped_items.value(sc.output_node)) { // Convert this to a promised connection - Node::OutputConnection oc = {si, sc.input}; + Node::Connection oc = {NodeOutput(si, sc.output_param), sc.input}; load_data.promised_connections.push_back(oc); it = project_data.desired_connections.erase(it); } else { @@ -459,7 +459,7 @@ void ProjectSerializer230220::PostConnect(const QVector &nodes, Serializ { foreach (const SerializedData::SerializedConnection& con, project_data->desired_connections) { if (Node *out = project_data->node_ptrs.value(con.output_node)) { - Node::ConnectEdge(out, con.input); + Node::ConnectEdge(NodeOutput(out, con.output_param), con.input); } } diff --git a/app/node/project/serializer/typeserializer.cpp b/app/node/project/serializer/typeserializer.cpp deleted file mode 100644 index 003613597f..0000000000 --- a/app/node/project/serializer/typeserializer.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2023 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 "typeserializer.h" - -namespace olive { - -AudioParams TypeSerializer::LoadAudioParams(QXmlStreamReader *reader) -{ - AudioParams a; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("samplerate")) { - a.set_sample_rate(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("channellayout")) { - a.set_channel_layout(reader->readElementText().toULongLong()); - } else if (reader->name() == QStringLiteral("format")) { - a.set_format(SampleFormat::from_string(reader->readElementText().toStdString())); - } else if (reader->name() == QStringLiteral("enabled")) { - a.set_enabled(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("streamindex")) { - a.set_stream_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("duration")) { - a.set_duration(reader->readElementText().toLongLong()); - } else if (reader->name() == QStringLiteral("timebase")) { - a.set_time_base(rational::fromString(reader->readElementText().toStdString())); - } else { - reader->skipCurrentElement(); - } - } - - return a; -} - -void TypeSerializer::SaveAudioParams(QXmlStreamWriter *writer, const AudioParams &a) -{ - writer->writeTextElement(QStringLiteral("samplerate"), QString::number(a.sample_rate())); - writer->writeTextElement(QStringLiteral("channellayout"), QString::number(a.channel_layout())); - writer->writeTextElement(QStringLiteral("format"), QString::fromStdString(a.format().to_string())); - writer->writeTextElement(QStringLiteral("enabled"), QString::number(a.enabled())); - writer->writeTextElement(QStringLiteral("streamindex"), QString::number(a.stream_index())); - writer->writeTextElement(QStringLiteral("duration"), QString::number(a.duration())); - writer->writeTextElement(QStringLiteral("timebase"), QString::fromStdString(a.time_base().toString())); -} - -} diff --git a/app/node/serializeddata.h b/app/node/serializeddata.h index 10ef7f132b..30f1a63ab5 100644 --- a/app/node/serializeddata.h +++ b/app/node/serializeddata.h @@ -34,6 +34,7 @@ struct SerializedData { struct SerializedConnection { NodeInput input; quintptr output_node; + QString output_param; }; struct BlockLink { @@ -48,10 +49,10 @@ struct SerializedData { QString input_id; int input_element; QString custom_name; - InputFlags custom_flags; - NodeValue::Type data_type; - QVariant default_val; - QHash custom_properties; + InputFlag custom_flags; + type_t data_type; + value_t default_val; + QHash custom_properties; }; QMap > positions; diff --git a/app/node/swizzlemap.h b/app/node/swizzlemap.h new file mode 100644 index 0000000000..4c4b425d7b --- /dev/null +++ b/app/node/swizzlemap.h @@ -0,0 +1,152 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 SWIZZLE_H +#define SWIZZLE_H + +#include +#include +#include +#include + +#include "common/xmlutils.h" + +namespace olive { + +class SwizzleMap +{ +public: + class From + { + public: + From() = default; + + From(size_t output, size_t element) + { + output_ = output; + element_ = element; + } + + const size_t &output() const { return output_; } + const size_t &element() const { return element_; } + + bool operator==(const From &f) const { return output_ == f.output_ && element_ == f.element_; } + + private: + size_t output_; + size_t element_; + + }; + + class To + { + public: + To() = default; + + To(size_t index, type_t tag) + { + index_ = index; + tag_ = tag; + } + + const size_t &index() const { return index_; } + const type_t &tag() const { return tag_; } + + bool operator==(const To &to) const { return index_ == to.index_ && tag_ == to.tag_; } + + private: + size_t index_; + type_t tag_; + }; + + SwizzleMap() = default; + + bool empty() const { return map_.empty(); } + + void clear() { map_.clear(); } + + void insert(size_t to, const From &from) + { + map_[to] = from; + } + + void remove(size_t to) + { + map_.erase(to); + } + + void load(QXmlStreamReader *reader) + { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("entry")) { + bool got_output = false, got_from = false, got_to = false; + size_t from, to, output; + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("from")) { + from = reader->readElementText().toULongLong(); + got_from = true; + } else if (reader->name() == QStringLiteral("output")) { + output = reader->readElementText().toULongLong(); + got_output = true; + } else if (reader->name() == QStringLiteral("to")) { + to = reader->readElementText().toULongLong(); + got_to = true; + } else { + reader->skipCurrentElement(); + } + } + + if (got_output && got_from && got_to) { + insert(to, From(output, from)); + } + } else { + reader->skipCurrentElement(); + } + } + } + + void save(QXmlStreamWriter *writer) const + { + for (auto it = map_.cbegin(); it != map_.cend(); it++) { + writer->writeStartElement(QStringLiteral("entry")); + writer->writeTextElement(QStringLiteral("to"), QString::number(it->first)); + writer->writeTextElement(QStringLiteral("output"), QString::number(it->second.output())); + writer->writeTextElement(QStringLiteral("from"), QString::number(it->second.element())); + writer->writeEndElement(); // entry + } + } + + bool operator==(const SwizzleMap &m) const { return map_ == m.map_; } + bool operator!=(const SwizzleMap &m) const { return map_ != m.map_; } + + std::map::const_iterator cbegin() const { return map_.cbegin(); } + std::map::const_iterator cend() const { return map_.cend(); } + + size_t size() const { return map_.size(); } + +private: + + std::map map_; + +}; + +} + +#endif // SWIZZLE_H diff --git a/app/node/time/timeformat/timeformat.cpp b/app/node/time/timeformat/timeformat.cpp index 50a870a86a..3709c75cc4 100644 --- a/app/node/time/timeformat/timeformat.cpp +++ b/app/node/time/timeformat/timeformat.cpp @@ -32,9 +32,9 @@ const QString TimeFormatNode::kLocalTimeInput = QStringLiteral("localtime_in"); TimeFormatNode::TimeFormatNode() { - AddInput(kTimeInput, NodeValue::kFloat); - AddInput(kFormatInput, NodeValue::kText, QStringLiteral("hh:mm:ss")); - AddInput(kLocalTimeInput, NodeValue::kBoolean); + AddInput(kTimeInput, TYPE_DOUBLE); + AddInput(kFormatInput, TYPE_STRING, QStringLiteral("hh:mm:ss")); + AddInput(kLocalTimeInput, TYPE_BOOL); } QString TimeFormatNode::Name() const @@ -66,14 +66,13 @@ void TimeFormatNode::Retranslate() SetInputName(kLocalTimeInput, tr("Interpret time as local time")); } -void TimeFormatNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TimeFormatNode::Value(const ValueParams &p) const { - qint64 ms_since_epoch = value[kTimeInput].toDouble()*1000; - bool time_is_local = value[kLocalTimeInput].toBool(); + qint64 ms_since_epoch = GetInputValue(p, kTimeInput).toDouble()*1000; + bool time_is_local = GetInputValue(p, kLocalTimeInput).toBool(); QDateTime dt = QDateTime::fromMSecsSinceEpoch(ms_since_epoch, time_is_local ? Qt::LocalTime : Qt::UTC); - QString format = value[kFormatInput].toString(); - QString output = dt.toString(format); - table->Push(NodeValue(NodeValue::kText, output, this)); + QString format = GetInputValue(p, kFormatInput).toString(); + return dt.toString(format); } } diff --git a/app/node/time/timeformat/timeformat.h b/app/node/time/timeformat/timeformat.h index ddc7ac8c1a..6f255e20b4 100644 --- a/app/node/time/timeformat/timeformat.h +++ b/app/node/time/timeformat/timeformat.h @@ -40,7 +40,7 @@ class TimeFormatNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &globals) const override; static const QString kTimeInput; static const QString kFormatInput; diff --git a/app/node/time/timeoffset/timeoffsetnode.cpp b/app/node/time/timeoffset/timeoffsetnode.cpp index 1a6201b5e8..0b4d424649 100644 --- a/app/node/time/timeoffset/timeoffsetnode.cpp +++ b/app/node/time/timeoffset/timeoffsetnode.cpp @@ -31,11 +31,11 @@ const QString TimeOffsetNode::kInputInput = QStringLiteral("input_in"); TimeOffsetNode::TimeOffsetNode() { - AddInput(kTimeInput, NodeValue::kRational, QVariant::fromValue(rational(0)), InputFlags(kInputFlagNotConnectable)); + AddInput(kTimeInput, TYPE_RATIONAL, rational(0), kInputFlagNotConnectable); SetInputProperty(kTimeInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kTimeInput, QStringLiteral("viewlock"), true); - AddInput(kInputInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + AddInput(kInputInput, kInputFlagNotKeyframable); } void TimeOffsetNode::Retranslate() @@ -67,9 +67,9 @@ TimeRange TimeOffsetNode::OutputTimeAdjustment(const QString &input, int element return super::OutputTimeAdjustment(input, element, input_time); } -void TimeOffsetNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TimeOffsetNode::Value(const ValueParams &p) const { - table->Push(value[kInputInput]); + return GetInputValue(p, kInputInput); } rational TimeOffsetNode::GetRemappedTime(const rational &input) const diff --git a/app/node/time/timeoffset/timeoffsetnode.h b/app/node/time/timeoffset/timeoffsetnode.h index f1890924f4..920e29cb4f 100644 --- a/app/node/time/timeoffset/timeoffsetnode.h +++ b/app/node/time/timeoffset/timeoffsetnode.h @@ -57,7 +57,7 @@ class TimeOffsetNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTimeInput; static const QString kInputInput; diff --git a/app/node/time/timeremap/timeremap.cpp b/app/node/time/timeremap/timeremap.cpp index d3035bb3da..a1de0e8724 100644 --- a/app/node/time/timeremap/timeremap.cpp +++ b/app/node/time/timeremap/timeremap.cpp @@ -31,11 +31,11 @@ const QString TimeRemapNode::kInputInput = QStringLiteral("input_in"); TimeRemapNode::TimeRemapNode() { - AddInput(kTimeInput, NodeValue::kRational, QVariant::fromValue(rational(0)), InputFlags(kInputFlagNotConnectable)); + AddInput(kTimeInput, TYPE_RATIONAL, rational(0), kInputFlagNotConnectable); SetInputProperty(kTimeInput, QStringLiteral("view"), RationalSlider::kTime); SetInputProperty(kTimeInput, QStringLiteral("viewlock"), true); - AddInput(kInputInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); + AddInput(kInputInput, kInputFlagNotKeyframable); } QString TimeRemapNode::Name() const @@ -87,9 +87,9 @@ void TimeRemapNode::Retranslate() SetInputName(kInputInput, QStringLiteral("Input")); } -void TimeRemapNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +value_t TimeRemapNode::Value(const ValueParams &p) const { - table->Push(value[kInputInput]); + return GetInputValue(p, kInputInput); } rational TimeRemapNode::GetRemappedTime(const rational &input) const diff --git a/app/node/time/timeremap/timeremap.h b/app/node/time/timeremap/timeremap.h index 3ba9cd8e13..2790e6b6f3 100644 --- a/app/node/time/timeremap/timeremap.h +++ b/app/node/time/timeremap/timeremap.h @@ -43,7 +43,7 @@ class TimeRemapNode : public Node virtual void Retranslate() override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual value_t Value(const ValueParams &p) const override; static const QString kTimeInput; static const QString kInputInput; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp deleted file mode 100644 index 86e91ea2d7..0000000000 --- a/app/node/traverser.cpp +++ /dev/null @@ -1,479 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "traverser.h" - -#include "node.h" -#include "node/block/clip/clip.h" -#include "render/job/footagejob.h" -#include "render/rendermanager.h" - -namespace olive { - -NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range) -{ - NodeValueDatabase database; - - // HACK: Pick up loop mode from clips - LoopMode old_loop_mode = loop_mode_; - if (const ClipBlock *clip = dynamic_cast(node)) { - loop_mode_ = clip->loop_mode(); - } - - // We need to insert tables into the database for each input - auto ignore = node->IgnoreInputsForRendering(); - foreach (const QString& input, node->inputs()) { - if (IsCancelled()) { - return NodeValueDatabase(); - } - - if (ignore.contains(input)) { - continue; - } - - database.Insert(input, ProcessInput(node, input, range)); - } - - loop_mode_ = old_loop_mode; - - return database; -} - -NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range) -{ - // Generate row - NodeValueRow row; - for (auto it=database->begin(); it!=database->end(); it++) { - // Get hint for which value should be pulled - NodeValue value = GenerateRowValue(node, it.key(), &it.value(), range); - row.insert(it.key(), value); - } - - // TEMP: Audio needs to be refactored to work with new job system. But refactoring hasn't been - // done yet, so we emulate old behavior here JUST FOR AUDIO. - for (auto it=row.begin(); it!=row.end(); it++) { - NodeValue &val = it.value(); - if (val.type() == NodeValue::kSamples) { - ResolveJobs(val); - } - } - // END TEMP - - return row; -} - -NodeValueRow NodeTraverser::GenerateRow(const Node *node, const TimeRange &range) -{ - // Generate database of input values of node - NodeValueDatabase database = GenerateDatabase(node, range); - - return GenerateRow(&database, node, range); -} - -NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table, const TimeRange &time) -{ - NodeValue value = GenerateRowValueElement(node, input, -1, table, time); - - if (value.array()) { - // Resolve each element of array - NodeValueTableArray tables = value.value(); - NodeValueArray output; - - for (auto it=tables.begin(); it!=tables.end(); it++) { - output[it->first] = GenerateRowValueElement(node, input, it->first, &it->second, time); - } - - value = NodeValue(value.type(), QVariant::fromValue(output), value.source(), value.array(), value.tag()); - } - - return value; -} - -NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table, const TimeRange &time) -{ - int value_index = GenerateRowValueElementIndex(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); - - if (value_index == -1) { - // If value was -1, try getting the last value - value_index = table->Count() - 1; - } - - if (value_index == -1) { - // If value is still -1, assume the table is empty and return nothing - return NodeValue(); - } - - NodeValue value = table->TakeAt(value_index); - - if (value.type() == NodeValue::kTexture && UseCache()) { - if (TexturePtr tex = value.toTexture()) { - QMutexLocker locker(node->video_frame_cache()->mutex()); - - node->video_frame_cache()->LoadState(); - - QString cache = node->video_frame_cache()->GetValidCacheFilename(time.in()); - if (!cache.isEmpty()) { - value.set_value(tex->toJob(CacheJob(cache, value))); - } - } - } - - return value; -} - -int NodeTraverser::GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table) -{ - QVector types = hint.types(); - - if (types.isEmpty()) { - types.append(preferred_type); - } - - if (hint.index() == -1) { - // Get most recent value with this type and tag - return table->GetValueIndex(types, hint.tag()); - } else { - // Try to find value at this index - int index = table->Count() - 1 - hint.index(); - int diff = 0; - - while (index + diff < table->Count() && index - diff >= 0) { - if (index + diff < table->Count() && types.contains(table->at(index + diff).type())) { - return index + diff; - } - if (index - diff >= 0 && types.contains(table->at(index - diff).type())) { - return index - diff; - } - diff++; - } - - return -1; - } -} - -int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table) -{ - return GenerateRowValueElementIndex(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); -} - -void NodeTraverser::Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range) -{ - transform_ = transform; - transform_start_ = start; - transform_now_ = nullptr; - - GenerateTable(end, range); - - transform_ = nullptr; -} - -NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) -{ - // If input is connected, retrieve value directly - if (node->IsInputConnectedForRender(input)) { - - TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range, true); - - // Value will equal something from the connected node, follow it - Node *output = node->GetConnectedRenderOutput(input); - NodeValueTable table = GenerateTable(output, adjusted_range, node); - return table; - - } else { - - // Store node - QVariant return_val; - bool is_array = node->InputIsArray(input); - - if (is_array) { - - // Value is an array, we will return a list of NodeValueTables - NodeValueTableArray array_tbl; - - Node::ActiveElements a = node->GetActiveElementsAtTime(input, range); - if (a.mode() == Node::ActiveElements::kAllElements) { - int sz = node->InputArraySize(input); - for (int i=0; iInputTimeAdjustment(input, -1, range, true); - - return_val = node->GetValueAtTime(input, adjusted_range.in()); - - } - - NodeValueTable return_table; - return_table.Push(node->GetInputDataType(input), return_val, node, is_array); - return return_table; - - } -} - -void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range) -{ - NodeValueTable& sub_tbl = array_tbl[element]; - TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range, true); - - if (node->IsInputConnectedForRender(input, element)) { - Node *output = node->GetConnectedRenderOutput(input, element); - sub_tbl = GenerateTable(output, adjusted_range, node); - } else { - QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element); - sub_tbl.Push(node->GetInputDataType(input), input_value, node); - } -} - -NodeTraverser::NodeTraverser() : - cancel_(nullptr), - transform_(nullptr), - loop_mode_(LoopMode::kLoopModeOff) -{ -} - -class GTTTime -{ -public: - GTTTime(const Node *n) { t = QDateTime::currentMSecsSinceEpoch(); node = n; } - - ~GTTTime() { qDebug() << "GT for" << node << "took" << (QDateTime::currentMSecsSinceEpoch() - t); } - - qint64 t; - const Node *node; - -}; - -NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range, const Node *next_node) -{ - // NOTE: Times how long a node takes to process, useful for profiling. - //GTTTime gtt(n);Q_UNUSED(gtt); - - // Use table cache to skip processing where available - if (value_cache_.contains(n)) { - QHash &node_value_map = value_cache_[n]; - if (node_value_map.contains(range)) { - return node_value_map.value(range); - } - } - - // Generate row for node - NodeValueDatabase database = GenerateDatabase(n, range); - - // Check for bypass - bool is_enabled; - if (!database[Node::kEnabledInput].Has(NodeValue::kBoolean)) { - // Fallback if we couldn't find a bool value - is_enabled = true; - } else { - is_enabled = database[Node::kEnabledInput].Get(NodeValue::kBoolean).toBool(); - } - - NodeValueTable table; - - if (is_enabled) { - NodeValueRow row = GenerateRow(&database, n, range); - - // Generate output table - table = database.Merge(); - - // By this point, the node should have all the inputs it needs to render correctly - NodeGlobals globals(video_params_, audio_params_, range, loop_mode_); - n->Value(row, globals, &table); - - // `transform_now_` is the next node in the path that needs to be traversed. It only ever goes - // "down" the graph so that any traversing going back up doesn't unnecessarily transform - // from unrelated nodes or the same node twice - if (transform_) { - if (transform_now_ == n || transform_start_ == n) { - if (transform_now_ == n) { - QTransform t = n->GizmoTransformation(row, globals); - if (!t.isIdentity()) { - (*transform_) *= t; - } - } - - transform_now_ = next_node; - } - } - } else { - // If this node has an effect input, ensure that is pushed last - NodeValueTable primary; - if (!n->GetEffectInputID().isEmpty()) { - primary = database.Take(n->GetEffectInputID()); - } - - table = database.Merge(); - table.Push(primary); - } - - value_cache_[n][range] = table; - - return table; -} - -TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val) -{ - return nullptr; -} - -QVector2D NodeTraverser::GenerateResolution() const -{ - return QVector2D(video_params_.square_pixel_width(), video_params_.height()); -} - -void NodeTraverser::ResolveJobs(NodeValue &val) -{ - if (val.type() == NodeValue::kTexture) { - - if (TexturePtr job_tex = val.toTexture()) { - if (AcceleratedJob *base_job = job_tex->job()) { - - if (resolved_texture_cache_.contains(job_tex.get())) { - val.set_value(resolved_texture_cache_.value(job_tex.get())); - } else { - // Resolve any sub-jobs - for (auto it=base_job->GetValues().begin(); it!=base_job->GetValues().end(); it++) { - // Jobs will almost always be submitted with one of these types - NodeValue &subval = it.value(); - ResolveJobs(subval); - } - - if (CacheJob *cj = dynamic_cast(base_job)) { - TexturePtr tex = ProcessVideoCacheJob(cj); - if (tex) { - val.set_value(tex); - } else { - val.set_value(cj->GetFallback()); - } - - } else if (ColorTransformJob *ctj = dynamic_cast(base_job)) { - - VideoParams ctj_params = job_tex->params(); - - ctj_params.set_format(GetCacheVideoParams().format()); - - TexturePtr dest = CreateTexture(ctj_params); - - // Resolve input texture - NodeValue v = ctj->GetInputTexture(); - ResolveJobs(v); - ctj->SetInputTexture(v); - - ProcessColorTransform(dest, val.source(), ctj); - - val.set_value(dest); - - } else if (ShaderJob *sj = dynamic_cast(base_job)) { - - VideoParams tex_params = job_tex->params(); - - TexturePtr tex = CreateTexture(tex_params); - - ProcessShader(tex, val.source(), sj); - - val.set_value(tex); - - } else if (GenerateJob *gj = dynamic_cast(base_job)) { - - VideoParams tex_params = job_tex->params(); - - TexturePtr tex = CreateTexture(tex_params); - - ProcessFrameGeneration(tex, val.source(), gj); - - // Convert to reference space - const QString &colorspace = tex_params.colorspace(); - if (!colorspace.isEmpty()) { - // Set format to primary format - tex_params.set_format(GetCacheVideoParams().format()); - - TexturePtr dest = CreateTexture(tex_params); - - ConvertToReferenceSpace(dest, tex, colorspace); - - tex = dest; - } - - val.set_value(tex); - - } else if (FootageJob *fj = dynamic_cast(base_job)) { - - rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), fj->loop_mode(), fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base()); - - TexturePtr tex; - - if (footage_time.isNaN()) { - // Push dummy texture - tex = CreateDummyTexture(fj->video_params()); - } else { - VideoParams managed_params = fj->video_params(); - managed_params.set_format(GetCacheVideoParams().format()); - - tex = CreateTexture(managed_params); - ProcessVideoFootage(tex, fj, footage_time); - } - - val.set_value(tex); - - } - - // Cache resolved value - resolved_texture_cache_.insert(job_tex.get(), val.toTexture()); - } - } - } - - } else if (val.type() == NodeValue::kSamples) { - - if (val.canConvert()) { - - SampleJob job = val.value(); - SampleBuffer output_buffer = CreateSampleBuffer(job.samples().audio_params(), job.samples().sample_count()); - ProcessSamples(output_buffer, val.source(), job.time(), job); - val.set_value(QVariant::fromValue(output_buffer)); - - } else if (val.canConvert()) { - - FootageJob job = val.value(); - SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), job.time().length()); - ProcessAudioFootage(buffer, &job, job.time()); - val.set_value(buffer); - - } - - } -} - -TexturePtr NodeTraverser::CreateDummyTexture(const VideoParams &p) -{ - return std::make_shared(p); -} - -} diff --git a/app/node/traverser.h b/app/node/traverser.h deleted file mode 100644 index 84284a0d80..0000000000 --- a/app/node/traverser.h +++ /dev/null @@ -1,169 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef NODETRAVERSER_H -#define NODETRAVERSER_H - -#include - -#include "codec/decoder.h" -#include "common/cancelableobject.h" -#include "node/output/track/track.h" -#include "render/job/cachejob.h" -#include "render/cancelatom.h" -#include "render/job/footagejob.h" -#include "render/job/colortransformjob.h" -#include "render/job/footagejob.h" -#include "value.h" - -namespace olive { - -class NodeTraverser -{ -public: - NodeTraverser(); - - NodeValueTable GenerateTable(const Node *n, const TimeRange &range, const Node *next_node = nullptr); - - virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); - - NodeValueRow GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range); - NodeValueRow GenerateRow(const Node *node, const TimeRange &range); - - NodeValue GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table, const TimeRange &time); - NodeValue GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table, const TimeRange &time); - int GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table); - int GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table); - - void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range); - - const VideoParams& GetCacheVideoParams() const - { - return video_params_; - } - - void SetCacheVideoParams(const VideoParams& params) - { - video_params_ = params; - } - - const AudioParams& GetCacheAudioParams() const - { - return audio_params_; - } - - void SetCacheAudioParams(const AudioParams& params) - { - audio_params_ = params; - } - -protected: - NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range); - - void ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range); - - virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time){} - - virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time){} - - virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job){} - - virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job){} - - virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job){} - - virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job){} - - virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs){} - - virtual TexturePtr ProcessVideoCacheJob(const CacheJob *val); - - virtual TexturePtr CreateTexture(const VideoParams &p) - { - return CreateDummyTexture(p); - } - - virtual SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) - { - // Return dummy by default - return SampleBuffer(); - } - - SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, const rational &length) - { - if (params.is_valid()) { - return CreateSampleBuffer(params, params.time_to_samples(length)); - } else { - return SampleBuffer(); - } - } - - QVector2D GenerateResolution() const; - - bool IsCancelled() - { - return cancel_ && cancel_->IsCancelled(); - } - - bool HeardCancel() const - { - return cancel_ && cancel_->HeardCancel(); - } - - CancelAtom *GetCancelPointer() const { return cancel_; } - void SetCancelPointer(CancelAtom *cancel) { cancel_ = cancel; } - - void ResolveJobs(NodeValue &value); - void ResolveAudioJobs(NodeValue &value); - - Block *GetCurrentBlock() const - { - return block_stack_.empty() ? nullptr : block_stack_.back(); - } - - LoopMode loop_mode() const { return loop_mode_; } - - virtual bool UseCache() const { return false; } - -private: - TexturePtr CreateDummyTexture(const VideoParams &p); - - VideoParams video_params_; - - AudioParams audio_params_; - - CancelAtom *cancel_; - - const Node *transform_start_; - const Node *transform_now_; - QTransform *transform_; - - std::list block_stack_; - - LoopMode loop_mode_; - - QHash > value_cache_; - QHash resolved_texture_cache_; - -}; - -} - -#endif // NODETRAVERSER_H diff --git a/app/node/type.h b/app/node/type.h new file mode 100644 index 0000000000..2719542e3e --- /dev/null +++ b/app/node/type.h @@ -0,0 +1,60 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 TYPE_H +#define TYPE_H + +#include + +namespace olive { + +class type_t +{ + public: + constexpr type_t() : type_(0) {} + constexpr type_t(const char *x) : type_(insn_to_num(x)) {} + + static type_t fromString(const QStringView &s) { return s.toUtf8().constData(); } + QString toString() const + { + const char *c = reinterpret_cast(&type_); + return QString::fromUtf8(c, strnlen(c, sizeof(type_))); + } + + bool operator==(const type_t &t) const { return type_ == t.type_; } + bool operator!=(const type_t &t) const { return !(*this == t); } + bool operator<(const type_t &t) const { return type_ < t.type_; } + bool operator<=(const type_t &t) const { return type_ <= t.type_; } + bool operator>(const type_t &t) const { return type_ > t.type_; } + bool operator>=(const type_t &t) const { return type_ >= t.type_; } + + private: + constexpr uint64_t insn_to_num(const char* x) + { + return (x && *x) ? *x + (insn_to_num(x+1) << 8) : 0; + } + + uint64_t type_; + +}; + +} + +#endif // TYPE_H diff --git a/app/node/value.cpp b/app/node/value.cpp index dab17809ed..c4a0882ad4 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -27,455 +27,410 @@ #include #include "common/tohex.h" +#include "render/job/samplejob.h" +#include "render/job/shaderjob.h" #include "render/subtitleparams.h" #include "render/videoparams.h" namespace olive { -QString NodeValue::ValueToString(Type data_type, const QVariant &value, bool value_is_a_key_track) -{ - if (!value_is_a_key_track && data_type == kVec2) { - QVector2D vec = value.value(); - - return QStringLiteral("%1:%2").arg(QString::number(vec.x()), - QString::number(vec.y())); - } else if (!value_is_a_key_track && data_type == kVec3) { - QVector3D vec = value.value(); - - return QStringLiteral("%1:%2:%3").arg(QString::number(vec.x()), - QString::number(vec.y()), - QString::number(vec.z())); - } else if (!value_is_a_key_track && data_type == kVec4) { - QVector4D vec = value.value(); - - return QStringLiteral("%1:%2:%3:%4").arg(QString::number(vec.x()), - QString::number(vec.y()), - QString::number(vec.z()), - QString::number(vec.w())); - } else if (!value_is_a_key_track && data_type == kColor) { - Color c = value.value(); - - return QStringLiteral("%1:%2:%3:%4").arg(QString::number(c.red()), - QString::number(c.green()), - QString::number(c.blue()), - QString::number(c.alpha())); - } else if (!value_is_a_key_track && data_type == kBezier) { - Bezier b = value.value(); - - return QStringLiteral("%1:%2:%3:%4:%5:%6").arg(QString::number(b.x()), - QString::number(b.y()), - QString::number(b.cp1_x()), - QString::number(b.cp1_y()), - QString::number(b.cp2_x()), - QString::number(b.cp2_y())); - } else if (data_type == kRational) { - return QString::fromStdString(value.value().toString()); - } else if (data_type == kTexture - || data_type == kSamples - || data_type == kNone) { - // These data types need no XML representation - return QString(); - } else if (data_type == kInt) { - return QString::number(value.value()); - } else if (data_type == kBinary) { - return value.toByteArray().toBase64(); - } else { - if (value.canConvert()) { - return value.toString(); +std::map > value_t::converters_; + +const std::vector value_t::XYZW_IDS = {"x", "y", "z", "w"}; +const std::vector value_t::RGBA_IDS = {"r", "g", "b", "a"}; + +QChar CHANNEL_SPLITTER = ':'; + +struct TextureChannel +{ + TexturePtr texture; + size_t channel; +}; + +struct SampleJobChannel +{ + AudioJobPtr job; + size_t channel; +}; + +value_t::value_t(TexturePtr texture) : + value_t(TYPE_TEXTURE) +{ + if (texture) { + size_t sz = texture->channel_count(); + data_.resize(sz); + for (size_t i = 0; i < sz; i++) { + data_[i] = TextureChannel({texture, i}); } - if (!value.isNull()) { - qWarning() << "Failed to convert type" << ToHex(data_type) << "to string"; + switch (sz) { + case 2: + data_[1].set_id("a"); + /* fall-through */ + case 1: + data_[0].set_id("l"); + break; + case 4: + data_[3].set_id("a"); + case 3: + data_[0].set_id("r"); + data_[1].set_id("g"); + data_[2].set_id("b"); + break; } + } +} - return QString(); +value_t::value_t(AudioJobPtr job) +{ + if (job) { + std::vector channel_names = job->params().channel_layout().getChannelNames(); + size_t sz = job->params().channel_count(); + data_.resize(sz); + for (size_t i = 0; i < sz; i++) { + data_[i] = component_t(SampleJobChannel({job, i}), channel_names.at(i)); + } + type_ = TYPE_SAMPLES; } } -QVector NodeValue::split_normal_value_into_track_values(Type type, const QVariant &value) +value_t::value_t(const SampleJob &job) : + value_t(AudioJob::Create(job.audio_params(), job)) { - QVector vals(get_number_of_keyframe_tracks(type)); +} - switch (type) { - case kVec2: - { - QVector2D vec = value.value(); - vals.replace(0, vec.x()); - vals.replace(1, vec.y()); - break; - } - case kVec3: - { - QVector3D vec = value.value(); - vals.replace(0, vec.x()); - vals.replace(1, vec.y()); - vals.replace(2, vec.z()); - break; - } - case kVec4: - { - QVector4D vec = value.value(); - vals.replace(0, vec.x()); - vals.replace(1, vec.y()); - vals.replace(2, vec.z()); - vals.replace(3, vec.w()); - break; - } - case kColor: - { - Color c = value.value(); - vals.replace(0, c.red()); - vals.replace(1, c.green()); - vals.replace(2, c.blue()); - vals.replace(3, c.alpha()); - break; - } - case kBezier: - { - Bezier b = value.value(); - vals.replace(0, b.x()); - vals.replace(1, b.y()); - vals.replace(2, b.cp1_x()); - vals.replace(3, b.cp1_y()); - vals.replace(4, b.cp2_x()); - vals.replace(5, b.cp2_y()); - break; - } - default: - vals.replace(0, value); +QString value_t::toString() const +{ + if (type_ != TYPE_STRING) { + return this->converted(TYPE_STRING).value(); } + return value(); +} - return vals; +ShaderCode GetSwizzleShaderCode(const QString &id) +{ + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/swizzle.frag"))); } -QVariant NodeValue::combine_track_values_into_normal_value(Type type, const QVector &split) +void SwizzleAudio(const void *context, const SampleJob &job, SampleBuffer &output) { - if (split.isEmpty()) { - return QVariant(); + for (int i = 0; i < output.channel_count(); i++) { + SampleBuffer b = job.Get(QStringLiteral("b.%1").arg(i)).toSamples(); + if (b.is_allocated()) { + size_t c = job.Get(QStringLiteral("c.%1").arg(i)).toInt(); + output.fast_set(b, i, c); + } } +} - switch (type) { - case kVec2: - { - return QVector2D(split.at(0).toFloat(), - split.at(1).toFloat()); - } - case kVec3: - { - return QVector3D(split.at(0).toFloat(), - split.at(1).toFloat(), - split.at(2).toFloat()); - } - case kVec4: - { - return QVector4D(split.at(0).toFloat(), - split.at(1).toFloat(), - split.at(2).toFloat(), - split.at(3).toFloat()); - } - case kColor: - { - return QVariant::fromValue(Color(split.at(0).toFloat(), - split.at(1).toFloat(), - split.at(2).toFloat(), - split.at(3).toFloat())); +TexturePtr value_t::toTexture() const +{ + if (type_ != TYPE_TEXTURE || data_.empty()) { + return TexturePtr(); } - case kBezier: - return QVariant::fromValue(Bezier(split.at(0).toDouble(), - split.at(1).toDouble(), - split.at(2).toDouble(), - split.at(3).toDouble(), - split.at(4).toDouble(), - split.at(5).toDouble())); - default: - return split.first(); + + bool swizzled = false; + + TextureChannel last; + VideoParams vp; + + for (auto it = data_.cbegin(); it != data_.cend(); it++) { + const TextureChannel &c = it->value(); + + if (it != data_.cbegin() && !swizzled) { + if (c.texture != last.texture || c.channel != last.channel + 1) { + swizzled = true; + } + } + + if (!vp.is_valid() && c.texture && c.texture->params().is_valid()) { + vp = c.texture->params(); + } + + last = c; } -} -int NodeValue::get_number_of_keyframe_tracks(Type type) -{ - switch (type) { - case NodeValue::kVec2: - return 2; - case NodeValue::kVec3: - return 3; - case NodeValue::kVec4: - case NodeValue::kColor: - return 4; - case NodeValue::kBezier: - return 6; - default: - return 1; + if (swizzled) { + // Return texture(s) wrapped in a swizzle shader + ShaderJob job; + job.set_function(GetSwizzleShaderCode); + job.Insert(QStringLiteral("red_texture"), this->value(0).texture); + job.Insert(QStringLiteral("green_texture"), this->value(1).texture); + job.Insert(QStringLiteral("blue_texture"), this->value(2).texture); + job.Insert(QStringLiteral("alpha_texture"), this->value(3).texture); + job.Insert(QStringLiteral("red_channel"), int(this->value(0).channel)); + job.Insert(QStringLiteral("green_channel"), int(this->value(1).channel)); + job.Insert(QStringLiteral("blue_channel"), int(this->value(2).channel)); + job.Insert(QStringLiteral("alpha_channel"), int(this->value(3).channel)); + return Texture::Job(vp, job); + } else { + // No swizzling has occurred + return last.texture; } } -QVariant NodeValue::StringToValue(Type data_type, const QString &string, bool value_is_a_key_track) +AudioJobPtr value_t::toAudioJob() const { - if (!value_is_a_key_track && data_type == kVec2) { - QStringList vals = string.split(':'); + if (type_ != TYPE_SAMPLES || data_.empty()) { + return AudioJobPtr(); + } - ValidateVectorString(&vals, 2); + bool swizzled = false; - return QVector2D(vals.at(0).toFloat(), vals.at(1).toFloat()); - } else if (!value_is_a_key_track && data_type == kVec3) { - QStringList vals = string.split(':'); + SampleJobChannel last; + AudioParams ap; + TimeRange time; - ValidateVectorString(&vals, 3); + for (auto it = data_.cbegin(); it != data_.cend(); it++) { + const SampleJobChannel &c = it->value(); - return QVector3D(vals.at(0).toFloat(), vals.at(1).toFloat(), vals.at(2).toFloat()); - } else if (!value_is_a_key_track && data_type == kVec4) { - QStringList vals = string.split(':'); + if (it != data_.cbegin() && !swizzled) { + if (c.job != last.job || c.channel != last.channel + 1) { + swizzled = true; + } + } + + if (c.job) { + if (!ap.is_valid() && c.job->params().is_valid()) { + ap = c.job->params(); + } + + if (time.length().isNull()) { + time = c.job->time(); + } + } - ValidateVectorString(&vals, 4); + last = c; + } - return QVector4D(vals.at(0).toFloat(), vals.at(1).toFloat(), vals.at(2).toFloat(), vals.at(3).toFloat()); - } else if (!value_is_a_key_track && data_type == kColor) { - QStringList vals = string.split(':'); + if (swizzled) { + if (ap.is_valid()) { + // Return texture(s) wrapped in a swizzle shader + ap.set_channel_layout(AudioChannelLayout::fromMask(av_get_default_channel_layout(data_.size()))); - ValidateVectorString(&vals, 4); + SampleJob swizzle(ValueParams(VideoParams(), ap, time, QString(), LoopMode(), nullptr, nullptr)); - return QVariant::fromValue(Color(vals.at(0).toDouble(), vals.at(1).toDouble(), vals.at(2).toDouble(), vals.at(3).toDouble())); - } else if (!value_is_a_key_track && data_type == kBezier) { - QStringList vals = string.split(':'); + for (size_t i = 0; i < data_.size(); i++) { + const SampleJobChannel &c = data_.at(i).value(); + swizzle.Insert(QStringLiteral("b.%1").arg(i), c.job); + swizzle.Insert(QStringLiteral("c.%1").arg(i), int(c.channel)); + } - ValidateVectorString(&vals, 6); + swizzle.set_function(SwizzleAudio, nullptr); - return QVariant::fromValue(Bezier(vals.at(0).toDouble(), vals.at(1).toDouble(), vals.at(2).toDouble(), vals.at(3).toDouble(), vals.at(4).toDouble(), vals.at(5).toDouble())); - } else if (data_type == kInt) { - return QVariant::fromValue(string.toLongLong()); - } else if (data_type == kRational) { - return QVariant::fromValue(rational::fromString(string.toStdString())); - } else if (data_type == kBinary) { - return QByteArray::fromBase64(string.toLatin1()); + return AudioJob::Create(ap, swizzle); + } else { + return nullptr; + } } else { - return string; + // No swizzling has occurred + return last.job; } } -void NodeValue::ValidateVectorString(QStringList* list, int count) +value_t value_t::fromSerializedString(type_t target_type, const QString &str) { - while (list->size() < count) { - list->append(QStringLiteral("0")); - } -} + QStringList l = str.split(CHANNEL_SPLITTER); + value_t v(TYPE_STRING, l.size()); -QString NodeValue::GetPrettyDataTypeName(Type type) -{ - switch (type) { - case kNone: - return QCoreApplication::translate("NodeValue", "None"); - case kInt: - case kCombo: - return QCoreApplication::translate("NodeValue", "Integer"); - case kFloat: - return QCoreApplication::translate("NodeValue", "Float"); - case kRational: - return QCoreApplication::translate("NodeValue", "Rational"); - case kBoolean: - return QCoreApplication::translate("NodeValue", "Boolean"); - case kColor: - return QCoreApplication::translate("NodeValue", "Color"); - case kMatrix: - return QCoreApplication::translate("NodeValue", "Matrix"); - case kText: - return QCoreApplication::translate("NodeValue", "Text"); - case kFont: - return QCoreApplication::translate("NodeValue", "Font"); - case kFile: - return QCoreApplication::translate("NodeValue", "File"); - case kTexture: - return QCoreApplication::translate("NodeValue", "Texture"); - case kSamples: - return QCoreApplication::translate("NodeValue", "Samples"); - case kVec2: - return QCoreApplication::translate("NodeValue", "Vector 2D"); - case kVec3: - return QCoreApplication::translate("NodeValue", "Vector 3D"); - case kVec4: - return QCoreApplication::translate("NodeValue", "Vector 4D"); - case kBezier: - return QCoreApplication::translate("NodeValue", "Bezier"); - case kVideoParams: - return QCoreApplication::translate("NodeValue", "Video Parameters"); - case kAudioParams: - return QCoreApplication::translate("NodeValue", "Audio Parameters"); - case kSubtitleParams: - return QCoreApplication::translate("NodeValue", "Subtitle Parameters"); - case kBinary: - return QCoreApplication::translate("NodeValue", "Binary"); - - case kDataTypeCount: - break; + for (size_t i = 0; i < v.size(); i++) { + v[i] = l[i]; } - return QCoreApplication::translate("NodeValue", "Unknown"); -} - -QString NodeValue::GetDataTypeName(Type type) -{ - switch (type) { - case kNone: - return QStringLiteral("none"); - case kInt: - return QStringLiteral("int"); - case kCombo: - return QStringLiteral("combo"); - case kFloat: - return QStringLiteral("float"); - case kRational: - return QStringLiteral("rational"); - case kBoolean: - return QStringLiteral("bool"); - case kColor: - return QStringLiteral("color"); - case kMatrix: - return QStringLiteral("matrix"); - case kText: - return QStringLiteral("text"); - case kFont: - return QStringLiteral("font"); - case kFile: - return QStringLiteral("file"); - case kTexture: - return QStringLiteral("texture"); - case kSamples: - return QStringLiteral("samples"); - case kVec2: - return QStringLiteral("vec2"); - case kVec3: - return QStringLiteral("vec3"); - case kVec4: - return QStringLiteral("vec4"); - case kBezier: - return QStringLiteral("bezier"); - case kVideoParams: - return QStringLiteral("vparam"); - case kAudioParams: - return QStringLiteral("aparam"); - case kSubtitleParams: - return QStringLiteral("sparam"); - case kBinary: - return QStringLiteral("binary"); - case kDataTypeCount: - break; + if (target_type != TYPE_STRING) { + v = v.converted(target_type); } - return QString(); + return v; } -NodeValue::Type NodeValue::GetDataTypeFromName(const QString &n) +QString value_t::toSerializedString() const { - // Slow but easy to maintain - for (int i=0; i(i); - if (GetDataTypeName(t) == n) { - return t; + if (this->type() == TYPE_STRING) { + QStringList l; + for (auto it = data_.cbegin(); it != data_.cend(); it++) { + l.append(it->value()); + } + return l.join(CHANNEL_SPLITTER); + } else { + bool ok; + value_t stringified = this->converted(TYPE_STRING, &ok); + if (ok) { + return stringified.toSerializedString(); + } else { + return QString(); } } - - return NodeValue::kNone; } -NodeValue NodeValueTable::Get(const QVector &type, const QString &tag) const +value_t value_t::converted(type_t to, bool *ok) const { - int value_index = GetValueIndex(type, tag); + // No-op if type is already requested + if (this->type() == to) { + if (ok) *ok = true; + return *this; + } - if (value_index >= 0) { - return values_.at(value_index); + // Find converter, no-op if none found + Converter_t c = converters_[this->type()][to]; + if (!c) { + if (ok) *ok = false; + return *this; } - return NodeValue(); + // Create new value with new type and same amount of channels + value_t v(to, data_.size()); + + // Run each channel through converter + for (size_t i = 0; i < data_.size(); i++) { + v.data_[0] = c(data_[0]); + } + + if (ok) *ok = true; + return v; } -NodeValue NodeValueTable::Take(const QVector &type, const QString &tag) +value_t::component_t converter_IntegerToString(const value_t::component_t &v) { - int value_index = GetValueIndex(type, tag); + return QString::number(v.value()); +} - if (value_index >= 0) { - return values_.takeAt(value_index); +value_t::component_t converter_StringToInteger(const value_t::component_t &v) +{ + if (!v.value().compare(QStringLiteral("false"))) { + return int64_t(0); + } else if (!v.value().compare(QStringLiteral("true"))) { + return int64_t(1); + } else { + return int64_t(v.value().toLongLong()); } +} - return NodeValue(); +value_t::component_t converter_DoubleToString(const value_t::component_t &v) +{ + return QString::number(v.value()); } -bool NodeValueTable::Has(NodeValue::Type type) const +value_t::component_t converter_StringToDouble(const value_t::component_t &v) { - for (int i=values_.size() - 1;i>=0;i--) { - const NodeValue& v = values_.at(i); + return v.value().toDouble(); +} - if (v.type() & type) { - return true; - } - } +value_t::component_t converter_RationalToString(const value_t::component_t &v) +{ + return v.value().toString(); +} - return false; +value_t::component_t converter_StringToRational(const value_t::component_t &v) +{ + return rational::fromString(v.value()); } -void NodeValueTable::Remove(const NodeValue &v) +value_t::component_t converter_BinaryToString(const value_t::component_t &v) { - for (int i=values_.size() - 1;i>=0;i--) { - const NodeValue& compare = values_.at(i); + return QString::fromLatin1(v.value().toBase64()); +} - if (compare == v) { - values_.removeAt(i); - return; - } - } +value_t::component_t converter_StringToBinary(const value_t::component_t &v) +{ + return QByteArray::fromBase64(v.value().toLatin1()); } -NodeValueTable NodeValueTable::Merge(QList tables) +value_t::component_t converter_IntegerToDouble(const value_t::component_t &v) { - if (tables.size() == 1) { - return tables.first(); - } + return double(v.value()); +} - int row = 0; +value_t::component_t converter_IntegerToRational(const value_t::component_t &v) +{ + return rational(v.value()); +} - NodeValueTable merged_table; +value_t::component_t converter_DoubleToInteger(const value_t::component_t &v) +{ + return int64_t(v.value()); +} - // Slipstreams all tables together - while (true) { - bool all_merged = true; +value_t::component_t converter_DoubleToRational(const value_t::component_t &v) +{ + return rational::fromDouble(v.value()); +} - foreach (const NodeValueTable& t, tables) { - if (row < t.Count()) { - all_merged = false; - } else { - continue; - } +value_t::component_t converter_RationalToInteger(const value_t::component_t &v) +{ + return int64_t(v.value().toDouble()); +} - int row_index = t.Count() - 1 - row; +value_t::component_t converter_RationalToDouble(const value_t::component_t &v) +{ + return v.value().toDouble(); +} - merged_table.Prepend(t.at(row_index)); - } +void value_t::registerConverter(const type_t &from, const type_t &to, Converter_t converter) +{ + if (converters_[from][to]) { + qWarning() << "Failed to register converter from" << from.toString() << "to" << to.toString() << "- converter already exists"; + return; + } - row++; + converters_[from][to] = converter; +} - if (all_merged) { - break; - } - } +void value_t::registerDefaultConverters() +{ + registerConverter(TYPE_INTEGER, TYPE_STRING, converter_IntegerToString); + registerConverter(TYPE_DOUBLE, TYPE_STRING, converter_DoubleToString); + registerConverter(TYPE_RATIONAL, TYPE_STRING, converter_RationalToString); + registerConverter(TYPE_BINARY, TYPE_STRING, converter_BinaryToString); + + registerConverter(TYPE_STRING, TYPE_INTEGER, converter_StringToInteger); + registerConverter(TYPE_STRING, TYPE_DOUBLE, converter_StringToDouble); + registerConverter(TYPE_STRING, TYPE_RATIONAL, converter_StringToRational); + registerConverter(TYPE_STRING, TYPE_BINARY, converter_StringToBinary); - return merged_table; + registerConverter(TYPE_INTEGER, TYPE_DOUBLE, converter_IntegerToDouble); + registerConverter(TYPE_INTEGER, TYPE_RATIONAL, converter_IntegerToRational); + + registerConverter(TYPE_DOUBLE, TYPE_INTEGER, converter_DoubleToInteger); + registerConverter(TYPE_DOUBLE, TYPE_RATIONAL, converter_DoubleToRational); + + registerConverter(TYPE_RATIONAL, TYPE_INTEGER, converter_RationalToInteger); + registerConverter(TYPE_RATIONAL, TYPE_DOUBLE, converter_RationalToDouble); } -int NodeValueTable::GetValueIndex(const QVector& types, const QString &tag) const +value_t::component_t value_t::component_t::converted(type_t from, type_t to, bool *ok) const { - int index = -1; + if (Converter_t c = converters_[from][to]) { + if (ok) *ok = true; + return c(*this); + } - for (int i=values_.size() - 1;i>=0;i--) { - const NodeValue& v = values_.at(i); + if (ok) *ok = false; + return *this; +} - if (types.contains(v.type())) { - index = i; +value_t::component_t value_t::component_t::fromSerializedString(type_t to, const QString &str) +{ + component_t c = str; + if (to != TYPE_STRING) { + c = c.converted(TYPE_STRING, to); + } + return c; +} - if (tag.isEmpty() || tag == v.tag()) { - break; - } - } +QString value_t::component_t::toSerializedString(type_t from) const +{ + if (from == TYPE_STRING) { + return value(); + } else { + component_t c = this->converted(from, TYPE_STRING); + return c.value(); } +} - return index; +QDebug operator<<(QDebug dbg, const type_t &t) +{ + return dbg << t.toString(); } } diff --git a/app/node/value.h b/app/node/value.h index d42ba9a598..256d50fef3 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -21,448 +21,310 @@ #ifndef NODEVALUE_H #define NODEVALUE_H +#include #include -#include -#include -#include -#include "common/qtutils.h" -#include "node/splitvalue.h" +#include "render/job/audiojob.h" +#include "render/samplebuffer.h" #include "render/texture.h" +#include "type.h" +#include "util/color.h" namespace olive { class Node; -class NodeValue; -class NodeValueTable; +class value_t; +class SampleJob; -using NodeValueArray = std::map; -using NodeValueTableArray = std::map; +using NodeValueArray = std::vector; -class NodeValue +QDebug operator<<(QDebug dbg, const type_t &t); + +constexpr type_t TYPE_NONE; +constexpr type_t TYPE_INTEGER = "int"; +constexpr type_t TYPE_DOUBLE = "dbl"; +constexpr type_t TYPE_RATIONAL = "rational"; +constexpr type_t TYPE_STRING = "str"; +constexpr type_t TYPE_TEXTURE = "tex"; +constexpr type_t TYPE_SAMPLES = "smp"; +constexpr type_t TYPE_BINARY = "bin"; +constexpr type_t TYPE_ARRAY = "arr"; +constexpr type_t TYPE_MATRIX = "mat"; + +class value_t { public: - /** - * @brief The types of data that can be passed between Nodes - */ - enum Type { - kNone, - - /** - ****************************** SPECIFIC IDENTIFIERS ****************************** - */ - - /** - * Integer type - * - * Resolves to int64_t. - */ - kInt, - - /** - * Decimal (floating-point) type - * - * Resolves to `double`. - */ - kFloat, - - /** - * Decimal (rational) type - * - * Resolves to `double`. - */ - kRational, - - /** - * Boolean type - * - * Resolves to `bool`. - */ - kBoolean, - - /** - * Floating-point type - * - * Resolves to `Color`. - * - * Colors passed around the nodes should always be in reference space and preferably use - */ - kColor, - - /** - * Matrix type - * - * Resolves to `QMatrix4x4`. - */ - kMatrix, - - /** - * Text type - * - * Resolves to `QString`. - */ - kText, - - /** - * Font type - * - * Resolves to `QFont`. - */ - kFont, - - /** - * File type - * - * Resolves to a `QString` containing an absolute file path. - */ - kFile, - - /** - * Image buffer type - * - * True value type depends on the render engine used. - */ - kTexture, - - /** - * Audio samples type - * - * Resolves to `SampleBufferPtr`. - */ - kSamples, - - /** - * Two-dimensional vector (XY) type - * - * Resolves to `QVector2D`. - */ - kVec2, - - /** - * Three-dimensional vector (XYZ) type - * - * Resolves to `QVector3D`. - */ - kVec3, - - /** - * Four-dimensional vector (XYZW) type - * - * Resolves to `QVector4D`. - */ - kVec4, - - /** - * Cubic bezier type that contains three X/Y coordinates, the main point, and two control points - * - * Resolves to `Bezier` - */ - kBezier, - - /** - * ComboBox type - * - * Resolves to `int` - the index currently selected - */ - kCombo, - - /** - * Video Parameters type - * - * Resolves to `VideoParams` - */ - kVideoParams, - - /** - * Audio Parameters type - * - * Resolves to `AudioParams` - */ - kAudioParams, - - /** - * Subtitle Parameters type - * - * Resolves to `SubtitleParams` - */ - kSubtitleParams, - - /** - * Binary Data - */ - kBinary, - - /** - * End of list - */ - kDataTypeCount + class component_t + { + public: + component_t() = default; + + template + component_t(const T &t, const type_t &id = type_t()) + { + set(t); + id_ = id; + } + + // Bad initializers, must catch these at runtime + component_t(const value_t &t) { abort(); } + component_t(const QVariant &t) { abort(); } + component_t(const std::vector &t) { abort(); } + + template + bool get(T *out) const + { + if (data_.has_value()) { + try { + *out = std::any_cast(data_); + return true; + } catch (std::bad_any_cast &) {} + } + + return false; + } + + template + T value() const + { + T t = T(); + + if (data_.has_value()) { + try { + t = std::any_cast(data_); + } catch (std::bad_any_cast &e) { + qCritical() << "Failed to cast" << data_.type().name() << "to" << typeid(T).name() << e.what(); + } + } + + return t; + } + + template + void set(const T &t) + { + data_ = t; + } + + const std::any &data() const { return data_; } + const type_t &id() const { return id_; } + void set_id(const type_t &id) { id_ = id; } + + component_t converted(type_t from, type_t to, bool *ok = nullptr) const; + + static component_t fromSerializedString(type_t to, const QString &s); + QString toSerializedString(type_t from) const; + + private: + std::any data_; + type_t id_; + }; - NodeValue() : - type_(kNone), - from_(nullptr), - array_(false) + value_t(const type_t &type, size_t channels = 1) : + type_(type) { + data_.resize(channels); } - template - NodeValue(Type type, const T& data, const Node* from = nullptr, bool array = false, const QString& tag = QString()) : - type_(type), - from_(from), - tag_(tag), - array_(array) + value_t(const type_t &type, const std::vector &components) : + type_(type) { - set_value(data); + data_ = components; } template - NodeValue(Type type, const T& data, const Node* from, const QString& tag) : - NodeValue(type, data, from, false, tag) + value_t(const type_t &type, const T &v) : + value_t(type) { + data_[0] = v; } - Type type() const - { - return type_; - } + value_t() : + type_(TYPE_NONE) + {} - template - T value() const - { - return data_.value(); - } + value_t(TexturePtr texture); - template - void set_value(const T &v) - { - data_ = QVariant::fromValue(v); - } + value_t(AudioJobPtr job); - const QVariant &data() const { return data_; } + value_t(const SampleJob &job); - template - bool canConvert() const + value_t(const SampleBuffer &samples) : + value_t(TYPE_SAMPLES, samples) { - return data_.canConvert(); } - const QString& tag() const + value_t(const QVector2D &vec) : + value_t(TYPE_DOUBLE, size_t(2)) { - return tag_; + data_[0] = component_t(double(vec.x()), XYZW_IDS.at(0)); + data_[1] = component_t(double(vec.y()), XYZW_IDS.at(1)); } - void set_tag(const QString& tag) + value_t(const QVector3D &vec) : + value_t(TYPE_DOUBLE, size_t(3)) { - tag_ = tag; + data_[0] = component_t(double(vec.x()), XYZW_IDS.at(0)); + data_[1] = component_t(double(vec.y()), XYZW_IDS.at(1)); + data_[2] = component_t(double(vec.z()), XYZW_IDS.at(2)); } - const Node* source() const + value_t(const QVector4D &vec) : + value_t(TYPE_DOUBLE, size_t(4)) { - return from_; + data_[0] = component_t(double(vec.x()), XYZW_IDS.at(0)); + data_[1] = component_t(double(vec.y()), XYZW_IDS.at(1)); + data_[2] = component_t(double(vec.z()), XYZW_IDS.at(2)); + data_[3] = component_t(double(vec.w()), XYZW_IDS.at(3)); } - bool array() const + value_t(const float &f) : + value_t(TYPE_DOUBLE, double(f)) { - return array_; } - bool operator==(const NodeValue& rhs) const + value_t(const double &d) : + value_t(TYPE_DOUBLE, d) { - return type_ == rhs.type_ && tag_ == rhs.tag_ && data_ == rhs.data_; } - operator bool() const + value_t(const int64_t &i) : + value_t(TYPE_INTEGER, i) { - return !data_.isNull(); } - static QString GetPrettyDataTypeName(Type type); - - static QString GetDataTypeName(Type type); - static NodeValue::Type GetDataTypeFromName(const QString &n); - - static QString ValueToString(Type data_type, const QVariant& value, bool value_is_a_key_track); - static QString ValueToString(const NodeValue &v, bool value_is_a_key_track) + value_t(const uint64_t &i) : + value_t(TYPE_INTEGER, int64_t(i)) { - return ValueToString(v.type_, v.data_, value_is_a_key_track); } - static QVariant StringToValue(Type data_type, const QString &string, bool value_is_a_key_track); - - static QVector split_normal_value_into_track_values(Type type, const QVariant &value); - - static QVariant combine_track_values_into_normal_value(Type type, const QVector& split); - - SplitValue to_split_value() const + value_t(const int &i) : + value_t(TYPE_INTEGER, int64_t(i)) { - return split_normal_value_into_track_values(type_, data_); } - /** - * @brief Returns whether a data type can be interpolated or not - */ - static bool type_can_be_interpolated(NodeValue::Type type) + value_t(const bool &i) : + value_t(TYPE_INTEGER, int64_t(i)) { - return type == kFloat - || type == kVec2 - || type == kVec3 - || type == kVec4 - || type == kBezier - || type == kColor - || type == kRational; } - static bool type_is_numeric(NodeValue::Type type) + value_t(const Color &i) : + value_t(TYPE_DOUBLE, size_t(4)) { - return type == kFloat - || type == kInt - || type == kRational; + data_[0] = component_t(double(i.red()), RGBA_IDS.at(0)); + data_[1] = component_t(double(i.green()), RGBA_IDS.at(1)); + data_[2] = component_t(double(i.blue()), RGBA_IDS.at(2)); + data_[3] = component_t(double(i.alpha()), RGBA_IDS.at(3)); } - static bool type_is_vector(NodeValue::Type type) + value_t(const QString &i) : + value_t(TYPE_STRING, i) { - return type == kVec2 - || type == kVec3 - || type == kVec4; } - static bool type_is_buffer(NodeValue::Type type) + value_t(const char *i) : + value_t(TYPE_STRING, QString::fromUtf8(i)) { - return type == kTexture - || type == kSamples; } - static int get_number_of_keyframe_tracks(Type type); - - static void ValidateVectorString(QStringList* list, int count); - - TexturePtr toTexture() const { return value(); } - SampleBuffer toSamples() const { return value(); } - bool toBool() const { return value(); } - double toDouble() const { return value(); } - int64_t toInt() const { return value(); } - rational toRational() const { return value(); } - QString toString() const { return value(); } - Color toColor() const { return value(); } - QMatrix4x4 toMatrix() const { return value(); } - VideoParams toVideoParams() const { return value(); } - AudioParams toAudioParams() const { return value(); } - QVector2D toVec2() const { return value(); } - QVector3D toVec3() const { return value(); } - QVector4D toVec4() const { return value(); } - Bezier toBezier() const { return value(); } - NodeValueArray toArray() const { return value(); } - -private: - Type type_; - QVariant data_; - const Node* from_; - QString tag_; - bool array_; - -}; - -class NodeValueTable -{ -public: - NodeValueTable() = default; - - NodeValue Get(NodeValue::Type type, const QString& tag = QString()) const + value_t(const rational &i) : + value_t(TYPE_RATIONAL, i) { - QVector types = {type}; - return Get(types, tag); } - NodeValue Get(const QVector& type, const QString& tag = QString()) const; - - NodeValue Take(NodeValue::Type type, const QString& tag = QString()) + value_t(const QByteArray &i) : + value_t(TYPE_BINARY, i) { - QVector types = {type}; - return Take(types, tag); } - NodeValue Take(const QVector& type, const QString& tag = QString()); - - void Push(const NodeValue& value) + value_t(const NodeValueArray &i) : + value_t(TYPE_ARRAY, i) { - values_.append(value); } - void Push(const NodeValueTable& value) + value_t(const QMatrix4x4 &i) : + value_t(TYPE_MATRIX, i) { - values_.append(value.values_); } - template - void Push(NodeValue::Type type, const T& data, const Node *from, bool array = false, const QString& tag = QString()) + const type_t &type() const { - Push(NodeValue(type, data, from, array, tag)); + return type_; } - template - void Push(NodeValue::Type type, const T& data, const Node *from, const QString& tag) - { - Push(NodeValue(type, data, from, false, tag)); - } + void set_type(const type_t &type) { type_ = type; } - void Prepend(const NodeValue& value) + component_t at(size_t channel) const { - values_.prepend(value); + if (channel < data_.size()) { + return data_[channel]; + } + return component_t(); } + component_t &operator[](size_t i) { return data_[i]; } + const component_t &operator[](size_t i) const { return data_[i]; } + template - void Prepend(NodeValue::Type type, const T& data, const Node *from, bool array = false, const QString& tag = QString()) + bool get(T *out, size_t channel = 0) const { - Prepend(NodeValue(type, data, from, array, tag)); + return at(channel).get(out); } template - void Prepend(NodeValue::Type type, const T& data, const Node *from, const QString& tag) + T value(size_t channel = 0) const { - Prepend(NodeValue(type, data, from, false, tag)); + return at(channel).value(); } - const NodeValue& at(int index) const - { - return values_.at(index); - } - NodeValue TakeAt(int index) - { - return values_.takeAt(index); - } + void resize(size_t s) { data_.resize(s); } + size_t size() const { return data_.size(); } - int Count() const - { - return values_.size(); - } + std::vector &data() { return data_; } + const std::vector &data() const { return data_; } - bool Has(NodeValue::Type type) const; - void Remove(const NodeValue& v); + bool isValid() const { return type_ != TYPE_NONE && !data_.empty(); } - void Clear() - { - values_.clear(); - } + bool toBool() const { return toInt(); } + double toDouble() const { return value(); } + int64_t toInt() const { return value(); } + rational toRational() const { return value(); } + QString toString() const; + Color toColor() const { return Color(value(0), value(1), value(2), value(3)); } + QVector2D toVec2() const { return QVector2D(value(0), value(1)); } + QVector3D toVec3() const { return QVector3D(value(0), value(1), value(2)); } + QVector4D toVec4() const { return QVector4D(value(0), value(1), value(2), value(3)); } + QMatrix4x4 toMatrix() const { return value(); } + QByteArray toBinary() const { return value(); } + NodeValueArray toArray() const { return value(); } + TexturePtr toTexture() const; + SampleBuffer toSamples() const { return value(); } + AudioJobPtr toAudioJob() const; - bool isEmpty() const - { - return values_.isEmpty(); - } + static value_t fromSerializedString(type_t target_type, const QString &s); + QString toSerializedString() const; - int GetValueIndex(const QVector &type, const QString& tag) const; + value_t converted(type_t to, bool *ok = nullptr) const; - static NodeValueTable Merge(QList tables); + typedef component_t(*Converter_t)(const component_t &v); + static void registerConverter(const type_t &from, const type_t &to, Converter_t converter); + static void registerDefaultConverters(); + + static const std::vector XYZW_IDS; + static const std::vector RGBA_IDS; private: - QVector values_; + type_t type_; + std::vector data_; + component_t meta_; -}; + static std::map > converters_; -using NodeValueRow = QHash; +}; } -Q_DECLARE_METATYPE(olive::NodeValue) -Q_DECLARE_METATYPE(olive::NodeValueTable) +Q_DECLARE_METATYPE(olive::value_t) #endif // NODEVALUE_H diff --git a/app/node/valuedatabase.h b/app/node/valuedatabase.h deleted file mode 100644 index 27745ee27e..0000000000 --- a/app/node/valuedatabase.h +++ /dev/null @@ -1,89 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef NODEVALUEDATABASE_H -#define NODEVALUEDATABASE_H - -#include "param.h" -#include "value.h" - -namespace olive { - -class NodeValueDatabase -{ -public: - NodeValueDatabase() = default; - - NodeValueTable& operator[](const QString& input_id) - { - return tables_[input_id]; - } - - void Insert(const QString& key, const NodeValueTable &value) - { - tables_.insert(key, value); - } - - NodeValueTable Take(const QString &key) - { - return tables_.take(key); - } - - NodeValueTable Merge() const; - - using Tables = QHash; - using const_iterator = Tables::const_iterator; - using iterator = Tables::iterator; - - inline const_iterator cbegin() const - { - return tables_.cbegin(); - } - - inline const_iterator cend() const - { - return tables_.cend(); - } - - inline iterator begin() - { - return tables_.begin(); - } - - inline iterator end() - { - return tables_.end(); - } - - inline bool contains(const QString& s) const - { - return tables_.contains(s); - } - -private: - Tables tables_; - -}; - -} - -Q_DECLARE_METATYPE(olive::NodeValueDatabase) - -#endif // NODEVALUEDATABASE_H diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt index 0f4c9348fa..9744e69f8f 100644 --- a/app/panel/CMakeLists.txt +++ b/app/panel/CMakeLists.txt @@ -25,7 +25,6 @@ add_subdirectory(pixelsampler) add_subdirectory(project) add_subdirectory(scope) add_subdirectory(sequenceviewer) -add_subdirectory(table) add_subdirectory(taskmanager) add_subdirectory(timebased) add_subdirectory(timeline) diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 28f7bf2ec8..d4b8cfd5e1 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -67,11 +67,6 @@ class ViewerPanelBase : public TimeBasedPanel GetViewerWidget()->UpdateTextureFromNode(); } - void AddPlaybackDevice(ViewerDisplayWidget *vw) - { - GetViewerWidget()->AddPlaybackDevice(vw); - } - void SetTimelineSelectedBlocks(const QVector &b) { GetViewerWidget()->SetTimelineSelectedBlocks(b); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 7b632d89f4..8b4b282578 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -57,7 +57,6 @@ set(OLIVE_SOURCES render/renderprocessor.h render/renderticket.cpp render/renderticket.h - render/shadercode.h render/subtitleparams.cpp render/subtitleparams.h render/texture.cpp diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index cb7ddcd3b9..49fee2d30d 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -24,6 +24,7 @@ #include "codec/frame.h" #include "render/playbackcache.h" #include "render/videoparams.h" +#include "util/timecodefunctions.h" namespace olive { diff --git a/app/render/job/CMakeLists.txt b/app/render/job/CMakeLists.txt index 6ad233bbe0..932cb73a5f 100644 --- a/app/render/job/CMakeLists.txt +++ b/app/render/job/CMakeLists.txt @@ -18,6 +18,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/job/acceleratedjob.cpp render/job/acceleratedjob.h + render/job/audiojob.cpp + render/job/audiojob.h render/job/footagejob.h render/job/generatejob.h render/job/samplejob.h diff --git a/app/render/job/acceleratedjob.h b/app/render/job/acceleratedjob.h index 7373d52c1e..214c8b8e72 100644 --- a/app/render/job/acceleratedjob.h +++ b/app/render/job/acceleratedjob.h @@ -21,19 +21,30 @@ #ifndef ACCELERATEDJOB_H #define ACCELERATEDJOB_H -#include "node/param.h" -#include "node/valuedatabase.h" +#include "node/value.h" namespace olive { class AcceleratedJob { public: + using NodeValueRow = QHash; + AcceleratedJob() = default; virtual ~AcceleratedJob(){} - NodeValue Get(const QString& input) const + const QString& GetShaderID() const + { + return id_; + } + + void SetShaderID(const QString& id) + { + id_ = id; + } + + value_t Get(const QString& input) const { return value_map_.value(input); } @@ -43,7 +54,7 @@ class AcceleratedJob value_map_.insert(input, row.value(input)); } - void Insert(const QString& input, const NodeValue& value) + void Insert(const QString& input, const value_t& value) { value_map_.insert(input, value); } @@ -63,6 +74,8 @@ class AcceleratedJob NodeValueRow &GetValues() { return value_map_; } private: + QString id_; + NodeValueRow value_map_; }; diff --git a/app/node/valuedatabase.cpp b/app/render/job/audiojob.cpp similarity index 63% rename from app/node/valuedatabase.cpp rename to app/render/job/audiojob.cpp index f83e2a7f86..8aa86f5bc2 100644 --- a/app/node/valuedatabase.cpp +++ b/app/render/job/audiojob.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,18 +18,28 @@ ***/ -#include "valuedatabase.h" +#include "audiojob.h" + +#include "acceleratedjob.h" +#include "footagejob.h" +#include "samplejob.h" namespace olive { -NodeValueTable NodeValueDatabase::Merge() const +AudioJob::~AudioJob() { - QHash copy = tables_; + delete job_; +} - // Kinda hacky, but we don't need this table to slipstream - copy.remove(QStringLiteral("global")); +TimeRange AudioJob::time() const +{ + if (FootageJob *f = dynamic_cast(job_)) { + return f->time(); + } else if (SampleJob *s = dynamic_cast(job_)) { + return s->value_params().time(); + } - return NodeValueTable::Merge(copy.values()); + return TimeRange(); } } diff --git a/app/render/job/audiojob.h b/app/render/job/audiojob.h new file mode 100644 index 0000000000..015d24a026 --- /dev/null +++ b/app/render/job/audiojob.h @@ -0,0 +1,75 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 AUDIOJOB_H +#define AUDIOJOB_H + +#include + +#include "common/define.h" +#include "render/audioparams.h" +#include "util/timerange.h" + +namespace olive { + +class AcceleratedJob; + +class AudioJob; +using AudioJobPtr = std::shared_ptr; + +class AudioJob +{ +public: + AudioJob() + { + job_ = nullptr; + } + + template + AudioJob(const AudioParams ¶ms, const T &job) + { + job_ = new T(job); + params_ = params; + } + + template + static AudioJobPtr Create(const AudioParams ¶ms, const T &job) + { + return std::make_shared(params, job); + } + + ~AudioJob(); + + DISABLE_COPY_MOVE(AudioJob) + + AcceleratedJob *job() const { return job_; } + const AudioParams ¶ms() const { return params_; } + + TimeRange time() const; + +private: + AudioParams params_; + AcceleratedJob *job_; + +}; + +} + +#endif // AUDIOJOB_H diff --git a/app/render/job/cachejob.h b/app/render/job/cachejob.h index 4d7800ce12..b156f4fe05 100644 --- a/app/render/job/cachejob.h +++ b/app/render/job/cachejob.h @@ -33,7 +33,7 @@ class CacheJob : public AcceleratedJob { public: CacheJob() = default; - CacheJob(const QString &filename, const NodeValue &fallback = NodeValue()) + CacheJob(const QString &filename, const value_t &fallback = value_t()) { filename_ = filename; } @@ -41,13 +41,13 @@ class CacheJob : public AcceleratedJob const QString &GetFilename() const { return filename_; } void SetFilename(const QString &s) { filename_ = s; } - const NodeValue &GetFallback() const { return fallback_; } - void SetFallback(const NodeValue &val) { fallback_ = val; } + const value_t &GetFallback() const { return fallback_; } + void SetFallback(const value_t &val) { fallback_ = val; } private: QString filename_; - NodeValue fallback_; + value_t fallback_; }; diff --git a/app/render/job/colortransformjob.h b/app/render/job/colortransformjob.h index 2a5bdda818..3519692729 100644 --- a/app/render/job/colortransformjob.h +++ b/app/render/job/colortransformjob.h @@ -28,6 +28,7 @@ #include "render/alphaassoc.h" #include "render/colorprocessor.h" #include "render/texture.h" +#include "shaderjob.h" namespace olive { @@ -39,7 +40,7 @@ class ColorTransformJob : public AcceleratedJob ColorTransformJob() { processor_ = nullptr; - custom_shader_src_ = nullptr; + custom_shader_ = nullptr; input_alpha_association_ = kAlphaNone; clear_destination_ = true; force_opaque_ = false; @@ -62,12 +63,12 @@ class ColorTransformJob : public AcceleratedJob void SetOverrideID(const QString &id) { id_ = id; } - const NodeValue &GetInputTexture() const { return input_texture_; } - void SetInputTexture(const NodeValue &tex) { input_texture_ = tex; } + const value_t &GetInputTexture() const { return input_texture_; } + void SetInputTexture(const value_t &tex) { input_texture_ = tex; } void SetInputTexture(TexturePtr tex) { Q_ASSERT(!tex->IsDummy()); - input_texture_ = NodeValue(NodeValue::kTexture, tex); + input_texture_ = tex; } ColorProcessorPtr GetColorProcessor() const { return processor_; } @@ -76,13 +77,8 @@ class ColorTransformJob : public AcceleratedJob const AlphaAssociated &GetInputAlphaAssociation() const { return input_alpha_association_; } void SetInputAlphaAssociation(const AlphaAssociated &e) { input_alpha_association_ = e; } - const Node *CustomShaderSource() const { return custom_shader_src_; } - const QString &CustomShaderID() const { return custom_shader_id_; } - void SetNeedsCustomShader(const Node *node, const QString &id = QString()) - { - custom_shader_src_ = node; - custom_shader_id_ = id; - } + ShaderJob::GetShaderCodeFunction_t GetCustomShaderFunction() const { return custom_shader_; } + void SetCustomShaderFunction(ShaderJob::GetShaderCodeFunction_t shader) { custom_shader_ = shader; } bool IsClearDestinationEnabled() const { return clear_destination_; } void SetClearDestinationEnabled(bool e) { clear_destination_ = e; } @@ -103,10 +99,9 @@ class ColorTransformJob : public AcceleratedJob ColorProcessorPtr processor_; QString id_; - NodeValue input_texture_; + value_t input_texture_; - const Node *custom_shader_src_; - QString custom_shader_id_; + ShaderJob::GetShaderCodeFunction_t custom_shader_; AlphaAssociated input_alpha_association_; diff --git a/app/render/job/generatejob.h b/app/render/job/generatejob.h index ed13998efa..acba2d9697 100644 --- a/app/render/job/generatejob.h +++ b/app/render/job/generatejob.h @@ -29,13 +29,34 @@ namespace olive { class GenerateJob : public AcceleratedJob { public: - GenerateJob() = default; + typedef void (*GenerateFrameFunction_t)(FramePtr frame, const GenerateJob &job); + + GenerateJob() + { + function_ = nullptr; + } + GenerateJob(const NodeValueRow &row) : GenerateJob() { Insert(row); } + void do_function(FramePtr frame) const + { + if (function_) { + function_(frame, *this); + } + } + + void set_function(GenerateFrameFunction_t function) + { + function_ = function; + } + +private: + GenerateFrameFunction_t function_; + }; } diff --git a/app/render/job/samplejob.h b/app/render/job/samplejob.h index 8d13baf893..92a6d83811 100644 --- a/app/render/job/samplejob.h +++ b/app/render/job/samplejob.h @@ -22,44 +22,58 @@ #define SAMPLEJOB_H #include "acceleratedjob.h" +#include "node/globals.h" namespace olive { class SampleJob : public AcceleratedJob { public: + typedef void (*ProcessSamplesCallback_t)(const void *context, const SampleJob &job, SampleBuffer &output); + SampleJob() { + sample_count_ = 0; + function_ = nullptr; + function_context_ = nullptr; } - SampleJob(const TimeRange &time, const NodeValue& value) + SampleJob(const ValueParams &p, size_t sample_count) { - samples_ = value.toSamples(); - time_ = time; + value_params_ = p.with_cache(nullptr); // Remove cache because it actually slows per-sample ops + sample_count_ = sample_count; + function_ = nullptr; + function_context_ = nullptr; } - SampleJob(const TimeRange &time, const QString& from, const NodeValueRow& row) + SampleJob(const ValueParams &p) : + SampleJob(p, p.aparams().time_to_samples(p.time().length())) { - samples_ = row[from].toSamples(); - time_ = time; } - const SampleBuffer &samples() const + const ValueParams &value_params() const { return value_params_; } + const AudioParams &audio_params() const { return value_params_.aparams(); } + size_t sample_count() const { return sample_count_; } + + void do_function(SampleBuffer &out) const { - return samples_; + if (function_) { + function_(function_context_, *this, out); + } } - bool HasSamples() const + void set_function(ProcessSamplesCallback_t f, const void *context) { - return samples_.is_allocated(); + function_ = f; + function_context_ = context; } - const TimeRange &time() const { return time_; } - private: - SampleBuffer samples_; + ValueParams value_params_; + size_t sample_count_; - TimeRange time_; + ProcessSamplesCallback_t function_; + const void *function_context_; }; diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h index 4cba60e7a2..9bf27e04d4 100644 --- a/app/render/job/shaderjob.h +++ b/app/render/job/shaderjob.h @@ -25,17 +25,42 @@ #include #include "acceleratedjob.h" +#include "common/filefunctions.h" #include "render/texture.h" namespace olive { +class ShaderCode { +public: + ShaderCode(const QString& frag_code = QString(), const QString& vert_code = QString()) : + frag_code_(frag_code), + vert_code_(vert_code) + { + } + + const QString& frag_code() const { return frag_code_; } + void set_frag_code(const QString &f) { frag_code_ = f; } + + const QString& vert_code() const { return vert_code_; } + void set_vert_code(const QString &v) { vert_code_ = v; } + +private: + QString frag_code_; + + QString vert_code_; + +}; + class ShaderJob : public AcceleratedJob { public: + typedef ShaderCode (*GetShaderCodeFunction_t)(const QString &id); + ShaderJob() { iterations_ = 1; iterative_input_ = nullptr; + function_ = nullptr; } ShaderJob(const NodeValueRow &row) : @@ -44,21 +69,6 @@ class ShaderJob : public AcceleratedJob Insert(row); } - const QString& GetShaderID() const - { - return shader_id_; - } - - void SetShaderID(const QString& id) - { - shader_id_ = id; - } - - void SetIterations(int iterations, const NodeInput& iterative_input) - { - SetIterations(iterations, iterative_input.input()); - } - void SetIterations(int iterations, const QString& iterative_input) { iterations_ = iterations; @@ -85,11 +95,6 @@ class ShaderJob : public AcceleratedJob return interpolation_; } - void SetInterpolation(const NodeInput& input, Texture::Interpolation interp) - { - interpolation_.insert(input.input(), interp); - } - void SetInterpolation(const QString& id, Texture::Interpolation interp) { interpolation_.insert(id, interp); @@ -105,9 +110,14 @@ class ShaderJob : public AcceleratedJob return vertex_overrides_; } -private: - QString shader_id_; + GetShaderCodeFunction_t function() const { return function_; } + void set_function(GetShaderCodeFunction_t f) { function_ = f; } + ShaderCode do_function() const + { + return function_ ? function_(GetShaderID()) : ShaderCode(); + } +private: int iterations_; QString iterative_input_; @@ -116,6 +126,8 @@ class ShaderJob : public AcceleratedJob QVector vertex_overrides_; + GetShaderCodeFunction_t function_; + }; } diff --git a/app/render/managedcolor.h b/app/render/managedcolor.h index 93411425ae..9b56c38778 100644 --- a/app/render/managedcolor.h +++ b/app/render/managedcolor.h @@ -21,9 +21,8 @@ #ifndef MANAGEDCOLOR_H #define MANAGEDCOLOR_H -#include - #include "colortransform.h" +#include "util/color.h" namespace olive { diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index b7451395f9..8585871fcb 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -383,11 +383,6 @@ Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) Color c = Color::fromData(data.data(), texture->format(), texture->channel_count()); - if (texture->channel_count() == VideoParams::kRGBChannelCount) { - // No alpha channel, set to 1.0 - c.set_alpha(1.0); - } - DetachTextureAsDestination(); return c; @@ -419,58 +414,30 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video } // This variable is used in the shader, let's set it - const NodeValue& value = it.value(); - - // Arrays are not currently supported in this system - if (value.array()) { - continue; - } + const value_t& value = it.value(); - switch (value.type()) { - case NodeValue::kInt: + if (value.type() == TYPE_INTEGER) { // 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. functions_->glUniform1i(variable_location, value.toInt()); - break; - case NodeValue::kFloat: - // kFloat technically specifies a double but as above, OpenGL doesn't support those. - functions_->glUniform1f(variable_location, value.toDouble()); - break; - case NodeValue::kVec2: - { - QVector2D v = value.toVec2(); - functions_->glUniform2fv(variable_location, 1, reinterpret_cast(&v)); - break; - } - case NodeValue::kVec3: - { - QVector3D v = value.toVec3(); - functions_->glUniform3fv(variable_location, 1, reinterpret_cast(&v)); - break; - } - case NodeValue::kVec4: - { - QVector4D v = value.toVec4(); - functions_->glUniform4fv(variable_location, 1, reinterpret_cast(&v)); - break; - } - case NodeValue::kMatrix: + } else if (value.type() == TYPE_DOUBLE) { + switch (value.size()) { + case 1: + functions_->glUniform1f(variable_location, value.value(0)); + break; + case 2: + functions_->glUniform2f(variable_location, value.value(0), value.value(1)); + break; + case 3: + functions_->glUniform3f(variable_location, value.value(0), value.value(1), value.value(2)); + break; + case 4: + functions_->glUniform4f(variable_location, value.value(0), value.value(1), value.value(2), value.value(3)); + break; + } + } else if (value.type() == TYPE_MATRIX) { functions_->glUniformMatrix4fv(variable_location, 1, false, value.toMatrix().constData()); - break; - case NodeValue::kCombo: - functions_->glUniform1i(variable_location, value.toInt()); - break; - case NodeValue::kColor: - { - Color color = value.toColor(); - functions_->glUniform4f(variable_location, color.red(), color.green(), color.blue(), color.alpha()); - break; - } - case NodeValue::kBoolean: - functions_->glUniform1i(variable_location, value.toBool()); - break; - case NodeValue::kTexture: - { + } else if (value.type() == TYPE_TEXTURE) { TexturePtr texture = value.toTexture(); // Set value to bound texture @@ -486,21 +453,6 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video if (enable_param_location > -1) { functions_->glUniform1i(enable_param_location, tex_id > 0); } - break; - } - case NodeValue::kSamples: - case NodeValue::kText: - case NodeValue::kRational: - case NodeValue::kFont: - case NodeValue::kFile: - case NodeValue::kVideoParams: - case NodeValue::kAudioParams: - case NodeValue::kSubtitleParams: - case NodeValue::kBezier: - case NodeValue::kBinary: - case NodeValue::kNone: - case NodeValue::kDataTypeCount: - break; } } @@ -518,13 +470,6 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video if (tex_id) { PrepareInputTexture(target, t.interpolation); - - if (texture->channel_count() == 1 && destination_params.channel_count() != 1) { - // Interpret this texture as a grayscale texture - functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED); - functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED); - functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); - } } } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index d4de7fe32b..94b7abebdd 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -21,7 +21,6 @@ #ifndef PLAYBACKCACHE_H #define PLAYBACKCACHE_H -#include #include #include #include @@ -29,8 +28,7 @@ #include #include "common/jobtime.h" - -using namespace olive::core; +#include "util/timerange.h" namespace olive { diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 9bf78ce102..2703c12368 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -24,7 +24,7 @@ #include #include "codec/conformmanager.h" -#include "node/input/multicam/multicamnode.h" +#include "common/qtutils.h" #include "node/inputdragger.h" #include "node/project.h" #include "render/diskmanager.h" @@ -40,7 +40,6 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : pause_thumbnails_(false), single_frame_render_(nullptr), display_color_processor_(nullptr), - multicam_(nullptr), ignore_cache_requests_(false) { copier_ = new ProjectCopier(this); @@ -235,7 +234,6 @@ void PreviewAutoCacher::VideoRendered() QVector tickets = video_immediate_passthroughs_.take(watcher); foreach (RenderTicketPtr t, tickets) { if (watcher->HasResult()) { - t->setProperty("multicam_output", watcher->GetTicket()->property("multicam_output")); t->Finish(watcher->Get()); } else { t->Finish(); @@ -614,14 +612,15 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, ViewerOutput *co rvp.AddCache(frame_cache); } + if (dynamic_cast(node)) { + rvp.node_output = QStringLiteral("all"); + } + rvp.return_type = dry ? RenderManager::kNull : RenderManager::kTexture; // Allow using cached images for this render job rvp.use_cache = true; - // Multicam - rvp.multicam = copier_->GetCopy(multicam_); - watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); return watcher; @@ -727,9 +726,6 @@ void PreviewAutoCacher::SetProject(Project *project) // Ensure all cache data is cleared video_cache_data_.clear(); audio_cache_data_.clear(); - - // Clear multicam reference - multicam_ = nullptr; } project_ = project; diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index c88f85695e..881af7dd21 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -91,8 +91,6 @@ class PreviewAutoCacher : public QObject void SetRendersPaused(bool e); void SetThumbnailsPaused(bool e); - void SetMulticamNode(MultiCamNode *n) { multicam_ = n; } - void SetIgnoreCacheRequests(bool e) { ignore_cache_requests_ = e; } public slots: @@ -181,8 +179,6 @@ public slots: ColorProcessorPtr display_color_processor_; - MultiCamNode *multicam_; - bool ignore_cache_requests_; private slots: diff --git a/app/render/projectcopier.cpp b/app/render/projectcopier.cpp index 9bcbb24918..ff41566e50 100644 --- a/app/render/projectcopier.cpp +++ b/app/render/projectcopier.cpp @@ -65,7 +65,7 @@ void ProjectCopier::SetProject(Project *project) // Add all connections foreach (Node* node, original_->nodes()) { for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - DoEdgeAdd(it->second, it->first); + DoEdgeAdd(it->first, it->second); } } @@ -165,22 +165,22 @@ void ProjectCopier::DoNodeRemove(Node *node) delete copy; } -void ProjectCopier::DoEdgeAdd(Node *output, const NodeInput &input) +void ProjectCopier::DoEdgeAdd(const NodeOutput &output, const NodeInput &input) { // Create same connection with our copied graph - Node* our_output = copy_map_.value(output); + Node* our_output = copy_map_.value(output.node()); Node* our_input = copy_map_.value(input.node()); - Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); + Node::ConnectEdge(NodeOutput(our_output, output.output()), NodeInput(our_input, input.input(), input.element())); } -void ProjectCopier::DoEdgeRemove(Node *output, const NodeInput &input) +void ProjectCopier::DoEdgeRemove(const NodeOutput &output, const NodeInput &input) { // Remove same connection with our copied graph - Node* our_output = copy_map_.value(output); + Node* our_output = copy_map_.value(output.node()); Node* our_input = copy_map_.value(input.node()); - Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element())); + Node::DisconnectEdge(NodeOutput(our_output, output.output()), NodeInput(our_input, input.input(), input.element())); } void ProjectCopier::DoValueChange(const NodeInput &input) @@ -227,23 +227,23 @@ void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy) void ProjectCopier::QueueNodeAdd(Node *node) { - graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr, QString(), QString()}); + graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), NodeOutput(), QString(), QString()}); UpdateGraphChangeValue(); } void ProjectCopier::QueueNodeRemove(Node *node) { - graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr, QString(), QString()}); + graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), NodeOutput(), QString(), QString()}); UpdateGraphChangeValue(); } -void ProjectCopier::QueueEdgeAdd(Node *output, const NodeInput &input) +void ProjectCopier::QueueEdgeAdd(const NodeOutput &output, const NodeInput &input) { graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output, QString(), QString()}); UpdateGraphChangeValue(); } -void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input) +void ProjectCopier::QueueEdgeRemove(const NodeOutput &output, const NodeInput &input) { graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output, QString(), QString()}); UpdateGraphChangeValue(); @@ -259,19 +259,19 @@ void ProjectCopier::QueueValueChange(const NodeInput &input) } }*/ - graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr, QString(), QString()}); + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, NodeOutput(), QString(), QString()}); UpdateGraphChangeValue(); } void ProjectCopier::QueueValueHintChange(const NodeInput &input) { - graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr, QString(), QString()}); + graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, NodeOutput(), QString(), QString()}); UpdateGraphChangeValue(); } void ProjectCopier::QueueProjectSettingChange(const QString &key, const QString &value) { - graph_update_queue_.push_back({QueuedJob::kProjectSettingChanged, nullptr, NodeInput(), nullptr, key, value}); + graph_update_queue_.push_back({QueuedJob::kProjectSettingChanged, nullptr, NodeInput(), NodeOutput(), key, value}); UpdateGraphChangeValue(); } diff --git a/app/render/projectcopier.h b/app/render/projectcopier.h index 727c62515b..88a55bb1f7 100644 --- a/app/render/projectcopier.h +++ b/app/render/projectcopier.h @@ -69,8 +69,8 @@ class ProjectCopier : public QObject private: void DoNodeAdd(Node* node); void DoNodeRemove(Node* node); - void DoEdgeAdd(Node *output, const NodeInput& input); - void DoEdgeRemove(Node *output, const NodeInput& input); + void DoEdgeAdd(const NodeOutput &output, const NodeInput& input); + void DoEdgeRemove(const NodeOutput &output, const NodeInput& input); void DoValueChange(const NodeInput& input); void DoValueHintChange(const NodeInput &input); void DoProjectSettingChange(const QString &key, const QString &value); @@ -98,7 +98,7 @@ class ProjectCopier : public QObject Type type; Node* node; NodeInput input; - Node *output; + NodeOutput output; QString key; QString value; @@ -117,9 +117,9 @@ private slots: void QueueNodeRemove(Node* node); - void QueueEdgeAdd(Node *output, const NodeInput& input); + void QueueEdgeAdd(const NodeOutput &output, const NodeInput& input); - void QueueEdgeRemove(Node *output, const NodeInput& input); + void QueueEdgeRemove(const NodeOutput &output, const NodeInput& input); void QueueValueChange(const NodeInput& input); diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index d00fa0d40c..18701262bf 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -108,9 +108,9 @@ TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const V color_cache_mutex_.unlock(); ShaderJob job; - job.Insert(QStringLiteral("top_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(top))); - job.Insert(QStringLiteral("bottom_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(bottom))); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(params.effective_width(), params.effective_height()))); + job.Insert(QStringLiteral("top_tex_in"), top); + job.Insert(QStringLiteral("bottom_tex_in"), bottom); + job.Insert(QStringLiteral("resolution_in"), QVector2D(params.effective_width(), params.effective_height())); TexturePtr output = CreateTexture(params); @@ -189,9 +189,9 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col color_job.GetColorProcessor()->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc); ShaderCode code; - if (const Node *shader_src = color_job.CustomShaderSource()) { + if (ShaderJob::GetShaderCodeFunction_t cf = color_job.GetCustomShaderFunction()) { // Use shader code from associated node - code = shader_src->GetShaderCode({color_job.CustomShaderID(), shader_desc->getShaderText()}); + code = cf(shader_desc->getShaderText()); } else { // Generate shader code using OCIO stub and our auto-generated name code = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/colormanage.frag")); @@ -293,18 +293,18 @@ void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *des ShaderJob job; job.Insert(QStringLiteral("ove_maintex"), color_job.GetInputTexture()); - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix())); - job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, color_job.GetCropMatrix().inverted())); - job.Insert(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, int(color_job.GetInputAlphaAssociation()))); - job.Insert(QStringLiteral("ove_force_opaque"), NodeValue(NodeValue::kBoolean, color_job.GetForceOpaque())); + job.Insert(QStringLiteral("ove_mvpmat"), color_job.GetTransformMatrix()); + job.Insert(QStringLiteral("ove_cropmatrix"), color_job.GetCropMatrix().inverted()); + job.Insert(QStringLiteral("ove_maintex_alpha"), color_job.GetInputAlphaAssociation()); + job.Insert(QStringLiteral("ove_force_opaque"), color_job.GetForceOpaque()); job.Insert(color_job.GetValues()); foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) { - job.Insert(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job.Insert(l.name, l.texture); job.SetInterpolation(l.name, l.interpolation); } foreach (const ColorContext::LUT& l, color_ctx.lut1d_textures) { - job.Insert(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job.Insert(l.name, l.texture); job.SetInterpolation(l.name, l.interpolation); } diff --git a/app/render/renderjobtracker.h b/app/render/renderjobtracker.h index d475818a73..e17216ee37 100644 --- a/app/render/renderjobtracker.h +++ b/app/render/renderjobtracker.h @@ -21,14 +21,11 @@ #ifndef RENDERJOBTRACKER_H #define RENDERJOBTRACKER_H -#include - #include "common/jobtime.h" +#include "util/timerange.h" namespace olive { -using namespace core; - class RenderJobTracker { public: diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index ab91b0280c..1d223bb6dd 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -117,7 +117,7 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) ticket->setProperty("cache", params.cache_dir); ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase)); ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id)); - ticket->setProperty("multicam", QtUtils::PtrToValue(params.multicam)); + ticket->setProperty("output", params.node_output); if (params.return_type == ReturnType::kNull) { dry_run_thread_->AddTicket(ticket); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 558478c471..a1f0a12b5b 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -28,7 +28,6 @@ #include "dialog/rendercancel/rendercancel.h" #include "node/output/viewer/viewer.h" #include "node/project.h" -#include "node/traverser.h" #include "render/previewautocacher.h" #include "render/renderer.h" #include "render/renderticket.h" @@ -118,7 +117,6 @@ class RenderManager : public QObject force_size = QSize(0, 0); force_channel_count = 0; mode = m; - multicam = nullptr; } void AddCache(FrameHashCache *cache) @@ -136,7 +134,7 @@ class RenderManager : public QObject bool use_cache; ReturnType return_type; RenderMode::Mode mode; - MultiCamNode *multicam; + QString node_output; QString cache_dir; rational cache_timebase; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 05a79b8c86..716ecde13e 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -26,6 +26,7 @@ #include #include "audio/audioprocessor.h" +#include "common/qtutils.h" #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" #include "node/project.h" @@ -33,8 +34,6 @@ namespace olive { -#define super NodeTraverser - RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache* decoder_cache, ShaderCache *shader_cache) : ticket_(ticket), render_ctx_(render_ctx), @@ -47,13 +46,13 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational { TimeRange range = TimeRange(time, time + frame_length); - NodeValueTable table; + value_t tex_val; if (Node* node = QtUtils::ValueToPtr(ticket_->property("node"))) { - table = GenerateTable(node, range); + ValueParams::Cache cache; + ValueParams vp(GetCacheVideoParams(), GetCacheAudioParams(), range, ticket_->property("output").toString(), LoopMode::kLoopModeOff, GetCancelPointer(), &cache); + tex_val = node->Value(vp); } - NodeValue tex_val = table.Get(NodeValue::kTexture); - ResolveJobs(tex_val); return tex_val.toTexture(); @@ -117,8 +116,8 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time // No color transform, just blit ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); + job.Insert(QStringLiteral("ove_maintex"), texture); + job.Insert(QStringLiteral("ove_mvpmat"), matrix); render_ctx_->BlitToTexture(render_ctx_->GetDefaultShader(), job, blit_tex.get()); @@ -219,13 +218,13 @@ void RenderProcessor::Run() { TimeRange time = ticket_->property("time").value(); - NodeValueTable table; + value_t sample_val; if (Node* node = QtUtils::ValueToPtr(ticket_->property("node"))) { - table = GenerateTable(node, time); + ValueParams::Cache cache; + ValueParams vp(GetCacheVideoParams(), GetCacheAudioParams(), time, ticket_->property("output").toString(), LoopMode::kLoopModeOff, GetCancelPointer(), &cache); + sample_val = node->Value(vp); } - NodeValue sample_val = table.Get(NodeValue::kSamples); - ResolveJobs(sample_val); SampleBuffer samples = sample_val.toSamples(); @@ -294,28 +293,6 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c return dec; } -NodeValueDatabase RenderProcessor::GenerateDatabase(const Node *node, const TimeRange &range) -{ - NodeValueDatabase db = super::GenerateDatabase(node, range); - - if (const MultiCamNode *multicam = dynamic_cast(node)) { - if (QtUtils::ValueToPtr(ticket_->property("multicam")) == multicam) { - int sz = multicam->GetSourceCount(); - QVector multicam_tex(sz); - for (int i=0; iGetConnectedRenderOutput(multicam->kSourcesInput, i), range, multicam); - NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &t, range); - ResolveJobs(val); - - multicam_tex[i] = val.toTexture(); - } - ticket_->setProperty("multicam_output", QVariant::fromValue(multicam_tex)); - } - } - - return db; -} - void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache) { RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache); @@ -343,7 +320,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ qWarning() << "HAVEN'T GOTTEN DEFAULT INPUT COLORSPACE"; } - Decoder::CodecStream default_codec_stream(stream->filename(), stream_data.stream_index(), GetCurrentBlock()); + Decoder::CodecStream default_codec_stream(stream->filename(), stream_data.stream_index(), nullptr); QString decoder_id = stream->decoder(); @@ -421,15 +398,13 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) { - DecoderPtr decoder = ResolveDecoderFromInput(stream->decoder(), Decoder::CodecStream(stream->filename(), stream->audio_params().stream_index(), nullptr)); + DecoderPtr decoder = ResolveDecoderFromInput(stream->decoder(), Decoder::CodecStream(stream->filename(), stream->audio_params().stream_index(), GetCurrentBlock())); if (decoder) { - const AudioParams& audio_params = GetCacheAudioParams(); - Decoder::RetrieveAudioStatus status = decoder->RetrieveAudio(destination, - input_time, audio_params, + input_time.in(), stream->cache_path(), - loop_mode(), + stream->loop_mode(), static_cast(ticket_->property("mode").toInt())); if (status == Decoder::kWaitingForConform) { @@ -438,13 +413,13 @@ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const Foota } } -void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job) +void RenderProcessor::ProcessShader(TexturePtr destination, const ShaderJob *job) { if (!render_ctx_) { return; } - QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), job->GetShaderID()); + QString full_shader_id = QStringLiteral("%1:%2").arg(QString::number(reinterpret_cast(job->function())), job->GetShaderID()); QMutexLocker locker(shader_cache_->mutex()); @@ -452,7 +427,7 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, co if (shader.isNull()) { // Since we have shader code, compile it now - shader = render_ctx_->CreateNativeShader(node->GetShaderCode(job->GetShaderID())); + shader = render_ctx_->CreateNativeShader(job->do_function()); if (shader.isNull()) { // Couldn't find or build the shader required @@ -468,38 +443,12 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, co render_ctx_->BlitToTexture(shader, *job, destination.get()); } -void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) +void RenderProcessor::ProcessSamples(SampleBuffer &destination, const SampleJob &job) { - if (!job.samples().is_allocated()) { - return; - } - - NodeValueRow value_db; - - const AudioParams& audio_params = GetCacheAudioParams(); - - for (size_t i=0;i(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 - for (auto j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) { - TimeRange r = TimeRange(this_sample_time, this_sample_time); - NodeValueTable value = ProcessInput(node, j.key(), r); - - value_db.insert(j.key(), GenerateRowValue(node, j.key(), &value, r)); - } - - node->ProcessSamples(value_db, - job.samples(), - destination, - i); - } + job.do_function(destination); } -void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job) +void RenderProcessor::ProcessColorTransform(TexturePtr destination, const ColorTransformJob *job) { if (!render_ctx_) { return; @@ -508,7 +457,7 @@ void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node * render_ctx_->BlitColorManaged(*job, destination.get()); } -void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job) +void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const GenerateJob *job) { if (!render_ctx_) { return; @@ -519,7 +468,7 @@ void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node frame->set_video_params(destination->params()); frame->allocate(); - node->GenerateFrame(frame, *job); + job->do_function(frame); destination->Upload(frame->data(), frame->linesize_pixels()); } @@ -545,12 +494,19 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob *val) TexturePtr RenderProcessor::CreateTexture(const VideoParams &p) { if (render_ctx_) { + // Create real texture with render context return render_ctx_->CreateTexture(p); } else { - return super::CreateTexture(p); + // Create dummy texture + return CreateDummyTexture(p); } } +TexturePtr RenderProcessor::CreateDummyTexture(const VideoParams &p) +{ + return std::make_shared(p); +} + void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) { if (!render_ctx_) { @@ -574,4 +530,142 @@ bool RenderProcessor::UseCache() const return static_cast(ticket_->property("mode").toInt()) == RenderMode::kOffline; } +void RenderProcessor::ResolveJobs(value_t &val) +{ + if (val.type() == TYPE_TEXTURE) { + + if (TexturePtr job_tex = val.toTexture()) { + if (AcceleratedJob *base_job = job_tex->job()) { + + if (resolved_texture_cache_.contains(job_tex.get())) { + val = resolved_texture_cache_.value(job_tex.get()); + } else { + // Resolve any sub-jobs + for (auto it=base_job->GetValues().begin(); it!=base_job->GetValues().end(); it++) { + // Jobs will almost always be submitted with one of these types + value_t &subval = it.value(); + ResolveJobs(subval); + } + + if (CacheJob *cj = dynamic_cast(base_job)) { + TexturePtr tex = ProcessVideoCacheJob(cj); + if (tex) { + val = tex; + } else { + val = cj->GetFallback(); + } + + } else if (ColorTransformJob *ctj = dynamic_cast(base_job)) { + + VideoParams ctj_params = job_tex->params(); + + ctj_params.set_format(GetCacheVideoParams().format()); + + TexturePtr dest = CreateTexture(ctj_params); + + // Resolve input texture + value_t v = ctj->GetInputTexture(); + ResolveJobs(v); + ctj->SetInputTexture(v); + + ProcessColorTransform(dest, ctj); + + val = dest; + + } else if (ShaderJob *sj = dynamic_cast(base_job)) { + + VideoParams tex_params = job_tex->params(); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessShader(tex, sj); + + val = tex; + + } else if (GenerateJob *gj = dynamic_cast(base_job)) { + + VideoParams tex_params = job_tex->params(); + + TexturePtr tex = CreateTexture(tex_params); + + ProcessFrameGeneration(tex, gj); + + // Convert to reference space + const QString &colorspace = tex_params.colorspace(); + if (!colorspace.isEmpty()) { + // Set format to primary format + tex_params.set_format(GetCacheVideoParams().format()); + + TexturePtr dest = CreateTexture(tex_params); + + ConvertToReferenceSpace(dest, tex, colorspace); + + tex = dest; + } + + val = tex; + + } else if (FootageJob *fj = dynamic_cast(base_job)) { + + rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), fj->loop_mode(), fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base()); + + TexturePtr tex; + + if (footage_time.isNaN()) { + // Push dummy texture + tex = CreateDummyTexture(fj->video_params()); + } else { + VideoParams managed_params = fj->video_params(); + managed_params.set_format(GetCacheVideoParams().format()); + + tex = CreateTexture(managed_params); + ProcessVideoFootage(tex, fj, footage_time); + } + + val = tex; + + } + + // Cache resolved value + resolved_texture_cache_.insert(job_tex.get(), val.toTexture()); + } + } + } + + } else if (val.type() == TYPE_SAMPLES) { + + if (AudioJobPtr job = val.toAudioJob()) { + if (resolved_sample_cache_.contains(job.get())) { + + val = resolved_sample_cache_.value(job.get()); + + } else { + + if (SampleJob *sjob = dynamic_cast(job->job())) { + + for (auto it=sjob->GetValues().begin(); it!=sjob->GetValues().end(); it++) { + // Jobs will almost always be submitted with one of these types + value_t &subval = it.value(); + ResolveJobs(subval); + } + + SampleBuffer output_buffer = CreateSampleBuffer(sjob->audio_params(), sjob->sample_count()); + ProcessSamples(output_buffer, *sjob); + val = output_buffer; + + } else if (FootageJob *fjob = dynamic_cast(job->job())) { + + SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), fjob->time().length()); + ProcessAudioFootage(buffer, fjob, fjob->time()); + val = buffer; + + } + + resolved_sample_cache_.insert(job.get(), val); + + } + } + } +} + } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index b3dd29f591..6e89d87d36 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -22,18 +22,20 @@ #define RENDERPROCESSOR_H #include "node/block/clip/clip.h" -#include "node/traverser.h" +#include "render/job/cachejob.h" +#include "render/job/colortransformjob.h" +#include "render/job/footagejob.h" +#include "render/job/samplejob.h" +#include "render/job/shaderjob.h" #include "render/renderer.h" #include "rendercache.h" #include "renderticket.h" namespace olive { -class RenderProcessor : public NodeTraverser +class RenderProcessor { public: - virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range) override; - static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); struct RenderedWaveform { @@ -44,30 +46,40 @@ class RenderProcessor : public NodeTraverser }; protected: - virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time) override; + void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time); - virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) override; + void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time); - virtual void ProcessShader(TexturePtr destination, const Node *node, const ShaderJob *job) override; + void ProcessShader(TexturePtr destination, const ShaderJob *job); - virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) override; + void ProcessSamples(SampleBuffer &destination, const SampleJob &job); - virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob *job) override; + void ProcessColorTransform(TexturePtr destination, const ColorTransformJob *job); - virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob *job) override; + void ProcessFrameGeneration(TexturePtr destination, const GenerateJob *job); - virtual TexturePtr ProcessVideoCacheJob(const CacheJob *val) override; + TexturePtr ProcessVideoCacheJob(const CacheJob *val); - virtual TexturePtr CreateTexture(const VideoParams &p) override; + TexturePtr CreateTexture(const VideoParams &p); + TexturePtr CreateDummyTexture(const VideoParams &p); - virtual SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) override + SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) { return SampleBuffer(params, sample_count); } - virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) override; + SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, const rational &length) + { + if (params.is_valid()) { + return CreateSampleBuffer(params, params.time_to_samples(length)); + } else { + return SampleBuffer(); + } + } + + void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs); - virtual bool UseCache() const override; + bool UseCache() const; private: RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); @@ -80,6 +92,18 @@ class RenderProcessor : public NodeTraverser DecoderPtr ResolveDecoderFromInput(const QString &decoder_id, const Decoder::CodecStream& stream); + void ResolveJobs(value_t &value); + + const VideoParams &GetCacheVideoParams() const { return vparam_; } + const AudioParams &GetCacheAudioParams() const { return aparam_; } + void SetCacheVideoParams(const VideoParams &vparam) { vparam_ = vparam; } + void SetCacheAudioParams(const AudioParams &aparam) { aparam_ = aparam; } + bool IsCancelled() const { return cancel_atom_ && cancel_atom_->IsCancelled(); } + bool HeardCancel() const { return cancel_atom_ && cancel_atom_->HeardCancel(); } + CancelAtom *GetCancelPointer() const { return cancel_atom_; } + void SetCancelPointer(CancelAtom *p) { cancel_atom_ = p; } + Block *GetCurrentBlock() const { return nullptr; } + RenderTicketPtr ticket_; Renderer* render_ctx_; @@ -88,6 +112,13 @@ class RenderProcessor : public NodeTraverser ShaderCache* shader_cache_; + VideoParams vparam_; + AudioParams aparam_; + CancelAtom *cancel_atom_; + + QHash resolved_texture_cache_; + QHash resolved_sample_cache_; + }; } diff --git a/app/render/subtitleparams.cpp b/app/render/subtitleparams.cpp index ecf985a8ca..442fa39945 100644 --- a/app/render/subtitleparams.cpp +++ b/app/render/subtitleparams.cpp @@ -125,9 +125,9 @@ void SubtitleParams::Load(QXmlStreamReader *reader) XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } } @@ -152,8 +152,8 @@ void SubtitleParams::Save(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("subtitles")); for (auto it=this->cbegin(); it!=this->cend(); it++) { writer->writeStartElement(QStringLiteral("subtitle")); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(it->time().in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(it->time().out().toString())); + writer->writeAttribute(QStringLiteral("in"), it->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), it->time().out().toString()); writer->writeCharacters(it->text()); writer->writeEndElement(); // subtitle } diff --git a/app/render/subtitleparams.h b/app/render/subtitleparams.h index 7d82273e72..33a8b2686c 100644 --- a/app/render/subtitleparams.h +++ b/app/render/subtitleparams.h @@ -21,13 +21,12 @@ #ifndef SUBTITLEPARAMS_H #define SUBTITLEPARAMS_H -#include #include #include #include #include -using namespace olive::core; +#include "util/timerange.h" namespace olive { diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 742e01227d..6f9bbc9e9c 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -27,6 +27,7 @@ extern "C" { #include #include +#include "common/qtutils.h" #include "core.h" namespace olive { @@ -71,7 +72,7 @@ VideoParams::VideoParams() : height_(0), depth_(0), format_(PixelFormat::INVALID), - channel_count_(0), + channel_count_(kNoChannels), interlacing_(Interlacing::kInterlaceNone), divider_(1) { @@ -83,7 +84,7 @@ VideoParams::VideoParams(int width, int height, PixelFormat format, int nb_chann height_(height), depth_(1), format_(format), - channel_count_(nb_channels), + channel_count_(static_cast(nb_channels)), pixel_aspect_ratio_(pixel_aspect_ratio), interlacing_(interlacing), divider_(divider) @@ -98,7 +99,7 @@ VideoParams::VideoParams(int width, int height, int depth, PixelFormat format, i height_(height), depth_(depth), format_(format), - channel_count_(nb_channels), + channel_count_(static_cast(nb_channels)), pixel_aspect_ratio_(pixel_aspect_ratio), interlacing_(interlacing), divider_(divider) @@ -114,7 +115,7 @@ VideoParams::VideoParams(int width, int height, const rational &time_base, Pixel depth_(1), time_base_(time_base), format_(format), - channel_count_(nb_channels), + channel_count_(static_cast(nb_channels)), pixel_aspect_ratio_(pixel_aspect_ratio), interlacing_(interlacing), divider_(divider), @@ -344,13 +345,13 @@ void VideoParams::Load(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("depth")) { set_depth(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("timebase")) { - set_time_base(rational::fromString(reader->readElementText().toStdString())); + set_time_base(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("format")) { set_format(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("channelcount")) { set_channel_count(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("pixelaspectratio")) { - set_pixel_aspect_ratio(rational::fromString(reader->readElementText().toStdString())); + set_pixel_aspect_ratio(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("interlacing")) { set_interlacing(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("divider")) { @@ -366,7 +367,7 @@ void VideoParams::Load(QXmlStreamReader *reader) } else if (reader->name() == QStringLiteral("videotype")) { set_video_type(static_cast(reader->readElementText().toInt())); } else if (reader->name() == QStringLiteral("framerate")) { - set_frame_rate(rational::fromString(reader->readElementText().toStdString())); + set_frame_rate(rational::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("starttime")) { set_start_time(reader->readElementText().toLongLong()); } else if (reader->name() == QStringLiteral("duration")) { @@ -388,10 +389,10 @@ void VideoParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("width"), QString::number(width_)); writer->writeTextElement(QStringLiteral("height"), QString::number(height_)); writer->writeTextElement(QStringLiteral("depth"), QString::number(depth_)); - writer->writeTextElement(QStringLiteral("timebase"), QString::fromStdString(time_base_.toString())); + writer->writeTextElement(QStringLiteral("timebase"), time_base_.toString()); writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); writer->writeTextElement(QStringLiteral("channelcount"), QString::number(channel_count_)); - writer->writeTextElement(QStringLiteral("pixelaspectratio"), QString::fromStdString(pixel_aspect_ratio_.toString())); + writer->writeTextElement(QStringLiteral("pixelaspectratio"), pixel_aspect_ratio_.toString()); writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_)); writer->writeTextElement(QStringLiteral("divider"), QString::number(divider_)); writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_)); @@ -399,7 +400,7 @@ void VideoParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("y"), QString::number(y_)); writer->writeTextElement(QStringLiteral("streamindex"), QString::number(stream_index_)); writer->writeTextElement(QStringLiteral("videotype"), QString::number(video_type_)); - writer->writeTextElement(QStringLiteral("framerate"), QString::fromStdString(frame_rate_.toString())); + writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString()); writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_)); writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_)); writer->writeTextElement(QStringLiteral("premultipliedalpha"), QString::number(premultiplied_alpha_)); @@ -407,4 +408,9 @@ void VideoParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("colorrange"), QString::number(color_range_)); } +uint qHash(const VideoParams &p, uint seed) +{ + return qHash(p.width(), seed) ^ qHash(p.height(), seed) ^ qHash(p.depth(), seed) ^ qHash(p.time_base(), seed) ^ qHash(QString::fromUtf8(p.format().to_string()), seed) ^ qHash(p.channel_count(), seed) ^ qHash(p.pixel_aspect_ratio(), seed) ^ qHash(p.interlacing(), seed) ^ qHash(p.divider(), seed); +} + } diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 84ae67b600..59a4b74b93 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -21,14 +21,14 @@ #ifndef VIDEOPARAMS_H #define VIDEOPARAMS_H -#include #include #include #include -namespace olive { +#include "render/pixelformat.h" +#include "util/rational.h" -using namespace core; +namespace olive { class VideoParams { public: @@ -51,6 +51,14 @@ class VideoParams { kColorRangeDefault = kColorRangeLimited }; + enum ChannelCount + { + kNoChannels, + kGrayOnlyChannelCount, + kGrayAndAlphaChannelCount, + kRGBChannelCount, + kRGBAChannelCount + }; VideoParams(); VideoParams(int width, int height, PixelFormat format, int nb_channels, @@ -169,16 +177,21 @@ class VideoParams { format_ = f; } - int channel_count() const + ChannelCount channel_count() const { return channel_count_; } - void set_channel_count(int c) + void set_channel_count(ChannelCount c) { channel_count_ = c; } + void set_channel_count(int c) + { + set_channel_count(static_cast(c)); + } + const rational& pixel_aspect_ratio() const { return pixel_aspect_ratio_; @@ -253,8 +266,6 @@ class VideoParams { 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 @@ -377,7 +388,7 @@ class VideoParams { PixelFormat format_; - int channel_count_; + ChannelCount channel_count_; rational pixel_aspect_ratio_; @@ -391,6 +402,7 @@ class VideoParams { int effective_depth_; int par_width_; + // Footage values bool enabled_; int stream_index_; Type video_type_; @@ -405,6 +417,8 @@ class VideoParams { }; +uint qHash(const VideoParams &p, uint seed = 0); + } Q_DECLARE_METATYPE(olive::VideoParams) diff --git a/app/shaders/swizzle.frag b/app/shaders/swizzle.frag new file mode 100644 index 0000000000..a357cd2876 --- /dev/null +++ b/app/shaders/swizzle.frag @@ -0,0 +1,38 @@ +// Input +uniform sampler2D red_texture; +uniform bool red_texture_enabled; +uniform sampler2D green_texture; +uniform bool green_texture_enabled; +uniform sampler2D blue_texture; +uniform bool blue_texture_enabled; +uniform sampler2D alpha_texture; +uniform bool alpha_texture_enabled; +uniform int red_channel; +uniform int green_channel; +uniform int blue_channel; +uniform int alpha_channel; + +in vec2 ove_texcoord; +out vec4 frag_color; + +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 1.0); + + if (red_texture_enabled) { + color.r = texture(red_texture, ove_texcoord)[red_channel]; + } + + if (green_texture_enabled) { + color.g = texture(green_texture, ove_texcoord)[green_channel]; + } + + if (blue_texture_enabled) { + color.b = texture(blue_texture, ove_texcoord)[blue_channel]; + } + + if (alpha_texture_enabled) { + color.a = texture(alpha_texture, ove_texcoord)[alpha_channel]; + } + + frag_color = color; +} diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index a171df1ce0..78c2807d38 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -20,6 +20,7 @@ #include "export.h" +#include "common/qtutils.h" #include "node/color/colormanager/colormanager.h" namespace olive { diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index 8357b5a7d0..3910736a5d 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -47,8 +47,7 @@ PreCacheTask::PreCacheTask(Footage *footage, int index, Sequence* sequence) footage_->setParent(project_); Node::CopyInputs(footage, footage_, false); - Node::ConnectEdge(footage_, NodeInput(viewer(), ViewerOutput::kTextureInput)); - viewer()->SetValueHintForInput(ViewerOutput::kTextureInput, Node::ValueHint({NodeValue::kTexture}, Track::Reference(Track::kVideo, index).ToString())); + Node::ConnectEdge(NodeOutput(footage_, Track::Reference(Track::kVideo, index).ToString()), NodeInput(viewer(), ViewerOutput::kTextureInput)); SetTitle(tr("Pre-caching %1:%2").arg(footage_->filename(), QString::number(index))); } diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index 6904575c0f..61b32eddfd 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -232,7 +232,7 @@ bool LoadOTIOTask::Run() // If the previous block was a transition, connect the current block to it if (prev_block_transition) { TransitionBlock* previous_transition_block = static_cast(previous_block); - Node::ConnectEdge(block, NodeInput(previous_transition_block, TransitionBlock::kInBlockInput)); + Node::ConnectEdge(NodeOutput(block), NodeInput(previous_transition_block, TransitionBlock::kInBlockInput)); prev_block_transition = false; } @@ -244,7 +244,7 @@ bool LoadOTIOTask::Run() transition_block->set_offsets_and_length(rational::fromRationalTime(otio_block_transition->in_offset()), rational::fromRationalTime(otio_block_transition->out_offset())); if (previous_block) { - Node::ConnectEdge(previous_block, NodeInput(transition_block, TransitionBlock::kOutBlockInput)); + Node::ConnectEdge(NodeOutput(previous_block), NodeInput(transition_block, TransitionBlock::kOutBlockInput)); } prev_block_transition = true; @@ -306,15 +306,15 @@ bool LoadOTIOTask::Run() TransformDistortNode* transform = new TransformDistortNode(); transform->setParent(sequence->parent()); - Node::ConnectEdge(probed_item, NodeInput(transform, TransformDistortNode::kTextureInput)); - Node::ConnectEdge(transform, NodeInput(block, ClipBlock::kBufferIn)); + Node::ConnectEdge(NodeOutput(probed_item), NodeInput(transform, TransformDistortNode::kTextureInput)); + Node::ConnectEdge(NodeOutput(transform), NodeInput(block, ClipBlock::kBufferIn)); block->SetNodePositionInContext(transform, QPointF(-1, 0)); } else { VolumeNode* volume_node = new VolumeNode(); volume_node->setParent(sequence->parent()); - Node::ConnectEdge(probed_item, NodeInput(volume_node, VolumeNode::kSamplesInput)); - Node::ConnectEdge(volume_node, NodeInput(block, ClipBlock::kBufferIn)); + Node::ConnectEdge(NodeOutput(probed_item), NodeInput(volume_node, VolumeNode::kSamplesInput)); + Node::ConnectEdge(NodeOutput(volume_node), NodeInput(block, ClipBlock::kBufferIn)); block->SetNodePositionInContext(volume_node, QPointF(-1, 0)); } } diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 0dfdfcc8b7..993f7d9e1b 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -20,6 +20,7 @@ #include "render.h" +#include "common/qtutils.h" #include "node/project/sequence/sequence.h" #include "render/rendermanager.h" diff --git a/app/timeline/timelinecommon.h b/app/timeline/timelinecommon.h index 0bafa466e0..40b7f8d94a 100644 --- a/app/timeline/timelinecommon.h +++ b/app/timeline/timelinecommon.h @@ -21,12 +21,8 @@ #ifndef TIMELINECOMMON_H #define TIMELINECOMMON_H -#include - #include "common/define.h" -using namespace olive::core; - namespace olive { class Block; diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index e4ad0f5821..47a2122f1c 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -152,9 +152,9 @@ bool TimelineMarker::load(QXmlStreamReader *reader) if (attr.name() == QStringLiteral("name")) { this->set_name(attr.value().toString()); } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); + in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); + out = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("color")) { this->set_color(attr.value().toInt()); } @@ -171,8 +171,8 @@ bool TimelineMarker::load(QXmlStreamReader *reader) void TimelineMarker::save(QXmlStreamWriter *writer) const { writer->writeAttribute(QStringLiteral("name"), this->name()); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(this->time().in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(this->time().out().toString())); + writer->writeAttribute(QStringLiteral("in"), this->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), this->time().out().toString()); writer->writeAttribute(QStringLiteral("color"), QString::number(this->color())); } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index f1b6a5f3c0..d27814d029 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -21,16 +21,14 @@ #ifndef TIMELINEMARKER_H #define TIMELINEMARKER_H -#include #include #include #include #include +#include "util/timerange.h" #include "undo/undocommand.h" -using namespace olive::core; - namespace olive { class TimelineMarker : public QObject diff --git a/app/timeline/timelineundogeneral.cpp b/app/timeline/timelineundogeneral.cpp index 9c18e9cdbb..475a1367e6 100644 --- a/app/timeline/timelineundogeneral.cpp +++ b/app/timeline/timelineundogeneral.cpp @@ -130,7 +130,7 @@ void TimelineAddTrackCommand::redo() track_->SetTrackHeight(timeline_->GetTrackAt(timeline_->GetTrackCount()-1)->GetTrackHeight()); } timeline_->ArrayAppend(); - Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1)); + Node::ConnectEdge(NodeOutput(track_), timeline_->track_input(timeline_->ArraySize() - 1)); qreal position_factor = 0.5; if (timeline_->type() == Track::kVideo) { @@ -150,10 +150,10 @@ void TimelineAddTrackCommand::redo() merge_->setParent(timeline_->GetParentGraph()); // Connect merge between what used to be here - Node::DisconnectEdge(previous_connection, direct_); - Node::ConnectEdge(merge_, direct_); - Node::ConnectEdge(previous_connection, base_); - Node::ConnectEdge(track_, blend_); + Node::DisconnectEdge(NodeOutput(previous_connection), direct_); + Node::ConnectEdge(NodeOutput(merge_), direct_); + Node::ConnectEdge(NodeOutput(previous_connection), base_); + Node::ConnectEdge(NodeOutput(track_), blend_); if (create_pos_command) { position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); @@ -162,7 +162,7 @@ void TimelineAddTrackCommand::redo() } } else if (direct_.IsValid() && !direct_.IsConnected()) { // If no merge, we have a direct connection, and nothing else is connected, connect this - Node::ConnectEdge(track_, direct_); + Node::ConnectEdge(NodeOutput(track_), direct_); if (create_pos_command) { // Just position directly next to the context node @@ -186,18 +186,18 @@ void TimelineAddTrackCommand::undo() if (merge_) { Node *previous_connection = base_.GetConnectedOutput(); - Node::DisconnectEdge(track_, blend_); - Node::DisconnectEdge(previous_connection, base_); - Node::DisconnectEdge(merge_, direct_); - Node::ConnectEdge(previous_connection, direct_); + Node::DisconnectEdge(NodeOutput(track_), blend_); + Node::DisconnectEdge(NodeOutput(previous_connection), base_); + Node::DisconnectEdge(NodeOutput(merge_), direct_); + Node::ConnectEdge(NodeOutput(previous_connection), direct_); merge_->setParent(&memory_manager_); } else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) { - Node::DisconnectEdge(track_, direct_); + Node::DisconnectEdge(NodeOutput(track_), direct_); } // Remove track - Node::DisconnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1)); + Node::DisconnectEdge(NodeOutput(track_), timeline_->track_input(timeline_->ArraySize() - 1)); timeline_->ArrayRemoveLast(); track_->setParent(&memory_manager_); } @@ -224,11 +224,11 @@ void TransitionRemoveCommand::redo() } if (in_block_) { - Node::DisconnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput)); + Node::DisconnectEdge(NodeOutput(in_block_), NodeInput(block_, TransitionBlock::kInBlockInput)); } if (out_block_) { - Node::DisconnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput)); + Node::DisconnectEdge(NodeOutput(out_block_), NodeInput(block_, TransitionBlock::kOutBlockInput)); } track_->RippleRemoveBlock(block_); @@ -255,11 +255,11 @@ void TransitionRemoveCommand::undo() } if (in_block_) { - Node::ConnectEdge(in_block_, NodeInput(block_, TransitionBlock::kInBlockInput)); + Node::ConnectEdge(NodeOutput(in_block_), NodeInput(block_, TransitionBlock::kInBlockInput)); } if (out_block_) { - Node::ConnectEdge(out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput)); + Node::ConnectEdge(NodeOutput(out_block_), NodeInput(block_, TransitionBlock::kOutBlockInput)); } // These if statements must be separated because in_offset and out_offset report different things @@ -629,13 +629,13 @@ void TimelineAddDefaultTransitionCommand::AddTransition(ClipBlock *c, CreateTran // Connect switch (mode) { case kIn: - commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kInBlockInput))); + commands_.append(new NodeEdgeAddCommand(NodeOutput(c), NodeInput(transition, TransitionBlock::kInBlockInput))); break; case kOutDual: - commands_.append(new NodeEdgeAddCommand(c->next(), NodeInput(transition, TransitionBlock::kInBlockInput))); + commands_.append(new NodeEdgeAddCommand(NodeOutput(c->next()), NodeInput(transition, TransitionBlock::kInBlockInput))); /* fall through */ case kOut: - commands_.append(new NodeEdgeAddCommand(c, NodeInput(transition, TransitionBlock::kOutBlockInput))); + commands_.append(new NodeEdgeAddCommand(NodeOutput(c), NodeInput(transition, TransitionBlock::kOutBlockInput))); break; } } diff --git a/app/timeline/timelineundosplit.cpp b/app/timeline/timelineundosplit.cpp index a4448e4424..9bbcdac580 100644 --- a/app/timeline/timelineundosplit.cpp +++ b/app/timeline/timelineundosplit.cpp @@ -67,11 +67,11 @@ void BlockSplitCommand::redo() TransitionBlock* potential_transition = dynamic_cast(new_block()->next()); if (potential_transition) { - for (const Node::OutputConnection& output : block_->output_connections()) { + for (const Node::Connection& output : block_->output_connections()) { if (output.second.node() == potential_transition) { moved_transition_ = NodeInput(potential_transition, TransitionBlock::kOutBlockInput); - Node::DisconnectEdge(block_, moved_transition_); - Node::ConnectEdge(new_block(), moved_transition_); + Node::DisconnectEdge(NodeOutput(block_), moved_transition_); + Node::ConnectEdge(NodeOutput(new_block()), moved_transition_); break; } } @@ -83,8 +83,8 @@ void BlockSplitCommand::undo() Track* track = block_->track(); if (moved_transition_.IsValid()) { - Node::DisconnectEdge(new_block(), moved_transition_); - Node::ConnectEdge(block_, moved_transition_); + Node::DisconnectEdge(NodeOutput(new_block()), moved_transition_); + Node::ConnectEdge(NodeOutput(block_), moved_transition_); } block_->set_length_and_media_out(old_length_); diff --git a/app/timeline/timelineworkarea.cpp b/app/timeline/timelineworkarea.cpp index ba2c09e79b..077f91c774 100644 --- a/app/timeline/timelineworkarea.cpp +++ b/app/timeline/timelineworkarea.cpp @@ -72,9 +72,9 @@ bool TimelineWorkArea::load(QXmlStreamReader *reader) if (reader->name() == QStringLiteral("enabled")) { this->set_enabled(reader->readElementText() != QStringLiteral("0")); } else if (reader->name() == QStringLiteral("in")) { - range_in = rational::fromString(reader->readElementText().toStdString()); + range_in = rational::fromString(reader->readElementText()); } else if (reader->name() == QStringLiteral("out")) { - range_out = rational::fromString(reader->readElementText().toStdString()); + range_out = rational::fromString(reader->readElementText()); } else { reader->skipCurrentElement(); } @@ -94,8 +94,8 @@ void TimelineWorkArea::save(QXmlStreamWriter *writer) const writer->writeAttribute(QStringLiteral("version"), QString::number(1)); writer->writeTextElement(QStringLiteral("enabled"), QString::number(this->enabled())); - writer->writeTextElement(QStringLiteral("in"), QString::fromStdString(this->in().toString())); - writer->writeTextElement(QStringLiteral("out"), QString::fromStdString(this->out().toString())); + writer->writeTextElement(QStringLiteral("in"), this->in().toString()); + writer->writeTextElement(QStringLiteral("out"), this->out().toString()); } const rational &TimelineWorkArea::in() const diff --git a/app/timeline/timelineworkarea.h b/app/timeline/timelineworkarea.h index e48e748e23..97230fb1d7 100644 --- a/app/timeline/timelineworkarea.h +++ b/app/timeline/timelineworkarea.h @@ -21,14 +21,14 @@ #ifndef TIMELINEWORKAREA_H #define TIMELINEWORKAREA_H -#include #include #include #include -namespace olive { +#include "util/rational.h" +#include "util/timerange.h" -using namespace core; +namespace olive { class TimelineWorkArea : public QObject { diff --git a/app/ui/colorcoding.h b/app/ui/colorcoding.h index 5cc16d58c0..6835766c3f 100644 --- a/app/ui/colorcoding.h +++ b/app/ui/colorcoding.h @@ -21,12 +21,11 @@ #ifndef COLORCODING_H #define COLORCODING_H -#include #include -namespace olive { +#include "util/color.h" -using namespace core; +namespace olive { class ColorCoding : public QObject { diff --git a/app/ui/humanstrings.cpp b/app/ui/humanstrings.cpp index 6a52a9ccb1..eb0d057215 100644 --- a/app/ui/humanstrings.cpp +++ b/app/ui/humanstrings.cpp @@ -2,6 +2,8 @@ #include +#include "common/ffmpegutils.h" + namespace olive { QString HumanStrings::SampleRateToString(const int &sample_rate) diff --git a/app/ui/humanstrings.h b/app/ui/humanstrings.h index 6c94fb2921..08079480e9 100644 --- a/app/ui/humanstrings.h +++ b/app/ui/humanstrings.h @@ -1,12 +1,11 @@ #ifndef HUMANSTRINGS_H #define HUMANSTRINGS_H -#include #include -namespace olive { +#include "render/sampleformat.h" -using namespace core; +namespace olive { class HumanStrings : public QObject { diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 26f2b10e8c..c171e9192c 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -34,7 +34,6 @@ add_subdirectory(menu) add_subdirectory(multicam) add_subdirectory(nodecombobox) add_subdirectory(nodeparamview) -add_subdirectory(nodetableview) add_subdirectory(nodetreeview) add_subdirectory(nodevaluetree) add_subdirectory(nodeview) diff --git a/app/widget/bezier/bezierwidget.h b/app/widget/bezier/bezierwidget.h index e16d0601de..0e59c8d070 100644 --- a/app/widget/bezier/bezierwidget.h +++ b/app/widget/bezier/bezierwidget.h @@ -21,16 +21,14 @@ #ifndef BEZIERWIDGET_H #define BEZIERWIDGET_H -#include #include #include +#include "util/bezier.h" #include "widget/slider/floatslider.h" namespace olive { -using namespace core; - class BezierWidget : public QWidget { Q_OBJECT diff --git a/app/widget/colorbutton/colorbutton.cpp b/app/widget/colorbutton/colorbutton.cpp index 798a41db82..6e9144c147 100644 --- a/app/widget/colorbutton/colorbutton.cpp +++ b/app/widget/colorbutton/colorbutton.cpp @@ -20,6 +20,7 @@ #include "colorbutton.h" +#include "common/qtutils.h" #include "dialog/color/colordialog.h" namespace olive { diff --git a/app/widget/colorwheel/colorgradientwidget.cpp b/app/widget/colorwheel/colorgradientwidget.cpp index e4cec9e7c7..31306f6f4a 100644 --- a/app/widget/colorwheel/colorgradientwidget.cpp +++ b/app/widget/colorwheel/colorgradientwidget.cpp @@ -23,6 +23,7 @@ #include #include "common/lerp.h" +#include "common/qtutils.h" #include "node/node.h" namespace olive { diff --git a/app/widget/colorwheel/colorwheelwidget.cpp b/app/widget/colorwheel/colorwheelwidget.cpp index 78d591978e..17b44276a8 100644 --- a/app/widget/colorwheel/colorwheelwidget.cpp +++ b/app/widget/colorwheel/colorwheelwidget.cpp @@ -23,6 +23,7 @@ #include #include +#include "common/qtutils.h" #include "node/node.h" namespace olive { diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index a49482057a..36c921cc5e 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -435,7 +435,7 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key); Node* node = key->parent(); - double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display); + double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).value(), display); const QString& input = key->input(); double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display); double limited = new_val; @@ -457,15 +457,14 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) for (size_t i=0; iset_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display)); + key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).value(), display) - scaled_diff, display)); } NodeKeyframe *tip_item = GetSelectedKeyframes().front(); - bool ok; - double num_value = tip_item->value().toDouble(&ok); + double num_value; - if (ok) { + if (tip_item->value().get(&num_value)) { tip = QStringLiteral("%1\n"); tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true)); } @@ -475,7 +474,7 @@ void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *comman { for (size_t i=0; ivalue().toDouble(), drag_keyframe_values_.at(i).toDouble())) { + if (!qFuzzyCompare(k->value().value(), drag_keyframe_values_.at(i).value())) { command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); } } @@ -582,7 +581,7 @@ qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key) { - double val = key->value().toDouble(); + double val = key->value().value(); val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key)); @@ -615,12 +614,9 @@ double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key) Node *node = key->parent(); const QString &input = key->input(); if (node->HasInputProperty(input, QStringLiteral("offset"))) { - QVariant v = node->GetInputProperty(input, QStringLiteral("offset")); + value_t v = node->GetInputProperty(input, QStringLiteral("offset")).value(); - // NOTE: Implement getting correct offset for the track based on the data type - QVector track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v); - - return track_vals.at(key->track()).toDouble(); + return v.at(key->track()).value(); } return 0; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index a01598548f..f6bafd9be8 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -118,7 +118,7 @@ public slots: QPointF dragging_bezier_point_opposing_start_; QPointF drag_start_; - QVector drag_keyframe_values_; + QVector drag_keyframe_values_; }; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 836984a0c6..78bbae7c28 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -258,8 +258,8 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, int element) void CurveWidget::ConnectInputInternal(Node *node, const QString &input, int element) { NodeInput input_ref(node, input, element); - int track_count = NodeValue::get_number_of_keyframe_tracks(input_ref.GetDataType()); - for (int i=0; iConnectInput(track_ref); selected_tracks_.append(track_ref); diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index b3d2cba8aa..bcddbc938f 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -21,14 +21,12 @@ #ifndef MENUSHARED_H #define MENUSHARED_H -#include +#include "util/rational.h" #include "widget/colorlabelmenu/colorlabelmenu.h" #include "widget/menu/menu.h" namespace olive { -using namespace core; - /** * @brief A static object that provides various "stock" menus for use throughout the application */ diff --git a/app/widget/multicam/multicamdisplay.cpp b/app/widget/multicam/multicamdisplay.cpp index 9fce75e68d..a5b79a67bf 100644 --- a/app/widget/multicam/multicamdisplay.cpp +++ b/app/widget/multicam/multicamdisplay.cpp @@ -26,9 +26,7 @@ namespace olive { MulticamDisplay::MulticamDisplay(QWidget *parent) : super(parent), - node_(nullptr), - rows_(0), - cols_(0) + node_(nullptr) { } @@ -57,113 +55,6 @@ void MulticamDisplay::OnPaint() } } -void MulticamDisplay::OnDestroy() -{ - shader_ = QVariant(); -} - -TexturePtr MulticamDisplay::LoadCustomTextureFromFrame(const QVariant &v) -{ - if (v.canConvert >()) { - QVector tex = v.value >(); - - TexturePtr main = renderer()->CreateTexture(this->GetViewportParams()); - - int rows, cols; - MultiCamNode::GetRowsAndColumns(tex.size(), &rows, &cols); - - if (shader_.isNull() || rows_ != rows || cols_ != cols) { - if (!shader_.isNull()) { - renderer()->DestroyNativeShader(shader_); - } - - shader_ = renderer()->CreateNativeShader(ShaderCode(GenerateShaderCode(rows, cols))); - - rows_ = rows; - cols_ = cols; - } - - ShaderJob job; - - for (int i=0; iBlitToTexture(shader_, job, main.get()); - - return main; - } else { - return super::LoadCustomTextureFromFrame(v); - } -} - -QString dblToGlsl(double d) -{ - return QString::number(d, 'f'); -} - -QString MulticamDisplay::GenerateShaderCode(int rows, int cols) -{ - int multiplier = std::max(cols, rows); - - QStringList shader; - - shader.append(QStringLiteral("in vec2 ove_texcoord;")); - shader.append(QStringLiteral("out vec4 frag_color;")); - - for (int x=0;x 0) { - shader.append(QStringLiteral(" else")); - } - if (x == cols-1) { - shader.append(QStringLiteral(" {")); - } else { - shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier)))); - } - - for (int y=0;y 0) { - shader.append(QStringLiteral(" else")); - } - if (y == rows-1) { - shader.append(QStringLiteral(" {")); - } else { - shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier)))); - } - QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x)); - shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg( - dblToGlsl( - double(x)/double(multiplier)), - dblToGlsl(multiplier), - dblToGlsl( - double(y)/double(multiplier)), - dblToGlsl(multiplier) - )); - shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input)); - shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input)); - shader.append(QStringLiteral(" } else {")); - shader.append(QStringLiteral(" discard;")); - shader.append(QStringLiteral(" }")); - shader.append(QStringLiteral(" }")); - } - - shader.append(QStringLiteral(" }")); - } - - shader.append(QStringLiteral("}")); - - return shader.join('\n'); -} - void MulticamDisplay::SetMulticamNode(MultiCamNode *n) { node_ = n; diff --git a/app/widget/multicam/multicamdisplay.h b/app/widget/multicam/multicamdisplay.h index 4d926f5f17..2898142d1d 100644 --- a/app/widget/multicam/multicamdisplay.h +++ b/app/widget/multicam/multicamdisplay.h @@ -37,19 +37,9 @@ class MulticamDisplay : public ViewerDisplayWidget protected: virtual void OnPaint() override; - virtual void OnDestroy() override; - - virtual TexturePtr LoadCustomTextureFromFrame(const QVariant &v) override; - private: - static QString GenerateShaderCode(int rows, int cols); - MultiCamNode *node_; - QVariant shader_; - int rows_; - int cols_; - }; } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index a6e5f311a2..0addf74986 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -35,6 +35,7 @@ class MulticamWidget : public TimeBasedWidget MulticamDisplay *GetDisplayWidget() const { return display_; } + MultiCamNode *GetMultiCamNode() const { return node_; } void SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time); protected: diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index 1b80edebc2..ad455bb8e4 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(paramwidget) + set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/nodeparamview/nodeparamview.cpp diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index a4aada805e..8d703ded24 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -372,7 +372,7 @@ void ReconnectOutputsIfNotDeletingNode(MultiUndoCommand *c, NodeViewDeleteComman // Uh-oh we're deleting this node too, instead connect to its outputs ReconnectOutputsIfNotDeletingNode(c, dc, output, proposed_reconnect.node(), context); } else { - c->add_child(new NodeEdgeAddCommand(output, it->second)); + c->add_child(new NodeEdgeAddCommand(it->first, it->second)); } } } @@ -381,6 +381,8 @@ void NodeParamView::DeleteSelected() { if (keyframe_view_ && keyframe_view_->hasFocus()) { keyframe_view_->DeleteSelected(); + } else if (DeleteInsideWidgets()) { + return; } else if (!selected_nodes_.isEmpty()) { MultiUndoCommand *c = new MultiUndoCommand(); @@ -726,6 +728,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::InputArraySizeChanged, this, &NodeParamView::InputArraySizeChanged); + connect(item, &NodeParamViewItem::ElementKeyframeTrackAdded, this, &NodeParamView::ElementKeyframeTrackAdded); item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); } @@ -738,7 +741,7 @@ int GetDistanceBetweenNodes(Node *start, Node *end) } for (auto it=start->input_connections().cbegin(); it!=start->input_connections().cend(); it++) { - int this_node_dist = GetDistanceBetweenNodes(it->second, end); + int this_node_dist = GetDistanceBetweenNodes(it->first.node(), end); if (this_node_dist != -1) { return 1 + this_node_dist; } @@ -784,6 +787,11 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) } } +bool NodeParamView::DeleteInsideWidgets() +{ + return focused_node_ && focused_node_->DeleteSelected(); +} + NodeParamViewContext *NodeParamView::GetContextItemFromContext(Node *ctx) { Track::Type ctx_type = Track::kCount; @@ -1025,4 +1033,27 @@ void NodeParamView::InputArraySizeChanged(const QString &input, int, int new_siz QueueKeyframePositionUpdate(); } +void NodeParamView::ElementKeyframeTrackAdded(const QString &input, int element, int track) +{ + NodeParamViewItem *sender = static_cast(this->sender()); + + KeyframeView::NodeConnections &connections = sender->GetKeyframeConnections(); + KeyframeView::InputConnections &inputs = connections[input]; + KeyframeView::ElementConnections &elements = inputs[element + 1]; + + auto conn = keyframe_view_->AddKeyframesOfTrack(NodeKeyframeTrackReference(NodeInput(sender->GetNode(), input, element), track)); + + if (track >= elements.size()) { + int old = elements.size(); + elements.resize(track + 1); + for (int i = old; i < elements.size(); i++) { + elements[i] = nullptr; + } + } + + elements[track] = conn; + + QueueKeyframePositionUpdate(); +} + } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 3899f4e664..6a0e37d2f2 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -123,6 +123,8 @@ public slots: void SortItemsInContext(NodeParamViewContext *context); + bool DeleteInsideWidgets(); + NodeParamViewContext *GetContextItemFromContext(Node *context); bool IsGroupMode() const @@ -189,6 +191,8 @@ private slots: void InputArraySizeChanged(const QString &input, int old_size, int new_size); + void ElementKeyframeTrackAdded(const QString &input, int element, int track); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 370164f5f9..55249f587e 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -26,7 +26,6 @@ #include "core.h" #include "node/node.h" #include "node/nodeundo.h" -#include "widget/collapsebutton/collapsebutton.h" #include "widget/menu/menu.h" namespace olive { @@ -34,7 +33,6 @@ namespace olive { NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, QWidget *parent) : QWidget(parent), input_(input), - connected_node_(nullptr), viewer_(nullptr) { QVBoxLayout *layout = new QVBoxLayout(this); @@ -51,11 +49,12 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, label_layout->setContentsMargins(0, 0, 0, 0); layout->addLayout(label_layout); - CollapseButton *collapse_btn = new CollapseButton(this); - collapse_btn->setChecked(false); - label_layout->addWidget(collapse_btn); + collapse_btn_ = new CollapseButton(this); + collapse_btn_->setChecked(false); + label_layout->addWidget(collapse_btn_); - label_layout->addWidget(new QLabel(tr("Connected to"), this)); + prefix_lbl_ = new QLabel(this); + label_layout->addWidget(prefix_lbl_); connected_to_lbl_ = new ClickableLabel(this); connected_to_lbl_->setCursor(Qt::PointingHandCursor); @@ -73,9 +72,9 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, connected_to_lbl_->setFont(link_font); if (input_.IsConnected()) { - InputConnected(input_.GetConnectedOutput(), input_); + InputConnected(input_.GetConnectedOutput2(), input_); } else { - InputDisconnected(nullptr, input_); + InputDisconnected(NodeOutput(), input_); } connect(input_.node(), &Node::InputConnected, this, &NodeParamViewConnectedLabel::InputConnected); @@ -83,42 +82,51 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, // Creating the tree is expensive, hold off until the user specifically requests it value_tree_ = nullptr; - connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible); + connect(collapse_btn_, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible); } void NodeParamViewConnectedLabel::SetViewerNode(ViewerOutput *viewer) { if (viewer_) { - disconnect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); + //disconnect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); } viewer_ = viewer; if (viewer_) { - connect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); + //connect(viewer_, &ViewerOutput::PlayheadChanged, this, &NodeParamViewConnectedLabel::UpdateValueTree); UpdateValueTree(); } } +bool NodeParamViewConnectedLabel::DeleteSelected() +{ + if (value_tree_) { + return value_tree_->DeleteSelected(); + } + + return false; +} + void NodeParamViewConnectedLabel::CreateTree() { // Set up table area - value_tree_ = new NodeValueTree(this); + value_tree_ = new ValueSwizzleWidget(this); layout()->addWidget(value_tree_); } -void NodeParamViewConnectedLabel::InputConnected(Node *output, const NodeInput& input) +void NodeParamViewConnectedLabel::InputConnected(const NodeOutput &output, const NodeInput& input) { if (input_ != input) { return; } - connected_node_ = output; + output_ = output; UpdateLabel(); } -void NodeParamViewConnectedLabel::InputDisconnected(Node *output, const NodeInput &input) +void NodeParamViewConnectedLabel::InputDisconnected(const NodeOutput &output, const NodeInput &input) { if (input_ != input) { return; @@ -126,7 +134,7 @@ void NodeParamViewConnectedLabel::InputDisconnected(Node *output, const NodeInpu Q_UNUSED(output) - connected_node_ = nullptr; + output_.Reset(); UpdateLabel(); } @@ -137,7 +145,7 @@ void NodeParamViewConnectedLabel::ShowLabelContextMenu() QAction* disconnect_action = m.addAction(tr("Disconnect")); connect(disconnect_action, &QAction::triggered, this, [this](){ - Core::instance()->undo_stack()->push(new NodeEdgeRemoveCommand(connected_node_, input_), Node::GetDisconnectCommandString(connected_node_, input_)); + Core::instance()->undo_stack()->push(new NodeEdgeRemoveCommand(output_, input_), Node::GetDisconnectCommandString(output_, input_)); }); m.exec(QCursor::pos()); @@ -145,28 +153,30 @@ void NodeParamViewConnectedLabel::ShowLabelContextMenu() void NodeParamViewConnectedLabel::ConnectionClicked() { - if (connected_node_) { - emit RequestSelectNode(connected_node_); + if (output_.IsValid()) { + emit RequestSelectNode(output_.node()); } } void NodeParamViewConnectedLabel::UpdateLabel() { - QString s; + collapse_btn_->setVisible(output_.IsValid()); + connected_to_lbl_->setVisible(output_.IsValid()); - if (connected_node_) { - s = connected_node_->Name(); + if (output_.IsValid()) { + prefix_lbl_->setText(tr("Connected to")); + connected_to_lbl_->setText(output_.node()->GetLabelAndName()); + prefix_lbl_->setForegroundRole(QPalette::Text); } else { - s = tr("Nothing"); + prefix_lbl_->setText(tr("(Not Connected)")); } - - connected_to_lbl_->setText(s); } void NodeParamViewConnectedLabel::UpdateValueTree() { if (value_tree_ && viewer_ && value_tree_->isVisible()) { - value_tree_->SetNode(input_, viewer_->GetPlayhead()); + ValueParams vp(viewer_->GetVideoParams(), viewer_->GetAudioParams(), 0, QString(), LoopMode::kLoopModeOff, nullptr, nullptr); + value_tree_->set(vp, input_); } } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 16a7428af1..97071d6243 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -22,8 +22,10 @@ #define NODEPARAMVIEWCONNECTEDLABEL_H #include "node/param.h" +#include "node/output/viewer/viewer.h" #include "widget/clickablelabel/clickablelabel.h" -#include "widget/nodevaluetree/nodevaluetree.h" +#include "widget/collapsebutton/collapsebutton.h" +#include "widget/nodevaluetree/valueswizzlewidget.h" namespace olive { @@ -34,13 +36,15 @@ class NodeParamViewConnectedLabel : public QWidget { void SetViewerNode(ViewerOutput *viewer); + bool DeleteSelected(); + signals: void RequestSelectNode(Node *n); private slots: - void InputConnected(Node *output, const NodeInput &input); + void InputConnected(const NodeOutput &output, const NodeInput &input); - void InputDisconnected(Node *output, const NodeInput &input); + void InputDisconnected(const NodeOutput &output, const NodeInput &input); void ShowLabelContextMenu(); @@ -53,13 +57,17 @@ private slots: void CreateTree(); + CollapseButton *collapse_btn_; + + QLabel *prefix_lbl_; + ClickableLabel* connected_to_lbl_; NodeInput input_; - Node *connected_node_; + NodeOutput output_; - NodeValueTree *value_tree_; + ValueSwizzleWidget *value_tree_; ViewerOutput *viewer_; diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index 477fc7765e..26d81f7032 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -171,13 +171,13 @@ void NodeParamViewContext::AddEffectMenuItemTriggered(QAction *a) command->add_child(new NodeSetPositionCommand(ctx, ctx, ctx->GetNodePositionInContext(ctx) + QPointF(1, 0))); if (ctx_input.IsConnected()) { - Node *prev_output = ctx_input.GetConnectedOutput(); + NodeOutput prev_output = ctx_input.GetConnectedOutput2(); command->add_child(new NodeEdgeRemoveCommand(prev_output, ctx_input)); command->add_child(new NodeEdgeAddCommand(prev_output, new_node_input)); } - command->add_child(new NodeEdgeAddCommand(n, ctx_input)); + command->add_child(new NodeEdgeAddCommand(NodeOutput(n), ctx_input)); } Core::instance()->undo_stack()->push(command, tr("Added %1 to Node Chain").arg(n->Name())); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 76e22da891..198e47647d 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -60,6 +60,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); connect(node_, &Node::InputArraySizeChanged, this, &NodeParamViewItem::InputArraySizeChanged); + connect(node_, &Node::KeyframeTrackAdded, this, &NodeParamViewItem::ElementKeyframeTrackAdded); // FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast // way of doing this, but "fine" for now. @@ -117,6 +118,15 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) body_->SetInputChecked(input, e); } +bool NodeParamViewItem::DeleteSelected() +{ + if (body_) { + return body_->DeleteSelected(); + } + + return false; +} + NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), @@ -177,6 +187,15 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe } } +NodeParamViewItemBody::~NodeParamViewItemBody() +{ + for (auto it = input_ui_map_.cbegin(); it != input_ui_map_.cend(); it++) { + const InputUI &u = *it; + + delete u.widget_bridge; + } +} + void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const QString &input, int element, int row) { NodeInput input_ref(node, input, element); @@ -332,7 +351,7 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const return lbl_center.y(); } -void NodeParamViewItemBody::EdgeChanged(Node *output, const NodeInput& input) +void NodeParamViewItemBody::EdgeChanged(const NodeOutput &output, const NodeInput& input) { Q_UNUSED(output) @@ -350,12 +369,14 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input) bool is_connected = NodeGroup::ResolveInput(input).IsConnected(); - foreach (QWidget* w, ui_objects.widget_bridge->widgets()) { - w->setVisible(!is_connected); + if (ui_objects.widget_bridge->has_widgets()) { + foreach (QWidget* w, ui_objects.widget_bridge->widgets()) { + w->setVisible(!is_connected); + } } // Show/hide connection label - ui_objects.connected_label->setVisible(is_connected); + ui_objects.connected_label->setVisible(is_connected || !ui_objects.widget_bridge->has_widgets()); if (ui_objects.key_control) { ui_objects.key_control->setVisible(!is_connected); @@ -370,21 +391,23 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input) void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodeParamViewWidgetBridge *bridge, int row) { - // Add widgets for this parameter to the layout - for (int i=0; iwidgets().size(); i++) { - QWidget* w = bridge->widgets().at(i); - - int col = i+kWidgetStartColumn; + if (bridge->has_widgets()) { + // Add widgets for this parameter to the layout + for (size_t i=0; iwidgets().size(); i++) { + QWidget* w = bridge->widgets().at(i); + + int col = i+kWidgetStartColumn; + + int colspan; + if (i == bridge->widgets().size()-1) { + // Span this widget among remaining columns + colspan = kMaxWidgetColumn - col; + } else { + colspan = 1; + } - int colspan; - if (i == bridge->widgets().size()-1) { - // Span this widget among remaining columns - colspan = kMaxWidgetColumn - col; - } else { - colspan = 1; + layout->addWidget(w, row, col, 1, colspan); } - - layout->addWidget(w, row, col, 1, colspan); } } @@ -413,7 +436,9 @@ void NodeParamViewItemBody::InputArraySizeChangedInternal(Node *node, const QStr // Our UI count is larger than the size, delete InputUI input_ui = input_ui_map_.take({node, input, i}); delete input_ui.main_label; - qDeleteAll(input_ui.widget_bridge->widgets()); + if (input_ui.widget_bridge->has_widgets()) { + qDeleteAll(input_ui.widget_bridge->widgets()); + } delete input_ui.widget_bridge; delete input_ui.connected_label; delete input_ui.key_control; @@ -528,6 +553,17 @@ void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e) } } +bool NodeParamViewItemBody::DeleteSelected() +{ + for (auto it = input_ui_map_.cbegin(); it != input_ui_map_.cend(); it++) { + if (it->connected_label && it->connected_label->DeleteSelected()) { + return true; + } + } + + return false; +} + void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input) { InputUI ui = input_ui_map_.value(input); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 56149064c3..f761372cdc 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -51,6 +51,8 @@ class NodeParamViewItemBody : public QWidget { public: NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); + virtual ~NodeParamViewItemBody() override; + void SetTimeTarget(ViewerOutput *target); void Retranslate(); @@ -62,6 +64,8 @@ class NodeParamViewItemBody : public QWidget { void SetInputChecked(const NodeInput &input, bool e); + bool DeleteSelected(); + signals: void RequestSelectNode(Node *node); @@ -140,7 +144,7 @@ class NodeParamViewItemBody : public QWidget { static const int kMaxWidgetColumn; private slots: - void EdgeChanged(Node *output, const NodeInput &input); + void EdgeChanged(const NodeOutput &output, const NodeInput &input); void ArrayCollapseBtnPressed(bool checked); @@ -211,6 +215,8 @@ class NodeParamViewItem : public NodeParamViewItemBase keyframe_connections_ = c; } + bool DeleteSelected(); + signals: void RequestSelectNode(Node *node); @@ -222,6 +228,8 @@ class NodeParamViewItem : public NodeParamViewItemBase void InputArraySizeChanged(const QString &input, int old_size, int new_size); + void ElementKeyframeTrackAdded(const QString &input, int element, int track); + protected slots: virtual void Retranslate() override; diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index 8d02dad740..e0e9dd66cd 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -235,9 +235,9 @@ void NodeParamViewKeyframeControl::KeyframeEnableBtnClicked(bool e) command->add_child(new NodeParamSetKeyframingCommand(input_, true)); // Create one keyframe across all tracks here - const QVector& key_vals = input_.node()->GetSplitStandardValue(input_); + const value_t& key_vals = input_.node()->GetStandardValue(input_); - for (int i=0;i& stored_vals = input_.node()->GetSplitValueAtTime(input_, GetCurrentTimeAsNodeTime()); + const value_t& stored_vals = input_.node()->GetValueAtTime(input_, GetCurrentTimeAsNodeTime()); // Delete all keyframes foreach (const NodeKeyframeTrack& track, input_.node()->GetKeyframeTracks(input_)) { @@ -267,7 +267,7 @@ void NodeParamViewKeyframeControl::KeyframeEnableBtnClicked(bool e) } // Update standard value - for (int i=0;iadd_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(input_, i), stored_vals.at(i))); } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 585f2526e6..dce34b1552 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -35,17 +35,24 @@ #include "nodeparamviewarraywidget.h" #include "nodeparamviewtextedit.h" #include "undo/undostack.h" -#include "widget/bezier/bezierwidget.h" #include "widget/colorbutton/colorbutton.h" #include "widget/filefield/filefield.h" -#include "widget/slider/floatslider.h" -#include "widget/slider/integerslider.h" -#include "widget/slider/rationalslider.h" +#include "widget/nodeparamview/paramwidget/arrayparamwidget.h" +#include "widget/nodeparamview/paramwidget/boolparamwidget.h" +#include "widget/nodeparamview/paramwidget/colorparamwidget.h" +#include "widget/nodeparamview/paramwidget/comboparamwidget.h" +#include "widget/nodeparamview/paramwidget/fileparamwidget.h" +#include "widget/nodeparamview/paramwidget/floatsliderparamwidget.h" +#include "widget/nodeparamview/paramwidget/fontparamwidget.h" +#include "widget/nodeparamview/paramwidget/integersliderparamwidget.h" +#include "widget/nodeparamview/paramwidget/rationalsliderparamwidget.h" +#include "widget/nodeparamview/paramwidget/textparamwidget.h" namespace olive { NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input, QObject *parent) : - QObject(parent) + QObject(parent), + widget_(nullptr) { do { input_hierarchy_.append(input); @@ -58,133 +65,81 @@ NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input, QObject *p CreateWidgets(); } -int GetSliderCount(NodeValue::Type type) -{ - return NodeValue::get_number_of_keyframe_tracks(type); -} - void NodeParamViewWidgetBridge::CreateWidgets() { QWidget *parent = dynamic_cast(this->parent()); if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) { - NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input(), parent); - connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked); - widgets_.append(w); + widget_ = new ArrayParamWidget(GetInnerInput().node(), GetInnerInput().input(), parent); + connect(static_cast(widget_), &ArrayParamWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked); } else { - // We assume the first data type is the "primary" type - NodeValue::Type t = GetDataType(); - switch (t) { - // None of these inputs have applicable UI widgets - case NodeValue::kNone: - case NodeValue::kTexture: - case NodeValue::kMatrix: - case NodeValue::kSamples: - case NodeValue::kVideoParams: - case NodeValue::kAudioParams: - case NodeValue::kSubtitleParams: - case NodeValue::kBinary: - case NodeValue::kDataTypeCount: - break; - case NodeValue::kInt: - { - CreateSliders(1, parent); - break; - } - case NodeValue::kRational: - { - CreateSliders(1, parent); - break; - } - case NodeValue::kFloat: - case NodeValue::kVec2: - case NodeValue::kVec3: - case NodeValue::kVec4: - { - CreateSliders(GetSliderCount(t), parent); - break; + if (AbstractParamWidget *a = GetInnerInput().node()->GetCustomWidget(GetInnerInput().input())) { + a->setParent(this); + widget_ = a; + } else { + // We assume the first data type is the "primary" type + type_t t = GetDataType(); + QString type = GetOuterInput().GetProperty(QStringLiteral("subtype")).toString(); + + if (t == TYPE_INTEGER) { + if (type == QStringLiteral("bool")) { + widget_ = new BoolParamWidget(this); + } else if (type == QStringLiteral("combo")) { + widget_ = new ComboParamWidget(this); + } else { + widget_ = new IntegerSliderParamWidget(this); + } + } else if (t == TYPE_RATIONAL) { + widget_ = new RationalSliderParamWidget(this); + } else if (t == TYPE_DOUBLE) { + if (type == QStringLiteral("color")) { + widget_ = new ColorParamWidget(GetInnerInput(), this); + } else { + widget_ = new FloatSliderParamWidget(this); + } + } else if (t == TYPE_STRING) { + if (type == QStringLiteral("file")) { + widget_ = new FileParamWidget(this); + } else if (type == QStringLiteral("font")) { + widget_ = new FontParamWidget(this); + } else { + widget_ = new TextParamWidget(this); + connect(static_cast(widget_), &TextParamWidget::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer); + } + } } - case NodeValue::kCombo: - { - QComboBox* combobox = new QComboBox(parent); - QStringList items = GetInnerInput().GetComboBoxStrings(); - foreach (const QString& s, items) { - combobox->addItem(s); - } + } - widgets_.append(combobox); - connect(combobox, static_cast(&QComboBox::currentIndexChanged), this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kFile: - { - FileField* file_field = new FileField(parent); - widgets_.append(file_field); - connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kColor: - { - ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager(), parent); - widgets_.append(color_button); - connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kText: - { - NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(parent); - widgets_.append(line_edit); - connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer); - break; - } - case NodeValue::kBoolean: - { - QCheckBox* check_box = new QCheckBox(parent); - widgets_.append(check_box); - connect(check_box, &QCheckBox::clicked, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kFont: - { - QFontComboBox* font_combobox = new QFontComboBox(parent); - widgets_.append(font_combobox); - connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kBezier: - { - BezierWidget *bezier = new BezierWidget(parent); - widgets_.append(bezier); - - connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(bezier->y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(bezier->cp1_x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(bezier->cp1_y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(bezier->cp2_x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - connect(bezier->cp2_y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; + if (widget_) { + // Whatever we created, initialize it + widget_->Initialize(parent, GetChannelCount()); + + value_t def = GetInnerInput().GetDefaultValue(); + if (def.isValid()) { + widget_->SetDefaultValue(def); } + + // Install event filter to disable widgets picking up scroll events + foreach (QWidget* w, widget_->GetWidgets()) { + w->installEventFilter(&scroll_filter_); } + // Connect signals + connect(widget_, &AbstractParamWidget::SliderDragged, this, &NodeParamViewWidgetBridge::ProcessSlider); + connect(widget_, &AbstractParamWidget::ChannelValueChanged, this, &NodeParamViewWidgetBridge::ValueChanged); + // Check all properties UpdateProperties(); UpdateWidgetValues(); - - // Install event filter to disable widgets picking up scroll events - foreach (QWidget* w, widgets_) { - w->installEventFilter(&scroll_filter_); - } - } } -void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track) +void NodeParamViewWidgetBridge::SetInputValue(const value_t::component_t &value, size_t track) { MultiUndoCommand* command = new MultiUndoCommand(); @@ -193,13 +148,15 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track) Core::instance()->undo_stack()->push(command, GetCommandName()); } -void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) +void NodeParamViewWidgetBridge::SetInputValueInternal(const value_t::component_t &value, size_t track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) { Node::SetValueAtTime(GetInnerInput(), GetCurrentTimeAsNodeTime(), value, track, command, insert_on_all_tracks_if_no_key); } -void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int slider_track, const QVariant &value) +void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, size_t slider_track) { + value_t::component_t value = slider->GetValueInternal().at(0); + if (slider->IsDragging()) { // While we're dragging, we block the input's normal signalling and create our own @@ -213,9 +170,6 @@ void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int sli } else if (dragger_.IsStarted()) { - // We were dragging and just stopped - dragger_.Drag(value); - MultiUndoCommand *command = new MultiUndoCommand(); dragger_.End(command); Core::instance()->undo_stack()->push(command, GetCommandName()); @@ -228,173 +182,9 @@ void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int sli } } -void NodeParamViewWidgetBridge::WidgetCallback() -{ - switch (GetDataType()) { - // None of these inputs have applicable UI widgets - case NodeValue::kNone: - case NodeValue::kTexture: - case NodeValue::kMatrix: - case NodeValue::kSamples: - case NodeValue::kVideoParams: - case NodeValue::kAudioParams: - case NodeValue::kSubtitleParams: - case NodeValue::kBinary: - case NodeValue::kDataTypeCount: - break; - case NodeValue::kInt: - { - // Widget is a IntegerSlider - IntegerSlider* slider = static_cast(sender()); - - ProcessSlider(slider, QVariant::fromValue(slider->GetValue())); - break; - } - case NodeValue::kFloat: - { - // Widget is a FloatSlider - FloatSlider* slider = static_cast(sender()); - - ProcessSlider(slider, slider->GetValue()); - break; - } - case NodeValue::kRational: - { - // Widget is a RationalSlider - RationalSlider* slider = static_cast(sender()); - ProcessSlider(slider, QVariant::fromValue(slider->GetValue())); - break; - } - case NodeValue::kVec2: - { - // Widget is a FloatSlider - FloatSlider* slider = static_cast(sender()); - - ProcessSlider(slider, slider->GetValue()); - break; - } - case NodeValue::kVec3: - { - // Widget is a FloatSlider - FloatSlider* slider = static_cast(sender()); - - ProcessSlider(slider, slider->GetValue()); - break; - } - case NodeValue::kVec4: - { - // Widget is a FloatSlider - FloatSlider* slider = static_cast(sender()); - - ProcessSlider(slider, slider->GetValue()); - break; - } - case NodeValue::kFile: - { - SetInputValue(static_cast(sender())->GetFilename(), 0); - break; - } - case NodeValue::kColor: - { - // Sender is a ColorButton - ManagedColor c = static_cast(sender())->GetColor(); - - MultiUndoCommand* command = new MultiUndoCommand(); - - SetInputValueInternal(c.red(), 0, command, false); - SetInputValueInternal(c.green(), 1, command, false); - SetInputValueInternal(c.blue(), 2, command, false); - SetInputValueInternal(c.alpha(), 3, command, false); - - Node* n = GetInnerInput().node(); - n->blockSignals(true); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_input"), c.color_input()); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_display"), c.color_output().display()); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_view"), c.color_output().view()); - n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_look"), c.color_output().look()); - n->blockSignals(false); - - Core::instance()->undo_stack()->push(command, GetCommandName()); - break; - } - case NodeValue::kText: - { - // Sender is a NodeParamViewRichText - SetInputValue(static_cast(sender())->text(), 0); - break; - } - case NodeValue::kBoolean: - { - // Widget is a QCheckBox - SetInputValue(static_cast(sender())->isChecked(), 0); - break; - } - case NodeValue::kFont: - { - // Widget is a QFontComboBox - SetInputValue(static_cast(sender())->currentFont().family(), 0); - break; - } - case NodeValue::kCombo: - { - // Widget is a QComboBox - QComboBox* cb = static_cast(widgets_.first()); - int index = cb->currentIndex(); - - // Subtract any splitters up until this point - for (int i=index-1; i>=0; i--) { - if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() == QStringLiteral("separator")) { - index--; - } - - } - - SetInputValue(index, 0); - break; - } - case NodeValue::kBezier: - { - // Widget is a FloatSlider (child of BezierWidget) - BezierWidget *bw = static_cast(widgets_.first()); - FloatSlider *fs = static_cast(sender()); - - int index = -1; - if (fs == bw->x_slider()) { - index = 0; - } else if (fs == bw->y_slider()) { - index = 1; - } else if (fs == bw->cp1_x_slider()) { - index = 2; - } else if (fs == bw->cp1_y_slider()) { - index = 3; - } else if (fs == bw->cp2_x_slider()) { - index = 4; - } else if (fs == bw->cp2_y_slider()) { - index = 5; - } - - if (index != -1) { - ProcessSlider(fs, index, fs->GetValue()); - } - break; - } - } -} - -template -void NodeParamViewWidgetBridge::CreateSliders(int count, QWidget *parent) +void NodeParamViewWidgetBridge::ValueChanged(size_t track, const value_t::component_t &val) { - for (int i=0;iSliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i)); - fs->SetLadderElementCount(2); - - // HACK: Force some spacing between sliders - fs->setContentsMargins(0, 0, QtUtils::QFontMetricsWidth(fs->fontMetrics(), QStringLiteral(" ")), 0); - - widgets_.append(fs); - connect(fs, &T::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - } + SetInputValue(val, track); } void NodeParamViewWidgetBridge::UpdateWidgetValues() @@ -403,123 +193,13 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() return; } - rational node_time; - if (GetInnerInput().IsKeyframing()) { - node_time = GetCurrentTimeAsNodeTime(); - } - - // We assume the first data type is the "primary" type - switch (GetDataType()) { - // None of these inputs have applicable UI widgets - case NodeValue::kNone: - case NodeValue::kTexture: - case NodeValue::kMatrix: - case NodeValue::kSamples: - case NodeValue::kVideoParams: - case NodeValue::kAudioParams: - case NodeValue::kSubtitleParams: - case NodeValue::kBinary: - case NodeValue::kDataTypeCount: - break; - case NodeValue::kInt: - { - static_cast(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).toLongLong()); - break; - } - case NodeValue::kFloat: - { - static_cast(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).toDouble()); - break; - } - case NodeValue::kRational: - { - static_cast(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).value()); - break; - } - case NodeValue::kVec2: - { - QVector2D vec2 = GetInnerInput().GetValueAtTime(node_time).value(); - - static_cast(widgets_.at(0))->SetValue(static_cast(vec2.x())); - static_cast(widgets_.at(1))->SetValue(static_cast(vec2.y())); - break; - } - case NodeValue::kVec3: - { - QVector3D vec3 = GetInnerInput().GetValueAtTime(node_time).value(); - - static_cast(widgets_.at(0))->SetValue(static_cast(vec3.x())); - static_cast(widgets_.at(1))->SetValue(static_cast(vec3.y())); - static_cast(widgets_.at(2))->SetValue(static_cast(vec3.z())); - break; - } - case NodeValue::kVec4: - { - QVector4D vec4 = GetInnerInput().GetValueAtTime(node_time).value(); - - static_cast(widgets_.at(0))->SetValue(static_cast(vec4.x())); - static_cast(widgets_.at(1))->SetValue(static_cast(vec4.y())); - static_cast(widgets_.at(2))->SetValue(static_cast(vec4.z())); - static_cast(widgets_.at(3))->SetValue(static_cast(vec4.w())); - break; - } - case NodeValue::kFile: - { - FileField* ff = static_cast(widgets_.first()); - ff->SetFilename(GetInnerInput().GetValueAtTime(node_time).toString()); - break; - } - case NodeValue::kColor: - { - ManagedColor mc = GetInnerInput().GetValueAtTime(node_time).value(); - - mc.set_color_input(GetInnerInput().GetProperty("col_input").toString()); - - QString d = GetInnerInput().GetProperty("col_display").toString(); - QString v = GetInnerInput().GetProperty("col_view").toString(); - QString l = GetInnerInput().GetProperty("col_look").toString(); - - mc.set_color_output(ColorTransform(d, v, l)); - - static_cast(widgets_.first())->SetColor(mc); - break; - } - case NodeValue::kText: - { - NodeParamViewTextEdit* e = static_cast(widgets_.first()); - e->setTextPreservingCursor(GetInnerInput().GetValueAtTime(node_time).toString()); - break; - } - case NodeValue::kBoolean: - static_cast(widgets_.first())->setChecked(GetInnerInput().GetValueAtTime(node_time).toBool()); - break; - case NodeValue::kFont: - { - QFontComboBox* fc = static_cast(widgets_.first()); - fc->blockSignals(true); - fc->setCurrentFont(GetInnerInput().GetValueAtTime(node_time).toString()); - fc->blockSignals(false); - break; - } - case NodeValue::kCombo: - { - QComboBox* cb = static_cast(widgets_.first()); - cb->blockSignals(true); - int index = GetInnerInput().GetValueAtTime(node_time).toInt(); - for (int i=0; icount(); i++) { - if (cb->itemData(i).toInt() == index) { - cb->setCurrentIndex(i); - } + if (widget_) { + rational node_time; + if (GetInnerInput().IsKeyframing()) { + node_time = GetCurrentTimeAsNodeTime(); } - cb->blockSignals(false); - break; - } - case NodeValue::kBezier: - { - BezierWidget* bw = static_cast(widgets_.first()); - bw->SetValue(GetInnerInput().GetValueAtTime(node_time).value()); - break; - } + + widget_->SetValue(GetInnerInput().GetValueAtTime(node_time)); } } @@ -540,8 +220,8 @@ QString NodeParamViewWidgetBridge::GetCommandName() const void NodeParamViewWidgetBridge::SetTimebase(const rational& timebase) { - if (GetDataType() == NodeValue::kRational) { - static_cast(widgets_.first())->SetTimebase(timebase); + if (widget_) { + widget_->SetTimebase(timebase); } } @@ -566,272 +246,23 @@ void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input, const } } -void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant &value) +void NodeParamViewWidgetBridge::SetProperty(const QString &key, const value_t &value) { - NodeValue::Type data_type = GetDataType(); - - // Parameters for all types - bool key_is_disable = key.startsWith(QStringLiteral("disable")); - if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) { - - bool e = value.toBool(); - if (key_is_disable) { - e = !e; - } - - if (key.size() == 7) { // just the word "disable" or "enabled" - for (int i=0; isetEnabled(e); - } - } else { // set specific track/widget - bool ok; - int element = key.mid(7).toInt(&ok); - int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); - - if (ok && element >= 0 && element < tracks) { - widgets_.at(element)->setEnabled(e); - } - } - - } - - if (key == QStringLiteral("tooltip")) { - for (int i = 0; i < widgets_.size(); i++) { - widgets_.at(i)->setToolTip(value.toString()); - } - } - - // Parameters for integers, floats, and vectors - if (NodeValue::type_is_numeric(data_type) || NodeValue::type_is_vector(data_type)) { - if (key == QStringLiteral("min")) { - switch (data_type) { - case NodeValue::kInt: - static_cast(widgets_.first())->SetMinimum(value.value()); - break; - case NodeValue::kFloat: - static_cast(widgets_.first())->SetMinimum(value.toDouble()); - break; - case NodeValue::kRational: - static_cast(widgets_.first())->SetMinimum(value.value()); - break; - case NodeValue::kVec2: - { - QVector2D min = value.value(); - static_cast(widgets_.at(0))->SetMinimum(min.x()); - static_cast(widgets_.at(1))->SetMinimum(min.y()); - break; - } - case NodeValue::kVec3: - { - QVector3D min = value.value(); - static_cast(widgets_.at(0))->SetMinimum(min.x()); - static_cast(widgets_.at(1))->SetMinimum(min.y()); - static_cast(widgets_.at(2))->SetMinimum(min.z()); - break; - } - case NodeValue::kVec4: - { - QVector4D min = value.value(); - static_cast(widgets_.at(0))->SetMinimum(min.x()); - static_cast(widgets_.at(1))->SetMinimum(min.y()); - static_cast(widgets_.at(2))->SetMinimum(min.z()); - static_cast(widgets_.at(3))->SetMinimum(min.w()); - break; - } - default: - break; - } - } else if (key == QStringLiteral("max")) { - switch (data_type) { - case NodeValue::kInt: - static_cast(widgets_.first())->SetMaximum(value.value()); - break; - case NodeValue::kFloat: - static_cast(widgets_.first())->SetMaximum(value.toDouble()); - break; - case NodeValue::kRational: - static_cast(widgets_.first())->SetMaximum(value.value()); - break; - case NodeValue::kVec2: - { - QVector2D max = value.value(); - static_cast(widgets_.at(0))->SetMaximum(max.x()); - static_cast(widgets_.at(1))->SetMaximum(max.y()); - break; - } - case NodeValue::kVec3: - { - QVector3D max = value.value(); - static_cast(widgets_.at(0))->SetMaximum(max.x()); - static_cast(widgets_.at(1))->SetMaximum(max.y()); - static_cast(widgets_.at(2))->SetMaximum(max.z()); - break; - } - case NodeValue::kVec4: - { - QVector4D max = value.value(); - static_cast(widgets_.at(0))->SetMaximum(max.x()); - static_cast(widgets_.at(1))->SetMaximum(max.y()); - static_cast(widgets_.at(2))->SetMaximum(max.z()); - static_cast(widgets_.at(3))->SetMaximum(max.w()); - break; - } - default: - break; - } - } else if (key == QStringLiteral("offset")) { - - int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); - - QVector offsets = NodeValue::split_normal_value_into_track_values(data_type, value); - - for (int i=0; i(widgets_.at(i))->SetOffset(offsets.at(i)); - } - - UpdateWidgetValues(); - - } else if (key.startsWith(QStringLiteral("color"))) { - - QColor c(value.toString()); - - int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); - - if (key.size() == 5) { - // Set for all tracks - for (int i=0; i(widgets_.at(i))->SetColor(c); - } - } else { - bool ok; - int element = key.mid(5).toInt(&ok); - if (ok && element >= 0 && element < tracks) { - static_cast(widgets_.at(element))->SetColor(c); - } - } - - } else if (key == QStringLiteral("base")) { - - double d = value.toDouble(); - for (int i=0; i(widgets_.at(i))->SetDragMultiplier(d); - } - - } - } - - // ComboBox strings changing - if (data_type == NodeValue::kCombo) { - if (key == QStringLiteral("combo_str")) { - QComboBox* cb = static_cast(widgets_.first()); - - int old_index = cb->currentIndex(); - - // Block the combobox changed signals since we anticipate the index will be the same and not require a re-render - cb->blockSignals(true); - - cb->clear(); - - QStringList items = value.toStringList(); - int index = 0; - foreach (const QString& s, items) { - if (s.isEmpty()) { - cb->insertSeparator(cb->count()); - cb->setItemData(cb->count()-1, -1); - } else { - cb->addItem(s, index); - index++; - } - } - - cb->setCurrentIndex(old_index); - - cb->blockSignals(false); - - // In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the - // value has changed - if (cb->currentIndex() != old_index) { - WidgetCallback(); - } - } - } - - // Parameters for floats and vectors only - if (data_type == NodeValue::kFloat || NodeValue::type_is_vector(data_type)) { - if (key == QStringLiteral("view")) { - FloatSlider::DisplayType display_type = static_cast(value.toInt()); - - foreach (QWidget* w, widgets_) { - static_cast(w)->SetDisplayType(display_type); - } - } else if (key == QStringLiteral("decimalplaces")) { - int dec_places = value.toInt(); - - foreach (QWidget* w, widgets_) { - static_cast(w)->SetDecimalPlaces(dec_places); - } - } else if (key == QStringLiteral("autotrim")) { - bool autotrim = value.toBool(); - - foreach (QWidget* w, widgets_) { - static_cast(w)->SetAutoTrimDecimalPlaces(autotrim); - } - } - } - - if (data_type == NodeValue::kRational) { - if (key == QStringLiteral("view")) { - RationalSlider::DisplayType display_type = static_cast(value.toInt()); - - foreach (QWidget* w, widgets_) { - static_cast(w)->SetDisplayType(display_type); - } - } else if (key == QStringLiteral("viewlock")) { - bool locked = value.toBool(); - - foreach (QWidget* w, widgets_) { - static_cast(w)->SetLockDisplayType(locked); - } - } - } - - // Parameters for files - if (data_type == NodeValue::kFile) { - FileField* ff = static_cast(widgets_.first()); - - if (key == QStringLiteral("placeholder")) { - ff->SetPlaceholder(value.toString()); - } else if (key == QStringLiteral("directory")) { - ff->SetDirectoryMode(value.toBool()); - } - } - - // Parameters for text - if (data_type == NodeValue::kText) { - NodeParamViewTextEdit *tex = static_cast(widgets_.first()); - - if (key == QStringLiteral("vieweronly")) { - tex->SetEditInViewerOnlyMode(value.toBool()); - } + if (key == QStringLiteral("subtype")) { + //RecreateWidgets(); + } else if (widget_) { + widget_->SetProperty(key, value); } } -void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type) +void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, type_t type) { if (sender() == GetOuterInput().node() && input == GetOuterInput().input()) { - // Delete all widgets - qDeleteAll(widgets_); - widgets_.clear(); - - // Create new widgets - CreateWidgets(); - - // Signal that widgets are new - emit WidgetsRecreated(GetOuterInput()); + RecreateWidgets(); } } -void NodeParamViewWidgetBridge::PropertyChanged(const QString &input, const QString &key, const QVariant &value) +void NodeParamViewWidgetBridge::PropertyChanged(const QString &input, const QString &key, const value_t &value) { bool found = false; @@ -858,6 +289,19 @@ void NodeParamViewWidgetBridge::UpdateProperties() } } +void NodeParamViewWidgetBridge::RecreateWidgets() +{ + // Delete all widgets + delete widget_; + widget_ = nullptr; + + // Create new widgets + CreateWidgets(); + + // Signal that widgets are new + emit WidgetsRecreated(GetOuterInput()); +} + bool NodeParamViewScrollBlocker::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index cfea84c3f2..a0cb2dd318 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -24,7 +24,7 @@ #include #include "node/inputdragger.h" -#include "widget/slider/base/numericsliderbase.h" +#include "widget/nodeparamview/paramwidget/abstractparamwidget.h" #include "widget/timetarget/timetarget.h" namespace olive { @@ -42,11 +42,13 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject public: NodeParamViewWidgetBridge(NodeInput input, QObject* parent); - const QVector& widgets() const + const std::vector& widgets() const { - return widgets_; + return widget_->GetWidgets(); } + bool has_widgets() const { return widget_; } + // Set the timebase of certain Timebased widgets void SetTimebase(const rational& timebase); @@ -64,20 +66,11 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject private: void CreateWidgets(); - void SetInputValue(const QVariant& value, int track); - - void SetInputValueInternal(const QVariant& value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); - - void ProcessSlider(NumericSliderBase* slider, int slider_track, const QVariant& value); - void ProcessSlider(NumericSliderBase* slider, const QVariant& value) - { - ProcessSlider(slider, widgets_.indexOf(slider), value); - } + void SetInputValue(const value_t::component_t &value, size_t track); - void SetProperty(const QString &key, const QVariant &value); + void SetInputValueInternal(const value_t::component_t &value, size_t track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); - template - void CreateSliders(int count, QWidget *parent); + void SetProperty(const QString &key, const value_t &value); void UpdateWidgetValues(); @@ -95,29 +88,38 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject QString GetCommandName() const; - NodeValue::Type GetDataType() const + type_t GetDataType() const { return GetOuterInput().GetDataType(); } + size_t GetChannelCount() const + { + return GetOuterInput().GetChannelCount(); + } + void UpdateProperties(); + void RecreateWidgets(); + QVector input_hierarchy_; - QVector widgets_; + AbstractParamWidget *widget_; NodeInputDragger dragger_; NodeParamViewScrollBlocker scroll_filter_; private slots: - void WidgetCallback(); - void InputValueChanged(const NodeInput& input, const TimeRange& range); - void InputDataTypeChanged(const QString& input, NodeValue::Type type); + void InputDataTypeChanged(const QString& input, type_t type); + + void PropertyChanged(const QString &input, const QString &key, const value_t &value); + + void ProcessSlider(NumericSliderBase* slider, size_t track); - void PropertyChanged(const QString &input, const QString &key, const QVariant &value); + void ValueChanged(size_t track, const value_t::component_t &val); }; diff --git a/app/widget/nodeparamview/paramwidget/CMakeLists.txt b/app/widget/nodeparamview/paramwidget/CMakeLists.txt new file mode 100644 index 0000000000..4e4db6f250 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/CMakeLists.txt @@ -0,0 +1,46 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/nodeparamview/paramwidget/abstractparamwidget.cpp + widget/nodeparamview/paramwidget/abstractparamwidget.h + widget/nodeparamview/paramwidget/arrayparamwidget.cpp + widget/nodeparamview/paramwidget/arrayparamwidget.h + widget/nodeparamview/paramwidget/bezierparamwidget.cpp + widget/nodeparamview/paramwidget/bezierparamwidget.h + widget/nodeparamview/paramwidget/boolparamwidget.cpp + widget/nodeparamview/paramwidget/boolparamwidget.h + widget/nodeparamview/paramwidget/colorparamwidget.cpp + widget/nodeparamview/paramwidget/colorparamwidget.h + widget/nodeparamview/paramwidget/comboparamwidget.cpp + widget/nodeparamview/paramwidget/comboparamwidget.h + widget/nodeparamview/paramwidget/fileparamwidget.cpp + widget/nodeparamview/paramwidget/fileparamwidget.h + widget/nodeparamview/paramwidget/floatsliderparamwidget.cpp + widget/nodeparamview/paramwidget/floatsliderparamwidget.h + widget/nodeparamview/paramwidget/fontparamwidget.cpp + widget/nodeparamview/paramwidget/fontparamwidget.h + widget/nodeparamview/paramwidget/integersliderparamwidget.cpp + widget/nodeparamview/paramwidget/integersliderparamwidget.h + widget/nodeparamview/paramwidget/numericsliderparamwidget.cpp + widget/nodeparamview/paramwidget/numericsliderparamwidget.h + widget/nodeparamview/paramwidget/rationalsliderparamwidget.cpp + widget/nodeparamview/paramwidget/rationalsliderparamwidget.h + widget/nodeparamview/paramwidget/textparamwidget.cpp + widget/nodeparamview/paramwidget/textparamwidget.h + PARENT_SCOPE +) diff --git a/app/widget/nodeparamview/paramwidget/abstractparamwidget.cpp b/app/widget/nodeparamview/paramwidget/abstractparamwidget.cpp new file mode 100644 index 0000000000..c4a44ee4fb --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/abstractparamwidget.cpp @@ -0,0 +1,83 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "abstractparamwidget.h" + +#include + +namespace olive { + +AbstractParamWidget::AbstractParamWidget(QObject *parent) : + QObject{parent} +{ + +} + +AbstractParamWidget::~AbstractParamWidget() +{ + qDeleteAll(widgets_); +} + +void AbstractParamWidget::SetProperty(const QString &key, const value_t &value) +{ + if (key == QStringLiteral("tooltip")) { + for (size_t i = 0; i < widgets_.size(); i++) { + widgets_.at(i)->setToolTip(value.toString()); + } + } else { + bool key_is_disable = key.startsWith(QStringLiteral("disable")); + if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) { + + bool e = value.toBool(); + if (key_is_disable) { + e = !e; + } + + if (key.size() == 7) { // just the word "disable" or "enabled" + for (size_t i=0; isetEnabled(e); + } + } else { // set specific track/widget + bool ok; + size_t element = key.midRef(7).toInt(&ok); + + if (ok && element < widgets_.size()) { + widgets_.at(element)->setEnabled(e); + } + } + + } + } +} + +void AbstractParamWidget::ArbitrateSliders() +{ + NumericSliderBase *w = static_cast(sender()); + + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == w) { + //emit ChannelValueChanged(i, w->GetValue()); + emit SliderDragged(w, i); + break; + } + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/abstractparamwidget.h b/app/widget/nodeparamview/paramwidget/abstractparamwidget.h new file mode 100644 index 0000000000..8f31a2fdc9 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/abstractparamwidget.h @@ -0,0 +1,69 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 ABSTRACTPARAMWIDGET_H +#define ABSTRACTPARAMWIDGET_H + +#include + +#include "node/value.h" +#include "widget/slider/base/numericsliderbase.h" + +namespace olive { + +class AbstractParamWidget : public QObject +{ + Q_OBJECT +public: + explicit AbstractParamWidget(QObject *parent = nullptr); + + virtual ~AbstractParamWidget() override; + + const std::vector &GetWidgets() const { return widgets_; } + + virtual void Initialize(QWidget *parent, size_t channels) = 0; + + virtual void SetValue(const value_t &val) = 0; + + virtual void SetDefaultValue(const value_t &val) {} + + virtual void SetProperty(const QString &key, const value_t &value); + + virtual void SetTimebase(const rational &timebase){} + +signals: + void ChannelValueChanged(size_t channel, const olive::value_t::component_t &val); + + void SliderDragged(NumericSliderBase *slider, size_t track); + +protected: + void AddWidget(QWidget *w) { widgets_.push_back(w); } + +protected slots: + void ArbitrateSliders(); + +private: + std::vector widgets_; + +}; + +} + +#endif // ABSTRACTPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/arrayparamwidget.cpp b/app/widget/nodeparamview/paramwidget/arrayparamwidget.cpp new file mode 100644 index 0000000000..cd0a3a5a8d --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/arrayparamwidget.cpp @@ -0,0 +1,42 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "arrayparamwidget.h" + +#include "widget/nodeparamview/nodeparamviewarraywidget.h" + +namespace olive { + +ArrayParamWidget::ArrayParamWidget(Node *node, const QString &input, QObject *parent) + : AbstractParamWidget{parent}, + node_(node), + input_(input) +{ + +} + +void ArrayParamWidget::Initialize(QWidget *parent, size_t channels) +{ + NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(node_, input_, parent); + connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &ArrayParamWidget::DoubleClicked); + AddWidget(w); +} + +} diff --git a/app/widget/nodetableview/nodetableview.h b/app/widget/nodeparamview/paramwidget/arrayparamwidget.h similarity index 63% rename from app/widget/nodetableview/nodetableview.h rename to app/widget/nodeparamview/paramwidget/arrayparamwidget.h index 3c4cf87ca0..403e06eca1 100644 --- a/app/widget/nodetableview/nodetableview.h +++ b/app/widget/nodeparamview/paramwidget/arrayparamwidget.h @@ -18,34 +18,32 @@ ***/ -#ifndef NODETABLEVIEW_H -#define NODETABLEVIEW_H +#ifndef ARRAYPARAMWIDGET_H +#define ARRAYPARAMWIDGET_H -#include - -#include "node/node.h" +#include "abstractparamwidget.h" namespace olive { -class NodeTableView : public QTreeWidget +class ArrayParamWidget : public AbstractParamWidget { Q_OBJECT public: - NodeTableView(QWidget* parent = nullptr); + explicit ArrayParamWidget(Node *node, const QString &input, QObject *parent = nullptr); - void SelectNodes(const QVector &nodes); + virtual void Initialize(QWidget *parent, size_t channels) override; - void DeselectNodes(const QVector& nodes); + virtual void SetValue(const value_t &val) override {} - void SetTime(const rational& time); +signals: + void DoubleClicked(); private: - QMap top_level_item_map_; - - rational last_time_; + Node *node_; + QString input_; }; } -#endif // NODETABLEVIEW_H +#endif // ARRAYPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/bezierparamwidget.cpp b/app/widget/nodeparamview/paramwidget/bezierparamwidget.cpp new file mode 100644 index 0000000000..4e138186b6 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/bezierparamwidget.cpp @@ -0,0 +1,81 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "bezierparamwidget.h" + +#include "widget/bezier/bezierwidget.h" + +namespace olive { + +BezierParamWidget::BezierParamWidget(QObject *parent) + : AbstractParamWidget{parent} +{ + +} + +void BezierParamWidget::Initialize(QWidget *parent, size_t channels) +{ + Q_ASSERT(channels == 6); + + BezierWidget *bezier = new BezierWidget(parent); + AddWidget(bezier); + + connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); + connect(bezier->y_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); + connect(bezier->cp1_x_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); + connect(bezier->cp1_y_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); + connect(bezier->cp2_x_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); + connect(bezier->cp2_y_slider(), &FloatSlider::ValueChanged, this, &BezierParamWidget::Arbitrate); +} + +void BezierParamWidget::SetValue(const value_t &val) +{ + Bezier b(val.value(0), val.value(1), val.value(2), val.value(3), val.value(4), val.value(5)); + BezierWidget* bw = static_cast(GetWidgets().at(0)); + bw->SetValue(b); +} + +void BezierParamWidget::Arbitrate() +{ + // Widget is a FloatSlider (child of BezierWidget) + BezierWidget *bw = static_cast(GetWidgets().at(0)); + FloatSlider *fs = static_cast(sender()); + + int index = -1; + if (fs == bw->x_slider()) { + index = 0; + } else if (fs == bw->y_slider()) { + index = 1; + } else if (fs == bw->cp1_x_slider()) { + index = 2; + } else if (fs == bw->cp1_y_slider()) { + index = 3; + } else if (fs == bw->cp2_x_slider()) { + index = 4; + } else if (fs == bw->cp2_y_slider()) { + index = 5; + } + + if (index != -1) { + emit SliderDragged(fs, index); + } +} + +} diff --git a/app/node/project/serializer/typeserializer.h b/app/widget/nodeparamview/paramwidget/bezierparamwidget.h similarity index 62% rename from app/node/project/serializer/typeserializer.h rename to app/widget/nodeparamview/paramwidget/bezierparamwidget.h index c1b2bd5058..967a50d9b0 100644 --- a/app/node/project/serializer/typeserializer.h +++ b/app/widget/nodeparamview/paramwidget/bezierparamwidget.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2023 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,29 +18,28 @@ ***/ -#ifndef TYPESERIALIZER_H -#define TYPESERIALIZER_H +#ifndef BEZIERPARAMWIDGET_H +#define BEZIERPARAMWIDGET_H -#include -#include -#include - -#include "common/xmlutils.h" +#include "abstractparamwidget.h" namespace olive { -using namespace core; - -class TypeSerializer +class BezierParamWidget : public AbstractParamWidget { + Q_OBJECT public: - TypeSerializer() = default; + BezierParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; - static AudioParams LoadAudioParams(QXmlStreamReader *reader); - static void SaveAudioParams(QXmlStreamWriter *writer, const AudioParams &a); +private slots: + void Arbitrate(); }; } -#endif // TYPESERIALIZER_H +#endif // BEZIERPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/boolparamwidget.cpp b/app/widget/nodeparamview/paramwidget/boolparamwidget.cpp new file mode 100644 index 0000000000..509a9e9c44 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/boolparamwidget.cpp @@ -0,0 +1,61 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "boolparamwidget.h" + +#include + +namespace olive { + +BoolParamWidget::BoolParamWidget(QObject *parent) + : AbstractParamWidget{parent} +{ + +} + +void BoolParamWidget::Initialize(QWidget *parent, size_t channels) +{ + for (size_t i = 0; i < channels; i++) { + QCheckBox *c = new QCheckBox(parent); + connect(c, &QCheckBox::clicked, this, &BoolParamWidget::Arbitrate); + AddWidget(c); + } +} + +void BoolParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->setChecked(val.value(i)); + } +} + +void BoolParamWidget::Arbitrate() +{ + QCheckBox *w = static_cast(sender()); + + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == w) { + emit ChannelValueChanged(i, int64_t(w->isChecked())); + break; + } + } +} + +} diff --git a/app/widget/nodetableview/nodetablewidget.cpp b/app/widget/nodeparamview/paramwidget/boolparamwidget.h similarity index 61% rename from app/widget/nodetableview/nodetablewidget.cpp rename to app/widget/nodeparamview/paramwidget/boolparamwidget.h index 2ebb1eac8f..37b984f57f 100644 --- a/app/widget/nodetableview/nodetablewidget.cpp +++ b/app/widget/nodeparamview/paramwidget/boolparamwidget.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,21 +18,28 @@ ***/ -#include "nodetablewidget.h" +#ifndef BOOLPARAMWIDGET_H +#define BOOLPARAMWIDGET_H -#include +#include "abstractparamwidget.h" namespace olive { -NodeTableWidget::NodeTableWidget(QWidget* parent) : - TimeBasedWidget(parent) +class BoolParamWidget : public AbstractParamWidget { - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); + Q_OBJECT +public: + explicit BoolParamWidget(QObject *parent = nullptr); - view_ = new NodeTableView(); - layout->addWidget(view_); -} + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + +private slots: + void Arbitrate(); + +}; } + +#endif // BOOLPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/colorparamwidget.cpp b/app/widget/nodeparamview/paramwidget/colorparamwidget.cpp new file mode 100644 index 0000000000..f38d467e8e --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/colorparamwidget.cpp @@ -0,0 +1,78 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "colorparamwidget.h" + +#include "node/project.h" +#include "widget/colorbutton/colorbutton.h" + +namespace olive { + +ColorParamWidget::ColorParamWidget(const NodeInput &input, QObject *parent) + : AbstractParamWidget{parent}, + input_(input) +{ + +} + +void ColorParamWidget::Initialize(QWidget *parent, size_t channels) +{ + Q_ASSERT(channels == 4); + + ColorButton* color_button = new ColorButton(input_.node()->project()->color_manager(), parent); + AddWidget(color_button); + connect(color_button, &ColorButton::ColorChanged, this, &ColorParamWidget::Arbitrate); +} + +void ColorParamWidget::SetValue(const value_t &val) +{ + ManagedColor mc = val.toColor(); + + mc.set_color_input(input_.GetProperty("col_input").toString()); + + QString d = input_.GetProperty("col_display").toString(); + QString v = input_.GetProperty("col_view").toString(); + QString l = input_.GetProperty("col_look").toString(); + + mc.set_color_output(ColorTransform(d, v, l)); + + static_cast(GetWidgets().at(0))->SetColor(mc); +} + +void ColorParamWidget::Arbitrate() +{ + // Sender is a ColorButton + ManagedColor c = static_cast(sender())->GetColor(); + + emit ChannelValueChanged(0, double(c.red())); + emit ChannelValueChanged(1, double(c.green())); + emit ChannelValueChanged(2, double(c.blue())); + emit ChannelValueChanged(3, double(c.alpha())); + + Node* n = input_.node(); + n->blockSignals(true); + n->SetInputProperty(input_.input(), QStringLiteral("col_input"), c.color_input()); + n->SetInputProperty(input_.input(), QStringLiteral("col_display"), c.color_output().display()); + n->SetInputProperty(input_.input(), QStringLiteral("col_view"), c.color_output().view()); + n->SetInputProperty(input_.input(), QStringLiteral("col_look"), c.color_output().look()); + n->blockSignals(false); +} + +} diff --git a/app/widget/nodeparamview/paramwidget/colorparamwidget.h b/app/widget/nodeparamview/paramwidget/colorparamwidget.h new file mode 100644 index 0000000000..310ef93185 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/colorparamwidget.h @@ -0,0 +1,49 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 COLORPARAMWIDGET_H +#define COLORPARAMWIDGET_H + +#include "abstractparamwidget.h" +#include "node/color/colormanager/colormanager.h" + +namespace olive { + +class ColorParamWidget : public AbstractParamWidget +{ + Q_OBJECT +public: + explicit ColorParamWidget(const NodeInput &input, QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + +private: + NodeInput input_; + +private slots: + void Arbitrate(); + +}; + +} + +#endif // COLORPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/comboparamwidget.cpp b/app/widget/nodeparamview/paramwidget/comboparamwidget.cpp new file mode 100644 index 0000000000..a8a8a78262 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/comboparamwidget.cpp @@ -0,0 +1,127 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "comboparamwidget.h" + +#include + +namespace olive { + +#define super AbstractParamWidget + +ComboParamWidget::ComboParamWidget(QObject *parent) + : super{parent} +{ +} + +void ComboParamWidget::Initialize(QWidget *parent, size_t channels) +{ + for (size_t i = 0; i < channels; i++) { + QComboBox* combobox = new QComboBox(parent); + AddWidget(combobox); + connect(combobox, static_cast(&QComboBox::currentIndexChanged), this, &ComboParamWidget::ArbitrateCombo); + } +} + +void ComboParamWidget::SetValue(const value_t &val) +{ + for (size_t j = 0; j < val.size() && j < GetWidgets().size(); j++) { + QComboBox* cb = static_cast(GetWidgets().at(j)); + cb->blockSignals(true); + int index = val.value(j); + for (int i=0; icount(); i++) { + if (cb->itemData(i).toInt() == index) { + cb->setCurrentIndex(i); + } + } + cb->blockSignals(false); + } +} + +void ComboParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("combo_str")) { + for (size_t j = 0; j < val.size() && j < GetWidgets().size(); j++) { + QComboBox* cb = static_cast(GetWidgets().at(j)); + + int old_index = cb->currentIndex(); + + // Block the combobox changed signals since we anticipate the index will be the same and not require a re-render + cb->blockSignals(true); + + cb->clear(); + + QStringList items = val.value(j); + int index = 0; + foreach (const QString& s, items) { + if (s.isEmpty()) { + cb->insertSeparator(cb->count()); + cb->setItemData(cb->count()-1, -1); + } else { + cb->addItem(s, index); + index++; + } + } + + cb->setCurrentIndex(old_index); + + cb->blockSignals(false); + + // In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the + // value has changed + if (cb->currentIndex() != old_index) { + ArbitrateSpecificCombo(cb); + } + } + } else { + super::SetProperty(key, val); + } +} + +void ComboParamWidget::ArbitrateSpecificCombo(QComboBox *cb) +{ + size_t channel = 0; + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == cb) { + channel = i; + break; + } + } + + int64_t index = cb->currentIndex(); + + // Subtract any splitters up until this point + for (int i=index-1; i>=0; i--) { + if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() == QStringLiteral("separator")) { + index--; + } + } + + emit ChannelValueChanged(channel, index); +} + +void ComboParamWidget::ArbitrateCombo() +{ + // Widget is a QComboBox + QComboBox* cb = static_cast(sender()); + ArbitrateSpecificCombo(cb); +} + +} diff --git a/app/render/shadercode.h b/app/widget/nodeparamview/paramwidget/comboparamwidget.h similarity index 55% rename from app/render/shadercode.h rename to app/widget/nodeparamview/paramwidget/comboparamwidget.h index 238d512435..31cfce67bb 100644 --- a/app/render/shadercode.h +++ b/app/widget/nodeparamview/paramwidget/comboparamwidget.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,34 +18,35 @@ ***/ -#ifndef SHADERCODE_H -#define SHADERCODE_H +#ifndef COMBOPARAMWIDGET_H +#define COMBOPARAMWIDGET_H -#include "common/filefunctions.h" +#include + +#include "abstractparamwidget.h" namespace olive { -class ShaderCode { +class ComboParamWidget : public AbstractParamWidget +{ + Q_OBJECT public: - ShaderCode(const QString& frag_code = QString(), const QString& vert_code = QString()) : - frag_code_(frag_code), - vert_code_(vert_code) - { - } + explicit ComboParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; - const QString& frag_code() const { return frag_code_; } - void set_frag_code(const QString &f) { frag_code_ = f; } + virtual void SetValue(const value_t &val) override; - const QString& vert_code() const { return vert_code_; } - void set_vert_code(const QString &v) { vert_code_ = v; } + virtual void SetProperty(const QString &key, const value_t &val) override; private: - QString frag_code_; + void ArbitrateSpecificCombo(QComboBox *b); - QString vert_code_; +private slots: + void ArbitrateCombo(); }; } -#endif // SHADERCODE_H +#endif // COMBOPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/fileparamwidget.cpp b/app/widget/nodeparamview/paramwidget/fileparamwidget.cpp new file mode 100644 index 0000000000..7260c7d998 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/fileparamwidget.cpp @@ -0,0 +1,79 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "fileparamwidget.h" + +#include "widget/filefield/filefield.h" + +namespace olive { + +#define super AbstractParamWidget + +FileParamWidget::FileParamWidget(QObject *parent) + : super{parent} +{ + +} + +void FileParamWidget::Initialize(QWidget *parent, size_t channels) +{ + for (size_t i = 0; i < channels; i++) { + FileField* file_field = new FileField(parent); + AddWidget(file_field); + connect(file_field, &FileField::FilenameChanged, this, &FileParamWidget::Arbitrate); + } +} + +void FileParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + FileField* ff = static_cast(GetWidgets().at(i)); + ff->SetFilename(val.value(i)); + } +} + +void FileParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("placeholder")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetPlaceholder(val.value(i)); + } + } else if (key == QStringLiteral("directory")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetDirectoryMode(val.value(i)); + } + } else { + super::SetProperty(key, val); + } +} + +void FileParamWidget::Arbitrate() +{ + FileField* ff = static_cast(sender()); + + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == ff) { + emit ChannelValueChanged(i, ff->GetFilename()); + break; + } + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/fileparamwidget.h b/app/widget/nodeparamview/paramwidget/fileparamwidget.h new file mode 100644 index 0000000000..2eed3465d7 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/fileparamwidget.h @@ -0,0 +1,47 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 FILEPARAMWIDGET_H +#define FILEPARAMWIDGET_H + +#include "abstractparamwidget.h" + +namespace olive { + +class FileParamWidget : public AbstractParamWidget +{ + Q_OBJECT +public: + explicit FileParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + + virtual void SetProperty(const QString &key, const value_t &val) override; + +private slots: + void Arbitrate(); + +}; + +} + +#endif // FILEPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.cpp b/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.cpp new file mode 100644 index 0000000000..fd19c81f61 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.cpp @@ -0,0 +1,90 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "floatsliderparamwidget.h" + +#include "widget/slider/floatslider.h" + +namespace olive { + +#define super NumericSliderParamWidget + +FloatSliderParamWidget::FloatSliderParamWidget(QObject *parent) + : super{parent} +{ +} + +void FloatSliderParamWidget::Initialize(QWidget *parent, size_t channels) +{ + CreateSliders(parent, channels); +} + +void FloatSliderParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetValue(val.value(i)); + } +} + +void FloatSliderParamWidget::SetDefaultValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetDefaultValue(val.value(i)); + } +} + +void FloatSliderParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("min")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetMinimum(val.value(i)); + } + } else if (key == QStringLiteral("max")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetMaximum(val.value(i)); + } + } else if (key == QStringLiteral("view")) { + FloatSlider::DisplayType display_type = static_cast(val.toInt()); + + foreach (QWidget* w, GetWidgets()) { + static_cast(w)->SetDisplayType(display_type); + } + } else if (key == QStringLiteral("decimalplaces")) { + int dec_places = val.toInt(); + + foreach (QWidget* w, GetWidgets()) { + static_cast(w)->SetDecimalPlaces(dec_places); + } + } else if (key == QStringLiteral("autotrim")) { + bool autotrim = val.toBool(); + + foreach (QWidget* w, GetWidgets()) { + static_cast(w)->SetAutoTrimDecimalPlaces(autotrim); + } + } else if (key == QStringLiteral("offset")) { + for (size_t i=0; i(GetWidgets().at(i))->SetOffset(val.value(i)); + } + } else { + super::SetProperty(key, val); + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.h b/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.h new file mode 100644 index 0000000000..78ca46f183 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/floatsliderparamwidget.h @@ -0,0 +1,46 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 FLOATSLIDERPARAMWIDGET_H +#define FLOATSLIDERPARAMWIDGET_H + +#include "numericsliderparamwidget.h" + +namespace olive { + +class FloatSliderParamWidget : public NumericSliderParamWidget +{ + Q_OBJECT +public: + explicit FloatSliderParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + + virtual void SetDefaultValue(const value_t &val) override; + + virtual void SetProperty(const QString &key, const value_t &val) override; + +}; + +} + +#endif // FLOATSLIDERPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/fontparamwidget.cpp b/app/widget/nodeparamview/paramwidget/fontparamwidget.cpp new file mode 100644 index 0000000000..3aae0c2292 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/fontparamwidget.cpp @@ -0,0 +1,64 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "fontparamwidget.h" + +#include + +namespace olive { + +FontParamWidget::FontParamWidget(QObject *parent) + : AbstractParamWidget{parent} +{ + +} + +void FontParamWidget::Initialize(QWidget *parent, size_t channels) +{ + for (size_t i = 0; i < channels; i++) { + QFontComboBox* font_combobox = new QFontComboBox(parent); + AddWidget(font_combobox); + connect(font_combobox, &QFontComboBox::currentFontChanged, this, &FontParamWidget::Arbitrate); + } +} + +void FontParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + QFontComboBox* fc = static_cast(GetWidgets().at(i)); + fc->blockSignals(true); + fc->setCurrentFont(val.value(i)); + fc->blockSignals(false); + } +} + +void FontParamWidget::Arbitrate() +{ + QFontComboBox* ff = static_cast(sender()); + + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == ff) { + emit ChannelValueChanged(i, ff->currentFont().toString()); + break; + } + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/fontparamwidget.h b/app/widget/nodeparamview/paramwidget/fontparamwidget.h new file mode 100644 index 0000000000..8294387908 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/fontparamwidget.h @@ -0,0 +1,45 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 FONTPARAMWIDGET_H +#define FONTPARAMWIDGET_H + +#include "abstractparamwidget.h" + +namespace olive { + +class FontParamWidget : public AbstractParamWidget +{ + Q_OBJECT +public: + explicit FontParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + +private slots: + void Arbitrate(); + +}; + +} + +#endif // FONTPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/integersliderparamwidget.cpp b/app/widget/nodeparamview/paramwidget/integersliderparamwidget.cpp new file mode 100644 index 0000000000..ea26849a05 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/integersliderparamwidget.cpp @@ -0,0 +1,72 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "integersliderparamwidget.h" + +#include "widget/slider/integerslider.h" + +namespace olive { + +#define super NumericSliderParamWidget + +IntegerSliderParamWidget::IntegerSliderParamWidget(QObject *parent) : + super(parent) +{ +} + +void IntegerSliderParamWidget::Initialize(QWidget *parent, size_t channels) +{ + CreateSliders(parent, channels); +} + +void IntegerSliderParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetValue(val.value(i)); + } +} + +void IntegerSliderParamWidget::SetDefaultValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetDefaultValue(val.value(i)); + } +} + +void IntegerSliderParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("min")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetMinimum(val.value(i)); + } + } else if (key == QStringLiteral("max")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetValue(val.value(i)); + } + } else if (key == QStringLiteral("offset")) { + for (size_t i=0; i(GetWidgets().at(i))->SetOffset(val.value(i)); + } + } else { + super::SetProperty(key, val); + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/integersliderparamwidget.h b/app/widget/nodeparamview/paramwidget/integersliderparamwidget.h new file mode 100644 index 0000000000..18b4e3b644 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/integersliderparamwidget.h @@ -0,0 +1,46 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 INTEGERSLIDERPARAMWIDGET_H +#define INTEGERSLIDERPARAMWIDGET_H + +#include "numericsliderparamwidget.h" + +namespace olive { + +class IntegerSliderParamWidget : public NumericSliderParamWidget +{ + Q_OBJECT +public: + IntegerSliderParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + + virtual void SetDefaultValue(const value_t &val) override; + + virtual void SetProperty(const QString &key, const value_t &val) override; + +}; + +} + +#endif // INTEGERSLIDERPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.cpp b/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.cpp new file mode 100644 index 0000000000..cdfda95887 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.cpp @@ -0,0 +1,62 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "numericsliderparamwidget.h" + +#include "widget/slider/base/numericsliderbase.h" + +namespace olive { + +#define super AbstractParamWidget + +NumericSliderParamWidget::NumericSliderParamWidget(QObject *parent) + : AbstractParamWidget{parent} +{ + +} + +void NumericSliderParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("color")) { + QColor c(val.toString()); + + if (key.size() == 5) { + // Set for all tracks + for (size_t i=0; i(GetWidgets().at(i))->SetColor(c); + } + } else { + bool ok; + size_t element = key.midRef(5).toInt(&ok); + if (ok && element < GetWidgets().size()) { + static_cast(GetWidgets().at(element))->SetColor(c); + } + } + } else if (key == QStringLiteral("base")) { + double d = val.toDouble(); + for (size_t i=0; i(GetWidgets().at(i))->SetDragMultiplier(d); + } + } else { + super::SetProperty(key, val); + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.h b/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.h new file mode 100644 index 0000000000..3dc72dab81 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/numericsliderparamwidget.h @@ -0,0 +1,59 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 NUMERICSLIDERPARAMWIDGET_H +#define NUMERICSLIDERPARAMWIDGET_H + +#include "abstractparamwidget.h" +#include "common/qtutils.h" + +namespace olive { + +class NumericSliderParamWidget : public AbstractParamWidget +{ + Q_OBJECT +public: + explicit NumericSliderParamWidget(QObject *parent = nullptr); + + virtual void SetProperty(const QString &key, const value_t &val) override; + +protected: + template + void CreateSliders(QWidget *parent, size_t channels) + { + for (size_t i = 0; i < channels; i++) { + T *s = new T(parent); + + s->SetLadderElementCount(2); + + // HACK: Force some spacing between sliders + s->setContentsMargins(0, 0, QtUtils::QFontMetricsWidth(s->fontMetrics(), QStringLiteral(" ")), 0); + + connect(s, &T::ValueChanged, this, &NumericSliderParamWidget::ArbitrateSliders); + + AddWidget(s); + } + } + +}; + +} + +#endif // NUMERICSLIDERPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.cpp b/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.cpp new file mode 100644 index 0000000000..95d36ad4a5 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.cpp @@ -0,0 +1,80 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "rationalsliderparamwidget.h" + +#include "widget/slider/rationalslider.h" + +namespace olive { + +#define super NumericSliderParamWidget + +RationalSliderParamWidget::RationalSliderParamWidget(QObject *parent) + : NumericSliderParamWidget{parent} +{ + +} + +void RationalSliderParamWidget::Initialize(QWidget *parent, size_t channels) +{ + CreateSliders(parent, channels); +} + +void RationalSliderParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetValue(val.value(i)); + } +} + +void RationalSliderParamWidget::SetDefaultValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetDefaultValue(val.value(i)); + } +} + +void RationalSliderParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("view")) { + RationalSlider::DisplayType display_type = static_cast(val.toInt()); + + foreach (QWidget* w, GetWidgets()) { + static_cast(w)->SetDisplayType(display_type); + } + } else if (key == QStringLiteral("viewlock")) { + bool locked = val.toBool(); + + foreach (QWidget* w, GetWidgets()) { + static_cast(w)->SetLockDisplayType(locked); + } + } else { + super::SetProperty(key, val); + } +} + +void RationalSliderParamWidget::SetTimebase(const rational &timebase) +{ + for (QWidget *w : GetWidgets()) { + static_cast(w)->SetTimebase(timebase); + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.h b/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.h new file mode 100644 index 0000000000..fee889e764 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/rationalsliderparamwidget.h @@ -0,0 +1,47 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 RATIONALSLIDERPARAMWIDGET_H +#define RATIONALSLIDERPARAMWIDGET_H + +#include "numericsliderparamwidget.h" + +namespace olive { + +class RationalSliderParamWidget : public NumericSliderParamWidget +{ +public: + explicit RationalSliderParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + + virtual void SetDefaultValue(const value_t &val) override; + + virtual void SetProperty(const QString &key, const value_t &val) override; + + virtual void SetTimebase(const rational &timebase) override; + +}; + +} + +#endif // RATIONALSLIDERPARAMWIDGET_H diff --git a/app/widget/nodeparamview/paramwidget/textparamwidget.cpp b/app/widget/nodeparamview/paramwidget/textparamwidget.cpp new file mode 100644 index 0000000000..f557622286 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/textparamwidget.cpp @@ -0,0 +1,76 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "textparamwidget.h" + +#include "widget/nodeparamview/nodeparamviewtextedit.h" + +namespace olive { + +#define super AbstractParamWidget + +TextParamWidget::TextParamWidget(QObject *parent) + : super{parent} +{ + +} + +void TextParamWidget::Initialize(QWidget *parent, size_t channels) +{ + for (size_t i = 0; i < channels; i++) { + NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(parent); + AddWidget(line_edit); + connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &TextParamWidget::Arbitrate); + connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &TextParamWidget::RequestEditInViewer); + } +} + +void TextParamWidget::SetValue(const value_t &val) +{ + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + NodeParamViewTextEdit* e = static_cast(GetWidgets().at(i)); + e->setTextPreservingCursor(val.value(i)); + } +} + +void TextParamWidget::SetProperty(const QString &key, const value_t &val) +{ + if (key == QStringLiteral("vieweronly")) { + for (size_t i = 0; i < val.size() && i < GetWidgets().size(); i++) { + static_cast(GetWidgets().at(i))->SetEditInViewerOnlyMode(val.value(i)); + } + } else { + super::SetProperty(key, val); + } +} + +void TextParamWidget::Arbitrate() +{ + NodeParamViewTextEdit* ff = static_cast(sender()); + + for (size_t i = 0; i < GetWidgets().size(); i++) { + if (GetWidgets().at(i) == ff) { + emit ChannelValueChanged(i, ff->text()); + break; + } + } +} + +} diff --git a/app/widget/nodeparamview/paramwidget/textparamwidget.h b/app/widget/nodeparamview/paramwidget/textparamwidget.h new file mode 100644 index 0000000000..68a594ffb4 --- /dev/null +++ b/app/widget/nodeparamview/paramwidget/textparamwidget.h @@ -0,0 +1,50 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 TEXTPARAMWIDGET_H +#define TEXTPARAMWIDGET_H + +#include "abstractparamwidget.h" + +namespace olive { + +class TextParamWidget : public AbstractParamWidget +{ + Q_OBJECT +public: + explicit TextParamWidget(QObject *parent = nullptr); + + virtual void Initialize(QWidget *parent, size_t channels) override; + + virtual void SetValue(const value_t &val) override; + + virtual void SetProperty(const QString &key, const value_t &val) override; + +signals: + void RequestEditInViewer(); + +private slots: + void Arbitrate(); + +}; + +} + +#endif // TEXTPARAMWIDGET_H diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp deleted file mode 100644 index f191f4b64a..0000000000 --- a/app/widget/nodetableview/nodetableview.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "nodetableview.h" - -#include -#include - -#include "node/traverser.h" - -namespace olive { - -NodeTableView::NodeTableView(QWidget* parent) : - QTreeWidget(parent) -{ - setColumnCount(3); - setHeaderLabels({tr("Type"), - tr("Source"), - tr("R/X"), - tr("G/Y"), - tr("B/Z"), - tr("A/W")}); -} - -void NodeTableView::SelectNodes(const QVector &nodes) -{ - foreach (Node* n, nodes) { - QTreeWidgetItem* top_item = new QTreeWidgetItem(); - top_item->setText(0, n->GetLabelAndName()); - top_item->setFirstColumnSpanned(true); - this->addTopLevelItem(top_item); - top_level_item_map_.insert(n, top_item); - } - - SetTime(last_time_); -} - -void NodeTableView::DeselectNodes(const QVector &nodes) -{ - foreach (Node* n, nodes) { - delete top_level_item_map_.take(n); - } -} - -void NodeTableView::SetTime(const rational &time) -{ - last_time_ = time; - - NodeTraverser traverser; - - for (auto i=top_level_item_map_.constBegin(); i!=top_level_item_map_.constEnd(); i++) { - Node* node = i.key(); - QTreeWidgetItem* item = i.value(); - - // Generate a value database for this node at this time - NodeValueDatabase db = traverser.GenerateDatabase(node, TimeRange(time, time)); - - // Delete any children of this item that aren't in this database - for (int j=0; jchildCount(); j++) { - if (!db.contains(item->child(j)->data(0, Qt::UserRole).toString())) { - delete item->takeChild(j); - j--; - } - } - - // Update all inputs - for (auto l=db.begin(); l!=db.end(); l++) { - const NodeValueTable& table = l.value(); - - if (!node->HasInputWithID(l.key())) { - // Filters out table entries that aren't inputs (like "global") - continue; - } - - QTreeWidgetItem* input_item = nullptr; - - for (int j=0; jchildCount(); j++) { - QTreeWidgetItem* compare = item->child(j); - - if (compare->data(0, Qt::UserRole).toString() == l.key()) { - input_item = compare; - break; - } - } - - if (!input_item) { - input_item = new QTreeWidgetItem(); - input_item->setText(0, node->GetInputName(l.key())); - input_item->setData(0, Qt::UserRole, l.key()); - input_item->setFirstColumnSpanned(true); - item->addChild(input_item); - } - - // Create children if necessary - while (input_item->childCount() < table.Count()) { - input_item->addChild(new QTreeWidgetItem()); - } - - // Remove children if necessary - while (input_item->childCount() > table.Count()) { - delete input_item->takeChild(input_item->childCount() - 1); - } - - for (int j=0;jchild(j); - - // Set data type name - sub_item->setText(0, NodeValue::GetPrettyDataTypeName(value.type())); - - // Determine source - QString source_name; - if (value.source()) { - source_name = value.source()->GetLabelAndName(); - } else { - source_name = tr("(unknown)"); - } - sub_item->setText(1, source_name); - - switch (value.type()) { - case NodeValue::kVideoParams: - case NodeValue::kAudioParams: - // These types have no string representation - break; - case NodeValue::kTexture: - { - // NodeTraverser puts video params in here - for (int k=0;ksetItemWidget(sub_item, 2 + k, new QCheckBox()); - } - break; - } - default: - { - QVector split_values = value.to_split_value(); - for (int k=0;ksetText(2 + k, NodeValue::ValueToString(value.type(), split_values.at(k), true)); - } - } - } - } - } - } -} - -} diff --git a/app/widget/nodetreeview/CMakeLists.txt b/app/widget/nodetreeview/CMakeLists.txt index a8b5b6dce8..e24083c666 100644 --- a/app/widget/nodetreeview/CMakeLists.txt +++ b/app/widget/nodetreeview/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/nodetreeview/nodetreeview.h widget/nodetreeview/nodetreeview.cpp + widget/nodetreeview/nodetreeview.h PARENT_SCOPE ) diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index b298bab615..bc898fc7e9 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -22,6 +22,8 @@ #include +#include "common/qtutils.h" + namespace olive { NodeTreeView::NodeTreeView(QWidget *parent) : @@ -171,7 +173,7 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey QString item_name; if (ref.track() == -1 - || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1 + || ref.input().GetChannelCount() == 1 || (ref.input().IsArray() && ref.input().element() == -1)) { if (ref.input().element() == -1) { item_name = ref.input().name(); @@ -222,7 +224,7 @@ void NodeTreeView::CreateItemsForTracks(QTreeWidgetItem *parent, const NodeInput bool NodeTreeView::UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref) { - return ref.input().GetDataType() == NodeValue::kColor; + return ref.input().GetProperty(QStringLiteral("subtype")).toString() == QStringLiteral("color"); } void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column) diff --git a/app/widget/nodevaluetree/CMakeLists.txt b/app/widget/nodevaluetree/CMakeLists.txt index 44ed10d5b0..296f90af61 100644 --- a/app/widget/nodevaluetree/CMakeLists.txt +++ b/app/widget/nodevaluetree/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/nodevaluetree/nodevaluetree.cpp widget/nodevaluetree/nodevaluetree.h + widget/nodevaluetree/valueswizzlewidget.cpp + widget/nodevaluetree/valueswizzlewidget.h PARENT_SCOPE ) diff --git a/app/widget/nodevaluetree/nodevaluetree.cpp b/app/widget/nodevaluetree/nodevaluetree.cpp index d980d57a1b..774dd3319b 100644 --- a/app/widget/nodevaluetree/nodevaluetree.cpp +++ b/app/widget/nodevaluetree/nodevaluetree.cpp @@ -1,8 +1,10 @@ #include "nodevaluetree.h" #include +#include -#include "node/traverser.h" +#include "core.h" +#include "node/nodeundo.h" namespace olive { @@ -12,7 +14,7 @@ NodeValueTree::NodeValueTree(QWidget *parent) : super(parent) { setColumnWidth(0, 0); - setColumnCount(4); + setColumnCount(2); QSizePolicy p = sizePolicy(); p.setHorizontalStretch(1); @@ -24,37 +26,69 @@ NodeValueTree::NodeValueTree(QWidget *parent) : Retranslate(); } -void NodeValueTree::SetNode(const NodeInput &input, const rational &time) +void NodeValueTree::SetNode(const NodeInput &input) { + if (input_ == input) { + return; + } + + if (Node *n = input_.node()) { + disconnect(n, &Node::InputValueHintChanged, this, &NodeValueTree::ValueHintChanged); + } + clear(); - NodeTraverser traverser; + input_ = input; + + NodeOutput output = input.GetConnectedOutput2(); + if (output.IsValid()) { + Node *connected_node = output.node(); + connected_node->Retranslate(); + + connect(input_.node(), &Node::InputValueHintChanged, this, &NodeValueTree::ValueHintChanged); + + const QVector &outputs = connected_node->outputs(); + + Node::ValueHint vh = input.node()->GetValueHintForInput(input.input(), input.element()); + + for (const Node::Output &o : outputs) { + // Add default output + QTreeWidgetItem *item = new QTreeWidgetItem(this); - Node *connected_node = input.GetConnectedOutput(); + item->setText(1, o.name); - NodeValueTable table = traverser.GenerateTable(connected_node, TimeRange(time, time)); + QRadioButton *radio = new QRadioButton(this); + radio->setProperty("input", QVariant::fromValue(input)); + radio->setProperty("output", o.id); + connect(radio, &QRadioButton::clicked, this, &NodeValueTree::RadioButtonChecked); - int index = traverser.GenerateRowValueElementIndex(input.node(), input.input(), input.element(), &table); + if (output.output() == o.id) { + radio->setChecked(true); + } - for (int i=0; isetFlags(Qt::NoItemFlags); - QRadioButton *radio = new QRadioButton(this); - radio->setProperty("input", QVariant::fromValue(input)); - radio->setProperty("hint", QVariant::fromValue(hint)); - if (i == index) { - radio->setChecked(true); + ValueSwizzleWidget *b = new ValueSwizzleWidget(); + b->set(ValueParams(), input_); + //connect(b, &ValueSwizzleWidget::value_changed, this, &NodeValueTree::SwizzleChanged); + this->setItemWidget(swizzler_items, 1, b); } - connect(radio, &QRadioButton::clicked, this, &NodeValueTree::RadioButtonChecked); + } +} - setItemWidget(item, 0, radio); - item->setText(1, NodeValue::GetPrettyDataTypeName(value.type())); - item->setText(2, NodeValue::ValueToString(value, false)); - item->setText(3, value.source()->GetLabelAndName()); +bool NodeValueTree::DeleteSelected() +{ + for (int i = 0; i < topLevelItemCount(); i++) { + ValueSwizzleWidget *w = GetSwizzleWidgetFromTopLevelItem(i); + if (w->DeleteSelected()) { + return true; + } } + + return false; } void NodeValueTree::changeEvent(QEvent *event) @@ -68,17 +102,61 @@ void NodeValueTree::changeEvent(QEvent *event) void NodeValueTree::Retranslate() { - setHeaderLabels({QString(), tr("Type"), tr("Value"), tr("Source")}); + setHeaderLabels({QString(), tr("Output")}); +} + +ValueSwizzleWidget *NodeValueTree::GetSwizzleWidgetFromTopLevelItem(int i) +{ + return static_cast(itemWidget(topLevelItem(i)->child(0), 1)); } void NodeValueTree::RadioButtonChecked(bool e) { if (e) { QRadioButton *btn = static_cast(sender()); - Node::ValueHint hint = btn->property("hint").value(); + QString tag = btn->property("output").toString(); NodeInput input = btn->property("input").value(); - input.node()->SetValueHintForInput(input.input(), hint, input.element()); + NodeOutput old_output = input.GetConnectedOutput2(); + MultiUndoCommand *command = new MultiUndoCommand(); + command->add_child(new NodeEdgeRemoveCommand(old_output, input)); + + old_output.set_output(tag); + command->add_child(new NodeEdgeAddCommand(old_output, input)); + + Core::instance()->undo_stack()->push(command, tr("Switched Connected Output Parameter")); + } +} + +void NodeValueTree::Update() +{ + SetNode(input_); +} + +void NodeValueTree::SwizzleChanged(const SwizzleMap &map) +{ + if (input_.IsValid()) { + Node::ValueHint hint = input_.node()->GetValueHintForInput(input_.input(), input_.element()); + hint.set_swizzle(map); + Core::instance()->undo_stack()->push(new NodeSetValueHintCommand(input_, hint), tr("Edited Channel Swizzle For Input")); + } +} + +void NodeValueTree::ValueHintChanged(const NodeInput &input) +{ + if (input == input_) { + Node::ValueHint vh = input.node()->GetValueHintForInput(input.input(), input.element()); + + NodeOutput output = input.GetConnectedOutput2(); + + for (int i = 0; i < this->topLevelItemCount(); i++) { + QTreeWidgetItem *item = this->topLevelItem(i); + QRadioButton *rb = static_cast(this->itemWidget(item, 0)); + + if (rb->property("output").toString() == output.output()) { + rb->setChecked(true); + } + } } } diff --git a/app/widget/nodevaluetree/nodevaluetree.h b/app/widget/nodevaluetree/nodevaluetree.h index 94f71ebf1f..11e6bb65f2 100644 --- a/app/widget/nodevaluetree/nodevaluetree.h +++ b/app/widget/nodevaluetree/nodevaluetree.h @@ -5,6 +5,7 @@ #include #include "node/node.h" +#include "valueswizzlewidget.h" namespace olive { @@ -14,7 +15,9 @@ class NodeValueTree : public QTreeWidget public: NodeValueTree(QWidget *parent = nullptr); - void SetNode(const NodeInput &input, const rational &time); + void SetNode(const NodeInput &input); + + bool DeleteSelected(); protected: virtual void changeEvent(QEvent *event) override; @@ -22,9 +25,19 @@ class NodeValueTree : public QTreeWidget private: void Retranslate(); + ValueSwizzleWidget *GetSwizzleWidgetFromTopLevelItem(int i); + + NodeInput input_; + private slots: void RadioButtonChecked(bool e); + void Update(); + + void SwizzleChanged(const SwizzleMap &map); + + void ValueHintChanged(const NodeInput &input); + }; } diff --git a/app/widget/nodevaluetree/valueswizzlewidget.cpp b/app/widget/nodevaluetree/valueswizzlewidget.cpp new file mode 100644 index 0000000000..74949c3a59 --- /dev/null +++ b/app/widget/nodevaluetree/valueswizzlewidget.cpp @@ -0,0 +1,548 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "valueswizzlewidget.h" + +#include +#include +#include + +#include "config/config.h" +#include "core.h" +#include "node/node.h" +#include "node/nodeundo.h" +#include "widget/menu/menu.h" + +namespace olive { + +#define super QGraphicsView + +ValueSwizzleWidget::ValueSwizzleWidget(QWidget *parent) : + super{parent} +{ + setFrameShape(QFrame::NoFrame); + setFrameShadow(QFrame::Plain); + setRenderHint(QPainter::Antialiasing); + setBackgroundRole(QPalette::Base); + setAlignment(Qt::AlignLeft | Qt::AlignTop); + setDragMode(RubberBandDrag); + + scene_ = new QGraphicsScene(this); + setScene(scene_); + + new_item_ = nullptr; + + channel_height_ = this->fontMetrics().height() * 3 / 2; + channel_width_ = channel_height_ * 2; + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +bool ValueSwizzleWidget::DeleteSelected() +{ + auto sel = scene_->selectedItems(); + if (sel.isEmpty()) { + return false; + } + + auto map = get_map_from_connectors(); + + for (auto s : sel) { + auto conn = static_cast(s); + map.remove(conn->to()); + } + + set_map(map); + modify_input(map); + + return true; +} + +void ValueSwizzleWidget::set(const ValueParams &g, const NodeInput &input) +{ + if (input_.IsValid()) { + disconnect(input_.node(), &Node::InputValueHintChanged, this, &ValueSwizzleWidget::hint_changed); + disconnect(input_.node(), &Node::InputConnected, this, &ValueSwizzleWidget::connection_changed); + disconnect(input_.node(), &Node::InputDisconnected, this, &ValueSwizzleWidget::connection_changed); + } + + input_ = input; + from_.clear(); + outputs_.clear(); + value_params_ = g; + + if (input_.IsValid()) { + Node *n = input_.node(); + + refresh_outputs(); + + set_map(n->GetValueHintForInput(input.input(), input.element()).swizzle()); + + connect(input_.node(), &Node::InputValueHintChanged, this, &ValueSwizzleWidget::hint_changed); + connect(input_.node(), &Node::InputConnected, this, &ValueSwizzleWidget::connection_changed); + connect(input_.node(), &Node::InputDisconnected, this, &ValueSwizzleWidget::connection_changed); + } +} + +void ValueSwizzleWidget::drawBackground(QPainter *gp, const QRectF &r) +{ + gp->drawPixmap(0, 0, pixmap_); +} + +void ValueSwizzleWidget::mousePressEvent(QMouseEvent *e) +{ + QPoint p = mapToScene(e->pos()).toPoint(); + + bool found = false; + + for (auto it = output_rects_.cbegin(); it != output_rects_.cend(); it++) { + const OutputRect &orr = *it; + if (orr.rect.contains(p)) { + const NodeOutput &o = orr.output; + + o.node()->Retranslate(); + + Menu m(this); + + m.setProperty("oldout", QVariant::fromValue(o)); + + for (auto jt = o.node()->outputs().cbegin(); jt != o.node()->outputs().cend(); jt++) { + m.AddActionWithData(jt->name, jt->id, o.output()); + } + + connect(&m, &Menu::triggered, this, &ValueSwizzleWidget::output_menu_selection); + + m.exec(QCursor::pos()); + + found = true; + break; + } + } + + if (!found) { + for (size_t i = 0; i < from_rects_.size(); i++) { + const std::vector &l = from_rects_.at(i); + for (size_t j = 0; j < l.size(); j++) { + if (l.at(j).contains(p)) { + drag_from_ = true; + drag_source_.from = SwizzleMap::From(i, j); + drag_start_ = get_connect_point_of_from(drag_source_.from); + new_item_connected_ = false; + + found = true; + break; + } + } + } + + if (!found) { + for (size_t j = 0; j < to_rects_.size(); j++) { + const QRect &r = to_rects_.at(j); + + if (r.contains(p)) { + drag_from_ = false; + drag_source_.to = j; + drag_start_ = get_connect_point_of_to(j); + new_item_connected_ = true; + + // "to"s can only have one connection, so if there's already a connection here, we'll remove it + for (auto it = connectors_.cbegin(); it != connectors_.cend(); it++) { + SwizzleConnectorItem *s = *it; + if (s->to() == drag_source_.to) { + // Grab this item + new_item_ = s; + + // Flip so we're dragging the connector from its old position + drag_from_ = true; + drag_source_.from = new_item_->from(); + drag_start_ = get_connect_point_of_from(drag_source_.from); + new_item_connected_ = true; + + // Remove from list because we'll delete this later + connectors_.erase(it); + break; + } + } + } + } + } + + if (found) { + if (!new_item_) { + new_item_ = new SwizzleConnectorItem(); + if (drag_from_) { + new_item_->set_from(drag_source_.from); + } else { + new_item_->set_to(drag_source_.to); + } + scene_->addItem(new_item_); + } + } else { + super::mousePressEvent(e); + } + } +} + +void ValueSwizzleWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (new_item_) { + QPoint p = mapToScene(e->pos()).toPoint(); + QPoint end_point; + + new_item_connected_ = false; + + if (drag_from_) { + // Only register with "to" rects + for (size_t j = 0; j < to_rects_.size(); j++) { + const QRect &r = to_rects_.at(j); + if (r.contains(p)) { + end_point = get_connect_point_of_to(j); + new_item_connected_ = true; + new_item_->set_to(j); + break; + } + } + } else { + // Only register with "from" rects + for (size_t i = 0; i < from_rects_.size(); i++) { + const std::vector &l = from_rects_.at(i); + for (size_t j = 0; j < l.size(); j++) { + const QRect &r = l.at(j); + if (r.contains(p)) { + end_point = get_connect_point_of_from(SwizzleMap::From(i, j)); + new_item_connected_ = true; + new_item_->set_from(SwizzleMap::From(i, j)); + break; + } + } + } + } + + if (!new_item_connected_) { + end_point = e->pos(); + } + new_item_->SetConnected(new_item_connected_); + + new_item_->SetPoints(drag_start_, end_point); + } else { + super::mouseMoveEvent(e); + } +} + +void ValueSwizzleWidget::mouseReleaseEvent(QMouseEvent *e) +{ + if (new_item_) { + SwizzleMap map = get_map_from_connectors(); + + if (new_item_connected_) { + map.insert(new_item_->to(), new_item_->from()); + } + + delete new_item_; + new_item_ = nullptr; + + set_map(map); + modify_input(map); + } else { + super::mouseReleaseEvent(e); + } +} + +void ValueSwizzleWidget::resizeEvent(QResizeEvent *e) +{ + this->setSceneRect(this->viewport()->rect()); + + refresh_pixmap(); + + super::resizeEvent(e); +} + +QRect ValueSwizzleWidget::draw_channel(QPainter *p, size_t i, int x, int y, const type_t &name) +{ + static const size_t kChannelColorCount = 4; + static const QColor kDefaultColor = QColor(16, 16, 16); + static const QColor kRGBAChannelColors[kChannelColorCount] = { + QColor(160, 32, 32), + QColor(32, 160, 32), + QColor(32, 32, 160), + QColor(64, 64, 64) + }; + + static const QColor kGrayChannelColors[kChannelColorCount] = { + QColor(104, 104, 104), + QColor(80, 80, 80), + QColor(56, 56, 56), + QColor(32, 32, 32), + }; + + QRect r(x, y, channel_width_, channel_height_); + + //const QColor *kChannelColors = type_ == TYPE_TEXTURE || type_ == TYPE_COLOR ? kRGBAChannelColors : kGrayChannelColors; + const QColor *kChannelColors = kGrayChannelColors; + + QColor main_col; + if (name == "r") { + main_col = kRGBAChannelColors[0]; + } else if (name == "g") { + main_col = kRGBAChannelColors[1]; + } else if (name == "b") { + main_col = kRGBAChannelColors[2]; + } else if (name == "a") { + main_col = kRGBAChannelColors[3]; + } else { + main_col = i < kChannelColorCount ? kChannelColors[i] : kDefaultColor; + } + + if (OLIVE_CONFIG("UseGradients").toBool()) { + QLinearGradient lg(r.topLeft(), r.bottomLeft()); + + lg.setColorAt(0.0, main_col.lighter()); + lg.setColorAt(1.0, main_col); + + p->setBrush(lg); + } else { + p->setBrush(main_col); + } + + p->setPen(Qt::black); + p->drawRect(r); + + p->setPen(Qt::white); + + QString t = (name == type_t()) ? QString::number(i) : name.toString(); + p->drawText(r, Qt::AlignCenter, t); + + return r; +} + +void ValueSwizzleWidget::set_map(const SwizzleMap &map) +{ + clear_all(); + + cached_map_ = map; + + if (cached_map_.empty()) { + // Connect everything directly for empty maps + size_t lim = std::min(from_count(), to_count()); + for (size_t i = 0; i < lim; i++) { + make_item(SwizzleMap::From(0, i), i); + } + } else { + for (auto it = cached_map_.cbegin(); it != cached_map_.cend(); it++) { + make_item(it->second, it->first); + } + } + + adjust_all(); +} + +size_t ValueSwizzleWidget::to_count() const +{ + return cached_map_.empty() ? from_count() : cached_map_.size(); +} + +QPoint ValueSwizzleWidget::get_connect_point_of_from(const SwizzleMap::From &from) +{ + if (from.output() < from_rects_.size()) { + const std::vector &l = from_rects_.at(from.output()); + if (from.element() < l.size()) { + const QRect &r = l.at(from.element()); + return QPoint(r.right(), r.center().y()); + } + } + + return QPoint(); +} + +QPoint ValueSwizzleWidget::get_connect_point_of_to(size_t index) +{ + if (index < to_rects_.size()) { + const QRect &r = to_rects_.at(index); + return QPoint(r.left(), r.center().y()); + } + + return QPoint(); +} + +void ValueSwizzleWidget::adjust_all() +{ + for (auto it = connectors_.cbegin(); it != connectors_.cend(); it++) { + SwizzleConnectorItem *s = *it; + s->SetPoints(get_connect_point_of_from(s->from()), get_connect_point_of_to(s->to())); + } +} + +void ValueSwizzleWidget::make_item(const SwizzleMap::From &from, size_t to) +{ + SwizzleConnectorItem *s = new SwizzleConnectorItem(); + s->set_from(from); + s->set_to(to); + s->SetConnected(true); + scene_->addItem(s); + connectors_.push_back(s); +} + +void ValueSwizzleWidget::clear_all() +{ + qDeleteAll(connectors_); + connectors_.clear(); +} + +SwizzleMap ValueSwizzleWidget::get_map_from_connectors() const +{ + SwizzleMap map; + for (auto it = connectors_.cbegin(); it != connectors_.cend(); it++) { + SwizzleConnectorItem *s = *it; + map.insert(s->to(), s->from()); + } + return map; +} + +void ValueSwizzleWidget::refresh_outputs() +{ + if (input_.IsValid()) { + Node *n = input_.node(); + + outputs_ = n->GetConnectedOutputs(input_); + + from_.resize(outputs_.size()); + for (size_t i = 0; i < from_.size(); i++) { + from_[i] = n->GetFakeConnectedValue(value_params_, outputs_[i], input_.input(), input_.element()); + } + + refresh_pixmap(); + } +} + +void ValueSwizzleWidget::refresh_pixmap() +{ + if (outputs_.empty()) { + return; + } + + QFontMetrics fm = fontMetrics(); + int to_height = channel_height_ * to_count(); + int from_height = 0; + + for (size_t i = 0; i < outputs_.size(); i++) { + from_height += fm.height() + channel_height_ * from_.at(i).size(); + } + + int required_height = std::max(from_height, to_height); + setFixedHeight(required_height); + + pixmap_ = QPixmap(viewport()->width(), required_height); + pixmap_.fill(Qt::transparent); + + QPainter p(&pixmap_); + + int from_y = 0; + + from_rects_.resize(outputs_.size()); + output_rects_.resize(outputs_.size()); + + for (size_t i = 0; i < outputs_.size(); i++) { + const NodeOutput &output = outputs_.at(i); + + output.node()->Retranslate(); + + QString lbl = QStringLiteral("%1 - %2 %3").arg(output.node()->GetLabelAndName(), output.node()->GetOutputName(output.output()), QChar(0x25BE)); + QRect lbl_rect(0, from_y, fm.horizontalAdvance(lbl), fm.height()); + + const value_t &from = from_.at(i); + + output_rects_[i] = {output, lbl_rect}; + + p.setPen(palette().text().color()); + p.drawText(lbl_rect, Qt::AlignLeft | Qt::AlignTop, lbl); + + from_y += fm.height(); + + from_rects_[i].resize(from.size()); + + for (size_t j = 0; j < from.size(); j++) { + QRect rct = draw_channel(&p, j, 0, from_y, from.at(j).id()); + from_rects_[i][j] = rct; + from_y += rct.height(); + } + } + + int to_y; + if (outputs_.size() == 1) { + to_y = fm.height(); + } else { + to_y = std::max(0, viewport()->height()/2 - (channel_height_ * int(to_count()))/2); + } + + to_rects_.resize(to_count()); + for (size_t j = 0; j < to_count(); j++) { + // FIXME: Get naming scheme for this... + QRect rct = draw_channel(&p, j, width() - channel_width_ - 1, to_y, type_t()); + to_rects_[j] = rct; + to_y += rct.height(); + } + + adjust_all(); + + viewport()->update(); +} + +void ValueSwizzleWidget::hint_changed(const NodeInput &input) +{ + if (input_ == input) { + Node::ValueHint vh = input.node()->GetValueHintForInput(input.input(), input.element()); + set_map(vh.swizzle()); + } +} + +void ValueSwizzleWidget::connection_changed(const NodeOutput &output, const NodeInput &input) +{ + if (input_ == input) { + refresh_outputs(); + } +} + +void ValueSwizzleWidget::output_menu_selection(QAction *action) +{ + NodeOutput old_out = static_cast(sender())->property("oldout").value(); + NodeOutput new_out(old_out.node(), action->data().toString()); + + MultiUndoCommand *c = new MultiUndoCommand(); + + c->add_child(new NodeEdgeRemoveCommand(old_out, input_)); + c->add_child(new NodeEdgeAddCommand(new_out, input_, old_out.node()->GetInputConnectionIndex(old_out, input_))); + + Core::instance()->undo_stack()->push(c, tr("Switched connection output from %1 to %2").arg(old_out.GetName(), new_out.GetName())); +} + +void ValueSwizzleWidget::modify_input(const SwizzleMap &map) +{ + Node::ValueHint hint = input_.node()->GetValueHintForInput(input_.input(), input_.element()); + hint.set_swizzle(map); + Core::instance()->undo_stack()->push(new NodeSetValueHintCommand(input_, hint), tr("Edited Channel Swizzle For Input")); +} + +SwizzleConnectorItem::SwizzleConnectorItem(QGraphicsItem *parent) : + CurvedConnectorItem(parent) +{ +} + +} diff --git a/app/widget/nodevaluetree/valueswizzlewidget.h b/app/widget/nodevaluetree/valueswizzlewidget.h new file mode 100644 index 0000000000..17b8a58317 --- /dev/null +++ b/app/widget/nodevaluetree/valueswizzlewidget.h @@ -0,0 +1,137 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 VALUESWIZZLEWIDGET_H +#define VALUESWIZZLEWIDGET_H + +#include +#include +#include +#include + +#include "node/globals.h" +#include "node/swizzlemap.h" +#include "widget/nodeview/curvedconnectoritem.h" + +namespace olive { + +class SwizzleConnectorItem : public CurvedConnectorItem +{ +public: + SwizzleConnectorItem(QGraphicsItem *parent = nullptr); + + const SwizzleMap::From &from() const { return from_; } + size_t to() const { return to_index_; } + void set_from(const SwizzleMap::From &f) { from_ = f; } + void set_to(size_t t) { to_index_ = t; } + +private: + SwizzleMap::From from_; + size_t to_index_; + +}; + +class ValueSwizzleWidget : public QGraphicsView +{ + Q_OBJECT +public: + explicit ValueSwizzleWidget(QWidget *parent = nullptr); + + bool DeleteSelected(); + + void set(const ValueParams &g, const NodeInput &input); + +protected: + virtual void drawBackground(QPainter *p, const QRectF &r) override; + + virtual void mousePressEvent(QMouseEvent *e) override; + virtual void mouseMoveEvent(QMouseEvent *e) override; + virtual void mouseReleaseEvent(QMouseEvent *e) override; + + virtual void resizeEvent(QResizeEvent *e) override; + +private: + QRect draw_channel(QPainter *p, size_t i, int x, int y, const type_t &name); + + void set_map(const SwizzleMap &map); + + size_t from_count() const { return from_.empty() ? 0 : from_.at(0).size(); } + size_t to_count() const; + + QPoint get_connect_point_of_from(const SwizzleMap::From &from); + QPoint get_connect_point_of_to(size_t index); + + void adjust_all(); + void make_item(const SwizzleMap::From &from, size_t to); + void clear_all(); + SwizzleMap get_map_from_connectors() const; + + void refresh_outputs(); + void refresh_pixmap(); + + QGraphicsScene *scene_; + int channel_width_; + int channel_height_; + bool drag_from_; + union DragSource + { + SwizzleMap::From from; + size_t to; + }; + DragSource drag_source_; + bool new_item_connected_; + + NodeInput input_; + std::vector outputs_; + std::vector from_; + std::vector< std::vector< QRect > > from_rects_; + std::vector to_rects_; + ValueParams value_params_; + + struct OutputRect + { + NodeOutput output; + QRect rect; + }; + + std::vector output_rects_; + + SwizzleMap cached_map_; + SwizzleConnectorItem *new_item_; + QPoint drag_start_; + + std::vector connectors_; + + QPixmap pixmap_; + +private slots: + void hint_changed(const NodeInput &input); + + void connection_changed(const NodeOutput &output, const NodeInput &input); + + void output_menu_selection(QAction *action); + + void modify_input(const SwizzleMap &map); + +}; + +} + +#endif // VALUESWIZZLEWIDGET_H diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 184ef1190c..60d6738ba8 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/nodeview/curvedconnectoritem.cpp + widget/nodeview/curvedconnectoritem.h widget/nodeview/nodeview.cpp widget/nodeview/nodeview.h widget/nodeview/nodeviewcommon.h diff --git a/app/widget/nodeview/curvedconnectoritem.cpp b/app/widget/nodeview/curvedconnectoritem.cpp new file mode 100644 index 0000000000..af8557f86b --- /dev/null +++ b/app/widget/nodeview/curvedconnectoritem.cpp @@ -0,0 +1,194 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "curvedconnectoritem.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "common/lerp.h" +#include "util/bezier.h" + +namespace olive { + +CurvedConnectorItem::CurvedConnectorItem(QGraphicsItem* parent) : + QGraphicsPathItem(parent) +{ + connected_ = false; + highlighted_ = false; + curved_ = true; + + setFlag(QGraphicsItem::ItemIsSelectable); + + // Ensures this UI object is drawn behind other objects + setZValue(-1); + + // Use font metrics to set edge width for basic high DPI support + edge_width_ = QFontMetrics(QFont()).height() / 12; +} + +void CurvedConnectorItem::SetConnected(bool c) +{ + connected_ = c; + + update(); +} + +void CurvedConnectorItem::SetHighlighted(bool e) +{ + highlighted_ = e; + + update(); +} + +void CurvedConnectorItem::SetPoints(const QPointF &start, const QPointF &end) +{ + cached_start_ = start; + cached_end_ = end; + + UpdateCurve(); +} + +void CurvedConnectorItem::SetCurved(bool e) +{ + curved_ = e; + + UpdateCurve(); +} + +void CurvedConnectorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) +{ + QPalette::ColorGroup group; + QPalette::ColorRole role; + + if (connected_) { + group = QPalette::Active; + } else { + group = QPalette::Disabled; + } + + if (highlighted_ != bool(option->state & QStyle::State_Selected)) { + role = QPalette::Highlight; + } else { + role = QPalette::Text; + } + + // Draw main path + QColor edge_color = qApp->palette().color(group, role); + + painter->setPen(QPen(edge_color, edge_width_)); + painter->setBrush(Qt::NoBrush); + painter->drawPath(path()); +} + +NodeViewCommon::FlowDirection CurvedConnectorItem::GetFromDirection() const +{ + return NodeViewCommon::kLeftToRight; +} + +NodeViewCommon::FlowDirection CurvedConnectorItem::GetToDirection() const +{ + return NodeViewCommon::kLeftToRight; +} + +void CurvedConnectorItem::UpdateCurve() +{ + const QPointF &start = cached_start_; + const QPointF &end = cached_end_; + + QPainterPath path; + path.moveTo(start); + + double angle = std::atan2(end.y() - start.y(), end.x() - start.x()); + + if (curved_) { + + double half_x = lerp(start.x(), end.x(), 0.5); + double half_y = lerp(start.y(), end.y(), 0.5); + + QPointF cp1, cp2; + + NodeViewCommon::FlowDirection from_flow = GetFromDirection(); + NodeViewCommon::FlowDirection to_flow = GetToDirection(); + + if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) { + // This is a technically unsupported scenario, but to avoid issues, we'll use a fallback + from_flow = NodeViewCommon::kLeftToRight; + to_flow = NodeViewCommon::kLeftToRight; + } else if (from_flow == NodeViewCommon::kInvalidDirection) { + from_flow = to_flow; + } else if (to_flow == NodeViewCommon::kInvalidDirection) { + to_flow = from_flow; + } + + if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) { + cp1 = QPointF(half_x, start.y()); + } else { + cp1 = QPointF(start.x(), half_y); + } + + if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) { + cp2 = QPointF(half_x, end.y()); + } else { + cp2 = QPointF(end.x(), half_y); + } + + path.cubicTo(cp1, cp2, end); + + if (!qFuzzyCompare(start.x(), end.x())) { + double continue_x = end.x() - std::cos(angle); + + double x1 = start.x(); + double x2 = cp1.x(); + double x3 = cp2.x(); + double x4 = end.x(); + double y1 = start.y(); + double y2 = cp1.y(); + double y3 = cp2.y(); + double y4 = end.y(); + + if (start.x() >= end.x()) { + std::swap(x1, x4); + std::swap(x2, x3); + std::swap(y1, y4); + std::swap(y2, y3); + } + + double t = Bezier::CubicXtoT(continue_x, x1, x2, x3, x4); + double y = Bezier::CubicTtoY(y1, y2, y3, y4, t); + + angle = std::atan2(end.y() - y, end.x() - continue_x); + } + + } else { + + path.lineTo(end); + + } + + setPath(mapFromScene(path)); +} + +} diff --git a/app/widget/nodeview/curvedconnectoritem.h b/app/widget/nodeview/curvedconnectoritem.h new file mode 100644 index 0000000000..24f54608c7 --- /dev/null +++ b/app/widget/nodeview/curvedconnectoritem.h @@ -0,0 +1,93 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 CURVEDCONNECTORITEM_H +#define CURVEDCONNECTORITEM_H + +#include + +#include "nodeviewcommon.h" + +namespace olive { + +class CurvedConnectorItem : public QGraphicsPathItem +{ +public: + CurvedConnectorItem(QGraphicsItem* parent = nullptr); + + /** + * @brief Set the connected state of this line + * + * When the edge is not connected, it visually depicts this by coloring the line grey. When an edge is connected or + * a potential connection is valid, the line is colored white. This function sets whether the line should be grey + * (false) or white (true). + * + * Using SetEdge() automatically sets this to true. Under most circumstances this should be left alone, and only + * be set when an edge is being created/dragged. + */ + void SetConnected(bool c); + + bool IsConnected() const + { + return connected_; + } + + /** + * @brief Set points to create curve from + */ + void SetPoints(const QPointF& start, const QPointF& end); + + /** + * @brief Set highlighted state + * + * Changes color of edge. + */ + void SetHighlighted(bool e); + + /** + * @brief Set whether edges should be drawn as curved or as straight lines + */ + void SetCurved(bool e); + +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + + virtual NodeViewCommon::FlowDirection GetFromDirection() const; + virtual NodeViewCommon::FlowDirection GetToDirection() const; + +private: + void UpdateCurve(); + + bool connected_; + + bool highlighted_; + + bool curved_; + + int edge_width_; + + QPointF cached_start_; + QPointF cached_end_; + +}; + +} + +#endif // CURVEDCONNECTORITEM_H diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index da2c3199fd..437e8df538 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -34,7 +34,6 @@ #include "node/group/group.h" #include "node/nodeundo.h" #include "node/project/serializer/serializer.h" -#include "node/traverser.h" #include "ui/icons/icons.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedview.h" @@ -1015,7 +1014,7 @@ void NodeView::ProcessMovingAttachedNodes(const QPoint &pos) if (new_drop_edge) { drop_input_.Reset(); - NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); + type_t drop_edge_data_type = new_drop_edge->input().GetDataType(); // Determine best input to connect to our new node if (attached_node->GetEffectInput().IsValid()) { @@ -1134,7 +1133,7 @@ QVector NodeView::ProcessDroppingAttachedNodes(MultiUndoCommand *command, // Place new edges drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + drop_edge_command->add_child(new NodeEdgeAddCommand(GuessOutput(dropping_node, drop_edge_->input()), drop_edge_->input())); } drop_edge_ = nullptr; @@ -1632,32 +1631,20 @@ void NodeView::EndEdgeDrag(bool cancel) if (creating_input.IsValid()) { // Make connection if (!reconnected_to_itself) { - Node *creating_output = create_edge_output_item_->GetNode(); + Node *creating_output_node = create_edge_output_item_->GetNode(); - while (NodeGroup *output_group = dynamic_cast(creating_output)) { - creating_output = output_group->GetOutputPassthrough(); + while (NodeGroup *output_group = dynamic_cast(creating_output_node)) { + creating_output_node = output_group->GetOutputPassthrough(); } + NodeOutput creating_output = GuessOutput(creating_output_node, creating_input); + while (NodeGroup *input_group = dynamic_cast(creating_input.node())) { creating_input = input_group->GetInputFromID(creating_input.input()); } - if (creating_input.IsConnected()) { - Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; - - Node *already_connected_output = creating_input.GetConnectedOutput(); - NodeViewContext *ctx = GetContextItemFromNodeItem(create_edge_input_item_); - if (ctx && !ctx->GetItemFromMap(already_connected_output)) { - if (QMessageBox::warning(this, QString(), tr("Input \"%1\" is currently connected to node \"%2\", which is not visible in this context. " - "By connecting this, that connection will be removed. Do you wish to continue?").arg(creating_input.name(), already_connected_output->GetLabelAndName()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - cancel = true; - } - } - - if (!cancel) { - command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); - } + if (Node::ConnectionExists(creating_output, creating_input)) { + cancel = true; } if (!cancel) { @@ -1668,8 +1655,8 @@ void NodeView::EndEdgeDrag(bool cancel) // If the output is not in the input's context, add it now. We check the item rather than // the node itself, because sometimes a node may not be in the context but another node // representing it will be (e.g. groups) - if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) { - command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output_node)) { + command->add_child(new NodeSetPositionCommand(creating_output_node, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); } } } @@ -1737,6 +1724,43 @@ void NodeView::ResizeOverlay() overlay_view_->resize(this->size()); } +NodeOutput NodeView::GuessOutput(Node *output, const NodeInput &input) +{ + std::vector types = input.node()->GetAcceptableTypesForInput(input.input()); + QString out_id; + size_t confidence = SIZE_MAX; + + qDebug() << "guessing output for" << types; + + if (!types.empty()) { + for (auto it = output->outputs().constBegin(); it != output->outputs().constEnd(); it++) { + type_t output_type = it->type; + + qDebug() << " got type" << output_type; + + for (size_t i = 0; i < types.size(); i++) { + if (output_type == types.at(i)) { + size_t this_confidence = i; + + if (this_confidence < confidence) { + qDebug() << " is acceptable, confidence" << this_confidence; + + out_id = it->id; + confidence = this_confidence; + + if (confidence == 0) { + // LITERALLY CAN'T GET ANY MORE CONFIDENT! + break; + } + } + } + } + } + } + + return NodeOutput(output, out_id); +} + NodeViewContext *NodeView::GetContextItemFromNodeItem(NodeViewItem *item) { QGraphicsItem *i = item; diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index fd497f338b..27c44b40bf 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -192,6 +192,8 @@ public slots: void ResizeOverlay(); + static NodeOutput GuessOutput(Node *output, const NodeInput &input); + NodeViewMiniMap *minimap_; NodeViewContext *GetContextItemFromNodeItem(NodeViewItem *item); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 661c53bb89..715c48a280 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/qtutils.h" #include "core.h" #include "node/block/block.h" #include "node/group/group.h" @@ -30,8 +31,8 @@ NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item) : lbl_ = QCoreApplication::translate("NodeViewContext", "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), Track::Reference::TypeToTranslatedString(block->track()->type()), - QString::fromStdString(Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay())), - QString::fromStdString(Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay()))); + Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); } else { lbl_ = context_->GetLabelAndName(); } @@ -104,7 +105,7 @@ void NodeViewContext::RemoveChild(Node *node) // be changed...) QVector edges_to_remove = item->GetAllEdgesRecursively(); foreach (NodeViewEdge *edge, edges_to_remove) { - if (node == item->GetNode() || edge->output() == node || edge->input().node() == node) { + if (node == item->GetNode() || edge->output().node() == node || edge->input().node() == node) { ChildInputDisconnected(edge->output(), edge->input()); } } @@ -128,17 +129,17 @@ void NodeViewContext::RemoveChild(Node *node) UpdateRect(); } -void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) +void NodeViewContext::ChildInputConnected(const NodeOutput &output, const NodeInput &input) { // Add edge if (!input.IsHidden()) { - if (NodeViewItem* output_item = item_map_.value(output)) { + if (NodeViewItem* output_item = item_map_.value(output.node())) { AddEdgeInternal(output, input, output_item, item_map_.value(input.node())->GetItemForInput(input)); } } } -bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &input) +bool NodeViewContext::ChildInputDisconnected(const NodeOutput &output, const NodeInput &input) { // Remove edge for (int i=0; ioutput_connections().cbegin(); it!=node->output_connections().cend(); it++) { if (!it->second.IsHidden()) { if (NodeViewItem *other_item = item_map_.value(it->second.node())) { - AddEdgeInternal(node, it->second, item, other_item->GetItemForInput(it->second)); + AddEdgeInternal(it->first, it->second, item, other_item->GetItemForInput(it->second)); } } } for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (!it->first.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second)) { - AddEdgeInternal(it->second, it->first, other_item, item->GetItemForInput(it->first)); + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->first.node())) { + AddEdgeInternal(it->first, it->second, other_item, item->GetItemForInput(it->second)); } } } } -void NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +void NodeViewContext::AddEdgeInternal(const NodeOutput &output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) { if (from == to) { return; diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index f88f6869a3..81fd486336 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -52,9 +52,9 @@ public slots: void RemoveChild(Node *node); - void ChildInputConnected(Node *output, const NodeInput& input); + void ChildInputConnected(const NodeOutput &output, const NodeInput& input); - bool ChildInputDisconnected(Node *output, const NodeInput& input); + bool ChildInputDisconnected(const NodeOutput &output, const NodeInput& input); signals: void ItemAboutToBeDeleted(NodeViewItem *item); @@ -67,7 +67,7 @@ public slots: private: void AddNodeInternal(Node *node, NodeViewItem *item); - void AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + void AddEdgeInternal(const NodeOutput &output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); Node *context_; diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index fd182fb8fd..838ea163e4 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -22,42 +22,38 @@ #include #include -#include -#include -#include "common/lerp.h" #include "nodeview.h" #include "nodeviewitem.h" #include "nodeviewscene.h" namespace olive { -#define super QGraphicsPathItem +#define super CurvedConnectorItem -NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input, +NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : + super(parent), + from_item_(nullptr), + to_item_(nullptr) +{ +} + +NodeViewEdge::NodeViewEdge(const NodeOutput &output, const NodeInput &input, NodeViewItem* from_item, NodeViewItem* to_item, QGraphicsItem* parent) : - super(parent), - output_(output), - input_(input), - from_item_(from_item), - to_item_(to_item) + NodeViewEdge(parent) { - Init(); + output_ = output; + input_ = input; + from_item_ = from_item; + to_item_ = to_item; + SetConnected(true); from_item_->AddEdge(this); to_item_->AddEdge(this); } -NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : - QGraphicsPathItem(parent), - from_item_(nullptr), - to_item_(nullptr) -{ - Init(); -} - NodeViewEdge::~NodeViewEdge() { if (from_item_) { @@ -105,151 +101,14 @@ void NodeViewEdge::Adjust() SetPoints(from_item()->GetOutputPoint(), to_item()->GetInputPoint()); } -void NodeViewEdge::SetConnected(bool c) -{ - connected_ = c; - - update(); -} - -void NodeViewEdge::SetHighlighted(bool e) -{ - highlighted_ = e; - - update(); -} - -void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) -{ - cached_start_ = start; - cached_end_ = end; - - UpdateCurve(); -} - -void NodeViewEdge::SetCurved(bool e) +NodeViewCommon::FlowDirection NodeViewEdge::GetFromDirection() const { - curved_ = e; - - UpdateCurve(); + return from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; } -void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) +NodeViewCommon::FlowDirection NodeViewEdge::GetToDirection() const { - QPalette::ColorGroup group; - QPalette::ColorRole role; - - if (connected_) { - group = QPalette::Active; - } else { - group = QPalette::Disabled; - } - - if (highlighted_ != bool(option->state & QStyle::State_Selected)) { - role = QPalette::Highlight; - } else { - role = QPalette::Text; - } - - // Draw main path - QColor edge_color = qApp->palette().color(group, role); - - painter->setPen(QPen(edge_color, edge_width_)); - painter->setBrush(Qt::NoBrush); - painter->drawPath(path()); -} - -void NodeViewEdge::Init() -{ - connected_ = false; - highlighted_ = false; - curved_ = true; - - setFlag(QGraphicsItem::ItemIsSelectable); - - // Ensures this UI object is drawn behind other objects - setZValue(-1); - - // Use font metrics to set edge width for basic high DPI support - edge_width_ = QFontMetrics(QFont()).height() / 12; -} - -void NodeViewEdge::UpdateCurve() -{ - const QPointF &start = cached_start_; - const QPointF &end = cached_end_; - - QPainterPath path; - path.moveTo(start); - - double angle = std::atan2(end.y() - start.y(), end.x() - start.x()); - - if (curved_) { - - double half_x = lerp(start.x(), end.x(), 0.5); - double half_y = lerp(start.y(), end.y(), 0.5); - - QPointF cp1, cp2; - - NodeViewCommon::FlowDirection from_flow = from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; - NodeViewCommon::FlowDirection to_flow = to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; - - if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) { - // This is a technically unsupported scenario, but to avoid issues, we'll use a fallback - from_flow = NodeViewCommon::kLeftToRight; - to_flow = NodeViewCommon::kLeftToRight; - } else if (from_flow == NodeViewCommon::kInvalidDirection) { - from_flow = to_flow; - } else if (to_flow == NodeViewCommon::kInvalidDirection) { - to_flow = from_flow; - } - - if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) { - cp1 = QPointF(half_x, start.y()); - } else { - cp1 = QPointF(start.x(), half_y); - } - - if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) { - cp2 = QPointF(half_x, end.y()); - } else { - cp2 = QPointF(end.x(), half_y); - } - - path.cubicTo(cp1, cp2, end); - - if (!qFuzzyCompare(start.x(), end.x())) { - double continue_x = end.x() - std::cos(angle); - - double x1 = start.x(); - double x2 = cp1.x(); - double x3 = cp2.x(); - double x4 = end.x(); - double y1 = start.y(); - double y2 = cp1.y(); - double y3 = cp2.y(); - double y4 = end.y(); - - if (start.x() >= end.x()) { - std::swap(x1, x4); - std::swap(x2, x3); - std::swap(y1, y4); - std::swap(y2, y3); - } - - double t = Bezier::CubicXtoT(continue_x, x1, x2, x3, x4); - double y = Bezier::CubicTtoY(y1, y2, y3, y4, t); - - angle = std::atan2(end.y() - y, end.x() - continue_x); - } - - } else { - - path.lineTo(end); - - } - - setPath(mapFromScene(path)); + return to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; } } diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 6e17dea307..83008197a8 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -24,7 +24,7 @@ #include #include -#include "nodeviewcommon.h" +#include "curvedconnectoritem.h" #include "node/node.h" namespace olive { @@ -36,10 +36,10 @@ class NodeViewItem; * * A fairly simple line widget use to visualize a connection between two node parameters (a NodeEdge). */ -class NodeViewEdge : public QGraphicsPathItem +class NodeViewEdge : public CurvedConnectorItem { public: - NodeViewEdge(Node *output, const NodeInput& input, + NodeViewEdge(const NodeOutput &output, const NodeInput& input, NodeViewItem* from_item, NodeViewItem* to_item, QGraphicsItem* parent = nullptr); @@ -47,80 +47,23 @@ class NodeViewEdge : public QGraphicsPathItem virtual ~NodeViewEdge() override; - Node *output() const - { - return output_; - } - - const NodeInput& input() const - { - return input_; - } - - int element() const - { - return element_; - } - - NodeViewItem* from_item() const - { - return from_item_; - } - - NodeViewItem* to_item() const - { - return to_item_; - } + const NodeOutput &output() const { return output_; } + const NodeInput &input() const { return input_; } + int element() const { return element_; } + NodeViewItem* from_item() const { return from_item_; } + NodeViewItem* to_item() const { return to_item_; } void set_from_item(NodeViewItem *i); - void set_to_item(NodeViewItem *i); void Adjust(); - /** - * @brief Set the connected state of this line - * - * When the edge is not connected, it visually depicts this by coloring the line grey. When an edge is connected or - * a potential connection is valid, the line is colored white. This function sets whether the line should be grey - * (false) or white (true). - * - * Using SetEdge() automatically sets this to true. Under most circumstances this should be left alone, and only - * be set when an edge is being created/dragged. - */ - void SetConnected(bool c); - - bool IsConnected() const - { - return connected_; - } - - /** - * @brief Set highlighted state - * - * Changes color of edge. - */ - void SetHighlighted(bool e); - - /** - * @brief Set points to create curve from - */ - void SetPoints(const QPointF& start, const QPointF& end); - - /** - * @brief Set whether edges should be drawn as curved or as straight lines - */ - void SetCurved(bool e); - protected: - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + virtual NodeViewCommon::FlowDirection GetFromDirection() const override; + virtual NodeViewCommon::FlowDirection GetToDirection() const override; private: - void Init(); - - void UpdateCurve(); - - Node *output_; + NodeOutput output_; NodeInput input_; @@ -130,17 +73,6 @@ class NodeViewEdge : public QGraphicsPathItem NodeViewItem* to_item_; - int edge_width_; - - bool connected_; - - bool highlighted_; - - bool curved_; - - QPointF cached_start_; - QPointF cached_end_; - }; } diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index e11d5fe225..4d5e64a114 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -198,9 +198,9 @@ void PlaybackControls::SetEndTime(const rational &r) end_time_ = r; - end_tc_lbl_->setText(QString::fromStdString(Timecode::time_to_timecode(end_time_, - time_base_, - Core::instance()->GetTimecodeDisplay()))); + end_tc_lbl_->setText(Timecode::time_to_timecode(end_time_, + time_base_, + Core::instance()->GetTimecodeDisplay())); } void PlaybackControls::ShowPauseButton() diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 04c0d71c72..1efa7f945e 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -160,7 +160,7 @@ int ProjectExplorer::ConfirmItemDeletion(Node* item) msgbox.setIcon(QMessageBox::Warning); QStringList connected_nodes_names; - foreach (const Node::OutputConnection& connected, item->output_connections()) { + foreach (const Node::Connection& connected, item->output_connections()) { if (!dynamic_cast(connected.second.node())) { connected_nodes_names.append(GetHumanReadableNodeName(connected.second.node())); } @@ -190,7 +190,7 @@ bool ProjectExplorer::DeleteItemsInternal(const QVector& selected, bool& bool can_delete_item = true; if (check_if_item_is_in_use) { - foreach (const Node::OutputConnection& oc, node->output_connections()) { + foreach (const Node::Connection& oc, node->output_connections()) { Folder* folder_test = dynamic_cast(oc.second.node()); if (!folder_test) { // This sequence outputs to SOMETHING, confirm the user if they want to delete this diff --git a/app/widget/projectexplorer/projectviewmodel.cpp b/app/widget/projectexplorer/projectviewmodel.cpp index d97f5b0c12..890105ee8a 100644 --- a/app/widget/projectexplorer/projectviewmodel.cpp +++ b/app/widget/projectexplorer/projectviewmodel.cpp @@ -369,7 +369,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action if (item != drop_location && item->folder() != drop_location && (!dynamic_cast(item) || !ItemIsParentOfChild(static_cast(item), drop_location))) { - move_command->add_child(new NodeEdgeRemoveCommand(item, NodeInput(item->folder(), Folder::kChildInput, item->folder()->index_of_child_in_array(item)))); + move_command->add_child(new NodeEdgeRemoveCommand(NodeOutput(item), NodeInput(item->folder(), Folder::kChildInput, item->folder()->index_of_child_in_array(item)))); move_command->add_child(new FolderAddChild(drop_location, item)); count++; } diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index a1648f50b0..c0b997c6ca 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -25,6 +25,7 @@ #include #include +#include "common/qtutils.h" #include "ui/colorcoding.h" namespace olive { diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index da4136a5fe..633bf18c7e 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -70,9 +70,9 @@ void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) ShaderJob shader_job; - shader_job.Insert(QStringLiteral("viewport"), NodeValue(NodeValue::kVec2, QVector2D(width(), height()))); - shader_job.Insert(QStringLiteral("histogram_scale"), NodeValue(NodeValue::kFloat, histogram_scale)); - shader_job.Insert(QStringLiteral("histogram_power"), NodeValue(NodeValue::kFloat, histogram_power)); + shader_job.Insert(QStringLiteral("viewport"), QVector2D(width(), height())); + shader_job.Insert(QStringLiteral("histogram_scale"), histogram_scale); + shader_job.Insert(QStringLiteral("histogram_power"), histogram_power); if (!texture_row_sums_ || texture_row_sums_->width() != this->width() @@ -83,11 +83,11 @@ void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) } // Draw managed texture to a sums texture - shader_job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); + shader_job.Insert(QStringLiteral("ove_maintex"), managed_tex); renderer()->BlitToTexture(pipeline, shader_job, texture_row_sums_.get()); // Draw sums into a histogram - shader_job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_row_sums_))); + shader_job.Insert(QStringLiteral("ove_maintex"), texture_row_sums_); renderer()->Blit(pipeline_secondary_, shader_job, texture_row_sums_->params()); // Draw line overlays diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp index d758f0b42e..666096002b 100644 --- a/app/widget/scope/scopebase/scopebase.cpp +++ b/app/widget/scope/scopebase/scopebase.cpp @@ -50,7 +50,7 @@ void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline) { ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); + job.Insert(QStringLiteral("ove_maintex"), managed_tex); renderer()->Blit(pipeline, job, GetViewportParams()); } diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 2f8f464071..a08a944779 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -54,23 +54,19 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) ShaderJob job; // Set viewport size - job.Insert(QStringLiteral("viewport"), - NodeValue(NodeValue::kVec2, QVector2D(width(), height()))); + job.Insert(QStringLiteral("viewport"), QVector2D(width(), height())); // Set luma coefficients double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; color_manager()->GetDefaultLumaCoefs(luma_coeffs); - job.Insert(QStringLiteral("luma_coeffs"), - NodeValue(NodeValue::kVec3, QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]))); + job.Insert(QStringLiteral("luma_coeffs"), QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2])); // Scale of the waveform relative to the viewport surface. - job.Insert(QStringLiteral("waveform_scale"), - NodeValue(NodeValue::kFloat, waveform_scale)); + job.Insert(QStringLiteral("waveform_scale"), waveform_scale); // Insert source texture - job.Insert(QStringLiteral("ove_maintex"), - NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); + job.Insert(QStringLiteral("ove_maintex"), managed_tex); renderer()->Blit(pipeline, job, GetViewportParams()); diff --git a/app/widget/slider/base/numericsliderbase.cpp b/app/widget/slider/base/numericsliderbase.cpp index 92fd7fb097..f29b920d12 100644 --- a/app/widget/slider/base/numericsliderbase.cpp +++ b/app/widget/slider/base/numericsliderbase.cpp @@ -71,13 +71,13 @@ void NumericSliderBase::LadderDragged(int value, double multiplier) dragged_diff_ += value * multiplier; // Store current value to try and prevent any unnecessary signalling if the value doesn't change - QVariant pre_set_value = GetValueInternal(); + InternalType pre_set_value = GetValueInternal(); setting_drag_value_ = true; SetValueInternal(AdjustDragDistanceInternal(drag_start_value_, dragged_diff_)); setting_drag_value_ = false; - if (GetValueInternal() != pre_set_value) { + if (!Equals(GetValueInternal(), pre_set_value)) { // We retrieve the value instead of storing it ourselves because SetValueInternal may do extra // processing (such as clamping). drag_ladder_->SetValue(GetFormattedValueToString()); @@ -141,7 +141,7 @@ bool NumericSliderBase::UsingLadders() const return ladder_element_count_ > 0 && OLIVE_CONFIG("UseSliderLadders").toBool(); } -QVariant NumericSliderBase::AdjustValue(const QVariant &value) const +NumericSliderBase::InternalType NumericSliderBase::AdjustValue(const InternalType &value) const { // Clamps between min/max if (has_min_ && ValueLessThan(value, min_value_)) { @@ -153,19 +153,19 @@ QVariant NumericSliderBase::AdjustValue(const QVariant &value) const return value; } -void NumericSliderBase::SetOffset(const QVariant &v) +void NumericSliderBase::SetOffset(const InternalType &v) { offset_ = v; UpdateLabel(); } -QVariant NumericSliderBase::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const +NumericSliderBase::InternalType NumericSliderBase::AdjustDragDistanceInternal(const InternalType &start, const double &drag) const { - return start.toDouble() + drag; + return start.converted(TYPE_DOUBLE).value() + drag; } -void NumericSliderBase::SetMinimumInternal(const QVariant &v) +void NumericSliderBase::SetMinimumInternal(const InternalType &v) { min_value_ = v; has_min_ = true; @@ -176,7 +176,7 @@ void NumericSliderBase::SetMinimumInternal(const QVariant &v) } } -void NumericSliderBase::SetMaximumInternal(const QVariant &v) +void NumericSliderBase::SetMaximumInternal(const InternalType &v) { max_value_ = v; has_max_ = true; @@ -187,14 +187,14 @@ void NumericSliderBase::SetMaximumInternal(const QVariant &v) } } -bool NumericSliderBase::ValueGreaterThan(const QVariant &lhs, const QVariant &rhs) const +bool NumericSliderBase::ValueGreaterThan(const InternalType &lhs, const InternalType &rhs) const { - return lhs.toDouble() > rhs.toDouble(); + return lhs.value() > rhs.value(); } -bool NumericSliderBase::ValueLessThan(const QVariant &lhs, const QVariant &rhs) const +bool NumericSliderBase::ValueLessThan(const InternalType &lhs, const InternalType &rhs) const { - return lhs.toDouble() < rhs.toDouble(); + return lhs.value() < rhs.value(); } bool NumericSliderBase::CanSetValue() const diff --git a/app/widget/slider/base/numericsliderbase.h b/app/widget/slider/base/numericsliderbase.h index 71f2557d1b..9407453b22 100644 --- a/app/widget/slider/base/numericsliderbase.h +++ b/app/widget/slider/base/numericsliderbase.h @@ -38,32 +38,32 @@ class NumericSliderBase : public SliderBase void SetDragMultiplier(const double& d); - void SetOffset(const QVariant& v); + void SetOffset(const InternalType& v); bool IsDragging() const; protected: - const QVariant& GetOffset() const + const InternalType& GetOffset() const { return offset_; } - virtual QVariant AdjustDragDistanceInternal(const QVariant &start, const double &drag) const; + virtual InternalType AdjustDragDistanceInternal(const InternalType &start, const double &drag) const; - void SetMinimumInternal(const QVariant& v); + void SetMinimumInternal(const InternalType& v); - void SetMaximumInternal(const QVariant& v); + void SetMaximumInternal(const InternalType& v); - virtual bool ValueGreaterThan(const QVariant& lhs, const QVariant& rhs) const; + virtual bool ValueGreaterThan(const InternalType& lhs, const InternalType& rhs) const; - virtual bool ValueLessThan(const QVariant& lhs, const QVariant& rhs) const; + virtual bool ValueLessThan(const InternalType& lhs, const InternalType& rhs) const; virtual bool CanSetValue() const override; private: bool UsingLadders() const; - virtual QVariant AdjustValue(const QVariant& value) const override; + virtual InternalType AdjustValue(const InternalType& value) const override; SliderLadder* drag_ladder_; @@ -72,16 +72,16 @@ class NumericSliderBase : public SliderBase bool dragged_; bool has_min_; - QVariant min_value_; + InternalType min_value_; bool has_max_; - QVariant max_value_; + InternalType max_value_; double dragged_diff_; - QVariant drag_start_value_; + InternalType drag_start_value_; - QVariant offset_; + InternalType offset_; double drag_multiplier_; diff --git a/app/widget/slider/base/sliderbase.cpp b/app/widget/slider/base/sliderbase.cpp index ef58ce8ed2..49d41865a5 100644 --- a/app/widget/slider/base/sliderbase.cpp +++ b/app/widget/slider/base/sliderbase.cpp @@ -71,12 +71,12 @@ void SliderBase::SetTristate() UpdateLabel(); } -const QVariant &SliderBase::GetValueInternal() const +const SliderBase::InternalType &SliderBase::GetValueInternal() const { return value_; } -void SliderBase::SetValueInternal(const QVariant &v) +void SliderBase::SetValueInternal(const InternalType &v) { if (!CanSetValue()) { return; @@ -90,7 +90,7 @@ void SliderBase::SetValueInternal(const QVariant &v) UpdateLabel(); } -void SliderBase::SetDefaultValue(const QVariant &v) +void SliderBase::SetDefaultValue(const InternalType &v) { default_value_ = v; } @@ -103,10 +103,10 @@ void SliderBase::changeEvent(QEvent *e) super::changeEvent(e); } -bool SliderBase::GetLabelSubstitution(const QVariant &v, QString *out) const +bool SliderBase::GetLabelSubstitution(const InternalType &v, QString *out) const { for (auto it=label_substitutions_.constBegin(); it!=label_substitutions_.constEnd(); it++) { - if (it->first == v) { + if (Equals(it->first, v)) { *out = it->second; return true; } @@ -130,7 +130,7 @@ void SliderBase::UpdateLabel() label_->setText(s); } -QVariant SliderBase::AdjustValue(const QVariant &value) const +SliderBase::InternalType SliderBase::AdjustValue(const InternalType &value) const { return value; } @@ -140,7 +140,7 @@ bool SliderBase::CanSetValue() const return true; } -void SliderBase::ValueSignalEvent(const QVariant &value) +void SliderBase::ValueSignalEvent(const InternalType &value) { Q_UNUSED(value) } @@ -162,7 +162,7 @@ void SliderBase::ShowEditor() void SliderBase::LineEditConfirmed() { bool is_valid = true; - QVariant test_val = StringToValue(editor_->text(), &is_valid); + InternalType test_val = StringToValue(editor_->text(), &is_valid); // Ensure editor doesn't signal that the focus is lost editor_->blockSignals(true); @@ -241,7 +241,7 @@ QString SliderBase::GetFormattedValueToString() const return GetFormattedValueToString(GetValueInternal()); } -QString SliderBase::GetFormattedValueToString(const QVariant &v) const +QString SliderBase::GetFormattedValueToString(const InternalType &v) const { if (format_plural_) { return tr(GetFormat().toUtf8().constData(), nullptr, v.toInt()); diff --git a/app/widget/slider/base/sliderbase.h b/app/widget/slider/base/sliderbase.h index 9828bb8489..fcd7c22f42 100644 --- a/app/widget/slider/base/sliderbase.h +++ b/app/widget/slider/base/sliderbase.h @@ -23,6 +23,7 @@ #include +#include "node/value.h" #include "sliderlabel.h" #include "sliderladder.h" #include "widget/focusablelineedit/focusablelineedit.h" @@ -33,6 +34,8 @@ class SliderBase : public QStackedWidget { Q_OBJECT public: + using InternalType = value_t; //QVariant; + SliderBase(QWidget* parent = nullptr); void SetAlignment(Qt::Alignment alignment); @@ -45,11 +48,11 @@ class SliderBase : public QStackedWidget bool IsFormatPlural() const; - void SetDefaultValue(const QVariant& v); + void SetDefaultValue(const InternalType& v); - QString GetFormattedValueToString(const QVariant& v) const; + QString GetFormattedValueToString(const InternalType& v) const; - void InsertLabelSubstitution(const QVariant &value, const QString &label) + void InsertLabelSubstitution(const InternalType &value, const QString &label) { label_substitutions_.append({value, label}); UpdateLabel(); @@ -60,6 +63,8 @@ class SliderBase : public QStackedWidget label_->SetColor(c); } + const InternalType& GetValueInternal() const; + public slots: void ShowEditor(); @@ -67,9 +72,7 @@ protected slots: void UpdateLabel(); protected: - const QVariant& GetValueInternal() const; - - void SetValueInternal(const QVariant& v); + void SetValueInternal(const InternalType& v); QString GetFormat() const; @@ -77,27 +80,29 @@ protected slots: SliderLabel* label() { return label_; } - virtual QString ValueToString(const QVariant &v) const = 0; + virtual QString ValueToString(const InternalType &v) const = 0; - virtual QVariant StringToValue(const QString& s, bool* ok) const = 0; + virtual InternalType StringToValue(const QString& s, bool* ok) const = 0; - virtual QVariant AdjustValue(const QVariant& value) const; + virtual InternalType AdjustValue(const InternalType& value) const; virtual bool CanSetValue() const; - virtual void ValueSignalEvent(const QVariant& value) = 0; + virtual void ValueSignalEvent(const InternalType& value) = 0; virtual void changeEvent(QEvent* e) override; + virtual bool Equals(const InternalType &a, const InternalType &b) const = 0; + private: - bool GetLabelSubstitution(const QVariant &v, QString *out) const; + bool GetLabelSubstitution(const InternalType &v, QString *out) const; SliderLabel* label_; FocusableLineEdit* editor_; - QVariant value_; - QVariant default_value_; + InternalType value_; + InternalType default_value_; bool tristate_; @@ -105,7 +110,7 @@ protected slots: bool format_plural_; - QVector > label_substitutions_; + QVector > label_substitutions_; private slots: void LineEditConfirmed(); diff --git a/app/widget/slider/floatslider.cpp b/app/widget/slider/floatslider.cpp index b03995170b..d7640e41f3 100644 --- a/app/widget/slider/floatslider.cpp +++ b/app/widget/slider/floatslider.cpp @@ -38,7 +38,7 @@ FloatSlider::FloatSlider(QWidget *parent) : double FloatSlider::GetValue() const { - return GetValueInternal().toDouble(); + return GetValueInternal().value(); } void FloatSlider::SetValue(const double &d) @@ -120,12 +120,15 @@ QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, return FloatToString(TransformValueToDisplay(val, display), decimal_places, autotrim_decimal_places); } -QString FloatSlider::ValueToString(const QVariant &v) const +QString FloatSlider::ValueToString(const InternalType &v) const { - return ValueToString(v.toDouble() + GetOffset().toDouble(), display_type_, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); + double d = v.converted(TYPE_DOUBLE).value(); + double o = GetOffset().value(); + + return ValueToString(d + o, display_type_, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); } -QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const +FloatSlider::InternalType FloatSlider::StringToValue(const QString &s, bool *ok) const { bool valid; double val = s.toDouble(&valid); @@ -141,10 +144,10 @@ QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const } // Return un-offset value - return val - GetOffset().toDouble(); + return val - GetOffset().value(); } -QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const +FloatSlider::InternalType FloatSlider::AdjustDragDistanceInternal(const InternalType &start, const double &drag) const { switch (display_type_) { case kNormal: @@ -152,7 +155,7 @@ QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const do break; case kDecibel: { - double current_db = Decibel::fromLinear(start.toDouble()); + double current_db = Decibel::fromLinear(start.value()); current_db += drag; double adjusted_linear = Decibel::toLinear(current_db); @@ -165,9 +168,14 @@ QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const do return super::AdjustDragDistanceInternal(start, drag); } -void FloatSlider::ValueSignalEvent(const QVariant &value) +void FloatSlider::ValueSignalEvent(const InternalType &value) +{ + emit ValueChanged(value.value()); +} + +bool FloatSlider::Equals(const InternalType &a, const InternalType &b) const { - emit ValueChanged(value.toDouble()); + return qFuzzyCompare(a.value(), b.value()); } } diff --git a/app/widget/slider/floatslider.h b/app/widget/slider/floatslider.h index eee55db410..94bef4b8b3 100644 --- a/app/widget/slider/floatslider.h +++ b/app/widget/slider/floatslider.h @@ -56,13 +56,15 @@ class FloatSlider : public DecimalSliderBase static QString ValueToString(double val, DisplayType display, int decimal_places, bool autotrim_decimal_places); protected: - virtual QString ValueToString(const QVariant& v) const override; + virtual QString ValueToString(const InternalType& v) const override; - virtual QVariant StringToValue(const QString& s, bool* ok) const override; + virtual InternalType StringToValue(const QString& s, bool* ok) const override; - virtual QVariant AdjustDragDistanceInternal(const QVariant &start, const double &drag) const override; + virtual InternalType AdjustDragDistanceInternal(const InternalType &start, const double &drag) const override; - virtual void ValueSignalEvent(const QVariant &value) override; + virtual void ValueSignalEvent(const InternalType &value) override; + + virtual bool Equals(const InternalType &a, const InternalType &b) const override; signals: void ValueChanged(double); diff --git a/app/widget/slider/integerslider.cpp b/app/widget/slider/integerslider.cpp index 1ce992b905..cc62d76ff4 100644 --- a/app/widget/slider/integerslider.cpp +++ b/app/widget/slider/integerslider.cpp @@ -32,35 +32,35 @@ IntegerSlider::IntegerSlider(QWidget* parent) : int64_t IntegerSlider::GetValue() { - return GetValueInternal().toLongLong(); + return GetValueInternal().value(); } void IntegerSlider::SetValue(const int64_t &v) { - SetValueInternal(QVariant::fromValue(v)); + SetValueInternal(v); } void IntegerSlider::SetMinimum(const int64_t &d) { - SetMinimumInternal(QVariant::fromValue(d)); + SetMinimumInternal(d); } void IntegerSlider::SetMaximum(const int64_t &d) { - SetMaximumInternal(QVariant::fromValue(d)); + SetMaximumInternal(d); } void IntegerSlider::SetDefaultValue(const int64_t &d) { - super::SetDefaultValue(QVariant::fromValue(d)); + super::SetDefaultValue(d); } -QString IntegerSlider::ValueToString(const QVariant &v) const +QString IntegerSlider::ValueToString(const InternalType &v) const { - return QString::number(v.toLongLong() + GetOffset().toLongLong()); + return QString::number(v.value() + GetOffset().value()); } -QVariant IntegerSlider::StringToValue(const QString &s, bool *ok) const +IntegerSlider::InternalType IntegerSlider::StringToValue(const QString &s, bool *ok) const { bool valid; @@ -71,24 +71,39 @@ QVariant IntegerSlider::StringToValue(const QString &s, bool *ok) const *ok = valid; } - decimal_val -= GetOffset().toLongLong(); + decimal_val -= GetOffset().value(); if (valid) { // But for an integer, we round it return qRound(decimal_val); } - return QVariant(); + return InternalType(); } -void IntegerSlider::ValueSignalEvent(const QVariant &value) +void IntegerSlider::ValueSignalEvent(const InternalType &value) { - emit ValueChanged(value.toInt()); + emit ValueChanged(value.value()); } -QVariant IntegerSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const +IntegerSlider::InternalType IntegerSlider::AdjustDragDistanceInternal(const InternalType &start, const double &drag) const { - return qRound64(super::AdjustDragDistanceInternal(start, drag).toDouble()); + return int64_t(super::AdjustDragDistanceInternal(start, drag).value()); +} + +bool IntegerSlider::Equals(const InternalType &a, const InternalType &b) const +{ + return a.value() == b.value(); +} + +bool IntegerSlider::ValueGreaterThan(const InternalType &lhs, const InternalType &rhs) const +{ + return lhs.value() > rhs.value(); +} + +bool IntegerSlider::ValueLessThan(const InternalType &lhs, const InternalType &rhs) const +{ + return lhs.value() < rhs.value(); } } diff --git a/app/widget/slider/integerslider.h b/app/widget/slider/integerslider.h index 452f41a68a..ddea3b4e0e 100644 --- a/app/widget/slider/integerslider.h +++ b/app/widget/slider/integerslider.h @@ -42,13 +42,19 @@ class IntegerSlider : public NumericSliderBase void SetDefaultValue(const int64_t& d); protected: - virtual QString ValueToString(const QVariant& v) const override; + virtual QString ValueToString(const InternalType& v) const override; - virtual QVariant StringToValue(const QString& s, bool* ok) const override; + virtual InternalType StringToValue(const QString& s, bool* ok) const override; - virtual void ValueSignalEvent(const QVariant &value) override; + virtual void ValueSignalEvent(const InternalType &value) override; - virtual QVariant AdjustDragDistanceInternal(const QVariant &start, const double &drag) const override; + virtual InternalType AdjustDragDistanceInternal(const InternalType &start, const double &drag) const override; + + virtual bool Equals(const InternalType &a, const InternalType &b) const override; + + virtual bool ValueGreaterThan(const InternalType& lhs, const InternalType& rhs) const override; + + virtual bool ValueLessThan(const InternalType& lhs, const InternalType& rhs) const override; signals: void ValueChanged(int64_t); diff --git a/app/widget/slider/rationalslider.cpp b/app/widget/slider/rationalslider.cpp index bcad1f4932..08a5804bac 100644 --- a/app/widget/slider/rationalslider.cpp +++ b/app/widget/slider/rationalslider.cpp @@ -20,6 +20,7 @@ #include "rationalslider.h" +#include "common/qtutils.h" #include "core.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" @@ -47,22 +48,22 @@ rational RationalSlider::GetValue() void RationalSlider::SetValue(const rational &d) { - SetValueInternal(QVariant::fromValue(d)); + SetValueInternal(d); } void RationalSlider::SetDefaultValue(const rational &r) { - super::SetDefaultValue(QVariant::fromValue(r)); + super::SetDefaultValue(r); } void RationalSlider::SetMinimum(const rational &d) { - SetMinimumInternal(QVariant::fromValue(d)); + SetMinimumInternal(d); } void RationalSlider::SetMaximum(const rational &d) { - SetMaximumInternal(QVariant::fromValue(d)); + SetMaximumInternal(d); } void RationalSlider::SetTimebase(const rational &timebase) @@ -95,7 +96,7 @@ void RationalSlider::DisableDisplayType(RationalSlider::DisplayType type) disabled_.append(type); } -QString RationalSlider::ValueToString(const QVariant &v) const +QString RationalSlider::ValueToString(const InternalType &v) const { rational r = v.value(); @@ -106,18 +107,17 @@ QString RationalSlider::ValueToString(const QVariant &v) const switch (display_type_) { case kTime: - return QString::fromStdString(Timecode::time_to_timecode(r, timebase_, Core::instance()->GetTimecodeDisplay())); + return Timecode::time_to_timecode(r, timebase_, Core::instance()->GetTimecodeDisplay()); case kFloat: return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); case kRational: - return QString::fromStdString(v.value().toString()); + default: + return v.value().toString(); } - - return v.toString(); } } -QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const +RationalSlider::InternalType RationalSlider::StringToValue(const QString &s, bool *ok) const { rational r; *ok = false; @@ -125,7 +125,7 @@ QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const switch (display_type_) { case kTime: { - r = Timecode::timecode_to_time(s.toStdString(), timebase_, Core::instance()->GetTimecodeDisplay(), ok); + r = Timecode::timecode_to_time(s, timebase_, Core::instance()->GetTimecodeDisplay(), ok); break; } case kFloat: @@ -141,35 +141,39 @@ QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const break; } case kRational: - r = rational::fromString(s.toStdString(), ok); + r = rational::fromString(s, ok); break; } - //return QVariant::fromValue(r - GetOffset().value()); - return QVariant::fromValue(r); + return r - GetOffset().value(); } -QVariant RationalSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const +RationalSlider::InternalType RationalSlider::AdjustDragDistanceInternal(const InternalType &start, const double &drag) const { // Assume we want smallest increment to be timebase or 1 frame - return QVariant::fromValue(start.value() + rational::fromDouble(drag)*timebase_); + return start.value() + rational::fromDouble(drag)*timebase_; } -void RationalSlider::ValueSignalEvent(const QVariant &v) +void RationalSlider::ValueSignalEvent(const InternalType &v) { emit ValueChanged(v.value()); } -bool RationalSlider::ValueGreaterThan(const QVariant &lhs, const QVariant &rhs) const +bool RationalSlider::ValueGreaterThan(const InternalType &lhs, const InternalType &rhs) const { return lhs.value() > rhs.value(); } -bool RationalSlider::ValueLessThan(const QVariant &lhs, const QVariant &rhs) const +bool RationalSlider::ValueLessThan(const InternalType &lhs, const InternalType &rhs) const { return lhs.value() < rhs.value(); } +bool RationalSlider::Equals(const InternalType &a, const InternalType &b) const +{ + return a.value() == b.value(); +} + void RationalSlider::ShowDisplayTypeMenu() { Menu m(this); diff --git a/app/widget/slider/rationalslider.h b/app/widget/slider/rationalslider.h index 4da25b96ac..f23cbaa4c2 100644 --- a/app/widget/slider/rationalslider.h +++ b/app/widget/slider/rationalslider.h @@ -21,15 +21,13 @@ #ifndef RATIONALSLIDER_H #define RATIONALSLIDER_H -#include #include #include "base/decimalsliderbase.h" +#include "util/rational.h" namespace olive { -using namespace core; - /** * @brief A olive::rational based slider * @@ -103,17 +101,19 @@ public slots: void SetValue(const rational& d); protected: - virtual QString ValueToString(const QVariant& v) const override; + virtual QString ValueToString(const InternalType& v) const override; + + virtual InternalType StringToValue(const QString& s, bool* ok) const override; - virtual QVariant StringToValue(const QString& s, bool* ok) const override; + virtual InternalType AdjustDragDistanceInternal(const InternalType &start, const double &drag) const override; - virtual QVariant AdjustDragDistanceInternal(const QVariant &start, const double &drag) const override; + virtual void ValueSignalEvent(const InternalType& v) override; - virtual void ValueSignalEvent(const QVariant& v) override; + virtual bool ValueGreaterThan(const InternalType& lhs, const InternalType& rhs) const override; - virtual bool ValueGreaterThan(const QVariant& lhs, const QVariant& rhs) const override; + virtual bool ValueLessThan(const InternalType& lhs, const InternalType& rhs) const override; - virtual bool ValueLessThan(const QVariant& lhs, const QVariant& rhs) const override; + virtual bool Equals(const InternalType &a, const InternalType &b) const override; signals: void ValueChanged(rational); diff --git a/app/widget/slider/stringslider.cpp b/app/widget/slider/stringslider.cpp index fcac6cf820..725d9641a3 100644 --- a/app/widget/slider/stringslider.cpp +++ b/app/widget/slider/stringslider.cpp @@ -34,7 +34,7 @@ StringSlider::StringSlider(QWidget* parent) : QString StringSlider::GetValue() const { - return GetValueInternal().toString(); + return GetValueInternal().value(); } void StringSlider::SetValue(const QString &v) @@ -47,21 +47,26 @@ void StringSlider::SetDefaultValue(const QString &v) super::SetDefaultValue(v); } -QString StringSlider::ValueToString(const QVariant &v) const +QString StringSlider::ValueToString(const InternalType &v) const { - QString vstr = v.toString(); + QString vstr = v.value(); return (vstr.isEmpty()) ? tr("(none)") : vstr; } -QVariant StringSlider::StringToValue(const QString &s, bool *ok) const +StringSlider::InternalType StringSlider::StringToValue(const QString &s, bool *ok) const { *ok = true; return s; } -void StringSlider::ValueSignalEvent(const QVariant &value) +void StringSlider::ValueSignalEvent(const InternalType &value) { - emit ValueChanged(value.toString()); + emit ValueChanged(value.value()); +} + +bool StringSlider::Equals(const InternalType &a, const InternalType &b) const +{ + return a.value() == b.value(); } } diff --git a/app/widget/slider/stringslider.h b/app/widget/slider/stringslider.h index 0e61c48f51..552e5e1d37 100644 --- a/app/widget/slider/stringslider.h +++ b/app/widget/slider/stringslider.h @@ -43,11 +43,13 @@ class StringSlider : public SliderBase void ValueChanged(const QString& str); protected: - virtual QString ValueToString(const QVariant& value) const override; + virtual QString ValueToString(const InternalType& value) const override; - virtual QVariant StringToValue(const QString &s, bool *ok) const override; + virtual InternalType StringToValue(const QString &s, bool *ok) const override; - virtual void ValueSignalEvent(const QVariant &value) override; + virtual void ValueSignalEvent(const InternalType &value) override; + + virtual bool Equals(const InternalType &a, const InternalType &b) const override; }; diff --git a/app/widget/standardcombos/channellayoutcombobox.h b/app/widget/standardcombos/channellayoutcombobox.h index 0cdf7a503a..e23c69aad0 100644 --- a/app/widget/standardcombos/channellayoutcombobox.h +++ b/app/widget/standardcombos/channellayoutcombobox.h @@ -21,15 +21,13 @@ #ifndef CHANNELLAYOUTCOMBOBOX_H #define CHANNELLAYOUTCOMBOBOX_H -#include #include +#include "render/audioparams.h" #include "ui/humanstrings.h" namespace olive { -using namespace core; - class ChannelLayoutComboBox : public QComboBox { Q_OBJECT @@ -37,21 +35,20 @@ class ChannelLayoutComboBox : public QComboBox ChannelLayoutComboBox(QWidget* parent = nullptr) : QComboBox(parent) { - foreach (const uint64_t& ch_layout, AudioParams::kSupportedChannelLayouts) { - this->addItem(HumanStrings::ChannelLayoutToString(ch_layout), - QVariant::fromValue(ch_layout)); + foreach (const AudioChannelLayout& ch_layout, AudioParams::kSupportedChannelLayouts) { + this->addItem(ch_layout.toHumanString(), QVariant::fromValue(ch_layout)); } } - uint64_t GetChannelLayout() const + AudioChannelLayout GetChannelLayout() const { - return this->currentData().toULongLong(); + return this->currentData().value(); } - void SetChannelLayout(uint64_t ch) + void SetChannelLayout(const AudioChannelLayout &ch) { for (int i=0; icount(); i++) { - if (this->itemData(i).toULongLong() == ch) { + if (this->itemData(i).value() == ch) { this->setCurrentIndex(i); break; } diff --git a/app/widget/standardcombos/frameratecombobox.h b/app/widget/standardcombos/frameratecombobox.h index c0d63d24df..080b5d2dda 100644 --- a/app/widget/standardcombos/frameratecombobox.h +++ b/app/widget/standardcombos/frameratecombobox.h @@ -27,6 +27,7 @@ #include #include +#include "common/qtutils.h" #include "render/videoparams.h" namespace olive { @@ -120,7 +121,7 @@ private slots: r = rational::fromDouble(d, &ok); } else { // Try converting to rational in case someone formatted that way - r = rational::fromString(s.toStdString(), &ok); + r = rational::fromString(s, &ok); } if (ok) { diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index 2c991bc340..595a707ebe 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -21,15 +21,12 @@ #ifndef SAMPLEFORMATCOMBOBOX_H #define SAMPLEFORMATCOMBOBOX_H -#include #include #include "ui/humanstrings.h" namespace olive { -using namespace core; - class SampleFormatComboBox : public QComboBox { Q_OBJECT diff --git a/app/widget/standardcombos/sampleratecombobox.h b/app/widget/standardcombos/sampleratecombobox.h index dabb86f388..e090700aaa 100644 --- a/app/widget/standardcombos/sampleratecombobox.h +++ b/app/widget/standardcombos/sampleratecombobox.h @@ -21,15 +21,13 @@ #ifndef SAMPLERATECOMBOBOX_H #define SAMPLERATECOMBOBOX_H -#include #include +#include "render/audioparams.h" #include "ui/humanstrings.h" namespace olive { -using namespace core; - class SampleRateComboBox : public QComboBox { Q_OBJECT diff --git a/app/widget/taskview/elapsedcounterwidget.cpp b/app/widget/taskview/elapsedcounterwidget.cpp index 4c7a4788ca..4f1b2efb62 100644 --- a/app/widget/taskview/elapsedcounterwidget.cpp +++ b/app/widget/taskview/elapsedcounterwidget.cpp @@ -20,14 +20,13 @@ #include "elapsedcounterwidget.h" -#include #include #include #include -namespace olive { +#include "util/timecodefunctions.h" -using namespace core; +namespace olive { ElapsedCounterWidget::ElapsedCounterWidget(QWidget* parent) : QWidget(parent), @@ -88,8 +87,8 @@ void ElapsedCounterWidget::UpdateTimers() remaining_ms = 0; } - elapsed_lbl_->setText(tr("Elapsed: %1").arg(QString::fromStdString(Timecode::time_to_string(elapsed_ms)))); - remaining_lbl_->setText(tr("Remaining: %1").arg(QString::fromStdString(Timecode::time_to_string(remaining_ms)))); + elapsed_lbl_->setText(tr("Elapsed: %1").arg(Timecode::time_to_string(elapsed_ms))); + remaining_lbl_->setText(tr("Remaining: %1").arg(Timecode::time_to_string(remaining_ms))); } } diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 7390fb1b46..b33cc3867e 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -309,9 +309,8 @@ class TimeBasedViewSelectionManager display_time = initial_drag_item_->time(); } - QString tip = QString::fromStdString(Timecode::time_to_timecode( - display_time, timebase_, - Core::instance()->GetTimecodeDisplay(), false)); + QString tip = Timecode::time_to_timecode(display_time, timebase_, + Core::instance()->GetTimecodeDisplay(), false); last_used_tip_format_ = tip_format; if (!tip_format.isEmpty()) { diff --git a/app/widget/timebased/timescaledobject.h b/app/widget/timebased/timescaledobject.h index ca4a597657..1c4f319b37 100644 --- a/app/widget/timebased/timescaledobject.h +++ b/app/widget/timebased/timescaledobject.h @@ -21,7 +21,6 @@ #ifndef TIMELINESCALEDOBJECT_H #define TIMELINESCALEDOBJECT_H -#include #include #include "node/block/block.h" diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 8d6101655c..118d07f90a 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -671,7 +671,7 @@ bool TimelineWidget::CopySelected(bool cut) } foreach (Block* block, selected_blocks_) { - properties[block][QStringLiteral("in")] = QString::fromStdString((block->in() - earliest_in).toString()); + properties[block][QStringLiteral("in")] = (block->in() - earliest_in).toString(); properties[block][QStringLiteral("track")] = block->track()->ToReference().ToString(); } @@ -1319,9 +1319,9 @@ void TimelineWidget::ShowContextMenu() Menu *thumbnail_menu = new Menu(tr("Show Thumbnails"), &menu); menu.addMenu(thumbnail_menu); - thumbnail_menu->AddActionWithData(tr("Disabled"), Timeline::kThumbnailOff, OLIVE_CONFIG("TimelineThumbnailMode")); - thumbnail_menu->AddActionWithData(tr("Only At In Points"), Timeline::kThumbnailInOut, OLIVE_CONFIG("TimelineThumbnailMode")); - thumbnail_menu->AddActionWithData(tr("Enabled"), Timeline::kThumbnailOn, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Disabled"), Timeline::kThumbnailOff, Timeline::ThumbnailMode(OLIVE_CONFIG("TimelineThumbnailMode").toInt())); + thumbnail_menu->AddActionWithData(tr("Only At In Points"), Timeline::kThumbnailInOut, Timeline::ThumbnailMode(OLIVE_CONFIG("TimelineThumbnailMode").toInt())); + thumbnail_menu->AddActionWithData(tr("Enabled"), Timeline::kThumbnailOn, Timeline::ThumbnailMode(OLIVE_CONFIG("TimelineThumbnailMode").toInt())); connect(thumbnail_menu, &Menu::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); } @@ -1390,7 +1390,7 @@ void TimelineWidget::SetViewWaveformsEnabled(bool e) void TimelineWidget::SetViewThumbnailsEnabled(QAction *action) { - OLIVE_CONFIG("TimelineThumbnailMode") = action->data(); + OLIVE_CONFIG("TimelineThumbnailMode") = action->data().toInt(); UpdateViewports(); } @@ -1542,11 +1542,11 @@ void TimelineWidget::MulticamEnabledTriggered(bool e) // connect to the multicam instead QVector inputs = c->FindWaysNodeArrivesHere(s); for (const NodeInput &i : inputs) { - command->add_child(new NodeEdgeRemoveCommand(s, i)); - command->add_child(new NodeEdgeAddCommand(n, i)); + command->add_child(new NodeEdgeRemoveCommand(NodeOutput(s), i)); + command->add_child(new NodeEdgeAddCommand(NodeOutput(n), i)); } - command->add_child(new NodeEdgeAddCommand(s, NodeInput(n, n->kSequenceInput))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(s), NodeInput(n, n->kSequenceInput))); // Move sequence node one unit back, and place multicam in sequence's spot QPointF sequence_pos = c->GetNodePositionInContext(s); @@ -1562,7 +1562,7 @@ void TimelineWidget::MulticamEnabledTriggered(bool e) if (MultiCamNode *mcn = dynamic_cast(i.node())) { for (auto it=mcn->output_connections().cbegin(); it!=mcn->output_connections().cend(); it++) { command->add_child(new NodeEdgeRemoveCommand(it->first, it->second)); - command->add_child(new NodeEdgeAddCommand(s, it->second)); + command->add_child(new NodeEdgeAddCommand(NodeOutput(s), it->second)); } command->add_child(new NodeRemoveAndDisconnectCommand(mcn)); @@ -1965,7 +1965,7 @@ bool TimelineWidget::PasteInternal(bool insert) for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { rational length = static_cast(it.key())->length(); - rational in = rational::fromString(it.value()[QStringLiteral("in")].toStdString()); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); paste_end = qMax(paste_end, paste_start + in + length); } @@ -1977,7 +1977,7 @@ bool TimelineWidget::PasteInternal(bool insert) for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { Block *block = static_cast(it.key()); - rational in = rational::fromString(it.value()[QStringLiteral("in")].toStdString()); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 0571ad8824..0c3eceecf0 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -25,6 +25,7 @@ #include "node/generator/shape/shapenode.h" #include "node/generator/solid/solid.h" #include "node/generator/text/textv3.h" +#include "node/generator/tone/tonegenerator.h" #include "node/nodeundo.h" #include "timeline/timelineundopointer.h" #include "widget/timelinewidget/timelinewidget.h" @@ -158,8 +159,10 @@ Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, case Tool::kAddableTitle: node_to_add = new TextGeneratorV3(); break; - case Tool::kAddableBars: case Tool::kAddableTone: + node_to_add = new ToneGenerator(); + break; + case Tool::kAddableBars: // Not implemented yet qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject(); break; @@ -174,7 +177,7 @@ Node *AddTool::CreateAddableClip(MultiUndoCommand *command, Sequence *sequence, if (node_to_add) { QPointF extra_node_offset(kDefaultDistanceFromOutput, 0); command->add_child(new NodeAddCommand(graph, node_to_add)); - command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(node_to_add), NodeInput(clip, ClipBlock::kBufferIn))); command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); if (!rect.isNull()) { diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 3e1b454fd7..17c6bd9cbc 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -149,9 +149,9 @@ void ImportTool::DragMove(TimelineViewMouseEvent *event) // Generate tooltip (showing earliest in point of imported clip) rational tooltip_timebase = parent()->GetTimebaseForTrackType(event->GetTrack().type()); - QString tooltip_text = QString::fromStdString(Timecode::time_to_timecode(earliest_ghost, - tooltip_timebase, - Core::instance()->GetTimecodeDisplay())); + QString tooltip_text = Timecode::time_to_timecode(earliest_ghost, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay()); // Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way // of the cursor) @@ -454,10 +454,8 @@ void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command) TransformDistortNode* transform = new TransformDistortNode(); command->add_child(new NodeAddCommand(dst_graph, transform)); - command->add_child(new NodeSetValueHintCommand(transform, TransformDistortNode::kTextureInput, -1, Node::ValueHint({NodeValue::kTexture}, footage_stream.output))); - - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); - command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(footage_stream.footage, footage_stream.output), NodeInput(transform, TransformDistortNode::kTextureInput))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(transform), NodeInput(clip, ClipBlock::kBufferIn))); command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); break; } @@ -466,10 +464,8 @@ void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command) VolumeNode* volume_node = new VolumeNode(); command->add_child(new NodeAddCommand(dst_graph, volume_node)); - command->add_child(new NodeSetValueHintCommand(volume_node, VolumeNode::kSamplesInput, -1, Node::ValueHint({NodeValue::kSamples}, footage_stream.output))); - - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); - command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(footage_stream.footage, footage_stream.output), NodeInput(volume_node, VolumeNode::kSamplesInput))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(volume_node), NodeInput(clip, ClipBlock::kBufferIn))); command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); break; } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 452e8dfe94..f2344dc223 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -594,10 +594,10 @@ void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - QString::fromStdString(Timecode::time_to_timecode(time_movement, - tooltip_timebase, - Core::instance()->GetTimecodeDisplay(), - true)), + Timecode::time_to_timecode(time_movement, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay(), + true), parent()); } @@ -736,12 +736,12 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) if (og_in_transition && relinks.contains(og_in_transition)) { TransitionBlock *cp_in_transition = static_cast(relinks.value(og_in_transition)); - command->add_child(new NodeEdgeAddCommand(cp_clip, NodeInput(cp_in_transition, TransitionBlock::kInBlockInput))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(cp_clip), NodeInput(cp_in_transition, TransitionBlock::kInBlockInput))); } if (og_out_transition && relinks.contains(og_out_transition)) { TransitionBlock *cp_out_transition = static_cast(relinks.value(og_out_transition)); - command->add_child(new NodeEdgeAddCommand(cp_clip, NodeInput(cp_out_transition, TransitionBlock::kOutBlockInput))); + command->add_child(new NodeEdgeAddCommand(NodeOutput(cp_clip), NodeInput(cp_out_transition, TransitionBlock::kOutBlockInput))); } } } diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp index aa7447df3e..9abae4ece6 100644 --- a/app/widget/timelinewidget/tool/slip.cpp +++ b/app/widget/timelinewidget/tool/slip.cpp @@ -57,10 +57,10 @@ void SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - QString::fromStdString(Timecode::time_to_timecode(time_movement, - tooltip_timebase, - Core::instance()->GetTimecodeDisplay(), - true)), + Timecode::time_to_timecode(time_movement, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay(), + true), parent()); } diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index a67496775e..16073b440d 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -132,10 +132,10 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) Block* in_block = (ghost_->GetMode() == Timeline::kTrimIn) ? active_block : friend_block; // Connect block to transition - command->add_child(new NodeEdgeAddCommand(out_block, + command->add_child(new NodeEdgeAddCommand(NodeOutput(out_block), NodeInput(transition, TransitionBlock::kOutBlockInput))); - command->add_child(new NodeEdgeAddCommand(in_block, + command->add_child(new NodeEdgeAddCommand(NodeOutput(in_block), NodeInput(transition, TransitionBlock::kInBlockInput))); command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5))); @@ -151,7 +151,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) } // Connect block to transition - command->add_child(new NodeEdgeAddCommand(block_to_transition, + command->add_child(new NodeEdgeAddCommand(NodeOutput(block_to_transition), NodeInput(transition, transition_input_to_connect))); command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0))); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 306a82f21a..7c1f2c05cc 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -105,9 +105,9 @@ void TimelineView::mouseMoveEvent(QMouseEvent *event) Block* b = GetItemAtScenePos(timeline_event.GetFrame(), timeline_event.GetTrack().index()); if (b) { setToolTip(tr("In: %1\nOut: %2\nDuration: %3").arg( - QString::fromStdString(Timecode::time_to_timecode(b->in(), timebase(), Core::instance()->GetTimecodeDisplay())), - QString::fromStdString(Timecode::time_to_timecode(b->out(), timebase(), Core::instance()->GetTimecodeDisplay())), - QString::fromStdString(Timecode::time_to_timecode(b->length(), timebase(), Core::instance()->GetTimecodeDisplay())) + Timecode::time_to_timecode(b->in(), timebase(), Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(b->out(), timebase(), Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(b->length(), timebase(), Core::instance()->GetTimecodeDisplay()) )); } else { setToolTip(QString()); @@ -484,7 +484,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->setClipRect(preview_rect); - if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { + if (OLIVE_CONFIG("TimelineThumbnailMode").toInt() == Timeline::kThumbnailOn) { Sequence *s = clip->track()->sequence(); int width = s->GetVideoParams().width(); diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.h b/app/widget/timelinewidget/view/timelineviewghostitem.h index 40a936ab45..0b17238b02 100644 --- a/app/widget/timelinewidget/view/timelineviewghostitem.h +++ b/app/widget/timelinewidget/view/timelineviewghostitem.h @@ -23,6 +23,7 @@ #include +#include "common/qtutils.h" #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" #include "node/output/track/track.h" diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 87cc879bb1..7b087af46b 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -205,7 +205,7 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) if (text_visible_) { QRect text_rect; Qt::Alignment text_align; - QString timecode_str = QString::fromStdString(Timecode::time_to_timecode(SceneToTime(i), timebase(), Core::instance()->GetTimecodeDisplay())); + QString timecode_str = Timecode::time_to_timecode(SceneToTime(i), timebase(), Core::instance()->GetTimecodeDisplay()); int timecode_width = QtUtils::QFontMetricsWidth(fm, timecode_str); int timecode_left; diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 8bcc2b9b50..c33980dfcf 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -64,9 +64,6 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl return; } - QDrag* drag = new QDrag(this); - QMimeData* mimedata = new QMimeData(); - QByteArray encoded_data; QDataStream data_stream(&encoded_data, QIODevice::WriteOnly); @@ -86,6 +83,9 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl } if (!streams.isEmpty()) { + QDrag* drag = new QDrag(this); + QMimeData* mimedata = new QMimeData(); + data_stream << streams << reinterpret_cast(GetConnectedNode()); mimedata->setData(Project::kItemMimeType, encoded_data); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 61d4c30285..b3c2730ad9 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -430,12 +430,14 @@ void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p) { if (multicam_panel_) { disconnect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow); + playback_devices_.removeOne(multicam_panel_->GetDisplayWidget()); } multicam_panel_ = p; if (multicam_panel_) { connect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow); + playback_devices_.push_back(multicam_panel_->GetDisplayWidget()); } } @@ -513,8 +515,8 @@ void ViewerWidget::UpdateAudioProcessor() ap.set_format(ViewerOutput::kDefaultSampleFormat); AudioParams packed(OLIVE_CONFIG("AudioOutputSampleRate").toInt(), - OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong(), - SampleFormat::from_string(OLIVE_CONFIG("AudioOutputSampleFormat").toString().toStdString())); + AudioChannelLayout::fromString(OLIVE_CONFIG("AudioOutputChannelLayout").toString()), + SampleFormat::from_string(OLIVE_CONFIG("AudioOutputSampleFormat").toString())); audio_processor_.Open(ap, packed, (playback_speed_ == 0) ? 1 : std::abs(playback_speed_)); } @@ -636,74 +638,71 @@ void ViewerWidget::SetWaveformMode(WaveformMode wf) UpdateWaveformViewFromMode(); } -void ViewerWidget::DetectMulticamNode(const rational &time) +MultiCamNode *ViewerWidget::DetectMulticamNode(const rational &time) { // Look for multicam node MultiCamNode *multicam = nullptr; ClipBlock *clip = nullptr; // Faster way to do this - if (multicam_panel_ && multicam_panel_->isVisible()) { - if (Sequence *s = dynamic_cast(GetConnectedNode())) { - // Prefer selected nodes - for (Node *n : qAsConst(node_view_selected_)) { - if ((multicam = dynamic_cast(n))) { - // Found multicam, now try to find corresponding clip from selected timeline blocks - for (Block *b : qAsConst(timeline_selected_blocks_)) { - if (ClipBlock *c = dynamic_cast(b)) { - if (c->range().Contains(time) && c->ContextContainsNode(multicam)) { - clip = c; - break; + if (multicam_panel_) { + if (multicam_panel_->isVisible()) { + if (Sequence *s = dynamic_cast(GetConnectedNode())) { + // Prefer selected nodes + for (Node *n : qAsConst(node_view_selected_)) { + if ((multicam = dynamic_cast(n))) { + // Found multicam, now try to find corresponding clip from selected timeline blocks + for (Block *b : qAsConst(timeline_selected_blocks_)) { + if (ClipBlock *c = dynamic_cast(b)) { + if (c->range().Contains(time) && c->ContextContainsNode(multicam)) { + clip = c; + break; + } } } + break; } - break; } - } - // Next, prefer multicam from selected block - if (!multicam) { - for (Block *b : qAsConst(timeline_selected_blocks_)) { - if (b->range().Contains(time)) { - if ((clip = dynamic_cast(b))) { - if ((multicam = clip->FindMulticam())) { - break; + // Next, prefer multicam from selected block + if (!multicam) { + for (Block *b : qAsConst(timeline_selected_blocks_)) { + if (b->range().Contains(time)) { + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } } } } } - } - if (!multicam) { - const QVector &tracks = s->GetTracks(); - for (Track *t : tracks) { - if (t->IsLocked()) { - continue; - } + if (!multicam) { + const QVector &tracks = s->GetTracks(); + for (Track *t : tracks) { + if (t->IsLocked()) { + continue; + } - Block *b = t->NearestBlockBeforeOrAt(time); - if ((clip = dynamic_cast(b))) { - if ((multicam = clip->FindMulticam())) { - break; + Block *b = t->NearestBlockBeforeOrAt(time); + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } } } } } } - } - if (multicam) { - if (multicam_panel_) { + if (multicam) { multicam_panel_->SetMulticamNode(GetConnectedNode(), multicam, clip, time); - } - // FIXME: Really dirty - RenderManager::instance()->GetCacher()->SetMulticamNode(multicam); - } else { - RenderManager::instance()->GetCacher()->SetMulticamNode(nullptr); - if (multicam_panel_) { + } else { multicam_panel_->SetMulticamNode(nullptr, nullptr, nullptr, time); } } + + return multicam; } bool ViewerWidget::IsVideoVisible() const @@ -897,22 +896,36 @@ void ViewerWidget::UpdateTextureFromNode() watcher->setProperty("start", QDateTime::currentMSecsSinceEpoch()); watcher->setProperty("time", QVariant::fromValue(time)); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame); - nonqueue_watchers_.append(watcher); + nonqueue_watchers_.push_back(watcher); // Clear queue because we want this frame more than any others RenderManager::instance()->GetCacher()->ClearSingleFrameRendersThatArentRunning(); - DetectMulticamNode(time); + watcher->SetTicket(GetSingleFrame(time)); - watcher->SetTicket(GetFrame(time)); + if (MultiCamNode *m = DetectMulticamNode(time)) { + // Render multicam frame too + RenderTicketWatcher *mcw = new RenderTicketWatcher(); + connect(mcw, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedMultiCamFrame); + nonqueue_multicam_watchers_.push_back(mcw); + mcw->SetTicket(RenderManager::instance()->GetCacher()->GetSingleFrame(m, this->GetConnectedNode(), time, false)); + } } else { // There is definitely no frame here, we can immediately flip to showing nothing nonqueue_watchers_.clear(); + nonqueue_multicam_watchers_.clear(); SetEmptyImage(); return; } } +void ViewerWidget::SetToolBarVisible(bool e) +{ + controls_->setVisible(e); + ruler()->setVisible(e); + scrollbar()->setVisible(e); +} + void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) { Q_ASSERT(speed != 0); @@ -1113,15 +1126,9 @@ bool ViewerWidget::ViewerMightBeAStill() void ViewerWidget::SetDisplayImage(RenderTicketPtr ticket) { foreach (ViewerDisplayWidget *dw, playback_devices_) { - QVariant push; - if (ticket) { - if (dynamic_cast(dw)) { - push = ticket->property("multicam_output"); - } else { - push = ticket->Get(); - } + if (!dynamic_cast(dw)) { + dw->SetImage(ticket ? ticket->Get() : QVariant()); } - dw->SetImage(push); } } @@ -1129,8 +1136,7 @@ RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment) { RenderTicketWatcher *watcher = nullptr; - rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, - timebase()); + rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, timebase()); if (FrameExistsAtTime(next_time) || ViewerMightBeAStill()) { if (increment) { @@ -1139,31 +1145,23 @@ RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment) watcher = new RenderTicketWatcher(); watcher->setProperty("time", QVariant::fromValue(next_time)); - DetectMulticamNode(next_time); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); queue_watchers_.append(watcher); - watcher->SetTicket(GetFrame(next_time)); + watcher->SetTicket(GetSingleFrame(next_time)); + + if (auto m = DetectMulticamNode(next_time)) { + RenderTicketWatcher *mcw = new RenderTicketWatcher(); + connect(mcw, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); + mcw->setProperty("time", QVariant::fromValue(next_time)); + mcw->setProperty("multicam", true); + queue_watchers_.push_back(mcw); + mcw->SetTicket(RenderManager::instance()->GetCacher()->GetSingleFrame(m, this->GetConnectedNode(), next_time, false)); + } } return watcher; } -RenderTicketPtr ViewerWidget::GetFrame(const rational &t) -{ - QString cache_fn = GetConnectedNode()->video_frame_cache()->GetValidCacheFilename(t); - - if (!QFileInfo::exists(cache_fn)) { - // Frame hasn't been cached, start render job - return GetSingleFrame(t); - } else { - // Frame has been cached, grab the frame - RenderTicketPtr ticket = std::make_shared(); - ticket->setProperty("time", QVariant::fromValue(t)); - QtConcurrent::run(static_cast(ViewerWidget::DecodeCachedImage), ticket, GetConnectedNode()->video_frame_cache()->GetCacheDirectory(), GetConnectedNode()->video_frame_cache()->GetUuid(), Timecode::time_to_timestamp(t, timebase(), Timecode::kFloor)); - return ticket; - } -} - void ViewerWidget::FinishPlayPreprocess() { // Check if we're still waiting for video or audio respectively @@ -1234,7 +1232,7 @@ void ViewerWidget::ContextMenuSetPlaybackRes(QAction *action) auto vp = GetConnectedNode()->GetVideoParams(); vp.set_divider(div); - auto c = new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(GetConnectedNode(), ViewerOutput::kVideoParamsInput, 0)), QVariant::fromValue(vp)); + auto c = new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(GetConnectedNode(), ViewerOutput::kVideoParamsInput, 0)), vp); Core::instance()->undo_stack()->push(c, tr("Changed Playback Resolution")); } @@ -1273,15 +1271,37 @@ void ViewerWidget::RendererGeneratedFrame() RenderTicketWatcher* ticket = static_cast(sender()); if (ticket->HasResult()) { - if (nonqueue_watchers_.contains(ticket)) { - while (!nonqueue_watchers_.isEmpty()) { - // Pop frames that are "old" - if (nonqueue_watchers_.takeFirst() == ticket) { - break; - } + while (!nonqueue_watchers_.empty()) { + // Pop frames that are "old" + auto f = nonqueue_watchers_.front(); + nonqueue_watchers_.pop_front(); + if (f == ticket) { + break; + } + } + + SetDisplayImage(ticket->GetTicket()); + } + + delete ticket; +} + +void ViewerWidget::RendererGeneratedMultiCamFrame() +{ + RenderTicketWatcher* ticket = static_cast(sender()); + + if (ticket->HasResult()) { + while (!nonqueue_multicam_watchers_.empty()) { + // Pop frames that are "old" + auto f = nonqueue_multicam_watchers_.front(); + nonqueue_multicam_watchers_.pop_front(); + if (f == ticket) { + break; } + } - SetDisplayImage(ticket->GetTicket()); + if (multicam_panel_) { + multicam_panel_->GetDisplayWidget()->SetImage(ticket->GetTicket()->Get()); } } @@ -1303,14 +1323,9 @@ void ViewerWidget::RendererGeneratedFrameForQueue() rational ts = watcher->property("time").value(); foreach (ViewerDisplayWidget *dw, playback_devices_) { - QVariant push; - if (dynamic_cast(dw)) { - push = watcher->GetTicket()->property("multicam_output"); - } else { - push = frame; + if (watcher->property("multicam").toBool() == bool(dynamic_cast(dw))) { + dw->queue()->AppendTimewise({ts, frame}, playback_speed_); } - - dw->queue()->AppendTimewise({ts, push}, playback_speed_); } if (prequeuing_video_) { @@ -1500,6 +1515,13 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) connect(show_fps_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowFPS); } + { + QAction *show_toolbar = menu.addAction(tr("Show Toolbar")); + show_toolbar->setCheckable(true); + show_toolbar->setChecked(ruler()->isVisible()); + connect(show_toolbar, &QAction::triggered, this, &ViewerWidget::SetToolBarVisible); + } + if (context_menu_widget_ == display_widget_) { auto subtitle_menu = new Menu(tr("Subtitles"), &menu); menu.addMenu(subtitle_menu); @@ -1560,8 +1582,8 @@ void ViewerWidget::Play(bool in_to_out_only) ); AudioParams ap(OLIVE_CONFIG("AudioRecordingSampleRate").toInt(), - OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong(), - SampleFormat::from_string(OLIVE_CONFIG("AudioRecordingSampleFormat").toString().toStdString())); + AudioChannelLayout::fromString(OLIVE_CONFIG("AudioRecordingChannelLayout").toString()), + SampleFormat::from_string(OLIVE_CONFIG("AudioRecordingSampleFormat").toString())); EncodingParams encode_param; encode_param.EnableAudio(ap, static_cast(OLIVE_CONFIG("AudioRecordingCodec").toInt())); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index e75c36dbdd..c623b8a2b9 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -103,11 +103,6 @@ class ViewerWidget : public TimeBasedWidget enable_audio_scrubbing_ = e; } - void AddPlaybackDevice(ViewerDisplayWidget *vw) - { - playback_devices_.push_back(vw); - } - void SetTimelineSelectedBlocks(const QVector &b) { timeline_selected_blocks_ = b; @@ -167,6 +162,8 @@ public slots: display_widget_->RequestStartEditingText(); } + void SetToolBarVisible(bool e); + signals: /** * @brief Wrapper for ViewerGLWidget::CursorColor() @@ -248,8 +245,6 @@ public slots: RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true); - RenderTicketPtr GetFrame(const rational& t); - void FinishPlayPreprocess(); int DeterminePlaybackQueueSize(); @@ -272,7 +267,7 @@ public slots: void CloseAudioProcessor(); - void DetectMulticamNode(const rational &time); + MultiCamNode *DetectMulticamNode(const rational &time); bool IsVideoVisible() const; @@ -305,7 +300,8 @@ public slots: bool prequeuing_video_; int prequeuing_audio_; - QList nonqueue_watchers_; + std::list nonqueue_watchers_; + std::list nonqueue_multicam_watchers_; rational last_length_; @@ -378,6 +374,8 @@ private slots: void RendererGeneratedFrame(); + void RendererGeneratedMultiCamFrame(); + void RendererGeneratedFrameForQueue(); void ViewerInvalidatedVideoRange(const olive::TimeRange &range); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index d2df88471f..083bbfb9d5 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -422,8 +422,8 @@ void ViewerDisplayWidget::OnPaint() } ShaderJob job; - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height()))); - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw))); + job.Insert(QStringLiteral("resolution_in"), QVector2D(texture_to_draw->width(), texture_to_draw->height())); + job.Insert(QStringLiteral("ove_maintex"), texture_to_draw); renderer()->BlitToTexture(deinterlace_shader_, job, deinterlace_texture_.get()); @@ -452,7 +452,8 @@ void ViewerDisplayWidget::OnPaint() p.setWorldTransform(gizmo_last_draw_transform_); - gizmos_->UpdateGizmoPositions(gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, LoopMode::kLoopModeOff)); + ValueParams::Cache cache; + gizmos_->UpdateGizmoPositions(ValueParams(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, QString(), LoopMode::kLoopModeOff, nullptr, &cache)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { if (gizmo->IsVisible()) { gizmo->Draw(&p); @@ -603,6 +604,12 @@ rational ViewerDisplayWidget::GetGizmoTime() return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, Node::kTransformTowardsInput); } +TimeRange ViewerDisplayWidget::GetGizmoTimeRange() +{ + rational gizmo_time = GetGizmoTime(); + return TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base()); +} + bool ViewerDisplayWidget::IsHandDrag(QMouseEvent *event) const { return event->button() == Qt::MiddleButton || Core::instance()->tool() == Tool::kHand; @@ -650,7 +657,7 @@ QTransform ViewerDisplayWidget::GenerateDisplayTransform() return gizmo_transform; } -QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const TimeRange &range) +QTransform ViewerDisplayWidget::GenerateGizmoTransform(const TimeRange &range) { QTransform t = GenerateDisplayTransform(); if (GetTimeTarget()) { @@ -662,7 +669,31 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const } QTransform nt; - gt.Transform(&nt, gizmos_, target, range); + + auto list = target->FindPath(gizmos_, target, 0); + + // Get time manually rather than using time target so we can iterate through them per node + std::list ranges; + ranges.push_front(TimeRange(time_, time_ + gizmo_params_.frame_rate_as_time_base())); + for (auto it = list.crbegin(); it != list.crend(); it++) { + // Transform time up the list + TimeRange r = it->node()->InputTimeAdjustment(it->input(), it->element(), ranges.front(), false); + ranges.push_front(r); + } + + for (auto it = list.cbegin(); it != list.cend(); it++) { + // Transform matrix down the list + ValueParams::Cache cache; + ValueParams vp(gizmo_params_, gizmo_audio_params_, ranges.front(), QString(), LoopMode::kLoopModeOff, nullptr, &cache); + Node *n = (*it).node(); + if (n->is_enabled()) { + QTransform this_transform = n->GizmoTransformation(vp); + if (!this_transform.isIdentity()) { + nt *= this_transform; + } + } + ranges.pop_front(); + } t.translate(gizmo_params_.width()*0.5, gizmo_params_.height()*0.5); t.scale(gizmo_params_.width(), gizmo_params_.height()); @@ -676,7 +707,7 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const return t; } -NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPointF &p) +NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const QPointF &p) { if (!gizmos_) { return nullptr; @@ -710,7 +741,9 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) { GenerateGizmoTransforms(); - gizmos_->UpdateGizmoPositions(gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, LoopMode::kLoopModeOff)); + + ValueParams::Cache cache; + gizmos_->UpdateGizmoPositions(ValueParams(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, QString(), LoopMode::kLoopModeOff, nullptr, &cache)); active_text_gizmo_ = text; connect(active_text_gizmo_, &TextGizmo::RectChanged, this, &ViewerDisplayWidget::UpdateActiveTextGizmoSize); @@ -828,12 +861,14 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) add_band_end_ = add_band_start_; add_band_ = true; - } else if ((current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { + } else if ((current_gizmo_ = TryGizmoPress(gizmo_last_draw_transform_inverted_.map(event->pos())))) { // Handle gizmo click gizmo_start_drag_ = event->pos(); gizmo_last_drag_ = gizmo_start_drag_; - current_gizmo_->SetGlobals(NodeGlobals(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime(), LoopMode::kLoopModeOff)); + + ValueParams::Cache cache; + current_gizmo_->SetGlobals(ValueParams(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime(), QString(), LoopMode::kLoopModeOff, nullptr, &cache)); } else { @@ -879,13 +914,11 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) if (!gizmo_drag_started_) { QPointF start = ScreenToScenePoint(gizmo_start_drag_); - rational gizmo_time = GetGizmoTime(); - NodeTraverser t; - t.SetCacheVideoParams(gizmo_params_); - t.SetCacheAudioParams(gizmo_audio_params_); - NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base())); + ValueParams::Cache cache;; + TimeRange gizmo_time = GetGizmoTimeRange(); + ValueParams p(gizmo_params_, gizmo_audio_params_, gizmo_time, QString(), LoopMode::kLoopModeOff, nullptr, &cache); - draggable->DragStart(row, start.x(), start.y(), gizmo_time); + draggable->DragStart(p, start.x(), start.y(), gizmo_time.in()); gizmo_drag_started_ = true; } @@ -1164,7 +1197,7 @@ bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool c if (check_if_outside) { if (local_pos.x() < 0 || local_pos.x() >= text_edit_->width() || local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) { // Allow clicking other gizmos so the user can resize while the text editor is active - if ((current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { + if ((current_gizmo_ = TryGizmoPress(gizmo_last_draw_transform_inverted_.map(event->pos())))) { return false; } else { CloseTextEditor(); @@ -1217,17 +1250,12 @@ void ViewerDisplayWidget::CloseTextEditor() void ViewerDisplayWidget::GenerateGizmoTransforms() { - NodeTraverser gt; - gt.SetCacheVideoParams(gizmo_params_); - gt.SetCacheAudioParams(gizmo_audio_params_); - gizmo_draw_time_ = GenerateGizmoTime(); - if (gizmos_) { - gizmo_db_ = gt.GenerateRow(gizmos_, gizmo_draw_time_); - } + ValueParams::Cache cache; + ValueParams p(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, QString(), LoopMode::kLoopModeOff, nullptr, &cache); - gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, gizmo_draw_time_); + gizmo_last_draw_transform_ = GenerateGizmoTransform(gizmo_draw_time_); gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(); } @@ -1238,8 +1266,8 @@ void ViewerDisplayWidget::DrawBlank(const VideoParams &device_params) } ShaderJob job; - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_)); - job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_)); + job.Insert(QStringLiteral("ove_mvpmat"), combined_matrix_flipped_); + job.Insert(QStringLiteral("ove_cropmatrix"), crop_matrix_); renderer()->Blit(blank_shader_, job, device_params, false); } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 5ffcd2c4f1..c0e5cebc3b 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -28,7 +28,6 @@ #include "node/gizmo/text.h" #include "node/node.h" #include "node/output/track/tracklist.h" -#include "node/traverser.h" #include "tool/tool.h" #include "viewerplaybacktimer.h" #include "viewerqueue.h" @@ -230,12 +229,10 @@ public slots: QTransform GenerateDisplayTransform(); - QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range); + QTransform GenerateGizmoTransform(const TimeRange &range); QTransform GenerateGizmoTransform() { - NodeTraverser t; - t.SetCacheVideoParams(gizmo_params_); - return GenerateGizmoTransform(t, GenerateGizmoTime()); + return GenerateGizmoTransform(GenerateGizmoTime()); } TimeRange GenerateGizmoTime() @@ -267,12 +264,13 @@ protected slots: static void DrawTextWithCrudeShadow(QPainter* painter, const QRect& rect, const QString& text, const QTextOption &opt = QTextOption()); rational GetGizmoTime(); + TimeRange GetGizmoTimeRange(); bool IsHandDrag(QMouseEvent* event) const; void UpdateMatrix(); - NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p); + NodeGizmo *TryGizmoPress(const QPointF &p); void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr); @@ -353,7 +351,6 @@ protected slots: ViewerSafeMarginInfo safe_margin_; Node* gizmos_; - NodeValueRow gizmo_db_; VideoParams gizmo_params_; AudioParams gizmo_audio_params_; QPoint gizmo_start_drag_; diff --git a/app/widget/viewer/viewersizer.h b/app/widget/viewer/viewersizer.h index 189e99a277..2b748706fc 100644 --- a/app/widget/viewer/viewersizer.h +++ b/app/widget/viewer/viewersizer.h @@ -21,13 +21,12 @@ #ifndef VIEWERSIZER_H #define VIEWERSIZER_H -#include #include #include -namespace olive { +#include "util/rational.h" -using namespace core; +namespace olive { /** * @brief A container widget that enforces the aspect ratio of a child widget diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 7b9927344c..e2bb7d48cd 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -113,7 +113,6 @@ MainWindow::MainWindow(QWidget *parent) : connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); - sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget()); sequence_viewer_panel_->ConnectMulticamWidget(multicam_panel_->GetMulticamWidget()); scope_panel_->SetViewerPanel(sequence_viewer_panel_); @@ -452,6 +451,8 @@ void MainWindow::closeEvent(QCloseEvent *e) scope_panel_->SetViewerPanel(nullptr); + sequence_viewer_panel_->ConnectMulticamWidget(multicam_panel_->GetMulticamWidget()); + PanelManager::instance()->DeleteAllPanels(); SaveCustomShortcuts(); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 7fbb81fc47..a591f880a0 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -35,7 +35,6 @@ #include "panel/param/param.h" #include "panel/project/project.h" #include "panel/scope/scope.h" -#include "panel/table/table.h" #include "panel/taskmanager/taskmanager.h" #include "panel/timeline/timeline.h" #include "panel/tool/tool.h" diff --git a/cmake/FindOlive.cmake b/cmake/FindOlive.cmake deleted file mode 100644 index 01b57204ae..0000000000 --- a/cmake/FindOlive.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2023 Olive Studios LLC -# -# 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(LIBOLIVE_COMPONENTS - Core - #Codec -) - -foreach (COMPONENT ${LIBOLIVE_COMPONENTS}) - string(TOLOWER ${COMPONENT} LOWER_COMPONENT) - string(TOUPPER ${COMPONENT} UPPER_COMPONENT) - - # Find include directory for this component - find_path(LIBOLIVE_${UPPER_COMPONENT}_INCLUDEDIR - olive/${LOWER_COMPONENT}/${LOWER_COMPONENT}.h - HINTS - "${LIBOLIVE_LOCATION}" - "$ENV{LIBOLIVE_LOCATION}" - "${LIBOLIVE_ROOT}" - "$ENV{LIBOLIVE_ROOT}" - PATH_SUFFIXES - include/ - ) - - find_library(LIBOLIVE_${UPPER_COMPONENT}_LIBRARY - olive${LOWER_COMPONENT} - HINTS - "${LIBOLIVE_LOCATION}" - "$ENV{LIBOLIVE_LOCATION}" - "${LIBOLIVE_ROOT}" - "$ENV{LIBOLIVE_ROOT}" - PATH_SUFFIXES - lib/ - ) - - list(APPEND LIBOLIVE_LIBRARIES ${LIBOLIVE_${UPPER_COMPONENT}_LIBRARY}) - list(APPEND LIBOLIVE_INCLUDE_DIRS ${LIBOLIVE_${UPPER_COMPONENT}_INCLUDEDIR}) -endforeach() - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(Olive - REQUIRED_VARS - LIBOLIVE_LIBRARIES - LIBOLIVE_INCLUDE_DIRS -) diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 135d557c33..a70c9570d3 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -14,8 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(core EXCLUDE_FROM_ALL) - set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically") set(KDDockWidgets_QT6 ${BUILD_QT6} CACHE INTERNAL "Conform KDDockWidgets' Qt 6 setting to ours") add_subdirectory(KDDockWidgets EXCLUDE_FROM_ALL) diff --git a/ext/core b/ext/core deleted file mode 160000 index 2777928248..0000000000 --- a/ext/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 277792824801495e868580ca86f6e7a1b53e4779 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000000..f4448fa503 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,17 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2023 Olive Studios LLC +# +# 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 . + +add_subdirectory(olive) diff --git a/lib/olive/CMakeLists.txt b/lib/olive/CMakeLists.txt new file mode 100644 index 0000000000..3f84a6d0e3 --- /dev/null +++ b/lib/olive/CMakeLists.txt @@ -0,0 +1,33 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2023 Olive Studios LLC +# +# 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 . + +add_subdirectory(src) + +add_library(olivecore + ${OLIVECORE_SOURCES} +) + +target_include_directories(olivecore PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_SOURCE_DIR}/app" +) +target_link_libraries(olivecore PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets) + +if (OpenTimelineIO_FOUND) + target_compile_definitions(olivecore PRIVATE USE_OTIO) + target_include_directories(olivecore PRIVATE ${OTIO_INCLUDE_DIRS}) + target_link_libraries(olivecore PRIVATE ${OTIO_LIBRARIES}) +endif() diff --git a/lib/olive/include/render/audiochannellayout.h b/lib/olive/include/render/audiochannellayout.h new file mode 100644 index 0000000000..247c91973e --- /dev/null +++ b/lib/olive/include/render/audiochannellayout.h @@ -0,0 +1,138 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 AUDIOCHANNELLAYOUT_H +#define AUDIOCHANNELLAYOUT_H + +extern "C" { +#include +} + +#include +#include + +#include "node/type.h" + +namespace olive { + +class AudioChannelLayout +{ +public: + static const AudioChannelLayout STEREO; + + AudioChannelLayout() + { + internal_ = {}; + } + + AudioChannelLayout(const AVChannelLayout &other) : + AudioChannelLayout() + { + av_channel_layout_copy(&internal_, &other); + } + + ~AudioChannelLayout() + { + av_channel_layout_uninit(&internal_); + } + + AudioChannelLayout(const AudioChannelLayout& other) : + AudioChannelLayout() + { + av_channel_layout_copy(&internal_, &other.internal_); + } + + AudioChannelLayout& operator=(const AudioChannelLayout& other) + { + av_channel_layout_copy(&internal_, &other.internal_); + return *this; + } + + bool operator==(const AVChannelLayout &c) const + { + return !av_channel_layout_compare(&internal_, &c); + } + + bool operator==(const AudioChannelLayout &c) const + { + return *this == c.internal_; + } + + bool operator!=(const AudioChannelLayout &c) const { return !(*this == c); } + bool operator!=(const AVChannelLayout &c) const { return !(*this == c); } + + bool isNull() const + { + return !av_channel_layout_check(&internal_); + } + + QString toString() const + { + char buf[100]; + av_channel_layout_describe(&internal_, buf, sizeof(buf)); + return buf; + } + + QString toHumanString() const + { + // TODO: Provide more usable strings than FFmpeg's descriptions + return toString(); + } + + static AudioChannelLayout fromString(const QString &s) + { + AudioChannelLayout layout; + av_channel_layout_from_string(&layout.internal_, s.toUtf8().constData()); + return layout; + } + + static AudioChannelLayout fromMask(const uint64_t &i) + { + AudioChannelLayout layout; + av_channel_layout_from_mask(&layout.internal_, i); + return layout; + } + + int count() const { return internal_.nb_channels; } + + void exportTo(AVChannelLayout &out) const + { + return exportTo(&out); + } + + void exportTo(AVChannelLayout *out) const + { + av_channel_layout_copy(out, &internal_); + } + + std::vector getChannelNames() const; + + const AVChannelLayout *ref() const { return &internal_; } + +private: + AVChannelLayout internal_; + +}; + +uint qHash(const AudioChannelLayout &l, uint seed = 0); + +} + +#endif // AUDIOCHANNELLAYOUT_H diff --git a/lib/olive/include/render/audioparams.h b/lib/olive/include/render/audioparams.h new file mode 100644 index 0000000000..65e03afa3c --- /dev/null +++ b/lib/olive/include/render/audioparams.h @@ -0,0 +1,181 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_AUDIOPARAMS_H +#define LIBOLIVECORE_AUDIOPARAMS_H + +extern "C" { +#include +} + +#include +#include +#include + +#include "audiochannellayout.h" +#include "sampleformat.h" +#include "util/rational.h" + +namespace olive { + +class AudioParams { +public: + AudioParams() : + sample_rate_(0), + format_(SampleFormat::INVALID) + { + set_default_footage_parameters(); + } + + AudioParams(const int& sample_rate, const AudioChannelLayout& channel_layout, const SampleFormat& format) : + sample_rate_(sample_rate), + channel_layout_(channel_layout), + format_(format) + { + set_default_footage_parameters(); + timebase_ = sample_rate_as_time_base(); + } + + int sample_rate() const + { + return sample_rate_; + } + + void set_sample_rate(int sample_rate) + { + sample_rate_ = sample_rate; + } + + const AudioChannelLayout &channel_layout() const + { + return channel_layout_; + } + + void set_channel_layout(const AudioChannelLayout &channel_layout) + { + channel_layout_ = channel_layout; + } + + rational time_base() const + { + return timebase_; + } + + void set_time_base(const rational& timebase) + { + timebase_ = timebase; + } + + rational sample_rate_as_time_base() const + { + return rational(1, sample_rate()); + } + + SampleFormat format() const + { + return format_; + } + + void set_format(SampleFormat format) + { + format_ = format; + } + + bool enabled() const + { + return enabled_; + } + + void set_enabled(bool e) + { + enabled_ = e; + } + + int stream_index() const + { + return stream_index_; + } + + void set_stream_index(int s) + { + stream_index_ = s; + } + + int64_t duration() const + { + return duration_; + } + + void set_duration(int64_t duration) + { + duration_ = duration; + } + + int64_t time_to_bytes(const double& time) const; + int64_t time_to_bytes(const rational& time) const; + int64_t time_to_bytes_per_channel(const double& time) const; + int64_t time_to_bytes_per_channel(const rational& time) const; + int64_t time_to_samples(const double& time) const; + int64_t time_to_samples(const rational& time) const; + int64_t samples_to_bytes(const int64_t& samples) const; + int64_t samples_to_bytes_per_channel(const int64_t& samples) const; + rational samples_to_time(const int64_t& samples) const; + int64_t bytes_to_samples(const int64_t &bytes) const; + rational bytes_to_time(const int64_t &bytes) const; + rational bytes_per_channel_to_time(const int64_t &bytes) const; + int channel_count() const; + int bytes_per_sample_per_channel() const; + int bits_per_sample() const; + bool is_valid() const; + + void load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + + bool operator==(const AudioParams& other) const; + bool operator!=(const AudioParams& other) const; + + static const std::vector kSupportedChannelLayouts; + static const std::vector kSupportedSampleRates; + +private: + void set_default_footage_parameters() + { + enabled_ = true; + stream_index_ = 0; + duration_ = 0; + } + + int sample_rate_; + + AudioChannelLayout channel_layout_; + + SampleFormat format_; + + // Footage-specific + int enabled_; // Switching this to int fixes GCC 11 stringop-overflow issue, I guess a byte-alignment issue? + int stream_index_; + int64_t duration_; + rational timebase_; + +}; + +} + +#endif // LIBOLIVECORE_AUDIOPARAMS_H diff --git a/lib/olive/include/render/pixelformat.h b/lib/olive/include/render/pixelformat.h new file mode 100644 index 0000000000..6bdae212e1 --- /dev/null +++ b/lib/olive/include/render/pixelformat.h @@ -0,0 +1,108 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_PIXELFORMAT_H +#define LIBOLIVECORE_PIXELFORMAT_H + +namespace olive { + +class PixelFormat +{ +public: + enum Format { + INVALID = -1, + U8, + U16, + F16, + F32, + COUNT + }; + + PixelFormat(Format f = INVALID) { f_ = f; } + + operator Format() const { return f_; } + + static int byte_count(Format f) + { + switch (f) { + case INVALID: + case COUNT: + break; + case U8: + return 1; + case U16: + case F16: + return 2; + case F32: + return 4; + } + + return 0; + } + + const char *to_string() const + { + switch (f_) { + case U8: return "u8"; + case U16: return "u16"; + case F16: return "f16"; + case F32: return "f32"; + case INVALID: + case COUNT: + break; + } + + return ""; + } + + int byte_count() const + { + return byte_count(f_); + } + + static bool is_float(Format f) + { + switch (f) { + case INVALID: + case COUNT: + case U8: + case U16: + break; + case F16: + case F32: + return true; + } + + return false; + } + + bool is_float() const + { + return is_float(f_); + } + +private: + Format f_; + +}; + +} + +#endif // LIBOLIVECORE_PIXELFORMAT_H diff --git a/lib/olive/include/render/samplebuffer.h b/lib/olive/include/render/samplebuffer.h new file mode 100644 index 0000000000..53b9bbade9 --- /dev/null +++ b/lib/olive/include/render/samplebuffer.h @@ -0,0 +1,123 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_SAMPLEBUFFER_H +#define LIBOLIVECORE_SAMPLEBUFFER_H + +#include +#include + +#include "audioparams.h" +#include "../util/rational.h" + +namespace olive { + +/** + * @brief A buffer of audio samples + * + * Audio samples in this structure are always stored in PLANAR (separated by channel). This is done to simplify audio + * rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer + * replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for + * playback, including reading to and from the cache. + */ +class SampleBuffer +{ +public: + SampleBuffer(); + SampleBuffer(const AudioParams& audio_params, const rational& length); + SampleBuffer(const AudioParams& audio_params, size_t samples_per_channel); + + SampleBuffer rip_channel(int channel) const; + std::vector rip_channel_vector(int channel) const; + + const AudioParams& audio_params() const; + void set_audio_params(const AudioParams& params); + + const size_t &sample_count() const { return sample_count_per_channel_; } + void set_sample_count(const size_t &sample_count); + void set_sample_count(const rational &length) + { + set_sample_count(audio_params_.time_to_samples(length)); + } + + float* data(int channel) + { + return data_[channel].data(); + } + + const float* data(int channel) const + { + return data_.at(channel).data(); + } + + std::vector to_raw_ptrs() + { + std::vector r(data_.size()); + for (size_t i=0; i > data_; + +}; + +} + +#endif // LIBOLIVECORE_SAMPLEBUFFER_H diff --git a/lib/olive/include/render/sampleformat.h b/lib/olive/include/render/sampleformat.h new file mode 100644 index 0000000000..1d9abda49b --- /dev/null +++ b/lib/olive/include/render/sampleformat.h @@ -0,0 +1,264 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_SAMPLEFORMAT_H +#define LIBOLIVECORE_SAMPLEFORMAT_H + +#include +#include + +namespace olive { + +class SampleFormat +{ +public: + enum Format { + INVALID = -1, + + U8P, + S16P, + S32P, + S64P, + F32P, + F64P, + + U8, + S16, + S32, + S64, + F32, + F64, + + COUNT, + + PLANAR_START = U8P, + PACKED_START = U8, + PLANAR_END = PACKED_START, + PACKED_END = COUNT, + }; + + SampleFormat(Format f = INVALID) { f_ = f; } + + operator Format() const { return f_; } + + static int byte_count(Format f) + { + switch (f) { + case U8: + case U8P: + return 1; + case S16: + case S16P: + return 2; + case S32: + case F32: + case S32P: + case F32P: + return 4; + case S64: + case F64: + case S64P: + case F64P: + return 8; + case INVALID: + case COUNT: + break; + } + + return 0; + } + + int byte_count() const + { + return byte_count(f_); + } + + static QString to_string(Format f) + { + switch (f) { + case INVALID: + case COUNT: + break; + case U8: return "u8"; + case S16: return "s16"; + case S32: return "s32"; + case S64: return "s64"; + case F32: return "f32"; + case F64: return "f64"; + case U8P: return "u8p"; + case S16P: return "s16p"; + case S32P: return "s32p"; + case S64P: return "s64p"; + case F32P: return "f32p"; + case F64P: return "f64p"; + } + + return ""; + } + + QString to_string() const + { + return to_string(f_); + } + + static SampleFormat from_string(const QString &s) + { + if (s.isEmpty()) { + return INVALID; + } else if (s == "u8") { + return U8; + } else if (s == "s16") { + return S16; + } else if (s == "s32") { + return S32; + } else if (s == "s64") { + return S64; + } else if (s == "f32") { + return F32; + } else if (s == "f64") { + return F64; + } else if (s == "u8p") { + return U8P; + } else if (s == "s16p") { + return S16P; + } else if (s == "s32p") { + return S32P; + } else if (s == "s64p") { + return S64P; + } else if (s == "f32p") { + return F32P; + } else if (s == "f64p") { + return F64P; + } else { + // Deprecated: sample formats used to be serialized as an integer. Handle that here, but we'll + // probably remove that eventually. + bool ok; + int i = s.toInt(&ok); + if (ok && i > INVALID && i < COUNT) { + return static_cast(i); + } else { + // Failed to deserialize from string + return INVALID; + } + } + } + + static bool is_packed(Format f) + { + return f >= PACKED_START && f < PACKED_END; + } + + bool is_packed() const { return is_packed(f_); } + + static bool is_planar(Format f) + { + return f >= PLANAR_START && f < PLANAR_END; + } + + bool is_planar() const { return is_planar(f_); } + + static SampleFormat to_packed_equivalent(SampleFormat fmt) + { + switch (fmt) { + + // For packed input, just return input + case U8: + case S16: + case S32: + case S64: + case F32: + case F64: + return fmt; + + // Convert to packed + case U8P: + return U8; + case S16P: + return S16; + case S32P: + return S32; + case S64P: + return S64; + case F32P: + return F32; + case F64P: + return F64; + + case INVALID: + case COUNT: + break; + } + + return INVALID; + } + + SampleFormat to_packed_equivalent() const + { + return to_packed_equivalent(f_); + } + + static SampleFormat to_planar_equivalent(SampleFormat fmt) + { + switch (fmt) { + + // Convert to planar + case U8: + return U8P; + case S16: + return S16P; + case S32: + return S32P; + case S64: + return S64P; + case F32: + return F32P; + case F64: + return F64P; + + // For planar input, just return input + case U8P: + case S16P: + case S32P: + case S64P: + case F32P: + case F64P: + return fmt; + + case INVALID: + case COUNT: + break; + } + + return INVALID; + } + + SampleFormat to_planar_equivalent() const + { + return to_planar_equivalent(f_); + } + +private: + Format f_; + +}; + +} + +#endif // LIBOLIVECORE_SAMPLEFORMAT_H diff --git a/lib/olive/include/util/bezier.h b/lib/olive/include/util/bezier.h new file mode 100644 index 0000000000..1d7cc4faf3 --- /dev/null +++ b/lib/olive/include/util/bezier.h @@ -0,0 +1,98 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_BEZIER_H +#define LIBOLIVECORE_BEZIER_H + +#include + +namespace olive { + +class Bezier +{ +public: + Bezier(); + Bezier(double x, double y); + Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y); + + const double &x() const {return x_; } + const double &y() const {return y_; } + const double &cp1_x() const { return cp1_x_; } + const double &cp1_y() const { return cp1_y_; } + const double &cp2_x() const { return cp2_x_; } + const double &cp2_y() const { return cp2_y_; } + + Imath::V2d to_vec() const + { + return Imath::V2d(x_, y_); + } + + Imath::V2d control_point_1_to_vec() const + { + return Imath::V2d(cp1_x_, cp1_y_); + } + + Imath::V2d control_point_2_to_vec() const + { + return Imath::V2d(cp2_x_, cp2_y_); + } + + void set_x(const double &x) { x_ = x; } + void set_y(const double &y) { y_ = y; } + void set_cp1_x(const double &cp1_x) { cp1_x_ = cp1_x; } + void set_cp1_y(const double &cp1_y) { cp1_y_ = cp1_y; } + void set_cp2_x(const double &cp2_x) { cp2_x_ = cp2_x; } + void set_cp2_y(const double &cp2_y) { cp2_y_ = cp2_y; } + + static double QuadraticXtoT(double x, double a, double b, double c); + + static double QuadraticTtoY(double a, double b, double c, double t); + + static double QuadraticXtoY(double x, const Imath::V2d &a, const Imath::V2d &b, const Imath::V2d &c) + { + return QuadraticTtoY(a.y, b.y, c.y, QuadraticXtoT(x, a.x, b.x, c.x)); + } + + static double CubicXtoT(double x, double a, double b, double c, double d); + + static double CubicTtoY(double a, double b, double c, double d, double t); + + static double CubicXtoY(double x, const Imath::V2d &a, const Imath::V2d &b, const Imath::V2d &c, const Imath::V2d &d) + { + return CubicTtoY(a.y, b.y, c.y, d.y, CubicXtoT(x, a.x, b.x, c.x, d.x)); + } + +private: + static double CalculateTFromX(bool cubic, double x, double a, double b, double c, double d); + + double x_; + double y_; + + double cp1_x_; + double cp1_y_; + + double cp2_x_; + double cp2_y_; + +}; + +} + +#endif // LIBOLIVECORE_BEZIER_H diff --git a/lib/olive/include/util/color.h b/lib/olive/include/util/color.h new file mode 100644 index 0000000000..921bc44bff --- /dev/null +++ b/lib/olive/include/util/color.h @@ -0,0 +1,150 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_COLOR_H +#define LIBOLIVECORE_COLOR_H + +#include "../render/pixelformat.h" + +namespace olive { + +/** + * @brief High precision 32-bit DataType based RGBA color value + */ +class Color +{ +public: + using DataType = float; + static constexpr unsigned int RGBA = 4; + + Color() + { + for (unsigned int i=0;i +#elif defined(__aarch64__) +#define OLIVE_PROCESSOR_ARM +#include "sse2neon.h" +#endif -NodeTablePanel::NodeTablePanel() : - TimeBasedPanel(QStringLiteral("NodeTablePanel")) -{ - SetTimeBasedWidget(new NodeTableWidget(this)); - - Retranslate(); -} - -void NodeTablePanel::Retranslate() -{ - SetTitle(tr("Table View")); -} - -} +#endif // LIBOLIVECORE_CPUOPTIMIZE_H diff --git a/lib/olive/include/util/log.h b/lib/olive/include/util/log.h new file mode 100644 index 0000000000..3323093258 --- /dev/null +++ b/lib/olive/include/util/log.h @@ -0,0 +1,52 @@ +#ifndef LOG_H +#define LOG_H + +#include + +namespace olive { + +class Log +{ +public: + Log(const char *type) + { + std::cerr << "[" << type << "]"; + } + + ~Log() + { + std::cerr << std::endl; + } + + template + Log &operator<<(const T &t) + { + std::cerr << " " << t; + return *this; + } + + static Log Debug() + { + return Log("DEBUG"); + } + + static Log Info() + { + return Log("INFO"); + } + + static Log Warning() + { + return Log("WARNING"); + } + + static Log Error() + { + return Log("ERROR"); + } + +}; + +} + +#endif // LOG_H diff --git a/app/node/splitvalue.h b/lib/olive/include/util/math.h similarity index 79% rename from app/node/splitvalue.h rename to lib/olive/include/util/math.h index 2a1d2a49cc..95afc389f2 100644 --- a/app/node/splitvalue.h +++ b/lib/olive/include/util/math.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,16 +18,11 @@ ***/ -#ifndef SPLITVALUE_H -#define SPLITVALUE_H - -#include -#include +#ifndef LIBOLIVECORE_MATH_H +#define LIBOLIVECORE_MATH_H namespace olive { -using SplitValue = QVector; - } -#endif // SPLITVALUE_H +#endif // LIBOLIVECORE_MATH_H diff --git a/lib/olive/include/util/rational.h b/lib/olive/include/util/rational.h new file mode 100644 index 0000000000..1e613565c3 --- /dev/null +++ b/lib/olive/include/util/rational.h @@ -0,0 +1,148 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_RATIONAL_H +#define LIBOLIVECORE_RATIONAL_H + +extern "C" { +#include +} + +#include +#include + +#ifdef USE_OTIO +#include +#endif + +namespace olive { + +class rational +{ +public: + rational(const int &numerator = 0) + { + r_.num = numerator; + r_.den = 1; + } + + rational(const int &numerator, const int &denominator) + { + r_.num = numerator; + r_.den = denominator; + + fix_signs(); + reduce(); + } + + rational(const rational &rhs) = default; + + rational(const AVRational& r) + { + r_ = r; + + fix_signs(); + } + + static rational fromDouble(const double& flt, bool *ok = nullptr); + static rational fromString(const QString& str, bool* ok = nullptr); + + static const rational NaN; + + //Assignment Operators + const rational& operator=(const rational &rhs); + const rational& operator+=(const rational &rhs); + const rational& operator-=(const rational &rhs); + const rational& operator/=(const rational &rhs); + const rational& operator*=(const rational &rhs); + + //Binary math operators + rational operator+(const rational &rhs) const; + rational operator-(const rational &rhs) const; + rational operator/(const rational &rhs) const; + rational operator*(const rational &rhs) const; + + //Relational and equality operators + bool operator<(const rational &rhs) const; + bool operator<=(const rational &rhs) const; + bool operator>(const rational &rhs) const; + bool operator>=(const rational &rhs) const; + bool operator==(const rational &rhs) const; + bool operator!=(const rational &rhs) const; + + //Unary operators + const rational& operator+() const { return *this; } + rational operator-() const { return rational(r_.num, -r_.den); } + bool operator!() const { return !r_.num; } + + //Function: convert to double + double toDouble() const; + + AVRational toAVRational() const; + +#ifdef USE_OTIO + static rational fromRationalTime(const opentime::RationalTime &t) + { + // Is this the best way to do this? + return fromDouble(t.to_seconds()); + } + + // Convert Olive rationals to opentime rationals with the given framerate (defaults to 24) + opentime::RationalTime toRationalTime(double framerate = 24) const; +#endif + + // Produce "flipped" version + rational flipped() const; + void flip(); + + // Returns whether the rational is valid but equal to zero or not + // + // A NaN is always a null, but a null is not always a NaN + bool isNull() const { return r_.num == 0; } + + // Returns whether this rational is not a valid number (denominator == 0) + bool isNaN() const { return r_.den == 0; } + + const int& numerator() const { return r_.num; } + const int& denominator() const { return r_.den; } + + QString toString() const; + + friend std::ostream& operator<<(std::ostream &out, const rational &value) + { + out << value.r_.num << '/' << value.r_.den; + + return out; + } + +private: + void fix_signs(); + void reduce(); + + AVRational r_; + +}; + +#define RATIONAL_MIN rational(INT_MIN) +#define RATIONAL_MAX rational(INT_MAX) + +} + +#endif // LIBOLIVECORE_RATIONAL_H diff --git a/lib/olive/include/util/sse2neon.h b/lib/olive/include/util/sse2neon.h new file mode 100644 index 0000000000..c3ce3e3189 --- /dev/null +++ b/lib/olive/include/util/sse2neon.h @@ -0,0 +1,8757 @@ +#ifndef SSE2NEON_H +#define SSE2NEON_H + +// This header file provides a simple API translation layer +// between SSE intrinsics to their corresponding Arm/Aarch64 NEON versions +// +// This header file does not yet translate all of the SSE intrinsics. +// +// Contributors to this work are: +// John W. Ratcliff +// Brandon Rowlett +// Ken Fast +// Eric van Beurden +// Alexander Potylitsin +// Hasindu Gamaarachchi +// Jim Huang +// Mark Cheng +// Malcolm James MacLeod +// Devin Hussey (easyaspi314) +// Sebastian Pop +// Developer Ecosystem Engineering +// Danila Kutenin +// François Turban (JishinMaster) +// Pei-Hsuan Hung +// Yang-Hao Yuan +// Syoyo Fujita +// Brecht Van Lommel + +/* + * sse2neon is freely redistributable under the MIT License. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Tunable configurations */ + +/* Enable precise implementation of math operations + * This would slow down the computation a bit, but gives consistent result with + * x86 SSE. (e.g. would solve a hole or NaN pixel in the rendering result) + */ +/* _mm_min|max_ps|ss|pd|sd */ +#ifndef SSE2NEON_PRECISE_MINMAX +#define SSE2NEON_PRECISE_MINMAX (0) +#endif +/* _mm_rcp_ps and _mm_div_ps */ +#ifndef SSE2NEON_PRECISE_DIV +#define SSE2NEON_PRECISE_DIV (0) +#endif +/* _mm_sqrt_ps and _mm_rsqrt_ps */ +#ifndef SSE2NEON_PRECISE_SQRT +#define SSE2NEON_PRECISE_SQRT (0) +#endif +/* _mm_dp_pd */ +#ifndef SSE2NEON_PRECISE_DP +#define SSE2NEON_PRECISE_DP (0) +#endif + +/* compiler specific definitions */ +#if defined(__GNUC__) || defined(__clang__) +#pragma push_macro("FORCE_INLINE") +#pragma push_macro("ALIGN_STRUCT") +#define FORCE_INLINE static inline __attribute__((always_inline)) +#define ALIGN_STRUCT(x) __attribute__((aligned(x))) +#define _sse2neon_likely(x) __builtin_expect(!!(x), 1) +#define _sse2neon_unlikely(x) __builtin_expect(!!(x), 0) +#else /* non-GNU / non-clang compilers */ +#warning "Macro name collisions may happen with unsupported compiler." +#ifndef FORCE_INLINE +#define FORCE_INLINE static inline +#endif +#ifndef ALIGN_STRUCT +#define ALIGN_STRUCT(x) __declspec(align(x)) +#endif +#define _sse2neon_likely(x) (x) +#define _sse2neon_unlikely(x) (x) +#endif + +#include +#include + +/* Architecture-specific build options */ +/* FIXME: #pragma GCC push_options is only available on GCC */ +#if defined(__GNUC__) +#if defined(__arm__) && __ARM_ARCH == 7 +/* According to ARM C Language Extensions Architecture specification, + * __ARM_NEON is defined to a value indicating the Advanced SIMD (NEON) + * architecture supported. + */ +#if !defined(__ARM_NEON) || !defined(__ARM_NEON__) +#error "You must enable NEON instructions (e.g. -mfpu=neon) to use SSE2NEON." +#endif +#if !defined(__clang__) +#pragma GCC push_options +#pragma GCC target("fpu=neon") +#endif +#elif defined(__aarch64__) +#if !defined(__clang__) +#pragma GCC push_options +#pragma GCC target("+simd") +#endif +#else +#error "Unsupported target. Must be either ARMv7-A+NEON or ARMv8-A." +#endif +#endif + +#include + +/* Rounding functions require either Aarch64 instructions or libm failback */ +#if !defined(__aarch64__) +#include +#endif + +/* "__has_builtin" can be used to query support for built-in functions + * provided by gcc/clang and other compilers that support it. + */ +#ifndef __has_builtin /* GCC prior to 10 or non-clang compilers */ +/* Compatibility with gcc <= 9 */ +#if __GNUC__ <= 9 +#define __has_builtin(x) HAS##x +#define HAS__builtin_popcount 1 +#define HAS__builtin_popcountll 1 +#else +#define __has_builtin(x) 0 +#endif +#endif + +/** + * MACRO for shuffle parameter for _mm_shuffle_ps(). + * Argument fp3 is a digit[0123] that represents the fp from argument "b" + * of mm_shuffle_ps that will be placed in fp3 of result. fp2 is the same + * for fp2 in result. fp1 is a digit[0123] that represents the fp from + * argument "a" of mm_shuffle_ps that will be places in fp1 of result. + * fp0 is the same for fp0 of result. + */ +#define _MM_SHUFFLE(fp3, fp2, fp1, fp0) \ + (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) + +/* Rounding mode macros. */ +#define _MM_FROUND_TO_NEAREST_INT 0x00 +#define _MM_FROUND_TO_NEG_INF 0x01 +#define _MM_FROUND_TO_POS_INF 0x02 +#define _MM_FROUND_TO_ZERO 0x03 +#define _MM_FROUND_CUR_DIRECTION 0x04 +#define _MM_FROUND_NO_EXC 0x08 +#define _MM_FROUND_RAISE_EXC 0x00 +#define _MM_FROUND_NINT (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_RAISE_EXC) +#define _MM_FROUND_FLOOR (_MM_FROUND_TO_NEG_INF | _MM_FROUND_RAISE_EXC) +#define _MM_FROUND_CEIL (_MM_FROUND_TO_POS_INF | _MM_FROUND_RAISE_EXC) +#define _MM_FROUND_TRUNC (_MM_FROUND_TO_ZERO | _MM_FROUND_RAISE_EXC) +#define _MM_FROUND_RINT (_MM_FROUND_CUR_DIRECTION | _MM_FROUND_RAISE_EXC) +#define _MM_FROUND_NEARBYINT (_MM_FROUND_CUR_DIRECTION | _MM_FROUND_NO_EXC) +#define _MM_ROUND_NEAREST 0x0000 +#define _MM_ROUND_DOWN 0x2000 +#define _MM_ROUND_UP 0x4000 +#define _MM_ROUND_TOWARD_ZERO 0x6000 +/* Flush zero mode macros. */ +#define _MM_FLUSH_ZERO_MASK 0x8000 +#define _MM_FLUSH_ZERO_ON 0x8000 +#define _MM_FLUSH_ZERO_OFF 0x0000 +/* Denormals are zeros mode macros. */ +#define _MM_DENORMALS_ZERO_MASK 0x0040 +#define _MM_DENORMALS_ZERO_ON 0x0040 +#define _MM_DENORMALS_ZERO_OFF 0x0000 + +/* indicate immediate constant argument in a given range */ +#define __constrange(a, b) const + +/* A few intrinsics accept traditional data types like ints or floats, but + * most operate on data types that are specific to SSE. + * If a vector type ends in d, it contains doubles, and if it does not have + * a suffix, it contains floats. An integer vector type can contain any type + * of integer, from chars to shorts to unsigned long longs. + */ +typedef int64x1_t __m64; +typedef float32x4_t __m128; /* 128-bit vector containing 4 floats */ +// On ARM 32-bit architecture, the float64x2_t is not supported. +// The data type __m128d should be represented in a different way for related +// intrinsic conversion. +#if defined(__aarch64__) +typedef float64x2_t __m128d; /* 128-bit vector containing 2 doubles */ +#else +typedef float32x4_t __m128d; +#endif +typedef int64x2_t __m128i; /* 128-bit vector containing integers */ + +/* type-safe casting between types */ + +#define vreinterpretq_m128_f16(x) vreinterpretq_f32_f16(x) +#define vreinterpretq_m128_f32(x) (x) +#define vreinterpretq_m128_f64(x) vreinterpretq_f32_f64(x) + +#define vreinterpretq_m128_u8(x) vreinterpretq_f32_u8(x) +#define vreinterpretq_m128_u16(x) vreinterpretq_f32_u16(x) +#define vreinterpretq_m128_u32(x) vreinterpretq_f32_u32(x) +#define vreinterpretq_m128_u64(x) vreinterpretq_f32_u64(x) + +#define vreinterpretq_m128_s8(x) vreinterpretq_f32_s8(x) +#define vreinterpretq_m128_s16(x) vreinterpretq_f32_s16(x) +#define vreinterpretq_m128_s32(x) vreinterpretq_f32_s32(x) +#define vreinterpretq_m128_s64(x) vreinterpretq_f32_s64(x) + +#define vreinterpretq_f16_m128(x) vreinterpretq_f16_f32(x) +#define vreinterpretq_f32_m128(x) (x) +#define vreinterpretq_f64_m128(x) vreinterpretq_f64_f32(x) + +#define vreinterpretq_u8_m128(x) vreinterpretq_u8_f32(x) +#define vreinterpretq_u16_m128(x) vreinterpretq_u16_f32(x) +#define vreinterpretq_u32_m128(x) vreinterpretq_u32_f32(x) +#define vreinterpretq_u64_m128(x) vreinterpretq_u64_f32(x) + +#define vreinterpretq_s8_m128(x) vreinterpretq_s8_f32(x) +#define vreinterpretq_s16_m128(x) vreinterpretq_s16_f32(x) +#define vreinterpretq_s32_m128(x) vreinterpretq_s32_f32(x) +#define vreinterpretq_s64_m128(x) vreinterpretq_s64_f32(x) + +#define vreinterpretq_m128i_s8(x) vreinterpretq_s64_s8(x) +#define vreinterpretq_m128i_s16(x) vreinterpretq_s64_s16(x) +#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x) +#define vreinterpretq_m128i_s64(x) (x) + +#define vreinterpretq_m128i_u8(x) vreinterpretq_s64_u8(x) +#define vreinterpretq_m128i_u16(x) vreinterpretq_s64_u16(x) +#define vreinterpretq_m128i_u32(x) vreinterpretq_s64_u32(x) +#define vreinterpretq_m128i_u64(x) vreinterpretq_s64_u64(x) + +#define vreinterpretq_f32_m128i(x) vreinterpretq_f32_s64(x) +#define vreinterpretq_f64_m128i(x) vreinterpretq_f64_s64(x) + +#define vreinterpretq_s8_m128i(x) vreinterpretq_s8_s64(x) +#define vreinterpretq_s16_m128i(x) vreinterpretq_s16_s64(x) +#define vreinterpretq_s32_m128i(x) vreinterpretq_s32_s64(x) +#define vreinterpretq_s64_m128i(x) (x) + +#define vreinterpretq_u8_m128i(x) vreinterpretq_u8_s64(x) +#define vreinterpretq_u16_m128i(x) vreinterpretq_u16_s64(x) +#define vreinterpretq_u32_m128i(x) vreinterpretq_u32_s64(x) +#define vreinterpretq_u64_m128i(x) vreinterpretq_u64_s64(x) + +#define vreinterpret_m64_s8(x) vreinterpret_s64_s8(x) +#define vreinterpret_m64_s16(x) vreinterpret_s64_s16(x) +#define vreinterpret_m64_s32(x) vreinterpret_s64_s32(x) +#define vreinterpret_m64_s64(x) (x) + +#define vreinterpret_m64_u8(x) vreinterpret_s64_u8(x) +#define vreinterpret_m64_u16(x) vreinterpret_s64_u16(x) +#define vreinterpret_m64_u32(x) vreinterpret_s64_u32(x) +#define vreinterpret_m64_u64(x) vreinterpret_s64_u64(x) + +#define vreinterpret_m64_f16(x) vreinterpret_s64_f16(x) +#define vreinterpret_m64_f32(x) vreinterpret_s64_f32(x) +#define vreinterpret_m64_f64(x) vreinterpret_s64_f64(x) + +#define vreinterpret_u8_m64(x) vreinterpret_u8_s64(x) +#define vreinterpret_u16_m64(x) vreinterpret_u16_s64(x) +#define vreinterpret_u32_m64(x) vreinterpret_u32_s64(x) +#define vreinterpret_u64_m64(x) vreinterpret_u64_s64(x) + +#define vreinterpret_s8_m64(x) vreinterpret_s8_s64(x) +#define vreinterpret_s16_m64(x) vreinterpret_s16_s64(x) +#define vreinterpret_s32_m64(x) vreinterpret_s32_s64(x) +#define vreinterpret_s64_m64(x) (x) + +#define vreinterpret_f32_m64(x) vreinterpret_f32_s64(x) + +#if defined(__aarch64__) +#define vreinterpretq_m128d_s32(x) vreinterpretq_f64_s32(x) +#define vreinterpretq_m128d_s64(x) vreinterpretq_f64_s64(x) + +#define vreinterpretq_m128d_u64(x) vreinterpretq_f64_u64(x) + +#define vreinterpretq_m128d_f32(x) vreinterpretq_f64_f32(x) +#define vreinterpretq_m128d_f64(x) (x) + +#define vreinterpretq_s64_m128d(x) vreinterpretq_s64_f64(x) + +#define vreinterpretq_u32_m128d(x) vreinterpretq_u32_f64(x) +#define vreinterpretq_u64_m128d(x) vreinterpretq_u64_f64(x) + +#define vreinterpretq_f64_m128d(x) (x) +#define vreinterpretq_f32_m128d(x) vreinterpretq_f32_f64(x) +#else +#define vreinterpretq_m128d_s32(x) vreinterpretq_f32_s32(x) +#define vreinterpretq_m128d_s64(x) vreinterpretq_f32_s64(x) + +#define vreinterpretq_m128d_u32(x) vreinterpretq_f32_u32(x) +#define vreinterpretq_m128d_u64(x) vreinterpretq_f32_u64(x) + +#define vreinterpretq_m128d_f32(x) (x) + +#define vreinterpretq_s64_m128d(x) vreinterpretq_s64_f32(x) + +#define vreinterpretq_u32_m128d(x) vreinterpretq_u32_f32(x) +#define vreinterpretq_u64_m128d(x) vreinterpretq_u64_f32(x) + +#define vreinterpretq_f32_m128d(x) (x) +#endif + +// A struct is defined in this header file called 'SIMDVec' which can be used +// by applications which attempt to access the contents of an __m128 struct +// directly. It is important to note that accessing the __m128 struct directly +// is bad coding practice by Microsoft: @see: +// https://docs.microsoft.com/en-us/cpp/cpp/m128 +// +// However, some legacy source code may try to access the contents of an __m128 +// struct directly so the developer can use the SIMDVec as an alias for it. Any +// casting must be done manually by the developer, as you cannot cast or +// otherwise alias the base NEON data type for intrinsic operations. +// +// union intended to allow direct access to an __m128 variable using the names +// that the MSVC compiler provides. This union should really only be used when +// trying to access the members of the vector as integer values. GCC/clang +// allow native access to the float members through a simple array access +// operator (in C since 4.6, in C++ since 4.8). +// +// Ideally direct accesses to SIMD vectors should not be used since it can cause +// a performance hit. If it really is needed however, the original __m128 +// variable can be aliased with a pointer to this union and used to access +// individual components. The use of this union should be hidden behind a macro +// that is used throughout the codebase to access the members instead of always +// declaring this type of variable. +typedef union ALIGN_STRUCT(16) SIMDVec { + float m128_f32[4]; // as floats - DON'T USE. Added for convenience. + int8_t m128_i8[16]; // as signed 8-bit integers. + int16_t m128_i16[8]; // as signed 16-bit integers. + int32_t m128_i32[4]; // as signed 32-bit integers. + int64_t m128_i64[2]; // as signed 64-bit integers. + uint8_t m128_u8[16]; // as unsigned 8-bit integers. + uint16_t m128_u16[8]; // as unsigned 16-bit integers. + uint32_t m128_u32[4]; // as unsigned 32-bit integers. + uint64_t m128_u64[2]; // as unsigned 64-bit integers. +} SIMDVec; + +// casting using SIMDVec +#define vreinterpretq_nth_u64_m128i(x, n) (((SIMDVec *) &x)->m128_u64[n]) +#define vreinterpretq_nth_u32_m128i(x, n) (((SIMDVec *) &x)->m128_u32[n]) +#define vreinterpretq_nth_u8_m128i(x, n) (((SIMDVec *) &x)->m128_u8[n]) + +/* SSE macros */ +#define _MM_GET_FLUSH_ZERO_MODE _sse2neon_mm_get_flush_zero_mode +#define _MM_SET_FLUSH_ZERO_MODE _sse2neon_mm_set_flush_zero_mode +#define _MM_GET_DENORMALS_ZERO_MODE _sse2neon_mm_get_denormals_zero_mode +#define _MM_SET_DENORMALS_ZERO_MODE _sse2neon_mm_set_denormals_zero_mode + +// Function declaration +// SSE +FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE(); +FORCE_INLINE __m128 _mm_move_ss(__m128, __m128); +FORCE_INLINE __m128 _mm_or_ps(__m128, __m128); +FORCE_INLINE __m128 _mm_set_ps1(float); +FORCE_INLINE __m128 _mm_setzero_ps(void); +// SSE2 +FORCE_INLINE __m128i _mm_and_si128(__m128i, __m128i); +FORCE_INLINE __m128i _mm_castps_si128(__m128); +FORCE_INLINE __m128i _mm_cmpeq_epi32(__m128i, __m128i); +FORCE_INLINE __m128i _mm_cvtps_epi32(__m128); +FORCE_INLINE __m128d _mm_move_sd(__m128d, __m128d); +FORCE_INLINE __m128i _mm_or_si128(__m128i, __m128i); +FORCE_INLINE __m128i _mm_set_epi32(int, int, int, int); +FORCE_INLINE __m128i _mm_set_epi64x(int64_t, int64_t); +FORCE_INLINE __m128d _mm_set_pd(double, double); +FORCE_INLINE __m128i _mm_set1_epi32(int); +FORCE_INLINE __m128i _mm_setzero_si128(); +// SSE4.1 +FORCE_INLINE __m128d _mm_ceil_pd(__m128d); +FORCE_INLINE __m128 _mm_ceil_ps(__m128); +FORCE_INLINE __m128d _mm_floor_pd(__m128d); +FORCE_INLINE __m128 _mm_floor_ps(__m128); +FORCE_INLINE __m128d _mm_round_pd(__m128d, int); +FORCE_INLINE __m128 _mm_round_ps(__m128, int); +// SSE4.2 +FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t, uint8_t); + +/* Backwards compatibility for compilers with lack of specific type support */ + +// Older gcc does not define vld1q_u8_x4 type +#if defined(__GNUC__) && !defined(__clang__) && \ + ((__GNUC__ <= 10 && defined(__arm__)) || \ + (__GNUC__ == 10 && __GNUC_MINOR__ < 3 && defined(__aarch64__)) || \ + (__GNUC__ <= 9 && defined(__aarch64__))) +FORCE_INLINE uint8x16x4_t _sse2neon_vld1q_u8_x4(const uint8_t *p) +{ + uint8x16x4_t ret; + ret.val[0] = vld1q_u8(p + 0); + ret.val[1] = vld1q_u8(p + 16); + ret.val[2] = vld1q_u8(p + 32); + ret.val[3] = vld1q_u8(p + 48); + return ret; +} +#else +// Wraps vld1q_u8_x4 +FORCE_INLINE uint8x16x4_t _sse2neon_vld1q_u8_x4(const uint8_t *p) +{ + return vld1q_u8_x4(p); +} +#endif + +/* Function Naming Conventions + * The naming convention of SSE intrinsics is straightforward. A generic SSE + * intrinsic function is given as follows: + * _mm__ + * + * The parts of this format are given as follows: + * 1. describes the operation performed by the intrinsic + * 2. identifies the data type of the function's primary arguments + * + * This last part, , is a little complicated. It identifies the + * content of the input values, and can be set to any of the following values: + * + ps - vectors contain floats (ps stands for packed single-precision) + * + pd - vectors cantain doubles (pd stands for packed double-precision) + * + epi8/epi16/epi32/epi64 - vectors contain 8-bit/16-bit/32-bit/64-bit + * signed integers + * + epu8/epu16/epu32/epu64 - vectors contain 8-bit/16-bit/32-bit/64-bit + * unsigned integers + * + si128 - unspecified 128-bit vector or 256-bit vector + * + m128/m128i/m128d - identifies input vector types when they are different + * than the type of the returned vector + * + * For example, _mm_setzero_ps. The _mm implies that the function returns + * a 128-bit vector. The _ps at the end implies that the argument vectors + * contain floats. + * + * A complete example: Byte Shuffle - pshufb (_mm_shuffle_epi8) + * // Set packed 16-bit integers. 128 bits, 8 short, per 16 bits + * __m128i v_in = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8); + * // Set packed 8-bit integers + * // 128 bits, 16 chars, per 8 bits + * __m128i v_perm = _mm_setr_epi8(1, 0, 2, 3, 8, 9, 10, 11, + * 4, 5, 12, 13, 6, 7, 14, 15); + * // Shuffle packed 8-bit integers + * __m128i v_out = _mm_shuffle_epi8(v_in, v_perm); // pshufb + * + * Data (Number, Binary, Byte Index): + +------+------+-------------+------+------+-------------+ + | 1 | 2 | 3 | 4 | Number + +------+------+------+------+------+------+------+------+ + | 0000 | 0001 | 0000 | 0010 | 0000 | 0011 | 0000 | 0100 | Binary + +------+------+------+------+------+------+------+------+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 5 | 6 | 7 | 8 | Number + +------+------+------+------+------+------+------+------+ + | 0000 | 0101 | 0000 | 0110 | 0000 | 0111 | 0000 | 1000 | Binary + +------+------+------+------+------+------+------+------+ + | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Index + +------+------+------+------+------+------+------+------+ + * Index (Byte Index): + +------+------+------+------+------+------+------+------+ + | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | + +------+------+------+------+------+------+------+------+ + * Result: + +------+------+------+------+------+------+------+------+ + | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | Index + +------+------+------+------+------+------+------+------+ + | 0001 | 0000 | 0000 | 0010 | 0000 | 0101 | 0000 | 0110 | Binary + +------+------+------+------+------+------+------+------+ + | 256 | 2 | 5 | 6 | Number + +------+------+------+------+------+------+------+------+ + + +------+------+------+------+------+------+------+------+ + | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | Index + +------+------+------+------+------+------+------+------+ + | 0000 | 0011 | 0000 | 0111 | 0000 | 0100 | 0000 | 1000 | Binary + +------+------+------+------+------+------+------+------+ + | 3 | 7 | 4 | 8 | Number + +------+------+------+------+------+------+-------------+ + */ + +/* Constants for use with _mm_prefetch. */ +enum _mm_hint { + _MM_HINT_NTA = 0, /* load data to L1 and L2 cache, mark it as NTA */ + _MM_HINT_T0 = 1, /* load data to L1 and L2 cache */ + _MM_HINT_T1 = 2, /* load data to L2 cache only */ + _MM_HINT_T2 = 3, /* load data to L2 cache only, mark it as NTA */ + _MM_HINT_ENTA = 4, /* exclusive version of _MM_HINT_NTA */ + _MM_HINT_ET0 = 5, /* exclusive version of _MM_HINT_T0 */ + _MM_HINT_ET1 = 6, /* exclusive version of _MM_HINT_T1 */ + _MM_HINT_ET2 = 7 /* exclusive version of _MM_HINT_T2 */ +}; + +// The bit field mapping to the FPCR(floating-point control register) +typedef struct { + uint16_t res0; + uint8_t res1 : 6; + uint8_t bit22 : 1; + uint8_t bit23 : 1; + uint8_t bit24 : 1; + uint8_t res2 : 7; +#if defined(__aarch64__) + uint32_t res3; +#endif +} fpcr_bitfield; + +// Takes the upper 64 bits of a and places it in the low end of the result +// Takes the lower 64 bits of b and places it into the high end of the result. +FORCE_INLINE __m128 _mm_shuffle_ps_1032(__m128 a, __m128 b) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a32, b10)); +} + +// takes the lower two 32-bit values from a and swaps them and places in high +// end of result takes the higher two 32 bit values from b and swaps them and +// places in low end of result. +FORCE_INLINE __m128 _mm_shuffle_ps_2301(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b23 = vrev64_f32(vget_high_f32(vreinterpretq_f32_m128(b))); + return vreinterpretq_m128_f32(vcombine_f32(a01, b23)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0321(__m128 a, __m128 b) +{ + float32x2_t a21 = vget_high_f32( + vextq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 3)); + float32x2_t b03 = vget_low_f32( + vextq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b), 3)); + return vreinterpretq_m128_f32(vcombine_f32(a21, b03)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2103(__m128 a, __m128 b) +{ + float32x2_t a03 = vget_low_f32( + vextq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 3)); + float32x2_t b21 = vget_high_f32( + vextq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b), 3)); + return vreinterpretq_m128_f32(vcombine_f32(a03, b21)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1010(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b10)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1001(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a01, b10)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0101(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32x2_t b01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(b))); + return vreinterpretq_m128_f32(vcombine_f32(a01, b01)); +} + +// keeps the low 64 bits of b in the low and puts the high 64 bits of a in the +// high +FORCE_INLINE __m128 _mm_shuffle_ps_3210(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b32)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0011(__m128 a, __m128 b) +{ + float32x2_t a11 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(a)), 1); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a11, b00)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_0022(__m128 a, __m128 b) +{ + float32x2_t a22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a22, b00)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2200(__m128 a, __m128 b) +{ + float32x2_t a00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t b22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(b)), 0); + return vreinterpretq_m128_f32(vcombine_f32(a00, b22)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_3202(__m128 a, __m128 b) +{ + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32x2_t a22 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 0); + float32x2_t a02 = vset_lane_f32(a0, a22, 1); /* TODO: use vzip ?*/ + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(a02, b32)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_1133(__m128 a, __m128 b) +{ + float32x2_t a33 = + vdup_lane_f32(vget_high_f32(vreinterpretq_f32_m128(a)), 1); + float32x2_t b11 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 1); + return vreinterpretq_m128_f32(vcombine_f32(a33, b11)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2010(__m128 a, __m128 b) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32_t b2 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a10, b20)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2001(__m128 a, __m128 b) +{ + float32x2_t a01 = vrev64_f32(vget_low_f32(vreinterpretq_f32_m128(a))); + float32_t b2 = vgetq_lane_f32(b, 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a01, b20)); +} + +FORCE_INLINE __m128 _mm_shuffle_ps_2032(__m128 a, __m128 b) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32_t b2 = vgetq_lane_f32(b, 2); + float32x2_t b00 = vdup_lane_f32(vget_low_f32(vreinterpretq_f32_m128(b)), 0); + float32x2_t b20 = vset_lane_f32(b2, b00, 1); + return vreinterpretq_m128_f32(vcombine_f32(a32, b20)); +} + +// Kahan summation for accurate summation of floating-point numbers. +// http://blog.zachbjornson.com/2019/08/11/fast-float-summation.html +FORCE_INLINE void _sse2neon_kadd_f32(float *sum, float *c, float y) +{ + y -= *c; + float t = *sum + y; + *c = (t - *sum) - y; + *sum = t; +} + +#if defined(__ARM_FEATURE_CRYPTO) +// Wraps vmull_p64 +FORCE_INLINE uint64x2_t _sse2neon_vmull_p64(uint64x1_t _a, uint64x1_t _b) +{ + poly64_t a = vget_lane_p64(vreinterpret_p64_u64(_a), 0); + poly64_t b = vget_lane_p64(vreinterpret_p64_u64(_b), 0); + return vreinterpretq_u64_p128(vmull_p64(a, b)); +} +#else // ARMv7 polyfill +// ARMv7/some A64 lacks vmull_p64, but it has vmull_p8. +// +// vmull_p8 calculates 8 8-bit->16-bit polynomial multiplies, but we need a +// 64-bit->128-bit polynomial multiply. +// +// It needs some work and is somewhat slow, but it is still faster than all +// known scalar methods. +// +// Algorithm adapted to C from +// https://www.workofard.com/2017/07/ghash-for-low-end-cores/, which is adapted +// from "Fast Software Polynomial Multiplication on ARM Processors Using the +// NEON Engine" by Danilo Camara, Conrado Gouvea, Julio Lopez and Ricardo Dahab +// (https://hal.inria.fr/hal-01506572) +static uint64x2_t _sse2neon_vmull_p64(uint64x1_t _a, uint64x1_t _b) +{ + poly8x8_t a = vreinterpret_p8_u64(_a); + poly8x8_t b = vreinterpret_p8_u64(_b); + + // Masks + uint8x16_t k48_32 = vcombine_u8(vcreate_u8(0x0000ffffffffffff), + vcreate_u8(0x00000000ffffffff)); + uint8x16_t k16_00 = vcombine_u8(vcreate_u8(0x000000000000ffff), + vcreate_u8(0x0000000000000000)); + + // Do the multiplies, rotating with vext to get all combinations + uint8x16_t d = vreinterpretq_u8_p16(vmull_p8(a, b)); // D = A0 * B0 + uint8x16_t e = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 1))); // E = A0 * B1 + uint8x16_t f = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 1), b)); // F = A1 * B0 + uint8x16_t g = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 2))); // G = A0 * B2 + uint8x16_t h = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 2), b)); // H = A2 * B0 + uint8x16_t i = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 3))); // I = A0 * B3 + uint8x16_t j = + vreinterpretq_u8_p16(vmull_p8(vext_p8(a, a, 3), b)); // J = A3 * B0 + uint8x16_t k = + vreinterpretq_u8_p16(vmull_p8(a, vext_p8(b, b, 4))); // L = A0 * B4 + + // Add cross products + uint8x16_t l = veorq_u8(e, f); // L = E + F + uint8x16_t m = veorq_u8(g, h); // M = G + H + uint8x16_t n = veorq_u8(i, j); // N = I + J + + // Interleave. Using vzip1 and vzip2 prevents Clang from emitting TBL + // instructions. +#if defined(__aarch64__) + uint8x16_t lm_p0 = vreinterpretq_u8_u64( + vzip1q_u64(vreinterpretq_u64_u8(l), vreinterpretq_u64_u8(m))); + uint8x16_t lm_p1 = vreinterpretq_u8_u64( + vzip2q_u64(vreinterpretq_u64_u8(l), vreinterpretq_u64_u8(m))); + uint8x16_t nk_p0 = vreinterpretq_u8_u64( + vzip1q_u64(vreinterpretq_u64_u8(n), vreinterpretq_u64_u8(k))); + uint8x16_t nk_p1 = vreinterpretq_u8_u64( + vzip2q_u64(vreinterpretq_u64_u8(n), vreinterpretq_u64_u8(k))); +#else + uint8x16_t lm_p0 = vcombine_u8(vget_low_u8(l), vget_low_u8(m)); + uint8x16_t lm_p1 = vcombine_u8(vget_high_u8(l), vget_high_u8(m)); + uint8x16_t nk_p0 = vcombine_u8(vget_low_u8(n), vget_low_u8(k)); + uint8x16_t nk_p1 = vcombine_u8(vget_high_u8(n), vget_high_u8(k)); +#endif + // t0 = (L) (P0 + P1) << 8 + // t1 = (M) (P2 + P3) << 16 + uint8x16_t t0t1_tmp = veorq_u8(lm_p0, lm_p1); + uint8x16_t t0t1_h = vandq_u8(lm_p1, k48_32); + uint8x16_t t0t1_l = veorq_u8(t0t1_tmp, t0t1_h); + + // t2 = (N) (P4 + P5) << 24 + // t3 = (K) (P6 + P7) << 32 + uint8x16_t t2t3_tmp = veorq_u8(nk_p0, nk_p1); + uint8x16_t t2t3_h = vandq_u8(nk_p1, k16_00); + uint8x16_t t2t3_l = veorq_u8(t2t3_tmp, t2t3_h); + + // De-interleave +#if defined(__aarch64__) + uint8x16_t t0 = vreinterpretq_u8_u64( + vuzp1q_u64(vreinterpretq_u64_u8(t0t1_l), vreinterpretq_u64_u8(t0t1_h))); + uint8x16_t t1 = vreinterpretq_u8_u64( + vuzp2q_u64(vreinterpretq_u64_u8(t0t1_l), vreinterpretq_u64_u8(t0t1_h))); + uint8x16_t t2 = vreinterpretq_u8_u64( + vuzp1q_u64(vreinterpretq_u64_u8(t2t3_l), vreinterpretq_u64_u8(t2t3_h))); + uint8x16_t t3 = vreinterpretq_u8_u64( + vuzp2q_u64(vreinterpretq_u64_u8(t2t3_l), vreinterpretq_u64_u8(t2t3_h))); +#else + uint8x16_t t1 = vcombine_u8(vget_high_u8(t0t1_l), vget_high_u8(t0t1_h)); + uint8x16_t t0 = vcombine_u8(vget_low_u8(t0t1_l), vget_low_u8(t0t1_h)); + uint8x16_t t3 = vcombine_u8(vget_high_u8(t2t3_l), vget_high_u8(t2t3_h)); + uint8x16_t t2 = vcombine_u8(vget_low_u8(t2t3_l), vget_low_u8(t2t3_h)); +#endif + // Shift the cross products + uint8x16_t t0_shift = vextq_u8(t0, t0, 15); // t0 << 8 + uint8x16_t t1_shift = vextq_u8(t1, t1, 14); // t1 << 16 + uint8x16_t t2_shift = vextq_u8(t2, t2, 13); // t2 << 24 + uint8x16_t t3_shift = vextq_u8(t3, t3, 12); // t3 << 32 + + // Accumulate the products + uint8x16_t cross1 = veorq_u8(t0_shift, t1_shift); + uint8x16_t cross2 = veorq_u8(t2_shift, t3_shift); + uint8x16_t mix = veorq_u8(d, cross1); + uint8x16_t r = veorq_u8(mix, cross2); + return vreinterpretq_u64_u8(r); +} +#endif // ARMv7 polyfill + +// C equivalent: +// __m128i _mm_shuffle_epi32_default(__m128i a, +// __constrange(0, 255) int imm) { +// __m128i ret; +// ret[0] = a[imm & 0x3]; ret[1] = a[(imm >> 2) & 0x3]; +// ret[2] = a[(imm >> 4) & 0x03]; ret[3] = a[(imm >> 6) & 0x03]; +// return ret; +// } +#define _mm_shuffle_epi32_default(a, imm) \ + __extension__({ \ + int32x4_t ret; \ + ret = vmovq_n_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm) & (0x3))); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 2) & 0x3), \ + ret, 1); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 4) & 0x3), \ + ret, 2); \ + ret = vsetq_lane_s32( \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), ((imm) >> 6) & 0x3), \ + ret, 3); \ + vreinterpretq_m128i_s32(ret); \ + }) + +// Takes the upper 64 bits of a and places it in the low end of the result +// Takes the lower 64 bits of a and places it into the high end of the result. +FORCE_INLINE __m128i _mm_shuffle_epi_1032(__m128i a) +{ + int32x2_t a32 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a32, a10)); +} + +// takes the lower two 32-bit values from a and swaps them and places in low end +// of result takes the higher two 32 bit values from a and swaps them and places +// in high end of result. +FORCE_INLINE __m128i _mm_shuffle_epi_2301(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + int32x2_t a23 = vrev64_s32(vget_high_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a23)); +} + +// rotates the least significant 32 bits into the most significant 32 bits, and +// shifts the rest down +FORCE_INLINE __m128i _mm_shuffle_epi_0321(__m128i a) +{ + return vreinterpretq_m128i_s32( + vextq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(a), 1)); +} + +// rotates the most significant 32 bits into the least significant 32 bits, and +// shifts the rest up +FORCE_INLINE __m128i _mm_shuffle_epi_2103(__m128i a) +{ + return vreinterpretq_m128i_s32( + vextq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(a), 3)); +} + +// gets the lower 64 bits of a, and places it in the upper 64 bits +// gets the lower 64 bits of a and places it in the lower 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_1010(__m128i a) +{ + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a10, a10)); +} + +// gets the lower 64 bits of a, swaps the 0 and 1 elements, and places it in the +// lower 64 bits gets the lower 64 bits of a, and places it in the upper 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_1001(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + int32x2_t a10 = vget_low_s32(vreinterpretq_s32_m128i(a)); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a10)); +} + +// gets the lower 64 bits of a, swaps the 0 and 1 elements and places it in the +// upper 64 bits gets the lower 64 bits of a, swaps the 0 and 1 elements, and +// places it in the lower 64 bits +FORCE_INLINE __m128i _mm_shuffle_epi_0101(__m128i a) +{ + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a01, a01)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_2211(__m128i a) +{ + int32x2_t a11 = vdup_lane_s32(vget_low_s32(vreinterpretq_s32_m128i(a)), 1); + int32x2_t a22 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 0); + return vreinterpretq_m128i_s32(vcombine_s32(a11, a22)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_0122(__m128i a) +{ + int32x2_t a22 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 0); + int32x2_t a01 = vrev64_s32(vget_low_s32(vreinterpretq_s32_m128i(a))); + return vreinterpretq_m128i_s32(vcombine_s32(a22, a01)); +} + +FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) +{ + int32x2_t a32 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t a33 = vdup_lane_s32(vget_high_s32(vreinterpretq_s32_m128i(a)), 1); + return vreinterpretq_m128i_s32(vcombine_s32(a32, a33)); +} + +// FORCE_INLINE __m128i _mm_shuffle_epi32_splat(__m128i a, __constrange(0,255) +// int imm) +#if defined(__aarch64__) +#define _mm_shuffle_epi32_splat(a, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vdupq_laneq_s32(vreinterpretq_s32_m128i(a), (imm))); \ + }) +#else +#define _mm_shuffle_epi32_splat(a, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vdupq_n_s32(vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm)))); \ + }) +#endif + +// NEON does not support a general purpose permute intrinsic +// Selects four specific single-precision, floating-point values from a and b, +// based on the mask i. +// +// C equivalent: +// __m128 _mm_shuffle_ps_default(__m128 a, __m128 b, +// __constrange(0, 255) int imm) { +// __m128 ret; +// ret[0] = a[imm & 0x3]; ret[1] = a[(imm >> 2) & 0x3]; +// ret[2] = b[(imm >> 4) & 0x03]; ret[3] = b[(imm >> 6) & 0x03]; +// return ret; +// } +// +// https://msdn.microsoft.com/en-us/library/vstudio/5f0858x0(v=vs.100).aspx +#define _mm_shuffle_ps_default(a, b, imm) \ + __extension__({ \ + float32x4_t ret; \ + ret = vmovq_n_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(a), (imm) & (0x3))); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(a), ((imm) >> 2) & 0x3), \ + ret, 1); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(b), ((imm) >> 4) & 0x3), \ + ret, 2); \ + ret = vsetq_lane_f32( \ + vgetq_lane_f32(vreinterpretq_f32_m128(b), ((imm) >> 6) & 0x3), \ + ret, 3); \ + vreinterpretq_m128_f32(ret); \ + }) + +// Shuffles the lower 4 signed or unsigned 16-bit integers in a as specified +// by imm. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/y41dkk37(v=vs.100) +// FORCE_INLINE __m128i _mm_shufflelo_epi16_function(__m128i a, +// __constrange(0,255) int +// imm) +#define _mm_shufflelo_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ + int16x4_t lowBits = vget_low_s16(ret); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, (imm) & (0x3)), ret, 0); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 2) & 0x3), ret, \ + 1); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 4) & 0x3), ret, \ + 2); \ + ret = vsetq_lane_s16(vget_lane_s16(lowBits, ((imm) >> 6) & 0x3), ret, \ + 3); \ + vreinterpretq_m128i_s16(ret); \ + }) + +// Shuffles the upper 4 signed or unsigned 16-bit integers in a as specified +// by imm. +// https://msdn.microsoft.com/en-us/library/13ywktbs(v=vs.100).aspx +// FORCE_INLINE __m128i _mm_shufflehi_epi16_function(__m128i a, +// __constrange(0,255) int +// imm) +#define _mm_shufflehi_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ + int16x4_t highBits = vget_high_s16(ret); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, (imm) & (0x3)), ret, 4); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 2) & 0x3), ret, \ + 5); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 4) & 0x3), ret, \ + 6); \ + ret = vsetq_lane_s16(vget_lane_s16(highBits, ((imm) >> 6) & 0x3), ret, \ + 7); \ + vreinterpretq_m128i_s16(ret); \ + }) + +/* MMX */ + +//_mm_empty is a no-op on arm +FORCE_INLINE void _mm_empty(void) {} + +/* SSE */ + +// Adds the four single-precision, floating-point values of a and b. +// +// r0 := a0 + b0 +// r1 := a1 + b1 +// r2 := a2 + b2 +// r3 := a3 + b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/c9848chc(v=vs.100).aspx +FORCE_INLINE __m128 _mm_add_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// adds the scalar single-precision floating point values of a and b. +// https://msdn.microsoft.com/en-us/library/be94x2y6(v=vs.100).aspx +FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b) +{ + float32_t b0 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 0); + float32x4_t value = vsetq_lane_f32(b0, vdupq_n_f32(0), 0); + // the upper values in the result must be the remnants of . + return vreinterpretq_m128_f32(vaddq_f32(a, value)); +} + +// Computes the bitwise AND of the four single-precision, floating-point values +// of a and b. +// +// r0 := a0 & b0 +// r1 := a1 & b1 +// r2 := a2 & b2 +// r3 := a3 & b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/73ck1xc5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_and_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vandq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +// Computes the bitwise AND-NOT of the four single-precision, floating-point +// values of a and b. +// +// r0 := ~a0 & b0 +// r1 := ~a1 & b1 +// r2 := ~a2 & b2 +// r3 := ~a3 & b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/68h7wd02(v=vs.100).aspx +FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vbicq_s32(vreinterpretq_s32_m128(b), + vreinterpretq_s32_m128(a))); // *NOTE* argument swap +} + +// Average packed unsigned 16-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu16 +FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b) +{ + return vreinterpret_m64_u16( + vrhadd_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b))); +} + +// Average packed unsigned 8-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu8 +FORCE_INLINE __m64 _mm_avg_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vrhadd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Compares for equality. +// https://msdn.microsoft.com/en-us/library/vstudio/36aectz5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpeq_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for equality. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/k423z28e(v=vs.100) +FORCE_INLINE __m128 _mm_cmpeq_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpeq_ps(a, b)); +} + +// Compares for greater than or equal. +// https://msdn.microsoft.com/en-us/library/vstudio/fs813y2t(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpge_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/kesh3ddc(v=vs.100) +FORCE_INLINE __m128 _mm_cmpge_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpge_ps(a, b)); +} + +// Compares for greater than. +// +// r0 := (a0 > b0) ? 0xffffffff : 0x0 +// r1 := (a1 > b1) ? 0xffffffff : 0x0 +// r2 := (a2 > b2) ? 0xffffffff : 0x0 +// r3 := (a3 > b3) ? 0xffffffff : 0x0 +// +// https://msdn.microsoft.com/en-us/library/vstudio/11dy102s(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpgt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/1xyyyy9e(v=vs.100) +FORCE_INLINE __m128 _mm_cmpgt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpgt_ps(a, b)); +} + +// Compares for less than or equal. +// +// r0 := (a0 <= b0) ? 0xffffffff : 0x0 +// r1 := (a1 <= b1) ? 0xffffffff : 0x0 +// r2 := (a2 <= b2) ? 0xffffffff : 0x0 +// r3 := (a3 <= b3) ? 0xffffffff : 0x0 +// +// https://msdn.microsoft.com/en-us/library/vstudio/1s75w83z(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmple_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/a7x0hbhw(v=vs.100) +FORCE_INLINE __m128 _mm_cmple_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmple_ps(a, b)); +} + +// Compares for less than +// https://msdn.microsoft.com/en-us/library/vstudio/f330yhc8(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmplt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32( + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Compares for less than +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fy94wye7(v=vs.100) +FORCE_INLINE __m128 _mm_cmplt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmplt_ps(a, b)); +} + +// Compares for inequality. +// https://msdn.microsoft.com/en-us/library/sf44thbx(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cmpneq_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for inequality. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ekya8fh4(v=vs.100) +FORCE_INLINE __m128 _mm_cmpneq_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpneq_ps(a, b)); +} + +// Compares for not greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/wsexys62(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnge_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for not greater than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fk2y80s8(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnge_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpnge_ps(a, b)); +} + +// Compares for not greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/d0xh7w0s(v=vs.100) +FORCE_INLINE __m128 _mm_cmpngt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for not greater than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) +FORCE_INLINE __m128 _mm_cmpngt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpngt_ps(a, b)); +} + +// Compares for not less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/6a330kxw(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnle_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for not less than or equal. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnle_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpnle_ps(a, b)); +} + +// Compares for not less than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/4686bbdw(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnlt_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_u32(vmvnq_u32( + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); +} + +// Compares for not less than. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/56b9z2wf(v=vs.100) +FORCE_INLINE __m128 _mm_cmpnlt_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpnlt_ps(a, b)); +} + +// Compares the four 32-bit floats in a and b to check if any values are NaN. +// Ordered compare between each value returns true for "orderable" and false for +// "not orderable" (NaN). +// https://msdn.microsoft.com/en-us/library/vstudio/0h9w00fx(v=vs.100).aspx see +// also: +// http://stackoverflow.com/questions/8627331/what-does-ordered-unordered-comparison-mean +// http://stackoverflow.com/questions/29349621/neon-isnanval-intrinsics +FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b) +{ + // Note: NEON does not have ordered compare builtin + // Need to compare a eq a and b eq b to check for NaN + // Do AND of results to get final + uint32x4_t ceqaa = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t ceqbb = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_u32(vandq_u32(ceqaa, ceqbb)); +} + +// Compares for ordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/343t62da(v=vs.100) +FORCE_INLINE __m128 _mm_cmpord_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpord_ps(a, b)); +} + +// Compares for unordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/khy6fk1t(v=vs.100) +FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b) +{ + uint32x4_t f32a = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a)); + uint32x4_t f32b = + vceqq_f32(vreinterpretq_f32_m128(b), vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_u32(vmvnq_u32(vandq_u32(f32a, f32b))); +} + +// Compares for unordered. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/2as2387b(v=vs.100) +FORCE_INLINE __m128 _mm_cmpunord_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_cmpunord_ps(a, b)); +} + +// Compares the lower single-precision floating point scalar values of a and b +// using an equality operation. : +// https://msdn.microsoft.com/en-us/library/93yx2h2b(v=vs.100).aspx +FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b) +{ + uint32x4_t a_eq_b = + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return vgetq_lane_u32(a_eq_b, 0) & 0x1; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a greater than or equal operation. : +// https://msdn.microsoft.com/en-us/library/8t80des6(v=vs.100).aspx +FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b) +{ + uint32x4_t a_ge_b = + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return vgetq_lane_u32(a_ge_b, 0) & 0x1; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a greater than operation. : +// https://msdn.microsoft.com/en-us/library/b0738e0t(v=vs.100).aspx +FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b) +{ + uint32x4_t a_gt_b = + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return vgetq_lane_u32(a_gt_b, 0) & 0x1; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a less than or equal operation. : +// https://msdn.microsoft.com/en-us/library/1w4t7c57(v=vs.90).aspx +FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b) +{ + uint32x4_t a_le_b = + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return vgetq_lane_u32(a_le_b, 0) & 0x1; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using a less than operation. : +// https://msdn.microsoft.com/en-us/library/2kwe606b(v=vs.90).aspx Important +// note!! The documentation on MSDN is incorrect! If either of the values is a +// NAN the docs say you will get a one, but in fact, it will return a zero!! +FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b) +{ + uint32x4_t a_lt_b = + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)); + return vgetq_lane_u32(a_lt_b, 0) & 0x1; +} + +// Compares the lower single-precision floating point scalar values of a and b +// using an inequality operation. : +// https://msdn.microsoft.com/en-us/library/bafh5e0a(v=vs.90).aspx +FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b) +{ + return !_mm_comieq_ss(a, b); +} + +// Convert packed signed 32-bit integers in b to packed single-precision +// (32-bit) floating-point elements, store the results in the lower 2 elements +// of dst, and copy the upper 2 packed elements from a to the upper elements of +// dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +// dst[95:64] := a[95:64] +// dst[127:96] := a[127:96] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_pi2ps +FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vcvt_f32_s32(vreinterpret_s32_m64(b)), + vget_high_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ps2pi +FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpret_m64_s32( + vget_low_s32(vcvtnq_s32_f32(vrndiq_f32(vreinterpretq_f32_m128(a))))); +#else + return vreinterpret_m64_s32(vcvt_s32_f32(vget_low_f32( + vreinterpretq_f32_m128(_mm_round_ps(a, _MM_FROUND_CUR_DIRECTION))))); +#endif +} + +// Convert the signed 32-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_si2ss +FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32((float) b, vreinterpretq_f32_m128(a), 0)); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer, and store the result in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ss2si +FORCE_INLINE int _mm_cvt_ss2si(__m128 a) +{ +#if defined(__aarch64__) + return vgetq_lane_s32(vcvtnq_s32_f32(vrndiq_f32(vreinterpretq_f32_m128(a))), + 0); +#else + float32_t data = vgetq_lane_f32( + vreinterpretq_f32_m128(_mm_round_ps(a, _MM_FROUND_CUR_DIRECTION)), 0); + return (int32_t) data; +#endif +} + +// Convert packed 16-bit integers in a to packed single-precision (32-bit) +// floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// m := j*32 +// dst[m+31:m] := Convert_Int16_To_FP32(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi16_ps +FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a) +{ + return vreinterpretq_m128_f32( + vcvtq_f32_s32(vmovl_s16(vreinterpret_s16_m64(a)))); +} + +// Convert packed 32-bit integers in b to packed single-precision (32-bit) +// floating-point elements, store the results in the lower 2 elements of dst, +// and copy the upper 2 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +// dst[95:64] := a[95:64] +// dst[127:96] := a[127:96] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_ps +FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vcvt_f32_s32(vreinterpret_s32_m64(b)), + vget_high_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert packed signed 32-bit integers in a to packed single-precision +// (32-bit) floating-point elements, store the results in the lower 2 elements +// of dst, then covert the packed signed 32-bit integers in b to +// single-precision (32-bit) floating-point element, and store the results in +// the upper 2 elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(a[31:0]) +// dst[63:32] := Convert_Int32_To_FP32(a[63:32]) +// dst[95:64] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:96] := Convert_Int32_To_FP32(b[63:32]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32x2_ps +FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32( + vcombine_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b)))); +} + +// Convert the lower packed 8-bit integers in a to packed single-precision +// (32-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*8 +// m := j*32 +// dst[m+31:m] := Convert_Int8_To_FP32(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi8_ps +FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32( + vmovl_s16(vget_low_s16(vmovl_s8(vreinterpret_s8_m64(a)))))); +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 16-bit integers, and store the results in dst. Note: this intrinsic +// will generate 0x7FFF, rather than 0x8000, for input values between 0x7FFF and +// 0x7FFFFFFF. +// +// FOR j := 0 to 3 +// i := 16*j +// k := 32*j +// IF a[k+31:k] >= FP32(0x7FFF) && a[k+31:k] <= FP32(0x7FFFFFFF) +// dst[i+15:i] := 0x7FFF +// ELSE +// dst[i+15:i] := Convert_FP32_To_Int16(a[k+31:k]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi16 +FORCE_INLINE __m64 _mm_cvtps_pi16(__m128 a) +{ + const __m128 i16Min = _mm_set_ps1((float) INT16_MIN); + const __m128 i16Max = _mm_set_ps1((float) INT16_MAX); + const __m128 i32Max = _mm_set_ps1((float) INT32_MAX); + const __m128i maxMask = _mm_castps_si128( + _mm_and_ps(_mm_cmpge_ps(a, i16Max), _mm_cmple_ps(a, i32Max))); + const __m128i betweenMask = _mm_castps_si128( + _mm_and_ps(_mm_cmpgt_ps(a, i16Min), _mm_cmplt_ps(a, i16Max))); + const __m128i minMask = _mm_cmpeq_epi32(_mm_or_si128(maxMask, betweenMask), + _mm_setzero_si128()); + __m128i max = _mm_and_si128(maxMask, _mm_set1_epi32(INT16_MAX)); + __m128i min = _mm_and_si128(minMask, _mm_set1_epi32(INT16_MIN)); + __m128i cvt = _mm_and_si128(betweenMask, _mm_cvtps_epi32(a)); + __m128i res32 = _mm_or_si128(_mm_or_si128(max, min), cvt); + return vreinterpret_m64_s16(vmovn_s32(vreinterpretq_s32_m128i(res32))); +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi32 +#define _mm_cvtps_pi32(a) _mm_cvt_ps2pi(a) + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 8-bit integers, and store the results in lower 4 elements of dst. +// Note: this intrinsic will generate 0x7F, rather than 0x80, for input values +// between 0x7F and 0x7FFFFFFF. +// +// FOR j := 0 to 3 +// i := 8*j +// k := 32*j +// IF a[k+31:k] >= FP32(0x7F) && a[k+31:k] <= FP32(0x7FFFFFFF) +// dst[i+7:i] := 0x7F +// ELSE +// dst[i+7:i] := Convert_FP32_To_Int8(a[k+31:k]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi8 +FORCE_INLINE __m64 _mm_cvtps_pi8(__m128 a) +{ + const __m128 i8Min = _mm_set_ps1((float) INT8_MIN); + const __m128 i8Max = _mm_set_ps1((float) INT8_MAX); + const __m128 i32Max = _mm_set_ps1((float) INT32_MAX); + const __m128i maxMask = _mm_castps_si128( + _mm_and_ps(_mm_cmpge_ps(a, i8Max), _mm_cmple_ps(a, i32Max))); + const __m128i betweenMask = _mm_castps_si128( + _mm_and_ps(_mm_cmpgt_ps(a, i8Min), _mm_cmplt_ps(a, i8Max))); + const __m128i minMask = _mm_cmpeq_epi32(_mm_or_si128(maxMask, betweenMask), + _mm_setzero_si128()); + __m128i max = _mm_and_si128(maxMask, _mm_set1_epi32(INT8_MAX)); + __m128i min = _mm_and_si128(minMask, _mm_set1_epi32(INT8_MIN)); + __m128i cvt = _mm_and_si128(betweenMask, _mm_cvtps_epi32(a)); + __m128i res32 = _mm_or_si128(_mm_or_si128(max, min), cvt); + int16x4_t res16 = vmovn_s32(vreinterpretq_s32_m128i(res32)); + int8x8_t res8 = vmovn_s16(vcombine_s16(res16, res16)); + uint32_t bitMask[2] = {0xFFFFFFFF, 0}; + int8x8_t mask = vreinterpret_s8_u32(vld1_u32(bitMask)); + + return vreinterpret_m64_s8(vorr_s8(vand_s8(mask, res8), vdup_n_s8(0))); +} + +// Convert packed unsigned 16-bit integers in a to packed single-precision +// (32-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// m := j*32 +// dst[m+31:m] := Convert_UInt16_To_FP32(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu16_ps +FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a) +{ + return vreinterpretq_m128_f32( + vcvtq_f32_u32(vmovl_u16(vreinterpret_u16_m64(a)))); +} + +// Convert the lower packed unsigned 8-bit integers in a to packed +// single-precision (32-bit) floating-point elements, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*8 +// m := j*32 +// dst[m+31:m] := Convert_UInt8_To_FP32(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu8_ps +FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_u32( + vmovl_u16(vget_low_u16(vmovl_u8(vreinterpret_u8_m64(a)))))); +} + +// Convert the signed 32-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_ss +#define _mm_cvtsi32_ss(a, b) _mm_cvt_si2ss(a, b) + +// Convert the signed 64-bit integer b to a single-precision (32-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper 3 packed elements from a to the upper elements of dst. +// +// dst[31:0] := Convert_Int64_To_FP32(b[63:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_ss +FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32((float) b, vreinterpretq_f32_m128(a), 0)); +} + +// Copy the lower single-precision (32-bit) floating-point element of a to dst. +// +// dst[31:0] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_f32 +FORCE_INLINE float _mm_cvtss_f32(__m128 a) +{ + return vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si32 +#define _mm_cvtss_si32(a) _mm_cvt_ss2si(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 64-bit integer, and store the result in dst. +// +// dst[63:0] := Convert_FP32_To_Int64(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si64 +FORCE_INLINE int64_t _mm_cvtss_si64(__m128 a) +{ +#if defined(__aarch64__) + return (int64_t) vgetq_lane_f32(vrndiq_f32(vreinterpretq_f32_m128(a)), 0); +#else + float32_t data = vgetq_lane_f32( + vreinterpretq_f32_m128(_mm_round_ps(a, _MM_FROUND_CUR_DIRECTION)), 0); + return (int64_t) data; +#endif +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ps2pi +FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a) +{ + return vreinterpret_m64_s32( + vget_low_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)))); +} + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer with truncation, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ss2si +FORCE_INLINE int _mm_cvtt_ss2si(__m128 a) +{ + return vgetq_lane_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)), 0); +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttps_pi32 +#define _mm_cvttps_pi32(a) _mm_cvtt_ps2pi(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 32-bit integer with truncation, and store the result in dst. +// +// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si32 +#define _mm_cvttss_si32(a) _mm_cvtt_ss2si(a) + +// Convert the lower single-precision (32-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP32_To_Int64_Truncate(a[31:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si64 +FORCE_INLINE int64_t _mm_cvttss_si64(__m128 a) +{ + return (int64_t) vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); +} + +// Divides the four single-precision, floating-point values of a and b. +// +// r0 := a0 / b0 +// r1 := a1 / b1 +// r2 := a2 / b2 +// r3 := a3 / b3 +// +// https://msdn.microsoft.com/en-us/library/edaw8147(v=vs.100).aspx +FORCE_INLINE __m128 _mm_div_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) && !SSE2NEON_PRECISE_DIV + return vreinterpretq_m128_f32( + vdivq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(b)); + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b))); +#if SSE2NEON_PRECISE_DIV + // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b))); +#endif + return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(a), recip)); +#endif +} + +// Divides the scalar single-precision floating point value of a by b. +// https://msdn.microsoft.com/en-us/library/4y73xa49(v=vs.100).aspx +FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b) +{ + float32_t value = + vgetq_lane_f32(vreinterpretq_f32_m128(_mm_div_ps(a, b)), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Extract a 16-bit integer from a, selected with imm8, and store the result in +// the lower element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_pi16 +#define _mm_extract_pi16(a, imm) \ + (int32_t) vget_lane_u16(vreinterpret_u16_m64(a), (imm)) + +// Free aligned memory that was allocated with _mm_malloc. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_free +FORCE_INLINE void _mm_free(void *addr) +{ + free(addr); +} + +// Macro: Get the flush zero bits from the MXCSR control and status register. +// The flush zero may contain any of the following flags: _MM_FLUSH_ZERO_ON or +// _MM_FLUSH_ZERO_OFF +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_FLUSH_ZERO_MODE +FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode() +{ + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + return r.field.bit24 ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF; +} + +// Macro: Get the rounding mode bits from the MXCSR control and status register. +// The rounding mode may contain any of the following flags: _MM_ROUND_NEAREST, +// _MM_ROUND_DOWN, _MM_ROUND_UP, _MM_ROUND_TOWARD_ZERO +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_ROUNDING_MODE +FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE() +{ + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + if (r.field.bit22) { + return r.field.bit23 ? _MM_ROUND_TOWARD_ZERO : _MM_ROUND_UP; + } else { + return r.field.bit23 ? _MM_ROUND_DOWN : _MM_ROUND_NEAREST; + } +} + +// Copy a to dst, and insert the 16-bit integer i into dst at the location +// specified by imm8. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_insert_pi16 +#define _mm_insert_pi16(a, b, imm) \ + __extension__({ \ + vreinterpret_m64_s16( \ + vset_lane_s16((b), vreinterpret_s16_m64(a), (imm))); \ + }) + +// Loads four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/zzd50xxt(v=vs.100).aspx +FORCE_INLINE __m128 _mm_load_ps(const float *p) +{ + return vreinterpretq_m128_f32(vld1q_f32(p)); +} + +// Load a single-precision (32-bit) floating-point element from memory into all +// elements of dst. +// +// dst[31:0] := MEM[mem_addr+31:mem_addr] +// dst[63:32] := MEM[mem_addr+31:mem_addr] +// dst[95:64] := MEM[mem_addr+31:mem_addr] +// dst[127:96] := MEM[mem_addr+31:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_ps1 +#define _mm_load_ps1 _mm_load1_ps + +// Loads an single - precision, floating - point value into the low word and +// clears the upper three words. +// https://msdn.microsoft.com/en-us/library/548bb9h4%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_load_ss(const float *p) +{ + return vreinterpretq_m128_f32(vsetq_lane_f32(*p, vdupq_n_f32(0), 0)); +} + +// Loads a single single-precision, floating-point value, copying it into all +// four words +// https://msdn.microsoft.com/en-us/library/vstudio/5cdkf716(v=vs.100).aspx +FORCE_INLINE __m128 _mm_load1_ps(const float *p) +{ + return vreinterpretq_m128_f32(vld1q_dup_f32(p)); +} + +// Sets the upper two single-precision, floating-point values with 64 +// bits of data loaded from the address p; the lower two values are passed +// through from a. +// +// r0 := a0 +// r1 := a1 +// r2 := *p0 +// r3 := *p1 +// +// https://msdn.microsoft.com/en-us/library/w92wta0x(v%3dvs.100).aspx +FORCE_INLINE __m128 _mm_loadh_pi(__m128 a, __m64 const *p) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vget_low_f32(a), vld1_f32((const float32_t *) p))); +} + +// Sets the lower two single-precision, floating-point values with 64 +// bits of data loaded from the address p; the upper two values are passed +// through from a. +// +// Return Value +// r0 := *p0 +// r1 := *p1 +// r2 := a2 +// r3 := a3 +// +// https://msdn.microsoft.com/en-us/library/s57cyak2(v=vs.100).aspx +FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p) +{ + return vreinterpretq_m128_f32( + vcombine_f32(vld1_f32((const float32_t *) p), vget_high_f32(a))); +} + +// Load 4 single-precision (32-bit) floating-point elements from memory into dst +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[31:0] := MEM[mem_addr+127:mem_addr+96] +// dst[63:32] := MEM[mem_addr+95:mem_addr+64] +// dst[95:64] := MEM[mem_addr+63:mem_addr+32] +// dst[127:96] := MEM[mem_addr+31:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_ps +FORCE_INLINE __m128 _mm_loadr_ps(const float *p) +{ + float32x4_t v = vrev64q_f32(vld1q_f32(p)); + return vreinterpretq_m128_f32(vextq_f32(v, v, 2)); +} + +// Loads four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/x1b16s7z%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_loadu_ps(const float *p) +{ + // for neon, alignment doesn't matter, so _mm_load_ps and _mm_loadu_ps are + // equivalent for neon + return vreinterpretq_m128_f32(vld1q_f32(p)); +} + +// Load unaligned 16-bit integer from memory into the first element of dst. +// +// dst[15:0] := MEM[mem_addr+15:mem_addr] +// dst[MAX:16] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si16 +FORCE_INLINE __m128i _mm_loadu_si16(const void *p) +{ + return vreinterpretq_m128i_s16( + vsetq_lane_s16(*(const int16_t *) p, vdupq_n_s16(0), 0)); +} + +// Load unaligned 64-bit integer from memory into the first element of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[MAX:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si64 +FORCE_INLINE __m128i _mm_loadu_si64(const void *p) +{ + return vreinterpretq_m128i_s64( + vcombine_s64(vld1_s64((const int64_t *) p), vdup_n_s64(0))); +} + +// Allocate aligned blocks of memory. +// https://software.intel.com/en-us/ +// cpp-compiler-developer-guide-and-reference-allocating-and-freeing-aligned-memory-blocks +FORCE_INLINE void *_mm_malloc(size_t size, size_t align) +{ + void *ptr; + if (align == 1) + return malloc(size); + if (align == 2 || (sizeof(void *) == 8 && align == 4)) + align = sizeof(void *); + if (!posix_memalign(&ptr, align, size)) + return ptr; + return NULL; +} + +// Conditionally store 8-bit integer elements from a into memory using mask +// (elements are not stored when the highest bit is not set in the corresponding +// element) and a non-temporal memory hint. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmove_si64 +FORCE_INLINE void _mm_maskmove_si64(__m64 a, __m64 mask, char *mem_addr) +{ + int8x8_t shr_mask = vshr_n_s8(vreinterpret_s8_m64(mask), 7); + __m128 b = _mm_load_ps((const float *) mem_addr); + int8x8_t masked = + vbsl_s8(vreinterpret_u8_s8(shr_mask), vreinterpret_s8_m64(a), + vreinterpret_s8_u64(vget_low_u64(vreinterpretq_u64_m128(b)))); + vst1_s8((int8_t *) mem_addr, masked); +} + +// Conditionally store 8-bit integer elements from a into memory using mask +// (elements are not stored when the highest bit is not set in the corresponding +// element) and a non-temporal memory hint. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_maskmovq +#define _m_maskmovq(a, mask, mem_addr) _mm_maskmove_si64(a, mask, mem_addr) + +// Compare packed signed 16-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MAX(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pi16 +FORCE_INLINE __m64 _mm_max_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vmax_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Computes the maximums of the four single-precision, floating-point values of +// a and b. +// https://msdn.microsoft.com/en-us/library/vstudio/ff5d607a(v=vs.100).aspx +FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b) +{ +#if SSE2NEON_PRECISE_MINMAX + float32x4_t _a = vreinterpretq_f32_m128(a); + float32x4_t _b = vreinterpretq_f32_m128(b); + return vreinterpretq_m128_f32(vbslq_f32(vcgtq_f32(_a, _b), _a, _b)); +#else + return vreinterpretq_m128_f32( + vmaxq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#endif +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed maximum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MAX(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pu8 +FORCE_INLINE __m64 _mm_max_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vmax_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Computes the maximum of the two lower scalar single-precision floating point +// values of a and b. +// https://msdn.microsoft.com/en-us/library/s6db5esz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b) +{ + float32_t value = vgetq_lane_f32(_mm_max_ps(a, b), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Compare packed signed 16-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := MIN(a[i+15:i], b[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pi16 +FORCE_INLINE __m64 _mm_min_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vmin_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Computes the minima of the four single-precision, floating-point values of a +// and b. +// https://msdn.microsoft.com/en-us/library/vstudio/wh13kadz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b) +{ +#if SSE2NEON_PRECISE_MINMAX + float32x4_t _a = vreinterpretq_f32_m128(a); + float32x4_t _b = vreinterpretq_f32_m128(b); + return vreinterpretq_m128_f32(vbslq_f32(vcltq_f32(_a, _b), _a, _b)); +#else + return vreinterpretq_m128_f32( + vminq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#endif +} + +// Compare packed unsigned 8-bit integers in a and b, and store packed minimum +// values in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := MIN(a[i+7:i], b[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pu8 +FORCE_INLINE __m64 _mm_min_pu8(__m64 a, __m64 b) +{ + return vreinterpret_m64_u8( + vmin_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); +} + +// Computes the minimum of the two lower scalar single-precision floating point +// values of a and b. +// https://msdn.microsoft.com/en-us/library/0a9y7xaa(v=vs.100).aspx +FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b) +{ + float32_t value = vgetq_lane_f32(_mm_min_ps(a, b), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); +} + +// Sets the low word to the single-precision, floating-point value of b +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/35hdzazd(v=vs.100) +FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vsetq_lane_f32(vgetq_lane_f32(vreinterpretq_f32_m128(b), 0), + vreinterpretq_f32_m128(a), 0)); +} + +// Moves the upper two values of B into the lower two values of A. +// +// r3 := a3 +// r2 := a2 +// r1 := b3 +// r0 := b2 +FORCE_INLINE __m128 _mm_movehl_ps(__m128 __A, __m128 __B) +{ + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(__A)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(__B)); + return vreinterpretq_m128_f32(vcombine_f32(b32, a32)); +} + +// Moves the lower two values of B into the upper two values of A. +// +// r3 := b1 +// r2 := b0 +// r1 := a1 +// r0 := a0 +FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B) +{ + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(__A)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(__B)); + return vreinterpretq_m128_f32(vcombine_f32(a10, b10)); +} + +// Create mask from the most significant bit of each 8-bit element in a, and +// store the result in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pi8 +FORCE_INLINE int _mm_movemask_pi8(__m64 a) +{ + uint8x8_t input = vreinterpret_u8_m64(a); +#if defined(__aarch64__) + static const int8x8_t shift = {0, 1, 2, 3, 4, 5, 6, 7}; + uint8x8_t tmp = vshr_n_u8(input, 7); + return vaddv_u8(vshl_u8(tmp, shift)); +#else + // Refer the implementation of `_mm_movemask_epi8` + uint16x4_t high_bits = vreinterpret_u16_u8(vshr_n_u8(input, 7)); + uint32x2_t paired16 = + vreinterpret_u32_u16(vsra_n_u16(high_bits, high_bits, 7)); + uint8x8_t paired32 = + vreinterpret_u8_u32(vsra_n_u32(paired16, paired16, 14)); + return vget_lane_u8(paired32, 0) | ((int) vget_lane_u8(paired32, 4) << 4); +#endif +} + +// NEON does not provide this method +// Creates a 4-bit mask from the most significant bits of the four +// single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/4490ys29(v=vs.100).aspx +FORCE_INLINE int _mm_movemask_ps(__m128 a) +{ + uint32x4_t input = vreinterpretq_u32_m128(a); +#if defined(__aarch64__) + static const int32x4_t shift = {0, 1, 2, 3}; + uint32x4_t tmp = vshrq_n_u32(input, 31); + return vaddvq_u32(vshlq_u32(tmp, shift)); +#else + // Uses the exact same method as _mm_movemask_epi8, see that for details. + // Shift out everything but the sign bits with a 32-bit unsigned shift + // right. + uint64x2_t high_bits = vreinterpretq_u64_u32(vshrq_n_u32(input, 31)); + // Merge the two pairs together with a 64-bit unsigned shift right + add. + uint8x16_t paired = + vreinterpretq_u8_u64(vsraq_n_u64(high_bits, high_bits, 31)); + // Extract the result. + return vgetq_lane_u8(paired, 0) | (vgetq_lane_u8(paired, 8) << 2); +#endif +} + +// Multiplies the four single-precision, floating-point values of a and b. +// +// r0 := a0 * b0 +// r1 := a1 * b1 +// r2 := a2 * b2 +// r3 := a3 * b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/22kbk6t9(v=vs.100).aspx +FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vmulq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Multiply the lower single-precision (32-bit) floating-point element in a and +// b, store the result in the lower element of dst, and copy the upper 3 packed +// elements from a to the upper elements of dst. +// +// dst[31:0] := a[31:0] * b[31:0] +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_ss +FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_mul_ps(a, b)); +} + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_pu16 +FORCE_INLINE __m64 _mm_mulhi_pu16(__m64 a, __m64 b) +{ + return vreinterpret_m64_u16(vshrn_n_u32( + vmull_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b)), 16)); +} + +// Computes the bitwise OR of the four single-precision, floating-point values +// of a and b. +// https://msdn.microsoft.com/en-us/library/vstudio/7ctdsyy0(v=vs.100).aspx +FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + vorrq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +// Average packed unsigned 8-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgb +#define _m_pavgb(a, b) _mm_avg_pu8(a, b) + +// Average packed unsigned 16-bit integers in a and b, and store the results in +// dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgw +#define _m_pavgw(a, b) _mm_avg_pu16(a, b) + +// Extract a 16-bit integer from a, selected with imm8, and store the result in +// the lower element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pextrw +#define _m_pextrw(a, imm) _mm_extract_pi16(a, imm) + +// Copy a to dst, and insert the 16-bit integer i into dst at the location +// specified by imm8. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_pinsrw +#define _m_pinsrw(a, i, imm) _mm_insert_pi16(a, i, imm) + +// Compare packed signed 16-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxsw +#define _m_pmaxsw(a, b) _mm_max_pi16(a, b) + +// Compare packed unsigned 8-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxub +#define _m_pmaxub(a, b) _mm_max_pu8(a, b) + +// Compare packed signed 16-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminsw +#define _m_pminsw(a, b) _mm_min_pi16(a, b) + +// Compare packed unsigned 8-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminub +#define _m_pminub(a, b) _mm_min_pu8(a, b) + +// Create mask from the most significant bit of each 8-bit element in a, and +// store the result in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmovmskb +#define _m_pmovmskb(a) _mm_movemask_pi8(a) + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmulhuw +#define _m_pmulhuw(a, b) _mm_mulhi_pu16(a, b) + +// Loads one cache line of data from address p to a location closer to the +// processor. https://msdn.microsoft.com/en-us/library/84szxsww(v=vs.100).aspx +FORCE_INLINE void _mm_prefetch(const void *p, int i) +{ + (void) i; + __builtin_prefetch(p); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce four +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_psadbw +#define _m_psadbw(a, b) _mm_sad_pu8(a, b) + +// Shuffle 16-bit integers in a using the control in imm8, and store the results +// in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pshufw +#define _m_pshufw(a, imm) _mm_shuffle_pi16(a, imm) + +// Compute the approximate reciprocal of packed single-precision (32-bit) +// floating-point elements in a, and store the results in dst. The maximum +// relative error for this approximation is less than 1.5*2^-12. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ps +FORCE_INLINE __m128 _mm_rcp_ps(__m128 in) +{ + float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(in)); + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in))); +#if SSE2NEON_PRECISE_DIV + // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in))); +#endif + return vreinterpretq_m128_f32(recip); +} + +// Compute the approximate reciprocal of the lower single-precision (32-bit) +// floating-point element in a, store the result in the lower element of dst, +// and copy the upper 3 packed elements from a to the upper elements of dst. The +// maximum relative error for this approximation is less than 1.5*2^-12. +// +// dst[31:0] := (1.0 / a[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ss +FORCE_INLINE __m128 _mm_rcp_ss(__m128 a) +{ + return _mm_move_ss(a, _mm_rcp_ps(a)); +} + +// Computes the approximations of the reciprocal square roots of the four +// single-precision floating point values of in. +// The current precision is 1% error. +// https://msdn.microsoft.com/en-us/library/22hfsh53(v=vs.100).aspx +FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in) +{ + float32x4_t out = vrsqrteq_f32(vreinterpretq_f32_m128(in)); +#if SSE2NEON_PRECISE_SQRT + // Additional Netwon-Raphson iteration for accuracy + out = vmulq_f32( + out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out)); + out = vmulq_f32( + out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out)); +#endif + return vreinterpretq_m128_f32(out); +} + +// Compute the approximate reciprocal square root of the lower single-precision +// (32-bit) floating-point element in a, store the result in the lower element +// of dst, and copy the upper 3 packed elements from a to the upper elements of +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rsqrt_ss +FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in) +{ + return vsetq_lane_f32(vgetq_lane_f32(_mm_rsqrt_ps(in), 0), in, 0); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce four +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_pu8 +FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b) +{ + uint64x1_t t = vpaddl_u32(vpaddl_u16( + vpaddl_u8(vabd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))))); + return vreinterpret_m64_u16( + vset_lane_u16(vget_lane_u64(t, 0), vdup_n_u16(0), 0)); +} + +// Macro: Set the flush zero bits of the MXCSR control and status register to +// the value in unsigned 32-bit integer a. The flush zero may contain any of the +// following flags: _MM_FLUSH_ZERO_ON or _MM_FLUSH_ZERO_OFF +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_FLUSH_ZERO_MODE +FORCE_INLINE void _sse2neon_mm_set_flush_zero_mode(unsigned int flag) +{ + // AArch32 Advanced SIMD arithmetic always uses the Flush-to-zero setting, + // regardless of the value of the FZ bit. + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + r.field.bit24 = (flag & _MM_FLUSH_ZERO_MASK) == _MM_FLUSH_ZERO_ON; + +#if defined(__aarch64__) + asm volatile("msr FPCR, %0" ::"r"(r)); /* write */ +#else + asm volatile("vmsr FPSCR, %0" ::"r"(r)); /* write */ +#endif +} + +// Sets the four single-precision, floating-point values to the four inputs. +// https://msdn.microsoft.com/en-us/library/vstudio/afh0zf75(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set_ps(float w, float z, float y, float x) +{ + float ALIGN_STRUCT(16) data[4] = {x, y, z, w}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Sets the four single-precision, floating-point values to w. +// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set_ps1(float _w) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); +} + +// Macro: Set the rounding mode bits of the MXCSR control and status register to +// the value in unsigned 32-bit integer a. The rounding mode may contain any of +// the following flags: _MM_ROUND_NEAREST, _MM_ROUND_DOWN, _MM_ROUND_UP, +// _MM_ROUND_TOWARD_ZERO +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_ROUNDING_MODE +FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding) +{ + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + switch (rounding) { + case _MM_ROUND_TOWARD_ZERO: + r.field.bit22 = 1; + r.field.bit23 = 1; + break; + case _MM_ROUND_DOWN: + r.field.bit22 = 0; + r.field.bit23 = 1; + break; + case _MM_ROUND_UP: + r.field.bit22 = 1; + r.field.bit23 = 0; + break; + default: //_MM_ROUND_NEAREST + r.field.bit22 = 0; + r.field.bit23 = 0; + } + +#if defined(__aarch64__) + asm volatile("msr FPCR, %0" ::"r"(r)); /* write */ +#else + asm volatile("vmsr FPSCR, %0" ::"r"(r)); /* write */ +#endif +} + +// Copy single-precision (32-bit) floating-point element a to the lower element +// of dst, and zero the upper 3 elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_ss +FORCE_INLINE __m128 _mm_set_ss(float a) +{ + float ALIGN_STRUCT(16) data[4] = {a, 0, 0, 0}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Sets the four single-precision, floating-point values to w. +// +// r0 := r1 := r2 := r3 := w +// +// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx +FORCE_INLINE __m128 _mm_set1_ps(float _w) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); +} + +// FIXME: _mm_setcsr() implementation supports changing the rounding mode only. +FORCE_INLINE void _mm_setcsr(unsigned int a) +{ + _MM_SET_ROUNDING_MODE(a); +} + +// FIXME: _mm_getcsr() implementation supports reading the rounding mode only. +FORCE_INLINE unsigned int _mm_getcsr() +{ + return _MM_GET_ROUNDING_MODE(); +} + +// Sets the four single-precision, floating-point values to the four inputs in +// reverse order. +// https://msdn.microsoft.com/en-us/library/vstudio/d2172ct3(v=vs.100).aspx +FORCE_INLINE __m128 _mm_setr_ps(float w, float z, float y, float x) +{ + float ALIGN_STRUCT(16) data[4] = {w, z, y, x}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +} + +// Clears the four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/tk1t2tbz(v=vs.100).aspx +FORCE_INLINE __m128 _mm_setzero_ps(void) +{ + return vreinterpretq_m128_f32(vdupq_n_f32(0)); +} + +// Shuffle 16-bit integers in a using the control in imm8, and store the results +// in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi16 +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_pi16(a, imm) \ + __extension__({ \ + vreinterpret_m64_s16(__builtin_shufflevector( \ + vreinterpret_s16_m64(a), vreinterpret_s16_m64(a), (imm & 0x3), \ + ((imm >> 2) & 0x3), ((imm >> 4) & 0x3), ((imm >> 6) & 0x3))); \ + }) +#else +#define _mm_shuffle_pi16(a, imm) \ + __extension__({ \ + int16x4_t ret; \ + ret = \ + vmov_n_s16(vget_lane_s16(vreinterpret_s16_m64(a), (imm) & (0x3))); \ + ret = vset_lane_s16( \ + vget_lane_s16(vreinterpret_s16_m64(a), ((imm) >> 2) & 0x3), ret, \ + 1); \ + ret = vset_lane_s16( \ + vget_lane_s16(vreinterpret_s16_m64(a), ((imm) >> 4) & 0x3), ret, \ + 2); \ + ret = vset_lane_s16( \ + vget_lane_s16(vreinterpret_s16_m64(a), ((imm) >> 6) & 0x3), ret, \ + 3); \ + vreinterpret_m64_s16(ret); \ + }) +#endif + +// Guarantees that every preceding store is globally visible before any +// subsequent store. +// https://msdn.microsoft.com/en-us/library/5h2w73d1%28v=vs.90%29.aspx +FORCE_INLINE void _mm_sfence(void) +{ + __sync_synchronize(); +} + +// FORCE_INLINE __m128 _mm_shuffle_ps(__m128 a, __m128 b, __constrange(0,255) +// int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_ps(a, b, imm) \ + __extension__({ \ + float32x4_t _input1 = vreinterpretq_f32_m128(a); \ + float32x4_t _input2 = vreinterpretq_f32_m128(b); \ + float32x4_t _shuf = __builtin_shufflevector( \ + _input1, _input2, (imm) & (0x3), ((imm) >> 2) & 0x3, \ + (((imm) >> 4) & 0x3) + 4, (((imm) >> 6) & 0x3) + 4); \ + vreinterpretq_m128_f32(_shuf); \ + }) +#else // generic +#define _mm_shuffle_ps(a, b, imm) \ + __extension__({ \ + __m128 ret; \ + switch (imm) { \ + case _MM_SHUFFLE(1, 0, 3, 2): \ + ret = _mm_shuffle_ps_1032((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 3, 0, 1): \ + ret = _mm_shuffle_ps_2301((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 3, 2, 1): \ + ret = _mm_shuffle_ps_0321((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 1, 0, 3): \ + ret = _mm_shuffle_ps_2103((a), (b)); \ + break; \ + case _MM_SHUFFLE(1, 0, 1, 0): \ + ret = _mm_movelh_ps((a), (b)); \ + break; \ + case _MM_SHUFFLE(1, 0, 0, 1): \ + ret = _mm_shuffle_ps_1001((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 1, 0, 1): \ + ret = _mm_shuffle_ps_0101((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 1, 0): \ + ret = _mm_shuffle_ps_3210((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 0, 1, 1): \ + ret = _mm_shuffle_ps_0011((a), (b)); \ + break; \ + case _MM_SHUFFLE(0, 0, 2, 2): \ + ret = _mm_shuffle_ps_0022((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 2, 0, 0): \ + ret = _mm_shuffle_ps_2200((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 0, 2): \ + ret = _mm_shuffle_ps_3202((a), (b)); \ + break; \ + case _MM_SHUFFLE(3, 2, 3, 2): \ + ret = _mm_movehl_ps((b), (a)); \ + break; \ + case _MM_SHUFFLE(1, 1, 3, 3): \ + ret = _mm_shuffle_ps_1133((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 1, 0): \ + ret = _mm_shuffle_ps_2010((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 0, 1): \ + ret = _mm_shuffle_ps_2001((a), (b)); \ + break; \ + case _MM_SHUFFLE(2, 0, 3, 2): \ + ret = _mm_shuffle_ps_2032((a), (b)); \ + break; \ + default: \ + ret = _mm_shuffle_ps_default((a), (b), (imm)); \ + break; \ + } \ + ret; \ + }) +#endif + +// Computes the approximations of square roots of the four single-precision, +// floating-point values of a. First computes reciprocal square roots and then +// reciprocals of the four values. +// +// r0 := sqrt(a0) +// r1 := sqrt(a1) +// r2 := sqrt(a2) +// r3 := sqrt(a3) +// +// https://msdn.microsoft.com/en-us/library/vstudio/8z67bwwk(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in) +{ +#if SSE2NEON_PRECISE_SQRT + float32x4_t recip = vrsqrteq_f32(vreinterpretq_f32_m128(in)); + + // Test for vrsqrteq_f32(0) -> positive infinity case. + // Change to zero, so that s * 1/sqrt(s) result is zero too. + const uint32x4_t pos_inf = vdupq_n_u32(0x7F800000); + const uint32x4_t div_by_zero = + vceqq_u32(pos_inf, vreinterpretq_u32_f32(recip)); + recip = vreinterpretq_f32_u32( + vandq_u32(vmvnq_u32(div_by_zero), vreinterpretq_u32_f32(recip))); + + // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32( + vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)), + recip); + recip = vmulq_f32( + vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)), + recip); + + // sqrt(s) = s * 1/sqrt(s) + return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(in), recip)); +#elif defined(__aarch64__) + return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in))); +#else + float32x4_t recipsq = vrsqrteq_f32(vreinterpretq_f32_m128(in)); + float32x4_t sq = vrecpeq_f32(recipsq); + return vreinterpretq_m128_f32(sq); +#endif +} + +// Computes the approximation of the square root of the scalar single-precision +// floating point value of in. +// https://msdn.microsoft.com/en-us/library/ahfsc22d(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in) +{ + float32_t value = + vgetq_lane_f32(vreinterpretq_f32_m128(_mm_sqrt_ps(in)), 0); + return vreinterpretq_m128_f32( + vsetq_lane_f32(value, vreinterpretq_f32_m128(in), 0)); +} + +// Stores four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/vstudio/s3h4ay6y(v=vs.100).aspx +FORCE_INLINE void _mm_store_ps(float *p, __m128 a) +{ + vst1q_f32(p, vreinterpretq_f32_m128(a)); +} + +// Store the lower single-precision (32-bit) floating-point element from a into +// 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte +// boundary or a general-protection exception may be generated. +// +// MEM[mem_addr+31:mem_addr] := a[31:0] +// MEM[mem_addr+63:mem_addr+32] := a[31:0] +// MEM[mem_addr+95:mem_addr+64] := a[31:0] +// MEM[mem_addr+127:mem_addr+96] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_ps1 +FORCE_INLINE void _mm_store_ps1(float *p, __m128 a) +{ + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + vst1q_f32(p, vdupq_n_f32(a0)); +} + +// Stores the lower single - precision, floating - point value. +// https://msdn.microsoft.com/en-us/library/tzz10fbx(v=vs.100).aspx +FORCE_INLINE void _mm_store_ss(float *p, __m128 a) +{ + vst1q_lane_f32(p, vreinterpretq_f32_m128(a), 0); +} + +// Store the lower single-precision (32-bit) floating-point element from a into +// 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte +// boundary or a general-protection exception may be generated. +// +// MEM[mem_addr+31:mem_addr] := a[31:0] +// MEM[mem_addr+63:mem_addr+32] := a[31:0] +// MEM[mem_addr+95:mem_addr+64] := a[31:0] +// MEM[mem_addr+127:mem_addr+96] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store1_ps +#define _mm_store1_ps _mm_store_ps1 + +// Stores the upper two single-precision, floating-point values of a to the +// address p. +// +// *p0 := a2 +// *p1 := a3 +// +// https://msdn.microsoft.com/en-us/library/a7525fs8(v%3dvs.90).aspx +FORCE_INLINE void _mm_storeh_pi(__m64 *p, __m128 a) +{ + *p = vreinterpret_m64_f32(vget_high_f32(a)); +} + +// Stores the lower two single-precision floating point values of a to the +// address p. +// +// *p0 := a0 +// *p1 := a1 +// +// https://msdn.microsoft.com/en-us/library/h54t98ks(v=vs.90).aspx +FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a) +{ + *p = vreinterpret_m64_f32(vget_low_f32(a)); +} + +// Store 4 single-precision (32-bit) floating-point elements from a into memory +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// MEM[mem_addr+31:mem_addr] := a[127:96] +// MEM[mem_addr+63:mem_addr+32] := a[95:64] +// MEM[mem_addr+95:mem_addr+64] := a[63:32] +// MEM[mem_addr+127:mem_addr+96] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_ps +FORCE_INLINE void _mm_storer_ps(float *p, __m128 a) +{ + float32x4_t tmp = vrev64q_f32(vreinterpretq_f32_m128(a)); + float32x4_t rev = vextq_f32(tmp, tmp, 2); + vst1q_f32(p, rev); +} + +// Stores four single-precision, floating-point values. +// https://msdn.microsoft.com/en-us/library/44e30x22(v=vs.100).aspx +FORCE_INLINE void _mm_storeu_ps(float *p, __m128 a) +{ + vst1q_f32(p, vreinterpretq_f32_m128(a)); +} + +// Stores 16-bits of integer data a at the address p. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si16 +FORCE_INLINE void _mm_storeu_si16(void *p, __m128i a) +{ + vst1q_lane_s16((int16_t *) p, vreinterpretq_s16_m128i(a), 0); +} + +// Stores 64-bits of integer data a at the address p. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si64 +FORCE_INLINE void _mm_storeu_si64(void *p, __m128i a) +{ + vst1q_lane_s64((int64_t *) p, vreinterpretq_s64_m128i(a), 0); +} + +// Store 64-bits of integer data from a into memory using a non-temporal memory +// hint. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pi +FORCE_INLINE void _mm_stream_pi(__m64 *p, __m64 a) +{ + vst1_s64((int64_t *) p, vreinterpret_s64_m64(a)); +} + +// Store 128-bits (composed of 4 packed single-precision (32-bit) floating- +// point elements) from a into memory using a non-temporal memory hint. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_ps +FORCE_INLINE void _mm_stream_ps(float *p, __m128 a) +{ +#if __has_builtin(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, (float32x4_t *) p); +#else + vst1q_f32(p, vreinterpretq_f32_m128(a)); +#endif +} + +// Subtracts the four single-precision, floating-point values of a and b. +// +// r0 := a0 - b0 +// r1 := a1 - b1 +// r2 := a2 - b2 +// r3 := a3 - b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/1zad2k61(v=vs.100).aspx +FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_f32( + vsubq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +} + +// Subtract the lower single-precision (32-bit) floating-point element in b from +// the lower single-precision (32-bit) floating-point element in a, store the +// result in the lower element of dst, and copy the upper 3 packed elements from +// a to the upper elements of dst. +// +// dst[31:0] := a[31:0] - b[31:0] +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_ss +FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_sub_ps(a, b)); +} + +// Macro: Transpose the 4x4 matrix formed by the 4 rows of single-precision +// (32-bit) floating-point elements in row0, row1, row2, and row3, and store the +// transposed matrix in these vectors (row0 now contains column 0, etc.). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=MM_TRANSPOSE4_PS +#define _MM_TRANSPOSE4_PS(row0, row1, row2, row3) \ + do { \ + float32x4x2_t ROW01 = vtrnq_f32(row0, row1); \ + float32x4x2_t ROW23 = vtrnq_f32(row2, row3); \ + row0 = vcombine_f32(vget_low_f32(ROW01.val[0]), \ + vget_low_f32(ROW23.val[0])); \ + row1 = vcombine_f32(vget_low_f32(ROW01.val[1]), \ + vget_low_f32(ROW23.val[1])); \ + row2 = vcombine_f32(vget_high_f32(ROW01.val[0]), \ + vget_high_f32(ROW23.val[0])); \ + row3 = vcombine_f32(vget_high_f32(ROW01.val[1]), \ + vget_high_f32(ROW23.val[1])); \ + } while (0) + +// according to the documentation, these intrinsics behave the same as the +// non-'u' versions. We'll just alias them here. +#define _mm_ucomieq_ss _mm_comieq_ss +#define _mm_ucomige_ss _mm_comige_ss +#define _mm_ucomigt_ss _mm_comigt_ss +#define _mm_ucomile_ss _mm_comile_ss +#define _mm_ucomilt_ss _mm_comilt_ss +#define _mm_ucomineq_ss _mm_comineq_ss + +// Return vector of type __m128i with undefined elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_undefined_si128 +FORCE_INLINE __m128i _mm_undefined_si128(void) +{ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif + __m128i a; + return a; +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +} + +// Return vector of type __m128 with undefined elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_ps +FORCE_INLINE __m128 _mm_undefined_ps(void) +{ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif + __m128 a; + return a; +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +} + +// Selects and interleaves the upper two single-precision, floating-point values +// from a and b. +// +// r0 := a2 +// r1 := b2 +// r2 := a3 +// r3 := b3 +// +// https://msdn.microsoft.com/en-us/library/skccxx7d%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vzip2q_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a1 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b1 = vget_high_f32(vreinterpretq_f32_m128(b)); + float32x2x2_t result = vzip_f32(a1, b1); + return vreinterpretq_m128_f32(vcombine_f32(result.val[0], result.val[1])); +#endif +} + +// Selects and interleaves the lower two single-precision, floating-point values +// from a and b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// +// https://msdn.microsoft.com/en-us/library/25st103b%28v=vs.90%29.aspx +FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vzip1q_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a1 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t b1 = vget_low_f32(vreinterpretq_f32_m128(b)); + float32x2x2_t result = vzip_f32(a1, b1); + return vreinterpretq_m128_f32(vcombine_f32(result.val[0], result.val[1])); +#endif +} + +// Computes bitwise EXOR (exclusive-or) of the four single-precision, +// floating-point values of a and b. +// https://msdn.microsoft.com/en-us/library/ss6k3wk8(v=vs.100).aspx +FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b) +{ + return vreinterpretq_m128_s32( + veorq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); +} + +/* SSE2 */ + +// Adds the 8 signed or unsigned 16-bit integers in a to the 8 signed or +// unsigned 16-bit integers in b. +// https://msdn.microsoft.com/en-us/library/fceha5k4(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Adds the 4 signed or unsigned 32-bit integers in a to the 4 signed or +// unsigned 32-bit integers in b. +// +// r0 := a0 + b0 +// r1 := a1 + b1 +// r2 := a2 + b2 +// r3 := a3 + b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vaddq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Adds the 4 signed or unsigned 64-bit integers in a to the 4 signed or +// unsigned 32-bit integers in b. +// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx +FORCE_INLINE __m128i _mm_add_epi64(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s64( + vaddq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +} + +// Adds the 16 signed or unsigned 8-bit integers in a to the 16 signed or +// unsigned 8-bit integers in b. +// https://technet.microsoft.com/en-us/subscriptions/yc7tcyzs(v=vs.90) +FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vaddq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Add packed double-precision (64-bit) floating-point elements in a and b, and +// store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_pd +FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vaddq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] + db[0]; + c[1] = da[1] + db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Add the lower double-precision (64-bit) floating-point element in a and b, +// store the result in the lower element of dst, and copy the upper element from +// a to the upper element of dst. +// +// dst[63:0] := a[63:0] + b[63:0] +// dst[127:64] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_sd +FORCE_INLINE __m128d _mm_add_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_add_pd(a, b)); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] + db[0]; + c[1] = da[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Add 64-bit integers a and b, and store the result in dst. +// +// dst[63:0] := a[63:0] + b[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_si64 +FORCE_INLINE __m64 _mm_add_si64(__m64 a, __m64 b) +{ + return vreinterpret_m64_s64( + vadd_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); +} + +// Adds the 8 signed 16-bit integers in a to the 8 signed 16-bit integers in b +// and saturates. +// +// r0 := SignedSaturate(a0 + b0) +// r1 := SignedSaturate(a1 + b1) +// ... +// r7 := SignedSaturate(a7 + b7) +// +// https://msdn.microsoft.com/en-us/library/1a306ef8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vqaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Add packed signed 8-bit integers in a and b using saturation, and store the +// results in dst. +// +// FOR j := 0 to 15 +// i := j*8 +// dst[i+7:i] := Saturate8( a[i+7:i] + b[i+7:i] ) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epi8 +FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vqaddq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Add packed unsigned 16-bit integers in a and b using saturation, and store +// the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epu16 +FORCE_INLINE __m128i _mm_adds_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vqaddq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Adds the 16 unsigned 8-bit integers in a to the 16 unsigned 8-bit integers in +// b and saturates.. +// https://msdn.microsoft.com/en-us/library/9hahyddy(v=vs.100).aspx +FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vqaddq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Compute the bitwise AND of packed double-precision (64-bit) floating-point +// elements in a and b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := a[i+63:i] AND b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_and_pd +FORCE_INLINE __m128d _mm_and_pd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_s64( + vandq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); +} + +// Computes the bitwise AND of the 128-bit value in a and the 128-bit value in +// b. +// +// r := a & b +// +// https://msdn.microsoft.com/en-us/library/vstudio/6d1txsa8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vandq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compute the bitwise NOT of packed double-precision (64-bit) floating-point +// elements in a and then AND with b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := ((NOT a[i+63:i]) AND b[i+63:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_andnot_pd +FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b) +{ + // *NOTE* argument swap + return vreinterpretq_m128d_s64( + vbicq_s64(vreinterpretq_s64_m128d(b), vreinterpretq_s64_m128d(a))); +} + +// Computes the bitwise AND of the 128-bit value in b and the bitwise NOT of the +// 128-bit value in a. +// +// r := (~a) & b +// +// https://msdn.microsoft.com/en-us/library/vstudio/1beaceh8(v=vs.100).aspx +FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vbicq_s32(vreinterpretq_s32_m128i(b), + vreinterpretq_s32_m128i(a))); // *NOTE* argument swap +} + +// Computes the average of the 8 unsigned 16-bit integers in a and the 8 +// unsigned 16-bit integers in b and rounds. +// +// r0 := (a0 + b0) / 2 +// r1 := (a1 + b1) / 2 +// ... +// r7 := (a7 + b7) / 2 +// +// https://msdn.microsoft.com/en-us/library/vstudio/y13ca3c8(v=vs.90).aspx +FORCE_INLINE __m128i _mm_avg_epu16(__m128i a, __m128i b) +{ + return (__m128i) vrhaddq_u16(vreinterpretq_u16_m128i(a), + vreinterpretq_u16_m128i(b)); +} + +// Computes the average of the 16 unsigned 8-bit integers in a and the 16 +// unsigned 8-bit integers in b and rounds. +// +// r0 := (a0 + b0) / 2 +// r1 := (a1 + b1) / 2 +// ... +// r15 := (a15 + b15) / 2 +// +// https://msdn.microsoft.com/en-us/library/vstudio/8zwh554a(v%3dvs.90).aspx +FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vrhaddq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Shift a left by imm8 bytes while shifting in zeros, and store the results in +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bslli_si128 +#define _mm_bslli_si128(a, imm) _mm_slli_si128(a, imm) + +// Shift a right by imm8 bytes while shifting in zeros, and store the results in +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bsrli_si128 +#define _mm_bsrli_si128(a, imm) _mm_srli_si128(a, imm) + +// Cast vector of type __m128d to type __m128. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_ps +FORCE_INLINE __m128 _mm_castpd_ps(__m128d a) +{ + return vreinterpretq_m128_s64(vreinterpretq_s64_m128d(a)); +} + +// Cast vector of type __m128d to type __m128i. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_si128 +FORCE_INLINE __m128i _mm_castpd_si128(__m128d a) +{ + return vreinterpretq_m128i_s64(vreinterpretq_s64_m128d(a)); +} + +// Cast vector of type __m128 to type __m128d. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castps_pd +FORCE_INLINE __m128d _mm_castps_pd(__m128 a) +{ + return vreinterpretq_m128d_s32(vreinterpretq_s32_m128(a)); +} + +// Applies a type cast to reinterpret four 32-bit floating point values passed +// in as a 128-bit parameter as packed 32-bit integers. +// https://msdn.microsoft.com/en-us/library/bb514099.aspx +FORCE_INLINE __m128i _mm_castps_si128(__m128 a) +{ + return vreinterpretq_m128i_s32(vreinterpretq_s32_m128(a)); +} + +// Cast vector of type __m128i to type __m128d. This intrinsic is only used for +// compilation and does not generate any instructions, thus it has zero latency. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castsi128_pd +FORCE_INLINE __m128d _mm_castsi128_pd(__m128i a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vreinterpretq_f64_m128i(a)); +#else + return vreinterpretq_m128d_f32(vreinterpretq_f32_m128i(a)); +#endif +} + +// Applies a type cast to reinterpret four 32-bit integers passed in as a +// 128-bit parameter as packed 32-bit floating point values. +// https://msdn.microsoft.com/en-us/library/bb514029.aspx +FORCE_INLINE __m128 _mm_castsi128_ps(__m128i a) +{ + return vreinterpretq_m128_s32(vreinterpretq_s32_m128i(a)); +} + +// Cache line containing p is flushed and invalidated from all caches in the +// coherency domain. : +// https://msdn.microsoft.com/en-us/library/ba08y07y(v=vs.100).aspx +FORCE_INLINE void _mm_clflush(void const *p) +{ + (void) p; + // no corollary for Neon? +} + +// Compares the 8 signed or unsigned 16-bit integers in a and the 8 signed or +// unsigned 16-bit integers in b for equality. +// https://msdn.microsoft.com/en-us/library/2ay060te(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vceqq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compare packed 32-bit integers in a and b for equality, and store the results +// in dst +FORCE_INLINE __m128i _mm_cmpeq_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vceqq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compares the 16 signed or unsigned 8-bit integers in a and the 16 signed or +// unsigned 8-bit integers in b for equality. +// https://msdn.microsoft.com/en-us/library/windows/desktop/bz5xk21a(v=vs.90).aspx +FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vceqq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for equality, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_pd +FORCE_INLINE __m128d _mm_cmpeq_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64( + vceqq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + // (a == b) -> (a_lo == b_lo) && (a_hi == b_hi) + uint32x4_t cmp = + vceqq_u32(vreinterpretq_u32_m128d(a), vreinterpretq_u32_m128d(b)); + uint32x4_t swapped = vrev64q_u32(cmp); + return vreinterpretq_m128d_u32(vandq_u32(cmp, swapped)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for equality, store the result in the lower element of dst, and copy the +// upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_sd +FORCE_INLINE __m128d _mm_cmpeq_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpeq_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for greater-than-or-equal, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_pd +FORCE_INLINE __m128d _mm_cmpge_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64( + vcgeq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) >= (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = (*(double *) &a1) >= (*(double *) &b1) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for greater-than-or-equal, store the result in the lower element of dst, +// and copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_sd +FORCE_INLINE __m128d _mm_cmpge_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmpge_pd(a, b)); +#else + // expand "_mm_cmpge_pd()" to reduce unnecessary operations + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) >= (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +// in b for greater than. +// +// r0 := (a0 > b0) ? 0xffff : 0x0 +// r1 := (a1 > b1) ? 0xffff : 0x0 +// ... +// r7 := (a7 > b7) ? 0xffff : 0x0 +// +// https://technet.microsoft.com/en-us/library/xd43yfsa(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcgtq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +// in b for greater than. +// https://msdn.microsoft.com/en-us/library/vstudio/1s9f2z0y(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vcgtq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +// in b for greater than. +// +// r0 := (a0 > b0) ? 0xff : 0x0 +// r1 := (a1 > b1) ? 0xff : 0x0 +// ... +// r15 := (a15 > b15) ? 0xff : 0x0 +// +// https://msdn.microsoft.com/zh-tw/library/wf45zt2b(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vcgtq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for greater-than, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_pd +FORCE_INLINE __m128d _mm_cmpgt_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64( + vcgtq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) > (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = (*(double *) &a1) > (*(double *) &b1) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for greater-than, store the result in the lower element of dst, and copy +// the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_sd +FORCE_INLINE __m128d _mm_cmpgt_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmpgt_pd(a, b)); +#else + // expand "_mm_cmpge_pd()" to reduce unnecessary operations + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) > (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for less-than-or-equal, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_pd +FORCE_INLINE __m128d _mm_cmple_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64( + vcleq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) <= (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = (*(double *) &a1) <= (*(double *) &b1) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for less-than-or-equal, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_sd +FORCE_INLINE __m128d _mm_cmple_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmple_pd(a, b)); +#else + // expand "_mm_cmpge_pd()" to reduce unnecessary operations + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) <= (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +// in b for less than. +// +// r0 := (a0 < b0) ? 0xffff : 0x0 +// r1 := (a1 < b1) ? 0xffff : 0x0 +// ... +// r7 := (a7 < b7) ? 0xffff : 0x0 +// +// https://technet.microsoft.com/en-us/library/t863edb2(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmplt_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcltq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + + +// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +// in b for less than. +// https://msdn.microsoft.com/en-us/library/vstudio/4ak0bf5d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cmplt_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vcltq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +// in b for lesser than. +// https://msdn.microsoft.com/en-us/library/windows/desktop/9s46csht(v=vs.90).aspx +FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vcltq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for less-than, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_pd +FORCE_INLINE __m128d _mm_cmplt_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64( + vcltq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) < (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = (*(double *) &a1) < (*(double *) &b1) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for less-than, store the result in the lower element of dst, and copy the +// upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_sd +FORCE_INLINE __m128d _mm_cmplt_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmplt_pd(a, b)); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) < (*(double *) &b0) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for not-equal, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_pd +FORCE_INLINE __m128d _mm_cmpneq_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_s32(vmvnq_s32(vreinterpretq_s32_u64( + vceqq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))))); +#else + // (a == b) -> (a_lo == b_lo) && (a_hi == b_hi) + uint32x4_t cmp = + vceqq_u32(vreinterpretq_u32_m128d(a), vreinterpretq_u32_m128d(b)); + uint32x4_t swapped = vrev64q_u32(cmp); + return vreinterpretq_m128d_u32(vmvnq_u32(vandq_u32(cmp, swapped))); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for not-equal, store the result in the lower element of dst, and copy the +// upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_sd +FORCE_INLINE __m128d _mm_cmpneq_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpneq_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for not-greater-than-or-equal, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_pd +FORCE_INLINE __m128d _mm_cmpnge_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64(veorq_u64( + vcgeq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b)), + vdupq_n_u64(UINT64_MAX))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = + !((*(double *) &a0) >= (*(double *) &b0)) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = + !((*(double *) &a1) >= (*(double *) &b1)) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for not-greater-than-or-equal, store the result in the lower element of +// dst, and copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_sd +FORCE_INLINE __m128d _mm_cmpnge_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpnge_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for not-greater-than, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_cmpngt_pd +FORCE_INLINE __m128d _mm_cmpngt_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64(veorq_u64( + vcgtq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b)), + vdupq_n_u64(UINT64_MAX))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = + !((*(double *) &a0) > (*(double *) &b0)) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = + !((*(double *) &a1) > (*(double *) &b1)) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for not-greater-than, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpngt_sd +FORCE_INLINE __m128d _mm_cmpngt_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpngt_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for not-less-than-or-equal, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_pd +FORCE_INLINE __m128d _mm_cmpnle_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64(veorq_u64( + vcleq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b)), + vdupq_n_u64(UINT64_MAX))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = + !((*(double *) &a0) <= (*(double *) &b0)) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = + !((*(double *) &a1) <= (*(double *) &b1)) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for not-less-than-or-equal, store the result in the lower element of dst, +// and copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_sd +FORCE_INLINE __m128d _mm_cmpnle_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpnle_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// for not-less-than, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_pd +FORCE_INLINE __m128d _mm_cmpnlt_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_u64(veorq_u64( + vcltq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b)), + vdupq_n_u64(UINT64_MAX))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = + !((*(double *) &a0) < (*(double *) &b0)) ? ~UINT64_C(0) : UINT64_C(0); + d[1] = + !((*(double *) &a1) < (*(double *) &b1)) ? ~UINT64_C(0) : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b for not-less-than, store the result in the lower element of dst, and copy +// the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_sd +FORCE_INLINE __m128d _mm_cmpnlt_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_cmpnlt_pd(a, b)); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// to see if neither is NaN, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_pd +FORCE_INLINE __m128d _mm_cmpord_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + // Excluding NaNs, any two floating point numbers can be compared. + uint64x2_t not_nan_a = + vceqq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(a)); + uint64x2_t not_nan_b = + vceqq_f64(vreinterpretq_f64_m128d(b), vreinterpretq_f64_m128d(b)); + return vreinterpretq_m128d_u64(vandq_u64(not_nan_a, not_nan_b)); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = ((*(double *) &a0) == (*(double *) &a0) && + (*(double *) &b0) == (*(double *) &b0)) + ? ~UINT64_C(0) + : UINT64_C(0); + d[1] = ((*(double *) &a1) == (*(double *) &a1) && + (*(double *) &b1) == (*(double *) &b1)) + ? ~UINT64_C(0) + : UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b to see if neither is NaN, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_sd +FORCE_INLINE __m128d _mm_cmpord_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmpord_pd(a, b)); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t d[2]; + d[0] = ((*(double *) &a0) == (*(double *) &a0) && + (*(double *) &b0) == (*(double *) &b0)) + ? ~UINT64_C(0) + : UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b +// to see if either is NaN, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_pd +FORCE_INLINE __m128d _mm_cmpunord_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + // Two NaNs are not equal in comparison operation. + uint64x2_t not_nan_a = + vceqq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(a)); + uint64x2_t not_nan_b = + vceqq_f64(vreinterpretq_f64_m128d(b), vreinterpretq_f64_m128d(b)); + return vreinterpretq_m128d_s32( + vmvnq_s32(vreinterpretq_s32_u64(vandq_u64(not_nan_a, not_nan_b)))); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = ((*(double *) &a0) == (*(double *) &a0) && + (*(double *) &b0) == (*(double *) &b0)) + ? UINT64_C(0) + : ~UINT64_C(0); + d[1] = ((*(double *) &a1) == (*(double *) &a1) && + (*(double *) &b1) == (*(double *) &b1)) + ? UINT64_C(0) + : ~UINT64_C(0); + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b to see if either is NaN, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_sd +FORCE_INLINE __m128d _mm_cmpunord_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_cmpunord_pd(a, b)); +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t d[2]; + d[0] = ((*(double *) &a0) == (*(double *) &a0) && + (*(double *) &b0) == (*(double *) &b0)) + ? UINT64_C(0) + : ~UINT64_C(0); + d[1] = a1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for greater-than-or-equal, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comige_sd +FORCE_INLINE int _mm_comige_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vgetq_lane_u64(vcgeq_f64(a, b), 0) & 0x1; +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + + return (*(double *) &a0 >= *(double *) &b0); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for greater-than, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comigt_sd +FORCE_INLINE int _mm_comigt_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vgetq_lane_u64(vcgtq_f64(a, b), 0) & 0x1; +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + + return (*(double *) &a0 > *(double *) &b0); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for less-than-or-equal, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comile_sd +FORCE_INLINE int _mm_comile_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vgetq_lane_u64(vcleq_f64(a, b), 0) & 0x1; +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + + return (*(double *) &a0 <= *(double *) &b0); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for less-than, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comilt_sd +FORCE_INLINE int _mm_comilt_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vgetq_lane_u64(vcltq_f64(a, b), 0) & 0x1; +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + + return (*(double *) &a0 < *(double *) &b0); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for equality, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comieq_sd +FORCE_INLINE int _mm_comieq_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vgetq_lane_u64(vceqq_f64(a, b), 0) & 0x1; +#else + uint32x4_t a_not_nan = + vceqq_u32(vreinterpretq_u32_m128d(a), vreinterpretq_u32_m128d(a)); + uint32x4_t b_not_nan = + vceqq_u32(vreinterpretq_u32_m128d(b), vreinterpretq_u32_m128d(b)); + uint32x4_t a_and_b_not_nan = vandq_u32(a_not_nan, b_not_nan); + uint32x4_t a_eq_b = + vceqq_u32(vreinterpretq_u32_m128d(a), vreinterpretq_u32_m128d(b)); + uint64x2_t and_results = vandq_u64(vreinterpretq_u64_u32(a_and_b_not_nan), + vreinterpretq_u64_u32(a_eq_b)); + return vgetq_lane_u64(and_results, 0) & 0x1; +#endif +} + +// Compare the lower double-precision (64-bit) floating-point element in a and b +// for not-equal, and return the boolean result (0 or 1). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comineq_sd +FORCE_INLINE int _mm_comineq_sd(__m128d a, __m128d b) +{ + return !_mm_comieq_sd(a, b); +} + +// Convert packed signed 32-bit integers in a to packed double-precision +// (64-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*32 +// m := j*64 +// dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepi32_pd +FORCE_INLINE __m128d _mm_cvtepi32_pd(__m128i a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcvtq_f64_s64(vmovl_s32(vget_low_s32(vreinterpretq_s32_m128i(a))))); +#else + double a0 = (double) vgetq_lane_s32(vreinterpretq_s32_m128i(a), 0); + double a1 = (double) vgetq_lane_s32(vreinterpretq_s32_m128i(a), 1); + return _mm_set_pd(a1, a0); +#endif +} + +// Converts the four signed 32-bit integer values of a to single-precision, +// floating-point values +// https://msdn.microsoft.com/en-us/library/vstudio/36bwxcx5(v=vs.100).aspx +FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a) +{ + return vreinterpretq_m128_f32(vcvtq_f32_s32(vreinterpretq_s32_m128i(a))); +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed 32-bit integers, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// k := 64*j +// dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_epi32 +FORCE_INLINE __m128i _mm_cvtpd_epi32(__m128d a) +{ + __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION); + double d0 = ((double *) &rnd)[0]; + double d1 = ((double *) &rnd)[1]; + return _mm_set_epi32(0, 0, (int32_t) d1, (int32_t) d0); +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed 32-bit integers, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// k := 64*j +// dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_pi32 +FORCE_INLINE __m64 _mm_cvtpd_pi32(__m128d a) +{ + __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION); + double d0 = ((double *) &rnd)[0]; + double d1 = ((double *) &rnd)[1]; + int32_t ALIGN_STRUCT(16) data[2] = {(int32_t) d0, (int32_t) d1}; + return vreinterpret_m64_s32(vld1_s32(data)); +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed single-precision (32-bit) floating-point elements, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := 32*j +// k := 64*j +// dst[i+31:i] := Convert_FP64_To_FP32(a[k+64:k]) +// ENDFOR +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_ps +FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a) +{ +#if defined(__aarch64__) + float32x2_t tmp = vcvt_f32_f64(vreinterpretq_f64_m128d(a)); + return vreinterpretq_m128_f32(vcombine_f32(tmp, vdup_n_f32(0))); +#else + float a0 = (float) ((double *) &a)[0]; + float a1 = (float) ((double *) &a)[1]; + return _mm_set_ps(0, 0, a1, a0); +#endif +} + +// Convert packed signed 32-bit integers in a to packed double-precision +// (64-bit) floating-point elements, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*32 +// m := j*64 +// dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_pd +FORCE_INLINE __m128d _mm_cvtpi32_pd(__m64 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcvtq_f64_s64(vmovl_s32(vreinterpret_s32_m64(a)))); +#else + double a0 = (double) vget_lane_s32(vreinterpret_s32_m64(a), 0); + double a1 = (double) vget_lane_s32(vreinterpret_s32_m64(a), 1); + return _mm_set_pd(a1, a0); +#endif +} + +// Converts the four single-precision, floating-point values of a to signed +// 32-bit integer values. +// +// r0 := (int) a0 +// r1 := (int) a1 +// r2 := (int) a2 +// r3 := (int) a3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/xdc42k5e(v=vs.100).aspx +// *NOTE*. The default rounding mode on SSE is 'round to even', which ARMv7-A +// does not support! It is supported on ARMv8-A however. +FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a) +{ +#if defined(__aarch64__) + switch (_MM_GET_ROUNDING_MODE()) { + case _MM_ROUND_NEAREST: + return vreinterpretq_m128i_s32(vcvtnq_s32_f32(a)); + case _MM_ROUND_DOWN: + return vreinterpretq_m128i_s32(vcvtmq_s32_f32(a)); + case _MM_ROUND_UP: + return vreinterpretq_m128i_s32(vcvtpq_s32_f32(a)); + default: // _MM_ROUND_TOWARD_ZERO + return vreinterpretq_m128i_s32(vcvtq_s32_f32(a)); + } +#else + float *f = (float *) &a; + switch (_MM_GET_ROUNDING_MODE()) { + case _MM_ROUND_NEAREST: { + uint32x4_t signmask = vdupq_n_u32(0x80000000); + float32x4_t half = vbslq_f32(signmask, vreinterpretq_f32_m128(a), + vdupq_n_f32(0.5f)); /* +/- 0.5 */ + int32x4_t r_normal = vcvtq_s32_f32(vaddq_f32( + vreinterpretq_f32_m128(a), half)); /* round to integer: [a + 0.5]*/ + int32x4_t r_trunc = vcvtq_s32_f32( + vreinterpretq_f32_m128(a)); /* truncate to integer: [a] */ + int32x4_t plusone = vreinterpretq_s32_u32(vshrq_n_u32( + vreinterpretq_u32_s32(vnegq_s32(r_trunc)), 31)); /* 1 or 0 */ + int32x4_t r_even = vbicq_s32(vaddq_s32(r_trunc, plusone), + vdupq_n_s32(1)); /* ([a] + {0,1}) & ~1 */ + float32x4_t delta = vsubq_f32( + vreinterpretq_f32_m128(a), + vcvtq_f32_s32(r_trunc)); /* compute delta: delta = (a - [a]) */ + uint32x4_t is_delta_half = + vceqq_f32(delta, half); /* delta == +/- 0.5 */ + return vreinterpretq_m128i_s32( + vbslq_s32(is_delta_half, r_even, r_normal)); + } + case _MM_ROUND_DOWN: + return _mm_set_epi32(floorf(f[3]), floorf(f[2]), floorf(f[1]), + floorf(f[0])); + case _MM_ROUND_UP: + return _mm_set_epi32(ceilf(f[3]), ceilf(f[2]), ceilf(f[1]), + ceilf(f[0])); + default: // _MM_ROUND_TOWARD_ZERO + return _mm_set_epi32((int32_t) f[3], (int32_t) f[2], (int32_t) f[1], + (int32_t) f[0]); + } +#endif +} + +// Convert packed single-precision (32-bit) floating-point elements in a to +// packed double-precision (64-bit) floating-point elements, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := 64*j +// k := 32*j +// dst[i+63:i] := Convert_FP32_To_FP64(a[k+31:k]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pd +FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcvt_f64_f32(vget_low_f32(vreinterpretq_f32_m128(a)))); +#else + double a0 = (double) vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + double a1 = (double) vgetq_lane_f32(vreinterpretq_f32_m128(a), 1); + return _mm_set_pd(a1, a0); +#endif +} + +// Copy the lower double-precision (64-bit) floating-point element of a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_f64 +FORCE_INLINE double _mm_cvtsd_f64(__m128d a) +{ +#if defined(__aarch64__) + return (double) vgetq_lane_f64(vreinterpretq_f64_m128d(a), 0); +#else + return ((double *) &a)[0]; +#endif +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 32-bit integer, and store the result in dst. +// +// dst[31:0] := Convert_FP64_To_Int32(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si32 +FORCE_INLINE int32_t _mm_cvtsd_si32(__m128d a) +{ +#if defined(__aarch64__) + return (int32_t) vgetq_lane_f64(vrndiq_f64(vreinterpretq_f64_m128d(a)), 0); +#else + __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION); + double ret = ((double *) &rnd)[0]; + return (int32_t) ret; +#endif +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64 +FORCE_INLINE int64_t _mm_cvtsd_si64(__m128d a) +{ +#if defined(__aarch64__) + return (int64_t) vgetq_lane_f64(vrndiq_f64(vreinterpretq_f64_m128d(a)), 0); +#else + __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION); + double ret = ((double *) &rnd)[0]; + return (int64_t) ret; +#endif +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64x +#define _mm_cvtsd_si64x _mm_cvtsd_si64 + +// Convert the lower double-precision (64-bit) floating-point element in b to a +// single-precision (32-bit) floating-point element, store the result in the +// lower element of dst, and copy the upper 3 packed elements from a to the +// upper elements of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_ss +FORCE_INLINE __m128 _mm_cvtsd_ss(__m128 a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vsetq_lane_f32( + vget_lane_f32(vcvt_f32_f64(vreinterpretq_f64_m128d(b)), 0), + vreinterpretq_f32_m128(a), 0)); +#else + return vreinterpretq_m128_f32(vsetq_lane_f32((float) ((double *) &b)[0], + vreinterpretq_f32_m128(a), 0)); +#endif +} + +// Copy the lower 32-bit integer in a to dst. +// +// dst[31:0] := a[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si32 +FORCE_INLINE int _mm_cvtsi128_si32(__m128i a) +{ + return vgetq_lane_s32(vreinterpretq_s32_m128i(a), 0); +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64 +FORCE_INLINE int64_t _mm_cvtsi128_si64(__m128i a) +{ + return vgetq_lane_s64(vreinterpretq_s64_m128i(a), 0); +} + +// Copy the lower 64-bit integer in a to dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x +#define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a) + +// Convert the signed 32-bit integer b to a double-precision (64-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_sd +FORCE_INLINE __m128d _mm_cvtsi32_sd(__m128d a, int32_t b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vsetq_lane_f64((double) b, vreinterpretq_f64_m128d(a), 0)); +#else + double bf = (double) b; + return vreinterpretq_m128d_s64( + vsetq_lane_s64(*(int64_t *) &bf, vreinterpretq_s64_m128d(a), 0)); +#endif +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x +#define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a) + +// Moves 32-bit integer a to the least significant 32 bits of an __m128 object, +// zero extending the upper bits. +// +// r0 := a +// r1 := 0x0 +// r2 := 0x0 +// r3 := 0x0 +// +// https://msdn.microsoft.com/en-us/library/ct3539ha%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_cvtsi32_si128(int a) +{ + return vreinterpretq_m128i_s32(vsetq_lane_s32(a, vdupq_n_s32(0), 0)); +} + +// Convert the signed 64-bit integer b to a double-precision (64-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_sd +FORCE_INLINE __m128d _mm_cvtsi64_sd(__m128d a, int64_t b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vsetq_lane_f64((double) b, vreinterpretq_f64_m128d(a), 0)); +#else + double bf = (double) b; + return vreinterpretq_m128d_s64( + vsetq_lane_s64(*(int64_t *) &bf, vreinterpretq_s64_m128d(a), 0)); +#endif +} + +// Moves 64-bit integer a to the least significant 64 bits of an __m128 object, +// zero extending the upper bits. +// +// r0 := a +// r1 := 0x0 +FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a) +{ + return vreinterpretq_m128i_s64(vsetq_lane_s64(a, vdupq_n_s64(0), 0)); +} + +// Copy 64-bit integer a to the lower element of dst, and zero the upper +// element. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_si128 +#define _mm_cvtsi64x_si128(a) _mm_cvtsi64_si128(a) + +// Convert the signed 64-bit integer b to a double-precision (64-bit) +// floating-point element, store the result in the lower element of dst, and +// copy the upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_sd +#define _mm_cvtsi64x_sd(a, b) _mm_cvtsi64_sd(a, b) + +// Convert the lower single-precision (32-bit) floating-point element in b to a +// double-precision (64-bit) floating-point element, store the result in the +// lower element of dst, and copy the upper element from a to the upper element +// of dst. +// +// dst[63:0] := Convert_FP32_To_FP64(b[31:0]) +// dst[127:64] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_sd +FORCE_INLINE __m128d _mm_cvtss_sd(__m128d a, __m128 b) +{ + double d = (double) vgetq_lane_f32(vreinterpretq_f32_m128(b), 0); +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vsetq_lane_f64(d, vreinterpretq_f64_m128d(a), 0)); +#else + return vreinterpretq_m128d_s64( + vsetq_lane_s64(*(int64_t *) &d, vreinterpretq_s64_m128d(a), 0)); +#endif +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_epi32 +FORCE_INLINE __m128i _mm_cvttpd_epi32(__m128d a) +{ + double a0 = ((double *) &a)[0]; + double a1 = ((double *) &a)[1]; + return _mm_set_epi32(0, 0, (int32_t) a1, (int32_t) a0); +} + +// Convert packed double-precision (64-bit) floating-point elements in a to +// packed 32-bit integers with truncation, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_pi32 +FORCE_INLINE __m64 _mm_cvttpd_pi32(__m128d a) +{ + double a0 = ((double *) &a)[0]; + double a1 = ((double *) &a)[1]; + int32_t ALIGN_STRUCT(16) data[2] = {(int32_t) a0, (int32_t) a1}; + return vreinterpret_m64_s32(vld1_s32(data)); +} + +// Converts the four single-precision, floating-point values of a to signed +// 32-bit integer values using truncate. +// https://msdn.microsoft.com/en-us/library/vstudio/1h005y6x(v=vs.100).aspx +FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a) +{ + return vreinterpretq_m128i_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a))); +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 32-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int32_Truncate(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si32 +FORCE_INLINE int32_t _mm_cvttsd_si32(__m128d a) +{ + double ret = *((double *) &a); + return (int32_t) ret; +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64 +FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a) +{ +#if defined(__aarch64__) + return vgetq_lane_s64(vcvtq_s64_f64(vreinterpretq_f64_m128d(a)), 0); +#else + double ret = *((double *) &a); + return (int64_t) ret; +#endif +} + +// Convert the lower double-precision (64-bit) floating-point element in a to a +// 64-bit integer with truncation, and store the result in dst. +// +// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64x +#define _mm_cvttsd_si64x(a) _mm_cvttsd_si64(a) + +// Divide packed double-precision (64-bit) floating-point elements in a by +// packed elements in b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := 64*j +// dst[i+63:i] := a[i+63:i] / b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_pd +FORCE_INLINE __m128d _mm_div_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vdivq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] / db[0]; + c[1] = da[1] / db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Divide the lower double-precision (64-bit) floating-point element in a by the +// lower double-precision (64-bit) floating-point element in b, store the result +// in the lower element of dst, and copy the upper element from a to the upper +// element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_sd +FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + float64x2_t tmp = + vdivq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b)); + return vreinterpretq_m128d_f64( + vsetq_lane_f64(vgetq_lane_f64(vreinterpretq_f64_m128d(a), 1), tmp, 1)); +#else + return _mm_move_sd(a, _mm_div_pd(a, b)); +#endif +} + +// Extracts the selected signed or unsigned 16-bit integer from a and zero +// extends. +// https://msdn.microsoft.com/en-us/library/6dceta0c(v=vs.100).aspx +// FORCE_INLINE int _mm_extract_epi16(__m128i a, __constrange(0,8) int imm) +#define _mm_extract_epi16(a, imm) \ + vgetq_lane_u16(vreinterpretq_u16_m128i(a), (imm)) + +// Inserts the least significant 16 bits of b into the selected 16-bit integer +// of a. +// https://msdn.microsoft.com/en-us/library/kaze8hz1%28v=vs.100%29.aspx +// FORCE_INLINE __m128i _mm_insert_epi16(__m128i a, int b, +// __constrange(0,8) int imm) +#define _mm_insert_epi16(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s16( \ + vsetq_lane_s16((b), vreinterpretq_s16_m128i(a), (imm))); \ + }) + +// Loads two double-precision from 16-byte aligned memory, floating-point +// values. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd +FORCE_INLINE __m128d _mm_load_pd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_f64(p)); +#else + const float *fp = (const float *) p; + float ALIGN_STRUCT(16) data[4] = {fp[0], fp[1], fp[2], fp[3]}; + return vreinterpretq_m128d_f32(vld1q_f32(data)); +#endif +} + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd1 +#define _mm_load_pd1 _mm_load1_pd + +// Load a double-precision (64-bit) floating-point element from memory into the +// lower of dst, and zero the upper element. mem_addr does not need to be +// aligned on any particular boundary. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_sd +FORCE_INLINE __m128d _mm_load_sd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vsetq_lane_f64(*p, vdupq_n_f64(0), 0)); +#else + const float *fp = (const float *) p; + float ALIGN_STRUCT(16) data[4] = {fp[0], fp[1], 0, 0}; + return vreinterpretq_m128d_f32(vld1q_f32(data)); +#endif +} + +// Loads 128-bit value. : +// https://msdn.microsoft.com/en-us/library/atzzad1h(v=vs.80).aspx +FORCE_INLINE __m128i _mm_load_si128(const __m128i *p) +{ + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); +} + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load1_pd +FORCE_INLINE __m128d _mm_load1_pd(const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_dup_f64(p)); +#else + return vreinterpretq_m128d_s64(vdupq_n_s64(*(const int64_t *) p)); +#endif +} + +// Load a double-precision (64-bit) floating-point element from memory into the +// upper element of dst, and copy the lower element from a to dst. mem_addr does +// not need to be aligned on any particular boundary. +// +// dst[63:0] := a[63:0] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadh_pd +FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcombine_f64(vget_low_f64(vreinterpretq_f64_m128d(a)), vld1_f64(p))); +#else + return vreinterpretq_m128d_f32(vcombine_f32( + vget_low_f32(vreinterpretq_f32_m128d(a)), vld1_f32((const float *) p))); +#endif +} + +// Load 64-bit integer from memory into the first element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_epi64 +FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p) +{ + /* Load the lower 64 bits of the value pointed to by p into the + * lower 64 bits of the result, zeroing the upper 64 bits of the result. + */ + return vreinterpretq_m128i_s32( + vcombine_s32(vld1_s32((int32_t const *) p), vcreate_s32(0))); +} + +// Load a double-precision (64-bit) floating-point element from memory into the +// lower element of dst, and copy the upper element from a to dst. mem_addr does +// not need to be aligned on any particular boundary. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_pd +FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vcombine_f64(vld1_f64(p), vget_high_f64(vreinterpretq_f64_m128d(a)))); +#else + return vreinterpretq_m128d_f32( + vcombine_f32(vld1_f32((const float *) p), + vget_high_f32(vreinterpretq_f32_m128d(a)))); +#endif +} + +// Load 2 double-precision (64-bit) floating-point elements from memory into dst +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[63:0] := MEM[mem_addr+127:mem_addr+64] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_pd +FORCE_INLINE __m128d _mm_loadr_pd(const double *p) +{ +#if defined(__aarch64__) + float64x2_t v = vld1q_f64(p); + return vreinterpretq_m128d_f64(vextq_f64(v, v, 1)); +#else + int64x2_t v = vld1q_s64((const int64_t *) p); + return vreinterpretq_m128d_s64(vextq_s64(v, v, 1)); +#endif +} + +// Loads two double-precision from unaligned memory, floating-point values. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_pd +FORCE_INLINE __m128d _mm_loadu_pd(const double *p) +{ + return _mm_load_pd(p); +} + +// Loads 128-bit value. : +// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx +FORCE_INLINE __m128i _mm_loadu_si128(const __m128i *p) +{ + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); +} + +// Load unaligned 32-bit integer from memory into the first element of dst. +// +// dst[31:0] := MEM[mem_addr+31:mem_addr] +// dst[MAX:32] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si32 +FORCE_INLINE __m128i _mm_loadu_si32(const void *p) +{ + return vreinterpretq_m128i_s32( + vsetq_lane_s32(*(const int32_t *) p, vdupq_n_s32(0), 0)); +} + +// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +// integers from b. +// +// r0 := (a0 * b0) + (a1 * b1) +// r1 := (a2 * b2) + (a3 * b3) +// r2 := (a4 * b4) + (a5 * b5) +// r3 := (a6 * b6) + (a7 * b7) +// https://msdn.microsoft.com/en-us/library/yht36sa6(v=vs.90).aspx +FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b) +{ + int32x4_t low = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)), + vget_low_s16(vreinterpretq_s16_m128i(b))); + int32x4_t high = vmull_s16(vget_high_s16(vreinterpretq_s16_m128i(a)), + vget_high_s16(vreinterpretq_s16_m128i(b))); + + int32x2_t low_sum = vpadd_s32(vget_low_s32(low), vget_high_s32(low)); + int32x2_t high_sum = vpadd_s32(vget_low_s32(high), vget_high_s32(high)); + + return vreinterpretq_m128i_s32(vcombine_s32(low_sum, high_sum)); +} + +// Conditionally store 8-bit integer elements from a into memory using mask +// (elements are not stored when the highest bit is not set in the corresponding +// element) and a non-temporal memory hint. mem_addr does not need to be aligned +// on any particular boundary. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmoveu_si128 +FORCE_INLINE void _mm_maskmoveu_si128(__m128i a, __m128i mask, char *mem_addr) +{ + int8x16_t shr_mask = vshrq_n_s8(vreinterpretq_s8_m128i(mask), 7); + __m128 b = _mm_load_ps((const float *) mem_addr); + int8x16_t masked = + vbslq_s8(vreinterpretq_u8_s8(shr_mask), vreinterpretq_s8_m128i(a), + vreinterpretq_s8_m128(b)); + vst1q_s8((int8_t *) mem_addr, masked); +} + +// Computes the pairwise maxima of the 8 signed 16-bit integers from a and the 8 +// signed 16-bit integers from b. +// https://msdn.microsoft.com/en-us/LIBRary/3x060h7c(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vmaxq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Computes the pairwise maxima of the 16 unsigned 8-bit integers from a and the +// 16 unsigned 8-bit integers from b. +// https://msdn.microsoft.com/en-us/library/st6634za(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vmaxq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b, +// and store packed maximum values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pd +FORCE_INLINE __m128d _mm_max_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) +#if SSE2NEON_PRECISE_MINMAX + float64x2_t _a = vreinterpretq_f64_m128d(a); + float64x2_t _b = vreinterpretq_f64_m128d(b); + return vreinterpretq_m128d_f64(vbslq_f64(vcgtq_f64(_a, _b), _a, _b)); +#else + return vreinterpretq_m128d_f64( + vmaxq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#endif +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) > (*(double *) &b0) ? a0 : b0; + d[1] = (*(double *) &a1) > (*(double *) &b1) ? a1 : b1; + + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b, store the maximum value in the lower element of dst, and copy the upper +// element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_sd +FORCE_INLINE __m128d _mm_max_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_max_pd(a, b)); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2] = {da[0] > db[0] ? da[0] : db[0], da[1]}; + return vreinterpretq_m128d_f32(vld1q_f32((float32_t *) c)); +#endif +} + +// Computes the pairwise minima of the 8 signed 16-bit integers from a and the 8 +// signed 16-bit integers from b. +// https://msdn.microsoft.com/en-us/library/vstudio/6te997ew(v=vs.100).aspx +FORCE_INLINE __m128i _mm_min_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vminq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Computes the pairwise minima of the 16 unsigned 8-bit integers from a and the +// 16 unsigned 8-bit integers from b. +// https://msdn.microsoft.com/ko-kr/library/17k8cf58(v=vs.100).aspxx +FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vminq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +// Compare packed double-precision (64-bit) floating-point elements in a and b, +// and store packed minimum values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pd +FORCE_INLINE __m128d _mm_min_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) +#if SSE2NEON_PRECISE_MINMAX + float64x2_t _a = vreinterpretq_f64_m128d(a); + float64x2_t _b = vreinterpretq_f64_m128d(b); + return vreinterpretq_m128d_f64(vbslq_f64(vcltq_f64(_a, _b), _a, _b)); +#else + return vreinterpretq_m128d_f64( + vminq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#endif +#else + uint64_t a0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(a)); + uint64_t a1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(a)); + uint64_t b0 = (uint64_t) vget_low_u64(vreinterpretq_u64_m128d(b)); + uint64_t b1 = (uint64_t) vget_high_u64(vreinterpretq_u64_m128d(b)); + uint64_t d[2]; + d[0] = (*(double *) &a0) < (*(double *) &b0) ? a0 : b0; + d[1] = (*(double *) &a1) < (*(double *) &b1) ? a1 : b1; + return vreinterpretq_m128d_u64(vld1q_u64(d)); +#endif +} + +// Compare the lower double-precision (64-bit) floating-point elements in a and +// b, store the minimum value in the lower element of dst, and copy the upper +// element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_sd +FORCE_INLINE __m128d _mm_min_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_min_pd(a, b)); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2] = {da[0] < db[0] ? da[0] : db[0], da[1]}; + return vreinterpretq_m128d_f32(vld1q_f32((float32_t *) c)); +#endif +} + +// Copy the lower 64-bit integer in a to the lower element of dst, and zero the +// upper element. +// +// dst[63:0] := a[63:0] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_epi64 +FORCE_INLINE __m128i _mm_move_epi64(__m128i a) +{ + return vreinterpretq_m128i_s64( + vsetq_lane_s64(0, vreinterpretq_s64_m128i(a), 1)); +} + +// Move the lower double-precision (64-bit) floating-point element from b to the +// lower element of dst, and copy the upper element from a to the upper element +// of dst. +// +// dst[63:0] := b[63:0] +// dst[127:64] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_sd +FORCE_INLINE __m128d _mm_move_sd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_f32( + vcombine_f32(vget_low_f32(vreinterpretq_f32_m128d(b)), + vget_high_f32(vreinterpretq_f32_m128d(a)))); +} + +// NEON does not provide a version of this function. +// Creates a 16-bit mask from the most significant bits of the 16 signed or +// unsigned 8-bit integers in a and zero extends the upper bits. +// https://msdn.microsoft.com/en-us/library/vstudio/s090c8fk(v=vs.100).aspx +FORCE_INLINE int _mm_movemask_epi8(__m128i a) +{ + // Use increasingly wide shifts+adds to collect the sign bits + // together. + // Since the widening shifts would be rather confusing to follow in little + // endian, everything will be illustrated in big endian order instead. This + // has a different result - the bits would actually be reversed on a big + // endian machine. + + // Starting input (only half the elements are shown): + // 89 ff 1d c0 00 10 99 33 + uint8x16_t input = vreinterpretq_u8_m128i(a); + + // Shift out everything but the sign bits with an unsigned shift right. + // + // Bytes of the vector:: + // 89 ff 1d c0 00 10 99 33 + // \ \ \ \ \ \ \ \ high_bits = (uint16x4_t)(input >> 7) + // | | | | | | | | + // 01 01 00 01 00 00 01 00 + // + // Bits of first important lane(s): + // 10001001 (89) + // \______ + // | + // 00000001 (01) + uint16x8_t high_bits = vreinterpretq_u16_u8(vshrq_n_u8(input, 7)); + + // Merge the even lanes together with a 16-bit unsigned shift right + add. + // 'xx' represents garbage data which will be ignored in the final result. + // In the important bytes, the add functions like a binary OR. + // + // 01 01 00 01 00 00 01 00 + // \_ | \_ | \_ | \_ | paired16 = (uint32x4_t)(input + (input >> 7)) + // \| \| \| \| + // xx 03 xx 01 xx 00 xx 02 + // + // 00000001 00000001 (01 01) + // \_______ | + // \| + // xxxxxxxx xxxxxx11 (xx 03) + uint32x4_t paired16 = + vreinterpretq_u32_u16(vsraq_n_u16(high_bits, high_bits, 7)); + + // Repeat with a wider 32-bit shift + add. + // xx 03 xx 01 xx 00 xx 02 + // \____ | \____ | paired32 = (uint64x1_t)(paired16 + (paired16 >> + // 14)) + // \| \| + // xx xx xx 0d xx xx xx 02 + // + // 00000011 00000001 (03 01) + // \\_____ || + // '----.\|| + // xxxxxxxx xxxx1101 (xx 0d) + uint64x2_t paired32 = + vreinterpretq_u64_u32(vsraq_n_u32(paired16, paired16, 14)); + + // Last, an even wider 64-bit shift + add to get our result in the low 8 bit + // lanes. xx xx xx 0d xx xx xx 02 + // \_________ | paired64 = (uint8x8_t)(paired32 + (paired32 >> + // 28)) + // \| + // xx xx xx xx xx xx xx d2 + // + // 00001101 00000010 (0d 02) + // \ \___ | | + // '---. \| | + // xxxxxxxx 11010010 (xx d2) + uint8x16_t paired64 = + vreinterpretq_u8_u64(vsraq_n_u64(paired32, paired32, 28)); + + // Extract the low 8 bits from each 64-bit lane with 2 8-bit extracts. + // xx xx xx xx xx xx xx d2 + // || return paired64[0] + // d2 + // Note: Little endian would return the correct value 4b (01001011) instead. + return vgetq_lane_u8(paired64, 0) | ((int) vgetq_lane_u8(paired64, 8) << 8); +} + +// Set each bit of mask dst based on the most significant bit of the +// corresponding packed double-precision (64-bit) floating-point element in a. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pd +FORCE_INLINE int _mm_movemask_pd(__m128d a) +{ + uint64x2_t input = vreinterpretq_u64_m128d(a); + uint64x2_t high_bits = vshrq_n_u64(input, 63); + return vgetq_lane_u64(high_bits, 0) | (vgetq_lane_u64(high_bits, 1) << 1); +} + +// Copy the lower 64-bit integer in a to dst. +// +// dst[63:0] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movepi64_pi64 +FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a) +{ + return vreinterpret_m64_s64(vget_low_s64(vreinterpretq_s64_m128i(a))); +} + +// Copy the 64-bit integer a to the lower element of dst, and zero the upper +// element. +// +// dst[63:0] := a[63:0] +// dst[127:64] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movpi64_epi64 +FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a) +{ + return vreinterpretq_m128i_s64( + vcombine_s64(vreinterpret_s64_m64(a), vdup_n_s64(0))); +} + +// Multiply the low unsigned 32-bit integers from each packed 64-bit element in +// a and b, and store the unsigned 64-bit results in dst. +// +// r0 := (a0 & 0xFFFFFFFF) * (b0 & 0xFFFFFFFF) +// r1 := (a2 & 0xFFFFFFFF) * (b2 & 0xFFFFFFFF) +FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b) +{ + // vmull_u32 upcasts instead of masking, so we downcast. + uint32x2_t a_lo = vmovn_u64(vreinterpretq_u64_m128i(a)); + uint32x2_t b_lo = vmovn_u64(vreinterpretq_u64_m128i(b)); + return vreinterpretq_m128i_u64(vmull_u32(a_lo, b_lo)); +} + +// Multiply packed double-precision (64-bit) floating-point elements in a and b, +// and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_pd +FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vmulq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] * db[0]; + c[1] = da[1] * db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Multiply the lower double-precision (64-bit) floating-point element in a and +// b, store the result in the lower element of dst, and copy the upper element +// from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_mul_sd +FORCE_INLINE __m128d _mm_mul_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_mul_pd(a, b)); +} + +// Multiply the low unsigned 32-bit integers from a and b, and store the +// unsigned 64-bit result in dst. +// +// dst[63:0] := a[31:0] * b[31:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_su32 +FORCE_INLINE __m64 _mm_mul_su32(__m64 a, __m64 b) +{ + return vreinterpret_m64_u64(vget_low_u64( + vmull_u32(vreinterpret_u32_m64(a), vreinterpret_u32_m64(b)))); +} + +// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +// integers from b. +// +// r0 := (a0 * b0)[31:16] +// r1 := (a1 * b1)[31:16] +// ... +// r7 := (a7 * b7)[31:16] +// +// https://msdn.microsoft.com/en-us/library/vstudio/59hddw1d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b) +{ + /* FIXME: issue with large values because of result saturation */ + // int16x8_t ret = vqdmulhq_s16(vreinterpretq_s16_m128i(a), + // vreinterpretq_s16_m128i(b)); /* =2*a*b */ return + // vreinterpretq_m128i_s16(vshrq_n_s16(ret, 1)); + int16x4_t a3210 = vget_low_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b3210 = vget_low_s16(vreinterpretq_s16_m128i(b)); + int32x4_t ab3210 = vmull_s16(a3210, b3210); /* 3333222211110000 */ + int16x4_t a7654 = vget_high_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b7654 = vget_high_s16(vreinterpretq_s16_m128i(b)); + int32x4_t ab7654 = vmull_s16(a7654, b7654); /* 7777666655554444 */ + uint16x8x2_t r = + vuzpq_u16(vreinterpretq_u16_s32(ab3210), vreinterpretq_u16_s32(ab7654)); + return vreinterpretq_m128i_u16(r.val[1]); +} + +// Multiply the packed unsigned 16-bit integers in a and b, producing +// intermediate 32-bit integers, and store the high 16 bits of the intermediate +// integers in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_epu16 +FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b) +{ + uint16x4_t a3210 = vget_low_u16(vreinterpretq_u16_m128i(a)); + uint16x4_t b3210 = vget_low_u16(vreinterpretq_u16_m128i(b)); + uint32x4_t ab3210 = vmull_u16(a3210, b3210); +#if defined(__aarch64__) + uint32x4_t ab7654 = + vmull_high_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b)); + uint16x8_t r = vuzp2q_u16(vreinterpretq_u16_u32(ab3210), + vreinterpretq_u16_u32(ab7654)); + return vreinterpretq_m128i_u16(r); +#else + uint16x4_t a7654 = vget_high_u16(vreinterpretq_u16_m128i(a)); + uint16x4_t b7654 = vget_high_u16(vreinterpretq_u16_m128i(b)); + uint32x4_t ab7654 = vmull_u16(a7654, b7654); + uint16x8x2_t r = + vuzpq_u16(vreinterpretq_u16_u32(ab3210), vreinterpretq_u16_u32(ab7654)); + return vreinterpretq_m128i_u16(r.val[1]); +#endif +} + +// Multiplies the 8 signed or unsigned 16-bit integers from a by the 8 signed or +// unsigned 16-bit integers from b. +// +// r0 := (a0 * b0)[15:0] +// r1 := (a1 * b1)[15:0] +// ... +// r7 := (a7 * b7)[15:0] +// +// https://msdn.microsoft.com/en-us/library/vstudio/9ks1472s(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vmulq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Compute the bitwise OR of packed double-precision (64-bit) floating-point +// elements in a and b, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_or_pd +FORCE_INLINE __m128d _mm_or_pd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_s64( + vorrq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); +} + +// Computes the bitwise OR of the 128-bit value in a and the 128-bit value in b. +// +// r := a | b +// +// https://msdn.microsoft.com/en-us/library/vstudio/ew8ty0db(v=vs.100).aspx +FORCE_INLINE __m128i _mm_or_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vorrq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Packs the 16 signed 16-bit integers from a and b into 8-bit integers and +// saturates. +// https://msdn.microsoft.com/en-us/library/k4y4f7w5%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vcombine_s8(vqmovn_s16(vreinterpretq_s16_m128i(a)), + vqmovn_s16(vreinterpretq_s16_m128i(b)))); +} + +// Packs the 8 signed 32-bit integers from a and b into signed 16-bit integers +// and saturates. +// +// r0 := SignedSaturate(a0) +// r1 := SignedSaturate(a1) +// r2 := SignedSaturate(a2) +// r3 := SignedSaturate(a3) +// r4 := SignedSaturate(b0) +// r5 := SignedSaturate(b1) +// r6 := SignedSaturate(b2) +// r7 := SignedSaturate(b3) +// +// https://msdn.microsoft.com/en-us/library/393t56f9%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vcombine_s16(vqmovn_s32(vreinterpretq_s32_m128i(a)), + vqmovn_s32(vreinterpretq_s32_m128i(b)))); +} + +// Packs the 16 signed 16 - bit integers from a and b into 8 - bit unsigned +// integers and saturates. +// +// r0 := UnsignedSaturate(a0) +// r1 := UnsignedSaturate(a1) +// ... +// r7 := UnsignedSaturate(a7) +// r8 := UnsignedSaturate(b0) +// r9 := UnsignedSaturate(b1) +// ... +// r15 := UnsignedSaturate(b7) +// +// https://msdn.microsoft.com/en-us/library/07ad1wx4(v=vs.100).aspx +FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b) +{ + return vreinterpretq_m128i_u8( + vcombine_u8(vqmovun_s16(vreinterpretq_s16_m128i(a)), + vqmovun_s16(vreinterpretq_s16_m128i(b)))); +} + +// Pause the processor. This is typically used in spin-wait loops and depending +// on the x86 processor typical values are in the 40-100 cycle range. The +// 'yield' instruction isn't a good fit beacuse it's effectively a nop on most +// Arm cores. Experience with several databases has shown has shown an 'isb' is +// a reasonable approximation. +FORCE_INLINE void _mm_pause() +{ + __asm__ __volatile__("isb\n"); +} + +// Compute the absolute differences of packed unsigned 8-bit integers in a and +// b, then horizontally sum each consecutive 8 differences to produce two +// unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low +// 16 bits of 64-bit elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_epu8 +FORCE_INLINE __m128i _mm_sad_epu8(__m128i a, __m128i b) +{ + uint16x8_t t = vpaddlq_u8(vabdq_u8((uint8x16_t) a, (uint8x16_t) b)); + return vreinterpretq_m128i_u64(vpaddlq_u32(vpaddlq_u16(t))); +} + +// Sets the 8 signed 16-bit integer values. +// https://msdn.microsoft.com/en-au/library/3e0fek84(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set_epi16(short i7, + short i6, + short i5, + short i4, + short i3, + short i2, + short i1, + short i0) +{ + int16_t ALIGN_STRUCT(16) data[8] = {i0, i1, i2, i3, i4, i5, i6, i7}; + return vreinterpretq_m128i_s16(vld1q_s16(data)); +} + +// Sets the 4 signed 32-bit integer values. +// https://msdn.microsoft.com/en-us/library/vstudio/019beekt(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set_epi32(int i3, int i2, int i1, int i0) +{ + int32_t ALIGN_STRUCT(16) data[4] = {i0, i1, i2, i3}; + return vreinterpretq_m128i_s32(vld1q_s32(data)); +} + +// Returns the __m128i structure with its two 64-bit integer values +// initialized to the values of the two 64-bit integers passed in. +// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx +FORCE_INLINE __m128i _mm_set_epi64(__m64 i1, __m64 i2) +{ + return _mm_set_epi64x((int64_t) i1, (int64_t) i2); +} + +// Returns the __m128i structure with its two 64-bit integer values +// initialized to the values of the two 64-bit integers passed in. +// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx +FORCE_INLINE __m128i _mm_set_epi64x(int64_t i1, int64_t i2) +{ + return vreinterpretq_m128i_s64( + vcombine_s64(vcreate_s64(i2), vcreate_s64(i1))); +} + +// Sets the 16 signed 8-bit integer values. +// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set_epi8(signed char b15, + signed char b14, + signed char b13, + signed char b12, + signed char b11, + signed char b10, + signed char b9, + signed char b8, + signed char b7, + signed char b6, + signed char b5, + signed char b4, + signed char b3, + signed char b2, + signed char b1, + signed char b0) +{ + int8_t ALIGN_STRUCT(16) + data[16] = {(int8_t) b0, (int8_t) b1, (int8_t) b2, (int8_t) b3, + (int8_t) b4, (int8_t) b5, (int8_t) b6, (int8_t) b7, + (int8_t) b8, (int8_t) b9, (int8_t) b10, (int8_t) b11, + (int8_t) b12, (int8_t) b13, (int8_t) b14, (int8_t) b15}; + return (__m128i) vld1q_s8(data); +} + +// Set packed double-precision (64-bit) floating-point elements in dst with the +// supplied values. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd +FORCE_INLINE __m128d _mm_set_pd(double e1, double e0) +{ + double ALIGN_STRUCT(16) data[2] = {e0, e1}; +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vld1q_f64((float64_t *) data)); +#else + return vreinterpretq_m128d_f32(vld1q_f32((float32_t *) data)); +#endif +} + +// Broadcast double-precision (64-bit) floating-point value a to all elements of +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd1 +#define _mm_set_pd1 _mm_set1_pd + +// Copy double-precision (64-bit) floating-point element a to the lower element +// of dst, and zero the upper element. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_sd +FORCE_INLINE __m128d _mm_set_sd(double a) +{ + return _mm_set_pd(0, a); +} + +// Sets the 8 signed 16-bit integer values to w. +// +// r0 := w +// r1 := w +// ... +// r7 := w +// +// https://msdn.microsoft.com/en-us/library/k0ya3x0e(v=vs.90).aspx +FORCE_INLINE __m128i _mm_set1_epi16(short w) +{ + return vreinterpretq_m128i_s16(vdupq_n_s16(w)); +} + +// Sets the 4 signed 32-bit integer values to i. +// +// r0 := i +// r1 := i +// r2 := i +// r3 := I +// +// https://msdn.microsoft.com/en-us/library/vstudio/h4xscxat(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set1_epi32(int _i) +{ + return vreinterpretq_m128i_s32(vdupq_n_s32(_i)); +} + +// Sets the 2 signed 64-bit integer values to i. +// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/whtfzhzk(v=vs.100) +FORCE_INLINE __m128i _mm_set1_epi64(__m64 _i) +{ + return vreinterpretq_m128i_s64(vdupq_n_s64((int64_t) _i)); +} + +// Sets the 2 signed 64-bit integer values to i. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_epi64x +FORCE_INLINE __m128i _mm_set1_epi64x(int64_t _i) +{ + return vreinterpretq_m128i_s64(vdupq_n_s64(_i)); +} + +// Sets the 16 signed 8-bit integer values to b. +// +// r0 := b +// r1 := b +// ... +// r15 := b +// +// https://msdn.microsoft.com/en-us/library/6e14xhyf(v=vs.100).aspx +FORCE_INLINE __m128i _mm_set1_epi8(signed char w) +{ + return vreinterpretq_m128i_s8(vdupq_n_s8(w)); +} + +// Broadcast double-precision (64-bit) floating-point value a to all elements of +// dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_pd +FORCE_INLINE __m128d _mm_set1_pd(double d) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vdupq_n_f64(d)); +#else + return vreinterpretq_m128d_s64(vdupq_n_s64(*(int64_t *) &d)); +#endif +} + +// Sets the 8 signed 16-bit integer values in reverse order. +// +// Return Value +// r0 := w0 +// r1 := w1 +// ... +// r7 := w7 +FORCE_INLINE __m128i _mm_setr_epi16(short w0, + short w1, + short w2, + short w3, + short w4, + short w5, + short w6, + short w7) +{ + int16_t ALIGN_STRUCT(16) data[8] = {w0, w1, w2, w3, w4, w5, w6, w7}; + return vreinterpretq_m128i_s16(vld1q_s16((int16_t *) data)); +} + +// Sets the 4 signed 32-bit integer values in reverse order +// https://technet.microsoft.com/en-us/library/security/27yb3ee5(v=vs.90).aspx +FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0) +{ + int32_t ALIGN_STRUCT(16) data[4] = {i3, i2, i1, i0}; + return vreinterpretq_m128i_s32(vld1q_s32(data)); +} + +// Set packed 64-bit integers in dst with the supplied values in reverse order. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_epi64 +FORCE_INLINE __m128i _mm_setr_epi64(__m64 e1, __m64 e0) +{ + return vreinterpretq_m128i_s64(vcombine_s64(e1, e0)); +} + +// Sets the 16 signed 8-bit integer values in reverse order. +// https://msdn.microsoft.com/en-us/library/2khb9c7k(v=vs.90).aspx +FORCE_INLINE __m128i _mm_setr_epi8(signed char b0, + signed char b1, + signed char b2, + signed char b3, + signed char b4, + signed char b5, + signed char b6, + signed char b7, + signed char b8, + signed char b9, + signed char b10, + signed char b11, + signed char b12, + signed char b13, + signed char b14, + signed char b15) +{ + int8_t ALIGN_STRUCT(16) + data[16] = {(int8_t) b0, (int8_t) b1, (int8_t) b2, (int8_t) b3, + (int8_t) b4, (int8_t) b5, (int8_t) b6, (int8_t) b7, + (int8_t) b8, (int8_t) b9, (int8_t) b10, (int8_t) b11, + (int8_t) b12, (int8_t) b13, (int8_t) b14, (int8_t) b15}; + return (__m128i) vld1q_s8(data); +} + +// Set packed double-precision (64-bit) floating-point elements in dst with the +// supplied values in reverse order. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_pd +FORCE_INLINE __m128d _mm_setr_pd(double e1, double e0) +{ + return _mm_set_pd(e0, e1); +} + +// Return vector of type __m128d with all elements set to zero. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setzero_pd +FORCE_INLINE __m128d _mm_setzero_pd(void) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vdupq_n_f64(0)); +#else + return vreinterpretq_m128d_f32(vdupq_n_f32(0)); +#endif +} + +// Sets the 128-bit value to zero +// https://msdn.microsoft.com/en-us/library/vstudio/ys7dw0kh(v=vs.100).aspx +FORCE_INLINE __m128i _mm_setzero_si128(void) +{ + return vreinterpretq_m128i_s32(vdupq_n_s32(0)); +} + +// Shuffles the 4 signed or unsigned 32-bit integers in a as specified by imm. +// https://msdn.microsoft.com/en-us/library/56f67xbk%28v=vs.90%29.aspx +// FORCE_INLINE __m128i _mm_shuffle_epi32(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_epi32(a, imm) \ + __extension__({ \ + int32x4_t _input = vreinterpretq_s32_m128i(a); \ + int32x4_t _shuf = __builtin_shufflevector( \ + _input, _input, (imm) & (0x3), ((imm) >> 2) & 0x3, \ + ((imm) >> 4) & 0x3, ((imm) >> 6) & 0x3); \ + vreinterpretq_m128i_s32(_shuf); \ + }) +#else // generic +#define _mm_shuffle_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + switch (imm) { \ + case _MM_SHUFFLE(1, 0, 3, 2): \ + ret = _mm_shuffle_epi_1032((a)); \ + break; \ + case _MM_SHUFFLE(2, 3, 0, 1): \ + ret = _mm_shuffle_epi_2301((a)); \ + break; \ + case _MM_SHUFFLE(0, 3, 2, 1): \ + ret = _mm_shuffle_epi_0321((a)); \ + break; \ + case _MM_SHUFFLE(2, 1, 0, 3): \ + ret = _mm_shuffle_epi_2103((a)); \ + break; \ + case _MM_SHUFFLE(1, 0, 1, 0): \ + ret = _mm_shuffle_epi_1010((a)); \ + break; \ + case _MM_SHUFFLE(1, 0, 0, 1): \ + ret = _mm_shuffle_epi_1001((a)); \ + break; \ + case _MM_SHUFFLE(0, 1, 0, 1): \ + ret = _mm_shuffle_epi_0101((a)); \ + break; \ + case _MM_SHUFFLE(2, 2, 1, 1): \ + ret = _mm_shuffle_epi_2211((a)); \ + break; \ + case _MM_SHUFFLE(0, 1, 2, 2): \ + ret = _mm_shuffle_epi_0122((a)); \ + break; \ + case _MM_SHUFFLE(3, 3, 3, 2): \ + ret = _mm_shuffle_epi_3332((a)); \ + break; \ + case _MM_SHUFFLE(0, 0, 0, 0): \ + ret = _mm_shuffle_epi32_splat((a), 0); \ + break; \ + case _MM_SHUFFLE(1, 1, 1, 1): \ + ret = _mm_shuffle_epi32_splat((a), 1); \ + break; \ + case _MM_SHUFFLE(2, 2, 2, 2): \ + ret = _mm_shuffle_epi32_splat((a), 2); \ + break; \ + case _MM_SHUFFLE(3, 3, 3, 3): \ + ret = _mm_shuffle_epi32_splat((a), 3); \ + break; \ + default: \ + ret = _mm_shuffle_epi32_default((a), (imm)); \ + break; \ + } \ + ret; \ + }) +#endif + +// Shuffle double-precision (64-bit) floating-point elements using the control +// in imm8, and store the results in dst. +// +// dst[63:0] := (imm8[0] == 0) ? a[63:0] : a[127:64] +// dst[127:64] := (imm8[1] == 0) ? b[63:0] : b[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pd +#if __has_builtin(__builtin_shufflevector) +#define _mm_shuffle_pd(a, b, imm8) \ + vreinterpretq_m128d_s64(__builtin_shufflevector( \ + vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b), imm8 & 0x1, \ + ((imm8 & 0x2) >> 1) + 2)) +#else +#define _mm_shuffle_pd(a, b, imm8) \ + _mm_castsi128_pd(_mm_set_epi64x( \ + vgetq_lane_s64(vreinterpretq_s64_m128d(b), (imm8 & 0x2) >> 1), \ + vgetq_lane_s64(vreinterpretq_s64_m128d(a), imm8 & 0x1))) +#endif + +// FORCE_INLINE __m128i _mm_shufflehi_epi16(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shufflehi_epi16(a, imm) \ + __extension__({ \ + int16x8_t _input = vreinterpretq_s16_m128i(a); \ + int16x8_t _shuf = __builtin_shufflevector( \ + _input, _input, 0, 1, 2, 3, ((imm) & (0x3)) + 4, \ + (((imm) >> 2) & 0x3) + 4, (((imm) >> 4) & 0x3) + 4, \ + (((imm) >> 6) & 0x3) + 4); \ + vreinterpretq_m128i_s16(_shuf); \ + }) +#else // generic +#define _mm_shufflehi_epi16(a, imm) _mm_shufflehi_epi16_function((a), (imm)) +#endif + +// FORCE_INLINE __m128i _mm_shufflelo_epi16(__m128i a, +// __constrange(0,255) int imm) +#if __has_builtin(__builtin_shufflevector) +#define _mm_shufflelo_epi16(a, imm) \ + __extension__({ \ + int16x8_t _input = vreinterpretq_s16_m128i(a); \ + int16x8_t _shuf = __builtin_shufflevector( \ + _input, _input, ((imm) & (0x3)), (((imm) >> 2) & 0x3), \ + (((imm) >> 4) & 0x3), (((imm) >> 6) & 0x3), 4, 5, 6, 7); \ + vreinterpretq_m128i_s16(_shuf); \ + }) +#else // generic +#define _mm_shufflelo_epi16(a, imm) _mm_shufflelo_epi16_function((a), (imm)) +#endif + +// Shift packed 16-bit integers in a left by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF count[63:0] > 15 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := ZeroExtend16(a[i+15:i] << count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi16 +FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~15)) + return _mm_setzero_si128(); + + int16x8_t vc = vdupq_n_s16((int16_t) c); + return vreinterpretq_m128i_s16(vshlq_s16(vreinterpretq_s16_m128i(a), vc)); +} + +// Shift packed 32-bit integers in a left by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF count[63:0] > 31 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := ZeroExtend32(a[i+31:i] << count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi32 +FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~31)) + return _mm_setzero_si128(); + + int32x4_t vc = vdupq_n_s32((int32_t) c); + return vreinterpretq_m128i_s32(vshlq_s32(vreinterpretq_s32_m128i(a), vc)); +} + +// Shift packed 64-bit integers in a left by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF count[63:0] > 63 +// dst[i+63:i] := 0 +// ELSE +// dst[i+63:i] := ZeroExtend64(a[i+63:i] << count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi64 +FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~63)) + return _mm_setzero_si128(); + + int64x2_t vc = vdupq_n_s64((int64_t) c); + return vreinterpretq_m128i_s64(vshlq_s64(vreinterpretq_s64_m128i(a), vc)); +} + +// Shift packed 16-bit integers in a left by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[7:0] > 15 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := ZeroExtend16(a[i+15:i] << imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi16 +FORCE_INLINE __m128i _mm_slli_epi16(__m128i a, int imm) +{ + if (_sse2neon_unlikely(imm & ~15)) + return _mm_setzero_si128(); + return vreinterpretq_m128i_s16( + vshlq_s16(vreinterpretq_s16_m128i(a), vdupq_n_s16(imm))); +} + +// Shift packed 32-bit integers in a left by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF imm8[7:0] > 31 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := ZeroExtend32(a[i+31:i] << imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi32 +FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm) +{ + if (_sse2neon_unlikely(imm & ~31)) + return _mm_setzero_si128(); + return vreinterpretq_m128i_s32( + vshlq_s32(vreinterpretq_s32_m128i(a), vdupq_n_s32(imm))); +} + +// Shift packed 64-bit integers in a left by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF imm8[7:0] > 63 +// dst[i+63:i] := 0 +// ELSE +// dst[i+63:i] := ZeroExtend64(a[i+63:i] << imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi64 +FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm) +{ + if (_sse2neon_unlikely(imm & ~63)) + return _mm_setzero_si128(); + return vreinterpretq_m128i_s64( + vshlq_s64(vreinterpretq_s64_m128i(a), vdupq_n_s64(imm))); +} + +// Shift a left by imm8 bytes while shifting in zeros, and store the results in +// dst. +// +// tmp := imm8[7:0] +// IF tmp > 15 +// tmp := 16 +// FI +// dst[127:0] := a[127:0] << (tmp*8) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_si128 +FORCE_INLINE __m128i _mm_slli_si128(__m128i a, int imm) +{ + if (_sse2neon_unlikely(imm & ~15)) + return _mm_setzero_si128(); + uint8x16_t tmp[2] = {vdupq_n_u8(0), vreinterpretq_u8_m128i(a)}; + return vreinterpretq_m128i_u8( + vld1q_u8(((uint8_t const *) tmp) + (16 - imm))); +} + +// Compute the square root of packed double-precision (64-bit) floating-point +// elements in a, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_pd +FORCE_INLINE __m128d _mm_sqrt_pd(__m128d a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vsqrtq_f64(vreinterpretq_f64_m128d(a))); +#else + double a0 = sqrt(((double *) &a)[0]); + double a1 = sqrt(((double *) &a)[1]); + return _mm_set_pd(a1, a0); +#endif +} + +// Compute the square root of the lower double-precision (64-bit) floating-point +// element in b, store the result in the lower element of dst, and copy the +// upper element from a to the upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_sd +FORCE_INLINE __m128d _mm_sqrt_sd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return _mm_move_sd(a, _mm_sqrt_pd(b)); +#else + return _mm_set_pd(((double *) &a)[1], sqrt(((double *) &b)[0])); +#endif +} + +// Shift packed 16-bit integers in a right by count while shifting in sign bits, +// and store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF count[63:0] > 15 +// dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0) +// ELSE +// dst[i+15:i] := SignExtend16(a[i+15:i] >> count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi16 +FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count) +{ + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); + if (_sse2neon_unlikely(c & ~15)) + return _mm_cmplt_epi16(a, _mm_setzero_si128()); + return vreinterpretq_m128i_s16(vshlq_s16((int16x8_t) a, vdupq_n_s16(-c))); +} + +// Shift packed 32-bit integers in a right by count while shifting in sign bits, +// and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF count[63:0] > 31 +// dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0) +// ELSE +// dst[i+31:i] := SignExtend32(a[i+31:i] >> count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi32 +FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count) +{ + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); + if (_sse2neon_unlikely(c & ~31)) + return _mm_cmplt_epi32(a, _mm_setzero_si128()); + return vreinterpretq_m128i_s32(vshlq_s32((int32x4_t) a, vdupq_n_s32(-c))); +} + +// Shift packed 16-bit integers in a right by imm8 while shifting in sign +// bits, and store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[7:0] > 15 +// dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0) +// ELSE +// dst[i+15:i] := SignExtend16(a[i+15:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi16 +FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm) +{ + const int count = (imm & ~15) ? 15 : imm; + return (__m128i) vshlq_s16((int16x8_t) a, vdupq_n_s16(-count)); +} + +// Shift packed 32-bit integers in a right by imm8 while shifting in sign bits, +// and store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF imm8[7:0] > 31 +// dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0) +// ELSE +// dst[i+31:i] := SignExtend32(a[i+31:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi32 +// FORCE_INLINE __m128i _mm_srai_epi32(__m128i a, __constrange(0,255) int imm) +#define _mm_srai_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + if (_sse2neon_unlikely((imm) == 0)) { \ + ret = a; \ + } else if (_sse2neon_likely(0 < (imm) && (imm) < 32)) { \ + ret = vreinterpretq_m128i_s32( \ + vshlq_s32(vreinterpretq_s32_m128i(a), vdupq_n_s32(-imm))); \ + } else { \ + ret = vreinterpretq_m128i_s32( \ + vshrq_n_s32(vreinterpretq_s32_m128i(a), 31)); \ + } \ + ret; \ + }) + +// Shift packed 16-bit integers in a right by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF count[63:0] > 15 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := ZeroExtend16(a[i+15:i] >> count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi16 +FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~15)) + return _mm_setzero_si128(); + + int16x8_t vc = vdupq_n_s16(-(int16_t) c); + return vreinterpretq_m128i_u16(vshlq_u16(vreinterpretq_u16_m128i(a), vc)); +} + +// Shift packed 32-bit integers in a right by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF count[63:0] > 31 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := ZeroExtend32(a[i+31:i] >> count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi32 +FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~31)) + return _mm_setzero_si128(); + + int32x4_t vc = vdupq_n_s32(-(int32_t) c); + return vreinterpretq_m128i_u32(vshlq_u32(vreinterpretq_u32_m128i(a), vc)); +} + +// Shift packed 64-bit integers in a right by count while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF count[63:0] > 63 +// dst[i+63:i] := 0 +// ELSE +// dst[i+63:i] := ZeroExtend64(a[i+63:i] >> count[63:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi64 +FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) +{ + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); + if (_sse2neon_unlikely(c & ~63)) + return _mm_setzero_si128(); + + int64x2_t vc = vdupq_n_s64(-(int64_t) c); + return vreinterpretq_m128i_u64(vshlq_u64(vreinterpretq_u64_m128i(a), vc)); +} + +// Shift packed 16-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[7:0] > 15 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := ZeroExtend16(a[i+15:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi16 +#define _mm_srli_epi16(a, imm) \ + __extension__({ \ + __m128i ret; \ + if (_sse2neon_unlikely(imm & ~15)) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_u16( \ + vshlq_u16(vreinterpretq_u16_m128i(a), vdupq_n_s16(-imm))); \ + } \ + ret; \ + }) + +// Shift packed 32-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// IF imm8[7:0] > 31 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := ZeroExtend32(a[i+31:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi32 +// FORCE_INLINE __m128i _mm_srli_epi32(__m128i a, __constrange(0,255) int imm) +#define _mm_srli_epi32(a, imm) \ + __extension__({ \ + __m128i ret; \ + if (_sse2neon_unlikely(imm & ~31)) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_u32( \ + vshlq_u32(vreinterpretq_u32_m128i(a), vdupq_n_s32(-imm))); \ + } \ + ret; \ + }) + +// Shift packed 64-bit integers in a right by imm8 while shifting in zeros, and +// store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF imm8[7:0] > 63 +// dst[i+63:i] := 0 +// ELSE +// dst[i+63:i] := ZeroExtend64(a[i+63:i] >> imm8[7:0]) +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi64 +#define _mm_srli_epi64(a, imm) \ + __extension__({ \ + __m128i ret; \ + if (_sse2neon_unlikely(imm & ~63)) { \ + ret = _mm_setzero_si128(); \ + } else { \ + ret = vreinterpretq_m128i_u64( \ + vshlq_u64(vreinterpretq_u64_m128i(a), vdupq_n_s64(-imm))); \ + } \ + ret; \ + }) + +// Shift a right by imm8 bytes while shifting in zeros, and store the results in +// dst. +// +// tmp := imm8[7:0] +// IF tmp > 15 +// tmp := 16 +// FI +// dst[127:0] := a[127:0] >> (tmp*8) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_si128 +FORCE_INLINE __m128i _mm_srli_si128(__m128i a, int imm) +{ + if (_sse2neon_unlikely(imm & ~15)) + return _mm_setzero_si128(); + uint8x16_t tmp[2] = {vreinterpretq_u8_m128i(a), vdupq_n_u8(0)}; + return vreinterpretq_m128i_u8(vld1q_u8(((uint8_t const *) tmp) + imm)); +} + +// Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point +// elements) from a into memory. mem_addr must be aligned on a 16-byte boundary +// or a general-protection exception may be generated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd +FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + vst1q_f64((float64_t *) mem_addr, vreinterpretq_f64_m128d(a)); +#else + vst1q_f32((float32_t *) mem_addr, vreinterpretq_f32_m128d(a)); +#endif +} + +// Store the lower double-precision (64-bit) floating-point element from a into +// 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte +// boundary or a general-protection exception may be generated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd1 +FORCE_INLINE void _mm_store_pd1(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + float64x1_t a_low = vget_low_f64(vreinterpretq_f64_m128d(a)); + vst1q_f64((float64_t *) mem_addr, + vreinterpretq_f64_m128d(vcombine_f64(a_low, a_low))); +#else + float32x2_t a_low = vget_low_f32(vreinterpretq_f32_m128d(a)); + vst1q_f32((float32_t *) mem_addr, + vreinterpretq_f32_m128d(vcombine_f32(a_low, a_low))); +#endif +} + +// Store the lower double-precision (64-bit) floating-point element from a into +// memory. mem_addr does not need to be aligned on any particular boundary. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_store_sd +FORCE_INLINE void _mm_store_sd(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + vst1_f64((float64_t *) mem_addr, vget_low_f64(vreinterpretq_f64_m128d(a))); +#else + vst1_u64((uint64_t *) mem_addr, vget_low_u64(vreinterpretq_u64_m128d(a))); +#endif +} + +// Stores four 32-bit integer values as (as a __m128i value) at the address p. +// https://msdn.microsoft.com/en-us/library/vstudio/edk11s13(v=vs.100).aspx +FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a) +{ + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); +} + +// Store the lower double-precision (64-bit) floating-point element from a into +// 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte +// boundary or a general-protection exception may be generated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=9,526,5601&text=_mm_store1_pd +#define _mm_store1_pd _mm_store_pd1 + +// Store the upper double-precision (64-bit) floating-point element from a into +// memory. +// +// MEM[mem_addr+63:mem_addr] := a[127:64] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeh_pd +FORCE_INLINE void _mm_storeh_pd(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + vst1_f64((float64_t *) mem_addr, vget_high_f64(vreinterpretq_f64_m128d(a))); +#else + vst1_f32((float32_t *) mem_addr, vget_high_f32(vreinterpretq_f32_m128d(a))); +#endif +} + +// Reads the lower 64 bits of b and stores them into the lower 64 bits of a. +// https://msdn.microsoft.com/en-us/library/hhwf428f%28v=vs.90%29.aspx +FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b) +{ + uint64x1_t hi = vget_high_u64(vreinterpretq_u64_m128i(*a)); + uint64x1_t lo = vget_low_u64(vreinterpretq_u64_m128i(b)); + *a = vreinterpretq_m128i_u64(vcombine_u64(lo, hi)); +} + +// Store the lower double-precision (64-bit) floating-point element from a into +// memory. +// +// MEM[mem_addr+63:mem_addr] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storel_pd +FORCE_INLINE void _mm_storel_pd(double *mem_addr, __m128d a) +{ +#if defined(__aarch64__) + vst1_f64((float64_t *) mem_addr, vget_low_f64(vreinterpretq_f64_m128d(a))); +#else + vst1_f32((float32_t *) mem_addr, vget_low_f32(vreinterpretq_f32_m128d(a))); +#endif +} + +// Store 2 double-precision (64-bit) floating-point elements from a into memory +// in reverse order. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// MEM[mem_addr+63:mem_addr] := a[127:64] +// MEM[mem_addr+127:mem_addr+64] := a[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_pd +FORCE_INLINE void _mm_storer_pd(double *mem_addr, __m128d a) +{ + float32x4_t f = vreinterpretq_f32_m128d(a); + _mm_store_pd(mem_addr, vreinterpretq_m128d_f32(vextq_f32(f, f, 2))); +} + +// Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point +// elements) from a into memory. mem_addr does not need to be aligned on any +// particular boundary. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_pd +FORCE_INLINE void _mm_storeu_pd(double *mem_addr, __m128d a) +{ + _mm_store_pd(mem_addr, a); +} + +// Stores 128-bits of integer data a at the address p. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si128 +FORCE_INLINE void _mm_storeu_si128(__m128i *p, __m128i a) +{ + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); +} + +// Stores 32-bits of integer data a at the address p. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si32 +FORCE_INLINE void _mm_storeu_si32(void *p, __m128i a) +{ + vst1q_lane_s32((int32_t *) p, vreinterpretq_s32_m128i(a), 0); +} + +// Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point +// elements) from a into memory using a non-temporal memory hint. mem_addr must +// be aligned on a 16-byte boundary or a general-protection exception may be +// generated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pd +FORCE_INLINE void _mm_stream_pd(double *p, __m128d a) +{ +#if __has_builtin(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, (float32x4_t *) p); +#elif defined(__aarch64__) + vst1q_f64(p, vreinterpretq_f64_m128d(a)); +#else + vst1q_s64((int64_t *) p, vreinterpretq_s64_m128d(a)); +#endif +} + +// Stores the data in a to the address p without polluting the caches. If the +// cache line containing address p is already in the cache, the cache will be +// updated. +// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx +FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a) +{ +#if __has_builtin(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, p); +#else + vst1q_s64((int64_t *) p, vreinterpretq_s64_m128i(a)); +#endif +} + +// Store 32-bit integer a into memory using a non-temporal hint to minimize +// cache pollution. If the cache line containing address mem_addr is already in +// the cache, the cache will be updated. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_si32 +FORCE_INLINE void _mm_stream_si32(int *p, int a) +{ + vst1q_lane_s32((int32_t *) p, vdupq_n_s32(a), 0); +} + +// Subtract packed 16-bit integers in b from packed 16-bit integers in a, and +// store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi16 +FORCE_INLINE __m128i _mm_sub_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Subtracts the 4 signed or unsigned 32-bit integers of b from the 4 signed or +// unsigned 32-bit integers of a. +// +// r0 := a0 - b0 +// r1 := a1 - b1 +// r2 := a2 - b2 +// r3 := a3 - b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/fhh866h0(v=vs.100).aspx +FORCE_INLINE __m128i _mm_sub_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vsubq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Subtract 2 packed 64-bit integers in b from 2 packed 64-bit integers in a, +// and store the results in dst. +// r0 := a0 - b0 +// r1 := a1 - b1 +FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s64( + vsubq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +} + +// Subtract packed 8-bit integers in b from packed 8-bit integers in a, and +// store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi8 +FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Subtract packed double-precision (64-bit) floating-point elements in b from +// packed double-precision (64-bit) floating-point elements in a, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := a[i+63:i] - b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_sub_pd +FORCE_INLINE __m128d _mm_sub_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vsubq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[2]; + c[0] = da[0] - db[0]; + c[1] = da[1] - db[1]; + return vld1q_f32((float32_t *) c); +#endif +} + +// Subtract the lower double-precision (64-bit) floating-point element in b from +// the lower double-precision (64-bit) floating-point element in a, store the +// result in the lower element of dst, and copy the upper element from a to the +// upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_sd +FORCE_INLINE __m128d _mm_sub_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_sub_pd(a, b)); +} + +// Subtract 64-bit integer b from 64-bit integer a, and store the result in dst. +// +// dst[63:0] := a[63:0] - b[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_si64 +FORCE_INLINE __m64 _mm_sub_si64(__m64 a, __m64 b) +{ + return vreinterpret_m64_s64( + vsub_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); +} + +// Subtracts the 8 signed 16-bit integers of b from the 8 signed 16-bit integers +// of a and saturates. +// +// r0 := SignedSaturate(a0 - b0) +// r1 := SignedSaturate(a1 - b1) +// ... +// r7 := SignedSaturate(a7 - b7) +// +// https://technet.microsoft.com/en-us/subscriptions/3247z5b8(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epi16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s16( + vqsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +} + +// Subtracts the 16 signed 8-bit integers of b from the 16 signed 8-bit integers +// of a and saturates. +// +// r0 := SignedSaturate(a0 - b0) +// r1 := SignedSaturate(a1 - b1) +// ... +// r15 := SignedSaturate(a15 - b15) +// +// https://technet.microsoft.com/en-us/subscriptions/by7kzks1(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vqsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Subtracts the 8 unsigned 16-bit integers of bfrom the 8 unsigned 16-bit +// integers of a and saturates.. +// https://technet.microsoft.com/en-us/subscriptions/index/f44y0s19(v=vs.90).aspx +FORCE_INLINE __m128i _mm_subs_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vqsubq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Subtracts the 16 unsigned 8-bit integers of b from the 16 unsigned 8-bit +// integers of a and saturates. +// +// r0 := UnsignedSaturate(a0 - b0) +// r1 := UnsignedSaturate(a1 - b1) +// ... +// r15 := UnsignedSaturate(a15 - b15) +// +// https://technet.microsoft.com/en-us/subscriptions/yadkxc18(v=vs.90) +FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vqsubq_u8(vreinterpretq_u8_m128i(a), vreinterpretq_u8_m128i(b))); +} + +#define _mm_ucomieq_sd _mm_comieq_sd +#define _mm_ucomige_sd _mm_comige_sd +#define _mm_ucomigt_sd _mm_comigt_sd +#define _mm_ucomile_sd _mm_comile_sd +#define _mm_ucomilt_sd _mm_comilt_sd +#define _mm_ucomineq_sd _mm_comineq_sd + +// Return vector of type __m128d with undefined elements. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_pd +FORCE_INLINE __m128d _mm_undefined_pd(void) +{ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif + __m128d a; + return a; +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +} + +// Interleaves the upper 4 signed or unsigned 16-bit integers in a with the +// upper 4 signed or unsigned 16-bit integers in b. +// +// r0 := a4 +// r1 := b4 +// r2 := a5 +// r3 := b5 +// r4 := a6 +// r5 := b6 +// r6 := a7 +// r7 := b7 +// +// https://msdn.microsoft.com/en-us/library/03196cz7(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vzip2q_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +#else + int16x4_t a1 = vget_high_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b1 = vget_high_s16(vreinterpretq_s16_m128i(b)); + int16x4x2_t result = vzip_s16(a1, b1); + return vreinterpretq_m128i_s16(vcombine_s16(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper 2 signed or unsigned 32-bit integers in a with the +// upper 2 signed or unsigned 32-bit integers in b. +// https://msdn.microsoft.com/en-us/library/65sa7cbs(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s32( + vzip2q_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +#else + int32x2_t a1 = vget_high_s32(vreinterpretq_s32_m128i(a)); + int32x2_t b1 = vget_high_s32(vreinterpretq_s32_m128i(b)); + int32x2x2_t result = vzip_s32(a1, b1); + return vreinterpretq_m128i_s32(vcombine_s32(result.val[0], result.val[1])); +#endif +} + +// Interleaves the upper signed or unsigned 64-bit integer in a with the +// upper signed or unsigned 64-bit integer in b. +// +// r0 := a1 +// r1 := b1 +FORCE_INLINE __m128i _mm_unpackhi_epi64(__m128i a, __m128i b) +{ + int64x1_t a_h = vget_high_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_h = vget_high_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_h, b_h)); +} + +// Interleaves the upper 8 signed or unsigned 8-bit integers in a with the upper +// 8 signed or unsigned 8-bit integers in b. +// +// r0 := a8 +// r1 := b8 +// r2 := a9 +// r3 := b9 +// ... +// r14 := a15 +// r15 := b15 +// +// https://msdn.microsoft.com/en-us/library/t5h7783k(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s8( + vzip2q_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +#else + int8x8_t a1 = + vreinterpret_s8_s16(vget_high_s16(vreinterpretq_s16_m128i(a))); + int8x8_t b1 = + vreinterpret_s8_s16(vget_high_s16(vreinterpretq_s16_m128i(b))); + int8x8x2_t result = vzip_s8(a1, b1); + return vreinterpretq_m128i_s8(vcombine_s8(result.val[0], result.val[1])); +#endif +} + +// Unpack and interleave double-precision (64-bit) floating-point elements from +// the high half of a and b, and store the results in dst. +// +// DEFINE INTERLEAVE_HIGH_QWORDS(src1[127:0], src2[127:0]) { +// dst[63:0] := src1[127:64] +// dst[127:64] := src2[127:64] +// RETURN dst[127:0] +// } +// dst[127:0] := INTERLEAVE_HIGH_QWORDS(a[127:0], b[127:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpackhi_pd +FORCE_INLINE __m128d _mm_unpackhi_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vzip2q_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + return vreinterpretq_m128d_s64( + vcombine_s64(vget_high_s64(vreinterpretq_s64_m128d(a)), + vget_high_s64(vreinterpretq_s64_m128d(b)))); +#endif +} + +// Interleaves the lower 4 signed or unsigned 16-bit integers in a with the +// lower 4 signed or unsigned 16-bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// r4 := a2 +// r5 := b2 +// r6 := a3 +// r7 := b3 +// +// https://msdn.microsoft.com/en-us/library/btxb17bw%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vzip1q_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); +#else + int16x4_t a1 = vget_low_s16(vreinterpretq_s16_m128i(a)); + int16x4_t b1 = vget_low_s16(vreinterpretq_s16_m128i(b)); + int16x4x2_t result = vzip_s16(a1, b1); + return vreinterpretq_m128i_s16(vcombine_s16(result.val[0], result.val[1])); +#endif +} + +// Interleaves the lower 2 signed or unsigned 32 - bit integers in a with the +// lower 2 signed or unsigned 32 - bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// +// https://msdn.microsoft.com/en-us/library/x8atst9d(v=vs.100).aspx +FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s32( + vzip1q_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +#else + int32x2_t a1 = vget_low_s32(vreinterpretq_s32_m128i(a)); + int32x2_t b1 = vget_low_s32(vreinterpretq_s32_m128i(b)); + int32x2x2_t result = vzip_s32(a1, b1); + return vreinterpretq_m128i_s32(vcombine_s32(result.val[0], result.val[1])); +#endif +} + +FORCE_INLINE __m128i _mm_unpacklo_epi64(__m128i a, __m128i b) +{ + int64x1_t a_l = vget_low_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_l = vget_low_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_l, b_l)); +} + +// Interleaves the lower 8 signed or unsigned 8-bit integers in a with the lower +// 8 signed or unsigned 8-bit integers in b. +// +// r0 := a0 +// r1 := b0 +// r2 := a1 +// r3 := b1 +// ... +// r14 := a7 +// r15 := b7 +// +// https://msdn.microsoft.com/en-us/library/xf7k860c%28v=vs.90%29.aspx +FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_s8( + vzip1q_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +#else + int8x8_t a1 = vreinterpret_s8_s16(vget_low_s16(vreinterpretq_s16_m128i(a))); + int8x8_t b1 = vreinterpret_s8_s16(vget_low_s16(vreinterpretq_s16_m128i(b))); + int8x8x2_t result = vzip_s8(a1, b1); + return vreinterpretq_m128i_s8(vcombine_s8(result.val[0], result.val[1])); +#endif +} + +// Unpack and interleave double-precision (64-bit) floating-point elements from +// the low half of a and b, and store the results in dst. +// +// DEFINE INTERLEAVE_QWORDS(src1[127:0], src2[127:0]) { +// dst[63:0] := src1[63:0] +// dst[127:64] := src2[63:0] +// RETURN dst[127:0] +// } +// dst[127:0] := INTERLEAVE_QWORDS(a[127:0], b[127:0]) +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpacklo_pd +FORCE_INLINE __m128d _mm_unpacklo_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vzip1q_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + return vreinterpretq_m128d_s64( + vcombine_s64(vget_low_s64(vreinterpretq_s64_m128d(a)), + vget_low_s64(vreinterpretq_s64_m128d(b)))); +#endif +} + +// Compute the bitwise XOR of packed double-precision (64-bit) floating-point +// elements in a and b, and store the results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// dst[i+63:i] := a[i+63:i] XOR b[i+63:i] +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_xor_pd +FORCE_INLINE __m128d _mm_xor_pd(__m128d a, __m128d b) +{ + return vreinterpretq_m128d_s64( + veorq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); +} + +// Computes the bitwise XOR of the 128-bit value in a and the 128-bit value in +// b. https://msdn.microsoft.com/en-us/library/fzt08www(v=vs.100).aspx +FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + veorq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +/* SSE3 */ + +// Alternatively add and subtract packed double-precision (64-bit) +// floating-point elements in a to/from packed elements in b, and store the +// results in dst. +// +// FOR j := 0 to 1 +// i := j*64 +// IF ((j & 1) == 0) +// dst[i+63:i] := a[i+63:i] - b[i+63:i] +// ELSE +// dst[i+63:i] := a[i+63:i] + b[i+63:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_addsub_pd +FORCE_INLINE __m128d _mm_addsub_pd(__m128d a, __m128d b) +{ + __m128d mask = _mm_set_pd(1.0f, -1.0f); +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vfmaq_f64(vreinterpretq_f64_m128d(a), + vreinterpretq_f64_m128d(b), + vreinterpretq_f64_m128d(mask))); +#else + return _mm_add_pd(_mm_mul_pd(b, mask), a); +#endif +} + +// Alternatively add and subtract packed single-precision (32-bit) +// floating-point elements in a to/from packed elements in b, and store the +// results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=addsub_ps +FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b) +{ + __m128 mask = {-1.0f, 1.0f, -1.0f, 1.0f}; +#if defined(__aarch64__) || defined(__ARM_FEATURE_FMA) /* VFPv4+ */ + return vreinterpretq_m128_f32(vfmaq_f32(vreinterpretq_f32_m128(a), + vreinterpretq_f32_m128(mask), + vreinterpretq_f32_m128(b))); +#else + return _mm_add_ps(_mm_mul_ps(b, mask), a); +#endif +} + +// Horizontally add adjacent pairs of double-precision (64-bit) floating-point +// elements in a and b, and pack the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pd +FORCE_INLINE __m128d _mm_hadd_pd(__m128d a, __m128d b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64( + vpaddq_f64(vreinterpretq_f64_m128d(a), vreinterpretq_f64_m128d(b))); +#else + double *da = (double *) &a; + double *db = (double *) &b; + double c[] = {da[0] + da[1], db[0] + db[1]}; + return vreinterpretq_m128d_u64(vld1q_u64((uint64_t *) c)); +#endif +} + +// Computes pairwise add of each argument as single-precision, floating-point +// values a and b. +// https://msdn.microsoft.com/en-us/library/yd9wecaa.aspx +FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vpaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); +#else + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(a)); + float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); + float32x2_t b10 = vget_low_f32(vreinterpretq_f32_m128(b)); + float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32( + vcombine_f32(vpadd_f32(a10, a32), vpadd_f32(b10, b32))); +#endif +} + +// Horizontally subtract adjacent pairs of double-precision (64-bit) +// floating-point elements in a and b, and pack the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pd +FORCE_INLINE __m128d _mm_hsub_pd(__m128d _a, __m128d _b) +{ +#if defined(__aarch64__) + float64x2_t a = vreinterpretq_f64_m128d(_a); + float64x2_t b = vreinterpretq_f64_m128d(_b); + return vreinterpretq_m128d_f64( + vsubq_f64(vuzp1q_f64(a, b), vuzp2q_f64(a, b))); +#else + double *da = (double *) &_a; + double *db = (double *) &_b; + double c[] = {da[0] - da[1], db[0] - db[1]}; + return vreinterpretq_m128d_u64(vld1q_u64((uint64_t *) c)); +#endif +} + +// Horizontally substract adjacent pairs of single-precision (32-bit) +// floating-point elements in a and b, and pack the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_ps +FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b) +{ + float32x4_t a = vreinterpretq_f32_m128(_a); + float32x4_t b = vreinterpretq_f32_m128(_b); +#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vsubq_f32(vuzp1q_f32(a, b), vuzp2q_f32(a, b))); +#else + float32x4x2_t c = vuzpq_f32(a, b); + return vreinterpretq_m128_f32(vsubq_f32(c.val[0], c.val[1])); +#endif +} + +// Load 128-bits of integer data from unaligned memory into dst. This intrinsic +// may perform better than _mm_loadu_si128 when the data crosses a cache line +// boundary. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128 +#define _mm_lddqu_si128 _mm_loadu_si128 + +// Load a double-precision (64-bit) floating-point element from memory into both +// elements of dst. +// +// dst[63:0] := MEM[mem_addr+63:mem_addr] +// dst[127:64] := MEM[mem_addr+63:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loaddup_pd +#define _mm_loaddup_pd _mm_load1_pd + +// Duplicate the low double-precision (64-bit) floating-point element from a, +// and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movedup_pd +FORCE_INLINE __m128d _mm_movedup_pd(__m128d a) +{ +#if (__aarch64__) + return vreinterpretq_m128d_f64( + vdupq_laneq_f64(vreinterpretq_f64_m128d(a), 0)); +#else + return vreinterpretq_m128d_u64( + vdupq_n_u64(vgetq_lane_u64(vreinterpretq_u64_m128d(a), 0))); +#endif +} + +// Duplicate odd-indexed single-precision (32-bit) floating-point elements +// from a, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movehdup_ps +FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a) +{ +#if __has_builtin(__builtin_shufflevector) + return vreinterpretq_m128_f32(__builtin_shufflevector( + vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 1, 1, 3, 3)); +#else + float32_t a1 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 1); + float32_t a3 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 3); + float ALIGN_STRUCT(16) data[4] = {a1, a1, a3, a3}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +#endif +} + +// Duplicate even-indexed single-precision (32-bit) floating-point elements +// from a, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_moveldup_ps +FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a) +{ +#if __has_builtin(__builtin_shufflevector) + return vreinterpretq_m128_f32(__builtin_shufflevector( + vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(a), 0, 0, 2, 2)); +#else + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + float32_t a2 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 2); + float ALIGN_STRUCT(16) data[4] = {a0, a0, a2, a2}; + return vreinterpretq_m128_f32(vld1q_f32(data)); +#endif +} + +/* SSSE3 */ + +// Compute the absolute value of packed signed 16-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// dst[i+15:i] := ABS(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi16 +FORCE_INLINE __m128i _mm_abs_epi16(__m128i a) +{ + return vreinterpretq_m128i_s16(vabsq_s16(vreinterpretq_s16_m128i(a))); +} + +// Compute the absolute value of packed signed 32-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 3 +// i := j*32 +// dst[i+31:i] := ABS(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi32 +FORCE_INLINE __m128i _mm_abs_epi32(__m128i a) +{ + return vreinterpretq_m128i_s32(vabsq_s32(vreinterpretq_s32_m128i(a))); +} + +// Compute the absolute value of packed signed 8-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 15 +// i := j*8 +// dst[i+7:i] := ABS(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi8 +FORCE_INLINE __m128i _mm_abs_epi8(__m128i a) +{ + return vreinterpretq_m128i_s8(vabsq_s8(vreinterpretq_s8_m128i(a))); +} + +// Compute the absolute value of packed signed 16-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 3 +// i := j*16 +// dst[i+15:i] := ABS(a[i+15:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi16 +FORCE_INLINE __m64 _mm_abs_pi16(__m64 a) +{ + return vreinterpret_m64_s16(vabs_s16(vreinterpret_s16_m64(a))); +} + +// Compute the absolute value of packed signed 32-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 1 +// i := j*32 +// dst[i+31:i] := ABS(a[i+31:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi32 +FORCE_INLINE __m64 _mm_abs_pi32(__m64 a) +{ + return vreinterpret_m64_s32(vabs_s32(vreinterpret_s32_m64(a))); +} + +// Compute the absolute value of packed signed 8-bit integers in a, and store +// the unsigned results in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// dst[i+7:i] := ABS(a[i+7:i]) +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi8 +FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) +{ + return vreinterpret_m64_s8(vabs_s8(vreinterpret_s8_m64(a))); +} + +// Concatenate 16-byte blocks in a and b into a 32-byte temporary result, shift +// the result right by imm8 bytes, and store the low 16 bytes in dst. +// +// tmp[255:0] := ((a[127:0] << 128)[255:0] OR b[127:0]) >> (imm8*8) +// dst[127:0] := tmp[127:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_epi8 +FORCE_INLINE __m128i _mm_alignr_epi8(__m128i a, __m128i b, int imm) +{ + if (_sse2neon_unlikely(imm & ~31)) + return _mm_setzero_si128(); + int idx; + uint8x16_t tmp[2]; + if (imm >= 16) { + idx = imm - 16; + tmp[0] = vreinterpretq_u8_m128i(a); + tmp[1] = vdupq_n_u8(0); + } else { + idx = imm; + tmp[0] = vreinterpretq_u8_m128i(b); + tmp[1] = vreinterpretq_u8_m128i(a); + } + return vreinterpretq_m128i_u8(vld1q_u8(((uint8_t const *) tmp) + idx)); +} + +// Concatenate 8-byte blocks in a and b into a 16-byte temporary result, shift +// the result right by imm8 bytes, and store the low 8 bytes in dst. +// +// tmp[127:0] := ((a[63:0] << 64)[127:0] OR b[63:0]) >> (imm8*8) +// dst[63:0] := tmp[63:0] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_pi8 +#define _mm_alignr_pi8(a, b, imm) \ + __extension__({ \ + __m64 ret; \ + if (_sse2neon_unlikely((imm) >= 16)) { \ + ret = vreinterpret_m64_s8(vdup_n_s8(0)); \ + } else { \ + uint8x8_t tmp_low, tmp_high; \ + if (imm >= 8) { \ + const int idx = imm - 8; \ + tmp_low = vreinterpret_u8_m64(a); \ + tmp_high = vdup_n_u8(0); \ + ret = vreinterpret_m64_u8(vext_u8(tmp_low, tmp_high, idx)); \ + } else { \ + const int idx = imm; \ + tmp_low = vreinterpret_u8_m64(b); \ + tmp_high = vreinterpret_u8_m64(a); \ + ret = vreinterpret_m64_u8(vext_u8(tmp_low, tmp_high, idx)); \ + } \ + } \ + ret; \ + }) + +// Computes pairwise add of each argument as a 16-bit signed or unsigned integer +// values a and b. +FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); +#if defined(__aarch64__) + return vreinterpretq_m128i_s16(vpaddq_s16(a, b)); +#else + return vreinterpretq_m128i_s16( + vcombine_s16(vpadd_s16(vget_low_s16(a), vget_high_s16(a)), + vpadd_s16(vget_low_s16(b), vget_high_s16(b)))); +#endif +} + +// Computes pairwise add of each argument as a 32-bit signed or unsigned integer +// values a and b. +FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + return vreinterpretq_m128i_s32( + vcombine_s32(vpadd_s32(vget_low_s32(a), vget_high_s32(a)), + vpadd_s32(vget_low_s32(b), vget_high_s32(b)))); +} + +// Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the +// signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi16 +FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b) +{ + return vreinterpret_m64_s16( + vpadd_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); +} + +// Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the +// signed 32-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi32 +FORCE_INLINE __m64 _mm_hadd_pi32(__m64 a, __m64 b) +{ + return vreinterpret_m64_s32( + vpadd_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b))); +} + +// Computes saturated pairwise sub of each argument as a 16-bit signed +// integer values a and b. +FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b) +{ +#if defined(__aarch64__) + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + return vreinterpretq_s64_s16( + vqaddq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))); +#else + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + // Interleave using vshrn/vmovn + // [a0|a2|a4|a6|b0|b2|b4|b6] + // [a1|a3|a5|a7|b1|b3|b5|b7] + int16x8_t ab0246 = vcombine_s16(vmovn_s32(a), vmovn_s32(b)); + int16x8_t ab1357 = vcombine_s16(vshrn_n_s32(a, 16), vshrn_n_s32(b, 16)); + // Saturated add + return vreinterpretq_m128i_s16(vqaddq_s16(ab0246, ab1357)); +#endif +} + +// Horizontally add adjacent pairs of signed 16-bit integers in a and b using +// saturation, and pack the signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadds_pi16 +FORCE_INLINE __m64 _mm_hadds_pi16(__m64 _a, __m64 _b) +{ + int16x4_t a = vreinterpret_s16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); +#if defined(__aarch64__) + return vreinterpret_s64_s16(vqadd_s16(vuzp1_s16(a, b), vuzp2_s16(a, b))); +#else + int16x4x2_t res = vuzp_s16(a, b); + return vreinterpret_s64_s16(vqadd_s16(res.val[0], res.val[1])); +#endif +} + +// Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack +// the signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi16 +FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vsubq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))); +#else + int16x8x2_t c = vuzpq_s16(a, b); + return vreinterpretq_m128i_s16(vsubq_s16(c.val[0], c.val[1])); +#endif +} + +// Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack +// the signed 32-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi32 +FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); +#if defined(__aarch64__) + return vreinterpretq_m128i_s32( + vsubq_s32(vuzp1q_s32(a, b), vuzp2q_s32(a, b))); +#else + int32x4x2_t c = vuzpq_s32(a, b); + return vreinterpretq_m128i_s32(vsubq_s32(c.val[0], c.val[1])); +#endif +} + +// Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack +// the signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pi16 +FORCE_INLINE __m64 _mm_hsub_pi16(__m64 _a, __m64 _b) +{ + int16x4_t a = vreinterpret_s16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); +#if defined(__aarch64__) + return vreinterpret_m64_s16(vsub_s16(vuzp1_s16(a, b), vuzp2_s16(a, b))); +#else + int16x4x2_t c = vuzp_s16(a, b); + return vreinterpret_m64_s16(vsub_s16(c.val[0], c.val[1])); +#endif +} + +// Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack +// the signed 32-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_hsub_pi32 +FORCE_INLINE __m64 _mm_hsub_pi32(__m64 _a, __m64 _b) +{ + int32x2_t a = vreinterpret_s32_m64(_a); + int32x2_t b = vreinterpret_s32_m64(_b); +#if defined(__aarch64__) + return vreinterpret_m64_s32(vsub_s32(vuzp1_s32(a, b), vuzp2_s32(a, b))); +#else + int32x2x2_t c = vuzp_s32(a, b); + return vreinterpret_m64_s32(vsub_s32(c.val[0], c.val[1])); +#endif +} + +// Computes saturated pairwise difference of each argument as a 16-bit signed +// integer values a and b. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_epi16 +FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); +#if defined(__aarch64__) + return vreinterpretq_m128i_s16( + vqsubq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))); +#else + int16x8x2_t c = vuzpq_s16(a, b); + return vreinterpretq_m128i_s16(vqsubq_s16(c.val[0], c.val[1])); +#endif +} + +// Horizontally subtract adjacent pairs of signed 16-bit integers in a and b +// using saturation, and pack the signed 16-bit results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_pi16 +FORCE_INLINE __m64 _mm_hsubs_pi16(__m64 _a, __m64 _b) +{ + int16x4_t a = vreinterpret_s16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); +#if defined(__aarch64__) + return vreinterpret_m64_s16(vqsub_s16(vuzp1_s16(a, b), vuzp2_s16(a, b))); +#else + int16x4x2_t c = vuzp_s16(a, b); + return vreinterpret_m64_s16(vqsub_s16(c.val[0], c.val[1])); +#endif +} + +// Vertically multiply each unsigned 8-bit integer from a with the corresponding +// signed 8-bit integer from b, producing intermediate signed 16-bit integers. +// Horizontally add adjacent pairs of intermediate signed 16-bit integers, +// and pack the saturated results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// dst[i+15:i] := Saturate_To_Int16( a[i+15:i+8]*b[i+15:i+8] + +// a[i+7:i]*b[i+7:i] ) +// ENDFOR +FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b) +{ +#if defined(__aarch64__) + uint8x16_t a = vreinterpretq_u8_m128i(_a); + int8x16_t b = vreinterpretq_s8_m128i(_b); + int16x8_t tl = vmulq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(a))), + vmovl_s8(vget_low_s8(b))); + int16x8_t th = vmulq_s16(vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(a))), + vmovl_s8(vget_high_s8(b))); + return vreinterpretq_m128i_s16( + vqaddq_s16(vuzp1q_s16(tl, th), vuzp2q_s16(tl, th))); +#else + // This would be much simpler if x86 would choose to zero extend OR sign + // extend, not both. This could probably be optimized better. + uint16x8_t a = vreinterpretq_u16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + + // Zero extend a + int16x8_t a_odd = vreinterpretq_s16_u16(vshrq_n_u16(a, 8)); + int16x8_t a_even = vreinterpretq_s16_u16(vbicq_u16(a, vdupq_n_u16(0xff00))); + + // Sign extend by shifting left then shifting right. + int16x8_t b_even = vshrq_n_s16(vshlq_n_s16(b, 8), 8); + int16x8_t b_odd = vshrq_n_s16(b, 8); + + // multiply + int16x8_t prod1 = vmulq_s16(a_even, b_even); + int16x8_t prod2 = vmulq_s16(a_odd, b_odd); + + // saturated add + return vreinterpretq_m128i_s16(vqaddq_s16(prod1, prod2)); +#endif +} + +// Vertically multiply each unsigned 8-bit integer from a with the corresponding +// signed 8-bit integer from b, producing intermediate signed 16-bit integers. +// Horizontally add adjacent pairs of intermediate signed 16-bit integers, and +// pack the saturated results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maddubs_pi16 +FORCE_INLINE __m64 _mm_maddubs_pi16(__m64 _a, __m64 _b) +{ + uint16x4_t a = vreinterpret_u16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); + + // Zero extend a + int16x4_t a_odd = vreinterpret_s16_u16(vshr_n_u16(a, 8)); + int16x4_t a_even = vreinterpret_s16_u16(vand_u16(a, vdup_n_u16(0xff))); + + // Sign extend by shifting left then shifting right. + int16x4_t b_even = vshr_n_s16(vshl_n_s16(b, 8), 8); + int16x4_t b_odd = vshr_n_s16(b, 8); + + // multiply + int16x4_t prod1 = vmul_s16(a_even, b_even); + int16x4_t prod2 = vmul_s16(a_odd, b_odd); + + // saturated add + return vreinterpret_m64_s16(vqadd_s16(prod1, prod2)); +} + +// Multiply packed signed 16-bit integers in a and b, producing intermediate +// signed 32-bit integers. Shift right by 15 bits while rounding up, and store +// the packed 16-bit integers in dst. +// +// r0 := Round(((int32_t)a0 * (int32_t)b0) >> 15) +// r1 := Round(((int32_t)a1 * (int32_t)b1) >> 15) +// r2 := Round(((int32_t)a2 * (int32_t)b2) >> 15) +// ... +// r7 := Round(((int32_t)a7 * (int32_t)b7) >> 15) +FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b) +{ + // Has issues due to saturation + // return vreinterpretq_m128i_s16(vqrdmulhq_s16(a, b)); + + // Multiply + int32x4_t mul_lo = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)), + vget_low_s16(vreinterpretq_s16_m128i(b))); + int32x4_t mul_hi = vmull_s16(vget_high_s16(vreinterpretq_s16_m128i(a)), + vget_high_s16(vreinterpretq_s16_m128i(b))); + + // Rounding narrowing shift right + // narrow = (int16_t)((mul + 16384) >> 15); + int16x4_t narrow_lo = vrshrn_n_s32(mul_lo, 15); + int16x4_t narrow_hi = vrshrn_n_s32(mul_hi, 15); + + // Join together + return vreinterpretq_m128i_s16(vcombine_s16(narrow_lo, narrow_hi)); +} + +// Multiply packed signed 16-bit integers in a and b, producing intermediate +// signed 32-bit integers. Truncate each intermediate integer to the 18 most +// significant bits, round by adding 1, and store bits [16:1] to dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhrs_pi16 +FORCE_INLINE __m64 _mm_mulhrs_pi16(__m64 a, __m64 b) +{ + int32x4_t mul_extend = + vmull_s16((vreinterpret_s16_m64(a)), (vreinterpret_s16_m64(b))); + + // Rounding narrowing shift right + return vreinterpret_m64_s16(vrshrn_n_s32(mul_extend, 15)); +} + +// Shuffle packed 8-bit integers in a according to shuffle control mask in the +// corresponding 8-bit element of b, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_epi8 +FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b) +{ + int8x16_t tbl = vreinterpretq_s8_m128i(a); // input a + uint8x16_t idx = vreinterpretq_u8_m128i(b); // input b + uint8x16_t idx_masked = + vandq_u8(idx, vdupq_n_u8(0x8F)); // avoid using meaningless bits +#if defined(__aarch64__) + return vreinterpretq_m128i_s8(vqtbl1q_s8(tbl, idx_masked)); +#elif defined(__GNUC__) + int8x16_t ret; + // %e and %f represent the even and odd D registers + // respectively. + __asm__ __volatile__( + "vtbl.8 %e[ret], {%e[tbl], %f[tbl]}, %e[idx]\n" + "vtbl.8 %f[ret], {%e[tbl], %f[tbl]}, %f[idx]\n" + : [ret] "=&w"(ret) + : [tbl] "w"(tbl), [idx] "w"(idx_masked)); + return vreinterpretq_m128i_s8(ret); +#else + // use this line if testing on aarch64 + int8x8x2_t a_split = {vget_low_s8(tbl), vget_high_s8(tbl)}; + return vreinterpretq_m128i_s8( + vcombine_s8(vtbl2_s8(a_split, vget_low_u8(idx_masked)), + vtbl2_s8(a_split, vget_high_u8(idx_masked)))); +#endif +} + +// Shuffle packed 8-bit integers in a according to shuffle control mask in the +// corresponding 8-bit element of b, and store the results in dst. +// +// FOR j := 0 to 7 +// i := j*8 +// IF b[i+7] == 1 +// dst[i+7:i] := 0 +// ELSE +// index[2:0] := b[i+2:i] +// dst[i+7:i] := a[index*8+7:index*8] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi8 +FORCE_INLINE __m64 _mm_shuffle_pi8(__m64 a, __m64 b) +{ + const int8x8_t controlMask = + vand_s8(vreinterpret_s8_m64(b), vdup_n_s8((int8_t)(0x1 << 7 | 0x07))); + int8x8_t res = vtbl1_s8(vreinterpret_s8_m64(a), controlMask); + return vreinterpret_m64_s8(res); +} + +// Negate packed 16-bit integers in a when the corresponding signed +// 16-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..7 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b) +{ + int16x8_t a = vreinterpretq_s16_m128i(_a); + int16x8_t b = vreinterpretq_s16_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFF : 0 + uint16x8_t ltMask = vreinterpretq_u16_s16(vshrq_n_s16(b, 15)); + // (b == 0) ? 0xFFFF : 0 +#if defined(__aarch64__) + int16x8_t zeroMask = vreinterpretq_s16_u16(vceqzq_s16(b)); +#else + int16x8_t zeroMask = vreinterpretq_s16_u16(vceqq_s16(b, vdupq_n_s16(0))); +#endif + + // bitwise select either a or negative 'a' (vnegq_s16(a) equals to negative + // 'a') based on ltMask + int16x8_t masked = vbslq_s16(ltMask, vnegq_s16(a), a); + // res = masked & (~zeroMask) + int16x8_t res = vbicq_s16(masked, zeroMask); + return vreinterpretq_m128i_s16(res); +} + +// Negate packed 32-bit integers in a when the corresponding signed +// 32-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..3 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b) +{ + int32x4_t a = vreinterpretq_s32_m128i(_a); + int32x4_t b = vreinterpretq_s32_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFFFFFF : 0 + uint32x4_t ltMask = vreinterpretq_u32_s32(vshrq_n_s32(b, 31)); + + // (b == 0) ? 0xFFFFFFFF : 0 +#if defined(__aarch64__) + int32x4_t zeroMask = vreinterpretq_s32_u32(vceqzq_s32(b)); +#else + int32x4_t zeroMask = vreinterpretq_s32_u32(vceqq_s32(b, vdupq_n_s32(0))); +#endif + + // bitwise select either a or negative 'a' (vnegq_s32(a) equals to negative + // 'a') based on ltMask + int32x4_t masked = vbslq_s32(ltMask, vnegq_s32(a), a); + // res = masked & (~zeroMask) + int32x4_t res = vbicq_s32(masked, zeroMask); + return vreinterpretq_m128i_s32(res); +} + +// Negate packed 8-bit integers in a when the corresponding signed +// 8-bit integer in b is negative, and store the results in dst. +// Element in dst are zeroed out when the corresponding element +// in b is zero. +// +// for i in 0..15 +// if b[i] < 0 +// r[i] := -a[i] +// else if b[i] == 0 +// r[i] := 0 +// else +// r[i] := a[i] +// fi +// done +FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b) +{ + int8x16_t a = vreinterpretq_s8_m128i(_a); + int8x16_t b = vreinterpretq_s8_m128i(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFF : 0 + uint8x16_t ltMask = vreinterpretq_u8_s8(vshrq_n_s8(b, 7)); + + // (b == 0) ? 0xFF : 0 +#if defined(__aarch64__) + int8x16_t zeroMask = vreinterpretq_s8_u8(vceqzq_s8(b)); +#else + int8x16_t zeroMask = vreinterpretq_s8_u8(vceqq_s8(b, vdupq_n_s8(0))); +#endif + + // bitwise select either a or nagative 'a' (vnegq_s8(a) return nagative 'a') + // based on ltMask + int8x16_t masked = vbslq_s8(ltMask, vnegq_s8(a), a); + // res = masked & (~zeroMask) + int8x16_t res = vbicq_s8(masked, zeroMask); + + return vreinterpretq_m128i_s8(res); +} + +// Negate packed 16-bit integers in a when the corresponding signed 16-bit +// integer in b is negative, and store the results in dst. Element in dst are +// zeroed out when the corresponding element in b is zero. +// +// FOR j := 0 to 3 +// i := j*16 +// IF b[i+15:i] < 0 +// dst[i+15:i] := -(a[i+15:i]) +// ELSE IF b[i+15:i] == 0 +// dst[i+15:i] := 0 +// ELSE +// dst[i+15:i] := a[i+15:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi16 +FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b) +{ + int16x4_t a = vreinterpret_s16_m64(_a); + int16x4_t b = vreinterpret_s16_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFF : 0 + uint16x4_t ltMask = vreinterpret_u16_s16(vshr_n_s16(b, 15)); + + // (b == 0) ? 0xFFFF : 0 +#if defined(__aarch64__) + int16x4_t zeroMask = vreinterpret_s16_u16(vceqz_s16(b)); +#else + int16x4_t zeroMask = vreinterpret_s16_u16(vceq_s16(b, vdup_n_s16(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s16(a) return nagative 'a') + // based on ltMask + int16x4_t masked = vbsl_s16(ltMask, vneg_s16(a), a); + // res = masked & (~zeroMask) + int16x4_t res = vbic_s16(masked, zeroMask); + + return vreinterpret_m64_s16(res); +} + +// Negate packed 32-bit integers in a when the corresponding signed 32-bit +// integer in b is negative, and store the results in dst. Element in dst are +// zeroed out when the corresponding element in b is zero. +// +// FOR j := 0 to 1 +// i := j*32 +// IF b[i+31:i] < 0 +// dst[i+31:i] := -(a[i+31:i]) +// ELSE IF b[i+31:i] == 0 +// dst[i+31:i] := 0 +// ELSE +// dst[i+31:i] := a[i+31:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi32 +FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b) +{ + int32x2_t a = vreinterpret_s32_m64(_a); + int32x2_t b = vreinterpret_s32_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFFFFFFFF : 0 + uint32x2_t ltMask = vreinterpret_u32_s32(vshr_n_s32(b, 31)); + + // (b == 0) ? 0xFFFFFFFF : 0 +#if defined(__aarch64__) + int32x2_t zeroMask = vreinterpret_s32_u32(vceqz_s32(b)); +#else + int32x2_t zeroMask = vreinterpret_s32_u32(vceq_s32(b, vdup_n_s32(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s32(a) return nagative 'a') + // based on ltMask + int32x2_t masked = vbsl_s32(ltMask, vneg_s32(a), a); + // res = masked & (~zeroMask) + int32x2_t res = vbic_s32(masked, zeroMask); + + return vreinterpret_m64_s32(res); +} + +// Negate packed 8-bit integers in a when the corresponding signed 8-bit integer +// in b is negative, and store the results in dst. Element in dst are zeroed out +// when the corresponding element in b is zero. +// +// FOR j := 0 to 7 +// i := j*8 +// IF b[i+7:i] < 0 +// dst[i+7:i] := -(a[i+7:i]) +// ELSE IF b[i+7:i] == 0 +// dst[i+7:i] := 0 +// ELSE +// dst[i+7:i] := a[i+7:i] +// FI +// ENDFOR +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi8 +FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) +{ + int8x8_t a = vreinterpret_s8_m64(_a); + int8x8_t b = vreinterpret_s8_m64(_b); + + // signed shift right: faster than vclt + // (b < 0) ? 0xFF : 0 + uint8x8_t ltMask = vreinterpret_u8_s8(vshr_n_s8(b, 7)); + + // (b == 0) ? 0xFF : 0 +#if defined(__aarch64__) + int8x8_t zeroMask = vreinterpret_s8_u8(vceqz_s8(b)); +#else + int8x8_t zeroMask = vreinterpret_s8_u8(vceq_s8(b, vdup_n_s8(0))); +#endif + + // bitwise select either a or nagative 'a' (vneg_s8(a) return nagative 'a') + // based on ltMask + int8x8_t masked = vbsl_s8(ltMask, vneg_s8(a), a); + // res = masked & (~zeroMask) + int8x8_t res = vbic_s8(masked, zeroMask); + + return vreinterpret_m64_s8(res); +} + +/* SSE4.1 */ + +// Blend packed 16-bit integers from a and b using control mask imm8, and store +// the results in dst. +// +// FOR j := 0 to 7 +// i := j*16 +// IF imm8[j] +// dst[i+15:i] := b[i+15:i] +// ELSE +// dst[i+15:i] := a[i+15:i] +// FI +// ENDFOR +// FORCE_INLINE __m128i _mm_blend_epi16(__m128i a, __m128i b, +// __constrange(0,255) int imm) +#define _mm_blend_epi16(a, b, imm) \ + __extension__({ \ + const uint16_t _mask[8] = {((imm) & (1 << 0)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 1)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 2)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 3)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 4)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 5)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 6)) ? (uint16_t) -1 : 0x0, \ + ((imm) & (1 << 7)) ? (uint16_t) -1 : 0x0}; \ + uint16x8_t _mask_vec = vld1q_u16(_mask); \ + uint16x8_t _a = vreinterpretq_u16_m128i(a); \ + uint16x8_t _b = vreinterpretq_u16_m128i(b); \ + vreinterpretq_m128i_u16(vbslq_u16(_mask_vec, _b, _a)); \ + }) + +// Blend packed double-precision (64-bit) floating-point elements from a and b +// using control mask imm8, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_pd +#define _mm_blend_pd(a, b, imm) \ + __extension__({ \ + const uint64_t _mask[2] = { \ + ((imm) & (1 << 0)) ? ~UINT64_C(0) : UINT64_C(0), \ + ((imm) & (1 << 1)) ? ~UINT64_C(0) : UINT64_C(0)}; \ + uint64x2_t _mask_vec = vld1q_u64(_mask); \ + uint64x2_t _a = vreinterpretq_u64_m128d(a); \ + uint64x2_t _b = vreinterpretq_u64_m128d(b); \ + vreinterpretq_m128d_u64(vbslq_u64(_mask_vec, _b, _a)); \ + }) + +// Blend packed single-precision (32-bit) floating-point elements from a and b +// using mask, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_ps +FORCE_INLINE __m128 _mm_blend_ps(__m128 _a, __m128 _b, const char imm8) +{ + const uint32_t ALIGN_STRUCT(16) + data[4] = {((imm8) & (1 << 0)) ? UINT32_MAX : 0, + ((imm8) & (1 << 1)) ? UINT32_MAX : 0, + ((imm8) & (1 << 2)) ? UINT32_MAX : 0, + ((imm8) & (1 << 3)) ? UINT32_MAX : 0}; + uint32x4_t mask = vld1q_u32(data); + float32x4_t a = vreinterpretq_f32_m128(_a); + float32x4_t b = vreinterpretq_f32_m128(_b); + return vreinterpretq_m128_f32(vbslq_f32(mask, b, a)); +} + +// Blend packed 8-bit integers from a and b using mask, and store the results in +// dst. +// +// FOR j := 0 to 15 +// i := j*8 +// IF mask[i+7] +// dst[i+7:i] := b[i+7:i] +// ELSE +// dst[i+7:i] := a[i+7:i] +// FI +// ENDFOR +FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask) +{ + // Use a signed shift right to create a mask with the sign bit + uint8x16_t mask = + vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_m128i(_mask), 7)); + uint8x16_t a = vreinterpretq_u8_m128i(_a); + uint8x16_t b = vreinterpretq_u8_m128i(_b); + return vreinterpretq_m128i_u8(vbslq_u8(mask, b, a)); +} + +// Blend packed double-precision (64-bit) floating-point elements from a and b +// using mask, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_pd +FORCE_INLINE __m128d _mm_blendv_pd(__m128d _a, __m128d _b, __m128d _mask) +{ + uint64x2_t mask = + vreinterpretq_u64_s64(vshrq_n_s64(vreinterpretq_s64_m128d(_mask), 63)); +#if defined(__aarch64__) + float64x2_t a = vreinterpretq_f64_m128d(_a); + float64x2_t b = vreinterpretq_f64_m128d(_b); + return vreinterpretq_m128d_f64(vbslq_f64(mask, b, a)); +#else + uint64x2_t a = vreinterpretq_u64_m128d(_a); + uint64x2_t b = vreinterpretq_u64_m128d(_b); + return vreinterpretq_m128d_u64(vbslq_u64(mask, b, a)); +#endif +} + +// Blend packed single-precision (32-bit) floating-point elements from a and b +// using mask, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_ps +FORCE_INLINE __m128 _mm_blendv_ps(__m128 _a, __m128 _b, __m128 _mask) +{ + // Use a signed shift right to create a mask with the sign bit + uint32x4_t mask = + vreinterpretq_u32_s32(vshrq_n_s32(vreinterpretq_s32_m128(_mask), 31)); + float32x4_t a = vreinterpretq_f32_m128(_a); + float32x4_t b = vreinterpretq_f32_m128(_b); + return vreinterpretq_m128_f32(vbslq_f32(mask, b, a)); +} + +// Round the packed double-precision (64-bit) floating-point elements in a up +// to an integer value, and store the results as packed double-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_pd +FORCE_INLINE __m128d _mm_ceil_pd(__m128d a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vrndpq_f64(vreinterpretq_f64_m128d(a))); +#else + double *f = (double *) &a; + return _mm_set_pd(ceil(f[1]), ceil(f[0])); +#endif +} + +// Round the packed single-precision (32-bit) floating-point elements in a up to +// an integer value, and store the results as packed single-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ps +FORCE_INLINE __m128 _mm_ceil_ps(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vrndpq_f32(vreinterpretq_f32_m128(a))); +#else + float *f = (float *) &a; + return _mm_set_ps(ceilf(f[3]), ceilf(f[2]), ceilf(f[1]), ceilf(f[0])); +#endif +} + +// Round the lower double-precision (64-bit) floating-point element in b up to +// an integer value, store the result as a double-precision floating-point +// element in the lower element of dst, and copy the upper element from a to the +// upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_sd +FORCE_INLINE __m128d _mm_ceil_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_ceil_pd(b)); +} + +// Round the lower single-precision (32-bit) floating-point element in b up to +// an integer value, store the result as a single-precision floating-point +// element in the lower element of dst, and copy the upper 3 packed elements +// from a to the upper elements of dst. +// +// dst[31:0] := CEIL(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ss +FORCE_INLINE __m128 _mm_ceil_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_ceil_ps(b)); +} + +// Compare packed 64-bit integers in a and b for equality, and store the results +// in dst +FORCE_INLINE __m128i _mm_cmpeq_epi64(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_u64( + vceqq_u64(vreinterpretq_u64_m128i(a), vreinterpretq_u64_m128i(b))); +#else + // ARMv7 lacks vceqq_u64 + // (a == b) -> (a_lo == b_lo) && (a_hi == b_hi) + uint32x4_t cmp = + vceqq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b)); + uint32x4_t swapped = vrev64q_u32(cmp); + return vreinterpretq_m128i_u32(vandq_u32(cmp, swapped)); +#endif +} + +// Converts the four signed 16-bit integers in the lower 64 bits to four signed +// 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi16_epi32(__m128i a) +{ + return vreinterpretq_m128i_s32( + vmovl_s16(vget_low_s16(vreinterpretq_s16_m128i(a)))); +} + +// Converts the two signed 16-bit integers in the lower 32 bits two signed +// 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a) +{ + int16x8_t s16x8 = vreinterpretq_s16_m128i(a); /* xxxx xxxx xxxx 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000x 000x 000B 000A */ + int64x2_t s64x2 = vmovl_s32(vget_low_s32(s32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_s64(s64x2); +} + +// Converts the two signed 32-bit integers in the lower 64 bits to two signed +// 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepi32_epi64(__m128i a) +{ + return vreinterpretq_m128i_s64( + vmovl_s32(vget_low_s32(vreinterpretq_s32_m128i(a)))); +} + +// Converts the four unsigned 8-bit integers in the lower 16 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + return vreinterpretq_m128i_s16(s16x8); +} + +// Converts the four unsigned 8-bit integers in the lower 32 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000D 000C 000B 000A */ + return vreinterpretq_m128i_s32(s32x4); +} + +// Converts the two signed 8-bit integers in the lower 32 bits to four +// signed 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a) +{ + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx xxBA */ + int16x8_t s16x8 = vmovl_s8(vget_low_s8(s8x16)); /* 0x0x 0x0x 0x0x 0B0A */ + int32x4_t s32x4 = vmovl_s16(vget_low_s16(s16x8)); /* 000x 000x 000B 000A */ + int64x2_t s64x2 = vmovl_s32(vget_low_s32(s32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_s64(s64x2); +} + +// Converts the four unsigned 16-bit integers in the lower 64 bits to four +// unsigned 32-bit integers. +FORCE_INLINE __m128i _mm_cvtepu16_epi32(__m128i a) +{ + return vreinterpretq_m128i_u32( + vmovl_u16(vget_low_u16(vreinterpretq_u16_m128i(a)))); +} + +// Converts the two unsigned 16-bit integers in the lower 32 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a) +{ + uint16x8_t u16x8 = vreinterpretq_u16_m128i(a); /* xxxx xxxx xxxx 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000x 000x 000B 000A */ + uint64x2_t u64x2 = vmovl_u32(vget_low_u32(u32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_u64(u64x2); +} + +// Converts the two unsigned 32-bit integers in the lower 64 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a) +{ + return vreinterpretq_m128i_u64( + vmovl_u32(vget_low_u32(vreinterpretq_u32_m128i(a)))); +} + +// Zero extend packed unsigned 8-bit integers in a to packed 16-bit integers, +// and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepu8_epi16 +FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx HGFE DCBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0H0G 0F0E 0D0C 0B0A */ + return vreinterpretq_m128i_u16(u16x8); +} + +// Converts the four unsigned 8-bit integers in the lower 32 bits to four +// unsigned 32-bit integers. +// https://msdn.microsoft.com/en-us/library/bb531467%28v=vs.100%29.aspx +FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx DCBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0x0x 0x0x 0D0C 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000D 000C 000B 000A */ + return vreinterpretq_m128i_u32(u32x4); +} + +// Converts the two unsigned 8-bit integers in the lower 16 bits to two +// unsigned 64-bit integers. +FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a) +{ + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx xxBA */ + uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0x0x 0x0x 0x0x 0B0A */ + uint32x4_t u32x4 = vmovl_u16(vget_low_u16(u16x8)); /* 000x 000x 000B 000A */ + uint64x2_t u64x2 = vmovl_u32(vget_low_u32(u32x4)); /* 0000 000B 0000 000A */ + return vreinterpretq_m128i_u64(u64x2); +} + +// Conditionally multiply the packed double-precision (64-bit) floating-point +// elements in a and b using the high 4 bits in imm8, sum the four products, and +// conditionally store the sum in dst using the low 4 bits of imm8. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_pd +FORCE_INLINE __m128d _mm_dp_pd(__m128d a, __m128d b, const int imm) +{ + // Generate mask value from constant immediate bit value + const int64_t bit0Mask = imm & 0x01 ? UINT64_MAX : 0; + const int64_t bit1Mask = imm & 0x02 ? UINT64_MAX : 0; +#if !SSE2NEON_PRECISE_DP + const int64_t bit4Mask = imm & 0x10 ? UINT64_MAX : 0; + const int64_t bit5Mask = imm & 0x20 ? UINT64_MAX : 0; +#endif + // Conditional multiplication +#if !SSE2NEON_PRECISE_DP + __m128d mul = _mm_mul_pd(a, b); + const __m128d mulMask = + _mm_castsi128_pd(_mm_set_epi64x(bit5Mask, bit4Mask)); + __m128d tmp = _mm_and_pd(mul, mulMask); +#else +#if defined(__aarch64__) + double d0 = (imm & 0x10) ? vgetq_lane_f64(vreinterpretq_f64_m128d(a), 0) * + vgetq_lane_f64(vreinterpretq_f64_m128d(b), 0) + : 0; + double d1 = (imm & 0x20) ? vgetq_lane_f64(vreinterpretq_f64_m128d(a), 1) * + vgetq_lane_f64(vreinterpretq_f64_m128d(b), 1) + : 0; +#else + double d0 = (imm & 0x10) ? ((double *) &a)[0] * ((double *) &b)[0] : 0; + double d1 = (imm & 0x20) ? ((double *) &a)[1] * ((double *) &b)[1] : 0; +#endif + __m128d tmp = _mm_set_pd(d1, d0); +#endif + // Sum the products +#if defined(__aarch64__) + double sum = vpaddd_f64(vreinterpretq_f64_m128d(tmp)); +#else + double sum = *((double *) &tmp) + *(((double *) &tmp) + 1); +#endif + // Conditionally store the sum + const __m128d sumMask = + _mm_castsi128_pd(_mm_set_epi64x(bit1Mask, bit0Mask)); + __m128d res = _mm_and_pd(_mm_set_pd1(sum), sumMask); + return res; +} + +// Conditionally multiply the packed single-precision (32-bit) floating-point +// elements in a and b using the high 4 bits in imm8, sum the four products, +// and conditionally store the sum in dst using the low 4 bits of imm. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_ps +FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) +{ +#if defined(__aarch64__) + /* shortcuts */ + if (imm == 0xFF) { + return _mm_set1_ps(vaddvq_f32(_mm_mul_ps(a, b))); + } + if (imm == 0x7F) { + float32x4_t m = _mm_mul_ps(a, b); + m[3] = 0; + return _mm_set1_ps(vaddvq_f32(m)); + } +#endif + + float s = 0, c = 0; + float32x4_t f32a = vreinterpretq_f32_m128(a); + float32x4_t f32b = vreinterpretq_f32_m128(b); + + /* To improve the accuracy of floating-point summation, Kahan algorithm + * is used for each operation. + */ + if (imm & (1 << 4)) + _sse2neon_kadd_f32(&s, &c, f32a[0] * f32b[0]); + if (imm & (1 << 5)) + _sse2neon_kadd_f32(&s, &c, f32a[1] * f32b[1]); + if (imm & (1 << 6)) + _sse2neon_kadd_f32(&s, &c, f32a[2] * f32b[2]); + if (imm & (1 << 7)) + _sse2neon_kadd_f32(&s, &c, f32a[3] * f32b[3]); + s += c; + + float32x4_t res = { + (imm & 0x1) ? s : 0, + (imm & 0x2) ? s : 0, + (imm & 0x4) ? s : 0, + (imm & 0x8) ? s : 0, + }; + return vreinterpretq_m128_f32(res); +} + +// Extracts the selected signed or unsigned 32-bit integer from a and zero +// extends. +// FORCE_INLINE int _mm_extract_epi32(__m128i a, __constrange(0,4) int imm) +#define _mm_extract_epi32(a, imm) \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm)) + +// Extracts the selected signed or unsigned 64-bit integer from a and zero +// extends. +// FORCE_INLINE __int64 _mm_extract_epi64(__m128i a, __constrange(0,2) int imm) +#define _mm_extract_epi64(a, imm) \ + vgetq_lane_s64(vreinterpretq_s64_m128i(a), (imm)) + +// Extracts the selected signed or unsigned 8-bit integer from a and zero +// extends. +// FORCE_INLINE int _mm_extract_epi8(__m128i a, __constrange(0,16) int imm) +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_epi8 +#define _mm_extract_epi8(a, imm) vgetq_lane_u8(vreinterpretq_u8_m128i(a), (imm)) + +// Extracts the selected single-precision (32-bit) floating-point from a. +// FORCE_INLINE int _mm_extract_ps(__m128 a, __constrange(0,4) int imm) +#define _mm_extract_ps(a, imm) vgetq_lane_s32(vreinterpretq_s32_m128(a), (imm)) + +// Round the packed double-precision (64-bit) floating-point elements in a down +// to an integer value, and store the results as packed double-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_pd +FORCE_INLINE __m128d _mm_floor_pd(__m128d a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128d_f64(vrndmq_f64(vreinterpretq_f64_m128d(a))); +#else + double *f = (double *) &a; + return _mm_set_pd(floor(f[1]), floor(f[0])); +#endif +} + +// Round the packed single-precision (32-bit) floating-point elements in a down +// to an integer value, and store the results as packed single-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ps +FORCE_INLINE __m128 _mm_floor_ps(__m128 a) +{ +#if defined(__aarch64__) + return vreinterpretq_m128_f32(vrndmq_f32(vreinterpretq_f32_m128(a))); +#else + float *f = (float *) &a; + return _mm_set_ps(floorf(f[3]), floorf(f[2]), floorf(f[1]), floorf(f[0])); +#endif +} + +// Round the lower double-precision (64-bit) floating-point element in b down to +// an integer value, store the result as a double-precision floating-point +// element in the lower element of dst, and copy the upper element from a to the +// upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_sd +FORCE_INLINE __m128d _mm_floor_sd(__m128d a, __m128d b) +{ + return _mm_move_sd(a, _mm_floor_pd(b)); +} + +// Round the lower single-precision (32-bit) floating-point element in b down to +// an integer value, store the result as a single-precision floating-point +// element in the lower element of dst, and copy the upper 3 packed elements +// from a to the upper elements of dst. +// +// dst[31:0] := FLOOR(b[31:0]) +// dst[127:32] := a[127:32] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ss +FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) +{ + return _mm_move_ss(a, _mm_floor_ps(b)); +} + +// Inserts the least significant 32 bits of b into the selected 32-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi32(__m128i a, int b, +// __constrange(0,4) int imm) +#define _mm_insert_epi32(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s32( \ + vsetq_lane_s32((b), vreinterpretq_s32_m128i(a), (imm))); \ + }) + +// Inserts the least significant 64 bits of b into the selected 64-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi64(__m128i a, __int64 b, +// __constrange(0,2) int imm) +#define _mm_insert_epi64(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s64( \ + vsetq_lane_s64((b), vreinterpretq_s64_m128i(a), (imm))); \ + }) + +// Inserts the least significant 8 bits of b into the selected 8-bit integer +// of a. +// FORCE_INLINE __m128i _mm_insert_epi8(__m128i a, int b, +// __constrange(0,16) int imm) +#define _mm_insert_epi8(a, b, imm) \ + __extension__({ \ + vreinterpretq_m128i_s8( \ + vsetq_lane_s8((b), vreinterpretq_s8_m128i(a), (imm))); \ + }) + +// Copy a to tmp, then insert a single-precision (32-bit) floating-point +// element from b into tmp using the control in imm8. Store tmp to dst using +// the mask in imm8 (elements are zeroed out when the corresponding bit is set). +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=insert_ps +#define _mm_insert_ps(a, b, imm8) \ + __extension__({ \ + float32x4_t tmp1 = \ + vsetq_lane_f32(vgetq_lane_f32(b, (imm8 >> 6) & 0x3), \ + vreinterpretq_f32_m128(a), 0); \ + float32x4_t tmp2 = \ + vsetq_lane_f32(vgetq_lane_f32(tmp1, 0), vreinterpretq_f32_m128(a), \ + ((imm8 >> 4) & 0x3)); \ + const uint32_t data[4] = {((imm8) & (1 << 0)) ? UINT32_MAX : 0, \ + ((imm8) & (1 << 1)) ? UINT32_MAX : 0, \ + ((imm8) & (1 << 2)) ? UINT32_MAX : 0, \ + ((imm8) & (1 << 3)) ? UINT32_MAX : 0}; \ + uint32x4_t mask = vld1q_u32(data); \ + float32x4_t all_zeros = vdupq_n_f32(0); \ + \ + vreinterpretq_m128_f32( \ + vbslq_f32(mask, all_zeros, vreinterpretq_f32_m128(tmp2))); \ + }) + +// epi versions of min/max +// Computes the pariwise maximums of the four signed 32-bit integer values of a +// and b. +// +// A 128-bit parameter that can be defined with the following equations: +// r0 := (a0 > b0) ? a0 : b0 +// r1 := (a1 > b1) ? a1 : b1 +// r2 := (a2 > b2) ? a2 : b2 +// r3 := (a3 > b3) ? a3 : b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/bb514055(v=vs.100).aspx +FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vmaxq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compare packed signed 8-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epi8 +FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vmaxq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed unsigned 16-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu16 +FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vmaxq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Compare packed unsigned 32-bit integers in a and b, and store packed maximum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 +FORCE_INLINE __m128i _mm_max_epu32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vmaxq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b))); +} + +// Computes the pariwise minima of the four signed 32-bit integer values of a +// and b. +// +// A 128-bit parameter that can be defined with the following equations: +// r0 := (a0 < b0) ? a0 : b0 +// r1 := (a1 < b1) ? a1 : b1 +// r2 := (a2 < b2) ? a2 : b2 +// r3 := (a3 < b3) ? a3 : b3 +// +// https://msdn.microsoft.com/en-us/library/vstudio/bb531476(v=vs.100).aspx +FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vminq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Compare packed signed 8-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epi8 +FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s8( + vminq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); +} + +// Compare packed unsigned 16-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epu16 +FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vminq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); +} + +// Compare packed unsigned 32-bit integers in a and b, and store packed minimum +// values in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 +FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u32( + vminq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b))); +} + +// Horizontally compute the minimum amongst the packed unsigned 16-bit integers +// in a, store the minimum and index in dst, and zero the remaining bits in dst. +// +// index[2:0] := 0 +// min[15:0] := a[15:0] +// FOR j := 0 to 7 +// i := j*16 +// IF a[i+15:i] < min[15:0] +// index[2:0] := j +// min[15:0] := a[i+15:i] +// FI +// ENDFOR +// dst[15:0] := min[15:0] +// dst[18:16] := index[2:0] +// dst[127:19] := 0 +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_minpos_epu16 +FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a) +{ + __m128i dst; + uint16_t min, idx = 0; + // Find the minimum value +#if defined(__aarch64__) + min = vminvq_u16(vreinterpretq_u16_m128i(a)); +#else + __m64 tmp; + tmp = vreinterpret_m64_u16( + vmin_u16(vget_low_u16(vreinterpretq_u16_m128i(a)), + vget_high_u16(vreinterpretq_u16_m128i(a)))); + tmp = vreinterpret_m64_u16( + vpmin_u16(vreinterpret_u16_m64(tmp), vreinterpret_u16_m64(tmp))); + tmp = vreinterpret_m64_u16( + vpmin_u16(vreinterpret_u16_m64(tmp), vreinterpret_u16_m64(tmp))); + min = vget_lane_u16(vreinterpret_u16_m64(tmp), 0); +#endif + // Get the index of the minimum value + int i; + for (i = 0; i < 8; i++) { + if (min == vgetq_lane_u16(vreinterpretq_u16_m128i(a), 0)) { + idx = (uint16_t) i; + break; + } + a = _mm_srli_si128(a, 2); + } + // Generate result + dst = _mm_setzero_si128(); + dst = vreinterpretq_m128i_u16( + vsetq_lane_u16(min, vreinterpretq_u16_m128i(dst), 0)); + dst = vreinterpretq_m128i_u16( + vsetq_lane_u16(idx, vreinterpretq_u16_m128i(dst), 1)); + return dst; +} + +// Compute the sum of absolute differences (SADs) of quadruplets of unsigned +// 8-bit integers in a compared to those in b, and store the 16-bit results in +// dst. Eight SADs are performed using one quadruplet from b and eight +// quadruplets from a. One quadruplet is selected from b starting at on the +// offset specified in imm8. Eight quadruplets are formed from sequential 8-bit +// integers selected from a starting at the offset specified in imm8. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mpsadbw_epu8 +FORCE_INLINE __m128i _mm_mpsadbw_epu8(__m128i a, __m128i b, const int imm) +{ + uint8x16_t _a, _b; + + switch (imm & 0x4) { + case 0: + // do nothing + _a = vreinterpretq_u8_m128i(a); + break; + case 4: + _a = vreinterpretq_u8_u32(vextq_u32(vreinterpretq_u32_m128i(a), + vreinterpretq_u32_m128i(a), 1)); + break; + default: +#if defined(__GNUC__) || defined(__clang__) + __builtin_unreachable(); +#endif + break; + } + + switch (imm & 0x3) { + case 0: + _b = vreinterpretq_u8_u32( + vdupq_n_u32(vgetq_lane_u32(vreinterpretq_u32_m128i(b), 0))); + break; + case 1: + _b = vreinterpretq_u8_u32( + vdupq_n_u32(vgetq_lane_u32(vreinterpretq_u32_m128i(b), 1))); + break; + case 2: + _b = vreinterpretq_u8_u32( + vdupq_n_u32(vgetq_lane_u32(vreinterpretq_u32_m128i(b), 2))); + break; + case 3: + _b = vreinterpretq_u8_u32( + vdupq_n_u32(vgetq_lane_u32(vreinterpretq_u32_m128i(b), 3))); + break; + default: +#if defined(__GNUC__) || defined(__clang__) + __builtin_unreachable(); +#endif + break; + } + + int16x8_t c04, c15, c26, c37; + uint8x8_t low_b = vget_low_u8(_b); + c04 = vabsq_s16(vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(_a), low_b))); + _a = vextq_u8(_a, _a, 1); + c15 = vabsq_s16(vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(_a), low_b))); + _a = vextq_u8(_a, _a, 1); + c26 = vabsq_s16(vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(_a), low_b))); + _a = vextq_u8(_a, _a, 1); + c37 = vabsq_s16(vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(_a), low_b))); +#if defined(__aarch64__) + // |0|4|2|6| + c04 = vpaddq_s16(c04, c26); + // |1|5|3|7| + c15 = vpaddq_s16(c15, c37); + + int32x4_t trn1_c = + vtrn1q_s32(vreinterpretq_s32_s16(c04), vreinterpretq_s32_s16(c15)); + int32x4_t trn2_c = + vtrn2q_s32(vreinterpretq_s32_s16(c04), vreinterpretq_s32_s16(c15)); + return vreinterpretq_m128i_s16(vpaddq_s16(vreinterpretq_s16_s32(trn1_c), + vreinterpretq_s16_s32(trn2_c))); +#else + int16x4_t c01, c23, c45, c67; + c01 = vpadd_s16(vget_low_s16(c04), vget_low_s16(c15)); + c23 = vpadd_s16(vget_low_s16(c26), vget_low_s16(c37)); + c45 = vpadd_s16(vget_high_s16(c04), vget_high_s16(c15)); + c67 = vpadd_s16(vget_high_s16(c26), vget_high_s16(c37)); + + return vreinterpretq_m128i_s16( + vcombine_s16(vpadd_s16(c01, c23), vpadd_s16(c45, c67))); +#endif +} + +// Multiply the low signed 32-bit integers from each packed 64-bit element in +// a and b, and store the signed 64-bit results in dst. +// +// r0 := (int64_t)(int32_t)a0 * (int64_t)(int32_t)b0 +// r1 := (int64_t)(int32_t)a2 * (int64_t)(int32_t)b2 +FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b) +{ + // vmull_s32 upcasts instead of masking, so we downcast. + int32x2_t a_lo = vmovn_s64(vreinterpretq_s64_m128i(a)); + int32x2_t b_lo = vmovn_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vmull_s32(a_lo, b_lo)); +} + +// Multiplies the 4 signed or unsigned 32-bit integers from a by the 4 signed or +// unsigned 32-bit integers from b. +// https://msdn.microsoft.com/en-us/library/vstudio/bb531409(v=vs.100).aspx +FORCE_INLINE __m128i _mm_mullo_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_s32( + vmulq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); +} + +// Packs the 8 unsigned 32-bit integers from a and b into unsigned 16-bit +// integers and saturates. +// +// r0 := UnsignedSaturate(a0) +// r1 := UnsignedSaturate(a1) +// r2 := UnsignedSaturate(a2) +// r3 := UnsignedSaturate(a3) +// r4 := UnsignedSaturate(b0) +// r5 := UnsignedSaturate(b1) +// r6 := UnsignedSaturate(b2) +// r7 := UnsignedSaturate(b3) +FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u16( + vcombine_u16(vqmovun_s32(vreinterpretq_s32_m128i(a)), + vqmovun_s32(vreinterpretq_s32_m128i(b)))); +} + +// Round the packed double-precision (64-bit) floating-point elements in a using +// the rounding parameter, and store the results as packed double-precision +// floating-point elements in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_pd +FORCE_INLINE __m128d _mm_round_pd(__m128d a, int rounding) +{ +#if defined(__aarch64__) + switch (rounding) { + case (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC): + return vreinterpretq_m128d_f64(vrndnq_f64(vreinterpretq_f64_m128d(a))); + case (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC): + return _mm_floor_pd(a); + case (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC): + return _mm_ceil_pd(a); + case (_MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC): + return vreinterpretq_m128d_f64(vrndq_f64(vreinterpretq_f64_m128d(a))); + default: //_MM_FROUND_CUR_DIRECTION + return vreinterpretq_m128d_f64(vrndiq_f64(vreinterpretq_f64_m128d(a))); + } +#else + double *v_double = (double *) &a; + + if (rounding == (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_NEAREST)) { + double res[2], tmp; + for (int i = 0; i < 2; i++) { + tmp = (v_double[i] < 0) ? -v_double[i] : v_double[i]; + double roundDown = floor(tmp); // Round down value + double roundUp = ceil(tmp); // Round up value + double diffDown = tmp - roundDown; + double diffUp = roundUp - tmp; + if (diffDown < diffUp) { + /* If it's closer to the round down value, then use it */ + res[i] = roundDown; + } else if (diffDown > diffUp) { + /* If it's closer to the round up value, then use it */ + res[i] = roundUp; + } else { + /* If it's equidistant between round up and round down value, + * pick the one which is an even number */ + double half = roundDown / 2; + if (half != floor(half)) { + /* If the round down value is odd, return the round up value + */ + res[i] = roundUp; + } else { + /* If the round up value is odd, return the round down value + */ + res[i] = roundDown; + } + } + res[i] = (v_double[i] < 0) ? -res[i] : res[i]; + } + return _mm_set_pd(res[1], res[0]); + } else if (rounding == (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_DOWN)) { + return _mm_floor_pd(a); + } else if (rounding == (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_UP)) { + return _mm_ceil_pd(a); + } + return _mm_set_pd(v_double[1] > 0 ? floor(v_double[1]) : ceil(v_double[1]), + v_double[0] > 0 ? floor(v_double[0]) : ceil(v_double[0])); +#endif +} + +// Round the packed single-precision (32-bit) floating-point elements in a using +// the rounding parameter, and store the results as packed single-precision +// floating-point elements in dst. +// software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_ps +FORCE_INLINE __m128 _mm_round_ps(__m128 a, int rounding) +{ +#if defined(__aarch64__) + switch (rounding) { + case (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndnq_f32(vreinterpretq_f32_m128(a))); + case (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC): + return _mm_floor_ps(a); + case (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC): + return _mm_ceil_ps(a); + case (_MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC): + return vreinterpretq_m128_f32(vrndq_f32(vreinterpretq_f32_m128(a))); + default: //_MM_FROUND_CUR_DIRECTION + return vreinterpretq_m128_f32(vrndiq_f32(vreinterpretq_f32_m128(a))); + } +#else + float *v_float = (float *) &a; + + if (rounding == (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_NEAREST)) { + uint32x4_t signmask = vdupq_n_u32(0x80000000); + float32x4_t half = vbslq_f32(signmask, vreinterpretq_f32_m128(a), + vdupq_n_f32(0.5f)); /* +/- 0.5 */ + int32x4_t r_normal = vcvtq_s32_f32(vaddq_f32( + vreinterpretq_f32_m128(a), half)); /* round to integer: [a + 0.5]*/ + int32x4_t r_trunc = vcvtq_s32_f32( + vreinterpretq_f32_m128(a)); /* truncate to integer: [a] */ + int32x4_t plusone = vreinterpretq_s32_u32(vshrq_n_u32( + vreinterpretq_u32_s32(vnegq_s32(r_trunc)), 31)); /* 1 or 0 */ + int32x4_t r_even = vbicq_s32(vaddq_s32(r_trunc, plusone), + vdupq_n_s32(1)); /* ([a] + {0,1}) & ~1 */ + float32x4_t delta = vsubq_f32( + vreinterpretq_f32_m128(a), + vcvtq_f32_s32(r_trunc)); /* compute delta: delta = (a - [a]) */ + uint32x4_t is_delta_half = + vceqq_f32(delta, half); /* delta == +/- 0.5 */ + return vreinterpretq_m128_f32( + vcvtq_f32_s32(vbslq_s32(is_delta_half, r_even, r_normal))); + } else if (rounding == (_MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_DOWN)) { + return _mm_floor_ps(a); + } else if (rounding == (_MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC) || + (rounding == _MM_FROUND_CUR_DIRECTION && + _MM_GET_ROUNDING_MODE() == _MM_ROUND_UP)) { + return _mm_ceil_ps(a); + } + return _mm_set_ps(v_float[3] > 0 ? floorf(v_float[3]) : ceilf(v_float[3]), + v_float[2] > 0 ? floorf(v_float[2]) : ceilf(v_float[2]), + v_float[1] > 0 ? floorf(v_float[1]) : ceilf(v_float[1]), + v_float[0] > 0 ? floorf(v_float[0]) : ceilf(v_float[0])); +#endif +} + +// Round the lower double-precision (64-bit) floating-point element in b using +// the rounding parameter, store the result as a double-precision floating-point +// element in the lower element of dst, and copy the upper element from a to the +// upper element of dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_sd +FORCE_INLINE __m128d _mm_round_sd(__m128d a, __m128d b, int rounding) +{ + return _mm_move_sd(a, _mm_round_pd(b, rounding)); +} + +// Round the lower single-precision (32-bit) floating-point element in b using +// the rounding parameter, store the result as a single-precision floating-point +// element in the lower element of dst, and copy the upper 3 packed elements +// from a to the upper elements of dst. Rounding is done according to the +// rounding[3:0] parameter, which can be one of: +// (_MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC) // round to nearest, and +// suppress exceptions +// (_MM_FROUND_TO_NEG_INF |_MM_FROUND_NO_EXC) // round down, and +// suppress exceptions +// (_MM_FROUND_TO_POS_INF |_MM_FROUND_NO_EXC) // round up, and suppress +// exceptions +// (_MM_FROUND_TO_ZERO |_MM_FROUND_NO_EXC) // truncate, and suppress +// exceptions _MM_FROUND_CUR_DIRECTION // use MXCSR.RC; see +// _MM_SET_ROUNDING_MODE +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_ss +FORCE_INLINE __m128 _mm_round_ss(__m128 a, __m128 b, int rounding) +{ + return _mm_move_ss(a, _mm_round_ps(b, rounding)); +} + +// Load 128-bits of integer data from memory into dst using a non-temporal +// memory hint. mem_addr must be aligned on a 16-byte boundary or a +// general-protection exception may be generated. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_load_si128 +FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p) +{ +#if __has_builtin(__builtin_nontemporal_store) + return __builtin_nontemporal_load(p); +#else + return vreinterpretq_m128i_s64(vld1q_s64((int64_t *) p)); +#endif +} + +// Compute the bitwise NOT of a and then AND with a 128-bit vector containing +// all 1's, and return 1 if the result is zero, otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_ones +FORCE_INLINE int _mm_test_all_ones(__m128i a) +{ + return (uint64_t)(vgetq_lane_s64(a, 0) & vgetq_lane_s64(a, 1)) == + ~(uint64_t) 0; +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and +// mask, and return 1 if the result is zero, otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_zeros +FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask) +{ + int64x2_t a_and_mask = + vandq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(mask)); + return !(vgetq_lane_s64(a_and_mask, 0) | vgetq_lane_s64(a_and_mask, 1)); +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and +// mask, and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute +// the bitwise NOT of a and then AND with mask, and set CF to 1 if the result is +// zero, otherwise set CF to 0. Return 1 if both the ZF and CF values are zero, +// otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_test_mix_ones_zero +FORCE_INLINE int _mm_test_mix_ones_zeros(__m128i a, __m128i mask) +{ + uint64x2_t zf = + vandq_u64(vreinterpretq_u64_m128i(mask), vreinterpretq_u64_m128i(a)); + uint64x2_t cf = + vbicq_u64(vreinterpretq_u64_m128i(mask), vreinterpretq_u64_m128i(a)); + uint64x2_t result = vandq_u64(zf, cf); + return !(vgetq_lane_u64(result, 0) | vgetq_lane_u64(result, 1)); +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and b, +// and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the +// bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, +// otherwise set CF to 0. Return the CF value. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testc_si128 +FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b) +{ + int64x2_t s64 = + vandq_s64(vreinterpretq_s64_s32(vmvnq_s32(vreinterpretq_s32_m128i(a))), + vreinterpretq_s64_m128i(b)); + return !(vgetq_lane_s64(s64, 0) | vgetq_lane_s64(s64, 1)); +} + +// Compute the bitwise AND of 128 bits (representing integer data) in a and b, +// and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the +// bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, +// otherwise set CF to 0. Return 1 if both the ZF and CF values are zero, +// otherwise return 0. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testnzc_si128 +#define _mm_testnzc_si128(a, b) _mm_test_mix_ones_zeros(a, b) + +// Compute the bitwise AND of 128 bits (representing integer data) in a and b, +// and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the +// bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, +// otherwise set CF to 0. Return the ZF value. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testz_si128 +FORCE_INLINE int _mm_testz_si128(__m128i a, __m128i b) +{ + int64x2_t s64 = + vandq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b)); + return !(vgetq_lane_s64(s64, 0) | vgetq_lane_s64(s64, 1)); +} + +/* SSE4.2 */ + +// Compares the 2 signed 64-bit integers in a and the 2 signed 64-bit integers +// in b for greater than. +FORCE_INLINE __m128i _mm_cmpgt_epi64(__m128i a, __m128i b) +{ +#if defined(__aarch64__) + return vreinterpretq_m128i_u64( + vcgtq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); +#else + return vreinterpretq_m128i_s64(vshrq_n_s64( + vqsubq_s64(vreinterpretq_s64_m128i(b), vreinterpretq_s64_m128i(a)), + 63)); +#endif +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 16-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb531411(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32ch %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u8(crc, v & 0xff); + crc = _mm_crc32_u8(crc, (v >> 8) & 0xff); +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 32-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb531394(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cw %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u16(crc, v & 0xffff); + crc = _mm_crc32_u16(crc, (v >> 16) & 0xffff); +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 64-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb514033(v=vs.100) +FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cx %w[c], %w[c], %x[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc = _mm_crc32_u32((uint32_t)(crc), v & 0xffffffff); + crc = _mm_crc32_u32((uint32_t)(crc), (v >> 32) & 0xffffffff); +#endif + return crc; +} + +// Starting with the initial value in crc, accumulates a CRC32 value for +// unsigned 8-bit integer v. +// https://msdn.microsoft.com/en-us/library/bb514036(v=vs.100) +FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v) +{ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + __asm__ __volatile__("crc32cb %w[c], %w[c], %w[v]\n\t" + : [c] "+r"(crc) + : [v] "r"(v)); +#else + crc ^= v; + for (int bit = 0; bit < 8; bit++) { + if (crc & 1) + crc = (crc >> 1) ^ UINT32_C(0x82f63b78); + else + crc = (crc >> 1); + } +#endif + return crc; +} + +/* AES */ + +#if !defined(__ARM_FEATURE_CRYPTO) +/* clang-format off */ +#define SSE2NEON_AES_DATA(w) \ + { \ + w(0x63), w(0x7c), w(0x77), w(0x7b), w(0xf2), w(0x6b), w(0x6f), \ + w(0xc5), w(0x30), w(0x01), w(0x67), w(0x2b), w(0xfe), w(0xd7), \ + w(0xab), w(0x76), w(0xca), w(0x82), w(0xc9), w(0x7d), w(0xfa), \ + w(0x59), w(0x47), w(0xf0), w(0xad), w(0xd4), w(0xa2), w(0xaf), \ + w(0x9c), w(0xa4), w(0x72), w(0xc0), w(0xb7), w(0xfd), w(0x93), \ + w(0x26), w(0x36), w(0x3f), w(0xf7), w(0xcc), w(0x34), w(0xa5), \ + w(0xe5), w(0xf1), w(0x71), w(0xd8), w(0x31), w(0x15), w(0x04), \ + w(0xc7), w(0x23), w(0xc3), w(0x18), w(0x96), w(0x05), w(0x9a), \ + w(0x07), w(0x12), w(0x80), w(0xe2), w(0xeb), w(0x27), w(0xb2), \ + w(0x75), w(0x09), w(0x83), w(0x2c), w(0x1a), w(0x1b), w(0x6e), \ + w(0x5a), w(0xa0), w(0x52), w(0x3b), w(0xd6), w(0xb3), w(0x29), \ + w(0xe3), w(0x2f), w(0x84), w(0x53), w(0xd1), w(0x00), w(0xed), \ + w(0x20), w(0xfc), w(0xb1), w(0x5b), w(0x6a), w(0xcb), w(0xbe), \ + w(0x39), w(0x4a), w(0x4c), w(0x58), w(0xcf), w(0xd0), w(0xef), \ + w(0xaa), w(0xfb), w(0x43), w(0x4d), w(0x33), w(0x85), w(0x45), \ + w(0xf9), w(0x02), w(0x7f), w(0x50), w(0x3c), w(0x9f), w(0xa8), \ + w(0x51), w(0xa3), w(0x40), w(0x8f), w(0x92), w(0x9d), w(0x38), \ + w(0xf5), w(0xbc), w(0xb6), w(0xda), w(0x21), w(0x10), w(0xff), \ + w(0xf3), w(0xd2), w(0xcd), w(0x0c), w(0x13), w(0xec), w(0x5f), \ + w(0x97), w(0x44), w(0x17), w(0xc4), w(0xa7), w(0x7e), w(0x3d), \ + w(0x64), w(0x5d), w(0x19), w(0x73), w(0x60), w(0x81), w(0x4f), \ + w(0xdc), w(0x22), w(0x2a), w(0x90), w(0x88), w(0x46), w(0xee), \ + w(0xb8), w(0x14), w(0xde), w(0x5e), w(0x0b), w(0xdb), w(0xe0), \ + w(0x32), w(0x3a), w(0x0a), w(0x49), w(0x06), w(0x24), w(0x5c), \ + w(0xc2), w(0xd3), w(0xac), w(0x62), w(0x91), w(0x95), w(0xe4), \ + w(0x79), w(0xe7), w(0xc8), w(0x37), w(0x6d), w(0x8d), w(0xd5), \ + w(0x4e), w(0xa9), w(0x6c), w(0x56), w(0xf4), w(0xea), w(0x65), \ + w(0x7a), w(0xae), w(0x08), w(0xba), w(0x78), w(0x25), w(0x2e), \ + w(0x1c), w(0xa6), w(0xb4), w(0xc6), w(0xe8), w(0xdd), w(0x74), \ + w(0x1f), w(0x4b), w(0xbd), w(0x8b), w(0x8a), w(0x70), w(0x3e), \ + w(0xb5), w(0x66), w(0x48), w(0x03), w(0xf6), w(0x0e), w(0x61), \ + w(0x35), w(0x57), w(0xb9), w(0x86), w(0xc1), w(0x1d), w(0x9e), \ + w(0xe1), w(0xf8), w(0x98), w(0x11), w(0x69), w(0xd9), w(0x8e), \ + w(0x94), w(0x9b), w(0x1e), w(0x87), w(0xe9), w(0xce), w(0x55), \ + w(0x28), w(0xdf), w(0x8c), w(0xa1), w(0x89), w(0x0d), w(0xbf), \ + w(0xe6), w(0x42), w(0x68), w(0x41), w(0x99), w(0x2d), w(0x0f), \ + w(0xb0), w(0x54), w(0xbb), w(0x16) \ + } +/* clang-format on */ + +/* X Macro trick. See https://en.wikipedia.org/wiki/X_Macro */ +#define SSE2NEON_AES_H0(x) (x) +static const uint8_t SSE2NEON_sbox[256] = SSE2NEON_AES_DATA(SSE2NEON_AES_H0); +#undef SSE2NEON_AES_H0 + +// In the absence of crypto extensions, implement aesenc using regular neon +// intrinsics instead. See: +// https://www.workofard.com/2017/01/accelerated-aes-for-the-arm64-linux-kernel/ +// https://www.workofard.com/2017/07/ghash-for-low-end-cores/ and +// https://github.com/ColinIanKing/linux-next-mirror/blob/b5f466091e130caaf0735976648f72bd5e09aa84/crypto/aegis128-neon-inner.c#L52 +// for more information Reproduced with permission of the author. +FORCE_INLINE __m128i _mm_aesenc_si128(__m128i EncBlock, __m128i RoundKey) +{ +#if defined(__aarch64__) + static const uint8_t shift_rows[] = {0x0, 0x5, 0xa, 0xf, 0x4, 0x9, + 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, + 0xc, 0x1, 0x6, 0xb}; + static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, + 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc}; + + uint8x16_t v; + uint8x16_t w = vreinterpretq_u8_m128i(EncBlock); + + // shift rows + w = vqtbl1q_u8(w, vld1q_u8(shift_rows)); + + // sub bytes + v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_sbox), w); + v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40); + v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80); + v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0); + + // mix columns + w = (v << 1) ^ (uint8x16_t)(((int8x16_t) v >> 7) & 0x1b); + w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v); + w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8)); + + // add round key + return vreinterpretq_m128i_u8(w) ^ RoundKey; + +#else /* ARMv7-A NEON implementation */ +#define SSE2NEON_AES_B2W(b0, b1, b2, b3) \ + (((uint32_t)(b3) << 24) | ((uint32_t)(b2) << 16) | ((uint32_t)(b1) << 8) | \ + (b0)) +#define SSE2NEON_AES_F2(x) ((x << 1) ^ (((x >> 7) & 1) * 0x011b /* WPOLY */)) +#define SSE2NEON_AES_F3(x) (SSE2NEON_AES_F2(x) ^ x) +#define SSE2NEON_AES_U0(p) \ + SSE2NEON_AES_B2W(SSE2NEON_AES_F2(p), p, p, SSE2NEON_AES_F3(p)) +#define SSE2NEON_AES_U1(p) \ + SSE2NEON_AES_B2W(SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p), p, p) +#define SSE2NEON_AES_U2(p) \ + SSE2NEON_AES_B2W(p, SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p), p) +#define SSE2NEON_AES_U3(p) \ + SSE2NEON_AES_B2W(p, p, SSE2NEON_AES_F3(p), SSE2NEON_AES_F2(p)) + static const uint32_t ALIGN_STRUCT(16) aes_table[4][256] = { + SSE2NEON_AES_DATA(SSE2NEON_AES_U0), + SSE2NEON_AES_DATA(SSE2NEON_AES_U1), + SSE2NEON_AES_DATA(SSE2NEON_AES_U2), + SSE2NEON_AES_DATA(SSE2NEON_AES_U3), + }; +#undef SSE2NEON_AES_B2W +#undef SSE2NEON_AES_F2 +#undef SSE2NEON_AES_F3 +#undef SSE2NEON_AES_U0 +#undef SSE2NEON_AES_U1 +#undef SSE2NEON_AES_U2 +#undef SSE2NEON_AES_U3 + + uint32_t x0 = _mm_cvtsi128_si32(EncBlock); + uint32_t x1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0x55)); + uint32_t x2 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0xAA)); + uint32_t x3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(EncBlock, 0xFF)); + + __m128i out = _mm_set_epi32( + (aes_table[0][x3 & 0xff] ^ aes_table[1][(x0 >> 8) & 0xff] ^ + aes_table[2][(x1 >> 16) & 0xff] ^ aes_table[3][x2 >> 24]), + (aes_table[0][x2 & 0xff] ^ aes_table[1][(x3 >> 8) & 0xff] ^ + aes_table[2][(x0 >> 16) & 0xff] ^ aes_table[3][x1 >> 24]), + (aes_table[0][x1 & 0xff] ^ aes_table[1][(x2 >> 8) & 0xff] ^ + aes_table[2][(x3 >> 16) & 0xff] ^ aes_table[3][x0 >> 24]), + (aes_table[0][x0 & 0xff] ^ aes_table[1][(x1 >> 8) & 0xff] ^ + aes_table[2][(x2 >> 16) & 0xff] ^ aes_table[3][x3 >> 24])); + + return _mm_xor_si128(out, RoundKey); +#endif +} + +// Perform the last round of an AES encryption flow on data (state) in a using +// the round key in RoundKey, and store the result in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128 +FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) +{ + /* FIXME: optimized for NEON */ + uint8_t v[4][4] = { + {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 0)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 5)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 10)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 15)]}, + {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 4)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 9)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 14)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 3)]}, + {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 8)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 13)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 2)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 7)]}, + {SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 12)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 1)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 6)], + SSE2NEON_sbox[vreinterpretq_nth_u8_m128i(a, 11)]}, + }; + for (int i = 0; i < 16; i++) + vreinterpretq_nth_u8_m128i(a, i) = + v[i / 4][i % 4] ^ vreinterpretq_nth_u8_m128i(RoundKey, i); + return a; +} + +// Emits the Advanced Encryption Standard (AES) instruction aeskeygenassist. +// This instruction generates a round key for AES encryption. See +// https://kazakov.life/2017/11/01/cryptocurrency-mining-on-ios-devices/ +// for details. +// +// https://msdn.microsoft.com/en-us/library/cc714138(v=vs.120).aspx +FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i key, const int rcon) +{ + uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0x55)); + uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0xFF)); + for (int i = 0; i < 4; ++i) { + ((uint8_t *) &X1)[i] = SSE2NEON_sbox[((uint8_t *) &X1)[i]]; + ((uint8_t *) &X3)[i] = SSE2NEON_sbox[((uint8_t *) &X3)[i]]; + } + return _mm_set_epi32(((X3 >> 8) | (X3 << 24)) ^ rcon, X3, + ((X1 >> 8) | (X1 << 24)) ^ rcon, X1); +} +#undef SSE2NEON_AES_DATA + +#else /* __ARM_FEATURE_CRYPTO */ +// Implements equivalent of 'aesenc' by combining AESE (with an empty key) and +// AESMC and then manually applying the real key as an xor operation. This +// unfortunately means an additional xor op; the compiler should be able to +// optimize this away for repeated calls however. See +// https://blog.michaelbrase.com/2018/05/08/emulating-x86-aes-intrinsics-on-armv8-a +// for more details. +FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i b) +{ + return vreinterpretq_m128i_u8( + vaesmcq_u8(vaeseq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0))) ^ + vreinterpretq_u8_m128i(b)); +} + +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128 +FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) +{ + return _mm_xor_si128(vreinterpretq_m128i_u8(vaeseq_u8( + vreinterpretq_u8_m128i(a), vdupq_n_u8(0))), + RoundKey); +} + +FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon) +{ + // AESE does ShiftRows and SubBytes on A + uint8x16_t u8 = vaeseq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)); + + uint8x16_t dest = { + // Undo ShiftRows step from AESE and extract X1 and X3 + u8[0x4], u8[0x1], u8[0xE], u8[0xB], // SubBytes(X1) + u8[0x1], u8[0xE], u8[0xB], u8[0x4], // ROT(SubBytes(X1)) + u8[0xC], u8[0x9], u8[0x6], u8[0x3], // SubBytes(X3) + u8[0x9], u8[0x6], u8[0x3], u8[0xC], // ROT(SubBytes(X3)) + }; + uint32x4_t r = {0, (unsigned) rcon, 0, (unsigned) rcon}; + return vreinterpretq_m128i_u8(dest) ^ vreinterpretq_m128i_u32(r); +} +#endif + +/* Others */ + +// Perform a carry-less multiplication of two 64-bit integers, selected from a +// and b according to imm8, and store the results in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_clmulepi64_si128 +FORCE_INLINE __m128i _mm_clmulepi64_si128(__m128i _a, __m128i _b, const int imm) +{ + uint64x2_t a = vreinterpretq_u64_m128i(_a); + uint64x2_t b = vreinterpretq_u64_m128i(_b); + switch (imm & 0x11) { + case 0x00: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_low_u64(a), vget_low_u64(b))); + case 0x01: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_high_u64(a), vget_low_u64(b))); + case 0x10: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_low_u64(a), vget_high_u64(b))); + case 0x11: + return vreinterpretq_m128i_u64( + _sse2neon_vmull_p64(vget_high_u64(a), vget_high_u64(b))); + default: + abort(); + } +} + +FORCE_INLINE unsigned int _sse2neon_mm_get_denormals_zero_mode() +{ + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + return r.field.bit24 ? _MM_DENORMALS_ZERO_ON : _MM_DENORMALS_ZERO_OFF; +} + +// Count the number of bits set to 1 in unsigned 32-bit integer a, and +// return that count in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u32 +FORCE_INLINE int _mm_popcnt_u32(unsigned int a) +{ +#if defined(__aarch64__) +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(a); +#else + return (int) vaddlv_u8(vcnt_u8(vcreate_u8((uint64_t) a))); +#endif +#else + uint32_t count = 0; + uint8x8_t input_val, count8x8_val; + uint16x4_t count16x4_val; + uint32x2_t count32x2_val; + + input_val = vld1_u8((uint8_t *) &a); + count8x8_val = vcnt_u8(input_val); + count16x4_val = vpaddl_u8(count8x8_val); + count32x2_val = vpaddl_u16(count16x4_val); + + vst1_u32(&count, count32x2_val); + return count; +#endif +} + +// Count the number of bits set to 1 in unsigned 64-bit integer a, and +// return that count in dst. +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u64 +FORCE_INLINE int64_t _mm_popcnt_u64(uint64_t a) +{ +#if defined(__aarch64__) +#if __has_builtin(__builtin_popcountll) + return __builtin_popcountll(a); +#else + return (int64_t) vaddlv_u8(vcnt_u8(vcreate_u8(a))); +#endif +#else + uint64_t count = 0; + uint8x8_t input_val, count8x8_val; + uint16x4_t count16x4_val; + uint32x2_t count32x2_val; + uint64x1_t count64x1_val; + + input_val = vld1_u8((uint8_t *) &a); + count8x8_val = vcnt_u8(input_val); + count16x4_val = vpaddl_u8(count8x8_val); + count32x2_val = vpaddl_u16(count16x4_val); + count64x1_val = vpaddl_u32(count32x2_val); + vst1_u64(&count, count64x1_val); + return count; +#endif +} + +FORCE_INLINE void _sse2neon_mm_set_denormals_zero_mode(unsigned int flag) +{ + // AArch32 Advanced SIMD arithmetic always uses the Flush-to-zero setting, + // regardless of the value of the FZ bit. + union { + fpcr_bitfield field; +#if defined(__aarch64__) + uint64_t value; +#else + uint32_t value; +#endif + } r; + +#if defined(__aarch64__) + asm volatile("mrs %0, FPCR" : "=r"(r.value)); /* read */ +#else + asm volatile("vmrs %0, FPSCR" : "=r"(r.value)); /* read */ +#endif + + r.field.bit24 = (flag & _MM_DENORMALS_ZERO_MASK) == _MM_DENORMALS_ZERO_ON; + +#if defined(__aarch64__) + asm volatile("msr FPCR, %0" ::"r"(r)); /* write */ +#else + asm volatile("vmsr FPSCR, %0" ::"r"(r)); /* write */ +#endif +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma pop_macro("ALIGN_STRUCT") +#pragma pop_macro("FORCE_INLINE") +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC pop_options +#endif + +#endif diff --git a/app/widget/nodetableview/nodetablewidget.h b/lib/olive/include/util/tests.h similarity index 56% rename from app/widget/nodetableview/nodetablewidget.h rename to lib/olive/include/util/tests.h index 24907553f4..0a035341fb 100644 --- a/app/widget/nodetableview/nodetablewidget.h +++ b/lib/olive/include/util/tests.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team + Copyright (C) 2023 Olive Studios LLC 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 @@ -18,40 +18,45 @@ ***/ -#ifndef NODETABLEWIDGET_H -#define NODETABLEWIDGET_H +#ifndef LIBOLIVECORE_TESTS_H +#define LIBOLIVECORE_TESTS_H -#include "nodetableview.h" -#include "widget/timebased/timebasedwidget.h" +#include namespace olive { -class NodeTableWidget : public TimeBasedWidget +class Tester { public: - NodeTableWidget(QWidget* parent = nullptr); + Tester() = default; - void SelectNodes(const QVector& nodes) - { - view_->SelectNodes(nodes); - } + typedef bool (*test_t)(); - void DeselectNodes(const QVector& nodes) + void add(const char *name, test_t test_function) { - view_->DeselectNodes(nodes); + test_names_.push_back(name); + test_functions_.push_back(test_function); } -protected: - virtual void TimeChangedEvent(const rational &time) override + bool run(); + + int exec() { - view_->SetTime(time); + if (run()) { + return 0; + } else { + return 1; + } } + static void echo(const char *fmt, ...); + private: - NodeTableView* view_; + std::list test_names_; + std::list test_functions_; }; } -#endif // NODETABLEWIDGET_H +#endif // LIBOLIVECORE_TESTS_H diff --git a/lib/olive/include/util/timecodefunctions.h b/lib/olive/include/util/timecodefunctions.h new file mode 100644 index 0000000000..34f6351aa2 --- /dev/null +++ b/lib/olive/include/util/timecodefunctions.h @@ -0,0 +1,78 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_TIMECODEFUNCTIONS_H +#define LIBOLIVECORE_TIMECODEFUNCTIONS_H + +#include "rational.h" + +namespace olive { + +/** + * @brief Functions for converting times/timecodes/timestamps + * + * Olive uses the following terminology through its code: + * + * `time` - time in seconds presented in a rational form + * `timebase` - the base time unit of an audio/video stream in seconds + * `timestamp` - an integer representation of a time in timebase units (in many cases is used like a frame number) + * `timecode` a user-friendly string representation of a time according to Timecode::Display + */ +class Timecode { +public: + enum Display { + kTimecodeDropFrame, + kTimecodeNonDropFrame, + kTimecodeSeconds, + kFrames, + kMilliseconds + }; + + enum Rounding { + kCeil, + kFloor, + kRound + }; + + /** + * @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation + */ + static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false); + static rational timecode_to_time(QString timecode, const rational& timebase, const Display& display, bool *ok = nullptr); + + static QString time_to_string(int64_t ms); + + static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound); + + static int64_t time_to_timestamp(const rational& time, const rational& timebase, Rounding floor = kRound); + static int64_t time_to_timestamp(const double& time, const rational& timebase, Rounding floor = kRound); + + static int64_t rescale_timestamp(const int64_t& ts, const rational& source, const rational& dest); + static int64_t rescale_timestamp_ceil(const int64_t& ts, const rational& source, const rational& dest); + + static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase); + + static bool timebase_is_drop_frame(const rational& timebase); + +}; + +} + +#endif // LIBOLIVECORE_TIMECODEFUNCTIONS_H diff --git a/lib/olive/include/util/timerange.h b/lib/olive/include/util/timerange.h new file mode 100644 index 0000000000..af92d0f124 --- /dev/null +++ b/lib/olive/include/util/timerange.h @@ -0,0 +1,310 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 LIBOLIVECORE_TIMERANGE_H +#define LIBOLIVECORE_TIMERANGE_H + +#include +#include + +#include "rational.h" + +namespace olive { + +class TimeRange { +public: + TimeRange() = default; + TimeRange(const rational& in, const rational& out); + TimeRange(const TimeRange& r) : + TimeRange(r.in(), r.out()) + { + } + + TimeRange &operator=(const TimeRange &r) + { + set_range(r.in(), r.out()); + return *this; + } + + const rational& in() const; + const rational& out() const; + const rational& length() const; + + void set_in(const rational& in); + void set_out(const rational& out); + void set_range(const rational& in, const rational& out); + + bool operator==(const TimeRange& r) const; + bool operator!=(const TimeRange& r) const; + + bool OverlapsWith(const TimeRange& a, bool in_inclusive = true, bool out_inclusive = true) const; + bool Contains(const TimeRange& a, bool in_inclusive = true, bool out_inclusive = true) const; + bool Contains(const rational& r) const; + + TimeRange Combined(const TimeRange& a) const; + static TimeRange Combine(const TimeRange &a, const TimeRange &b); + TimeRange Intersected(const TimeRange& a) const; + static TimeRange Intersect(const TimeRange &a, const TimeRange &b); + + TimeRange operator+(const rational& rhs) const; + TimeRange operator-(const rational& rhs) const; + + const TimeRange& operator+=(const rational &rhs); + const TimeRange& operator-=(const rational &rhs); + + std::list Split(const int &chunk_size) const; + +private: + void normalize(); + + rational in_; + rational out_; + rational length_; + +}; + +class TimeRangeList { +public: + TimeRangeList() = default; + + TimeRangeList(std::initializer_list r) : + array_(r) + { + } + + void insert(const TimeRangeList &list_to_add); + void insert(TimeRange range_to_add); + + void remove(const TimeRange& remove); + void remove(const TimeRangeList &list); + + template + static void util_remove(std::vector *list, const TimeRange &remove) + { + std::vector additions; + + for (auto it = list->begin(); it != list->end(); ) { + T& compare = *it; + + if (remove.Contains(compare)) { + // This element is entirely encompassed in this range, remove it + it = list->erase(it); + } else { + if (compare.Contains(remove, false, false)) { + // The remove range is within this element, only choice is to split the element into two + T new_range = compare; + new_range.set_in(remove.out()); + compare.set_out(remove.in()); + + additions.push_back(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()); + } else if (compare.in() < remove.out() && compare.out() > remove.out()) { + // This element's in point overlaps the range's out, we'll trim it + compare.set_in(remove.out()); + } + + it++; + } + } + } + + list->insert(list->end(), additions.begin(), additions.end()); + } + + bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const; + + bool contains(const rational &r) const + { + for (const TimeRange &range : array_) { + if (range.Contains(r)) { + return true; + } + } + + return false; + } + + bool OverlapsWith(const TimeRange& r, bool in_inclusive = true, bool out_inclusive = true) const + { + for (const TimeRange &range : array_) { + if (range.OverlapsWith(r, in_inclusive, out_inclusive)) { + return true; + } + } + + return false; + } + + bool isEmpty() const + { + return array_.empty(); + } + + 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 = std::vector::const_iterator; + + const_iterator begin() const + { + return array_.cbegin(); + } + + const_iterator end() const + { + return array_.cend(); + } + + const_iterator cbegin() const + { + return begin(); + } + + const_iterator cend() const + { + return end(); + } + + const TimeRange& first() const + { + return array_.front(); + } + + const TimeRange& last() const + { + return array_.back(); + } + + const TimeRange& at(int index) const + { + return array_.at(index); + } + + const std::vector& internal_array() const + { + return array_; + } + + bool operator==(const TimeRangeList &rhs) const + { + return array_ == rhs.array_; + } + +private: + std::vector array_; + +}; + +class TimeRangeListFrameIterator +{ +public: + TimeRangeListFrameIterator(); + TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase); + + rational Snap(const rational &r) const; + + bool GetNext(rational *out); + + bool HasNext() const; + + std::vector ToVector() const + { + TimeRangeListFrameIterator copy(list_, timebase_); + std::vector times; + rational r; + while (copy.GetNext(&r)) { + times.push_back(r); + } + return times; + } + + int size(); + + void reset() + { + *this = TimeRangeListFrameIterator(); + } + + void insert(const TimeRange &range) + { + list_.insert(range); + } + + void insert(const TimeRangeList &list) + { + list_.insert(list); + } + + bool IsCustomRange() const + { + return custom_range_; + } + + void SetCustomRange(bool e) + { + custom_range_ = e; + } + + int frame_index() const + { + return frame_index_; + } + +private: + void UpdateIndexIfNecessary(); + + TimeRangeList list_; + + rational timebase_; + + rational current_; + + int range_index_; + + int size_; + + int frame_index_; + + bool custom_range_; + +}; + +} + +#endif // LIBOLIVECORE_TIMERANGE_H diff --git a/app/widget/nodetableview/CMakeLists.txt b/lib/olive/src/CMakeLists.txt similarity index 78% rename from app/widget/nodetableview/CMakeLists.txt rename to lib/olive/src/CMakeLists.txt index 2132ccd6a9..8552d18823 100644 --- a/app/widget/nodetableview/CMakeLists.txt +++ b/lib/olive/src/CMakeLists.txt @@ -14,11 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - widget/nodetableview/nodetableview.cpp - widget/nodetableview/nodetableview.h - widget/nodetableview/nodetablewidget.cpp - widget/nodetableview/nodetablewidget.h +add_subdirectory(render) +add_subdirectory(util) + +set(OLIVECORE_SOURCES + ${OLIVECORE_SOURCES} PARENT_SCOPE ) + diff --git a/lib/olive/src/render/CMakeLists.txt b/lib/olive/src/render/CMakeLists.txt new file mode 100644 index 0000000000..1c72352150 --- /dev/null +++ b/lib/olive/src/render/CMakeLists.txt @@ -0,0 +1,24 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVECORE_SOURCES + ${OLIVECORE_SOURCES} + src/render/audiochannellayout.cpp + src/render/audioparams.cpp + src/render/samplebuffer.cpp + PARENT_SCOPE +) + diff --git a/lib/olive/src/render/audiochannellayout.cpp b/lib/olive/src/render/audiochannellayout.cpp new file mode 100644 index 0000000000..59c89cfe19 --- /dev/null +++ b/lib/olive/src/render/audiochannellayout.cpp @@ -0,0 +1,61 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "render/audiochannellayout.h" + +namespace olive { + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +AudioChannelLayout const AudioChannelLayout::STEREO = AVChannelLayout(AV_CHANNEL_LAYOUT_STEREO); +#pragma GCC diagnostic pop + +uint qHash(const AudioChannelLayout &l, uint seed) +{ + const AVChannelLayout *r = l.ref(); + + uint h = ::qHash(r->order, seed); + + h ^= ::qHash(r->nb_channels, seed); + + if (r->order == AV_CHANNEL_ORDER_NATIVE || r->order == AV_CHANNEL_ORDER_AMBISONIC) { + h ^= ::qHash(r->u.mask, seed); + } else { + h ^= ::qHashBits(r->u.map, sizeof(AVChannelCustom) * r->nb_channels, seed); + } + + return h; +} + +std::vector AudioChannelLayout::getChannelNames() const +{ + std::vector channels(internal_.nb_channels); + + char buf[8]; + for (int i = 0; i < internal_.nb_channels; i++) { + AVChannel channel = av_channel_layout_channel_from_index(&internal_, i); + av_channel_name(buf, sizeof(buf), channel); + channels[i] = buf; + } + + return channels; +} + +} diff --git a/lib/olive/src/render/audioparams.cpp b/lib/olive/src/render/audioparams.cpp new file mode 100644 index 0000000000..1d81dfd3d0 --- /dev/null +++ b/lib/olive/src/render/audioparams.cpp @@ -0,0 +1,194 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "render/audioparams.h" + +#include + +#include "common/xmlutils.h" + +namespace olive { + +const std::vector AudioParams::kSupportedSampleRates = { + 8000, // 8000 Hz + 11025, // 11025 Hz + 16000, // 16000 Hz + 22050, // 22050 Hz + 24000, // 24000 Hz + 32000, // 32000 Hz + 44100, // 44100 Hz + 48000, // 48000 Hz + 88200, // 88200 Hz + 96000 // 96000 Hz +}; + +const std::vector AudioParams::kSupportedChannelLayouts = { + AVChannelLayout(AV_CHANNEL_LAYOUT_MONO), + AVChannelLayout(AV_CHANNEL_LAYOUT_STEREO), + AVChannelLayout(AV_CHANNEL_LAYOUT_2_1), + AVChannelLayout(AV_CHANNEL_LAYOUT_5POINT1), + AVChannelLayout(AV_CHANNEL_LAYOUT_7POINT1) +}; + +bool AudioParams::operator==(const AudioParams &other) const +{ + return (format() == other.format() + && sample_rate() == other.sample_rate() + && time_base() == other.time_base() + && channel_layout() == other.channel_layout()); +} + +bool AudioParams::operator!=(const AudioParams &other) const +{ + return !(*this == other); +} + +int64_t AudioParams::time_to_bytes(const double &time) const +{ + return time_to_bytes_per_channel(time) * channel_count(); +} + +int64_t AudioParams::time_to_bytes(const rational &time) const +{ + return time_to_bytes(time.toDouble()); +} + +int64_t AudioParams::time_to_bytes_per_channel(const double &time) const +{ + assert(is_valid()); + + return int64_t(time_to_samples(time)) * bytes_per_sample_per_channel(); +} + +int64_t AudioParams::time_to_bytes_per_channel(const rational &time) const +{ + return time_to_bytes_per_channel(time.toDouble()); +} + +int64_t AudioParams::time_to_samples(const double &time) const +{ + assert(is_valid()); + + return std::round(double(sample_rate()) * time); +} + +int64_t AudioParams::time_to_samples(const rational &time) const +{ + return time_to_samples(time.toDouble()); +} + +int64_t AudioParams::samples_to_bytes(const int64_t &samples) const +{ + assert(is_valid()); + + return samples_to_bytes_per_channel(samples) * channel_count(); +} + +int64_t AudioParams::samples_to_bytes_per_channel(const int64_t &samples) const +{ + assert(is_valid()); + + return samples * bytes_per_sample_per_channel(); +} + +rational AudioParams::samples_to_time(const int64_t &samples) const +{ + return sample_rate_as_time_base() * samples; +} + +int64_t AudioParams::bytes_to_samples(const int64_t &bytes) const +{ + assert(is_valid()); + + return bytes / (channel_count() * bytes_per_sample_per_channel()); +} + +rational AudioParams::bytes_to_time(const int64_t &bytes) const +{ + assert(is_valid()); + + return samples_to_time(bytes_to_samples(bytes)); +} + +rational AudioParams::bytes_per_channel_to_time(const int64_t &bytes) const +{ + assert(is_valid()); + + return samples_to_time(bytes_to_samples(bytes * channel_count())); +} + +int AudioParams::channel_count() const +{ + return channel_layout_.count(); +} + +int AudioParams::bytes_per_sample_per_channel() const +{ + return format_.byte_count(); +} + +int AudioParams::bits_per_sample() const +{ + return bytes_per_sample_per_channel() * 8; +} + +bool AudioParams::is_valid() const +{ + return (!time_base().isNull() + && !channel_layout().isNull() + && format_ > SampleFormat::INVALID + && format_ < SampleFormat::COUNT); +} + +void AudioParams::load(QXmlStreamReader *reader) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("samplerate")) { + set_sample_rate(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("channellayout")) { + set_channel_layout(AudioChannelLayout::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("format")) { + set_format(SampleFormat::from_string(reader->readElementText())); + } else if (reader->name() == QStringLiteral("enabled")) { + set_enabled(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("streamindex")) { + set_stream_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("duration")) { + set_duration(reader->readElementText().toLongLong()); + } else if (reader->name() == QStringLiteral("timebase")) { + set_time_base(rational::fromString(reader->readElementText())); + } else { + reader->skipCurrentElement(); + } + } +} + +void AudioParams::save(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate())); + writer->writeTextElement(QStringLiteral("channellayout"), channel_layout().toString()); + writer->writeTextElement(QStringLiteral("format"), format().to_string()); + writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled())); + writer->writeTextElement(QStringLiteral("streamindex"), QString::number(stream_index())); + writer->writeTextElement(QStringLiteral("duration"), QString::number(duration())); + writer->writeTextElement(QStringLiteral("timebase"), time_base().toString()); +} + +} diff --git a/lib/olive/src/render/samplebuffer.cpp b/lib/olive/src/render/samplebuffer.cpp new file mode 100644 index 0000000000..56272baaef --- /dev/null +++ b/lib/olive/src/render/samplebuffer.cpp @@ -0,0 +1,301 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "render/samplebuffer.h" + +#include +#include +#include +#include + +#include "util/cpuoptimize.h" +#include "util/log.h" + +namespace olive { + +SampleBuffer::SampleBuffer() : + sample_count_per_channel_(0) +{ +} + +SampleBuffer::SampleBuffer(const AudioParams &audio_params, const rational &length) : + audio_params_(audio_params) +{ + sample_count_per_channel_ = audio_params_.time_to_samples(length); + allocate(); +} + +SampleBuffer::SampleBuffer(const AudioParams &audio_params, size_t samples_per_channel) : + audio_params_(audio_params), + sample_count_per_channel_(samples_per_channel) +{ + allocate(); +} + +SampleBuffer SampleBuffer::rip_channel(int channel) const +{ + AudioParams p = this->audio_params_; + p.set_channel_layout(AVChannelLayout(AV_CHANNEL_LAYOUT_MONO)); + + SampleBuffer b(p, this->sample_count_per_channel_); + b.fast_set(*this, 0, channel); + return b; +} + +std::vector SampleBuffer::rip_channel_vector(int channel) const +{ + return data_.at(channel); +} + +const AudioParams &SampleBuffer::audio_params() const +{ + return audio_params_; +} + +void SampleBuffer::set_audio_params(const AudioParams ¶ms) +{ + if (is_allocated()) { + Log::Warning() << "Tried to set parameters on allocated sample buffer"; + return; + } + + audio_params_ = params; +} + +void SampleBuffer::set_sample_count(const size_t &sample_count) +{ + if (is_allocated()) { + Log::Warning() << "Tried to set sample count on allocated sample buffer"; + return; + } + + sample_count_per_channel_ = sample_count; +} + +void SampleBuffer::allocate() +{ + if (!audio_params_.is_valid()) { + Log::Warning() << "Tried to allocate sample buffer with invalid audio parameters"; + return; + } + + if (!sample_count_per_channel_) { + Log::Warning() << "Tried to allocate sample buffer with zero sample count"; + return; + } + + if (is_allocated()) { + Log::Warning() << "Tried to allocate already allocated sample buffer"; + return; + } + + data_.resize(audio_params_.channel_count()); + for (int i=0; i(sample_count_per_channel_) / speed); + + std::vector< std::vector > output_data; + + output_data.resize(audio_params_.channel_count()); + for (int i=0; i(i) * speed); + + for (int j=0;jchannel_count() == output->channel_count()); + assert(input->sample_count_per_channel_ == output->sample_count_per_channel_); + + for (int i=0;iaudio_params().channel_count();i++) { + transform_volume_for_channel(i, f, input, output); + } +} + +void SampleBuffer::transform_volume_for_channel(int channel, float volume, const SampleBuffer *input, SampleBuffer *output) +{ + const float *cdat = input->data_[channel].data(); + float *odat = output->data_[channel].data(); + size_t unopt_start = 0; + + assert(input->channel_count() == output->channel_count()); + assert(input->sample_count_per_channel_ == output->sample_count_per_channel_); + +#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM) + __m128 mult = _mm_load1_ps(&volume); + unopt_start = (input->sample_count_per_channel_ / 4) * 4; + for (size_t j=0; jsample_count_per_channel_; j++) { + odat[j] = cdat[j] * volume; + } +} + +void SampleBuffer::transform_volume_for_sample(size_t sample_index, float volume) +{ + for (int i=0;i(data_[i].data()) + start_byte, 0, end_byte - start_byte); + } +} + +void SampleBuffer::set(int channel, const float *data, size_t sample_offset, size_t sample_length) +{ + if (!is_allocated()) { + Log::Warning() << "Tried to fill an unallocated sample buffer"; + return; + } + + memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length); +} + +void SampleBuffer::fast_set(const SampleBuffer &other, int to, int from) +{ + if (from == -1) { + from = to; + } + + data_[to] = other.data_[from]; +} + +void SampleBuffer::clamp_channel(int channel) +{ + const float min = -1.0f; + const float max = 1.0f; + + float *cdat = data_[channel].data(); + size_t unopt_start = 0; + +#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM) + __m128 min_sse = _mm_load1_ps(&min); + __m128 max_sse = _mm_load1_ps(&max); + + unopt_start = (sample_count_per_channel_ / 4) * 4; + for (size_t j=0; j. + +set(OLIVECORE_SOURCES + ${OLIVECORE_SOURCES} + src/util/bezier.cpp + src/util/color.cpp + src/util/rational.cpp + src/util/tests.cpp + src/util/timecodefunctions.cpp + src/util/timerange.cpp + PARENT_SCOPE +) + diff --git a/lib/olive/src/util/bezier.cpp b/lib/olive/src/util/bezier.cpp new file mode 100644 index 0000000000..9673ff6cf1 --- /dev/null +++ b/lib/olive/src/util/bezier.cpp @@ -0,0 +1,108 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/bezier.h" + +#include + +namespace olive { + +Bezier::Bezier() : + x_(0), + y_(0), + cp1_x_(0), + cp1_y_(0), + cp2_x_(0), + cp2_y_(0) +{ +} + +Bezier::Bezier(double x, double y) : + x_(x), + y_(y), + cp1_x_(0), + cp1_y_(0), + cp2_x_(0), + cp2_y_(0) +{ +} + +Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y) : + x_(x), + y_(y), + cp1_x_(cp1_x), + cp1_y_(cp1_y), + cp2_x_(cp2_x), + cp2_y_(cp2_y) +{ +} + +double Bezier::QuadraticXtoT(double x, double a, double b, double c) +{ + // Clamp to prevent infinite loop + x = std::clamp(x, a, c); + + return CalculateTFromX(false, x, a, b, c, 0); +} + +double Bezier::QuadraticTtoY(double a, double b, double c, double t) +{ + return std::pow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + std::pow(t, 2)*c; +} + +double Bezier::CubicXtoT(double x, double a, double b, double c, double d) +{ + // Clamp to prevent infinite loop + x = std::clamp(x, a, d); + + return CalculateTFromX(true, x, a, b, c, d); +} + +double Bezier::CubicTtoY(double a, double b, double c, double d, double t) +{ + return std::pow(1.0 - t, 3)*a + 3*std::pow(1.0 - t, 2)*t*b + 3*(1.0 - t)*std::pow(t, 2)*c + std::pow(t, 3)*d; +} + +double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double c, double d) +{ + double bottom = 0.0; + double top = 1.0; + + while (true) { + if (bottom == top) { + return bottom; + } + + double mid = (bottom + top) * 0.5; + double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid); + + if (std::abs(test - x) < 0.000001) { + return mid; + } else if (x > test) { + bottom = mid; + } else { + top = mid; + } + } + + return NAN; +} + +} diff --git a/lib/olive/src/util/color.cpp b/lib/olive/src/util/color.cpp new file mode 100644 index 0000000000..96a826ee19 --- /dev/null +++ b/lib/olive/src/util/color.cpp @@ -0,0 +1,328 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/color.h" + +#include +#include +#include +#include + +namespace olive { + +Color Color::fromHsv(const DataType &h, const DataType &s, const DataType &v) +{ + DataType C = s * v; + DataType X = C * (1.0 - std::abs(std::fmod(h / 60.0, 2.0) - 1.0)); + DataType m = v - C; + DataType Rs, Gs, Bs; + + if(h >= 0.0 && h < 60.0) { + Rs = C; + Gs = X; + Bs = 0.0; + } + else if(h >= 60.0 && h < 120.0) { + Rs = X; + Gs = C; + Bs = 0.0; + } + else if(h >= 120.0 && h < 180.0) { + Rs = 0.0; + Gs = C; + Bs = X; + } + else if(h >= 180.0 && h < 240.0) { + Rs = 0.0; + Gs = X; + Bs = C; + } + else if(h >= 240.0 && h < 300.0) { + Rs = X; + Gs = 0.0; + Bs = C; + } + else { + Rs = C; + Gs = 0.0; + Bs = X; + } + + return Color(Rs + m, Gs + m, Bs + m); +} + +Color::Color(const char *data, const PixelFormat &format, int ch_layout) +{ + *this = fromData(data, format, ch_layout); +} + +void Color::toHsv(DataType *hue, DataType *sat, DataType *val) const +{ + DataType fCMax = std::max(std::max(red(), green()), blue()); + DataType fCMin = std::min(std::min(red(), green()), blue()); + DataType fDelta = fCMax - fCMin; + + if(fDelta > 0) { + if(fCMax == red()) { + *hue = 60 * (fmod(((green() - blue()) / fDelta), 6)); + } else if(fCMax == green()) { + *hue = 60 * (((blue() - red()) / fDelta) + 2); + } else if(fCMax == blue()) { + *hue = 60 * (((red() - green()) / fDelta) + 4); + } + + if(fCMax > 0) { + *sat = fDelta / fCMax; + } else { + *sat = 0; + } + + *val = fCMax; + } else { + *hue = 0; + *sat = 0; + *val = fCMax; + } + + if(*hue < 0) { + *hue = 360 + *hue; + } +} + +Color::DataType Color::hsv_hue() const +{ + DataType h, s, v; + toHsv(&h, &s, &v); + return h; +} + +Color::DataType Color::hsv_saturation() const +{ + DataType h, s, v; + toHsv(&h, &s, &v); + return s; +} + +Color::DataType Color::value() const +{ + DataType h, s, v; + toHsv(&h, &s, &v); + return v; +} + +void Color::toHsl(DataType *hue, DataType *sat, DataType *lightness) const +{ + DataType fCMin = std::min(red(), std::min(green(), blue())); + DataType fCMax = std::max(red(), std::max(green(), blue())); + + *lightness = 0.5 * (fCMin + fCMax); + + if (fCMin == fCMax) + { + *sat = 0; + *hue = 0; + return; + + } + else if (*lightness < 0.5) + { + *sat = (fCMax - fCMin) / (fCMax + fCMin); + } + else + { + *sat = (fCMax - fCMin) / (2.0 - fCMax - fCMin); + } + + if (fCMax == red()) + { + *hue = 60 * (green() - blue()) / (fCMax - fCMin); + } + if (fCMax == green()) + { + *hue = 60 * (blue() - red()) / (fCMax - fCMin) + 120; + } + if (fCMax == blue()) + { + *hue = 60 * (red() - green()) / (fCMax - fCMin) + 240; + } + if (*hue < 0) + { + *hue = *hue + 360; + } +} + +Color::DataType Color::hsl_hue() const +{ + DataType h, s, l; + toHsl(&h, &s, &l); + return h; +} + +Color::DataType Color::hsl_saturation() const +{ + DataType h, s, l; + toHsl(&h, &s, &l); + return s; +} + +Color::DataType Color::lightness() const +{ + DataType h, s, l; + toHsl(&h, &s, &l); + return l; +} + +void Color::toData(char *out, const PixelFormat &format, unsigned int nb_channels) const +{ + unsigned int count = std::min(RGBA, nb_channels); + + for (unsigned int i = 0; i < count; i++) { + DataType f = data_[i]; + + switch (format) { + case PixelFormat::INVALID: + case PixelFormat::COUNT: + break; + case PixelFormat::U8: + reinterpret_cast(out)[i] = f * 255.0; + break; + case PixelFormat::U16: + reinterpret_cast(out)[i] = f * 65535.0; + break; + case PixelFormat::F16: + reinterpret_cast(out)[i] = f; + break; + case PixelFormat::F32: + reinterpret_cast(out)[i] = f; + break; + } + } +} + +Color Color::fromData(const char *in, const PixelFormat &format, unsigned int nb_channels) +{ + Color c; + + unsigned int count = std::min(RGBA, nb_channels); + + for (unsigned int i = 0; i < count; i++) { + DataType &f = c.data_[i]; + + switch (format) { + case PixelFormat::INVALID: + case PixelFormat::COUNT: + break; + case PixelFormat::U8: + f = DataType(reinterpret_cast(in)[i]) / 255.0; + break; + case PixelFormat::U16: + f = DataType(reinterpret_cast(in)[i]) / 65535.0; + break; + case PixelFormat::F16: + f = DataType(reinterpret_cast(in)[i]); + break; + case PixelFormat::F32: + f = DataType(reinterpret_cast(in)[i]); + break; + } + } + + switch (nb_channels) { + case 1: + // Convert grayscale channel to RGBA + c.set_alpha(1.0); + c.set_green(c.red()); + c.set_blue(c.red()); + break; + case 2: + // Map grayscale and alpha to RGBA + c.set_alpha(c.green()); + c.set_green(c.red()); + c.set_blue(c.red()); + break; + case 3: + // Map RGB to RGBA + c.set_alpha(1.0); + break; + } + + return c; +} + +Color::DataType Color::GetRoughLuminance() const +{ + return (2*red()+blue()+3*green())/6.0; +} + +Color &Color::operator+=(const Color &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] += rhs.data_[i]; + } + + return *this; +} + +Color &Color::operator-=(const Color &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] -= rhs.data_[i]; + } + + return *this; +} + +Color &Color::operator+=(const DataType &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] += rhs; + } + + return *this; +} + +Color &Color::operator-=(const DataType &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] -= rhs; + } + + return *this; +} + +Color &Color::operator*=(const DataType &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] *= rhs; + } + + return *this; +} + +Color &Color::operator/=(const DataType &rhs) +{ + for (int i = 0; i < RGBA; i++) { + data_[i] /= rhs; + } + + return *this; +} + +} diff --git a/lib/olive/src/util/rational.cpp b/lib/olive/src/util/rational.cpp new file mode 100644 index 0000000000..4f6dd51502 --- /dev/null +++ b/lib/olive/src/util/rational.cpp @@ -0,0 +1,294 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/rational.h" + +#include +#include + +namespace olive { + +const rational rational::NaN = rational(0, 0); + +rational rational::fromDouble(const double &flt, bool* ok) +{ + if (isnan(flt)) { + // Return NaN rational + if (ok) *ok = false; + return NaN; + } + + // Use FFmpeg function for the time being + AVRational r = av_d2q(flt, INT_MAX); + + if (r.den == 0) { + // If den == 0, we were unable to convert to a rational + if (ok) { + *ok = false; + } + } else { + // Otherwise, assume we received a real rational + if (ok) { + *ok = true; + } + } + + return r; +} + +rational rational::fromString(const QString &str, bool* ok) +{ + QStringList elements = str.split('/'); + + bool ok2; + switch (elements.size()) { + case 1: + { + int num = elements.front().toInt(&ok2); + if (ok2) { + if (ok) *ok = true; + return num; + } + } + case 2: + { + int num = elements.at(0).toInt(&ok2); + if (ok2) { + int den = elements.at(1).toInt(&ok2); + if (ok2) { + if (ok) *ok = true; + return rational(num, den); + } + } + } + default: + break; + } + + // Returns NaN with ok set to false + if (ok) { + *ok = false; + } + return NaN; +} + +//Function: convert to double + +double rational::toDouble() const +{ + if (r_.den != 0) { + return av_q2d(r_); + } else { + return std::numeric_limits::quiet_NaN(); + } +} + +AVRational rational::toAVRational() const +{ + return r_; +} + +#ifdef USE_OTIO +opentime::RationalTime rational::toRationalTime(double framerate) const +{ + // Is this the best way of doing this? + // Olive can store rationals as 0/0 which causes errors in OTIO + opentime::RationalTime time = opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den); + return time.rescaled_to(framerate); +} +#endif + +rational rational::flipped() const +{ + rational r = *this; + r.flip(); + return r; +} + +void rational::flip() +{ + if (!isNull()) { + std::swap(r_.den, r_.num); + fix_signs(); + } +} + +QString rational::toString() const +{ + return QStringLiteral("%1/%2").arg(QString::number(r_.num), QString::number(r_.den)); +} + +void rational::fix_signs() +{ + if (r_.den < 0) { + // Normalize so that denominator is always positive + r_.den = -r_.den; + r_.num = -r_.num; + } else if (r_.den == 0) { + // Normalize to 0/0 (aka NaN) if denominator is zero + r_.num = 0; + } else if (r_.num == 0) { + // Normalize to 0/1 if numerator is zero + r_.den = 1; + } +} + +void rational::reduce() +{ + av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX); +} + +//Assignment Operators + +const rational& rational::operator=(const rational &rhs) +{ + r_ = rhs.r_; + return *this; +} + +const rational& rational::operator+=(const rational &rhs) +{ + if(*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN || rhs == RATIONAL_MAX) { + *this = NaN; + } else if (!isNaN()) { + if (rhs.isNaN()) { + *this = NaN; + } else { + r_ = av_add_q(r_, rhs.r_); + fix_signs(); + } + } + + return *this; +} + +const rational& rational::operator-=(const rational &rhs) +{ + if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN || rhs == RATIONAL_MAX) { + *this = NaN; + } else if (!isNaN()) { + if (rhs.isNaN()) { + *this = NaN; + } else { + r_ = av_sub_q(r_, rhs.r_); + fix_signs(); + } + } + + return *this; +} + +const rational& rational::operator*=(const rational &rhs) +{ + if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN || rhs == RATIONAL_MAX) { + *this = NaN; + } else if (!isNaN()) { + if (rhs.isNaN()) { + *this = NaN; + } else { + r_ = av_mul_q(r_, rhs.r_); + fix_signs(); + } + } + + return *this; +} + +const rational& rational::operator/=(const rational &rhs) +{ + if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN || rhs == RATIONAL_MAX) { + *this = NaN; + } else if (!isNaN()) { + if (rhs.isNaN()) { + *this = NaN; + } else { + r_ = av_div_q(r_, rhs.r_); + fix_signs(); + } + } + + return *this; +} + +//Binary math operators + +rational rational::operator+(const rational &rhs) const +{ + rational answer(*this); + answer += rhs; + return answer; +} + +rational rational::operator-(const rational &rhs) const +{ + rational answer(*this); + answer -= rhs; + return answer; +} + +rational rational::operator/(const rational &rhs) const +{ + rational answer(*this); + answer /= rhs; + return answer; +} + +rational rational::operator*(const rational &rhs) const +{ + rational answer(*this); + answer *= rhs; + return answer; +} + +//Relational and equality operators + +bool rational::operator<(const rational &rhs) const +{ + return av_cmp_q(r_, rhs.r_) == -1; +} + +bool rational::operator<=(const rational &rhs) const +{ + int cmp = av_cmp_q(r_, rhs.r_); + return cmp == 0 || cmp == -1; +} + +bool rational::operator>(const rational &rhs) const +{ + return av_cmp_q(r_, rhs.r_) == 1; +} + +bool rational::operator>=(const rational &rhs) const +{ + int cmp = av_cmp_q(r_, rhs.r_); + return cmp == 0 || cmp == 1; +} + +bool rational::operator==(const rational &rhs) const +{ + return av_cmp_q(r_, rhs.r_) == 0; +} + +bool rational::operator!=(const rational &rhs) const +{ + return !(*this == rhs); +} + +} diff --git a/lib/olive/src/util/tests.cpp b/lib/olive/src/util/tests.cpp new file mode 100644 index 0000000000..ef8647757f --- /dev/null +++ b/lib/olive/src/util/tests.cpp @@ -0,0 +1,61 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/tests.h" + +#include +#include +#include + +namespace olive { + +bool Tester::run() +{ + size_t index = 1; + size_t count = test_functions_.size(); + + while (!test_functions_.empty()) { + echo("[%lu/%lu] %s :: ", index, count, test_names_.front()); + + if (test_functions_.front()()) { + echo("PASSED\n"); + } else { + echo("FAILED\n"); + return false; + } + + test_names_.pop_front(); + test_functions_.pop_front(); + } + + return true; +} + +void Tester::echo(const char *fmt, ...) +{ + va_list a; + va_start(a, fmt); + + vfprintf(stderr, fmt, a); + + va_end(a); +} + +} diff --git a/lib/olive/src/util/timecodefunctions.cpp b/lib/olive/src/util/timecodefunctions.cpp new file mode 100644 index 0000000000..0d91b428c9 --- /dev/null +++ b/lib/olive/src/util/timecodefunctions.cpp @@ -0,0 +1,333 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/timecodefunctions.h" + +extern "C" { +#include +} + +#include +#include + +namespace olive { + +QString leftpad(int64_t t, int count) +{ + return QStringLiteral("%1").arg(qlonglong(t), count, 10, QChar('0')); +} + +QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) +{ + if (timebase.isNull() || timebase.flipped().toDouble() < 1) { + return "INVALID TIMEBASE"; + } + + double time_dbl = time.toDouble(); + + switch (display) { + case kTimecodeNonDropFrame: + case kTimecodeDropFrame: + case kTimecodeSeconds: + { + const char *prefix = ""; + + if (time_dbl < 0) { + prefix = "-"; + } else if (show_plus_if_positive) { + prefix = "+"; + } + + if (display == kTimecodeSeconds) { + time_dbl = std::abs(time_dbl); + + int64_t total_seconds = std::floor(time_dbl); + + int64_t hours = total_seconds / 3600; + int64_t mins = total_seconds / 60 - hours * 60; + int64_t secs = total_seconds - mins * 60; + int64_t fraction = std::llround((time_dbl - static_cast(total_seconds)) * 1000); + + return QStringLiteral("%1%2:%3:%4.%5").arg(prefix, + leftpad(hours, 2), + leftpad(mins, 2), + leftpad(secs, 2), + leftpad(fraction, 3)); + } else { + // Determine what symbol to separate frames (";" is used for drop frame, ":" is non-drop frame) + const char *frame_token; + double frame_rate = timebase.flipped().toDouble(); + int rounded_frame_rate = std::llround(frame_rate); + int64_t frames, secs, mins, hours; + int64_t f = std::abs(time_to_timestamp(time, timebase)); + + if (display == kTimecodeDropFrame && timebase_is_drop_frame(timebase)) { + frame_token = ";"; + + /** + * CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE + * + * Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team + * Given an int called framenumber and a double called framerate + * Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. + */ + + // If frame number is greater than 24 hrs, next operation will rollover clock + f %= (std::llround(frame_rate*3600)*24); + + // Number of frames per ten minutes + int64_t framesPer10Minutes = std::llround(frame_rate * 600); + int64_t d = f / framesPer10Minutes; + int64_t m = f % framesPer10Minutes; + + // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int64_t dropFrames = std::llround(frame_rate * (2.0/30.0)); + + // Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + f += dropFrames*9*d; + if (m > dropFrames) { + f += dropFrames * ((m - dropFrames) / (std::llround(frame_rate)*60 - dropFrames)); + } + } else { + frame_token = ":"; + } + + // non-drop timecode + hours = f / (3600*rounded_frame_rate); + mins = f / (60*rounded_frame_rate) % 60; + secs = f / rounded_frame_rate % 60; + frames = f % rounded_frame_rate; + + return QStringLiteral("%1%2:%3:%4%5%6").arg(prefix, + leftpad(hours, 2), + leftpad(mins, 2), + leftpad(secs, 2), + frame_token, + leftpad(frames, 2)); + } + } + case kFrames: + return QString::number(time_to_timestamp(time, timebase)); + case kMilliseconds: + return QString::number(std::llround(time_dbl * 1000)); + } + + return "INVALID TIMECODE MODE"; +} + +int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok) +{ + return s.toLongLong(ok); +} + +double StrToDoubleEmptyTolerant(const QString &s, bool *ok) +{ + return s.toDouble(ok); +} + +rational Timecode::timecode_to_time(QString timecode, const rational &timebase, const Timecode::Display &display, bool *ok) +{ + timecode = timecode.trimmed(); + if (timecode.isEmpty()) { + goto err_fatal; + } + + switch (display) { + case kTimecodeNonDropFrame: + case kTimecodeDropFrame: + case kTimecodeSeconds: + { + QStringList timecode_split = timecode.split(QRegExp("(:)|(;)")); + + const int element_count = display == kTimecodeSeconds ? 3 : 4; + + // Remove excess tokens (we're only interested in HH:MM:SS.FF) + while (timecode_split.size() > element_count) { + timecode_split.removeLast(); + } + + // For easier index calculations, ensure minimum size + while (timecode_split.size() < element_count) { + timecode_split.prepend(QString()); + } + + bool negative = (timecode.at(0) == '-'); + + double frame_rate = timebase.flipped().toDouble(); + int rounded_frame_rate = std::lround(frame_rate); + + bool valid; + rational time; + + int64_t hours = StrToInt64EmptyTolerant(timecode_split.at(0), &valid); + if (!valid) goto err_fatal; + int64_t mins = StrToInt64EmptyTolerant(timecode_split.at(1), &valid); + if (!valid) goto err_fatal; + + if (display == kTimecodeSeconds) { + double secs = StrToDoubleEmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; + + time = rational::fromDouble(hours * 3600 + mins * 60 + secs); + } else { + int64_t secs = StrToInt64EmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; + int64_t frames = StrToInt64EmptyTolerant(timecode_split.at(3), &valid); + if (!valid) goto err_fatal; + + int64_t sec_count = (hours*3600 + mins*60 + secs); + int64_t frame_count = sec_count*rounded_frame_rate + frames; + + if (display == kTimecodeDropFrame && timebase_is_drop_frame(timebase)) { + + // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int64_t dropFrames = std::llround(frame_rate * (2.0/30.0)); + + // d and m need to be calculated from + int64_t real_fr_ts = std::llround(static_cast(sec_count)*frame_rate) + frames; + + int64_t framesPer10Minutes = std::llround(frame_rate * 600); + int64_t d = real_fr_ts / framesPer10Minutes; + int64_t m = real_fr_ts % framesPer10Minutes; + + if (m > dropFrames) { + frame_count -= dropFrames * ((m - dropFrames) / (std::llround(frame_rate)*60 - dropFrames)); + } + frame_count -= dropFrames*9*d; + } + + time = timestamp_to_time(frame_count, timebase); + } + + if (ok) *ok = true; + + if (negative) time = -time; + + return time; + } + case kMilliseconds: + { + double timecode_secs = timecode.toDouble(ok); + + // Convert milliseconds to seconds + timecode_secs *= 0.001; + + // Convert seconds to rational + return rational::fromDouble(timecode_secs, ok); + } + case kFrames: + return timecode.toLongLong(ok); + } + +err_fatal: + if (ok) *ok = false; + return 0; +} + +QString Timecode::time_to_string(int64_t ms) +{ + int64_t total_seconds = ms / 1000; + int64_t ss = total_seconds % 60; + int64_t mm = (total_seconds / 60) % 60; + int64_t hh = total_seconds / 3600; + + return QStringLiteral("%1:%2:%3").arg(leftpad(hh, 2), + leftpad(mm, 2), + leftpad(ss, 2)); +} + +rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor) +{ + // Just convert to a timestamp in timebase units and back + int64_t timestamp = time_to_timestamp(time, timebase, floor); + + return timestamp_to_time(timestamp, timebase); +} + +rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &timebase) +{ + int64_t num = int64_t(timebase.numerator()) * timestamp; + int64_t den = timebase.denominator(); + + int num_r, den_r; + + av_reduce(&num_r, &den_r, num, den, INT_MAX); + + return rational(num_r, den_r); +} + +bool Timecode::timebase_is_drop_frame(const rational &timebase) +{ + return (timebase.numerator() != 1); +} + +int64_t Timecode::time_to_timestamp(const rational &time, const rational &timebase, Rounding floor) +{ + return time_to_timestamp(time.toDouble(), timebase, floor); +} + +int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase, Rounding floor) +{ + const double d = time * timebase.flipped().toDouble(); + + if (std::isnan(d)) { + return 0; + } + + const double eps = 0.000000000001; + + switch (floor) { + case kRound: + default: + return std::llround(d); + case kFloor: + if (d > std::ceil(d)-eps) { + return std::ceil(d); + } else { + return std::floor(d); + } + case kCeil: + if (d < std::floor(d)+eps) { + return std::floor(d); + } else { + return std::ceil(d); + } + } +} + +int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source, const rational &dest) +{ + if (source == dest) { + return ts; + } + + return av_rescale_q(ts, source.toAVRational(), dest.toAVRational()); +} + +int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &source, const rational &dest) +{ + if (source == dest) { + return ts; + } + + return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(), AV_ROUND_UP); +} + +} diff --git a/lib/olive/src/util/timerange.cpp b/lib/olive/src/util/timerange.cpp new file mode 100644 index 0000000000..c07ba1fdcc --- /dev/null +++ b/lib/olive/src/util/timerange.cpp @@ -0,0 +1,381 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "util/timerange.h" + +#include +#include +#include + +#include "util/timecodefunctions.h" + +namespace olive { + +TimeRange::TimeRange(const rational &in, const rational &out) : + in_(in), + out_(out) +{ + normalize(); +} + +const rational &TimeRange::in() const +{ + return in_; +} + +const rational &TimeRange::out() const +{ + return out_; +} + +const rational &TimeRange::length() const +{ + return length_; +} + +void TimeRange::set_in(const rational &in) +{ + in_ = in; + normalize(); +} + +void TimeRange::set_out(const rational &out) +{ + out_ = out; + normalize(); +} + +void TimeRange::set_range(const rational &in, const rational &out) +{ + in_ = in; + out_ = out; + normalize(); +} + +bool TimeRange::operator==(const TimeRange &r) const +{ + return in() == r.in() && out() == r.out(); +} + +bool TimeRange::operator!=(const TimeRange &r) const +{ + return in() != r.in() || out() != r.out(); +} + +bool TimeRange::OverlapsWith(const TimeRange &a, bool in_inclusive, bool out_inclusive) const +{ + bool doesnt_overlap_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in()); + + bool doesnt_overlap_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out()); + + return !doesnt_overlap_in && !doesnt_overlap_out; +} + +TimeRange TimeRange::Combined(const TimeRange &a) const +{ + return Combine(a, *this); +} + +bool TimeRange::Contains(const TimeRange &compare, bool in_inclusive, bool out_inclusive) const +{ + bool contains_in = (in_inclusive) ? (compare.in() >= in()) : (compare.in() > in()); + + bool contains_out = (out_inclusive) ? (compare.out() <= out()) : (compare.out() < out()); + + return contains_in && contains_out; +} + +bool TimeRange::Contains(const rational &r) const +{ + return r >= in_ && r < out_; +} + +TimeRange TimeRange::Combine(const TimeRange &a, const TimeRange &b) +{ + return TimeRange(std::min(a.in(), b.in()), + std::max(a.out(), b.out())); +} + +TimeRange TimeRange::Intersected(const TimeRange &a) const +{ + return Intersect(a, *this); +} + +TimeRange TimeRange::Intersect(const TimeRange &a, const TimeRange &b) +{ + return TimeRange(std::max(a.in(), b.in()), + std::min(a.out(), b.out())); +} + +TimeRange TimeRange::operator+(const rational &rhs) const +{ + TimeRange answer(*this); + answer += rhs; + return answer; +} + +TimeRange TimeRange::operator-(const rational &rhs) const +{ + TimeRange answer(*this); + answer -= rhs; + return answer; +} + +const TimeRange &TimeRange::operator+=(const rational &rhs) +{ + set_range(in_ + rhs, out_ + rhs); + + return *this; +} + +const TimeRange &TimeRange::operator-=(const rational &rhs) +{ + set_range(in_ - rhs, out_ - rhs); + + return *this; +} + +std::list TimeRange::Split(const int &chunk_size) const +{ + std::list split_ranges; + + int start_time = std::floor(this->in().toDouble() / static_cast(chunk_size)) * chunk_size; + int end_time = std::ceil(this->out().toDouble() / static_cast(chunk_size)) * chunk_size; + + for (int i=start_time; iin(), rational(i)), + std::min(this->out(), rational(i + chunk_size)))); + } + + return split_ranges; +} + +void TimeRange::normalize() +{ + // If `out` is earlier than `in`, swap them + if (out_ < in_) + { + std::swap(out_, in_); + } + + // Calculate length + if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN || in_ == RATIONAL_MAX) { + length_ = rational::NaN; + } else { + length_ = out_ - in_; + } +} + +void TimeRangeList::insert(const TimeRangeList &list_to_add) +{ + for (auto it=list_to_add.cbegin(); it!=list_to_add.cend(); it++) { + insert(*it); + } +} + +void TimeRangeList::insert(TimeRange range_to_add) +{ + // See if list contains this range + if (contains(range_to_add)) { + return; + } + + // Does not contain range, so we'll almost certainly be adding it in some way + for (auto it = array_.begin(); it != array_.end(); ) { + const TimeRange& compare = *it; + + if (compare.OverlapsWith(range_to_add)) { + range_to_add = TimeRange::Combine(range_to_add, compare); + it = array_.erase(it); + } else { + it++; + } + } + + array_.push_back(range_to_add); +} + +void TimeRangeList::remove(const TimeRange &remove) +{ + util_remove(&array_, remove); +} + +void TimeRangeList::remove(const TimeRangeList &list) +{ + for (const TimeRange &r : list) { + remove(r); + } +} + +bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const +{ + for (int i=0;i= range.out()) { + // No intersect + continue; + } else { + // Crop the time range to the range and add it to the list + TimeRange cropped(std::max(range.in(), compare.in()), + std::min(range.out(), compare.out())); + + intersect_list.insert(cropped); + } + } + + return intersect_list; +} + +TimeRangeListFrameIterator::TimeRangeListFrameIterator() : + TimeRangeListFrameIterator(TimeRangeList(), rational::NaN) +{ +} + +TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase) : + list_(list), + timebase_(timebase), + range_index_(-1), + size_(-1), + frame_index_(0), + custom_range_(false) +{ + if (!list_.isEmpty() && timebase_.isNull()) { + std::cerr << "TimeRangeListFrameIterator created with null timebase but non-empty list, this will likely lead to infinite loops" << std::endl; + } + + UpdateIndexIfNecessary(); +} + +rational TimeRangeListFrameIterator::Snap(const rational &r) const +{ + return Timecode::snap_time_to_timebase(r, timebase_, Timecode::kFloor); +} + +bool TimeRangeListFrameIterator::GetNext(rational *out) +{ + if (!HasNext()) { + return false; + } + + // Output current value + *out = current_; + + // Determine next value by adding timebase + current_ += timebase_; + + // If this time is outside the current range, jump to the next one + UpdateIndexIfNecessary(); + + // Increment frame index + frame_index_++; + + return true; +} + +bool TimeRangeListFrameIterator::HasNext() const +{ + return range_index_ < list_.size(); +} + +int TimeRangeListFrameIterator::size() +{ + if (size_ == -1) { + // Size isn't calculated automatically for optimization, so we'll calculate it now + size_ = 0; + + for (const TimeRange &range : list_) { + rational start = Snap(range.in()); + rational end = Timecode::snap_time_to_timebase(range.out(), timebase_, Timecode::kFloor); + + if (end == range.out()) { + end -= timebase_; + } + + int64_t start_ts = Timecode::time_to_timestamp(start, timebase_); + int64_t end_ts = Timecode::time_to_timestamp(end, timebase_); + + size_ += 1 + (end_ts - start_ts); + } + } + + return size_; +} + +void TimeRangeListFrameIterator::UpdateIndexIfNecessary() +{ + while (range_index_ < list_.size() && (range_index_ == -1 || current_ >= list_.at(range_index_).out())) { + range_index_++; + + if (range_index_ < list_.size()) { + current_ = Snap(list_.at(range_index_).in()); + } + } +} + +} diff --git a/tests/general/CMakeLists.txt b/tests/general/CMakeLists.txt index a41a69ea46..ac940576e3 100644 --- a/tests/general/CMakeLists.txt +++ b/tests/general/CMakeLists.txt @@ -15,3 +15,7 @@ # along with this program. If not, see . olive_add_test(General common-tests common-tests.cpp) +olive_add_test(General rational-tests rational-tests.cpp) +olive_add_test(General stringutils-tests stringutils-tests.cpp) +olive_add_test(General timecode-tests timecode-tests.cpp) +olive_add_test(General timerange-tests timerange-tests.cpp) diff --git a/tests/general/rational-test.cpp b/tests/general/rational-test.cpp new file mode 100644 index 0000000000..c67a1a55f5 --- /dev/null +++ b/tests/general/rational-test.cpp @@ -0,0 +1,102 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 + +#include "util/rational.h" +#include "util/tests.h" + +using namespace olive::core; + +bool rational_to_from_string_test() +{ + rational r(1, 30); + + std::string s = r.toString(); + + rational r2 = rational::fromString(s); + + return r == r2; +} + +bool rational_to_from_string_test2() +{ + rational r(69, 420); + + std::string s = r.toString(); + + rational r2 = rational::fromString(s); + + return r == r2; +} + +bool rational_defaults() +{ + // By default, rationals are valid 0/1 + rational basic_constructor; + + if (!basic_constructor.isNull()) { + return false; + } + + if (basic_constructor.isNaN()) { + return false; + } + + return true; +} + +bool rational_nan() +{ + // Create a NaN with a 0 denominator + rational nan = rational(0, 0); + if (!nan.isNaN()) return false; + if (!nan.isNull()) return false; + + // Create a non-NaN with a zero numerator + rational zero_nonnan(0, 999); + if (!zero_nonnan.isNull()) return false; + if (zero_nonnan.isNaN()) return false; + + // Create a non-NaN with a non-zero numerator + rational nonzer_nonnan(1, 30); + if (nonzer_nonnan.isNull()) return false; + if (nonzer_nonnan.isNaN()) return false; + + return true; +} + +bool rational_nan_constant() +{ + return rational::NaN.isNaN(); +} + +int main() +{ + Tester t; + + t.add("rational::defaults", rational_defaults); + t.add("rational::NaN", rational_nan); + t.add("rational::NaN_constant", rational_nan_constant); + t.add("rational::toString/fromString", rational_to_from_string_test); + t.add("rational::toString/fromString2", rational_to_from_string_test2); + + return t.exec(); +} diff --git a/tests/general/stringutils-test.cpp b/tests/general/stringutils-test.cpp new file mode 100644 index 0000000000..fe96c5509e --- /dev/null +++ b/tests/general/stringutils-test.cpp @@ -0,0 +1,46 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 + +#include "util/stringutils.h" +#include "util/tests.h" + +using namespace olive::core; + +bool stringutils_format_test() +{ + const char *expected = "Hello, world!"; + std::string f = StringUtils::format("%s, %s!", "Hello", "world"); + if (strcmp(f.c_str(), expected) != 0) { + return false; + } + + return true; +} + +int main() +{ + Tester t; + + t.add("StringUtils::format", stringutils_format_test); + + return t.exec(); +} diff --git a/tests/general/timecode-test.cpp b/tests/general/timecode-test.cpp new file mode 100644 index 0000000000..29ce5a78d2 --- /dev/null +++ b/tests/general/timecode-test.cpp @@ -0,0 +1,60 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 + +#include "util/timecodefunctions.h" +#include "util/tests.h" + +using namespace olive::core; + +bool timecodefunctions_time_to_timecode_test() +{ + rational drop_frame_30(1001, 30000); + + std::string timecode = Timecode::time_to_timecode(rational(1), drop_frame_30, Timecode::kTimecodeDropFrame); + if (strcmp(timecode.c_str(), "00:00:01;00") != 0) { + return false; + } + + return true; +} + +bool timecodefunctions_time_to_timecode_test2() +{ + rational bizarre_timebase(156632219); + + std::string timecode = Timecode::time_to_timecode(rational(0), bizarre_timebase, Timecode::kTimecodeDropFrame); + if (strcmp(timecode.c_str(), "INVALID TIMEBASE") != 0) { + return false; + } + + return true; +} + +int main() +{ + Tester t; + + t.add("Timecode::time_to_timecode", timecodefunctions_time_to_timecode_test); + t.add("Timecode::time_to_timecode2", timecodefunctions_time_to_timecode_test2); + + return t.exec(); +} diff --git a/tests/general/timerange-test.cpp b/tests/general/timerange-test.cpp new file mode 100644 index 0000000000..fc13277ec0 --- /dev/null +++ b/tests/general/timerange-test.cpp @@ -0,0 +1,95 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 + +#include "util/timerange.h" +#include "util/tests.h" + +using namespace olive::core; + +bool timerangelist_remove_test() +{ + TimeRangeList t; + + t.insert(TimeRange(0, 30)); + t.remove(TimeRange(2, 5)); + + return true; +} + +bool timerangelist_mergeadjacent_test() +{ + TimeRangeList t; + + // TimeRangeList should merge 1 and 3 together since they're adjacent + t.insert(TimeRange(0, 6)); + t.insert(TimeRange(20, 30)); + t.insert(TimeRange(6, 10)); + + if (!(t.size() == 2)) { return false; } + if (!(t.first() == TimeRange(20, 30))) { return false; } + if (!(t.at(1) == TimeRange(0, 10))) { return false; } + + // TimeRangeList should ignore these because it's already contained + TimeRangeList noop_test = t; + + noop_test.insert(TimeRange(4, 7)); + if (!(noop_test == t)) { return false; } + + noop_test.insert(TimeRange(0, 3)); + if (!(noop_test == t)) { return false; } + + noop_test.insert(TimeRange(25, 30)); + if (!(noop_test == t)) { return false; } + + // TimeRangeList should combine all these together + TimeRangeList combine_test_no_overlap = t; + combine_test_no_overlap.insert(TimeRange(10, 20)); + if (!(combine_test_no_overlap.size() == 1)) { return false; } + if (!(combine_test_no_overlap.first() == TimeRange(0, 30))) { return false; } + + TimeRangeList combine_test_in_overlap = t; + combine_test_in_overlap.insert(TimeRange(9, 20)); + if (!(combine_test_in_overlap.size() == 1)) { return false; } + if (!(combine_test_in_overlap.first() == TimeRange(0, 30))) { return false; } + + TimeRangeList combine_test_out_overlap = t; + combine_test_out_overlap.insert(TimeRange(10, 21)); + if (!(combine_test_out_overlap.size() == 1)) { return false; } + if (!(combine_test_out_overlap.first() == TimeRange(0, 30))) { return false; } + + TimeRangeList combine_test_both_overlap = t; + combine_test_both_overlap.insert(TimeRange(9, 21)); + if (!(combine_test_both_overlap.size() == 1)) { return false; } + if (!(combine_test_both_overlap.first() == TimeRange(0, 30))) { return false; } + + return true; +} + +int main() +{ + Tester t; + + t.add("TimeRangeList::remove", timerangelist_remove_test); + t.add("TimeRangeList::merge_adjacent", timerangelist_mergeadjacent_test); + + return t.exec(); +} diff --git a/tests/timeline/timeline-tests.cpp b/tests/timeline/timeline-tests.cpp index 6cb72c387a..8c278ce878 100644 --- a/tests/timeline/timeline-tests.cpp +++ b/tests/timeline/timeline-tests.cpp @@ -459,10 +459,10 @@ OLIVE_ADD_TEST(ReplaceBlockWithGap_ClipsAndTransitions) b_out->setParent(&project); track->AppendBlock(b_out); - Node::ConnectEdge(a, NodeInput(a_in, UsingTransition::kInBlockInput)); - Node::ConnectEdge(a, NodeInput(a_to_b, UsingTransition::kOutBlockInput)); - Node::ConnectEdge(b, NodeInput(a_to_b, UsingTransition::kInBlockInput)); - Node::ConnectEdge(b, NodeInput(b_out, UsingTransition::kOutBlockInput)); + Node::ConnectEdge(NodeOutput(a), NodeInput(a_in, UsingTransition::kInBlockInput)); + Node::ConnectEdge(NodeOutput(a), NodeInput(a_to_b, UsingTransition::kOutBlockInput)); + Node::ConnectEdge(NodeOutput(b), NodeInput(a_to_b, UsingTransition::kInBlockInput)); + Node::ConnectEdge(NodeOutput(b), NodeInput(b_out, UsingTransition::kOutBlockInput)); { // Replace A with gap