diff --git a/packages/skia/src/sksg/Recorder/commands/Shaders.ts b/packages/skia/src/sksg/Recorder/commands/Shaders.ts index 8d93b568b1..3a8de49a88 100644 --- a/packages/skia/src/sksg/Recorder/commands/Shaders.ts +++ b/packages/skia/src/sksg/Recorder/commands/Shaders.ts @@ -46,7 +46,7 @@ const declareShader = ( const shader = ctx.track( source.makeShaderWithChildren( processUniforms(source, uniforms), - ctx.shaders.splice(0, children), + ctx.shaders.splice(Math.max(0, ctx.shaders.length - children), children), m3 ) ); diff --git a/packages/skia/src/sksg/__tests__/ShaderChildren.spec.tsx b/packages/skia/src/sksg/__tests__/ShaderChildren.spec.tsx new file mode 100644 index 0000000000..1e41681538 --- /dev/null +++ b/packages/skia/src/sksg/__tests__/ShaderChildren.spec.tsx @@ -0,0 +1,52 @@ +import React from "react"; + +import { importSkia } from "../../renderer/__tests__/setup"; +import type { SkImage } from "../../skia/types"; +import { SkiaSGRoot } from "../Reconciler"; + +const SIZE = 16; + +// mix() with a runtime uniform keeps both children alive through the SkSL +// optimizer, so the effect always reports two children and returns the first. +const FIRST_CHILD = ` +uniform shader c0; +uniform shader c1; +uniform float w; + +half4 main(float2 xy) { + return mix(c0.eval(xy), c1.eval(xy), w); +}`; + +const colorAt = (image: SkImage, x: number, y: number) => { + const pixels = image.readPixels() as Uint8Array; + const offset = (y * image.width() + x) * 4; + return [pixels[offset], pixels[offset + 1], pixels[offset + 2]]; +}; + +describe("Shader children", () => { + it("resolves the children of a nested multi-child shader", async () => { + const { Skia } = importSkia(); + const source = Skia.RuntimeEffect.Make(FIRST_CHILD)!; + expect(source).toBeTruthy(); + const root = new SkiaSGRoot(Skia); + await root.render( + + + + + + + + + + ); + const surface = Skia.Surface.Make(SIZE, SIZE)!; + root.drawOnCanvas(surface.getCanvas()); + surface.flush(); + const image = surface.makeImageSnapshot(); + root.unmount(); + // The inner shader must consume the two shaders declared under it, leaving + // red as the first child of the outer one. + expect(colorAt(image, SIZE / 2, SIZE / 2)).toEqual([255, 0, 0]); + }); +});