Browse Source

Initial shader node implementation and caching changes

Krzysztof Krysiński 5 months ago
parent
commit
8aaf2e01d0

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 82cec52a169a9a712bab5fb00f9c210afa6b2fc6
+Subproject commit d8709f209277059a0b514a2294d8236411d597b9

+ 10 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CacheTriggerFlags.cs

@@ -0,0 +1,10 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[Flags]
+public enum CacheTriggerFlags
+{
+    None = 0,
+    Inputs = 1,
+    Timeline = 2,
+    All = Inputs | Timeline
+}

+ 18 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -41,6 +41,9 @@ public abstract class Node : IReadOnlyNode, IDisposable
     }
 
     protected virtual bool ExecuteOnlyOnCacheChange => false;
+    protected virtual CacheTriggerFlags CacheTrigger => CacheTriggerFlags.Inputs;
+
+    private KeyFrameTime lastFrameTime;
 
     protected internal bool IsDisposed => _isDisposed;
     private bool _isDisposed;
@@ -71,7 +74,19 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
     protected virtual bool CacheChanged(RenderContext context)
     {
-        return inputs.Any(x => x.CacheChanged);
+        bool changed = false;
+
+        if (CacheTrigger.HasFlag(CacheTriggerFlags.Inputs))
+        {
+            changed |= inputs.Any(x => x.CacheChanged);
+        }
+
+        if (CacheTrigger.HasFlag(CacheTriggerFlags.Timeline))
+        {
+            changed |= lastFrameTime.Frame != context.FrameTime.Frame || Math.Abs(lastFrameTime.NormalizedTime - context.FrameTime.NormalizedTime) > float.Epsilon;
+        }
+
+        return changed;
     }
 
     protected virtual void UpdateCache(RenderContext context)
@@ -80,6 +95,8 @@ public abstract class Node : IReadOnlyNode, IDisposable
         {
             input.UpdateCache();
         }
+
+        lastFrameTime = context.FrameTime;
     }
 
     public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action)

+ 133 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -0,0 +1,133 @@
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Shaders;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[NodeInfo("Shader")]
+public class ShaderNode : RenderNode, IRenderInput
+{
+    public RenderInputProperty Background { get; }
+    public InputProperty<string> ShaderCode { get; }
+
+    private Shader? shader;
+    private Shader? lastImageShader;
+    private string lastErrors;
+    private string lastShaderCode;
+    private Paint paint;
+
+    protected override bool ExecuteOnlyOnCacheChange => true;
+    protected override CacheTriggerFlags CacheTrigger => CacheTriggerFlags.All;
+
+    public ShaderNode()
+    {
+        Background = CreateRenderInput("Background", "BACKGROUND");
+        ShaderCode = CreateInput("ShaderCode", "SHADER_CODE", "");
+        paint = new Paint();
+        Output.FirstInChain = null;
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        base.OnExecute(context);
+
+        if (lastShaderCode != ShaderCode.Value)
+        {
+            Uniforms uniforms = null;
+
+            uniforms = GenerateUniforms(context);
+
+            shader?.Dispose();
+
+            if (uniforms != null)
+            {
+                shader = Shader.CreateFromString(ShaderCode.Value, uniforms, out lastErrors);
+            }
+            else
+            {
+                shader = Shader.CreateFromString(ShaderCode.Value, out lastErrors);
+            }
+
+            lastShaderCode = ShaderCode.Value;
+
+            if (shader == null)
+            {
+                return;
+            }
+        }
+        else if(shader != null)
+        {
+            Uniforms uniforms = GenerateUniforms(context);
+            shader = shader.WithUpdatedUniforms(uniforms);
+        }
+
+        paint.Shader = shader;
+    }
+
+    private Uniforms GenerateUniforms(RenderContext context)
+    {
+        Uniforms uniforms;
+        uniforms = new Uniforms();
+
+        uniforms.Add("iResolution", new Uniform("iResolution", context.DocumentSize));
+        uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
+        uniforms.Add("iFrame", new Uniform("iFrame", context.FrameTime.Frame));
+
+        if(Background.Value == null)
+        {
+            lastImageShader?.Dispose();
+            lastImageShader = null;
+            return uniforms;
+        }
+
+        Texture texture = RequestTexture(50, context.DocumentSize, context.ProcessingColorSpace);
+        Background.Value.Paint(context, texture.DrawingSurface);
+
+        var snapshot = texture.DrawingSurface.Snapshot();
+        lastImageShader?.Dispose();
+        lastImageShader = snapshot.ToShader();
+
+        uniforms.Add("iImage", new Uniform("iImage", lastImageShader));
+
+        snapshot.Dispose();
+        return uniforms;
+    }
+
+    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    {
+        if (shader == null)
+        {
+            surface.Canvas.DrawColor(Colors.Magenta, BlendMode.Src);
+            return;
+        }
+
+        if (Background.Value != null)
+        {
+            int saved = surface.Canvas.SaveLayer(paint);
+            Background.Value.Paint(context, surface);
+            surface.Canvas.RestoreToCount(saved);
+        }
+
+        surface.Canvas.DrawRect(0, 0, context.DocumentSize.X, context.DocumentSize.Y, paint);
+    }
+
+    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    {
+        return null;
+    }
+
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    {
+        return false;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new ShaderNode();
+    }
+}

+ 8 - 5
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -1010,12 +1010,15 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public bool RenderFrames(List<Image> frames, Func<Surface, Surface> processFrameAction = null)
     {
-        if (AnimationDataViewModel.KeyFrames.Count == 0)
-            return false;
-
         var keyFrames = AnimationDataViewModel.KeyFrames;
-        var firstFrame = keyFrames.Min(x => x.StartFrameBindable);
-        var lastFrame = keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable);
+        int firstFrame = 0;
+        int lastFrame = AnimationDataViewModel.FramesCount;
+
+        if (keyFrames.Count > 0)
+        {
+            firstFrame = keyFrames.Min(x => x.StartFrameBindable);
+            lastFrame = keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable);
+        }
 
         for (int i = firstFrame; i < lastFrame; i++)
         {

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/ShaderNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("SHADER_NODE", "EFFECTS", "\uE80B")]
+internal class ShaderNodeViewModel : NodeViewModel<ShaderNode>;