merge of long discussed new pipeline
This commit is contained in:
+41
-48
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -52,10 +52,9 @@ QAudioInput* audio_input = nullptr;
|
||||
QFile output_recording;
|
||||
bool recording = false;
|
||||
|
||||
bool audio_rendering = false;
|
||||
int audio_rendering_rate = 0;
|
||||
|
||||
qint8 audio_ibuffer[audio_ibuffer_size];
|
||||
float audio_ibuffer[audio_ibuffer_size];
|
||||
qint64 audio_ibuffer_read = 0;
|
||||
long audio_ibuffer_frame = 0;
|
||||
double audio_ibuffer_timecode = 0;
|
||||
@@ -70,7 +69,7 @@ QAudioDeviceInfo get_audio_device(QAudio::Mode mode) {
|
||||
QList<QAudioDeviceInfo> devs = QAudioDeviceInfo::availableDevices(mode);
|
||||
|
||||
// try to retrieve preferred device from config
|
||||
QString preferred_device = (mode == QAudio::AudioOutput) ? olive::CurrentConfig.preferred_audio_output : olive::CurrentConfig.preferred_audio_input;
|
||||
QString preferred_device = (mode == QAudio::AudioOutput) ? olive::config.preferred_audio_output : olive::config.preferred_audio_input;
|
||||
if (!preferred_device.isEmpty()) {
|
||||
for (int i=0;i<devs.size();i++) {
|
||||
// try to match available devices with preferred device
|
||||
@@ -99,12 +98,12 @@ void init_audio() {
|
||||
stop_audio();
|
||||
|
||||
QAudioFormat audio_format;
|
||||
audio_format.setSampleRate(olive::CurrentConfig.audio_rate);
|
||||
audio_format.setSampleRate(olive::config.audio_rate);
|
||||
audio_format.setChannelCount(2);
|
||||
audio_format.setSampleSize(16);
|
||||
audio_format.setSampleSize(32);
|
||||
audio_format.setCodec("audio/pcm");
|
||||
audio_format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
audio_format.setSampleType(QAudioFormat::SignedInt);
|
||||
audio_format.setSampleType(QAudioFormat::Float);
|
||||
|
||||
QAudioDeviceInfo info = get_audio_device(QAudio::AudioOutput);
|
||||
|
||||
@@ -147,19 +146,20 @@ void stop_audio() {
|
||||
void clear_audio_ibuffer() {
|
||||
if (audio_thread != nullptr) audio_thread->lock.lock();
|
||||
audio_write_lock.lock();
|
||||
memset(audio_ibuffer, 0, audio_ibuffer_size);
|
||||
memset(audio_ibuffer, 0, audio_ibuffer_size * sizeof(float));
|
||||
audio_ibuffer_read = 0;
|
||||
audio_write_lock.unlock();
|
||||
if (audio_thread != nullptr) audio_thread->lock.unlock();
|
||||
}
|
||||
|
||||
int current_audio_freq() {
|
||||
return audio_rendering ? audio_rendering_rate : audio_output->format().sampleRate();
|
||||
return olive::Global->is_exporting()
|
||||
? audio_rendering_rate : audio_output->format().sampleRate();
|
||||
}
|
||||
|
||||
qint64 get_buffer_offset_from_frame(double framerate, long frame) {
|
||||
if (frame >= audio_ibuffer_frame) {
|
||||
int multiplier = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
|
||||
int multiplier = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
|
||||
return qFloor((double(frame - audio_ibuffer_frame)/framerate)*current_audio_freq())*multiplier;
|
||||
} else {
|
||||
qWarning() << "Invalid values passed to get_buffer_offset_from_frame" << frame << "<" << audio_ibuffer_frame;
|
||||
@@ -191,15 +191,13 @@ void AudioSenderThread::run() {
|
||||
if (close) {
|
||||
break;
|
||||
} else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) {
|
||||
int written_bytes = 0;
|
||||
|
||||
int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size;
|
||||
int max_write = audio_ibuffer_size - adjusted_read_index;
|
||||
int adjusted_read_index = (audio_ibuffer_read%audio_ibuffer_size);
|
||||
int max_write = (audio_ibuffer_size - adjusted_read_index) * sizeof(float);
|
||||
int actual_write = send_audio_to_output(adjusted_read_index, max_write);
|
||||
written_bytes += actual_write;
|
||||
if (actual_write == max_write) {
|
||||
// got all the bytes, write again
|
||||
written_bytes += send_audio_to_output(0, audio_ibuffer_size);
|
||||
send_audio_to_output(0, audio_ibuffer_size);
|
||||
}
|
||||
|
||||
audio_scrub = false;
|
||||
@@ -210,42 +208,38 @@ void AudioSenderThread::run() {
|
||||
|
||||
int AudioSenderThread::send_audio_to_output(qint64 offset, int max) {
|
||||
// send audio to device
|
||||
qint64 actual_write = audio_io_device->write(reinterpret_cast<const char*>(audio_ibuffer)+offset, max);
|
||||
audio_write_lock.lock();
|
||||
|
||||
qint64 audio_ibuffer_limit = audio_ibuffer_read + actual_write;
|
||||
qint64 actual_write = audio_io_device->write(reinterpret_cast<const char*>(&audio_ibuffer[offset]), max);
|
||||
|
||||
if (actual_write > 0) {
|
||||
// average values and send to audio monitor
|
||||
int channels = audio_output->format().channelCount();
|
||||
qint64 lim = offset + actual_write;
|
||||
QVector<double> averages;
|
||||
qint64 lim = offset + (actual_write/sizeof(float));
|
||||
QVector<float> averages;
|
||||
averages.resize(channels);
|
||||
averages.fill(0);
|
||||
averages.fill(0.0);
|
||||
|
||||
int counter = 0;
|
||||
qint16 sample;
|
||||
for (qint64 i=offset;i<lim;i+=2) {
|
||||
sample = qint16(((audio_ibuffer[i+1] & 0xFF) << 8) | (audio_ibuffer[i] & 0xFF));
|
||||
averages[counter] = qMax((double(qAbs(sample))/32768.0), averages[counter]);
|
||||
counter = (counter+1)%channels;
|
||||
}
|
||||
for (int i=0;i<channels;i++) {
|
||||
averages[i] = log_volume(1.0-(averages[i]));
|
||||
for (qint64 i=offset;i<lim;i++) {
|
||||
int channel = i%channels;
|
||||
averages[channel] = qMax(qAbs(audio_ibuffer[i]), averages[channel]);
|
||||
}
|
||||
|
||||
panel_timeline->audio_monitor->set_value(averages);
|
||||
panel_timeline.first()->audio_monitor->set_value(averages);
|
||||
}
|
||||
|
||||
memset(audio_ibuffer+offset, 0, actual_write);
|
||||
memset(&audio_ibuffer[offset], 0, actual_write);
|
||||
|
||||
audio_ibuffer_read = audio_ibuffer_limit;
|
||||
audio_ibuffer_read += (actual_write / sizeof(float));
|
||||
|
||||
audio_write_lock.unlock();
|
||||
|
||||
return actual_write;
|
||||
}
|
||||
|
||||
double log_volume(double linear) {
|
||||
// expects a value between 0 and 1 (or more if amplifying)
|
||||
return (qExp(linear)-1)/(M_E-1);
|
||||
return (qExp(linear)-1.0f)/(M_E-1.0f);
|
||||
}
|
||||
|
||||
void int32_to_char_array(qint32 i, char* array) {
|
||||
@@ -325,8 +319,7 @@ void write_wave_trailer(QFile& f) {
|
||||
}
|
||||
|
||||
bool start_recording() {
|
||||
if (olive::ActiveSequence == nullptr) {
|
||||
qCritical() << "No active sequence to record into";
|
||||
if (!olive::Global->CheckForActiveSequence(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -356,8 +349,8 @@ bool start_recording() {
|
||||
}
|
||||
|
||||
QAudioFormat audio_format = audio_output->format();
|
||||
if (olive::CurrentConfig.recording_mode != audio_format.channelCount()) {
|
||||
audio_format.setChannelCount(olive::CurrentConfig.recording_mode);
|
||||
if (olive::config.recording_mode != audio_format.channelCount()) {
|
||||
audio_format.setChannelCount(olive::config.recording_mode);
|
||||
}
|
||||
|
||||
QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput);
|
||||
|
||||
+13
-14
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -55,13 +55,12 @@ extern AudioSenderThread* audio_thread;
|
||||
extern QMutex audio_write_lock;
|
||||
|
||||
#define audio_ibuffer_size 192000
|
||||
extern qint8 audio_ibuffer[audio_ibuffer_size];
|
||||
extern float audio_ibuffer[audio_ibuffer_size];
|
||||
extern qint64 audio_ibuffer_read;
|
||||
extern long audio_ibuffer_frame;
|
||||
extern double audio_ibuffer_timecode;
|
||||
extern bool audio_scrub;
|
||||
extern bool recording;
|
||||
extern bool audio_rendering;
|
||||
extern int audio_rendering_rate;
|
||||
void clear_audio_ibuffer();
|
||||
|
||||
|
||||
+118
-84
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -28,63 +28,67 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QtMath>
|
||||
#include <QAudioOutput>
|
||||
#include <QStatusBar>
|
||||
#include <math.h>
|
||||
|
||||
#include "panels/panels.h"
|
||||
#include "project/projectelements.h"
|
||||
#include "rendering/audio.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "panels/panels.h"
|
||||
#include "global/timing.h"
|
||||
#include "global/config.h"
|
||||
#include "global/global.h"
|
||||
#include "global/debug.h"
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
// Enable verbose audio messages - good for debugging reversed audio
|
||||
//#define AUDIOWARNINGS
|
||||
|
||||
const AVPixelFormat kDestPixFmt = AV_PIX_FMT_RGBA;
|
||||
const AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_S16;
|
||||
const AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_FLTP;
|
||||
|
||||
double bytes_to_seconds(int nb_bytes, int nb_channels, int sample_rate) {
|
||||
return (double(nb_bytes >> 1) / nb_channels / sample_rate);
|
||||
double samples_to_seconds(int nb_samples, int nb_channels, int sample_rate) {
|
||||
return (double(nb_samples) / double(nb_channels) / double(sample_rate));
|
||||
}
|
||||
|
||||
void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int nb_bytes, QVector<Clip*> nests) {
|
||||
int samples_to_bytes(int nb_samples, int nb_channels) {
|
||||
return nb_samples * nb_channels * sizeof(float);
|
||||
}
|
||||
|
||||
void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int nb_samples, int nb_channels, QVector<Clip*> nests) {
|
||||
// perform all audio effects
|
||||
double timecode_end;
|
||||
timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate);
|
||||
timecode_end = timecode_start + samples_to_seconds(nb_samples, frame->channels, frame->sample_rate);
|
||||
|
||||
for (int j=0;j<clip->effects.size();j++) {
|
||||
Effect* e = clip->effects.at(j).get();
|
||||
Node* e = clip->effects.at(j).get();
|
||||
if (e->IsEnabled()) {
|
||||
e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2);
|
||||
e->process_audio(timecode_start, timecode_end, reinterpret_cast<float**>(frame->data), nb_samples, nb_channels, kTransitionNone);
|
||||
}
|
||||
}
|
||||
if (clip->opening_transition != nullptr) {
|
||||
if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
double transition_start = (clip->clip_in(true) / clip->sequence->frame_rate);
|
||||
double transition_end = (clip->clip_in(true) + clip->opening_transition->get_length()) / clip->sequence->frame_rate;
|
||||
double transition_start = (clip->clip_in(true) / clip->track()->sequence()->frame_rate);
|
||||
double transition_end = (clip->clip_in(true) + clip->opening_transition->get_length()) / clip->track()->sequence()->frame_rate;
|
||||
if (timecode_end < transition_end) {
|
||||
double adjustment = transition_end - transition_start;
|
||||
double adjusted_range_start = (timecode_start - transition_start) / adjustment;
|
||||
double adjusted_range_end = (timecode_end - transition_start) / adjustment;
|
||||
clip->opening_transition->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionOpening);
|
||||
clip->opening_transition->process_audio(adjusted_range_start, adjusted_range_end, reinterpret_cast<float**>(frame->data), nb_samples, nb_channels, kTransitionOpening);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clip->closing_transition != nullptr) {
|
||||
if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
long length_with_transitions = clip->timeline_out(true) - clip->timeline_in(true);
|
||||
double transition_start = (clip->clip_in(true) + length_with_transitions - clip->closing_transition->get_length()) / clip->sequence->frame_rate;
|
||||
double transition_end = (clip->clip_in(true) + length_with_transitions) / clip->sequence->frame_rate;
|
||||
double transition_start = (clip->clip_in(true) + length_with_transitions - clip->closing_transition->get_length()) / clip->track()->sequence()->frame_rate;
|
||||
double transition_end = (clip->clip_in(true) + length_with_transitions) / clip->track()->sequence()->frame_rate;
|
||||
if (timecode_start > transition_start) {
|
||||
double adjustment = transition_end - transition_start;
|
||||
double adjusted_range_start = (timecode_start - transition_start) / adjustment;
|
||||
double adjusted_range_end = (timecode_end - transition_start) / adjustment;
|
||||
clip->closing_transition->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionClosing);
|
||||
clip->closing_transition->process_audio(adjusted_range_start, adjusted_range_end, reinterpret_cast<float**>(frame->data), nb_samples, nb_channels, kTransitionClosing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,9 +97,10 @@ void apply_audio_effects(Clip* clip, double timecode_start, AVFrame* frame, int
|
||||
Clip* next_nest = nests.last();
|
||||
nests.removeLast();
|
||||
apply_audio_effects(next_nest,
|
||||
timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->sequence->frame_rate),
|
||||
timecode_start + (double(clip->timeline_in(true)-clip->clip_in(true))/clip->track()->sequence()->frame_rate),
|
||||
frame,
|
||||
nb_bytes,
|
||||
nb_samples,
|
||||
nb_channels,
|
||||
nests);
|
||||
}
|
||||
}
|
||||
@@ -122,16 +127,16 @@ void Cacher::CacheAudioWorker() {
|
||||
bool reverse_audio = IsReversed();
|
||||
|
||||
long frame_skip = 0;
|
||||
double last_fr = clip->sequence->frame_rate;
|
||||
double last_fr = clip->track()->sequence()->frame_rate;
|
||||
if (!nests_.isEmpty()) {
|
||||
for (int i=nests_.size()-1;i>=0;i--) {
|
||||
timeline_in = rescale_frame_number(timeline_in, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
timeline_out = rescale_frame_number(timeline_out, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
target_frame = rescale_frame_number(target_frame, last_fr, nests_.at(i)->sequence->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
timeline_in = rescale_frame_number(timeline_in, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
timeline_out = rescale_frame_number(timeline_out, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
target_frame = rescale_frame_number(target_frame, last_fr, nests_.at(i)->track()->sequence()->frame_rate) + nests_.at(i)->timeline_in(true) - nests_.at(i)->clip_in(true);
|
||||
|
||||
timeline_out = qMin(timeline_out, nests_.at(i)->timeline_out(true));
|
||||
|
||||
frame_skip = rescale_frame_number(frame_skip, last_fr, nests_.at(i)->sequence->frame_rate);
|
||||
frame_skip = rescale_frame_number(frame_skip, last_fr, nests_.at(i)->track()->sequence()->frame_rate);
|
||||
|
||||
long validator = nests_.at(i)->timeline_in(true) - timeline_in;
|
||||
if (validator > 0) {
|
||||
@@ -139,12 +144,13 @@ void Cacher::CacheAudioWorker() {
|
||||
//timeline_in = nests_.at(i)->timeline_in(true);
|
||||
}
|
||||
|
||||
last_fr = nests_.at(i)->sequence->frame_rate;
|
||||
last_fr = nests_.at(i)->track()->sequence()->frame_rate;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp_reverse) {
|
||||
long seq_end = olive::ActiveSequence->getEndFrame();
|
||||
// FIXME breakable?
|
||||
long seq_end = Timeline::GetTopSequence()->GetEndFrame();
|
||||
timeline_in = seq_end - timeline_in;
|
||||
timeline_out = seq_end - timeline_out;
|
||||
target_frame = seq_end - target_frame;
|
||||
@@ -156,16 +162,16 @@ void Cacher::CacheAudioWorker() {
|
||||
|
||||
while (true) {
|
||||
AVFrame* frame;
|
||||
int nb_bytes = INT_MAX;
|
||||
int nb_samples = INT_MAX;
|
||||
|
||||
if (clip->media() == nullptr) {
|
||||
frame = frame_;
|
||||
nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast<AVSampleFormat>(frame->format)) * frame->channels;
|
||||
while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_bytes) && nb_bytes > 0) {
|
||||
nb_samples = frame->nb_samples;
|
||||
while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_samples) && nb_samples > 0) {
|
||||
// create "new frame"
|
||||
memset(frame_->data[0], 0, nb_bytes);
|
||||
apply_audio_effects(clip, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests_);
|
||||
frame_->pts += nb_bytes;
|
||||
memset(frame_->data[0], 0, nb_samples);
|
||||
apply_audio_effects(clip, samples_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_samples, frame->channels, nests_);
|
||||
frame_->pts += nb_samples;
|
||||
frame_sample_index_ = 0;
|
||||
if (audio_buffer_write == 0) {
|
||||
audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame));
|
||||
@@ -183,7 +189,7 @@ void Cacher::CacheAudioWorker() {
|
||||
|
||||
// retrieve frame
|
||||
bool new_frame = false;
|
||||
while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_bytes) && nb_bytes > 0) {
|
||||
while ((frame_sample_index_ == -1 || frame_sample_index_ >= nb_samples) && nb_samples > 0) {
|
||||
|
||||
// no more audio left in frame, get a new one
|
||||
if (!reached_end) {
|
||||
@@ -337,10 +343,10 @@ void Cacher::CacheAudioWorker() {
|
||||
if (frame_sample_index_ < 0) {
|
||||
frame_sample_index_ = 0;
|
||||
} else {
|
||||
frame_sample_index_ -= nb_bytes;
|
||||
frame_sample_index_ -= nb_samples;
|
||||
}
|
||||
|
||||
nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast<AVSampleFormat>(frame->format)) * frame->channels;
|
||||
nb_samples = frame->nb_samples;
|
||||
|
||||
if (audio_just_reset) {
|
||||
// get precise sample offset for the elected clip_in from this audio frame
|
||||
@@ -355,7 +361,7 @@ void Cacher::CacheAudioWorker() {
|
||||
dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (reverse_target * timebase);
|
||||
dout << "fsi-calc:" << frame_sample_index;
|
||||
#endif
|
||||
if (reverse_audio) frame_sample_index_ = nb_bytes - frame_sample_index_;
|
||||
if (reverse_audio) frame_sample_index_ = nb_samples - frame_sample_index_;
|
||||
audio_just_reset = false;
|
||||
}
|
||||
|
||||
@@ -392,9 +398,19 @@ void Cacher::CacheAudioWorker() {
|
||||
#endif
|
||||
|
||||
// apply any audio effects to the data
|
||||
if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast<AVSampleFormat>(frame->format)) * frame->channels;
|
||||
if (nb_samples == INT_MAX) {
|
||||
nb_samples = frame->nb_samples;
|
||||
}
|
||||
if (new_frame) {
|
||||
apply_audio_effects(clip, bytes_to_seconds(audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)clip->clip_in(true)/clip->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests_);
|
||||
apply_audio_effects(clip,
|
||||
samples_to_seconds(audio_buffer_write, 2, current_audio_freq())
|
||||
+ audio_ibuffer_timecode
|
||||
+ (double(clip->clip_in(true))/clip->track()->sequence()->frame_rate)
|
||||
- (double(timeline_in)/last_fr),
|
||||
frame,
|
||||
nb_samples,
|
||||
frame->channels,
|
||||
nests_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,30 +418,25 @@ void Cacher::CacheAudioWorker() {
|
||||
if (frame->nb_samples == 0) {
|
||||
break;
|
||||
} else {
|
||||
qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->sequence->frame_rate, timeline_out);
|
||||
qint64 buffer_timeline_out = get_buffer_offset_from_frame(clip->track()->sequence()->frame_rate, timeline_out);
|
||||
|
||||
audio_write_lock.lock();
|
||||
|
||||
int sample_skip = 4*qMax(0, qAbs(playback_speed_)-1);
|
||||
int sample_byte_size = av_get_bytes_per_sample(static_cast<AVSampleFormat>(frame->format));
|
||||
int sample_skip = qMax(0, qAbs(playback_speed_)-1);
|
||||
|
||||
while (frame_sample_index_ < nb_bytes
|
||||
while (frame_sample_index_ < nb_samples
|
||||
&& audio_buffer_write < audio_ibuffer_read+(audio_ibuffer_size>>1)
|
||||
&& audio_buffer_write < buffer_timeline_out) {
|
||||
for (int i=0;i<frame->channels;i++) {
|
||||
int upper_byte_index = (audio_buffer_write+1)%audio_ibuffer_size;
|
||||
int lower_byte_index = (audio_buffer_write)%audio_ibuffer_size;
|
||||
qint16 old_sample = static_cast<qint16>((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
|
||||
qint16 new_sample = static_cast<qint16>((frame->data[0][frame_sample_index_+1] & 0xFF) << 8 | (frame->data[0][frame_sample_index_] & 0xFF));
|
||||
qint16 mixed_sample = mix_audio_sample(old_sample, new_sample);
|
||||
int buffer_index = audio_buffer_write%audio_ibuffer_size;
|
||||
|
||||
audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF);
|
||||
audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF);
|
||||
audio_ibuffer[buffer_index] += reinterpret_cast<float*>(frame->data[i])[frame_sample_index_];
|
||||
|
||||
audio_buffer_write+=sample_byte_size;
|
||||
frame_sample_index_+=sample_byte_size;
|
||||
audio_buffer_write++;
|
||||
}
|
||||
|
||||
frame_sample_index_++;
|
||||
|
||||
frame_sample_index_ += sample_skip;
|
||||
|
||||
if (audio_reset_) break;
|
||||
@@ -443,7 +454,7 @@ void Cacher::CacheAudioWorker() {
|
||||
if (audio_thread != nullptr) audio_thread->notifyReceiver();
|
||||
}
|
||||
|
||||
if (frame_sample_index_ >= nb_bytes) {
|
||||
if (frame_sample_index_ >= nb_samples) {
|
||||
frame_sample_index_ = -1;
|
||||
} else {
|
||||
// assume we have no more data to send
|
||||
@@ -597,15 +608,15 @@ void Cacher::CacheVideoWorker() {
|
||||
// For reversed playback, we flip the queue stats as "upcoming" frames are going to be played before the "previous"
|
||||
// frames now
|
||||
if (reversed) {
|
||||
previous_queue_type = olive::CurrentConfig.upcoming_queue_type;
|
||||
previous_queue_size = olive::CurrentConfig.upcoming_queue_size;
|
||||
upcoming_queue_type = olive::CurrentConfig.previous_queue_type;
|
||||
upcoming_queue_size = olive::CurrentConfig.previous_queue_size;
|
||||
previous_queue_type = olive::config.upcoming_queue_type;
|
||||
previous_queue_size = olive::config.upcoming_queue_size;
|
||||
upcoming_queue_type = olive::config.previous_queue_type;
|
||||
upcoming_queue_size = olive::config.previous_queue_size;
|
||||
} else {
|
||||
previous_queue_type = olive::CurrentConfig.previous_queue_type;
|
||||
previous_queue_size = olive::CurrentConfig.previous_queue_size;
|
||||
upcoming_queue_type = olive::CurrentConfig.upcoming_queue_type;
|
||||
upcoming_queue_size = olive::CurrentConfig.upcoming_queue_size;
|
||||
previous_queue_type = olive::config.previous_queue_type;
|
||||
previous_queue_size = olive::config.previous_queue_size;
|
||||
upcoming_queue_type = olive::config.upcoming_queue_type;
|
||||
upcoming_queue_size = olive::config.upcoming_queue_size;
|
||||
}
|
||||
|
||||
// Determine "previous" queue statistics
|
||||
@@ -794,7 +805,7 @@ void Cacher::CacheVideoWorker() {
|
||||
void Cacher::Reset() {
|
||||
// if we seek to a whole other place in the timeline, we'll need to reset the cache with new values
|
||||
if (clip->media() == nullptr) {
|
||||
if (clip->track() >= 0) {
|
||||
if (clip->type() == olive::kTypeAudio) {
|
||||
// a null-media audio clip is usually an auto-generated sound clip such as Tone or Noise
|
||||
reached_end = false;
|
||||
audio_target_frame = playhead_;
|
||||
@@ -859,10 +870,8 @@ Cacher::Cacher(Clip* c) :
|
||||
{}
|
||||
|
||||
void Cacher::OpenWorker() {
|
||||
qint64 time_start = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
// set some defaults for the audio cacher
|
||||
if (clip->track() >= 0) {
|
||||
if (clip->type() == olive::kTypeAudio) {
|
||||
audio_reset_ = false;
|
||||
frame_sample_index_ = -1;
|
||||
audio_buffer_write = 0;
|
||||
@@ -870,10 +879,10 @@ void Cacher::OpenWorker() {
|
||||
reached_end = false;
|
||||
|
||||
if (clip->media() == nullptr) {
|
||||
if (clip->track() >= 0) {
|
||||
if (clip->type() == olive::kTypeAudio) {
|
||||
frame_ = av_frame_alloc();
|
||||
frame_->format = kDestSampleFmt;
|
||||
frame_->channel_layout = clip->sequence->audio_layout;
|
||||
frame_->channel_layout = clip->track()->sequence()->audio_layout;
|
||||
frame_->channels = av_get_channel_layout_nb_channels(frame_->channel_layout);
|
||||
frame_->sample_rate = current_audio_freq();
|
||||
frame_->nb_samples = 2048;
|
||||
@@ -891,7 +900,8 @@ void Cacher::OpenWorker() {
|
||||
QByteArray ba;
|
||||
|
||||
// do we have a proxy?
|
||||
if (m->proxy
|
||||
if ((!olive::Global->is_exporting() || !olive::config.dont_use_proxies_on_export)
|
||||
&& m->proxy
|
||||
&& !m->proxy_path.isEmpty()
|
||||
&& QFileInfo::exists(m->proxy_path)) {
|
||||
ba = m->proxy_path.toUtf8();
|
||||
@@ -989,7 +999,26 @@ void Cacher::OpenWorker() {
|
||||
last_filter = yadif_filter;
|
||||
}
|
||||
|
||||
const char* chosen_format = av_get_pix_fmt_name(kDestPixFmt);
|
||||
AVPixelFormat possible_pix_fmts[] = {
|
||||
AV_PIX_FMT_RGBA,
|
||||
AV_PIX_FMT_RGBA64,
|
||||
AV_PIX_FMT_NONE
|
||||
};
|
||||
|
||||
AVPixelFormat pix_fmt = avcodec_find_best_pix_fmt_of_list(possible_pix_fmts,
|
||||
static_cast<AVPixelFormat>(stream->codecpar->format),
|
||||
1,
|
||||
nullptr);
|
||||
|
||||
if (pix_fmt == AV_PIX_FMT_RGBA) {
|
||||
qDebug() << "This is an 8-bit image.";
|
||||
media_pixel_format_ = olive::PIX_FMT_RGBA8;
|
||||
} else {
|
||||
qDebug() << "This is an HDR image.";
|
||||
media_pixel_format_ = olive::PIX_FMT_RGBA16;
|
||||
}
|
||||
|
||||
const char* chosen_format = av_get_pix_fmt_name(pix_fmt);
|
||||
snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format);
|
||||
|
||||
AVFilterContext* format_conv;
|
||||
@@ -1011,8 +1040,8 @@ void Cacher::OpenWorker() {
|
||||
|
||||
reverse_frame->format = kDestSampleFmt;
|
||||
reverse_frame->nb_samples = current_audio_freq()*10;
|
||||
reverse_frame->channel_layout = clip->sequence->audio_layout;
|
||||
reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout);
|
||||
reverse_frame->channel_layout = clip->track()->sequence()->audio_layout;
|
||||
reverse_frame->channels = av_get_channel_layout_nb_channels(clip->track()->sequence()->audio_layout);
|
||||
av_frame_get_buffer(reverse_frame, 0);
|
||||
|
||||
queue_.append(reverse_frame);
|
||||
@@ -1092,13 +1121,13 @@ void Cacher::OpenWorker() {
|
||||
frame_ = av_frame_alloc();
|
||||
}
|
||||
|
||||
qInfo() << "Clip opened on track" << clip->track() << "(took" << (QDateTime::currentMSecsSinceEpoch() - time_start) << "ms)";
|
||||
qInfo() << "Clip opened on track" << clip->track();
|
||||
|
||||
is_valid_state_ = true;
|
||||
}
|
||||
|
||||
void Cacher::CacheWorker() {
|
||||
if (clip->track() < 0) {
|
||||
if (clip->type() == olive::kTypeVideo) {
|
||||
// clip is a video track, start caching video
|
||||
CacheVideoWorker();
|
||||
} else {
|
||||
@@ -1189,7 +1218,7 @@ void Cacher::Open()
|
||||
caching_ = true;
|
||||
queued_ = false;
|
||||
|
||||
start((clip->track() < 0) ? QThread::HighPriority : QThread::TimeCriticalPriority);
|
||||
start((clip->type() == olive::kTypeVideo) ? QThread::HighPriority : QThread::TimeCriticalPriority);
|
||||
}
|
||||
|
||||
void Cacher::Cache(long playhead, bool scrubbing, QVector<Clip*>& nests, int playback_speed)
|
||||
@@ -1338,6 +1367,11 @@ ClipQueue *Cacher::queue()
|
||||
return &queue_;
|
||||
}
|
||||
|
||||
const olive::PixelFormat &Cacher::media_pixel_format()
|
||||
{
|
||||
return media_pixel_format_;
|
||||
}
|
||||
|
||||
int Cacher::RetrieveFrameFromDecoder(AVFrame* f) {
|
||||
int result = 0;
|
||||
int receive_ret;
|
||||
|
||||
+27
-12
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -43,6 +43,7 @@ extern "C" {
|
||||
#include <QMutex>
|
||||
|
||||
#include "rendering/clipqueue.h"
|
||||
#include "rendering/pixelformats.h"
|
||||
|
||||
class Clip;
|
||||
|
||||
@@ -254,6 +255,15 @@ public:
|
||||
*/
|
||||
ClipQueue* queue();
|
||||
|
||||
/**
|
||||
* @brief Retrieve OpenGL information about this media's bit depth
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A olive::rendering::PixelFormat value corresponding to a member of olive::rendering::bit_depths.
|
||||
*/
|
||||
const olive::PixelFormat& media_pixel_format();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Reference to the parent clip. Set in the constructor and never changed during this object's lifetime.
|
||||
@@ -582,6 +592,11 @@ private:
|
||||
* @brief Internal function using the Cacher's known information to determine whether this media is playing in reverse
|
||||
*/
|
||||
bool IsReversed();
|
||||
|
||||
/**
|
||||
* @brief Internal struct holding bit depth information for the current media
|
||||
*/
|
||||
olive::PixelFormat media_pixel_format_;
|
||||
};
|
||||
|
||||
#endif // CACHER_H
|
||||
|
||||
+12
-12
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
|
||||
+12
-12
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
|
||||
+30
-32
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -29,13 +29,11 @@ extern "C" {
|
||||
|
||||
#include <QApplication>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
#include "global/global.h"
|
||||
#include "timeline/sequence.h"
|
||||
#include "panels/panels.h"
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "rendering/renderthread.h"
|
||||
@@ -162,7 +160,8 @@ bool ExportThread::SetupVideo() {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Set export to be multithreaded
|
||||
@@ -193,16 +192,16 @@ bool ExportThread::SetupVideo() {
|
||||
video_frame = av_frame_alloc();
|
||||
av_frame_make_writable(video_frame);
|
||||
video_frame->format = AV_PIX_FMT_RGBA;
|
||||
video_frame->width = olive::ActiveSequence->width;
|
||||
video_frame->height = olive::ActiveSequence->height;
|
||||
video_frame->width = params_.sequence->width;
|
||||
video_frame->height = params_.sequence->height;
|
||||
av_frame_get_buffer(video_frame, 0);
|
||||
|
||||
av_init_packet(&video_pkt);
|
||||
|
||||
// Set up conversion context
|
||||
sws_ctx = sws_getContext(
|
||||
olive::ActiveSequence->width,
|
||||
olive::ActiveSequence->height,
|
||||
params_.sequence->width,
|
||||
params_.sequence->height,
|
||||
AV_PIX_FMT_RGBA,
|
||||
params_.video_width,
|
||||
params_.video_height,
|
||||
@@ -289,7 +288,7 @@ bool ExportThread::SetupAudio() {
|
||||
acodec_ctx->channel_layout,
|
||||
acodec_ctx->sample_fmt,
|
||||
acodec_ctx->sample_rate,
|
||||
olive::ActiveSequence->audio_layout,
|
||||
params_.sequence->audio_layout,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
acodec_ctx->sample_rate,
|
||||
0,
|
||||
@@ -408,14 +407,14 @@ void ExportThread::Export()
|
||||
long remaining_frames, frame_count = 1;
|
||||
|
||||
// Use Sequence Viewer's render thread - TODO separate this into a new render thread for background rendering
|
||||
RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer();
|
||||
RenderThread* renderer = panel_sequence_viewer->viewer_widget()->get_renderer();
|
||||
|
||||
// Override connection from RenderThread
|
||||
disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
|
||||
disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint()));
|
||||
connect(renderer, SIGNAL(ready()), this, SLOT(wake()));
|
||||
|
||||
// Loop from now (set to the beginning frame earlier) to the end of the frame
|
||||
while (olive::ActiveSequence->playhead <= params_.end_frame && !interrupt_) {
|
||||
while (params_.sequence->playhead <= params_.end_frame && !interrupt_) {
|
||||
|
||||
// Start timing how long this frame will take
|
||||
frame_start_time = QDateTime::currentMSecsSinceEpoch();
|
||||
@@ -424,14 +423,14 @@ void ExportThread::Export()
|
||||
if (params_.audio_enabled) {
|
||||
waiting_for_audio_ = true;
|
||||
SetAudioWakeObject(this);
|
||||
olive::rendering::compose_audio(nullptr, olive::ActiveSequence.get(), 1, true);
|
||||
olive::rendering::compose_audio(nullptr, params_.sequence, 1, true);
|
||||
}
|
||||
|
||||
// If we're exporting video, trigger a render on the RenderThread
|
||||
if (params_.video_enabled) {
|
||||
do {
|
||||
// TODO optimize by rendering the next frame while encoding the last
|
||||
renderer->start_render(nullptr, olive::ActiveSequence.get(), 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4);
|
||||
renderer->start_render(nullptr, params_.sequence, 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4);
|
||||
|
||||
// Wait for RenderThread to return
|
||||
waitCond.wait(&mutex);
|
||||
@@ -450,7 +449,7 @@ void ExportThread::Export()
|
||||
}
|
||||
|
||||
// Get the current sequence playhead in seconds (used for timestamp calculations later on)
|
||||
double timecode_secs = double(olive::ActiveSequence->playhead - params_.start_frame) / olive::ActiveSequence->frame_rate;
|
||||
double timecode_secs = double(params_.sequence->playhead - params_.start_frame) / params_.sequence->frame_rate;
|
||||
|
||||
// If we're exporting video, construct an AVFrame in the destination codec's pixel format to convert the raw RGBA
|
||||
// OpenGL buffer to
|
||||
@@ -536,15 +535,15 @@ void ExportThread::Export()
|
||||
// Generating encoding statistics (e.g. the time it took to encode this frame/estimated remaining time)
|
||||
frame_time = (QDateTime::currentMSecsSinceEpoch()-frame_start_time);
|
||||
total_time += frame_time;
|
||||
remaining_frames = (params_.end_frame - olive::ActiveSequence->playhead);
|
||||
remaining_frames = (params_.end_frame - params_.sequence->playhead);
|
||||
avg_time = (total_time/frame_count);
|
||||
eta = (remaining_frames*avg_time);
|
||||
|
||||
// Emit a signal for the percent of the sequence that's been encoded so far
|
||||
emit ProgressChanged(qRound((double(olive::ActiveSequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta);
|
||||
emit ProgressChanged(qRound((double(params_.sequence->playhead - params_.start_frame) / double(params_.end_frame - params_.start_frame)) * 100.0), eta);
|
||||
|
||||
// Increment sequence playhead
|
||||
olive::ActiveSequence->playhead++;
|
||||
params_.sequence->playhead++;
|
||||
|
||||
// Increment frame count (used for generating encoding statistics above)
|
||||
frame_count++;
|
||||
@@ -552,7 +551,7 @@ void ExportThread::Export()
|
||||
|
||||
// Restore original connection from RenderThread
|
||||
disconnect(renderer, SIGNAL(ready()), this, SLOT(wake()));
|
||||
connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint()));
|
||||
connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint()));
|
||||
|
||||
if (interrupt_) {
|
||||
return;
|
||||
@@ -561,8 +560,7 @@ void ExportThread::Export()
|
||||
if (params_.video_enabled) vpkt_alloc = true;
|
||||
if (params_.audio_enabled) apkt_alloc = true;
|
||||
|
||||
olive::Global->set_rendering_state(false);
|
||||
close_active_clips(olive::ActiveSequence.get());
|
||||
olive::Global->set_export_state(false);
|
||||
|
||||
// If audio is enabled, flush the rest of the audio out of swresample
|
||||
if (params_.audio_enabled) {
|
||||
|
||||
+26
-20
@@ -1,31 +1,37 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
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 EXPORTTHREAD_H
|
||||
#define EXPORTTHREAD_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#include <QThread>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "timeline/sequence.h"
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVFrame;
|
||||
@@ -35,19 +41,19 @@ struct AVCodec;
|
||||
struct SwsContext;
|
||||
struct SwrContext;
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#define COMPRESSION_TYPE_CBR 0
|
||||
#define COMPRESSION_TYPE_CFR 1
|
||||
#define COMPRESSION_TYPE_TARGETSIZE 2
|
||||
#define COMPRESSION_TYPE_TARGETBR 3
|
||||
enum CompressionType {
|
||||
COMPRESSION_TYPE_CBR,
|
||||
COMPRESSION_TYPE_CFR,
|
||||
COMPRESSION_TYPE_TARGETSIZE,
|
||||
COMPRESSION_TYPE_TARGETBR
|
||||
};
|
||||
|
||||
// structs that store parameters passed from the export dialogs to this thread
|
||||
|
||||
struct ExportParams {
|
||||
|
||||
// export parameters
|
||||
Sequence* sequence;
|
||||
QString filename;
|
||||
bool video_enabled;
|
||||
int video_codec;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "framebuffercollection.h"
|
||||
|
||||
FramebufferCollection::FramebufferCollection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FramebufferCollection::Create(QOpenGLContext* ctx,
|
||||
int width,
|
||||
int height,
|
||||
int count)
|
||||
{
|
||||
Q_ASSERT(count > 1);
|
||||
|
||||
fbo_.resize(count);
|
||||
for (int i=0;i<fbo_.size();i++) {
|
||||
fbo_[i].Create(ctx, width, height);
|
||||
}
|
||||
fbo_index_ = -1;
|
||||
}
|
||||
|
||||
void FramebufferCollection::Destroy()
|
||||
{
|
||||
fbo_.clear();
|
||||
}
|
||||
|
||||
GLuint FramebufferCollection::CurrentTexture()
|
||||
{
|
||||
if (!IsCreated() || fbo_index_ < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return fbo_.at(fbo_index_%fbo_.size()).texture();
|
||||
}
|
||||
|
||||
const FramebufferObject& FramebufferCollection::CurrentFramebuffer()
|
||||
{
|
||||
Q_ASSERT(IsCreated());
|
||||
|
||||
return fbo_.at(fbo_index_%fbo_.size());
|
||||
}
|
||||
|
||||
const FramebufferObject& FramebufferCollection::NextFramebuffer()
|
||||
{
|
||||
fbo_index_++;
|
||||
|
||||
return CurrentFramebuffer();
|
||||
}
|
||||
|
||||
bool FramebufferCollection::TextureBelongsToCollection(GLuint tex)
|
||||
{
|
||||
if (tex == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0;i<fbo_.size();i++) {
|
||||
if (fbo_.at(i).texture() == tex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FramebufferCollection::IsCreated()
|
||||
{
|
||||
return !fbo_.isEmpty();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef FRAMEBUFFERCOLLECTION_H
|
||||
#define FRAMEBUFFERCOLLECTION_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "framebufferobject.h"
|
||||
|
||||
class FramebufferCollection
|
||||
{
|
||||
public:
|
||||
FramebufferCollection();
|
||||
|
||||
void Create(QOpenGLContext *ctx, int width, int height, int count);
|
||||
void Destroy();
|
||||
|
||||
GLuint CurrentTexture();
|
||||
const FramebufferObject& CurrentFramebuffer();
|
||||
const FramebufferObject& NextFramebuffer();
|
||||
bool TextureBelongsToCollection(GLuint tex);
|
||||
|
||||
bool IsCreated();
|
||||
private:
|
||||
QVector<FramebufferObject> fbo_;
|
||||
int fbo_index_;
|
||||
};
|
||||
|
||||
#endif // FRAMEBUFFERCOLLECTION_H
|
||||
@@ -1,6 +1,32 @@
|
||||
/***
|
||||
|
||||
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 "framebufferobject.h"
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QDebug>
|
||||
|
||||
#include "global/config.h"
|
||||
#include "global/global.h"
|
||||
#include "pixelformats.h"
|
||||
|
||||
FramebufferObject::FramebufferObject() :
|
||||
buffer_(0),
|
||||
@@ -41,8 +67,20 @@ void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height)
|
||||
f->glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
|
||||
// allocate storage for texture
|
||||
f->glTexImage2D(
|
||||
GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr
|
||||
const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->is_exporting() ?
|
||||
olive::config.export_bit_depth :
|
||||
olive::config.playback_bit_depth);
|
||||
|
||||
ctx->functions()->glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
bit_depth.internal_format,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
bit_depth.pixel_format,
|
||||
bit_depth.pixel_type,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// set texture filtering to bilinear
|
||||
@@ -50,10 +88,14 @@ void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height)
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// attach texture to framebuffer
|
||||
f->glFramebufferTexture2D(
|
||||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0
|
||||
ctx->extraFunctions()->glFramebufferTexture2D(
|
||||
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0
|
||||
);
|
||||
|
||||
// clear new texture
|
||||
ctx->functions()->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// release texture
|
||||
f->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
@@ -72,12 +114,44 @@ void FramebufferObject::Destroy()
|
||||
ctx_ = nullptr;
|
||||
}
|
||||
|
||||
const GLuint &FramebufferObject::buffer()
|
||||
void FramebufferObject::BindBuffer() const
|
||||
{
|
||||
if (ctx_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
|
||||
}
|
||||
|
||||
void FramebufferObject::ReleaseBuffer() const
|
||||
{
|
||||
if (ctx_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void FramebufferObject::BindTexture() const
|
||||
{
|
||||
if (ctx_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx_->functions()->glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
}
|
||||
|
||||
void FramebufferObject::ReleaseTexture() const
|
||||
{
|
||||
if (ctx_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx_->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
const GLuint &FramebufferObject::buffer() const
|
||||
{
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
const GLuint &FramebufferObject::texture()
|
||||
const GLuint &FramebufferObject::texture() const
|
||||
{
|
||||
return texture_;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
/***
|
||||
|
||||
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 FRAMEBUFFEROBJECT_H
|
||||
#define FRAMEBUFFEROBJECT_H
|
||||
|
||||
@@ -13,8 +33,14 @@ public:
|
||||
void Create(QOpenGLContext* ctx, int width, int height);
|
||||
void Destroy();
|
||||
|
||||
const GLuint& buffer();
|
||||
const GLuint& texture();
|
||||
const GLuint& buffer() const;
|
||||
const GLuint& texture() const;
|
||||
|
||||
void BindBuffer() const;
|
||||
void ReleaseBuffer() const;
|
||||
|
||||
void BindTexture() const;
|
||||
void ReleaseTexture() const;
|
||||
private:
|
||||
QOpenGLContext* ctx_;
|
||||
GLuint buffer_;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/***
|
||||
|
||||
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 "pixelformats.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace olive {
|
||||
|
||||
QVector<PixelFormatInfo> pixel_formats;
|
||||
|
||||
void InitializePixelFormats() {
|
||||
|
||||
pixel_formats.resize(PIX_FMT_COUNT);
|
||||
|
||||
pixel_formats[PIX_FMT_RGBA8].name = QCoreApplication::translate("bitdepths", "8-bit");
|
||||
pixel_formats[PIX_FMT_RGBA8].internal_format = GL_RGBA8;
|
||||
pixel_formats[PIX_FMT_RGBA8].pixel_format = GL_RGBA;
|
||||
pixel_formats[PIX_FMT_RGBA8].pixel_type = GL_UNSIGNED_BYTE;
|
||||
pixel_formats[PIX_FMT_RGBA8].bytes_per_pixel = 4;
|
||||
|
||||
pixel_formats[PIX_FMT_RGBA16].name = QCoreApplication::translate("bitdepths", "16-bit Integer");
|
||||
pixel_formats[PIX_FMT_RGBA16].internal_format = GL_RGBA16;
|
||||
pixel_formats[PIX_FMT_RGBA16].pixel_format = GL_RGBA;
|
||||
pixel_formats[PIX_FMT_RGBA16].pixel_type = GL_UNSIGNED_SHORT;
|
||||
pixel_formats[PIX_FMT_RGBA16].bytes_per_pixel = 8;
|
||||
|
||||
pixel_formats[PIX_FMT_RGBA16F].name = QCoreApplication::translate("bitdepths", "Half-Float (16-bit)");
|
||||
pixel_formats[PIX_FMT_RGBA16F].internal_format = GL_RGBA16F;
|
||||
pixel_formats[PIX_FMT_RGBA16F].pixel_format = GL_RGBA;
|
||||
pixel_formats[PIX_FMT_RGBA16F].pixel_type = GL_HALF_FLOAT;
|
||||
pixel_formats[PIX_FMT_RGBA16F].bytes_per_pixel = 8;
|
||||
|
||||
pixel_formats[PIX_FMT_RGBA32F].name = QCoreApplication::translate("bitdepths", "Full-Float (32-bit)");
|
||||
pixel_formats[PIX_FMT_RGBA32F].internal_format = GL_RGBA32F;
|
||||
pixel_formats[PIX_FMT_RGBA32F].pixel_format = GL_RGBA;
|
||||
pixel_formats[PIX_FMT_RGBA32F].pixel_type = GL_FLOAT;
|
||||
pixel_formats[PIX_FMT_RGBA32F].bytes_per_pixel = 16;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/***
|
||||
|
||||
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 BITDEPTHS_H
|
||||
#define BITDEPTHS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief The PixelFormat enum
|
||||
*
|
||||
* Olive's internal supported pixel formats. With the exception of OLIVE_PIX_FMT_COUNT, these must all
|
||||
* be defined in InitializePixelFormats().
|
||||
*/
|
||||
enum PixelFormat {
|
||||
PIX_FMT_RGBA8,
|
||||
PIX_FMT_RGBA16,
|
||||
PIX_FMT_RGBA16F,
|
||||
PIX_FMT_RGBA32F,
|
||||
PIX_FMT_COUNT
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The PixelFormatInfo struct
|
||||
*
|
||||
* A struct of information pertaining to each enum PixelFormat. Primarily this is a means of retrieving OpenGL texture
|
||||
* information for different pixel formats/bit depths. Using the values in pixel_formats is always recommended over
|
||||
* manually using OpenGL constants (e.g. GL_RGBA or GL_RGBA32F) directly.
|
||||
*/
|
||||
struct PixelFormatInfo {
|
||||
QString name;
|
||||
GLint internal_format;
|
||||
GLenum pixel_format;
|
||||
GLenum pixel_type;
|
||||
int bytes_per_pixel;
|
||||
};
|
||||
|
||||
extern QVector<PixelFormatInfo> pixel_formats;
|
||||
|
||||
void InitializePixelFormats();
|
||||
|
||||
}
|
||||
|
||||
#endif // BITDEPTHS_H
|
||||
@@ -0,0 +1,29 @@
|
||||
/***
|
||||
|
||||
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 QOPENGLSHADERPROGRAMPTR_H
|
||||
#define QOPENGLSHADERPROGRAMPTR_H
|
||||
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <memory>
|
||||
|
||||
using QOpenGLShaderProgramPtr = std::shared_ptr<QOpenGLShaderProgram>;
|
||||
|
||||
#endif // QOPENGLSHADERPROGRAMPTR_H
|
||||
+357
-228
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -24,32 +24,57 @@ extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef OLIVE_OCIO
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
#endif
|
||||
|
||||
#include "timeline/clip.h"
|
||||
#include "timeline/sequence.h"
|
||||
#include "project/media.h"
|
||||
#include "effects/effect.h"
|
||||
#include "nodes/node.h"
|
||||
#include "project/footage.h"
|
||||
#include "effects/transition.h"
|
||||
|
||||
#include "ui/collapsiblewidget.h"
|
||||
|
||||
#include "rendering/audio.h"
|
||||
|
||||
#include "global/math.h"
|
||||
#include "global/timing.h"
|
||||
#include "global/config.h"
|
||||
|
||||
#include "panels/timeline.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "qopenglshaderprogramptr.h"
|
||||
#include "shadergenerators.h"
|
||||
|
||||
GLfloat olive::rendering::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
|
||||
};
|
||||
|
||||
GLfloat olive::rendering::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
|
||||
};
|
||||
|
||||
GLfloat olive::rendering::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
|
||||
};
|
||||
|
||||
void PrepareToDraw(QOpenGLFunctions* f) {
|
||||
f->glGenerateMipmap(GL_TEXTURE_2D);
|
||||
@@ -59,63 +84,98 @@ void PrepareToDraw(QOpenGLFunctions* f) {
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
}
|
||||
|
||||
void full_blit() {
|
||||
PrepareToDraw(QOpenGLContext::currentContext()->functions());
|
||||
void olive::rendering::Blit(QOpenGLShaderProgram* pipeline, bool flipped, QMatrix4x4 matrix) {
|
||||
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glOrtho(0, 1, 0, 1, -1, 1);
|
||||
QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions();
|
||||
PrepareToDraw(func);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0, 0); // top left
|
||||
glVertex2f(0, 0); // top left
|
||||
glTexCoord2f(1, 0); // top right
|
||||
glVertex2f(1, 0); // top right
|
||||
glTexCoord2f(1, 1); // bottom right
|
||||
glVertex2f(1, 1); // bottom right
|
||||
glTexCoord2f(0, 1); // bottom left
|
||||
glVertex2f(0, 1); // bottom left
|
||||
glEnd();
|
||||
QOpenGLVertexArrayObject m_vao;
|
||||
m_vao.create();
|
||||
m_vao.bind();
|
||||
|
||||
QOpenGLBuffer m_vbo;
|
||||
m_vbo.create();
|
||||
m_vbo.bind();
|
||||
m_vbo.allocate(blit_vertices, 18 * sizeof(GLfloat));
|
||||
m_vbo.release();
|
||||
|
||||
QOpenGLBuffer m_vbo2;
|
||||
m_vbo2.create();
|
||||
m_vbo2.bind();
|
||||
m_vbo2.allocate(flipped ? flipped_blit_texcoords : blit_texcoords, 12 * sizeof(GLfloat));
|
||||
m_vbo2.release();
|
||||
|
||||
pipeline->bind();
|
||||
|
||||
pipeline->setUniformValue("mvp_matrix", matrix);
|
||||
pipeline->setUniformValue("texture", 0);
|
||||
|
||||
GLuint vertex_location = pipeline->attributeLocation("a_position");
|
||||
m_vbo.bind();
|
||||
func->glEnableVertexAttribArray(vertex_location);
|
||||
func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
m_vbo.release();
|
||||
|
||||
GLuint tex_location = pipeline->attributeLocation("a_texcoord");
|
||||
m_vbo2.bind();
|
||||
func->glEnableVertexAttribArray(tex_location);
|
||||
func->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
m_vbo2.release();
|
||||
|
||||
func->glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
pipeline->release();
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void draw_clip(QOpenGLContext* ctx, GLuint fbo, GLuint texture, bool clear) {
|
||||
void draw_clip(QOpenGLContext* ctx,
|
||||
QOpenGLShaderProgram* pipeline,
|
||||
GLuint fbo,
|
||||
GLuint texture,
|
||||
bool clear) {
|
||||
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
|
||||
|
||||
if (clear) {
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
full_blit();
|
||||
olive::rendering::Blit(pipeline);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
GLuint draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) {
|
||||
fbo->bind();
|
||||
GLuint draw_clip(QOpenGLContext* ctx,
|
||||
QOpenGLShaderProgram* pipeline,
|
||||
const FramebufferObject& fbo,
|
||||
GLuint texture,
|
||||
bool clear) {
|
||||
|
||||
fbo.BindBuffer();
|
||||
|
||||
if (clear) {
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
full_blit();
|
||||
olive::rendering::Blit(pipeline);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
fbo->release();
|
||||
fbo.ReleaseBuffer();
|
||||
|
||||
return fbo.texture();
|
||||
|
||||
return fbo->texture();
|
||||
}
|
||||
|
||||
void process_effect(Clip* c,
|
||||
Effect* e,
|
||||
void process_effect(QOpenGLContext* ctx,
|
||||
QOpenGLShaderProgram* pipeline,
|
||||
Clip* c,
|
||||
Node* e,
|
||||
double timecode,
|
||||
GLTextureCoords& coords,
|
||||
GLuint& composite_texture,
|
||||
@@ -123,21 +183,25 @@ void process_effect(Clip* c,
|
||||
bool& texture_failed,
|
||||
int data) {
|
||||
if (e->IsEnabled()) {
|
||||
if (e->Flags() & Effect::CoordsFlag) {
|
||||
if (e->Flags() & Node::CoordsFlag) {
|
||||
e->process_coords(timecode, coords, data);
|
||||
}
|
||||
bool can_process_shaders = ((e->Flags() & Effect::ShaderFlag) && olive::CurrentRuntimeConfig.shaders_are_enabled);
|
||||
if (can_process_shaders || (e->Flags() & Effect::SuperimposeFlag)) {
|
||||
e->startEffect();
|
||||
if (can_process_shaders && e->is_glsl_linked()) {
|
||||
bool can_process_shaders = ((e->Flags() & Node::ShaderFlag) && olive::runtime_config.shaders_are_enabled);
|
||||
if (can_process_shaders || (e->Flags() & Node::SuperimposeFlag)) {
|
||||
|
||||
if (!e->is_open()) {
|
||||
e->open();
|
||||
}
|
||||
|
||||
if (can_process_shaders && e->is_shader_linked()) {
|
||||
for (int i=0;i<e->getIterations();i++) {
|
||||
e->process_shader(timecode, coords, i);
|
||||
composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true);
|
||||
composite_texture = draw_clip(ctx, e->GetShaderPipeline(), c->fbo.at(fbo_switcher), composite_texture, true);
|
||||
fbo_switcher = !fbo_switcher;
|
||||
}
|
||||
}
|
||||
if (e->Flags() & Effect::SuperimposeFlag) {
|
||||
GLuint superimpose_texture = e->process_superimpose(timecode);
|
||||
if (e->Flags() & Node::SuperimposeFlag) {
|
||||
GLuint superimpose_texture = e->process_superimpose(ctx, timecode);
|
||||
|
||||
if (superimpose_texture == 0) {
|
||||
qWarning() << "Superimpose texture was nullptr, retrying...";
|
||||
@@ -150,38 +214,37 @@ void process_effect(Clip* c,
|
||||
} else {
|
||||
// if the source texture is not already a framebuffer texture,
|
||||
// we'll need to make it one before drawing a superimpose effect on it
|
||||
if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) {
|
||||
draw_clip(c->fbo[!fbo_switcher], composite_texture, true);
|
||||
if (composite_texture != c->fbo.at(0).texture() && composite_texture != c->fbo.at(1).texture()) {
|
||||
draw_clip(ctx, pipeline, c->fbo.at(!fbo_switcher), composite_texture, true);
|
||||
}
|
||||
|
||||
composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false);
|
||||
composite_texture = draw_clip(ctx, pipeline, c->fbo.at(!fbo_switcher), superimpose_texture, false);
|
||||
}
|
||||
}
|
||||
e->endEffect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
// qint64 time = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
GLuint final_fbo = params.main_buffer;
|
||||
GLuint final_fbo = params.type == olive::kTypeVideo ? params.main_buffer->buffer() : 0;
|
||||
|
||||
Sequence* s = params.seq;
|
||||
long playhead = s->playhead;
|
||||
|
||||
if (!params.nests.isEmpty()) {
|
||||
|
||||
for (int i=0;i<params.nests.size();i++) {
|
||||
s = params.nests.at(i)->media()->to_sequence().get();
|
||||
playhead += params.nests.at(i)->clip_in(true) - params.nests.at(i)->timeline_in(true);
|
||||
playhead = rescale_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate);
|
||||
playhead = rescale_frame_number(playhead, params.nests.at(i)->track()->sequence()->frame_rate, s->frame_rate);
|
||||
}
|
||||
|
||||
if (params.video && params.nests.last()->fbo != nullptr) {
|
||||
params.nests.last()->fbo[0]->bind();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
final_fbo = params.nests.last()->fbo[0]->handle();
|
||||
if (params.type == olive::kTypeVideo && !params.nests.last()->fbo.isEmpty()) {
|
||||
params.nests.last()->fbo.at(0).BindBuffer();
|
||||
params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
final_fbo = params.nests.last()->fbo.at(0).buffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int audio_track_count = 0;
|
||||
@@ -189,14 +252,15 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
QVector<Clip*> current_clips;
|
||||
|
||||
// loop through clips, find currently active, and sort by track
|
||||
for (int i=0;i<s->clips.size();i++) {
|
||||
QVector<Clip*> sequence_clips = s->GetAllClips();
|
||||
for (int i=0;i<sequence_clips.size();i++) {
|
||||
|
||||
Clip* c = s->clips.at(i).get();
|
||||
Clip* c = sequence_clips.at(i);
|
||||
|
||||
if (c != nullptr) {
|
||||
|
||||
// if clip is video and we're processing video
|
||||
if ((c->track() < 0) == params.video) {
|
||||
if (c->type() == params.type) {
|
||||
|
||||
bool clip_is_active = false;
|
||||
|
||||
@@ -205,7 +269,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
Footage* m = c->media()->to_footage();
|
||||
|
||||
// does the clip have a valid media source?
|
||||
if (!m->invalid && !(c->track() >= 0 && !is_audio_device_set())) {
|
||||
if (!m->invalid && !(c->type() == olive::kTypeAudio && !is_audio_device_set())) {
|
||||
|
||||
// is the media process and ready?
|
||||
if (m->ready) {
|
||||
@@ -222,7 +286,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
clip_is_active = true;
|
||||
|
||||
// increment audio track count
|
||||
if (c->track() >= 0) audio_track_count++;
|
||||
if (c->type() == olive::kTypeAudio) audio_track_count++;
|
||||
|
||||
} else if (c->IsOpen()) {
|
||||
|
||||
@@ -256,7 +320,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
|
||||
// track sorting is only necessary for video clips
|
||||
// audio clips are mixed equally, so we skip sorting for those
|
||||
if (params.video) {
|
||||
if (params.type == olive::kTypeVideo) {
|
||||
|
||||
// insertion sort by track
|
||||
for (int j=0;j<current_clips.size();j++) {
|
||||
@@ -277,18 +341,17 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
}
|
||||
}
|
||||
|
||||
if (params.video) {
|
||||
QMatrix4x4 projection;
|
||||
|
||||
if (params.type == olive::kTypeVideo) {
|
||||
// set default coordinates based on the sequence, with 0 in the direct center
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
params.ctx->functions()->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
int half_width = s->width/2;
|
||||
int half_height = s->height/2;
|
||||
glOrtho(-half_width, half_width, -half_height, half_height, -1, 10);
|
||||
|
||||
projection.ortho(-half_width, half_width, -half_height, half_height, -1, 1);
|
||||
}
|
||||
|
||||
// loop through current clips
|
||||
@@ -307,10 +370,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
|
||||
if (got_mutex && c->IsOpen()) {
|
||||
// if clip is a video clip
|
||||
if (c->track() < 0) {
|
||||
|
||||
// reset OpenGL to full color
|
||||
glColor4f(1.0, 1.0, 1.0, 1.0);
|
||||
if (c->type() == olive::kTypeVideo) {
|
||||
|
||||
// textureID variable contains texture to be drawn on screen at the end
|
||||
GLuint textureID = 0;
|
||||
@@ -319,47 +379,55 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
int video_width = c->media_width();
|
||||
int video_height = c->media_height();
|
||||
|
||||
// prepare framebuffers for backend drawing operations
|
||||
if (c->fbo.isEmpty()) {
|
||||
// create 3 fbos for nested sequences, 2 for most clips
|
||||
int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
|
||||
|
||||
c->fbo.resize(fbo_count);
|
||||
|
||||
for (int j=0;j<fbo_count;j++) {
|
||||
c->fbo[j].Create(params.ctx, video_width, video_height);
|
||||
}
|
||||
}
|
||||
|
||||
bool convert_frame_to_internal = false;
|
||||
|
||||
// if media is footage
|
||||
if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
|
||||
// retrieve video frame from cache and store it in c->texture
|
||||
c->Cache(qMax(playhead, c->timeline_in()), false, params.nests, params.playback_speed);
|
||||
c->Cache(qMax(playhead, c->timeline_in(true)), false, params.nests, params.playback_speed);
|
||||
if (!c->Retrieve()) {
|
||||
params.texture_failed = true;
|
||||
} else {
|
||||
// retrieve ID from c->texture
|
||||
textureID = c->texture->textureId();
|
||||
textureID = c->texture;
|
||||
}
|
||||
|
||||
if (textureID == 0) {
|
||||
|
||||
qWarning() << "Failed to create texture";
|
||||
}
|
||||
}
|
||||
|
||||
// prepare framebuffers for backend drawing operations
|
||||
if (c->fbo == nullptr) {
|
||||
// create 3 fbos for nested sequences, 2 for most clips
|
||||
int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
|
||||
} else {
|
||||
|
||||
c->fbo = new QOpenGLFramebufferObject* [size_t(fbo_count)];
|
||||
convert_frame_to_internal = true;
|
||||
|
||||
for (int j=0;j<fbo_count;j++) {
|
||||
c->fbo[j] = new QOpenGLFramebufferObject(video_width, video_height);
|
||||
}
|
||||
}
|
||||
|
||||
// if clip should actually be shown on screen in this frame
|
||||
if (playhead >= c->timeline_in(true)
|
||||
&& playhead < c->timeline_out(true)) {
|
||||
glPushMatrix();
|
||||
|
||||
// simple bool for switching between the two framebuffers
|
||||
bool fbo_switcher = false;
|
||||
|
||||
glViewport(0, 0, video_width, video_height);
|
||||
params.ctx->functions()->glViewport(0, 0, video_width, video_height);
|
||||
|
||||
if (c->media() != nullptr) {
|
||||
if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
|
||||
// for a nested sequence, run this function again on that sequence and retrieve the texture
|
||||
|
||||
// add nested sequence to nest list
|
||||
@@ -372,44 +440,70 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
params.nests.removeLast();
|
||||
|
||||
// compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1]
|
||||
fbo_switcher = true;
|
||||
fbo_switcher = !fbo_switcher;
|
||||
|
||||
} else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
|
||||
if (!c->media()->to_footage()->alpha_is_premultiplied) {
|
||||
// alpha is not premultiplied, we'll need to multiply it for the rest of the pipeline
|
||||
params.premultiply_program->bind();
|
||||
|
||||
textureID = draw_clip(c->fbo[0], textureID, true);
|
||||
|
||||
params.premultiply_program->release();
|
||||
|
||||
fbo_switcher = true;
|
||||
}
|
||||
|
||||
#ifdef OLIVE_OCIO
|
||||
// convert to linear colorspace
|
||||
bool linear_convert = true;
|
||||
if (linear_convert)
|
||||
// Convert frame from source to linear colorspace
|
||||
if (olive::config.enable_color_management)
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
// Convert texture to sequence's internal format
|
||||
if (textureID != c->fbo.at(0).texture() && textureID != c->fbo.at(1).texture()) {
|
||||
textureID = draw_clip(params.ctx, params.pipeline, c->fbo.at(fbo_switcher), textureID, true);
|
||||
fbo_switcher = !fbo_switcher;
|
||||
}
|
||||
|
||||
// Check if this clip has an OCIO shader set up or not
|
||||
if (c->ocio_shader == nullptr) {
|
||||
|
||||
|
||||
// Set default input colorspace
|
||||
QString input_cs = OCIO::ROLE_SCENE_LINEAR;
|
||||
|
||||
if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
input_cs = c->media()->to_footage()->Colorspace();
|
||||
}
|
||||
|
||||
// Try to get a shader based on the input color space to scene linear
|
||||
try {
|
||||
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
|
||||
OCIO::ConstProcessorRcPtr processor = config->getProcessor(input_cs.toUtf8(),
|
||||
OCIO::ROLE_SCENE_LINEAR);
|
||||
|
||||
c->ocio_shader = olive::shader::SetupOCIO(params.ctx,
|
||||
c->ocio_lut_texture,
|
||||
processor,
|
||||
c->media()->to_footage()->alpha_is_associated);
|
||||
} catch (OCIO::Exception& e) {
|
||||
qWarning() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we got a shader, and if so, blit with it
|
||||
if (c->ocio_shader != nullptr) {
|
||||
textureID = olive::rendering::OCIOBlit(c->ocio_shader.get(),
|
||||
c->ocio_lut_texture,
|
||||
c->fbo.at(fbo_switcher),
|
||||
textureID);
|
||||
|
||||
fbo_switcher = !fbo_switcher;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set up default coordinates for drawing the clip
|
||||
GLTextureCoords coords;
|
||||
coords.grid_size = 1;
|
||||
coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2;
|
||||
coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2;
|
||||
coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2;
|
||||
coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2;
|
||||
coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1;
|
||||
coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0;
|
||||
coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0;
|
||||
coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1;
|
||||
coords.blendmode = -1;
|
||||
coords.vertex_top_left = QVector3D(-video_width/2, -video_height/2, 0.0f);
|
||||
coords.vertex_top_right = QVector3D(video_width/2, -video_height/2, 0.0f);
|
||||
coords.vertex_bottom_left = QVector3D(-video_width/2, video_height/2, 0.0f);
|
||||
coords.vertex_bottom_right = QVector3D(video_width/2, video_height/2, 0.0f);
|
||||
coords.texture_top_left = QVector2D(0.0f, 0.0f);
|
||||
coords.texture_top_right = QVector2D(1.0f, 0.0f);
|
||||
coords.texture_bottom_left = QVector2D(0.0f, 1.0f);
|
||||
coords.texture_bottom_right = QVector2D(1.0f, 1.0f);
|
||||
coords.opacity = 1.0;
|
||||
|
||||
// == EFFECT CODE START ==
|
||||
@@ -420,8 +514,8 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
// run through all of the clip's effects
|
||||
for (int j=0;j<c->effects.size();j++) {
|
||||
|
||||
Effect* e = c->effects.at(j).get();
|
||||
process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
|
||||
Node* e = c->effects.at(j).get();
|
||||
process_effect(params.ctx, params.pipeline, c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
|
||||
|
||||
}
|
||||
|
||||
@@ -429,7 +523,7 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
if (c->opening_transition != nullptr) {
|
||||
int transition_progress = playhead - c->timeline_in(true);
|
||||
if (transition_progress < c->opening_transition->get_length()) {
|
||||
process_effect(c, c->opening_transition.get(), double(transition_progress)/double(c->opening_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening);
|
||||
process_effect(params.ctx, params.pipeline, c, c->opening_transition.get(), double(transition_progress)/double(c->opening_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,32 +531,37 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
if (c->closing_transition != nullptr) {
|
||||
int transition_progress = playhead - (c->timeline_out(true) - c->closing_transition->get_length());
|
||||
if (transition_progress >= 0 && transition_progress < c->closing_transition->get_length()) {
|
||||
process_effect(c, c->closing_transition.get(), double(transition_progress)/double(c->closing_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing);
|
||||
process_effect(params.ctx, params.pipeline, c, c->closing_transition.get(), double(transition_progress)/double(c->closing_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing);
|
||||
}
|
||||
}
|
||||
|
||||
// == EFFECT CODE END ==
|
||||
|
||||
|
||||
// Check whether the parent clip is auto-scaledc
|
||||
// Check whether the parent clip is auto-scaled
|
||||
if (c->autoscaled()
|
||||
&& (video_width != s->width
|
||||
&& video_height != s->height)) {
|
||||
float width_multiplier = float(s->width) / float(video_width);
|
||||
float height_multiplier = float(s->height) / float(video_height);
|
||||
float scale_multiplier = qMin(width_multiplier, height_multiplier);
|
||||
glScalef(scale_multiplier, scale_multiplier, 1);
|
||||
|
||||
coords.matrix.scale(scale_multiplier, scale_multiplier);
|
||||
}
|
||||
|
||||
// Configure effect gizmos if they exist
|
||||
if (params.gizmos != nullptr) {
|
||||
params.gizmos->gizmo_draw(timecode, coords); // set correct gizmo coords
|
||||
params.gizmos->gizmo_world_to_screen(); // convert gizmo coords to screen coords
|
||||
// set correct gizmo coords at this matrix
|
||||
params.gizmos->gizmo_draw(timecode, coords);
|
||||
|
||||
// convert gizmo coords to screen coords
|
||||
params.gizmos->gizmo_world_to_screen(coords.matrix, projection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (textureID > 0) {
|
||||
|
||||
// set viewport to sequence size
|
||||
params.ctx->functions()->glViewport(0, 0, s->width, s->height);
|
||||
|
||||
@@ -474,46 +573,99 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
|
||||
// use clip textures for nested sequences, otherwise use main frame buffers
|
||||
GLuint back_buffer_1;
|
||||
GLuint back_buffer_2;
|
||||
GLuint backend_tex_1;
|
||||
GLuint backend_tex_2;
|
||||
GLuint comp_texture;
|
||||
if (params.nests.size() > 0) {
|
||||
back_buffer_1 = params.nests.last()->fbo[1]->handle();
|
||||
backend_tex_1 = params.nests.last()->fbo[1]->texture();
|
||||
backend_tex_2 = params.nests.last()->fbo[2]->texture();
|
||||
back_buffer_1 = params.nests.last()->fbo[1].buffer();
|
||||
back_buffer_2 = params.nests.last()->fbo[2].buffer();
|
||||
backend_tex_1 = params.nests.last()->fbo[1].texture();
|
||||
backend_tex_2 = params.nests.last()->fbo[2].texture();
|
||||
comp_texture = params.nests.last()->fbo[0].texture();
|
||||
} else {
|
||||
back_buffer_1 = params.backend_buffer1;
|
||||
backend_tex_1 = params.backend_attachment1;
|
||||
backend_tex_2 = params.backend_attachment2;
|
||||
back_buffer_1 = params.backend_buffer1->buffer();
|
||||
back_buffer_2 = params.backend_buffer2->buffer();
|
||||
backend_tex_1 = params.backend_buffer1->texture();
|
||||
backend_tex_2 = params.backend_buffer2->texture();
|
||||
comp_texture = params.main_buffer->texture();
|
||||
}
|
||||
|
||||
// render a backbuffer
|
||||
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, back_buffer_1);
|
||||
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
params.ctx->functions()->glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// bind final clip texture
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
// set texture filter to bilinear
|
||||
PrepareToDraw(params.ctx->functions());
|
||||
|
||||
// draw clip on screen according to gl coordinates
|
||||
glBegin(GL_QUADS);
|
||||
params.pipeline->bind();
|
||||
|
||||
glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left
|
||||
glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left
|
||||
glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right
|
||||
glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right
|
||||
glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right
|
||||
glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right
|
||||
glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left
|
||||
glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left
|
||||
params.pipeline->setUniformValue("mvp_matrix", projection * coords.matrix);
|
||||
params.pipeline->setUniformValue("texture", 0);
|
||||
params.pipeline->setUniformValue("opacity", coords.opacity);
|
||||
|
||||
glEnd();
|
||||
GLfloat vertices[] = {
|
||||
coords.vertex_top_left.x(), coords.vertex_top_left.y(), 0.0f,
|
||||
coords.vertex_top_right.x(), coords.vertex_top_right.y(), 0.0f,
|
||||
coords.vertex_bottom_right.x(), coords.vertex_bottom_right.y(), 0.0f,
|
||||
|
||||
coords.vertex_top_left.x(), coords.vertex_top_left.y(), 0.0f,
|
||||
coords.vertex_bottom_left.x(), coords.vertex_bottom_left.y(), 0.0f,
|
||||
coords.vertex_bottom_right.x(), coords.vertex_bottom_right.y(), 0.0f,
|
||||
};
|
||||
|
||||
GLfloat texcoords[] = {
|
||||
coords.texture_top_left.x(), coords.texture_top_left.y(),
|
||||
coords.texture_top_right.x(), coords.texture_top_right.y(),
|
||||
coords.texture_bottom_right.x(), coords.texture_bottom_right.y(),
|
||||
|
||||
coords.texture_top_left.x(), coords.texture_top_left.y(),
|
||||
coords.texture_bottom_left.x(), coords.texture_bottom_left.y(),
|
||||
coords.texture_bottom_right.x(), coords.texture_bottom_right.y(),
|
||||
};
|
||||
|
||||
QOpenGLVertexArrayObject vao;
|
||||
vao.create();
|
||||
vao.bind();
|
||||
|
||||
QOpenGLBuffer vertex_buffer;
|
||||
vertex_buffer.create();
|
||||
vertex_buffer.bind();
|
||||
vertex_buffer.allocate(vertices, 18 * sizeof(GLfloat));
|
||||
vertex_buffer.release();
|
||||
|
||||
QOpenGLBuffer texcoord_buffer;
|
||||
texcoord_buffer.create();
|
||||
texcoord_buffer.bind();
|
||||
texcoord_buffer.allocate(texcoords, 12 * sizeof(GLfloat));
|
||||
texcoord_buffer.release();
|
||||
|
||||
GLuint vertex_location = params.pipeline->attributeLocation("a_position");
|
||||
vertex_buffer.bind();
|
||||
params.ctx->functions()->glEnableVertexAttribArray(vertex_location);
|
||||
params.ctx->functions()->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
vertex_buffer.release();
|
||||
|
||||
GLuint tex_location = params.pipeline->attributeLocation("a_texcoord");
|
||||
texcoord_buffer.bind();
|
||||
params.ctx->functions()->glEnableVertexAttribArray(tex_location);
|
||||
params.ctx->functions()->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
texcoord_buffer.release();
|
||||
|
||||
params.ctx->functions()->glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
params.pipeline->setUniformValue("opacity", 1.0f);
|
||||
|
||||
params.pipeline->release();
|
||||
|
||||
// release final clip texture
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
@@ -531,14 +683,12 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
|
||||
|
||||
|
||||
// copy front buffer to back buffer (only if we're using blending modes - which we usually will be)
|
||||
if (!olive::CurrentRuntimeConfig.disable_blending) {
|
||||
if (params.nests.size() > 0) {
|
||||
draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true);
|
||||
} else {
|
||||
draw_clip(params.ctx, params.backend_buffer2, params.main_attachment, true);
|
||||
}
|
||||
// copy front buffer to back buffer (only if we're using a blending mode)
|
||||
/*
|
||||
if (coords.blendmode >= 0) {
|
||||
draw_clip(params.ctx, params.pipeline, back_buffer_2, comp_texture, true);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@@ -550,18 +700,19 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
// bind front buffer as draw buffer
|
||||
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo);
|
||||
|
||||
// if (olive::CurrentRuntimeConfig.disable_blending) {
|
||||
// some GPUs don't like the blending shader, so we provide a pure GL fallback here
|
||||
// Check if we're using a blend mode (< 0 means no blend mode)
|
||||
//if (coords.blendmode < 0) {
|
||||
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
|
||||
|
||||
glColor4f(coords.opacity, coords.opacity, coords.opacity, coords.opacity);
|
||||
|
||||
full_blit();
|
||||
olive::rendering::Blit(params.pipeline);
|
||||
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
//} else {
|
||||
|
||||
/*
|
||||
} else {
|
||||
|
||||
// load background texture into texture unit 0
|
||||
params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_2);
|
||||
@@ -577,9 +728,9 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
params.blend_mode_program->setUniformValue("background", 0);
|
||||
params.blend_mode_program->setUniformValue("foreground", 1);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
params.ctx->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
full_blit();
|
||||
olive::rendering::Blit(params.pipeline);
|
||||
|
||||
// release blend mode shader
|
||||
params.blend_mode_program->release();
|
||||
@@ -590,8 +741,10 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
// unbind texture from texture unit 0
|
||||
params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
|
||||
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
*/
|
||||
|
||||
*/
|
||||
|
||||
//}
|
||||
|
||||
// unbind framebuffer
|
||||
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@@ -601,10 +754,8 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
|
||||
// == END FINAL DRAW ON SEQUENCE BUFFER ==
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
} else {
|
||||
} else if (c->type() == olive::kTypeAudio) {
|
||||
if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
params.nests.append(c);
|
||||
compose_sequence(params);
|
||||
@@ -646,15 +797,9 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) {
|
||||
WakeAudioWakeObject();
|
||||
}
|
||||
|
||||
if (params.video) {
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
// qDebug() << "compose sequence took" << QDateTime::currentMSecsSinceEpoch() - time;
|
||||
|
||||
if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) {
|
||||
if (!params.nests.isEmpty() && !params.nests.last()->fbo.isEmpty()) {
|
||||
// returns nested clip's texture
|
||||
return params.nests.last()->fbo[0]->texture();
|
||||
return params.nests.last()->fbo[0].texture();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -665,57 +810,41 @@ void olive::rendering::compose_audio(Viewer* viewer, Sequence* seq, int playback
|
||||
params.viewer = viewer;
|
||||
params.ctx = nullptr;
|
||||
params.seq = seq;
|
||||
params.video = false;
|
||||
params.type = olive::kTypeAudio;
|
||||
params.gizmos = nullptr;
|
||||
params.wait_for_mutexes = wait_for_mutexes;
|
||||
params.playback_speed = playback_speed;
|
||||
params.blend_mode_program = nullptr;
|
||||
compose_sequence(params);
|
||||
}
|
||||
|
||||
long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) {
|
||||
return qRound((double(framenumber)/source_frame_rate)*target_frame_rate);
|
||||
}
|
||||
|
||||
double get_timecode(Clip* c, long playhead) {
|
||||
return double(playhead_to_clip_frame(c, playhead))/c->sequence->frame_rate;
|
||||
}
|
||||
|
||||
long playhead_to_clip_frame(Clip* c, long playhead) {
|
||||
return (qMax(0L, playhead - c->timeline_in(true)) + c->clip_in(true));
|
||||
}
|
||||
|
||||
double playhead_to_clip_seconds(Clip* c, long playhead) {
|
||||
// returns time in seconds
|
||||
long clip_frame = playhead_to_clip_frame(c, playhead);
|
||||
|
||||
if (c->reversed()) {
|
||||
clip_frame = c->media_length() - clip_frame - 1;
|
||||
GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline,
|
||||
GLuint lut,
|
||||
const FramebufferObject& fbo,
|
||||
GLuint texture)
|
||||
{
|
||||
if (pipeline == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double secs = (double(clip_frame)/c->sequence->frame_rate)*c->speed().value;
|
||||
if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
secs *= c->media()->to_footage()->speed;
|
||||
}
|
||||
QOpenGLContext* ctx = QOpenGLContext::currentContext();
|
||||
QOpenGLExtraFunctions* xf = ctx->extraFunctions();
|
||||
|
||||
return secs;
|
||||
}
|
||||
xf->glActiveTexture(GL_TEXTURE2);
|
||||
xf->glBindTexture(GL_TEXTURE_3D, lut);
|
||||
xf->glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
int64_t seconds_to_timestamp(Clip *c, double seconds) {
|
||||
return qRound64(seconds * av_q2d(av_inv_q(c->time_base())));
|
||||
}
|
||||
pipeline->bind();
|
||||
|
||||
int64_t playhead_to_timestamp(Clip* c, long playhead) {
|
||||
return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead));
|
||||
}
|
||||
pipeline->setUniformValue("tex2", 2);
|
||||
|
||||
void close_active_clips(Sequence* s) {
|
||||
if (s != nullptr) {
|
||||
for (int i=0;i<s->clips.size();i++) {
|
||||
Clip* c = s->clips.at(i).get();
|
||||
if (c != nullptr) {
|
||||
c->Close(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
//textureID = draw_clip(params.ctx, pipeline, c->fbo.at(fbo_switcher), textureID, true);
|
||||
GLuint textureID = draw_clip(ctx, pipeline, fbo, texture, true);
|
||||
|
||||
pipeline->release();
|
||||
|
||||
xf->glActiveTexture(GL_TEXTURE2);
|
||||
xf->glBindTexture(GL_TEXTURE_3D, 0);
|
||||
xf->glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
return textureID;
|
||||
}
|
||||
|
||||
+66
-227
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -24,16 +24,18 @@
|
||||
#include <QOpenGLContext>
|
||||
#include <QVector>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
#include "timeline/sequence.h"
|
||||
#include "effects/effect.h"
|
||||
#include "nodes/node.h"
|
||||
#include "panels/viewer.h"
|
||||
|
||||
/**
|
||||
* @brief The ComposeSequenceParams struct
|
||||
*
|
||||
* Struct sent to the compose_sequence() function.
|
||||
*/
|
||||
* @brief The ComposeSequenceParams struct
|
||||
*
|
||||
* Struct sent to the compose_sequence() function.
|
||||
*/
|
||||
struct ComposeSequenceParams {
|
||||
|
||||
/**
|
||||
@@ -52,6 +54,13 @@ struct ComposeSequenceParams {
|
||||
*/
|
||||
QOpenGLContext* ctx;
|
||||
|
||||
/**
|
||||
* @brief The OpenGL pipeline used for rendering
|
||||
*
|
||||
* \see olive::rendering::GetPipeline().
|
||||
*/
|
||||
QOpenGLShaderProgram* pipeline;
|
||||
|
||||
/**
|
||||
* @brief The sequence to compose
|
||||
*
|
||||
@@ -71,16 +80,16 @@ struct ComposeSequenceParams {
|
||||
/**
|
||||
* @brief Set compose mode to video or audio
|
||||
*
|
||||
* **TRUE** if this function should render video, **FALSE** if this function should render audio.
|
||||
* Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio.
|
||||
*/
|
||||
bool video;
|
||||
olive::TrackType type;
|
||||
|
||||
/**
|
||||
* @brief Set to the Effect whose gizmos were chosen to be drawn on screen
|
||||
*
|
||||
* The currently active Effect that compose_sequence() will update the gizmos of.
|
||||
*/
|
||||
Effect* gizmos;
|
||||
Node* gizmos;
|
||||
|
||||
/**
|
||||
* @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown.
|
||||
@@ -129,19 +138,6 @@ struct ComposeSequenceParams {
|
||||
*/
|
||||
int playback_speed;
|
||||
|
||||
/**
|
||||
* @brief Blending mode shader
|
||||
*
|
||||
* Used only for video rendering. Never accessed with audio rendering.
|
||||
*
|
||||
* A program containing the current active
|
||||
* blending mode shader that can be bound during rendering. Must be compiled and linked beforehand. See
|
||||
* RenderThread::blend_mode_program for how this is properly set up.
|
||||
*
|
||||
* \see ComposeSequenceParams::video
|
||||
*/
|
||||
QOpenGLShaderProgram* blend_mode_program;
|
||||
|
||||
/**
|
||||
* @brief Premultiply alpha shader
|
||||
*
|
||||
@@ -161,16 +157,7 @@ struct ComposeSequenceParams {
|
||||
*
|
||||
* When compose_sequence() is rendering the final image, this framebuffer will be bound.
|
||||
*/
|
||||
GLuint main_buffer;
|
||||
|
||||
/**
|
||||
* @brief The attachment to the framebuffer in main_buffer
|
||||
*
|
||||
* Used only for video rendering. Never accessed with audio rendering.
|
||||
*
|
||||
* The OpenGL texture attached to the framebuffer referenced by main_buffer.
|
||||
*/
|
||||
GLuint main_attachment;
|
||||
const FramebufferObject* main_buffer;
|
||||
|
||||
/**
|
||||
* @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer
|
||||
@@ -178,15 +165,7 @@ struct ComposeSequenceParams {
|
||||
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
|
||||
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
|
||||
*/
|
||||
GLuint backend_buffer1;
|
||||
|
||||
/**
|
||||
* @brief Backend OpenGL framebuffer 1's texture attachment
|
||||
*
|
||||
* The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to
|
||||
* ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders.
|
||||
*/
|
||||
GLuint backend_attachment1;
|
||||
const FramebufferObject* backend_buffer1;
|
||||
|
||||
/**
|
||||
* @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer
|
||||
@@ -194,51 +173,33 @@ struct ComposeSequenceParams {
|
||||
* In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging"
|
||||
* between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose.
|
||||
*/
|
||||
GLuint backend_buffer2;
|
||||
|
||||
/**
|
||||
* @brief Backend OpenGL framebuffer 2's texture attachment
|
||||
*
|
||||
* The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to
|
||||
* ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders.
|
||||
*/
|
||||
GLuint backend_attachment2;
|
||||
|
||||
/**
|
||||
* @brief OpenGL shader containing OpenColorIO shader information
|
||||
*/
|
||||
QOpenGLShaderProgram* ocio_shader;
|
||||
|
||||
/**
|
||||
* @brief OpenGL texture containing LUT obtained form OpenColorIO
|
||||
*/
|
||||
GLuint ocio_lut_texture;
|
||||
const FramebufferObject* backend_buffer2;
|
||||
};
|
||||
|
||||
namespace olive {
|
||||
namespace rendering {
|
||||
/**
|
||||
* @brief Compose a frame of a given sequence
|
||||
*
|
||||
* For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will
|
||||
* automatically open and close clips (memory allocation and file handles) as necessary, communicate with the
|
||||
* Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and
|
||||
* finally composite all the currently active clips together into a final texture.
|
||||
*
|
||||
* Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time
|
||||
* of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so,
|
||||
* the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and
|
||||
* if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence
|
||||
* is being played, in which case just play the next frame rather than redrawing an old frame).
|
||||
*
|
||||
* @param params
|
||||
*
|
||||
* A struct of parameters to use while rendering.
|
||||
*
|
||||
* @return A reference to the OpenGL texture resulting from the render. Will usually be equal to
|
||||
* ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference
|
||||
* to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame.
|
||||
*/
|
||||
* @brief Compose a frame of a given sequence
|
||||
*
|
||||
* For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will
|
||||
* automatically open and close clips (memory allocation and file handles) as necessary, communicate with the
|
||||
* Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and
|
||||
* finally composite all the currently active clips together into a final texture.
|
||||
*
|
||||
* Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time
|
||||
* of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so,
|
||||
* the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and
|
||||
* if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence
|
||||
* is being played, in which case just play the next frame rather than redrawing an old frame).
|
||||
*
|
||||
* @param params
|
||||
*
|
||||
* A struct of parameters to use while rendering.
|
||||
*
|
||||
* @return A reference to the OpenGL texture resulting from the render. Will usually be equal to
|
||||
* ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference
|
||||
* to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame.
|
||||
*/
|
||||
GLuint compose_sequence(ComposeSequenceParams ¶ms);
|
||||
|
||||
/**
|
||||
@@ -268,141 +229,19 @@ void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rescale a frame number between two frame rates
|
||||
*
|
||||
* Converts a frame number from one frame rate to its equivalent in another frame rate
|
||||
*
|
||||
* @param framenumber
|
||||
*
|
||||
* The frame number to convert
|
||||
*
|
||||
* @param source_frame_rate
|
||||
*
|
||||
* Frame rate that the frame number is currently in
|
||||
*
|
||||
* @param target_frame_rate
|
||||
*
|
||||
* Frame rate to convert to
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Rescaled frame number
|
||||
*/
|
||||
long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate);
|
||||
void UpdateOCIOGLState(const ComposeSequenceParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Get timecode
|
||||
*
|
||||
* Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start
|
||||
* of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media;
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* Clip to get the timecode of
|
||||
*
|
||||
* @param playhead
|
||||
*
|
||||
* Sequence playhead to convert to a clip/media timecode
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Timecode in seconds
|
||||
*/
|
||||
double get_timecode(Clip *c, long playhead);
|
||||
|
||||
/**
|
||||
* @brief Convert playhead frame number to a clip frame number
|
||||
*
|
||||
* Converts a Timeline playhead to a the current clip's frame. Equivalent to
|
||||
* `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames.
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* The clip to get the current frame number of
|
||||
*
|
||||
* @param playhead
|
||||
*
|
||||
* The current Timeline frame number
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* The curren frame number of the clip at `playhead`
|
||||
*/
|
||||
long playhead_to_clip_frame(Clip* c, long playhead);
|
||||
|
||||
/**
|
||||
* @brief Converts the playhead to clip seconds
|
||||
*
|
||||
* Get the current timecode at the playhead in terms of clip seconds.
|
||||
*
|
||||
* FIXME: Possible duplicate of get_timecode()? Will need to research this more.
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* Clip to return clip seconds of.
|
||||
*
|
||||
* @param playhead
|
||||
*
|
||||
* Current Timeline playhead to convert to clip seconds
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Clip time in seconds
|
||||
*/
|
||||
double playhead_to_clip_seconds(Clip *c, long playhead);
|
||||
|
||||
/**
|
||||
* @brief Convert seconds to FFmpeg timestamp
|
||||
*
|
||||
* Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base
|
||||
* units.
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* Clip to get timestamp of
|
||||
*
|
||||
* @param seconds
|
||||
*
|
||||
* Clip time in seconds
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* An FFmpeg-compatible timestamp in AVStream->time_base units.
|
||||
*/
|
||||
int64_t seconds_to_timestamp(Clip* c, double seconds);
|
||||
|
||||
/**
|
||||
* @brief Convert Timeline playhead to FFmpeg timestamp
|
||||
*
|
||||
* Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base
|
||||
* units.
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* Clip to get timestamp of
|
||||
*
|
||||
* @param playhead
|
||||
*
|
||||
* Timeline playhead to convert to a timestamp
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* An FFmpeg-compatible timestamp in AVStream->time_base units.
|
||||
*/
|
||||
int64_t playhead_to_timestamp(Clip *c, long playhead);
|
||||
|
||||
/**
|
||||
* @brief Close all open clips in a Sequence
|
||||
*
|
||||
* Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a
|
||||
* result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that
|
||||
* Sequence too.
|
||||
*
|
||||
* @param s
|
||||
*
|
||||
* The Sequence to close all clips on.
|
||||
*/
|
||||
void close_active_clips(Sequence* s);
|
||||
namespace olive {
|
||||
namespace rendering {
|
||||
extern GLfloat blit_vertices[];
|
||||
extern GLfloat blit_texcoords[];
|
||||
extern GLfloat flipped_blit_texcoords[];
|
||||
void Blit(QOpenGLShaderProgram* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
|
||||
GLuint OCIOBlit(QOpenGLShaderProgram *pipeline,
|
||||
GLuint lut,
|
||||
const FramebufferObject& fbo,
|
||||
GLuint texture);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // RENDERFUNCTIONS_H
|
||||
|
||||
+144
-72
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -22,30 +22,35 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QImage>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QDebug>
|
||||
#ifdef OLIVE_OCIO
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
#endif
|
||||
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
#include "timeline/sequence.h"
|
||||
#include "effects/effectloaders.h"
|
||||
#include "global/config.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "rendering/shadergenerators.h"
|
||||
|
||||
RenderThread::RenderThread() :
|
||||
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),
|
||||
ocio_lut_texture(0),
|
||||
ocio_shader(nullptr),
|
||||
running(true),
|
||||
front_buffer_switcher(false)
|
||||
ocio_config_date(0),
|
||||
front_buffer_switcher(false),
|
||||
pipeline_program(nullptr)
|
||||
{
|
||||
surface.create();
|
||||
}
|
||||
@@ -80,6 +85,9 @@ void RenderThread::run() {
|
||||
}
|
||||
|
||||
// create any buffers that don't yet exist
|
||||
if (!composite_buffer.IsCreated()) {
|
||||
composite_buffer.Create(ctx, seq->width, seq->height);
|
||||
}
|
||||
if (!front_buffer_1.IsCreated()) {
|
||||
front_buffer_1.Create(ctx, seq->width, seq->height);
|
||||
}
|
||||
@@ -93,19 +101,18 @@ void RenderThread::run() {
|
||||
back_buffer_2.Create(ctx, seq->width, seq->height);
|
||||
}
|
||||
|
||||
if (blend_mode_program == nullptr) {
|
||||
// create shader program to make blending modes work
|
||||
// If there's no pipeline shader, create it now
|
||||
if (pipeline_program == nullptr) {
|
||||
delete_shaders();
|
||||
|
||||
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();
|
||||
pipeline_program = olive::shader::GetPipeline();
|
||||
}
|
||||
|
||||
premultiply_program = new QOpenGLShaderProgram();
|
||||
premultiply_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert");
|
||||
premultiply_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/premultiply.frag");
|
||||
premultiply_program->link();
|
||||
// If there's no OpenColorIO shader or the configuration has changed, (re-)create it now
|
||||
if (olive::config.enable_color_management && ocio_shader == nullptr) {
|
||||
destroy_ocio();
|
||||
|
||||
set_up_ocio();
|
||||
}
|
||||
|
||||
// draw frame
|
||||
@@ -137,6 +144,53 @@ const GLuint &RenderThread::get_texture()
|
||||
|
||||
void RenderThread::set_up_ocio()
|
||||
{
|
||||
|
||||
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
|
||||
|
||||
// Get current OCIO display from Config (or defaults if there is no setting)
|
||||
QString display = olive::config.ocio_display;
|
||||
if (display.isEmpty()) {
|
||||
display = config->getDefaultDisplay();
|
||||
}
|
||||
|
||||
QString view = olive::config.ocio_view;
|
||||
if (view.isEmpty()) {
|
||||
view = config->getDefaultView(display.toUtf8());
|
||||
}
|
||||
|
||||
// Get current display stats
|
||||
OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
|
||||
transform->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR);
|
||||
transform->setDisplay(display.toUtf8());
|
||||
transform->setView(view.toUtf8());
|
||||
|
||||
if (!olive::config.ocio_look.isEmpty()) {
|
||||
transform->setLooksOverride(olive::config.ocio_look.toUtf8());
|
||||
transform->setLooksOverrideEnabled(true);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Using the current configuration, try to get an OCIO processor with a corresponding input and output colorspace
|
||||
OCIO::ConstProcessorRcPtr processor = config->getProcessor(transform);
|
||||
|
||||
// Create a OCIO shader with this processor
|
||||
ocio_shader = olive::shader::SetupOCIO(ctx, ocio_lut_texture, processor, true);
|
||||
|
||||
} catch(OCIO::Exception & e) {
|
||||
qCritical() << e.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderThread::destroy_ocio()
|
||||
{
|
||||
// Destroy LUT texture
|
||||
if (ocio_lut_texture > 0) {
|
||||
ctx->functions()->glDeleteTextures(1, &ocio_lut_texture);
|
||||
}
|
||||
ocio_lut_texture = 0;
|
||||
ocio_shader = nullptr;
|
||||
}
|
||||
|
||||
void RenderThread::paint() {
|
||||
@@ -145,43 +199,66 @@ void RenderThread::paint() {
|
||||
params.viewer = nullptr;
|
||||
params.ctx = ctx;
|
||||
params.seq = seq;
|
||||
params.video = true;
|
||||
params.type = olive::kTypeVideo;
|
||||
params.texture_failed = false;
|
||||
params.wait_for_mutexes = true;
|
||||
params.playback_speed = playback_speed_;
|
||||
params.blend_mode_program = blend_mode_program;
|
||||
params.premultiply_program = premultiply_program;
|
||||
params.backend_buffer1 = back_buffer_1.buffer();
|
||||
params.backend_buffer2 = back_buffer_2.buffer();
|
||||
params.backend_attachment1 = back_buffer_1.texture();
|
||||
params.backend_attachment2 = back_buffer_2.texture();
|
||||
params.main_buffer = front_buffer_switcher ? front_buffer_1.buffer() : front_buffer_2.buffer();
|
||||
params.main_attachment = front_buffer_switcher ? front_buffer_1.texture() : front_buffer_2.texture();
|
||||
params.pipeline = pipeline_program.get();
|
||||
params.backend_buffer1 = &back_buffer_1;
|
||||
params.backend_buffer2 = &back_buffer_2;
|
||||
params.main_buffer = &composite_buffer;
|
||||
|
||||
// get currently selected gizmos
|
||||
gizmos = seq->GetSelectedGizmo();
|
||||
params.gizmos = gizmos;
|
||||
|
||||
QOpenGLFunctions* f = ctx->functions();
|
||||
|
||||
f->glEnable(GL_BLEND);
|
||||
|
||||
// bind composite framebuffer for drawing
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, composite_buffer.buffer());
|
||||
|
||||
// Clear framebuffer to nothing
|
||||
f->glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
f->glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Compose the current frame
|
||||
olive::rendering::compose_sequence(params);
|
||||
|
||||
// Copy composite buffer to front buffer
|
||||
// First lock the appropriate mutex for exclusivity
|
||||
QMutex& active_mutex = front_buffer_switcher ? front_mutex1 : front_mutex2;
|
||||
active_mutex.lock();
|
||||
|
||||
// bind framebuffer for drawing
|
||||
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, params.main_buffer);
|
||||
FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2;
|
||||
|
||||
glLoadIdentity();
|
||||
// Blit the composite buffer to one of the front buffers
|
||||
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
// If we're color managing, conver the linear composited frame to display color space
|
||||
if (olive::config.enable_color_management && ocio_shader != nullptr) {
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
olive::rendering::OCIOBlit(ocio_shader.get(),
|
||||
ocio_lut_texture,
|
||||
buffer,
|
||||
composite_buffer.texture());
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
|
||||
olive::rendering::compose_sequence(params);
|
||||
// If we're not color managing, just blit normally
|
||||
buffer.BindBuffer();
|
||||
f->glClear(GL_COLOR_BUFFER_BIT);
|
||||
composite_buffer.BindTexture();
|
||||
olive::rendering::Blit(pipeline_program.get());
|
||||
composite_buffer.ReleaseTexture();
|
||||
buffer.ReleaseBuffer();
|
||||
|
||||
}
|
||||
|
||||
// flush changes
|
||||
ctx->functions()->glFinish();
|
||||
f->glFinish();
|
||||
|
||||
f->glDisable(GL_BLEND);
|
||||
|
||||
texture_failed = params.texture_failed;
|
||||
|
||||
@@ -192,11 +269,11 @@ void RenderThread::paint() {
|
||||
// texture failed, try again
|
||||
queued = true;
|
||||
} else {
|
||||
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer);
|
||||
QImage img(tex_width, tex_height, QImage::Format_RGBA8888);
|
||||
glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
|
||||
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer());
|
||||
QImage img(tex_width, tex_height, QImage::Format_RGBA8888_Premultiplied);
|
||||
f->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);
|
||||
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
save_fn = "";
|
||||
}
|
||||
}
|
||||
@@ -204,28 +281,25 @@ void RenderThread::paint() {
|
||||
if (pixel_buffer != nullptr) {
|
||||
|
||||
// set main framebuffer to the current read buffer
|
||||
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer);
|
||||
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.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);
|
||||
f->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);
|
||||
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
pixel_buffer = nullptr;
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
// release
|
||||
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void RenderThread::start_render(QOpenGLContext *share,
|
||||
@@ -290,6 +364,7 @@ void RenderThread::wait_until_paused()
|
||||
}
|
||||
|
||||
void RenderThread::delete_buffers() {
|
||||
composite_buffer.Destroy();
|
||||
front_buffer_1.Destroy();
|
||||
front_buffer_2.Destroy();
|
||||
back_buffer_1.Destroy();
|
||||
@@ -297,17 +372,14 @@ void RenderThread::delete_buffers() {
|
||||
}
|
||||
|
||||
void RenderThread::delete_shaders() {
|
||||
delete blend_mode_program;
|
||||
blend_mode_program = nullptr;
|
||||
|
||||
delete premultiply_program;
|
||||
premultiply_program = nullptr;
|
||||
pipeline_program = nullptr;
|
||||
}
|
||||
|
||||
void RenderThread::delete_ctx() {
|
||||
if (ctx != nullptr) {
|
||||
delete_shaders();
|
||||
delete_buffers();
|
||||
destroy_ocio();
|
||||
}
|
||||
|
||||
delete ctx;
|
||||
|
||||
+28
-30
@@ -1,20 +1,20 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 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.
|
||||
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/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
@@ -30,14 +30,9 @@
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
#include "timeline/sequence.h"
|
||||
#include "effects/effect.h"
|
||||
#include "nodes/node.h"
|
||||
#include "rendering/framebufferobject.h"
|
||||
|
||||
// copied from source code to OCIODisplay
|
||||
const int LUT3D_EDGE_SIZE = 32;
|
||||
|
||||
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
|
||||
const int NUM_3D_ENTRIES = 98304;
|
||||
#include "qopenglshaderprogramptr.h"
|
||||
|
||||
class RenderThread : public QThread {
|
||||
Q_OBJECT
|
||||
@@ -49,7 +44,7 @@ public:
|
||||
QMutex* get_texture_mutex();
|
||||
const GLuint& get_texture();
|
||||
|
||||
Effect* gizmos;
|
||||
Node* gizmos;
|
||||
void paint();
|
||||
void start_render(QOpenGLContext* share,
|
||||
Sequence *s,
|
||||
@@ -65,15 +60,20 @@ public:
|
||||
public slots:
|
||||
// cleanup functions
|
||||
void delete_ctx();
|
||||
void delete_buffers();
|
||||
void delete_shaders();
|
||||
void destroy_ocio();
|
||||
signals:
|
||||
void ready();
|
||||
private:
|
||||
// cleanup functions
|
||||
void delete_buffers();
|
||||
void delete_shaders();
|
||||
|
||||
// OpenColorIO functions
|
||||
void set_up_ocio();
|
||||
void destroy_ocio();
|
||||
|
||||
// OpenColorIO variables
|
||||
GLuint ocio_lut_texture;
|
||||
QOpenGLShaderProgramPtr ocio_shader;
|
||||
qint64 ocio_config_date;
|
||||
|
||||
FramebufferObject front_buffer_1;
|
||||
QMutex front_mutex1;
|
||||
@@ -81,6 +81,8 @@ private:
|
||||
FramebufferObject front_buffer_2;
|
||||
QMutex front_mutex2;
|
||||
|
||||
FramebufferObject composite_buffer;
|
||||
|
||||
bool front_buffer_switcher;
|
||||
|
||||
QWaitCondition wait_cond_;
|
||||
@@ -92,17 +94,13 @@ private:
|
||||
QOffscreenSurface surface;
|
||||
QOpenGLContext* share_ctx;
|
||||
QOpenGLContext* ctx;
|
||||
QOpenGLShaderProgram* blend_mode_program;
|
||||
QOpenGLShaderProgram* premultiply_program;
|
||||
QOpenGLShaderProgramPtr pipeline_program;
|
||||
|
||||
FramebufferObject back_buffer_1;
|
||||
FramebufferObject back_buffer_2;
|
||||
|
||||
float ocio_lut_data[NUM_3D_ENTRIES];
|
||||
GLuint ocio_lut_texture;
|
||||
QOpenGLShaderProgram* ocio_shader;
|
||||
|
||||
Sequence* seq;
|
||||
|
||||
int playback_speed_;
|
||||
int divider;
|
||||
int tex_width;
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "shadergenerators.h"
|
||||
|
||||
#include <QOpenGLExtraFunctions>
|
||||
|
||||
QOpenGLShaderProgramPtr olive::shader::GetPipeline(const QString& function_name, const QString& shader_code)
|
||||
{
|
||||
QOpenGLShaderProgramPtr program = std::make_shared<QOpenGLShaderProgram>();
|
||||
|
||||
// Generate vertex shader
|
||||
QString vert_shader = "#version 110\n"
|
||||
"\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump int;\n"
|
||||
"precision mediump 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";
|
||||
|
||||
// Generate fragment shader
|
||||
QString frag_shader = "#version 110\n"
|
||||
"\n"
|
||||
"#ifdef GL_ES\n"
|
||||
"precision mediump int;\n"
|
||||
"precision mediump float;\n"
|
||||
"#endif\n"
|
||||
"\n"
|
||||
"uniform sampler2D texture;\n"
|
||||
"uniform float opacity;\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_shader.append("\n"
|
||||
"void main() {\n"
|
||||
" if (color_only) {\n"
|
||||
" gl_FragColor = color_only_color;"
|
||||
" } else {\n"
|
||||
" vec4 color = texture2D(texture, v_texcoord)*opacity;\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_shader.append(shader_code);
|
||||
|
||||
frag_shader.append(QString("\n"
|
||||
"void main() {\n"
|
||||
" vec4 color = %1(texture2D(texture, v_texcoord))*opacity;\n"
|
||||
" gl_FragColor = color;\n"
|
||||
"}\n").arg(function_name));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Add shaders to program
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_shader);
|
||||
program->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_shader);
|
||||
program->link();
|
||||
|
||||
// Set opacity default to 100%
|
||||
program->bind();
|
||||
program->setUniformValue("opacity", 1.0f);
|
||||
program->release();
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
QString olive::shader::GetAlphaDisassociateFunction(const QString &function_name)
|
||||
{
|
||||
return QString("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 olive::shader::GetAlphaReassociateFunction(const QString &function_name)
|
||||
{
|
||||
return QString("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 olive::shader::GetAlphaAssociateFunction(const QString &function_name)
|
||||
{
|
||||
return QString("vec4 %1(vec4 col) {\n"
|
||||
" return vec4(col.rgb * col.a, col.a);\n"
|
||||
"}\n").arg(function_name);
|
||||
}
|
||||
|
||||
// copied from source code to OCIODisplay
|
||||
const int OCIO_LUT3D_EDGE_SIZE = 32;
|
||||
|
||||
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
|
||||
const int OCIO_NUM_3D_ENTRIES = 98304;
|
||||
|
||||
QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx,
|
||||
GLuint& lut_texture,
|
||||
OCIO::ConstProcessorRcPtr processor,
|
||||
bool alpha_is_associated)
|
||||
{
|
||||
|
||||
QOpenGLExtraFunctions* xf = ctx->extraFunctions();
|
||||
|
||||
// Create LUT texture
|
||||
xf->glGenTextures(1, &lut_texture);
|
||||
|
||||
// Bind LUT
|
||||
xf->glBindTexture(GL_TEXTURE_3D, lut_texture);
|
||||
|
||||
// Set texture parameters
|
||||
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// Allocate storage for texture
|
||||
xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB,
|
||||
OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE,
|
||||
0, GL_RGB,GL_FLOAT, nullptr);
|
||||
|
||||
//
|
||||
// SET UP GLSL SHADER
|
||||
//
|
||||
|
||||
OCIO::GpuShaderDesc shaderDesc;
|
||||
const char* ocio_func_name = "OCIODisplay";
|
||||
shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0);
|
||||
shaderDesc.setFunctionName(ocio_func_name);
|
||||
shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE);
|
||||
|
||||
//
|
||||
// COMPUTE 3D LUT
|
||||
//
|
||||
|
||||
GLfloat* ocio_lut_data = new GLfloat[OCIO_NUM_3D_ENTRIES];
|
||||
processor->getGpuLut3D(ocio_lut_data, shaderDesc);
|
||||
|
||||
// Upload LUT data to texture
|
||||
xf->glTexSubImage3D(GL_TEXTURE_3D, 0,
|
||||
0, 0, 0,
|
||||
OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE,
|
||||
GL_RGB, GL_FLOAT, ocio_lut_data);
|
||||
|
||||
delete [] ocio_lut_data;
|
||||
|
||||
// Create OCIO shader code
|
||||
QString shader_text(processor->getGpuShaderText(shaderDesc));
|
||||
|
||||
QString shader_call;
|
||||
|
||||
// Enforce alpha association
|
||||
if (alpha_is_associated) {
|
||||
|
||||
// If alpha is already associated, we'll need to disassociate and reassociate
|
||||
shader_text.append("\n");
|
||||
|
||||
QString disassociate_func_name = "disassoc";
|
||||
shader_text.append(GetAlphaDisassociateFunction(disassociate_func_name));
|
||||
|
||||
QString reassociate_func_name = "reassoc";
|
||||
shader_text.append(GetAlphaReassociateFunction(reassociate_func_name));
|
||||
|
||||
// Make OCIO call pass through disassociate and reassociate function
|
||||
shader_call = QString("%3(%1(%2(col), tex2));").arg(ocio_func_name,
|
||||
disassociate_func_name,
|
||||
reassociate_func_name);
|
||||
|
||||
} else {
|
||||
|
||||
// If alpha is not already associated, we can just associate after OCIO
|
||||
|
||||
// Add associate function
|
||||
QString associate_func_name = "assoc";
|
||||
shader_text.append(GetAlphaAssociateFunction(associate_func_name));
|
||||
|
||||
// Make OCIO call pass through associate function
|
||||
shader_call = QString("%2(%1(col, tex2));").arg(ocio_func_name, associate_func_name);
|
||||
|
||||
}
|
||||
|
||||
// Add process() function, which GetPipeline() will call if specified
|
||||
QString process_function_name = "process";
|
||||
shader_text.append(QString("\n"
|
||||
"uniform sampler3D tex2;\n"
|
||||
"\n"
|
||||
"vec4 %2(vec4 col) {\n"
|
||||
" return %1\n"
|
||||
"}\n").arg(shader_call, process_function_name));
|
||||
|
||||
|
||||
// Get pipeline-based shader to inject OCIO shader into
|
||||
QOpenGLShaderProgramPtr shader = olive::shader::GetPipeline(process_function_name, shader_text);
|
||||
|
||||
// Release LUT
|
||||
xf->glBindTexture(GL_TEXTURE_3D, 0);
|
||||
|
||||
return shader;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef SHADERGENERATORS_H
|
||||
#define SHADERGENERATORS_H
|
||||
|
||||
#include "qopenglshaderprogramptr.h"
|
||||
#include "framebufferobject.h"
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
namespace olive {
|
||||
namespace shader {
|
||||
|
||||
QOpenGLShaderProgramPtr GetPipeline(const QString &function_name = QString(), const QString &shader_code = QString());
|
||||
|
||||
QOpenGLShaderProgramPtr SetupOCIO(QOpenGLContext *ctx,
|
||||
GLuint &lut_texture,
|
||||
OCIO::ConstProcessorRcPtr processor,
|
||||
bool alpha_is_associated);
|
||||
|
||||
QString GetAlphaDisassociateFunction(const QString& function_name);
|
||||
QString GetAlphaReassociateFunction(const QString& function_name);
|
||||
QString GetAlphaAssociateFunction(const QString& function_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SHADERGENERATORS_H
|
||||
Reference in New Issue
Block a user