diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 188187f3f..bacc74cea 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "project/footage.h" @@ -19,7 +20,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : QDialog(parent), item(i) { - setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); + setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QGridLayout* grid = new QGridLayout(); @@ -29,21 +30,21 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : Footage* f = item->to_footage(); - grid->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); + grid->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); row++; track_list = new QListWidget(); for (int i=0;ivideo_tracks.size();i++) { const FootageStream& fs = f->video_tracks.at(i); - QListWidgetItem* item = new QListWidgetItem( - tr("Video %1: %2x%3 %4FPS").arg( - QString::number(fs.file_index), - QString::number(fs.video_width), - QString::number(fs.video_height), - QString::number(fs.video_frame_rate) - ) - ); + QListWidgetItem* item = new QListWidgetItem( + tr("Video %1: %2x%3 %4FPS").arg( + QString::number(fs.file_index), + QString::number(fs.video_width), + QString::number(fs.video_height), + QString::number(fs.video_frame_rate) + ) + ); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole+1, fs.file_index); @@ -51,13 +52,13 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : } for (int i=0;iaudio_tracks.size();i++) { const FootageStream& fs = f->audio_tracks.at(i); - QListWidgetItem* item = new QListWidgetItem( - tr("Audio %1: %2Hz %3 channels").arg( - QString::number(fs.file_index), - QString::number(fs.audio_frequency), - QString::number(fs.audio_channels) - ) - ); + QListWidgetItem* item = new QListWidgetItem( + tr("Audio %1: %2Hz %3 channels").arg( + QString::number(fs.file_index), + QString::number(fs.audio_frequency), + QString::number(fs.audio_channels) + ) + ); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole+1, fs.file_index); @@ -69,7 +70,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : if (f->video_tracks.size() > 0) { // frame conforming if (!f->video_tracks.at(0).infinite_length) { - grid->addWidget(new QLabel(tr("Conform to Frame Rate:")), row, 0); + grid->addWidget(new QLabel(tr("Conform to Frame Rate:")), row, 0); conform_fr = new QDoubleSpinBox(); conform_fr->setMinimum(0.01); conform_fr->setValue(f->video_tracks.at(0).video_frame_rate * f->speed); @@ -78,30 +79,37 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : row++; + // premultiplied alpha mode + premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied")); + premultiply_alpha_setting->setChecked(f->alpha_is_premultiplied); + grid->addWidget(premultiply_alpha_setting, row, 0); + + row++; + // deinterlacing mode - interlacing_box = new QComboBox(); - interlacing_box->addItem( - tr("Auto (%1)").arg( - get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) - ) - ); + interlacing_box = new QComboBox(); + interlacing_box->addItem( + tr("Auto (%1)").arg( + get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) + ) + ); interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); - interlacing_box->setCurrentIndex( - (f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) - ? 0 - : f->video_tracks.at(0).video_interlacing + 1); + interlacing_box->setCurrentIndex( + (f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) + ? 0 + : f->video_tracks.at(0).video_interlacing + 1); - grid->addWidget(new QLabel(tr("Interlacing:")), row, 0); + grid->addWidget(new QLabel(tr("Interlacing:")), row, 0); grid->addWidget(interlacing_box, row, 1); row++; } name_box = new QLineEdit(item->get_name()); - grid->addWidget(new QLabel(tr("Name:")), row, 0); + grid->addWidget(new QLabel(tr("Name:")), row, 0); grid->addWidget(name_box, row, 1); row++; @@ -160,6 +168,9 @@ void MediaPropertiesDialog::accept() { refresh_clips = true; } } + + // set premultiplied alpha + f->alpha_is_premultiplied = premultiply_alpha_setting->isChecked(); } // set name @@ -168,7 +179,10 @@ void MediaPropertiesDialog::accept() { ca->append(mr); ca->appendPost(new CloseAllClipsCommand()); ca->appendPost(new UpdateFootageTooltip(item)); - if (refresh_clips) ca->appendPost(new RefreshClips(item)); + if (refresh_clips) { + ca->appendPost(new RefreshClips(item)); + } + ca->appendPost(new UpdateViewer()); undo_stack.push(ca); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index b5699dff7..fe58fa9e2 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -9,6 +9,7 @@ class QLineEdit; class Media; class QListWidget; class QDoubleSpinBox; +class QCheckBox; class MediaPropertiesDialog : public QDialog { Q_OBJECT @@ -20,6 +21,7 @@ private: Media* item; QListWidget* track_list; QDoubleSpinBox* conform_fr; + QCheckBox* premultiply_alpha_setting; private slots: void accept(); }; diff --git a/effects/chromakey.frag b/effects/chromakey.frag index a94b03f79..4f57d8b1e 100644 --- a/effects/chromakey.frag +++ b/effects/chromakey.frag @@ -47,11 +47,15 @@ void main(void) { float mask = colorclose(cb, cr, cb_key, cr_key, (tola/100.0), (tolb/100.0)); if (mode == 0) { // composite - float submask = 1.0-mask; + //float submask = 1.0-mask; + float submask = 0.0; texture_color.r = max(texture_color.r - submask*key_color.r, 0.0) + submask; texture_color.g = max(texture_color.g - submask*key_color.g, 0.0) + submask; texture_color.b = max(texture_color.b - submask*key_color.b, 0.0) + submask; texture_color.a *= mask; + + // premultiply + texture_color.rgb *= texture_color.a; } else if (mode == 1) { // alpha texture_color.rgb = vec3(mask); } else if (mode == 2) { // original diff --git a/effects/internal/blending.frag b/effects/internal/blending.frag new file mode 100644 index 000000000..21cdbe8a9 --- /dev/null +++ b/effects/internal/blending.frag @@ -0,0 +1,238 @@ +#version 110 + +const int BLEND_MODE_ADD = 0; +const int BLEND_MODE_AVERAGE = 1; +const int BLEND_MODE_COLORBURN = 2; +const int BLEND_MODE_COLORDODGE = 3; +const int BLEND_MODE_DARKEN = 4; +const int BLEND_MODE_DIFFERENCE = 5; +const int BLEND_MODE_EXCLUSION = 6; +const int BLEND_MODE_GLOW = 7; +const int BLEND_MODE_HARDLIGHT = 8; +const int BLEND_MODE_HARDMIX = 9; +const int BLEND_MODE_LIGHTEN = 10; +const int BLEND_MODE_LINEARBURN = 11; +const int BLEND_MODE_LINEARDODGE = 12; +const int BLEND_MODE_LINEARLIGHT = 13; +const int BLEND_MODE_MULTIPLY = 14; +const int BLEND_MODE_NEGATION = 15; +const int BLEND_MODE_NORMAL = 16; +const int BLEND_MODE_OVERLAY = 17; +const int BLEND_MODE_PHOENIX = 18; +const int BLEND_MODE_PINLIGHT = 19; +const int BLEND_MODE_REFLECT = 20; +const int BLEND_MODE_SCREEN = 21; +const int BLEND_MODE_SOFTLIGHT = 22; +const int BLEND_MODE_SUBSTRACT = 23; +const int BLEND_MODE_SUBTRACT = 24; +const int BLEND_MODE_VIVIDLIGHT = 25; + +uniform sampler2D background; +uniform sampler2D foreground; + +uniform int blendmode; +uniform float opacity; + +varying vec2 vTexCoord; + +// adapted from https://github.com/jamieowen/glsl-blend +// and http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html + +// float blending functions +float blend_color_burn(float base, float blend) { + return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); +} + +float blend_color_dodge(float base, float blend) { + return (blend==1.0)?blend:min(base/(1.0-blend),1.0); +} + +float blend_vivid_light(float base, float blend) { + return (blend<0.5)?blend_color_burn(base,(2.0*blend)):blend_color_dodge(base,(2.0*(blend-0.5))); +} + +vec3 blend_vivid_light(vec3 base, vec3 blend) { + return vec3(blend_vivid_light(base.r,blend.r),blend_vivid_light(base.g,blend.g),blend_vivid_light(base.b,blend.b)); +} + +float blend_hard_mix(float base, float blend) { + return (blend_vivid_light(base,blend)<0.5)?0.0:1.0; +} + +float blend_lighten(float base, float blend) { + return max(blend,base); +} + +float blend_overlay(float base, float blend) { + return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); +} + +vec3 blend_overlay(vec3 base, vec3 blend) { + return vec3(blend_overlay(base.r,blend.r),blend_overlay(base.g,blend.g),blend_overlay(base.b,blend.b)); +} + +float blend_darken(float base, float blend) { + return min(blend, base); +} + +float blend_linear_burn(float base, float blend) { + return max(base+blend-1.0,0.0); +} + +vec3 blend_linear_burn(vec3 base, vec3 blend) { + return max(base+blend-vec3(1.0),vec3(0.0)); +} + +float blend_linear_dodge(float base, float blend) { + return min(base+blend,1.0); +} + +vec3 blend_linear_dodge(vec3 base, vec3 blend) { + return min(base+blend,vec3(1.0)); +} + +float blend_linear_light(float base, float blend) { + return blend<0.5?blend_linear_burn(base,(2.0*blend)):blend_linear_dodge(base,(2.0*(blend-0.5))); +} + +float blend_pin_light(float base, float blend) { + return (blend<0.5)?blend_darken(base,(2.0*blend)):blend_lighten(base,(2.0*(blend-0.5))); +} + +float blend_reflect(float base, float blend) { + return (blend==1.0)?blend:min(base*base/(1.0-blend),1.0); +} + +vec3 blend_reflect(vec3 base, vec3 blend) { + return vec3(blend_reflect(base.r,blend.r),blend_reflect(base.g,blend.g),blend_reflect(base.b,blend.b)); +} + +float blend_screen(float base, float blend) { + return 1.0-((1.0-base)*(1.0-blend)); +} + +float blend_substract(float base, float blend) { + return max(base+blend-1.0,0.0); +} + +float blend_soft_light(float base, float blend) { + return (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend)); +} + +// RGB blending function, alpha is handled below +vec3 blend(vec3 base, vec3 blend) { + switch (blendmode) { + + case BLEND_MODE_AVERAGE: + return (base+blend)/2.0; + + case BLEND_MODE_COLORBURN: + return vec3(blend_color_burn(base.r, blend.r), blend_color_burn(base.g, blend.g), blend_color_burn(base.b, blend.b)); + + case BLEND_MODE_COLORDODGE: + return vec3(blend_color_dodge(base.r, blend.r), blend_color_dodge(base.g, blend.g), blend_color_dodge(base.b, blend.b)); + + case BLEND_MODE_DARKEN: + return vec3(blend_darken(base.r, blend.r), blend_darken(base.g, blend.g), blend_darken(base.b, blend.b)); + + case BLEND_MODE_DIFFERENCE: + return abs(base-blend); + + case BLEND_MODE_EXCLUSION: + return base+blend-2.0*base*blend; + + case BLEND_MODE_GLOW: + return blend_reflect(blend, base); + + case BLEND_MODE_HARDLIGHT: + return blend_overlay(blend,base); + + case BLEND_MODE_HARDMIX: + return vec3(blend_hard_mix(base.r,blend.r),blend_hard_mix(base.g,blend.g),blend_hard_mix(base.b,blend.b)); + + case BLEND_MODE_LIGHTEN: + return vec3(blend_lighten(base.r,blend.r),blend_lighten(base.g,blend.g),blend_lighten(base.b,blend.b)); + + case BLEND_MODE_LINEARBURN: + case BLEND_MODE_SUBTRACT: + return blend_linear_burn(base, blend); + + case BLEND_MODE_ADD: + case BLEND_MODE_LINEARDODGE: + return blend_linear_dodge(base, blend); + + case BLEND_MODE_LINEARLIGHT: + return vec3(blend_linear_light(base.r,blend.r),blend_linear_light(base.g,blend.g),blend_linear_light(base.b,blend.b)); + + case BLEND_MODE_MULTIPLY: + return (base * blend); + + case BLEND_MODE_NEGATION: + return vec3(1.0)-abs(vec3(1.0)-base-blend); + + case BLEND_MODE_OVERLAY: + return blend_overlay(base, blend); + + case BLEND_MODE_PHOENIX: + return min(base,blend)-max(base,blend)+vec3(1.0); + + case BLEND_MODE_PINLIGHT: + return vec3(blend_pin_light(base.r,blend.r),blend_pin_light(base.g,blend.g),blend_pin_light(base.b,blend.b)); + + case BLEND_MODE_REFLECT: + return blend_reflect(base, blend); + + case BLEND_MODE_SCREEN: + return vec3(blend_screen(base.r,blend.r),blend_screen(base.g,blend.g),blend_screen(base.b,blend.b)); + + case BLEND_MODE_SUBSTRACT: + return max(base+blend-vec3(1.0),vec3(0.0)); + + case BLEND_MODE_SOFTLIGHT: + return vec3(blend_soft_light(base.r,blend.r),blend_soft_light(base.g,blend.g),blend_soft_light(base.b,blend.b)); + + case BLEND_MODE_VIVIDLIGHT: + return vec3(blend_vivid_light(base.r,blend.r),blend_vivid_light(base.g,blend.g),blend_vivid_light(base.b,blend.b)); + + case BLEND_MODE_NORMAL: + default: + return blend; + + } +} + +void main(void) { + vec4 bg_color = texture2D(background, vTexCoord); + vec4 fg_color = texture2D(foreground, vTexCoord); + + // blend textures together + vec3 composite = blend(bg_color.rgb, fg_color.rgb); + + // add foreground and background alpha's together + float alpha_opac = fg_color.a*opacity; + + switch (blendmode) { + case BLEND_MODE_OVERLAY: + case BLEND_MODE_LIGHTEN: + case BLEND_MODE_SCREEN: + case BLEND_MODE_COLORDODGE: + case BLEND_MODE_LINEARDODGE: + case BLEND_MODE_ADD: + case BLEND_MODE_SOFTLIGHT: + case BLEND_MODE_NEGATION: + case BLEND_MODE_AVERAGE: + case BLEND_MODE_REFLECT: + case BLEND_MODE_EXCLUSION: + case BLEND_MODE_DIFFERENCE: + composite *= alpha_opac; + break; + } + + vec4 full_composite = vec4(composite + bg_color.rgb*(1.0-alpha_opac), bg_color.a + fg_color.a); + // vec4 full_composite = vec4(mix(bg_color.rgb, composite, alpha_opac), bg_color.a + alpha_opac); + + // full_composite = mix(bg_color, full_composite, alpha_opac); + + // output to color + gl_FragColor = full_composite; +} \ No newline at end of file diff --git a/effects/internal/common.vert b/effects/internal/common.vert new file mode 100644 index 000000000..2d088fa2f --- /dev/null +++ b/effects/internal/common.vert @@ -0,0 +1,8 @@ +#version 110 + +varying vec2 vTexCoord; + +void main() { + vTexCoord = gl_MultiTexCoord0.xy; + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +} \ No newline at end of file diff --git a/effects/cornerpin.frag b/effects/internal/cornerpin.frag similarity index 100% rename from effects/cornerpin.frag rename to effects/internal/cornerpin.frag diff --git a/effects/cornerpin.vert b/effects/internal/cornerpin.vert similarity index 100% rename from effects/cornerpin.vert rename to effects/internal/cornerpin.vert diff --git a/effects/internal/cornerpineffect.cpp b/effects/internal/cornerpineffect.cpp index c48526601..11730619e 100644 --- a/effects/internal/cornerpineffect.cpp +++ b/effects/internal/cornerpineffect.cpp @@ -8,23 +8,23 @@ CornerPinEffect::CornerPinEffect(Clip *c, const EffectMeta *em) : Effect(c, em) enable_coords = true; enable_shader = true; - EffectRow* top_left = add_row(tr("Top Left")); + EffectRow* top_left = add_row(tr("Top Left")); top_left_x = top_left->add_field(EFFECT_FIELD_DOUBLE, "topleftx"); top_left_y = top_left->add_field(EFFECT_FIELD_DOUBLE, "toplefty"); - EffectRow* top_right = add_row(tr("Top Right")); + EffectRow* top_right = add_row(tr("Top Right")); top_right_x = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprightx"); top_right_y = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprighty"); - EffectRow* bottom_left = add_row(tr("Bottom Left")); + EffectRow* bottom_left = add_row(tr("Bottom Left")); bottom_left_x = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomleftx"); bottom_left_y = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomlefty"); - EffectRow* bottom_right = add_row(tr("Bottom Right")); + EffectRow* bottom_right = add_row(tr("Bottom Right")); bottom_right_x = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrightx"); bottom_right_y = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrighty"); - perspective = add_row(tr("Perspective"))->add_field(EFFECT_FIELD_BOOL, "perspective"); + perspective = add_row(tr("Perspective"))->add_field(EFFECT_FIELD_BOOL, "perspective"); perspective->set_bool_value(true); top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); diff --git a/effects/dropshadow.frag b/effects/internal/dropshadow.frag similarity index 95% rename from effects/dropshadow.frag rename to effects/internal/dropshadow.frag index 1bed6e491..402c4c547 100644 --- a/effects/dropshadow.frag +++ b/effects/internal/dropshadow.frag @@ -13,6 +13,7 @@ uniform float shadowdistance; varying vec2 vTexCoord; void main(void) { + /* vec4 master_px = texture2D(image, vTexCoord); if (shadow == 1) { vec2 shadow_dist = vec2(shadowdistance)/resolution; @@ -39,5 +40,7 @@ void main(void) { gl_FragColor = composition; } else { gl_FragColor = master_px; - } + } + */ + gl_FragColor = texture2D(image, vTexCoord); } \ No newline at end of file diff --git a/effects/internal/internalshaders.qrc b/effects/internal/internalshaders.qrc new file mode 100644 index 000000000..39b1a3aee --- /dev/null +++ b/effects/internal/internalshaders.qrc @@ -0,0 +1,10 @@ + + + blending.frag + common.vert + cornerpin.frag + cornerpin.vert + premultiply.frag + dropshadow.frag + + diff --git a/effects/internal/premultiply.frag b/effects/internal/premultiply.frag new file mode 100644 index 000000000..bc16e5cb4 --- /dev/null +++ b/effects/internal/premultiply.frag @@ -0,0 +1,10 @@ +#version 110 + +uniform sampler2D tex; +varying vec2 vTexCoord; + +void main(void) { + vec4 c = texture2D(tex, vTexCoord); + c.rgb *= c.a; + gl_FragColor = c; +} \ No newline at end of file diff --git a/effects/internal/texteffect.cpp b/effects/internal/texteffect.cpp index afdbf2607..7f4c36940 100644 --- a/effects/internal/texteffect.cpp +++ b/effects/internal/texteffect.cpp @@ -94,67 +94,67 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : } void blurred2(QImage& result, const QRect& rect, int radius, bool alphaOnly = false) { - int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; - int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; + int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; + int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; - int r1 = rect.top(); - int r2 = rect.bottom(); - int c1 = rect.left(); - int c2 = rect.right(); + int r1 = rect.top(); + int r2 = rect.bottom(); + int c1 = rect.left(); + int c2 = rect.right(); - int bpl = result.bytesPerLine(); - int rgba[4]; - unsigned char* p; + int bpl = result.bytesPerLine(); + int rgba[4]; + unsigned char* p; - int i1 = 0; - int i2 = 3; + int i1 = 0; + int i2 = 3; - if (alphaOnly) - i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + if (alphaOnly) + i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r1) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r1) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; - p += bpl; - for (int j = r1; j < r2; j++, p += bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } + p += bpl; + for (int j = r1; j < r2; j++, p += bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c1 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c1 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; - p += 4; - for (int j = c1; j < c2; j++, p += 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } + p += 4; + for (int j = c1; j < c2; j++, p += 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } - for (int col = c1; col <= c2; col++) { - p = result.scanLine(r2) + col * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; + for (int col = c1; col <= c2; col++) { + p = result.scanLine(r2) + col * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; - p -= bpl; - for (int j = r1; j < r2; j++, p -= bpl) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } + p -= bpl; + for (int j = r1; j < r2; j++, p -= bpl) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } - for (int row = r1; row <= r2; row++) { - p = result.scanLine(row) + c2 * 4; - for (int i = i1; i <= i2; i++) - rgba[i] = p[i] << 4; + for (int row = r1; row <= r2; row++) { + p = result.scanLine(row) + c2 * 4; + for (int i = i1; i <= i2; i++) + rgba[i] = p[i] << 4; - p -= 4; - for (int j = c1; j < c2; j++, p -= 4) - for (int i = i1; i <= i2; i++) - p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; - } + p -= 4; + for (int j = c1; j < c2; j++, p -= 4) + for (int i = i1; i <= i2; i++) + p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; + } } void TextEffect::redraw(double timecode) { @@ -254,25 +254,25 @@ void TextEffect::redraw(double timecode) { path.addText(text_x, text_y, font, lines.at(i)); } - // draw software shadow - if (!enable_shader && shadow_bool->get_bool_value(timecode)) { - p.setPen(Qt::NoPen); - int shadow_offset = shadow_distance->get_double_value(timecode); + // draw software shadow + if (shadow_bool->get_bool_value(timecode)) { + p.setPen(Qt::NoPen); + int shadow_offset = shadow_distance->get_double_value(timecode); - QPainterPath shadow_path(path); - shadow_path.translate(shadow_offset, shadow_offset); + QPainterPath shadow_path(path); + shadow_path.translate(shadow_offset, shadow_offset); - QColor col = shadow_color->get_color_value(timecode); - col.setAlpha(0); - img.fill(col); + QColor col = shadow_color->get_color_value(timecode); + col.setAlpha(0); + img.fill(col); - col.setAlphaF(shadow_opacity->get_double_value(timecode)*0.01); - p.setBrush(col); - p.drawPath(shadow_path); + col.setAlphaF(shadow_opacity->get_double_value(timecode)*0.01); + p.setBrush(col); + p.drawPath(shadow_path); - int blurSoftness = shadow_softness->get_double_value(timecode); - if (blurSoftness > 0) blurred2(img, img.rect(), blurSoftness, false); - } + int blurSoftness = shadow_softness->get_double_value(timecode); + if (blurSoftness > 0) blurred2(img, img.rect(), blurSoftness, false); + } // draw outline int outline_width_val = outline_width->get_double_value(timecode); @@ -288,10 +288,11 @@ void TextEffect::redraw(double timecode) { p.setPen(Qt::NoPen); p.setBrush(set_color_button->get_color_value(timecode)); p.drawPath(path); + + p.end(); } void TextEffect::shadow_enable(bool e) { - enable_shader = (e && !config.use_software_fallback); close(); shadow_color->set_enabled(e); diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index b313e9298..a095b3f5a 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -23,19 +23,14 @@ #include "panels/viewer.h" #include "ui/viewerwidget.h" -#define BLEND_MODE_NORMAL 0 -#define BLEND_MODE_SCREEN 1 -#define BLEND_MODE_MULTIPLY 2 -#define BLEND_MODE_OVERLAY 3 - TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { enable_coords = true; - EffectRow* position_row = add_row(tr("Position")); + EffectRow* position_row = add_row(tr("Position")); position_x = position_row->add_field(EFFECT_FIELD_DOUBLE, "posx"); // position X position_y = position_row->add_field(EFFECT_FIELD_DOUBLE, "posy"); // position Y - EffectRow* scale_row = add_row(tr("Scale")); + EffectRow* scale_row = add_row(tr("Scale")); scale_x = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scalex"); // scale X (and Y is uniform scale is selected) scale_x->set_double_minimum_value(0); scale_x->set_double_maximum_value(3000); @@ -43,27 +38,49 @@ TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) scale_y->set_double_minimum_value(0); scale_y->set_double_maximum_value(3000); - EffectRow* uniform_scale_row = add_row(tr("Uniform Scale")); + EffectRow* uniform_scale_row = add_row(tr("Uniform Scale")); uniform_scale_field = uniform_scale_row->add_field(EFFECT_FIELD_BOOL, "uniformscale"); // uniform scale option - EffectRow* rotation_row = add_row(tr("Rotation")); + EffectRow* rotation_row = add_row(tr("Rotation")); rotation = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "rotation"); - EffectRow* anchor_point_row = add_row(tr("Anchor Point")); + EffectRow* anchor_point_row = add_row(tr("Anchor Point")); anchor_x_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchorx"); // anchor point X anchor_y_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchory"); // anchor point Y - EffectRow* opacity_row = add_row(tr("Opacity")); + EffectRow* opacity_row = add_row(tr("Opacity")); opacity = opacity_row->add_field(EFFECT_FIELD_DOUBLE, "opacity"); // opacity opacity->set_double_minimum_value(0); opacity->set_double_maximum_value(100); - EffectRow* blend_mode_row = add_row(tr("Blend Mode")); + EffectRow* blend_mode_row = add_row(tr("Blend Mode")); blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO, "blendmode"); // blend mode - blend_mode_box->add_combo_item(tr("Normal"), BLEND_MODE_NORMAL); - blend_mode_box->add_combo_item(tr("Overlay"), BLEND_MODE_OVERLAY); - blend_mode_box->add_combo_item(tr("Screen"), BLEND_MODE_SCREEN); - blend_mode_box->add_combo_item(tr("Multiply"), BLEND_MODE_MULTIPLY); + blend_mode_box->add_combo_item(tr("Normal"), BLEND_MODE_NORMAL); + blend_mode_box->add_combo_item(tr("Darken"), BLEND_MODE_DARKEN); + blend_mode_box->add_combo_item(tr("Multiply"), BLEND_MODE_MULTIPLY); + blend_mode_box->add_combo_item(tr("Color Burn"), BLEND_MODE_COLORBURN); + blend_mode_box->add_combo_item(tr("Linear Burn"), BLEND_MODE_LINEARBURN); + blend_mode_box->add_combo_item(tr("Lighten"), BLEND_MODE_LIGHTEN); + blend_mode_box->add_combo_item(tr("Screen"), BLEND_MODE_SCREEN); + blend_mode_box->add_combo_item(tr("Color Dodge"), BLEND_MODE_COLORDODGE); + blend_mode_box->add_combo_item(tr("Linear Dodge (Add)"), BLEND_MODE_LINEARDODGE); + blend_mode_box->add_combo_item(tr("Overlay"), BLEND_MODE_OVERLAY); + blend_mode_box->add_combo_item(tr("Soft Light"), BLEND_MODE_SOFTLIGHT); + blend_mode_box->add_combo_item(tr("Hard Light"), BLEND_MODE_HARDLIGHT); + blend_mode_box->add_combo_item(tr("Vivid Light"), BLEND_MODE_VIVIDLIGHT); + blend_mode_box->add_combo_item(tr("Linear Light"), BLEND_MODE_LINEARLIGHT); + blend_mode_box->add_combo_item(tr("Pin Light"), BLEND_MODE_PINLIGHT); + blend_mode_box->add_combo_item(tr("Hard Mix"), BLEND_MODE_HARDMIX); + blend_mode_box->add_combo_item(tr("Difference"), BLEND_MODE_DIFFERENCE); + blend_mode_box->add_combo_item(tr("Exclusion"), BLEND_MODE_EXCLUSION); + blend_mode_box->add_combo_item(tr("Reflect"), BLEND_MODE_REFLECT); +// blend_mode_box->add_combo_item(tr("Subtract"), BLEND_MODE_SUBTRACT); + blend_mode_box->add_combo_item(tr("Substract"), BLEND_MODE_SUBSTRACT); +// blend_mode_box->add_combo_item(tr("Add"), BLEND_MODE_ADD); + blend_mode_box->add_combo_item(tr("Average"), BLEND_MODE_AVERAGE); + blend_mode_box->add_combo_item(tr("Glow"), BLEND_MODE_GLOW); + blend_mode_box->add_combo_item(tr("Negation"), BLEND_MODE_NEGATION); + blend_mode_box->add_combo_item(tr("Phoenix"), BLEND_MODE_PHOENIX); // set up gizmos top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); @@ -188,11 +205,11 @@ void TransformEffect::toggle_uniform_scale(bool enabled) { void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { // position - glTranslatef(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); + glTranslated(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); // anchor point - int anchor_x_offset = (anchor_x_box->get_double_value(timecode)); - int anchor_y_offset = (anchor_y_box->get_double_value(timecode)); + int anchor_x_offset = qRound(anchor_x_box->get_double_value(timecode)); + int anchor_y_offset = qRound(anchor_y_box->get_double_value(timecode)); coords.vertexTopLeftX -= anchor_x_offset; coords.vertexTopRightX -= anchor_x_offset; coords.vertexBottomLeftX -= anchor_x_offset; @@ -203,35 +220,18 @@ void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, i coords.vertexBottomRightY -= anchor_y_offset; // rotation - glRotatef(rotation->get_double_value(timecode), 0, 0, 1); + glRotated(rotation->get_double_value(timecode), 0, 0, 1); // scale - float sx = scale_x->get_double_value(timecode)*0.01; - float sy = (uniform_scale_field->get_bool_value(timecode)) ? sx : scale_y->get_double_value(timecode)*0.01; - glScalef(sx, sy, 1); + double sx = scale_x->get_double_value(timecode)*0.01; + double sy = (uniform_scale_field->get_bool_value(timecode)) ? sx : scale_y->get_double_value(timecode)*0.01; + glScaled(sx, sy, 1); // blend mode - switch (blend_mode_box->get_combo_data(timecode).toInt()) { - case BLEND_MODE_NORMAL: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case BLEND_MODE_OVERLAY: - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case BLEND_MODE_SCREEN: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - case BLEND_MODE_MULTIPLY: - glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); - break; - default: - qCritical() << "Invalid blend mode. This is a bug - please contact developers"; - } + coords.blendmode = blend_mode_box->get_combo_data(timecode).toInt(); // opacity - float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); - glColor4f(1.0, 1.0, 1.0, color[3]*(opacity->get_double_value(timecode)*0.01)); + coords.opacity *= float(opacity->get_double_value(timecode)*0.01); } void TransformEffect::gizmo_draw(double, GLTextureCoords& coords) { diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 2da9aaf27..adfc3a510 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -248,6 +248,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { m->out = attr.value().toLong(); } else if (attr.name() == "speed") { m->speed = attr.value().toDouble(); + } else if (attr.name() == "alphapremul") { + m->alpha_is_premultiplied = (attr.value() == "1"); } } diff --git a/olive.pro b/olive.pro index 344dcc27e..115c65575 100644 --- a/olive.pro +++ b/olive.pro @@ -256,7 +256,8 @@ unix:!mac { } RESOURCES += \ - icons/icons.qrc + icons/icons.qrc \ + effects/internal/internalshaders.qrc unix:!mac:isEmpty(PREFIX) { PREFIX = /usr/local diff --git a/panels/project.cpp b/panels/project.cpp index fb6deffb8..fac48f0c1 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -83,67 +83,67 @@ Project::Project(QWidget *parent) : toolbar->setSpacing(0); toolbar_widget->setLayout(toolbar); - QPushButton* toolbar_new = new QPushButton(toolbar_widget); - QIcon icon1; - icon1.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On); - icon1.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_new->setIcon(icon1); + QPushButton* toolbar_new = new QPushButton(toolbar_widget); + QIcon icon1; + icon1.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On); + icon1.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_new->setIcon(icon1); toolbar_new->setToolTip("New"); connect(toolbar_new, SIGNAL(clicked(bool)), this, SLOT(make_new_menu())); toolbar->addWidget(toolbar_new); QPushButton* toolbar_open = new QPushButton(toolbar_widget); - QIcon icon2; - icon2.addFile(QStringLiteral(":/icons/open.png"), QSize(), QIcon::Normal, QIcon::On); - icon2.addFile(QStringLiteral(":/icons/open-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_open->setIcon(icon2); + QIcon icon2; + icon2.addFile(QStringLiteral(":/icons/open.png"), QSize(), QIcon::Normal, QIcon::On); + icon2.addFile(QStringLiteral(":/icons/open-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_open->setIcon(icon2); toolbar_open->setToolTip("Open Project"); connect(toolbar_open, SIGNAL(clicked(bool)), mainWindow, SLOT(open_project())); toolbar->addWidget(toolbar_open); QPushButton* toolbar_save = new QPushButton(toolbar_widget); - QIcon icon3; - icon3.addFile(QStringLiteral(":/icons/save.png"), QSize(), QIcon::Normal, QIcon::On); - icon3.addFile(QStringLiteral(":/icons/save-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_save->setIcon(icon3); + QIcon icon3; + icon3.addFile(QStringLiteral(":/icons/save.png"), QSize(), QIcon::Normal, QIcon::On); + icon3.addFile(QStringLiteral(":/icons/save-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_save->setIcon(icon3); toolbar_save->setToolTip("Save Project"); connect(toolbar_save, SIGNAL(clicked(bool)), mainWindow, SLOT(save_project())); toolbar->addWidget(toolbar_save); QPushButton* toolbar_undo = new QPushButton(toolbar_widget); - QIcon icon4; - icon4.addFile(QStringLiteral(":/icons/undo.png"), QSize(), QIcon::Normal, QIcon::On); - icon4.addFile(QStringLiteral(":/icons/undo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_undo->setIcon(icon4); + QIcon icon4; + icon4.addFile(QStringLiteral(":/icons/undo.png"), QSize(), QIcon::Normal, QIcon::On); + icon4.addFile(QStringLiteral(":/icons/undo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_undo->setIcon(icon4); toolbar_undo->setToolTip("Undo"); connect(toolbar_undo, SIGNAL(clicked(bool)), mainWindow, SLOT(undo())); toolbar->addWidget(toolbar_undo); QPushButton* toolbar_redo = new QPushButton(toolbar_widget); - QIcon icon5; - icon5.addFile(QStringLiteral(":/icons/redo.png"), QSize(), QIcon::Normal, QIcon::On); - icon5.addFile(QStringLiteral(":/icons/redo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_redo->setIcon(icon5); + QIcon icon5; + icon5.addFile(QStringLiteral(":/icons/redo.png"), QSize(), QIcon::Normal, QIcon::On); + icon5.addFile(QStringLiteral(":/icons/redo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_redo->setIcon(icon5); toolbar_redo->setToolTip("Redo"); connect(toolbar_redo, SIGNAL(clicked(bool)), mainWindow, SLOT(redo())); toolbar->addWidget(toolbar_redo); toolbar->addStretch(); - QPushButton* toolbar_tree_view = new QPushButton(toolbar_widget); - QIcon icon6; - icon6.addFile(QStringLiteral(":/icons/treeview.png"), QSize(), QIcon::Normal, QIcon::On); - icon6.addFile(QStringLiteral(":/icons/treeview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_tree_view->setIcon(icon6); - toolbar_tree_view->setToolTip("Tree View"); - connect(toolbar_tree_view, SIGNAL(clicked(bool)), this, SLOT(set_tree_view())); - toolbar->addWidget(toolbar_tree_view); + QPushButton* toolbar_tree_view = new QPushButton(toolbar_widget); + QIcon icon6; + icon6.addFile(QStringLiteral(":/icons/treeview.png"), QSize(), QIcon::Normal, QIcon::On); + icon6.addFile(QStringLiteral(":/icons/treeview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_tree_view->setIcon(icon6); + toolbar_tree_view->setToolTip("Tree View"); + connect(toolbar_tree_view, SIGNAL(clicked(bool)), this, SLOT(set_tree_view())); + toolbar->addWidget(toolbar_tree_view); QPushButton* toolbar_icon_view = new QPushButton(toolbar_widget); - QIcon icon7; - icon7.addFile(QStringLiteral(":/icons/iconview.png"), QSize(), QIcon::Normal, QIcon::On); - icon7.addFile(QStringLiteral(":/icons/iconview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_icon_view->setIcon(icon7); + QIcon icon7; + icon7.addFile(QStringLiteral(":/icons/iconview.png"), QSize(), QIcon::Normal, QIcon::On); + icon7.addFile(QStringLiteral(":/icons/iconview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_icon_view->setIcon(icon7); toolbar_icon_view->setToolTip("Icon View"); connect(toolbar_icon_view, SIGNAL(clicked(bool)), this, SLOT(set_icon_view())); toolbar->addWidget(toolbar_icon_view); @@ -953,6 +953,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("in", QString::number(f->in)); stream.writeAttribute("out", QString::number(f->out)); stream.writeAttribute("speed", QString::number(f->speed)); + stream.writeAttribute("alphapremul", QString::number(f->alpha_is_premultiplied)); for (int j=0;jvideo_tracks.size();j++) { const FootageStream& ms = f->video_tracks.at(j); stream.writeStartElement("video"); diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 42f05b7a7..741ff166c 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -264,6 +264,7 @@ void Viewer::seek(long p) { } reset_all_audio(); audio_scrub = true; + last_playhead = seq->playhead; update_parents(update_fx); } diff --git a/panels/viewer.h b/panels/viewer.h index 7181d426d..002caea1a 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -37,7 +37,6 @@ public: void update_playhead_timecode(long p); void update_end_timecode(); void update_header_zoom(); - void update_viewer(); void clear_in(); void clear_out(); void clear_inout_point(); @@ -88,6 +87,7 @@ public slots: void go_to_out(); void go_to_end(); void close_media(); + void update_viewer(); private slots: void update_playhead(); @@ -105,6 +105,7 @@ private: QString panel_name; double minimum_zoom; bool playing_in_to_out; + long last_playhead; void set_zoom_value(double d); void set_sb_max(); void set_playback_speed(int s); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 7d7f294ca..ae08fbb69 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -766,16 +766,29 @@ void open_clip_worker(Clip* clip) { AVFilterContext* last_filter = clip->buffersrc_ctx; + char filter_args[100]; + if (ms->video_interlacing != VIDEO_PROGRESSIVE) { AVFilterContext* yadif_filter; - char yadif_args[100]; - snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, nullptr, clip->filter_graph); + snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, clip->filter_graph); avfilter_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; } + // ffmpeg premultiplier + /* + if (!clip->media->to_footage()->alpha_is_premultiplied) { + AVFilterContext* premultiply_filter; + snprintf(filter_args, sizeof(filter_args), "inplace=1"); + avfilter_graph_create_filter(&premultiply_filter, avfilter_get_by_name("premultiply"), "premultiply", filter_args, nullptr, clip->filter_graph); + + avfilter_link(last_filter, 0, premultiply_filter, 0); + last_filter = premultiply_filter; + } + */ + /* stabilization code */ /*bool stabilize = false; if (stabilize) { @@ -791,19 +804,12 @@ void open_clip_worker(Clip* clip) { } }*/ - enum AVPixelFormat valid_pix_fmts[] = { -// AV_PIX_FMT_RGB24, - AV_PIX_FMT_RGBA, - AV_PIX_FMT_NONE - }; - - clip->pix_fmt = avcodec_find_best_pix_fmt_of_list(valid_pix_fmts, static_cast(clip->stream->codecpar->format), 1, nullptr); + clip->pix_fmt = AV_PIX_FMT_RGBA; const char* chosen_format = av_get_pix_fmt_name(static_cast(clip->pix_fmt)); - char format_args[100]; - snprintf(format_args, sizeof(format_args), "pix_fmts=%s", chosen_format); + snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format); AVFilterContext* format_conv; - avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", format_args, nullptr, clip->filter_graph); + avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, clip->filter_graph); avfilter_link(last_filter, 0, format_conv, 0); avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); diff --git a/playback/playback.cpp b/playback/playback.cpp index 9eb56fcfc..1d6289ce8 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -370,9 +370,15 @@ int retrieve_next_frame(Clip* c, AVFrame* f) { } bool is_clip_active(Clip* c, long playhead) { + // these buffers allow clips to be opened and prepared well before they're displayed + // as well as closed a little after they're not needed anymore + int open_buffer = qCeil(c->sequence->frame_rate*2); + int close_buffer = qCeil(c->sequence->frame_rate); + + return c->enabled - && c->get_timeline_in_with_transition() < playhead + ceil(c->sequence->frame_rate*2) - && c->get_timeline_out_with_transition() > playhead + && c->get_timeline_in_with_transition() < playhead + open_buffer + && c->get_timeline_out_with_transition() > playhead - close_buffer && playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition() < c->getMaximumLength(); } diff --git a/project/effect.cpp b/project/effect.cpp index 650935174..2beee31f7 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -29,9 +29,7 @@ #include "effects/internal/paneffect.h" #include "effects/internal/shakeeffect.h" #include "effects/internal/cornerpineffect.h" -#ifndef NOVST #include "effects/internal/vsthost.h" -#endif #include "effects/internal/fillleftrighteffect.h" #include "effects/internal/frei0reffect.h" @@ -502,7 +500,7 @@ void Effect::load(QXmlStreamReader& stream) { for (int l=0;lfieldCount();l++) { if (row->field(l)->id == attr.value()) { field_number = l; - qInfo() << "Found field by ID"; +// qInfo() << "Found field by ID"; break; } } @@ -662,10 +660,6 @@ void Effect::open() { } else { isOpen = true; } - - if (enable_superimpose) { - texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - } } void Effect::close() { @@ -749,30 +743,40 @@ void Effect::process_shader(double timecode, GLTextureCoords&) { void Effect::process_coords(double, GLTextureCoords&, int) {} GLuint Effect::process_superimpose(double timecode) { - bool recreate_texture = false; + bool dimensions_changed = false; + bool redrew_image = false; + int width = parent_clip->getWidth(); int height = parent_clip->getHeight(); if (width != img.width() || height != img.height()) { - img = QImage(width, height, QImage::Format_RGBA8888); - recreate_texture = true; + img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); + dimensions_changed = true; } - if (valueHasChanged(timecode) || recreate_texture || enable_always_update) { + if (valueHasChanged(timecode) || dimensions_changed || enable_always_update) { redraw(timecode); + redrew_image = true; } - if (texture != nullptr) { - if (recreate_texture || texture->width() != img.width() || texture->height() != img.height()) { - delete_texture(); - texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - texture->setData(img); - } else { - texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits()); - } - return texture->textureId(); + if (texture == nullptr || texture->width() != img.width() || texture->height() != img.height()) { + delete_texture(); + + texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + texture->setSize(img.width(), img.height()); + texture->setFormat(QOpenGLTexture::RGBA8_UNorm); + texture->setMipLevels(texture->maximumMipLevels()); + texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); + texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + + redrew_image = true; } - return 0; + + if (redrew_image) { + texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits()); + } + + return texture->textureId(); } void Effect::process_audio(double, double, quint8*, int, int) {} diff --git a/project/effect.h b/project/effect.h index 6779501c4..dd681dc98 100644 --- a/project/effect.h +++ b/project/effect.h @@ -74,6 +74,36 @@ enum EffectInternal { EFFECT_INTERNAL_COUNT }; +enum EffectBlendMode { + BLEND_MODE_ADD, + BLEND_MODE_AVERAGE, + BLEND_MODE_COLORBURN, + BLEND_MODE_COLORDODGE, + BLEND_MODE_DARKEN, + BLEND_MODE_DIFFERENCE, + BLEND_MODE_EXCLUSION, + BLEND_MODE_GLOW, + BLEND_MODE_HARDLIGHT, + BLEND_MODE_HARDMIX, + BLEND_MODE_LIGHTEN, + BLEND_MODE_LINEARBURN, + BLEND_MODE_LINEARDODGE, + BLEND_MODE_LINEARLIGHT, + BLEND_MODE_MULTIPLY, + BLEND_MODE_NEGATION, + BLEND_MODE_NORMAL, + BLEND_MODE_OVERLAY, + BLEND_MODE_PHOENIX, + BLEND_MODE_PINLIGHT, + BLEND_MODE_REFLECT, + BLEND_MODE_SCREEN, + BLEND_MODE_SOFTLIGHT, + BLEND_MODE_SUBSTRACT, + BLEND_MODE_SUBTRACT, + BLEND_MODE_VIVIDLIGHT, + BLEND_MODE_COUNT +}; + struct GLTextureCoords { int grid_size; @@ -102,6 +132,9 @@ struct GLTextureCoords { float textureBottomLeftX; float textureBottomLeftY; float textureBottomLeftQ; + + int blendmode; + float opacity; }; qint16 mix_audio_sample(qint16 a, qint16 b); @@ -159,7 +192,7 @@ public: const char* ffmpeg_filter; - virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); + virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size); virtual void process_shader(double timecode, GLTextureCoords&); virtual void process_coords(double timecode, GLTextureCoords& coords, int data); virtual GLuint process_superimpose(double timecode); diff --git a/project/effectloaders.cpp b/project/effectloaders.cpp index 716145695..e9cee3c69 100644 --- a/project/effectloaders.cpp +++ b/project/effectloaders.cpp @@ -22,7 +22,9 @@ void load_internal_effects() { EffectMeta em; - // internal effects + // load internal effects + em.path = ":/internalshaders"; + em.type = EFFECT_TYPE_EFFECT; em.subtype = EFFECT_TYPE_AUDIO; diff --git a/project/footage.cpp b/project/footage.cpp index 2f3651017..c9dbd83f6 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -11,7 +11,15 @@ extern "C" { #include "project/clip.h" -Footage::Footage() : ready(false), preview_gen(nullptr), invalid(false), in(0), out(0), speed(1.0) { +Footage::Footage() : + ready(false), + preview_gen(nullptr), + invalid(false), + in(0), + out(0), + speed(1.0), + alpha_is_premultiplied(false) +{ ready_lock.lock(); } diff --git a/project/footage.h b/project/footage.h index d6408857b..56ae6cace 100644 --- a/project/footage.h +++ b/project/footage.h @@ -9,9 +9,11 @@ #include #include -#define VIDEO_PROGRESSIVE 0 -#define VIDEO_TOP_FIELD_FIRST 1 -#define VIDEO_BOTTOM_FIELD_FIRST 2 +enum VideoInterlacingMode { + VIDEO_PROGRESSIVE, + VIDEO_TOP_FIELD_FIRST, + VIDEO_BOTTOM_FIELD_FIRST +}; struct Sequence; struct Clip; @@ -52,6 +54,7 @@ struct Footage { bool ready; bool invalid; double speed; + bool alpha_is_premultiplied; PreviewGenerator* preview_gen; QMutex ready_lock; diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index 31734b339..bb557381a 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -56,6 +56,24 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it QMenu* new_menu = menu.addMenu(tr("New")); mainWindow->make_new_menu(new_menu); + QMenu* view_menu = menu.addMenu(tr("View")); + + QAction* tree_view_action = view_menu->addAction(tr("Tree View")); + connect(tree_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_tree_view())); + + QAction* icon_view_action = view_menu->addAction(tr("Icon View")); + connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); + + QAction* toolbar_action = view_menu->addAction(tr("Show Toolbar")); + toolbar_action->setCheckable(true); + toolbar_action->setChecked(project_parent->toolbar_widget->isVisible()); + connect(toolbar_action, SIGNAL(triggered(bool)), project_parent->toolbar_widget, SLOT(setVisible(bool))); + + QAction* show_sequences = view_menu->addAction(tr("Show Sequences")); + show_sequences->setCheckable(true); + show_sequences->setChecked(panel_project->sorter->get_show_sequences()); + connect(show_sequences, SIGNAL(triggered(bool)), panel_project->sorter, SLOT(set_show_sequences(bool))); + if (items.size() > 0) { Media* m = project_parent->item_to_media(items.at(0)); @@ -120,24 +138,6 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it } } - menu.addSeparator(); - - QAction* tree_view_action = menu.addAction(tr("Tree View")); - connect(tree_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_tree_view())); - - QAction* icon_view_action = menu.addAction(tr("Icon View")); - connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); - - QAction* toolbar_action = menu.addAction(tr("Show Toolbar")); - toolbar_action->setCheckable(true); - toolbar_action->setChecked(project_parent->toolbar_widget->isVisible()); - connect(toolbar_action, SIGNAL(triggered(bool)), project_parent->toolbar_widget, SLOT(setVisible(bool))); - - QAction* show_sequences = menu.addAction(tr("Show Sequences")); - show_sequences->setCheckable(true); - show_sequences->setChecked(panel_project->sorter->get_show_sequences()); - connect(show_sequences, SIGNAL(triggered(bool)), panel_project->sorter, SLOT(set_show_sequences(bool))); - menu.exec(QCursor::pos()); } diff --git a/project/undo.cpp b/project/undo.cpp index 89f3458b2..47b797c34 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -791,7 +791,7 @@ void SetAutoscaleAction::undo() { for (int i=0;iautoscale = !clips.at(i)->autoscale; } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); mainWindow->setWindowModified(old_project_changed); } @@ -799,7 +799,7 @@ void SetAutoscaleAction::redo() { for (int i=0;iautoscale = !clips.at(i)->autoscale; } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); mainWindow->setWindowModified(true); } @@ -1276,3 +1276,11 @@ void RefreshClips::redo() { } } } + +void UpdateViewer::undo() { + redo(); +} + +void UpdateViewer::redo() { + panel_sequence_viewer->viewer_widget->frame_update(); +} diff --git a/project/undo.h b/project/undo.h index c4fbf7619..9f94ee3a4 100644 --- a/project/undo.h +++ b/project/undo.h @@ -644,4 +644,10 @@ private: Media* media; }; +class UpdateViewer : public QUndoCommand { +public: + void undo(); + void redo(); +}; + #endif // UNDO_H diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp index bbab2bd2b..9180385d4 100644 --- a/ui/renderfunctions.cpp +++ b/ui/renderfunctions.cpp @@ -28,31 +28,11 @@ extern "C" { #include } -//#define GL_DEFAULT_BLEND glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) -#define GL_DEFAULT_BLEND glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE) - -GLuint draw_clip(QOpenGLContext* ctx, QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { +void full_blit() { glPushMatrix(); glLoadIdentity(); glOrtho(0, 1, 0, 1, -1, 1); - GLint current_fbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - - fbo->bind(); - - if (clear) glClear(GL_COLOR_BUFFER_BIT); - - // get current blend mode - GLint src_rgb, src_alpha, dst_rgb, dst_alpha; - glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_alpha); - glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_alpha); - - ctx->functions()->GL_DEFAULT_BLEND; - - glBindTexture(GL_TEXTURE_2D, texture); glBegin(GL_QUADS); glTexCoord2f(0, 0); // top left glVertex2f(0, 0); // top left @@ -63,22 +43,45 @@ GLuint draw_clip(QOpenGLContext* ctx, QOpenGLFramebufferObject* fbo, GLuint text glTexCoord2f(0, 1); // bottom left glVertex2f(0, 1); // bottom left glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); - -// fbo->release(); - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - - // restore previous blendFunc - ctx->functions()->glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha); - - //if (default_fbo != nullptr) default_fbo->bind(); 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(QOpenGLContext* ctx, - Clip* c, +void process_effect(Clip* c, Effect* e, double timecode, GLTextureCoords& coords, @@ -90,20 +93,33 @@ void process_effect(QOpenGLContext* ctx, if (e->enable_coords) { e->process_coords(timecode, coords, data); } - if ((e->enable_shader && shaders_are_enabled) || e->enable_superimpose) { + bool can_process_shaders = (e->enable_shader && shaders_are_enabled); + if (can_process_shaders || e->enable_superimpose) { e->startEffect(); - if ((e->enable_shader && shaders_are_enabled) && e->is_glsl_linked()) { + if (can_process_shaders && e->is_glsl_linked()) { e->process_shader(timecode, coords); - composite_texture = draw_clip(ctx, c->fbo[fbo_switcher], composite_texture, true); + 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 { - composite_texture = draw_clip(ctx, c->fbo[!fbo_switcher], superimpose_texture, false); + // 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(); @@ -111,36 +127,23 @@ void process_effect(QOpenGLContext* ctx, } } -GLuint compose_sequence(Viewer* viewer, - QOpenGLContext* ctx, - Sequence* seq, - QVector& nests, - bool video, - bool render_audio, - Effect** gizmos, - bool& texture_failed, - bool rendering, - int playback_speed) { - GLint current_fbo = 0; - if (video) { - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - } +GLuint compose_sequence(ComposeSequenceParams ¶ms) { + GLuint final_fbo = params.main_buffer; - Sequence* s = seq; + Sequence* s = params.seq; long playhead = s->playhead; - if (!nests.isEmpty()) { - for (int i=0;imedia->to_sequence(); - playhead += nests.at(i)->clip_in - nests.at(i)->get_timeline_in_with_transition(); - playhead = refactor_frame_number(playhead, nests.at(i)->sequence->frame_rate, s->frame_rate); + if (!params.nests.isEmpty()) { + for (int i=0;imedia->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 (video && nests.last()->fbo != nullptr) { - nests.last()->fbo[0]->bind(); + if (params.video && params.nests.last()->fbo != nullptr) { + params.nests.last()->fbo[0]->bind(); glClear(GL_COLOR_BUFFER_BIT); -// nests.last()->fbo[0]->release(); - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + final_fbo = params.nests.last()->fbo[0]->handle(); } } @@ -148,53 +151,85 @@ GLuint compose_sequence(Viewer* viewer, QVector current_clips; + // loop through clips, find currently active, and sort by track for (int i=0;iclips.size();i++) { + Clip* c = s->clips.at(i); - // if clip starts within one second and/or hasn't finished yet if (c != nullptr) { -// if (!(!nests.isEmpty() && !same_sign(c->track, nests.last()->track))) { - if ((c->track < 0) == video) { + + // 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) { Footage* 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)) { - // if thread is already working, we don't want to touch this, - // but we also don't want to hang the UI thread + + // open if not open if (!c->open) { - open_clip(c, !rendering); + open_clip(c, !params.rendering); } + 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 { - //qWarning() << "Media '" + m->name + "' was not ready, retrying..."; - texture_failed = true; + + // 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, !rendering); + if (!c->open) open_clip(c, !params.rendering); 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; - for (int j=0;jtrack < c->track) { - current_clips.insert(j, c); - added = true; - break; + + // 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;jtrack < c->track) { + current_clips.insert(j, c); + added = true; + break; + } } + } + if (!added) { current_clips.append(c); } @@ -203,101 +238,115 @@ GLuint compose_sequence(Viewer* viewer, } } - int half_width = s->width/2; - int half_height = s->height/2; - - if (video) { + 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;imedia != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { qWarning() << "Tried to display clip" << i << "but it's closed"; - texture_failed = true; + params.texture_failed = true; } else { + // if clip is a video clip if (c->track < 0) { - ctx->functions()->GL_DEFAULT_BLEND; + // 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 (c->media != nullptr) { - switch (c->media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - // set up opengl texture - if (c->texture == nullptr) { - c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height); - c->texture->setFormat(get_gl_tex_fmt_from_av(c->pix_fmt)); - c->texture->setMipLevels(c->texture->maximumMipLevels()); - c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8); - } - get_clip_frame(c, qMax(playhead, c->timeline_in), texture_failed); - textureID = c->texture->textureId(); - break; - case MEDIA_TYPE_SEQUENCE: - textureID = -1; - break; + // 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(get_gl_tex_fmt_from_av(c->pix_fmt)); + c->texture->setMipLevels(c->texture->maximumMipLevels()); + c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); + c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), 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; } } - if (textureID == 0 && c->media != nullptr) { - qWarning() << "Texture hasn't been created yet"; - texture_failed = true; - } else if (playhead >= c->get_timeline_in_with_transition()) { + // 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* [fbo_count]; + + for (int j=0;jfbo[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(); - // start preparing cache - if (c->fbo == nullptr) { - c->fbo = new QOpenGLFramebufferObject* [2]; - c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height); - c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height); - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - } - - // clear fbos - /*c->fbo[0]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[0]->release(); - c->fbo[1]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[1]->release();*/ - - + // simple bool for switching between the two framebuffers bool fbo_switcher = false; glViewport(0, 0, video_width, video_height); - GLuint composite_texture; + 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 && !c->media->to_footage()->alpha_is_premultiplied) { + // alpha is not premultiplied, we'll need to premultiply it for the rest of the pipeline + params.premultiply_program->bind(); + + textureID = draw_clip(c->fbo[0], textureID, true); + + params.premultiply_program->release(); - if (c->media == nullptr) { - c->fbo[fbo_switcher]->bind(); - glClear(GL_COLOR_BUFFER_BIT); -// c->fbo[fbo_switcher]->release(); - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - composite_texture = c->fbo[fbo_switcher]->texture(); - } else { - // for nested sequences - if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) { - nests.append(c); - textureID = compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering, false); - nests.removeLast(); fbo_switcher = true; } - - composite_texture = draw_clip(ctx, c->fbo[fbo_switcher], textureID, true); } - fbo_switcher = !fbo_switcher; - - // set up default coords + // set up default coordinates for drawing the clip GLTextureCoords coords; coords.grid_size = 1; coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; @@ -308,8 +357,10 @@ GLuint compose_sequence(Viewer* viewer, 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; - // set up autoscale + // 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); @@ -317,143 +368,203 @@ GLuint compose_sequence(Viewer* viewer, glScalef(scale_multiplier, scale_multiplier, 1); } - // EFFECT CODE START + // == EFFECT CODE START == + + // get current sequence time in seconds (used for effects) double timecode = get_timecode(c, playhead); + // set up variables for gizmos later Effect* first_gizmo_effect = nullptr; Effect* selected_effect = nullptr; + // run through all of the clip's effects for (int j=0;jeffects.size();j++) { Effect* e = c->effects.at(j); - process_effect(ctx, c, e, timecode, coords, composite_texture, fbo_switcher, texture_failed, TA_NO_TRANSITION); + process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, TA_NO_TRANSITION); + // 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) { - (*gizmos) = selected_effect; + (*params.gizmos) = selected_effect; } else if (is_clip_selected(c, true)) { - (*gizmos) = first_gizmo_effect; + (*params.gizmos) = first_gizmo_effect; } + // if the clip has an opening transition, process that now if (c->get_opening_transition() != nullptr) { int transition_progress = playhead - c->get_timeline_in_with_transition(); if (transition_progress < c->get_opening_transition()->get_length()) { - process_effect(ctx, c, c->get_opening_transition(), (double)transition_progress/(double)c->get_opening_transition()->get_length(), coords, composite_texture, fbo_switcher, texture_failed, TA_OPENING_TRANSITION); + process_effect(c, c->get_opening_transition(), double(transition_progress)/double(c->get_opening_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, TA_OPENING_TRANSITION); } } + // if the clip has a closing transition, process that now if (c->get_closing_transition() != nullptr) { int transition_progress = playhead - (c->get_timeline_out_with_transition() - c->get_closing_transition()->get_length()); if (transition_progress >= 0 && transition_progress < c->get_closing_transition()->get_length()) { - process_effect(ctx, c, c->get_closing_transition(), (double)transition_progress/(double)c->get_closing_transition()->get_length(), coords, composite_texture, fbo_switcher, texture_failed, TA_CLOSING_TRANSITION); + process_effect(c, c->get_closing_transition(), double(transition_progress)/double(c->get_closing_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, TA_CLOSING_TRANSITION); } } - // EFFECT CODE END - if (!nests.isEmpty()) { - nests.last()->fbo[0]->bind(); - } - glViewport(0, 0, s->width, s->height); + // == EFFECT CODE END == - glBindTexture(GL_TEXTURE_2D, composite_texture); + if (textureID > 0) { + // set viewport to sequence size + params.ctx->functions()->glViewport(0, 0, s->width, s->height); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBegin(GL_QUADS); - if (coords.grid_size <= 1) { - float z = 0.0f; + // == 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 - glVertex3f(coords.vertexTopLeftX, coords.vertexTopLeftY, z); // top left + glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right - glVertex3f(coords.vertexTopRightX, coords.vertexTopRightY, z); // top right + glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right - glVertex3f(coords.vertexBottomRightX, coords.vertexBottomRightY, z); // bottom right + glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left - glVertex3f(coords.vertexBottomLeftX, coords.vertexBottomLeftY, z); // bottom left - } else { - float rows = coords.grid_size; - float cols = coords.grid_size; + glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left - for (float k=0;kfunctions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glTexCoord2f(float_lerp(coords.textureTopLeftX, coords.textureTopRightX, col_prog), float_lerp(coords.textureTopLeftY, coords.textureBottomLeftY, row_prog)); // top left - glVertex2f(float_lerp(vertexTLX, vertexTRX, col_prog), float_lerp(vertexTLY, vertexBLY, row_prog)); // top left - glTexCoord2f(float_lerp(coords.textureTopLeftX, coords.textureTopRightX, next_col_prog), float_lerp(coords.textureTopRightY, coords.textureBottomRightY, row_prog)); // top right - glVertex2f(float_lerp(vertexTLX, vertexTRX, next_col_prog), float_lerp(vertexTRY, vertexBRY, row_prog)); // top right - glTexCoord2f(float_lerp(coords.textureBottomLeftX, coords.textureBottomRightX, next_col_prog), float_lerp(coords.textureTopRightY, coords.textureBottomRightY, next_row_prog)); // bottom right - glVertex2f(float_lerp(vertexBLX, vertexBRX, next_col_prog), float_lerp(vertexTRY, vertexBRY, next_row_prog)); // bottom right - glTexCoord2f(float_lerp(coords.textureBottomLeftX, coords.textureBottomRightX, col_prog), float_lerp(coords.textureTopLeftY, coords.textureBottomLeftY, next_row_prog)); // bottom left - glVertex2f(float_lerp(vertexBLX, vertexBRX, col_prog), float_lerp(vertexTLY, vertexBLY, next_row_prog)); // bottom left - } + + + // == END RENDER CLIP IN CONTEXT OF SEQUENCE == + + + + // + // + // PROCESS POST-SHADERS + // + // + + + + // copy front buffer to back buffer + 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); + + // 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 == } - glEnd(); - - glBindTexture(GL_TEXTURE_2D, 0); // unbind texture - // prepare gizmos - if ((*gizmos) != nullptr - && nests.isEmpty() - && ((*gizmos) == first_gizmo_effect - || (*gizmos) == selected_effect)) { - (*gizmos)->gizmo_draw(timecode, coords); // set correct gizmo coords - (*gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords - } - - if (!nests.isEmpty()) { - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + 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(); - - /*GLfloat motion_blur_frac = (GLfloat) motion_blur_prog / (GLfloat) motion_blur_lim; - if (motion_blur_prog == 0) { - glAccum(GL_LOAD, motion_blur_frac); - } else { - glAccum(GL_ACCUM, motion_blur_frac); - } - motion_blur_prog++;*/ } } else { - if (render_audio || (config.enable_audio_scrubbing && audio_scrub && seq->playhead > c->timeline_in)) { + if (params.render_audio || (config.enable_audio_scrubbing && audio_scrub && params.seq->playhead > c->timeline_in)) { if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { - nests.append(c); - compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering, playback_speed); - nests.removeLast(); + params.nests.append(c); + compose_sequence(params); + params.nests.removeLast(); } else { if (c->lock.tryLock()) { // clip is not caching, start caching audio - cache_clip(c, playhead, c->audio_reset, !render_audio, nests, playback_speed); + cache_clip(c, playhead, c->audio_reset, !params.render_audio, params.nests, params.playback_speed); c->lock.unlock(); } } } // visually update all the keyframe values - if (c->sequence == seq) { // only if you can currently see them + 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;ieffects.size();i++) { Effect* e = c->effects.at(i); @@ -469,24 +580,32 @@ GLuint compose_sequence(Viewer* viewer, } } - if (audio_track_count == 0 && viewer != nullptr) { - viewer->play_wake(); + if (audio_track_count == 0 && params.viewer != nullptr) { + params.viewer->play_wake(); } - if (video) { + if (params.video) { glPopMatrix(); } - if (!nests.isEmpty() && nests.last()->fbo != nullptr) { + if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) { // returns nested clip's texture - return nests.last()->fbo[0]->texture(); + return params.nests.last()->fbo[0]->texture(); } return 0; } void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio, int playback_speed) { - QVector nests; - bool texture_failed; - compose_sequence(viewer, nullptr, seq, nests, false, render_audio, nullptr, texture_failed, audio_rendering, playback_speed); + ComposeSequenceParams params; + params.viewer = viewer; + params.ctx = nullptr; + params.seq = seq; + params.video = false; + params.render_audio = render_audio; + params.gizmos = nullptr; + params.rendering = audio_rendering; + params.playback_speed = playback_speed; + params.blend_mode_program = nullptr; + compose_sequence(params); } diff --git a/ui/renderfunctions.h b/ui/renderfunctions.h index 6523a16ba..0830d6cb4 100644 --- a/ui/renderfunctions.h +++ b/ui/renderfunctions.h @@ -6,19 +6,32 @@ class Effect; class Viewer; +class QOpenGLShaderProgram; struct Sequence; struct Clip; -GLuint compose_sequence(Viewer* viewer, - QOpenGLContext* ctx, - Sequence* seq, - QVector& nests, - bool video, - bool render_audio, - Effect **gizmos, - bool &texture_failed, - bool rendering, - int playback_speed); +struct ComposeSequenceParams { + Viewer* viewer; + QOpenGLContext* ctx; + Sequence* seq; + QVector nests; + bool video; + bool render_audio; + Effect** gizmos; + bool texture_failed; + bool rendering; + int playback_speed; + QOpenGLShaderProgram* blend_mode_program; + QOpenGLShaderProgram* premultiply_program; + GLuint main_buffer; + GLuint main_attachment; + GLuint backend_buffer1; + GLuint backend_attachment1; + GLuint backend_buffer2; + GLuint backend_attachment2; +}; + +GLuint compose_sequence(ComposeSequenceParams ¶ms); void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio, int playback_speed); diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp index a4ee4da23..a8ef4fee5 100644 --- a/ui/renderthread.cpp +++ b/ui/renderthread.cpp @@ -10,11 +10,13 @@ #include "project/sequence.h" RenderThread::RenderThread() : - frameBuffer(0), - texColorBuffer(0), + 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), @@ -41,47 +43,94 @@ void RenderThread::run() { } queued = false; - if (share_ctx != nullptr) { if (ctx != nullptr) { ctx->makeCurrent(&surface); // gen fbo - if (frameBuffer == 0) { + if (front_buffer == 0) { + // delete any existing framebuffers delete_fbo(); - ctx->functions()->glGenFramebuffers(1, &frameBuffer); + + // create framebuffers + ctx->functions()->glGenFramebuffers(1, &front_buffer); + ctx->functions()->glGenFramebuffers(1, &back_buffer_1); + ctx->functions()->glGenFramebuffers(1, &back_buffer_2); } - // bind - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer); // gen texture - if (texColorBuffer == 0 || tex_width != seq->width || tex_height != seq->height) { - delete_texture(); - glGenTextures(1, &texColorBuffer); - glBindTexture(GL_TEXTURE_2D, texColorBuffer); - glTexImage2D( - GL_TEXTURE_2D, 0, GL_RGB, seq->width, seq->height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr - ); + if (front_texture == 0 || tex_width != seq->width || tex_height != seq->height) { + // cache texture size tex_width = seq->width; tex_height = seq->height; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - ctx->functions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0 - ); - glBindTexture(GL_TEXTURE_2D, 0); + + // 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); + } } - // draw + 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 -// glFlush(); - glFinish(); + ctx->functions()->glFinish(); // release - ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0); + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); emit ready(); } @@ -96,8 +145,6 @@ void RenderThread::run() { void RenderThread::paint() { glLoadIdentity(); - texture_failed = false; - glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -109,15 +156,35 @@ void RenderThread::paint() { glEnable(GL_DEPTH); gizmos = nullptr; - QVector nests; - compose_sequence(nullptr, ctx, seq, nests, true, false, &gizmos, texture_failed, false, temp_reverse); + + ComposeSequenceParams params; + params.viewer = nullptr; + params.ctx = ctx; + params.seq = seq; + params.video = true; + params.texture_failed = false; + params.render_audio = false; + params.gizmos = &gizmos; + params.rendering = 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, frameBuffer); + 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); @@ -127,7 +194,7 @@ void RenderThread::paint() { } if (pixel_buffer != nullptr) { - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer); glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer); ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); pixel_buffer = nullptr; @@ -138,14 +205,12 @@ void RenderThread::paint() { glDisable(GL_TEXTURE_2D); } -void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int idivider, bool itemp_reverse) { +void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int idivider) { seq = s; // stall any dependent actions texture_failed = true; - temp_reverse = itemp_reverse; - if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) { share_ctx = share; delete_ctx(); @@ -175,24 +240,37 @@ void RenderThread::cancel() { } void RenderThread::delete_texture() { - if (texColorBuffer > 0) { - ctx->functions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 - ); - glDeleteTextures(1, &texColorBuffer); + if (front_texture > 0) { + GLuint tex[3] = {front_texture, back_texture_1, back_texture_2}; + glDeleteTextures(3, tex); } - texColorBuffer = 0; + front_texture = 0; + back_texture_1 = 0; + back_texture_2 = 0; } void RenderThread::delete_fbo() { - if (frameBuffer > 0) { - ctx->functions()->glDeleteFramebuffers(1, &frameBuffer); + if (front_buffer > 0) { + GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2}; + ctx->functions()->glDeleteFramebuffers(3, fbos); } - frameBuffer = 0; + 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(); diff --git a/ui/renderthread.h b/ui/renderthread.h index 54eecd646..201839dce 100644 --- a/ui/renderthread.h +++ b/ui/renderthread.h @@ -1,4 +1,4 @@ -#ifndef RENDERTHREAD_H +#ifndef RENDERTHREAD_H #define RENDERTHREAD_H #include @@ -7,6 +7,7 @@ #include #include #include +#include struct Sequence; class Effect; @@ -18,11 +19,11 @@ public: ~RenderThread(); void run(); QMutex mutex; - GLuint frameBuffer; - GLuint texColorBuffer; + GLuint front_buffer; + GLuint front_texture; Effect* gizmos; void paint(); - void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0, bool itemp_reverse = false); + void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0); bool did_texture_fail(); void cancel(); @@ -35,11 +36,20 @@ 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; + Sequence* seq; int divider; int tex_width; @@ -47,7 +57,6 @@ private: bool queued; bool texture_failed; bool running; - bool temp_reverse; QString save_fn; GLvoid *pixel_buffer; }; diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index b215fbc88..976d5dedf 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -834,7 +834,6 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { // default audio effects (after custom effects) c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT))); c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT))); - //c->media_type = MEDIA_TYPE_TONE; } push_undo = true; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 196968d58..648199df2 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -562,7 +562,7 @@ void ViewerWidget::paintGL() { // draw texture from render thread - glBindTexture(GL_TEXTURE_2D, renderer->texColorBuffer); + glBindTexture(GL_TEXTURE_2D, renderer->front_texture); glBegin(GL_QUADS); @@ -592,7 +592,7 @@ void ViewerWidget::paintGL() { glDisable(GL_TEXTURE_2D); if (window != nullptr && window->isVisible()) { - window->set_texture(renderer->texColorBuffer, double(viewer->seq->width)/double(viewer->seq->height), &renderer->mutex); + window->set_texture(renderer->front_texture, double(viewer->seq->width)/double(viewer->seq->height), &renderer->mutex); } renderer->mutex.unlock(); diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index a4909ac5f..098a1201c 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -25,7 +25,7 @@ class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions { Q_OBJECT public: - ViewerWidget(QWidget *parent = 0); + ViewerWidget(QWidget *parent = nullptr); ~ViewerWidget(); void delete_function();