fix line endings

This commit is contained in:
itsmattkc
2019-06-15 07:39:55 -07:00
parent 13d6f9f5b2
commit 0acee942f4
300 changed files with 49260 additions and 49254 deletions
+426 -426
View File
@@ -1,426 +1,426 @@
/***
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 "audio.h"
#include "global/global.h"
#include "timeline/sequence.h"
#include "panels/panels.h"
#include "global/config.h"
#include "ui/audiomonitor.h"
#include "rendering/renderfunctions.h"
#include "global/debug.h"
#include <QApplication>
#include <QAudioOutput>
#include <QAudioInput>
#include <QtMath>
#include <QFile>
#include <QDir>
#include <QComboBox>
extern "C" {
#include <libavcodec/avcodec.h>
}
QAudioOutput* audio_output;
QIODevice* audio_io_device;
bool audio_device_set = false;
bool audio_scrub = false;
QMutex audio_write_lock;
QAudioInput* audio_input = nullptr;
QFile output_recording;
bool recording = false;
int audio_rendering_rate = 0;
float audio_ibuffer[audio_ibuffer_size];
qint64 audio_ibuffer_read = 0;
long audio_ibuffer_frame = 0;
double audio_ibuffer_timecode = 0;
AudioSenderThread* audio_thread = nullptr;
bool is_audio_device_set() {
return audio_device_set;
}
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::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
if (devs.at(i).deviceName() == preferred_device) {
return devs.at(i);
}
}
}
// if no preferred output is set, try to get the default device
QAudioDeviceInfo default_device = (mode == QAudio::AudioOutput) ? QAudioDeviceInfo::defaultOutputDevice() : QAudioDeviceInfo::defaultInputDevice();
if (!default_device.isNull()) {
return default_device;
}
// if no default output could be retrieved, just use the first in the list
if (devs.size() > 0) {
return devs.at(0);
}
// couldn't find any audio devices, return null device
return QAudioDeviceInfo();
}
void init_audio() {
stop_audio();
QAudioFormat audio_format;
audio_format.setSampleRate(olive::config.audio_rate);
audio_format.setChannelCount(2);
audio_format.setSampleSize(32);
audio_format.setCodec("audio/pcm");
audio_format.setByteOrder(QAudioFormat::LittleEndian);
audio_format.setSampleType(QAudioFormat::Float);
QAudioDeviceInfo info = get_audio_device(QAudio::AudioOutput);
// see if desired format can be used by the device, use nearest if not
if (!info.isFormatSupported(audio_format)) {
qWarning() << "Audio format is not supported by backend, using nearest";
audio_format = info.nearestFormat(audio_format);
}
audio_output = new QAudioOutput(info, audio_format);
audio_output->moveToThread(QApplication::instance()->thread());
audio_output->setNotifyInterval(5);
// connect
audio_io_device = audio_output->start();
if (audio_io_device == nullptr) {
qWarning() << "Received nullptr audio device. No compatible audio output was found.";
} else {
audio_device_set = true;
// start sender thread
audio_thread = new AudioSenderThread();
QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver()));
audio_thread->start(QThread::TimeCriticalPriority);
clear_audio_ibuffer();
}
}
void stop_audio() {
if (audio_device_set) {
audio_thread->stop();
audio_output->stop();
delete audio_output;
audio_device_set = false;
}
}
void clear_audio_ibuffer() {
if (audio_thread != nullptr) audio_thread->lock.lock();
audio_write_lock.lock();
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 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_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;
return 0;
}
}
AudioSenderThread::AudioSenderThread() : close(false) {
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
}
void AudioSenderThread::stop() {
close = true;
cond.wakeAll();
wait();
}
void AudioSenderThread::notifyReceiver() {
cond.wakeAll();
}
void AudioSenderThread::run() {
// start data loop
send_audio_to_output(0, audio_ibuffer_size);
lock.lock();
while (true) {
cond.wait(&lock);
if (close) {
break;
} else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) {
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);
if (actual_write == max_write) {
// got all the bytes, write again
send_audio_to_output(0, audio_ibuffer_size);
}
audio_scrub = false;
}
}
lock.unlock();
}
int AudioSenderThread::send_audio_to_output(qint64 offset, int max) {
// send audio to device
audio_write_lock.lock();
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/sizeof(float));
QVector<float> averages;
averages.resize(channels);
averages.fill(0.0);
for (qint64 i=offset;i<lim;i++) {
int channel = i%channels;
averages[channel] = qMax(qAbs(audio_ibuffer[i]), averages[channel]);
}
panel_timeline.first()->audio_monitor->set_value(averages);
}
memset(&audio_ibuffer[offset], 0, actual_write);
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.0f)/(M_E-1.0f);
}
void int32_to_char_array(qint32 i, char* array) {
memcpy(array, &i, 4);
}
void write_wave_header(QFile& f, const QAudioFormat& format) {
qint32 int32bit;
char arr[4];
// 4 byte riff header
f.write("RIFF");
// 4 byte file size, filled in later
for (int i=0;i<4;i++) f.putChar(0);
// 4 byte file type header + 4 byte format chunk marker
f.write("WAVEfmt");
f.putChar(0x20);
// 4 byte length of the above format data (always 16 bytes)
f.putChar(16);
for (int i=0;i<3;i++) f.putChar(0);
// 2 byte type format (1 is PCM)
f.putChar(1);
f.putChar(0);
// 2 byte channel count
int32bit = format.channelCount();
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// 4 byte integer for sample rate
int32bit = format.sampleRate();
int32_to_char_array(int32bit, arr);
f.write(arr, 4);
// 4 byte integer for bytes per second
int32bit = (format.sampleRate() * format.sampleSize() * format.channelCount()) / 8;
int32_to_char_array(int32bit, arr);
f.write(arr, 4);
// 2 byte integer for bytes per sample per channel
int32bit = (format.sampleSize() * format.channelCount()) / 8;
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// 2 byte integer for bits per sample (16)
int32bit = format.sampleSize();
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// data chunk header
f.write("data");
// 4 byte integer for data chunk size (filled in later)?
for (int i=0;i<4;i++) f.putChar(0);
}
void write_wave_trailer(QFile& f) {
char arr[4];
f.seek(4);
// 4 bytes for total file size - 8 bytes
qint32 file_size = qint32(f.size()) - 8;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
f.seek(40);
// 4 bytes for data chunk size (file size - header)
file_size = qint32(f.size()) - 44;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
}
bool start_recording() {
if (!olive::Global->CheckForActiveSequence(true)) {
return false;
}
QString audio_path = QCoreApplication::translate("Audio", "%1 Audio").arg(olive::ActiveProjectFilename);
QDir audio_dir(audio_path);
if (!audio_dir.exists() && !audio_dir.mkpath(".")) {
qCritical() << "Failed to create audio directory";
return false;
}
QString audio_file_path;
int file_number = 0;
do {
file_number++;
QString audio_filename = QString("%1.wav").arg(
QCoreApplication::translate("Audio", "Recording %1").arg(QString::number(file_number))
);
audio_file_path = audio_dir.filePath(audio_filename);
} while (QFile(audio_file_path).exists());
output_recording.setFileName(audio_file_path);
if (!output_recording.open(QFile::WriteOnly)) {
qCritical() << "Failed to open output file. Does Olive have permission to write to this directory?";
return false;
}
QAudioFormat audio_format = audio_output->format();
if (olive::config.recording_mode != audio_format.channelCount()) {
audio_format.setChannelCount(olive::config.recording_mode);
}
QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput);
if (!info.isFormatSupported(audio_format)) {
qWarning() << "Default format not supported, using nearest";
audio_format = info.nearestFormat(audio_format);
}
write_wave_header(output_recording, audio_format);
audio_input = new QAudioInput(info, audio_format);
audio_input->start(&output_recording);
recording = true;
return true;
}
void stop_recording() {
if (recording) {
audio_input->stop();
write_wave_trailer(output_recording);
output_recording.close();
delete audio_input;
audio_input = nullptr;
recording = false;
}
}
QString get_recorded_audio_filename() {
return output_recording.fileName();
}
void combobox_audio_sample_rates(QComboBox *combobox) {
combobox->addItem("22050 Hz", 22050);
combobox->addItem("24000 Hz", 24000);
combobox->addItem("32000 Hz", 32000);
combobox->addItem("44100 Hz", 44100);
combobox->addItem("48000 Hz", 48000);
combobox->addItem("88200 Hz", 88200);
combobox->addItem("96000 Hz", 96000);
}
QObject* audio_wake_object = nullptr;
QMutex audio_wake_mutex;
QObject* GetAudioWakeObject()
{
audio_wake_mutex.lock();
QObject* wake_object = audio_wake_object;
audio_wake_object = nullptr;
audio_wake_mutex.unlock();
return wake_object;
}
void SetAudioWakeObject(QObject *o)
{
audio_wake_mutex.lock();
audio_wake_object = o;
audio_wake_mutex.unlock();
}
void WakeAudioWakeObject() {
QObject* audio_wake_object = GetAudioWakeObject();
if (audio_wake_object != nullptr) {
QMetaObject::invokeMethod(audio_wake_object, "play_wake", Qt::QueuedConnection);
}
}
/***
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 "audio.h"
#include "global/global.h"
#include "timeline/sequence.h"
#include "panels/panels.h"
#include "global/config.h"
#include "ui/audiomonitor.h"
#include "rendering/renderfunctions.h"
#include "global/debug.h"
#include <QApplication>
#include <QAudioOutput>
#include <QAudioInput>
#include <QtMath>
#include <QFile>
#include <QDir>
#include <QComboBox>
extern "C" {
#include <libavcodec/avcodec.h>
}
QAudioOutput* audio_output;
QIODevice* audio_io_device;
bool audio_device_set = false;
bool audio_scrub = false;
QMutex audio_write_lock;
QAudioInput* audio_input = nullptr;
QFile output_recording;
bool recording = false;
int audio_rendering_rate = 0;
float audio_ibuffer[audio_ibuffer_size];
qint64 audio_ibuffer_read = 0;
long audio_ibuffer_frame = 0;
double audio_ibuffer_timecode = 0;
AudioSenderThread* audio_thread = nullptr;
bool is_audio_device_set() {
return audio_device_set;
}
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::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
if (devs.at(i).deviceName() == preferred_device) {
return devs.at(i);
}
}
}
// if no preferred output is set, try to get the default device
QAudioDeviceInfo default_device = (mode == QAudio::AudioOutput) ? QAudioDeviceInfo::defaultOutputDevice() : QAudioDeviceInfo::defaultInputDevice();
if (!default_device.isNull()) {
return default_device;
}
// if no default output could be retrieved, just use the first in the list
if (devs.size() > 0) {
return devs.at(0);
}
// couldn't find any audio devices, return null device
return QAudioDeviceInfo();
}
void init_audio() {
stop_audio();
QAudioFormat audio_format;
audio_format.setSampleRate(olive::config.audio_rate);
audio_format.setChannelCount(2);
audio_format.setSampleSize(32);
audio_format.setCodec("audio/pcm");
audio_format.setByteOrder(QAudioFormat::LittleEndian);
audio_format.setSampleType(QAudioFormat::Float);
QAudioDeviceInfo info = get_audio_device(QAudio::AudioOutput);
// see if desired format can be used by the device, use nearest if not
if (!info.isFormatSupported(audio_format)) {
qWarning() << "Audio format is not supported by backend, using nearest";
audio_format = info.nearestFormat(audio_format);
}
audio_output = new QAudioOutput(info, audio_format);
audio_output->moveToThread(QApplication::instance()->thread());
audio_output->setNotifyInterval(5);
// connect
audio_io_device = audio_output->start();
if (audio_io_device == nullptr) {
qWarning() << "Received nullptr audio device. No compatible audio output was found.";
} else {
audio_device_set = true;
// start sender thread
audio_thread = new AudioSenderThread();
QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver()));
audio_thread->start(QThread::TimeCriticalPriority);
clear_audio_ibuffer();
}
}
void stop_audio() {
if (audio_device_set) {
audio_thread->stop();
audio_output->stop();
delete audio_output;
audio_device_set = false;
}
}
void clear_audio_ibuffer() {
if (audio_thread != nullptr) audio_thread->lock.lock();
audio_write_lock.lock();
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 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_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;
return 0;
}
}
AudioSenderThread::AudioSenderThread() : close(false) {
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
}
void AudioSenderThread::stop() {
close = true;
cond.wakeAll();
wait();
}
void AudioSenderThread::notifyReceiver() {
cond.wakeAll();
}
void AudioSenderThread::run() {
// start data loop
send_audio_to_output(0, audio_ibuffer_size);
lock.lock();
while (true) {
cond.wait(&lock);
if (close) {
break;
} else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) {
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);
if (actual_write == max_write) {
// got all the bytes, write again
send_audio_to_output(0, audio_ibuffer_size);
}
audio_scrub = false;
}
}
lock.unlock();
}
int AudioSenderThread::send_audio_to_output(qint64 offset, int max) {
// send audio to device
audio_write_lock.lock();
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/sizeof(float));
QVector<float> averages;
averages.resize(channels);
averages.fill(0.0);
for (qint64 i=offset;i<lim;i++) {
int channel = i%channels;
averages[channel] = qMax(qAbs(audio_ibuffer[i]), averages[channel]);
}
panel_timeline.first()->audio_monitor->set_value(averages);
}
memset(&audio_ibuffer[offset], 0, actual_write);
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.0f)/(M_E-1.0f);
}
void int32_to_char_array(qint32 i, char* array) {
memcpy(array, &i, 4);
}
void write_wave_header(QFile& f, const QAudioFormat& format) {
qint32 int32bit;
char arr[4];
// 4 byte riff header
f.write("RIFF");
// 4 byte file size, filled in later
for (int i=0;i<4;i++) f.putChar(0);
// 4 byte file type header + 4 byte format chunk marker
f.write("WAVEfmt");
f.putChar(0x20);
// 4 byte length of the above format data (always 16 bytes)
f.putChar(16);
for (int i=0;i<3;i++) f.putChar(0);
// 2 byte type format (1 is PCM)
f.putChar(1);
f.putChar(0);
// 2 byte channel count
int32bit = format.channelCount();
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// 4 byte integer for sample rate
int32bit = format.sampleRate();
int32_to_char_array(int32bit, arr);
f.write(arr, 4);
// 4 byte integer for bytes per second
int32bit = (format.sampleRate() * format.sampleSize() * format.channelCount()) / 8;
int32_to_char_array(int32bit, arr);
f.write(arr, 4);
// 2 byte integer for bytes per sample per channel
int32bit = (format.sampleSize() * format.channelCount()) / 8;
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// 2 byte integer for bits per sample (16)
int32bit = format.sampleSize();
int32_to_char_array(int32bit, arr);
f.write(arr, 2);
// data chunk header
f.write("data");
// 4 byte integer for data chunk size (filled in later)?
for (int i=0;i<4;i++) f.putChar(0);
}
void write_wave_trailer(QFile& f) {
char arr[4];
f.seek(4);
// 4 bytes for total file size - 8 bytes
qint32 file_size = qint32(f.size()) - 8;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
f.seek(40);
// 4 bytes for data chunk size (file size - header)
file_size = qint32(f.size()) - 44;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
}
bool start_recording() {
if (!olive::Global->CheckForActiveSequence(true)) {
return false;
}
QString audio_path = QCoreApplication::translate("Audio", "%1 Audio").arg(olive::ActiveProjectFilename);
QDir audio_dir(audio_path);
if (!audio_dir.exists() && !audio_dir.mkpath(".")) {
qCritical() << "Failed to create audio directory";
return false;
}
QString audio_file_path;
int file_number = 0;
do {
file_number++;
QString audio_filename = QString("%1.wav").arg(
QCoreApplication::translate("Audio", "Recording %1").arg(QString::number(file_number))
);
audio_file_path = audio_dir.filePath(audio_filename);
} while (QFile(audio_file_path).exists());
output_recording.setFileName(audio_file_path);
if (!output_recording.open(QFile::WriteOnly)) {
qCritical() << "Failed to open output file. Does Olive have permission to write to this directory?";
return false;
}
QAudioFormat audio_format = audio_output->format();
if (olive::config.recording_mode != audio_format.channelCount()) {
audio_format.setChannelCount(olive::config.recording_mode);
}
QAudioDeviceInfo info = get_audio_device(QAudio::AudioInput);
if (!info.isFormatSupported(audio_format)) {
qWarning() << "Default format not supported, using nearest";
audio_format = info.nearestFormat(audio_format);
}
write_wave_header(output_recording, audio_format);
audio_input = new QAudioInput(info, audio_format);
audio_input->start(&output_recording);
recording = true;
return true;
}
void stop_recording() {
if (recording) {
audio_input->stop();
write_wave_trailer(output_recording);
output_recording.close();
delete audio_input;
audio_input = nullptr;
recording = false;
}
}
QString get_recorded_audio_filename() {
return output_recording.fileName();
}
void combobox_audio_sample_rates(QComboBox *combobox) {
combobox->addItem("22050 Hz", 22050);
combobox->addItem("24000 Hz", 24000);
combobox->addItem("32000 Hz", 32000);
combobox->addItem("44100 Hz", 44100);
combobox->addItem("48000 Hz", 48000);
combobox->addItem("88200 Hz", 88200);
combobox->addItem("96000 Hz", 96000);
}
QObject* audio_wake_object = nullptr;
QMutex audio_wake_mutex;
QObject* GetAudioWakeObject()
{
audio_wake_mutex.lock();
QObject* wake_object = audio_wake_object;
audio_wake_object = nullptr;
audio_wake_mutex.unlock();
return wake_object;
}
void SetAudioWakeObject(QObject *o)
{
audio_wake_mutex.lock();
audio_wake_object = o;
audio_wake_mutex.unlock();
}
void WakeAudioWakeObject() {
QObject* audio_wake_object = GetAudioWakeObject();
if (audio_wake_object != nullptr) {
QMetaObject::invokeMethod(audio_wake_object, "play_wake", Qt::QueuedConnection);
}
}
+85 -85
View File
@@ -1,85 +1,85 @@
/***
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 AUDIO_H
#define AUDIO_H
#include <QVector>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QIODevice>
#include <QAudioOutput>
#include <QComboBox>
#include "timeline/sequence.h"
class AudioSenderThread : public QThread {
Q_OBJECT
public:
AudioSenderThread();
void run();
void stop();
QWaitCondition cond;
bool close;
QMutex lock;
public slots:
void notifyReceiver();
private:
QVector<qint16> samples;
int send_audio_to_output(qint64 offset, int max);
};
double log_volume(double linear);
extern QAudioOutput* audio_output;
extern QIODevice* audio_io_device;
extern AudioSenderThread* audio_thread;
extern QMutex audio_write_lock;
#define audio_ibuffer_size 192000
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 int audio_rendering_rate;
void clear_audio_ibuffer();
QObject *GetAudioWakeObject();
void SetAudioWakeObject(QObject* o);
void WakeAudioWakeObject();
int current_audio_freq();
bool is_audio_device_set();
void init_audio();
void stop_audio();
qint64 get_buffer_offset_from_frame(double framerate, long frame);
bool start_recording();
void stop_recording();
QString get_recorded_audio_filename();
void combobox_audio_sample_rates(QComboBox* combobox);
#endif // AUDIO_H
/***
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 AUDIO_H
#define AUDIO_H
#include <QVector>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QIODevice>
#include <QAudioOutput>
#include <QComboBox>
#include "timeline/sequence.h"
class AudioSenderThread : public QThread {
Q_OBJECT
public:
AudioSenderThread();
void run();
void stop();
QWaitCondition cond;
bool close;
QMutex lock;
public slots:
void notifyReceiver();
private:
QVector<qint16> samples;
int send_audio_to_output(qint64 offset, int max);
};
double log_volume(double linear);
extern QAudioOutput* audio_output;
extern QIODevice* audio_io_device;
extern AudioSenderThread* audio_thread;
extern QMutex audio_write_lock;
#define audio_ibuffer_size 192000
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 int audio_rendering_rate;
void clear_audio_ibuffer();
QObject *GetAudioWakeObject();
void SetAudioWakeObject(QObject* o);
void WakeAudioWakeObject();
int current_audio_freq();
bool is_audio_device_set();
void init_audio();
void stop_audio();
qint64 get_buffer_offset_from_frame(double framerate, long frame);
bool start_recording();
void stop_recording();
QString get_recorded_audio_filename();
void combobox_audio_sample_rates(QComboBox* combobox);
#endif // AUDIO_H
+1459 -1459
View File
File diff suppressed because it is too large Load Diff
+602 -602
View File
File diff suppressed because it is too large Load Diff
+105 -105
View File
@@ -1,105 +1,105 @@
/***
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 "clipqueue.h"
ClipQueue::ClipQueue()
{
}
ClipQueue::~ClipQueue()
{
clear();
}
void ClipQueue::lock()
{
queue_lock.lock();
}
bool ClipQueue::tryLock()
{
return queue_lock.tryLock();
}
void ClipQueue::unlock()
{
queue_lock.unlock();
}
void ClipQueue::append(AVFrame *frame)
{
queue.append(frame);
}
AVFrame *ClipQueue::at(int i)
{
return queue.at(i);
}
AVFrame *ClipQueue::first()
{
return queue.first();
}
AVFrame *ClipQueue::last()
{
return queue.last();
}
void ClipQueue::removeFirst()
{
removeAt(0);
}
void ClipQueue::removeLast()
{
removeAt(queue.size()-1);
}
void ClipQueue::removeAt(int i)
{
av_frame_free(&queue[i]);
queue.removeAt(i);
}
void ClipQueue::clear()
{
while (queue.size() > 0) {
removeAt(0);
}
}
int ClipQueue::size()
{
return queue.size();
}
bool ClipQueue::isEmpty()
{
return queue.isEmpty();
}
bool ClipQueue::contains(AVFrame *frame)
{
return queue.contains(frame);
}
/***
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 "clipqueue.h"
ClipQueue::ClipQueue()
{
}
ClipQueue::~ClipQueue()
{
clear();
}
void ClipQueue::lock()
{
queue_lock.lock();
}
bool ClipQueue::tryLock()
{
return queue_lock.tryLock();
}
void ClipQueue::unlock()
{
queue_lock.unlock();
}
void ClipQueue::append(AVFrame *frame)
{
queue.append(frame);
}
AVFrame *ClipQueue::at(int i)
{
return queue.at(i);
}
AVFrame *ClipQueue::first()
{
return queue.first();
}
AVFrame *ClipQueue::last()
{
return queue.last();
}
void ClipQueue::removeFirst()
{
removeAt(0);
}
void ClipQueue::removeLast()
{
removeAt(queue.size()-1);
}
void ClipQueue::removeAt(int i)
{
av_frame_free(&queue[i]);
queue.removeAt(i);
}
void ClipQueue::clear()
{
while (queue.size() > 0) {
removeAt(0);
}
}
int ClipQueue::size()
{
return queue.size();
}
bool ClipQueue::isEmpty()
{
return queue.isEmpty();
}
bool ClipQueue::contains(AVFrame *frame)
{
return queue.contains(frame);
}
+185 -185
View File
@@ -1,185 +1,185 @@
/***
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 CLIPQUEUE_H
#define CLIPQUEUE_H
extern "C" {
#include <libavformat/avformat.h>
}
#include <QVector>
#include <QMutex>
/**
* @brief The ClipQueue class
*
* A fairly simple wrapper for a QVector and QMutex that cleans up AVFrames automatically when removing them.
*/
class ClipQueue {
public:
/**
* @brief ClipQueue Constructor
*/
ClipQueue();
/**
* @brief ClipQueue Destructor
*
* Automatically clears queue freeing any memory consumed by any AVFrames
*/
~ClipQueue();
// Thread safety (QMutex compatible)
/**
* @brief Lock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::lock() for more
* information.
*/
void lock();
/**
* @brief Try to lock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. Tries to lock, but doesn't block
* the calling thread and wait if it can't lock it. See QMutex::tryLock() for more information.
*
* @return
*
* **TRUE** if the lock succeeded, **FALSE** if not.
*/
bool tryLock();
/**
* @brief Unlock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::unlock() for more
* information.
*/
void unlock();
// Array handling (QVector compatible)
/**
* @brief Add a frame to the end of the queue
*
* @param frame
*
* The frame to add
*/
void append(AVFrame* frame);
/**
* @brief Retrieve a frame at a certain index
*
* @param i
*
* Index to retrieve frame from
*
* @return
*
* AVFrame at this index
*/
AVFrame* at(int i);
/**
* @brief Retrieve first frame in the queue
*
* @return
*
* The first AVFrame in the queue
*/
AVFrame* first();
/**
* @brief Retrieve last frame in the queue
*
* @return
*
* The last AVFrame in the queue
*/
AVFrame* last();
/**
* @brief Remove first frame in the queue
*
* Frees all memory occupied by this frame and removes it from the queue
*/
void removeFirst();
/**
* @brief Remove last frame in the queue
*
* Frees all memory occupied by this frame and removes it from the queue
*/
void removeLast();
/**
* @brief Remove frame in the queue at a certain index
*
* Frees all memory occupied by this frame and removes it from the queue
*
* @param i
*
* Index to remove a frame at
*/
void removeAt(int i);
/**
* @brief Clear entire queue
*
* Frees all memory occupied by all frames and clears the entire queue
*/
void clear();
/**
* @brief Retrieve current size of the queue
*
* @return
*
* Current the current size of the queue. All indexes in the queue are guaranteed to be valid references to an
* AVFrame.
*/
int size();
/**
* @brief Returns whether the queue is empty of not.
*
* @return
*
* **TRUE** if the queue is empty and contains no frames, **FALSE** if not.
*/
bool isEmpty();
/**
* @brief Returns whether the queue contains a frame or not
*
* @return
*
* **TRUE** if the queue contains the specified frame, **FALSE** if not.
*/
bool contains(AVFrame* frame);
private:
QVector<AVFrame*> queue;
QMutex queue_lock;
};
#endif // CLIPQUEUE_H
/***
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 CLIPQUEUE_H
#define CLIPQUEUE_H
extern "C" {
#include <libavformat/avformat.h>
}
#include <QVector>
#include <QMutex>
/**
* @brief The ClipQueue class
*
* A fairly simple wrapper for a QVector and QMutex that cleans up AVFrames automatically when removing them.
*/
class ClipQueue {
public:
/**
* @brief ClipQueue Constructor
*/
ClipQueue();
/**
* @brief ClipQueue Destructor
*
* Automatically clears queue freeing any memory consumed by any AVFrames
*/
~ClipQueue();
// Thread safety (QMutex compatible)
/**
* @brief Lock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::lock() for more
* information.
*/
void lock();
/**
* @brief Try to lock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. Tries to lock, but doesn't block
* the calling thread and wait if it can't lock it. See QMutex::tryLock() for more information.
*
* @return
*
* **TRUE** if the lock succeeded, **FALSE** if not.
*/
bool tryLock();
/**
* @brief Unlock queue mutex
*
* Used for multithreading to ensure queue is only accessed by one thread at a time. See QMutex::unlock() for more
* information.
*/
void unlock();
// Array handling (QVector compatible)
/**
* @brief Add a frame to the end of the queue
*
* @param frame
*
* The frame to add
*/
void append(AVFrame* frame);
/**
* @brief Retrieve a frame at a certain index
*
* @param i
*
* Index to retrieve frame from
*
* @return
*
* AVFrame at this index
*/
AVFrame* at(int i);
/**
* @brief Retrieve first frame in the queue
*
* @return
*
* The first AVFrame in the queue
*/
AVFrame* first();
/**
* @brief Retrieve last frame in the queue
*
* @return
*
* The last AVFrame in the queue
*/
AVFrame* last();
/**
* @brief Remove first frame in the queue
*
* Frees all memory occupied by this frame and removes it from the queue
*/
void removeFirst();
/**
* @brief Remove last frame in the queue
*
* Frees all memory occupied by this frame and removes it from the queue
*/
void removeLast();
/**
* @brief Remove frame in the queue at a certain index
*
* Frees all memory occupied by this frame and removes it from the queue
*
* @param i
*
* Index to remove a frame at
*/
void removeAt(int i);
/**
* @brief Clear entire queue
*
* Frees all memory occupied by all frames and clears the entire queue
*/
void clear();
/**
* @brief Retrieve current size of the queue
*
* @return
*
* Current the current size of the queue. All indexes in the queue are guaranteed to be valid references to an
* AVFrame.
*/
int size();
/**
* @brief Returns whether the queue is empty of not.
*
* @return
*
* **TRUE** if the queue is empty and contains no frames, **FALSE** if not.
*/
bool isEmpty();
/**
* @brief Returns whether the queue contains a frame or not
*
* @return
*
* **TRUE** if the queue contains the specified frame, **FALSE** if not.
*/
bool contains(AVFrame* frame);
private:
QVector<AVFrame*> queue;
QMutex queue_lock;
};
#endif // CLIPQUEUE_H
+705 -705
View File
File diff suppressed because it is too large Load Diff
+141 -141
View File
@@ -1,141 +1,141 @@
/***
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 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;
struct AVPacket;
struct AVStream;
struct AVCodec;
struct SwsContext;
struct SwrContext;
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;
int video_width;
int video_height;
double video_frame_rate;
int video_compression_type;
double video_bitrate;
bool audio_enabled;
int audio_codec;
int audio_sampling_rate;
int audio_bitrate;
long start_frame;
long end_frame;
};
struct VideoCodecParams {
int pix_fmt;
int threads;
};
class ExportThread : public QThread {
Q_OBJECT
public:
ExportThread(const ExportParams& params, const VideoCodecParams& vparams, QObject* parent = nullptr);
virtual void run() override;
const QString& GetError();
bool WasInterrupted();
signals:
void ProgressChanged(int value, qint64 remaining_ms);
public slots:
void Interrupt();
void play_wake();
private:
bool Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream);
bool SetupVideo();
bool SetupAudio();
bool SetupContainer();
void Export();
void Cleanup();
QOffscreenSurface surface;
bool interrupt_;
// params imported from dialogs
ExportParams params_;
VideoCodecParams vcodec_params_;
AVFormatContext* fmt_ctx;
AVStream* video_stream;
AVCodec* vcodec;
AVCodecContext* vcodec_ctx;
AVFrame* video_frame;
AVFrame* sws_frame;
SwsContext* sws_ctx;
AVStream* audio_stream;
AVCodec* acodec;
AVFrame* audio_frame;
AVFrame* swr_frame;
AVCodecContext* acodec_ctx;
AVPacket video_pkt;
AVPacket audio_pkt;
SwrContext* swr_ctx;
bool vpkt_alloc;
bool apkt_alloc;
int aframe_bytes;
int ret;
char* c_filename;
QMutex mutex;
QWaitCondition waitCond;
QString export_error;
bool waiting_for_audio_;
private slots:
void wake();
};
#endif // EXPORTTHREAD_H
/***
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 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;
struct AVPacket;
struct AVStream;
struct AVCodec;
struct SwsContext;
struct SwrContext;
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;
int video_width;
int video_height;
double video_frame_rate;
int video_compression_type;
double video_bitrate;
bool audio_enabled;
int audio_codec;
int audio_sampling_rate;
int audio_bitrate;
long start_frame;
long end_frame;
};
struct VideoCodecParams {
int pix_fmt;
int threads;
};
class ExportThread : public QThread {
Q_OBJECT
public:
ExportThread(const ExportParams& params, const VideoCodecParams& vparams, QObject* parent = nullptr);
virtual void run() override;
const QString& GetError();
bool WasInterrupted();
signals:
void ProgressChanged(int value, qint64 remaining_ms);
public slots:
void Interrupt();
void play_wake();
private:
bool Encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream);
bool SetupVideo();
bool SetupAudio();
bool SetupContainer();
void Export();
void Cleanup();
QOffscreenSurface surface;
bool interrupt_;
// params imported from dialogs
ExportParams params_;
VideoCodecParams vcodec_params_;
AVFormatContext* fmt_ctx;
AVStream* video_stream;
AVCodec* vcodec;
AVCodecContext* vcodec_ctx;
AVFrame* video_frame;
AVFrame* sws_frame;
SwsContext* sws_ctx;
AVStream* audio_stream;
AVCodec* acodec;
AVFrame* audio_frame;
AVFrame* swr_frame;
AVCodecContext* acodec_ctx;
AVPacket video_pkt;
AVPacket audio_pkt;
SwrContext* swr_ctx;
bool vpkt_alloc;
bool apkt_alloc;
int aframe_bytes;
int ret;
char* c_filename;
QMutex mutex;
QWaitCondition waitCond;
QString export_error;
bool waiting_for_audio_;
private slots:
void wake();
};
#endif // EXPORTTHREAD_H
+68 -68
View File
@@ -1,68 +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();
}
#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 -27
View File
@@ -1,27 +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
#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
+155 -155
View File
@@ -1,155 +1,155 @@
/***
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),
texture_(0),
ctx_(nullptr)
{}
FramebufferObject::~FramebufferObject()
{
Destroy();
}
bool FramebufferObject::IsCreated()
{
return ctx_ != nullptr;
}
void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height)
{
// free any previous textures
Destroy();
// set context to new context provided
ctx_ = ctx;
QOpenGLFunctions* f = ctx->functions();
// create framebuffer object
f->glGenFramebuffers(1, &buffer_);
// create texture
f->glGenTextures(1, &texture_);
// bind framebuffer for attaching
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
// bind texture
f->glBindTexture(GL_TEXTURE_2D, texture_);
// allocate storage for texture
const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->effective_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
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// attach texture to framebuffer
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);
// release framebuffer
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void FramebufferObject::Destroy()
{
if (ctx_ != nullptr) {
ctx_->functions()->glDeleteFramebuffers(1, &buffer_);
ctx_->functions()->glDeleteTextures(1, &texture_);
}
ctx_ = nullptr;
}
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
{
return texture_;
}
/***
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),
texture_(0),
ctx_(nullptr)
{}
FramebufferObject::~FramebufferObject()
{
Destroy();
}
bool FramebufferObject::IsCreated()
{
return ctx_ != nullptr;
}
void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height)
{
// free any previous textures
Destroy();
// set context to new context provided
ctx_ = ctx;
QOpenGLFunctions* f = ctx->functions();
// create framebuffer object
f->glGenFramebuffers(1, &buffer_);
// create texture
f->glGenTextures(1, &texture_);
// bind framebuffer for attaching
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer_);
// bind texture
f->glBindTexture(GL_TEXTURE_2D, texture_);
// allocate storage for texture
const olive::PixelFormatInfo& bit_depth = olive::pixel_formats.at(olive::Global->effective_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
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// attach texture to framebuffer
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);
// release framebuffer
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void FramebufferObject::Destroy()
{
if (ctx_ != nullptr) {
ctx_->functions()->glDeleteFramebuffers(1, &buffer_);
ctx_->functions()->glDeleteTextures(1, &texture_);
}
ctx_ = nullptr;
}
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
{
return texture_;
}
+50 -50
View File
@@ -1,50 +1,50 @@
/***
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
#include <QOpenGLContext>
class FramebufferObject
{
public:
FramebufferObject();
~FramebufferObject();
bool IsCreated();
void Create(QOpenGLContext* ctx, int width, int height);
void Destroy();
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_;
GLuint texture_;
};
#endif // FRAMEBUFFEROBJECT_H
/***
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
#include <QOpenGLContext>
class FramebufferObject
{
public:
FramebufferObject();
~FramebufferObject();
bool IsCreated();
void Create(QOpenGLContext* ctx, int width, int height);
void Destroy();
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_;
GLuint texture_;
};
#endif // FRAMEBUFFEROBJECT_H
+43 -43
View File
@@ -1,43 +1,43 @@
#include "memorybuffer.h"
#include <QDebug>
#include "decoders/pixelformatconverter.h"
MemoryBuffer::MemoryBuffer()
{
}
void MemoryBuffer::Create(int width, int height, const olive::PixelFormat &format)
{
width_ = width;
height_ = height;
format_ = format;
data_.resize(olive::pix_fmt_conv->GetBufferSize(format, width, height));
}
const int &MemoryBuffer::width() const
{
return width_;
}
const int &MemoryBuffer::height() const
{
return height_;
}
const olive::PixelFormat &MemoryBuffer::format() const
{
return format_;
}
uint8_t *MemoryBuffer::data()
{
return data_.data();
}
const uint8_t *MemoryBuffer::const_data() const
{
return data_.constData();
}
#include "memorybuffer.h"
#include <QDebug>
#include "decoders/pixelformatconverter.h"
MemoryBuffer::MemoryBuffer()
{
}
void MemoryBuffer::Create(int width, int height, const olive::PixelFormat &format)
{
width_ = width;
height_ = height;
format_ = format;
data_.resize(olive::pix_fmt_conv->GetBufferSize(format, width, height));
}
const int &MemoryBuffer::width() const
{
return width_;
}
const int &MemoryBuffer::height() const
{
return height_;
}
const olive::PixelFormat &MemoryBuffer::format() const
{
return format_;
}
uint8_t *MemoryBuffer::data()
{
return data_.data();
}
const uint8_t *MemoryBuffer::const_data() const
{
return data_.constData();
}
+28 -28
View File
@@ -1,28 +1,28 @@
#ifndef MEMORYBUFFER_H
#define MEMORYBUFFER_H
#include <QVector>
#include "pixelformats.h"
class MemoryBuffer
{
public:
MemoryBuffer();
void Create(int width, int height, const olive::PixelFormat& format);
const int& width() const;
const int& height() const;
const olive::PixelFormat& format() const;
uint8_t* data();
const uint8_t* const_data() const;
private:
QVector<uint8_t> data_;
int width_;
int height_;
olive::PixelFormat format_;
};
#endif // MEMORYBUFFER_H
#ifndef MEMORYBUFFER_H
#define MEMORYBUFFER_H
#include <QVector>
#include "pixelformats.h"
class MemoryBuffer
{
public:
MemoryBuffer();
void Create(int width, int height, const olive::PixelFormat& format);
const int& width() const;
const int& height() const;
const olive::PixelFormat& format() const;
uint8_t* data();
const uint8_t* const_data() const;
private:
QVector<uint8_t> data_;
int width_;
int height_;
olive::PixelFormat format_;
};
#endif // MEMORYBUFFER_H
+60 -60
View File
@@ -1,60 +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;
}
}
/***
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 -65
View File
@@ -1,65 +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
/***
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 -29
View File
@@ -1,29 +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
/***
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
File diff suppressed because it is too large Load Diff
+247 -247
View File
@@ -1,247 +1,247 @@
/***
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 RENDERFUNCTIONS_H
#define RENDERFUNCTIONS_H
#include <QOpenGLContext>
#include <QVector>
#include <QOpenGLShaderProgram>
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE::v1;
#include "timeline/sequence.h"
#include "nodes/oldeffectnode.h"
#include "panels/viewer.h"
/**
* @brief The ComposeSequenceParams struct
*
* Struct sent to the compose_sequence() function.
*/
struct ComposeSequenceParams {
/**
* @brief Reference to the Viewer class that's calling compose_sequence()
*
* Primarily used for calling Viewer::play_wake() when appropriate.
*/
Viewer* viewer;
/**
* @brief The OpenGL context to use while rendering.
*
* For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed.
*
* \see ComposeSequenceParams::video
*/
QOpenGLContext* ctx;
/**
* @brief The OpenGL pipeline used for rendering
*
* \see olive::rendering::GetPipeline().
*/
QOpenGLShaderProgram* pipeline;
/**
* @brief The sequence to compose
*
* In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame
* to render.
*/
Sequence* seq;
/**
* @brief Array to store the nested sequence hierarchy
*
* Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to
* handle nested sequences.
*/
QVector<Clip*> nests;
/**
* @brief Set compose mode to video or audio
*
* Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio.
*/
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.
*/
OldEffectNode* gizmos;
/**
* @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown.
*
* A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw
* any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a
* "best effort", but not the actual image.
*
* This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**.
*
* \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better
* way to communicate this information.
*
* Additionally, since
* compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with
* stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect
* of this though is that the preview may become less responsive if it's stuck trying to render one frame. With
* the current system, the preview may show incomplete frames occasionally but at least it will show something.
* This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be
* removed.
*/
bool texture_failed;
/**
* @brief Run all cachers in the same thread that compose_sequence() is in
*
* Standard behavior is that all clips cache frames in their own thread and signals are sent between
* compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling
* the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a
* technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used
* when rendering as timing isn't as important as creating output frames as quickly as possible.
*
* \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes
* it easier to synchronize everything, export performance could probably benefit from keeping them in separate
* threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could
* be removed.
*/
bool wait_for_mutexes;
/**
* @brief Set the current playback speed (adjusted with Shuttle Left/Right)
*
* Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed.
*
* \see ComposeSequenceParams::video
*/
int playback_speed;
/**
* @brief Premultiply alpha shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* compose_sequence()'s internal composition
* expects premultipled alpha, but it will pre-emptively multiply any footage that is not set as already
* premultiplied (see Footage::alpha_is_premultiplied) using this shader. Must be compiled and linked beforehand.
* See RenderThread::premultiply_program for how this is properly set up.
*/
QOpenGLShaderProgram* premultiply_program;
/**
* @brief The OpenGL framebuffer object that the final texture to be shown is rendered to.
*
* Used only for video rendering. Never accessed with audio rendering.
*
* When compose_sequence() is rendering the final image, this framebuffer will be bound.
*/
const FramebufferObject* main_buffer;
/**
* @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer
*
* 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.
*/
const FramebufferObject* backend_buffer1;
/**
* @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer
*
* 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.
*/
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.
*/
GLuint compose_sequence(ComposeSequenceParams &params);
/**
* @brief Convenience wrapper function for compose_sequence() to render audio
*
* Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for
* video rendering. For audio rendering, this function is easier to handle and will correctly set up
* compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object.
*
* @param viewer
*
* The Viewer object calling this function
*
* @param seq
*
* The Sequence whose audio to render.
*
* @param playback_speed
*
* The current playback speed (controlled by Shuttle Left/Right)
*
* @param
*
* Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**.
*/
void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_for_mutexes);
}
}
void UpdateOCIOGLState(const ComposeSequenceParams &params);
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
/***
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 RENDERFUNCTIONS_H
#define RENDERFUNCTIONS_H
#include <QOpenGLContext>
#include <QVector>
#include <QOpenGLShaderProgram>
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE::v1;
#include "timeline/sequence.h"
#include "nodes/oldeffectnode.h"
#include "panels/viewer.h"
/**
* @brief The ComposeSequenceParams struct
*
* Struct sent to the compose_sequence() function.
*/
struct ComposeSequenceParams {
/**
* @brief Reference to the Viewer class that's calling compose_sequence()
*
* Primarily used for calling Viewer::play_wake() when appropriate.
*/
Viewer* viewer;
/**
* @brief The OpenGL context to use while rendering.
*
* For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed.
*
* \see ComposeSequenceParams::video
*/
QOpenGLContext* ctx;
/**
* @brief The OpenGL pipeline used for rendering
*
* \see olive::rendering::GetPipeline().
*/
QOpenGLShaderProgram* pipeline;
/**
* @brief The sequence to compose
*
* In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame
* to render.
*/
Sequence* seq;
/**
* @brief Array to store the nested sequence hierarchy
*
* Should be left empty. This array gets passed around compose_sequence() as it calls itself recursively to
* handle nested sequences.
*/
QVector<Clip*> nests;
/**
* @brief Set compose mode to video or audio
*
* Accepts olive::kTypeVideo to render video, olive::kTypeAudio if this function should render audio.
*/
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.
*/
OldEffectNode* gizmos;
/**
* @brief A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown.
*
* A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw
* any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a
* "best effort", but not the actual image.
*
* This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**.
*
* \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better
* way to communicate this information.
*
* Additionally, since
* compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with
* stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect
* of this though is that the preview may become less responsive if it's stuck trying to render one frame. With
* the current system, the preview may show incomplete frames occasionally but at least it will show something.
* This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be
* removed.
*/
bool texture_failed;
/**
* @brief Run all cachers in the same thread that compose_sequence() is in
*
* Standard behavior is that all clips cache frames in their own thread and signals are sent between
* compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling
* the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a
* technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used
* when rendering as timing isn't as important as creating output frames as quickly as possible.
*
* \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes
* it easier to synchronize everything, export performance could probably benefit from keeping them in separate
* threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could
* be removed.
*/
bool wait_for_mutexes;
/**
* @brief Set the current playback speed (adjusted with Shuttle Left/Right)
*
* Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed.
*
* \see ComposeSequenceParams::video
*/
int playback_speed;
/**
* @brief Premultiply alpha shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* compose_sequence()'s internal composition
* expects premultipled alpha, but it will pre-emptively multiply any footage that is not set as already
* premultiplied (see Footage::alpha_is_premultiplied) using this shader. Must be compiled and linked beforehand.
* See RenderThread::premultiply_program for how this is properly set up.
*/
QOpenGLShaderProgram* premultiply_program;
/**
* @brief The OpenGL framebuffer object that the final texture to be shown is rendered to.
*
* Used only for video rendering. Never accessed with audio rendering.
*
* When compose_sequence() is rendering the final image, this framebuffer will be bound.
*/
const FramebufferObject* main_buffer;
/**
* @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer
*
* 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.
*/
const FramebufferObject* backend_buffer1;
/**
* @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer
*
* 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.
*/
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.
*/
GLuint compose_sequence(ComposeSequenceParams &params);
/**
* @brief Convenience wrapper function for compose_sequence() to render audio
*
* Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for
* video rendering. For audio rendering, this function is easier to handle and will correctly set up
* compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object.
*
* @param viewer
*
* The Viewer object calling this function
*
* @param seq
*
* The Sequence whose audio to render.
*
* @param playback_speed
*
* The current playback speed (controlled by Shuttle Left/Right)
*
* @param
*
* Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**.
*/
void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_for_mutexes);
}
}
void UpdateOCIOGLState(const ComposeSequenceParams &params);
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
+387 -387
View File
@@ -1,387 +1,387 @@
/***
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 "renderthread.h"
#include <QApplication>
#include <QImage>
#include <QDateTime>
#include <QFileInfo>
#include <QOpenGLExtraFunctions>
#include <QDebug>
#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),
seq(nullptr),
tex_width(-1),
tex_height(-1),
queued(false),
texture_failed(false),
ocio_lut_texture(0),
ocio_shader(nullptr),
running(true),
ocio_config_date(0),
front_buffer_switcher(false),
pipeline_program(nullptr)
{
surface.create();
}
RenderThread::~RenderThread() {
surface.destroy();
}
void RenderThread::run() {
wait_lock_.lock();
while (running) {
if (!queued) {
wait_cond_.wait(&wait_lock_);
}
if (!running) {
break;
}
queued = false;
if (share_ctx != nullptr) {
if (ctx != nullptr) {
ctx->makeCurrent(&surface);
// if the sequence size has changed, we'll need to reinitialize the textures
if (seq->width() != tex_width || seq->height() != tex_height) {
delete_buffers();
// cache sequence values for future checks
tex_width = seq->width();
tex_height = seq->height();
}
// 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());
}
if (!front_buffer_2.IsCreated()) {
front_buffer_2.Create(ctx, seq->width(), seq->height());
}
if (!back_buffer_1.IsCreated()) {
back_buffer_1.Create(ctx, seq->width(), seq->height());
}
if (!back_buffer_2.IsCreated()) {
back_buffer_2.Create(ctx, seq->width(), seq->height());
}
// If there's no pipeline shader, create it now
if (pipeline_program == nullptr) {
delete_shaders();
pipeline_program = olive::shader::GetPipeline();
}
// 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
paint();
front_buffer_switcher = !front_buffer_switcher;
emit ready();
}
}
}
delete_ctx();
wait_lock_.unlock();
}
QMutex *RenderThread::get_texture_mutex()
{
// return the mutex for the opposite texture being drawn to by the renderer
return front_buffer_switcher ? &front_mutex2 : &front_mutex1;
}
const GLuint &RenderThread::get_texture()
{
// return the opposite texture to the texture being drawn to by the renderer
return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.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() {
// set up compose_sequence() parameters
ComposeSequenceParams params;
params.viewer = nullptr;
params.ctx = ctx;
params.seq = seq;
params.type = olive::kTypeVideo;
params.texture_failed = false;
params.wait_for_mutexes = true;
params.playback_speed = playback_speed_;
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();
FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2;
// Blit the composite buffer to one of the front buffers
// If we're color managing, conver the linear composited frame to display color space
if (olive::config.enable_color_management && ocio_shader != nullptr) {
olive::rendering::OCIOBlit(ocio_shader.get(),
ocio_lut_texture,
buffer,
composite_buffer.texture());
} else {
// 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
f->glFinish();
f->glDisable(GL_BLEND);
texture_failed = params.texture_failed;
active_mutex.unlock();
if (!save_fn.isEmpty()) {
if (texture_failed) {
// texture failed, try again
queued = true;
} else {
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);
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
save_fn = "";
}
}
if (pixel_buffer != nullptr) {
// set main framebuffer to the current read buffer
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer());
// store pixels in 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
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
pixel_buffer = nullptr;
}
// release
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void RenderThread::start_render(QOpenGLContext *share,
Sequence* s,
int playback_speed,
const QString& save,
GLvoid* pixels,
int pixel_linesize,
int idivider) {
Q_UNUSED(idivider);
seq = s;
playback_speed_ = playback_speed;
// stall any dependent actions
texture_failed = true;
if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) {
share_ctx = share;
delete_ctx();
ctx = new QOpenGLContext();
ctx->setFormat(share_ctx->format());
ctx->setShareContext(share_ctx);
ctx->create();
ctx->moveToThread(this);
}
save_fn = save;
pixel_buffer = pixels;
pixel_buffer_linesize = pixel_linesize;
queued = true;
wait_cond_.wakeAll();
}
bool RenderThread::did_texture_fail() {
return texture_failed;
}
void RenderThread::cancel() {
running = false;
wait_cond_.wakeAll();
wait();
}
void RenderThread::wait_until_paused()
{
// Wait for thread to finish whatever it's doing before proceeding.
//
// FIXME: This is slow. Perhaps there's a better way...
if (wait_lock_.tryLock()) {
wait_lock_.unlock();
return;
} else {
wait_lock_.lock();
wait_lock_.unlock();
}
}
void RenderThread::delete_buffers() {
composite_buffer.Destroy();
front_buffer_1.Destroy();
front_buffer_2.Destroy();
back_buffer_1.Destroy();
back_buffer_2.Destroy();
}
void RenderThread::delete_shaders() {
pipeline_program = nullptr;
}
void RenderThread::delete_ctx() {
if (ctx != nullptr) {
delete_shaders();
delete_buffers();
destroy_ocio();
}
delete ctx;
ctx = nullptr;
}
/***
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 "renderthread.h"
#include <QApplication>
#include <QImage>
#include <QDateTime>
#include <QFileInfo>
#include <QOpenGLExtraFunctions>
#include <QDebug>
#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),
seq(nullptr),
tex_width(-1),
tex_height(-1),
queued(false),
texture_failed(false),
ocio_lut_texture(0),
ocio_shader(nullptr),
running(true),
ocio_config_date(0),
front_buffer_switcher(false),
pipeline_program(nullptr)
{
surface.create();
}
RenderThread::~RenderThread() {
surface.destroy();
}
void RenderThread::run() {
wait_lock_.lock();
while (running) {
if (!queued) {
wait_cond_.wait(&wait_lock_);
}
if (!running) {
break;
}
queued = false;
if (share_ctx != nullptr) {
if (ctx != nullptr) {
ctx->makeCurrent(&surface);
// if the sequence size has changed, we'll need to reinitialize the textures
if (seq->width() != tex_width || seq->height() != tex_height) {
delete_buffers();
// cache sequence values for future checks
tex_width = seq->width();
tex_height = seq->height();
}
// 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());
}
if (!front_buffer_2.IsCreated()) {
front_buffer_2.Create(ctx, seq->width(), seq->height());
}
if (!back_buffer_1.IsCreated()) {
back_buffer_1.Create(ctx, seq->width(), seq->height());
}
if (!back_buffer_2.IsCreated()) {
back_buffer_2.Create(ctx, seq->width(), seq->height());
}
// If there's no pipeline shader, create it now
if (pipeline_program == nullptr) {
delete_shaders();
pipeline_program = olive::shader::GetPipeline();
}
// 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
paint();
front_buffer_switcher = !front_buffer_switcher;
emit ready();
}
}
}
delete_ctx();
wait_lock_.unlock();
}
QMutex *RenderThread::get_texture_mutex()
{
// return the mutex for the opposite texture being drawn to by the renderer
return front_buffer_switcher ? &front_mutex2 : &front_mutex1;
}
const GLuint &RenderThread::get_texture()
{
// return the opposite texture to the texture being drawn to by the renderer
return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.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() {
// set up compose_sequence() parameters
ComposeSequenceParams params;
params.viewer = nullptr;
params.ctx = ctx;
params.seq = seq;
params.type = olive::kTypeVideo;
params.texture_failed = false;
params.wait_for_mutexes = true;
params.playback_speed = playback_speed_;
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();
FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2;
// Blit the composite buffer to one of the front buffers
// If we're color managing, conver the linear composited frame to display color space
if (olive::config.enable_color_management && ocio_shader != nullptr) {
olive::rendering::OCIOBlit(ocio_shader.get(),
ocio_lut_texture,
buffer,
composite_buffer.texture());
} else {
// 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
f->glFinish();
f->glDisable(GL_BLEND);
texture_failed = params.texture_failed;
active_mutex.unlock();
if (!save_fn.isEmpty()) {
if (texture_failed) {
// texture failed, try again
queued = true;
} else {
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);
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
save_fn = "";
}
}
if (pixel_buffer != nullptr) {
// set main framebuffer to the current read buffer
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer());
// store pixels in 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
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
pixel_buffer = nullptr;
}
// release
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void RenderThread::start_render(QOpenGLContext *share,
Sequence* s,
int playback_speed,
const QString& save,
GLvoid* pixels,
int pixel_linesize,
int idivider) {
Q_UNUSED(idivider);
seq = s;
playback_speed_ = playback_speed;
// stall any dependent actions
texture_failed = true;
if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) {
share_ctx = share;
delete_ctx();
ctx = new QOpenGLContext();
ctx->setFormat(share_ctx->format());
ctx->setShareContext(share_ctx);
ctx->create();
ctx->moveToThread(this);
}
save_fn = save;
pixel_buffer = pixels;
pixel_buffer_linesize = pixel_linesize;
queued = true;
wait_cond_.wakeAll();
}
bool RenderThread::did_texture_fail() {
return texture_failed;
}
void RenderThread::cancel() {
running = false;
wait_cond_.wakeAll();
wait();
}
void RenderThread::wait_until_paused()
{
// Wait for thread to finish whatever it's doing before proceeding.
//
// FIXME: This is slow. Perhaps there's a better way...
if (wait_lock_.tryLock()) {
wait_lock_.unlock();
return;
} else {
wait_lock_.lock();
wait_lock_.unlock();
}
}
void RenderThread::delete_buffers() {
composite_buffer.Destroy();
front_buffer_1.Destroy();
front_buffer_2.Destroy();
back_buffer_1.Destroy();
back_buffer_2.Destroy();
}
void RenderThread::delete_shaders() {
pipeline_program = nullptr;
}
void RenderThread::delete_ctx() {
if (ctx != nullptr) {
delete_shaders();
delete_buffers();
destroy_ocio();
}
delete ctx;
ctx = nullptr;
}
+116 -116
View File
@@ -1,116 +1,116 @@
/***
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 RENDERTHREAD_H
#define RENDERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLShaderProgram>
#include "timeline/sequence.h"
#include "nodes/oldeffectnode.h"
#include "rendering/framebufferobject.h"
#include "qopenglshaderprogramptr.h"
class RenderThread : public QThread {
Q_OBJECT
public:
RenderThread();
~RenderThread();
void run();
QMutex* get_texture_mutex();
const GLuint& get_texture();
OldEffectNode* gizmos;
void paint();
void start_render(QOpenGLContext* share,
Sequence *s,
int playback_speed,
const QString &save = nullptr,
GLvoid *pixels = nullptr,
int pixel_linesize = 0,
int idivider = 0);
bool did_texture_fail();
void cancel();
void wait_until_paused();
public slots:
// cleanup functions
void delete_ctx();
void delete_buffers();
void delete_shaders();
void destroy_ocio();
signals:
void ready();
private:
// OpenColorIO functions
void set_up_ocio();
// OpenColorIO variables
GLuint ocio_lut_texture;
QOpenGLShaderProgramPtr ocio_shader;
qint64 ocio_config_date;
FramebufferObject front_buffer_1;
QMutex front_mutex1;
FramebufferObject front_buffer_2;
QMutex front_mutex2;
FramebufferObject composite_buffer;
bool front_buffer_switcher;
QWaitCondition wait_cond_;
QMutex wait_lock_;
QWaitCondition main_thread_wait_cond_;
QMutex main_thread_lock_;
QOffscreenSurface surface;
QOpenGLContext* share_ctx;
QOpenGLContext* ctx;
QOpenGLShaderProgramPtr pipeline_program;
FramebufferObject back_buffer_1;
FramebufferObject back_buffer_2;
Sequence* seq;
int playback_speed_;
int divider;
int tex_width;
int tex_height;
bool queued;
bool texture_failed;
bool running;
QString save_fn;
GLvoid *pixel_buffer;
int pixel_buffer_linesize;
};
#endif // RENDERTHREAD_H
/***
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 RENDERTHREAD_H
#define RENDERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLShaderProgram>
#include "timeline/sequence.h"
#include "nodes/oldeffectnode.h"
#include "rendering/framebufferobject.h"
#include "qopenglshaderprogramptr.h"
class RenderThread : public QThread {
Q_OBJECT
public:
RenderThread();
~RenderThread();
void run();
QMutex* get_texture_mutex();
const GLuint& get_texture();
OldEffectNode* gizmos;
void paint();
void start_render(QOpenGLContext* share,
Sequence *s,
int playback_speed,
const QString &save = nullptr,
GLvoid *pixels = nullptr,
int pixel_linesize = 0,
int idivider = 0);
bool did_texture_fail();
void cancel();
void wait_until_paused();
public slots:
// cleanup functions
void delete_ctx();
void delete_buffers();
void delete_shaders();
void destroy_ocio();
signals:
void ready();
private:
// OpenColorIO functions
void set_up_ocio();
// OpenColorIO variables
GLuint ocio_lut_texture;
QOpenGLShaderProgramPtr ocio_shader;
qint64 ocio_config_date;
FramebufferObject front_buffer_1;
QMutex front_mutex1;
FramebufferObject front_buffer_2;
QMutex front_mutex2;
FramebufferObject composite_buffer;
bool front_buffer_switcher;
QWaitCondition wait_cond_;
QMutex wait_lock_;
QWaitCondition main_thread_wait_cond_;
QMutex main_thread_lock_;
QOffscreenSurface surface;
QOpenGLContext* share_ctx;
QOpenGLContext* ctx;
QOpenGLShaderProgramPtr pipeline_program;
FramebufferObject back_buffer_1;
FramebufferObject back_buffer_2;
Sequence* seq;
int playback_speed_;
int divider;
int tex_width;
int tex_height;
bool queued;
bool texture_failed;
bool running;
QString save_fn;
GLvoid *pixel_buffer;
int pixel_buffer_linesize;
};
#endif // RENDERTHREAD_H
+230 -230
View File
@@ -1,230 +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;
}
#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 -26
View File
@@ -1,26 +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
#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