Overdraw Canvas save layer restore

148 views
Skip to first unread message

Clark Kent

unread,
Aug 31, 2022, 11:47:01 PM8/31/22
to skia-discuss
```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();

overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();
```
expected alpha is 2, actual alpha is 1

```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();
overdrawCanvas.Restore();
```

expected alpha is 2, actual alpha is 1

```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.Restore();
overdrawCanvas.Restore();
```

expected alpha is 2, actual alpha is 2

```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();
overdrawCanvas.Restore();
```

expected alpha is 2, actual alpha is 2

Clark Kent

unread,
Sep 1, 2022, 1:52:08 AM9/1/22
to skia-discuss
the first
```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();

overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();
```
can be solved by saving the layer internally with SkOverdrawCanvas's internal fpaint instead of the provided paint

`saveLayer(rect, overdrawPaint(paint));`

however the second


```
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.SaveLayer(new SKRect(0, 0, 800, 427), SKColors.White.ToPaint());
overdrawCanvas.DrawRect(new SKRect(0, 0, 800, 427), SKColors.DarkGreen.ToPaint());
overdrawCanvas.Restore();
overdrawCanvas.Restore();
```
is not as easy, as we would be merging an alpha 1 layer into an alpha 1 layer, which should sum alpha to expected 2

however this should ONLY happen if savelayer was used, since it should be reasonable to assume only WE (SkOverdrawCanvas) touch the layer alpha that is being merged

Clark Kent

unread,
Sep 1, 2022, 1:55:44 AM9/1/22
to skia-discuss
the only problem is, would this be correct, and if so, how would we sum the layer alpha's correctly (while avoiding overflow and excessive redraw)?

Clark Kent

unread,
Sep 1, 2022, 1:57:41 AM9/1/22
to skia-discuss
additionally would it be GAURENTEED that ONLY the SkOverdrawCanvas can touch the layer pixels between SaveLayer and Restore

Clark Kent

unread,
Sep 1, 2022, 10:44:58 PM9/1/22
to skia-discuss
the second seems to be fixed via

```
using AndroidUI.Utils;
using SkiaSharp;

namespace AndroidUI.Graphics
{
    public class SKOverdrawCanvas2 : SKCanvasForwarder
    {
        class CanvasInfo
        {
            public SKOverdrawCanvas overdrawCanvas;
            public SKCanvas canvas;
            public SKBitmap bitmap;

            public CanvasInfo(SKOverdrawCanvas overdrawCanvas, SKCanvas canvas, SKBitmap bitmap = null)
            {
                this.overdrawCanvas = overdrawCanvas;
                this.canvas = canvas;
                this.bitmap = bitmap;
            }
        }

        Stack<CanvasInfo> stack_canvas = new(); // class
        Stack<bool> stack_is_layer = new();
        CanvasInfo top_canvas;
        string shader_error_message;
        SKRuntimeEffect sksl;

        public string Shader_error_message => shader_error_message;

        public SKOverdrawCanvas2(SKCanvas canvas)
        {
            stack_is_layer.Push(false);
            pushCanvas(new SKOverdrawCanvas(canvas), canvas);

            sksl = SKRuntimeEffect.Create(
                "uniform shader src;\n" +
                "uniform shader dst;\n" +
                "\n" +
                "half4 main() {\n" +
                "    float srcAlpha = sample(src).a;\n" +
                "    float dstAlpha = sample(dst).a;\n" +
                "    float alpha = srcAlpha + dstAlpha;\n" +
                "    return half4(0, 0, 0, alpha > 1.0 ? 1.0 : alpha);\n" +
                "}\n",
                out shader_error_message
            );
        }

        void pushCanvas(SKOverdrawCanvas overdrawCanvas, SKCanvas canvas, SKBitmap bitmap = null)
        {
            top_canvas = new(overdrawCanvas, canvas, bitmap);
            stack_canvas.Push(top_canvas);
            ReleaseNativeObject();
            SetNativeObject(top_canvas.overdrawCanvas, false);
        }

        void pushCanvas()
        {
            if (top_canvas.canvas.Surface.Context != null)
            {
                // GPU
                var surface = SKSurface.Create(top_canvas.canvas.Surface.Context, false, top_canvas.canvas.Info);
                pushCanvas(new SKOverdrawCanvas(surface.Canvas), surface.Canvas);
            }
            else
            {
                // CPU
                SKBitmap bitmap = new(top_canvas.canvas.Info);
                SKCanvas canvas = new(bitmap);
                pushCanvas(new SKOverdrawCanvas(canvas), canvas, bitmap);
            }
        }

        CanvasInfo popCanvas()
        {
            var info = stack_canvas.Pop();
            top_canvas = stack_canvas.Peek();
            ReleaseNativeObject();
            SetNativeObject(top_canvas.overdrawCanvas, false);
            return info;
        }

        public override void RestoreToCount(int count)
        {
            // safety check
            if (count < 2)
            {
                count = 2;
            }

            int n = SaveCount - count;
            for (int i = 0; i < n; ++i)
            {
                Restore();
            }
        }

        protected override void Dispose(bool disposing)
        {
            RestoreToCount(1);
            RestoreInternal();
            // dispose temporary overdraw canvas
            ReleaseNativeObject();
            stack_canvas.Pop().overdrawCanvas.Dispose();
            sksl.Dispose();
            base.Dispose(disposing);
        }

        public override int Save()
        {
            stack_is_layer.Push(false);
            return base.Save();
        }

        public override int SaveLayer(SKPaint paint)
        {
            return SaveLayer(DeviceClipBounds, paint);
        }

        public override int SaveLayer(SKRect limit, SKPaint paint)
        {
            Log.d("CLIP", "limit: " + limit);
            if (paint != null && paint.NothingToDraw)
            {
                // no need for the layer (or any of the draws until the matching restore()
                // increase internal save count
                base.Save();
                stack_is_layer.Push(false);
                ClipRect(new SKRect(0, 0, 0, 0));
            }
            else
            {
                // increase internal save count
                base.Save();
                stack_is_layer.Push(true);
                // push everything to stack
                pushCanvas();
                ClipRect(limit);
            }
            return SaveCount - 1;
        }

        public override void Restore()
        {
            // noop if we are at base

            if (stack_is_layer.Count == 1) return;

            RestoreInternal();
        }

        private void RestoreInternal()
        {
            bool is_layer = stack_is_layer.Pop();
            if (!is_layer)
            {
                // we are restoring a save()
                base.Restore();
                return;
            }
            // we are restoring a layer
            SKImage srcImage;

            Flush();

            bool is_gpu = top_canvas.canvas.Surface.Context != null;

            var info = popCanvas();


            if (is_gpu)
            {
                // we are restoring a GPU layer
                var surface = info.canvas.Surface;
                srcImage = surface.Snapshot();
                // canvas is owned by surface
                surface.Dispose();
            }
            else
            {
                // we are restoring a CPU layer
                SKBitmap bitmap = info.bitmap;
                srcImage = bitmap.AsImage();
                bitmap.Dispose();
                info.canvas.Dispose();
            }

            info = null;

            using var raster = srcImage.ToRasterImage();
            using var pixelsAlpha = raster.PeekPixels();
            int alpha = pixelsAlpha.GetPixelSpan()[0];
            Log.d("OVERDRAW 2", "src alpha: " + alpha);


            using var srcShader = srcImage.ToShader();

            using var dstImage = top_canvas.canvas.Surface.Snapshot();
            using var dstShader = dstImage.ToShader();

            SKRuntimeEffectChildren children = new(sksl) {
                    { "src", srcShader },
                    { "dst", dstShader }
                };

            using var shader = sksl.ToShader(false, new(sksl), children);
            using var paint = new SKPaint();
            paint.BlendMode = SKBlendMode.Src; // shader pixels only
            paint.Shader = shader;
            top_canvas.canvas.DrawPaint(paint);
            srcImage.Dispose();
        }
    }
}
```

Clark Kent

unread,
Sep 6, 2022, 1:21:51 AM9/6/22
to skia-discuss
specifically

as-is (for skia Version 88)
- this does not work at all for layer based work
- a workaround is to replace the color matrix with a custom paint that does blend mode Plus, and has a color with alpha byte 1 respectively for SkColor and SkColor4f ( (float)(1 / 255.0f) )
- for CPU backend we additionally need to ADD the contents of the current layer TO the contents of the previous layer, this seems to happen automatically in GPU backend, for example if the current layer is alpha 2,1, and the previous layer is alpha 1,5, then the previous layer should end up with alpha 3,6
- for CPU backend, we COULD manually save and restore the layer via CPU layer bitmap creation however this is very costly compared to internal layer save/restore done by skia canvas

Clark Kent

unread,
Sep 6, 2022, 1:25:02 AM9/6/22
to skia-discuss
specifically, adding the current alpha to previous alpha is achieved by a shader

```
uniform shader src;

half4 main(float2 coords) {
    return half4(0, 0, 0, sample(src, coords).a);
}
```

and using blend mode Plus to add the shader output to the canvas's current output via drawPaint with the layer being restored being converted into a shader and fed into `src` child uniform

Clark Kent

unread,
Sep 6, 2022, 1:27:43 AM9/6/22
to skia-discuss
this could also (in theory) be achieved without a shader via a simply blend mode however no such blend mode currently exist that fully sums the src color together with the dst color (including the alpha channels)
Reply all
Reply to author
Forward
0 new messages