Quellcode durchsuchen

failed attempt at making backend skia gpu backed

flabbet vor 1 Jahr
Ursprung
Commit
0654046b57

+ 90 - 41
src/ChunkyImageLib/ChunkyImage.cs

@@ -62,7 +62,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
     private static Paint InverseClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstOut };
     private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
-    private static Paint SmoothReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
+
+    private static Paint SmoothReplacingPaint { get; } =
+        new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
+
     private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
     private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
 
@@ -130,6 +133,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                 rect ??= chunkBounds;
                 rect = rect.Value.Union(chunkBounds);
             }
+
             foreach (var operation in queuedOperations)
             {
                 foreach (var pos in operation.affectedArea.Chunks)
@@ -139,6 +143,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                     rect = rect.Value.Union(chunkBounds);
                 }
             }
+
             return rect;
         }
     }
@@ -156,6 +161,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                 rect ??= chunkBounds;
                 rect = rect.Value.Union(chunkBounds);
             }
+
             return rect;
         }
     }
@@ -179,7 +185,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             {
                 if (committedChunks[suggestedResolution].TryGetValue(chunkPos, out Chunk? requestedResChunk))
                 {
-                    RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize)).Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
+                    RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize))
+                        .Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
 
                     RectI? chunkPreciseBounds = requestedResChunk.FindPreciseBounds(visibleArea);
                     if (chunkPreciseBounds is null)
@@ -191,17 +198,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                 }
                 else
                 {
-                    RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize)).Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
+                    RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize))
+                        .Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
 
                     RectI? chunkPreciseBounds = fullResChunk.FindPreciseBounds(visibleArea);
                     if (chunkPreciseBounds is null)
                         continue;
-                    RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier).Offset(chunkPos * chunkSize).RoundOutwards();
+                    RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier)
+                        .Offset(chunkPos * chunkSize).RoundOutwards();
 
                     preciseBounds ??= globalChunkBounds;
                     preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
                 }
             }
+
             preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
             preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
 
@@ -281,12 +291,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             {
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
-                Color committedColor = committedChunk is null ?
-                    Colors.Transparent :
-                    committedChunk.Surface.GetSRGBPixel(posInChunk);
-                Color latestColor = latestChunk is null ?
-                    Colors.Transparent :
-                    latestChunk.Surface.GetSRGBPixel(posInChunk);
+                Color committedColor = committedChunk is null
+                    ? Colors.Transparent
+                    : committedChunk.Surface.GetSRGBPixel(posInChunk);
+                Color latestColor = latestChunk is null
+                    ? Colors.Transparent
+                    : latestChunk.Surface.GetSRGBPixel(posInChunk);
                 // using a whole chunk just to draw 1 pixel is kinda dumb,
                 // but this should be faster than any approach that requires allocations
                 using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
@@ -303,7 +313,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     /// True if the chunk existed and was drawn, otherwise false
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -346,9 +357,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
 
             // combine with committed and then draw
             using var tempChunk = Chunk.Create(resolution);
-            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0, ReplacingPaint);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
+                ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
-            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0, blendModePaint);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0,
+                blendModePaint);
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);
@@ -377,7 +390,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -442,7 +456,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             activeClips.Add(clippingMask);
         }
     }
@@ -454,7 +469,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             this.clippingPath = clippingPath;
         }
     }
@@ -469,7 +485,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             blendMode = mode;
         }
     }
@@ -481,7 +498,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             horizontalSymmetryAxis = position;
         }
     }
@@ -493,7 +511,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be executed when there are no queued operations");
             verticalSymmetryAxis = position;
         }
     }
@@ -531,7 +550,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawEllipse(RectI location, Color strokeColor, Color fillColor, int strokeWidth, Paint? paint = null)
+    public void EnqueueDrawEllipse(RectI location, Color strokeColor, Color fillColor, int strokeWidth,
+        Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -585,7 +605,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             EnqueueOperation(operation);
         }
     }
-    
+
     public void EnqueueApplyMask(ChunkyImage mask)
     {
         lock (lockObject)
@@ -598,7 +618,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
 
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap, BlendMode blendMode, RectI? customBounds = null)
+    public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap,
+        BlendMode blendMode, RectI? customBounds = null)
     {
         lock (lockObject)
         {
@@ -620,7 +641,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawSkiaLine(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color, BlendMode blendMode)
+    public void EnqueueDrawSkiaLine(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color,
+        BlendMode blendMode)
     {
         lock (lockObject)
         {
@@ -663,6 +685,16 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         }
     }
 
+    public void EnqueueDrawShader(Shader shader)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            ShaderOperation operation = new(shader);
+            EnqueueOperation(operation);
+        }
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueDrawCommitedChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
     {
@@ -673,13 +705,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             EnqueueOperation(operation);
         }
     }
