upgrade to ffmpeg 4.2, rework mask for speedup
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / playback3d.C
index 68c914c381607e0ffa3d1594c0fe215b4eb4b6d4..503e57185b1c01d14462e25a02ea50f93c7933f4 100644 (file)
@@ -38,6 +38,7 @@
 #include "pluginclient.h"
 #include "pluginvclient.h"
 #include "edlsession.h"
+#include "track.h"
 #include "transportque.inc"
 #include "vframe.h"
 
@@ -270,39 +271,81 @@ static const char *feather_frag =
        "#version 430\n"
        "layout(location=0) out vec4 color;\n"
        "uniform sampler2D tex;\n"
-// apparently, only doubles index properly in shared buffers
-       "buffer buf { dvec2 points[]; };\n"
+       "const int MAX = 1024;\n"
+       "uniform float psf[MAX];\n"
+       "uniform int n;\n"
+       "uniform vec2 dxy;\n"
+       "uniform vec2 twh;\n"
+       "\n"
+       "void main() {\n"
+       "       vec2 tc = gl_FragCoord.xy/textureSize(tex,0);\n"
+       "       color = texture(tex, tc);\n"
+       "       float c = color.r, f = c*psf[0];\n"
+       "       for( int i=1; i<n; ++i ) {\n"
+       "               vec2 dd = float(i)*dxy;\n"
+       "               vec2 a = tc+dd, ac = min(max(vec2(0.),a), twh);\n"
+       "               vec2 b = tc-dd, bc = min(max(vec2(0.),b), twh);\n"
+       "               float fa = texture2D(tex, ac).r * psf[i];\n"
+       "               float fb = texture2D(tex, bc).r * psf[i];\n"
+       "               float m = max(fa, fb);\n"
+       "               if( f < m ) f = m;\n"
+       "       }\n"
+       "       if( c < f ) color = vec4(f);\n"
+       "}\n";
+
+static const char *max_frag =
+       "#version 430\n"
+       "layout(location=0) out vec4 color;\n"
+       "uniform sampler2D tex;\n"
+       "uniform sampler2D tex1;\n"
        "uniform float r;\n"
        "uniform float v;\n"
+       "\n"
        "void main() {\n"
-       "       vec2 tex_st = gl_FragCoord.xy/textureSize(tex,0);\n"
-       "       color = texture(tex, tex_st);\n"
-       "       if( r==0. ) return;\n"
-       "       float rv = r*v>0. ? 1 : -1;\n"
-       "       float rr = r*r, dr = 1./rr;\n"
-       "       float vv = v>=0 ? 1.-v : 1.+v;\n"
-       "       float fg = rv>=0 ? vv : 1.;\n"
-       "       float bg = rv>=0 ? 1. : vv;\n"
-       "       int len = points.length();\n"
-       "       for( int i=0; i<len; ++i ) {\n"
-       "               float dx = float(points[i].x) - gl_FragCoord.x;\n"
-       "               float dy = float(points[i].y) - gl_FragCoord.y;\n"
-       "               float dd = dx*dx + dy*dy;\n"
-       "               if( dd >= rr ) continue;\n"
-       "               float d = dd*dr;\n"
-       "               float a = (1.-d)*fg + d*bg;\n"
-       "               if( rv*(color.a-a) > 0 ) color = vec4(a);\n"
-       "       }\n"
+       "       vec2 tc = gl_FragCoord.xy/textureSize(tex,0);\n"
+       "       color = texture2D(tex1, tc);\n"
+       "       float c = texture2D(tex, tc).r;\n"
+       "       float b = r<0 ? 1. : 0.;\n"
+       "       if( c == b ) return;\n"
+       "       float iv = v>=0. ? 1. : -1.;\n"
+       "       float rr = r!=0. ? r : 1.;\n"
+       "       float rv = rr*v>=0. ? 1. : -1.;\n"
+       "       float vv = v>=0. ? 1.-v : 1.+v;\n"
+       "       float fg = rv>0. ? vv : 1.;\n"
+       "       float bg = rv>0. ? 1. : vv;\n"
+       "       float a = c*fg + (1.-c)*bg;\n"
+       "       if( iv*(color.a-a) > 0. ) color = vec4(a);\n"
        "}\n";
 
