Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SkSLEffect #5

Open
wants to merge 1 commit into
base: h/11.2.3-hotfix
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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