From 0bef1160209c6974039c8c7fed4ae9e7b6fe3eca Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 26 Feb 2020 11:35:44 +1100 Subject: [PATCH 01/26] oiiodecoder: set correct image stream Fixes bug where loading a saved project with still images could crash. --- app/codec/oiio/oiiodecoder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 837946af0..271f2a688 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -83,6 +83,9 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) image_stream->set_width(spec.width); image_stream->set_height(spec.height); + // Images will always have just one stream + image_stream->set_index(0); + // OIIO automatically premultiplies alpha // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this likely reduces the // fidelity? From 9585786c4ba9d41f5f23e0b39988bf0da899071c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 26 Feb 2020 14:07:47 +1100 Subject: [PATCH 02/26] exporter: removed unused and problematic function This array this code made was never actually used and in fact the code was potentially crash-prone. --- app/render/backend/exporter.cpp | 26 -------------------------- app/render/backend/exporter.h | 2 -- 2 files changed, 28 deletions(-) diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index cabaca780..cb064a1cd 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -236,32 +236,6 @@ void Exporter::VideoHashesComplete() TimeRangeList ranges; ranges.append(TimeRange(0, viewer_node_->Length())); - QMap::const_iterator i; - QMap::iterator j; - - // Copy time hash map - QMap time_hash_map = video_backend_->frame_cache()->time_hash_map(); - - // Check for any times that share duplicate hashes - for (i=time_hash_map.begin();i!=time_hash_map.end();i++) { - j = time_hash_map.begin(); - - while (j != time_hash_map.end()) { - if (i != j && i.value() == j.value()) { - // Remove the time range from this and - ranges.RemoveTimeRange(TimeRange(j.key(), j.key() + video_backend_->params().time_base())); - - QList times_with_this_hash = matched_frames_.value(i.value()); - times_with_this_hash.append(j.key()); - matched_frames_.insert(i.value(), times_with_this_hash); - - j = time_hash_map.erase(j); - } else { - j++; - } - } - } - // Set video backend to render mode but NOT hash or download video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly); video_backend_->SetOnlySignalLastFrameRequested(false); diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h index f5b111372..490a775f0 100644 --- a/app/render/backend/exporter.h +++ b/app/render/backend/exporter.h @@ -73,8 +73,6 @@ private: QHash cached_frames_; - QHash< QByteArray, QList > matched_frames_; - private slots: void FrameRendered(const rational &time, FramePtr value); From d7cde3b22e90aee3a7b13f0667adf432141886f7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 26 Feb 2020 14:36:51 +1100 Subject: [PATCH 03/26] timelinewidget: clear block items before destruction Prevents block items signalling anything after the TimelineWidget has already been destroyed. --- app/widget/timelinewidget/timelinewidget.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index fface9274..f8cb8c398 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -122,6 +122,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) : TimelineWidget::~TimelineWidget() { + // Ensure no blocks are selected before any child widgets are destroyed (prevents corrupt ViewSelectionChanged() signal) + Clear(); + qDeleteAll(tools_); } @@ -129,14 +132,14 @@ void TimelineWidget::Clear() { SetTimebase(0); - QMapIterator iterator(block_items_); + QMap::iterator iterator = block_items_.begin(); - while (iterator.hasNext()) { - iterator.next(); + while (iterator != block_items_.end()) { + TimelineViewBlockItem* item = iterator.value(); - if (iterator.value() != nullptr) { - delete iterator.value(); - } + iterator = block_items_.erase(iterator); + + delete item; } block_items_.clear(); From 428cfc6210cc52dd8fb4e5c4380e9dbf7f359dc6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 00:58:19 +1100 Subject: [PATCH 04/26] exportdialog: use sequence name and project directory to auto-select export filename --- app/dialog/export/export.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index c6b8a4582..055c14565 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -414,8 +414,18 @@ void ExportDialog::LoadPresets() void ExportDialog::SetDefaultFilename() { - QString doc_location = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QString file_location = QDir(doc_location).filePath("export"); + Sequence* s = static_cast(viewer_node_->parent()); + Project* p = s->project(); + + QDir doc_location; + + if (p->filename().isEmpty()) { + doc_location.setPath(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + } else { + doc_location = QFileInfo(p->filename()).dir(); + } + + QString file_location = doc_location.filePath(s->name()); filename_edit_->setText(file_location); } From 0fbffc9b8f97167d39f2ee4ef4cf8b99efcb7ee3 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 01:05:22 +1100 Subject: [PATCH 05/26] ffmpegdecoder: mild code cleanup --- app/codec/ffmpeg/ffmpegdecoder.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 48f0f9649..dec199a32 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -36,6 +36,7 @@ extern "C" { #include "codec/waveinput.h" #include "common/define.h" #include "common/filefunctions.h" +#include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "ffmpegcommon.h" #include "render/diskmanager.h" @@ -230,18 +231,21 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) // See if our RAM cache already has a frame that matches this timestamp if (!cached_frames_.isEmpty()) { - if (cache_at_zero_ && target_ts < cached_frames_.first()->native_timestamp()) { + if (target_ts < cached_frames_.first()->native_timestamp()) { - return_frame = cached_frames_.first(); - cached_frames_.accessedFirst(); + if (cache_at_zero_) { + return_frame = cached_frames_.first(); + cached_frames_.accessedFirst(); + } - } else if (cache_at_eof_ && target_ts > cached_frames_.last()->native_timestamp()) { + } else if (target_ts > cached_frames_.last()->native_timestamp()) { - return_frame = cached_frames_.last(); - cached_frames_.accessedLast(); + if (cache_at_eof_) { + return_frame = cached_frames_.last(); + cached_frames_.accessedLast(); + } - } else if (target_ts >= cached_frames_.first()->native_timestamp() - && target_ts <= cached_frames_.last()->native_timestamp()) { + } else { // We already have this frame in the cache, find it for (int i=0;i Date: Thu, 27 Feb 2020 11:29:25 +1100 Subject: [PATCH 06/26] projectexplorer: re-implemented reveal in explorer function --- .../projectexplorer/projectexplorer.cpp | 47 ++++++++++++++++++- app/widget/projectexplorer/projectexplorer.h | 2 + 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 1d070dfb2..f2baebebb 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -21,7 +21,11 @@ #include "projectexplorer.h" #include +#include +#include #include +#include +#include #include #include "common/define.h" @@ -242,9 +246,25 @@ void ProjectExplorer::ShowContextMenu() QAction* project_properties = menu.addAction(tr("&Project Properties...")); connect(project_properties, &QAction::triggered, Core::instance(), &Core::DialogProjectPropertiesShow); } else { + if (selected_items.first()->type() == Item::kFootage) { + QString reveal_text; + +#ifdef Q_OS_WINDOWS + reveal_text = tr("Reveal in Explorer"); +#elif Q_OS_MAC + reveal_text = tr("Reveal in Finder"); +#else + reveal_text = tr("Reveal in File Manager"); + #endif + + QAction* reveal_action = menu.addAction(reveal_text); + connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage); + + menu.addSeparator(); + } + QAction* properties_action = menu.addAction(tr("P&roperties")); - // FIXME: Support for multiple items if (selected_items.first()->type() == Item::kFootage) { connect(properties_action, &QAction::triggered, this, &ProjectExplorer::ShowFootagePropertiesDialog); } else if (selected_items.first()->type() == Item::kSequence) { @@ -269,6 +289,31 @@ void ProjectExplorer::ShowSequencePropertiesDialog() sd.exec(); } +void ProjectExplorer::RevealSelectedFootage() +{ + Footage* footage = static_cast(SelectedItems().first()); + +#ifdef Q_OS_WINDOWS + // Explorer + QStringList args; + args << "/select," << QDir::toNativeSeparators(footage->filename()); + QProcess::startDetached("explorer", args); +#elif Q_OS_MAC + QStringList args; + args << "-e"; + args << "tell application \"Finder\""; + args << "-e"; + args << "activate"; + args << "-e"; + args << "select POSIX file \""+footage->filename()+"\""; + args << "-e"; + args << "end tell"; + QProcess::startDetached("osascript", args); +#else + QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(footage->filename()).dir().absolutePath())); +#endif +} + Project *ProjectExplorer::project() { return model_.project(); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 65cf68335..910ebfd19 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -155,6 +155,8 @@ private slots: void ShowSequencePropertiesDialog(); + void RevealSelectedFootage(); + }; #endif // PROJECTEXPLORER_H From 02ce48d0c61c0f03b4bb0d71d0dc2a4ec5e2a908 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 13:45:44 +1100 Subject: [PATCH 07/26] various: pixel optimizations Several things are accomplished in this commit, including: - Use OIIO instead of our own functions for pixel format conversions (cleaner code/less for us to maintain) - Fold all PixelService functions into the PixelFormat class (cleaner code) - Moved OpenGL pixel definitions to OpenGL classes and out of the global classes. - Add support for RGB buffers as well as RGBA (optimization) --- app/codec/ffmpeg/ffmpegcommon.cpp | 14 + app/codec/ffmpeg/ffmpegdecoder.cpp | 19 +- app/codec/ffmpeg/ffmpegencoder.cpp | 6 +- app/codec/frame.cpp | 4 +- app/codec/oiio/oiiodecoder.cpp | 22 +- app/codec/oiio/oiiodecoder.h | 4 +- app/core.cpp | 6 +- app/dialog/export/export.cpp | 4 +- .../tabs/preferencesqualitytab.cpp | 19 +- app/node/input/media/video/video.cpp | 2 +- app/render/CMakeLists.txt | 2 - app/render/backend/exporter.cpp | 4 +- app/render/backend/opengl/openglproxy.cpp | 10 +- .../backend/opengl/openglrenderfunctions.cpp | 61 +++ .../backend/opengl/openglrenderfunctions.h | 7 + app/render/backend/opengl/opengltexture.cpp | 17 +- app/render/backend/opengl/openglworker.cpp | 2 +- app/render/backend/videorenderbackend.cpp | 2 +- app/render/backend/videorenderworker.cpp | 15 +- app/render/colormanager.cpp | 9 + app/render/colorprocessor.cpp | 2 +- app/render/pixelformat.cpp | 234 +++++++++++ app/render/pixelformat.h | 106 ++++- app/render/pixelservice.cpp | 366 ------------------ app/render/pixelservice.h | 106 ----- app/widget/viewer/viewer.cpp | 6 +- app/widget/viewer/viewerglwidget.cpp | 6 +- 27 files changed, 491 insertions(+), 564 deletions(-) delete mode 100644 app/render/pixelservice.cpp delete mode 100644 app/render/pixelservice.h diff --git a/app/codec/ffmpeg/ffmpegcommon.cpp b/app/codec/ffmpeg/ffmpegcommon.cpp index d1236816a..7d560d28c 100644 --- a/app/codec/ffmpeg/ffmpegcommon.cpp +++ b/app/codec/ffmpeg/ffmpegcommon.cpp @@ -3,7 +3,9 @@ AVPixelFormat FFmpegCommon::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt) { AVPixelFormat possible_pix_fmts[] = { + AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64, AV_PIX_FMT_NONE }; @@ -73,8 +75,14 @@ AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_ return AV_PIX_FMT_RGBA; case PixelFormat::PIX_FMT_RGBA16U: return AV_PIX_FMT_RGBA64; + case PixelFormat::PIX_FMT_RGB8: + return AV_PIX_FMT_RGB24; + case PixelFormat::PIX_FMT_RGB16U: + return AV_PIX_FMT_RGB48; case PixelFormat::PIX_FMT_RGBA16F: case PixelFormat::PIX_FMT_RGBA32F: + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGB32F: case PixelFormat::PIX_FMT_INVALID: case PixelFormat::PIX_FMT_COUNT: break; @@ -86,8 +94,14 @@ AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_ PixelFormat::Format FFmpegCommon::GetCompatiblePixelFormat(const PixelFormat::Format &pix_fmt) { switch (pix_fmt) { + case PixelFormat::PIX_FMT_RGB8: + return PixelFormat::PIX_FMT_RGB8; case PixelFormat::PIX_FMT_RGBA8: return PixelFormat::PIX_FMT_RGBA8; + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGB32F: + return PixelFormat::PIX_FMT_RGB16U; case PixelFormat::PIX_FMT_RGBA16U: case PixelFormat::PIX_FMT_RGBA16F: case PixelFormat::PIX_FMT_RGBA32F: diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index dec199a32..149e353fe 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -40,7 +40,7 @@ extern "C" { #include "common/timecodefunctions.h" #include "ffmpegcommon.h" #include "render/diskmanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" FFmpegDecoder::FFmpegDecoder() : fmt_ctx_(nullptr), @@ -154,11 +154,20 @@ bool FFmpegDecoder::Open() // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats - if (ideal_pix_fmt_ == AV_PIX_FMT_RGBA) { + switch (ideal_pix_fmt_) { + case AV_PIX_FMT_RGB24: + native_pix_fmt_ = PixelFormat::PIX_FMT_RGB8; + break; + case AV_PIX_FMT_RGBA: native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA8; - } else if (ideal_pix_fmt_ == AV_PIX_FMT_RGBA64) { + break; + case AV_PIX_FMT_RGB48: + native_pix_fmt_ = PixelFormat::PIX_FMT_RGB16U; + break; + case AV_PIX_FMT_RGBA64: native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA16U; - } else { + break; + default: // We should never get here, but just in case... qFatal("Invalid output format"); } @@ -378,7 +387,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) // Convert frame to RGBA for the rest of the pipeline uint8_t* output_data = reinterpret_cast(working_frame_converted->data()); - int output_linesize = working_frame_converted->width() * kRGBAChannels * PixelService::BytesPerChannel(native_pix_fmt_); + int output_linesize = working_frame_converted->width() * PixelFormat::ChannelCount(native_pix_fmt_) * PixelFormat::BytesPerChannel(native_pix_fmt_); sws_scale(scale_ctx_, working_frame->data, diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 6c0d6410d..ac3556f03 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -3,7 +3,7 @@ #include #include "ffmpegcommon.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) : Encoder(params), @@ -188,12 +188,12 @@ void FFmpegEncoder::WriteInternal(FramePtr frame) // We may need to convert this frame to a frame that swscale will understand if (frame->format() != video_conversion_fmt_) { - frame = PixelService::ConvertPixelFormat(frame, video_conversion_fmt_); + frame = PixelFormat::ConvertPixelFormat(frame, video_conversion_fmt_); } // Use swscale context to convert formats/linesizes input_data = frame->const_data(); - input_linesize = frame->width() * PixelService::BytesPerPixel(video_conversion_fmt_); + input_linesize = frame->width() * PixelFormat::BytesPerPixel(video_conversion_fmt_); error_code = sws_scale(video_scale_ctx_, reinterpret_cast(&input_data), &input_linesize, diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index 0cdf5f144..07fc08012 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -23,8 +23,6 @@ #include #include -#include "render/pixelservice.h" - Frame::Frame() : width_(0), height_(0), @@ -139,7 +137,7 @@ void Frame::allocate() { // Assume this frame is intended to be a video frame if (width_ > 0 && height_ > 0) { - data_.resize(PixelService::GetBufferSize(static_cast(format_), width_, height_)); + data_.resize(PixelFormat::GetBufferSize(static_cast(format_), width_, height_)); } else if (sample_count_ > 0) { data_.resize(audio_params_.samples_to_bytes(sample_count_)); } diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 271f2a688..4aca39e6b 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -115,25 +115,24 @@ bool OIIODecoder::Open() width_ = spec.width; height_ = spec.height; + is_rgba_ = (spec.nchannels == kRGBAChannels); + // Weirdly, switch statement doesn't work correctly here if (spec.format == OIIO::TypeDesc::UINT8) { - pix_fmt_ = PixelFormat::PIX_FMT_RGBA8; + pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; } else if (spec.format == OIIO::TypeDesc::UINT16) { - pix_fmt_ = PixelFormat::PIX_FMT_RGBA16U; + pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; } else if (spec.format == OIIO::TypeDesc::HALF) { - pix_fmt_ = PixelFormat::PIX_FMT_RGBA16F; + pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; } else if (spec.format == OIIO::TypeDesc::FLOAT) { - pix_fmt_ = PixelFormat::PIX_FMT_RGBA32F; + pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; } else { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; return false; } // FIXME: Many OIIO pixel formats are not handled here - - is_rgba_ = (spec.nchannels == kRGBAChannels); - - pix_fmt_info_ = PixelService::GetPixelFormatInfo(static_cast(pix_fmt_)); + type_ = PixelFormat::GetOIIOTypeDesc(pix_fmt_); open_ = true; @@ -170,12 +169,7 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode) frame_->allocate(); // Use the native format to determine what format OIIO should return - // FIXME: Behavior of RGB images as opposed to RGBA? - image_->read_image(pix_fmt_info_.oiio_desc, frame_->data()); - - if (!is_rgba_) { - PixelService::ConvertRGBtoRGBA(frame_); - } + image_->read_image(type_, frame_->data()); } return frame_; diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index fbbc7e8b5..92f1ea4c4 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -24,7 +24,7 @@ #include #include "codec/decoder.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" class OIIODecoder : public Decoder { @@ -57,7 +57,7 @@ private: PixelFormat::Format pix_fmt_; - PixelFormat::Info pix_fmt_info_; + OIIO::TypeDesc type_; bool is_rgba_; diff --git a/app/core.cpp b/app/core.cpp index df478bb3e..ac88b58a1 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -48,7 +48,7 @@ #include "render/backend/opengl/opengltexturecache.h" #include "render/colormanager.h" #include "render/diskmanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" #include "task/taskmanager.h" #include "ui/style/style.h" #include "undo/undostack.h" @@ -145,7 +145,7 @@ void Core::Stop() DiskManager::DestroyInstance(); - PixelService::DestroyInstance(); + PixelFormat::DestroyInstance(); NodeFactory::Destroy(); @@ -385,7 +385,7 @@ void Core::StartGUI(bool full_screen) TaskManager::CreateInstance(); // Initialize pixel service - PixelService::CreateInstance(); + PixelFormat::CreateInstance(); // Connect the PanelFocusManager to the application's focus change signal connect(qApp, diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 055c14565..1c8f62a8a 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -12,7 +12,7 @@ #include "project/item/sequence/sequence.h" #include "project/project.h" #include "render/backend/exporter.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" #include "ui/icons/icons.h" ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : @@ -210,7 +210,7 @@ void ExportDialog::accept() VideoRenderingParams video_render_params(dest_width, dest_height, video_tab_->frame_rate().flipped(), - PixelService::instance()->GetConfiguredFormatForMode(render_mode), + PixelFormat::instance()->GetConfiguredFormatForMode(render_mode), render_mode); AudioRenderingParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(), diff --git a/app/dialog/preferences/tabs/preferencesqualitytab.cpp b/app/dialog/preferences/tabs/preferencesqualitytab.cpp index 1f0432f3c..357444335 100644 --- a/app/dialog/preferences/tabs/preferencesqualitytab.cpp +++ b/app/dialog/preferences/tabs/preferencesqualitytab.cpp @@ -5,7 +5,7 @@ #include #include "render/colormanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" PreferencesQualityTab::PreferencesQualityTab() { @@ -26,13 +26,13 @@ PreferencesQualityTab::PreferencesQualityTab() quality_stack_ = new QStackedWidget(); offline_group_ = new PreferencesQualityGroup(tr("Offline Quality")); - offline_group_->bit_depth_combobox()->setCurrentIndex(PixelService::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); + offline_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); offline_group_->sample_fmt_combobox()->setCurrentIndex(SampleFormat::GetConfiguredFormatForMode(RenderMode::kOffline)); offline_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOffline)); quality_stack_->addWidget(offline_group_); online_group_ = new PreferencesQualityGroup(tr("Online Quality")); - online_group_->bit_depth_combobox()->setCurrentIndex(PixelService::instance()->GetConfiguredFormatForMode(RenderMode::kOnline)); + online_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline)); online_group_->sample_fmt_combobox()->setCurrentIndex(SampleFormat::GetConfiguredFormatForMode(RenderMode::kOnline)); online_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOnline)); quality_stack_->addWidget(online_group_); @@ -46,8 +46,8 @@ void PreferencesQualityTab::Accept() { ColorManager::SetOCIOMethodForMode(RenderMode::kOffline, static_cast(offline_group_->ocio_method()->currentIndex())); ColorManager::SetOCIOMethodForMode(RenderMode::kOnline, static_cast(online_group_->ocio_method()->currentIndex())); - PixelService::instance()->SetConfiguredFormatForMode(RenderMode::kOffline, static_cast(offline_group_->bit_depth_combobox()->currentData().toInt())); - PixelService::instance()->SetConfiguredFormatForMode(RenderMode::kOnline, static_cast(online_group_->bit_depth_combobox()->currentData().toInt())); + PixelFormat::instance()->SetConfiguredFormatForMode(RenderMode::kOffline, static_cast(offline_group_->bit_depth_combobox()->currentData().toInt())); + PixelFormat::instance()->SetConfiguredFormatForMode(RenderMode::kOnline, static_cast(online_group_->bit_depth_combobox()->currentData().toInt())); SampleFormat::SetConfiguredFormatForMode(RenderMode::kOffline, static_cast(offline_group_->sample_fmt_combobox()->currentData().toInt())); SampleFormat::SetConfiguredFormatForMode(RenderMode::kOnline, static_cast(online_group_->sample_fmt_combobox()->currentData().toInt())); } @@ -70,8 +70,13 @@ PreferencesQualityGroup::PreferencesQualityGroup(const QString &title, QWidget * // Populate with bit depths for (int i=0;iaddItem(PixelService::GetPixelFormatInfo(static_cast(i)).name, - i); + PixelFormat::Format pix_fmt = static_cast(i); + + // We always render with an alpha channel internally + if (PixelFormat::FormatHasAlphaChannel(pix_fmt)) { + bit_depth_combobox_->addItem(PixelFormat::GetName(pix_fmt), + i); + } } video_layout->addWidget(bit_depth_combobox_, row, 1); diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp index abc5bf9bd..9135a216d 100644 --- a/app/node/input/media/video/video.cpp +++ b/app/node/input/media/video/video.cpp @@ -7,7 +7,7 @@ #include "codec/ffmpeg/ffmpegdecoder.h" #include "core.h" #include "project/item/footage/footage.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" VideoInput::VideoInput() { diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 99225da03..600d8a1e3 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -28,8 +28,6 @@ set(OLIVE_SOURCES render/diskmanager.cpp render/pixelformat.h render/pixelformat.cpp - render/pixelservice.h - render/pixelservice.cpp render/rendermodes.h render/videoparams.h render/videoparams.cpp diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index cb064a1cd..8a22e65d3 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -3,7 +3,7 @@ #include "render/backend/audio/audiobackend.h" #include "render/backend/opengl/openglbackend.h" #include "render/colormanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" Exporter::Exporter(ViewerOutput* viewer, Encoder *encoder, @@ -121,7 +121,7 @@ void Exporter::EncodeFrame() // OCIO conversion requires a frame in 32F format if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) { - frame = PixelService::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); } // Color conversion must be done with unassociated alpha, and the pipeline is always associated diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index de72d1ee8..7aad9279f 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -9,7 +9,7 @@ #include "openglcolorprocessor.h" #include "openglrenderfunctions.h" #include "render/colormanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" OpenGLProxy::OpenGLProxy(QObject *parent) : QObject(parent), @@ -90,7 +90,7 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR } // Convert frame to float for OCIO - frame = PixelService::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); // Perform color transform color_processor->ConvertFrame(frame); @@ -384,8 +384,6 @@ void OpenGLProxy::TextureToBuffer(const QVariant &tex_in, void *buffer) { OpenGLTextureCache::ReferencePtr texture = tex_in.value(); - PixelFormat::Info format_info = PixelService::GetPixelFormatInfo(video_params_.format()); - QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions(); buffer_.Attach(texture->texture()); buffer_.Bind(); @@ -394,8 +392,8 @@ void OpenGLProxy::TextureToBuffer(const QVariant &tex_in, void *buffer) 0, video_params_.effective_width(), video_params_.effective_height(), - format_info.pixel_format, - format_info.gl_pixel_type, + OpenGLRenderFunctions::GetPixelFormat(video_params_.format()), + OpenGLRenderFunctions::GetPixelType(video_params_.format()), buffer); buffer_.Release(); diff --git a/app/render/backend/opengl/openglrenderfunctions.cpp b/app/render/backend/opengl/openglrenderfunctions.cpp index 00c8a32af..c315eb8e9 100644 --- a/app/render/backend/opengl/openglrenderfunctions.cpp +++ b/app/render/backend/opengl/openglrenderfunctions.cpp @@ -71,6 +71,67 @@ void OpenGLRenderFunctions::PrepareToDraw(QOpenGLFunctions* f) { f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } +GLint OpenGLRenderFunctions::GetInternalFormat(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + return GL_RGB8; + case PixelFormat::PIX_FMT_RGBA8: + return GL_RGBA8; + case PixelFormat::PIX_FMT_RGB16U: + return GL_RGB16; + case PixelFormat::PIX_FMT_RGBA16U: + return GL_RGBA16; + case PixelFormat::PIX_FMT_RGB16F: + return GL_RGB16F; + case PixelFormat::PIX_FMT_RGBA16F: + return GL_RGBA16F; + case PixelFormat::PIX_FMT_RGB32F: + return GL_RGB32F; + case PixelFormat::PIX_FMT_RGBA32F: + return GL_RGBA32F; + + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + return GL_INVALID_VALUE; +} + +GLenum OpenGLRenderFunctions::GetPixelFormat(const PixelFormat::Format &format) +{ + if (PixelFormat::FormatHasAlphaChannel(format)) { + return GL_RGBA; + } else { + return GL_RGB; + } +} + +GLenum OpenGLRenderFunctions::GetPixelType(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + return GL_UNSIGNED_BYTE; + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + return GL_UNSIGNED_SHORT; + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + return GL_HALF_FLOAT; + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + return GL_FLOAT; + + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + return GL_INVALID_VALUE; +} + void OpenGLRenderFunctions::Blit(OpenGLShaderPtr pipeline, bool flipped, QMatrix4x4 matrix) { // FIXME: is currentContext() reliable here? QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions(); diff --git a/app/render/backend/opengl/openglrenderfunctions.h b/app/render/backend/opengl/openglrenderfunctions.h index 7540836da..22ff0c1ca 100644 --- a/app/render/backend/opengl/openglrenderfunctions.h +++ b/app/render/backend/opengl/openglrenderfunctions.h @@ -26,6 +26,7 @@ #include #include "openglshader.h" +#include "render/pixelformat.h" class OpenGLRenderFunctions { public: @@ -50,6 +51,12 @@ public: static void PrepareToDraw(QOpenGLFunctions* f); + static GLint GetInternalFormat(const PixelFormat::Format& format); + + static GLenum GetPixelFormat(const PixelFormat::Format& format); + + static GLenum GetPixelType(const PixelFormat::Format& format); + }; #endif // OPENGLFUNCTIONS_H diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp index 03ebbfdb0..6088153c9 100644 --- a/app/render/backend/opengl/opengltexture.cpp +++ b/app/render/backend/opengl/opengltexture.cpp @@ -23,7 +23,8 @@ #include #include -#include "render/pixelservice.h" +#include "openglrenderfunctions.h" +#include "render/pixelformat.h" OpenGLTexture::OpenGLTexture() : created_ctx_(nullptr), @@ -120,16 +121,14 @@ void OpenGLTexture::Upload(const void *data) Bind(); - PixelFormat::Info info = PixelService::GetPixelFormatInfo(format_); - created_ctx_->functions()->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, - info.pixel_format, - info.gl_pixel_type, + OpenGLRenderFunctions::GetPixelFormat(format_), + OpenGLRenderFunctions::GetPixelType(format_), data); Release(); @@ -152,16 +151,14 @@ void OpenGLTexture::CreateInternal(QOpenGLContext* create_ctx, GLuint* tex, cons f->glBindTexture(GL_TEXTURE_2D, *tex); // Allocate storage for texture - const PixelFormat::Info& bit_depth = PixelService::GetPixelFormatInfo(format_); - f->glTexImage2D(GL_TEXTURE_2D, 0, - bit_depth.internal_format, + OpenGLRenderFunctions::GetInternalFormat(format_), width_, height_, 0, - bit_depth.pixel_format, - bit_depth.gl_pixel_type, + OpenGLRenderFunctions::GetPixelFormat(format_), + OpenGLRenderFunctions::GetPixelType(format_), data); // Set texture filtering to bilinear diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index b3d364628..8d5bed4b1 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -7,7 +7,7 @@ #include "openglcolorprocessor.h" #include "openglrenderfunctions.h" #include "render/colormanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, DecoderCache* decoder_cache, QObject *parent) : VideoRenderWorker(frame_cache, decoder_cache, parent) diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 0ebf86f22..263069c78 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -31,7 +31,7 @@ #include "config/config.h" #include "render/diskmanager.h" #include "render/diskmanager.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" #include "videorenderworker.h" VideoRenderBackend::VideoRenderBackend(QObject *parent) : diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 951f2926f..b7d386deb 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -1,17 +1,17 @@ #include "videorenderworker.h" #include "common/define.h" +#include "common/functiontimer.h" #include "node/block/transition/transition.h" #include "node/node.h" #include "project/project.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, DecoderCache* decoder_cache, QObject *parent) : RenderWorker(decoder_cache, parent), frame_cache_(frame_cache), operating_mode_(kHashRenderCache) { - } const VideoRenderingParams &VideoRenderWorker::video_params() @@ -225,10 +225,11 @@ void VideoRenderWorker::CloseInternal() void VideoRenderWorker::Download(const rational& time, QVariant texture, QString filename) { - PixelFormat::Info format_info = PixelService::GetPixelFormatInfo(video_params().format()); - // Set up OIIO::ImageSpec for compressing cached images on disk - OIIO::ImageSpec spec(video_params().effective_width(), video_params().effective_height(), kRGBAChannels, format_info.oiio_desc); + OIIO::ImageSpec spec(video_params().effective_width(), + video_params().effective_height(), + PixelFormat::ChannelCount(video_params().format()), + PixelFormat::GetOIIOTypeDesc(video_params().format())); if (video_params_.format() != PixelFormat::PIX_FMT_RGBA8 && video_params_.format() != PixelFormat::PIX_FMT_RGBA16U) { @@ -249,7 +250,7 @@ void VideoRenderWorker::Download(const rational& time, QVariant texture, QString if (out) { out->open(working_fn_std, spec); - out->write_image(format_info.oiio_desc, download_buffer_.data()); + out->write_image(PixelFormat::GetOIIOTypeDesc(video_params().format()), download_buffer_.data()); out->close(); #if OIIO_VERSION < 10903 @@ -276,7 +277,7 @@ void VideoRenderWorker::Download(const rational& time, QVariant texture, QString void VideoRenderWorker::ResizeDownloadBuffer() { - download_buffer_.resize(PixelService::GetBufferSize(video_params_.format(), video_params_.effective_width(), video_params_.effective_height())); + download_buffer_.resize(PixelFormat::GetBufferSize(video_params_.format(), video_params_.effective_width(), video_params_.effective_height())); } NodeValueTable VideoRenderWorker::RenderBlock(const TrackOutput *track, const TimeRange &range) diff --git a/app/render/colormanager.cpp b/app/render/colormanager.cpp index 1ed688b9c..b9f66ba0d 100644 --- a/app/render/colormanager.cpp +++ b/app/render/colormanager.cpp @@ -123,6 +123,11 @@ void ColorManager::SetOCIOMethodForMode(RenderMode::Mode mode, ColorManager::OCI void ColorManager::AssociateAlphaPixFmtFilter(ColorManager::AlphaAction action, FramePtr f) { + if (!PixelFormat::FormatHasAlphaChannel(f->format())) { + // This frame has no alpha channel, do nothing + return; + } + int pixel_count = f->width() * f->height() * kRGBAChannels; switch (static_cast(f->format())) { @@ -130,15 +135,19 @@ void ColorManager::AssociateAlphaPixFmtFilter(ColorManager::AlphaAction action, case PixelFormat::PIX_FMT_COUNT: qWarning() << "Alpha association functions received an invalid pixel format"; break; + case PixelFormat::PIX_FMT_RGB8: case PixelFormat::PIX_FMT_RGBA8: + case PixelFormat::PIX_FMT_RGB16U: case PixelFormat::PIX_FMT_RGBA16U: qWarning() << "Alpha association functions only works on float-based pixel formats at this time"; break; + case PixelFormat::PIX_FMT_RGB16F: case PixelFormat::PIX_FMT_RGBA16F: { AssociateAlphaInternal(action, reinterpret_cast(f->data()), pixel_count); break; } + case PixelFormat::PIX_FMT_RGB32F: case PixelFormat::PIX_FMT_RGBA32F: { AssociateAlphaInternal(action, reinterpret_cast(f->data()), pixel_count); diff --git a/app/render/colorprocessor.cpp b/app/render/colorprocessor.cpp index d311b17b8..98bac702e 100644 --- a/app/render/colorprocessor.cpp +++ b/app/render/colorprocessor.cpp @@ -38,7 +38,7 @@ ColorProcessor::ColorProcessor(OCIO::ConstConfigRcPtr config, void ColorProcessor::ConvertFrame(FramePtr f) { - OCIO::PackedImageDesc img(reinterpret_cast(f->data()), f->width(), f->height(), kRGBAChannels); + OCIO::PackedImageDesc img(reinterpret_cast(f->data()), f->width(), f->height(), PixelFormat::ChannelCount(f->format())); processor->apply(img); } diff --git a/app/render/pixelformat.cpp b/app/render/pixelformat.cpp index 4076ae21f..d37aa3f36 100644 --- a/app/render/pixelformat.cpp +++ b/app/render/pixelformat.cpp @@ -19,3 +19,237 @@ ***/ #include "pixelformat.h" + +#include +#include +#include + +#include "common/define.h" +#include "core.h" + +bool PixelFormat::FormatHasAlphaChannel(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGBA8: + case PixelFormat::PIX_FMT_RGBA16U: + case PixelFormat::PIX_FMT_RGBA16F: + case PixelFormat::PIX_FMT_RGBA32F: + return true; + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + return false; +} + +OIIO::TypeDesc PixelFormat::GetOIIOTypeDesc(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + return OIIO::TypeDesc::UINT8; + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + return OIIO::TypeDesc::UINT16; + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + return OIIO::TypeDesc::HALF; + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + return OIIO::TypeDesc::FLOAT; + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + return OIIO::TypeDesc::UNKNOWN; +} + +QString PixelFormat::GetName(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + return tr("8-bit"); + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + return tr("16-bit Integer"); + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + return tr("Half-Float (16-bit)"); + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + return tr("Full-Float (32-bit)"); + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + return tr("Unknown (%1)").arg(format); +} + +PixelFormat* PixelFormat::instance_ = nullptr; + +void PixelFormat::CreateInstance() +{ + instance_ = new PixelFormat(); +} + +void PixelFormat::DestroyInstance() +{ + delete instance_; +} + +PixelFormat *PixelFormat::instance() +{ + return instance_; +} + +PixelFormat::Format PixelFormat::GetConfiguredFormatForMode(RenderMode::Mode mode) +{ + return static_cast(Core::GetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat")).toInt()); +} + +void PixelFormat::SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format) +{ + if (format != GetConfiguredFormatForMode(mode)) { + Core::SetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat"), format); + + emit FormatChanged(); + } +} + +PixelFormat::Format PixelFormat::OIIOFormatToOliveFormat(OIIO::TypeDesc desc, bool has_alpha) +{ + if (desc == OIIO::TypeDesc::UINT8) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; + } else if (desc == OIIO::TypeDesc::UINT16) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; + } else if (desc == OIIO::TypeDesc::HALF) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; + } else if (desc == OIIO::TypeDesc::FLOAT) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; + } + + return PixelFormat::PIX_FMT_INVALID; +} + +/*PixelFormat::Info PixelFormat::GetPixelFormatInfo(const PixelFormat::Format &format) +{ + PixelFormat::Info info; + + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + info.name = tr("8-bit"); + info.internal_format = (format == PixelFormat::PIX_FMT_RGB8) ? GL_RGB8 : GL_RGBA8; + info.gl_pixel_type = GL_UNSIGNED_BYTE; + info.oiio_desc = OIIO::TypeDesc::UINT8; + break; + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + info.name = tr("16-bit Integer"); + info.internal_format = (format == PixelFormat::PIX_FMT_RGB16U) ? GL_RGB8 : GL_RGBA16; + info.gl_pixel_type = GL_UNSIGNED_SHORT; + info.oiio_desc = OIIO::TypeDesc::UINT16; + break; + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + info.name = tr("Half-Float (16-bit)"); + info.internal_format = (format == PixelFormat::PIX_FMT_RGB8) ? GL_RGB8 : GL_RGBA16F; + info.gl_pixel_type = GL_HALF_FLOAT; + info.oiio_desc = OIIO::TypeDesc::HALF; + break; + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + info.name = tr("Full-Float (32-bit)"); + info.internal_format = (format == PixelFormat::PIX_FMT_RGB8) ? GL_RGB8 : GL_RGBA32F; + info.gl_pixel_type = GL_FLOAT; + info.oiio_desc = OIIO::TypeDesc::FLOAT; + break; + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + qFatal("Invalid pixel format requested"); + } + + info.pixel_format = GL_RGBA; + info.bytes_per_pixel = BytesPerPixel(format); + + return info; +}*/ + +int PixelFormat::GetBufferSize(const PixelFormat::Format &format, const int &width, const int &height) +{ + return BytesPerPixel(format) * width * height; +} + +int PixelFormat::BytesPerPixel(const PixelFormat::Format &format) +{ + return BytesPerChannel(format) * ChannelCount(format); +} + +int PixelFormat::BytesPerChannel(const PixelFormat::Format &format) +{ + switch (format) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + return 1; + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16U: + case PixelFormat::PIX_FMT_RGBA16F: + return 2; + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + return 4; + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + break; + } + + qFatal("Invalid pixel format requested"); + + // qFatal will abort so we won't get here, but this suppresses compiler warnings + return 0; +} + +int PixelFormat::ChannelCount(const PixelFormat::Format &format) +{ + if (PixelFormat::FormatHasAlphaChannel(format)) { + return kRGBAChannels; + } else { + return kRGBChannels; + } +} + +FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Format &dest_format) +{ + if (frame->format() == dest_format) { + return frame; + } + + FramePtr converted = Frame::Create(); + + // Copy parameters + converted->set_width(frame->width()); + converted->set_height(frame->height()); + converted->set_timestamp(frame->timestamp()); + converted->set_format(dest_format); + converted->allocate(); + + OIIO::TypeDesc src_type = GetOIIOTypeDesc(frame->format()); + OIIO::TypeDesc dst_type = GetOIIOTypeDesc(dest_format); + + if (OIIO::convert_type(src_type, frame->data(), dst_type, converted->data())) { + return converted; + } else { + qDebug() << "Failed to convert type:" << OIIO::geterror().c_str(); + return nullptr; + } +} + diff --git a/app/render/pixelformat.h b/app/render/pixelformat.h index 918d65ad8..e92984f84 100644 --- a/app/render/pixelformat.h +++ b/app/render/pixelformat.h @@ -25,7 +25,14 @@ #include #include -class PixelFormat { +#include "render/rendermodes.h" + +class Frame; +using FramePtr = std::shared_ptr; + +class PixelFormat : public QObject +{ + Q_OBJECT public: /** * @brief Olive's internal supported pixel formats. @@ -38,27 +45,92 @@ public: PIX_FMT_RGBA16F, PIX_FMT_RGBA32F, + PIX_FMT_RGB8, + PIX_FMT_RGB16U, + PIX_FMT_RGB16F, + PIX_FMT_RGB32F, + PIX_FMT_COUNT }; + static void CreateInstance(); + static void DestroyInstance(); + static PixelFormat* instance(); + /** - * @brief A struct of information pertaining to each enum PixelFormat. - * - * Primarily this is a means of retrieving OpenGL texture information for different pixel formats/bit depths. Both - * RAM and VRAM buffers will need a PixelFormat. To keep consistency between the OpenGL code and CPU code when using - * a given PixelFormat, the PixelFormatInfo struct contains all necessary variables that you'll need to plug into - * OpenGL. - * - * Use the static function PixelService::GetPixelFormatInfo to generate a PixelFormatInfo object. + * @brief Returns the configured pixel format for a given mode */ - struct Info { - QString name; - GLint internal_format; - GLenum pixel_format; - GLenum gl_pixel_type; - int bytes_per_pixel; - OIIO::TypeDesc oiio_desc; - }; + Format GetConfiguredFormatForMode(RenderMode::Mode mode); + void SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format); + + static Format OIIOFormatToOliveFormat(OIIO::TypeDesc desc, bool has_alpha); + + /** + * @brief Returns the minimum buffer size (in bytes) necessary for a given format, width, and height. + * + * @param format + * + * The format of the data the buffer should contain. Must be a member of the olive::PixelFormat enum. + * + * @param width + * + * The width (in pixels) of the buffer. + * + * @param height + * + * The height (in pixels) of the buffer. + */ + static int GetBufferSize(const Format &format, const int& width, const int& height); + + /** + * @brief Returns the number of bytes per pixel for a certain format + * + * Different formats use different sizes of data for pixels. Use this function to determine how many bytes a pixel + * requires for a certain format. The number of bytes will always be a multiple of 4 since all formats use RGBA and + * are at least 1 bpc. + */ + static int BytesPerPixel(const Format &format); + + /** + * @brief Returns the number of bytes per channel for a certain format + */ + static int BytesPerChannel(const Format& format); + + /** + * @brief Return the number of channels in this format + */ + static int ChannelCount(const Format& format); + + /** + * @brief Convert a frame to a pixel format + * + * If the frame's pixel format == the destination format, this just returns `frame`. + */ + static FramePtr ConvertPixelFormat(FramePtr frame, const Format &dest_format); + + /** + * @brief Simple convenience function returning whether a pixel format has an alpha channel or not + */ + static bool FormatHasAlphaChannel(const Format& format); + + /** + * @brief Get corresponding OpenImageIO TypeDesc for a given pixel format + */ + static OIIO::TypeDesc GetOIIOTypeDesc(const Format& format); + + /** + * @brief Get format name + */ + static QString GetName(const Format& format); + +signals: + void FormatChanged(); + +private: + PixelFormat() = default; + + static PixelFormat* instance_; + }; #endif // BITDEPTHS_H diff --git a/app/render/pixelservice.cpp b/app/render/pixelservice.cpp deleted file mode 100644 index 53e58437b..000000000 --- a/app/render/pixelservice.cpp +++ /dev/null @@ -1,366 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "pixelservice.h" - -#include -#include -#include - -#include "common/define.h" -#include "core.h" - -PixelService* PixelService::instance_ = nullptr; - -void PixelService::CreateInstance() -{ - instance_ = new PixelService(); -} - -void PixelService::DestroyInstance() -{ - delete instance_; -} - -PixelService *PixelService::instance() -{ - return instance_; -} - -PixelFormat::Format PixelService::GetConfiguredFormatForMode(RenderMode::Mode mode) -{ - return static_cast(Core::GetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat")).toInt()); -} - -void PixelService::SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format) -{ - if (format != GetConfiguredFormatForMode(mode)) { - Core::SetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat"), format); - - emit FormatChanged(); - } -} - -PixelFormat::Format PixelService::OIIOFormatToOliveFormat(OIIO::TypeDesc desc) -{ - if (desc == OIIO::TypeDesc::UINT8) { - return PixelFormat::PIX_FMT_RGBA8; - } else if (desc == OIIO::TypeDesc::UINT16) { - return PixelFormat::PIX_FMT_RGBA16U; - } else if (desc == OIIO::TypeDesc::HALF) { - return PixelFormat::PIX_FMT_RGBA16F; - } else if (desc == OIIO::TypeDesc::FLOAT) { - return PixelFormat::PIX_FMT_RGBA32F; - } - - return PixelFormat::PIX_FMT_INVALID; -} - -PixelFormat::Info PixelService::GetPixelFormatInfo(const PixelFormat::Format &format) -{ - PixelFormat::Info info; - - switch (format) { - case PixelFormat::PIX_FMT_RGBA8: - info.name = tr("8-bit"); - info.internal_format = GL_RGBA8; - info.gl_pixel_type = GL_UNSIGNED_BYTE; - info.oiio_desc = OIIO::TypeDesc::UINT8; - break; - case PixelFormat::PIX_FMT_RGBA16U: - info.name = tr("16-bit Integer"); - info.internal_format = GL_RGBA16; - info.gl_pixel_type = GL_UNSIGNED_SHORT; - info.oiio_desc = OIIO::TypeDesc::UINT16; - break; - case PixelFormat::PIX_FMT_RGBA16F: - info.name = tr("Half-Float (16-bit)"); - info.internal_format = GL_RGBA16F; - info.gl_pixel_type = GL_HALF_FLOAT; - info.oiio_desc = OIIO::TypeDesc::HALF; - break; - case PixelFormat::PIX_FMT_RGBA32F: - info.name = tr("Full-Float (32-bit)"); - info.internal_format = GL_RGBA32F; - info.gl_pixel_type = GL_FLOAT; - info.oiio_desc = OIIO::TypeDesc::FLOAT; - break; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - qFatal("Invalid pixel format requested"); - } - - info.pixel_format = GL_RGBA; - info.bytes_per_pixel = BytesPerPixel(format); - - return info; -} - -int PixelService::GetBufferSize(const PixelFormat::Format &format, const int &width, const int &height) -{ - return BytesPerPixel(format) * width * height; -} - -int PixelService::BytesPerPixel(const PixelFormat::Format &format) -{ - return BytesPerChannel(format) * kRGBAChannels; -} - -int PixelService::BytesPerChannel(const PixelFormat::Format &format) -{ - switch (format) { - case PixelFormat::PIX_FMT_RGBA8: - return 1; - case PixelFormat::PIX_FMT_RGBA16U: - case PixelFormat::PIX_FMT_RGBA16F: - return 2; - case PixelFormat::PIX_FMT_RGBA32F: - return 4; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - break; - } - - qFatal("Invalid pixel format requested"); - - // qFatal will abort so we won't get here, but this suppresses compiler warnings - return 0; -} - -FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const PixelFormat::Format &dest_format) -{ - if (frame->format() == dest_format) { - return frame; - } - - // FIXME: It'd be nice if this was multithreaded soon - - FramePtr converted = Frame::Create(); - - // Copy parameters - converted->set_width(frame->width()); - converted->set_height(frame->height()); - converted->set_timestamp(frame->timestamp()); - converted->set_format(dest_format); - converted->allocate(); - - int pix_count = frame->width() * frame->height() * kRGBAChannels; - - bool valid = true; - - switch (static_cast(frame->format())) { - case PixelFormat::PIX_FMT_RGBA8: - { - uint8_t* source = reinterpret_cast(frame->data()); - - switch (dest_format) { - case PixelFormat::PIX_FMT_RGBA16U: // 8-bit Integer -> 16-bit Integer - { - uint16_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] * 257); - } - break; - } - case PixelFormat::PIX_FMT_RGBA16F: // 8-bit Integer -> 16-bit Float - { - qfloat16* destination = reinterpret_cast(converted->data()); - for (int i=0;i 32-bit Float - { - float* destination = reinterpret_cast(converted->data()); - for (int i=0;i(frame->data()); - - switch (dest_format) { - case PixelFormat::PIX_FMT_RGBA8: // 16-bit Integer -> 8-bit Integer - { - uint8_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] / 257); - } - break; - } - case PixelFormat::PIX_FMT_RGBA16F: // 16-bit Integer -> 16-bit Float - { - qfloat16* destination = reinterpret_cast(converted->data()); - for (int i=0;i 32-bit Float - { - float* destination = reinterpret_cast(converted->data()); - for (int i=0;i(frame->data()); - - switch (dest_format) { - case PixelFormat::PIX_FMT_RGBA8: // 16-bit Float -> 8-bit Integer - { - uint8_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] * 255.0f); - } - break; - } - case PixelFormat::PIX_FMT_RGBA16U: // 16-bit Float -> 16-bit Integer - { - uint16_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] * 65535.0f); - } - break; - } - case PixelFormat::PIX_FMT_RGBA32F: // 16-bit Float -> 32-bit Float - { - float* destination = reinterpret_cast(converted->data()); - for (int i=0;i(frame->data()); - - switch (dest_format) { - case PixelFormat::PIX_FMT_RGBA8: // 32-bit Float -> 8-bit Integer - { - uint8_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] * 255.0f); - } - break; - } - case PixelFormat::PIX_FMT_RGBA16U: // 32-bit Float -> 16-bit Integer - { - uint16_t* destination = reinterpret_cast(converted->data()); - for (int i=0;i(source[i] * 65535.0f); - } - break; - } - case PixelFormat::PIX_FMT_RGBA16F: // 32-bit Float -> 16-bit Float - { - qfloat16* destination = reinterpret_cast(converted->data()); - for (int i=0;i(frame->format()); - - int rgb_pixel_size = BytesPerChannel(dest_format) * kRGBChannels; - int rgb_frame_size = frame->width() * frame->height() * rgb_pixel_size; - int rgb_iter = rgb_frame_size - rgb_pixel_size; - - int rgba_pixel_size = BytesPerChannel(dest_format) * kRGBAChannels; - int rgba_frame_size = frame->width() * frame->height() * rgba_pixel_size; - int rgba_iter = rgba_frame_size - rgba_pixel_size; - - // Work backwards to save time - while (rgb_iter >= 0) { - memcpy(&frame->data()[rgba_iter], &frame->data()[rgb_iter], static_cast(rgb_pixel_size)); - - uint8_t* alpha_ptr = reinterpret_cast(frame->data()) + rgba_iter + rgb_pixel_size; - - // Write a full alpha value according to the format - switch (dest_format) { - case PixelFormat::PIX_FMT_RGBA8: - *alpha_ptr = UINT8_MAX; - break; - case PixelFormat::PIX_FMT_RGBA16U: - *reinterpret_cast(alpha_ptr) = UINT16_MAX; - break; - case PixelFormat::PIX_FMT_RGBA16F: - *reinterpret_cast(alpha_ptr) = 1.0f; - break; - case PixelFormat::PIX_FMT_RGBA32F: - *reinterpret_cast(alpha_ptr) = 1.0f; - break; - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - qFatal("Invalid pixel format requested"); - } - - rgb_iter -= rgb_pixel_size; - rgba_iter -= rgba_pixel_size; - } -} diff --git a/app/render/pixelservice.h b/app/render/pixelservice.h deleted file mode 100644 index 0a874f444..000000000 --- a/app/render/pixelservice.h +++ /dev/null @@ -1,106 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef PIXELSERVICE_H -#define PIXELSERVICE_H - -#include - -#include "codec/frame.h" -#include "pixelformat.h" -#include "render/rendermodes.h" - -class PixelService : public QObject -{ - Q_OBJECT -public: - static void CreateInstance(); - static void DestroyInstance(); - static PixelService* instance(); - - /** - * @brief Returns the configured pixel format for a given mode - */ - PixelFormat::Format GetConfiguredFormatForMode(RenderMode::Mode mode); - void SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format); - - static PixelFormat::Format OIIOFormatToOliveFormat(OIIO::TypeDesc desc); - - /** - * @brief Return a PixelFormatInfo containing information for a certain format - * - * \see PixelFormatInfo - */ - static PixelFormat::Info GetPixelFormatInfo(const PixelFormat::Format& format); - - /** - * @brief Returns the minimum buffer size (in bytes) necessary for a given format, width, and height. - * - * @param format - * - * The format of the data the buffer should contain. Must be a member of the olive::PixelFormat enum. - * - * @param width - * - * The width (in pixels) of the buffer. - * - * @param height - * - * The height (in pixels) of the buffer. - */ - static int GetBufferSize(const PixelFormat::Format &format, const int& width, const int& height); - - /** - * @brief Returns the number of bytes per pixel for a certain format - * - * Different formats use different sizes of data for pixels. Use this function to determine how many bytes a pixel - * requires for a certain format. The number of bytes will always be a multiple of 4 since all formats use RGBA and - * are at least 1 bpc. - */ - static int BytesPerPixel(const PixelFormat::Format &format); - - /** - * @brief Returns the number of bytes per channel for a certain format - */ - static int BytesPerChannel(const PixelFormat::Format& format); - - /** - * @brief Convert a frame to a pixel format - * - * If the frame's pixel format == the destination format, this just returns `frame`. - */ - static FramePtr ConvertPixelFormat(FramePtr frame, const PixelFormat::Format &dest_format); - - /** - * @brief Convert an RGB image to an RGBA image - */ - static void ConvertRGBtoRGBA(FramePtr frame); - -signals: - void FormatChanged(); - -private: - PixelService() = default; - - static PixelService* instance_; - -}; - -#endif // PIXELSERVICE_H diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 81add9f61..f14080eed 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -31,7 +31,7 @@ #include "config/config.h" #include "project/item/sequence/sequence.h" #include "project/project.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" #include "widget/menu/menu.h" ViewerWidget::ViewerWidget(QWidget *parent) : @@ -85,7 +85,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange); audio_renderer_ = new AudioBackend(this); - connect(PixelService::instance(), &PixelService::FormatChanged, this, &ViewerWidget::UpdateRendererParameters); + connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters); SetAutoMaxScrollBar(true); } @@ -296,7 +296,7 @@ void ViewerWidget::UpdateRendererParameters() RenderMode::Mode render_mode = RenderMode::kOffline; VideoRenderingParams vparam(GetConnectedNode()->video_params(), - PixelService::instance()->GetConfiguredFormatForMode(render_mode), + PixelFormat::instance()->GetConfiguredFormatForMode(render_mode), render_mode, divider_); diff --git a/app/widget/viewer/viewerglwidget.cpp b/app/widget/viewer/viewerglwidget.cpp index 9deac2bdd..76b2f9efc 100644 --- a/app/widget/viewer/viewerglwidget.cpp +++ b/app/widget/viewer/viewerglwidget.cpp @@ -27,9 +27,10 @@ #include #include +#include "common/define.h" #include "render/backend/opengl/openglrenderfunctions.h" #include "render/backend/opengl/openglshader.h" -#include "render/pixelservice.h" +#include "render/pixelformat.h" #ifdef Q_OS_LINUX bool ViewerGLWidget::nouveau_check_done_ = false; @@ -83,7 +84,8 @@ void ViewerGLWidget::SetImage(const QString &fn) if (input) { - PixelFormat::Format image_format = PixelService::OIIOFormatToOliveFormat(input->spec().format); + PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format, + input->spec().nchannels == kRGBAChannels); // Ensure the following texture operations are done in our context (in case we're in a separate window for instance) makeCurrent(); From d87b4fde8c049c0b13f73162574a13b022541356 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 15:47:08 +1100 Subject: [PATCH 08/26] renderer/decoder: use divider at the footage level Optimizes rendering if a divider is being used. --- app/codec/decoder.cpp | 2 +- app/codec/decoder.h | 2 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 63 ++++++++++++++--------- app/codec/ffmpeg/ffmpegdecoder.h | 8 +-- app/codec/oiio/oiiodecoder.cpp | 30 ++++++----- app/codec/oiio/oiiodecoder.h | 5 +- app/render/backend/opengl/openglproxy.cpp | 12 +++-- app/render/backend/opengl/openglproxy.h | 1 + 8 files changed, 75 insertions(+), 48 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index ed0dea143..224818912 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -61,7 +61,7 @@ void Decoder::set_stream(StreamPtr fs) stream_ = fs; } -FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/) +FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/) { return nullptr; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 86ffc2710..af3507e33 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -142,7 +142,7 @@ public: * A FramePtr of valid data at this timecode or nullptr if there was nothing to retrieve at the provided timecode or * the media could not be opened. */ - virtual FramePtr RetrieveVideo(const rational& timecode); + virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider); /** * @brief Retrieve video frame diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 149e353fe..8b8b1ee2b 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -46,6 +46,7 @@ FFmpegDecoder::FFmpegDecoder() : fmt_ctx_(nullptr), codec_ctx_(nullptr), scale_ctx_(nullptr), + scale_divider_(-1), cache_at_zero_(false), cache_at_eof_(false), opts_(nullptr) @@ -172,22 +173,6 @@ bool FFmpegDecoder::Open() qFatal("Invalid output format"); } - scale_ctx_ = sws_getContext(avstream_->codecpar->width, - avstream_->codecpar->height, - static_cast(avstream_->codecpar->format), - avstream_->codecpar->width, - avstream_->codecpar->height, - ideal_pix_fmt_, - 0, - nullptr, - nullptr, - nullptr); - - if (!scale_ctx_) { - Error(QStringLiteral("Failed to allocate SwsContext")); - return false; - } - second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base))); QMetaObject::invokeMethod(&clear_timer_, "start"); @@ -220,7 +205,7 @@ Decoder::RetrieveState FFmpegDecoder::GetRetrieveState(const rational& time) return kReady; } -FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) +FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r) { QMutexLocker locker(&mutex_); @@ -237,6 +222,12 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) Frame* return_frame = nullptr; + if (divider != scale_divider_) { + ClearFrameCache(); + FreeScaler(); + SetupScaler(divider); + } + // See if our RAM cache already has a frame that matches this timestamp if (!cached_frames_.isEmpty()) { @@ -375,8 +366,8 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) } // Whatever it is, keep this frame in memory for the time being just in case - Frame* working_frame_converted = cached_frames_.append(VideoRenderingParams(avstream_->codecpar->width, - avstream_->codecpar->height, + Frame* working_frame_converted = cached_frames_.append(VideoRenderingParams(avstream_->codecpar->width / divider, + avstream_->codecpar->height / divider, avstream_->time_base, native_pix_fmt_, RenderMode::kOffline)); @@ -949,10 +940,7 @@ void FFmpegDecoder::ClearResources() ClearFrameCache(); - if (scale_ctx_) { - sws_freeContext(scale_ctx_); - scale_ctx_ = nullptr; - } + FreeScaler(); if (codec_ctx_) { avcodec_free_context(&codec_ctx_); @@ -967,6 +955,35 @@ void FFmpegDecoder::ClearResources() open_ = false; } +void FFmpegDecoder::SetupScaler(const int ÷r) +{ + scale_ctx_ = sws_getContext(avstream_->codecpar->width, + avstream_->codecpar->height, + static_cast(avstream_->codecpar->format), + avstream_->codecpar->width / divider, + avstream_->codecpar->height / divider, + ideal_pix_fmt_, + SWS_FAST_BILINEAR, + nullptr, + nullptr, + nullptr); + + if (!scale_ctx_) { + Error(QStringLiteral("Failed to allocate SwsContext")); + } else { + scale_divider_ = divider; + } +} + +void FFmpegDecoder::FreeScaler() +{ + if (scale_ctx_) { + sws_freeContext(scale_ctx_); + scale_ctx_ = nullptr; + scale_divider_ = -1; + } +} + void FFmpegDecoder::ClearTimerEvent() { QMutexLocker locker(&mutex_); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index e80c70593..4ceb50184 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -54,7 +54,7 @@ public: virtual bool Open() override; virtual RetrieveState GetRetrieveState(const rational &time) override; - virtual FramePtr RetrieveVideo(const rational &timecode) override; + virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; virtual FramePtr RetrieveAudio(const rational &timecode, const rational &length, const AudioRenderingParams& params) override; virtual void Close() override; @@ -102,12 +102,13 @@ private: void CacheFrameToDisk(AVFrame* f); - //void RemoveFirstFromFrameCache(); - //void RemoveLastFromFrameCache(); void ClearFrameCache(); void ClearResources(); + void SetupScaler(const int& divider); + void FreeScaler(); + AVFormatContext* fmt_ctx_; AVCodecContext* codec_ctx_; AVStream* avstream_; @@ -116,6 +117,7 @@ private: PixelFormat::Format native_pix_fmt_; SwsContext* scale_ctx_; + int scale_divider_; FFmpegFrameCache::Client cached_frames_; bool cache_at_zero_; diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 4aca39e6b..c2de2b690 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -20,6 +20,7 @@ #include "oiiodecoder.h" +#include #include #include @@ -29,7 +30,7 @@ QStringList OIIODecoder::supported_formats_; OIIODecoder::OIIODecoder() : image_(nullptr), - frame_(nullptr) + buffer_(nullptr) { } @@ -134,6 +135,9 @@ bool OIIODecoder::Open() // FIXME: Many OIIO pixel formats are not handled here type_ = PixelFormat::GetOIIOTypeDesc(pix_fmt_); + buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type_), OIIO::InitializePixels::No); + image_->read_image(type_, buffer_->localpixels()); + open_ = true; return true; @@ -150,7 +154,7 @@ Decoder::RetrieveState OIIODecoder::GetRetrieveState(const rational &time) return kReady; } -FramePtr OIIODecoder::RetrieveVideo(const rational &timecode) +FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) { QMutexLocker locker(&mutex_); @@ -158,21 +162,20 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode) return nullptr; } - Q_UNUSED(timecode) + FramePtr frame = Frame::Create(); - if (!frame_) { - frame_ = Frame::Create(); + frame->set_width(width_ / divider); + frame->set_height(height_ / divider); + frame->set_format(pix_fmt_); + frame->allocate(); - frame_->set_width(width_); - frame_->set_height(height_); - frame_->set_format(pix_fmt_); - frame_->allocate(); + OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format), frame->data()); - // Use the native format to determine what format OIIO should return - image_->read_image(type_, frame_->data()); + if (!OIIO::ImageBufAlgo::resize(dst, *buffer_)) { + qWarning() << "OIIO resize failed"; } - return frame_; + return frame; } void OIIODecoder::Close() @@ -187,7 +190,8 @@ void OIIODecoder::Close() image_ = nullptr; } - frame_ = nullptr; + delete buffer_; + buffer_ = nullptr; } bool OIIODecoder::SupportsVideo() diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 92f1ea4c4..be6e5f21e 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -22,6 +22,7 @@ #define OIIODECODER_H #include +#include #include "codec/decoder.h" #include "render/pixelformat.h" @@ -37,7 +38,7 @@ public: virtual bool Open() override; virtual RetrieveState GetRetrieveState(const rational &time) override; - virtual FramePtr RetrieveVideo(const rational &timecode) override; + virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; virtual void Close() override; virtual bool SupportsVideo() override; @@ -61,7 +62,7 @@ private: bool is_rgba_; - FramePtr frame_; + OIIO::ImageBuf* buffer_; static QStringList supported_formats_; diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index 7aad9279f..76bf19cbb 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -60,7 +60,9 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR if (stream->type() == Stream::kImage && still_image_cache_.Has(stream.get())) { CachedStill cs = still_image_cache_.Get(stream.get()); - if (cs.colorspace == colorspace_match && cs.alpha_is_associated == video_stream->premultiplied_alpha()) { + if (cs.colorspace == colorspace_match + && cs.alpha_is_associated == video_stream->premultiplied_alpha() + && cs.divider == video_params_.divider()) { footage_tex_ref = cs.texture; } else { still_image_cache_.Remove(stream.get()); @@ -80,7 +82,7 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(video_params_.mode()); - FramePtr frame = decoder->RetrieveVideo(range.in());; + FramePtr frame = decoder->RetrieveVideo(range.in(), video_params_.divider()); // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU if (ocio_method == ColorManager::kOCIOAccurate) { @@ -154,7 +156,7 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR } if (stream->type() == Stream::kImage) { - still_image_cache_.Add(stream.get(), {footage_tex_ref, colorspace_match, video_stream->premultiplied_alpha()}); + still_image_cache_.Add(stream.get(), {footage_tex_ref, colorspace_match, video_stream->premultiplied_alpha(), video_params_.divider()}); } } @@ -281,8 +283,8 @@ void OpenGLProxy::RunNodeAccelerated(const Node *node, const TimeRange &range, c int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id())); if (res_param_location > -1) { shader->setUniformValue(res_param_location, - static_cast(texture->texture()->width()), - static_cast(texture->texture()->height())); + static_cast(texture->texture()->width() * video_params_.divider()), + static_cast(texture->texture()->height() * video_params_.divider())); } } diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h index d1b397706..9f7f6e362 100644 --- a/app/render/backend/opengl/openglproxy.h +++ b/app/render/backend/opengl/openglproxy.h @@ -69,6 +69,7 @@ private: OpenGLTextureCache::ReferencePtr texture; QString colorspace; bool alpha_is_associated; + int divider; }; RenderCache still_image_cache_; From a0f37ef827462d5b2e54891164215bf3d625ee02 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 15:49:15 +1100 Subject: [PATCH 09/26] pixelformat: added missing include for non-windows systems --- app/render/pixelformat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/render/pixelformat.h b/app/render/pixelformat.h index e92984f84..eec25f1d8 100644 --- a/app/render/pixelformat.h +++ b/app/render/pixelformat.h @@ -21,6 +21,7 @@ #ifndef BITDEPTHS_H #define BITDEPTHS_H +#include #include #include #include From 83c5cd2bc1670f5be4da87e59df16906c93f32c9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 16:03:56 +1100 Subject: [PATCH 10/26] oiiodecoder: made line backwards compatible with OIIO < 2.1.x --- app/codec/oiio/oiiodecoder.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index c2de2b690..1e0e5562b 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -135,7 +135,11 @@ bool OIIODecoder::Open() // FIXME: Many OIIO pixel formats are not handled here type_ = PixelFormat::GetOIIOTypeDesc(pix_fmt_); +#if OIIO_VERSION < 20100 + buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type_)); +#else buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type_), OIIO::InitializePixels::No); +#endif image_->read_image(type_, buffer_->localpixels()); open_ = true; From a64f066e2ee4e065248417791567b3f2d6234c4c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 17:18:39 +1100 Subject: [PATCH 11/26] renderer: don't upconvert to RGBA if the source image is only RGB --- app/render/backend/opengl/openglproxy.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index 76bf19cbb..c1e6cf72d 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -86,22 +86,26 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU if (ocio_method == ColorManager::kOCIOAccurate) { + bool has_alpha = PixelFormat::FormatHasAlphaChannel(frame->format()); + // If alpha is associated, disassociate for the color transform - if (video_stream->premultiplied_alpha()) { + if (has_alpha && video_stream->premultiplied_alpha()) { ColorManager::DisassociateAlpha(frame); } // Convert frame to float for OCIO - frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + frame = PixelFormat::ConvertPixelFormat(frame, has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F); // Perform color transform color_processor->ConvertFrame(frame); // Associate alpha - if (video_stream->premultiplied_alpha()) { - ColorManager::ReassociateAlpha(frame); - } else { - ColorManager::AssociateAlpha(frame); + if (has_alpha) { + if (video_stream->premultiplied_alpha()) { + ColorManager::ReassociateAlpha(frame); + } else { + ColorManager::AssociateAlpha(frame); + } } } From 273522f28a466ce570dc013a2f2d0ea776702d48 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 17:19:07 +1100 Subject: [PATCH 12/26] diskmanager: don't fail disk cache clear if the files simply don't exist QFile::remove() will return false if the file doesn't exist, but for our purposes that's as good as the file having been deleted. --- app/render/diskmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/diskmanager.cpp b/app/render/diskmanager.cpp index 10bf36817..053f7935a 100644 --- a/app/render/diskmanager.cpp +++ b/app/render/diskmanager.cpp @@ -159,7 +159,7 @@ bool DiskManager::ClearDiskCache(bool quick_delete) const HashTime& ht = disk_data_.at(i); // We return a false result if any of the files fail to delete, but still try to delete as many as we can - if (QFile::remove(ht.file_name)) { + if (QFile::remove(ht.file_name) || !QFileInfo::exists(ht.file_name)) { emit DeletedFrame(ht.hash); disk_data_.removeAt(i); i--; From 735e180d5a9dd15722a56d7348e6ade156744790 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 17:19:59 +1100 Subject: [PATCH 13/26] pixelservice: use more widely supported OIIO function in image buffer conversions --- app/render/pixelformat.cpp | 10 +++++----- app/render/pixelformat.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/render/pixelformat.cpp b/app/render/pixelformat.cpp index d37aa3f36..9fa451c3b 100644 --- a/app/render/pixelformat.cpp +++ b/app/render/pixelformat.cpp @@ -20,6 +20,7 @@ #include "pixelformat.h" +#include "OpenImageIO/imagebuf.h" #include #include #include @@ -47,7 +48,7 @@ bool PixelFormat::FormatHasAlphaChannel(const PixelFormat::Format &format) return false; } -OIIO::TypeDesc PixelFormat::GetOIIOTypeDesc(const PixelFormat::Format &format) +OIIO::TypeDesc::BASETYPE PixelFormat::GetOIIOTypeDesc(const PixelFormat::Format &format) { switch (format) { case PixelFormat::PIX_FMT_RGB8: @@ -242,13 +243,12 @@ FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Form converted->set_format(dest_format); converted->allocate(); - OIIO::TypeDesc src_type = GetOIIOTypeDesc(frame->format()); - OIIO::TypeDesc dst_type = GetOIIOTypeDesc(dest_format); + OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(), frame->height(), ChannelCount(frame->format()), GetOIIOTypeDesc(frame->format())), frame->data()); + OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(), ChannelCount(converted->format()), GetOIIOTypeDesc(converted->format())), converted->data()); - if (OIIO::convert_type(src_type, frame->data(), dst_type, converted->data())) { + if (dst.copy_pixels(src)) { return converted; } else { - qDebug() << "Failed to convert type:" << OIIO::geterror().c_str(); return nullptr; } } diff --git a/app/render/pixelformat.h b/app/render/pixelformat.h index eec25f1d8..7f9a59555 100644 --- a/app/render/pixelformat.h +++ b/app/render/pixelformat.h @@ -117,7 +117,7 @@ public: /** * @brief Get corresponding OpenImageIO TypeDesc for a given pixel format */ - static OIIO::TypeDesc GetOIIOTypeDesc(const Format& format); + static OIIO::TypeDesc::BASETYPE GetOIIOTypeDesc(const Format& format); /** * @brief Get format name From f9a54ebdb637a3150e8c8902de9cef66c9f18d1a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 17:46:49 +1100 Subject: [PATCH 14/26] renderer: remove frames from hash map as well when the diskmanager deletes them --- app/render/backend/videorenderbackend.cpp | 2 +- app/render/backend/videorenderframecache.cpp | 21 +++++++++++++++++++- app/render/backend/videorenderframecache.h | 10 +++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 263069c78..6a7fe2e7a 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -343,7 +343,7 @@ void VideoRenderBackend::TruncateFrameCacheLength(const rational &length) void VideoRenderBackend::FrameRemovedFromDiskCache(const QByteArray &hash) { - QList deleted_frames = frame_cache()->FramesWithHash(hash); + QList deleted_frames = frame_cache()->TakeFramesWithHash(hash); foreach (const rational& frame, deleted_frames) { TimeRange invalidated(frame, frame+params_.time_base()); diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/backend/videorenderframecache.cpp index a45591414..3df6aa615 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/backend/videorenderframecache.cpp @@ -83,7 +83,7 @@ void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &has currently_caching_list_.removeOne(hash); } -QList VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) +QList VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) const { QList times; @@ -98,6 +98,25 @@ QList VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) return times; } +QList VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash) +{ + QList times; + + QMap::iterator iterator = time_hash_map_.begin(); + + while (iterator != time_hash_map_.end()) { + if (iterator.value() == hash) { + times.append(iterator.key()); + + iterator = time_hash_map_.erase(iterator); + } else { + iterator++; + } + } + + return times; +} + const QMap &VideoRenderFrameCache::time_hash_map() const { return time_hash_map_; diff --git a/app/render/backend/videorenderframecache.h b/app/render/backend/videorenderframecache.h index 6c5af9447..cd9902e40 100644 --- a/app/render/backend/videorenderframecache.h +++ b/app/render/backend/videorenderframecache.h @@ -43,7 +43,15 @@ public: void RemoveHashFromCurrentlyCaching(const QByteArray& hash); - QList FramesWithHash(const QByteArray& hash); + /** + * @brief Returns a list of frames that use a particular hash + */ + QList FramesWithHash(const QByteArray& hash) const; + + /** + * @brief Same as FramesWithHash() but also removes these frames from the map + */ + QList TakeFramesWithHash(const QByteArray& hash); const QMap& time_hash_map() const; From fde5e47901acd5038398e8a1634272c5ee341db9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 17:47:11 +1100 Subject: [PATCH 15/26] projectexplorer:use `if defined()` and `elif defined()` for improved compiler compatibility --- app/widget/projectexplorer/projectexplorer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index f2baebebb..d252491b9 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -249,9 +249,9 @@ void ProjectExplorer::ShowContextMenu() if (selected_items.first()->type() == Item::kFootage) { QString reveal_text; -#ifdef Q_OS_WINDOWS +#if defined(Q_OS_WINDOWS) reveal_text = tr("Reveal in Explorer"); -#elif Q_OS_MAC +#elif defined(Q_OS_MAC) reveal_text = tr("Reveal in Finder"); #else reveal_text = tr("Reveal in File Manager"); @@ -293,12 +293,12 @@ void ProjectExplorer::RevealSelectedFootage() { Footage* footage = static_cast(SelectedItems().first()); -#ifdef Q_OS_WINDOWS +#if defined(Q_OS_WINDOWS) // Explorer QStringList args; args << "/select," << QDir::toNativeSeparators(footage->filename()); QProcess::startDetached("explorer", args); -#elif Q_OS_MAC +#elif defined(Q_OS_MAC) QStringList args; args << "-e"; args << "tell application \"Finder\""; From adcab06d4eb9bd182af23cd92fd5a00a85c54107 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 18:15:55 +1100 Subject: [PATCH 16/26] fixed faulty while loop break --- app/codec/ffmpeg/ffmpegdecoder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 8b8b1ee2b..104c8b24e 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -392,10 +392,10 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid // We found the frame we want return_frame = working_frame_converted; } + } - if (return_frame) { - break; - } + if (return_frame) { + break; } } From d1681bc294d090e55a0f1d0c9878803e4b31c1a7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 27 Feb 2020 18:42:08 +1100 Subject: [PATCH 17/26] travis: move app bundle to root before zipping --- .travis/script.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis/script.sh b/.travis/script.sh index d32a7fdd9..33feb95fd 100644 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -17,19 +17,19 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then exit 1 fi - BUNDLE_PATH=$(find . -name "Olive.app") + BUNDLE_NAME=Olive.app - echo Found app at: $BUNDLE_PATH + mv app/$BUNDLE_NAME . # Move Qt deps into bundle - macdeployqt $BUNDLE_PATH + macdeployqt $BUNDLE_NAME # Fix other deps that macdeployqt missed wget -c -nv https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py - python2 macdeployqtfix.py $BUNDLE_PATH/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/ + python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/ # Distribute in zip - zip -r Olive-$VERSION-macOS.zip $BUNDLE_PATH + zip -r Olive-$VERSION-macOS.zip $BUNDLE_NAME elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then From c32ed5955c752ec33fed24f2caed141d18205007 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 13:21:32 +1100 Subject: [PATCH 18/26] implemented simple crash handler --- app/CMakeLists.txt | 40 ++++++- app/common/CMakeLists.txt | 2 + app/common/crashhandler.cpp | 117 +++++++++++++++++++ app/common/crashhandler.h | 6 + app/dialog/crashhandler/crashhandler.cpp | 47 ++++++++ app/dialog/crashhandler/crashhandler.h | 19 +++ app/dialog/crashhandler/crashhandlermain.cpp | 17 +++ app/main.cpp | 4 + 8 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 app/common/crashhandler.cpp create mode 100644 app/common/crashhandler.h create mode 100644 app/dialog/crashhandler/crashhandler.cpp create mode 100644 app/dialog/crashhandler/crashhandler.h create mode 100644 app/dialog/crashhandler/crashhandlermain.cpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 334bed5a9..353532f36 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -108,7 +108,8 @@ target_include_directories( ${OIIO_INCLUDE_DIRS} ) -target_link_libraries(${OLIVE_TARGET} +target_link_libraries( + ${OLIVE_TARGET} PRIVATE Qt5::Core Qt5::Gui @@ -127,6 +128,14 @@ target_link_libraries(${OLIVE_TARGET} ${OIIO_LIBRARIES} ) +if (WIN32) + target_link_libraries( + ${OLIVE_TARGET} + PRIVATE + DbgHelp + ) +endif() + set(OLIVE_TS_FILES # FIXME: Empty variable ) @@ -151,3 +160,32 @@ if(DOXYGEN_FOUND) set(DOXYGEN_EXTRACT_PRIVATE "YES") doxygen_add_docs(docs ALL ${OLIVE_SOURCES}) endif() + +set(OLIVE_CRASH_TARGET "crashhandler") + +set(OLIVE_CRASH_SOURCES + dialog/crashhandler/crashhandler.h + dialog/crashhandler/crashhandler.cpp + dialog/crashhandler/crashhandlermain.cpp +) + +if (WIN32) + add_executable( + ${OLIVE_CRASH_TARGET} + WIN32 + ${OLIVE_CRASH_SOURCES} + ) +else() + add_executable( + ${OLIVE_CRASH_TARGET} + ${OLIVE_CRASH_SOURCES} + ) +endif() + +target_link_libraries( + ${OLIVE_CRASH_TARGET} + PRIVATE + Qt5::Core + Qt5::Gui + Qt5::Widgets +) diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 743df6778..c02c8acf2 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -22,6 +22,8 @@ set(OLIVE_SOURCES common/channellayout.h common/clamp.h common/constructors.h + common/crashhandler.h + common/crashhandler.cpp common/debug.h common/debug.cpp common/define.h diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp new file mode 100644 index 000000000..8ed1579be --- /dev/null +++ b/app/common/crashhandler.cpp @@ -0,0 +1,117 @@ +#include "crashhandler.h" + +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WINDOWS +#include +#include +#include +#include +#endif + +void crash_handler(int sig) { + QString log_path = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath("olive_crash"); + QFile output(log_path); + + output.open(QFile::WriteOnly); + QTextStream ostream(&output); + + ostream << "Signal: " << sig << "\n\n"; + +#if defined(Q_OS_WINDOWS) + // Use Windows stackwalk API + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + CONTEXT context; + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + SymInitialize(process, NULL, TRUE); + + DWORD image; + STACKFRAME64 stackframe; + ZeroMemory(&stackframe, sizeof(STACKFRAME64)); + +#ifdef _M_IX86 + image = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context.Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Esp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + image = IMAGE_FILE_MACHINE_AMD64; + stackframe.AddrPC.Offset = context.Rip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Rsp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Rsp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + image = IMAGE_FILE_MACHINE_IA64; + stackframe.AddrPC.Offset = context.StIIP; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.IntSp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrBStore.Offset = context.RsBSP; + stackframe.AddrBStore.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.IntSp; + stackframe.AddrStack.Mode = AddrModeFlat; +#endif + + for (int i = 0; i < 50; i++) { + + BOOL result = StackWalk64( + image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL); + + if (!result) { break; } + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + + ostream << "[" << i << "] "; + + if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) { + ostream << symbol->Name; + //printf("[%i] %s\n", i, symbol->Name); + } else { + ostream << "???"; + //printf("[%i] ???\n", i); + } + + ostream << "\n"; + } + + SymCleanup(process); +#elif defined(Q_OS_LINUX) + void *array[10]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif + + output.close(); + + QProcess::startDetached(QStringLiteral("crashhandler"), {log_path}); + + exit(1); +} diff --git a/app/common/crashhandler.h b/app/common/crashhandler.h new file mode 100644 index 000000000..4044cdc5e --- /dev/null +++ b/app/common/crashhandler.h @@ -0,0 +1,6 @@ +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +void crash_handler(int sig); + +#endif // CRASHHANDLER_H diff --git a/app/dialog/crashhandler/crashhandler.cpp b/app/dialog/crashhandler/crashhandler.cpp new file mode 100644 index 000000000..0d1078cb3 --- /dev/null +++ b/app/dialog/crashhandler/crashhandler.cpp @@ -0,0 +1,47 @@ +#include "crashhandler.h" + +#include +#include +#include +#include +#include + +CrashHandlerDialog::CrashHandlerDialog(const char *log_file) +{ + setWindowTitle(tr("Olive")); + + QVBoxLayout* layout = new QVBoxLayout(this); + + layout->addWidget(new QLabel(tr("We're sorry, Olive has crashed. Please send the following log to the developers to " + "help resolve this."))); + + QTextEdit* edit = new QTextEdit(); + layout->addWidget(edit); + + QDialogButtonBox* buttons = new QDialogButtonBox(); + + // FIXME: Implement auto-reporting + //buttons->addButton(tr("Send Error Report"), QDialogButtonBox::AcceptRole); + //buttons->addButton(tr("Don't Send"), QDialogButtonBox::RejectRole); + buttons->addButton(QDialogButtonBox::Ok); + + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + layout->addWidget(buttons); + + QFile log(log_file); + if (log.open(QFile::ReadOnly | QFile::Text)) { + edit->setText(log.readAll()); + log.close(); + } +} + +void CrashHandlerDialog::accept() +{ + QDialog::accept(); +} + +void CrashHandlerDialog::reject() +{ + QDialog::reject(); +} diff --git a/app/dialog/crashhandler/crashhandler.h b/app/dialog/crashhandler/crashhandler.h new file mode 100644 index 000000000..80aed6143 --- /dev/null +++ b/app/dialog/crashhandler/crashhandler.h @@ -0,0 +1,19 @@ +#ifndef CRASHHANDLERDIALOG_H +#define CRASHHANDLERDIALOG_H + +#include + +class CrashHandlerDialog : public QDialog +{ + Q_OBJECT +public: + CrashHandlerDialog(const char* log_file); + +public slots: + virtual void accept() override; + + virtual void reject() override; + +}; + +#endif // CRASHHANDLERDIALOG_H diff --git a/app/dialog/crashhandler/crashhandlermain.cpp b/app/dialog/crashhandler/crashhandlermain.cpp new file mode 100644 index 000000000..945835d6d --- /dev/null +++ b/app/dialog/crashhandler/crashhandlermain.cpp @@ -0,0 +1,17 @@ +#include "crashhandler.h" + +#include + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + return 1; + } + + QApplication a(argc, argv); + + CrashHandlerDialog chd(argv[1]); + chd.open(); + + return a.exec(); +} diff --git a/app/main.cpp b/app/main.cpp index e4e7491d6..869e10ad7 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,11 +35,15 @@ extern "C" { #include #include "core.h" +#include "common/crashhandler.h" #include "common/debug.h" int main(int argc, char *argv[]) { av_log_set_level(AV_LOG_QUIET); + signal(SIGSEGV, crash_handler); + signal(SIGABRT, crash_handler); + // Set OpenGL display profile (3.2 Core) QSurfaceFormat format; format.setVersion(3, 2); From ff69e69be45f9ce6bc7596fafa1c5d717d260772 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 13:37:02 +1100 Subject: [PATCH 19/26] crashhandler: updated for linux --- app/common/crashhandler.cpp | 13 ++++++++----- app/dialog/about/about.cpp | 3 +++ app/main.cpp | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp index 8ed1579be..fdb9a6941 100644 --- a/app/common/crashhandler.cpp +++ b/app/common/crashhandler.cpp @@ -1,5 +1,6 @@ #include "crashhandler.h" +#include #include #include #include @@ -7,15 +8,17 @@ #include #include -#ifdef Q_OS_WINDOWS +#if defined(Q_OS_WINDOWS) #include #include #include #include +#elif defined(Q_OS_LINUX) +#include #endif void crash_handler(int sig) { - QString log_path = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath("olive_crash"); + QString log_path = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath(QStringLiteral("olive_crash")); QFile output(log_path); output.open(QFile::WriteOnly); @@ -105,13 +108,13 @@ void crash_handler(int sig) { size = backtrace(array, 10); // print out all the frames to stderr - fprintf(stderr, "Error: signal %d:\n", sig); - backtrace_symbols_fd(array, size, STDERR_FILENO); + backtrace_symbols_fd(array, size, output.handle()); #endif output.close(); - QProcess::startDetached(QStringLiteral("crashhandler"), {log_path}); + QString crash_handler_exe = QDir(qApp->applicationDirPath()).filePath(QStringLiteral("crashhandler")); + QProcess::startDetached(crash_handler_exe, {log_path}); exit(1); } diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index d406aa4a7..d881bf81b 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -28,6 +28,9 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent) { + char* test = nullptr; + test[250] = 69; + setWindowTitle(tr("About %1").arg(QApplication::applicationName())); QVBoxLayout* layout = new QVBoxLayout(this); diff --git a/app/main.cpp b/app/main.cpp index 869e10ad7..7c8df6577 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -33,6 +33,7 @@ extern "C" { #include #include +#include #include "core.h" #include "common/crashhandler.h" From 3ba27b5fc1926b8cd2595cfadbdf548e4f0add06 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 13:40:35 +1100 Subject: [PATCH 20/26] crashhandler: removed old test code --- app/common/crashhandler.cpp | 2 ++ app/dialog/about/about.cpp | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp index fdb9a6941..450046f08 100644 --- a/app/common/crashhandler.cpp +++ b/app/common/crashhandler.cpp @@ -100,6 +100,8 @@ void crash_handler(int sig) { } SymCleanup(process); +#elif defined(Q_OS_MAC) + // FIXME: No Mac backtrace support yet #elif defined(Q_OS_LINUX) void *array[10]; size_t size; diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index d881bf81b..d406aa4a7 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -28,9 +28,6 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent) { - char* test = nullptr; - test[250] = 69; - setWindowTitle(tr("About %1").arg(QApplication::applicationName())); QVBoxLayout* layout = new QVBoxLayout(this); From 15ac7f0e9df14fcff00a1fdcf9bea24d07bb7397 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 15:49:02 +1100 Subject: [PATCH 21/26] renderer: removed old signal that isn't used anymore --- app/render/backend/videorenderbackend.cpp | 17 ----------------- app/render/backend/videorenderbackend.h | 1 - app/render/backend/videorenderworker.cpp | 3 --- app/render/backend/videorenderworker.h | 8 +++----- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 6a7fe2e7a..964243e91 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -140,7 +140,6 @@ void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor) video_processor->SetOperatingMode(operating_mode_); - connect(video_processor, &VideoRenderWorker::CompletedFrame, this, &VideoRenderBackend::ThreadCompletedFrame, Qt::QueuedConnection); connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame, Qt::QueuedConnection); connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload, Qt::QueuedConnection); connect(video_processor, &VideoRenderWorker::HashAlreadyExists, this, &VideoRenderBackend::ThreadHashAlreadyExists, Qt::QueuedConnection); @@ -262,22 +261,6 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue() return TimeRange(frame_range.in(), frame_range.in()); } -void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value) -{ - if (!only_signal_last_frame_requested_ || last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) { - Q_UNUSED(job_time) - Q_UNUSED(value) - //EmitCachedFrameReady(path.in(), value, job_time); - } - - if (!(operating_mode_ & VideoRenderWorker::kDownloadOnly)) { - // If we're not downloading, the worker is done here - SetWorkerBusyState(static_cast(sender()), false); - - CacheNext(); - } -} - void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed) { SetWorkerBusyState(static_cast(sender()), false); diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 517232782..bd2b823c1 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -128,7 +128,6 @@ private: bool limit_caching_; private slots: - void ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value); void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed); void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash); void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index b7d386deb..1859f9cca 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -62,9 +62,6 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con // Find texture in hash QVariant texture = value.Get(NodeParam::kTexture); - // Signal that we have a frame in memory that could be shown right now - emit CompletedFrame(path, job_time, hash, texture); - // If we actually have a texture, download it into the disk cache if (!texture.isNull()) { Download(path.in(), texture, frame_cache_->CachePathName(hash, video_params_.format())); diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 0520f8412..b9ec19169 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -48,13 +48,11 @@ public: void SetOperatingMode(const OperatingMode& mode); signals: - void CompletedFrame(NodeDependency CurrentPath, qint64 job_time, QByteArray hash, QVariant value); + void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash, bool texture_existed); - void CompletedDownload(NodeDependency CurrentPath, qint64 job_time, QByteArray hash, bool texture_existed); + void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash); - void HashAlreadyBeingCached(NodeDependency CurrentPath, qint64 job_time, QByteArray hash); - - void HashAlreadyExists(NodeDependency CurrentPath, qint64 job_time, QByteArray hash); + void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash); void GeneratedFrame(const rational &time, FramePtr frame); From 566a0729c1754944a68f30a72128dad904ea493d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 15:49:26 +1100 Subject: [PATCH 22/26] timeruler: ignore cache signals if cache status isn't enabled Minor optimization. --- app/widget/timeruler/timeruler.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 4b831d836..a5ef88a9a 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -88,11 +88,13 @@ const int64_t &TimeRuler::GetTime() void TimeRuler::SetCacheStatusLength(const rational &length) { - cache_length_ = length; + if (show_cache_status_) { + cache_length_ = length; - dirty_cache_ranges_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX)); + dirty_cache_ranges_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX)); - update(); + update(); + } } void TimeRuler::SetTime(const int64_t &r) @@ -111,16 +113,20 @@ void TimeRuler::SetScroll(int s) void TimeRuler::CacheInvalidatedRange(const TimeRange& range) { - dirty_cache_ranges_.InsertTimeRange(range); + if (show_cache_status_) { + dirty_cache_ranges_.InsertTimeRange(range); - update(); + update(); + } } void TimeRuler::CacheTimeReady(const rational &time) { - dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_)); + if (show_cache_status_) { + dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_)); - update(); + update(); + } } void TimeRuler::paintEvent(QPaintEvent *) From 31edfdc28bb8a596cbbd5526aa1590e4556b03c8 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 28 Feb 2020 16:44:05 +1100 Subject: [PATCH 23/26] crashhandlerdialog: added system information and made text area read only --- app/dialog/crashhandler/crashhandler.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/dialog/crashhandler/crashhandler.cpp b/app/dialog/crashhandler/crashhandler.cpp index 0d1078cb3..ac3867795 100644 --- a/app/dialog/crashhandler/crashhandler.cpp +++ b/app/dialog/crashhandler/crashhandler.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -16,8 +17,15 @@ CrashHandlerDialog::CrashHandlerDialog(const char *log_file) "help resolve this."))); QTextEdit* edit = new QTextEdit(); + edit->setReadOnly(true); layout->addWidget(edit); + edit->append(QStringLiteral("Build Environment: %1 (%2)").arg(QSysInfo::buildCpuArchitecture(), QSysInfo::buildAbi())); + edit->append(QStringLiteral("Run Environment: %1").arg(QSysInfo::currentCpuArchitecture())); + edit->append(QStringLiteral("Kernel: %1 %2").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion())); + edit->append(QStringLiteral("System: %1 (%2 %3)").arg(QSysInfo::prettyProductName(), QSysInfo::productType(), QSysInfo::productVersion())); + edit->append(QString()); + QDialogButtonBox* buttons = new QDialogButtonBox(); // FIXME: Implement auto-reporting @@ -31,7 +39,13 @@ CrashHandlerDialog::CrashHandlerDialog(const char *log_file) QFile log(log_file); if (log.open(QFile::ReadOnly | QFile::Text)) { - edit->setText(log.readAll()); + edit->append(log.readAll()); + + QMetaObject::invokeMethod(edit->verticalScrollBar(), + "setValue", + Qt::QueuedConnection, + Q_ARG(int, 0)); + log.close(); } } From 2ec160525cc467ff39c6a135779846b5d2a18ed6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 29 Feb 2020 00:40:28 +1100 Subject: [PATCH 24/26] cmake: made OpenEXR a direct dependency since we use it for the caching system --- CMakeLists.txt | 2 ++ app/CMakeLists.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 498a83fea..ccb6e9478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,8 @@ find_package(OpenColorIO REQUIRED) find_package(OpenImageIO REQUIRED) +find_package(OpenEXR REQUIRED) + find_package(Qt5 5.6 REQUIRED COMPONENTS Core diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 353532f36..a6b6bd9d3 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -106,6 +106,7 @@ target_include_directories( ${FFMPEG_INCLUDE_DIRS} ${OCIO_INCLUDE_DIRS} ${OIIO_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} ) target_link_libraries( @@ -126,6 +127,7 @@ target_link_libraries( FFMPEG::swresample ${OCIO_LIBRARIES} ${OIIO_LIBRARIES} + ${OPENEXR_LIBRARIES} ) if (WIN32) From 163ad76456374d51b8516315ee67a35e2f9a4188 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 29 Feb 2020 01:00:17 +1100 Subject: [PATCH 25/26] renderer: rewrote frame compression algorithm This function was noticeably lagging the main thread while caching. The cause was OpenEXR's internal thread pool competing with our main thread. Since we have our own system of worker threads, its thread pool was unnecessary for caching, however for normal playback it was a useful optimization. Unfortunately OIIO (which we were using to save EXRs) didn't provide quite enough control over OpenEXR's threading behavior (only providing control for over the global thread pool and not on a per-image basis), so for caching we've switched to using OpenEXR directly. This has noticeably sped up the main thread while causing no noticeable slowdown to the caching process. --- app/main.cpp | 2 +- app/render/backend/videorenderframecache.cpp | 8 +- app/render/backend/videorenderworker.cpp | 101 ++++++++++++++----- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 7c8df6577..e8252bbbf 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -31,9 +31,9 @@ extern "C" { #include } +#include #include #include -#include #include "core.h" #include "common/crashhandler.h" diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/backend/videorenderframecache.cpp index 3df6aa615..deebefff4 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/backend/videorenderframecache.cpp @@ -126,9 +126,11 @@ QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const Pixel { QString ext; - if (pix_fmt == PixelFormat::PIX_FMT_RGBA8 || pix_fmt == PixelFormat::PIX_FMT_RGBA16U) { - // For some reason, integer EXRs are extremely slow to load, so we use TIFF instead. - ext = QStringLiteral("tiff"); + if (pix_fmt == PixelFormat::PIX_FMT_RGB8 + || pix_fmt == PixelFormat::PIX_FMT_RGBA8 + || pix_fmt == PixelFormat::PIX_FMT_RGB16U + || pix_fmt == PixelFormat::PIX_FMT_RGBA16U) { + ext = QStringLiteral("jpg"); } else { ext = QStringLiteral("exr"); } diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 1859f9cca..4c4221426 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -1,5 +1,10 @@ #include "videorenderworker.h" +#include +#include +#include +#include + #include "common/define.h" #include "common/functiontimer.h" #include "node/block/transition/transition.h" @@ -222,39 +227,89 @@ void VideoRenderWorker::CloseInternal() void VideoRenderWorker::Download(const rational& time, QVariant texture, QString filename) { - // Set up OIIO::ImageSpec for compressing cached images on disk - OIIO::ImageSpec spec(video_params().effective_width(), - video_params().effective_height(), - PixelFormat::ChannelCount(video_params().format()), - PixelFormat::GetOIIOTypeDesc(video_params().format())); - - if (video_params_.format() != PixelFormat::PIX_FMT_RGBA8 - && video_params_.format() != PixelFormat::PIX_FMT_RGBA16U) { - // Integer types don't use EXR (they use TIFF instead) because EXR is very slow with integer formats - spec.attribute("compression", "dwaa:200"); - } - if (operating_mode_ & kDownloadOnly) { TextureToBuffer(texture, download_buffer_.data()); - std::string working_fn_std = filename.toStdString(); + switch (video_params().format()) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + { + // Integer types are stored in JPEG which we run through OIIO - auto out = OIIO::ImageOutput::create(working_fn_std); + std::string fn_std = filename.toStdString(); - // Keep export to this thread only - out->threads(1); + auto out = OIIO::ImageOutput::create(fn_std); - if (out) { - out->open(working_fn_std, spec); - out->write_image(PixelFormat::GetOIIOTypeDesc(video_params().format()), download_buffer_.data()); - out->close(); + if (out) { + // Attempt to keep this write to one thread + out->threads(1); + + out->open(fn_std, OIIO::ImageSpec(video_params().effective_width(), + video_params().effective_height(), + PixelFormat::ChannelCount(video_params().format()), + PixelFormat::GetOIIOTypeDesc(video_params().format()))); + + out->write_image(PixelFormat::GetOIIOTypeDesc(video_params().format()), download_buffer_.data()); + + out->close(); #if OIIO_VERSION < 10903 - OIIO::ImageOutput::destroy(out); + OIIO::ImageOutput::destroy(out); #endif - } else { - qWarning() << "Failed to open output file:" << filename; + } else { + qCritical() << "Failed to write JPEG file:" << OIIO::geterror().c_str(); + } + break; + } + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + { + // Floating point types are stored in EXR + Imf::PixelType pix_type; + + if (video_params().format() == PixelFormat::PIX_FMT_RGB16F + || video_params().format() == PixelFormat::PIX_FMT_RGBA16F) { + pix_type = Imf::HALF; + } else { + pix_type = Imf::FLOAT; + } + + Imf::Header header(video_params().effective_width(), + video_params().effective_height()); + header.channels().insert("R", Imf::Channel(pix_type)); + header.channels().insert("G", Imf::Channel(pix_type)); + header.channels().insert("B", Imf::Channel(pix_type)); + header.channels().insert("A", Imf::Channel(pix_type)); + + header.compression() = Imf::DWAA_COMPRESSION; + header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); + + Imf::OutputFile out(filename.toUtf8(), header, 0); + + int bpc = PixelFormat::BytesPerChannel(video_params().format()); + + size_t xs = kRGBAChannels * bpc; + size_t ys = video_params().effective_width() * kRGBAChannels * bpc; + + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", Imf::Slice(pix_type, download_buffer_.data(), xs, ys)); + framebuffer.insert("G", Imf::Slice(pix_type, download_buffer_.data() + bpc, xs, ys)); + framebuffer.insert("B", Imf::Slice(pix_type, download_buffer_.data() + 2*bpc, xs, ys)); + framebuffer.insert("A", Imf::Slice(pix_type, download_buffer_.data() + 3*bpc, xs, ys)); + out.setFrameBuffer(framebuffer); + + out.writePixels(video_params().effective_height()); + break; + } + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + qCritical() << "Unable to cache invalid pixel format" << video_params().format(); + break; } } else { From b134ac877fc904bc762437e9e789a4d8d44f2f3c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 29 Feb 2020 01:17:41 +1100 Subject: [PATCH 26/26] cmake: use better FindOpenEXR.cmake script --- cmake/FindOpenEXR.cmake | 187 +++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/cmake/FindOpenEXR.cmake b/cmake/FindOpenEXR.cmake index f036c742e..2d71428ed 100644 --- a/cmake/FindOpenEXR.cmake +++ b/cmake/FindOpenEXR.cmake @@ -1,94 +1,99 @@ +# +# Copyright 2016 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +find_path(OPENEXR_INCLUDE_DIR + OpenEXR/half.h +HINTS + "${OPENEXR_LOCATION}" + "$ENV{OPENEXR_LOCATION}" +PATH_SUFFIXES + include/ +DOC + "OpenEXR headers path" +) + +if(OPENEXR_INCLUDE_DIR) + set(openexr_config_file "${OPENEXR_INCLUDE_DIR}/OpenEXR/OpenEXRConfig.h") + if(EXISTS ${openexr_config_file}) + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_STRING.*$") + string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MAJOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MINOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP}) + endif() +endif() + +foreach(OPENEXR_LIB + Half + Iex + Imath + IlmImf + IlmThread + ) + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION} + ${OPENEXR_LIB} + HINTS + "${OPENEXR_LOCATION}" + "$ENV{OPENEXR_LOCATION}" + PATH_SUFFIXES + lib/ + DOC + "OPENEXR's ${OPENEXR_LIB} library path" + ) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY) + list(APPEND OPENEXR_LIBRARIES ${OPENEXR_${OPENEXR_LIB}_LIBRARY}) + endif() +endforeach(OPENEXR_LIB) + +# So #include works +list(APPEND OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR}) +list(APPEND OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR}/OpenEXR) + include(FindPackageHandleStandardArgs) - -find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h) -find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR) - -file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H) - -string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}") -string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}") -set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}") - -include(SelectLibraryConfigurations) - -if(NOT OpenEXR_BASE_LIBRARY) - find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_BASE) -endif() - -if(NOT OpenEXR_UTIL_LIBRARY) - find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_UTIL) -endif() - -if(NOT OpenEXR_HALF_LIBRARY) - find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_HALF) -endif() - -if(NOT OpenEXR_IEX_LIBRARY) - find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_IEX) -endif() - -if(NOT OpenEXR_MATH_LIBRARY) - find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_MATH) -endif() - -if(NOT OpenEXR_THREAD_LIBRARY) - find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_THREAD) -endif() - -if(NOT OpenEXR_IEXMATH_LIBRARY) - find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX}) - find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}_d) - select_library_configurations(OpenEXR_IEXMATH) -endif() - -set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}") -set(OPENEXR_Half_LIBRARY "${OpenEXR_HALF_LIBRARY}") -set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}") -set(OPENEXR_Iex_LIBRARY "${OpenEXR_IEX_LIBRARY}") -set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}") -set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}") -set(OPENEXR_IlmImf_LIBRARY "${OpenEXR_BASE_LIBRARY}") -set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}") -set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}") - -set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}") - -set(OpenEXR_LIBRARIES - ${OpenEXR_LIBRARY} - ${OpenEXR_MATH_LIBRARY} - ${OpenEXR_IEXMATH_LIBRARY} - ${OpenEXR_UTIL_LIBRARY} - ${OpenEXR_HALF_LIBRARY} - ${OpenEXR_IEX_LIBRARY} - ${OpenEXR_THREAD_LIBRARY} +find_package_handle_standard_args(OpenEXR + REQUIRED_VARS + OPENEXR_INCLUDE_DIRS + OPENEXR_LIBRARIES + VERSION_VAR + OPENEXR_VERSION ) -set(OPENEXR_LIBRARIES - ${OPENEXR_HALF_LIBRARY} - ${OPENEXR_IEX_LIBRARY} - ${OPENEXR_IMATH_LIBRARY} - ${OPENEXR_ILMIMF_LIBRARY} - ${OPENEXR_ILMTHREAD_LIBRARY} -) - -set(OpenEXR_INCLUDE_DIR ${OpenEXR_INCLUDE_DIRS}) -set(OPENEXR_INCLUDE_DIRS ${OpenEXR_INCLUDE_DIRS}) -set(OPENEXR_INCLUDE_DIR ${OPENEXR_INCLUDE_PATHS}) - -FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS) - -if(OpenEXR_FOUND) - set(OPENEXR_FOUND 1) -endif()