-static const char *alpha_frag =
+static const char *multiply_mask4_frag =
        "uniform sampler2D tex;\n"
-       "uniform sampler2D tex2;\n"
-       "uniform vec2 tex2_dimensions;\n"
-       "void main() {\n" \
+       "uniform sampler2D tex1;\n"
+       "void main()\n"
+       "{\n"
        "       gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
-       "       vec4 canvas = texture2D(tex2, gl_FragCoord.xy / tex2_dimensions);\n"
-       "       gl_FragColor.a = canvas.a;\n"
+       "       gl_FragColor.a *= texture2D(tex1, gl_TexCoord[0].st).r;\n"
+       "}\n";
+
+static const char *multiply_mask3_frag =
+       "uniform sampler2D tex;\n"
+       "uniform sampler2D tex1;\n"
+       "void main()\n"
+       "{\n"
+       "       gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
+       "       float a = texture2D(tex1, gl_TexCoord[0].st).r;\n"
+       "       gl_FragColor.rgb *= vec3(a, a, a);\n"
+       "}\n";
+
+static const char *multiply_yuvmask3_frag =
+       "uniform sampler2D tex;\n"
+       "uniform sampler2D tex1;\n"
+       "void main()\n"
+       "{\n"
+       "       gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
+       "       float a = texture2D(tex1, gl_TexCoord[0].st).r;\n"
+       "       gl_FragColor.gb -= vec2(0.5, 0.5);\n"
+       "       gl_FragColor.rgb *= vec3(a, a, a);\n"
+       "       gl_FragColor.gb += vec2(0.5, 0.5);\n"
        "}\n";
 
 static const char *fade_rgba_frag =
@@ -368,32 +411,11 @@ void Playback3DCommand::copy_from(BC_SynchronousCommand *command)
        BC_SynchronousCommand::copy_from(command);
 }
 
-//#define GL_BUG 1
-#ifdef GL_BUG
-static void GLAPIENTRY glDebugCallback(GLenum source, GLenum type,
-       GLuint id, GLenum severity, GLsizei length, const GLchar* message,
-       const void* userParam)
-{
-  fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
-       ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ),
-       type, severity, message );
-}
-#endif
-
 Playback3D::Playback3D(MWindow *mwindow)
  : BC_Synchronous()
 {
        this->mwindow = mwindow;
        temp_texture = 0;
-#ifdef GL_BUG
-       //Enabling OpenGL debug output
-       // this does not work
-       glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
-       glEnable(GL_DEBUG_OUTPUT);
-       glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
-       glDebugMessageCallback(glDebugCallback, 0);
-       glEnable(GL_DEBUG_OUTPUT);
-#endif
 }
 
 Playback3D::~Playback3D()
@@ -655,29 +677,21 @@ void Playback3D::write_buffer_sync(Playback3DCommand *command)
        BC_WindowBase *window =
                command->canvas->lock_canvas("Playback3D::write_buffer_sync");
        if( window ) {
+               window->enable_opengl();
 // Update hidden cursor
                window->update_video_cursor();
-// Make sure OpenGL is enabled first.
-               window->enable_opengl();
-
+               command->frame->enable_opengl();
+               command->frame->init_screen();
 //printf("Playback3D::write_buffer_sync 1 %d\n", window->get_id());
-               int flip_y = 0;
                int frame_state = command->frame->get_opengl_state();
-               switch( frame_state ) {
-// Upload texture and composite to screen
-                       case VFrame::RAM:
-                               flip_y = 1;
-                       case VFrame::SCREEN:
-                               command->frame->to_texture();
-                               window->enable_opengl();
-// Composite texture to screen and swap buffer
-                       case VFrame::TEXTURE:
-                               draw_output(command, flip_y);
-                               break;
-                       default:
-                               printf("Playback3D::write_buffer_sync unknown state\n");
-                               break;
+               if( frame_state != VFrame::TEXTURE )
+                       command->frame->to_texture();
+               if( frame_state != VFrame::RAM ) {
+                       command->in_y1 = command->frame->get_h() - command->in_y1;
+                       command->in_y2 = command->frame->get_h() - command->in_y2;
                }
+               window->enable_opengl();
+               draw_output(command, 1);
                command->frame->set_opengl_state(frame_state);
        }
        command->canvas->unlock_canvas();
@@ -712,7 +726,7 @@ void Playback3D::draw_output(Playback3DCommand *command, int flip_y)
                if(!command->is_cleared)
                {
 // If we get here, the virtual console was not used.
-                       init_frame(command, 0);
+                       color_frame(command, 0,0,0,0);
                }
 
 // Texture
@@ -742,14 +756,8 @@ void Playback3D::draw_output(Playback3DCommand *command, int flip_y)
 
 
 //printf("Playback3D::draw_output 2 %f,%f %f,%f -> %f,%f %f,%f\n",
-// command->in_x1,
-// command->in_y1,
-// command->in_x2,
-// command->in_y2,
-// command->out_x1,
-// command->out_y1,
-// command->out_x2,
-// command->out_y2);
+// command->in_x1, command->in_y1, command->in_x2, command->in_y2,
+// command->out_x1, command->out_y1, command->out_x2, command->out_y2);
 
                glUseProgram(0);
 
@@ -760,11 +768,11 @@ void Playback3D::draw_output(Playback3DCommand *command, int flip_y)
 }
 
 
