Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bc58f68
Add SKImageFilter.CreateRuntimeShader API
mattleibow Apr 27, 2026
664f4dc
Merge remote-tracking branch 'origin/main' into mattleibow/dev-runtim…
mattleibow Apr 28, 2026
531d597
Refactor RuntimeShader API to ToImageFilter/BuildImageFilter pattern
mattleibow May 5, 2026
9cbf0ee
Polish API: reorder params, add simpler overloads, move tests
mattleibow May 5, 2026
aa61329
Final API design: 5+10 overloads, multi-child, unsharp mask sample
mattleibow May 5, 2026
b8c1f8d
Add SKRuntimeImageFilterBuilder, use marshalled strings
mattleibow May 5, 2026
648bd56
Merge remote-tracking branch 'origin/main' into mattleibow/dev-runtim…
mattleibow May 5, 2026
dc034f3
Fix samples: SampleCategories -> SampleManager after merge
mattleibow May 5, 2026
8815273
Make Title, Description, DateAdded abstract on SampleBase
mattleibow May 5, 2026
089fc64
Narrow 'new sample' window from 10 days to 3 days
mattleibow May 5, 2026
f4211fe
Add SKRuntimeEffectImageFilterInputs for typed input binding
mattleibow May 5, 2026
88acd54
Show Inputs pattern explicitly in RuntimeShader sample
mattleibow May 5, 2026
4890d5c
Remove unnecessary Inputs assignment in single-child sample
mattleibow May 5, 2026
cc85ad9
Revert SampleBase/SampleManager changes, keep DateOnly? nullable
mattleibow May 5, 2026
1748c34
Restore DateOnly? on all existing samples
mattleibow May 5, 2026
b299ae8
Use Dictionary for Inputs, only pass explicitly set entries
mattleibow May 6, 2026
61049a8
Make Names/Count/Contains reflect set inputs, not effect children
mattleibow May 6, 2026
fc7d917
Use rented arrays in ToImageFilter to avoid allocations
mattleibow May 6, 2026
4b6d6e0
Add internal AsData/AsArray for future allocation work (#3859)
mattleibow May 6, 2026
a930d0c
Add tests for Inputs Names/Count/Contains, Reset, invalid name, null …
mattleibow May 6, 2026
37da64d
Fix object leaks in tests - dispose all SKShader and SKImageFilter in…
mattleibow May 6, 2026
9baa36c
Use RentHandlesArray for input handles instead of stackalloc
mattleibow May 6, 2026
8c106c4
Eliminate children.ToArray() copies in image filter path
mattleibow May 6, 2026
afd467d
Remove raw array overloads from ToImageFilter
mattleibow May 6, 2026
fe66083
Simplify to two clean private methods: Single and Multi
mattleibow May 6, 2026
d8492c3
Pass SKRuntimeEffectImageFilterInputs directly to ToImageFilterMulti
mattleibow May 6, 2026
90fbbfb
Simplify C API: drop children param, use STArray
mattleibow May 6, 2026
c76edb7
Restore children param in C API, pass through from C#
mattleibow May 6, 2026
ad13038
Fix maxSampleRadius on single-child path, cache builders in samples
mattleibow May 6, 2026
923a130
Address review: fix exception param, Names allocation, GC safety, tes…
mattleibow May 6, 2026
7a8363f
Restrict Inputs to shader-type children, fix vignette allocation
mattleibow May 6, 2026
537817d
Fix stackalloc scope error in vignette sample
mattleibow May 6, 2026
bc918de
Merge remote-tracking branch 'origin/main' into mattleibow/dev-runtim…
mattleibow May 13, 2026
1703ab0
Merge remote-tracking branch 'origin/main' into mattleibow/dev-runtim…
mattleibow May 17, 2026
954660e
Add ApiTags to runtime shader image filter gallery samples
mattleibow May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions binding/SkiaSharp/SKRuntimeEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
return new SKRuntimeBlenderBuilder (effect);
}

public static SKRuntimeImageFilterBuilder BuildImageFilter (string sksl)
{
var effect = CreateShader (sksl, out var errors);
ValidateResult (effect, errors);
return new SKRuntimeImageFilterBuilder (effect);
}

