Skip to content

Commit

Permalink
SkSLEffect
Browse files Browse the repository at this point in the history
  • Loading branch information
kkwpsv committed Feb 6, 2025
1 parent 151d97a commit 0a1c72f
Show file tree
Hide file tree
Showing 15 changed files with 421 additions and 6 deletions.
6 changes: 3 additions & 3 deletions build/SkiaSharp.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.9" />
</ItemGroup>
<ItemGroup Condition="'$(AvsIncludeSkiaSharp3)' == 'true'">
<PackageReference Include="SkiaSharp" Version="3.118.0-preview.1.2" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="3.118.0-preview.1.2" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.118.0-preview.1.2" />
<PackageReference Include="SkiaSharp" Version="3.118.0-runtimeshader" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="3.118.0-runtimeshader" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.118.0-runtimeshader" />
</ItemGroup>
</Project>
Binary file added samples/ControlCatalog/Assets/noise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions samples/ControlCatalog/ControlCatalog.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
<AvsIncludeSkiaSharp3>true</AvsIncludeSkiaSharp3>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
Expand Down
3 changes: 3 additions & 0 deletions samples/ControlCatalog/MainView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@
<TabItem Header="Screens">
<pages:ScreenPage />
</TabItem>
<TabItem Header="SkSLEffect">
<pages:SkSLEffectPage />
</TabItem>
<FlyoutBase.AttachedFlyout>
<Flyout>
<StackPanel Width="152" Spacing="8">
Expand Down
12 changes: 12 additions & 0 deletions samples/ControlCatalog/Pages/SKSLEffectPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.SkSLEffectPage">
<UniformGrid Columns="3" Height="200">
<Rectangle x:Name="Rectangle0" Fill="Red" Margin="20"/>
<Rectangle x:Name="Rectangle1" Fill="Red" Margin="20"/>
<Rectangle x:Name="Rectangle2" Fill="Red" Margin="20"/>
</UniformGrid>
</UserControl>
181 changes: 181 additions & 0 deletions samples/ControlCatalog/Pages/SKSLEffectPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Skia.Effects;
using Avalonia.Styling;
using SkiaSharp;

namespace ControlCatalog.Pages
{
public partial class SkSLEffectPage : UserControl
{
public SkSLEffectPage()
{
InitializeComponent();
InitEffect();
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}

private void InitEffect()
{
var rectangle1 = this.FindControl<Rectangle>("Rectangle1")!;
var shaderBuilder = CreateSimpleShaderBuilder();
if (shaderBuilder != null)
{
rectangle1.Effect = new SkSLEffect(shaderBuilder)
{
ChildShaderNames = ["src"],
Inputs = [null],
};
}

var rectangle2 = this.FindControl<Rectangle>("Rectangle2")!;
try
{
var effect = new DissolveSkSLEffect();
effect.Progress = 0.5f;
effect[!DissolveSkSLEffect.ResolutionProperty] = new Binding
{
Source = rectangle2,
Path = "Bounds.Size",
};
rectangle2.Effect = effect;

var animation = new Animation
{
Duration = TimeSpan.FromSeconds(2),
IterationCount = IterationCount.Infinite,
PlaybackDirection = PlaybackDirection.Alternate,
Children = {
new KeyFrame
{
Setters =
{
new Setter(DissolveSkSLEffect.ProgressProperty, 0f),
},
KeyTime = TimeSpan.FromSeconds(0),
},
new KeyFrame
{
Setters =
{
new Setter(DissolveSkSLEffect.ProgressProperty, 1f),
},
KeyTime = TimeSpan.FromSeconds(2),
}
}
};

_ = animation.RunAsync(effect);
}
catch
{
// Do not crash.
}
}

private SKRuntimeShaderBuilder? CreateSimpleShaderBuilder()
{
var sksl = @"
uniform shader src;
float4 main(float2 coord) {
return src.eval(coord).bgra;
}
";
var effect = SKRuntimeEffect.CreateShader(sksl, out var str);
if (effect != null)
{
return new SKRuntimeShaderBuilder(effect);
}
else
{
return null;
}
}
}

public class DissolveSkSLEffect : SkSLEffect
{
public static readonly StyledProperty<float> ProgressProperty = AvaloniaProperty.Register<DissolveSkSLEffect, float>(nameof(Progress), default);

public float Progress
{
get => GetValue(ProgressProperty);
set => SetValue(ProgressProperty, value);
}

public static readonly StyledProperty<Size> ResolutionProperty = AvaloniaProperty.Register<DissolveSkSLEffect, Size>(nameof(Resolution), default);

public Size Resolution
{
get => GetValue(ResolutionProperty);
set => SetValue(ResolutionProperty, value);
}

public DissolveSkSLEffect() : base(CreateShaderBuilder())
{
ChildShaderNames = ["src"];
Inputs = [null];
AffectsRender<SkSLEffect>(ProgressProperty, ResolutionProperty);

RegisterUniform("progress", ProgressProperty);
RegisterUniform("resolution", ResolutionProperty);
}

private static SKRuntimeShaderBuilder? s_shaderBuilder;
private static SKRuntimeShaderBuilder CreateShaderBuilder()
{
if (s_shaderBuilder != null)
{
return s_shaderBuilder;
}

var sksl = @"
uniform float2 resolution;
uniform shader src;
uniform shader noise;
uniform float2 noiseResolution;
uniform float progress;
float4 main(float2 coord) {
float val = noise.eval(fract(coord / resolution) * noiseResolution).x;
if(val < progress)
{
return src.eval(coord);
}
else
{
return float4(0,0,0,0);
}
}
";
var effect = SKRuntimeEffect.CreateShader(sksl, out var str);
if (effect != null)
{
var noise = AssetLoader.Open(new Uri("avares://ControlCatalog/Assets/noise.png"));
var noiseImage = SKImage.FromEncodedData(noise);
var noiseImageShader = SKShader.CreateImage(noiseImage);
var builder = new SKRuntimeShaderBuilder(effect);
builder.Uniforms["noiseResolution"] = new SKSize(noiseImage.Width, noiseImage.Height);
builder.Children["noise"] = noiseImageShader;
s_shaderBuilder = builder;
return s_shaderBuilder;
}
else
{
throw new NotSupportedException();
}
}
}
}
16 changes: 15 additions & 1 deletion src/Avalonia.Base/Media/Effects/EffectExtesions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ internal static Thickness GetEffectOutputPadding(this IEffect? effect)
return new Thickness(Math.Max(0, 0 - rc.X),
Math.Max(0, 0 - rc.Y), Math.Max(0, rc.Right), Math.Max(0, rc.Bottom));
}
if (effect is IShaderEffect)
{
// Shader effect should not have padding.
return default;
}