-void Playback3D::init_frame(Playback3DCommand *command, int is_yuv)
+void Playback3D::color_frame(Playback3DCommand *command,
+               float r, float g, float b, float a)
 {
 #ifdef HAVE_GL
-       float gbuv = is_yuv ? 0.5 : 0.0;
-       glClearColor(0.0, gbuv, gbuv, 0.0);
+       glClearColor(r, g, b, a);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 #endif
 }
@@ -788,14 +796,21 @@ void Playback3D::clear_output_sync(Playback3DCommand *command)
 // If we get here, the virtual console is being used.
                command->canvas->get_canvas()->enable_opengl();
                int is_yuv = 0;
+               int color = BLACK, alpha = 0;
 // Using pbuffer for refresh frame.
                if( command->frame ) {
                        command->frame->enable_opengl();
+                       color = command->frame->get_clear_color();
+                       alpha = command->frame->get_clear_alpha();
                        int color_model = command->canvas->mwindow->edl->session->color_model;
                        is_yuv = BC_CModels::is_yuv(color_model);
                }
-
-               init_frame(command, is_yuv);
+               int a = alpha;
+               int r = (color>>16) & 0xff;
+               int g = (color>>8) & 0xff;
+               int b = (color>>0) & 0xff;
+               if( is_yuv ) YUV::yuv.rgb_to_yuv_8(r, g, b);
+               color_frame(command, r/255.f, g/255.f, b/255.f, a/255.f);
        }
        command->canvas->unlock_canvas();
 #endif
@@ -1178,6 +1193,7 @@ public:
        void bind(int texture_unit);
        void read_screen(int x, int y, int w, int h);
        void set_output_texture();
+       void unset_output_texture();
        GLuint fb, rb;
 };
 
@@ -1210,6 +1226,7 @@ void fb_texture::bind(int texture_unit)
 
 void fb_texture::read_screen(int x, int y, int w, int h)
 {
+       bind(1);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glReadBuffer(GL_BACK);
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, x,y, w,h);
@@ -1217,8 +1234,9 @@ void fb_texture::read_screen(int x, int y, int w, int h)
 
 void fb_texture::set_output_texture()
 {
-       glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, get_texture_id(), 0);
-       GLenum dbo[1] = { GL_COLOR_ATTACHMENT0, }; // bind layout(location=0) out vec4 color;
+       GLenum at = GL_COLOR_ATTACHMENT0;
+       glFramebufferTexture(GL_FRAMEBUFFER, at, get_texture_id(), 0);
+       GLenum dbo[1] = { at, }; // bind layout(location=0) out vec4 color;
        glDrawBuffers(1, dbo);
        int ret = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if( ret != GL_FRAMEBUFFER_COMPLETE ) {
@@ -1226,22 +1244,40 @@ void fb_texture::set_output_texture()
                return;
        }
 }
