*/}}
Browse Source

Layer merge and undo

Yiming Wu 1 year ago
parent
commit
aed152bda9
3 changed files with 197 additions and 113 deletions
  1. 186 101
      ouroperations.c
  2. 1 1
      ourpaint.c
  3. 10 11
      ourpaint.h

+ 186 - 101
ouroperations.c

@@ -81,12 +81,31 @@ void main() {\n\
 }\n\
 ";
 
+const char OUR_COMPOSITION_SHADER[]="#version 430\n\
+layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;\n\
+layout(rgba16, binding = 0) uniform image2D top;\n\
+layout(rgba16, binding = 1) uniform image2D bottom;\n\
+uniform int uMode;\n\
+vec4 mix_over(vec4 colora, vec4 colorb){\n\
+    vec4 c; c.a=colora.a+colorb.a*(1-colora.a);\n\
+    c.rgb=(colora.rgb+colorb.rgb*(1-colora.a));\n\
+    return c;\n\
+}\n\
+void main() {\n\
+    ivec2 px=ivec2(gl_GlobalInvocationID.xy);\n\
+    vec4 c1=imageLoad(top,px); vec4 c2=imageLoad(bottom,px);\n\
+    imageStore(bottom,px,mix_over(c1,c2));\n\
+    imageStore(top,px,vec4(0,0,0,0));\n\
+}";
+
+
+void our_LayerEnsureTiles(OurLayer* ol, real xmin,real xmax, real ymin,real ymax, int Aligned, int *tl, int *tr, int* tu, int* tb);
+void our_LayerEnsureTileDirect(OurLayer* ol, int col, int row);
+void our_RecordUndo(OurLayer* ol, real xmin,real xmax, real ymin,real ymax,int Aligned,int Push);
+
 void our_CanvasAlphaMix(uint16_t* target, uint16_t* source){
     real a_1=(real)(65535-source[3])/65535;
-    target[3]=source[3]+target[3]*a_1;
-    target[0]=source[0]+target[0]*a_1;
-    target[1]=source[1]+target[1]*a_1;
-    target[2]=source[2]+target[2]*a_1;
+    target[3]=source[3]+target[3]*a_1; target[0]=source[0]+target[0]*a_1; target[1]=source[1]+target[1]*a_1; target[2]=source[2]+target[2]*a_1;
 }
 
 void our_InitsRGBProfile(int Linear, void** ptr, int* psize, char* copyright, char* manufacturer, char* description){
@@ -150,8 +169,17 @@ void ourui_LayersPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProp
         laShowItem(uil,c,0,"OUR_new_layer");
     }laEndCondition(uil,b);
 
-    laShowItemFull(uil,c,0,"our.canvas.layers",0,0,0,0);
+    laUiItem* lui=laShowItemFull(uil,c,0,"our.canvas.layers",0,0,0,0);
+
+    b=laOnConditionThat(uil,c,laPropExpression(0,"our.canvas.current_layer"));{
+        laUiItem* b1=laBeginRow(uil,c,0,0);
+        laShowItem(uil,c,&lui->PP,"remove")->Flags|=LA_UI_FLAGS_ICON;
+        laShowItem(uil,c,&lui->PP,"merge");
+        laShowSeparator(uil,c)->Expand=1;
+        laEndRow(uil,b1);
+    }laEndCondition(uil,b);
 
+    laShowSeparator(uil,c);
     b=laBeginRow(uil,c,0,0);
     laShowLabel(uil,c,"Background",0,0)->Expand=1;
     laShowItemFull(uil,c,0,"our.canvas.background_color",LA_WIDGET_FLOAT_COLOR,0,0,0);
@@ -255,13 +283,13 @@ void our_CanvasDrawTextures(){
     tnsUseImmShader; tnsEnableShaderv(T->immShader);
     for(OurLayer* l=Our->Layers.pLast;l;l=l->Item.pPrev){
         int any=0;
-        for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){
+        for(int row=0;row<OUR_TILES_PER_ROW;row++){
             if(!l->TexTiles[row]) continue;
-            for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){
+            for(int col=0;col<OUR_TILES_PER_ROW;col++){
                 if(!l->TexTiles[row][col] || !l->TexTiles[row][col]->Texture) continue;
                 int sx=l->TexTiles[row][col]->l,sy=l->TexTiles[row][col]->b;
-                real pad=(real)OUR_TEX_TILE_SEAM/OUR_TEX_TILE_W; int seam=OUR_TEX_TILE_SEAM;
-                tnsDraw2DTextureArg(l->TexTiles[row][col]->Texture,sx+seam,sy+OUR_TEX_TILE_W-seam,OUR_TEX_TILE_W-seam*2,-OUR_TEX_TILE_W+seam*2,0,pad,pad,pad,pad);
+                real pad=(real)OUR_TILE_SEAM/OUR_TILE_W; int seam=OUR_TILE_SEAM;
+                tnsDraw2DTextureArg(l->TexTiles[row][col]->Texture,sx+seam,sy+OUR_TILE_W-seam,OUR_TILE_W-seam*2,-OUR_TILE_W+seam*2,0,pad,pad,pad,pad);
                 any=1;
             }
         }
@@ -272,17 +300,17 @@ void our_CanvasDrawTiles(){
     OurLayer* l=Our->CurrentLayer; if(!l) return;
     tnsUseImmShader; tnsEnableShaderv(T->immShader); tnsUniformUseTexture(T->immShader,0,0); tnsUseNoTexture();
     int any=0;
-    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){
         if(!l->TexTiles[row]) continue;
-        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){
             if(!l->TexTiles[row][col] || !l->TexTiles[row][col]->Texture) continue;
             int sx=l->TexTiles[row][col]->l,sy=l->TexTiles[row][col]->b;
-            //tnsVertex2d(sx, sy); tnsVertex2d(sx+OUR_TEX_TILE_W,sy);
-            //tnsVertex2d(sx+OUR_TEX_TILE_W, sy+OUR_TEX_TILE_W); tnsVertex2d(sx,sy+OUR_TEX_TILE_W);
+            //tnsVertex2d(sx, sy); tnsVertex2d(sx+OUR_TILE_W,sy);
+            //tnsVertex2d(sx+OUR_TILE_W, sy+OUR_TILE_W); tnsVertex2d(sx,sy+OUR_TILE_W);
             //tnsColor4dv(laAccentColor(LA_BT_NORMAL));
             //tnsPackAs(GL_TRIANGLE_FAN);
-            tnsVertex2d(sx, sy); tnsVertex2d(sx+OUR_TEX_TILE_W,sy);
-            tnsVertex2d(sx+OUR_TEX_TILE_W, sy+OUR_TEX_TILE_W); tnsVertex2d(sx,sy+OUR_TEX_TILE_W);
+            tnsVertex2d(sx, sy); tnsVertex2d(sx+OUR_TILE_W,sy);
+            tnsVertex2d(sx+OUR_TILE_W, sy+OUR_TILE_W); tnsVertex2d(sx,sy+OUR_TILE_W);
             tnsColor4dv(laAccentColor(LA_BT_TEXT));
             tnsPackAs(GL_LINE_LOOP);    
         }
@@ -395,8 +423,8 @@ OurLayer* our_NewLayer(char* name){
     return l;
 }
 void ourbeforefree_Layer(OurLayer* l){
-    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!l->TexTiles[row]) continue;
-        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!l->TexTiles[row][col]) continue;
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!l->TexTiles[row]) continue;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!l->TexTiles[row][col]) continue;
             if(l->TexTiles[row][col]->Texture) tnsDeleteTexture(l->TexTiles[row][col]->Texture); l->TexTiles[row][col]->Texture=0;
             if(l->TexTiles[row][col]->Data) free(l->TexTiles[row][col]->Data); l->TexTiles[row][col]->Data=0;
             if(l->TexTiles[row][col]->FullData) free(l->TexTiles[row][col]->FullData); l->TexTiles[row][col]->FullData=0;
