started porting renderer to new portable form
This commit is contained in:
@@ -14,15 +14,22 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(opengl)
|
||||
add_subdirectory(vulkan)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
render/backend/cudabackend.h
|
||||
render/backend/cudabackend.cpp
|
||||
render/backend/metalbackend.h
|
||||
render/backend/metalbackend.cpp
|
||||
render/backend/openclbackend.h
|
||||
render/backend/openclbackend.cpp
|
||||
render/backend/renderbackend.h
|
||||
render/backend/renderbackend.cpp
|
||||
render/backend/audiorenderbackend.h
|
||||
render/backend/audiorenderbackend.cpp
|
||||
render/backend/videorenderbackend.h
|
||||
render/backend/videorenderbackend.cpp
|
||||
|
||||
# FIXME: Remove these
|
||||
render/backend/videorendererdownloadthread.h
|
||||
render/backend/videorendererdownloadthread.cpp
|
||||
render/backend/videorendererprocessthread.h
|
||||
render/backend/videorendererprocessthread.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "audiorenderbackend.h"
|
||||
|
||||
AudioRenderBackend::AudioRenderBackend()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef AUDIORENDERBACKEND_H
|
||||
#define AUDIORENDERBACKEND_H
|
||||
|
||||
#include "renderbackend.h"
|
||||
|
||||
class AudioRenderBackend : public RenderBackend
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioRenderBackend();
|
||||
|
||||
public slots:
|
||||
virtual void InvalidateCache(const rational &start_range, const rational &end_range);
|
||||
};
|
||||
|
||||
#endif // AUDIORENDERBACKEND_H
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "cudabackend.h"
|
||||
|
||||
CUDABackend::CUDABackend()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef CUDABACKEND_H
|
||||
#define CUDABACKEND_H
|
||||
|
||||
|
||||
class CUDABackend
|
||||
{
|
||||
public:
|
||||
CUDABackend();
|
||||
};
|
||||
|
||||
#endif // CUDABACKEND_H
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "metalbackend.h"
|
||||
|
||||
MetalBackend::MetalBackend()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef METALBACKEND_H
|
||||
#define METALBACKEND_H
|
||||
|
||||
|
||||
class MetalBackend
|
||||
{
|
||||
public:
|
||||
MetalBackend();
|
||||
};
|
||||
|
||||
#endif // METALBACKEND_H
|
||||
@@ -1,128 +0,0 @@
|
||||
#include "openclbackend.h"
|
||||
|
||||
OpenCLBackend::OpenCLBackend()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool OpenCLBackend::Init()
|
||||
{
|
||||
// Get platform and device information
|
||||
cl_platform_id platform_id = nullptr;
|
||||
cl_uint ret_num_devices;
|
||||
cl_uint ret_num_platforms;
|
||||
cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
|
||||
ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_GPU, 1,
|
||||
&device_id_, &ret_num_devices);
|
||||
|
||||
if (ret != CL_SUCCESS) {
|
||||
qWarning() << "Failed to find compatible OpenCL device";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create an OpenCL context
|
||||
context_ = clCreateContext(nullptr, 1, &device_id_, nullptr, nullptr, &ret);
|
||||
|
||||
// Create a command queue
|
||||
//command_queue_ = clCreateCommandQueue(context_, device_id_, 0, &ret);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenCLBackend::GenerateFrame(const rational &time)
|
||||
{
|
||||
Q_UNUSED(time)
|
||||
/*
|
||||
// Copy the lists A and B to their respective memory buffers
|
||||
cl_int ret = clEnqueueWriteBuffer(command_queue_, a_mem_obj, CL_TRUE, 0,
|
||||
BITMAP_SZ, source_bmp, 0, NULL, NULL);
|
||||
|
||||
// Set the arguments of the kernel
|
||||
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&a_mem_obj);
|
||||
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&c_mem_obj);
|
||||
|
||||
// Execute the OpenCL kernel on the list
|
||||
size_t global_item_size = BITMAP_SZ; // Process the entire lists
|
||||
size_t local_item_size = 30; // Divide work items into groups of 64
|
||||
ret = clEnqueueNDRangeKernel(command_queue_, kernel, 1, NULL,
|
||||
&global_item_size, &local_item_size, 0, NULL, NULL);
|
||||
|
||||
if (ret != CL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to run\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Read the memory buffer C on the device to the local variable C
|
||||
ret = clEnqueueReadBuffer(command_queue_, c_mem_obj, CL_TRUE, 0,
|
||||
BITMAP_SZ, dest_bmp, 0, NULL, NULL);
|
||||
|
||||
// Clean up
|
||||
ret = clFlush(command_queue_);
|
||||
ret = clFinish(command_queue_);
|
||||
*/
|
||||
}
|
||||
|
||||
void OpenCLBackend::Close()
|
||||
{
|
||||
Decompile();
|
||||
|
||||
cl_int ret;
|
||||
|
||||
/*ret = clReleaseKernel(kernel);
|
||||
ret = clReleaseMemObject(a_mem_obj);
|
||||
ret = clReleaseMemObject(c_mem_obj);*/
|
||||
ret = clReleaseCommandQueue(command_queue_);
|
||||
ret = clReleaseContext(context_);
|
||||
}
|
||||
|
||||
void OpenCLBackend::Decompile()
|
||||
{
|
||||
clReleaseProgram(program_);
|
||||
}
|
||||
|
||||
void OpenCLBackend::Compile()
|
||||
{
|
||||
/*
|
||||
cl_int ret;
|
||||
|
||||
// Create memory buffers on the device for each vector
|
||||
cl_mem a_mem_obj = clCreateBuffer(context_, CL_MEM_READ_ONLY, BITMAP_SZ, nullptr, &ret);
|
||||
cl_mem c_mem_obj = clCreateBuffer(context_, CL_MEM_WRITE_ONLY, BITMAP_SZ, nullptr, &ret);
|
||||
|
||||
// Create a program from the kernel source
|
||||
program_ = clCreateProgramWithSource(context_,
|
||||
1,
|
||||
(const char **)&source_str,
|
||||
(const size_t *)&source_size,
|
||||
&ret);
|
||||
|
||||
// Build the program
|
||||
ret = clBuildProgram(program_, 1, &device_id_, nullptr, nullptr, nullptr);
|
||||
|
||||
if (ret != CL_SUCCESS) {
|
||||
// Decompile failed, the user will probably want to know why
|
||||
size_t error_len = 0;
|
||||
|
||||
clGetProgramBuildInfo(program_, device_id_, CL_PROGRAM_BUILD_LOG, 0, nullptr, &error_len);
|
||||
char* err = new char[error_len];
|
||||
clGetProgramBuildInfo(program_, device_id_, CL_PROGRAM_BUILD_LOG, error_len, err, nullptr);
|
||||
|
||||
SetError(err);
|
||||
//fprintf(stderr, "%s\n", err);
|
||||
|
||||
delete [] err;
|
||||
}
|
||||
|
||||
// Create the OpenCL kernel
|
||||
cl_kernel kernel = clCreateKernel(program_, "vector_add", &ret);
|
||||
*/
|
||||
}
|
||||
|
||||
void OpenCLBackend::GenerateCode()
|
||||
{
|
||||
if (viewer_node() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef OPENCLBACKEND_H
|
||||
#define OPENCLBACKEND_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenCL/opencl.h>
|
||||
#else
|
||||
#include <CL/cl.h>
|
||||
#endif
|
||||
|
||||
#include "renderbackend.h"
|
||||
|
||||
class OpenCLBackend : public RenderBackend
|
||||
{
|
||||
public:
|
||||
OpenCLBackend();
|
||||
|
||||
virtual bool Init() override;
|
||||
|
||||
virtual void GenerateFrame(const rational& time) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
void Compile();
|
||||
|
||||
protected:
|
||||
virtual void Decompile() override;
|
||||
|
||||
private:
|
||||
void GenerateCode();
|
||||
|
||||
cl_context context_;
|
||||
|
||||
cl_program program_;
|
||||
|
||||
cl_command_queue command_queue_;
|
||||
|
||||
cl_device_id device_id_;
|
||||
};
|
||||
|
||||
#endif // OPENCLBACKEND_H
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
render/backend/opengl/functions.h
|
||||
render/backend/opengl/functions.cpp
|
||||
render/backend/opengl/openglbackend.h
|
||||
render/backend/opengl/openglbackend.cpp
|
||||
render/backend/opengl/openglframebuffer.h
|
||||
render/backend/opengl/openglframebuffer.cpp
|
||||
render/backend/opengl/openglshader.h
|
||||
render/backend/opengl/openglshader.cpp
|
||||
render/backend/opengl/opengltexture.h
|
||||
render/backend/opengl/opengltexture.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,147 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "functions.h"
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QOpenGLBuffer>
|
||||
|
||||
const GLfloat 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 GLfloat blit_texcoords[] = {
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
|
||||
0.0, 0.0,
|
||||
0.0, 1.0,
|
||||
1.0, 1.0
|
||||
};
|
||||
|
||||
const GLfloat flipped_blit_texcoords[] = {
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
1.0, 0.0,
|
||||
|
||||
0.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 0.0
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Set up texture parameters and mipmap for drawing
|
||||
*
|
||||
* Internal function used just before drawing to allow mipmapped bilinear filtering when drawing textures small.
|
||||
*
|
||||
* @param f
|
||||
*
|
||||
* Currently active QOpenGLFunctions object (use context()->functions() if unsure).
|
||||
*/
|
||||
void PrepareToDraw(QOpenGLFunctions* f) {
|
||||
f->glGenerateMipmap(GL_TEXTURE_2D);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
}
|
||||
|
||||
void olive::gl::Blit(OpenGLShaderPtr pipeline, bool flipped, QMatrix4x4 matrix) {
|
||||
// FIXME: is currentContext() reliable here?
|
||||
QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions();
|
||||
|
||||
PrepareToDraw(func);
|
||||
|
||||
QOpenGLVertexArrayObject m_vao;
|
||||
m_vao.create();
|
||||
m_vao.bind();
|
||||
|
||||
QOpenGLBuffer m_vbo;
|
||||
m_vbo.create();
|
||||
m_vbo.bind();
|
||||
m_vbo.allocate(blit_vertices, 18 * static_cast<int>(sizeof(GLfloat)));
|
||||
m_vbo.release();
|
||||
|
||||
QOpenGLBuffer m_vbo2;
|
||||
m_vbo2.create();
|
||||
m_vbo2.bind();
|
||||
m_vbo2.allocate(flipped ? flipped_blit_texcoords : blit_texcoords, 12 * static_cast<int>(sizeof(GLfloat)));
|
||||
m_vbo2.release();
|
||||
|
||||
pipeline->bind();
|
||||
|
||||
pipeline->setUniformValue("mvp_matrix", matrix);
|
||||
pipeline->setUniformValue("texture", 0);
|
||||
|
||||
GLuint vertex_location = static_cast<GLuint>(pipeline->attributeLocation("a_position"));
|
||||
m_vbo.bind();
|
||||
func->glEnableVertexAttribArray(vertex_location);
|
||||
func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
m_vbo.release();
|
||||
|
||||
GLuint tex_location = static_cast<GLuint>(pipeline->attributeLocation("a_texcoord"));
|
||||
m_vbo2.bind();
|
||||
func->glEnableVertexAttribArray(tex_location);
|
||||
func->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
m_vbo2.release();
|
||||
|
||||
func->glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
pipeline->release();
|
||||
|
||||
m_vbo2.destroy();
|
||||
m_vbo.destroy();
|
||||
m_vao.release();
|
||||
m_vao.destroy();
|
||||
}
|
||||
|
||||
void olive::gl::OCIOBlit(OpenGLShaderPtr pipeline,
|
||||
GLuint lut,
|
||||
bool flipped,
|
||||
QMatrix4x4 matrix)
|
||||
{
|
||||
QOpenGLContext* ctx = QOpenGLContext::currentContext();
|
||||
QOpenGLExtraFunctions* xf = ctx->extraFunctions();
|
||||
|
||||
xf->glActiveTexture(GL_TEXTURE2);
|
||||
xf->glBindTexture(GL_TEXTURE_3D, lut);
|
||||
xf->glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
pipeline->bind();
|
||||
|
||||
pipeline->setUniformValue("tex2", 2);
|
||||
|
||||
olive::gl::Blit(pipeline, flipped, matrix);
|
||||
|
||||
pipeline->release();
|
||||
|
||||
xf->glActiveTexture(GL_TEXTURE2);
|
||||
xf->glBindTexture(GL_TEXTURE_3D, 0);
|
||||
xf->glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef GLFUNC_H
|
||||
#define GLFUNC_H
|
||||
|
||||
#include <QMatrix4x4>
|
||||
|
||||
#include "openglshader.h"
|
||||
|
||||
namespace olive {
|
||||
namespace gl {
|
||||
|
||||
/**
|
||||
* @brief Draw texture on screen
|
||||
*
|
||||
* @param pipeline
|
||||
*
|
||||
* Shader to use for the texture drawing
|
||||
*
|
||||
* @param flipped
|
||||
*
|
||||
* Draw the texture vertically flipped (defaults to FALSE)
|
||||
*
|
||||
* @param matrix
|
||||
*
|
||||
* Transformation matrix to use when drawing (defaults to no transform)
|
||||
*/
|
||||
void Blit(OpenGLShaderPtr pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
|
||||
|
||||
void OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLFUNC_H
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "openglbackend.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
OpenGLBackend::OpenGLBackend(QOpenGLContext *share_ctx) :
|
||||
share_ctx_(share_ctx)
|
||||
{
|
||||
}
|
||||
|
||||
OpenGLBackend::~OpenGLBackend()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool OpenGLBackend::Init()
|
||||
{
|
||||
threads_.resize(QThread::idealThreadCount());
|
||||
|
||||
// Some OpenGL implementations (notably wgl) require the context not to be current before sharing. We block the main
|
||||
// thread here to prevent QOpenGLWidget trying to reclaim the context before we're done
|
||||
QSurface* old_surface = share_ctx_->surface();
|
||||
share_ctx_->doneCurrent();
|
||||
|
||||
// Initiate one thread per CPU core
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
// Instantiate thread
|
||||
QThread* thread = new QThread(this);
|
||||
threads_.replace(i, thread);
|
||||
|
||||
// Create one processor object for each thread
|
||||
OpenGLProcessor* processor = new OpenGLProcessor(share_ctx_, thread);
|
||||
|
||||
// FIXME: Hardcoded values
|
||||
processor->SetParameters(VideoRenderingParams(viewer_node()->video_params(), olive::PIX_FMT_RGBA16F, olive::kOffline));
|
||||
|
||||
// Finally, we can move it to its own thread
|
||||
processor->moveToThread(thread);
|
||||
}
|
||||
|
||||
// We've finished creating shared contexts, we can now restore the context to its previous current state
|
||||
share_ctx_->makeCurrent(old_surface);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLBackend::GenerateFrame(const rational &time)
|
||||
{
|
||||
Q_UNUSED(time)
|
||||
}
|
||||
|
||||
void OpenGLBackend::Close()
|
||||
{
|
||||
Decompile();
|
||||
|
||||
// Clear all cores
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
delete threads_.at(i);
|
||||
}
|
||||
threads_.clear();
|
||||
}
|
||||
|
||||
bool OpenGLBackend::Compile()
|
||||
{
|
||||
Decompile();
|
||||
|
||||
if (viewer_node() == nullptr || !viewer_node()->texture_input()->IsConnected()) {
|
||||
// Nothing to be done, nothing to compile
|
||||
return true;
|
||||
}
|
||||
|
||||
// Traverse node graph compiling where necessary
|
||||
bool ret = TraverseCompiling(viewer_node());
|
||||
|
||||
if (ret) {
|
||||
qDebug() << "Compiled successfully!";
|
||||
} else {
|
||||
qDebug() << "Compile failed:" << GetError();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OpenGLBackend::Decompile()
|
||||
{
|
||||
foreach (const CompiledNode& info, compiled_nodes_) {
|
||||
delete info.program;
|
||||
}
|
||||
compiled_nodes_.clear();
|
||||
}
|
||||
|
||||
bool OpenGLBackend::TraverseCompiling(Node *n)
|
||||
{
|
||||
foreach (NodeParam* param, n->parameters()) {
|
||||
if (param->type() == NodeParam::kInput && param->IsConnected()) {
|
||||
NodeOutput* connected_output = static_cast<NodeInput*>(param)->get_connected_output();
|
||||
|
||||
// Generate the ID we'd use for this shader
|
||||
QString output_id = GenerateShaderID(connected_output);
|
||||
|
||||
// Check if we have a shader or not
|
||||
if (GetShaderFromID(output_id) == nullptr) {
|
||||
// Since we don't have a shader, compile one now
|
||||
QString node_code = connected_output->parent()->Code(connected_output);
|
||||
|
||||
// If the node has no code, it mustn't be GPU accelerated
|
||||
if (!node_code.isEmpty()) {
|
||||
// Since we have shader code, compile it now
|
||||
CompiledNode compiled_info;
|
||||
compiled_info.id = output_id;
|
||||
|
||||
if (!(compiled_info.program = new QOpenGLShaderProgram())) {
|
||||
SetError("Failed to create OpenGL shader object");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compiled_info.program->create()) {
|
||||
SetError("Failed to create OpenGL shader on device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compiled_info.program->addShaderFromSourceCode(QOpenGLShader::Fragment, node_code)) {
|
||||
SetError("Failed to add OpenGL shader code");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compiled_info.program->link()) {
|
||||
SetError("Failed to compile OpenGL shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
compiled_nodes_.append(compiled_info);
|
||||
|
||||
qDebug() << "Compiled" << compiled_info.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TraverseCompiling(connected_output->parent())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QOpenGLShaderProgram* OpenGLBackend::GetShaderFromID(const QString &id)
|
||||
{
|
||||
foreach (const CompiledNode& info, compiled_nodes_) {
|
||||
if (info.id == id) {
|
||||
return info.program;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString OpenGLBackend::GenerateShaderID(NodeOutput *output)
|
||||
{
|
||||
// Creates a unique identifier for this specific node and this specific output
|
||||
return QString("%1:%2").arg(output->parent()->id(), output->id());
|
||||
}
|
||||
|
||||
OpenGLProcessor::OpenGLProcessor(QOpenGLContext *share_ctx, QObject *parent) :
|
||||
QObject(parent),
|
||||
share_ctx_(share_ctx),
|
||||
ctx_(nullptr),
|
||||
functions_(nullptr)
|
||||
{
|
||||
surface_.create();
|
||||
}
|
||||
|
||||
OpenGLProcessor::~OpenGLProcessor()
|
||||
{
|
||||
surface_.destroy();
|
||||
}
|
||||
|
||||
bool OpenGLProcessor::IsStarted()
|
||||
{
|
||||
return ctx_ != nullptr;
|
||||
}
|
||||
|
||||
void OpenGLProcessor::SetParameters(const VideoRenderingParams &video_params)
|
||||
{
|
||||
video_params_ = video_params;
|
||||
}
|
||||
|
||||
void OpenGLProcessor::Init()
|
||||
{
|
||||
// Create context object
|
||||
ctx_ = new QOpenGLContext();
|
||||
|
||||
// Set share context
|
||||
ctx_->setShareContext(share_ctx_);
|
||||
|
||||
// Create OpenGL context (automatically destroys any existing if there is one)
|
||||
if (!ctx_->create()) {
|
||||
qWarning() << "Failed to create OpenGL context in thread" << thread();
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make context current on that surface
|
||||
if (!ctx_->makeCurrent(&surface_)) {
|
||||
qWarning() << "Failed to makeCurrent() on offscreen surface in thread" << thread();
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store OpenGL functions instance
|
||||
functions_ = ctx_->functions();
|
||||
|
||||
// Set up OpenGL parameters as necessary
|
||||
functions_->glEnable(GL_BLEND);
|
||||
UpdateViewportFromParams();
|
||||
|
||||
buffer_.Create(ctx_);
|
||||
}
|
||||
|
||||
void OpenGLProcessor::Close()
|
||||
{
|
||||
buffer_.Destroy();
|
||||
|
||||
functions_ = nullptr;
|
||||
delete ctx_;
|
||||
}
|
||||
|
||||
void OpenGLProcessor::UpdateViewportFromParams()
|
||||
{
|
||||
if (functions_ != nullptr && video_params_.is_valid()) {
|
||||
functions_->glViewport(0, 0, video_params_.effective_width(), video_params_.effective_height());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef OPENGLBACKEND_H
|
||||
#define OPENGLBACKEND_H
|
||||
|
||||
#include <memory>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
#include "../renderbackend.h"
|
||||
#include "openglframebuffer.h"
|
||||
|
||||
class OpenGLProcessor : public QObject {
|
||||
public:
|
||||
OpenGLProcessor(QOpenGLContext* share_ctx, QObject* parent = nullptr);
|
||||
|
||||
virtual ~OpenGLProcessor() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(OpenGLProcessor)
|
||||
|
||||
bool IsStarted();
|
||||
|
||||
void SetParameters(const VideoRenderingParams& video_params);
|
||||
|
||||
public slots:
|
||||
void Init();
|
||||
|
||||
void Close();
|
||||
|
||||
private:
|
||||
void UpdateViewportFromParams();
|
||||
|
||||
QOpenGLContext* share_ctx_;
|
||||
|
||||
QOpenGLContext* ctx_;
|
||||
QOffscreenSurface surface_;
|
||||
|
||||
QOpenGLFunctions* functions_;
|
||||
|
||||
OpenGLFramebuffer buffer_;
|
||||
|
||||
VideoRenderingParams video_params_;
|
||||
};
|
||||
|
||||
class OpenGLBackend : public RenderBackend
|
||||
{
|
||||
public:
|
||||
OpenGLBackend(QOpenGLContext* share_ctx);
|
||||
|
||||
virtual ~OpenGLBackend() override;
|
||||
|
||||
virtual bool Init() override;
|
||||
|
||||
virtual void GenerateFrame(const rational& time) override;
|
||||
|
||||
virtual void Close() override;
|
||||
|
||||
public slots:
|
||||
virtual bool Compile() override;
|
||||
|
||||
virtual void Decompile() override;
|
||||
|
||||
private:
|
||||
QOpenGLContext* share_ctx_;
|
||||
|
||||
struct CompiledNode {
|
||||
QString id;
|
||||
QOpenGLShaderProgram* program;
|
||||
};
|
||||
|
||||
bool TraverseCompiling(Node* n);
|
||||
|
||||
QOpenGLShaderProgram *GetShaderFromID(const QString& id);
|
||||
|
||||
QString GenerateShaderID(NodeOutput* output);
|
||||
|
||||
QList<CompiledNode> compiled_nodes_;
|
||||
|
||||
QVector<QThread*> threads_;
|
||||
QVector<OpenGLProcessor*> processors_;
|
||||
};
|
||||
|
||||
#endif // OPENGLBACKEND_H
|
||||
@@ -0,0 +1,157 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "openglframebuffer.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
|
||||
OpenGLFramebuffer::OpenGLFramebuffer() :
|
||||
context_(nullptr),
|
||||
buffer_(0),
|
||||
texture_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OpenGLFramebuffer::~OpenGLFramebuffer()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Create(QOpenGLContext *ctx)
|
||||
{
|
||||
if (ctx == nullptr) {
|
||||
qWarning() << tr("RenderTexture::Create was passed an invalid context");
|
||||
return;
|
||||
}
|
||||
|
||||
// Free any previous framebuffer
|
||||
Destroy();
|
||||
|
||||
context_ = ctx;
|
||||
|
||||
connect(context_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()));
|
||||
|
||||
// Create framebuffer object
|
||||
context_->functions()->glGenFramebuffers(1, &buffer_);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Destroy()
|
||||
{
|
||||
if (context_ != nullptr) {
|
||||
disconnect(context_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()));
|
||||
|
||||
context_->functions()->glDeleteFramebuffers(1, &buffer_);
|
||||
|
||||
buffer_ = 0;
|
||||
|
||||
context_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLFramebuffer::IsCreated() const
|
||||
{
|
||||
return (buffer_ > 0);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Bind()
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
context_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Release()
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
context_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Attach(RenderTexturePtr texture)
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
texture_ = texture;
|
||||
AttachInternal(texture_->texture(), false);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::AttachBackBuffer(RenderTexturePtr texture)
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
texture_ = texture;
|
||||
AttachInternal(texture_->back_texture(), true);
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::Detach()
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLFunctions* f = context_->functions();
|
||||
|
||||
// bind framebuffer for attaching
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
|
||||
|
||||
context_->extraFunctions()->glFramebufferTexture2D(
|
||||
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0
|
||||
);
|
||||
|
||||
// release framebuffer
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
texture_ = nullptr;
|
||||
}
|
||||
|
||||
const GLuint &OpenGLFramebuffer::buffer() const
|
||||
{
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
void OpenGLFramebuffer::AttachInternal(GLuint tex, bool clear)
|
||||
{
|
||||
Detach();
|
||||
|
||||
QOpenGLFunctions* f = context_->functions();
|
||||
|
||||
// bind framebuffer for attaching
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
|
||||
|
||||
context_->extraFunctions()->glFramebufferTexture2D(
|
||||
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0
|
||||
);
|
||||
|
||||
if (clear) {
|
||||
context_->functions()->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
context_->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// release framebuffer
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OPENGLFRAMEBUFFER_H
|
||||
#define OPENGLFRAMEBUFFER_H
|
||||
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#include "opengltexture.h"
|
||||
|
||||
class OpenGLFramebuffer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OpenGLFramebuffer();
|
||||
virtual ~OpenGLFramebuffer() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(OpenGLFramebuffer)
|
||||
|
||||
void Create(QOpenGLContext *ctx);
|
||||
|
||||
bool IsCreated() const;
|
||||
|
||||
void Bind();
|
||||
|
||||
void Release();
|
||||
|
||||
void Attach(RenderTexturePtr texture);
|
||||
|
||||
void AttachBackBuffer(RenderTexturePtr texture);
|
||||
|
||||
void Detach();
|
||||
|
||||
const GLuint& buffer() const;
|
||||
|
||||
public slots:
|
||||
void Destroy();
|
||||
|
||||
private:
|
||||
void AttachInternal(GLuint tex, bool clear);
|
||||
|
||||
QOpenGLContext* context_;
|
||||
|
||||
GLuint buffer_;
|
||||
|
||||
RenderTexturePtr texture_;
|
||||
};
|
||||
|
||||
#endif // OPENGLFRAMEBUFFER_H
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "openglshader.h"
|
||||
|
||||
OpenGLShader::OpenGLShader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const QString &shader_code)
|
||||
{
|
||||
OpenGLShaderPtr program = std::make_shared<OpenGLShader>();
|
||||
|
||||
// Add shaders to program
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Vertex, CodeDefaultVertex());
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Fragment, CodeDefaultFragment(function_name, shader_code));
|
||||
program->link();
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
QString OpenGLShader::CodeDefaultFragment(const QString &function_name, const QString &shader_code)
|
||||
{
|
||||
QString frag_code = QStringLiteral("#version 110\n"
|
||||
"\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision highp int;\n"
|
||||
"precision highp float;\n"
|
||||
"#endif\n"
|
||||
"\n"
|
||||
"uniform sampler2D texture;\n"
|
||||
"uniform bool color_only;\n"
|
||||
"uniform vec4 color_only_color;\n"
|
||||
"varying vec2 v_texcoord;\n"
|
||||
"\n");
|
||||
|
||||
// Finish the function with the main function
|
||||
|
||||
// Check if additional code was passed to this function, add it here
|
||||
if (shader_code.isEmpty()) {
|
||||
|
||||
// If not, just add a pure main() function
|
||||
|
||||
frag_code.append(QStringLiteral("\n"
|
||||
"void main() {\n"
|
||||
" if (color_only) {\n"
|
||||
" gl_FragColor = color_only_color;"
|
||||
" } else {\n"
|
||||
" vec4 color = texture2D(texture, v_texcoord);\n"
|
||||
" gl_FragColor = color;\n"
|
||||
" }\n"
|
||||
"}\n"));
|
||||
|
||||
} else {
|
||||
|
||||
// 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 `v_texcoord`.
|
||||
|
||||
frag_code.append(shader_code);
|
||||
|
||||
frag_code.append(QString(QStringLiteral("\n"
|
||||
"void main() {\n"
|
||||
" vec4 color = %1(texture2D(texture, v_texcoord));\n"
|
||||
" gl_FragColor = color;\n"
|
||||
"}\n")).arg(function_name));
|
||||
|
||||
}
|
||||
|
||||
return frag_code;
|
||||
}
|
||||
|
||||
QString OpenGLShader::CodeDefaultVertex()
|
||||
{
|
||||
// Generate vertex shader
|
||||
return QStringLiteral("#version 110\n"
|
||||
"\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision highp int;\n"
|
||||
"precision highp float;\n"
|
||||
"#endif\n"
|
||||
"\n"
|
||||
"uniform mat4 mvp_matrix;\n"
|
||||
"\n"
|
||||
"attribute vec4 a_position;\n"
|
||||
"attribute vec2 a_texcoord;\n"
|
||||
"\n"
|
||||
"varying vec2 v_texcoord;\n"
|
||||
"\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = mvp_matrix * a_position;\n"
|
||||
" v_texcoord = a_texcoord;\n"
|
||||
"}\n");
|
||||
}
|
||||
|
||||
QString OpenGLShader::CodeAlphaDisassociate(const QString &function_name)
|
||||
{
|
||||
return QString(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 QString(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 QString(QStringLiteral("vec4 %1(vec4 col) {\n"
|
||||
" return vec4(col.rgb * col.a, col.a);\n"
|
||||
"}\n")).arg(function_name);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef OPENGLSHADER_H
|
||||
#define OPENGLSHADER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
class OpenGLShader;
|
||||
using OpenGLShaderPtr = std::shared_ptr<OpenGLShader>;
|
||||
|
||||
class OpenGLShader : public QOpenGLShaderProgram {
|
||||
public:
|
||||
OpenGLShader();
|
||||
|
||||
static OpenGLShaderPtr CreateDefault(const QString &function_name = QString(),
|
||||
const QString &shader_code = QString());
|
||||
|
||||
static QString CodeDefaultFragment(const 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);
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif // OPENGLSHADER_H
|
||||
@@ -0,0 +1,243 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "opengltexture.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
|
||||
#include "render/pixelservice.h"
|
||||
|
||||
OpenGLTexture::OpenGLTexture() :
|
||||
context_(nullptr),
|
||||
texture_(0),
|
||||
back_texture_(0),
|
||||
width_(0),
|
||||
height_(0),
|
||||
format_(olive::PIX_FMT_INVALID)
|
||||
{
|
||||
}
|
||||
|
||||
OpenGLTexture::~OpenGLTexture()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool OpenGLTexture::IsCreated() const
|
||||
{
|
||||
return (texture_ != 0);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const olive::PixelFormat &format, void* data)
|
||||
{
|
||||
Create(ctx, width, height, format, kSingleBuffer, data);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const olive::PixelFormat &format, const OpenGLTexture::Type &type, void *data)
|
||||
{
|
||||
if (ctx == nullptr) {
|
||||
qWarning() << tr("RenderTexture::Create was passed an invalid context");
|
||||
return;
|
||||
}
|
||||
|
||||
Destroy();
|
||||
|
||||
context_ = ctx;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
|
||||
connect(context_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()));
|
||||
|
||||
// Create main texture
|
||||
CreateInternal(&texture_, data);
|
||||
|
||||
if (type == kDoubleBuffer) {
|
||||
// Create back texture
|
||||
CreateInternal(&back_texture_, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLTexture::Destroy()
|
||||
{
|
||||
if (context_ != nullptr) {
|
||||
disconnect(context_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()));
|
||||
|
||||
context_->functions()->glDeleteTextures(1, &texture_);
|
||||
texture_ = 0;
|
||||
|
||||
context_->functions()->glDeleteTextures(1, &back_texture_);
|
||||
back_texture_ = 0;
|
||||
|
||||
context_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLTexture::Bind()
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
qWarning() << "RenderTexture::Bind() called with an invalid context";
|
||||
return;
|
||||
}
|
||||
|
||||
context_->functions()->glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Release()
|
||||
{
|
||||
if (context_ == nullptr) {
|
||||
qWarning() << "RenderTexture::Release() called with an invalid context";
|
||||
return;
|
||||
}
|
||||
|
||||
context_->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
const int &OpenGLTexture::width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
const int &OpenGLTexture::height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
const olive::PixelFormat &OpenGLTexture::format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
|
||||
QOpenGLContext *OpenGLTexture::context() const
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
const GLuint &OpenGLTexture::texture() const
|
||||
{
|
||||
return texture_;
|
||||
}
|
||||
|
||||
const GLuint &OpenGLTexture::back_texture() const
|
||||
{
|
||||
return back_texture_;
|
||||
}
|
||||
|
||||
void OpenGLTexture::SwapFrontAndBack()
|
||||
{
|
||||
GLuint temp = texture_;
|
||||
texture_ = back_texture_;
|
||||
back_texture_ = temp;
|
||||
}
|
||||
|
||||
void OpenGLTexture::Upload(const void *data)
|
||||
{
|
||||
if (!IsCreated()) {
|
||||
qWarning() << tr("RenderTexture::Upload() called while it wasn't created");
|
||||
return;
|
||||
}
|
||||
|
||||
Bind();
|
||||
|
||||
PixelFormatInfo info = PixelService::GetPixelFormatInfo(format_);
|
||||
|
||||
context_->functions()->glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
width_,
|
||||
height_,
|
||||
info.pixel_format,
|
||||
info.pixel_type,
|
||||
data);
|
||||
|
||||
Release();
|
||||
}
|
||||
|
||||
uchar *OpenGLTexture::Download() const
|
||||
{
|
||||
if (!IsCreated()) {
|
||||
qWarning() << tr("RenderTexture::Download() called while it wasn't created");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QOpenGLFunctions* f = context_->functions();
|
||||
|
||||
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 OpenGLTexture::CreateInternal(GLuint* tex, void *data)
|
||||
{
|
||||
QOpenGLFunctions* f = context_->functions();
|
||||
|
||||
// Create texture
|
||||
f->glGenTextures(1, tex);
|
||||
|
||||
// Verify texture
|
||||
if (texture_ == 0) {
|
||||
qWarning() << tr("OpenGL texture creation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind texture
|
||||
f->glBindTexture(GL_TEXTURE_2D, *tex);
|
||||
|
||||
// Allocate storage for texture
|
||||
const PixelFormatInfo& bit_depth = PixelService::GetPixelFormatInfo(format_);
|
||||
|
||||
f->glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
bit_depth.internal_format,
|
||||
width_,
|
||||
height_,
|
||||
0,
|
||||
bit_depth.pixel_format,
|
||||
bit_depth.pixel_type,
|
||||
data
|
||||
);
|
||||
|
||||
// Set texture filtering to bilinear
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// Release texture
|
||||
f->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OPENGLTEXTURE_H
|
||||
#define OPENGLTEXTURE_H
|
||||
|
||||
#include <memory>
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
#include "render/pixelformat.h"
|
||||
|
||||
class OpenGLTexture : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Type {
|
||||
kSingleBuffer,
|
||||
kDoubleBuffer
|
||||
};
|
||||
|
||||
OpenGLTexture();
|
||||
virtual ~OpenGLTexture() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(OpenGLTexture)
|
||||
|
||||
void Create(QOpenGLContext* ctx, int width, int height, const olive::PixelFormat &format, void *data = nullptr);
|
||||
void Create(QOpenGLContext* ctx, int width, int height, const olive::PixelFormat &format, const Type& type, void *data = nullptr);
|
||||
|
||||
bool IsCreated() const;
|
||||
|
||||
void Bind();
|
||||
|
||||
void Release();
|
||||
|
||||
const int& width() const;
|
||||
|
||||
const int& height() const;
|
||||
|
||||
const olive::PixelFormat &format() const;
|
||||
|
||||
QOpenGLContext* context() const;
|
||||
|
||||
const GLuint& texture() const;
|
||||
const GLuint& back_texture() const;
|
||||
void SwapFrontAndBack();
|
||||
|
||||
void Upload(const void *data);
|
||||
|
||||
uchar *Download() const;
|
||||
|
||||
public slots:
|
||||
void Destroy();
|
||||
|
||||
private:
|
||||
void CreateInternal(GLuint *tex, void *data = nullptr);
|
||||
|
||||
QOpenGLContext* context_;
|
||||
|
||||
GLuint texture_;
|
||||
|
||||
GLuint back_texture_;
|
||||
|
||||
int width_;
|
||||
|
||||
int height_;
|
||||
|
||||
olive::PixelFormat format_;
|
||||
};
|
||||
|
||||
using RenderTexturePtr = std::shared_ptr<OpenGLTexture>;
|
||||
Q_DECLARE_METATYPE(RenderTexturePtr)
|
||||
|
||||
#endif // OPENGLTEXTURE_H
|
||||
@@ -8,7 +8,6 @@ RenderBackend::RenderBackend() :
|
||||
|
||||
RenderBackend::~RenderBackend()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const QString &RenderBackend::GetError()
|
||||
@@ -16,13 +15,89 @@ const QString &RenderBackend::GetError()
|
||||
return error_;
|
||||
}
|
||||
|
||||
void RenderBackend::set_viewer_node(ViewerOutput *viewer_node)
|
||||
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
{
|
||||
if (viewer_node_ != nullptr) {
|
||||
disconnect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(Compile()));
|
||||
}
|
||||
|
||||
viewer_node_ = viewer_node;
|
||||
|
||||
if (viewer_node_ != nullptr) {
|
||||
connect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(Compile()));
|
||||
}
|
||||
|
||||
Decompile();
|
||||
}
|
||||
|
||||
void RenderBackend::InvalidateCache(const rational &start_range, const rational &end_range)
|
||||
{
|
||||
if (!params_.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust range to min/max values
|
||||
rational start_range_adj = qMax(rational(0), start_range);
|
||||
rational end_range_adj = qMin(viewer_node_->Length(), end_range);
|
||||
|
||||
qDebug() << "Cache invalidated between"
|
||||
<< start_range_adj.toDouble()
|
||||
<< "and"
|
||||
<< end_range_adj.toDouble();
|
||||
|
||||
// Snap start_range to timebase
|
||||
double start_range_dbl = start_range_adj.toDouble();
|
||||
double start_range_numf = start_range_dbl * static_cast<double>(params_.time_base().denominator());
|
||||
int64_t start_range_numround = qFloor(start_range_numf/static_cast<double>(params_.time_base().numerator())) * params_.time_base().numerator();
|
||||
rational true_start_range(start_range_numround, params_.time_base().denominator());
|
||||
|
||||
for (rational r=true_start_range;r<=end_range_adj;r+=params_.time_base()) {
|
||||
// Try to order the queue from closest to the playhead to furthest
|
||||
rational last_time = last_time_requested_;
|
||||
|
||||
rational diff = r - last_time;
|
||||
|
||||
if (diff < 0) {
|
||||
// FIXME: Hardcoded number
|
||||
// If the number is before the playhead, we still prioritize its closeness but not nearly as much (5:1 in this
|
||||
// example)
|
||||
diff = qAbs(diff) * 5;
|
||||
}
|
||||
|
||||
bool contains = false;
|
||||
bool added = false;
|
||||
QLinkedList<rational>::iterator insert_iterator;
|
||||
|
||||
for (QLinkedList<rational>::iterator i = cache_queue_.begin();i != cache_queue_.end();i++) {
|
||||
rational compare = *i;
|
||||
|
||||
if (!added) {
|
||||
rational compare_diff = compare - last_time;
|
||||
|
||||
if (compare_diff > diff) {
|
||||
insert_iterator = i;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (compare == r) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!contains) {
|
||||
if (added) {
|
||||
cache_queue_.insert(insert_iterator, r);
|
||||
} else {
|
||||
cache_queue_.append(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
void RenderBackend::SetError(const QString &error)
|
||||
{
|
||||
error_ = error;
|
||||
|
||||
@@ -3,28 +3,33 @@
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
|
||||
class RenderBackend : QObject
|
||||
class RenderBackend : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenderBackend();
|
||||
virtual ~RenderBackend();
|
||||
virtual ~RenderBackend() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(RenderBackend)
|
||||
|
||||
virtual bool Init() = 0;
|
||||
|
||||
virtual void GenerateFrame(const rational& time) = 0;
|
||||
|
||||
virtual void GenerateSamples(const rational& time, const rational& length) = 0;
|
||||
|
||||
virtual void Close() = 0;
|
||||
|
||||
const QString& GetError();
|
||||
|
||||
void set_viewer_node(ViewerOutput* viewer_node);
|
||||
void SetViewerNode(ViewerOutput* viewer_node);
|
||||
|
||||
public slots:
|
||||
virtual void InvalidateCache(const rational &start_range, const rational &end_range) = 0;
|
||||
|
||||
virtual bool Compile() = 0;
|
||||
|
||||
protected:
|
||||
virtual void Decompile() = 0;
|
||||
|
||||
protected:
|
||||
void SetError(const QString& error);
|
||||
|
||||
ViewerOutput* viewer_node() const;
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "videorenderer.h"
|
||||
|
||||
#include <OpenImageIO/imageio.h>
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "render/gl/functions.h"
|
||||
#include "render/gl/shadergenerators.h"
|
||||
#include "render/pixelservice.h"
|
||||
|
||||
VideoRendererProcessor::VideoRendererProcessor(QObject *parent) :
|
||||
QObject(parent),
|
||||
started_(false),
|
||||
caching_(false),
|
||||
push_time_(-1),
|
||||
starting_(false),
|
||||
viewer_node_(nullptr)
|
||||
{
|
||||
// FIXME: Cache name should actually be the name of the sequence
|
||||
SetCacheName("Test");
|
||||
}
|
||||
|
||||
VideoRendererProcessor::~VideoRendererProcessor()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::SetCacheName(const QString &s)
|
||||
{
|
||||
cache_name_ = s;
|
||||
cache_time_ = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
GenerateCacheIDInternal();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range)
|
||||
{
|
||||
if (!params_.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust range to min/max values
|
||||
rational start_range_adj = qMax(rational(0), start_range);
|
||||
rational end_range_adj = qMin(viewer_node_->Length(), end_range);
|
||||
|
||||
qDebug() << "Cache invalidated between"
|
||||
<< start_range_adj.toDouble()
|
||||
<< "and"
|
||||
<< end_range_adj.toDouble();
|
||||
|
||||
// Snap start_range to timebase
|
||||
double start_range_dbl = start_range_adj.toDouble();
|
||||
double start_range_numf = start_range_dbl * static_cast<double>(params_.time_base().denominator());
|
||||
int64_t start_range_numround = qFloor(start_range_numf/static_cast<double>(params_.time_base().numerator())) * params_.time_base().numerator();
|
||||
rational true_start_range(start_range_numround, params_.time_base().denominator());
|
||||
|
||||
for (rational r=true_start_range;r<=end_range_adj;r+=params_.time_base()) {
|
||||
// Try to order the queue from closest to the playhead to furthest
|
||||
rational last_time = last_time_requested_;
|
||||
|
||||
rational diff = r - last_time;
|
||||
|
||||
if (diff < 0) {
|
||||
// FIXME: Hardcoded number
|
||||
// If the number is before the playhead, we still prioritize its closeness but not nearly as much (5:1 in this
|
||||
// example)
|
||||
diff = qAbs(diff) * 5;
|
||||
}
|
||||
|
||||
bool contains = false;
|
||||
bool added = false;
|
||||
QLinkedList<rational>::iterator insert_iterator;
|
||||
|
||||
for (QLinkedList<rational>::iterator i = cache_queue_.begin();i != cache_queue_.end();i++) {
|
||||
rational compare = *i;
|
||||
|
||||
if (!added) {
|
||||
rational compare_diff = compare - last_time;
|
||||
|
||||
if (compare_diff > diff) {
|
||||
insert_iterator = i;
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (compare == r) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!contains) {
|
||||
if (added) {
|
||||
cache_queue_.insert(insert_iterator, r);
|
||||
} else {
|
||||
cache_queue_.append(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::SetParameters(const VideoRenderingParams& params)
|
||||
{
|
||||
// Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again
|
||||
// next time this Node has to process anything.
|
||||
Stop();
|
||||
|
||||
// Set new parameters
|
||||
params_ = params;
|
||||
|
||||
// Regenerate the cache ID
|
||||
GenerateCacheIDInternal();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::Start()
|
||||
{
|
||||
if (started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLContext* ctx = QOpenGLContext::currentContext();
|
||||
|
||||
int background_thread_count = QThread::idealThreadCount();
|
||||
|
||||
// Some OpenGL implementations (notably wgl) require the context not to be current before sharing
|
||||
QSurface* old_surface = ctx->surface();
|
||||
ctx->doneCurrent();
|
||||
|
||||
threads_.resize(background_thread_count);
|
||||
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
threads_[i] = std::make_shared<RendererProcessThread>(this, ctx, params_);
|
||||
threads_[i]->StartThread(QThread::LowPriority);
|
||||
|
||||
// Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the
|
||||
// other threads
|
||||
connect(threads_.at(i).get(),
|
||||
SIGNAL(RequestSibling(NodeDependency)),
|
||||
this,
|
||||
SLOT(ThreadRequestSibling(NodeDependency)),
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// Connect first thread (master thread) to the callback
|
||||
connect(threads_.first().get(),
|
||||
SIGNAL(CachedFrame(RenderTexturePtr, const rational&, const QByteArray&)),
|
||||
this,
|
||||
SLOT(ThreadCallback(RenderTexturePtr, const rational&, const QByteArray&)),
|
||||
Qt::QueuedConnection);
|
||||
connect(threads_.first().get(),
|
||||
SIGNAL(FrameSkipped(const rational&, const QByteArray&)),
|
||||
this,
|
||||
SLOT(ThreadSkippedFrame(const rational&, const QByteArray&)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
download_threads_.resize(background_thread_count);
|
||||
|
||||
for (int i=0;i<download_threads_.size();i++) {
|
||||
// Create download thread
|
||||
download_threads_[i] = std::make_shared<VideoRendererDownloadThread>(ctx, params_);
|
||||
download_threads_[i]->StartThread(QThread::LowPriority);
|
||||
|
||||
connect(download_threads_[i].get(),
|
||||
SIGNAL(Downloaded(const QByteArray&)),
|
||||
this,
|
||||
SLOT(DownloadThreadComplete(const QByteArray&)),
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
last_download_thread_ = 0;
|
||||
|
||||
// Restore context now that thread creation is complete
|
||||
ctx->makeCurrent(old_surface);
|
||||
|
||||
// Create master texture (the one sent to the viewer)
|
||||
master_texture_ = std::make_shared<RenderTexture>();
|
||||
master_texture_->Create(ctx, params_.effective_width(), params_.effective_height(), params_.format());
|
||||
|
||||
// Create internal FBO for copying textures
|
||||
copy_buffer_.Create(ctx);
|
||||
copy_buffer_.Attach(master_texture_);
|
||||
copy_pipeline_ = olive::ShaderGenerator::DefaultPipeline();
|
||||
|
||||
cache_frame_load_buffer_.resize(PixelService::GetBufferSize(params_.format(), params_.effective_width(), params_.effective_height()));
|
||||
|
||||
started_ = true;
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::Stop()
|
||||
{
|
||||
if (!started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
started_ = false;
|
||||
|
||||
foreach (RendererDownloadThreadPtr download_thread_, download_threads_) {
|
||||
download_thread_->Cancel();
|
||||
}
|
||||
download_threads_.clear();
|
||||
|
||||
foreach (RendererProcessThreadPtr process_thread, threads_) {
|
||||
process_thread->Cancel();
|
||||
}
|
||||
threads_.clear();
|
||||
|
||||
copy_buffer_.Destroy();
|
||||
master_texture_ = nullptr;
|
||||
copy_pipeline_ = nullptr;
|
||||
|
||||
cache_frame_load_buffer_.clear();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::GenerateCacheIDInternal()
|
||||
{
|
||||
if (cache_name_.isEmpty() || !params_.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate an ID that is more or less guaranteed to be unique to this Sequence
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(cache_name_.toUtf8());
|
||||
hash.addData(QString::number(cache_time_).toUtf8());
|
||||
hash.addData(QString::number(params_.width()).toUtf8());
|
||||
hash.addData(QString::number(params_.height()).toUtf8());
|
||||
hash.addData(QString::number(params_.format()).toUtf8());
|
||||
hash.addData(QString::number(params_.divider()).toUtf8());
|
||||
|
||||
QByteArray bytes = hash.result();
|
||||
cache_id_ = bytes.toHex();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::CacheNext()
|
||||
{
|
||||
if (cache_queue_.isEmpty() || viewer_node_ == nullptr || caching_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure cache has started
|
||||
Start();
|
||||
|
||||
rational cache_frame = cache_queue_.takeFirst();
|
||||
|
||||
qDebug() << "Caching" << cache_frame.toDouble();
|
||||
|
||||
threads_.first()->Queue(NodeDependency(viewer_node_->texture_input()->get_connected_output(), cache_frame, cache_frame), true, false);
|
||||
|
||||
caching_ = true;
|
||||
}
|
||||
|
||||
QString VideoRendererProcessor::CachePathName(const QByteArray &hash)
|
||||
{
|
||||
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_);
|
||||
this_cache_dir.mkpath(".");
|
||||
|
||||
QString filename = QString("%1.exr").arg(QString(hash.toHex()));
|
||||
|
||||
return this_cache_dir.filePath(filename);
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::DeferMap(const rational &time, const QByteArray &hash)
|
||||
{
|
||||
deferred_maps_.append({time, hash});
|
||||
}
|
||||
|
||||
bool VideoRendererProcessor::HasHash(const QByteArray &hash)
|
||||
{
|
||||
return QFileInfo::exists(CachePathName(hash));
|
||||
}
|
||||
|
||||
bool VideoRendererProcessor::IsCaching(const QByteArray &hash)
|
||||
{
|
||||
cache_hash_list_mutex_.lock();
|
||||
|
||||
bool is_caching = cache_hash_list_.contains(hash);
|
||||
|
||||
cache_hash_list_mutex_.unlock();
|
||||
|
||||
return is_caching;
|
||||
}
|
||||
|
||||
bool VideoRendererProcessor::TryCache(const QByteArray &hash)
|
||||
{
|
||||
cache_hash_list_mutex_.lock();
|
||||
|
||||
bool is_caching = cache_hash_list_.contains(hash);
|
||||
|
||||
if (!is_caching) {
|
||||
cache_hash_list_.append(hash);
|
||||
}
|
||||
|
||||
cache_hash_list_mutex_.unlock();
|
||||
|
||||
return !is_caching;
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash)
|
||||
{
|
||||
// Threads are all done now, time to proceed
|
||||
caching_ = false;
|
||||
|
||||
DeferMap(time, hash);
|
||||
|
||||
if (texture != nullptr) {
|
||||
// We received a texture, time to start downloading it
|
||||
QString fn = CachePathName(hash);
|
||||
|
||||
download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture,
|
||||
fn,
|
||||
hash);
|
||||
|
||||
last_download_thread_++;
|
||||
} else {
|
||||
// There was no texture here, we must update the viewer
|
||||
DownloadThreadComplete(hash);
|
||||
}
|
||||
|
||||
// If the connected output is using this time, signal it to update
|
||||
if (last_time_requested_ == time) {
|
||||
|
||||
copy_buffer_.Bind();
|
||||
|
||||
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
|
||||
|
||||
if (texture == nullptr) {
|
||||
|
||||
// No texture, clear the master and push it
|
||||
f->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
f->glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
} else {
|
||||
texture->Bind();
|
||||
|
||||
f->glViewport(0, 0, master_texture_->width(), master_texture_->height());
|
||||
|
||||
olive::gl::Blit(copy_pipeline_);
|
||||
|
||||
texture->Release();
|
||||
}
|
||||
|
||||
copy_buffer_.Release();
|
||||
|
||||
push_time_ = time;
|
||||
|
||||
emit CachedFrameReady(time);
|
||||
}
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::ThreadRequestSibling(NodeDependency dep)
|
||||
{
|
||||
// Try to queue another thread to run this dep in advance
|
||||
for (int i=1;i<threads_.size();i++) {
|
||||
if (threads_.at(i)->Queue(dep, false, true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::ThreadSkippedFrame(const rational& time, const QByteArray& hash)
|
||||
{
|
||||
caching_ = false;
|
||||
|
||||
DeferMap(time, hash);
|
||||
|
||||
if (!IsCaching(hash)) {
|
||||
DownloadThreadComplete(hash);
|
||||
|
||||
// Signal output to update value
|
||||
emit CachedFrameReady(time);
|
||||
}
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::DownloadThreadComplete(const QByteArray &hash)
|
||||
{
|
||||
cache_hash_list_mutex_.lock();
|
||||
cache_hash_list_.removeAll(hash);
|
||||
cache_hash_list_mutex_.unlock();
|
||||
|
||||
for (int i=0;i<deferred_maps_.size();i++) {
|
||||
const HashTimeMapping& deferred = deferred_maps_.at(i);
|
||||
|
||||
if (deferred_maps_.at(i).hash == hash) {
|
||||
// Insert into hash map
|
||||
time_hash_map_.insert(deferred.time, deferred.hash);
|
||||
|
||||
deferred_maps_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoRendererThreadBase* VideoRendererProcessor::CurrentThread()
|
||||
{
|
||||
return dynamic_cast<VideoRendererThreadBase*>(QThread::currentThread());
|
||||
}
|
||||
|
||||
RenderInstance *VideoRendererProcessor::CurrentInstance()
|
||||
{
|
||||
VideoRendererThreadBase* thread = CurrentThread();
|
||||
|
||||
if (thread != nullptr) {
|
||||
return thread->render_instance();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RenderTexturePtr VideoRendererProcessor::GetCachedFrame(const rational &time)
|
||||
{
|
||||
last_time_requested_ = time;
|
||||
|
||||
if (push_time_ >= 0) {
|
||||
rational temp_push_time = push_time_;
|
||||
push_time_ = -1;
|
||||
|
||||
if (time == temp_push_time) {
|
||||
return master_texture_;
|
||||
}
|
||||
}
|
||||
|
||||
if (viewer_node_ == nullptr) {
|
||||
// Nothing is connected - nothing to show or render
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cache_id_.isEmpty()) {
|
||||
qWarning() << "No cache ID";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!params_.is_valid()) {
|
||||
qWarning() << "Invalid parameters";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Find frame in map
|
||||
if (time_hash_map_.contains(time)) {
|
||||
QString fn = CachePathName(time_hash_map_[time]);
|
||||
|
||||
if (QFileInfo::exists(fn)) {
|
||||
auto in = OIIO::ImageInput::open(fn.toStdString());
|
||||
|
||||
if (in) {
|
||||
in->read_image(PixelService::GetPixelFormatInfo(params_.format()).oiio_desc, cache_frame_load_buffer_.data());
|
||||
|
||||
in->close();
|
||||
|
||||
master_texture_->Upload(cache_frame_load_buffer_.data());
|
||||
|
||||
return master_texture_;
|
||||
} else {
|
||||
qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VideoRendererProcessor::SetViewerNode(ViewerOutput *viewer)
|
||||
{
|
||||
if (viewer_node_ != nullptr) {
|
||||
disconnect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&)));
|
||||
}
|
||||
|
||||
viewer_node_ = viewer;
|
||||
|
||||
if (viewer_node_ != nullptr) {
|
||||
connect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&)));
|
||||
|
||||
// FIXME: Hardcoded format, mode, and divider
|
||||
SetParameters(VideoRenderingParams(viewer_node_->video_params(), olive::PIX_FMT_RGBA16F, olive::kOffline, 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef VIDEORENDERERBACKEND_H
|
||||
#define VIDEORENDERERBACKEND_H
|
||||
|
||||
#include <QLinkedList>
|
||||
#include <QOpenGLTexture>
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/rendermodes.h"
|
||||
#include "videorendererdownloadthread.h"
|
||||
#include "videorendererprocessthread.h"
|
||||
|
||||
/**
|
||||
* @brief A multithreaded OpenGL based renderer for node systems
|
||||
*/
|
||||
class VideoRendererProcessor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Renderer Constructor
|
||||
*
|
||||
* Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop()
|
||||
* when the Renderer is about to be destroyed.
|
||||
*/
|
||||
VideoRendererProcessor(QObject* parent);
|
||||
|
||||
virtual ~VideoRendererProcessor() override;
|
||||
|
||||
void SetCacheName(const QString& s);
|
||||
|
||||
/**
|
||||
* @brief Set parameters of the Renderer
|
||||
*
|
||||
* The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers
|
||||
* to use. The Renderer must be stopped when calling this function.
|
||||
*
|
||||
* @param width
|
||||
*
|
||||
* Buffer width
|
||||
*
|
||||
* @param height
|
||||
*
|
||||
* Buffer height
|
||||
*
|
||||
* @param format
|
||||
*
|
||||
* Buffer pixel format
|
||||
*/
|
||||
void SetParameters(const VideoRenderingParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Return whether a frame with this hash already exists
|
||||
*/
|
||||
bool HasHash(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Return whether a frame is currently being cached
|
||||
*/
|
||||
bool IsCaching(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Check if a frame is currently being cached, and if not reserve it
|
||||
*/
|
||||
bool TryCache(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Return current instance of a RenderThread (or nullptr if there is none)
|
||||
*
|
||||
* 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 VideoRendererThreadBase* CurrentThread();
|
||||
|
||||
static RenderInstance* CurrentInstance();
|
||||
|
||||
RenderTexturePtr GetCachedFrame(const rational& time);
|
||||
|
||||
void SetViewerNode(ViewerOutput* viewer);
|
||||
|
||||
signals:
|
||||
void CachedFrameReady(const rational& time);
|
||||
|
||||
private:
|
||||
struct HashTimeMapping {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Allocate and start the multithreaded backend
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Terminate and deallocate the multithreaded backend
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* @brief Internal function for generating the cache ID
|
||||
*/
|
||||
void GenerateCacheIDInternal();
|
||||
|
||||
/**
|
||||
* @brief Function called when there are frames in the queue to cache
|
||||
*
|
||||
* This function is NOT thread-safe and should only be called in the main thread.
|
||||
*/
|
||||
void CacheNext();
|
||||
|
||||
bool ShouldPushTexture(const rational &time);
|
||||
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
QString CachePathName(const QByteArray &hash);
|
||||
|
||||
void DeferMap(const rational &time, const QByteArray &hash);
|
||||
|
||||
/**
|
||||
* @brief Internal list of RenderProcessThreads
|
||||
*/
|
||||
QVector<RendererProcessThreadPtr> threads_;
|
||||
|
||||
/**
|
||||
* @brief Internal variable that contains whether the Renderer has started or not
|
||||
*/
|
||||
bool started_;
|
||||
|
||||
VideoRenderingParams params_;
|
||||
|
||||
rational last_time_requested_;
|
||||
|
||||
QLinkedList<rational> cache_queue_;
|
||||
QString cache_name_;
|
||||
qint64 cache_time_;
|
||||
QString cache_id_;
|
||||
|
||||
bool caching_;
|
||||
QVector<uchar*> cache_frame_load_buffer_;
|
||||
|
||||
QVector<RendererDownloadThreadPtr> download_threads_;
|
||||
int last_download_thread_;
|
||||
|
||||
RenderTexturePtr master_texture_;
|
||||
rational push_time_;
|
||||
|
||||
OpenGLFramebuffer copy_buffer_;
|
||||
OpenGLShaderPtr copy_pipeline_;
|
||||
|
||||
QMap<rational, QByteArray> time_hash_map_;
|
||||
|
||||
QMutex cache_hash_list_mutex_;
|
||||
QVector<QByteArray> cache_hash_list_;
|
||||
|
||||
QList<HashTimeMapping> deferred_maps_;
|
||||
|
||||
bool starting_;
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
private slots:
|
||||
void InvalidateCache(const rational &start_range, const rational &end_range);
|
||||
|
||||
void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
|
||||
|
||||
void ThreadRequestSibling(NodeDependency dep);
|
||||
|
||||
void ThreadSkippedFrame(const rational &time, const QByteArray &hash);
|
||||
|
||||
void DownloadThreadComplete(const QByteArray &hash);
|
||||
|
||||
};
|
||||
|
||||
#endif // VIDEORENDERERBACKEND_H
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "videorendererdownloadthread.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFloat16>
|
||||
#include <OpenImageIO/imageio.h>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "render/pixelservice.h"
|
||||
|
||||
VideoRendererDownloadThread::VideoRendererDownloadThread(QOpenGLContext *share_ctx,
|
||||
const VideoRenderingParams& params) :
|
||||
VideoRendererThreadBase(share_ctx, params),
|
||||
cancelled_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void VideoRendererDownloadThread::Queue(RenderTexturePtr texture, const QString& fn, const QByteArray &hash)
|
||||
{
|
||||
texture_queue_lock_.lock();
|
||||
|
||||
texture_queue_.append({texture, fn, hash});
|
||||
|
||||
wait_cond_.wakeAll();
|
||||
|
||||
texture_queue_lock_.unlock();
|
||||
}
|
||||
|
||||
void VideoRendererDownloadThread::Cancel()
|
||||
{
|
||||
cancelled_ = true;
|
||||
|
||||
texture_queue_lock_.lock();
|
||||
wait_cond_.wakeAll();
|
||||
texture_queue_lock_.unlock();
|
||||
|
||||
wait();
|
||||
}
|
||||
|
||||
void VideoRendererDownloadThread::ProcessLoop()
|
||||
{
|
||||
QOpenGLFunctions* f = render_instance()->context()->functions();
|
||||
QOpenGLExtraFunctions* xf = render_instance()->context()->extraFunctions();
|
||||
|
||||
f->glGenFramebuffers(1, &read_buffer_);
|
||||
|
||||
DownloadQueueEntry entry;
|
||||
|
||||
int buffer_size = PixelService::GetBufferSize(render_instance()->params().format(),
|
||||
render_instance()->params().width(),
|
||||
render_instance()->params().height());
|
||||
|
||||
QVector<uchar> data_buffer;
|
||||
data_buffer.resize(buffer_size);
|
||||
|
||||
PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(render_instance()->params().format());
|
||||
|
||||
// Set up OIIO::ImageSpec for compressing cached images on disk
|
||||
OIIO::ImageSpec spec(render_instance()->params().width(), render_instance()->params().height(), kRGBAChannels, format_info.oiio_desc);
|
||||
spec.attribute("compression", "dwaa:200");
|
||||
|
||||
while (!cancelled_) {
|
||||
// Check queue for textures to download (use mutex to prevent collisions)
|
||||
texture_queue_lock_.lock();
|
||||
|
||||
while (texture_queue_.isEmpty()) {
|
||||
// Main waiting condition
|
||||
wait_cond_.wait(&texture_queue_lock_);
|
||||
|
||||
if (cancelled_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cancelled_) {
|
||||
texture_queue_lock_.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
entry = texture_queue_.takeFirst();
|
||||
|
||||
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,
|
||||
entry.texture->texture(),
|
||||
0);
|
||||
|
||||
f->glReadPixels(0,
|
||||
0,
|
||||
entry.texture->width(),
|
||||
entry.texture->height(),
|
||||
format_info.pixel_format,
|
||||
format_info.pixel_type,
|
||||
data_buffer.data());
|
||||
|
||||
xf->glFramebufferTexture2D(GL_READ_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0);
|
||||
|
||||
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
std::string working_fn_std = entry.filename.toStdString();
|
||||
|
||||
std::unique_ptr<OIIO::ImageOutput> out = OIIO::ImageOutput::create(working_fn_std);
|
||||
|
||||
if (out) {
|
||||
out->open(working_fn_std, spec);
|
||||
out->write_image(format_info.oiio_desc, data_buffer.data());
|
||||
out->close();
|
||||
|
||||
emit Downloaded(entry.hash);
|
||||
} else {
|
||||
qWarning() << tr("Failed to open output file \"%1\"").arg(entry.filename);
|
||||
}
|
||||
}
|
||||
|
||||
f->glDeleteFramebuffers(1, &read_buffer_);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef RENDERERDOWNLOADTHREAD_H
|
||||
#define RENDERERDOWNLOADTHREAD_H
|
||||
|
||||
#include "videorendererthreadbase.h"
|
||||
|
||||
class VideoRendererDownloadThread : public VideoRendererThreadBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoRendererDownloadThread(QOpenGLContext* share_ctx,
|
||||
const VideoRenderingParams ¶ms);
|
||||
|
||||
void Queue(RenderTexturePtr texture, const QString &fn, const QByteArray &hash);
|
||||
|
||||
public slots:
|
||||
virtual void Cancel() override;
|
||||
|
||||
signals:
|
||||
void Downloaded(const QByteArray& hash);
|
||||
|
||||
protected:
|
||||
virtual void ProcessLoop() override;
|
||||
|
||||
private:
|
||||
struct DownloadQueueEntry {
|
||||
RenderTexturePtr texture;
|
||||
QString filename;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
GLuint read_buffer_;
|
||||
|
||||
QVector<DownloadQueueEntry> texture_queue_;
|
||||
|
||||
QMutex texture_queue_lock_;
|
||||
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
QByteArray hash_;
|
||||
|
||||
};
|
||||
|
||||
using RendererDownloadThreadPtr = std::shared_ptr<VideoRendererDownloadThread>;
|
||||
|
||||
#endif // RENDERERDOWNLOADTHREAD_H
|
||||
@@ -0,0 +1,151 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "videorendererprocessthread.h"
|
||||
|
||||
#include "videorenderer.h"
|
||||
|
||||
RendererProcessThread::RendererProcessThread(VideoRendererProcessor* parent,
|
||||
QOpenGLContext *share_ctx,
|
||||
const VideoRenderingParams ¶ms) :
|
||||
VideoRendererThreadBase(share_ctx, params),
|
||||
parent_(parent),
|
||||
cancelled_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool RendererProcessThread::Queue(const NodeDependency& dep, bool wait, bool sibling)
|
||||
{
|
||||
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;
|
||||
sibling_ = sibling;
|
||||
|
||||
// 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::Cancel()
|
||||
{
|
||||
cancelled_ = true;
|
||||
|
||||
mutex_.lock();
|
||||
wait_cond_.wakeAll();
|
||||
mutex_.unlock();
|
||||
|
||||
wait();
|
||||
}
|
||||
|
||||
void RendererProcessThread::ProcessLoop()
|
||||
{
|
||||
while (!cancelled_) {
|
||||
// Main waiting condition
|
||||
wait_cond_.wait(&mutex_);
|
||||
|
||||
if (cancelled_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
texture_ = nullptr;
|
||||
|
||||
QList<Node*> all_deps;
|
||||
bool has_hash = false;
|
||||
bool can_cache = true;
|
||||
|
||||
if (!sibling_) {
|
||||
node_to_process->Lock();
|
||||
|
||||
all_deps = node_to_process->GetDependencies();
|
||||
foreach (Node* dep, all_deps) {
|
||||
dep->Lock();
|
||||
}
|
||||
|
||||
// Check hash
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
||||
node_to_process->Hash(&hasher, output_to_process, path_.in());
|
||||
hash_ = hasher.result();
|
||||
|
||||
has_hash = parent_->HasHash(hash_);
|
||||
can_cache = false;
|
||||
}
|
||||
|
||||
if (!has_hash){
|
||||
|
||||
if ((can_cache = parent_->TryCache(hash_))) {
|
||||
|
||||
QList<NodeDependency> deps = node_to_process->RunDependencies(output_to_process, path_.in());
|
||||
|
||||
// Ask for other threads to run these deps while we're here
|
||||
if (!deps.isEmpty()) {
|
||||
for (int i=1;i<deps.size();i++) {
|
||||
emit RequestSibling(deps.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the requested value
|
||||
texture_ = output_to_process->get_value(path_.in(), path_.in()).value<RenderTexturePtr>();
|
||||
|
||||
render_instance()->context()->functions()->glFinish();
|
||||
}
|
||||
}
|
||||
|
||||
if (!sibling_) {
|
||||
foreach (Node* dep, all_deps) {
|
||||
dep->Unlock();
|
||||
}
|
||||
|
||||
node_to_process->Unlock();
|
||||
}
|
||||
|
||||
if (can_cache) {
|
||||
// We cached this frame, signal that it will need to be downloaded to disk
|
||||
emit CachedFrame(texture_, path_.in(), hash_);
|
||||
} else {
|
||||
// This hash already exists, no need to cache, just map it
|
||||
emit FrameSkipped(path_.in(), hash_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERERPROCESSTHREAD_H
|
||||
#define RENDERERPROCESSTHREAD_H
|
||||
|
||||
#include "videorendererthreadbase.h"
|
||||
|
||||
class VideoRendererProcessor;
|
||||
|
||||
class RendererProcessThread : public VideoRendererThreadBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RendererProcessThread(VideoRendererProcessor* parent,
|
||||
QOpenGLContext* share_ctx,
|
||||
const VideoRenderingParams ¶ms);
|
||||
|
||||
bool Queue(const NodeDependency &dep, bool wait, bool sibling);
|
||||
|
||||
public slots:
|
||||
virtual void Cancel() override;
|
||||
|
||||
protected:
|
||||
virtual void ProcessLoop() override;
|
||||
|
||||
signals:
|
||||
void RequestSibling(NodeDependency dep);
|
||||
|
||||
void CachedFrame(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
|
||||
|
||||
void FrameSkipped(const rational& time, const QByteArray& hash);
|
||||
|
||||
private:
|
||||
VideoRendererProcessor* parent_;
|
||||
|
||||
NodeDependency path_;
|
||||
|
||||
QByteArray hash_;
|
||||
|
||||
RenderTexturePtr texture_;
|
||||
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
bool sibling_;
|
||||
|
||||
};
|
||||
|
||||
using RendererProcessThreadPtr = std::shared_ptr<RendererProcessThread>;
|
||||
|
||||
#endif // RENDERERPROCESSTHREAD_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
render/backend/vulkan/vulkanbackend.h
|
||||
render/backend/vulkan/vulkanbackend.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "vulkanbackend.h"
|
||||
|
||||
VulkanBackend::VulkanBackend()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef VULKANBACKEND_H
|
||||
#define VULKANBACKEND_H
|
||||
|
||||
|
||||
class VulkanBackend
|
||||
{
|
||||
public:
|
||||
VulkanBackend();
|
||||
};
|
||||
|
||||
#endif // VULKANBACKEND_H
|
||||
Reference in New Issue
Block a user