diff --git a/debug.cpp b/debug.cpp index e948b5e51..b64e38ecd 100644 --- a/debug.cpp +++ b/debug.cpp @@ -34,79 +34,79 @@ QFile debug_file; QTextStream debug_stream; void open_debug_file() { - QDir debug_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); - debug_dir.mkpath("."); - if (debug_dir.exists()) { - debug_file.setFileName(debug_dir.path() + "/debug_log"); - if (debug_file.open(QFile::WriteOnly)) { - debug_stream.setDevice(&debug_file); - } else { - qWarning() << "Couldn't open debug log file, debug log will not be saved"; - } + QDir debug_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + debug_dir.mkpath("."); + if (debug_dir.exists()) { + debug_file.setFileName(debug_dir.path() + "/debug_log"); + if (debug_file.open(QFile::WriteOnly)) { + debug_stream.setDevice(&debug_file); + } else { + qWarning() << "Couldn't open debug log file, debug log will not be saved"; } + } } void close_debug_file() { - if (debug_file.isOpen()) { - debug_file.close(); - } + if (debug_file.isOpen()) { + debug_file.close(); + } } void debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - debug_mutex.lock(); - const QByteArray localMsg = msg.toLocal8Bit(); - const QDateTime now = QDateTime::currentDateTime(); - const QByteArray timeRepr(now.toString(Qt::ISODate).toLocal8Bit()); - QString msgTag; - QString fontColor; - switch (type) { - case QtDebugMsg: - msgTag = "DEBUG"; - fontColor = "grey"; - break; - case QtInfoMsg: - msgTag = "INFO"; - fontColor = "blue"; - break; - case QtWarningMsg: - msgTag = "WARNING"; - fontColor = "yellow"; - break; - case QtCriticalMsg: - msgTag = "ERROR"; - fontColor = "red"; - break; - case QtFatalMsg: - msgTag = "FATAL"; - fontColor = "red"; - break; - default: - fprintf(stderr, "Unknown debug msg type"); - fflush(stderr); - break; - }//switch + debug_mutex.lock(); + const QByteArray localMsg = msg.toLocal8Bit(); + const QDateTime now = QDateTime::currentDateTime(); + const QByteArray timeRepr(now.toString(Qt::ISODate).toLocal8Bit()); + QString msgTag; + QString fontColor; + switch (type) { + case QtDebugMsg: + msgTag = "DEBUG"; + fontColor = "grey"; + break; + case QtInfoMsg: + msgTag = "INFO"; + fontColor = "blue"; + break; + case QtWarningMsg: + msgTag = "WARNING"; + fontColor = "yellow"; + break; + case QtCriticalMsg: + msgTag = "ERROR"; + fontColor = "red"; + break; + case QtFatalMsg: + msgTag = "FATAL"; + fontColor = "red"; + break; + default: + fprintf(stderr, "Unknown debug msg type"); + fflush(stderr); + break; + }//switch - /*fprintf(stderr, "%s [%s] %s (%s:%u, %s)\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data(), + /*fprintf(stderr, "%s [%s] %s (%s:%u, %s)\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data(), context.file, context.line, context.function);*/ - fprintf(stderr, "%s [%s] %s\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data()); + fprintf(stderr, "%s [%s] %s\n", timeRepr.data(), msgTag.toLocal8Bit().constData(), localMsg.data()); - if (debug_file.isOpen()) { - debug_stream << QString("[%1] %2 (%3:%4, %5)\n") - .arg(msgTag, localMsg, context.file, QString::number(context.line), context.function); - } - debug_info.prepend(QString("[%2] %3 (%4:%5, %6)
") - .arg(fontColor, msgTag, localMsg, context.file, QString::number(context.line), context.function)); - fflush(stderr); - if (olive::DebugDialog != nullptr && olive::DebugDialog->isVisible()) { - QMetaObject::invokeMethod(olive::DebugDialog, "update_log", Qt::QueuedConnection); - } - debug_mutex.unlock(); + if (debug_file.isOpen()) { + debug_stream << QString("[%1] %2 (%3:%4, %5)\n") + .arg(msgTag, localMsg, context.file, QString::number(context.line), context.function); + } + debug_info.prepend(QString("[%2] %3 (%4:%5, %6)
") + .arg(fontColor, msgTag, localMsg, context.file, QString::number(context.line), context.function)); + fflush(stderr); + if (olive::DebugDialog != nullptr && olive::DebugDialog->isVisible()) { + QMetaObject::invokeMethod(olive::DebugDialog, "update_log", Qt::QueuedConnection); + } + debug_mutex.unlock(); } const QString &get_debug_str() { - return debug_info; + return debug_info; } diff --git a/effects/add.blend b/effects/add.blend new file mode 100644 index 000000000..886c2e617 --- /dev/null +++ b/effects/add.blend @@ -0,0 +1,14 @@ +float blendAdd(float base, float blend) { + return min(base+blend,1.0); +} + +vec3 blendAdd(vec3 base, vec3 blend) { + return min(base+blend,vec3(1.0)); +} + +vec3 blendAdd(vec3 base, vec3 blend, float opacity) { + return (blendAdd(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendAdd) +#olive name Add \ No newline at end of file diff --git a/effects/average.blend b/effects/average.blend new file mode 100644 index 000000000..098e734a3 --- /dev/null +++ b/effects/average.blend @@ -0,0 +1,10 @@ +vec3 blendAverage(vec3 base, vec3 blend) { + return (base+blend)/2.0; +} + +vec3 blendAverage(vec3 base, vec3 blend, float opacity) { + return (blendAverage(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendAverage) +#olive name Average \ No newline at end of file diff --git a/effects/color-burn.blend b/effects/color-burn.blend new file mode 100644 index 000000000..4cea15cd4 --- /dev/null +++ b/effects/color-burn.blend @@ -0,0 +1,14 @@ +float blendColorBurn(float base, float blend) { + return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); +} + +vec3 blendColorBurn(vec3 base, vec3 blend) { + return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); +} + +vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { + return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendColorBurn) +#olive name Color Burn \ No newline at end of file diff --git a/effects/color-dodge.blend b/effects/color-dodge.blend new file mode 100644 index 000000000..495ff401c --- /dev/null +++ b/effects/color-dodge.blend @@ -0,0 +1,14 @@ +float blendColorDodge(float base, float blend) { + return (blend==1.0)?blend:min(base/(1.0-blend),1.0); +} + +vec3 blendColorDodge(vec3 base, vec3 blend) { + return vec3(blendColorDodge(base.r,blend.r),blendColorDodge(base.g,blend.g),blendColorDodge(base.b,blend.b)); +} + +vec3 blendColorDodge(vec3 base, vec3 blend, float opacity) { + return (blendColorDodge(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendColorDodge) +#olive name Color Dodge \ No newline at end of file diff --git a/effects/darken.blend b/effects/darken.blend new file mode 100644 index 000000000..f5a81931f --- /dev/null +++ b/effects/darken.blend @@ -0,0 +1,14 @@ +float blendDarken(float base, float blend) { + return min(blend,base); +} + +vec3 blendDarken(vec3 base, vec3 blend) { + return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b)); +} + +vec3 blendDarken(vec3 base, vec3 blend, float opacity) { + return (blendDarken(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendDarken) +#olive name Darken \ No newline at end of file diff --git a/effects/difference.blend b/effects/difference.blend new file mode 100644 index 000000000..e65ba45eb --- /dev/null +++ b/effects/difference.blend @@ -0,0 +1,10 @@ +vec3 blendDifference(vec3 base, vec3 blend) { + return abs(base-blend); +} + +vec3 blendDifference(vec3 base, vec3 blend, float opacity) { + return (blendDifference(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendDifference) +#olive name Difference \ No newline at end of file diff --git a/effects/exclusion.blend b/effects/exclusion.blend new file mode 100644 index 000000000..292fe17ce --- /dev/null +++ b/effects/exclusion.blend @@ -0,0 +1,10 @@ +vec3 blendExclusion(vec3 base, vec3 blend) { + return base+blend-2.0*base*blend; +} + +vec3 blendExclusion(vec3 base, vec3 blend, float opacity) { + return (blendExclusion(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendExclusion) +#olive name Exclusion \ No newline at end of file diff --git a/effects/glow.blend b/effects/glow.blend new file mode 100644 index 000000000..a770cb917 --- /dev/null +++ b/effects/glow.blend @@ -0,0 +1,12 @@ +#pragma glslify: blendReflect = require(./reflect) + +vec3 blendGlow(vec3 base, vec3 blend) { + return blendReflect(blend,base); +} + +vec3 blendGlow(vec3 base, vec3 blend, float opacity) { + return (blendGlow(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendGlow) +#olive name Glow \ No newline at end of file diff --git a/effects/hard-light.blend b/effects/hard-light.blend new file mode 100644 index 000000000..ed917e72d --- /dev/null +++ b/effects/hard-light.blend @@ -0,0 +1,12 @@ +#pragma glslify: blendOverlay = require(./overlay) + +vec3 blendHardLight(vec3 base, vec3 blend) { + return blendOverlay(blend,base); +} + +vec3 blendHardLight(vec3 base, vec3 blend, float opacity) { + return (blendHardLight(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendHardLight) +#olive name Hard Light \ No newline at end of file diff --git a/effects/hard-mix.blend b/effects/hard-mix.blend new file mode 100644 index 000000000..4ba29a4dc --- /dev/null +++ b/effects/hard-mix.blend @@ -0,0 +1,16 @@ +#pragma glslify: blendVividLight = require(./vivid-light) + +float blendHardMix(float base, float blend) { + return (blendVividLight(base,blend)<0.5)?0.0:1.0; +} + +vec3 blendHardMix(vec3 base, vec3 blend) { + return vec3(blendHardMix(base.r,blend.r),blendHardMix(base.g,blend.g),blendHardMix(base.b,blend.b)); +} + +vec3 blendHardMix(vec3 base, vec3 blend, float opacity) { + return (blendHardMix(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendHardMix) +#olive name Hard Mix \ No newline at end of file diff --git a/effects/internal/blending.frag b/effects/internal/blending.frag deleted file mode 100644 index c8d14d593..000000000 --- a/effects/internal/blending.frag +++ /dev/null @@ -1,207 +0,0 @@ -#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) { - if (blendmode == BLEND_MODE_AVERAGE) { - return (base+blend)/2.0; - } else if (blendmode == 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)); - } else if (blendmode == 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)); - } else if (blendmode == BLEND_MODE_DARKEN) { - return vec3(blend_darken(base.r, blend.r), blend_darken(base.g, blend.g), blend_darken(base.b, blend.b)); - } else if (blendmode == BLEND_MODE_DIFFERENCE) { - return abs(base-blend); - } else if (blendmode == BLEND_MODE_EXCLUSION) { - return base+blend-2.0*base*blend; - } else if (blendmode == BLEND_MODE_GLOW) { - return blend_reflect(blend, base); - } else if (blendmode == BLEND_MODE_HARDLIGHT) { - return blend_overlay(blend,base); - } else if (blendmode == 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)); - } else if (blendmode == BLEND_MODE_LIGHTEN) { - return vec3(blend_lighten(base.r,blend.r),blend_lighten(base.g,blend.g),blend_lighten(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_LINEARBURN || blendmode == BLEND_MODE_SUBTRACT) { - return blend_linear_burn(base, blend); - } else if (blendmode == BLEND_MODE_LINEARDODGE || blendmode == BLEND_MODE_ADD) { - return blend_linear_dodge(base, blend); - } else if (blendmode == 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)); - } else if (blendmode == BLEND_MODE_MULTIPLY) { - return (base * blend); - } else if (blendmode == BLEND_MODE_NEGATION) { - return vec3(1.0)-abs(vec3(1.0)-base-blend); - } else if (blendmode == BLEND_MODE_OVERLAY) { - return blend_overlay(base, blend); - } else if (blendmode == BLEND_MODE_PHOENIX) { - return min(base,blend)-max(base,blend)+vec3(1.0); - } else if (blendmode == 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)); - } else if (blendmode == BLEND_MODE_REFLECT) { - return blend_reflect(base, blend); - } else if (blendmode == BLEND_MODE_SCREEN) { - return vec3(blend_screen(base.r,blend.r),blend_screen(base.g,blend.g),blend_screen(base.b,blend.b)); - } else if (blendmode == BLEND_MODE_SUBSTRACT) { - return max(base+blend-vec3(1.0),vec3(0.0)); - } else if (blendmode == 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)); - } else if (blendmode == 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)); - } else { - 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; - - if (blendmode == BLEND_MODE_OVERLAY - || blendmode == BLEND_MODE_LIGHTEN - || blendmode == BLEND_MODE_SCREEN - || blendmode == BLEND_MODE_COLORDODGE - || blendmode == BLEND_MODE_LINEARDODGE - || blendmode == BLEND_MODE_ADD - || blendmode == BLEND_MODE_SOFTLIGHT - || blendmode == BLEND_MODE_NEGATION - || blendmode == BLEND_MODE_AVERAGE - || blendmode == BLEND_MODE_REFLECT - || blendmode == BLEND_MODE_EXCLUSION - || blendmode == BLEND_MODE_DIFFERENCE) { - composite *= fg_color.a; - } - - vec4 full_composite = vec4(composite + bg_color.rgb*(1.0-fg_color.a), 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, opacity); - - // output to color - gl_FragColor = full_composite; -} \ No newline at end of file diff --git a/effects/internal/internalshaders.qrc b/effects/internal/internalshaders.qrc index 39b1a3aee..ae51e0b27 100644 --- a/effects/internal/internalshaders.qrc +++ b/effects/internal/internalshaders.qrc @@ -1,6 +1,5 @@ - blending.frag common.vert cornerpin.frag cornerpin.vert diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index 07dd08d3f..84216f2ab 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -36,6 +36,7 @@ #include "io/math.h" #include "ui/labelslider.h" #include "ui/comboboxex.h" +#include "project/effectloaders.h" #include "panels/project.h" #include "debug.h" @@ -44,230 +45,212 @@ #include "ui/viewerwidget.h" TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { - enable_coords = true; + enable_coords = true; - 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* 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")); - 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); - scale_y = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scaley"); // scale Y (disabled if uniform scale is selected) - scale_y->set_double_minimum_value(0); - scale_y->set_double_maximum_value(3000); + 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); + scale_y = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scaley"); // scale Y (disabled if uniform scale is selected) + scale_y->set_double_minimum_value(0); + scale_y->set_double_maximum_value(3000); - 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* 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")); - rotation = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "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")); - 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* 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")); - opacity = opacity_row->add_field(EFFECT_FIELD_DOUBLE, "opacity"); // opacity - opacity->set_double_minimum_value(0); - opacity->set_double_maximum_value(100); + 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")); - blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO, "blendmode", 2); // blend mode - 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); + EffectRow* blend_mode_row = add_row(tr("Blend Mode")); + blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO, "blendmode", 2); // blend mode + blend_mode_box->add_combo_item(tr("Normal"), -1); - // set up gizmos - top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); - top_left_gizmo->x_field1 = scale_x; + // add loaded blending modes + olive::effects_loaded.lock(); + for (int i=0;iadd_combo_item(olive::blend_modes.at(i).name, i); + } + olive::effects_loaded.unlock(); - top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_center_gizmo->set_cursor(Qt::SizeVerCursor); - top_center_gizmo->y_field1 = scale_x; + // set up gizmos + top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); + top_left_gizmo->x_field1 = scale_x; - top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); - top_right_gizmo->x_field1 = scale_x; + top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_center_gizmo->set_cursor(Qt::SizeVerCursor); + top_center_gizmo->y_field1 = scale_x; - bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); - bottom_left_gizmo->x_field1 = scale_x; + top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); + top_right_gizmo->x_field1 = scale_x; - bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); - bottom_center_gizmo->y_field1 = scale_x; + bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); + bottom_left_gizmo->x_field1 = scale_x; - bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); - bottom_right_gizmo->x_field1 = scale_x; + bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); + bottom_center_gizmo->y_field1 = scale_x; - left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - left_center_gizmo->set_cursor(Qt::SizeHorCursor); - left_center_gizmo->x_field1 = scale_x; + bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); + bottom_right_gizmo->x_field1 = scale_x; - right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - right_center_gizmo->set_cursor(Qt::SizeHorCursor); - right_center_gizmo->x_field1 = scale_x; + left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + left_center_gizmo->set_cursor(Qt::SizeHorCursor); + left_center_gizmo->x_field1 = scale_x; - anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); - anchor_gizmo->set_cursor(Qt::SizeAllCursor); - anchor_gizmo->x_field1 = anchor_x_box; - anchor_gizmo->y_field1 = anchor_y_box; - anchor_gizmo->x_field2 = position_x; - anchor_gizmo->y_field2 = position_y; + right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + right_center_gizmo->set_cursor(Qt::SizeHorCursor); + right_center_gizmo->x_field1 = scale_x; - rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); - rotate_gizmo->color = Qt::green; - rotate_gizmo->set_cursor(Qt::SizeAllCursor); - rotate_gizmo->x_field1 = rotation; + anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); + anchor_gizmo->set_cursor(Qt::SizeAllCursor); + anchor_gizmo->x_field1 = anchor_x_box; + anchor_gizmo->y_field1 = anchor_y_box; + anchor_gizmo->x_field2 = position_x; + anchor_gizmo->y_field2 = position_y; - rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); - rect_gizmo->x_field1 = position_x; - rect_gizmo->y_field1 = position_y; + rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); + rotate_gizmo->color = Qt::green; + rotate_gizmo->set_cursor(Qt::SizeAllCursor); + rotate_gizmo->x_field1 = rotation; - connect(uniform_scale_field, SIGNAL(toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); + rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); + rect_gizmo->x_field1 = position_x; + rect_gizmo->y_field1 = position_y; - // set defaults - uniform_scale_field->set_bool_value(true); - blend_mode_box->set_combo_index(0); - set = false; - refresh(); + connect(uniform_scale_field, SIGNAL(toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); + + // set defaults + uniform_scale_field->set_bool_value(true); + blend_mode_box->set_combo_index(0); + set = false; + refresh(); } void adjust_field(EffectField* field, double old_offset, double new_offset) { - if (field->keyframes.size() > 0) { - for (int i=0;ikeyframes.size();i++) { - field->keyframes[i].data = field->keyframes.at(i).data.toDouble() - old_offset + new_offset; - } - } else { - field->set_current_data(field->get_current_data().toDouble() - old_offset + new_offset); - } + if (field->keyframes.size() > 0) { + for (int i=0;ikeyframes.size();i++) { + field->keyframes[i].data = field->keyframes.at(i).data.toDouble() - old_offset + new_offset; + } + } else { + field->set_current_data(field->get_current_data().toDouble() - old_offset + new_offset); + } } void TransformEffect::refresh() { - if (parent_clip != nullptr && parent_clip->sequence != nullptr) { - double new_default_pos_x = parent_clip->sequence->width/2; - double new_default_pos_y = parent_clip->sequence->height/2; + if (parent_clip != nullptr && parent_clip->sequence != nullptr) { + double new_default_pos_x = parent_clip->sequence->width/2; + double new_default_pos_y = parent_clip->sequence->height/2; - /*if (set) { - adjust_field(position_x, default_pos_x, new_default_pos_x); - adjust_field(position_y, default_pos_y, new_default_pos_y); - }*/ + /*if (set) { + adjust_field(position_x, default_pos_x, new_default_pos_x); + adjust_field(position_y, default_pos_y, new_default_pos_y); + }*/ - double default_pos_x = new_default_pos_x; - double default_pos_y = new_default_pos_y; + double default_pos_x = new_default_pos_x; + double default_pos_y = new_default_pos_y; - position_x->set_double_default_value(default_pos_x); - position_y->set_double_default_value(default_pos_y); - scale_x->set_double_default_value(100); - scale_y->set_double_default_value(100); + position_x->set_double_default_value(default_pos_x); + position_y->set_double_default_value(default_pos_y); + scale_x->set_double_default_value(100); + scale_y->set_double_default_value(100); - anchor_x_box->set_double_default_value(0); - anchor_y_box->set_double_default_value(0); - opacity->set_double_default_value(100); + anchor_x_box->set_double_default_value(0); + anchor_y_box->set_double_default_value(0); + opacity->set_double_default_value(100); - double x_percent_multipler = 200.0 / parent_clip->sequence->width; - double y_percent_multipler = 200.0 / parent_clip->sequence->height; - top_left_gizmo->x_field_multi1 = -x_percent_multipler; - top_left_gizmo->y_field_multi1 = -y_percent_multipler; - top_center_gizmo->y_field_multi1 = -y_percent_multipler; - top_right_gizmo->x_field_multi1 = x_percent_multipler; - top_right_gizmo->y_field_multi1 = -y_percent_multipler; - bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; - bottom_left_gizmo->y_field_multi1 = y_percent_multipler; - bottom_center_gizmo->y_field_multi1 = y_percent_multipler; - bottom_right_gizmo->x_field_multi1 = x_percent_multipler; - bottom_right_gizmo->y_field_multi1 = y_percent_multipler; - left_center_gizmo->x_field_multi1 = -x_percent_multipler; - right_center_gizmo->x_field_multi1 = x_percent_multipler; - rotate_gizmo->x_field_multi1 = x_percent_multipler; + double x_percent_multipler = 200.0 / parent_clip->sequence->width; + double y_percent_multipler = 200.0 / parent_clip->sequence->height; + top_left_gizmo->x_field_multi1 = -x_percent_multipler; + top_left_gizmo->y_field_multi1 = -y_percent_multipler; + top_center_gizmo->y_field_multi1 = -y_percent_multipler; + top_right_gizmo->x_field_multi1 = x_percent_multipler; + top_right_gizmo->y_field_multi1 = -y_percent_multipler; + bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; + bottom_left_gizmo->y_field_multi1 = y_percent_multipler; + bottom_center_gizmo->y_field_multi1 = y_percent_multipler; + bottom_right_gizmo->x_field_multi1 = x_percent_multipler; + bottom_right_gizmo->y_field_multi1 = y_percent_multipler; + left_center_gizmo->x_field_multi1 = -x_percent_multipler; + right_center_gizmo->x_field_multi1 = x_percent_multipler; + rotate_gizmo->x_field_multi1 = x_percent_multipler; - set = true; - } + set = true; + } } void TransformEffect::toggle_uniform_scale(bool enabled) { - scale_y->set_enabled(!enabled); + scale_y->set_enabled(!enabled); - top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; - bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; - top_left_gizmo->y_field1 = enabled ? nullptr : scale_y; - top_right_gizmo->y_field1 = enabled ? nullptr : scale_y; - bottom_left_gizmo->y_field1 = enabled ? nullptr : scale_y; - bottom_right_gizmo->y_field1 = enabled ? nullptr : scale_y; + top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + top_left_gizmo->y_field1 = enabled ? nullptr : scale_y; + top_right_gizmo->y_field1 = enabled ? nullptr : scale_y; + bottom_left_gizmo->y_field1 = enabled ? nullptr : scale_y; + bottom_right_gizmo->y_field1 = enabled ? nullptr : scale_y; } void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { - // position - glTranslated(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); + // position + 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 = 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; - coords.vertexBottomRightX -= anchor_x_offset; - coords.vertexTopLeftY -= anchor_y_offset; - coords.vertexTopRightY -= anchor_y_offset; - coords.vertexBottomLeftY -= anchor_y_offset; - coords.vertexBottomRightY -= anchor_y_offset; + // anchor point + 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; + coords.vertexBottomRightX -= anchor_x_offset; + coords.vertexTopLeftY -= anchor_y_offset; + coords.vertexTopRightY -= anchor_y_offset; + coords.vertexBottomLeftY -= anchor_y_offset; + coords.vertexBottomRightY -= anchor_y_offset; - // rotation - glRotated(rotation->get_double_value(timecode), 0, 0, 1); + // rotation + glRotated(rotation->get_double_value(timecode), 0, 0, 1); - // scale - 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); + // scale + 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 - coords.blendmode = blend_mode_box->get_combo_data(timecode).toInt(); + // blend mode + coords.blendmode = blend_mode_box->get_combo_data(timecode).toInt(); - // opacity - coords.opacity *= float(opacity->get_double_value(timecode)*0.01); + // opacity + coords.opacity *= float(opacity->get_double_value(timecode)*0.01); } void TransformEffect::gizmo_draw(double, GLTextureCoords& coords) { - top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - top_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopLeftX, coords.vertexTopRightX, 0.5), lerp(coords.vertexTopLeftY, coords.vertexTopRightY, 0.5)); - top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - right_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopRightX, coords.vertexBottomRightX, 0.5), lerp(coords.vertexTopRightY, coords.vertexBottomRightY, 0.5)); - bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - bottom_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomRightX, coords.vertexBottomLeftX, 0.5), lerp(coords.vertexBottomRightY, coords.vertexBottomLeftY, 0.5)); - bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); - left_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomLeftX, coords.vertexTopLeftX, 0.5), lerp(coords.vertexBottomLeftY, coords.vertexTopLeftY, 0.5)); + top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); + top_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopLeftX, coords.vertexTopRightX, 0.5), lerp(coords.vertexTopLeftY, coords.vertexTopRightY, 0.5)); + top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); + right_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopRightX, coords.vertexBottomRightX, 0.5), lerp(coords.vertexTopRightY, coords.vertexBottomRightY, 0.5)); + bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); + bottom_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomRightX, coords.vertexBottomLeftX, 0.5), lerp(coords.vertexBottomRightY, coords.vertexBottomLeftY, 0.5)); + bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + left_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomLeftX, coords.vertexTopLeftX, 0.5), lerp(coords.vertexBottomLeftY, coords.vertexTopLeftY, 0.5)); - rotate_gizmo->world_pos[0] = QPoint(lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), -0.1), lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1)); + rotate_gizmo->world_pos[0] = QPoint(lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), -0.1), lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1)); - rect_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - rect_gizmo->world_pos[1] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - rect_gizmo->world_pos[2] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - rect_gizmo->world_pos[3] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + rect_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); + rect_gizmo->world_pos[1] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); + rect_gizmo->world_pos[2] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); + rect_gizmo->world_pos[3] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); } diff --git a/effects/lighten.blend b/effects/lighten.blend new file mode 100644 index 000000000..37f3708da --- /dev/null +++ b/effects/lighten.blend @@ -0,0 +1,14 @@ +float blendLighten(float base, float blend) { + return max(blend,base); +} + +vec3 blendLighten(vec3 base, vec3 blend) { + return vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b)); +} + +vec3 blendLighten(vec3 base, vec3 blend, float opacity) { + return (blendLighten(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendLighten) +#olive name Lighten \ No newline at end of file diff --git a/effects/linear-burn.blend b/effects/linear-burn.blend new file mode 100644 index 000000000..6bbce530c --- /dev/null +++ b/effects/linear-burn.blend @@ -0,0 +1,16 @@ +float blendLinearBurn(float base, float blend) { + // Note : Same implementation as BlendSubtractf + return max(base+blend-1.0,0.0); +} + +vec3 blendLinearBurn(vec3 base, vec3 blend) { + // Note : Same implementation as BlendSubtract + return max(base+blend-vec3(1.0),vec3(0.0)); +} + +vec3 blendLinearBurn(vec3 base, vec3 blend, float opacity) { + return (blendLinearBurn(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendLinearBurn) +#olive name Linear Burn \ No newline at end of file diff --git a/effects/linear-dodge.blend b/effects/linear-dodge.blend new file mode 100644 index 000000000..1a5cb5709 --- /dev/null +++ b/effects/linear-dodge.blend @@ -0,0 +1,16 @@ +float blendLinearDodge(float base, float blend) { + // Note : Same implementation as BlendAddf + return min(base+blend,1.0); +} + +vec3 blendLinearDodge(vec3 base, vec3 blend) { + // Note : Same implementation as BlendAdd + return min(base+blend,vec3(1.0)); +} + +vec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) { + return (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendLinearDodge) +#olive name Linear Dodge \ No newline at end of file diff --git a/effects/linear-light.blend b/effects/linear-light.blend new file mode 100644 index 000000000..97d8b02cc --- /dev/null +++ b/effects/linear-light.blend @@ -0,0 +1,17 @@ +#pragma glslify: blendLinearDodge = require(./linear-dodge) +#pragma glslify: blendLinearBurn = require(./linear-burn) + +float blendLinearLight(float base, float blend) { + return blend<0.5?blendLinearBurn(base,(2.0*blend)):blendLinearDodge(base,(2.0*(blend-0.5))); +} + +vec3 blendLinearLight(vec3 base, vec3 blend) { + return vec3(blendLinearLight(base.r,blend.r),blendLinearLight(base.g,blend.g),blendLinearLight(base.b,blend.b)); +} + +vec3 blendLinearLight(vec3 base, vec3 blend, float opacity) { + return (blendLinearLight(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendLinearLight) +#olive name Linear Light \ No newline at end of file diff --git a/effects/multiply.blend b/effects/multiply.blend new file mode 100644 index 000000000..55ed2d341 --- /dev/null +++ b/effects/multiply.blend @@ -0,0 +1,10 @@ +vec3 blendMultiply(vec3 base, vec3 blend) { + return base*blend; +} + +vec3 blendMultiply(vec3 base, vec3 blend, float opacity) { + return (blendMultiply(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendMultiply) +#olive name Multiply \ No newline at end of file diff --git a/effects/negation.blend b/effects/negation.blend new file mode 100644 index 000000000..6b8c812b5 --- /dev/null +++ b/effects/negation.blend @@ -0,0 +1,10 @@ +vec3 blendNegation(vec3 base, vec3 blend) { + return vec3(1.0)-abs(vec3(1.0)-base-blend); +} + +vec3 blendNegation(vec3 base, vec3 blend, float opacity) { + return (blendNegation(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendNegation) +#olive name Negation \ No newline at end of file diff --git a/effects/normal.blend b/effects/normal.blend new file mode 100644 index 000000000..a3fc1b63b --- /dev/null +++ b/effects/normal.blend @@ -0,0 +1,10 @@ +vec3 blendNormal(vec3 base, vec3 blend) { + return blend; +} + +vec3 blendNormal(vec3 base, vec3 blend, float opacity) { + return (blendNormal(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendNormal) +#olive name Normal \ No newline at end of file diff --git a/effects/overlay.blend b/effects/overlay.blend new file mode 100644 index 000000000..4500d21be --- /dev/null +++ b/effects/overlay.blend @@ -0,0 +1,14 @@ +float blendOverlay(float base, float blend) { + return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); +} + +vec3 blendOverlay(vec3 base, vec3 blend) { + return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); +} + +vec3 blendOverlay(vec3 base, vec3 blend, float opacity) { + return (blendOverlay(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendOverlay) +#olive name Overlay \ No newline at end of file diff --git a/effects/phoenix.blend b/effects/phoenix.blend new file mode 100644 index 000000000..096139621 --- /dev/null +++ b/effects/phoenix.blend @@ -0,0 +1,10 @@ +vec3 blendPhoenix(vec3 base, vec3 blend) { + return min(base,blend)-max(base,blend)+vec3(1.0); +} + +vec3 blendPhoenix(vec3 base, vec3 blend, float opacity) { + return (blendPhoenix(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendPhoenix) +#olive name Phoenix \ No newline at end of file diff --git a/effects/pin-light.blend b/effects/pin-light.blend new file mode 100644 index 000000000..bff45cf31 --- /dev/null +++ b/effects/pin-light.blend @@ -0,0 +1,17 @@ +#pragma glslify: blendLighten = require(./lighten) +#pragma glslify: blendDarken = require(./darken) + +float blendPinLight(float base, float blend) { + return (blend<0.5)?blendDarken(base,(2.0*blend)):blendLighten(base,(2.0*(blend-0.5))); +} + +vec3 blendPinLight(vec3 base, vec3 blend) { + return vec3(blendPinLight(base.r,blend.r),blendPinLight(base.g,blend.g),blendPinLight(base.b,blend.b)); +} + +vec3 blendPinLight(vec3 base, vec3 blend, float opacity) { + return (blendPinLight(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendPinLight) +#olive name Pin Light \ No newline at end of file diff --git a/effects/reflect.blend b/effects/reflect.blend new file mode 100644 index 000000000..75578520b --- /dev/null +++ b/effects/reflect.blend @@ -0,0 +1,14 @@ +float blendReflect(float base, float blend) { + return (blend==1.0)?blend:min(base*base/(1.0-blend),1.0); +} + +vec3 blendReflect(vec3 base, vec3 blend) { + return vec3(blendReflect(base.r,blend.r),blendReflect(base.g,blend.g),blendReflect(base.b,blend.b)); +} + +vec3 blendReflect(vec3 base, vec3 blend, float opacity) { + return (blendReflect(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendReflect) +#olive name Reflect \ No newline at end of file diff --git a/effects/screen.blend b/effects/screen.blend new file mode 100644 index 000000000..f2c4ce838 --- /dev/null +++ b/effects/screen.blend @@ -0,0 +1,14 @@ +float blendScreen(float base, float blend) { + return 1.0-((1.0-base)*(1.0-blend)); +} + +vec3 blendScreen(vec3 base, vec3 blend) { + return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); +} + +vec3 blendScreen(vec3 base, vec3 blend, float opacity) { + return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendScreen) +#olive name Screen \ No newline at end of file diff --git a/effects/soft-light.blend b/effects/soft-light.blend new file mode 100644 index 000000000..8a6991214 --- /dev/null +++ b/effects/soft-light.blend @@ -0,0 +1,14 @@ +float blendSoftLight(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)); +} + +vec3 blendSoftLight(vec3 base, vec3 blend) { + return vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b)); +} + +vec3 blendSoftLight(vec3 base, vec3 blend, float opacity) { + return (blendSoftLight(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendSoftLight) +#olive name Soft Light \ No newline at end of file diff --git a/effects/substract.blend b/effects/substract.blend new file mode 100644 index 000000000..2e3804719 --- /dev/null +++ b/effects/substract.blend @@ -0,0 +1,14 @@ +float blendSubstract(float base, float blend) { + return max(base+blend-1.0,0.0); +} + +vec3 blendSubstract(vec3 base, vec3 blend) { + return max(base+blend-vec3(1.0),vec3(0.0)); +} + +vec3 blendSubstract(vec3 base, vec3 blend, float opacity) { + return (blendSubstract(base, blend) * opacity + blend * (1.0 - opacity)); +} + +#pragma glslify: export(blendSubstract) +#olive name Substract \ No newline at end of file diff --git a/effects/subtract.blend b/effects/subtract.blend new file mode 100644 index 000000000..aa93f2fab --- /dev/null +++ b/effects/subtract.blend @@ -0,0 +1,14 @@ +float blendSubtract(float base, float blend) { + return max(base+blend-1.0,0.0); +} + +vec3 blendSubtract(vec3 base, vec3 blend) { + return max(base+blend-vec3(1.0),vec3(0.0)); +} + +vec3 blendSubtract(vec3 base, vec3 blend, float opacity) { + return (blendSubtract(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendSubtract) +#olive name Subtract \ No newline at end of file diff --git a/effects/vivid-light.blend b/effects/vivid-light.blend new file mode 100644 index 000000000..2f5fc9469 --- /dev/null +++ b/effects/vivid-light.blend @@ -0,0 +1,17 @@ +#pragma glslify: blendColorDodge = require(./color-dodge) +#pragma glslify: blendColorBurn = require(./color-burn) + +float blendVividLight(float base, float blend) { + return (blend<0.5)?blendColorBurn(base,(2.0*blend)):blendColorDodge(base,(2.0*(blend-0.5))); +} + +vec3 blendVividLight(vec3 base, vec3 blend) { + return vec3(blendVividLight(base.r,blend.r),blendVividLight(base.g,blend.g),blendVividLight(base.b,blend.b)); +} + +vec3 blendVividLight(vec3 base, vec3 blend, float opacity) { + return (blendVividLight(base, blend) * opacity + base * (1.0 - opacity)); +} + +#pragma glslify: export(blendVividLight) +#olive name Vivid Light \ No newline at end of file diff --git a/io/config.cpp b/io/config.cpp index 5b09d877b..1490ddcbd 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -285,6 +285,5 @@ void Config::save(QString path) { } RuntimeConfig::RuntimeConfig() : - shaders_are_enabled(true), - disable_blending(false) + shaders_are_enabled(true) {} diff --git a/io/config.h b/io/config.h index a83eb30d7..c9fc41d92 100644 --- a/io/config.h +++ b/io/config.h @@ -561,14 +561,6 @@ struct RuntimeConfig { */ bool shaders_are_enabled; - /** - * @brief Disable blending modes - * - * Some users had difficulty utilizing blending modes (provided by shaders). Set this to **TRUE** to bypass - * shader-based blending modes and utilize standard (less versatile) OpenGL blending instead. - */ - bool disable_blending; - /** * @brief Load an external translation file * diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 6b2dc103b..d33a6c2a7 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -32,6 +32,7 @@ #include "rendering/renderfunctions.h" #include "io/previewgenerator.h" #include "effects/internal/voideffect.h" +#include "project/effectloaders.h" #include "debug.h" #include @@ -115,7 +116,7 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { // Effect loading occurs in another thread, and while it's usually very quick, just for safety we wait here // for all the effects to finish loading - panel_effect_controls->effects_loaded.lock(); + olive::effects_loaded.lock(); const EffectMeta* meta = nullptr; @@ -124,7 +125,7 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { meta = get_meta_from_name(effect_name); } - panel_effect_controls->effects_loaded.unlock(); + olive::effects_loaded.unlock(); int type; if (tag == "opening") { diff --git a/main.cpp b/main.cpp index 69f99ee5e..e36f50fe0 100644 --- a/main.cpp +++ b/main.cpp @@ -34,7 +34,7 @@ extern "C" { #include } -int main(int argc, char *argv[]) { +int main(int argc, char *argv[]) { olive::Global = std::unique_ptr(new OliveGlobal); bool launch_fullscreen = false; @@ -60,7 +60,6 @@ int main(int argc, char *argv[]) { "\t-f, --fullscreen\tStart in full screen mode\n" "\t--disable-shaders\tDisable OpenGL shaders (for debugging)\n" "\t--no-debug\t\tDisable internal debug log and output directly to console\n" - "\t--disable-blend-modes\tDisable shader-based blending for older GPUs\n" "\t--translation \tSet an external language file to use\n" "\n" "Environment Variables:\n" @@ -75,8 +74,6 @@ int main(int argc, char *argv[]) { olive::CurrentRuntimeConfig.shaders_are_enabled = false; } else if (!strcmp(argv[i], "--no-debug")) { use_internal_logger = false; - } else if (!strcmp(argv[i], "--disable-blend-modes")) { - olive::CurrentRuntimeConfig.disable_blending = true; } else if (!strcmp(argv[i], "--translation")) { if (i + 1 < argc && argv[i + 1][0] != '-') { // load translation file diff --git a/mainwindow.cpp b/mainwindow.cpp index 2ed4ea568..dd0ac9e8d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -948,6 +948,8 @@ void MainWindow::closeEvent(QCloseEvent *e) { panel_footage_viewer->set_main_sequence(); + olive::UndoStack.clear(); + QString data_dir = get_data_path(); QString config_path = get_config_path(); if (!data_dir.isEmpty() && !autorecovery_filename.isEmpty()) { diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 174baa795..d93849d24 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -39,6 +39,7 @@ #include "ui/collapsiblewidget.h" #include "project/sequence.h" #include "project/undo.h" +#include "project/effectloaders.h" #include "panels/project.h" #include "panels/timeline.h" #include "panels/viewer.h" @@ -194,13 +195,13 @@ void EffectControls::show_effect_menu(int type, int subtype) { effect_menu_type = type; effect_menu_subtype = subtype; - effects_loaded.lock(); + olive::effects_loaded.lock(); QMenu effects_menu(this); effects_menu.setToolTipsVisible(true); - for (int i=0;isetObjectName("v"); @@ -1804,8 +1804,8 @@ void Timeline::transition_tool_click() { transition_menu.addSeparator(); - for (int i=0;isetObjectName("a"); diff --git a/project/effect.cpp b/project/effect.cpp index a49ae6977..fab503d07 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -67,12 +67,14 @@ #include #include -QVector effects; +QVector olive::effects; +QVector olive::blend_modes; +QString olive::generated_blending_shader; EffectPtr Effect::Create(Clip* c, const EffectMeta* em) { - if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) { - // must be an internal effect - switch (em->internal) { + if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) { + // must be an internal effect + switch (em->internal) { case EFFECT_INTERNAL_TRANSFORM: return EffectPtr(new TransformEffect(c, em)); case EFFECT_INTERNAL_TEXT: return EffectPtr(new TextEffect(c, em)); case EFFECT_INTERNAL_TIMECODE: return EffectPtr(new TimecodeEffect(c, em)); @@ -90,851 +92,851 @@ EffectPtr Effect::Create(Clip* c, const EffectMeta* em) { #ifndef NOFREI0R case EFFECT_INTERNAL_FREI0R: return EffectPtr(new Frei0rEffect(c, em)); #endif - } - } else if (!em->filename.isEmpty()) { - // load effect from file + } + } else if (!em->filename.isEmpty()) { + // load effect from file return EffectPtr(new Effect(c, em)); - } else { - qCritical() << "Invalid effect data"; + } else { + qCritical() << "Invalid effect data"; QMessageBox::critical(olive::MainWindow, - QCoreApplication::translate("Effect", "Invalid effect"), - QCoreApplication::translate("Effect", "No candidate for effect '%1'. This effect may be corrupt. Try reinstalling it or Olive.").arg(em->name)); - } - return nullptr; + QCoreApplication::translate("Effect", "Invalid effect"), + QCoreApplication::translate("Effect", "No candidate for effect '%1'. This effect may be corrupt. Try reinstalling it or Olive.").arg(em->name)); + } + return nullptr; } const EffectMeta* Effect::GetInternalMeta(int internal_id, int type) { - for (int i=0;ienabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); - ui = new QWidget(container); - ui_layout = new QGridLayout(ui); - ui_layout->setSpacing(4); - container->setContents(ui); + // set up base UI + container = new CollapsibleWidget(); + connect(container->enabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); + ui = new QWidget(container); + ui_layout = new QGridLayout(ui); + ui_layout->setSpacing(4); + container->setContents(ui); - connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); + connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); - if (em != nullptr) { - // set up UI from effect file - container->setText(em->name); + if (em != nullptr) { + // set up UI from effect file + container->setText(em->name); - if (!em->filename.isEmpty() && em->internal == -1) { - QFile effect_file(em->filename); - if (effect_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&effect_file); + if (!em->filename.isEmpty() && em->internal == -1) { + QFile effect_file(em->filename); + if (effect_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&effect_file); - while (!reader.atEnd()) { - if (reader.name() == "row" && reader.isStartElement()) { - QString row_name; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename << "- ID cannot be empty."; - } else if (type > -1) { - EffectField* field = row->add_field(type, id); - connect(field, SIGNAL(changed()), this, SLOT(field_changed())); - switch (type) { - case EFFECT_FIELD_DOUBLE: - for (int i=0;iset_double_default_value(attr.value().toDouble()); - } else if (attr.name() == "min") { - field->set_double_minimum_value(attr.value().toDouble()); - } else if (attr.name() == "max") { - field->set_double_maximum_value(attr.value().toDouble()); - } - } - break; - case EFFECT_FIELD_COLOR: - { - QColor color; - for (int i=0;iset_color_value(color); - } - break; - case EFFECT_FIELD_STRING: - for (int i=0;iset_string_value(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_BOOL: - for (int i=0;iset_bool_value(attr.value() == "1"); - } - } - break; - case EFFECT_FIELD_COMBO: - { - int combo_index = 0; - for (int i=0;iadd_combo_item(reader.text().toString(), 0); - } - } - field->set_combo_index(combo_index); - } - break; - case EFFECT_FIELD_FONT: - for (int i=0;iset_font_name(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_FILE: - for (int i=0;iset_filename(attr.value().toString()); - } - } - break; - } - } - } - } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - enable_shader = true; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; - } - break; - } - } - }*/ - reader.readNext(); - } + if (id.isEmpty()) { + qCritical() << "Couldn't load field from" << em->filename << "- ID cannot be empty."; + } else if (type > -1) { + EffectField* field = row->add_field(type, id); + connect(field, SIGNAL(changed()), this, SLOT(field_changed())); + switch (type) { + case EFFECT_FIELD_DOUBLE: + for (int i=0;iset_double_default_value(attr.value().toDouble()); + } else if (attr.name() == "min") { + field->set_double_minimum_value(attr.value().toDouble()); + } else if (attr.name() == "max") { + field->set_double_maximum_value(attr.value().toDouble()); + } + } + break; + case EFFECT_FIELD_COLOR: + { + QColor color; + for (int i=0;iset_color_value(color); + } + break; + case EFFECT_FIELD_STRING: + for (int i=0;iset_string_value(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_BOOL: + for (int i=0;iset_bool_value(attr.value() == "1"); + } + } + break; + case EFFECT_FIELD_COMBO: + { + int combo_index = 0; + for (int i=0;iadd_combo_item(reader.text().toString(), 0); + } + } + field->set_combo_index(combo_index); + } + break; + case EFFECT_FIELD_FONT: + for (int i=0;iset_font_name(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_FILE: + for (int i=0;iset_filename(attr.value().toString()); + } + } + break; + } + } + } + } + } + } else if (reader.name() == "shader" && reader.isStartElement()) { + enable_shader = true; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename; + enable_superimpose = false; + } + break; + } + } + }*/ + reader.readNext(); + } - effect_file.close(); - } else { - qCritical() << "Failed to open effect file" << em->filename; - } - } - } + effect_file.close(); + } else { + qCritical() << "Failed to open effect file" << em->filename; + } + } + } } Effect::~Effect() { - if (isOpen) { - close(); - } + if (isOpen) { + close(); + } - delete container; + delete container; - for (int i=0;irows.at(i); - copy_row->setKeyframing(row->isKeyframing()); - for (int j=0;jfieldCount();j++) { - EffectField* field = row->field(j); - EffectField* copy_field = copy_row->field(j); - copy_field->keyframes = field->keyframes; - copy_field->set_current_data(field->get_current_data()); - } - } + for (int i=0;irows.at(i); + copy_row->setKeyframing(row->isKeyframing()); + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + EffectField* copy_field = copy_row->field(j); + copy_field->keyframes = field->keyframes; + copy_field->set_current_data(field->get_current_data()); + } + } } EffectRow* Effect::add_row(const QString& name, bool savable, bool keyframable) { - EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size(), keyframable); - rows.append(row); - return row; + EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size(), keyframable); + rows.append(row); + return row; } EffectRow* Effect::row(int i) { - return rows.at(i); + return rows.at(i); } int Effect::row_count() { - return rows.size(); + return rows.size(); } EffectGizmo *Effect::add_gizmo(int type) { - EffectGizmo* gizmo = new EffectGizmo(type); - gizmos.append(gizmo); - return gizmo; + EffectGizmo* gizmo = new EffectGizmo(type); + gizmos.append(gizmo); + return gizmo; } EffectGizmo *Effect::gizmo(int i) { - return gizmos.at(i); + return gizmos.at(i); } int Effect::gizmo_count() { - return gizmos.size(); + return gizmos.size(); } void Effect::refresh() {} void Effect::field_changed() { - panel_sequence_viewer->viewer_widget->frame_update(); - panel_graph_editor->update_panel(); + panel_sequence_viewer->viewer_widget->frame_update(); + panel_graph_editor->update_panel(); } void Effect::show_context_menu(const QPoint& pos) { - if (meta->type == EFFECT_TYPE_EFFECT) { + if (meta->type == EFFECT_TYPE_EFFECT) { QMenu menu(olive::MainWindow); - int index = get_index_in_clip(); + int index = get_index_in_clip(); - menu.addAction(tr("Cu&t"), panel_effect_controls, SLOT(cut())); - menu.addAction(tr("&Copy"), panel_effect_controls, SLOT(copy(bool))); + menu.addAction(tr("Cu&t"), panel_effect_controls, SLOT(cut())); + menu.addAction(tr("&Copy"), panel_effect_controls, SLOT(copy(bool))); - panel_effect_controls->add_effect_paste_action(&menu); + panel_effect_controls->add_effect_paste_action(&menu); - menu.addSeparator(); + menu.addSeparator(); - if (index > 0) { - menu.addAction(tr("Move &Up"), this, SLOT(move_up())); - } + if (index > 0) { + menu.addAction(tr("Move &Up"), this, SLOT(move_up())); + } - if (index < parent_clip->effects.size() - 1) { - menu.addAction(tr("Move &Down"), this, SLOT(move_down())); - } + if (index < parent_clip->effects.size() - 1) { + menu.addAction(tr("Move &Down"), this, SLOT(move_down())); + } - menu.addSeparator(); + menu.addSeparator(); - menu.addAction(tr("D&elete"), this, SLOT(delete_self())); + menu.addAction(tr("D&elete"), this, SLOT(delete_self())); - menu.addSeparator(); + menu.addSeparator(); - menu.addAction(tr("Load Settings From File"), this, SLOT(load_from_file())); + menu.addAction(tr("Load Settings From File"), this, SLOT(load_from_file())); - menu.addAction(tr("Save Settings to File"), this, SLOT(save_to_file())); + menu.addAction(tr("Save Settings to File"), this, SLOT(save_to_file())); - menu.exec(container->title_bar->mapToGlobal(pos)); - } + menu.exec(container->title_bar->mapToGlobal(pos)); + } } void Effect::delete_self() { - EffectDeleteCommand* command = new EffectDeleteCommand(); - command->clips.append(parent_clip); - command->fx.append(get_index_in_clip()); - olive::UndoStack.push(command); - update_ui(true); + EffectDeleteCommand* command = new EffectDeleteCommand(); + command->clips.append(parent_clip); + command->fx.append(get_index_in_clip()); + olive::UndoStack.push(command); + update_ui(true); } void Effect::move_up() { - MoveEffectCommand* command = new MoveEffectCommand(); - command->clip = parent_clip; - command->from = get_index_in_clip(); - command->to = command->from - 1; - olive::UndoStack.push(command); - panel_effect_controls->reload_clips(); - panel_sequence_viewer->viewer_widget->frame_update(); + MoveEffectCommand* command = new MoveEffectCommand(); + command->clip = parent_clip; + command->from = get_index_in_clip(); + command->to = command->from - 1; + olive::UndoStack.push(command); + panel_effect_controls->reload_clips(); + panel_sequence_viewer->viewer_widget->frame_update(); } void Effect::move_down() { - MoveEffectCommand* command = new MoveEffectCommand(); - command->clip = parent_clip; - command->from = get_index_in_clip(); - command->to = command->from + 1; - olive::UndoStack.push(command); - panel_effect_controls->reload_clips(); - panel_sequence_viewer->viewer_widget->frame_update(); + MoveEffectCommand* command = new MoveEffectCommand(); + command->clip = parent_clip; + command->from = get_index_in_clip(); + command->to = command->from + 1; + olive::UndoStack.push(command); + panel_effect_controls->reload_clips(); + panel_sequence_viewer->viewer_widget->frame_update(); } void Effect::save_to_file() { - // save effect settings to file + // save effect settings to file QString file = QFileDialog::getSaveFileName(olive::MainWindow, - tr("Save Effect Settings"), - QString(), - tr("Effect XML Settings %1").arg("(*.xml)")); + tr("Save Effect Settings"), + QString(), + tr("Effect XML Settings %1").arg("(*.xml)")); - // if the user picked a file - if (!file.isEmpty()) { + // if the user picked a file + if (!file.isEmpty()) { // ensure file ends with .xml extension if (!file.endsWith(".xml", Qt::CaseInsensitive)) { file.append(".xml"); } - QFile file_handle(file); - if (file_handle.open(QFile::WriteOnly)) { + QFile file_handle(file); + if (file_handle.open(QFile::WriteOnly)) { - file_handle.write(save_to_string()); + file_handle.write(save_to_string()); - file_handle.close(); - } else { + file_handle.close(); + } else { QMessageBox::critical(olive::MainWindow, - tr("Save Settings Failed"), - tr("Failed to open \"%1\" for writing.").arg(file), - QMessageBox::Ok); - } - } + tr("Save Settings Failed"), + tr("Failed to open \"%1\" for writing.").arg(file), + QMessageBox::Ok); + } + } } void Effect::load_from_file() { - // load effect settings from file + // load effect settings from file QString file = QFileDialog::getOpenFileName(olive::MainWindow, - tr("Load Effect Settings"), - QString(), - tr("Effect XML Settings %1").arg("(*.xml)")); + tr("Load Effect Settings"), + QString(), + tr("Effect XML Settings %1").arg("(*.xml)")); - // if the user picked a file - if (!file.isEmpty()) { - QFile file_handle(file); - if (file_handle.open(QFile::ReadOnly)) { + // if the user picked a file + if (!file.isEmpty()) { + QFile file_handle(file); + if (file_handle.open(QFile::ReadOnly)) { olive::UndoStack.push(new SetEffectData(EffectPtr(this), file_handle.readAll())); - file_handle.close(); + file_handle.close(); - update_ui(false); - } else { + update_ui(false); + } else { QMessageBox::critical(olive::MainWindow, - tr("Load Settings Failed"), - tr("Failed to open \"%1\" for reading.").arg(file), - QMessageBox::Ok); - } - } + tr("Load Settings Failed"), + tr("Failed to open \"%1\" for reading.").arg(file), + QMessageBox::Ok); + } + } } int Effect::get_index_in_clip() { - if (parent_clip != nullptr) { - for (int i=0;ieffects.size();i++) { + if (parent_clip != nullptr) { + for (int i=0;ieffects.size();i++) { if (parent_clip->effects.at(i).get() == this) { - return i; - } - } - } - return -1; + return i; + } + } + } + return -1; } bool Effect::is_enabled() { - return container->enabled_check->isChecked(); + return container->enabled_check->isChecked(); } void Effect::set_enabled(bool b) { - container->enabled_check->setChecked(b); + container->enabled_check->setChecked(b); } QVariant load_data_from_string(int type, const QString& string) { - switch (type) { - case EFFECT_FIELD_DOUBLE: return string.toDouble(); - case EFFECT_FIELD_COLOR: return QColor(string); - case EFFECT_FIELD_BOOL: return (string == "1"); - case EFFECT_FIELD_COMBO: return string.toInt(); - case EFFECT_FIELD_STRING: - case EFFECT_FIELD_FONT: - case EFFECT_FIELD_FILE: - return string; - } - return QVariant(); + switch (type) { + case EFFECT_FIELD_DOUBLE: return string.toDouble(); + case EFFECT_FIELD_COLOR: return QColor(string); + case EFFECT_FIELD_BOOL: return (string == "1"); + case EFFECT_FIELD_COMBO: return string.toInt(); + case EFFECT_FIELD_STRING: + case EFFECT_FIELD_FONT: + case EFFECT_FIELD_FILE: + return string; + } + return QVariant(); } QString save_data_to_string(int type, const QVariant& data) { - switch (type) { - case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); - case EFFECT_FIELD_COLOR: return data.value().name(); - case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); - case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); - case EFFECT_FIELD_STRING: - case EFFECT_FIELD_FONT: - case EFFECT_FIELD_FILE: - return data.toString(); - } - return QString(); + switch (type) { + case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); + case EFFECT_FIELD_COLOR: return data.value().name(); + case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); + case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); + case EFFECT_FIELD_STRING: + case EFFECT_FIELD_FONT: + case EFFECT_FIELD_FILE: + return data.toString(); + } + return QString(); } void Effect::load(QXmlStreamReader& stream) { - int row_count = 0; + int row_count = 0; - QString tag = stream.name().toString(); + QString tag = stream.name().toString(); - while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { - stream.readNext(); - if (stream.name() == "row" && stream.isStartElement()) { - if (row_count < rows.size()) { - EffectRow* row = rows.at(row_count); - int field_count = 0; + while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { + stream.readNext(); + if (stream.name() == "row" && stream.isStartElement()) { + if (row_count < rows.size()) { + EffectRow* row = rows.at(row_count); + int field_count = 0; - while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) { - stream.readNext(); + while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) { + stream.readNext(); - // read field - if (stream.name() == "field" && stream.isStartElement()) { - if (field_count < row->fieldCount()) { - // match field using ID - int field_number = field_count; - for (int k=0;kfieldCount();l++) { - if (row->field(l)->id == attr.value()) { - field_number = l; + // read field + if (stream.name() == "field" && stream.isStartElement()) { + if (field_count < row->fieldCount()) { + // match field using ID + int field_number = field_count; + for (int k=0;kfieldCount();l++) { + if (row->field(l)->id == attr.value()) { + field_number = l; // qInfo() << "Found field by ID"; - break; - } - } - break; - } - } + break; + } + } + break; + } + } - EffectField* field = row->field(field_number); + EffectField* field = row->field(field_number); - // get current field value - for (int k=0;kset_current_data(load_data_from_string(field->type, attr.value().toString())); - break; - } - } + // get current field value + for (int k=0;kset_current_data(load_data_from_string(field->type, attr.value().toString())); + break; + } + } - while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) { - stream.readNext(); + while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) { + stream.readNext(); - // read keyframes - if (stream.name() == "key" && stream.isStartElement()) { - row->setKeyframing(true); + // read keyframes + if (stream.name() == "key" && stream.isStartElement()) { + row->setKeyframing(true); - EffectKeyframe key; - for (int k=0;ktype, attr.value().toString()); - } else if (attr.name() == "frame") { - key.time = attr.value().toLong(); - } else if (attr.name() == "type") { - key.type = attr.value().toInt(); - } else if (attr.name() == "prehx") { - key.pre_handle_x = attr.value().toDouble(); - } else if (attr.name() == "prehy") { - key.pre_handle_y = attr.value().toDouble(); - } else if (attr.name() == "posthx") { - key.post_handle_x = attr.value().toDouble(); - } else if (attr.name() == "posthy") { - key.post_handle_y = attr.value().toDouble(); - } - } - field->keyframes.append(key); - } - } - } else { - qCritical() << "Too many fields for effect" << id << "row" << row_count << ". Project might be corrupt. (Got" << field_count << ", expected <" << row->fieldCount()-1 << ")"; - } - field_count++; - } - } + EffectKeyframe key; + for (int k=0;ktype, attr.value().toString()); + } else if (attr.name() == "frame") { + key.time = attr.value().toLong(); + } else if (attr.name() == "type") { + key.type = attr.value().toInt(); + } else if (attr.name() == "prehx") { + key.pre_handle_x = attr.value().toDouble(); + } else if (attr.name() == "prehy") { + key.pre_handle_y = attr.value().toDouble(); + } else if (attr.name() == "posthx") { + key.post_handle_x = attr.value().toDouble(); + } else if (attr.name() == "posthy") { + key.post_handle_y = attr.value().toDouble(); + } + } + field->keyframes.append(key); + } + } + } else { + qCritical() << "Too many fields for effect" << id << "row" << row_count << ". Project might be corrupt. (Got" << field_count << ", expected <" << row->fieldCount()-1 << ")"; + } + field_count++; + } + } - } else { - qCritical() << "Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")"; - } - row_count++; - } else if (stream.isStartElement()) { - custom_load(stream); - } - } + } else { + qCritical() << "Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")"; + } + row_count++; + } else if (stream.isStartElement()) { + custom_load(stream); + } + } } void Effect::custom_load(QXmlStreamReader &) {} void Effect::save(QXmlStreamWriter& stream) { - stream.writeAttribute("name", meta->category + "/" + meta->name); - stream.writeAttribute("enabled", QString::number(is_enabled())); + stream.writeAttribute("name", meta->category + "/" + meta->name); + stream.writeAttribute("enabled", QString::number(is_enabled())); - for (int i=0;isavable) { - stream.writeStartElement("row"); // row - for (int j=0;jfieldCount();j++) { - EffectField* field = row->field(j); - stream.writeStartElement("field"); // field - stream.writeAttribute("id", field->id); - stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data())); - for (int k=0;kkeyframes.size();k++) { - const EffectKeyframe& key = field->keyframes.at(k); - stream.writeStartElement("key"); - stream.writeAttribute("value", save_data_to_string(field->type, key.data)); - stream.writeAttribute("frame", QString::number(key.time)); - stream.writeAttribute("type", QString::number(key.type)); - stream.writeAttribute("prehx", QString::number(key.pre_handle_x)); - stream.writeAttribute("prehy", QString::number(key.pre_handle_y)); - stream.writeAttribute("posthx", QString::number(key.post_handle_x)); - stream.writeAttribute("posthy", QString::number(key.post_handle_y)); - stream.writeEndElement(); // key - } - stream.writeEndElement(); // field - } - stream.writeEndElement(); // row - } - } + for (int i=0;isavable) { + stream.writeStartElement("row"); // row + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + stream.writeStartElement("field"); // field + stream.writeAttribute("id", field->id); + stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data())); + for (int k=0;kkeyframes.size();k++) { + const EffectKeyframe& key = field->keyframes.at(k); + stream.writeStartElement("key"); + stream.writeAttribute("value", save_data_to_string(field->type, key.data)); + stream.writeAttribute("frame", QString::number(key.time)); + stream.writeAttribute("type", QString::number(key.type)); + stream.writeAttribute("prehx", QString::number(key.pre_handle_x)); + stream.writeAttribute("prehy", QString::number(key.pre_handle_y)); + stream.writeAttribute("posthx", QString::number(key.post_handle_x)); + stream.writeAttribute("posthy", QString::number(key.post_handle_y)); + stream.writeEndElement(); // key + } + stream.writeEndElement(); // field + } + stream.writeEndElement(); // row + } + } } void Effect::load_from_string(const QByteArray &s) { - // clear existing keyframe data - for (int i=0;isetKeyframing(false); - for (int j=0;jfieldCount();j++) { - EffectField* field = row->field(j); - field->keyframes.clear(); - } - } + // clear existing keyframe data + for (int i=0;isetKeyframing(false); + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + field->keyframes.clear(); + } + } - // write settings with xml writer - QXmlStreamReader stream(s); + // write settings with xml writer + QXmlStreamReader stream(s); - while (!stream.atEnd()) { - stream.readNext(); + while (!stream.atEnd()) { + stream.readNext(); - // find the effect opening tag - if (stream.name() == "effect" && stream.isStartElement()) { + // find the effect opening tag + if (stream.name() == "effect" && stream.isStartElement()) { - // check the name to see if it matches this effect - const QXmlStreamAttributes& attributes = stream.attributes(); - for (int i=0;ipath.isEmpty() || (vertPath.isEmpty() && fragPath.isEmpty())) return; - QList effects_paths = get_effects_paths(); - const QString& test_fn = vertPath.isEmpty() ? fragPath : vertPath; - for (int i=0;ipath.isEmpty() || (vertPath.isEmpty() && fragPath.isEmpty())) return; + QList effects_paths = get_effects_paths(); + const QString& test_fn = vertPath.isEmpty() ? fragPath : vertPath; + for (int i=0;iaddShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath)) { - qInfo() << "Vertex shader added successfully"; - } else { - glsl_compiled = false; - qWarning() << "Vertex shader could not be added"; - } - } - if (!fragPath.isEmpty()) { - if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath)) { - qInfo() << "Fragment shader added successfully"; - } else { - glsl_compiled = false; - qWarning() << "Fragment shader could not be added"; - } - } - if (glsl_compiled) { - if (glslProgram->link()) { - qInfo() << "Shader program linked successfully"; - } else { - qWarning() << "Shader program failed to link"; - } - } - isOpen = true; - } - } else { - isOpen = true; - } + if (isOpen) { + qWarning() << "Tried to open an effect that was already open"; + close(); + } + if (olive::CurrentRuntimeConfig.shaders_are_enabled && enable_shader) { + if (QOpenGLContext::currentContext() == nullptr) { + qWarning() << "No current context to create a shader program for - will retry next repaint"; + } else { + glslProgram = new QOpenGLShaderProgram(); + validate_meta_path(); + bool glsl_compiled = true; + if (!vertPath.isEmpty()) { + if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath)) { + qInfo() << "Vertex shader added successfully"; + } else { + glsl_compiled = false; + qWarning() << "Vertex shader could not be added"; + } + } + if (!fragPath.isEmpty()) { + if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath)) { + qInfo() << "Fragment shader added successfully"; + } else { + glsl_compiled = false; + qWarning() << "Fragment shader could not be added"; + } + } + if (glsl_compiled) { + if (glslProgram->link()) { + qInfo() << "Shader program linked successfully"; + } else { + qWarning() << "Shader program failed to link"; + } + } + isOpen = true; + } + } else { + isOpen = true; + } } void Effect::close() { - if (!isOpen) { - qWarning() << "Tried to close an effect that was already closed"; - } - delete_texture(); - if (glslProgram != nullptr) { - delete glslProgram; - glslProgram = nullptr; - } - isOpen = false; + if (!isOpen) { + qWarning() << "Tried to close an effect that was already closed"; + } + delete_texture(); + if (glslProgram != nullptr) { + delete glslProgram; + glslProgram = nullptr; + } + isOpen = false; } bool Effect::is_glsl_linked() { - return glslProgram != nullptr && glslProgram->isLinked(); + return glslProgram != nullptr && glslProgram->isLinked(); } void Effect::startEffect() { - if (!isOpen) { - open(); - qWarning() << "Tried to start a closed effect - opening"; - } - if (olive::CurrentRuntimeConfig.shaders_are_enabled - && enable_shader - && glslProgram->isLinked()) { - bound = glslProgram->bind(); - } + if (!isOpen) { + open(); + qWarning() << "Tried to start a closed effect - opening"; + } + if (olive::CurrentRuntimeConfig.shaders_are_enabled + && enable_shader + && glslProgram->isLinked()) { + bound = glslProgram->bind(); + } } void Effect::endEffect() { - if (bound) glslProgram->release(); - bound = false; + if (bound) glslProgram->release(); + bound = false; } int Effect::getIterations() { - return iterations; + return iterations; } void Effect::setIterations(int i) { - iterations = i; + iterations = i; } void Effect::process_image(double, uint8_t *, uint8_t *, int){} EffectPtr Effect::copy(Clip *c) { EffectPtr copy = Effect::Create(c, meta); - copy->set_enabled(is_enabled()); - copy_field_keyframes(copy); - return copy; + copy->set_enabled(is_enabled()); + copy_field_keyframes(copy); + return copy; } void Effect::process_shader(double timecode, GLTextureCoords&, int iteration) { glslProgram->setUniformValue("resolution", parent_clip->media_width(), parent_clip->media_height()); - glslProgram->setUniformValue("time", GLfloat(timecode)); - glslProgram->setUniformValue("iteration", iteration); + glslProgram->setUniformValue("time", GLfloat(timecode)); + glslProgram->setUniformValue("iteration", iteration); - for (int i=0;ifieldCount();j++) { - EffectField* field = row->field(j); - if (!field->id.isEmpty()) { - switch (field->type) { - case EFFECT_FIELD_DOUBLE: - glslProgram->setUniformValue(field->id.toUtf8().constData(), GLfloat(field->get_double_value(timecode))); - break; - case EFFECT_FIELD_COLOR: - glslProgram->setUniformValue( - field->id.toUtf8().constData(), - GLfloat(field->get_color_value(timecode).redF()), - GLfloat(field->get_color_value(timecode).greenF()), - GLfloat(field->get_color_value(timecode).blueF()) - ); - break; - case EFFECT_FIELD_STRING: break; // can you even send a string to a uniform value? - case EFFECT_FIELD_BOOL: - glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_bool_value(timecode)); - break; - case EFFECT_FIELD_COMBO: - glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_combo_index(timecode)); - break; - case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value? - case EFFECT_FIELD_FILE: break; // can you even send a string to a uniform value? - } - } - } - } + for (int i=0;ifieldCount();j++) { + EffectField* field = row->field(j); + if (!field->id.isEmpty()) { + switch (field->type) { + case EFFECT_FIELD_DOUBLE: + glslProgram->setUniformValue(field->id.toUtf8().constData(), GLfloat(field->get_double_value(timecode))); + break; + case EFFECT_FIELD_COLOR: + glslProgram->setUniformValue( + field->id.toUtf8().constData(), + GLfloat(field->get_color_value(timecode).redF()), + GLfloat(field->get_color_value(timecode).greenF()), + GLfloat(field->get_color_value(timecode).blueF()) + ); + break; + case EFFECT_FIELD_STRING: break; // can you even send a string to a uniform value? + case EFFECT_FIELD_BOOL: + glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_bool_value(timecode)); + break; + case EFFECT_FIELD_COMBO: + glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_combo_index(timecode)); + break; + case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value? + case EFFECT_FIELD_FILE: break; // can you even send a string to a uniform value? + } + } + } + } } void Effect::process_coords(double, GLTextureCoords&, int) {} GLuint Effect::process_superimpose(double timecode) { - bool dimensions_changed = false; - bool redrew_image = false; + bool dimensions_changed = false; + bool redrew_image = false; int width = parent_clip->media_width(); int height = parent_clip->media_height(); - if (width != img.width() || height != img.height()) { - img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); - dimensions_changed = true; - } + if (width != img.width() || height != img.height()) { + img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); + dimensions_changed = true; + } - if (valueHasChanged(timecode) || dimensions_changed || enable_always_update) { - redraw(timecode); - redrew_image = true; - } + if (valueHasChanged(timecode) || dimensions_changed || enable_always_update) { + redraw(timecode); + redrew_image = true; + } - if (texture == nullptr || texture->width() != img.width() || texture->height() != img.height()) { - delete_texture(); + 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); + 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; - } + redrew_image = true; + } - if (redrew_image) { - texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits()); - } + if (redrew_image) { + texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits()); + } - return texture->textureId(); + return texture->textureId(); } void Effect::process_audio(double, double, quint8*, int, int) {} @@ -942,158 +944,158 @@ void Effect::process_audio(double, double, quint8*, int, int) {} void Effect::gizmo_draw(double, GLTextureCoords &) {} void Effect::gizmo_move(EffectGizmo* gizmo, int x_movement, int y_movement, double timecode, bool done) { - for (int i=0;ix_field1 != nullptr) { - gizmo->x_field1->set_double_value(gizmo->x_field1->get_double_value(timecode) + x_movement*gizmo->x_field_multi1); - gizmo->x_field1->make_key_from_change(ca); - } - if (gizmo->y_field1 != nullptr) { - gizmo->y_field1->set_double_value(gizmo->y_field1->get_double_value(timecode) + y_movement*gizmo->y_field_multi1); - gizmo->y_field1->make_key_from_change(ca); - } - if (gizmo->x_field2 != nullptr) { - gizmo->x_field2->set_double_value(gizmo->x_field2->get_double_value(timecode) + x_movement*gizmo->x_field_multi2); - gizmo->x_field2->make_key_from_change(ca); - } - if (gizmo->y_field2 != nullptr) { - gizmo->y_field2->set_double_value(gizmo->y_field2->get_double_value(timecode) + y_movement*gizmo->y_field_multi2); - gizmo->y_field2->make_key_from_change(ca); - } - if (done) olive::UndoStack.push(ca); - break; - } - } + for (int i=0;ix_field1 != nullptr) { + gizmo->x_field1->set_double_value(gizmo->x_field1->get_double_value(timecode) + x_movement*gizmo->x_field_multi1); + gizmo->x_field1->make_key_from_change(ca); + } + if (gizmo->y_field1 != nullptr) { + gizmo->y_field1->set_double_value(gizmo->y_field1->get_double_value(timecode) + y_movement*gizmo->y_field_multi1); + gizmo->y_field1->make_key_from_change(ca); + } + if (gizmo->x_field2 != nullptr) { + gizmo->x_field2->set_double_value(gizmo->x_field2->get_double_value(timecode) + x_movement*gizmo->x_field_multi2); + gizmo->x_field2->make_key_from_change(ca); + } + if (gizmo->y_field2 != nullptr) { + gizmo->y_field2->set_double_value(gizmo->y_field2->get_double_value(timecode) + y_movement*gizmo->y_field_multi2); + gizmo->y_field2->make_key_from_change(ca); + } + if (done) olive::UndoStack.push(ca); + break; + } + } } void Effect::gizmo_world_to_screen() { - GLfloat view_val[16]; - GLfloat projection_val[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, view_val); - glGetFloatv(GL_PROJECTION_MATRIX, projection_val); + GLfloat view_val[16]; + GLfloat projection_val[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, view_val); + glGetFloatv(GL_PROJECTION_MATRIX, projection_val); - QMatrix4x4 view_matrix(view_val); - QMatrix4x4 projection_matrix(projection_val); + QMatrix4x4 view_matrix(view_val); + QMatrix4x4 projection_matrix(projection_val); - for (int i=0;iget_point_count();j++) { - QVector4D screen_pos = QVector4D(g->world_pos[j].x(), g->world_pos[j].y(), 0, 1.0) * (view_matrix * projection_matrix); + for (int j=0;jget_point_count();j++) { + QVector4D screen_pos = QVector4D(g->world_pos[j].x(), g->world_pos[j].y(), 0, 1.0) * (view_matrix * projection_matrix); - int adjusted_sx1 = qRound(((screen_pos.x()*0.5f)+0.5f)*parent_clip->sequence->width); - int adjusted_sy1 = qRound((1.0f-((screen_pos.y()*0.5f)+0.5f))*parent_clip->sequence->height); + int adjusted_sx1 = qRound(((screen_pos.x()*0.5f)+0.5f)*parent_clip->sequence->width); + int adjusted_sy1 = qRound((1.0f-((screen_pos.y()*0.5f)+0.5f))*parent_clip->sequence->height); - g->screen_pos[j] = QPoint(adjusted_sx1, adjusted_sy1); - } - } + g->screen_pos[j] = QPoint(adjusted_sx1, adjusted_sy1); + } + } } bool Effect::are_gizmos_enabled() { - return (gizmos.size() > 0); + return (gizmos.size() > 0); } void Effect::redraw(double) { - /* - // run javascript - QPainter p(&img); - painter_wrapper.img = &img; - painter_wrapper.painter = &p; + /* + // run javascript + QPainter p(&img); + painter_wrapper.img = &img; + painter_wrapper.painter = &p; - jsEngine.globalObject().setProperty("painter", wrapper_obj); + jsEngine.globalObject().setProperty("painter", wrapper_obj); jsEngine.globalObject().setProperty("width", parent_clip->media_width()); jsEngine.globalObject().setProperty("height", parent_clip->media_height()); - for (int i=0;ifieldCount();j++) { - EffectField* field = row->field(j); - if (!field->id.isEmpty()) { - switch (field->type) { - case EFFECT_FIELD_DOUBLE: - jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode)); - break; - case EFFECT_FIELD_COLOR: - jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name()); - break; - case EFFECT_FIELD_STRING: - jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode)); - break; - case EFFECT_FIELD_BOOL: - jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode)); - break; - case EFFECT_FIELD_COMBO: - jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode)); - break; - case EFFECT_FIELD_FONT: - jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode)); - break; - } - } - } - } + for (int i=0;ifieldCount();j++) { + EffectField* field = row->field(j); + if (!field->id.isEmpty()) { + switch (field->type) { + case EFFECT_FIELD_DOUBLE: + jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode)); + break; + case EFFECT_FIELD_COLOR: + jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name()); + break; + case EFFECT_FIELD_STRING: + jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode)); + break; + case EFFECT_FIELD_BOOL: + jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode)); + break; + case EFFECT_FIELD_COMBO: + jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode)); + break; + case EFFECT_FIELD_FONT: + jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode)); + break; + } + } + } + } - jsEngine.evaluate(script); - */ + jsEngine.evaluate(script); + */ } bool Effect::valueHasChanged(double timecode) { - if (cachedValues.size() == 0) { - for (int i=0;ifieldCount();j++) { - cachedValues.append(crow->field(j)->get_current_data()); - } - } - return true; - } else { - bool changed = false; - int index = 0; - for (int i=0;ifieldCount();j++) { - EffectField* field = crow->field(j); - field->validate_keyframe_data(timecode); - if (cachedValues.at(index) != field->get_current_data()) { - changed = true; - } - cachedValues[index] = field->get_current_data(); - index++; - } - } - return changed; - } + if (cachedValues.size() == 0) { + for (int i=0;ifieldCount();j++) { + cachedValues.append(crow->field(j)->get_current_data()); + } + } + return true; + } else { + bool changed = false; + int index = 0; + for (int i=0;ifieldCount();j++) { + EffectField* field = crow->field(j); + field->validate_keyframe_data(timecode); + if (cachedValues.at(index) != field->get_current_data()) { + changed = true; + } + cachedValues[index] = field->get_current_data(); + index++; + } + } + return changed; + } } void Effect::delete_texture() { - if (texture != nullptr) { - delete texture; - texture = nullptr; - } + if (texture != nullptr) { + delete texture; + texture = nullptr; + } } const EffectMeta* get_meta_from_name(const QString& input) { - int split_index = input.indexOf('/'); - QString category; - if (split_index > -1) { - category = input.left(split_index); - } - QString name = input.mid(split_index + 1); + int split_index = input.indexOf('/'); + QString category; + if (split_index > -1) { + category = input.left(split_index); + } + QString name = input.mid(split_index + 1); - for (int j=0;j(a) + static_cast(b); - mixed_sample = qMax(qMin(mixed_sample, static_cast(INT16_MAX)), static_cast(INT16_MIN)); - return static_cast(mixed_sample); + qint32 mixed_sample = static_cast(a) + static_cast(b); + mixed_sample = qMax(qMin(mixed_sample, static_cast(INT16_MAX)), static_cast(INT16_MIN)); + return static_cast(mixed_sample); } diff --git a/project/effect.h b/project/effect.h index 64224ec97..d97c2214b 100644 --- a/project/effect.h +++ b/project/effect.h @@ -58,7 +58,21 @@ struct EffectMeta { int type; int subtype; }; -extern QVector effects; + +struct BlendMode { + QString name; + QString url; + QString function_name; + + bool loaded; +}; + +namespace olive { + extern QVector effects; + extern QVector blend_modes; + + extern QString generated_blending_shader; +} double log_volume(double linear); @@ -94,36 +108,6 @@ 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; @@ -281,11 +265,4 @@ private: void validate_meta_path(); }; -class EffectInit : public QThread { -public: - EffectInit(); -protected: - void run(); -}; - #endif // EFFECT_H diff --git a/project/effectloaders.cpp b/project/effectloaders.cpp index 443caec1e..15f9ecc8b 100644 --- a/project/effectloaders.cpp +++ b/project/effectloaders.cpp @@ -33,239 +33,378 @@ #include +QMutex olive::effects_loaded; + #ifndef NOFREI0R #include typedef void (*f0rGetPluginInfo)(f0r_plugin_info_t* info); #endif void load_internal_effects() { - if (!olive::CurrentRuntimeConfig.shaders_are_enabled) qWarning() << "Shaders are disabled, some effects may be nonfunctional"; + if (!olive::CurrentRuntimeConfig.shaders_are_enabled) qWarning() << "Shaders are disabled, some effects may be nonfunctional"; - EffectMeta em; + EffectMeta em; - // load internal effects - em.path = ":/internalshaders"; + // load internal effects + em.path = ":/internalshaders"; - em.type = EFFECT_TYPE_EFFECT; - em.subtype = EFFECT_TYPE_AUDIO; + em.type = EFFECT_TYPE_EFFECT; + em.subtype = EFFECT_TYPE_AUDIO; - em.name = "Volume"; - em.internal = EFFECT_INTERNAL_VOLUME; - effects.append(em); + em.name = "Volume"; + em.internal = EFFECT_INTERNAL_VOLUME; + olive::effects.append(em); - em.name = "Pan"; - em.internal = EFFECT_INTERNAL_PAN; - effects.append(em); + em.name = "Pan"; + em.internal = EFFECT_INTERNAL_PAN; + olive::effects.append(em); #ifndef NOVST - em.name = "VST Plugin 2.x"; - em.internal = EFFECT_INTERNAL_VST; - effects.append(em); + em.name = "VST Plugin 2.x"; + em.internal = EFFECT_INTERNAL_VST; + olive::effects.append(em); #endif - em.name = "Tone"; - em.internal = EFFECT_INTERNAL_TONE; - effects.append(em); + em.name = "Tone"; + em.internal = EFFECT_INTERNAL_TONE; + olive::effects.append(em); - em.name = "Noise"; - em.internal = EFFECT_INTERNAL_NOISE; - effects.append(em); + em.name = "Noise"; + em.internal = EFFECT_INTERNAL_NOISE; + olive::effects.append(em); - em.name = "Fill Left/Right"; - em.internal = EFFECT_INTERNAL_FILLLEFTRIGHT; - effects.append(em); + em.name = "Fill Left/Right"; + em.internal = EFFECT_INTERNAL_FILLLEFTRIGHT; + olive::effects.append(em); - em.subtype = EFFECT_TYPE_VIDEO; + em.subtype = EFFECT_TYPE_VIDEO; - em.name = "Transform"; - em.category = "Distort"; - em.internal = EFFECT_INTERNAL_TRANSFORM; - effects.append(em); + em.name = "Transform"; + em.category = "Distort"; + em.internal = EFFECT_INTERNAL_TRANSFORM; + olive::effects.append(em); - em.name = "Corner Pin"; - em.internal = EFFECT_INTERNAL_CORNERPIN; - effects.append(em); + em.name = "Corner Pin"; + em.internal = EFFECT_INTERNAL_CORNERPIN; + olive::effects.append(em); - /*em.name = "Mask"; - em.internal = EFFECT_INTERNAL_MASK; - effects.append(em);*/ + /*em.name = "Mask"; + em.internal = EFFECT_INTERNAL_MASK; + olive::effects.append(em);*/ - em.name = "Shake"; - em.internal = EFFECT_INTERNAL_SHAKE; - effects.append(em); + em.name = "Shake"; + em.internal = EFFECT_INTERNAL_SHAKE; + olive::effects.append(em); - em.name = "Text"; - em.category = "Render"; - em.internal = EFFECT_INTERNAL_TEXT; - effects.append(em); + em.name = "Text"; + em.category = "Render"; + em.internal = EFFECT_INTERNAL_TEXT; + olive::effects.append(em); - em.name = "Timecode"; - em.internal = EFFECT_INTERNAL_TIMECODE; - effects.append(em); + em.name = "Timecode"; + em.internal = EFFECT_INTERNAL_TIMECODE; + olive::effects.append(em); - em.name = "Solid"; - em.internal = EFFECT_INTERNAL_SOLID; - effects.append(em); + em.name = "Solid"; + em.internal = EFFECT_INTERNAL_SOLID; + olive::effects.append(em); - // internal transitions - em.type = EFFECT_TYPE_TRANSITION; - em.category = ""; + // internal transitions + em.type = EFFECT_TYPE_TRANSITION; + em.category = ""; - em.name = "Cross Dissolve"; - em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE; - effects.append(em); + em.name = "Cross Dissolve"; + em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE; + olive::effects.append(em); - em.subtype = EFFECT_TYPE_AUDIO; + em.subtype = EFFECT_TYPE_AUDIO; - em.name = "Linear Fade"; - em.internal = TRANSITION_INTERNAL_LINEARFADE; - effects.append(em); + em.name = "Linear Fade"; + em.internal = TRANSITION_INTERNAL_LINEARFADE; + olive::effects.append(em); - em.name = "Exponential Fade"; - em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE; - effects.append(em); + em.name = "Exponential Fade"; + em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE; + olive::effects.append(em); - em.name = "Logarithmic Fade"; - em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE; - effects.append(em); + em.name = "Logarithmic Fade"; + em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE; + olive::effects.append(em); } void load_shader_effects() { - QList effects_paths = get_effects_paths(); + QList effects_paths = get_effects_paths(); - for (int h=0;h entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files); - for (int i=0;i entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files); + for (int i=0;i blend_mode_entries = effects_dir.entryList(QStringList("*.blend"), QDir::Files); + for (int i=0;istart(); + EffectInit* init_thread = new EffectInit(); + QObject::connect(init_thread, SIGNAL(finished()), init_thread, SLOT(deleteLater())); + init_thread->start(); } #ifndef NOFREI0R void load_frei0r_effects_worker(const QString& dir, EffectMeta& em, QVector& loaded_names) { - QDir search_dir(dir); - if (search_dir.exists()) { - QList entry_list = search_dir.entryList(LibFilter(), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); - for (int j=0;j(LibAddress(effect, "f0r_get_plugin_info")); - if (get_info_func != nullptr) { - f0r_plugin_info_t info; - get_info_func(&info); + QDir search_dir(dir); + if (search_dir.exists()) { + QList entry_list = search_dir.entryList(LibFilter(), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + for (int j=0;j(LibAddress(effect, "f0r_get_plugin_info")); + if (get_info_func != nullptr) { + f0r_plugin_info_t info; + get_info_func(&info); - if (!loaded_names.contains(info.name) - && info.plugin_type == F0R_PLUGIN_TYPE_FILTER - && info.color_model == F0R_COLOR_MODEL_RGBA8888) { - em.name = info.name; - em.path = dir; - em.filename = entry_list.at(j); - em.tooltip = QString("%1\n%2\n%3\n%4").arg(em.name, info.author, info.explanation, em.filename); + if (!loaded_names.contains(info.name) + && info.plugin_type == F0R_PLUGIN_TYPE_FILTER + && info.color_model == F0R_COLOR_MODEL_RGBA8888) { + em.name = info.name; + em.path = dir; + em.filename = entry_list.at(j); + em.tooltip = QString("%1\n%2\n%3\n%4").arg(em.name, info.author, info.explanation, em.filename); - loaded_names.append(em.name); + loaded_names.append(em.name); - effects.append(em); - } + olive::effects.append(em); + } // qDebug() << "Found:" << info.name << "by" << info.author; - } - LibClose(effect); - } + } + LibClose(effect); + } // qDebug() << search_dir.filePath(entry_list.at(j)); - } - } - } + } + } + } } void load_frei0r_effects() { - QList effect_dirs = get_effects_paths(); + QList effect_dirs = get_effects_paths(); - // add defined paths for frei0r plugins on unix + // add defined paths for frei0r plugins on unix #if defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__) - effect_dirs.prepend("/usr/lib/frei0r-1"); - effect_dirs.prepend("/usr/local/lib/frei0r-1"); - effect_dirs.prepend(QDir::homePath() + "/.frei0r-1/lib"); + effect_dirs.prepend("/usr/lib/frei0r-1"); + effect_dirs.prepend("/usr/local/lib/frei0r-1"); + effect_dirs.prepend(QDir::homePath() + "/.frei0r-1/lib"); #endif - QString env_path(qgetenv("FREI0R_PATH")); - if (!env_path.isEmpty()) effect_dirs.append(env_path); + QString env_path(qgetenv("FREI0R_PATH")); + if (!env_path.isEmpty()) effect_dirs.append(env_path); - QVector loaded_names; + QVector loaded_names; - // search for paths - EffectMeta em; - em.category = "Frei0r"; - em.type = EFFECT_TYPE_EFFECT; - em.subtype = EFFECT_TYPE_VIDEO; - em.internal = EFFECT_INTERNAL_FREI0R; + // search for paths + EffectMeta em; + em.category = "Frei0r"; + em.type = EFFECT_TYPE_EFFECT; + em.subtype = EFFECT_TYPE_VIDEO; + em.internal = EFFECT_INTERNAL_FREI0R; - for (int i=0;i 0 && line.at(0) == '#') { + + if (line.startsWith("#olive name ")) { + + // The blending mode can specify its own name + b.name = line.mid(12); + + } else if (line.startsWith("#pragma glslify: export(")) { + + // Get function name + b.function_name = line.mid(24, line.length()-25); + + } else if (line.contains("require")) { + + // This function wanted to include an external shader + + int index_of_last_bracked = line.lastIndexOf('(') + 1; + + QString include_fn = line.mid(index_of_last_bracked, line.length() - index_of_last_bracked - 1); + include_fn.append(".blend"); + QString include_path = QFileInfo(b.url).dir().filePath(include_fn); + + // see if this file is already in the blend modes list + bool found = false; + for (int i=0;i 0.0) {\n" + " float true_opacity = opacity * fg_color.a;\n" + " vec3 unmultipled_fg = max(vec3(0.0), min(vec3(1.0), fg_color.rgb / fg_color.a));\n" + " vec3 blended_rgb = blend(bg_color.rgb, unmultipled_fg, true_opacity);\n" // Use switcher function above to blend RGBs + " vec4 composite = vec4(blended_rgb, bg_color.a + true_opacity);\n" + " gl_FragColor = composite;\n" + " } else {\n" + " gl_FragColor = bg_color;\n" + " }\n" + "}\n"); +} + EffectInit::EffectInit() { - panel_effect_controls->effects_loaded.lock(); + olive::effects_loaded.lock(); } void EffectInit::run() { - qInfo() << "Initializing effects..."; - load_internal_effects(); - load_shader_effects(); + qInfo() << "Initializing effects..."; + load_internal_effects(); + load_shader_effects(); #ifndef NOFREI0R - load_frei0r_effects(); + load_frei0r_effects(); #endif - panel_effect_controls->effects_loaded.unlock(); - qInfo() << "Finished initializing effects"; + GenerateBlendingShader(); + olive::effects_loaded.unlock(); + qInfo() << "Finished initializing effects"; } diff --git a/project/effectloaders.h b/project/effectloaders.h index 9a4c2a86e..1c09268d4 100644 --- a/project/effectloaders.h +++ b/project/effectloaders.h @@ -22,7 +22,20 @@ #define EFFECTLOADERS_H #include +#include +#include + +namespace olive { + extern QMutex effects_loaded; +} void init_effects(); +class EffectInit : public QThread { +public: + EffectInit(); +protected: + void run(); +}; + #endif // EFFECTLOADERS_H diff --git a/rendering/renderfunctions.cpp b/rendering/renderfunctions.cpp index 4f6a08990..bbf0b8b79 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -367,11 +367,11 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { 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(); + params.ctx->functions()->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ZERO, GL_ONE, GL_ZERO); textureID = draw_clip(c->fbo[0], textureID, true); - params.premultiply_program->release(); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); fbo_switcher = true; } @@ -399,7 +399,7 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { 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.blendmode = -1; coords.opacity = 1.0; // if auto-scale is enabled, auto-scale the clip @@ -514,8 +514,8 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { - // copy front buffer to back buffer (only if we're using blending modes - which we usually will be) - if (!olive::CurrentRuntimeConfig.disable_blending) { + // copy front buffer to back buffer (only if we're using a blending mode) + if (coords.blendmode >= 0) { if (params.nests.size() > 0) { draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true); } else { @@ -533,8 +533,8 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { // bind front buffer as draw buffer params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo); - if (olive::CurrentRuntimeConfig.disable_blending) { - // some GPUs don't like the blending shader, so we provide a pure GL fallback here + // Check if we're using a blend mode (< 0 means no blend mode) + if (coords.blendmode < 0) { params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1); @@ -543,7 +543,9 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { 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); @@ -572,6 +574,7 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { // 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 diff --git a/rendering/renderfunctions.h b/rendering/renderfunctions.h index 3c1f49734..1d7ed9dc5 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -31,375 +31,363 @@ #include "panels/viewer.h" /** - * @brief The ComposeSequenceParams struct - * - * Struct sent to the compose_sequence() function. - */ + * @brief The ComposeSequenceParams struct + * + * Struct sent to the compose_sequence() function. + */ struct ComposeSequenceParams { - /** - * @brief Reference to the Viewer class that's calling compose_sequence() - * - * Primarily used for calling Viewer::play_wake() when appropriate. - */ - Viewer* viewer; + /** + * @brief Reference to the Viewer class that's calling compose_sequence() + * + * Primarily used for calling Viewer::play_wake() when appropriate. + */ + Viewer* viewer; - /** - * @brief The OpenGL context to use while rendering. - * - * For video rendering, this must be a valid OpenGL context. For audio, this variable is never accessed. - * - * \see ComposeSequenceParams::video - */ - QOpenGLContext* ctx; + /** + * @brief The OpenGL 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 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 nests; + /** + * @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 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 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 - * - * The currently active Effect that compose_sequence() will update the gizmos of. - */ - Effect* gizmos; + /** + * @brief Set to the Effect whose gizmos were chosen to be drawn on screen + * + * The currently active Effect that compose_sequence() will update the gizmos of. + */ + Effect* gizmos; - /** - * @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 A variable that compose_sequence() will set to **TRUE** if any of the clips couldn't be shown. + * + * A footage item or shader may not be ready at the time this frame is drawn. If compose_sequence() couldn't draw + * any of the clips in the scene, this variable is set to **TRUE** indicating that the image rendered is a + * "best effort", but not the actual image. + * + * This variable should be checked after compose_sequence() and a repaint should be triggered if it's **TRUE**. + * + * \note This variable is probably bad design and is a relic of an earlier rendering backend. There may be a better + * way to communicate this information. + * + * Additionally, since + * compose_sequence() for video will now always run in a separate thread anyway, there's no real issue with + * stalling it to wait for footage to complete opening or whatever may be lagging behind. A possible side effect + * of this though is that the preview may become less responsive if it's stuck trying to render one frame. With + * the current system, the preview may show incomplete frames occasionally but at least it will show something. + * This may be preferable. See ComposeSequenceParams::single_threaded for a similar function that could be + * removed. + */ + bool texture_failed; - /** - * @brief Run all cachers in the same thread that compose_sequence() is in - * - * Standard behavior is that all clips cache frames in their own thread and signals are sent between - * compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling - * the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a - * technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used - * when rendering as timing isn't as important as creating output frames as quickly as possible. - * - * \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes - * it easier to synchronize everything, export performance could probably benefit from keeping them in separate - * threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could - * be removed. - */ - bool wait_for_mutexes; + /** + * @brief Run all cachers in the same thread that compose_sequence() is in + * + * Standard behavior is that all clips cache frames in their own thread and signals are sent between + * compose_sequence() and the clip's cacher thread regarding which frames to display and cache without stalling + * the compose_sequence() thread. Setting this to **TRUE** will run all cachers in the same thread creating a + * technically more "perfect" connection between them that will also stall the compose_sequence() thread. Used + * when rendering as timing isn't as important as creating output frames as quickly as possible. + * + * \note Exporting should probably be rewritten without this. While running all the cachers in one thread makes + * it easier to synchronize everything, export performance could probably benefit from keeping them in separate + * threads and syncing up with them. See ComposeSequenceParams::texture_failed for a similar function that could + * be removed. + */ + bool wait_for_mutexes; - /** - * @brief Set the current playback speed (adjusted with Shuttle Left/Right) - * - * Only used for audio rendering to determine how many samples to skip in order to play audio at the correct speed. - * - * \see ComposeSequenceParams::video - */ - int playback_speed; + /** + * @brief 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 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 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 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 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 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 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 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 shader containing OpenColorIO shader information - */ - QOpenGLShaderProgram* ocio_shader; - - /** - * @brief OpenGL texture containing LUT obtained form OpenColorIO - */ - GLuint ocio_lut_texture; + /** + * @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. - */ + * @brief Compose a frame of a given sequence + * + * For any given Sequence, this function will render the current frame indicated by Sequence::playhead. Will + * automatically open and close clips (memory allocation and file handles) as necessary, communicate with the + * Clip::cacher objects to retrieve upcoming frames and store them in memory, run Effect processing functions, and + * finally composite all the currently active clips together into a final texture. + * + * Will sometimes render a frame incomplete or inaccurately, e.g. if a video file hadn't finished opening by the time + * of the render or a clip's cacher didn't have the requested frame available at the time of the render. If so, + * the `texture_failed` variable of `params` will be set to **TRUE**. Check this after calling compose_sequence() and + * if it is **TRUE**, compose_sequence() should be called again later to attempt another render (unless the Sequence + * is being played, in which case just play the next frame rather than redrawing an old frame). + * + * @param params + * + * A struct of parameters to use while rendering. + * + * @return A reference to the OpenGL texture resulting from the render. Will usually be equal to + * ComposeSequenceParams::main_attachment unless it's rendering a nested sequence, in which case it'll be a reference + * to one of the textures referenced by Clip::fbo. Can be used directly to draw the rendered frame. + */ GLuint compose_sequence(ComposeSequenceParams ¶ms); /** - * @brief Convenience wrapper function for compose_sequence() to render audio - * - * Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for - * video rendering. For audio rendering, this function is easier to handle and will correctly set up - * compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object. - * - * @param viewer - * - * The Viewer object calling this function - * - * @param seq - * - * The Sequence whose audio to render. - * - * @param playback_speed - * - * The current playback speed (controlled by Shuttle Left/Right) - * - * @param - * - * Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**. - */ + * @brief Convenience wrapper function for compose_sequence() to render audio + * + * Much of the functionality provided (and parameters required) by compose_sequence() is only useful/necessary for + * video rendering. For audio rendering, this function is easier to handle and will correctly set up + * compose_sequence() to render audio without the cumbersome effort of setting up a ComposeSequenceParams object. + * + * @param viewer + * + * The Viewer object calling this function + * + * @param seq + * + * The Sequence whose audio to render. + * + * @param playback_speed + * + * The current playback speed (controlled by Shuttle Left/Right) + * + * @param + * + * Whether to wait for media to open or simply fail if the media is not yet open. This should usually be **FALSE**. + */ void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed, bool wait_for_mutexes); /** - * @brief Rescale a frame number between two frame rates - * - * Converts a frame number from one frame rate to its equivalent in another frame rate - * - * @param framenumber - * - * The frame number to convert - * - * @param source_frame_rate - * - * Frame rate that the frame number is currently in - * - * @param target_frame_rate - * - * Frame rate to convert to - * - * @return - * - * Rescaled frame number - */ + * @brief Rescale a frame number between two frame rates + * + * Converts a frame number from one frame rate to its equivalent in another frame rate + * + * @param framenumber + * + * The frame number to convert + * + * @param source_frame_rate + * + * Frame rate that the frame number is currently in + * + * @param target_frame_rate + * + * Frame rate to convert to + * + * @return + * + * Rescaled frame number + */ long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); /** - * @brief Get timecode - * - * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start - * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; - * - * @param c - * - * Clip to get the timecode of - * - * @param playhead - * - * Sequence playhead to convert to a clip/media timecode - * - * @return - * - * Timecode in seconds - */ -double get_timecode(Clip *c, long playhead); + * @brief Get timecode + * + * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start + * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; + * + * @param c + * + * Clip to get the timecode of + * + * @param playhead + * + * Sequence playhead to convert to a clip/media timecode + * + * @return + * + * Timecode in seconds + */ +double get_timecode(Clip *c, long playhead); /** - * @brief Convert playhead frame number to a clip frame number - * - * Converts a Timeline playhead to a the current clip's frame. Equivalent to - * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. - * - * @param c - * - * The clip to get the current frame number of - * - * @param playhead - * - * The current Timeline frame number - * - * @return - * - * The curren frame number of the clip at `playhead` - */ + * @brief Convert playhead frame number to a clip frame number + * + * Converts a Timeline playhead to a the current clip's frame. Equivalent to + * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. + * + * @param c + * + * The clip to get the current frame number of + * + * @param playhead + * + * The current Timeline frame number + * + * @return + * + * The curren frame number of the clip at `playhead` + */ long playhead_to_clip_frame(Clip* c, long playhead); /** - * @brief Converts the playhead to clip seconds - * - * Get the current timecode at the playhead in terms of clip seconds. - * - * FIXME: Possible duplicate of get_timecode()? Will need to research this more. - * - * @param c - * - * Clip to return clip seconds of. - * - * @param playhead - * - * Current Timeline playhead to convert to clip seconds - * - * @return - * - * Clip time in seconds - */ -double playhead_to_clip_seconds(Clip *c, long playhead); + * @brief Converts the playhead to clip seconds + * + * Get the current timecode at the playhead in terms of clip seconds. + * + * FIXME: Possible duplicate of get_timecode()? Will need to research this more. + * + * @param c + * + * Clip to return clip seconds of. + * + * @param playhead + * + * Current Timeline playhead to convert to clip seconds + * + * @return + * + * Clip time in seconds + */ +double playhead_to_clip_seconds(Clip *c, long playhead); /** - * @brief Convert seconds to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param seconds - * - * Clip time in seconds - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ + * @brief Convert seconds to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param seconds + * + * Clip time in seconds + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ int64_t seconds_to_timestamp(Clip* c, double seconds); /** - * @brief Convert Timeline playhead to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param playhead - * - * Timeline playhead to convert to a timestamp - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ -int64_t playhead_to_timestamp(Clip *c, long playhead); + * @brief Convert Timeline playhead to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param playhead + * + * Timeline playhead to convert to a timestamp + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ +int64_t playhead_to_timestamp(Clip *c, long playhead); /** - * @brief Close all open clips in a Sequence - * - * Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a - * result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that - * Sequence too. - * - * @param s - * - * The Sequence to close all clips on. - */ + * @brief Close all open clips in a Sequence + * + * Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a + * result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that + * Sequence too. + * + * @param s + * + * The Sequence to close all clips on. + */ void close_active_clips(SequencePtr s); #endif // RENDERFUNCTIONS_H diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index 0c3ff96f3..8babc9edc 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -32,13 +32,13 @@ namespace OCIO = OCIO_NAMESPACE; #include "rendering/renderfunctions.h" #include "project/sequence.h" +#include "project/effectloaders.h" RenderThread::RenderThread() : gizmos(nullptr), share_ctx(nullptr), ctx(nullptr), blend_mode_program(nullptr), - premultiply_program(nullptr), seq(nullptr), tex_width(-1), tex_height(-1), @@ -99,13 +99,10 @@ void RenderThread::run() { blend_mode_program = new QOpenGLShaderProgram(); blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/internalshaders/common.vert"); - blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/internalshaders/blending.frag"); + olive::effects_loaded.lock(); + blend_mode_program->addShaderFromSourceCode(QOpenGLShader::Fragment, olive::generated_blending_shader); + olive::effects_loaded.unlock(); 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(); } // draw frame @@ -150,7 +147,6 @@ void RenderThread::paint() { params.wait_for_mutexes = true; params.playback_speed = 1; params.blend_mode_program = blend_mode_program; - params.premultiply_program = premultiply_program; params.backend_buffer1 = back_buffer_1.buffer(); params.backend_buffer2 = back_buffer_2.buffer(); params.backend_attachment1 = back_buffer_1.texture(); @@ -277,9 +273,6 @@ void RenderThread::delete_buffers() { void RenderThread::delete_shaders() { delete blend_mode_program; blend_mode_program = nullptr; - - delete premultiply_program; - premultiply_program = nullptr; } void RenderThread::delete_ctx() { diff --git a/rendering/renderthread.h b/rendering/renderthread.h index e18be91d3..7f2c6cdcd 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -92,7 +92,6 @@ private: QOpenGLContext* share_ctx; QOpenGLContext* ctx; QOpenGLShaderProgram* blend_mode_program; - QOpenGLShaderProgram* premultiply_program; FramebufferObject back_buffer_1; FramebufferObject back_buffer_2;