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

Rewrite internals of EffectDrawBase #310

Merged
merged 6 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
43 changes: 28 additions & 15 deletions SukiUI.Demo/Features/Effects/ShaderToyRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,59 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.Rendering.Composition;
using SkiaSharp;
using SukiUI.Utilities.Effects;

namespace SukiUI.Demo.Features.Effects
{
public class ShaderToyRenderer : Control
{
private readonly ShaderToyDraw _draw;
private CompositionCustomVisual? _customVisual;
private SukiEffect? _sukiEffect;

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var comp = ElementComposition.GetElementVisual(this)?.Compositor;
if (comp == null || _customVisual?.Compositor == comp) return;
var visualHandler = new ShaderToyDraw();
_customVisual = comp.CreateCustomVisual(visualHandler);
ElementComposition.SetElementChildVisual(this, _customVisual);
_customVisual.SendHandlerMessage(EffectDrawBase.StartAnimations);
if(_sukiEffect != null) _customVisual.SendHandlerMessage(_sukiEffect);
Update();
}

public ShaderToyRenderer()
private void Update()
{
_draw = new ShaderToyDraw(Bounds);
if (_customVisual == null) return;
_customVisual.Size = new Vector(Bounds.Width, Bounds.Height);
}

public override void Render(DrawingContext context)
public void SetEffect(SukiEffect effect)
{
_draw.Bounds = Bounds;
context.Custom(_draw);
_sukiEffect = effect;
_customVisual?.SendHandlerMessage(effect);
}

public void SetEffect(SukiEffect effect)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
_draw.Effect = effect;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
base.OnPropertyChanged(change);
if(change.Property == BoundsProperty)
Update();
}

private class ShaderToyDraw : EffectDrawBase
{
public ShaderToyDraw(Rect bounds) : base(bounds)
public ShaderToyDraw()
{
AnimationEnabled = true;
AnimationSpeedScale = 2f;
}

protected override void Render(SKCanvas canvas, SKRect rect)
{
canvas.Scale(1,-1);
canvas.Translate(0, (float)-Bounds.Height);
using var mainShaderPaint = new SKPaint();

if (Effect is not null)
Expand All @@ -48,7 +62,6 @@ protected override void Render(SKCanvas canvas, SKRect rect)
mainShaderPaint.Shader = shader;
canvas.DrawRect(rect, mainShaderPaint);
}
canvas.Restore();
}

protected override void RenderSoftware(SKCanvas canvas, SKRect rect)
Expand Down
63 changes: 43 additions & 20 deletions SukiUI/Controls/Loading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Rendering.Composition;
using SkiaSharp;
using SukiUI.Extensions;
using SukiUI.Utilities.Effects;
Expand Down Expand Up @@ -37,62 +38,77 @@ public IBrush? Foreground
{ LoadingStyle.Glow, SukiEffect.FromEmbeddedResource("glow") },
{ LoadingStyle.Pellets, SukiEffect.FromEmbeddedResource("pellets") },
};

private readonly LoadingEffectDraw _draw;

private CompositionCustomVisual? _customVisual;
public Loading()
{
Width = 50;
Height = 50;
_draw = new LoadingEffectDraw(Bounds);
}

public override void Render(DrawingContext context)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_draw.Bounds = Bounds;
_draw.Effect = Effects[LoadingStyle];
base.OnAttachedToVisualTree(e);
var comp = ElementComposition.GetElementVisual(this)?.Compositor;
if (comp == null || _customVisual?.Compositor == comp) return;
var visualHandler = new LoadingEffectDraw();
_customVisual = comp.CreateCustomVisual(visualHandler);
ElementComposition.SetElementChildVisual(this, _customVisual);
_customVisual.SendHandlerMessage(EffectDrawBase.StartAnimations);
if (Foreground is null)
this[!ForegroundProperty] = new DynamicResourceExtension("SukiPrimaryColor");
if (Foreground is ImmutableSolidColorBrush brush)
brush.Color.ToFloatArrayNonAlloc(_draw.Color);
context.Custom(_draw);
brush.Color.ToFloatArrayNonAlloc(_color);
_customVisual.SendHandlerMessage(_color);
_customVisual.SendHandlerMessage(Effects[LoadingStyle]);
Update();
}

private void Update()
{
if (_customVisual == null) return;
_customVisual.Size = new Vector(Bounds.Width, Bounds.Height);
}

private readonly float[] _color = new float[3];

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property != ForegroundProperty) return;
if (Foreground is ImmutableSolidColorBrush brush)
brush.Color.ToFloatArrayNonAlloc(_draw.Color);
if (change.Property == BoundsProperty)
Update();
else if (change.Property == ForegroundProperty && Foreground is ImmutableSolidColorBrush brush)
{
brush.Color.ToFloatArrayNonAlloc(_color);
_customVisual?.SendHandlerMessage(_color);
}
else if (change.Property == LoadingStyleProperty)
_customVisual?.SendHandlerMessage(Effects[LoadingStyle]);
}

