diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 99521453c..847d6e426 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -44,6 +44,7 @@ set(OLIVE_SOURCES common/ratiodialog.cpp common/rational.h common/rational.cpp + common/threadsafemap.h common/threadedobject.h common/threadedobject.cpp common/timecodefunctions.h diff --git a/app/common/threadsafemap.h b/app/common/threadsafemap.h new file mode 100644 index 000000000..0d4a6bc7e --- /dev/null +++ b/app/common/threadsafemap.h @@ -0,0 +1,27 @@ +#ifndef THREADSAFEMAP_H +#define THREADSAFEMAP_H + +#include +#include + +template +class ThreadSafeMap +{ +public: + ThreadSafeMap() = default; + + void insert(K key, V value) + { + mutex_.lock(); + map_.insert(key, value); + mutex_.unlock(); + } + +private: + QMutex mutex_; + + QMap map_; + +}; + +#endif // THREADSAFEMAP_H diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 1b3c2b9fc..c569c0293 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -49,6 +49,7 @@ set(OLIVE_SOURCES render/renderprocessor.h render/renderprocessor.cpp render/shaderinfo.h + render/stillimagecache.h render/videoparams.h render/videoparams.cpp PARENT_SCOPE diff --git a/app/render/backend/opengl/openglrenderer.cpp b/app/render/backend/opengl/openglrenderer.cpp index a37a84055..8280f6518 100644 --- a/app/render/backend/opengl/openglrenderer.cpp +++ b/app/render/backend/opengl/openglrenderer.cpp @@ -24,6 +24,36 @@ OLIVE_NAMESPACE_ENTER +const QVector blit_vertices = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + + -1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f +}; + +const QVector blit_texcoords = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f +}; + +const QVector flipped_blit_texcoords = { + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f +}; + OpenGLRenderer::OpenGLRenderer(QObject* parent) : Renderer(parent), context_(nullptr) @@ -75,18 +105,45 @@ void OpenGLRenderer::PostInit() // Store OpenGL functions instance functions_ = context_->functions(); functions_->glBlendFunc(GL_ONE, GL_ZERO); + + // Set up framebuffer used for various things + functions_->glGenFramebuffers(1, &framebuffer_); + + // Set up vertex array object + vao_.create(); + + // Set up vertex buffer + vert_vbo_.create(); + vert_vbo_.bind(); + vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat)); + vert_vbo_.release(); + + // Set up fragment buffer + frag_vbo_.create(); + frag_vbo_.bind(); + frag_vbo_.allocate(blit_texcoords.constData(), blit_texcoords.size() * sizeof(GLfloat)); + frag_vbo_.release(); } void OpenGLRenderer::Destroy() { + // Delete vertex array object + vao_.destroy(); + + // Delete framebuffer + functions_->glDeleteFramebuffers(1, &framebuffer_); + + // Delete all shaders + qDeleteAll(shader_cache_); + shader_cache_.clear(); + + // Delete context if it belongs to us if (context_->parent() == this) { delete context_; } context_ = nullptr; - qDeleteAll(shader_cache_); - shader_cache_.clear(); - + // Destroy surface if we created it if (surface_.isValid()) { surface_.destroy(); } @@ -183,11 +240,11 @@ Renderer::TexturePtr OpenGLRenderer::ProcessShader(const Node *node, const TimeR } if (frag_code.isEmpty()) { - frag_code = OpenGLShader::CodeDefaultFragment(); + frag_code = Node::ReadFileAsString(QStringLiteral(":/shaders/default.frag")); } if (vert_code.isEmpty()) { - vert_code = OpenGLShader::CodeDefaultVertex(); + vert_code = Node::ReadFileAsString(QStringLiteral(":/shaders/default.vert")); } shader = new QOpenGLShaderProgram(this); @@ -354,8 +411,6 @@ Renderer::TexturePtr OpenGLRenderer::ProcessShader(const Node *node, const TimeR static_cast(params.width()), static_cast(params.height())); - shader->release(); - // Create the output textures PixelFormat::Format output_format = (input_textures_have_alpha || job.GetAlphaChannelRequired()) ? PixelFormat::GetFormatWithAlphaChannel(params.format()) @@ -393,14 +448,28 @@ Renderer::TexturePtr OpenGLRenderer::ProcessShader(const Node *node, const TimeR for (int i=0; iglActiveTexture(GL_TEXTURE0 + i); functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i)); - OpenGLRenderFunctions::PrepareToDraw(functions_); + PrepareInputTexture(job.GetBilinearFiltering()); } + // Bind vertex array object + vao_.bind(); + + // Set buffers + int vertex_location = shader->attributeLocation("a_position"); + vert_vbo_.bind(); + functions_->glEnableVertexAttribArray(vertex_location); + functions_->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + vert_vbo_.release(); + + int tex_location = shader->attributeLocation("a_texcoord"); + frag_vbo_.bind(); + functions_->glEnableVertexAttribArray(tex_location); + functions_->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + frag_vbo_.release(); + for (int iteration=0; iterationbind(); shader->setUniformValue("ove_iteration", iteration); - shader->release(); // Replace iterative input if (iteration == 0) { @@ -410,18 +479,22 @@ Renderer::TexturePtr OpenGLRenderer::ProcessShader(const Node *node, const TimeR output_tex = dst_refs[iteration%2]; functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); - functions_->glBindTexture(GL_TEXTURE_2D, input_tex->texture()->texture()); - OpenGLRenderFunctions::PrepareToDraw(functions_); + functions_->glBindTexture(GL_TEXTURE_2D, input_tex->id().value()); + PrepareInputTexture(job.GetBilinearFiltering()); } - buffer_.Attach(output_tex, true); - buffer_.Bind(); + functions_->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + functions_->glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + output_tex->id().value(), + 0); // Blit this texture through this shader - OpenGLRenderFunctions::Blit(shader); + functions_->glDrawArrays(GL_TRIANGLES, 0, blit_vertices.size() / 3); - buffer_.Release(); - buffer_.Detach(); + // Reset framebuffer to default + functions_->glBindFramebuffer(GL_FRAMEBUFFER, 0); } // Release any textures we bound before @@ -430,16 +503,15 @@ Renderer::TexturePtr OpenGLRenderer::ProcessShader(const Node *node, const TimeR functions_->glBindTexture(GL_TEXTURE_2D, 0); } + // Release vertex array object + vao_.release(); + + // Release shader + shader->release(); + return output_tex; } -/*VideoParams OpenGLRenderer::GetParamsFromTexture(QVariant texture) -{ - GLuint t = texture.value(); - - return texture_params_.value(t); -}*/ - GLint OpenGLRenderer::GetInternalFormat(PixelFormat::Format format) { switch (format) { @@ -501,4 +573,21 @@ GLenum OpenGLRenderer::GetPixelType(PixelFormat::Format format) return GL_INVALID_VALUE; } +void OpenGLRenderer::PrepareInputTexture(bool bilinear) +{ + if (bilinear) { + // Use mipmapped bilinear + functions_->glGenerateMipmap(GL_TEXTURE_2D); + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + // Use nearest + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglrenderer.h b/app/render/backend/opengl/openglrenderer.h index 5c6bbb1d2..00cf5275d 100644 --- a/app/render/backend/opengl/openglrenderer.h +++ b/app/render/backend/opengl/openglrenderer.h @@ -22,8 +22,10 @@ #define OPENGLCONTEXT_H #include +#include #include #include +#include #include #include "render/backend/renderer.h" @@ -60,7 +62,7 @@ public slots: const OLIVE_NAMESPACE::ShaderJob &job, const OLIVE_NAMESPACE::VideoParams ¶ms) override; - virtual QVariant TransformColor(QVariant texture, ColorProcessorPtr processor) override; + virtual TexturePtr TransformColor(Texture* texture, OLIVE_NAMESPACE::ColorProcessorPtr processor) override; private: static GLint GetInternalFormat(PixelFormat::Format format); @@ -69,12 +71,22 @@ private: static GLenum GetPixelType(PixelFormat::Format format); + void PrepareInputTexture(bool bilinear); + QOpenGLContext* context_; QOpenGLFunctions* functions_; QOffscreenSurface surface_; + QOpenGLVertexArrayObject vao_; + + QOpenGLBuffer vert_vbo_; + + QOpenGLBuffer frag_vbo_; + + GLuint framebuffer_; + QHash shader_cache_; }; diff --git a/app/render/backend/opengl/openglshader.cpp b/app/render/backend/opengl/openglshader.cpp deleted file mode 100644 index 7e76f619a..000000000 --- a/app/render/backend/opengl/openglshader.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "openglshader.h" - -#include -OLIVE_NAMESPACE_ENTER - -OpenGLShaderPtr OpenGLShader::Create() -{ - return std::make_shared(); -} - -OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const QString &shader_code) -{ - OpenGLShaderPtr program = Create(); - - // Add shaders to program - program->addShaderFromSourceCode(QOpenGLShader::Vertex, CodeDefaultVertex()); - program->addShaderFromSourceCode(QOpenGLShader::Fragment, CodeDefaultFragment(function_name, shader_code)); - program->link(); - - return program; -} - -// copied from source code to OCIODisplay -const int OCIO_LUT3D_EDGE_SIZE = 64; - -// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE -const int OCIO_NUM_3D_ENTRIES = 3*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE; - -OpenGLShaderPtr OpenGLShader::CreateOCIO(QOpenGLContext* ctx, - GLuint& lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated) -{ - QOpenGLExtraFunctions* xf = ctx->extraFunctions(); - - // Set up shader description - OCIO::GpuShaderDesc shaderDesc; - const char* ocio_func_name = "OCIODisplay"; - shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); - shaderDesc.setFunctionName(ocio_func_name); - shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); - - // Compute LUT - std::vector ocio_lut_data(OCIO_NUM_3D_ENTRIES); - processor->getGpuLut3D(&ocio_lut_data[0], shaderDesc); - - // Create LUT texture - xf->glGenTextures(1, &lut_texture); - - // Bind LUT - xf->glActiveTexture(GL_TEXTURE1); - xf->glBindTexture(GL_TEXTURE_3D, lut_texture); - - // Set texture parameters - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - // Allocate storage for texture - xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F, - OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, - 0, GL_RGB, GL_FLOAT, &ocio_lut_data[0]); - - // Create OCIO shader code - QString shader_text; - - // Workaround since OCIO doesn't support the GLSL version we use - shader_text.append(QStringLiteral("#define texture2D texture\n" - "#define texture3D texture\n")); - - // Append OCIO shader code - shader_text.append(processor->getGpuShaderText(shaderDesc)); - - QString shader_call; - - // Enforce alpha association - if (alpha_is_associated) { - - // If alpha is already associated, we'll need to disassociate and reassociate - shader_text.append("\n"); - - QString disassociate_func_name = "disassoc"; - shader_text.append(CodeAlphaDisassociate(disassociate_func_name)); - - QString reassociate_func_name = "reassoc"; - shader_text.append(CodeAlphaReassociate(reassociate_func_name)); - - // Make OCIO call pass through disassociate and reassociate function - shader_call = QStringLiteral("%3(%1(%2(col), ove_ociolut));").arg(ocio_func_name, - disassociate_func_name, - reassociate_func_name); - - } else { - - // If alpha is not already associated, we can just associate after OCIO - - // Add associate function - QString associate_func_name = "assoc"; - shader_text.append(CodeAlphaAssociate(associate_func_name)); - - // Make OCIO call pass through associate function - shader_call = QStringLiteral("%2(%1(col, ove_ociolut));").arg(ocio_func_name, associate_func_name); - - } - - // Add process() function, which GetPipeline() will call if specified - QString process_function_name = "process"; - shader_text.append(QStringLiteral("\n" - "uniform sampler3D ove_ociolut;\n" - "\n" - "vec4 %2(vec4 col) {\n" - " return %1\n" - "}\n").arg(shader_call, process_function_name)); - - - // Get pipeline-based shader to inject OCIO shader into - OpenGLShaderPtr shader = OpenGLShader::CreateDefault(process_function_name, shader_text); - - // Release LUT - xf->glBindTexture(GL_TEXTURE_3D, 0); - - xf->glActiveTexture(GL_TEXTURE0); - - return shader; -} - -QString OpenGLShader::CodeDefaultFragment(QString function_name, const QString &shader_code) -{ - // Create shader header - QString frag_code = QStringLiteral("#version 150\n" - "\n" - "#ifdef GL_ES\n" - "precision highp int;\n" - "precision highp float;\n" - "#endif\n" - "\n" - "uniform sampler2D ove_maintex;\n" - "uniform vec2 ove_resolution;\n" - "uniform bool ove_deinterlace;\n" - "\n" - "in vec2 ove_texcoord;\n" - "\n" - "out vec4 fragColor;\n" - "\n"); - - // Check if additional code was passed to this function, add it here - if (!function_name.isEmpty() && !shader_code.isEmpty()) { - - // If additional code was passed, add it and reference it in main(). - // - // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. - // The texture coordinate can be acquired through `ove_texcoord`. - - frag_code.append(shader_code); - - } else { - - // No function to call - function_name = QString(); - - } - - // Our function_name arg will either resolve to the function added to this or to nothing, in - // which case they'll just be benign brackets. - frag_code.append(QStringLiteral("\n" - "void main() {\n" - " vec2 using_texcoord = ove_texcoord;\n" - " if (ove_deinterlace) {\n" - " // A very basic deinterlace that halves the vertical\n" - " // resolution and linearly interpolates the two fields\n" - " // by reading the texture coord between them.\n" - " float half_vert = round(ove_resolution.y / 2.0);\n" - " using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert;\n" - " }\n" - " vec4 color = %1(texture(ove_maintex, using_texcoord));\n" - " fragColor = color;\n" - "}\n").arg(function_name)); - - return frag_code; -} - -QString OpenGLShader::CodeDefaultVertex() -{ - // Generate vertex shader - return QStringLiteral("#version 150\n" - "\n" - "#ifdef GL_ES\n" - "precision highp int;\n" - "precision highp float;\n" - "#endif\n" - "\n" - "uniform mat4 ove_mvpmat;\n" - "\n" - "in vec4 a_position;\n" - "in vec2 a_texcoord;\n" - "\n" - "out vec2 ove_texcoord;\n" - "\n" - "void main() {\n" - " gl_Position = ove_mvpmat * a_position;\n" - " ove_texcoord = a_texcoord;\n" - "}\n"); -} - -QString OpenGLShader::CodeAlphaDisassociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " if (col.a > 0.0) {\n" - " return vec4(col.rgb / col.a, col.a);" - " }\n" - " return col;\n" - "}\n").arg(function_name); -} - -QString OpenGLShader::CodeAlphaReassociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " if (col.a > 0.0) {\n" - " return vec4(col.rgb * col.a, col.a);" - " }\n" - " return col;\n" - "}\n").arg(function_name); -} - -QString OpenGLShader::CodeAlphaAssociate(const QString &function_name) -{ - return QStringLiteral("vec4 %1(vec4 col) {\n" - " return vec4(col.rgb * col.a, col.a);\n" - "}\n").arg(function_name); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglshader.h b/app/render/backend/opengl/openglshader.h deleted file mode 100644 index 452dc3c3d..000000000 --- a/app/render/backend/opengl/openglshader.h +++ /dev/null @@ -1,65 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef OPENGLSHADER_H -#define OPENGLSHADER_H - -#include -#include - -#include -namespace OCIO = OCIO_NAMESPACE::v1; - -#include "common/define.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLShader; -using OpenGLShaderPtr = std::shared_ptr; - -/** - * @brief A simple QOpenGLShaderProgram derivative with static functions for creating - */ -class OpenGLShader : public QOpenGLShaderProgram { -public: - OpenGLShader() = default; - - static OpenGLShaderPtr Create(); - - static OpenGLShaderPtr CreateDefault(const QString &function_name = QString(), - const QString &shader_code = QString()); - - static OpenGLShaderPtr CreateOCIO(QOpenGLContext* ctx, - GLuint& lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated); - - static QString CodeDefaultFragment(QString function_name = QString(), - const QString &shader_code = QString()); - static QString CodeDefaultVertex(); - static QString CodeAlphaDisassociate(const QString& function_name); - static QString CodeAlphaReassociate(const QString& function_name); - static QString CodeAlphaAssociate(const QString& function_name); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLSHADER_H diff --git a/app/render/backend/renderer.h b/app/render/backend/renderer.h index 7f3e3d334..e30bb0c9d 100644 --- a/app/render/backend/renderer.h +++ b/app/render/backend/renderer.h @@ -132,7 +132,7 @@ public slots: const OLIVE_NAMESPACE::ShaderJob &job, const OLIVE_NAMESPACE::VideoParams ¶ms) = 0; - virtual QVariant TransformColor(QVariant texture, OLIVE_NAMESPACE::ColorProcessorPtr processor) = 0; + virtual TexturePtr TransformColor(Texture* texture, OLIVE_NAMESPACE::ColorProcessorPtr processor) = 0; virtual void Render() = 0; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 9979b2b29..99f7f47be 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -80,6 +80,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, const rational ticket->setProperty("matrix", matrix); ticket->setProperty("mode", mode); ticket->setProperty("type", kTypeVideo); + ticket->setProperty("cache", viewer->video_frame_cache()->GetCacheDirectory()); // Queue appending the ticket and running the next job on our thread to make this function thread-safe QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection, diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index dc6d898d4..8c194b358 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -197,30 +197,59 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const TrackOutput *track, con QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational &input_time) { + Renderer::TexturePtr value = nullptr; + + // Check the still frame cache. On large frames such as high resolution still images, uploading + // and color managing them for every frame is a waste of time, so we implement a small cache here + // to optimize such a situation VideoStreamPtr video_stream = std::static_pointer_cast(stream); - rational time_match = (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time; - QString colorspace_match = video_stream->get_colorspace_match_string(); - - QVariant value; - bool found_cache = false; - const VideoParams& video_params = Node::ValueToPtr(ticket_->property("viewer"))->video_params(); + StillImageCache::Entry want_entry = {nullptr, + stream, + video_stream->get_colorspace_match_string(), + video_stream->premultiplied_alpha(), + video_params.divider(), + (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time}; - if (still_image_cache_.contains(stream.get())) { - const CachedStill& cs = still_image_cache_[stream.get()]; + still_image_cache_->mutex()->lock(); - if (cs.colorspace == colorspace_match - && cs.alpha_is_associated == video_stream->premultiplied_alpha() - && cs.divider == video_params.divider() - && cs.time == time_match) { - value = cs.texture; - found_cache = true; - } else { - still_image_cache_.remove(stream.get()); + foreach (const StillImageCache::Entry& e, still_image_cache_->entries()) { + if (StillImageCache::CompareEntryMetadata(want_entry, e)) { + // Found an exact match of the texture we want in the cache, use it instead of reading it + // ourselves + value = e.texture; + break; } } - if (!found_cache) { + if (!value) { + // Failed to find the texture, let's see if it's being generated by another processor + foreach (const StillImageCache::Entry& e, still_image_cache_->pending()) { + if (StillImageCache::CompareEntryMetadata(want_entry, e)) { + // An exact match of this texture is pending, let's wait for it + while (!value) { + // FIXME: Hacky way of waiting for other threads + still_image_cache_->mutex()->unlock(); + QThread::msleep(1); + still_image_cache_->mutex()->lock(); + + value = e.texture; + } + break; + } + } + } + + if (value) { + // Found the texture, we can release the cache now + still_image_cache_->mutex()->unlock(); + } else { + // Wasn't in still image cache, so we'll have to retrieve it from the decoder + + // Let other processors know we're getting this texture + still_image_cache_->PushPending(want_entry); + + still_image_cache_->mutex()->unlock(); DecoderPtr decoder = ResolveDecoderFromInput(stream); @@ -230,22 +259,26 @@ QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational & if (frame) { // Return a texture from the derived class + Renderer::TexturePtr unmanaged_texture = render_ctx_->CreateTexture(frame->video_params(), frame->data(), frame->linesize_pixels()); + + Renderer::TexturePtr managed_texture = render_ctx_->TransformColor(unmanaged_texture, ) + value = FootageFrameToTexture(stream, frame); - if (!value.isNull()) { - // Put this into the image cache instead - still_image_cache_.insert(stream.get(), {value, - colorspace_match, - video_stream->premultiplied_alpha(), - video_params .divider(), - time_match}); - } + still_image_cache_->mutex()->lock(); + + still_image_cache_->RemovePending(want_entry); + + // Put this into the image cache instead + want_entry.texture = value; + still_image_cache_->PushEntry(want_entry); + + still_image_cache_->mutex()->unlock(); } } - } - return value; + return QVariant::fromValue(value); } QVariant RenderProcessor::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) @@ -369,7 +402,9 @@ QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const Generat node->GenerateFrame(frame, job); - return CachedFrameToTexture(frame); + Renderer::TexturePtr texture = render_ctx_->CreateTexture(frame->video_params(), frame->data(), frame->linesize_pixels()); + + return QVariant::fromValue(texture); } QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time) @@ -393,7 +428,8 @@ QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time) f->video_params().interlacing(), video_params.divider())); - return CachedFrameToTexture(f); + Renderer::TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels()); + return QVariant::fromValue(texture); } } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index f41d4961d..bf6942770 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -23,6 +23,7 @@ #include "node/traverser.h" #include "render/backend/renderer.h" +#include "stillimagecache.h" #include "threading/threadticket.h" OLIVE_NAMESPACE_ENTER @@ -68,6 +69,8 @@ private: Renderer* render_ctx_; + StillImageCache* still_image_cache_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/shaderinfo.h b/app/render/shaderinfo.h index 1fa685e2b..8af331d9a 100644 --- a/app/render/shaderinfo.h +++ b/app/render/shaderinfo.h @@ -122,6 +122,7 @@ public: { iterations_ = 1; iterative_input_ = nullptr; + bilinear_ = true; } const QString& GetShaderID() const @@ -150,6 +151,16 @@ public: return iterative_input_; } + bool GetBilinearFiltering() const + { + return bilinear_; + } + + void SetBilinearFiltering(bool e) + { + bilinear_ = e; + } + private: QString id_; @@ -157,6 +168,8 @@ private: NodeInput* iterative_input_; + bool bilinear_; + }; class ShaderCode { diff --git a/app/render/stillimagecache.h b/app/render/stillimagecache.h new file mode 100644 index 000000000..9a0b17cb7 --- /dev/null +++ b/app/render/stillimagecache.h @@ -0,0 +1,83 @@ +#ifndef STILLIMAGECACHE_H +#define STILLIMAGECACHE_H + +#include + +#include "common/rational.h" +#include "project/item/footage/stream.h" +#include "render/backend/renderer.h" + +OLIVE_NAMESPACE_ENTER + +class StillImageCache +{ +public: + struct Entry { + Renderer::TexturePtr texture; + StreamPtr stream; + QString colorspace; + bool alpha_is_associated; + int divider; + rational time; + }; + + QMutex* mutex() + { + return &mutex_; + } + + const QVector& entries() const + { + return entries_; + } + + const QVector& pending() const + { + return pending_; + } + + static bool CompareEntryMetadata(const Entry& a, const Entry& b) + { + return (a.stream == b.stream + && a.colorspace == b.colorspace + && a.alpha_is_associated == b.alpha_is_associated + && a.divider == b.divider + && a.time == b.time); + } + + void PushPending(const Entry& e) + { + pending_.prepend(e); + } + + void PushEntry(const Entry& e) + { + entries_.prepend(e); + + if (entries_.size() > 8) { + entries_.removeLast(); + } + } + + void RemovePending(const Entry& e) + { + for (int i=0; i entries_; + + QVector pending_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // STILLIMAGECACHE_H diff --git a/app/shaders/default.frag b/app/shaders/default.frag new file mode 100644 index 000000000..62a34b668 --- /dev/null +++ b/app/shaders/default.frag @@ -0,0 +1,21 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +// Input texture +uniform sampler2D ove_maintex; + +// Input texture coordinate +in vec2 ove_texcoord; + +// Output color +out vec4 fragColor; + +void main() { + vec2 using_texcoord = ove_texcoord; + vec4 color = texture(ove_maintex, ove_texcoord); + fragColor = color; +} \ No newline at end of file diff --git a/app/shaders/default.vert b/app/shaders/default.vert new file mode 100644 index 000000000..2569ec9f2 --- /dev/null +++ b/app/shaders/default.vert @@ -0,0 +1,18 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +uniform mat4 ove_mvpmat; + +in vec4 a_position; +in vec2 a_texcoord; + +out vec2 ove_texcoord; + +void main() { + gl_Position = ove_mvpmat * a_position; + ove_texcoord = a_texcoord; +} \ No newline at end of file diff --git a/app/shaders/deinterlace.frag b/app/shaders/deinterlace.frag new file mode 100644 index 000000000..bda9c732f --- /dev/null +++ b/app/shaders/deinterlace.frag @@ -0,0 +1,26 @@ +#version 150 + +#ifdef GL_ES +precision highp int; +precision highp float; +#endif + +uniform sampler2D ove_maintex; +uniform vec2 ove_resolution; + +in vec2 ove_texcoord; + +out vec4 fragColor; + +void main() { + vec2 using_texcoord = ove_texcoord; + + // A very basic deinterlace that halves the vertical + // resolution and linearly interpolates the two fields + // by reading the texture coord between them. + float half_vert = round(ove_resolution.y / 2.0); + using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert; + + vec4 color = %1(texture(ove_maintex, using_texcoord)); + fragColor = color; +} diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 60afd5b97..210d1cb23 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -65,13 +65,13 @@ bool ExportTask::Run() // If a transformation matrix is applied to this video, create it here if (params_.video_scaling_method() != ExportParams::kStretch) { - QMatrix4x4 mat = ExportParams::GenerateMatrix(params_.video_scaling_method(), + // FIXME: Re-implement this + + /*QMatrix4x4 mat = ExportParams::GenerateMatrix(params_.video_scaling_method(), viewer()->video_params().width(), viewer()->video_params().height(), params_.video_params().width(), - params_.video_params().height()); - - // FIXME: Re-implement this + params_.video_params().height());*/ } // Create color processor diff --git a/app/widget/colorwheel/colorswatchwidget.cpp b/app/widget/colorwheel/colorswatchwidget.cpp index f243e46e2..be6722a83 100644 --- a/app/widget/colorwheel/colorswatchwidget.cpp +++ b/app/widget/colorwheel/colorswatchwidget.cpp @@ -22,8 +22,6 @@ #include -#include "render/backend/opengl/openglrenderfunctions.h" - OLIVE_NAMESPACE_ENTER ColorSwatchWidget::ColorSwatchWidget(QWidget *parent) : diff --git a/app/widget/colorwheel/colorswatchwidget.h b/app/widget/colorwheel/colorswatchwidget.h index de8c1a828..dc0a43f20 100644 --- a/app/widget/colorwheel/colorswatchwidget.h +++ b/app/widget/colorwheel/colorswatchwidget.h @@ -23,7 +23,6 @@ #include -#include "render/backend/opengl/openglshader.h" #include "render/color.h" #include "render/colorprocessor.h" diff --git a/app/widget/colorwheel/colorwheelwidget.h b/app/widget/colorwheel/colorwheelwidget.h index 17c739886..78ddb26c6 100644 --- a/app/widget/colorwheel/colorwheelwidget.h +++ b/app/widget/colorwheel/colorwheelwidget.h @@ -24,7 +24,6 @@ #include #include "colorswatchwidget.h" -#include "render/backend/opengl/openglshader.h" #include "render/color.h" OLIVE_NAMESPACE_ENTER diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index ff4f1296b..16e8b260b 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -36,7 +36,7 @@ WaveformScope::WaveformScope(QWidget* parent) : { } -OpenGLShaderPtr WaveformScope::CreateShader() +QVariant WaveformScope::CreateShader() { OpenGLShaderPtr pipeline = OpenGLShader::Create(); diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index 04a464e52..4aeebc105 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -32,7 +32,7 @@ public: WaveformScope(QWidget* parent = nullptr); protected: - virtual OpenGLShaderPtr CreateShader() override; + virtual QVariant CreateShader() override; virtual void DrawScope() override; diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 9c3a6ceba..7b98b3630 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -94,8 +94,6 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI painter->setPen(QColor(64, 64, 64)); TrackOutput* track = TrackOutput::TrackFromBlock(block_); if (track) { - QMutexLocker locker(track->waveform_lock()); - AudioVisualWaveform::DrawWaveform(painter, rect().toRect(), this->GetScale(), diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 430d8cbf7..354d622cf 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -22,10 +22,9 @@ #define VIEWERGLWIDGET_H #include +#include #include "node/node.h" -#include "render/backend/opengl/openglcolorprocessor.h" -#include "render/backend/opengl/openglshader.h" #include "render/color.h" #include "render/colormanager.h" #include "tool/tool.h"