+void fb_texture::unset_output_texture()
+{
+       glDrawBuffers(0, 0);
+       int at = GL_COLOR_ATTACHMENT0;
+       glFramebufferTexture(GL_FRAMEBUFFER, at, 0, 0);
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       glDisable(GL_TEXTURE_2D);
+}
+
+
+class zglTessData : public ArrayList<double *>
+{
+public:
+       zglTessData() { set_array_delete(); }
+       ~zglTessData() { remove_all_objects(); }
+};
 
 static void combineData(GLdouble coords[3],
                GLdouble *vertex_data[4], GLfloat weight[4],
                GLdouble **outData, void *data)
 {
-       ArrayList<double *> *invented = (ArrayList<double *> *)data;
+       zglTessData *invented = (zglTessData *)data;
        GLdouble *vertex = new double[6];
        invented->append(vertex);
        vertex[0] = coords[0];
        vertex[1] = coords[1];
        vertex[2] = coords[2];
        for( int i=3; i<6; ++i ) {
-               vertex[i] = weight[0] * vertex_data[0][i] +
-                       weight[1] * vertex_data[1][i] +
-                       weight[2] * vertex_data[2][i] +
-                       weight[3] * vertex_data[3][i];
+               GLdouble v = 0;
+               for( int k=0; k<4; ++k ) {
+                       if( !weight[k] || !vertex_data[k] ) continue;
+                       v += weight[k] * vertex_data[k][i];
+               }
+               vertex[i] = v;
        }
        *outData = vertex;
 }
