Skip to content

Commit

Permalink
Merge pull request #232 from sirdoombox/main
Browse files Browse the repository at this point in the history
Include a simple shader toy style editor in demo app.
  • Loading branch information
kikipoulet authored Jul 1, 2024
2 parents 01509e2 + 727b02a commit e31a762
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 2 deletions.
40 changes: 40 additions & 0 deletions SukiUI.Demo/Features/Effects/EffectsView.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<UserControl x:Class="SukiUI.Demo.Features.Effects.EffectsView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:effects="clr-namespace:SukiUI.Demo.Features.Effects"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="effects:EffectsViewModel"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,*" RowDefinitions="*,Auto,Auto">
<avaloniaEdit:TextEditor Name="Editor"
ShowLineNumbers="True"
Text="" />
<TextBox Name="ErrorText"
Grid.Row="1"
Grid.Column="0"
IsReadOnly="True"
UseFloatingWatermark="True"
Watermark="Compiler Errors" />
<Button Grid.Row="2"
Grid.Column="0"
Classes="Flat"
Click="Button_OnClick"
Content="Compile and Run" />

<Border Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
Margin="25"
Padding="5"
BorderBrush="{DynamicResource SukiBorderBrush}"
BorderThickness="2.5"
CornerRadius="5">
<effects:ShaderToyRenderer Name="ShaderToyRenderer" />
</Border>
</Grid>

</UserControl>
67 changes: 67 additions & 0 deletions SukiUI.Demo/Features/Effects/EffectsView.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Styling;
using AvaloniaEdit;
using AvaloniaEdit.TextMate;
using SukiUI.Utilities.Effects;
using TextMateSharp.Grammars;

namespace SukiUI.Demo.Features.Effects
{
public partial class EffectsView : UserControl
{
private TextEditor _textEditor;
private ShaderToyRenderer _toyRenderer;
private TextBox _errorText;

public EffectsView()
{
InitializeComponent();
}

protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

_textEditor = this.FindControl<TextEditor>("Editor")!;

_toyRenderer = this.FindControl<ShaderToyRenderer>("ShaderToyRenderer")!;

_errorText = this.FindControl<TextBox>("ErrorText")!;

var effect = SukiEffect.FromEmbeddedResource("shaderart");
_textEditor.Text = effect.ToString();
_toyRenderer.SetEffect(effect);

OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
}

private void OnBaseThemeChanged(ThemeVariant currentTheme)
{
var registryOptions = new RegistryOptions(
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);

var textMateInstallation = _textEditor.InstallTextMate(registryOptions);
textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
.GetLanguageByExtension(".hlsl").Id));
}

private void Button_OnClick(object? sender, RoutedEventArgs e)
{
try
{
_errorText.Text = string.Empty;
var effect = SukiEffect.FromString(_textEditor.Text);
_toyRenderer.SetEffect(effect);
}
catch(Exception ex)
{
_errorText.Text = ex.Message;
}
}
}
}
9 changes: 9 additions & 0 deletions SukiUI.Demo/Features/Effects/EffectsViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Material.Icons;

namespace SukiUI.Demo.Features.Effects
{
public class EffectsViewModel() : DemoPageBase("Effects", MaterialIconKind.MagicWand)
{

}
}
55 changes: 55 additions & 0 deletions SukiUI.Demo/Features/Effects/ShaderToyRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using SkiaSharp;
using SukiUI.Utilities.Effects;

namespace SukiUI.Demo.Features.Effects
{
public class ShaderToyRenderer : Control
{
private readonly ShaderToyDraw _draw;

public ShaderToyRenderer()
{
_draw = new ShaderToyDraw(Bounds);
}

public override void Render(DrawingContext context)
{
_draw.Bounds = Bounds;
context.Custom(_draw);
}

public void SetEffect(SukiEffect effect)
{
_draw.Effect = effect;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}

private class ShaderToyDraw : EffectDrawBase
{
public ShaderToyDraw(Rect bounds) : base(bounds)
{
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 = EffectWithUniforms();
mainShaderPaint.Shader = shader;
canvas.DrawRect(rect, mainShaderPaint);
}
canvas.Restore();
}
}
}
}
33 changes: 33 additions & 0 deletions SukiUI.Demo/Features/Effects/shaderart.sksl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//https://www.shadertoy.com/view/mtyGWy

vec3 palette( float t ) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263,0.416,0.557);

return a + b*cos( 6.28318*(c*t+d) );
}

vec4 main(vec2 fragCoord ) {
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
vec2 uv0 = uv;
vec3 finalColor = vec3(0.0);

for (float i = 0.0; i < 4.0; i++) {
uv = fract(uv * 1.5) - 0.5;

float d = length(uv) * exp(-length(uv0));

vec3 col = palette(length(uv0) + i*.4 + iTime*.4);

d = sin(d*8. + iTime)/8.;
d = abs(d);

d = pow(0.01 / d, 1.2);

finalColor += col * d;
}

return vec4(finalColor, 1.0);
}
8 changes: 8 additions & 0 deletions SukiUI.Demo/SukiUI.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@
<EmbeddedResource Include="Assets\clouds.sksl" />
<EmbeddedResource Include="Assets\weird.sksl" />
</ItemGroup>

<ItemGroup>
<UpToDateCheckInput Remove="Features\ControlsLibrary\Effects\EffectsView.axaml" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Features\Effects\shaderart.sksl" />
</ItemGroup>
</Project>
14 changes: 12 additions & 2 deletions SukiUI/Utilities/Effects/SukiEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,18 @@ public class SukiEffect

private static readonly List<SukiEffect> LoadedEffects = new();

private readonly string _rawShaderString;
private readonly string _shaderString;

/// <summary>
/// The compiled <see cref="SKRuntimeEffect"/> that will actually be used in draw calls.
/// </summary>
public SKRuntimeEffect Effect { get; }

private SukiEffect(string shaderString)
private SukiEffect(string shaderString, string rawShaderString)
{
_shaderString = shaderString;
_rawShaderString = rawShaderString;
var compiledEffect = SKRuntimeEffect.Create(_shaderString, out var errors);
Effect = compiledEffect ?? throw new ShaderCompilationException(errors);
}
Expand Down Expand Up @@ -96,7 +98,7 @@ public static SukiEffect FromString(string shaderString)
sb.AppendLine(uniform);
sb.Append(shaderString);
var withUniforms = sb.ToString();
return new SukiEffect(withUniforms);
return new SukiEffect(withUniforms, shaderString);
}


Expand Down Expand Up @@ -151,6 +153,14 @@ internal SKShader ToShaderWithUniforms(float timeSeconds, ThemeVariant activeVar
(float r, float g, float b) ToFloat(Color col) =>
(col.R / 255f, col.G / 255f, col.B / 255f);
}

/// <summary>
/// Returns the pure shader string without uniforms.
/// </summary>
public override string ToString()
{
return _rawShaderString;
}

private class ShaderCompilationException : Exception
{
Expand Down

0 comments on commit e31a762

Please sign in to comment.