private static void ValidateResult (SKRuntimeEffect effect, string errors)
{
if (effect is null) {
Expand Down Expand Up @@ -190,6 +197,73 @@
}
}

// ToImageFilter - single-child

public SKImageFilter ToImageFilter () =>
ToImageFilterSingle (null, null, null, null, 0);

public SKImageFilter ToImageFilter (string? childShaderName, SKImageFilter? input) =>

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 205 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ToImageFilterSingle (null, null, childShaderName, input, 0);

public SKImageFilter ToImageFilter (string? childShaderName, SKImageFilter? input, float maxSampleRadius) =>

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 208 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ToImageFilterSingle (null, null, childShaderName, input, maxSampleRadius);

public SKImageFilter ToImageFilter (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children) =>
ToImageFilterSingle (uniforms, children, null, null, 0);

public SKImageFilter ToImageFilter (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, string? childShaderName, SKImageFilter? input) =>

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 214 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ToImageFilterSingle (uniforms, children, childShaderName, input, 0);

public SKImageFilter ToImageFilter (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, string? childShaderName, SKImageFilter? input, float maxSampleRadius) =>

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 217 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ToImageFilterSingle (uniforms, children, childShaderName, input, maxSampleRadius);

// ToImageFilter - with SKRuntimeEffectImageFilterInputs

public SKImageFilter ToImageFilter (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, SKRuntimeEffectImageFilterInputs inputs) =>
ToImageFilter (uniforms, children, inputs, 0);

public SKImageFilter ToImageFilter (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, SKRuntimeEffectImageFilterInputs inputs, float maxSampleRadius)
{
if (inputs.Count == 0)
return ToImageFilterSingle (uniforms, children, null, null, maxSampleRadius);

return ToImageFilterMulti (uniforms, children, inputs, maxSampleRadius);
}

// ToImageFilter - private implementations

private SKImageFilter ToImageFilterSingle (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, string? childShaderName, SKImageFilter? input, float maxSampleRadius)

Check warning on line 235 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 235 in binding/SkiaSharp/SKRuntimeEffect.cs

View workflow job for this annotation

GitHub Actions / Build Site

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
using var uniformData = uniforms?.ToData ();
var uniformsHandle = uniformData?.Handle ?? IntPtr.Zero;
using var childrenHandles = Utils.RentHandlesArray (children?.AsArray (), true);

Comment thread
mattleibow marked this conversation as resolved.
fixed (IntPtr* ch = childrenHandles) {
Comment thread
mattleibow marked this conversation as resolved.
return SKImageFilter.GetObject (SkiaApi.sk_runtimeeffect_make_image_filter (
Handle, uniformsHandle, ch, (IntPtr)childrenHandles.Length,
maxSampleRadius, childShaderName ?? string.Empty, input?.Handle ?? IntPtr.Zero));
}
}

private SKImageFilter ToImageFilterMulti (SKRuntimeEffectUniforms uniforms, SKRuntimeEffectChildren children, SKRuntimeEffectImageFilterInputs inputs, float maxSampleRadius)
{
var count = inputs.Count;
using var uniformData = uniforms?.ToData ();
var uniformsHandle = uniformData?.Handle ?? IntPtr.Zero;
using var childrenHandles = Utils.RentHandlesArray (children?.AsArray (), true);
using var nameRental = Utils.RentArray<string> (count);
using var filterRental = Utils.RentArray<SKImageFilter?> (count);
inputs.WriteTo (nameRental.Array, filterRental.Array);
using var inputHandles = Utils.RentHandlesArray (filterRental.Array, true);
Comment thread
mattleibow marked this conversation as resolved.

fixed (IntPtr* ch = childrenHandles)
fixed (IntPtr* ih = inputHandles) {
return SKImageFilter.GetObject (SkiaApi.sk_runtimeeffect_make_image_filter_with_children (
Handle, uniformsHandle, ch, (IntPtr)childrenHandles.Length,
maxSampleRadius, nameRental.Array, ih, count));
}
}

//

internal static SKRuntimeEffect GetObject (IntPtr handle) =>
Expand Down Expand Up @@ -317,6 +391,7 @@
return SKData.CreateCopy (data.Data, data.Size);
}