@@ -406,11 +434,38 @@ void ourbeforefree_Layer(OurLayer* l){
     }
 }
 void our_RemoveLayer(OurLayer* l){
-    strSafeDestroy(&l->Name); lstRemoveItem(&Our->Layers, l);
+    strSafeDestroy(&l->Name);
     if(Our->CurrentLayer==l){ OurLayer* nl=l->Item.pPrev?l->Item.pPrev:l->Item.pNext; memAssignRef(Our, &Our->CurrentLayer, nl); }
+    lstRemoveItem(&Our->Layers, l);
     ourbeforefree_Layer(l);
     memLeave(l);
 }
+int our_MergeLayer(OurLayer* l){
+    OurLayer* ol=l->Item.pNext; if(!ol) return 0; int xmin=INT_MAX,xmax=-INT_MAX,ymin=INT_MAX,ymax=-INT_MAX; int seam=OUR_TILE_SEAM;
+    glUseProgram(Our->CompositionProgram);
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!l->TexTiles[row]) continue;// Should not happen.
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!l->TexTiles[row][col]) continue; OurTexTile*t=l->TexTiles[row][col];
+            int tl,tr,tu,tb; our_LayerEnsureTileDirect(ol,row,col);
+            OurTexTile*ot=ol->TexTiles[row][col]; if(!ot) continue;
+            glBindImageTexture(0, t->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+            glBindImageTexture(1, ot->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+            glDispatchCompute(32,32,1);
+            xmin=TNS_MIN2(xmin,t->l+seam);xmax=TNS_MAX2(xmax,t->r-seam); ymin=TNS_MIN2(ymin,t->b+seam);ymax=TNS_MAX2(ymax,t->u-seam);
+        }
+    }
+    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+
+    if(xmin>xmax||ymin>ymax) return 0;
+
+    our_RecordUndo(l,xmin,xmax,ymin,ymax,1,0);
+    our_RecordUndo(ol,xmin,xmax,ymin,ymax,1,0);
+    our_RemoveLayer(l);
+    laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");
+    laPushDifferences("Merge layers",0);
+
+    return 1;
+}
+
 OurBrush* our_NewBrush(char* name, real Size, real Hardness, real DabsPerSize, real Transparency, real Smudge, real SmudgeResampleLength,
     int PressureSize, int PressureHardness, int PressureTransparency, int PressureSmudge){
     OurBrush* b=memAcquireHyper(sizeof(OurBrush)); strSafeSet(&b->Name,name); lstAppendItem(&Our->Brushes, b);
@@ -445,13 +500,13 @@ void our_TileEnsureUndoBuffer(OurTexTile* t, real xmin,real xmax, real ymin,real
     int bufsize=cols*4*rows*sizeof(uint16_t);
     t->CopyBuffer=calloc(1,bufsize);
     for(int row=0;row<rows;row++){
-        memcpy(&t->CopyBuffer[row*cols*4],&t->FullData[((+row+t->cb)*OUR_TEX_TILE_W+t->cl)*4],sizeof(uint16_t)*4*cols);
+        memcpy(&t->CopyBuffer[row*cols*4],&t->FullData[((+row+t->cb)*OUR_TILE_W+t->cl)*4],sizeof(uint16_t)*4*cols);
     }
     uint16_t* temp=malloc(bufsize);
     tnsBindTexture(t->Texture);
     glGetTextureSubImage(t->Texture->GLTexHandle, 0, t->cl, t->cb, 0, cols,rows,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, temp);
     for(int row=0;row<rows;row++){
-        memcpy(&t->FullData[((+row+t->cb)*OUR_TEX_TILE_W+t->cl)*4],&temp[row*cols*4],sizeof(uint16_t)*4*cols);
+        memcpy(&t->FullData[((+row+t->cb)*OUR_TILE_W+t->cl)*4],&temp[row*cols*4],sizeof(uint16_t)*4*cols);
     }
 }
 void our_TileSwapBuffers(OurTexTile* t, uint16_t* data, int IsRedo, int l, int r, int u, int b){
@@ -459,8 +514,8 @@ void our_TileSwapBuffers(OurTexTile* t, uint16_t* data, int IsRedo, int l, int r
     uint16_t* temp=malloc(bufsize);
     memcpy(temp,data,bufsize);
     for(int row=0;row<rows;row++){
-        memcpy(&data[row*cols*4],&t->FullData[((+row+b)*OUR_TEX_TILE_W+l)*4],sizeof(uint16_t)*4*cols);
-        memcpy(&t->FullData[((+row+b)*OUR_TEX_TILE_W+l)*4],&temp[row*cols*4],sizeof(uint16_t)*4*cols);
+        memcpy(&data[row*cols*4],&t->FullData[((+row+b)*OUR_TILE_W+l)*4],sizeof(uint16_t)*4*cols);
+        memcpy(&t->FullData[((+row+b)*OUR_TILE_W+l)*4],&temp[row*cols*4],sizeof(uint16_t)*4*cols);
     }
     tnsBindTexture(t->Texture);
     glGetError();
@@ -471,12 +526,14 @@ void our_TileSwapBuffers(OurTexTile* t, uint16_t* data, int IsRedo, int l, int r
 }
 void ourundo_Tiles(OurUndo* undo){
     for(OurUndoTile* ut=undo->Tiles.pFirst;ut;ut=ut->Item.pNext){
+        our_LayerEnsureTileDirect(undo->Layer,ut->row,ut->col);
         our_TileSwapBuffers(undo->Layer->TexTiles[ut->row][ut->col], ut->CopyData, 0, ut->l, ut->r, ut->u, ut->b);
     }
     laNotifyUsers("our.canvas");
 }
 void ourredo_Tiles(OurUndo* undo){
     for(OurUndoTile* ut=undo->Tiles.pFirst;ut;ut=ut->Item.pNext){
+        our_LayerEnsureTileDirect(undo->Layer,ut->row,ut->col);
         our_TileSwapBuffers(undo->Layer->TexTiles[ut->row][ut->col], ut->CopyData, 0, ut->l, ut->r, ut->u, ut->b);
     }
     laNotifyUsers("our.canvas");
@@ -487,13 +544,21 @@ void ourundo_Free(OurUndo* undo,int FromLeft){
     memFree(undo);
 }
 #define OUR_XXYY_TO_COL_ROW_RANGE\
-    l=(int)(floor(OUR_TEX_TILE_CTR+(xmin-OUR_TEX_TILE_SEAM)/OUR_TEX_TILE_W_USE+0.5));\
-    r=(int)(floor(OUR_TEX_TILE_CTR+(xmax+OUR_TEX_TILE_SEAM)/OUR_TEX_TILE_W_USE+0.5));\
-    u=(int)(floor(OUR_TEX_TILE_CTR+(ymax+OUR_TEX_TILE_SEAM)/OUR_TEX_TILE_W_USE+0.5));\
-    b=(int)(floor(OUR_TEX_TILE_CTR+(ymin-OUR_TEX_TILE_SEAM)/OUR_TEX_TILE_W_USE+0.5));
-void our_RecordUndo(OurLayer* ol, real xmin,real xmax, real ymin,real ymax){
+    l=(int)(floor(OUR_TILE_CTR+(xmin-OUR_TILE_SEAM)/OUR_TILE_W_USE+0.5));\
+    r=(int)(floor(OUR_TILE_CTR+(xmax+OUR_TILE_SEAM)/OUR_TILE_W_USE+0.5));\
+    u=(int)(floor(OUR_TILE_CTR+(ymax+OUR_TILE_SEAM)/OUR_TILE_W_USE+0.5));\
+    b=(int)(floor(OUR_TILE_CTR+(ymin-OUR_TILE_SEAM)/OUR_TILE_W_USE+0.5));\
+    TNS_CLAMP(l,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(r,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(u,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(b,0,OUR_TILES_PER_ROW-1);
+#define OUR_XXYY_TO_COL_ROW_ALIGNED\
+    l=(int)(floor(OUR_TILE_CTR+(xmin)/OUR_TILE_W_USE+0.5));\
+    r=(int)(floor(OUR_TILE_CTR+(xmax-1)/OUR_TILE_W_USE+0.5));\
+    u=(int)(floor(OUR_TILE_CTR+(ymax-1)/OUR_TILE_W_USE+0.5));\
+    b=(int)(floor(OUR_TILE_CTR+(ymin)/OUR_TILE_W_USE+0.5));\
+    TNS_CLAMP(l,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(r,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(u,0,OUR_TILES_PER_ROW-1); TNS_CLAMP(b,0,OUR_TILES_PER_ROW-1);
+void our_RecordUndo(OurLayer* ol, real xmin,real xmax, real ymin,real ymax,int Aligned,int Push){
     if(xmax<xmin || ymax<ymin) return;
-    int l,r,u,b; OUR_XXYY_TO_COL_ROW_RANGE;
+    int l,r,u,b;
+    if(Aligned){ OUR_XXYY_TO_COL_ROW_ALIGNED }else{ OUR_XXYY_TO_COL_ROW_RANGE; }
     OurUndo* undo=memAcquire(sizeof(OurUndo)); undo->Layer=ol;
     for(int row=b;row<=u;row++){ if(!ol->TexTiles[row]) continue;// Should not happen.
         for(int col=l;col<=r;col++){ if(!ol->TexTiles[row][col]) continue; OurTexTile*t=ol->TexTiles[row][col];
@@ -509,84 +574,76 @@ void our_RecordUndo(OurLayer* ol, real xmin,real xmax, real ymin,real ymax){
     if(!undo->Tiles.pFirst){ memFree(undo); return; /*unlikely;*/ }
     laFreeNewerDifferences();
     laRecordCustomDifferences(undo,ourundo_Tiles,ourredo_Tiles,ourundo_Free);
-    laPushDifferences("Paint",0);
+    if(Push){ laPushDifferences("Paint",0); }
+}
+void our_LayerEnsureTileDirect(OurLayer* ol, int row, int col){
+    if(!ol->TexTiles[row]){ol->TexTiles[row]=memAcquireSimple(sizeof(OurTexTile*)*OUR_TILES_PER_ROW);}
+    if(ol->TexTiles[row][col]) return;
+    ol->TexTiles[row][col]=memAcquireSimple(sizeof(OurTexTile));
+    OurTexTile*t=ol->TexTiles[row][col];
+    t->Texture=tnsCreate2DTexture(GL_RGBA16,OUR_TILE_W,OUR_TILE_W,0);
+    int sx=((real)col-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE,sy=((real)row-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE;
+    t->l=sx-OUR_TILE_SEAM,t->b=sy-OUR_TILE_SEAM; t->r=t->l+OUR_TILE_W; t->u=t->b+OUR_TILE_W;
+    uint16_t initColor[]={0,0,0,0};
+    glClearTexImage(t->Texture->GLTexHandle, 0, GL_RGBA, GL_UNSIGNED_SHORT, 0);
+    t->FullData=calloc(OUR_TILE_W*4,OUR_TILE_W*sizeof(uint16_t));
 }
 void our_LayerEnsureTiles(OurLayer* ol, real xmin,real xmax, real ymin,real ymax, int Aligned, int *tl, int *tr, int* tu, int* tb){
     int l,r,u,b;
-    if(Aligned){
-        l=(int)(floor(OUR_TEX_TILE_CTR+(xmin)/OUR_TEX_TILE_W_USE+0.5));
-        r=(int)(floor(OUR_TEX_TILE_CTR+(xmax-1)/OUR_TEX_TILE_W_USE+0.5));
-        u=(int)(floor(OUR_TEX_TILE_CTR+(ymax-1)/OUR_TEX_TILE_W_USE+0.5));
-        b=(int)(floor(OUR_TEX_TILE_CTR+(ymin)/OUR_TEX_TILE_W_USE+0.5));
-    }else{
-        OUR_XXYY_TO_COL_ROW_RANGE;
-    }
-    TNS_CLAMP(l,0,OUR_TEX_TILES_PER_ROW-1);
-    TNS_CLAMP(r,0,OUR_TEX_TILES_PER_ROW-1);
-    TNS_CLAMP(u,0,OUR_TEX_TILES_PER_ROW-1);
-    TNS_CLAMP(b,0,OUR_TEX_TILES_PER_ROW-1);
+    if(Aligned){ OUR_XXYY_TO_COL_ROW_ALIGNED }else{ OUR_XXYY_TO_COL_ROW_RANGE; }
     for(int row=b;row<=u;row++){
-        if(!ol->TexTiles[row]){ol->TexTiles[row]=memAcquireSimple(sizeof(OurTexTile*)*OUR_TEX_TILES_PER_ROW);}
         for(int col=l;col<=r;col++){
-            if(ol->TexTiles[row][col]) continue;
-            ol->TexTiles[row][col]=memAcquireSimple(sizeof(OurTexTile));
-            OurTexTile*t=ol->TexTiles[row][col];
-            t->Texture=tnsCreate2DTexture(GL_RGBA16,OUR_TEX_TILE_W,OUR_TEX_TILE_W,0);
-            int sx=((real)col-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE,sy=((real)row-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE;
-            t->l=sx-OUR_TEX_TILE_SEAM,t->b=sy-OUR_TEX_TILE_SEAM; t->r=t->l+OUR_TEX_TILE_W; t->u=t->b+OUR_TEX_TILE_W;
-            uint16_t initColor[]={0,0,0,0};
-            glClearTexImage(t->Texture->GLTexHandle, 0, GL_RGBA, GL_UNSIGNED_SHORT, 0);
-            t->FullData=calloc(OUR_TEX_TILE_W*4,OUR_TEX_TILE_W*sizeof(uint16_t));
+            our_LayerEnsureTileDirect(ol,row,col);
         }
     }
     *tl=l; *tr=r; *tu=u; *tb=b;
 }
 void our_TileTextureToImage(OurTexTile* ot, int SX, int SY, int composite){
     if(!ot->Texture) return;
-    int bufsize=sizeof(uint16_t)*OUR_TEX_TILE_W_USE*OUR_TEX_TILE_W_USE*4;
-    ot->Data=malloc(bufsize); int seam=OUR_TEX_TILE_SEAM; int width=OUR_TEX_TILE_W_USE;
+    int bufsize=sizeof(uint16_t)*OUR_TILE_W_USE*OUR_TILE_W_USE*4;
+    ot->Data=malloc(bufsize); int seam=OUR_TILE_SEAM; int width=OUR_TILE_W_USE;
     tnsBindTexture(ot->Texture); glPixelStorei(GL_PACK_ALIGNMENT, 2);
     glGetTextureSubImage(ot->Texture->GLTexHandle, 0, seam, seam, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ot->Data);
     if(composite){
-        for(int row=0;row<OUR_TEX_TILE_W_USE;row++){
-            for(int col=0;col<OUR_TEX_TILE_W_USE;col++){
-                our_CanvasAlphaMix(&Our->ImageBuffer[((SY+row)*Our->ImageW+SX+col)*4], &ot->Data[(row*OUR_TEX_TILE_W_USE+col)*4]);
+        for(int row=0;row<OUR_TILE_W_USE;row++){
+            for(int col=0;col<OUR_TILE_W_USE;col++){
+                our_CanvasAlphaMix(&Our->ImageBuffer[((SY+row)*Our->ImageW+SX+col)*4], &ot->Data[(row*OUR_TILE_W_USE+col)*4]);
             }
         }
     }else{
-        for(int row=0;row<OUR_TEX_TILE_W_USE;row++){
-            memcpy(&Our->ImageBuffer[((SY+row)*Our->ImageW+SX)*4],&ot->Data[(row*OUR_TEX_TILE_W_USE)*4],sizeof(uint16_t)*4*OUR_TEX_TILE_W_USE);
+        for(int row=0;row<OUR_TILE_W_USE;row++){
+            memcpy(&Our->ImageBuffer[((SY+row)*Our->ImageW+SX)*4],&ot->Data[(row*OUR_TILE_W_USE)*4],sizeof(uint16_t)*4*OUR_TILE_W_USE);
         }
     }
     free(ot->Data); ot->Data=0;
 }
 void our_TileImageToTexture(OurTexTile* ot, int SX, int SY){
     if(!ot->Texture) return;
-    int pl=(SX!=0)?OUR_TEX_TILE_SEAM:0, pr=((SX+OUR_TEX_TILE_W_USE)!=Our->ImageW)?OUR_TEX_TILE_SEAM:0;
-    int pu=(SY!=0)?OUR_TEX_TILE_SEAM:0, pb=((SY+OUR_TEX_TILE_W_USE)!=Our->ImageH)?OUR_TEX_TILE_SEAM:0;
-    int bufsize=sizeof(uint16_t)*(OUR_TEX_TILE_W+pl+pr)*(OUR_TEX_TILE_W+pu+pb)*4;
-    ot->Data=malloc(bufsize); int width=OUR_TEX_TILE_W_USE+pl+pr, height=OUR_TEX_TILE_W_USE+pu+pb;
+    int pl=(SX!=0)?OUR_TILE_SEAM:0, pr=((SX+OUR_TILE_W_USE)!=Our->ImageW)?OUR_TILE_SEAM:0;
+    int pu=(SY!=0)?OUR_TILE_SEAM:0, pb=((SY+OUR_TILE_W_USE)!=Our->ImageH)?OUR_TILE_SEAM:0;
+    int bufsize=sizeof(uint16_t)*(OUR_TILE_W+pl+pr)*(OUR_TILE_W+pu+pb)*4;
+    ot->Data=malloc(bufsize); int width=OUR_TILE_W_USE+pl+pr, height=OUR_TILE_W_USE+pu+pb;
     for(int row=0;row<height;row++){
         memcpy(&ot->Data[((row)*width)*4],&Our->ImageBuffer[((SY+row-pu)*Our->ImageW+SX-pl)*4],sizeof(uint16_t)*4*width);
     }
     if(!our_BufferAnythingVisible(ot->Data, bufsize/sizeof(uint16_t)/4)){ tnsDeleteTexture(ot->Texture); ot->Texture=0; }
     else{
         tnsBindTexture(ot->Texture);
-        glTexSubImage2D(GL_TEXTURE_2D, 0, OUR_TEX_TILE_SEAM-pl, OUR_TEX_TILE_SEAM-pu, width, height, GL_RGBA, GL_UNSIGNED_SHORT, ot->Data);
+        glTexSubImage2D(GL_TEXTURE_2D, 0, OUR_TILE_SEAM-pl, OUR_TILE_SEAM-pu, width, height, GL_RGBA, GL_UNSIGNED_SHORT, ot->Data);
     }
     free(ot->Data); ot->Data=0;
 }
 int our_LayerEnsureImageBuffer(OurLayer* ol, int OnlyCalculate){
     int l=1000,r=-1000,u=-1000,b=1000; int any=0;
-    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
         if(row<b) b=row; if(row>u) u=row;
-        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
             if(col<l) l=col; if(col>r) r=col; any++;
         }
     }
     if(!any) return 0;
-    Our->ImageW = OUR_TEX_TILE_W_USE*(r-l+1); Our->ImageH = OUR_TEX_TILE_W_USE*(u-b+1);
-    Our->ImageX =((real)l-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE; Our->ImageY=((real)b-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE;
+    Our->ImageW = OUR_TILE_W_USE*(r-l+1); Our->ImageH = OUR_TILE_W_USE*(u-b+1);
+    Our->ImageX =((real)l-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE; Our->ImageY=((real)b-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE;
     if(!OnlyCalculate){
         if(Our->ImageBuffer) free(Our->ImageBuffer);
         Our->ImageBuffer = calloc(Our->ImageW*4,Our->ImageH*sizeof(uint16_t));
@@ -615,17 +672,17 @@ void our_CanvasFillImageBufferBackground(){
     }
 }
 void our_LayerToImageBuffer(OurLayer* ol, int composite){
-    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
-        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
-            int sx=ol->TexTiles[row][col]->l+OUR_TEX_TILE_SEAM,sy=ol->TexTiles[row][col]->b+OUR_TEX_TILE_SEAM;
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+            int sx=ol->TexTiles[row][col]->l+OUR_TILE_SEAM,sy=ol->TexTiles[row][col]->b+OUR_TILE_SEAM;
             our_TileTextureToImage(ol->TexTiles[row][col], sx-Our->ImageX, sy-Our->ImageY, composite);
         }
     }
 }
 void our_LayerToTexture(OurLayer* ol){
-    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
-        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
-            int sx=ol->TexTiles[row][col]->l+OUR_TEX_TILE_SEAM,sy=ol->TexTiles[row][col]->b+OUR_TEX_TILE_SEAM;
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+            int sx=ol->TexTiles[row][col]->l+OUR_TILE_SEAM,sy=ol->TexTiles[row][col]->b+OUR_TILE_SEAM;
             our_TileImageToTexture(ol->TexTiles[row][col], sx-Our->ImageX, sy-Our->ImageY);
         }
     }
@@ -699,12 +756,12 @@ int our_ImageExportPNG(FILE* fp, int WriteToBuffer, void** buf, int* sizeof_buf,
     return 1;
 }
 void our_EnsureImageBufferOnRead(OurLayer*l, int W, int H, int UseOffsets, int StartX, int StartY){
-    int tw=W/OUR_TEX_TILE_W_USE, th=H/OUR_TEX_TILE_W_USE;
-    int w=tw*OUR_TEX_TILE_W_USE, h=th*OUR_TEX_TILE_W_USE;
-    if(w<W){ tw+=1; w+=OUR_TEX_TILE_W_USE; } if(h<H){ th+=1; h+=OUR_TEX_TILE_W_USE; }
+    int tw=W/OUR_TILE_W_USE, th=H/OUR_TILE_W_USE;
+    int w=tw*OUR_TILE_W_USE, h=th*OUR_TILE_W_USE;
+    if(w<W){ tw+=1; w+=OUR_TILE_W_USE; } if(h<H){ th+=1; h+=OUR_TILE_W_USE; }
 
-    int ix=UseOffsets?StartX:(-tw/2*OUR_TEX_TILE_W_USE-OUR_TEX_TILE_W_USE/2);
-    int iy=UseOffsets?StartY:(th/2*OUR_TEX_TILE_W_USE+OUR_TEX_TILE_W_USE/2);
+    int ix=UseOffsets?StartX:(-tw/2*OUR_TILE_W_USE-OUR_TILE_W_USE/2);
+    int iy=UseOffsets?StartY:(th/2*OUR_TILE_W_USE+OUR_TILE_W_USE/2);
     int tl,tr,tu,tb;
     our_LayerEnsureTiles(l,ix,ix+W,iy-H,iy,1,&tl,&tr,&tu,&tb);
     our_LayerEnsureImageBuffer(l, 0);
@@ -930,7 +987,7 @@ void our_PaintDoDabs(OurLayer* l,int tl, int tr, int tu, int tb, int Start, int
             glBindImageTexture(0, ott->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
             int sx=l->TexTiles[row][col]->l,sy=l->TexTiles[row][col]->b;
             for(int i=Start;i<End;i++){
-                our_PaintDoDab(&Our->Dabs[i],sx,sx+OUR_TEX_TILE_W,sy,sy+OUR_TEX_TILE_W);
+                our_PaintDoDab(&Our->Dabs[i],sx,sx+OUR_TILE_W,sy,sy+OUR_TILE_W);
             }
         }
     }
@@ -958,10 +1015,10 @@ void our_PaintDoDabsWithSmudgeSegments(OurLayer* l,int tl, int tr, int tu, int t
         if(oss->Resample || Our->CurrentBrush->SmudgeRestart){
             glUniformSubroutinesuiv(GL_COMPUTE_SHADER,1,&Our->RoutineDoSample);
             int x=Our->Dabs[oss->Start].X, y=Our->Dabs[oss->Start].Y; float ssize=Our->Dabs[oss->Start].Size+1.5;
-            int colmax=(int)(floor(OUR_TEX_TILE_CTR+(float)(x+ssize)/OUR_TEX_TILE_W_USE+0.5)); TNS_CLAMP(colmax,0,OUR_TEX_TILES_PER_ROW-1);
-            int rowmax=(int)(floor(OUR_TEX_TILE_CTR+(float)(y+ssize)/OUR_TEX_TILE_W_USE+0.5)); TNS_CLAMP(rowmax,0,OUR_TEX_TILES_PER_ROW-1);
-            int colmin=(int)(floor(OUR_TEX_TILE_CTR+(float)(x-ssize)/OUR_TEX_TILE_W_USE+0.5)); TNS_CLAMP(colmin,0,OUR_TEX_TILES_PER_ROW-1);
-            int rowmin=(int)(floor(OUR_TEX_TILE_CTR+(float)(y-ssize)/OUR_TEX_TILE_W_USE+0.5)); TNS_CLAMP(rowmin,0,OUR_TEX_TILES_PER_ROW-1);
+            int colmax=(int)(floor(OUR_TILE_CTR+(float)(x+ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(colmax,0,OUR_TILES_PER_ROW-1);
+            int rowmax=(int)(floor(OUR_TILE_CTR+(float)(y+ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(rowmax,0,OUR_TILES_PER_ROW-1);
+            int colmin=(int)(floor(OUR_TILE_CTR+(float)(x-ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(colmin,0,OUR_TILES_PER_ROW-1);
+            int rowmin=(int)(floor(OUR_TILE_CTR+(float)(y-ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(rowmin,0,OUR_TILES_PER_ROW-1);
             glBindImageTexture(1, Our->SmudgeTexture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
             for(int col=colmin;col<=colmax;col++){
                 for(int row=rowmin;row<=rowmax;row++){
@@ -1039,18 +1096,29 @@ void our_DoCropping(OurCanvasDraw* cd, real x, real y){
 
 int ourinv_NewLayer(laOperator* a, laEvent* e){
     our_NewLayer("Our Layer"); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+    laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");
     return LA_FINISHED;
 }
 int ourinv_RemoveLayer(laOperator* a, laEvent* e){
     OurLayer* l=a->This?a->This->EndInstance:0; if(!l) return LA_CANCELED;
     our_RemoveLayer(l); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+    laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");
     return LA_FINISHED;
 }
 int ourinv_MoveLayer(laOperator* a, laEvent* e){
-    OurLayer* l=a->This?a->This->EndInstance:0; if(!l) return LA_CANCELED;
+    OurLayer* l=a->This?a->This->EndInstance:0; if(!l) return LA_CANCELED; int changed=0;
     char* direction=strGetArgumentString(a->ExtraInstructionsP,"direction");
-    if(strSame(direction,"up")&&l->Item.pPrev){ lstMoveUp(&Our->Layers, l); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst); }
-    elif(l->Item.pNext){ lstMoveDown(&Our->Layers, l); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst); }
+    if(strSame(direction,"up")&&l->Item.pPrev){ lstMoveUp(&Our->Layers, l); changed=1; }
+    elif(l->Item.pNext){ lstMoveDown(&Our->Layers, l); changed=1; }
+    if(changed){ laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst); laRecordDifferences(0,"our.canvas.layers"); }
+    return LA_FINISHED;
+}
+int ourchk_MergeLayer(laPropPack *This, laStringSplitor *ss){
+    OurLayer* l=This->EndInstance; if(!l || !l->Item.pNext) return 0; return 1;
+}
+int ourinv_MergeLayer(laOperator* a, laEvent* e){
+    OurLayer* l=a->This?a->This->EndInstance:0; if(!l || !l->Item.pNext) return LA_CANCELED;
+    if(our_MergeLayer(l)){ laNotifyUsers("our.canvas"); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst); }
     return LA_FINISHED;
 }
 int ourinv_ExportLayer(laOperator* a, laEvent* e){
@@ -1171,7 +1239,7 @@ int ourinv_Action(laOperator* a, laEvent* e){
 int ourmod_Paint(laOperator* a, laEvent* e){
     OurLayer* l=Our->CurrentLayer; OurCanvasDraw *ex = a->This?a->This->EndInstance:0; OurBrush* ob=Our->CurrentBrush; if(!l||!ex||!ob) return LA_CANCELED;
     if(e->Type==LA_L_MOUSE_UP || e->Type==LA_R_MOUSE_DOWN || e->Type==LA_ESCAPE_DOWN){
-        our_RecordUndo(l,Our->xmin,Our->xmax,Our->ymin,Our->ymax); ex->HideBrushCircle=0; laShowCursor();
+        our_RecordUndo(l,Our->xmin,Our->xmax,Our->ymin,Our->ymax,0,1); ex->HideBrushCircle=0; laShowCursor();
         return LA_FINISHED;
     }
 
@@ -1335,6 +1403,7 @@ void ourRegisterEverything(){
     laCreateOperatorType("OUR_new_layer","New Layer","Create a new layer",0,0,0,ourinv_NewLayer,0,'+',0);
     laCreateOperatorType("OUR_remove_layer","Remove Layer","Remove this layer",0,0,0,ourinv_RemoveLayer,0,L'🗴',0);
     laCreateOperatorType("OUR_move_layer","Move Layer","Remove this layer",0,0,0,ourinv_MoveLayer,0,0,0);
+    laCreateOperatorType("OUR_merge_layer","Merge Layer","Merge this layer with the layer below it",ourchk_MergeLayer,0,0,ourinv_MergeLayer,0,0,0);
     laCreateOperatorType("OUR_export_layer","Export Layer","Export this layer",0,0,0,ourinv_ExportLayer,ourmod_ExportLayer,L'🖫',0);
     laCreateOperatorType("OUR_import_layer","Import Layer","Import a PNG into a layer",0,0,0,ourinv_ImportLayer,ourmod_ImportLayer,L'🗁',0);
     laCreateOperatorType("OUR_new_brush","New Brush","Create a new brush",0,0,0,ourinv_NewBrush,0,'+',0);
@@ -1435,7 +1504,8 @@ void ourRegisterEverything(){
     laAddRawProperty(pc,"image","Image","The image data of this tile",0,0,ourget_LayerImage,ourset_LayerImage,LA_UDF_ONLY);
     laAddOperatorProperty(pc,"move","Move","Move Layer","OUR_move_layer",0,0);
     laAddOperatorProperty(pc,"remove","Remove","Remove layer","OUR_remove_layer",L'🗴',0);
-
+    laAddOperatorProperty(pc,"merge","Merge","Merge Layer","OUR_merge_layer",L'🠳',0);
+    
     laCanvasTemplate* ct=laRegisterCanvasTemplate("our_CanvasDraw", "our_canvas", ourextramod_Canvas, our_CanvasDrawCanvas, our_CanvasDrawOverlay, our_CanvasDrawInit, la_CanvasDestroy);
     pc = laCanvasHasExtraProps(ct,sizeof(OurCanvasDraw),2);
     km = &ct->KeyMapper;
@@ -1463,13 +1533,15 @@ void ourRegisterEverything(){
     laSaveProp("our.canvas");
     laSaveProp("our.tools");
 
+    laAddRootDBInst("our.canvas");
+
     laGetSaverDummy(Our,Our->CanvasSaverDummyProp);
 
     laSetFrameCallbacks(ourPreFrame,0,0);
 }
 
 
-void ourInit(){
+int ourInit(){
     Our=memAcquire(sizeof(OurPaint));
 
     ourRegisterEverything();
@@ -1481,25 +1553,33 @@ void ourInit(){
     Our->SmudgeTexture=tnsCreate2DTexture(GL_RGBA16,256,1,0);
 
     Our->CanvasShader = glCreateShader(GL_COMPUTE_SHADER);
-    const GLchar* source = OUR_CANVAS_SHADER;
-    glShaderSource(Our->CanvasShader, 1, &source, NULL);
-    glCompileShader(Our->CanvasShader);
+    const GLchar* source1 = OUR_CANVAS_SHADER;
+    glShaderSource(Our->CanvasShader, 1, &source1, NULL); glCompileShader(Our->CanvasShader);
     glGetShaderiv(Our->CanvasShader, GL_COMPILE_STATUS, &status);
     if (status == GL_FALSE){
-        glGetShaderInfoLog(Our->CanvasShader, sizeof(error), 0, error);
-        printf("Compute shader error:\n%s", error);
-        glDeleteShader(Our->CanvasShader);
-        return -1;
+        glGetShaderInfoLog(Our->CanvasShader, sizeof(error), 0, error); printf("Canvas shader error:\n%s", error); glDeleteShader(Our->CanvasShader); return 0;
     }
 
     Our->CanvasProgram = glCreateProgram();
-    glAttachShader(Our->CanvasProgram, Our->CanvasShader);
-    glLinkProgram(Our->CanvasProgram);
+    glAttachShader(Our->CanvasProgram, Our->CanvasShader); glLinkProgram(Our->CanvasProgram);
     glGetProgramiv(Our->CanvasProgram, GL_LINK_STATUS, &status);
     if (status == GL_FALSE){
-        glGetProgramInfoLog(Our->CanvasProgram, sizeof(error), 0, error);
-        printf("Shader Linking error:\n%s", error);
-        return 0;
+        glGetProgramInfoLog(Our->CanvasProgram, sizeof(error), 0, error); printf("Canvas program Linking error:\n%s", error); return 0;
+    }
+
+    Our->CompositionShader = glCreateShader(GL_COMPUTE_SHADER);
+    const GLchar* source2 = OUR_COMPOSITION_SHADER;
+    glShaderSource(Our->CompositionShader, 1, &source2, NULL); glCompileShader(Our->CompositionShader);
+    glGetShaderiv(Our->CompositionShader, GL_COMPILE_STATUS, &status);
+    if (status == GL_FALSE){
+        glGetShaderInfoLog(Our->CompositionShader, sizeof(error), 0, error); printf("Composition shader error:\n%s", error); glDeleteShader(Our->CompositionShader); return 0;
+    }
+
+    Our->CompositionProgram = glCreateProgram();
+    glAttachShader(Our->CompositionProgram, Our->CompositionShader); glLinkProgram(Our->CompositionProgram);
+    glGetProgramiv(Our->CompositionProgram, GL_LINK_STATUS, &status);
+    if (status == GL_FALSE){
+        glGetProgramInfoLog(Our->CompositionProgram, sizeof(error), 0, error); printf("Composition program Linking error:\n%s", error); return 0;
     }
 
     Our->uBrushCorner=glGetUniformLocation(Our->CanvasProgram,"uBrushCorner");
@@ -1517,15 +1597,20 @@ void ourInit(){
     Our->RoutineDoDabs=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoDabs");
     Our->RoutineDoSample=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoSample");
 
+    Our->uMode=glGetUniformLocation(Our->CompositionProgram,"uMode");
+
     Our->X=-2800/2; Our->W=2800;
     Our->Y=2400/2;  Our->H=2400;
     Our->BorderAlpha=0.6;
 
     Our->LockRadius=1;
+    Our->EnableBrushCircle=1;
 
     Our->PenID=-1;
     Our->EraserID=-1;
 
     tnsEnableShaderv(T->immShader);
+
+    return 1;
 }
 

+ 1 - 1
ourpaint.c

@@ -7,7 +7,7 @@ extern OurPaint *Our;
 int main(int argc, char *argv[]){
     laGetReady();
 
-    ourInit();
+    if(!ourInit()){ laShutoff(); return -1; }
 
     laRefreshUDFRegistries();
     laEnsureUserPreferences();

+ 10 - 11
ourpaint.h

@@ -22,13 +22,11 @@ STRUCTURE(OurCanvasDraw){
 };
 
 
-#define OUR_TILE_W 64
-#define OUR_TEX_TILE_W 1024
-#define OUR_TEX_TILES_PER_ROW 100
-#define OUR_TILES_PER_ROW (OUR_TEX_TILES_PER_ROW*(OUR_TEX_TILE_W/OUR_TILE_W))
-#define OUR_TEX_TILE_CTR (OUR_TEX_TILES_PER_ROW/2)
-#define OUR_TEX_TILE_SEAM 12
-#define OUR_TEX_TILE_W_USE (OUR_TEX_TILE_W-OUR_TEX_TILE_SEAM*2)
+#define OUR_TILE_W 1024
+#define OUR_TILES_PER_ROW 100
+#define OUR_TILE_CTR (OUR_TILES_PER_ROW/2)
+#define OUR_TILE_SEAM 12
+#define OUR_TILE_W_USE (OUR_TILE_W-OUR_TILE_SEAM*2)
 
 STRUCTURE(OurTexTile){
     tnsTexture* Texture;
@@ -42,7 +40,7 @@ STRUCTURE(OurLayer){
     laListItem Item;
     laSafeString Name;
     int OffsetX,OffsetY;
-    OurTexTile** TexTiles[OUR_TEX_TILES_PER_ROW];
+    OurTexTile** TexTiles[OUR_TILES_PER_ROW];
 };
 
 STRUCTURE(OurLayerWrite){
@@ -184,8 +182,8 @@ STRUCTURE(OurPaint){
     int EnableBrushCircle;
 
     tnsTexture* SmudgeTexture;
-    GLuint CanvasShader;
-    GLuint CanvasProgram;
+    GLuint CanvasShader;      GLuint CanvasProgram;
+    GLuint CompositionShader; GLuint CompositionProgram;
     GLint uBrushCorner;
     GLint uBrushCenter;
     GLint uBrushSize;
@@ -199,6 +197,7 @@ STRUCTURE(OurPaint){
     GLint uBrushErasing;
     GLint RoutineDoDabs;
     GLint RoutineDoSample;
+    GLint uMode;
 
     real CurrentColor[3];
     real BackgroundColor[3];
@@ -215,7 +214,7 @@ STRUCTURE(OurPaint){
     void* icc_sRGB; int iccsize_sRGB;
 };
 
-void ourInit();
+int ourInit();
 void ourRegisterNodes();
 int ourRebuildBrushEval();
 int ourEvalBrush();