Browse Source

Recursive renderer with opacity support

Equbuxu 3 years ago
parent
commit
4771dab9c8

+ 11 - 0
src/ChunkyImageLib/ChunkyImage.cs

@@ -61,6 +61,17 @@ namespace ChunkyImageLib
             ProcessQueueFinal();
             ProcessQueueFinal();
         }
         }
 
 
+        public HashSet<Vector2i> FindAllChunks()
+        {
+            var allChunks = chunks.Select(chunk => chunk.Key).ToHashSet();
+            allChunks.UnionWith(uncommitedChunks.Select(chunk => chunk.Key).ToHashSet());
+            foreach (var (operation, opChunks) in queuedOperations)
+            {
+                allChunks.UnionWith(opChunks);
+            }
+            return allChunks;
+        }
+
         public HashSet<Vector2i> FindAffectedChunks()
         public HashSet<Vector2i> FindAffectedChunks()
         {
         {
             var chunks = uncommitedChunks.Select(chunk => chunk.Key).ToHashSet();
             var chunks = uncommitedChunks.Select(chunk => chunk.Key).ToHashSet();

+ 1 - 0
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -6,5 +6,6 @@ namespace ChunkyImageLib
     {
     {
         Chunk? GetChunk(Vector2i pos);
         Chunk? GetChunk(Vector2i pos);
         HashSet<Vector2i> FindAffectedChunks();
         HashSet<Vector2i> FindAffectedChunks();
+        HashSet<Vector2i> FindAllChunks();
     }
     }
 }
 }

+ 2 - 2
src/PixiEditorPrototype/Models/ActionAccumulator.cs

@@ -51,13 +51,13 @@ namespace PixiEditorPrototype.Models
                 }
                 }
 
 
                 document.FinalBitmap.Lock();
                 document.FinalBitmap.Lock();