+
     public void EnqueueDrawUpToDateChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
     {
         ThrowIfDisposed();
         ChunkyImageOperation operation = new(image, pos, flipHor, flipVer, true);
         EnqueueOperation(operation);
     }
-    
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueClearRegion(RectI region)
     {
@@ -842,7 +875,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                 {
                     if (resolution == ChunkResolution.Full)
                     {
-                        throw new InvalidOperationException("Trying to commit a full res chunk that wasn't fully processed");
+                        throw new InvalidOperationException(
+                            "Trying to commit a full res chunk that wasn't fully processed");
                     }
                     else
                     {
@@ -891,13 +925,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
                     if (lockTransparency)
                     {
                         using Chunk tempChunk = Chunk.Create(resolution);
-                        tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0, ReplacingPaint);
-                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
-                        OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface, tempChunk.Surface.DrawingSurface);
+                        tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0,
+                            ReplacingPaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
+                            blendModePaint);
+                        OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface,
+                            tempChunk.Surface.DrawingSurface);
                     }
                     else
                     {
-                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
+                            blendModePaint);
                     }
 
                     chunk.Dispose();
@@ -912,7 +950,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             {
                 if (resolution == ChunkResolution.Full)
                     continue;
-                if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) || halfChunk.QueueProgress != queuedOperations.Count)
+                if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) ||
+                    halfChunk.QueueProgress != queuedOperations.Count)
                 {
                     if (committedChunks[resolution].TryGetValue(pos, out var committedLowResChunk))
                     {
@@ -971,7 +1010,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             ThrowIfDisposed();
             var chunks = new HashSet<VecI>();
             RectI? rect = null;
-            
+
             for (int i = fromOperationIndex; i < queuedOperations.Count; i++)
             {
                 var (_, area) = queuedOperations[i];
@@ -992,7 +1031,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     private void MaybeCreateAndProcessQueueForChunk(VecI chunkPos, ChunkResolution resolution)
     {
         if (!latestChunksData[resolution].TryGetValue(chunkPos, out LatestChunkData chunkData))
-            chunkData = new() { QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos) };
+            chunkData = new()
+            {
+                QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos)
+            };
         if (chunkData.QueueProgress == queuedOperations.Count)
             return;
 
@@ -1015,12 +1057,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             }
 
             if (chunkData.QueueProgress <= i)
-                chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!, chunkPos, resolution, chunkData);
+                chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!,
+                    chunkPos, resolution, chunkData);
         }
 
         if (initialized)
         {
-            if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
+            if (lockTransparency && !chunkData.IsDeleted &&
+                MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
             {
                 var committed = GetCommittedChunk(chunkPos, resolution);
                 OperationHelper.ClampAlpha(targetChunk!.Surface.DrawingSurface, committed!.Surface.DrawingSurface);
@@ -1053,7 +1097,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             if (mask.CommittedChunkExists(chunkPos))
             {
-                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
+                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero,
+                    ClippingPaint);
             }
             else
             {
@@ -1118,7 +1163,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         return chunkData.IsDeleted;
     }
 
-    private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk, ChunkResolution resolution, VecI chunkPos)
+    private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk,
+        ChunkResolution resolution, VecI chunkPos)
     {
         if (operationAffectedArea is null)
             return;
@@ -1130,7 +1176,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             using VectorPath transformedPath = new(clippingPath);
             VecD trans = -chunkPos * FullChunkSize * scale;
-            
+
             transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
             targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
         }
@@ -1156,7 +1202,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         {
             ThrowIfDisposed();
             if (queuedOperations.Count > 0)
-                throw new InvalidOperationException("This function can only be used when there are no queued operations");
+                throw new InvalidOperationException(
+                    "This function can only be used when there are no queued operations");
             FindAndDeleteEmptyCommittedChunks();
             return committedChunks[ChunkResolution.Full].Count == 0;
         }
@@ -1171,7 +1218,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
 
     private static bool IsOutsideBounds(VecI chunkPos, VecI imageSize)
     {
-        return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X || chunkPos.Y * FullChunkSize >= imageSize.Y;
+        return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X ||
+               chunkPos.Y * FullChunkSize >= imageSize.Y;
     }
 
     private void FindAndDeleteEmptyCommittedChunks()