@@ -1249,7 +1285,7 @@ static void combineData(GLdouble coords[3],
 // dbug
 static void zglBegin(GLenum mode) { glBegin(mode); }
 static void zglEnd() { glEnd(); }
-static void zglVertex3dv(const GLdouble *v) { glVertex3dv(v); }
+static void zglVertex(const GLdouble *v) { glVertex3dv(v); }
 
 #endif
 
@@ -1282,212 +1318,163 @@ void Playback3D::do_mask_sync(Playback3DCommand *command)
                int w = command->frame->get_w();
                int h = command->frame->get_h();
                MaskEdges edges;
-               float faders[SUBMASKS], feathers[SUBMASKS], bg = 1;
-               MaskPointSet point_set[SUBMASKS];
+               float faders[SUBMASKS], feathers[SUBMASKS], cc = 1;
+               MaskPoints point_set[SUBMASKS];
 // Draw every submask as a new polygon
                int total_submasks = command->keyframe_set->total_submasks(
                        command->start_position_project, PLAY_FORWARD);
+               int show_mask = command->keyframe_set->track->masks;
 
                for(int k = 0; k < total_submasks; k++) {
-                       MaskPointSet &points = point_set[k];
+                       MaskPoints &points = point_set[k];
                        command->keyframe_set->get_points(&points,
                                k, command->start_position_project, PLAY_FORWARD);
                        float fader = command->keyframe_set->get_fader(
                                command->start_position_project, k, PLAY_FORWARD);
                        float v = fader/100.;
                        faders[k] = v;
-                       if( v < 0 && (v+=1) < bg ) bg = v;
                        float feather = command->keyframe_set->get_feather(
                                command->start_position_project, k, PLAY_FORWARD);
                        feathers[k] = feather;
-               }
-// clear screen
-               glDisable(GL_TEXTURE_2D);
-               glClearColor(bg, bg, bg, bg);
-               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-               for(int k = 0; k < total_submasks; k++) {
-                       MaskPointSet &points = point_set[k];
                        MaskEdge &edge = *edges.append(new MaskEdge());
-                       int first_point = 0;
-// Need to tabulate every vertex in persistent memory because
-// gluTessVertex doesn't copy them.
-                       for(int i = 0; i < points.total; i++) {
-                               MaskPoint *point1 = points.values[i];
-                               MaskPoint *point2 = (i >= points.total - 1) ?
-                                       points.values[0] : points.values[i + 1];
-
-                               float x, y;
-                               int segments = 0;
-                               if( point1->control_x2 == 0 && point1->control_y2 == 0 &&
-                                   point2->control_x1 == 0 && point2->control_y1 == 0 )
-                                       segments = 1;
-
-                               float x0 = point1->x, y0 = point1->y;
-                               float x1 = point1->x + point1->control_x2;
-                               float y1 = point1->y + point1->control_y2;
-                               float x2 = point2->x + point2->control_x1;
-                               float y2 = point2->y + point2->control_y1;
-                               float x3 = point2->x, y3 = point2->y;
-
-                               // forward differencing bezier curves implementation taken from GPL code at
-                               // http://cvs.sourceforge.net/viewcvs.py/guliverkli/guliverkli/src/subtitles/Rasterizer.cpp?rev=1.3
-
-                               float cx3, cx2, cx1, cx0, cy3, cy2, cy1, cy0;
-
-                               // [-1 +3 -3 +1]
-                               // [+3 -6 +3  0]
-                               // [-3 +3  0  0]
-                               // [+1  0  0  0]
-
-                               cx3 = -  x0 + 3*x1 - 3*x2 + x3;
-                               cx2 =  3*x0 - 6*x1 + 3*x2;
-                               cx1 = -3*x0 + 3*x1;
-                               cx0 =    x0;
-
-                               cy3 = -  y0 + 3*y1 - 3*y2 + y3;
-                               cy2 =  3*y0 - 6*y1 + 3*y2;
-                               cy1 = -3*y0 + 3*y1;
-                               cy0 =    y0;
-
-                               // This equation is from Graphics Gems I.
-                               //
-                               // The idea is that since we're approximating a cubic curve with lines,
-                               // any error we incur is due to the curvature of the line, which we can
-                               // estimate by calculating the maximum acceleration of the curve.  For
-                               // a cubic, the acceleration (second derivative) is a line, meaning that
-                               // the absolute maximum acceleration must occur at either the beginning
-                               // (|c2|) or the end (|c2+c3|).  Our bounds here are a little more
-                               // conservative than that, but that's okay.
-                               if (segments == 0) {
-                                       float maxaccel1 = fabs(2*cy2) + fabs(6*cy3);
-                                       float maxaccel2 = fabs(2*cx2) + fabs(6*cx3);
-
-                                       float maxaccel = maxaccel1 > maxaccel2 ? maxaccel1 : maxaccel2;
-                                       float h = 1.0;
-
-                                       if(maxaccel > 8.0) h = sqrt((8.0) / maxaccel);
-                                       segments = int(1/h);
-                               }
+                       if( !v || !((show_mask>>k) & 1) || !points.size() ) continue;
+                       edge.load(point_set[k], h);
+                       if( v >= 0 ) continue;
+                       float vv = 1 + v;
+                       if( cc > vv ) cc = vv;
+               }
 
-                               for(int j = 0; j <= segments; j++) {
-                                       float t = (float)j / segments;
-                                       x = cx0 + t*(cx1 + t*(cx2 + t*cx3));
-                                       y = cy0 + t*(cy1 + t*(cy2 + t*cy3));
+               fb_texture *mask = new fb_texture(w, h, color_model);
+               mask->set_output_texture();
+               glClearColor(cc, cc, cc, cc);
+               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+               mask->unset_output_texture();
 
-                                       if(j > 0 || first_point) {
-                                               edge.append(x, y - h);
-                                               first_point = 0;
-                                       }
-                               }
-                       }
-                       if( edge.size() > 0 ) {
-// draw polygon
-                               float fader = faders[k];
-                               float v = fader < 0 ? 1 : 1-fader;
-                               glColor4f(v, v, v, v);
+               unsigned int feather_shader =
+                       VFrame::make_shader(0, in_vertex_frag, feather_frag, 0);
+               unsigned int max_shader =
+                       VFrame::make_shader(0, in_vertex_frag, max_frag, 0);
+               if( feather_shader && max_shader ) {
+                       fb_texture *in = new fb_texture(w, h, color_model);
+                       fb_texture *out = new fb_texture(w, h, color_model);
+                       float tw = 1./out->get_texture_w(), th = 1./out->get_texture_h();
+                       float tw1 = (w-1)*tw, th1 = (h-1)*th;
+                       for(int k = 0; k < total_submasks; k++) {
+                               MaskEdge &edge = *edges[k];
+                               if( edge.size() < 3 ) continue;
+                               float r = feathers[k], v = faders[k];
+                               glBindFramebuffer(GL_FRAMEBUFFER, 0);
+                               glActiveTexture(GL_TEXTURE0);
+                               glDisable(GL_TEXTURE_2D);
+                               float b = r>=0 ? 0. : 1.;
+                               float f = r>=0 ? 1. : 0.;
+                               glClearColor(b, b, b, b);
+                               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+                               glColor4f(f, f, f, f);
+                               glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                                int display_list = glGenLists(1);
-                               glNewList(display_list, GL_COMPILE);
 #if 0
-                               glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+                               glNewList(display_list, GL_COMPILE);
                                glBegin(GL_POLYGON);
                                MaskCoord *c = &edge[0];
                                for( int i=edge.size(); --i>=0; ++c )
                                        glVertex2f(c->x, c->y);
                                glEnd();
+                               glEndList();
+                               glCallList(display_list);
 #else
+                               { zglTessData invented;
                                GLUtesselator *tess = gluNewTess();
-                               gluTessCallback(tess, GLU_TESS_VERTEX,(GLvoid (*)()) &zglVertex3dv);
+                               gluTessProperty(tess, GLU_TESS_TOLERANCE, 0.5);
+                               gluTessCallback(tess, GLU_TESS_VERTEX,(GLvoid (*)()) &zglVertex);
                                gluTessCallback(tess, GLU_TESS_BEGIN,(GLvoid (*)()) &zglBegin);
                                gluTessCallback(tess, GLU_TESS_END,(GLvoid (*)()) &zglEnd);
                                gluTessCallback(tess, GLU_TESS_COMBINE_DATA,(GLvoid (*)()) &combineData);
-                               ArrayList<double *> invented;
-                               invented.set_array_delete();
-
-                               gluTessBeginPolygon(tess, &invented);
+                               glNewList(display_list, GL_COMPILE);
+                               gluTessBeginPolygon(tess, &invented);
                                gluTessBeginContour(tess);
                                MaskCoord *c = &edge[0];
                                for( int i=edge.size(); --i>=0; ++c )
                                        gluTessVertex(tess, (GLdouble *)c, c);
                                gluTessEndContour(tess);
-                               gluTessEndPolygon(tess);
-                               gluDeleteTess(tess);
-                               invented.remove_all_objects();
-#endif
+                               gluTessEndPolygon(tess);
                                glEndList();
                                glCallList(display_list);
+                               gluDeleteTess(tess); }
+#endif
                                glDeleteLists(1, display_list);
-                       }
-               }
-
-// in/out textures
-               fb_texture *in = new fb_texture(w, h, color_model);
-               in->bind(0);
-               in->read_screen(0,0, w,h);
-               fb_texture *out = new fb_texture(w, h, color_model);
-
-               unsigned int frag_shader =
-                       VFrame::make_shader(0, in_vertex_frag, feather_frag, 0);
-               if( frag_shader > 0 ) {
-                       GLuint points[1];
-                       glGenBuffers(1, points);
-                       for(int k = 0; k < total_submasks; k++) {
-                               MaskEdge &edge = *edges[k];
-                               if( !edge.size() ) continue;
-                               if( !faders[k] ) continue;
-                               if( !feathers[k] ) continue;
-                               MaskSpots spots;
-                               for( int i=0; i<edge.size(); ++i ) {
-                                       MaskCoord &a = edge[i];
-                                       MaskCoord &b = i<edge.size()-1 ? edge[i+1] : edge[0];
-                                       draw_spots(spots, a.x,a.y+h, b.x,b.y+h);
+                               in->read_screen(0,0, w,h);
+//in->write_tex("/tmp/in0.ppm");
+                               if( r ) {
+                                       double sig2 = -log(255.0)/(r*r);
+                                       int n = abs((int)r) + 1;
+                                       if( n > 1024 ) n = 1024; // MAX
+                                       float psf[n];  // point spot fn
+                                       for( int i=0; i<n; ++i )
+                                               psf[i] = exp(i*i * sig2);
+                                       glUseProgram(feather_shader);
+                                       glUniform1fv(glGetUniformLocation(feather_shader, "psf"), n, psf);
+                                       glUniform1i(glGetUniformLocation(feather_shader, "n"), n);
+                                       glUniform2f(glGetUniformLocation(feather_shader, "dxy"), tw, 0.);
+                                       glUniform2f(glGetUniformLocation(feather_shader, "twh"), tw1, th1);
+                                       glUniform1i(glGetUniformLocation(feather_shader, "tex"), 0);
+                                       in->bind(0);
+                                       out->set_output_texture();
+                                       out->draw_texture(0,0, w,h, 0,0, w,h);
+                                       out->unset_output_texture();
+//out->write_tex("/tmp/out1.ppm");
+                                       fb_texture *t = in;  in = out;  out = t;
+                                       glUniform2f(glGetUniformLocation(feather_shader, "dxy"), 0., th);
+                                       in->bind(0);
+                                       out->set_output_texture();
+                                       out->draw_texture(0,0, w,h, 0,0, w,h);
+                                       out->unset_output_texture();
+//out->write_tex("/tmp/out2.ppm");
+                                       glUseProgram(0);
+                                       t = in;  in = out;  out = t;
                                }
-                               int sz = spots.size() * sizeof(MaskSpot);
-                               glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, points[0], 0, sz);
-                               glBufferData(GL_SHADER_STORAGE_BUFFER, sz, &spots[0], GL_DYNAMIC_COPY);
-                               glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
-                               glUseProgram(frag_shader);
-                               float r = feathers[k], v = faders[k];
-                               glUniform1f(glGetUniformLocation(frag_shader, "r"), r);
-                               glUniform1f(glGetUniformLocation(frag_shader, "v"), v);
+
+                               glUseProgram(max_shader);
                                in->bind(0);
-                               glUniform1i(glGetUniformLocation(frag_shader, "tex"), 0);
-                               out->set_output_texture();
+//in->write_tex("/tmp/in1.ppm");
+//mask->write_tex("/tmp/mask1.ppm");
+                               mask->bind(1);
+                               glUniform1i(glGetUniformLocation(max_shader, "tex"), 0);
+                               glUniform1i(glGetUniformLocation(max_shader, "tex1"), 1);
+                               glUniform1f(glGetUniformLocation(max_shader, "r"), r);
+                               glUniform1f(glGetUniformLocation(max_shader, "v"), v);
                                glViewport(0,0, w,h);
+                               out->set_output_texture();
                                out->draw_texture(0,0, w,h, 0,0, w,h);
+                               out->unset_output_texture();
                                glUseProgram(0);
-                               fb_texture *t = in;  in = out;  out = t;
+                               fb_texture *t = mask;  mask = out;  out = t;
+//mask->write_tex("/tmp/mask2.ppm");
                        }
-                       glDeleteBuffers(1, points);
+                       delete in;
+                       delete out;
                }
 
-               glDrawBuffers(0, 0);
-               glBindFramebuffer(GL_FRAMEBUFFER, 0);
-
-               unsigned int shader = VFrame::make_shader(0, alpha_frag, 0);
+               const char *alpha_shader = BC_CModels::has_alpha(color_model) ?
+                               multiply_mask4_frag :
+                       !BC_CModels::is_yuv(color_model) ?
+                               multiply_mask3_frag :
+                               multiply_yuvmask3_frag;
+               unsigned int shader = VFrame::make_shader(0, alpha_shader, 0);
                glUseProgram(shader);
                if( shader > 0 ) {
                        command->frame->bind_texture(0);
-                       in->BC_Texture::bind(1);
+                       mask->BC_Texture::bind(1);
                        glUniform1i(glGetUniformLocation(shader, "tex"), 0);
-                       glUniform1i(glGetUniformLocation(shader, "tex2"), 1);
-                       glUniform2f(glGetUniformLocation(shader, "tex2_dimensions"),
-                                       (float)in->get_texture_w(),
-                                       (float)in->get_texture_h());
-//                     if( BC_CModels::components(color_model ) == 4) {
-//                             glEnable(GL_BLEND);
-//                             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-//                     }
+                       glUniform1i(glGetUniformLocation(shader, "tex1"), 1);
                }
                command->frame->draw_texture();
                command->frame->set_opengl_state(VFrame::SCREEN);
                glUseProgram(0);
-               delete in;
-               delete out;
-// Default drawable
-               glDisable(GL_TEXTURE_2D);
+               delete mask;
                glColor4f(1, 1, 1, 1);
                glActiveTexture(GL_TEXTURE0);
+               glDisable(GL_TEXTURE_2D);
                window->enable_opengl();
        }
        command->canvas->unlock_canvas();