1 /***
2 
3     Olive - Non-Linear Video Editor
4     Copyright (C) 2019  Olive Team
5 
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ***/
20 
21 #include "renderthread.h"
22 
23 #include <QApplication>
24 #include <QImage>
25 #include <QOpenGLFunctions>
26 #include <QDateTime>
27 #include <QDebug>
28 #ifdef OLIVE_OCIO
29 #include <OpenColorIO/OpenColorIO.h>
30 namespace OCIO = OCIO_NAMESPACE;
31 #endif
32 
33 #include "rendering/renderfunctions.h"
34 #include "timeline/sequence.h"
35 
RenderThread()36 RenderThread::RenderThread() :
37   gizmos(nullptr),
38   front_buffer_switcher(false),
39   share_ctx(nullptr),
40   ctx(nullptr),
41   blend_mode_program(nullptr),
42   premultiply_program(nullptr),
43   seq(nullptr),
44   tex_width(-1),
45   tex_height(-1),
46   queued(false),
47   texture_failed(false),
48   running(true)
49 {
50   surface.create();
51 }
52 
~RenderThread()53 RenderThread::~RenderThread() {
54   surface.destroy();
55 }
56 
run()57 void RenderThread::run() {
58   wait_lock_.lock();
59 
60   while (running) {
61     if (!queued) {
62       wait_cond_.wait(&wait_lock_);
63     }
64     if (!running) {
65       break;
66     }
67     queued = false;
68 
69     if (share_ctx != nullptr) {
70       if (ctx != nullptr) {
71         ctx->makeCurrent(&surface);
72 
73         // if the sequence size has changed, we'll need to reinitialize the textures
74         if (seq->width != tex_width || seq->height != tex_height) {
75           delete_buffers();
76 
77           // cache sequence values for future checks
78           tex_width = seq->width;
79           tex_height = seq->height;
80         }
81 
82         // create any buffers that don't yet exist
83         if (!front_buffer_1.IsCreated()) {
84           front_buffer_1.Create(ctx, seq->width, seq->height);
85         }
86         if (!front_buffer_2.IsCreated()) {
87           front_buffer_2.Create(ctx, seq->width, seq->height);
88         }
89         if (!back_buffer_1.IsCreated()) {
90           back_buffer_1.Create(ctx, seq->width, seq->height);
91         }
92         if (!back_buffer_2.IsCreated()) {
93           back_buffer_2.Create(ctx, seq->width, seq->height);
94         }
95 
96         if (blend_mode_program == nullptr) {
97           // create shader program to make blending modes work
98           delete_shaders();
99 
100           blend_mode_program = new QOpenGLShaderProgram();
101           blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert");
102           blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/blending.frag");
103           blend_mode_program->link();
104 
105           premultiply_program = new QOpenGLShaderProgram();
106           premultiply_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert");
107           premultiply_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/premultiply.frag");
108           premultiply_program->link();
109         }
110 
111         // draw frame
112         paint();
113 
114         front_buffer_switcher = !front_buffer_switcher;
115 
116         emit ready();
117       }
118     }
119   }
120 
121   delete_ctx();
122 
123   wait_lock_.unlock();
124 }
125 
get_texture_mutex()126 QMutex *RenderThread::get_texture_mutex()
127 {
128   // return the mutex for the opposite texture being drawn to by the renderer
129   return front_buffer_switcher ? &front_mutex2 : &front_mutex1;
130 }
131 
get_texture()132 const GLuint &RenderThread::get_texture()
133 {
134   // return the opposite texture to the texture being drawn to by the renderer
135   return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.texture();
136 }
137 
set_up_ocio()138 void RenderThread::set_up_ocio()
139 {
140 }
141 
paint()142 void RenderThread::paint() {
143   // set up compose_sequence() parameters
144   ComposeSequenceParams params;
145   params.viewer = nullptr;
146   params.ctx = ctx;
147   params.seq = seq;
148   params.video = true;
149   params.texture_failed = false;
150   params.wait_for_mutexes = true;
151   params.playback_speed = playback_speed_;
152   params.blend_mode_program = blend_mode_program;
153   params.premultiply_program = premultiply_program;
154   params.backend_buffer1 = back_buffer_1.buffer();
155   params.backend_buffer2 = back_buffer_2.buffer();
156   params.backend_attachment1 = back_buffer_1.texture();
157   params.backend_attachment2 = back_buffer_2.texture();
158   params.main_buffer = front_buffer_switcher ? front_buffer_1.buffer() : front_buffer_2.buffer();
159   params.main_attachment = front_buffer_switcher ? front_buffer_1.texture() : front_buffer_2.texture();
160 
161   // get currently selected gizmos
162   gizmos = seq->GetSelectedGizmo();
163   params.gizmos = gizmos;
164 
165   QMutex& active_mutex = front_buffer_switcher ? front_mutex1 : front_mutex2;
166   active_mutex.lock();
167 
168   // bind framebuffer for drawing
169   ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, params.main_buffer);
170 
171   glLoadIdentity();
172 
173   glClearColor(0.0, 0.0, 0.0, 0.0);
174   glClear(GL_COLOR_BUFFER_BIT);
175 
176   glMatrixMode(GL_MODELVIEW);
177 
178   glEnable(GL_TEXTURE_2D);
179   glEnable(GL_BLEND);
180 
181   olive::rendering::compose_sequence(params);
182 
183   // flush changes
184   ctx->functions()->glFinish();
185 
186   texture_failed = params.texture_failed;
187 
188   active_mutex.unlock();
189 
190   if (!save_fn.isEmpty()) {
191     if (texture_failed) {
192       // texture failed, try again
193       queued = true;
194     } else {
195       ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer);
196       QImage img(tex_width, tex_height, QImage::Format_RGBA8888);
197       glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
198       img.save(save_fn);
199       ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
200       save_fn = "";
201     }
202   }
203 
204   if (pixel_buffer != nullptr) {
205 
206     // set main framebuffer to the current read buffer
207     ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer);
208 
209     // store pixels in buffer
210     glReadPixels(0,
211                  0,
212                  pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize,
213                  tex_height,
214                  GL_RGBA,
215                  GL_UNSIGNED_BYTE,
216                  pixel_buffer);
217 
218     // release current read buffer
219     ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
220 
221     pixel_buffer = nullptr;
222   }
223 
224   glDisable(GL_BLEND);
225   glDisable(GL_TEXTURE_2D);
226 
227   // release
228   ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
229 }
230 
start_render(QOpenGLContext * share,Sequence * s,int playback_speed,const QString & save,GLvoid * pixels,int pixel_linesize,int idivider,bool wait)231 void RenderThread::start_render(QOpenGLContext *share,
232                                 Sequence* s,
233                                 int playback_speed,
234                                 const QString& save,
235                                 GLvoid* pixels,
236                                 int pixel_linesize,
237                                 int idivider,
238                                 bool wait) {
239   Q_UNUSED(idivider)
240 
241   seq = s;
242 
243   playback_speed_ = playback_speed;
244 
245   // stall any dependent actions
246   texture_failed = true;
247 
248   if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) {
249     share_ctx = share;
250     delete_ctx();
251     ctx = new QOpenGLContext();
252     ctx->setFormat(share_ctx->format());
253     ctx->setShareContext(share_ctx);
254     ctx->create();
255     ctx->moveToThread(this);
256   }
257 
258   save_fn = save;
259   pixel_buffer = pixels;
260   pixel_buffer_linesize = pixel_linesize;
261 
262   queued = true;
263 
264   if (wait) {
265     wait_lock_.lock();
266   }
267 
268   wait_cond_.wakeAll();
269 
270   if (wait) {
271     wait_lock_.unlock();
272   }
273 }
274 
did_texture_fail()275 bool RenderThread::did_texture_fail() {
276   return texture_failed;
277 }
278 
cancel()279 void RenderThread::cancel() {
280   running = false;
281   wait_cond_.wakeAll();
282   wait();
283 }
284 
wait_until_paused()285 void RenderThread::wait_until_paused()
286 {
287 
288   // Wait for thread to finish whatever it's doing before proceeding.
289   //
290   // FIXME: This is slow. Perhaps there's a better way...
291 
292   if (wait_lock_.tryLock()) {
293     wait_lock_.unlock();
294     return;
295   } else {
296     wait_lock_.lock();
297     wait_lock_.unlock();
298   }
299 }
300 
delete_buffers()301 void RenderThread::delete_buffers() {
302   front_buffer_1.Destroy();
303   front_buffer_2.Destroy();
304   back_buffer_1.Destroy();
305   back_buffer_2.Destroy();
306 }
307 
delete_shaders()308 void RenderThread::delete_shaders() {
309   delete blend_mode_program;
310   blend_mode_program = nullptr;
311 
312   delete premultiply_program;
313   premultiply_program = nullptr;
314 }
315 
delete_ctx()316 void RenderThread::delete_ctx() {
317   if (ctx != nullptr) {
318     delete_shaders();
319     delete_buffers();
320   }
321 
322   delete ctx;
323   ctx = nullptr;
324 }
325