@@ -1223,7 +1271,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             newChunk.Surface.DrawingSurface.Canvas.Save();
             newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
 
-            newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0, SmoothReplacingPaint);
+            newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0,
+                SmoothReplacingPaint);
             newChunk.Surface.DrawingSurface.Canvas.Restore();
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
@@ -1329,5 +1378,5 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
             ChunkyImage clone = CloneFromCommitted();
             return clone;
         }
-    } 
+    }
 }

+ 34 - 0
src/ChunkyImageLib/Operations/ShaderOperation.cs

@@ -0,0 +1,34 @@
+using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.Numerics;
+
+namespace ChunkyImageLib.Operations;
+
+public class ShaderOperation : IDrawOperation
+{
+    public bool IgnoreEmptyChunks { get; } = false;
+    public Shader Shader { get; }
+    
+    
+    public ShaderOperation(Shader shader)
+    {
+        Shader = shader;
+    }
+
+    public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
+    {
+        using Paint paint = new Paint();
+        paint.Shader = Shader;
+        targetChunk.Surface.DrawingSurface.Canvas.DrawPaint(paint);
+    }
+
+    public AffectedArea FindAffectedArea(VecI imageSize)
+    {
+        return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(new RectI(VecI.Zero, imageSize), ChunkyImage.FullChunkSize));
+    }
+
+    public void Dispose()
+    {
+    }
+}

+ 1 - 0
src/ChunkyImageLib/Surface.cs

@@ -137,6 +137,7 @@ public class Surface : IDisposable, ICloneable
     {
         if (other.Size != Size)
             throw new ArgumentException("Target Surface must have the same dimensions");
+        DrawingSurface.Canvas.Flush();
         int bytesC = Size.X * Size.Y * BytesPerPixel;
         using var pixmap = other.DrawingSurface.PeekPixels();
         Buffer.MemoryCopy((void*)PixelBuffer, (void*)pixmap.GetPixels(), bytesC, bytesC);

+ 1 - 1
src/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		    <AvaloniaVersion>11.0.11</AvaloniaVersion>
+		    <AvaloniaVersion>[11.0.11]</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />

+ 5 - 0
src/PixiEditor.AvaloniaUI.Desktop/Program.cs

@@ -17,5 +17,10 @@ public class Program
     public static AppBuilder BuildAvaloniaApp()
         => AppBuilder.Configure<App>()
             .UsePlatformDetect()
+            .With(new Win32PlatformOptions()
+            {
+                RenderingMode = new [] { Win32RenderingMode.Wgl },
+                OverlayPopups = true
+            })
             .LogToTrace();
 }

+ 27 - 22
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -19,6 +19,8 @@
     <ImplicitUsings>true</ImplicitUsings>
     <LangVersion>latest</LangVersion>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+    <AvaloniaAccessUnstablePrivateApis>true</AvaloniaAccessUnstablePrivateApis>
+    <Avalonia_I_Want_To_Use_Private_Apis_In_Nuget_Package_And_Promise_To_Pin_The_Exact_Avalonia_Version_In_Package_Dependency>true</Avalonia_I_Want_To_Use_Private_Apis_In_Nuget_Package_And_Promise_To_Pin_The_Exact_Avalonia_Version_In_Package_Dependency>
   </PropertyGroup>
 
   <ItemGroup Condition="'$(RuntimeIdentifier)'=='win-x64'">
@@ -76,12 +78,15 @@
     <PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.1"/>
     <PackageReference Include="PixiEditor.ColorPicker.AvaloniaUI" Version="1.0.5"/>
   </ItemGroup>
+  
+  <PropertyGroup>
+  </PropertyGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\..\..\PixiDocks\src\PixiDocks.Avalonia\PixiDocks.Avalonia.csproj"/>
     <ProjectReference Include="..\ChunkyImageLib\ChunkyImageLib.csproj"/>
