Add SkSL image filter support (ToImageFilter / SKRuntimeImageFilterBuilder)#3778
Add SkSL image filter support (ToImageFilter / SKRuntimeImageFilterBuilder)#3778mattleibow wants to merge 35 commits into
Conversation
Expose SkImageFilters::RuntimeShader (available since Skia m98, with maxSampleRadius since m116) through SkiaSharp's full API stack. C API (externals/skia): - sk_imagefilter_new_runtime_shader: single-child variant - sk_imagefilter_new_runtime_shader_with_children: multi-child with maxSampleRadius for boundary handling C# wrapper (SKImageFilter): - CreateRuntimeShader(builder, childShaderName) - CreateRuntimeShader(builder, childShaderName, input) - CreateRuntimeShader(builder, maxSampleRadius, childShaderName) - CreateRuntimeShader(builder, maxSampleRadius, childShaderName, input) Tests: 12 tests covering creation, rendering, uniform passing, chaining with other filters, and maxSampleRadius. Gallery sample: RuntimeShaderImageFilterSample with 5 interactive SkSL effects (invert, grayscale, sepia, vignette, edge detect). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📦 Try the packages from this PRWarning Do not run these scripts without first reviewing the code in this PR. Step 1 — Download the packages bash / macOS / Linux: curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 3778PowerShell / Windows: iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 3778"Step 2 — Add the local NuGet source dotnet nuget add source ~/.skiasharp/hives/pr-3778/packages --name skiasharp-pr-3778More options
Or download manually from Azure Pipelines — look for the Remove the source when you're done: dotnet nuget remove source skiasharp-pr-3778 |
…e-shader-image-filters # Conflicts: # externals/skia
115fb71 to
664f4dc
Compare
Move the runtime shader image filter API from static factories on SKImageFilter to instance methods that match the existing pattern: - SKRuntimeEffect.ToImageFilter() — matches ToShader/ToColorFilter/ToBlender - SKRuntimeShaderBuilder.BuildImageFilter() — matches Build() → SKShader Remove SKImageFilter.CreateRuntimeShader static methods. Update tests and gallery sample to use the new API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reorder parameters: childShaderName first (required), input second (common), maxSampleRadius last (rare) — matches upstream C++ usage - Add simpler ToImageFilter overloads on SKRuntimeEffect that don't require explicit uniforms/children (matches ToShader()/ToBlender()) - Remove redundant maxSampleRadius-first overloads in favor of (childShaderName, input, maxSampleRadius) order - Move runtime shader image filter tests from SKImageFilterTest to SKRuntimeEffectTest.ImageFilters nested class alongside existing Shaders/ColorFilters test classes - Add ToImageFilter test that exercises the effect directly with explicit uniforms and children Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redesign the RuntimeShader image filter API based on thorough analysis of the upstream C++ implementation (SkRuntimeImageFilter.cpp): C API (sk_runtimeeffect.h): - Rename from sk_imagefilter_new_runtime_shader* to sk_runtimeeffect_make_image_filter* matching existing pattern - Single-child and multi-child variants SKRuntimeEffect.ToImageFilter (10 overloads): - () — auto-detect single child, implicit source - (string?, SKImageFilter?) — nullable name + input - (string?, SKImageFilter?, float) — with maxSampleRadius - (string[], SKImageFilter?[]) — multi-child parallel arrays - (string[], SKImageFilter?[], float) — multi-child with radius - Same 5 with (uniforms, children, ...) prepended SKRuntimeShaderBuilder.BuildImageFilter (5 overloads): - Same single/multi-child patterns, delegates to Effect.ToImageFilter Tests (14 in SKRuntimeEffectTest.ImageFilters): - Zero-arg auto-detect, fails with multiple children - Named child, null name auto-detect, with input, with radius - Rendering tests with pixel verification - Multi-child creation and rendering - ToImageFilter directly on effect with/without explicit uniforms Samples: - Updated RuntimeShaderImageFilterSample - New UnsharpMaskSample: multi-child pattern from upstream gm (content + blurred children, adjustable blur and strength) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SKRuntimeImageFilterBuilder with Build() overloads matching the existing SKRuntimeShaderBuilder/ColorFilter/Blender pattern - Add SKRuntimeEffect.BuildImageFilter() factory method - Remove BuildImageFilter from SKRuntimeShaderBuilder - Configure libSkiaSharp.json to marshal childShaderName as LPStr and childShaderNames as LPArray/LPStr, eliminating manual string marshalling (GCHandle, GetEncodedText, pinning) - Fix em-dash characters to ASCII dashes in comments - Update tests and samples to use new builder type - Suppress pre-existing CS0618 in FillPathSample.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e-shader-image-filters # Conflicts: # externals/skia
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Force all gallery samples to explicitly declare these properties. Fix DateOnly? to non-nullable DateOnly across all samples and SampleManager. Add DateAdded to new image filter samples. Fix picker cast in RuntimeShaderImageFilterSample. Fix sample category references (SampleCategories -> SampleManager). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New class mirrors SKRuntimeEffectUniforms/SKRuntimeEffectChildren pattern. Builder.Inputs["child"] = blur sets image filter inputs by child shader name. Build() reads from Inputs automatically. Builder now has just Build() and Build(float maxSampleRadius). No more parallel string[]/SKImageFilter?[] arrays in user code. Usage: builder.Inputs["content"] = null; // source image builder.Inputs["blurred"] = blur; // blur filter var filter = builder.Build(); Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
📖 Documentation Preview The documentation for this PR has been deployed and is available at: 🔗 View Staging Site This preview will be updated automatically when you push new commits to this PR. This comment is automatically updated by the documentation staging workflow. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only children the user actually set via Inputs[name] = filter are passed to the C API. Unset children are not included, matching the C++ behavior where only listed childShaderNames get overwritten. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Names, Count, Contains, and enumeration now map to the dictionary of explicitly set inputs. Validation on Add still checks against the effect's child names via a HashSet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WriteTo writes into caller-provided arrays instead of allocating. Caller uses Utils.RentArray for pooled arrays. Internal ToImageFilterMulti takes explicit count for rented arrays that may be oversized. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
C++ ref-counts uniform data and children without copying, so these internal accessors could skip the defensive copy. However, since C# writes directly into the SKData buffer, sharing is unsafe if uniforms are mutated after creating a filter. Keep using ToData/ToArray for now; #3859 tracks the proper fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bb4f3bd to
4b6d6e0
Compare
…vs unset Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…stances Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Consistent with children handles pattern. Both use pooled IntPtr arrays from ArrayPool via Utils.RentHandlesArray. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Private ToImageFilter methods now take SKRuntimeEffectUniforms and SKRuntimeEffectChildren directly, using AsArray() to access the backing SKObject[] without copying. Uniforms still use ToData() since the SKData buffer is mutable and must be snapshotted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t coverage - ArgumentOutOfRangeException uses nameof(name) not the value - Names returns dictionary Keys directly (no ToList allocation) - ToData() held in using local to prevent GC before P/Invoke - Null-vs-unset test renders and compares actual pixels - cachedBuilders sized from ShaderSources.Length Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- SKRuntimeEffectImageFilterInputs now queries child type metadata and only accepts uniform shader children, rejecting colorFilter and blender children with a clear error message - Replace per-frame float[] allocation with stackalloc span for vignette resolution uniform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
stackalloc span cannot be passed to ref struct SKRuntimeEffectUniform due to scope escape rules. Revert to float[] - acceptable for a sample. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e-shader-image-filters # Conflicts: # externals/skia
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
samples/Gallery/Shared/Samples/RuntimeShaderImageFilterSample.cs:113
OnDestroy()does not callbase.OnDestroy(). The base implementation handles cleanup for the animation refresh loop (CTS cancellation/disposal) and is called by other samples; please callbase.OnDestroy()to keep lifecycle behavior consistent.
protected override void OnDestroy()
{
sourceBitmap?.Dispose();
sourceBitmap = null;
for (var i = 0; i < cachedBuilders.Length; i++)
{
cachedBuilders[i]?.Dispose();
cachedBuilders[i] = null;
}
}
samples/Gallery/Shared/Samples/UnsharpMaskSample.cs:54
OnDestroy()does not callbase.OnDestroy(). The base method cancels/disposes the animation refresh CTS; calling it keeps cleanup consistent with other samples and avoids leaks if animation is added later.
protected override void OnDestroy()
{
sourceBitmap?.Dispose();
sourceBitmap = null;
builder?.Dispose();
builder = null;
}
| protected override Task OnInit() | ||
| { | ||
| using var stream = new SKManagedStream(SampleMedia.Images.Baboon); | ||
| sourceBitmap = SKBitmap.Decode(stream); | ||
| for (var i = 0; i < ShaderSources.Length; i++) | ||
| cachedBuilders[i] = SKRuntimeEffect.BuildImageFilter(ShaderSources[i]); | ||
| return Task.CompletedTask; | ||
| } |
| protected override Task OnInit() | ||
| { | ||
| using var stream = new SKManagedStream(SampleMedia.Images.Baboon); | ||
| sourceBitmap = SKBitmap.Decode(stream); | ||
| builder = SKRuntimeEffect.BuildImageFilter(UnsharpShader); | ||
| return Task.CompletedTask; | ||
| } |
…e-shader-image-filters
The gallery redesign (main) made ApiTags abstract on SampleBase. Add tags for SKRuntimeEffect, SKRuntimeImageFilterBuilder, and related APIs to both RuntimeShaderImageFilterSample and UnsharpMaskSample. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ReviewC# side🔴 Missing
|
Add support for custom SkSL-based image filters via
SKRuntimeEffect.ToImageFilterandSKRuntimeImageFilterBuilder. This wrapsSkImageFilters::RuntimeShader(Skia m98+, maxSampleRadius m116+), enabling custom GPU image processing effects that compose with built-in filters.Fixes #3776
API
SKRuntimeImageFilterBuilder(primary API)SKRuntimeEffect.ToImageFilter(low-level)New types
SKRuntimeImageFilterBuilder— builder withInputsproperty andBuild()overloadsSKRuntimeEffectImageFilterInputs— dictionary-backed collection mapping child shader names toSKImageFilter?inputs.Names/Count/Containsreflect set entries. Validates names against the effect.C API (
sk_runtimeeffect.h)sk_runtimeeffect_make_image_filter— single-child with maxSampleRadiussk_runtimeeffect_make_image_filter_with_children— multi-child, usesSTArrayfor stack-backed allocationsCompanion skia PR: mono/skia#202
Tests
18 tests in
SKRuntimeEffectTest.ImageFilters:Gallery Samples