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