Merge pull request #325 from olive-editor/threadedgl2

move renderer to separate thread
This commit is contained in:
itsmattkc
2019-01-17 23:10:27 +11:00
committed by GitHub
37 changed files with 1597 additions and 1020 deletions
+35 -28
View File
@@ -6,9 +6,12 @@
#include "panels/timeline.h"
#include "panels/viewer.h"
#include "ui/viewerwidget.h"
#include "ui/renderthread.h"
#include "ui/renderfunctions.h"
#include "playback/playback.h"
#include "playback/audio.h"
#include "dialogs/exportdialog.h"
#include "mainwindow.h"
#include "debug.h"
extern "C" {
@@ -111,7 +114,7 @@ bool ExportThread::setupVideo() {
vcodec_ctx->sample_aspect_ratio = {1, 1};
vcodec_ctx->pix_fmt = vcodec->pix_fmts[0]; // maybe be breakable code
vcodec_ctx->framerate = av_d2q(video_frame_rate, INT_MAX);
if (video_compression_type == COMPRESSION_TYPE_CBR) vcodec_ctx->bit_rate = video_bitrate * 1000000;
if (video_compression_type == COMPRESSION_TYPE_CBR) vcodec_ctx->bit_rate = qRound(video_bitrate * 1000000);
vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate);
video_stream->time_base = vcodec_ctx->time_base;
@@ -312,12 +315,7 @@ bool ExportThread::setupContainer() {
void ExportThread::run() {
panel_sequence_viewer->pause();
if (!panel_sequence_viewer->viewer_widget->context()->makeCurrent(&surface)) {
qCritical() << "Make current failed";
ed->export_error = tr("could not make OpenGL context current");
return;
}
panel_sequence_viewer->seek(start_frame);
// copy filename
QByteArray ba = filename.toUtf8();
@@ -339,28 +337,35 @@ void ExportThread::run() {
}
}
panel_sequence_viewer->seek(start_frame);
panel_sequence_viewer->reset_all_audio();
QOpenGLFramebufferObject fbo(sequence->width, sequence->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE);
fbo.bind();
panel_sequence_viewer->viewer_widget->default_fbo = &fbo;
long file_audio_samples = 0;
qint64 start_time, frame_time, avg_time, eta, total_time = 0;
long remaining_frames, frame_count = 1;
RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer();
disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
connect(renderer, SIGNAL(ready()), this, SLOT(wake()));
mutex.lock();
while (sequence->playhead <= end_frame && continueEncode) {
start_time = QDateTime::currentMSecsSinceEpoch();
panel_sequence_viewer->viewer_widget->paintGL();
if (audio_enabled) {
compose_audio(nullptr, sequence, true);
}
if (video_enabled) {
do {
// TODO optimize by rendering the next frame while encoding the last
renderer->start_render(nullptr, sequence, nullptr, video_frame->data[0]);
waitCond.wait(&mutex);
if (!continueEncode) break;
} while (renderer->did_texture_fail());
if (!continueEncode) break;
}
// encode last frame while rendering next frame
double timecode_secs = (double) (sequence->playhead-start_frame) / sequence->frame_rate;
if (video_enabled) {
// get image from opengl
glReadPixels(0, 0, video_frame->linesize[0]/4, sequence->height, GL_RGBA, GL_UNSIGNED_BYTE, video_frame->data[0]);
// change pixel format
sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize);
sws_frame->pts = qRound(timecode_secs/av_q2d(video_stream->time_base));
@@ -371,6 +376,7 @@ void ExportThread::run() {
if (audio_enabled) {
// do we need to encode more audio samples?
while (continueEncode && file_audio_samples <= (timecode_secs*audio_sampling_rate)) {
// copy samples from audio buffer to AVFrame
int adjusted_read = audio_ibuffer_read%audio_ibuffer_size;
int copylen = qMin(aframe_bytes, audio_ibuffer_size-adjusted_read);
memcpy(audio_frame->data[0], audio_ibuffer+adjusted_read, copylen);
@@ -405,20 +411,22 @@ void ExportThread::run() {
// qInfo() << "Encoded frame" << sequence->playhead << "- took" << frame_time << "ms (avg:" << avg_time << "ms, remaining:" << remaining_frames << ", ETA:" << eta << ")";
emit progress_changed(qRound(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100), eta);
emit progress_changed(qRound((double(sequence->playhead-start_frame) / double(end_frame-start_frame)) * 100.0), eta);
sequence->playhead++;
frame_count++;
}
disconnect(renderer, SIGNAL(ready()), this, SLOT(wake()));
connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
mutex.unlock();
if (continueEncode) {
if (video_enabled) vpkt_alloc = true;
if (audio_enabled) apkt_alloc = true;
}
panel_sequence_viewer->viewer_widget->default_fbo = nullptr;
rendering = false;
fbo.release();
mainWindow->set_rendering_state(false);
if (audio_enabled && continueEncode) {
// flush swresample
@@ -478,9 +486,8 @@ void ExportThread::run() {
}
delete [] c_filename;
}
panel_sequence_viewer->viewer_widget->context()->doneCurrent();
panel_sequence_viewer->viewer_widget->context()->moveToThread(qApp->thread());
rendering = false;
void ExportThread::wake() {
waitCond.wakeAll();
}
+12 -5
View File
@@ -3,6 +3,8 @@
#include <QThread>
#include <QOffscreenSurface>
#include <QMutex>
#include <QWaitCondition>
class ExportDialog;
struct AVFormatContext;
@@ -52,19 +54,21 @@ public:
bool continueEncode;
signals:
void progress_changed(int value, qint64 remaining_ms);
public slots:
void wake();
private:
bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale);
bool setupVideo();
bool setupAudio();
bool setupContainer();
AVFormatContext* fmt_ctx;
AVFormatContext* fmt_ctx;
AVStream* video_stream;
AVCodec* vcodec;
AVCodecContext* vcodec_ctx;
AVFrame* video_frame;
AVFrame* sws_frame;
SwsContext* sws_ctx;
SwsContext* sws_ctx;
AVStream* audio_stream;
AVCodec* acodec;
AVFrame* audio_frame;
@@ -72,14 +76,17 @@ private:
AVCodecContext* acodec_ctx;
AVPacket video_pkt;
AVPacket audio_pkt;
SwrContext* swr_ctx;
SwrContext* swr_ctx;
bool vpkt_alloc;
bool apkt_alloc;
bool vpkt_alloc;
bool apkt_alloc;
int aframe_bytes;
int ret;
char* c_filename;
QMutex mutex;
QWaitCondition waitCond;
};
#endif // EXPORTTHREAD_H
+20 -20
View File
@@ -153,12 +153,12 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
if (type == LOAD_TYPE_VERSION) {
int proj_version = stream.readElementText().toInt();
if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) {
if (QMessageBox::warning(
mainWindow,
tr("Version Mismatch"),
tr("This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?"),
QMessageBox::Yes,
QMessageBox::No) == QMessageBox::No) {
if (QMessageBox::warning(
mainWindow,
tr("Version Mismatch"),
tr("This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?"),
QMessageBox::Yes,
QMessageBox::No) == QMessageBox::No) {
show_err = false;
return false;
}
@@ -444,11 +444,11 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
if (!found) {
correct_clip->linked.removeAt(j);
j--;
if (QMessageBox::warning(mainWindow,
tr("Invalid Clip Link"),
tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"),
QMessageBox::Yes,
QMessageBox::No) == QMessageBox::No) {
if (QMessageBox::warning(mainWindow,
tr("Invalid Clip Link"),
tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"),
QMessageBox::Yes,
QMessageBox::No) == QMessageBox::No) {
delete s;
return false;
}
@@ -601,7 +601,7 @@ void LoadThread::run() {
xml_error = false;
if (show_err) emit error();
} else if (stream.hasError()) {
error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber()));
error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber()));
xml_error = true;
emit error();
cont = false;
@@ -641,15 +641,15 @@ void LoadThread::cancel() {
void LoadThread::error_func() {
if (xml_error) {
qCritical() << "Error parsing XML." << error_str;
QMessageBox::critical(mainWindow,
tr("XML Parsing Error"),
tr("Couldn't load '%1'. %2").arg(project_url, error_str),
QMessageBox::Ok);
QMessageBox::critical(mainWindow,
tr("XML Parsing Error"),
tr("Couldn't load '%1'. %2").arg(project_url, error_str),
QMessageBox::Ok);
} else {
QMessageBox::critical(mainWindow,
tr("Project Load Error"),
tr("Error loading project: %1").arg(error_str),
QMessageBox::Ok);
QMessageBox::critical(mainWindow,
tr("Project Load Error"),
tr("Error loading project: %1").arg(error_str),
QMessageBox::Ok);
}
}