-                var renderResult = await renderer.ProcessChanges(result!, document.FinalBitmapSurface, document.FinalBitmap.PixelWidth, document.FinalBitmap.PixelHeight);
+                var renderResult = await renderer.ProcessChanges(result!, document.FinalBitmapSurface, new(document.FinalBitmap.PixelWidth, document.FinalBitmap.PixelHeight));
 
 
                 foreach (IRenderInfo info in renderResult)
                 foreach (IRenderInfo info in renderResult)
                 {
                 {
                     if (info is DirtyRect_RenderInfo dirtyRect)
                     if (info is DirtyRect_RenderInfo dirtyRect)
                     {
                     {
-                        document.FinalBitmap.AddDirtyRect(new(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
+                        document.FinalBitmap.AddDirtyRect(new(dirtyRect.Pos.X, dirtyRect.Pos.Y, dirtyRect.Size.X, dirtyRect.Size.Y));
                     }
                     }
                 }
                 }
                 document.FinalBitmap.Unlock();
                 document.FinalBitmap.Unlock();

+ 8 - 10
src/StructureRenderer/RenderInfos/DirtyRect_RenderInfo.cs

@@ -1,18 +1,16 @@
-namespace StructureRenderer.RenderInfos
+using ChunkyImageLib.DataHolders;
+
+namespace StructureRenderer.RenderInfos
 {
 {
     public record struct DirtyRect_RenderInfo : IRenderInfo
     public record struct DirtyRect_RenderInfo : IRenderInfo
     {
     {
-        public DirtyRect_RenderInfo(int x, int y, int width, int height)
+        public DirtyRect_RenderInfo(Vector2i pos, Vector2i size)
         {
         {
-            X = x;
-            Y = y;
-            Width = width;
-            Height = height;
+            Pos = pos;
+            Size = size;
         }
         }
 
 
-        public int X { get; }
-        public int Y { get; }
-        public int Width { get; }
-        public int Height { get; }
+        public Vector2i Pos { get; }
+        public Vector2i Size { get; }
     }
     }
 }
 }

+ 106 - 45
src/StructureRenderer/Renderer.cs

@@ -11,7 +11,9 @@ namespace StructureRenderer
     public class Renderer
     public class Renderer
     {
     {
         private DocumentChangeTracker tracker;
         private DocumentChangeTracker tracker;
+        private List<Surface> temporarySurfaces = new();
         private Surface? backSurface;
         private Surface? backSurface;
+        private static SKPaint PaintToDrawChunksWith = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
         private static SKPaint BlendingPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
         private static SKPaint BlendingPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
         private static SKPaint ClearPaint = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
         private static SKPaint ClearPaint = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
         public Renderer(DocumentChangeTracker tracker)
         public Renderer(DocumentChangeTracker tracker)
@@ -19,108 +21,167 @@ namespace StructureRenderer
             this.tracker = tracker;
             this.tracker = tracker;
         }
         }
 
 
-        public async Task<List<IRenderInfo>> ProcessChanges(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, int screenW, int screenH)
+        public async Task<List<IRenderInfo>> ProcessChanges(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, Vector2i screenSize)
         {
         {
-            return await Task.Run(() => Render(changes, screenSurface, screenW, screenH)).ConfigureAwait(true);
+            return await Task.Run(() => Render(changes, screenSurface, screenSize)).ConfigureAwait(true);
         }
         }
 
 
-        private HashSet<Vector2i> FindChunksToRerender(IReadOnlyList<IChangeInfo> changes)
+        private HashSet<Vector2i>? FindChunksToRerender(IReadOnlyList<IChangeInfo> changes)
         {
         {
             HashSet<Vector2i> chunks = new();
             HashSet<Vector2i> chunks = new();
             foreach (var change in changes)
             foreach (var change in changes)
             {
             {
-                if (change is LayerImageChunks_ChangeInfo layerImageChunks)
+                switch (change)
                 {
                 {
-                    if (layerImageChunks.Chunks == null)
-                        throw new Exception("Chunks must not be null");
-                    chunks.UnionWith(layerImageChunks.Chunks);
+                    case LayerImageChunks_ChangeInfo layerImageChunks:
+                        if (layerImageChunks.Chunks == null)
+                            throw new Exception("Chunks must not be null");
+                        chunks.UnionWith(layerImageChunks.Chunks);
+                        break;
+                    case CreateStructureMember_ChangeInfo:
+                    case DeleteStructureMember_ChangeInfo:
+                    case MoveStructureMember_ChangeInfo:
+                        return null;
+                    case StructureMemberOpacity_ChangeInfo opacityChangeInfo:
+                        var memberWithOpacity = tracker.Document.FindMemberOrThrow(opacityChangeInfo.GuidValue);
+                        if (memberWithOpacity is IReadOnlyLayer layerWithOpacity)
+                            chunks.UnionWith(layerWithOpacity.LayerImage.FindAllChunks());
+                        else
+                            return null;
+                        break;
+                    case StructureMemberProperties_ChangeInfo propertiesChangeInfo:
+                        if (!propertiesChangeInfo.IsVisibleChanged)
+                            break;
+                        var memberWithVisibility = tracker.Document.FindMemberOrThrow(propertiesChangeInfo.GuidValue);
+                        if (memberWithVisibility is IReadOnlyLayer layerWithVisibility)
+                            chunks.UnionWith(layerWithVisibility.LayerImage.FindAllChunks());
+                        else
+                            return null;
+                        break;
                 }
                 }
             }
             }
             return chunks;
             return chunks;
         }
         }
 
 
-        private List<IRenderInfo> Render(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, int screenW, int screenH)
+        private List<IRenderInfo> Render(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, Vector2i screenSize)
         {
         {
             bool redrawEverything = false;
             bool redrawEverything = false;
-            if (backSurface == null || backSurface.Width != screenW || backSurface.Height != screenH)
+            if (backSurface == null || backSurface.Width != screenSize.X || backSurface.Height != screenSize.Y)
             {
             {
                 backSurface?.Dispose();
                 backSurface?.Dispose();
-                backSurface = new(screenW, screenH);
+                backSurface = new(screenSize.X, screenSize.Y);
                 redrawEverything = true;
                 redrawEverything = true;
             }
             }
+            HashSet<Vector2i>? chunks = null;
+            if (!redrawEverything)
+                chunks = FindChunksToRerender(changes);
+            if (chunks == null)
+                redrawEverything = true;
+
+            AllocateTempSurfaces(tracker.Document.ReadOnlyStructureRoot);
+
+            List<IRenderInfo> infos = new();
 
 
-            DirtyRect_RenderInfo? info = null;
             // draw to back surface
             // draw to back surface
             if (redrawEverything)
             if (redrawEverything)
             {
             {
-                RenderScreen(screenW, screenH, screenSurface);
-                info = new(0, 0, screenW, screenH);
+                RenderScreen(screenSize, screenSurface, tracker.Document.ReadOnlyStructureRoot);
+                infos.Add(new DirtyRect_RenderInfo(new Vector2i(0, 0), screenSize));
             }
             }
             else
             else
             {
             {
-                HashSet<Vector2i> chunks = FindChunksToRerender(changes);
-                var (minX, minY, maxX, maxY) = (int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
-                foreach (var chunkPos in chunks)
-                {
-                    RenderChunk(chunkPos, screenSurface);
-                    (minX, minY) = (Math.Min(chunkPos.X, minX), Math.Min(chunkPos.Y, minY));
-                    (maxX, maxY) = (Math.Max(chunkPos.X, maxX), Math.Max(chunkPos.Y, maxY));
-                }
-                if (minX != int.MaxValue)
+                foreach (var chunkPos in chunks!)
                 {
                 {
-                    info = new(
-                        minX * ChunkyImage.ChunkSize,
-                        minY * ChunkyImage.ChunkSize,
-                        (maxX - minX + 1) * ChunkyImage.ChunkSize,
-                        (maxY - minY + 1) * ChunkyImage.ChunkSize);
+                    screenSurface.Canvas.DrawRect(SKRect.Create(chunkPos * ChunkyImage.ChunkSize, new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)), ClearPaint);
+                    var renderedSurface = RenderChunkRecursively(chunkPos, 0, tracker.Document.ReadOnlyStructureRoot);
+                    if (renderedSurface != null)
+                        screenSurface.Canvas.DrawSurface(renderedSurface.SkiaSurface, chunkPos * ChunkyImage.ChunkSize, BlendingPaint);
+                    infos.Add(new DirtyRect_RenderInfo(
+                        chunkPos * ChunkyImage.ChunkSize,
+                        new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)
+                        ));
                 }
                 }
             }
             }
 
 
-            // transfer back surface to screen surface
+            // transfer the back surface to the screen surface
             screenSurface.Canvas.DrawSurface(backSurface.SkiaSurface, 0, 0);
             screenSurface.Canvas.DrawSurface(backSurface.SkiaSurface, 0, 0);
 
 
-            return info == null ? new() : new() { info };
+            return infos;
         }
         }
 
 
-        private void RenderScreen(int screenW, int screenH, SKSurface screenSurface)
+        private void RenderScreen(Vector2i screenSize, SKSurface screenSurface, IReadOnlyFolder structureRoot)
         {
         {
-            int chunksWidth = (int)Math.Ceiling(screenW / (float)ChunkyImage.ChunkSize);
-            int chunksHeight = (int)Math.Ceiling(screenH / (float)ChunkyImage.ChunkSize);
+            int chunksWidth = (int)Math.Ceiling(screenSize.X / (float)ChunkyImage.ChunkSize);
+            int chunksHeight = (int)Math.Ceiling(screenSize.Y / (float)ChunkyImage.ChunkSize);
+            screenSurface.Canvas.Clear();
             for (int x = 0; x < chunksWidth; x++)
             for (int x = 0; x < chunksWidth; x++)
             {
             {
                 for (int y = 0; y < chunksHeight; y++)
                 for (int y = 0; y < chunksHeight; y++)
                 {
                 {
-                    RenderChunk(new(x, y), screenSurface);
+                    var renderedSurface = RenderChunkRecursively(new(x, y), 0, structureRoot);
+                    if (renderedSurface != null)
+                        screenSurface.Canvas.DrawSurface(renderedSurface.SkiaSurface, x * ChunkyImage.ChunkSize, y * ChunkyImage.ChunkSize, BlendingPaint);
                 }
                 }
             }
             }
         }
         }
 
 
-        private void RenderChunk(Vector2i chunkPos, SKSurface screenSurface)
+        private void AllocateTempSurfaces(IReadOnlyFolder structureRoot)
+        {
+            int depth = FindDeepestLayerDepth(structureRoot, 0);
+            while (temporarySurfaces.Count < depth)
+            {
+                temporarySurfaces.Add(new Surface(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize));
+            }
+        }
+
+        private int FindDeepestLayerDepth(IReadOnlyFolder folder, int folderDepth)
         {
         {
-            screenSurface.Canvas.DrawRect(SKRect.Create(chunkPos * ChunkyImage.ChunkSize, new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)), ClearPaint);
-            ForEachLayer((layer) =>
+            int deepestLayer = -1;
+            foreach (var child in folder.ReadOnlyChildren)
             {
             {
-                var chunk = layer.LayerImage.GetChunk(chunkPos);
-                if (chunk == null)
-                    return;
-                using var snapshot = chunk.Snapshot();
-                screenSurface.Canvas.DrawImage(snapshot, chunkPos * ChunkyImage.ChunkSize, BlendingPaint);
-            }, tracker.Document.ReadOnlyStructureRoot);
+                if (child is IReadOnlyLayer layer)
+                {
+                    deepestLayer = folderDepth + 1;
+                }
+                else if (child is IReadOnlyFolder innerFolder)
+                {
+                    deepestLayer = FindDeepestLayerDepth(innerFolder, folderDepth + 1);
+                }
+            }
+            return deepestLayer;
         }
         }
 
 
