fix line endings
This commit is contained in:
+426
-426
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+602
-602
File diff suppressed because it is too large
Load Diff
+105
-105
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+141
-141
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+850
-850
File diff suppressed because it is too large
Load Diff
+247
-247
@@ -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 ¶ms);
|
||||
|
||||
/**
|
||||
* @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 ¶ms);
|
||||
|
||||
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 ¶ms);
|
||||
|
||||
/**
|
||||
* @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 ¶ms);
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user