IEnumerator IEnumerable.GetEnumerator () =>
GetEnumerator ();

Expand Down Expand Up @@ -404,6 +479,8 @@
public SKObject[] ToArray () =>
children.ToArray ();

internal SKObject[] AsArray () => children;

IEnumerator IEnumerable.GetEnumerator () =>
GetEnumerator ();

Expand All @@ -415,6 +492,72 @@
}
}

public unsafe class SKRuntimeEffectImageFilterInputs : IEnumerable<string>, IDisposable
{
private readonly HashSet<string> validNames;
private readonly Dictionary<string, SKImageFilter?> inputs;

public SKRuntimeEffectImageFilterInputs (SKRuntimeEffect effect)
{
_ = effect ?? throw new ArgumentNullException (nameof (effect));

validNames = new HashSet<string> ();
var allChildren = effect.Children;
for (var i = 0; i < allChildren.Count; i++) {
SKRuntimeEffectChildNative child;
SkiaApi.sk_runtimeeffect_get_child_from_index (effect.Handle, i, &child);
if (child.fType == SKRuntimeEffectChildTypeNative.Shader)
validNames.Add (allChildren[i]);
}
inputs = new Dictionary<string, SKImageFilter?> ();
}

public IReadOnlyCollection<string> Names =>
inputs.Keys;

public int Count =>
inputs.Count;

public void Reset () =>
inputs.Clear ();

public bool Contains (string name) =>
inputs.ContainsKey (name);

public SKImageFilter? this[string name] {
set => Add (name, value);
}

public void Add (string name, SKImageFilter? value)
{
if (!validNames.Contains (name))
throw new ArgumentOutOfRangeException (nameof (name), name, $"Shader child was not found for name: '{name}'.");

Comment thread
mattleibow marked this conversation as resolved.
inputs[name] = value;
}

internal int WriteTo (string[] names, SKImageFilter?[] filters)
{
var i = 0;
foreach (var kvp in inputs) {
names[i] = kvp.Key;
filters[i] = kvp.Value;
i++;
}
return inputs.Count;
}

IEnumerator IEnumerable.GetEnumerator () =>
GetEnumerator ();

public IEnumerator<string> GetEnumerator () =>
inputs.Keys.GetEnumerator ();

public void Dispose ()
{
}
}

public unsafe readonly ref struct SKRuntimeEffectUniform
{
internal enum DataType
Expand Down Expand Up @@ -668,6 +811,23 @@
Effect.ToShader (Uniforms, Children, localMatrix);
}

public class SKRuntimeImageFilterBuilder : SKRuntimeEffectBuilder
{
public SKRuntimeImageFilterBuilder (SKRuntimeEffect effect)
: base (effect)
{
Inputs = new SKRuntimeEffectImageFilterInputs (effect);
}

public SKRuntimeEffectImageFilterInputs Inputs { get; }

public SKImageFilter Build () =>
Effect.ToImageFilter (Uniforms, Children, Inputs);

public SKImageFilter Build (float maxSampleRadius) =>
Effect.ToImageFilter (Uniforms, Children, Inputs, maxSampleRadius);
}

