diff --git a/docs/docs/shaders/assets/simple-uniform.png b/docs/docs/shaders/assets/simple-uniform.png index b0255bcff7..c624c10028 100644 Binary files a/docs/docs/shaders/assets/simple-uniform.png and b/docs/docs/shaders/assets/simple-uniform.png differ diff --git a/docs/docs/shaders/language.md b/docs/docs/shaders/language.md index a0e2c4e508..4fda84cbfb 100644 --- a/docs/docs/shaders/language.md +++ b/docs/docs/shaders/language.md @@ -30,11 +30,11 @@ if (!source) { Creates a shader from source. Shaders can be nested with one another. -| Name | Type | Description | -|:---------|:----------------|:------------------------------| -| source | `RuntimeEffect` | Compiled shaders | -| uniforms | `number` | uniform values | -| children | `Shader` | Shaders to be used as uniform | +| Name | Type | Description | +|:---------|:---------------------------------------------------------------------|:------------------------------| +| source | `RuntimeEffect` | Compiled shaders | +| uniforms | { [name: string]: number | Vector | number[]} | uniform values | +| children | `Shader` | Shaders to be used as uniform | ### Simple Shader @@ -63,25 +63,30 @@ const SimpleShader = () => { ### Using Uniforms +Uniforms are variables used to parametrize shaders. +The following uniform types are supported: `float`, `float2`, `float3`, `float4`, `float2x2`, `float3x3`, `float4x4`, `int`, `int2`, `int3` and, `int4`. + ```tsx twoslash -import {Canvas, Skia, Paint, Shader, Fill} from "@shopify/react-native-skia"; +import {Canvas, Skia, Paint, Shader, Fill, vec} from "@shopify/react-native-skia"; const source = Skia.RuntimeEffect.Make(` -uniform float blue; +uniform vec2 c; uniform float r; +uniform float blue; vec4 main(vec2 pos) { vec2 normalized = pos/vec2(2 * r); - return distance(pos, vec2(r)) > r ? vec4(1) : vec4(normalized.x, normalized.y, blue, 1); + return distance(pos, c) > r ? vec4(1) : vec4(normalized, blue, 1); }`)!; const UniformShader = () => { - const blue = 1.0; const r = 128; + const c = vec(2 * r, r); + const blue = 1.0; return ( - + @@ -122,4 +127,4 @@ const NestedShader = () => { }; ``` -![Simple Shader](assets/nested.png) \ No newline at end of file +![Simple Shader](assets/nested.png) diff --git a/example/src/Examples/Filters/Filters.tsx b/example/src/Examples/Filters/Filters.tsx index 87d5cd8bf6..14524aa1ba 100644 --- a/example/src/Examples/Filters/Filters.tsx +++ b/example/src/Examples/Filters/Filters.tsx @@ -27,7 +27,10 @@ export const Filters = () => { return ( - [mix(progress.value, 1, 100)]}> + ({ r: mix(progress.value, 1, 100) })} + > { - + = 2 && arguments[1].isBool() ? arguments[1].getBool() : false; - auto matrix = count == 3 ? JsiSkMatrix::fromValue(runtime, arguments[2]).get() : nullptr; + auto matrix = count >= 3 && !arguments[2].isUndefined() && !arguments[2].isNull() ? JsiSkMatrix::fromValue(runtime, arguments[2]).get() : nullptr; // Create and return shader as host object auto shader = getObject()->makeShader(std::move(uniforms), nullptr, @@ -60,7 +60,7 @@ namespace RNSkia children.push_back(shader); } - auto matrix = count == 4 ? JsiSkMatrix::fromValue(runtime, arguments[3]).get() : nullptr; + auto matrix = count >= 4 && !arguments[3].isUndefined() && !arguments[3].isNull() ? JsiSkMatrix::fromValue(runtime, arguments[3]).get() : nullptr; // Create and return shader as host object auto shader = getObject()->makeShader(std::move(uniforms), children.data(), @@ -131,8 +131,11 @@ namespace RNSkia // Convert to skia uniforms for (int i = 0; i < jsiUniformsSize; i++) { - // We only support floats for now - float value = jsiUniforms.getValueAtIndex(runtime, i).asNumber(); + auto it = getObject()->uniforms().begin() + i; + RuntimeEffectUniform u = fromUniform(*it); + float fValue = jsiUniforms.getValueAtIndex(runtime, i).asNumber(); + int iValue = static_cast(fValue); + auto value = u.isInteger ? iValue : fValue; memcpy(SkTAddOffset(uniforms->writable_data(), i * sizeof(value)), &value, sizeof(value)); } diff --git a/package/src/renderer/components/shaders/Shader.tsx b/package/src/renderer/components/shaders/Shader.tsx index 2b34f3de22..49abea33e2 100644 --- a/package/src/renderer/components/shaders/Shader.tsx +++ b/package/src/renderer/components/shaders/Shader.tsx @@ -3,12 +3,23 @@ import type { ReactNode } from "react"; import { isShader } from "../../../skia"; import type { IRuntimeEffect } from "../../../skia"; +import type { Vector, AnimatedProps, TransformProps } from "../../processors"; import { useDeclaration } from "../../nodes/Declaration"; -import type { AnimatedProps } from "../../processors/Animations/Animations"; +import { localMatrix } from "../../processors"; +import { hasProperty } from "../../typeddash"; -export interface ShaderProps { +const isVector = (obj: unknown): obj is Vector => + hasProperty(obj, "x") && hasProperty(obj, "y"); + +type Uniform = number | number[] | Vector; + +interface Uniforms { + [name: string]: Uniform; +} + +export interface ShaderProps extends TransformProps { source: IRuntimeEffect; - uniforms: number[]; + uniforms: Uniforms; isOpaque?: boolean; children?: ReactNode | ReactNode[]; } @@ -16,11 +27,23 @@ export interface ShaderProps { export const Shader = (props: AnimatedProps) => { const declaration = useDeclaration( props, - ({ uniforms, source, isOpaque }, children) => { + ({ uniforms, source, isOpaque, ...transform }, children) => { + const processedUniforms = new Array(source.getUniformCount()) + .fill(0) + .map((_, i) => { + const name = source.getUniformName(i); + const value = uniforms[name]; + if (isVector(value)) { + return [value.x, value.y]; + } + return value; + }) + .flat(4); return source.makeShaderWithChildren( - uniforms, + processedUniforms, isOpaque, - children.filter(isShader) + children.filter(isShader), + localMatrix(transform) ); } ); diff --git a/package/src/renderer/processors/Shapes.ts b/package/src/renderer/processors/Shapes.ts index a113aaee42..5553a40ae2 100644 --- a/package/src/renderer/processors/Shapes.ts +++ b/package/src/renderer/processors/Shapes.ts @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import type { IRect, IRRect } from "../../skia"; +import { hasProperty } from "../typeddash"; import type { Vector as Point } from "./math/Vector"; import { vec } from "./math/Vector"; @@ -24,9 +25,6 @@ interface ScalarCircleDef { export type CircleDef = PointCircleDef | ScalarCircleDef; -const hasProperty = (obj: unknown, key: string) => - !!(typeof obj === "object" && obj !== null && key in obj); - const isCircleScalarDef = (def: CircleDef): def is ScalarCircleDef => hasProperty(def, "cx"); export const processCircle = (def: CircleDef) => { diff --git a/package/src/renderer/typeddash.ts b/package/src/renderer/typeddash.ts index b648dbfc1a..0f62658443 100644 --- a/package/src/renderer/typeddash.ts +++ b/package/src/renderer/typeddash.ts @@ -1,5 +1,8 @@ export const mapKeys = (obj: T) => Object.keys(obj) as (keyof T)[]; +export const hasProperty = (obj: unknown, key: string) => + !!(typeof obj === "object" && obj !== null && key in obj); + export const exhaustiveCheck = (a: never): never => { throw new Error(`Unexhaustive handling for ${a}`); };