public class LoadingEffectDraw : EffectDrawBase
{
public float[] Color { get; } = { 1.0f, 0f, 0f };
private float[] _color = { 1.0f, 0f, 0f };

public LoadingEffectDraw(Rect bounds) : base(bounds)
public LoadingEffectDraw()
{
AnimationEnabled = true;
AnimationSpeedScale = 2f;
}

protected override void Render(SKCanvas canvas, SKRect rect)
{
canvas.Scale(1, -1);
canvas.Translate(0, (float)-Bounds.Height);
using var mainShaderPaint = new SKPaint();

if (Effect is not null)
{
using var shader = EffectWithCustomUniforms(effect => new SKRuntimeEffectUniforms(effect)
{
{ "iForeground", Color }
{ "iForeground", _color }
});
mainShaderPaint.Shader = shader;
canvas.DrawRect(rect, mainShaderPaint);
}

canvas.Restore();
}

// I'm not really sure how to render this properly in software fallback scenarios.
Expand All @@ -102,6 +118,13 @@ protected override void RenderSoftware(SKCanvas canvas, SKRect rect)
{
throw new System.NotImplementedException();
}

public override void OnMessage(object message)
{
base.OnMessage(message);
if (message is float[] color)
_color = color;
}
}
}

Expand Down
74 changes: 41 additions & 33 deletions SukiUI/Controls/SukiBackground.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.Rendering.Composition;
using SukiUI.Enums;
using SukiUI.Utilities.Effects;

Expand Down Expand Up @@ -54,7 +51,7 @@ public string? ShaderCode
AvaloniaProperty.Register<SukiWindow, bool>(nameof(AnimationEnabled), defaultValue: false);

/// <summary>
/// Enables/disables animations - DEFAULT: False
/// [WARNING: This feature is experimental and has relatively high GPU utilisation] Enables/disables animations - DEFAULT: False
/// </summary>
public bool AnimationEnabled
{
Expand All @@ -65,8 +62,8 @@ public bool AnimationEnabled
public static readonly StyledProperty<bool> TransitionsEnabledProperty =
AvaloniaProperty.Register<SukiBackground, bool>(nameof(TransitionsEnabled), defaultValue: false);

/// <summary>
/// Enables/disables transition animations when switching backgrounds - DEFAULT: False
/// <summary>
/// Enables/disables transition animations when switching backgrounds, Currently non-functional - DEFAULT: False
/// </summary>
public bool TransitionsEnabled
{
Expand Down Expand Up @@ -94,54 +91,65 @@ public bool ForceSoftwareRendering
set => SetValue(ForceSoftwareRenderingProperty, value);
}

private readonly EffectBackgroundDraw _draw;

private CompositionCustomVisual? _customVisual;
public SukiBackground()
{
IsHitTestVisible = false;
_draw = new EffectBackgroundDraw(new Rect(0, 0, Bounds.Width, Bounds.Height));
}

protected override void OnLoaded(RoutedEventArgs e)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnLoaded(e);
_draw.ForceSoftwareRendering = ForceSoftwareRendering;
_draw.TransitionsEnabled = TransitionsEnabled;
_draw.TransitionTime = TransitionTime;
_draw.AnimationEnabled = AnimationEnabled;
base.OnAttachedToVisualTree(e);
var comp = ElementComposition.GetElementVisual(this)?.Compositor;
if (comp == null || _customVisual?.Compositor == comp) return;
var visualHandler = new EffectBackgroundDraw();
_customVisual = comp.CreateCustomVisual(visualHandler);
ElementComposition.SetElementChildVisual(this, _customVisual);
_customVisual.SendHandlerMessage(TransitionTime);
HandleBackgroundStyleChanges();
Update();
}

private void Update()
{
if (_customVisual == null) return;
_customVisual.Size = new Vector(Bounds.Width, Bounds.Height);
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ForceSoftwareRenderingProperty && change.NewValue is bool forceSoftwareRendering)
_draw.ForceSoftwareRendering = forceSoftwareRendering;
if (change.Property == BoundsProperty)
Update();
else if (change.Property == ForceSoftwareRenderingProperty && change.NewValue is bool forceSoftwareRendering)
_customVisual?.SendHandlerMessage(forceSoftwareRendering
? EffectDrawBase.EnableForceSoftwareRendering
: EffectDrawBase.DisableForceSoftwareRendering);
else if(change.Property == TransitionsEnabledProperty && change.NewValue is bool transitionEnabled)
_draw.TransitionsEnabled = transitionEnabled;
_customVisual?.SendHandlerMessage(transitionEnabled
? EffectBackgroundDraw.EnableTransitions
: EffectBackgroundDraw.DisableTransitions);
else if(change.Property == TransitionTimeProperty && change.NewValue is double transitionTime)
_draw.TransitionTime = transitionTime;
else if(change.Property == AnimationEnabledProperty && change.NewValue is bool animationEnabled)
_draw.AnimationEnabled = animationEnabled;
_customVisual?.SendHandlerMessage(transitionTime);
else if (change.Property == AnimationEnabledProperty && change.NewValue is bool animationEnabled)
_customVisual?.SendHandlerMessage(animationEnabled
? EffectDrawBase.StartAnimations
: EffectDrawBase.StopAnimations);
else if(change.Property == StyleProperty || change.Property == ShaderFileProperty || change.Property == ShaderCodeProperty)
HandleBackgroundStyleChanges();
}

public override void Render(DrawingContext context)
{
_draw.Bounds = Bounds;
context.Custom(_draw);
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}


private void HandleBackgroundStyleChanges()
{
SukiEffect effect;
if (ShaderFile is not null)
_draw.Effect = SukiEffect.FromEmbeddedResource(ShaderFile);
effect = SukiEffect.FromEmbeddedResource(ShaderFile);
else if (ShaderCode is not null)
_draw.Effect = SukiEffect.FromString(ShaderCode);
effect = SukiEffect.FromString(ShaderCode);
else
_draw.Effect = SukiEffect.FromEmbeddedResource(Style.ToString());
effect = SukiEffect.FromEmbeddedResource(Style.ToString());
_customVisual?.SendHandlerMessage(effect);
}
}
}
Loading