public class SKRuntimeColorFilterBuilder : SKRuntimeEffectBuilder
{
public SKRuntimeColorFilterBuilder (SKRuntimeEffect effect)
Expand Down
38 changes: 38 additions & 0 deletions binding/SkiaSharp/SkiaApi.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13619,6 +13619,44 @@ internal static sk_runtimeeffect_t sk_runtimeeffect_make_for_shader (sk_string_t
(sk_runtimeeffect_make_for_shader_delegate ??= GetSymbol<Delegates.sk_runtimeeffect_make_for_shader> ("sk_runtimeeffect_make_for_shader")).Invoke (sksl, error);
#endif

// sk_imagefilter_t* sk_runtimeeffect_make_image_filter(sk_runtimeeffect_t* effect, sk_data_t* uniforms, sk_flattenable_t** children, size_t childCount, float maxSampleRadius, const char* childShaderName, const sk_imagefilter_t* input)
#if !USE_DELEGATES
#if USE_LIBRARY_IMPORT
[LibraryImport (SKIA)]
internal static partial sk_imagefilter_t sk_runtimeeffect_make_image_filter (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPStr)] String childShaderName, sk_imagefilter_t input);
#else // !USE_LIBRARY_IMPORT
[DllImport (SKIA, CallingConvention = CallingConvention.Cdecl)]
internal static extern sk_imagefilter_t sk_runtimeeffect_make_image_filter (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPStr)] String childShaderName, sk_imagefilter_t input);
#endif
#else
private partial class Delegates {
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
internal delegate sk_imagefilter_t sk_runtimeeffect_make_image_filter (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPStr)] String childShaderName, sk_imagefilter_t input);
}
private static Delegates.sk_runtimeeffect_make_image_filter sk_runtimeeffect_make_image_filter_delegate;
internal static sk_imagefilter_t sk_runtimeeffect_make_image_filter (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPStr)] String childShaderName, sk_imagefilter_t input) =>
(sk_runtimeeffect_make_image_filter_delegate ??= GetSymbol<Delegates.sk_runtimeeffect_make_image_filter> ("sk_runtimeeffect_make_image_filter")).Invoke (effect, uniforms, children, childCount, maxSampleRadius, childShaderName, input);
#endif

// sk_imagefilter_t* sk_runtimeeffect_make_image_filter_with_children(sk_runtimeeffect_t* effect, sk_data_t* uniforms, sk_flattenable_t** children, size_t childCount, float maxSampleRadius, const char** childShaderNames, const sk_imagefilter_t** inputs, int inputCount)
#if !USE_DELEGATES
#if USE_LIBRARY_IMPORT
[LibraryImport (SKIA)]
internal static partial sk_imagefilter_t sk_runtimeeffect_make_image_filter_with_children (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[] childShaderNames, sk_imagefilter_t* inputs, Int32 inputCount);
#else // !USE_LIBRARY_IMPORT
[DllImport (SKIA, CallingConvention = CallingConvention.Cdecl)]
internal static extern sk_imagefilter_t sk_runtimeeffect_make_image_filter_with_children (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[] childShaderNames, sk_imagefilter_t* inputs, Int32 inputCount);
#endif
#else
private partial class Delegates {
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
internal delegate sk_imagefilter_t sk_runtimeeffect_make_image_filter_with_children (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[] childShaderNames, sk_imagefilter_t* inputs, Int32 inputCount);
}
private static Delegates.sk_runtimeeffect_make_image_filter_with_children sk_runtimeeffect_make_image_filter_with_children_delegate;
internal static sk_imagefilter_t sk_runtimeeffect_make_image_filter_with_children (sk_runtimeeffect_t effect, sk_data_t uniforms, sk_flattenable_t* children, /* size_t */ IntPtr childCount, Single maxSampleRadius, [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[] childShaderNames, sk_imagefilter_t* inputs, Int32 inputCount) =>
(sk_runtimeeffect_make_image_filter_with_children_delegate ??= GetSymbol<Delegates.sk_runtimeeffect_make_image_filter_with_children> ("sk_runtimeeffect_make_image_filter_with_children")).Invoke (effect, uniforms, children, childCount, maxSampleRadius, childShaderNames, inputs, inputCount);
#endif

// sk_shader_t* sk_runtimeeffect_make_shader(sk_runtimeeffect_t* effect, sk_data_t* uniforms, sk_flattenable_t** children, size_t childCount, const sk_matrix_t* localMatrix)
#if !USE_DELEGATES
#if USE_LIBRARY_IMPORT
Expand Down
10 changes: 10 additions & 0 deletions binding/libSkiaSharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,16 @@
"3": "[MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[]"
}
},
"sk_runtimeeffect_make_image_filter": {
"parameters": {
"5": "[MarshalAs (UnmanagedType.LPStr)] String"
}
},
"sk_runtimeeffect_make_image_filter_with_children": {
"parameters": {
"5": "[MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[]"
}
},
"sk_path_parse_svg_string": {
"parameters": {
"1": "[MarshalAs (UnmanagedType.LPStr)] String"
Expand Down
2 changes: 1 addition & 1 deletion externals/skia
Loading
Loading