SceneRenderer.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using ChunkyImageLib.DataHolders;
  2. using Drawie.Backend.Core;
  3. using Drawie.Backend.Core.Numerics;
  4. using PixiEditor.ChangeableDocument.Changeables.Interfaces;
  5. using PixiEditor.ChangeableDocument.Rendering;
  6. using Drawie.Backend.Core.Surfaces;
  7. using Drawie.Backend.Core.Surfaces.ImageData;
  8. using Drawie.Numerics;
  9. using PixiEditor.ChangeableDocument.Changeables.Animations;
  10. using PixiEditor.ChangeableDocument.Changeables.Graph;
  11. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  12. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  13. using PixiEditor.Models.Handlers;
  14. namespace PixiEditor.Models.Rendering;
  15. internal class SceneRenderer
  16. {
  17. public const double ZoomDiffToRerender = 20;
  18. public IReadOnlyDocument Document { get; }
  19. public IDocument DocumentViewModel { get; }
  20. public bool HighResRendering { get; set; } = true;
  21. private Dictionary<string, Texture> cachedTextures = new();
  22. private bool lastHighResRendering = true;
  23. private int lastGraphCacheHash = -1;
  24. private KeyFrameTime lastFrameTime;
  25. public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
  26. {
  27. Document = trackerDocument;
  28. DocumentViewModel = documentViewModel;
  29. }
  30. public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
  31. {
  32. if (Document.Renderer.IsBusy || DocumentViewModel.Busy) return;
  33. RenderOnionSkin(target, resolution, targetOutput);
  34. string adjustedTargetOutput = targetOutput ?? "";
  35. IReadOnlyNodeGraph finalGraph = SolveFinalNodeGraph(targetOutput);
  36. bool shouldRerender = ShouldRerender(target, resolution, adjustedTargetOutput, finalGraph);
  37. if (shouldRerender)
  38. {
  39. if (cachedTextures.ContainsKey(adjustedTargetOutput))
  40. {
  41. cachedTextures[adjustedTargetOutput]?.Dispose();
  42. }
  43. var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
  44. cachedTextures[adjustedTargetOutput] = rendered;
  45. }
  46. else
  47. {
  48. var cachedTexture = cachedTextures[adjustedTargetOutput];
  49. Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
  50. int saved = target.Canvas.Save();
  51. target.Canvas.SetMatrix(matrixDiff);
  52. target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
  53. target.Canvas.RestoreToCount(saved);
  54. }
  55. }
  56. private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput, IReadOnlyNodeGraph finalGraph)
  57. {
  58. DrawingSurface renderTarget = target;
  59. Texture? renderTexture = null;
  60. bool restoreCanvas = false;
  61. if (RenderInDocumentSize())
  62. {
  63. renderTexture = Texture.ForProcessing(Document.Size, Document.ProcessingColorSpace);
  64. renderTarget = renderTexture.DrawingSurface;
  65. }
  66. else
  67. {
  68. renderTexture = Texture.ForProcessing(renderTarget.DeviceClipBounds.Size, Document.ProcessingColorSpace);
  69. renderTarget = renderTexture.DrawingSurface;
  70. target.Canvas.Save();
  71. renderTarget.Canvas.Save();
  72. renderTarget.Canvas.SetMatrix(target.Canvas.TotalMatrix);
  73. target.Canvas.SetMatrix(Matrix3X3.Identity);
  74. restoreCanvas = true;
  75. }
  76. RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
  77. resolution, Document.Size, Document.ProcessingColorSpace);
  78. context.TargetOutput = targetOutput;
  79. finalGraph.Execute(context);
  80. if (renderTexture != null)
  81. {
  82. target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
  83. if (restoreCanvas)
  84. {
  85. target.Canvas.Restore();
  86. }
  87. }
  88. return renderTexture;
  89. }
  90. private bool RenderInDocumentSize()
  91. {
  92. return !HighResRendering || !HighDpiRenderNodePresent(Document.NodeGraph);
  93. }
  94. private bool ShouldRerender(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
  95. IReadOnlyNodeGraph finalGraph)
  96. {
  97. if (!cachedTextures.TryGetValue(targetOutput ?? "", out var cachedTexture) || cachedTexture == null ||
  98. cachedTexture.IsDisposed)
  99. {
  100. return true;
  101. }
  102. if (lastHighResRendering != HighResRendering)
  103. {
  104. lastHighResRendering = HighResRendering;
  105. return true;
  106. }
  107. bool renderInDocumentSize = RenderInDocumentSize();
  108. VecI compareSize = renderInDocumentSize ? Document.Size : target.DeviceClipBounds.Size;
  109. if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
  110. {
  111. return true;
  112. }
  113. if(lastFrameTime.Frame != DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame)
  114. {
  115. lastFrameTime = DocumentViewModel.AnimationHandler.ActiveFrameTime;
  116. return true;
  117. }
  118. if (!renderInDocumentSize)
  119. {
  120. double lengthDiff = target.LocalClipBounds.Size.Length - cachedTexture.DrawingSurface.LocalClipBounds.Size.Length;
  121. if (lengthDiff > 0 || target.LocalClipBounds.Pos != cachedTexture.DrawingSurface.LocalClipBounds.Pos || lengthDiff < -ZoomDiffToRerender)
  122. {
  123. return true;
  124. }
  125. }
  126. int currentGraphCacheHash = finalGraph.GetCacheHash();
  127. if (lastGraphCacheHash != currentGraphCacheHash)
  128. {
  129. lastGraphCacheHash = currentGraphCacheHash;
  130. return true;
  131. }
  132. return false;
  133. }
  134. private Matrix3X3 SolveMatrixDiff(DrawingSurface target, Texture cachedTexture)
  135. {
  136. Matrix3X3 old = cachedTexture.DrawingSurface.Canvas.TotalMatrix;
  137. Matrix3X3 current = target.Canvas.TotalMatrix;
  138. Matrix3X3 solveMatrixDiff = current.Concat(old.Invert());
  139. return solveMatrixDiff;
  140. }
  141. private IReadOnlyNodeGraph SolveFinalNodeGraph(string? targetOutput)
  142. {
  143. if (targetOutput == null)
  144. {
  145. return Document.NodeGraph;
  146. }
  147. CustomOutputNode[] outputNodes = Document.NodeGraph.AllNodes.OfType<CustomOutputNode>().ToArray();
  148. foreach (CustomOutputNode outputNode in outputNodes)
  149. {
  150. if (outputNode.OutputName.Value == targetOutput)
  151. {
  152. return GraphFromOutputNode(outputNode);
  153. }
  154. }
  155. return Document.NodeGraph;
  156. }
  157. private IReadOnlyNodeGraph GraphFromOutputNode(CustomOutputNode outputNode)
  158. {
  159. NodeGraph graph = new();
  160. outputNode.TraverseBackwards(n =>
  161. {
  162. if (n is Node node)
  163. {
  164. graph.AddNode(node);
  165. }
  166. return true;
  167. });
  168. graph.CustomOutputNode = outputNode;
  169. return graph;
  170. }
  171. private bool HighDpiRenderNodePresent(IReadOnlyNodeGraph documentNodeGraph)
  172. {
  173. bool highDpiRenderNodePresent = false;
  174. documentNodeGraph.TryTraverse(n =>
  175. {
  176. if (n is IHighDpiRenderNode { AllowHighDpiRendering: true })
  177. {
  178. highDpiRenderNodePresent = true;
  179. }
  180. });
  181. return highDpiRenderNodePresent;
  182. }
  183. private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
  184. {
  185. var animationData = Document.AnimationData;
  186. if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
  187. {
  188. return;
  189. }
  190. double onionOpacity = animationData.OnionOpacity / 100.0;
  191. double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
  192. var finalGraph = SolveFinalNodeGraph(targetOutput);
  193. // Render previous frames'
  194. for (int i = 1; i <= animationData.OnionFrames; i++)
  195. {
  196. int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame - i;
  197. if (frame < DocumentViewModel.AnimationHandler.FirstFrame)
  198. {
  199. break;
  200. }
  201. double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
  202. RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace,
  203. finalOpacity);
  204. onionContext.TargetOutput = targetOutput;
  205. finalGraph.Execute(onionContext);
  206. }
  207. // Render next frames
  208. for (int i = 1; i <= animationData.OnionFrames; i++)
  209. {
  210. int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame + i;
  211. if (frame >= DocumentViewModel.AnimationHandler.LastFrame)
  212. {
  213. break;
  214. }
  215. double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
  216. RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace,
  217. finalOpacity);
  218. onionContext.TargetOutput = targetOutput;
  219. finalGraph.Execute(onionContext);
  220. }
  221. }
  222. }