throw new ArgumentException("Unknown effect type: " + effect.GetType());
}
Expand Down Expand Up @@ -53,4 +58,13 @@ internal static bool EffectEquals(this IImmutableEffect? immutable, IEffect? rig
return immutable.Equals(right);
return false;
}
}

internal static bool EffectEquals(this IEffect? left, IEffect? right)
{
if (left == null && right == null)
return true;
if (left != null && right != null)
return left.Equals(right);
return false;
}
}
12 changes: 12 additions & 0 deletions src/Avalonia.Base/Media/Effects/IShaderEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Avalonia.Media
{
public interface IShaderEffect : IEffect
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Transport;

namespace Avalonia.Rendering.Composition.Server
{
internal sealed class ServerCompositionSimpleShaderEffect : SimpleServerRenderResource, IShaderEffect, IImmutableEffect
{
public ServerCompositionSimpleShaderEffect(ServerCompositor compositor) : base(compositor)
{
}

public object? ShaderObject { get; set; }

public string[] ChildShaderNames { get; set; } = [];

public object?[] Inputs = [];

protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{
base.DeserializeChangesCore(reader, committedAt);

(ShaderObject as IDisposable)?.Dispose();
ShaderObject = reader.ReadObject<object>();
ChildShaderNames = reader.ReadObject<string[]>();
Inputs = reader.ReadObject<object?[]>();
}

public bool Equals(IEffect? other)
{
return false;
}

public override void Dispose()
{
base.Dispose();
(ShaderObject as IDisposable)?.Dispose();
}
}
}
28 changes: 28 additions & 0 deletions src/Avalonia.Base/Rendering/Composition/Visual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public abstract partial class CompositionVisual
{
private IBrush? _opacityMask;

private IEffect? _effectField;

private protected virtual void OnRootChangedCore()
{
}
Expand Down Expand Up @@ -48,6 +50,32 @@ public IBrush? OpacityMask
}
}

public IEffect? Effect
{
get => _effectField;
set
{
if (ReferenceEquals(_effectField, value))
return;

if (_effectField is ICompositionRenderResource<IImmutableEffect> oldCompositorEffect)
{
oldCompositorEffect.ReleaseOnCompositor(Compositor);
_effectField = null;
EffectTransportField = null;
}

if (value is ICompositionRenderResource<IImmutableEffect> newCompositorEffect)
{
newCompositorEffect.AddRefOnCompositor(Compositor);
EffectTransportField = newCompositorEffect.GetForCompositor(Compositor);
_effectField = value;
}
else
EffectTransportField = value?.ToImmutable();
}
}

internal Matrix? TryGetServerGlobalTransform()
{
if (Root == null)
Expand Down
6 changes: 5 additions & 1 deletion src/Avalonia.Base/Visual.Composition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Avalonia.Collections.Pooled;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.VisualTree;

Expand Down Expand Up @@ -36,6 +37,7 @@ internal virtual void DetachFromCompositor()

CompositionVisual.DrawList = null;
CompositionVisual.OpacityMask = null;
CompositionVisual.Effect = null;
CompositionVisual = null;
}
}
Expand Down Expand Up @@ -145,7 +147,9 @@ internal virtual void SynchronizeCompositionProperties()
comp.OpacityMask = OpacityMask;

if (!comp.Effect.EffectEquals(Effect))
comp.Effect = Effect?.ToImmutable();
{
comp.Effect = Effect;
}

comp.RenderOptions = RenderOptions;

Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/composition-schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<Property Name="AdornedVisual" Type="CompositionVisual?" Internal="true" />
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" ClientName="OpacityMaskBrushTransportField" Type="Avalonia.Media.IBrush?" Private="true" />
<Property Name="Effect" Type="Avalonia.Media.IImmutableEffect?" Internal="true" />
<Property Name="Effect" ClientName="EffectTransportField" Type="Avalonia.Media.IImmutableEffect?" Private="true" />
<Property Name="RenderOptions" Type="Avalonia.Media.RenderOptions" />
</Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>
Expand Down
1 change: 1 addition & 0 deletions src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<IncludeLinuxSkia>true</IncludeLinuxSkia>
<IncludeWasmSkia>true</IncludeWasmSkia>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AvsIncludeSkiaSharp3>true</AvsIncludeSkiaSharp3>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\NoiseAsset_256X256_PNG.png" />
Expand Down
Loading

0 comments on commit 0a1c72f

Please sign in to comment.