From deff1142419a9fe8f8af7114f82c352227bbc58b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Aug 2019 12:30:42 +1000 Subject: [PATCH] media node upgrades to reference pixel format --- app/common/filefunctions.cpp | 12 ++ app/common/filefunctions.h | 2 + app/node/input/media/media.cpp | 135 ++++++++++++++---- app/node/input/media/media.h | 16 ++- app/node/output/viewer/viewer.cpp | 2 + app/node/processor/renderer/CMakeLists.txt | 8 +- app/node/processor/renderer/renderer.cpp | 127 ++++++++-------- app/node/processor/renderer/renderer.h | 22 ++- .../renderer/rendererdownloadthread.cpp | 105 ++++++++++++++ .../renderer/rendererdownloadthread.h | 34 +++++ .../renderer/rendererprocessthread.cpp | 88 ++++++++++++ .../renderer/rendererprocessthread.h | 55 +++++++ ...dererthread.cpp => rendererthreadbase.cpp} | 70 ++------- ...{rendererthread.h => rendererthreadbase.h} | 32 ++--- app/render/colorservice.cpp | 11 +- app/render/colorservice.h | 5 +- app/render/gl/shadergenerators.h | 6 +- app/render/renderframebuffer.cpp | 6 - app/render/renderinstance.cpp | 2 + app/render/rendertexture.cpp | 29 +++- app/render/rendertexture.h | 4 +- 21 files changed, 574 insertions(+), 197 deletions(-) create mode 100644 app/node/processor/renderer/rendererdownloadthread.cpp create mode 100644 app/node/processor/renderer/rendererdownloadthread.h create mode 100644 app/node/processor/renderer/rendererprocessthread.cpp create mode 100644 app/node/processor/renderer/rendererprocessthread.h rename app/node/processor/renderer/{rendererthread.cpp => rendererthreadbase.cpp} (51%) rename app/node/processor/renderer/{rendererthread.h => rendererthreadbase.h} (74%) diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index 9e23a5456..3ce46dace 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -61,3 +61,15 @@ QString GetMediaIndexFilename(const QString &filename) { return QDir(GetMediaIndexLocation()).filePath(filename); } + +QString GetMediaCacheLocation() +{ + QDir local_appdata_dir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); + + QDir media_cache_dir = local_appdata_dir.filePath("mediacache"); + + // Attempt to ensure this folder exists + media_cache_dir.mkpath("."); + + return media_cache_dir.absolutePath(); +} diff --git a/app/common/filefunctions.h b/app/common/filefunctions.h index a7a20bba1..f6b990555 100644 --- a/app/common/filefunctions.h +++ b/app/common/filefunctions.h @@ -29,4 +29,6 @@ QString GetMediaIndexLocation(); QString GetMediaIndexFilename(const QString& filename); +QString GetMediaCacheLocation(); + #endif // FILEFUNCTIONS_H diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 03697e2cd..a01fbfef0 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -34,8 +34,10 @@ // End test code MediaInput::MediaInput() : - //ocio_shader_(nullptr), - decoder_(nullptr) + decoder_(nullptr), + color_service_(nullptr), + pipeline_(nullptr), + ocio_texture_(0) { footage_input_ = new NodeInput("footage_in"); footage_input_->add_data_input(NodeInput::kFootage); @@ -72,9 +74,20 @@ QString MediaInput::Description() void MediaInput::Release() { - texture_.Destroy(); + internal_tex_.Destroy(); decoder_ = nullptr; + color_service_ = nullptr; + pipeline_ = nullptr; + + if (ocio_texture_ != 0) { + ocio_ctx_->functions()->glDeleteTextures(1, &ocio_texture_); + } +} + +NodeInput *MediaInput::matrix_input() +{ + return matrix_input_; } NodeOutput *MediaInput::texture_output() @@ -89,6 +102,8 @@ void MediaInput::SetFootage(Footage *f) QVariant MediaInput::Value(NodeOutput *output, const rational &time) { + bool alpha_is_associated = false; + if (output == texture_output_) { // Find the current Renderer instance RenderInstance* renderer = RendererProcessor::CurrentInstance(); @@ -126,42 +141,106 @@ QVariant MediaInput::Value(NodeOutput *output, const rational &time) return 0; } - RenderTexturePtr texture = std::make_shared(); + if (color_service_ == nullptr) { + // FIXME: Hardcoded values for texting + color_service_ = std::make_shared("srgb", OCIO::ROLE_SCENE_LINEAR); + } - texture->Create(renderer->context(), + // OpenColorIO v1's color transforms can be done on GPU, which improves performance but reduces accuracy. When + // online, we prefer accuracy over performance so we use the CPU path instead: + // NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever + if (renderer->mode() == olive::RenderMode::kOnline) { + // Convert to 32F, which is required for OpenColorIO's color transformation + frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F); + + if (alpha_is_associated) { + // FIXME: Unassociate alpha here if associated + } + + // Transform color to reference space + color_service_->ConvertFrame(frame); + + if (alpha_is_associated) { + // FIXME: Reassociate alpha here + } else { + // FIXME: Associate alpha here + } + } + + // We use an internal texture to bring the texture into GPU space before performing transformations + + // Ensure the texture is the accurate to the frame + if (internal_tex_.width() != frame->width() + || internal_tex_.height() != frame->height() + || internal_tex_.format() != frame->format()) { + internal_tex_.Destroy(); + } + + // Create or upload the new data to the texture + if (!internal_tex_.IsCreated()) { + internal_tex_.Create(renderer->context(), + frame->width(), + frame->height(), + static_cast(frame->format()), + frame->data()); + } else { + internal_tex_.Upload(frame->data()); + } + + // Create new texture in reference space to send throughout the rest of the graph + + RenderTexturePtr output_texture = std::make_shared(); + + output_texture->Create(renderer->context(), renderer->width(), renderer->height(), - static_cast(frame->format()), - frame->data()); + renderer->format(), + RenderTexture::kDoubleBuffer); - return QVariant::fromValue(texture); + // Using the transformation matrix, blit our internal texture (in frame format) to our output texture (in + // reference format) - /*renderer->buffer()->Upload(frame->data()); + if (renderer->mode() == olive::RenderMode::kOffline) { + // For offline rendering, OCIO's GPU path is acceptable: + // NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever - texture_output_->set_value(renderer->buffer()->texture());*/ + // Use an OCIO pipeline shader (which wraps in a default pipeline and will also handle alpha association) + if (pipeline_ == nullptr) { + /*pipeline_ = olive::ShaderGenerator::OCIOPipeline(renderer->context(), + ocio_texture_, // FIXME: A raw GLuint texture, should wrap this up + color_service_->GetProcessor(), + alpha_is_associated); - // Convert the frame to the Renderer format - // frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA16F); + // Used for cleanup later + ocio_ctx_ = renderer->context();*/ - // Convert the frame to the Renderer color space - //color_service_.ConvertFrame(frame); + pipeline_ = olive::ShaderGenerator::DefaultPipeline(); + } + } else if (pipeline_ == nullptr) { + // In online, the color transformation was performed on the CPU (see above), so we only need to blit + pipeline_ = olive::ShaderGenerator::DefaultPipeline(); + } - // Upload this frame to the GPU - /*if (buffer_.IsCreated()) { - buffer_.Upload(frame->data()); - } else { - buffer_.Create(QOpenGLContext::currentContext(), - static_cast(frame->format()), - frame->width(), - frame->height(), - frame->data()); - }*/ + // Draw onto the output texture using the renderer's framebuffer + renderer->buffer()->Attach(output_texture); + renderer->buffer()->Bind(); - // Draw according to matrix - // BLIT + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); - //texture_output_->set_value(tex_buf_.texture()); - // End test code + // Draw with the internal texture + internal_tex_.Bind(); + + // Use pipeline to blit using transformation matrix from input + QMatrix4x4 m; + olive::gl::Blit(pipeline_, false); + + // Release everything + internal_tex_.Release(); + renderer->buffer()->Detach(); + renderer->buffer()->Release(); + + return QVariant::fromValue(output_texture); } return 0; diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index 08b5469cc..8d9023fb5 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -26,11 +26,8 @@ #include "decoder/decoder.h" #include "node/node.h" #include "render/colorservice.h" - -// FIXME: Test code only #include "render/rendertexture.h" -#include "render/gl/shaderptr.h" -// End test code +#include "render/gl/shadergenerators.h" /** * @brief A node that imports an image @@ -48,6 +45,8 @@ public: virtual void Release() override; + NodeInput* matrix_input(); + NodeOutput* texture_output(); void SetFootage(Footage* f); @@ -62,11 +61,16 @@ private: NodeOutput* texture_output_; - RenderTexture texture_; + RenderTexture internal_tex_; DecoderPtr decoder_; - ColorService color_service_; + ColorServicePtr color_service_; + + ShaderPtr pipeline_; + + QOpenGLContext* ocio_ctx_; + GLuint ocio_texture_; }; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index a37f2012d..428643cfb 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -108,6 +108,8 @@ void ViewerOutput::ViewerTimeChanged(const rational &t) // Send the texture to the Viewer if (current_texture != nullptr) { attached_viewer_->SetTexture(current_texture->texture()); + } else { + attached_viewer_->SetTexture(0); } current_time_ = t; diff --git a/app/node/processor/renderer/CMakeLists.txt b/app/node/processor/renderer/CMakeLists.txt index 6f78b5cc4..fd148df72 100644 --- a/app/node/processor/renderer/CMakeLists.txt +++ b/app/node/processor/renderer/CMakeLists.txt @@ -18,7 +18,11 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} node/processor/renderer/renderer.h node/processor/renderer/renderer.cpp - node/processor/renderer/rendererthread.h - node/processor/renderer/rendererthread.cpp + node/processor/renderer/rendererthreadbase.h + node/processor/renderer/rendererthreadbase.cpp + node/processor/renderer/rendererdownloadthread.h + node/processor/renderer/rendererdownloadthread.cpp + node/processor/renderer/rendererprocessthread.h + node/processor/renderer/rendererprocessthread.cpp PARENT_SCOPE ) diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index 346a22036..33eb536f6 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -24,9 +24,12 @@ #include #include #include +#include #include #include +#include "common/filefunctions.h" + RendererProcessor::RendererProcessor() : started_(false), width_(0), @@ -70,6 +73,7 @@ void RendererProcessor::SetCacheName(const QString &s) GenerateCacheIDInternal(); } +#include QVariant RendererProcessor::Value(NodeOutput* output, const rational& time) { if (output == texture_output_) { @@ -89,55 +93,25 @@ QVariant RendererProcessor::Value(NodeOutput* output, const rational& time) } // FIXME: Test code only - return texture_input_->get_value(time); + QString fn = CachePathName(time); + if (QFileInfo::exists(fn)) { + QFile cache_img(fn); + + if (cache_img.open(QFile::ReadOnly)) { + + QByteArray bytes = cache_img.readAll(); + + master_texture_->Upload(bytes.constData()); + + cache_img.close(); + + return QVariant::fromValue(master_texture_); + } + } // End test code } return 0; - - - - - - // This Renderer node relies on a disk cache so this Process() function should be quite fast. Either it returns the - // cached frame or it returns nothing. - - // Perhaps it should lookahead to load textures into VRAM in advance? - - // Should it cache the final result or the result in an 8-bit image or a 16-bit intermediate image? - - /*qDebug() << QString("Requesting %1/%2").arg(QString::number(time.numerator()), QString::number(time.denominator())); - - if (cache_map_.contains(time)) { - qDebug() << " We have this frame!"; - } else { - qDebug() << " No frame at this address"; - cache_map_.insert(time, true); - }*/ - - - - - - // FIXME: Test code only - //GLuint tex = texture_input_->get_value(time).value(); - - //glReadPixels() - - //texture_output_->set_value(tex); - // End test code - - /* - // Ensure we have started - if (!started_) { - Start(); - - if (!started_) { - qWarning() << tr("An error occurred starting the Renderer node"); - return; - } - } - */ } void RendererProcessor::Release() @@ -152,9 +126,13 @@ void RendererProcessor::InvalidateCache(const rational &start_range, const ratio << "and" << end_range.toDouble(); - // FIXME: Snap start_range to timebase + // Snap start_range to timebase + double start_range_dbl = start_range.toDouble(); + double start_range_numf = start_range_dbl * static_cast(timebase_.denominator()); + int64_t start_range_numround = qFloor(start_range_numf/static_cast(timebase_.numerator())) * timebase_.numerator(); + rational true_start_range(start_range_numround, timebase_.denominator()); - for (rational r=start_range;r<=end_range;r+=timebase_) { + for (rational r=true_start_range;r<=end_range;r+=timebase_) { if (!cache_queue_.contains(r)) { cache_queue_.append(r); } @@ -196,10 +174,12 @@ void RendererProcessor::Start() return; } + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + threads_.resize(QThread::idealThreadCount()); for (int i=0;i(QOpenGLContext::currentContext(), width_, height_, format_, mode_); + threads_[i] = std::make_shared(ctx, width_, height_, format_, mode_); threads_[i]->StartThread(QThread::HighPriority); // Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the @@ -208,6 +188,14 @@ void RendererProcessor::Start() connect(threads_[i].get(), SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestSibling(NodeDependency)), Qt::QueuedConnection); } + // Create download thread + download_thread_ = std::make_shared(ctx, width_, height_, format_, mode_); + download_thread_->StartThread(QThread::HighPriority); + + // Create master texture (the one sent to the viewer) + master_texture_ = std::make_shared(); + master_texture_->Create(ctx, width_, height_, format_); + started_ = true; } @@ -219,11 +207,15 @@ void RendererProcessor::Stop() started_ = false; - for (int i=0;iCancel(); + foreach (RendererProcessThreadPtr process_thread, threads_) { + process_thread->Cancel(); } - threads_.clear(); + + download_thread_->Cancel(); + download_thread_ = nullptr; + + master_texture_ = nullptr; } void RendererProcessor::GenerateCacheIDInternal() @@ -253,20 +245,26 @@ void RendererProcessor::CacheNext() // Make sure cache has started Start(); - rational time_to_cache = cache_queue_.takeFirst(); + cache_frame_ = cache_queue_.takeFirst(); - qDebug() << "Caching" << time_to_cache.toDouble(); + qDebug() << "[RendererProcessor] Caching" << cache_frame_.toDouble(); // Run this probe in another thread master_thread_ = threads_.at(0).get(); - master_thread_->Queue(NodeDependency(texture_input_->get_connected_output(), time_to_cache), true); + master_thread_->Queue(NodeDependency(texture_input_->get_connected_output(), cache_frame_), true); caching_ = true; } -// FIXME: Test code only -#include "node/output/viewer/viewer.h" -// End test code +QString RendererProcessor::CachePathName(const rational &time) +{ + QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_); + this_cache_dir.mkpath("."); + + QString filename = QString("%1.%2.jpg").arg(QString::number(time.numerator()), QString::number(time.denominator())); + + return this_cache_dir.filePath(filename); +} void RendererProcessor::ThreadCallback() { @@ -275,6 +273,15 @@ void RendererProcessor::ThreadCallback() caching_ = false; // FIXME: Save the texture results here + RenderTexturePtr texture = texture_input_->get_value(cache_frame_).value(); + + + QString fn = CachePathName(cache_frame_); + if (texture == nullptr && QFileInfo::exists(fn)) { + QFile(fn).remove(); + } else { + download_thread_->Queue(texture, fn); + } CacheNext(); } @@ -290,14 +297,14 @@ void RendererProcessor::ThreadRequestSibling(NodeDependency dep) } } -RendererThread* RendererProcessor::CurrentThread() +RendererThreadBase* RendererProcessor::CurrentThread() { - return dynamic_cast(QThread::currentThread()); + return dynamic_cast(QThread::currentThread()); } RenderInstance *RendererProcessor::CurrentInstance() { - RendererThread* thread = CurrentThread(); + RendererThreadBase* thread = CurrentThread(); if (thread != nullptr) { return thread->render_instance(); diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index 1a864b3cd..1c0742d2c 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -26,7 +26,8 @@ #include "node/node.h" #include "render/pixelformat.h" #include "render/rendermodes.h" -#include "rendererthread.h" +#include "rendererdownloadthread.h" +#include "rendererprocessthread.h" /** * @brief A multithreaded OpenGL based renderer for node systems @@ -85,7 +86,7 @@ public: * This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if * the cast fails (e.g. if this function is called from the main thread rather than a RendererThread). */ - static RendererThread* CurrentThread(); + static RendererThreadBase* CurrentThread(); static RenderInstance* CurrentInstance(); @@ -120,9 +121,14 @@ private: void CacheNext(); /** - * @brief Internal list of RenderThreads + * @brief Return the path of the cached image at this time */ - QVector threads_; + QString CachePathName(const rational& time); + + /** + * @brief Internal list of RenderProcessThreads + */ + QVector threads_; /** * @brief Internal variable that contains whether the Renderer has started or not @@ -148,8 +154,14 @@ private: QString cache_name_; qint64 cache_time_; QString cache_id_; + bool caching_; - RendererThread* master_thread_; + RendererProcessThread* master_thread_; + rational cache_frame_; + + RendererDownloadThreadPtr download_thread_; + + RenderTexturePtr master_texture_; private slots: void ThreadCallback(); diff --git a/app/node/processor/renderer/rendererdownloadthread.cpp b/app/node/processor/renderer/rendererdownloadthread.cpp new file mode 100644 index 000000000..4c499b883 --- /dev/null +++ b/app/node/processor/renderer/rendererdownloadthread.cpp @@ -0,0 +1,105 @@ +#include "rendererdownloadthread.h" + +#include + +#include "render/pixelservice.h" + +RendererDownloadThread::RendererDownloadThread(QOpenGLContext *share_ctx, + const int &width, + const int &height, + const olive::PixelFormat &format, + const olive::RenderMode &mode) : + RendererThreadBase(share_ctx, width, height, format, mode) +{ + +} + +void RendererDownloadThread::Queue(RenderTexturePtr texture, const QString& fn) +{ + texture_queue_lock_.lock(); + + texture_queue_.append(texture); + download_filenames_.append(fn); + + wait_cond_.wakeAll(); + + texture_queue_lock_.unlock(); +} + +void RendererDownloadThread::ProcessLoop() +{ + QOpenGLFunctions* f = render_instance()->context()->functions(); + QOpenGLExtraFunctions* xf = render_instance()->context()->extraFunctions(); + + f->glGenFramebuffers(1, &read_buffer_); + + RenderTexturePtr working_texture; + QString working_filename; + + int buffer_size = PixelService::GetBufferSize(render_instance()->format(), + render_instance()->width(), + render_instance()->height()); + + uchar* data_buffer = new uchar[buffer_size]; + + while (!Cancelled()) { + // Check queue for textures to download (use mutex to prevent collisions) + texture_queue_lock_.lock(); + + do { + if (texture_queue_.isEmpty()) { + working_texture = nullptr; + } else { + working_texture = texture_queue_.takeFirst(); + working_filename = download_filenames_.takeFirst(); + } + + if (working_texture == nullptr) { + // Main waiting condition + wait_cond_.wait(&texture_queue_lock_); + } + } while (working_texture == nullptr); + + texture_queue_lock_.unlock(); + + // Download the texture + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, read_buffer_); + + xf->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + working_texture->texture(), + 0); + + PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(working_texture->format()); + + f->glReadPixels(0, + 0, + working_texture->width(), + working_texture->height(), + format_info.pixel_format, + format_info.pixel_type, + data_buffer); + + render_instance()->context()->extraFunctions()->glFramebufferTexture2D( + GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 + ); + + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + QFile data_dump(working_filename); + + if (data_dump.open(QFile::WriteOnly)) { + data_dump.write(reinterpret_cast(data_buffer), buffer_size); + + data_dump.close(); + } + + qDebug() << "Saved" << working_filename; + + } + + delete [] data_buffer; + + f->glDeleteFramebuffers(1, &read_buffer_); +} diff --git a/app/node/processor/renderer/rendererdownloadthread.h b/app/node/processor/renderer/rendererdownloadthread.h new file mode 100644 index 000000000..b950f8a79 --- /dev/null +++ b/app/node/processor/renderer/rendererdownloadthread.h @@ -0,0 +1,34 @@ +#ifndef RENDERERDOWNLOADTHREAD_H +#define RENDERERDOWNLOADTHREAD_H + +#include "rendererthreadbase.h" + +class RendererDownloadThread : public RendererThreadBase +{ + Q_OBJECT +public: + RendererDownloadThread(QOpenGLContext* share_ctx, + const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); + + void Queue(RenderTexturePtr texture, const QString &fn); + +protected: + virtual void ProcessLoop() override; + +private: + GLuint read_buffer_; + + QVector texture_queue_; + + QVector download_filenames_; + + QMutex texture_queue_lock_; + +}; + +using RendererDownloadThreadPtr = std::shared_ptr; + +#endif // RENDERERDOWNLOADTHREAD_H diff --git a/app/node/processor/renderer/rendererprocessthread.cpp b/app/node/processor/renderer/rendererprocessthread.cpp new file mode 100644 index 000000000..f1a4d0898 --- /dev/null +++ b/app/node/processor/renderer/rendererprocessthread.cpp @@ -0,0 +1,88 @@ +/*** + + 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 "rendererprocessthread.h" + +RendererProcessThread::RendererProcessThread(QOpenGLContext *share_ctx, + const int &width, + const int &height, + const olive::PixelFormat &format, + const olive::RenderMode &mode) : + RendererThreadBase(share_ctx, width, height, format, mode) +{ + +} + +bool RendererProcessThread::Queue(const NodeDependency& dep, bool wait) +{ + if (wait) { + // Wait for thread to be available + mutex_.lock(); + } else if (!mutex_.tryLock()) { + return false; + } + + // We can now change params without the other thread using them + path_ = dep; + + // Prepare to wait for thread to respond + caller_mutex_.lock(); + + // Wake up our main thread + wait_cond_.wakeAll(); + mutex_.unlock(); + + // Wait for thread to start before returning + wait_cond_.wait(&caller_mutex_); + caller_mutex_.unlock(); + + return true; +} + +void RendererProcessThread::ProcessLoop() +{ + while (!Cancelled()) { + // Main waiting condition + wait_cond_.wait(&mutex_); + + // Wake up main thread + caller_mutex_.lock(); + wait_cond_.wakeAll(); + caller_mutex_.unlock(); + + // Process the Node + NodeOutput* output_to_process = path_.node(); + Node* node_to_process = output_to_process->parent(); + + QList deps = node_to_process->RunDependencies(output_to_process, path_.time()); + + // Ask for other threads to run these deps while we're here + if (!deps.isEmpty()) { + for (int i=1;iget_value(path_.time()); + + emit FinishedPath(); + } +} diff --git a/app/node/processor/renderer/rendererprocessthread.h b/app/node/processor/renderer/rendererprocessthread.h new file mode 100644 index 000000000..bb875defd --- /dev/null +++ b/app/node/processor/renderer/rendererprocessthread.h @@ -0,0 +1,55 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RENDERERPROCESSTHREAD_H +#define RENDERERPROCESSTHREAD_H + +#include "rendererthreadbase.h" + +class RendererProcessThread : public RendererThreadBase +{ + Q_OBJECT +public: + RendererProcessThread(QOpenGLContext* share_ctx, + const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); + + bool Queue(const NodeDependency &dep, bool wait); + +protected: + virtual void ProcessLoop() override; + +signals: + void RequestSibling(NodeDependency dep); + + void FinishedPath(); + +private: + NodeDependency path_; + + rational time_; + +}; + +using RendererProcessThreadPtr = std::shared_ptr; + +#endif // RENDERERPROCESSTHREAD_H diff --git a/app/node/processor/renderer/rendererthread.cpp b/app/node/processor/renderer/rendererthreadbase.cpp similarity index 51% rename from app/node/processor/renderer/rendererthread.cpp rename to app/node/processor/renderer/rendererthreadbase.cpp index 9ba2e939c..34589638e 100644 --- a/app/node/processor/renderer/rendererthread.cpp +++ b/app/node/processor/renderer/rendererthreadbase.cpp @@ -18,11 +18,11 @@ ***/ -#include "rendererthread.h" +#include "rendererthreadbase.h" #include -RendererThread::RendererThread(QOpenGLContext *share_ctx, const int &width, const int &height, const olive::PixelFormat &format, const olive::RenderMode &mode) : +RendererThreadBase::RendererThreadBase(QOpenGLContext *share_ctx, const int &width, const int &height, const olive::PixelFormat &format, const olive::RenderMode &mode) : share_ctx_(share_ctx), cancelled_(false), width_(width), @@ -33,33 +33,7 @@ RendererThread::RendererThread(QOpenGLContext *share_ctx, const int &width, cons { } -bool RendererThread::Queue(const NodeDependency& dep, bool wait) -{ - if (wait) { - // Wait for thread to be available - mutex_.lock(); - } else if (!mutex_.tryLock()) { - return false; - } - - // We can now change params without the other thread using them - path_ = dep; - - // Prepare to wait for thread to respond - caller_mutex_.lock(); - - // Wake up our main thread - wait_cond_.wakeAll(); - mutex_.unlock(); - - // Wait for thread to start before returning - wait_cond_.wait(&caller_mutex_); - caller_mutex_.unlock(); - - return true; -} - -void RendererThread::Cancel() +void RendererThreadBase::Cancel() { // Escape main loop cancelled_ = true; @@ -68,12 +42,12 @@ void RendererThread::Cancel() wait(); } -RenderInstance *RendererThread::render_instance() +RenderInstance *RendererThreadBase::render_instance() { return render_instance_; } -void RendererThread::run() +void RendererThreadBase::run() { // Lock mutex for main loop mutex_.lock(); @@ -92,33 +66,8 @@ void RendererThread::run() if (instance.Start()) { // Main loop (use Cancel() to exit it) - while (!cancelled_) { - // Main waiting condition - wait_cond_.wait(&mutex_); + ProcessLoop(); - // Wake up main thread - caller_mutex_.lock(); - wait_cond_.wakeAll(); - caller_mutex_.unlock(); - - // Process the Node - NodeOutput* output_to_process = path_.node(); - Node* node_to_process = output_to_process->parent(); - - QList deps = node_to_process->RunDependencies(output_to_process, path_.time()); - - // Ask for other threads to run these deps while we're here - if (!deps.isEmpty()) { - for (int i=1;iget_value(path_.time()); - - emit FinishedPath(); - } } // Free all resources @@ -129,7 +78,12 @@ void RendererThread::run() mutex_.unlock(); } -void RendererThread::StartThread(QThread::Priority priority) +bool RendererThreadBase::Cancelled() +{ + return cancelled_; +} + +void RendererThreadBase::StartThread(QThread::Priority priority) { caller_mutex_.lock(); diff --git a/app/node/processor/renderer/rendererthread.h b/app/node/processor/renderer/rendererthreadbase.h similarity index 74% rename from app/node/processor/renderer/rendererthread.h rename to app/node/processor/renderer/rendererthreadbase.h index 6b3840cb8..fc1b8f302 100644 --- a/app/node/processor/renderer/rendererthread.h +++ b/app/node/processor/renderer/rendererthreadbase.h @@ -29,33 +29,28 @@ #include "node/node.h" #include "render/renderinstance.h" -class RendererThread : public QThread +class RendererThreadBase : public QThread { Q_OBJECT public: - RendererThread(QOpenGLContext* share_ctx, - const int& width, - const int& height, - const olive::PixelFormat& format, - const olive::RenderMode& mode); - - bool Queue(const NodeDependency &dep, bool wait); + RendererThreadBase(QOpenGLContext* share_ctx, + const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); void Cancel(); RenderInstance* render_instance(); - virtual void run() override; - void StartThread(Priority priority = InheritPriority); -signals: - void RequestSibling(NodeDependency dep); + virtual void run() override; - void FinishedPath(); +protected: + virtual void ProcessLoop() = 0; -private: - QOpenGLContext* share_ctx_; + bool Cancelled(); QWaitCondition wait_cond_; @@ -63,9 +58,8 @@ private: QMutex caller_mutex_; - NodeDependency path_; - - rational time_; +private: + QOpenGLContext* share_ctx_; bool cancelled_; @@ -81,6 +75,6 @@ private: }; -using RendererThreadPtr = std::shared_ptr; +using RendererThreadPtr = std::shared_ptr; #endif // RENDERTHREAD_H diff --git a/app/render/colorservice.cpp b/app/render/colorservice.cpp index b24d924ae..533d60bd8 100644 --- a/app/render/colorservice.cpp +++ b/app/render/colorservice.cpp @@ -2,13 +2,13 @@ const int kRGBAChannels = 4; -ColorService::ColorService() +ColorService::ColorService(const char* source_space, const char* dest_space) { // FIXME: Hardcoded values for testing purposes OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromFile("/run/media/matt/Home/OpenColorIO/ocio.configs.0.7v4/nuke-default/config.ocio"); - processor = config->getProcessor("srgb", - OCIO::ROLE_SCENE_LINEAR); + processor = config->getProcessor(source_space, + dest_space); } void ColorService::ConvertFrame(FramePtr f) @@ -17,3 +17,8 @@ void ColorService::ConvertFrame(FramePtr f) processor->apply(img); } + +OpenColorIO::v1::ConstProcessorRcPtr ColorService::GetProcessor() +{ + return processor; +} diff --git a/app/render/colorservice.h b/app/render/colorservice.h index 4cbf87cfd..5d2de01b1 100644 --- a/app/render/colorservice.h +++ b/app/render/colorservice.h @@ -6,14 +6,17 @@ namespace OCIO = OCIO_NAMESPACE::v1; #include "decoder/frame.h" +#include "render/gl/shadergenerators.h" class ColorService { public: - ColorService(); + ColorService(const char *source_space, const char *dest_space); void ConvertFrame(FramePtr f); + OCIO::ConstProcessorRcPtr GetProcessor(); + private: OCIO::ConstProcessorRcPtr processor; }; diff --git a/app/render/gl/shadergenerators.h b/app/render/gl/shadergenerators.h index 7aad6f375..d18702c14 100644 --- a/app/render/gl/shadergenerators.h +++ b/app/render/gl/shadergenerators.h @@ -41,9 +41,9 @@ public: static ShaderPtr DefaultPipeline(const QString &function_name = QString(), const QString &shader_code = QString()); static ShaderPtr OCIOPipeline(QOpenGLContext *ctx, - GLuint &lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated); + GLuint &lut_texture, + OCIO::ConstProcessorRcPtr processor, + bool alpha_is_associated); static QString AlphaDisassociateFunction(const QString& function_name); static QString AlphaReassociateFunction(const QString& function_name); diff --git a/app/render/renderframebuffer.cpp b/app/render/renderframebuffer.cpp index 0fede0011..1149355d5 100644 --- a/app/render/renderframebuffer.cpp +++ b/app/render/renderframebuffer.cpp @@ -127,16 +127,10 @@ void RenderFramebuffer::AttachInternal(GLuint tex) // bind framebuffer for attaching f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_); - // bind texture - f->glBindTexture(GL_TEXTURE_2D, tex); - context_->extraFunctions()->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0 ); - // release texture - f->glBindTexture(GL_TEXTURE_2D, 0); - // release framebuffer f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } diff --git a/app/render/renderinstance.cpp b/app/render/renderinstance.cpp index 87aa9dfc3..1b592c490 100644 --- a/app/render/renderinstance.cpp +++ b/app/render/renderinstance.cpp @@ -66,6 +66,8 @@ bool RenderInstance::Start() buffer_.Create(&ctx_); + ctx_.functions()->glViewport(0, 0, width_, height_); + return true; } diff --git a/app/render/rendertexture.cpp b/app/render/rendertexture.cpp index e09c76600..184532c89 100644 --- a/app/render/rendertexture.cpp +++ b/app/render/rendertexture.cpp @@ -20,6 +20,7 @@ #include "rendertexture.h" +#include #include #include "render/pixelservice.h" @@ -139,7 +140,7 @@ void RenderTexture::SwapFrontAndBack() back_texture_ = temp; } -void RenderTexture::Upload(void *data) +void RenderTexture::Upload(const void *data) { if (!IsCreated()) { qWarning() << tr("RenderTexture::Upload() called while it wasn't created"); @@ -163,16 +164,36 @@ void RenderTexture::Upload(void *data) Release(); } -void *RenderTexture::Download() const +uchar *RenderTexture::Download() const { if (!IsCreated()) { qWarning() << tr("RenderTexture::Download() called while it wasn't created"); return nullptr; } - // FIXME: Implement this + QOpenGLFunctions* f = context_->functions(); - return nullptr; + GLuint read_fbo; + + f->glGenFramebuffers(1, &read_fbo); + + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo); + + context_->extraFunctions()->glFramebufferTexture2D( + GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0 + ); + + PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(format_); + + uchar* data = new uchar[PixelService::GetBufferSize(format_, width_, height_)]; + + f->glReadPixels(0, 0, width_, height_, format_info.pixel_format, format_info.pixel_type, data); + + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + f->glDeleteFramebuffers(1, &read_fbo); + + return data; } void RenderTexture::CreateInternal(void *data) diff --git a/app/render/rendertexture.h b/app/render/rendertexture.h index 52590a243..6225adb89 100644 --- a/app/render/rendertexture.h +++ b/app/render/rendertexture.h @@ -64,9 +64,9 @@ public: const GLuint& back_texture() const; void SwapFrontAndBack(); - void Upload(void* data); + void Upload(const void *data); - void* Download() const; + uchar *Download() const; private: void CreateInternal(void *data = nullptr);