/*** 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 "renderthread.h" #include #include #include #include #include "ui/renderfunctions.h" #include "playback/playback.h" #include "project/sequence.h" RenderThread::RenderThread() : front_buffer(0), front_texture(0), gizmos(nullptr), share_ctx(nullptr), ctx(nullptr), blend_mode_program(nullptr), premultiply_program(nullptr), seq(nullptr), tex_width(-1), tex_height(-1), queued(false), texture_failed(false), running(true) { surface.create(); } RenderThread::~RenderThread() { surface.destroy(); } void RenderThread::run() { mutex.lock(); while (running) { if (!queued) { waitCond.wait(&mutex); } if (!running) { break; } queued = false; if (share_ctx != nullptr) { if (ctx != nullptr) { ctx->makeCurrent(&surface); // gen fbo if (front_buffer == 0) { // delete any existing framebuffers delete_fbo(); // create framebuffers ctx->functions()->glGenFramebuffers(1, &front_buffer); ctx->functions()->glGenFramebuffers(1, &back_buffer_1); ctx->functions()->glGenFramebuffers(1, &back_buffer_2); } // gen texture if (front_texture == 0 || tex_width != seq->width || tex_height != seq->height) { // cache texture size tex_width = seq->width; tex_height = seq->height; // delete any existing textures delete_texture(); // create texture glGenTextures(1, &front_texture); glGenTextures(1, &back_texture_1); glGenTextures(1, &back_texture_2); GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2}; GLuint textures[3] = {front_texture, back_texture_1, back_texture_2}; for (int i=0;i<3;i++) { // bind framebuffer for attaching ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[i]); // bind texture glBindTexture(GL_TEXTURE_2D, textures[i]); // allocate storage for texture glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, seq->width, seq->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr ); // set texture filtering to bilinear glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // attach texture to framebuffer ctx->functions()->glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0 ); // release texture glBindTexture(GL_TEXTURE_2D, 0); // release framebuffer ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } } if (blend_mode_program == nullptr) { // create shader program to make blending modes work delete_shader_program(); blend_mode_program = new QOpenGLShaderProgram(); blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert"); blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/blending.frag"); blend_mode_program->link(); premultiply_program = new QOpenGLShaderProgram(); premultiply_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert"); premultiply_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/premultiply.frag"); premultiply_program->link(); } // bind framebuffer for drawing ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, front_buffer); // draw frame paint(); // flush changes ctx->functions()->glFinish(); // release ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); emit ready(); } } } delete_ctx(); mutex.unlock(); } void RenderThread::paint() { glLoadIdentity(); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glEnable(GL_DEPTH); gizmos = nullptr; ComposeSequenceParams params; params.viewer = nullptr; params.ctx = ctx; params.seq = seq; params.video = true; params.texture_failed = false; params.render_audio = false; params.gizmos = &gizmos; params.rendering = false; params.playback_speed = 1; params.blend_mode_program = blend_mode_program; params.premultiply_program = premultiply_program; params.backend_buffer1 = back_buffer_1; params.backend_buffer2 = back_buffer_2; params.backend_attachment1 = back_texture_1; params.backend_attachment2 = back_texture_2; params.main_buffer = front_buffer; params.main_attachment = front_texture; compose_sequence(params); texture_failed = params.texture_failed; if (!save_fn.isEmpty()) { if (texture_failed) { // texture failed, try again queued = true; } else { ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer); QImage img(tex_width, tex_height, QImage::Format_RGBA8888); glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); img.save(save_fn); ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); save_fn = ""; } } if (pixel_buffer != nullptr) { // set main framebuffer to the current read buffer ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer); // store pixels in buffer glReadPixels(0, 0, pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer); // release current read buffer ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); pixel_buffer = nullptr; } glDisable(GL_DEPTH); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int pixel_linesize, int idivider) { Q_UNUSED(idivider); seq = s; // stall any dependent actions texture_failed = true; if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) { share_ctx = share; delete_ctx(); ctx = new QOpenGLContext(); ctx->setFormat(share_ctx->format()); ctx->setShareContext(share_ctx); ctx->create(); ctx->moveToThread(this); } save_fn = save; pixel_buffer = pixels; pixel_buffer_linesize = pixel_linesize; queued = true; waitCond.wakeAll(); } bool RenderThread::did_texture_fail() { return texture_failed; } void RenderThread::cancel() { running = false; waitCond.wakeAll(); wait(); } void RenderThread::delete_texture() { if (front_texture > 0) { GLuint tex[3] = {front_texture, back_texture_1, back_texture_2}; glDeleteTextures(3, tex); } front_texture = 0; back_texture_1 = 0; back_texture_2 = 0; } void RenderThread::delete_fbo() { if (front_buffer > 0) { GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2}; ctx->functions()->glDeleteFramebuffers(3, fbos); } front_buffer = 0; back_buffer_1 = 0; back_buffer_2 = 0; } void RenderThread::delete_shader_program() { if (blend_mode_program != nullptr) { delete blend_mode_program; delete premultiply_program; } blend_mode_program = nullptr; premultiply_program = nullptr; } void RenderThread::delete_ctx() { if (ctx != nullptr) { delete_shader_program(); delete_texture(); delete_fbo(); ctx->doneCurrent(); delete ctx; } ctx = nullptr; }