-    <ProjectReference Include="..\PixiEditor.AnimationRenderer.Core\PixiEditor.AnimationRenderer.Core.csproj" />
-    <ProjectReference Include="..\PixiEditor.AnimationRenderer.FFmpeg\PixiEditor.AnimationRenderer.FFmpeg.csproj" />
+    <ProjectReference Include="..\PixiEditor.AnimationRenderer.Core\PixiEditor.AnimationRenderer.Core.csproj"/>
+    <ProjectReference Include="..\PixiEditor.AnimationRenderer.FFmpeg\PixiEditor.AnimationRenderer.FFmpeg.csproj"/>
     <ProjectReference Include="..\PixiEditor.ChangeableDocument.Gen\PixiEditor.ChangeableDocument.Gen.csproj"/>
     <ProjectReference Include="..\PixiEditor.ChangeableDocument\PixiEditor.ChangeableDocument.csproj"/>
     <ProjectReference Include="..\PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj"/>
@@ -119,26 +124,26 @@
   </ItemGroup>
 
   <ItemGroup>
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Copy.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Cut.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Paste.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\PasteAsNewLayer.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\PasteReferenceLayer.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\CenterContent.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\ResizeCanvas.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\ResizeDocument.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate180Deg.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate180DegLayers.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate270Deg.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate270DegLayers.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate90Deg.png" />
-    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate90DegLayers.png" />
-    <UpToDateCheckInput Remove="Images\News\Article.png" />
-    <UpToDateCheckInput Remove="Images\News\Misc.png" />
-    <UpToDateCheckInput Remove="Images\News\NewVersion.png" />
-    <UpToDateCheckInput Remove="Images\News\OfficialAnnouncement.png" />
-    <UpToDateCheckInput Remove="Images\News\YouTube.png" />
-    <UpToDateCheckInput Remove="Styles\Templates\NodeProperties\ImageNodePropertyView.axaml" />
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Copy.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Cut.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\Paste.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\PasteAsNewLayer.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Clipboard\PasteReferenceLayer.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\CenterContent.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\ResizeCanvas.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\ResizeDocument.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate180Deg.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate180DegLayers.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate270Deg.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate270DegLayers.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate90Deg.png"/>
+    <UpToDateCheckInput Remove="Images\Commands\PixiEditor\Document\Rotate90DegLayers.png"/>
+    <UpToDateCheckInput Remove="Images\News\Article.png"/>
+    <UpToDateCheckInput Remove="Images\News\Misc.png"/>
+    <UpToDateCheckInput Remove="Images\News\NewVersion.png"/>
+    <UpToDateCheckInput Remove="Images\News\OfficialAnnouncement.png"/>
+    <UpToDateCheckInput Remove="Images\News\YouTube.png"/>
+    <UpToDateCheckInput Remove="Styles\Templates\NodeProperties\ImageNodePropertyView.axaml"/>
   </ItemGroup>
 
   <ItemGroup>

+ 30 - 1
src/PixiEditor.AvaloniaUI/Views/MainWindow.axaml.cs

@@ -4,7 +4,11 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Interactivity;
+using Avalonia.OpenGL;
+using Avalonia.Rendering.Composition;
+using Avalonia.Skia;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Initialization;
@@ -52,8 +56,10 @@ internal partial class MainWindow : Window
             .BuildServiceProvider();
 
         AsyncImageLoader.ImageLoader.AsyncImageLoader = new DiskCachedWebImageLoader();
+        
+        GRContext recordingContext = GetGrRecordingContext();
 
-        SkiaDrawingBackend skiaDrawingBackend = new SkiaDrawingBackend();
+        SkiaDrawingBackend skiaDrawingBackend = new SkiaDrawingBackend(recordingContext);
         DrawingBackendApi.SetupBackend(skiaDrawingBackend);
 
         preferences = services.GetRequiredService<IPreferences>();
@@ -64,6 +70,29 @@ internal partial class MainWindow : Window
         InitializeComponent();
     }
 
