giant rendering overhaul and many small fixes

This commit is contained in:
itsmattkc
2019-02-25 13:47:18 -08:00
parent b9d7efa0bc
commit c3373524b1
1429 changed files with 10234 additions and 194593 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
#include "audiomonitor.h"
#include "project/sequence.h"
#include "playback/audio.h"
#include "rendering/audio.h"
#include "panels/panels.h"
#include "panels/timeline.h"
+85 -83
View File
@@ -127,8 +127,8 @@ void GraphView::set_view_to_selection() {
max_dbl = qMax(key.data.toDouble(), max_dbl);
}
min_time -= row->parent_effect->parent_clip->clip_in;
max_time -= row->parent_effect->parent_clip->clip_in;
min_time -= row->parent_effect->parent_clip->clip_in();
max_time -= row->parent_effect->parent_clip->clip_in();
set_view_to_rect(min_time, min_dbl, max_time, max_dbl);
}
@@ -153,8 +153,8 @@ void GraphView::set_view_to_all() {
}
}
if (can_set) {
min_time -= row->parent_effect->parent_clip->clip_in;
max_time -= row->parent_effect->parent_clip->clip_in;
min_time -= row->parent_effect->parent_clip->clip_in();
max_time -= row->parent_effect->parent_clip->clip_in();
set_view_to_rect(min_time, min_dbl, max_time, max_dbl);
}
@@ -608,85 +608,87 @@ void GraphView::mouseMoveEvent(QMouseEvent *event) {
if (field_visibility.at(i)) {
QVector<int> sorted_keys = sort_keys_from_field(f);
if (event->pos().x() <= get_screen_x(f->keyframes.at(sorted_keys.first()).time)) {
int y_comp = get_screen_y(f->keyframes.at(sorted_keys.first()).data.toDouble());
if (event->pos().y() >= y_comp-kBezierLineSize
&& event->pos().y() <= y_comp+kBezierLineSize) {
// dout << "make an EARLY key on field" << i;
click_add = true;
click_add_type = f->keyframes.at(sorted_keys.first()).type;
}
} else if (event->pos().x() >= get_screen_x(f->keyframes.at(sorted_keys.last()).time)) {
int y_comp = get_screen_y(f->keyframes.at(sorted_keys.last()).data.toDouble());
if (event->pos().y() >= y_comp-kBezierLineSize
&& event->pos().y() <= y_comp+kBezierLineSize) {
// dout << "make an LATE key on field" << i;
click_add = true;
click_add_type = f->keyframes.at(sorted_keys.last()).type;
}
} else {
for (int j=1;j<sorted_keys.size();j++) {
const EffectKeyframe& last_key = f->keyframes.at(sorted_keys.at(j-1));
const EffectKeyframe& key = f->keyframes.at(sorted_keys.at(j));
if (!sorted_keys.isEmpty()) {
if (event->pos().x() <= get_screen_x(f->keyframes.at(sorted_keys.first()).time)) {
int y_comp = get_screen_y(f->keyframes.at(sorted_keys.first()).data.toDouble());
if (event->pos().y() >= y_comp-kBezierLineSize
&& event->pos().y() <= y_comp+kBezierLineSize) {
// dout << "make an EARLY key on field" << i;
click_add = true;
click_add_type = f->keyframes.at(sorted_keys.first()).type;
}
} else if (event->pos().x() >= get_screen_x(f->keyframes.at(sorted_keys.last()).time)) {
int y_comp = get_screen_y(f->keyframes.at(sorted_keys.last()).data.toDouble());
if (event->pos().y() >= y_comp-kBezierLineSize
&& event->pos().y() <= y_comp+kBezierLineSize) {
// dout << "make an LATE key on field" << i;
click_add = true;
click_add_type = f->keyframes.at(sorted_keys.last()).type;
}
} else {
for (int j=1;j<sorted_keys.size();j++) {
const EffectKeyframe& last_key = f->keyframes.at(sorted_keys.at(j-1));
const EffectKeyframe& key = f->keyframes.at(sorted_keys.at(j));
int last_key_x = get_screen_x(last_key.time);
int key_x = get_screen_x(key.time);
int last_key_y = get_screen_y(last_key.data.toDouble());
int key_y = get_screen_y(key.data.toDouble());
int last_key_x = get_screen_x(last_key.time);
int key_x = get_screen_x(key.time);
int last_key_y = get_screen_y(last_key.data.toDouble());
int key_y = get_screen_y(key.data.toDouble());
click_add_type = last_key.type;
click_add_type = last_key.type;
if (event->pos().x() >= last_key_x
&& event->pos().x() <= key_x) {
QRect mouse_rect(event->pos().x()-kBezierLineSize, event->pos().y()-kBezierLineSize, kBezierLineSize+kBezierLineSize, kBezierLineSize+kBezierLineSize);
// NOTE: FILTHY copy/paste from paintEvent
if (last_key.type == EFFECT_KEYFRAME_HOLD) {
// hold
if (event->pos().y() >= last_key_y-kBezierLineSize
&& event->pos().y() <= last_key_y+kBezierLineSize) {
// dout << "make an HOLD key on field" << i << "after key" << j;
click_add = true;
}
} else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) {
QPainterPath bezier_path;
bezier_path.moveTo(last_key_x, last_key_y);
if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) {
// cubic bezier
bezier_path.cubicTo(
QPointF(last_key_x+last_key.post_handle_x*x_zoom, last_key_y-last_key.post_handle_y*y_zoom),
QPointF(key_x+key.pre_handle_x*x_zoom, key_y-key.pre_handle_y*y_zoom),
QPointF(key_x, key_y)
);
} else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier
// last keyframe is the bezier one
bezier_path.quadTo(
QPointF(last_key_x+last_key.post_handle_x*x_zoom, last_key_y-last_key.post_handle_y*y_zoom),
QPointF(key_x, key_y)
);
} else {
// this keyframe is the bezier one
bezier_path.quadTo(
QPointF(key_x+key.pre_handle_x*x_zoom, key_y-key.pre_handle_y*y_zoom),
QPointF(key_x, key_y)
);
}
if (bezier_path.intersects(mouse_rect)) {
// dout << "make an BEZIER key on field" << i << "after key" << j;
click_add = true;
}
} else {
// linear
QPainterPath linear_path;
linear_path.moveTo(last_key_x, last_key_y);
linear_path.lineTo(key_x, key_y);
if (linear_path.intersects(mouse_rect)) {
// dout << "make an LINEAR key on field" << i << "after key" << j;
click_add = true;
}
}
}
}
}
if (event->pos().x() >= last_key_x
&& event->pos().x() <= key_x) {
QRect mouse_rect(event->pos().x()-kBezierLineSize, event->pos().y()-kBezierLineSize, kBezierLineSize+kBezierLineSize, kBezierLineSize+kBezierLineSize);
// NOTE: FILTHY copy/paste from paintEvent
if (last_key.type == EFFECT_KEYFRAME_HOLD) {
// hold
if (event->pos().y() >= last_key_y-kBezierLineSize
&& event->pos().y() <= last_key_y+kBezierLineSize) {
// dout << "make an HOLD key on field" << i << "after key" << j;
click_add = true;
}
} else if (last_key.type == EFFECT_KEYFRAME_BEZIER || key.type == EFFECT_KEYFRAME_BEZIER) {
QPainterPath bezier_path;
bezier_path.moveTo(last_key_x, last_key_y);
if (last_key.type == EFFECT_KEYFRAME_BEZIER && key.type == EFFECT_KEYFRAME_BEZIER) {
// cubic bezier
bezier_path.cubicTo(
QPointF(last_key_x+last_key.post_handle_x*x_zoom, last_key_y-last_key.post_handle_y*y_zoom),
QPointF(key_x+key.pre_handle_x*x_zoom, key_y-key.pre_handle_y*y_zoom),
QPointF(key_x, key_y)
);
} else if (key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier
// last keyframe is the bezier one
bezier_path.quadTo(
QPointF(last_key_x+last_key.post_handle_x*x_zoom, last_key_y-last_key.post_handle_y*y_zoom),
QPointF(key_x, key_y)
);
} else {
// this keyframe is the bezier one
bezier_path.quadTo(
QPointF(key_x+key.pre_handle_x*x_zoom, key_y-key.pre_handle_y*y_zoom),
QPointF(key_x, key_y)
);
}
if (bezier_path.intersects(mouse_rect)) {
// dout << "make an BEZIER key on field" << i << "after key" << j;
click_add = true;
}
} else {
// linear
QPainterPath linear_path;
linear_path.moveTo(last_key_x, last_key_y);
linear_path.lineTo(key_x, key_y);
if (linear_path.intersects(mouse_rect)) {
// dout << "make an LINEAR key on field" << i << "after key" << j;
click_add = true;
}
}
}
}
}
}
}
if (click_add) {
click_add_field = f;
@@ -796,7 +798,7 @@ void GraphView::set_row(EffectRow *r) {
for (int i=0;i<row->fieldCount();i++) {
field_visibility[i] = row->field(i)->is_enabled();
}
visible_in = row->parent_effect->parent_clip->timeline_in;
visible_in = row->parent_effect->parent_clip->timeline_in();
set_view_to_all();
} else {
update();
@@ -864,7 +866,7 @@ void GraphView::set_zoom(double xz, double yz) {
int GraphView::get_screen_x(double d) {
if (row != nullptr) {
d -= row->parent_effect->parent_clip->clip_in;
d -= row->parent_effect->parent_clip->clip_in();
}
return qRound((d*x_zoom) - x_scroll);
}
@@ -876,7 +878,7 @@ int GraphView::get_screen_y(double d) {
long GraphView::get_value_x(int i) {
long frame = qRound((i + x_scroll)/x_zoom);
if (row != nullptr) {
frame += row->parent_effect->parent_clip->clip_in;
frame += row->parent_effect->parent_clip->clip_in();
}
return frame;
}
+3 -1
View File
@@ -55,5 +55,7 @@ void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r, int
// adjusts keyframe's internal time (in clip time) to timeline time
long adjust_row_keyframe(EffectRow* row, long time, long visible_in) {
return time-row->parent_effect->parent_clip->clip_in+(row->parent_effect->parent_clip->timeline_in-visible_in);
return time
- row->parent_effect->parent_clip->clip_in()
+ (row->parent_effect->parent_clip->timeline_in() - visible_in);
}
+313 -310
View File
@@ -44,394 +44,397 @@
#include <QMenu>
KeyframeView::KeyframeView(QWidget *parent) :
QWidget(parent),
visible_in(0),
visible_out(0),
mousedown(false),
dragging(false),
keys_selected(false),
select_rect(false),
x_scroll(0),
y_scroll(0),
scroll_drag(false)
QWidget(parent),
visible_in(0),
visible_out(0),
mousedown(false),
dragging(false),
keys_selected(false),
select_rect(false),
x_scroll(0),
y_scroll(0),
scroll_drag(false)
{
setFocusPolicy(Qt::ClickFocus);
setMouseTracking(true);
setFocusPolicy(Qt::ClickFocus);
setMouseTracking(true);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&)));
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&)));
}
void KeyframeView::show_context_menu(const QPoint& pos) {
if (selected_fields.size() > 0) {
QMenu menu(this);
if (selected_fields.size() > 0) {
QMenu menu(this);
QAction* linear = menu.addAction(tr("Linear"));
linear->setData(EFFECT_KEYFRAME_LINEAR);
QAction* bezier = menu.addAction(tr("Bezier"));
bezier->setData(EFFECT_KEYFRAME_BEZIER);
QAction* hold = menu.addAction(tr("Hold"));
hold->setData(EFFECT_KEYFRAME_HOLD);
menu.addSeparator();
menu.addAction("Graph Editor");
QAction* linear = menu.addAction(tr("Linear"));
linear->setData(EFFECT_KEYFRAME_LINEAR);
QAction* bezier = menu.addAction(tr("Bezier"));
bezier->setData(EFFECT_KEYFRAME_BEZIER);
QAction* hold = menu.addAction(tr("Hold"));
hold->setData(EFFECT_KEYFRAME_HOLD);
menu.addSeparator();
menu.addAction("Graph Editor");
connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_set_key_type(QAction*)));
menu.exec(mapToGlobal(pos));
}
connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_set_key_type(QAction*)));
menu.exec(mapToGlobal(pos));
}
}
void KeyframeView::menu_set_key_type(QAction* a) {
if (a->data().isNull()) {
// load graph editor
panel_graph_editor->show();
} else {
ComboAction* ca = new ComboAction();
for (int i=0;i<selected_fields.size();i++) {
EffectField* f = selected_fields.at(i);
ca->append(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt()));
}
olive::UndoStack.push(ca);
update_ui(false);
}
if (a->data().isNull()) {
// load graph editor
panel_graph_editor->show();
} else {
ComboAction* ca = new ComboAction();
for (int i=0;i<selected_fields.size();i++) {
EffectField* f = selected_fields.at(i);
ca->append(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt()));
}
olive::UndoStack.push(ca);
update_ui(false);
}
}
void KeyframeView::paintEvent(QPaintEvent*) {
QPainter p(this);
QPainter p(this);
rowY.clear();
rows.clear();
rowY.clear();
rows.clear();
if (panel_effect_controls->selected_clips.size() > 0) {
visible_in = LONG_MAX;
visible_out = 0;
if (panel_effect_controls->selected_clips.size() > 0) {
visible_in = LONG_MAX;
visible_out = 0;
for (int j=0;j<panel_effect_controls->selected_clips.size();j++) {
ClipPtr c = olive::ActiveSequence->clips.at(panel_effect_controls->selected_clips.at(j));
visible_in = qMin(visible_in, c->timeline_in);
visible_out = qMax(visible_out, c->timeline_out);
}
for (int j=0;j<panel_effect_controls->selected_clips.size();j++) {
ClipPtr c = olive::ActiveSequence->clips.at(panel_effect_controls->selected_clips.at(j));
visible_in = qMin(visible_in, c->timeline_in());
visible_out = qMax(visible_out, c->timeline_out());
}
for (int j=0;j<panel_effect_controls->selected_clips.size();j++) {
ClipPtr c = olive::ActiveSequence->clips.at(panel_effect_controls->selected_clips.at(j));
for (int i=0;i<c->effects.size();i++) {
EffectPtr e = c->effects.at(i);
if (e->container->is_expanded()) {
for (int j=0;j<e->row_count();j++) {
EffectRow* row = e->row(j);
for (int j=0;j<panel_effect_controls->selected_clips.size();j++) {
ClipPtr c = olive::ActiveSequence->clips.at(panel_effect_controls->selected_clips.at(j));
for (int i=0;i<c->effects.size();i++) {
EffectPtr e = c->effects.at(i);
if (e->container->is_expanded()) {
for (int j=0;j<e->row_count();j++) {
EffectRow* row = e->row(j);
ClickableLabel* label = row->label;
QWidget* contents = e->container->contents;
ClickableLabel* label = row->label;
QWidget* contents = e->container->contents;
QVector<long> key_times;
int keyframe_y = label->y() + (label->height()>>1) + mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y() - e->container->title_bar->height()/* - y_scroll*/;
for (int l=0;l<row->fieldCount();l++) {
EffectField* f = row->field(l);
for (int k=0;k<f->keyframes.size();k++) {
if (!key_times.contains(f->keyframes.at(k).time)) {
bool keyframe_selected = keyframeIsSelected(f, k);
long keyframe_frame = adjust_row_keyframe(row, f->keyframes.at(k).time, visible_in);
QVector<long> key_times;
int keyframe_y = label->y() + (label->height()>>1) + mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y() - e->container->title_bar->height()/* - y_scroll*/;
for (int l=0;l<row->fieldCount();l++) {
EffectField* f = row->field(l);
for (int k=0;k<f->keyframes.size();k++) {
if (!key_times.contains(f->keyframes.at(k).time)) {
bool keyframe_selected = keyframeIsSelected(f, k);
long keyframe_frame = adjust_row_keyframe(row, f->keyframes.at(k).time, visible_in);
// see if any other keyframes have this time
int appearances = 0;
for (int m=0;m<row->fieldCount();m++) {
EffectField* compf = row->field(m);
for (int n=0;n<compf->keyframes.size();n++) {
if (f->keyframes.at(k).time == compf->keyframes.at(n).time) {
appearances++;
}
}
}
// see if any other keyframes have this time
int appearances = 0;
for (int m=0;m<row->fieldCount();m++) {
EffectField* compf = row->field(m);
for (int n=0;n<compf->keyframes.size();n++) {
if (f->keyframes.at(k).time == compf->keyframes.at(n).time) {
appearances++;
}
}
}
if (appearances != row->fieldCount()) {
QColor cc = get_curve_color(l, row->fieldCount());
draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected, cc.red(), cc.green(), cc.blue());
} else {
draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected);
}
if (appearances != row->fieldCount()) {
QColor cc = get_curve_color(l, row->fieldCount());
draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected, cc.red(), cc.green(), cc.blue());
} else {
draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected);
}
key_times.append(f->keyframes.at(k).time);
}
}
}
key_times.append(f->keyframes.at(k).time);
}
}
}
rows.append(row);
rowY.append(keyframe_y);
}
}
}
}
rows.append(row);
rowY.append(keyframe_y);
}
}
}
}
int max_width = getScreenPointFromFrame(panel_effect_controls->zoom, visible_out - visible_in);
if (max_width < width()) {
p.fillRect(QRect(max_width, 0, width(), height()), QColor(0, 0, 0, 64));
}
panel_effect_controls->horizontalScrollBar->setMaximum(qMax(max_width - width(), 0));
header->set_visible_in(visible_in);
int max_width = getScreenPointFromFrame(panel_effect_controls->zoom, visible_out - visible_in);
if (max_width < width()) {
p.fillRect(QRect(max_width, 0, width(), height()), QColor(0, 0, 0, 64));
}
panel_effect_controls->horizontalScrollBar->setMaximum(qMax(max_width - width(), 0));
header->set_visible_in(visible_in);
int playhead_x = getScreenPointFromFrame(panel_effect_controls->zoom, olive::ActiveSequence->playhead-visible_in) - x_scroll;
if (dragging && panel_timeline->snapped) {
p.setPen(Qt::white);
} else {
p.setPen(Qt::red);
}
p.drawLine(playhead_x, 0, playhead_x, height());
}
int playhead_x = getScreenPointFromFrame(panel_effect_controls->zoom, olive::ActiveSequence->playhead-visible_in) - x_scroll;
if (dragging && panel_timeline->snapped) {
p.setPen(Qt::white);
} else {
p.setPen(Qt::red);
}
p.drawLine(playhead_x, 0, playhead_x, height());
}
if (select_rect) {
draw_selection_rectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h));
}
if (select_rect) {
draw_selection_rectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h));
}
/*if (mouseover && mouseover_row < rowY.size()) {
draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, mouseover_frame - visible_in), rowY.at(mouseover_row), true);
/*if (mouseover && mouseover_row < rowY.size()) {
draw_keyframe(p, getScreenPointFromFrame(panel_effect_controls->zoom, mouseover_frame - visible_in), rowY.at(mouseover_row), true);
}*/
}
void KeyframeView::wheelEvent(QWheelEvent *e) {
emit wheel_event_signal(e);
emit wheel_event_signal(e);
}
bool KeyframeView::keyframeIsSelected(EffectField *field, int keyframe) {
for (int i=0;i<selected_fields.size();i++) {
if (selected_fields.at(i) == field && selected_keyframes.at(i) == keyframe) {
return true;
}
}
return false;
for (int i=0;i<selected_fields.size();i++) {
if (selected_fields.at(i) == field && selected_keyframes.at(i) == keyframe) {
return true;
}
}
return false;
}
void KeyframeView::update_keys() {
// panel_graph_editor->update_panel();
update();
// panel_graph_editor->update_panel();
update();
}
void KeyframeView::delete_selected_keyframes() {
delete_keyframes(selected_fields, selected_keyframes);
delete_keyframes(selected_fields, selected_keyframes);
}
void KeyframeView::set_x_scroll(int s) {
x_scroll = s;
update_keys();
x_scroll = s;
update_keys();
}
void KeyframeView::set_y_scroll(int s) {
y_scroll = s;
update_keys();
y_scroll = s;
update_keys();
}
void KeyframeView::resize_move(double d) {
panel_effect_controls->zoom *= d;
header->update_zoom(panel_effect_controls->zoom);
update();
panel_effect_controls->zoom *= d;
header->update_zoom(panel_effect_controls->zoom);
update();
}
void KeyframeView::mousePressEvent(QMouseEvent *event) {
rect_select_x = event->x();
rect_select_y = event->y();
rect_select_w = 0;
rect_select_h = 0;
rect_select_x = event->x();
rect_select_y = event->y();
rect_select_w = 0;
rect_select_h = 0;
if (panel_timeline->tool == TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) {
scroll_drag = true;
return;
}
if (panel_timeline->tool == TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) {
scroll_drag = true;
return;
}
old_key_vals.clear();
old_key_vals.clear();
int mouse_x = event->x() + x_scroll;
int mouse_y = event->y();
int row_index = -1;
int field_index = -1;
int keyframe_index = -1;
long frame_diff = 0;
long frame_min = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x-KEYFRAME_SIZE);
drag_frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x);
long frame_max = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x+KEYFRAME_SIZE);
for (int i=0;i<rowY.size();i++) {
if (mouse_y > rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && mouse_y < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) {
EffectRow* row = rows.at(i);
int mouse_x = event->x() + x_scroll;
int mouse_y = event->y();
int row_index = -1;
int field_index = -1;
int keyframe_index = -1;
long frame_diff = 0;
long frame_min = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x-KEYFRAME_SIZE);
drag_frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x);
long frame_max = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x+KEYFRAME_SIZE);
for (int i=0;i<rowY.size();i++) {
if (mouse_y > rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && mouse_y < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) {
EffectRow* row = rows.at(i);
row->focus_row();
row->focus_row();
for (int k=0;k<row->fieldCount();k++) {
EffectField* f = row->field(k);
for (int j=0;j<f->keyframes.size();j++) {
long eval_keyframe_time = f->keyframes.at(j).time-row->parent_effect->parent_clip->clip_in+(row->parent_effect->parent_clip->timeline_in-visible_in);
if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) {
long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start);
if (keyframe_index == -1 || eval_frame_diff < frame_diff) {
row_index = i;
field_index = k;
keyframe_index = j;
frame_diff = eval_frame_diff;
}
}
}
}
break;
}
}
bool already_selected = false;
keys_selected = false;
if (keyframe_index > -1) {
already_selected = keyframeIsSelected(rows.at(row_index)->field(field_index), keyframe_index);
} else {
select_rect = true;
}
if (!already_selected) {
if (!(event->modifiers() & Qt::ShiftModifier)) {
selected_fields.clear();
selected_keyframes.clear();
}
if (keyframe_index > -1) {
selected_fields.append(rows.at(row_index)->field(field_index));
selected_keyframes.append(keyframe_index);
for (int k=0;k<row->fieldCount();k++) {
EffectField* f = row->field(k);
for (int j=0;j<f->keyframes.size();j++) {
long eval_keyframe_time = f->keyframes.at(j).time-row->parent_effect->parent_clip->clip_in()+(row->parent_effect->parent_clip->timeline_in()-visible_in);
if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) {
long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start);
if (keyframe_index == -1 || eval_frame_diff < frame_diff) {
row_index = i;
field_index = k;
keyframe_index = j;
frame_diff = eval_frame_diff;
}
}
}
}
break;
}
}
bool already_selected = false;
keys_selected = false;
if (keyframe_index > -1) {
already_selected = keyframeIsSelected(rows.at(row_index)->field(field_index), keyframe_index);
} else {
select_rect = true;
}
if (!already_selected) {
if (!(event->modifiers() & Qt::ShiftModifier)) {
selected_fields.clear();
selected_keyframes.clear();
}
if (keyframe_index > -1) {
selected_fields.append(rows.at(row_index)->field(field_index));
selected_keyframes.append(keyframe_index);
// find other field with keyframes at the same time
long comp_time = rows.at(row_index)->field(field_index)->keyframes.at(keyframe_index).time;
for (int i=0;i<rows.at(row_index)->fieldCount();i++) {
if (i != field_index) {
EffectField* f = rows.at(row_index)->field(i);
for (int j=0;j<f->keyframes.size();j++) {
if (f->keyframes.at(j).time == comp_time) {
selected_fields.append(f);
selected_keyframes.append(j);
}
}
}
}
}
}
// find other field with keyframes at the same time
long comp_time = rows.at(row_index)->field(field_index)->keyframes.at(keyframe_index).time;
for (int i=0;i<rows.at(row_index)->fieldCount();i++) {
if (i != field_index) {
EffectField* f = rows.at(row_index)->field(i);
for (int j=0;j<f->keyframes.size();j++) {
if (f->keyframes.at(j).time == comp_time) {
selected_fields.append(f);
selected_keyframes.append(j);
}
}
}
}
}
}
if (selected_fields.size() > 0) {
for (int i=0;i<selected_fields.size();i++) {
old_key_vals.append(selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time);
}
keys_selected = true;
}
if (selected_fields.size() > 0) {
for (int i=0;i<selected_fields.size();i++) {
old_key_vals.append(selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time);
}
keys_selected = true;
}
rect_select_offset = selected_fields.size();
rect_select_offset = selected_fields.size();
update_keys();
update_keys();
if (event->button() == Qt::LeftButton) {
mousedown = true;
}
if (event->button() == Qt::LeftButton) {
mousedown = true;
}
}
void KeyframeView::mouseMoveEvent(QMouseEvent* event) {
if (panel_timeline->tool == TIMELINE_TOOL_HAND) {
setCursor(Qt::OpenHandCursor);
} else {
unsetCursor();
}
if (scroll_drag) {
panel_effect_controls->horizontalScrollBar->setValue(panel_effect_controls->horizontalScrollBar->value() + rect_select_x - event->pos().x());
panel_effect_controls->verticalScrollBar->setValue(panel_effect_controls->verticalScrollBar->value() + rect_select_y - event->pos().y());
rect_select_x = event->pos().x();
rect_select_y = event->pos().y();
} else if (mousedown) {
int mouse_x = event->x() + x_scroll;
if (select_rect) {
// do a rect select
selected_fields.resize(rect_select_offset);
selected_keyframes.resize(rect_select_offset);
if (panel_timeline->tool == TIMELINE_TOOL_HAND) {
setCursor(Qt::OpenHandCursor);
} else {
unsetCursor();
}
if (scroll_drag) {
panel_effect_controls->horizontalScrollBar->setValue(panel_effect_controls->horizontalScrollBar->value() + rect_select_x - event->pos().x());
panel_effect_controls->verticalScrollBar->setValue(panel_effect_controls->verticalScrollBar->value() + rect_select_y - event->pos().y());
rect_select_x = event->pos().x();
rect_select_y = event->pos().y();
} else if (mousedown) {
int mouse_x = event->x() + x_scroll;
rect_select_w = event->x() - rect_select_x;
rect_select_h = event->y() - rect_select_y;
long current_frame = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x);
panel_effect_controls->scroll_to_frame(current_frame + visible_in);
int min_row = qMin(rect_select_y, event->y())-KEYFRAME_SIZE;
int max_row = qMax(rect_select_y, event->y())+KEYFRAME_SIZE;
if (select_rect) {
// do a rect select
selected_fields.resize(rect_select_offset);
selected_keyframes.resize(rect_select_offset);
long frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, rect_select_x+x_scroll);
long frame_end = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x);
long min_frame = qMin(frame_start, frame_end)-KEYFRAME_SIZE;
long max_frame = qMax(frame_start, frame_end)+KEYFRAME_SIZE;
rect_select_w = event->x() - rect_select_x;
rect_select_h = event->y() - rect_select_y;
for (int i=0;i<rowY.size();i++) {
if (rowY.at(i) >= min_row && rowY.at(i) <= max_row) {
EffectRow* row = rows.at(i);
for (int k=0;k<row->fieldCount();k++) {
EffectField* field = row->field(k);
for (int j=0;j<field->keyframes.size();j++) {
long keyframe_frame = adjust_row_keyframe(row, field->keyframes.at(j).time, visible_in);
if (!keyframeIsSelected(field, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) {
selected_fields.append(field);
selected_keyframes.append(j);
}
}
}
}
}
int min_row = qMin(rect_select_y, event->y())-KEYFRAME_SIZE;
int max_row = qMax(rect_select_y, event->y())+KEYFRAME_SIZE;
update_keys();
} else if (keys_selected) {
// move keyframes
long frame_diff = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x) - drag_frame_start;
long frame_start = getFrameFromScreenPoint(panel_effect_controls->zoom, rect_select_x+x_scroll);
long min_frame = qMin(frame_start, current_frame)-KEYFRAME_SIZE;
long max_frame = qMax(frame_start, current_frame)+KEYFRAME_SIZE;
// snapping to playhead
panel_timeline->snapped = false;
if (panel_timeline->snapping) {
for (int i=0;i<selected_keyframes.size();i++) {
EffectField* field = selected_fields.at(i);
ClipPtr c = field->parent_row->parent_effect->parent_clip;
long key_time = old_key_vals.at(i) + frame_diff - c->clip_in + c->timeline_in;
long key_eval = key_time;
if (panel_timeline->snap_to_point(olive::ActiveSequence->playhead, &key_eval)) {
frame_diff += (key_eval - key_time);
break;
}
}
}
for (int i=0;i<rowY.size();i++) {
if (rowY.at(i) >= min_row && rowY.at(i) <= max_row) {
EffectRow* row = rows.at(i);
for (int k=0;k<row->fieldCount();k++) {
EffectField* field = row->field(k);
for (int j=0;j<field->keyframes.size();j++) {
long keyframe_frame = adjust_row_keyframe(row, field->keyframes.at(j).time, visible_in);
if (!keyframeIsSelected(field, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) {
selected_fields.append(field);
selected_keyframes.append(j);
}
}
}
}
}
// validate frame_diff (make sure no keyframes overlap each other)
for (int i=0;i<selected_fields.size();i++) {
EffectField* field = selected_fields.at(i);
long eval_key = old_key_vals.at(i);
for (int j=0;j<field->keyframes.size();j++) {
while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) {
if (last_frame_diff > frame_diff) {
frame_diff++;
panel_timeline->snapped = false;
} else {
frame_diff--;
panel_timeline->snapped = false;
}
}
}
}
update_keys();
} else if (keys_selected) {
// move keyframes
long frame_diff = current_frame - drag_frame_start;
// apply frame_diffs
for (int i=0;i<selected_keyframes.size();i++) {
EffectField* field = selected_fields.at(i);
field->keyframes[selected_keyframes.at(i)].time = old_key_vals.at(i) + frame_diff;
}
// snapping to playhead
panel_timeline->snapped = false;
if (panel_timeline->snapping) {
for (int i=0;i<selected_keyframes.size();i++) {
EffectField* field = selected_fields.at(i);
ClipPtr c = field->parent_row->parent_effect->parent_clip;
long key_time = old_key_vals.at(i) + frame_diff - c->clip_in() + c->timeline_in();
long key_eval = key_time;
if (panel_timeline->snap_to_point(olive::ActiveSequence->playhead, &key_eval)) {
frame_diff += (key_eval - key_time);
break;
}
}
}
last_frame_diff = frame_diff;
// validate frame_diff (make sure no keyframes overlap each other)
for (int i=0;i<selected_fields.size();i++) {
EffectField* field = selected_fields.at(i);
long eval_key = old_key_vals.at(i);
for (int j=0;j<field->keyframes.size();j++) {
while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) {
if (last_frame_diff > frame_diff) {
frame_diff++;
panel_timeline->snapped = false;
} else {
frame_diff--;
panel_timeline->snapped = false;
}
}
}
}
dragging = true;
// apply frame_diffs
for (int i=0;i<selected_keyframes.size();i++) {
EffectField* field = selected_fields.at(i);
field->keyframes[selected_keyframes.at(i)].time = old_key_vals.at(i) + frame_diff;
}
update_ui(false);
}
}
last_frame_diff = frame_diff;
dragging = true;
update_ui(false);
}
}
}
void KeyframeView::mouseReleaseEvent(QMouseEvent*) {
if (dragging) {
ComboAction* ca = new ComboAction();
for (int i=0;i<selected_fields.size();i++) {
ca->append(new SetLong(
&selected_fields.at(i)->keyframes[selected_keyframes.at(i)].time,
old_key_vals.at(i),
selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time
));
}
olive::UndoStack.push(ca);
}
if (dragging) {
ComboAction* ca = new ComboAction();
for (int i=0;i<selected_fields.size();i++) {
ca->append(new SetLong(
&selected_fields.at(i)->keyframes[selected_keyframes.at(i)].time,
old_key_vals.at(i),
selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time
));
}
olive::UndoStack.push(ca);
}
select_rect = false;
dragging = false;
mousedown = false;
scroll_drag = false;
panel_timeline->snapped = false;
update_ui(false);
select_rect = false;
dragging = false;
mousedown = false;
scroll_drag = false;
panel_timeline->snapped = false;
update_ui(false);
}
+9
View File
@@ -22,8 +22,17 @@
#include <QEvent>
QVector<Panel*> olive::panels;
Panel::Panel(QWidget *parent) : QDockWidget (parent) {
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
olive::panels.append(this);
}
Panel::~Panel()
{
olive::panels.removeAll(this);
}
bool Panel::event(QEvent *e) {
+5
View File
@@ -27,9 +27,14 @@ class Panel : public QDockWidget {
Q_OBJECT
public:
Panel(QWidget* parent = nullptr);
virtual ~Panel() override;
virtual bool event(QEvent* e) override;
protected:
virtual void Retranslate() = 0;
};
namespace olive {
extern QVector<Panel*> panels;
}
#endif // PANEL_H
-676
View File
@@ -1,676 +0,0 @@
/***
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 "renderfunctions.h"
extern "C" {
#include <libavformat/avformat.h>
}
#include <QOpenGLFramebufferObject>
#include <QApplication>
#include <QDesktopWidget>
#include <QDebug>
#ifdef OLIVE_OCIO
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE;
#endif
#include "project/clip.h"
#include "project/sequence.h"
#include "project/media.h"
#include "project/effect.h"
#include "project/footage.h"
#include "project/transition.h"
#include "ui/collapsiblewidget.h"
#include "playback/audio.h"
#include "playback/playback.h"
#include "io/math.h"
#include "io/config.h"
#include "panels/timeline.h"
#include "panels/viewer.h"
void full_blit() {
glPushMatrix();
glLoadIdentity();
glOrtho(0, 1, 0, 1, -1, 1);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); // top left
glVertex2f(0, 0); // top left
glTexCoord2f(1, 0); // top right
glVertex2f(1, 0); // top right
glTexCoord2f(1, 1); // bottom right
glVertex2f(1, 1); // bottom right
glTexCoord2f(0, 1); // bottom left
glVertex2f(0, 1); // bottom left
glEnd();
glPopMatrix();
}
void draw_clip(QOpenGLContext* ctx, GLuint fbo, GLuint texture, bool clear) {
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
if (clear) {
glClear(GL_COLOR_BUFFER_BIT);
}
glBindTexture(GL_TEXTURE_2D, texture);
full_blit();
glBindTexture(GL_TEXTURE_2D, 0);
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
GLuint draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) {
fbo->bind();
if (clear) {
glClear(GL_COLOR_BUFFER_BIT);
}
glBindTexture(GL_TEXTURE_2D, texture);
full_blit();
glBindTexture(GL_TEXTURE_2D, 0);
fbo->release();
return fbo->texture();
}
void process_effect(ClipPtr c,
EffectPtr e,
double timecode,
GLTextureCoords& coords,
GLuint& composite_texture,
bool& fbo_switcher,
bool& texture_failed,
int data) {
if (e->is_enabled()) {
if (e->enable_coords) {
e->process_coords(timecode, coords, data);
}
bool can_process_shaders = (e->enable_shader && olive::CurrentRuntimeConfig.shaders_are_enabled);
if (can_process_shaders || e->enable_superimpose) {
e->startEffect();
if (can_process_shaders && e->is_glsl_linked()) {
for (int i=0;i<e->getIterations();i++) {
e->process_shader(timecode, coords, i);
composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true);
fbo_switcher = !fbo_switcher;
}
}
if (e->enable_superimpose) {
GLuint superimpose_texture = e->process_superimpose(timecode);
if (superimpose_texture == 0) {
qWarning() << "Superimpose texture was nullptr, retrying...";
texture_failed = true;
} else if (composite_texture == 0) {
// if there is no previous texture, just return the superimposes texture
// UNLESS this is a shader-extended superimpose effect in which case,
// we'll need to draw it below
composite_texture = superimpose_texture;
} else {
// if the source texture is not already a framebuffer texture,
// we'll need to make it one before drawing a superimpose effect on it
if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) {
draw_clip(c->fbo[!fbo_switcher], composite_texture, true);
}
composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false);
}
}
e->endEffect();
}
}
}
GLuint compose_sequence(ComposeSequenceParams &params) {
GLuint final_fbo = params.main_buffer;
SequencePtr s = params.seq;
long playhead = s->playhead;
if (!params.nests.isEmpty()) {
for (int i=0;i<params.nests.size();i++) {
s = params.nests.at(i)->media->to_sequence();
playhead += params.nests.at(i)->clip_in - params.nests.at(i)->get_timeline_in_with_transition();
playhead = refactor_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate);
}
if (params.video && params.nests.last()->fbo != nullptr) {
params.nests.last()->fbo[0]->bind();
glClear(GL_COLOR_BUFFER_BIT);
final_fbo = params.nests.last()->fbo[0]->handle();
}
}
int audio_track_count = 0;
QVector<ClipPtr> current_clips;
// loop through clips, find currently active, and sort by track
for (int i=0;i<s->clips.size();i++) {
ClipPtr c = s->clips.at(i);
if (c != nullptr) {
// if clip is video and we're processing video
if ((c->track < 0) == params.video) {
bool clip_is_active = false;
// is the clip a "footage" clip?
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
FootagePtr m = c->media->to_footage();
// does the clip have a valid media source?
if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) {
// is the media process and ready?
if (m->ready) {
const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream);
// does the media have a valid media stream source and is it active?
if (ms != nullptr && is_clip_active(c, playhead)) {
// open if not open
if (!c->open) {
open_clip(c, !params.single_threaded);
}
clip_is_active = true;
// increment audio track count
if (c->track >= 0) audio_track_count++;
} else if (c->finished_opening) {
// close the clip if it isn't active anymore
close_clip(c, false);
}
} else {
// media wasn't ready, schedule a redraw
params.texture_failed = true;
}
}
} else {
// if the clip is a nested sequence or null clip, just open it
if (is_clip_active(c, playhead)) {
if (!c->open) open_clip(c, !params.single_threaded);
clip_is_active = true;
} else if (c->finished_opening) {
close_clip(c, false);
}
}
// if the clip is active, added it to "current_clips", sorted by track
if (clip_is_active) {
bool added = false;
// track sorting is only necessary for video clips
// audio clips are mixed equally, so we skip sorting for those
if (params.video) {
// insertion sort by track
for (int j=0;j<current_clips.size();j++) {
if (current_clips.at(j)->track < c->track) {
current_clips.insert(j, c);
added = true;
break;
}
}
}
if (!added) {
current_clips.append(c);
}
}
}
}
}
if (params.video) {
// set default coordinates based on the sequence, with 0 in the direct center
glPushMatrix();
glLoadIdentity();
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
int half_width = s->width/2;
int half_height = s->height/2;
glOrtho(-half_width, half_width, -half_height, half_height, -1, 10);
}
// loop through current clips
for (int i=0;i<current_clips.size();i++) {
ClipPtr c = current_clips.at(i);
// check if this clip was successfully opened by earlier function
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) {
qWarning() << "Tried to display clip" << i << "but it's closed";
params.texture_failed = true;
} else {
c->render_lock.lock();
// if clip is a video clip
if (c->track < 0) {
// reset OpenGL to full color
glColor4f(1.0, 1.0, 1.0, 1.0);
// textureID variable contains texture to be drawn on screen at the end
GLuint textureID = 0;
// store video source dimensions
int video_width = c->getWidth();
int video_height = c->getHeight();
// if media is footage
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
if (c->texture == nullptr) {
// opengl texture doesn't exist yet, create it
c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height);
c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
c->texture->setMipLevels(c->texture->maximumMipLevels());
c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8);
}
// retrieve video frame from cache and store it in c->texture
get_clip_frame(c, qMax(playhead, c->timeline_in), params.texture_failed);
// retrieve ID from c->texture
textureID = c->texture->textureId();
if (textureID == 0) {
qWarning() << "Failed to create texture";
return 0;
}
}
// prepare framebuffers for backend drawing operations
if (c->fbo == nullptr) {
// create 3 fbos for nested sequences, 2 for most clips
int fbo_count = (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
c->fbo = new QOpenGLFramebufferObject* [size_t(fbo_count)];
for (int j=0;j<fbo_count;j++) {
c->fbo[j] = new QOpenGLFramebufferObject(video_width, video_height);
}
}
// if clip should actually be shown on screen in this frame
if (playhead >= c->get_timeline_in_with_transition()
&& playhead < c->get_timeline_out_with_transition()) {
glPushMatrix();
// simple bool for switching between the two framebuffers
bool fbo_switcher = false;
glViewport(0, 0, video_width, video_height);
if (c->media != nullptr) {
if (c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
// for a nested sequence, run this function again on that sequence and retrieve the texture
// add nested sequence to nest list
params.nests.append(c);
// compose sequence
textureID = compose_sequence(params);
// remove sequence from nest list
params.nests.removeLast();
// compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1]
fbo_switcher = true;
} else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
if (!c->media->to_footage()->alpha_is_premultiplied) {
// alpha is not premultiplied, we'll need to multiply it for the rest of the pipeline
params.premultiply_program->bind();
textureID = draw_clip(c->fbo[0], textureID, true);
params.premultiply_program->release();
fbo_switcher = true;
}
// convert to linear colorspace
#ifdef OLIVE_OCIO
bool linear_convert = true;
if (linear_convert)
{
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
const char* g_display = config->getDefaultDisplay();
const char* g_transformName = config->getDefaultView(g_display);
const char* g_inputColorSpace = OCIO::ROLE_SCENE_LINEAR;
std::string cs = config->parseColorSpaceFromString("srgb");
if (!cs.empty()) {
g_inputColorSpace = cs.c_str();
}
}
#endif
}
}
// set up default coordinates for drawing the clip
GLTextureCoords coords;
coords.grid_size = 1;
coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2;
coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2;
coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2;
coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2;
coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1;
coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0;
coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0;
coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1;
coords.blendmode = BLEND_MODE_NORMAL;
coords.opacity = 1.0;
// if auto-scale is enabled, auto-scale the clip
if (c->autoscale && (video_width != s->width && video_height != s->height)) {
float width_multiplier = float(s->width) / float(video_width);
float height_multiplier = float(s->height) / float(video_height);
float scale_multiplier = qMin(width_multiplier, height_multiplier);
glScalef(scale_multiplier, scale_multiplier, 1);
}
// == EFFECT CODE START ==
// get current sequence time in seconds (used for effects)
double timecode = get_timecode(c, playhead);
// set up variables for gizmos later
EffectPtr first_gizmo_effect = nullptr;
EffectPtr selected_effect = nullptr;
// run through all of the clip's effects
for (int j=0;j<c->effects.size();j++) {
EffectPtr e = c->effects.at(j);
process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
// retrieve gizmo data from effect
if (e->are_gizmos_enabled()) {
if (first_gizmo_effect == nullptr) first_gizmo_effect = e;
if (e->container->selected) selected_effect = e;
}
}
// using gizmo data, set definitive gizmo
if (selected_effect != nullptr) {
(*params.gizmos) = selected_effect;
} else if (is_clip_selected(c, true)) {
(*params.gizmos) = first_gizmo_effect;
}
// if the clip has an opening transition, process that now
if (c->opening_transition != nullptr) {
int transition_progress = playhead - c->get_timeline_in_with_transition();
if (transition_progress < c->opening_transition->get_length()) {
process_effect(c, c->opening_transition, double(transition_progress)/double(c->opening_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening);
}
}
// if the clip has a closing transition, process that now
if (c->closing_transition != nullptr) {
int transition_progress = playhead - (c->get_timeline_out_with_transition() - c->closing_transition->get_length());
if (transition_progress >= 0 && transition_progress < c->closing_transition->get_length()) {
process_effect(c, c->closing_transition, double(transition_progress)/double(c->closing_transition->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing);
}
}
// == EFFECT CODE END ==
if (textureID > 0) {
// set viewport to sequence size
params.ctx->functions()->glViewport(0, 0, s->width, s->height);
// == START RENDER CLIP IN CONTEXT OF SEQUENCE ==
// use clip textures for nested sequences, otherwise use main frame buffers
GLuint back_buffer_1;
GLuint backend_tex_1;
GLuint backend_tex_2;
if (params.nests.size() > 0) {
back_buffer_1 = params.nests.last()->fbo[1]->handle();
backend_tex_1 = params.nests.last()->fbo[1]->texture();
backend_tex_2 = params.nests.last()->fbo[2]->texture();
} else {
back_buffer_1 = params.backend_buffer1;
backend_tex_1 = params.backend_attachment1;
backend_tex_2 = params.backend_attachment2;
}
// render a backbuffer
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, back_buffer_1);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
// bind final clip texture
glBindTexture(GL_TEXTURE_2D, textureID);
// set texture filter to bilinear
params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// draw clip on screen according to gl coordinates
glBegin(GL_QUADS);
glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left
glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left
glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right
glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right
glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right
glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right
glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left
glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left
glEnd();
// release final clip texture
glBindTexture(GL_TEXTURE_2D, 0);
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// == END RENDER CLIP IN CONTEXT OF SEQUENCE ==
//
//
// PROCESS POST-SHADERS
//
//
// copy front buffer to back buffer (only if we're using blending modes - which we usually will be)
if (!olive::CurrentRuntimeConfig.disable_blending) {
if (params.nests.size() > 0) {
draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true);
} else {
draw_clip(params.ctx, params.backend_buffer2, params.main_attachment, true);
}
}
// == START FINAL DRAW ON SEQUENCE BUFFER ==
// bind front buffer as draw buffer
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo);
if (olive::CurrentRuntimeConfig.disable_blending) {
// some GPUs don't like the blending shader, so we provide a pure GL fallback here
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
glColor4f(coords.opacity, coords.opacity, coords.opacity, coords.opacity);
full_blit();
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
} else {
// load background texture into texture unit 0
params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_2);
// load foreground texture into texture unit 1
params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
// bind and configure blending mode shader
params.blend_mode_program->bind();
params.blend_mode_program->setUniformValue("blendmode", coords.blendmode);
params.blend_mode_program->setUniformValue("opacity", coords.opacity);
params.blend_mode_program->setUniformValue("background", 0);
params.blend_mode_program->setUniformValue("foreground", 1);
glClear(GL_COLOR_BUFFER_BIT);
full_blit();
// release blend mode shader
params.blend_mode_program->release();
// unbind texture from texture unit 1
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
// unbind texture from texture unit 0
params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
}
// unbind framebuffer
params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// == END FINAL DRAW ON SEQUENCE BUFFER ==
}
// prepare gizmos
if ((*params.gizmos) != nullptr
&& params.nests.isEmpty()
&& ((*params.gizmos) == first_gizmo_effect
|| (*params.gizmos) == selected_effect)) {
(*params.gizmos)->gizmo_draw(timecode, coords); // set correct gizmo coords
(*params.gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords
}
glPopMatrix();
}
} else {
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
params.nests.append(c);
compose_sequence(params);
params.nests.removeLast();
} else {
if (c->lock.tryLock()) {
// Check whether cacher is currently active, if not activate it now
cache_clip(c, playhead, c->audio_reset, (params.viewer != nullptr && !params.viewer->playing), params.nests, params.playback_speed);
c->lock.unlock();
}
}
// visually update all the keyframe values
if (c->sequence == params.seq) { // only if you can currently see them
double ts = (playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition())/s->frame_rate;
for (int i=0;i<c->effects.size();i++) {
EffectPtr e = c->effects.at(i);
for (int j=0;j<e->row_count();j++) {
EffectRow* r = e->row(j);
for (int k=0;k<r->fieldCount();k++) {
r->field(k)->validate_keyframe_data(ts);
}
}
}
}
}
c->render_lock.unlock();
}
}
if (audio_track_count == 0 && params.viewer != nullptr) {
params.viewer->play_wake();
}
if (params.video) {
glPopMatrix();
}
if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) {
// returns nested clip's texture
return params.nests.last()->fbo[0]->texture();
}
return 0;
}
void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed) {
ComposeSequenceParams params;
params.viewer = viewer;
params.ctx = nullptr;
params.seq = seq;
params.video = false;
params.gizmos = nullptr;
params.single_threaded = audio_rendering;
params.playback_speed = playback_speed;
params.blend_mode_program = nullptr;
compose_sequence(params);
}
-265
View File
@@ -1,265 +0,0 @@
/***
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 "project/sequence.h"
#include "project/effect.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 sequence to compose
*
* In addition to clips, sequences also contain the playhead position so compose_sequence() knows which frame
* to render.
*/
SequencePtr 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<ClipPtr> nests;
/**
* @brief Set compose mode to video or audio
*
* **TRUE** if this function should render video, **FALSE** if this function should render audio.
*/
bool video;
/**
* @brief Set to the Effect whose gizmos were chosen to be drawn on screen
*
* A pointer to a pointer that will be set to the Effect whose gizmos are being rendered and should therefore
* be interacted with if the user uses them.
*/
EffectPtr* 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 single_threaded;
/**
* @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 Blending mode shader
*
* Used only for video rendering. Never accessed with audio rendering.
*
* A program containing the current active
* blending mode shader that can be bound during rendering. Must be compiled and linked beforehand. See
* RenderThread::blend_mode_program for how this is properly set up.
*
* \see ComposeSequenceParams::video
*/
QOpenGLShaderProgram* blend_mode_program;
/**
* @brief Premultiply alpha shader
*
* 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.
*/
GLuint main_buffer;
/**
* @brief The attachment to the framebuffer in main_buffer
*
* Used only for video rendering. Never accessed with audio rendering.
*
* The OpenGL texture attached to the framebuffer referenced by main_buffer.
*/
GLuint main_attachment;
/**
* @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.
*/
GLuint backend_buffer1;
/**
* @brief Backend OpenGL framebuffer 1's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment1;
/**
* @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.
*/
GLuint backend_buffer2;
/**
* @brief Backend OpenGL framebuffer 2's texture attachment
*
* The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to
* ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders.
*/
GLuint backend_attachment2;
/**
* @brief OpenGL shader containing OpenColorIO shader information
*/
QOpenGLShaderProgram* ocio_shader;
/**
* @brief OpenGL texture containing LUT obtained form OpenColorIO
*/
GLuint ocio_lut_texture;
};
/**
* @brief Compose a frame of a given sequence
*
* For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will
* automatically open and close clips (memory allocation and file handles) as necessary, communicate with the
* Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and
* finally composite all the currently active clips together into a final texture.
*
* Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time
* of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so,
* the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and
* if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence
* is being played, in which case just play the next frame rather than redrawing an old frame).
*
* @param params
*
* A struct of parameters to use while rendering.
*
* @return A reference to the OpenGL texture resulting from the render. Will usually be equal to
* ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference
* to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame.
*/
GLuint compose_sequence(ComposeSequenceParams &params);
/**
* @brief Convenience wrapper function for compose_sequence() to render audio
*
* Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for
* video rendering. For audio rendering, this function is easier to handle and will correctly set up
* compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object.
*
* @param viewer
*
* The Viewer object calling this function
*
* @param seq
*
* The Sequence whose audio to render.
*
* @param playback_speed
*
* The current playback speed (controlled by Shuttle Left/Right)
*/
void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed);
#endif // RENDERFUNCTIONS_H
-313
View File
@@ -1,313 +0,0 @@
/***
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 <QOpenGLFunctions>
#include <QDebug>
#include "ui/renderfunctions.h"
#include "playback/playback.h"
#include "project/sequence.h"
RenderThread::RenderThread() :
front_buffer(0),
front_texture(0),
gizmos(nullptr),
share_ctx(nullptr),
ctx(nullptr),
blend_mode_program(nullptr),
premultiply_program(nullptr),
seq(nullptr),
tex_width(-1),
tex_height(-1),
queued(false),
texture_failed(false),
running(true)
{
surface.create();
}
RenderThread::~RenderThread() {
surface.destroy();
}
void RenderThread::run() {
mutex.lock();
while (running) {
if (!queued) {
waitCond.wait(&mutex);
}
if (!running) {
break;
}
queued = false;
if (share_ctx != nullptr) {
if (ctx != nullptr) {
ctx->makeCurrent(&surface);
// gen fbo
if (front_buffer == 0) {
// delete any existing framebuffers
delete_fbo();
// create framebuffers
ctx->functions()->glGenFramebuffers(1, &front_buffer);
ctx->functions()->glGenFramebuffers(1, &back_buffer_1);
ctx->functions()->glGenFramebuffers(1, &back_buffer_2);
}
// gen texture
if (front_texture == 0 || tex_width != seq->width || tex_height != seq->height) {
// cache texture size
tex_width = seq->width;
tex_height = seq->height;
// delete any existing textures
delete_texture();
// create texture
glGenTextures(1, &front_texture);
glGenTextures(1, &back_texture_1);
glGenTextures(1, &back_texture_2);
GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2};
GLuint textures[3] = {front_texture, back_texture_1, back_texture_2};
for (int i=0;i<3;i++) {
// bind framebuffer for attaching
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[i]);
// bind texture
glBindTexture(GL_TEXTURE_2D, textures[i]);
// allocate storage for texture
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, seq->width, seq->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr
);
// set texture filtering to bilinear
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// attach texture to framebuffer
ctx->functions()->glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0
);
// release texture
glBindTexture(GL_TEXTURE_2D, 0);
// release framebuffer
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
}
if (blend_mode_program == nullptr) {
// create shader program to make blending modes work
delete_shader_program();
blend_mode_program = new QOpenGLShaderProgram();
blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert");
blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/blending.frag");
blend_mode_program->link();
premultiply_program = new QOpenGLShaderProgram();
premultiply_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert");
premultiply_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/premultiply.frag");
premultiply_program->link();
}
// bind framebuffer for drawing
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, front_buffer);
// draw frame
paint();
// flush changes
ctx->functions()->glFinish();
// release
ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
emit ready();
}
}
}
delete_ctx();
mutex.unlock();
}
void RenderThread::paint() {
glLoadIdentity();
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glEnable(GL_DEPTH);
gizmos = nullptr;
ComposeSequenceParams params;
params.viewer = nullptr;
params.ctx = ctx;
params.seq = seq;
params.video = true;
params.texture_failed = false;
params.gizmos = &gizmos;
params.single_threaded = false;
params.playback_speed = 1;
params.blend_mode_program = blend_mode_program;
params.premultiply_program = premultiply_program;
params.backend_buffer1 = back_buffer_1;
params.backend_buffer2 = back_buffer_2;
params.backend_attachment1 = back_texture_1;
params.backend_attachment2 = back_texture_2;
params.main_buffer = front_buffer;
params.main_attachment = front_texture;
compose_sequence(params);
texture_failed = params.texture_failed;
if (!save_fn.isEmpty()) {
if (texture_failed) {
// texture failed, try again
queued = true;
} else {
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer);
QImage img(tex_width, tex_height, QImage::Format_RGBA8888);
glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
img.save(save_fn);
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
save_fn = "";
}
}
if (pixel_buffer != nullptr) {
// set main framebuffer to the current read buffer
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer);
// store pixels in buffer
glReadPixels(0,
0,
pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize,
tex_height,
GL_RGBA,
GL_UNSIGNED_BYTE,
pixel_buffer);
// release current read buffer
ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
pixel_buffer = nullptr;
}
glDisable(GL_DEPTH);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
}
void RenderThread::start_render(QOpenGLContext *share, SequencePtr s, const QString& save, GLvoid* pixels, int pixel_linesize, int idivider) {
Q_UNUSED(idivider);
seq = s;
// 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;
waitCond.wakeAll();
}
bool RenderThread::did_texture_fail() {
return texture_failed;
}
void RenderThread::cancel() {
running = false;
waitCond.wakeAll();
wait();
}
void RenderThread::delete_texture() {
if (front_texture > 0) {
GLuint tex[3] = {front_texture, back_texture_1, back_texture_2};
glDeleteTextures(3, tex);
}
front_texture = 0;
back_texture_1 = 0;
back_texture_2 = 0;
}
void RenderThread::delete_fbo() {
if (front_buffer > 0) {
GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2};
ctx->functions()->glDeleteFramebuffers(3, fbos);
}
front_buffer = 0;
back_buffer_1 = 0;
back_buffer_2 = 0;
}
void RenderThread::delete_shader_program() {
if (blend_mode_program != nullptr) {
delete blend_mode_program;
delete premultiply_program;
}
blend_mode_program = nullptr;
premultiply_program = nullptr;
}
void RenderThread::delete_ctx() {
if (ctx != nullptr) {
delete_shader_program();
delete_texture();
delete_fbo();
ctx->doneCurrent();
delete ctx;
}
ctx = nullptr;
}
-93
View File
@@ -1,93 +0,0 @@
/***
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 "project/sequence.h"
#include "project/effect.h"
// copied from source code to OCIODisplay
const int LUT3D_EDGE_SIZE = 32;
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
const int NUM_3D_ENTRIES = 98304;
class RenderThread : public QThread {
Q_OBJECT
public:
RenderThread();
~RenderThread();
void run();
QMutex mutex;
GLuint front_buffer;
GLuint front_texture;
EffectPtr gizmos;
void paint();
void start_render(QOpenGLContext* share, SequencePtr s, const QString &save = nullptr, GLvoid *pixels = nullptr, int pixel_linesize = 0, int idivider = 0);
bool did_texture_fail();
void cancel();
public slots:
// cleanup functions
void delete_ctx();
signals:
void ready();
private:
// cleanup functions
void delete_texture();
void delete_fbo();
void delete_shader_program();
QWaitCondition waitCond;
QOffscreenSurface surface;
QOpenGLContext* share_ctx;
QOpenGLContext* ctx;
QOpenGLShaderProgram* blend_mode_program;
QOpenGLShaderProgram* premultiply_program;
GLuint back_buffer_1;
GLuint back_buffer_2;
GLuint back_texture_1;
GLuint back_texture_2;
float ocio_lut_data[NUM_3D_ENTRIES];
SequencePtr seq;
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
+1 -1
View File
@@ -25,7 +25,7 @@
#include "panels/timeline.h"
#include "panels/viewer.h"
#include "panels/panels.h"
#include "playback/playback.h"
#include "rendering/renderfunctions.h"
#include "project/undo.h"
#include "project/sequence.h"
#include "mainwindow.h"
+210 -202
View File
File diff suppressed because it is too large Load Diff
+430 -436
View File
File diff suppressed because it is too large Load Diff
+50 -50
View File
@@ -35,7 +35,7 @@
#include "project/effect.h"
#include "ui/viewerwindow.h"
#include "ui/viewercontainer.h"
#include "ui/renderthread.h"
#include "rendering/renderthread.h"
class Viewer;
class QOpenGLFramebufferObject;
@@ -43,64 +43,64 @@ struct GLTextureCoords;
class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions
{
Q_OBJECT
Q_OBJECT
public:
ViewerWidget(QWidget *parent = nullptr);
~ViewerWidget();
ViewerWidget(QWidget *parent = nullptr);
~ViewerWidget();
void delete_function();
void close_window();
void delete_function();
void close_window();
void paintGL();
void initializeGL();
Viewer* viewer;
ViewerContainer* container;
void paintGL();
void initializeGL();
Viewer* viewer;
ViewerContainer* container;
bool waveform;
ClipPtr waveform_clip;
const FootageStream* waveform_ms;
double waveform_zoom;
int waveform_scroll;
bool waveform;
ClipPtr waveform_clip;
const FootageStream* waveform_ms;
double waveform_zoom;
int waveform_scroll;
void frame_update();
RenderThread* get_renderer();
void set_scroll(double x, double y);
void frame_update();
RenderThread* get_renderer();
void set_scroll(double x, double y);
public slots:
void set_waveform_scroll(int s);
void set_fullscreen(int screen = 0);
void set_waveform_scroll(int s);
void set_fullscreen(int screen = 0);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent* event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent* event);
private:
void draw_waveform_func();
void draw_title_safe_area();
void draw_gizmos();
EffectGizmo* get_gizmo_from_mouse(int x, int y);
void move_gizmos(QMouseEvent *event, bool done);
bool dragging;
void seek_from_click(int x);
EffectPtr gizmos;
int drag_start_x;
int drag_start_y;
int gizmo_x_mvmt;
int gizmo_y_mvmt;
EffectGizmo* selected_gizmo;
RenderThread* renderer;
ViewerWindow* window;
double x_scroll;
double y_scroll;
void draw_waveform_func();
void draw_title_safe_area();
void draw_gizmos();
EffectGizmo* get_gizmo_from_mouse(int x, int y);
void move_gizmos(QMouseEvent *event, bool done);
bool dragging;
void seek_from_click(int x);
EffectPtr gizmos;
int drag_start_x;
int drag_start_y;
int gizmo_x_mvmt;
int gizmo_y_mvmt;
EffectGizmo* selected_gizmo;
RenderThread* renderer;
ViewerWindow* window;
double x_scroll;
double y_scroll;
private slots:
void context_destroy();
void retry();
void show_context_menu();
void save_frame();
void queue_repaint();
void fullscreen_menu_action(QAction* action);
void set_fit_zoom();
void set_custom_zoom();
void set_menu_zoom(QAction *action);
void context_destroy();
void retry();
void show_context_menu();
void save_frame();
void queue_repaint();
void fullscreen_menu_action(QAction* action);
void set_fit_zoom();
void set_custom_zoom();
void set_menu_zoom(QAction *action);
};
#endif // VIEWERWIDGET_H
+97 -97
View File
@@ -34,130 +34,130 @@
#include "mainwindow.h"
ViewerWindow::ViewerWindow(QWidget *parent) :
QOpenGLWidget(parent, Qt::Window),
texture(0),
mutex(nullptr),
show_fullscreen_msg(false)
QOpenGLWidget(parent, Qt::Window),
texture(0),
mutex(nullptr),
show_fullscreen_msg(false)
{
setMouseTracking(true);
setMouseTracking(true);
fullscreen_msg_timer.setInterval(2000);
connect(&fullscreen_msg_timer, SIGNAL(timeout()), this, SLOT(fullscreen_msg_timeout()));
fullscreen_msg_timer.setInterval(2000);
connect(&fullscreen_msg_timer, SIGNAL(timeout()), this, SLOT(fullscreen_msg_timeout()));
}
void ViewerWindow::set_texture(GLuint t, double iar, QMutex* imutex) {
texture = t;
ar = iar;
mutex = imutex;
update();
texture = t;
ar = iar;
mutex = imutex;
update();
}
void ViewerWindow::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
hide();
}
if (e->key() == Qt::Key_Escape) {
hide();
}
}
void ViewerWindow::mousePressEvent(QMouseEvent *e) {
if (show_fullscreen_msg && fullscreen_msg_rect.contains(e->pos())) {
hide();
}
if (show_fullscreen_msg && fullscreen_msg_rect.contains(e->pos())) {
hide();
}
}
void ViewerWindow::mouseMoveEvent(QMouseEvent *) {
fullscreen_msg_timer.start();
if (!show_fullscreen_msg) {
show_fullscreen_msg = true;
update();
}
fullscreen_msg_timer.start();
if (!show_fullscreen_msg) {
show_fullscreen_msg = true;
update();
}
}
void ViewerWindow::paintGL() {
if (texture > 0) {
if (mutex != nullptr) mutex->lock();
if (texture > 0) {
if (mutex != nullptr) mutex->lock();
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture);
glBindTexture(GL_TEXTURE_2D, texture);
glLoadIdentity();
glOrtho(0, 1, 0, 1, -1, 1);
glLoadIdentity();
glOrtho(0, 1, 0, 1, -1, 1);
glBegin(GL_QUADS);
glBegin(GL_QUADS);
double top = 0;
double left = 0;
double right = 1;
double bottom = 1;
double top = 0;
double left = 0;
double right = 1;
double bottom = 1;
double widget_ar = double(width()) / double(height());
double widget_ar = double(width()) / double(height());
if (widget_ar > ar) {
double width = 1.0 * ar / widget_ar;
left = (1.0 - width)*0.5;
right = left + width;
} else {
double height = 1.0 / ar * widget_ar;
top = (1.0 - height)*0.5;
bottom = top + height;
}
glVertex2d(left, top);
glTexCoord2d(0, 0);
glVertex2d(left, bottom);
glTexCoord2d(1, 0);
glVertex2d(right, bottom);
glTexCoord2d(1, 1);
glVertex2d(right, top);
glTexCoord2d(0, 1);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
if (mutex != nullptr) mutex->unlock();
}
if (show_fullscreen_msg) {
QPainter p(this);
QFont f = p.font();
f.setPointSize(24);
p.setFont(f);
QFontMetrics fm(f);
QString fs_str = tr("Exit Fullscreen");
p.setPen(Qt::white);
p.setBrush(QColor(0, 0, 0, 128));
int text_width = fm.width(fs_str);
int text_x = (width()/2)-(text_width/2);
int text_y = fm.height()+fm.ascent();
int rect_padding = 8;
fullscreen_msg_rect = QRect(text_x-rect_padding,
fm.height()-rect_padding,
text_width+rect_padding+rect_padding,
fm.height()+rect_padding+rect_padding);
p.drawRect(fullscreen_msg_rect);
p.drawText(text_x, text_y, fs_str);
if (widget_ar > ar) {
double width = 1.0 * ar / widget_ar;
left = (1.0 - width)*0.5;
right = left + width;
} else {
double height = 1.0 / ar * widget_ar;
top = (1.0 - height)*0.5;
bottom = top + height;
}
glVertex2d(left, top);
glTexCoord2d(0, 0);
glVertex2d(left, bottom);
glTexCoord2d(1, 0);
glVertex2d(right, bottom);
glTexCoord2d(1, 1);
glVertex2d(right, top);
glTexCoord2d(0, 1);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
if (mutex != nullptr) mutex->unlock();
}
if (show_fullscreen_msg) {
QPainter p(this);
QFont f = p.font();
f.setPointSize(24);
p.setFont(f);
QFontMetrics fm(f);
QString fs_str = tr("Exit Fullscreen");
p.setPen(Qt::white);
p.setBrush(QColor(0, 0, 0, 128));
int text_width = fm.width(fs_str);
int text_x = (width()/2)-(text_width/2);
int text_y = fm.height()+fm.ascent();
int rect_padding = 8;
fullscreen_msg_rect = QRect(text_x-rect_padding,
fm.height()-rect_padding,
text_width+rect_padding+rect_padding,
fm.height()+rect_padding+rect_padding);
p.drawRect(fullscreen_msg_rect);
p.drawText(text_x, text_y, fs_str);
}
}
void ViewerWindow::fullscreen_msg_timeout() {
fullscreen_msg_timer.stop();
if (show_fullscreen_msg) {
show_fullscreen_msg = false;
update();
}
fullscreen_msg_timer.stop();
if (show_fullscreen_msg) {
show_fullscreen_msg = false;
update();
}
}