merge of long discussed new pipeline

This commit is contained in:
itsmattkc
2019-05-04 10:48:20 +10:00
382 changed files with 22362 additions and 11653 deletions
+41 -48
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+68
View File
@@ -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();
}
+27
View File
@@ -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
+80 -6
View File
@@ -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_;
}
+28 -2
View File
@@ -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_;
+60
View File
@@ -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;
}
}
+65
View File
@@ -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
+29
View File
@@ -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
View File
@@ -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 &params) {
// 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 &params) {
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 &params) {
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 &params) {
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 &params) {
// 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 &params) {
}
}
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 &params) {
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 &params) {
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 &params) {
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 &params) {
// 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 &params) {
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 &params) {
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 &params) {
// 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 &params) {
// 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 &params) {
// 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 &params) {
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 &params) {
// 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 &params) {
// == 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 &params) {
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
View File
@@ -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 &params);
/**
@@ -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 &params);
/**
* @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
View File
@@ -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
View File
@@ -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;
+230
View File
@@ -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;
}
+26
View File
@@ -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