+    private static GRContext GetGrRecordingContext()
+    {
+        Compositor compositor = Compositor.TryGetDefaultCompositor();
+        var interop = compositor.TryGetCompositionGpuInterop();
+        var contextSharingFeature =
+            compositor.TryGetRenderInterfaceFeature(typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)).Result as IOpenGlTextureSharingRenderInterfaceContextFeature;
+
+        if (contextSharingFeature.CanCreateSharedContext)
+        {
+
+            IGlContext? glContext = contextSharingFeature.CreateSharedContext();
+            glContext.MakeCurrent();
+            return GRContext.CreateGl(GRGlInterface.Create(glContext.GlInterface.GetProcAddress));
+        }
+
+        var contextFactory = AvaloniaLocator.Current.GetRequiredService<IPlatformGraphicsOpenGlContextFactory>();
+        var ctx = contextFactory.CreateContext(null);
+        ctx.MakeCurrent();
+        var ctxInterface = GRGlInterface.Create(ctx.GlInterface.GetProcAddress);
+        var grContext = GRContext.CreateGl(ctxInterface);
+        return grContext;
+    }
+
     public static MainWindow CreateWithRecoveredDocuments(CrashReport report, out bool showMissingFilesDialog)
     {
         var window = GetMainWindow();

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

@@ -0,0 +1,60 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class ShaderNode : Node
+{
+    public InputProperty<ChunkyImage?> Input { get; set; }
+    public InputProperty<string> Shader { get; set; }
+    public OutputProperty<ChunkyImage?> Output { get; set; }
+    
+    private string _shaderCode = """
+                                 half4 main(float2 coord) {
+                                   float t = coord.x / 128;
+                                   half4 white = half4(1);
+                                   half4 black = half4(0,0,0,1);
+                                   return mix(white, black, t);
+                                 }
+                                 """;
+    
+    public ShaderNode()
+    {
+        Input = CreateInput<ChunkyImage?>("Input", "INPUT", null);
+        Shader = CreateInput<string>("Shader", "SHADER", "");
+        Output = CreateOutput<ChunkyImage?>("Output", "OUTPUT", null);
+    }
+    
+    protected override ChunkyImage? OnExecute(KeyFrameTime frameTime)
+    {
+        if (Input.Value == null)
+        {
+            return null;
+        }
+
+        var shader =
+            PixiEditor.DrawingApi.Core.Surface.PaintImpl.Shader.CreateFromSksl(_shaderCode, false, out string errors);
+        if (shader == null)
+        {
+            Console.WriteLine(errors);
+            return null;
+        }
+
+        ChunkyImage output = Input.Value.CloneFromCommitted();
+        output.EnqueueDrawShader(shader);
+        output.CommitChanges();
+        
+        Output.Value = output;
+        return output;
+    }
+
+    public override bool Validate()
+    {
+        return true;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new ShaderNode();
+    }
+}

+ 2 - 2
src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs

@@ -28,12 +28,12 @@ public static class DocumentEvaluator
             {
                 chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
-
+            
             evaluated.DrawMostUpToDateChunkOn(chunkPos, resolution, chunk.Surface.DrawingSurface, VecI.Zero,
                 context.ReplacingPaintWithOpacity);
 
             chunk.Surface.DrawingSurface.Canvas.Restore();
-
+            
             return chunk;
         }
         catch (ObjectDisposedException)

+ 2 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorFilterImplementation.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Concurrent;
 using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -28,5 +29,6 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             return ManagedInstances[objectPointer];
         }
+
     }
 }

+ 28 - 6
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs

@@ -13,17 +13,24 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         private readonly SkiaPixmapImplementation _pixmapImplementation;
         private readonly SkiaCanvasImplementation _canvasImplementation;
         private readonly SkiaPaintImplementation _paintImplementation;
+        private GRContext grContext;
 
-        public SkiaSurfaceImplementation(SkiaPixmapImplementation pixmapImplementation, SkiaCanvasImplementation canvasImplementation, SkiaPaintImplementation paintImplementation)
+        public SkiaSurfaceImplementation(GRContext context, SkiaPixmapImplementation pixmapImplementation, SkiaCanvasImplementation canvasImplementation, SkiaPaintImplementation paintImplementation)
         {
             _pixmapImplementation = pixmapImplementation;
             _canvasImplementation = canvasImplementation;
             _paintImplementation = paintImplementation;
+            grContext = context;
         }
         
         public Pixmap PeekPixels(DrawingSurface drawingSurface)
         {
             SKPixmap pixmap = ManagedInstances[drawingSurface.ObjectPointer].PeekPixels();
+            if (pixmap == null)
+            {
+                return null;
+            }
+            
             return _pixmapImplementation.CreateFrom(pixmap);
         }
 
@@ -38,31 +45,45 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             SKCanvas canvas = _canvasImplementation[surfaceToDraw.ObjectPointer];
             SKPaint paint = _paintImplementation[drawingPaint.ObjectPointer];