-        private void ForEachLayer(Action<IReadOnlyLayer> action, IReadOnlyFolder folder)
+        private Surface? RenderChunkRecursively(Vector2i chunkPos, int depth, IReadOnlyFolder folder)
         {
         {
+            Surface? surface = temporarySurfaces.Count > depth ? temporarySurfaces[depth] : null;
+            surface?.SkiaSurface.Canvas.Clear();
             foreach (var child in folder.ReadOnlyChildren)
             foreach (var child in folder.ReadOnlyChildren)
             {
             {
+                if (!child.IsVisible)
+                    continue;
                 if (child is IReadOnlyLayer layer)
                 if (child is IReadOnlyLayer layer)
                 {
                 {
-                    action(layer);
+                    var chunk = layer.LayerImage.GetChunk(chunkPos);
+                    if (chunk == null)
+                        continue;
+                    if (surface == null)
+                        throw new Exception("Not enough surfaces have been allocated to draw the entire layer tree");
+                    using var snapshot = chunk.Snapshot();
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                    surface.SkiaSurface.Canvas.DrawImage(snapshot, 0, 0, PaintToDrawChunksWith);
                 }
                 }
                 else if (child is IReadOnlyFolder innerFolder)
                 else if (child is IReadOnlyFolder innerFolder)
                 {
                 {
-                    ForEachLayer(action, innerFolder);
+                    var renderedSurface = RenderChunkRecursively(chunkPos, depth + 1, innerFolder);
+                    if (renderedSurface == null)
+                        continue;
+                    if (surface == null)
+                        throw new Exception("Not enough surfaces have been allocated to draw the entire layer tree");
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                    surface.SkiaSurface.Canvas.DrawSurface(renderedSurface.SkiaSurface, 0, 0, PaintToDrawChunksWith);
                 }
                 }
             }
             }
+            return surface;
         }
         }
     }
     }
 }
 }