Browse Source

Move a part of the renderer into ChangeableDocument

Equbuxu 3 years ago
parent
commit
bc58fed464

+ 1 - 0
src/ChangeableDocument/Changeables/Document.cs

@@ -16,6 +16,7 @@ namespace ChangeableDocument.Changeables
         internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
         internal Selection Selection { get; } = new();
         public Vector2i Size { get; set; } = DefaultSize;
+
         public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new Exception("Could not find member with guid " + guid.ToString());
         public StructureMember? FindMember(Guid guid)
         {

+ 47 - 0
src/ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -0,0 +1,47 @@
+using ChangeableDocument.Changeables.Interfaces;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChangeableDocument.Rendering
+{
+    public static class ChunkRenderer
+    {
+        private static SKPaint PaintToDrawChunksWith = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        public static Chunk RenderWholeStructure(Vector2i pos, IReadOnlyFolder root)
+        {
+            return RenderChunkRecursively(pos, 0, root, null);
+        }
+
+        public static Chunk RenderSpecificLayers(Vector2i pos, IReadOnlyFolder root, HashSet<Guid> layers)
+        {
+            return RenderChunkRecursively(pos, 0, root, layers);
+        }
+
+        private static Chunk RenderChunkRecursively(Vector2i chunkPos, int depth, IReadOnlyFolder folder, HashSet<Guid>? visibleLayers)
+        {
+            Chunk targetChunk = Chunk.Create();
+            targetChunk.Surface.SkiaSurface.Canvas.Clear();
+            foreach (var child in folder.ReadOnlyChildren)
+            {
+                if (!child.IsVisible)
+                    continue;
+                if (child is IReadOnlyLayer layer && (visibleLayers == null || visibleLayers.Contains(layer.GuidValue)))
+                {
+                    IReadOnlyChunk? chunk = layer.ReadOnlyLayerImage.GetLatestChunk(chunkPos);
+                    if (chunk == null)
+                        continue;
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                    chunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
+                }
+                else if (child is IReadOnlyFolder innerFolder)
+                {
+                    using Chunk renderedChunk = RenderChunkRecursively(chunkPos, depth + 1, innerFolder, visibleLayers);
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                    renderedChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
+                }
+            }
+            return targetChunk;
+        }
+    }
+}

+ 2 - 2
src/ChunkyImageLib/Chunk.cs

@@ -3,10 +3,10 @@ using SkiaSharp;
 
 namespace ChunkyImageLib
 {
-    public class Chunk : IDisposable
+    public class Chunk : IDisposable, IReadOnlyChunk
     {
         private bool returned = false;
-        internal Surface Surface { get; }
+        public Surface Surface { get; }
         public Vector2i PixelSize { get; }
         public ChunkResolution Resolution { get; }
         private Chunk(ChunkResolution resolution)

+ 6 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -41,7 +41,7 @@ namespace ChunkyImageLib
             var chunks = FindAllChunks();
             foreach (var chunk in chunks)
             {
-                var image = GetLatestChunk(chunk);
+                var image = (Chunk?)GetLatestChunk(chunk);
                 if (image != null)
                     output.DrawImage(chunk * ChunkSize, image.Surface);
             }
@@ -52,7 +52,7 @@ namespace ChunkyImageLib
         /// <summary>
         /// Returns the latest version of the chunk, with uncommitted changes applied if they exist
         /// </summary>
-        public Chunk? GetLatestChunk(Vector2i pos)
+        public IReadOnlyChunk? GetLatestChunk(Vector2i pos)
         {
             if (queuedOperations.Count == 0)
                 return MaybeGetChunk(pos, committedChunks);
@@ -63,7 +63,7 @@ namespace ChunkyImageLib
         /// <summary>
         /// Returns the committed version of the chunk ignoring any uncommitted changes
         /// </summary>
-        internal Chunk? GetCommittedChunk(Vector2i pos)
+        internal IReadOnlyChunk? GetCommittedChunk(Vector2i pos)
         {
             return MaybeGetChunk(pos, committedChunks);
         }
@@ -210,7 +210,7 @@ namespace ChunkyImageLib
             if (chunkData.QueueProgress == queuedOperations.Count)
                 return;
 
-            List<Chunk> activeClips = new();
+            List<IReadOnlyChunk> activeClips = new();
             bool isFullyMaskedOut = false;
             bool somethingWasApplied = false;
             for (int i = 0; i < queuedOperations.Count; i++)
@@ -246,7 +246,7 @@ namespace ChunkyImageLib
 
         private bool ApplyOperationToChunk(
             IOperation operation,
-            List<Chunk> activeClips,
+            List<IReadOnlyChunk> activeClips,
             bool isFullyMaskedOut,
             Chunk targetChunk,
             Vector2i chunkPos,
@@ -272,7 +272,7 @@ namespace ChunkyImageLib
                 chunkOperation.DrawOnChunk(tempChunk, chunkPos);
                 foreach (var mask in activeClips)
                 {
-                    tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(mask.Surface.SkiaSurface, 0, 0, ClippingPaint);
+                    mask.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
                 }
                 tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0));
                 return false;

+ 1 - 1
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -10,7 +10,7 @@ namespace ChunkyImageLib
         {
             foreach (var chunkPos in committedChunksToSave)
             {
-                Chunk? chunk = image.GetCommittedChunk(chunkPos);
+                Chunk? chunk = (Chunk?)image.GetCommittedChunk(chunkPos);
                 if (chunk == null)
                 {
                     savedChunks.Add((chunkPos, null));

+ 12 - 0
src/ChunkyImageLib/IReadOnlyChunk.cs

@@ -0,0 +1,12 @@
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChunkyImageLib
+{
+    public interface IReadOnlyChunk
+    {
+        Vector2i PixelSize { get; }
+        ChunkResolution Resolution { get; }
+        void DrawOnSurface(SKSurface surface, Vector2i pos, SKPaint? paint = null);
+    }
+}

+ 1 - 1
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -4,7 +4,7 @@ namespace ChunkyImageLib
 {
     public interface IReadOnlyChunkyImage
     {
-        Chunk? GetLatestChunk(Vector2i pos);
+        IReadOnlyChunk? GetLatestChunk(Vector2i pos);
         HashSet<Vector2i> FindAffectedChunks();
         HashSet<Vector2i> FindAllChunks();
     }

+ 1 - 7
src/PixiEditorPrototype.sln

@@ -9,11 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeableDocument", "Chang
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{EFA4866B-F03E-4F6F-A7B8-1CA6467D5D17}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructureRenderer", "StructureRenderer\StructureRenderer.csproj", "{2B396104-7F74-4E03-849E-0AD6EF003666}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLibTest", "ChunkyImageLibTest\ChunkyImageLibTest.csproj", "{794971CA-8CD2-4D1D-BDD9-F41E2638D138}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChunkyImageLibVis", "ChunkyImageLibVis\ChunkyImageLibVis.csproj", "{3B0A0186-8AC0-4B1D-8587-4CE4E0E12567}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLibVis", "ChunkyImageLibVis\ChunkyImageLibVis.csproj", "{3B0A0186-8AC0-4B1D-8587-4CE4E0E12567}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -33,10 +31,6 @@ Global
 		{EFA4866B-F03E-4F6F-A7B8-1CA6467D5D17}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{EFA4866B-F03E-4F6F-A7B8-1CA6467D5D17}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{EFA4866B-F03E-4F6F-A7B8-1CA6467D5D17}.Release|Any CPU.Build.0 = Release|Any CPU
-		{2B396104-7F74-4E03-849E-0AD6EF003666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{2B396104-7F74-4E03-849E-0AD6EF003666}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{2B396104-7F74-4E03-849E-0AD6EF003666}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{2B396104-7F74-4E03-849E-0AD6EF003666}.Release|Any CPU.Build.0 = Release|Any CPU
 		{794971CA-8CD2-4D1D-BDD9-F41E2638D138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{794971CA-8CD2-4D1D-BDD9-F41E2638D138}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{794971CA-8CD2-4D1D-BDD9-F41E2638D138}.Release|Any CPU.ActiveCfg = Release|Any CPU

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

@@ -1,10 +1,10 @@
 using ChangeableDocument;
 using ChangeableDocument.Actions;
 using ChangeableDocument.ChangeInfos;
+using PixiEditorPrototype.Models.Rendering;
+using PixiEditorPrototype.Models.Rendering.RenderInfos;
 using PixiEditorPrototype.ViewModels;
 using SkiaSharp;
-using StructureRenderer;
-using StructureRenderer.RenderInfos;
 using System.Collections.Generic;
 
 namespace PixiEditorPrototype.Models
@@ -17,7 +17,7 @@ namespace PixiEditorPrototype.Models
         private DocumentChangeTracker tracker;
         private DocumentViewModel document;
         private DocumentUpdater documentUpdater;
-        private Renderer renderer;
+        private WriteableBitmapUpdater renderer;
 
         public ActionAccumulator(DocumentChangeTracker tracker, DocumentUpdater updater, DocumentViewModel document)
         {

+ 16 - 0
src/PixiEditorPrototype/Models/Rendering/RenderInfos/DirtyRect_RenderInfo.cs

@@ -0,0 +1,16 @@
+using ChunkyImageLib.DataHolders;
+
+namespace PixiEditorPrototype.Models.Rendering.RenderInfos
+{
+    public record struct DirtyRect_RenderInfo : IRenderInfo
+    {
+        public DirtyRect_RenderInfo(Vector2i pos, Vector2i size)
+        {
+            Pos = pos;
+            Size = size;
+        }
+
+        public Vector2i Pos { get; }
+        public Vector2i Size { get; }
+    }
+}

+ 6 - 0
src/PixiEditorPrototype/Models/Rendering/RenderInfos/IRenderInfo.cs

@@ -0,0 +1,6 @@
+namespace PixiEditorPrototype.Models.Rendering.RenderInfos
+{
+    public interface IRenderInfo
+    {
+    }
+}

+ 163 - 0
src/PixiEditorPrototype/Models/Rendering/WriteableBitmapUpdater.cs

@@ -0,0 +1,163 @@
+using ChangeableDocument;
+using ChangeableDocument.Changeables.Interfaces;
+using ChangeableDocument.ChangeInfos;
+using ChangeableDocument.Rendering;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+using PixiEditorPrototype.Models.Rendering.RenderInfos;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace PixiEditorPrototype.Models.Rendering
+{
+    public class WriteableBitmapUpdater
+    {
+        private DocumentChangeTracker tracker;
+        private Surface? backSurface;
+        private static SKPaint BlendingPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        private static SKPaint ReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+        private static SKPaint SelectionPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver, Color = new(0xa0FFFFFF) };
+        private static SKPaint ClearPaint = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
+
+        public WriteableBitmapUpdater(DocumentChangeTracker tracker)
+        {
+            this.tracker = tracker;
+        }
+
+        public async Task<List<IRenderInfo>> ProcessChanges(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, Vector2i screenSize)
+        {
+            return await Task.Run(() => Render(changes, screenSurface, screenSize)).ConfigureAwait(true);
+        }
+
+        private HashSet<Vector2i>? FindChunksToRerender(IReadOnlyList<IChangeInfo> changes)
+        {
+            HashSet<Vector2i> chunks = new();
+            foreach (var change in changes)
+            {
+                switch (change)
+                {
+                    case LayerImageChunks_ChangeInfo layerImageChunks:
+                        if (layerImageChunks.Chunks == null)
+                            throw new Exception("Chunks must not be null");
+                        chunks.UnionWith(layerImageChunks.Chunks);
+                        break;
+                    case Selection_ChangeInfo selection:
+                        if (tracker.Document.ReadOnlySelection.ReadOnlyIsEmptyAndInactive)
+                        {
+                            return null;
+                        }
+                        else
+                        {
+                            if (selection.Chunks == null)
+                                throw new Exception("Chunks must not be null");
+                            chunks.UnionWith(selection.Chunks);
+                        }
+                        break;
+                    case CreateStructureMember_ChangeInfo:
+                    case DeleteStructureMember_ChangeInfo:
+                    case MoveStructureMember_ChangeInfo:
+                    case Size_ChangeInfo:
+                        return null;
+                    case StructureMemberOpacity_ChangeInfo opacityChangeInfo:
+                        var memberWithOpacity = tracker.Document.FindMemberOrThrow(opacityChangeInfo.GuidValue);
+                        if (memberWithOpacity is IReadOnlyLayer layerWithOpacity)
+                            chunks.UnionWith(layerWithOpacity.ReadOnlyLayerImage.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.ReadOnlyLayerImage.FindAllChunks());
+                        else
+                            return null;
+                        break;
+                }
+            }
+            return chunks;
+        }
+
+        private List<IRenderInfo> Render(IReadOnlyList<IChangeInfo> changes, SKSurface screenSurface, Vector2i screenSize)
+        {
+            bool redrawEverything = false;
+            if (backSurface == null || backSurface.Size != screenSize)
+            {
+                backSurface?.Dispose();
+                backSurface = new(screenSize);
+                redrawEverything = true;
+            }
+            HashSet<Vector2i>? chunks = null;
+            if (!redrawEverything)
+                chunks = FindChunksToRerender(changes);
+            if (chunks == null)
+                redrawEverything = true;
+
+
+            List<IRenderInfo> infos = new();
+
+            // draw to back surface
+            if (redrawEverything)
+            {
+                RenderScreen(screenSize, backSurface.SkiaSurface, tracker.Document.ReadOnlyStructureRoot);
+                infos.Add(new DirtyRect_RenderInfo(new Vector2i(0, 0), screenSize));
+            }
+            else
+            {
+                foreach (var chunkPos in chunks!)
+                {
+                    backSurface.SkiaSurface.Canvas.DrawRect(SKRect.Create(chunkPos * ChunkyImage.ChunkSize, new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)), ClearPaint);
+                    RenderChunk(chunkPos, backSurface.SkiaSurface);
+                    infos.Add(new DirtyRect_RenderInfo(
+                        chunkPos * ChunkyImage.ChunkSize,
+                        new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)
+                        ));
+                }
+            }
+
+            // transfer the back surface to the screen surface
+
+            foreach (var info in infos)
+            {
+                if (info is DirtyRect_RenderInfo dirtyRect)
+                {
+                    screenSurface.Canvas.Save();
+                    screenSurface.Canvas.ClipRect(SKRect.Create(dirtyRect.Pos, dirtyRect.Size));
+                    screenSurface.Canvas.DrawSurface(backSurface.SkiaSurface, 0, 0, ReplacingPaint);
+                    screenSurface.Canvas.Restore();
+                }
+            }
+
+            return infos;
+        }
+
+        private void RenderScreen(Vector2i screenSize, SKSurface screenSurface, IReadOnlyFolder structureRoot)
+        {
+            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 y = 0; y < chunksHeight; y++)
+                {
+                    RenderChunk(new(x, y), screenSurface);
+                }
+            }
+        }
+
+        private void RenderChunk(Vector2i chunkPos, SKSurface screenSurface)
+        {
+            using Chunk renderedChunk = ChunkRenderer.RenderWholeStructure(chunkPos, tracker.Document.ReadOnlyStructureRoot);
+            screenSurface.Canvas.DrawSurface(renderedChunk.Surface.SkiaSurface, chunkPos * ChunkyImage.ChunkSize, BlendingPaint);
+
+            if (tracker.Document.ReadOnlySelection.ReadOnlyIsEmptyAndInactive)
+                return;
+            IReadOnlyChunk? selectionChunk = tracker.Document.ReadOnlySelection.ReadOnlySelectionImage.GetLatestChunk(chunkPos);
+            if (selectionChunk != null)
+                selectionChunk.DrawOnSurface(screenSurface, chunkPos * ChunkyImage.ChunkSize, SelectionPaint);
+        }
+    }
+}

+ 0 - 1
src/PixiEditorPrototype/PixiEditorPrototype.csproj

@@ -15,7 +15,6 @@
   <ItemGroup>
     <ProjectReference Include="..\ChangeableDocument\ChangeableDocument.csproj" />
     <ProjectReference Include="..\ChunkyImageLib\ChunkyImageLib.csproj" />
-    <ProjectReference Include="..\StructureRenderer\StructureRenderer.csproj" />
   </ItemGroup>
 
 </Project>

+ 11 - 0
src/PixiEditorPrototype/Views/DocumentView.xaml

@@ -40,6 +40,17 @@
                             <pe:ReversedOrderStackPanel/>
                         </ItemsPanelTemplate>
                     </TreeView.ItemsPanel>
+                    <TreeView.ItemContainerStyle>
+                        <Style TargetType="TreeViewItem">
+                            <Setter Property="ItemsPanel">
+                                <Setter.Value>
+                                    <ItemsPanelTemplate>
+                                        <pe:ReversedOrderStackPanel/>
+                                    </ItemsPanelTemplate>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </TreeView.ItemContainerStyle>
                     <i:Interaction.Triggers>
                         <i:EventTrigger EventName="SelectedItemChanged">
                             <i:InvokeCommandAction Command="{Binding ChangeSelectedItemCommand}" PassEventArgsToCommand="True"/>