-            ManagedInstances[drawingSurface.ObjectPointer].Draw(canvas, x, y, paint);
+            var instance = ManagedInstances[drawingSurface.ObjectPointer];
+            instance.Draw(canvas, x, y, paint);
         }
         
         public DrawingSurface Create(ImageInfo imageInfo, IntPtr pixels, int rowBytes)
         {
-            SKSurface skSurface = SKSurface.Create(imageInfo.ToSkImageInfo(), pixels, rowBytes);
+            SKSurface skSurface = SKSurface.Create(grContext, false, imageInfo.ToSkImageInfo());
+            
+            var canvas = skSurface.Canvas;
+            canvas.DrawImage(SKImage.FromPixelCopy(imageInfo.ToSkImageInfo(), pixels, rowBytes), new SKPoint(0, 0));
+            
             return CreateDrawingSurface(skSurface);
         }
 
         public DrawingSurface Create(ImageInfo imageInfo, IntPtr pixelBuffer)
         {
-            SKSurface skSurface = SKSurface.Create(imageInfo.ToSkImageInfo(), pixelBuffer);
+            SKSurface skSurface = SKSurface.Create(grContext, false, imageInfo.ToSkImageInfo());
+            
+            var canvas = skSurface.Canvas;
+            canvas.DrawImage(SKImage.FromPixelCopy(imageInfo.ToSkImageInfo(), pixelBuffer), new SKPoint(0, 0));
+            
             return CreateDrawingSurface(skSurface);
         }
 
         public DrawingSurface Create(Pixmap pixmap)
         {
             SKPixmap skPixmap = _pixmapImplementation[pixmap.ObjectPointer];
-            SKSurface skSurface = SKSurface.Create(skPixmap);
+            SKImageInfo info = skPixmap.Info;
+            SKSurface skSurface = SKSurface.Create(grContext, false, info);
+            
+            var canvas = skSurface.Canvas;
+            canvas.DrawImage(SKImage.FromPixels(skPixmap), new SKPoint(0, 0));
+            
             return CreateDrawingSurface(skSurface);
         }
 
         public DrawingSurface Create(ImageInfo imageInfo)
         {
-            SKSurface skSurface = SKSurface.Create(imageInfo.ToSkImageInfo());
+            SKSurface skSurface = SKSurface.Create(grContext, false, imageInfo.ToSkImageInfo());
             return CreateDrawingSurface(skSurface);
         }
 
@@ -84,6 +105,7 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 
             DrawingSurface surface = new DrawingSurface(skSurface.Handle, canvas);
             ManagedInstances[skSurface.Handle] = skSurface;
+            
             return surface;
         }
     }

+ 8 - 3
src/PixiEditor.DrawingApi.Skia/SkiaDrawingBackend.cs

@@ -2,6 +2,7 @@
 using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
 using PixiEditor.DrawingApi.Core.Bridge.Operations;
 using PixiEditor.DrawingApi.Skia.Implementations;
+using SkiaSharp;
 
 namespace PixiEditor.DrawingApi.Skia
 {
@@ -19,8 +20,9 @@ namespace PixiEditor.DrawingApi.Skia
         public IColorSpaceImplementation ColorSpaceImplementation { get; }
         public IBitmapImplementation BitmapImplementation { get; }
         public IColorFilterImplementation ColorFilterImplementation { get; set; }
+        public IShaderImplementation ShaderImplementation { get; set; }
 
-        public SkiaDrawingBackend()
+        public SkiaDrawingBackend(GRContext graphicsContext)
         {
             ColorImplementation = new SkiaColorImplementation();
             
@@ -33,7 +35,10 @@ namespace PixiEditor.DrawingApi.Skia
             SkiaColorFilterImplementation colorFilterImpl = new SkiaColorFilterImplementation();
             ColorFilterImplementation = colorFilterImpl;
             
-            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation(colorFilterImpl);
+            SkiaShaderImplementation shaderImpl = new SkiaShaderImplementation();
+            ShaderImplementation = shaderImpl;
+            
+            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation(colorFilterImpl, shaderImpl);
             PaintImplementation = paintImpl;
             
             SkiaPathImplementation pathImpl = new SkiaPathImplementation();
@@ -52,7 +57,7 @@ namespace PixiEditor.DrawingApi.Skia
             
             SkiaCanvasImplementation canvasImpl = new SkiaCanvasImplementation(paintImpl, imgImpl, bitmapImpl, pathImpl);
             
-            var surfaceImpl = new SkiaSurfaceImplementation(pixmapImpl, canvasImpl, paintImpl);
+            var surfaceImpl = new SkiaSurfaceImplementation(graphicsContext, pixmapImpl, canvasImpl, paintImpl);
 
             canvasImpl.SetSurfaceImplementation(surfaceImpl);
             imgImpl.SetSurfaceImplementation(surfaceImpl);

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll