Starting to play with custom Skia shaders
# compose-desktop
k
Starting to play with custom Skia shaders
💪 5
😲 18
👏 10
Code:
Copy code
val sksl = """
            float f(vec3 p) {
                p.z -= 10.;
                float a = p.z * .1;
                p.xy *= mat2(cos(a), sin(a), -sin(a), cos(a));
                return .1 - length(cos(p.xy) + sin(p.yz));
            }
            
            half4 main(vec2 fragcoord) { 
                vec3 d = .5 - fragcoord.xy1 / 400;
                vec3 p=vec3(0);
                for (int i = 0; i < 32; i++) p += f(p) * d;
                return ((sin(p) + vec3(2, 5, 9)) / length(p)).xyz1;
            }
        """

        val runtimeEffect = RuntimeEffect.makeForShader(sksl)
        val shader = runtimeEffect.makeShader(
            uniforms = null,
            children = null,
            localMatrix = null,
            isOpaque = false
        )

        val shaderTile = ImageBitmap(width = 400, height = 400)
        val shaderCanvas = Canvas(shaderTile.asDesktopBitmap())
        val shaderPaint = Paint()
        shaderPaint.setShader(shader)
        shaderCanvas.drawPaint(shaderPaint)
K 1
❤️ 7
Then
shaderTile
can be passed to
DrawScope.drawImage
as a regular compose bitmap
s
I wish we had something like this for Android 🤗
@Kirill Grouchnikov btw, is there any delay caused by compiling the custom shader?
k
2ms to compile the shader. 240ms to fill the bitmap
And as a followup, using a Skia shader directly on a Skia canvas in on the order of 20-30 us (microseconds), offloading everything to the GPU
s
Really nice. 😍 Such a beauty produced by a formula I barely understand. 😅 With some tweaking you could make a dynamic wallpaper library or something like that. Right now the code only works for a square, but with some tweaking I'm sure you could produce the perfect wallpaper for any resolution. 😄
Copy code
val background = produceState<ImageBitmap?>(initialValue = null) {
    value = withContext(Dispatchers.Default) {

        val sksl = """
            float f(vec3 p) {
                p.z -= 10.;
                float a = p.z * .1;
                p.xy *= mat2(cos(a), sin(a), -sin(a), cos(a));
                return .1 - length(cos(p.xy) + sin(p.yz));
            }

            half4 main(vec2 fragcoord) {
                vec3 d = .5 - fragcoord.xy1 / 800;
                vec3 p=vec3(0);
                for (int i = 0; i < 32; i++) p += f(p) * d;
                return ((sin(p) + vec3(2, 5, 9)) / length(p)).xyz1;
            }
        """

        val runtimeEffect = RuntimeEffect.makeForShader(sksl)
        val shader = runtimeEffect.makeShader(
            uniforms = null,
            children = null,
            localMatrix = null,
            isOpaque = false
        )

        val shaderTile = ImageBitmap(width = 800, height = 800)
        val shaderCanvas = Canvas(shaderTile.asDesktopBitmap())
        val shaderPaint = Paint()
        shaderPaint.setShader(shader)
        shaderCanvas.drawPaint(shaderPaint)

        return@withContext shaderTile
    }
}

Box {

    background.value?.let {
        Image(bitmap = it, null)
    }
}
i
240ms to fill the bitmap
directly on a Skia canvas in on the order of 20-30 us (microseconds), offloading everything to the GPU
Yeah, drawing into the nativeCanvas is much faster, as we use context that uses GPU. Usually, when we draw into Canvas(Bitmap), we don't use GPU, we use software rendering. So, slowness is expected.
m
Usually, when we draw into Canvas(Bitmap), we don't use GPU, we use software rendering. So, slowness is expected.
Will this be eventually overcame?
256 Views