|
@@ -1,33 +1,38 @@
|
|
|
-using ChunkyImageLib.DataHolders;
|
|
|
|
|
|
|
+using Avalonia.Threading;
|
|
|
|
|
+using ChunkyImageLib;
|
|
|
|
|
+using ChunkyImageLib.DataHolders;
|
|
|
|
|
+using ChunkyImageLib.Operations;
|
|
|
using Drawie.Backend.Core;
|
|
using Drawie.Backend.Core;
|
|
|
|
|
+using Drawie.Backend.Core.Bridge;
|
|
|
using Drawie.Backend.Core.Numerics;
|
|
using Drawie.Backend.Core.Numerics;
|
|
|
using PixiEditor.ChangeableDocument.Changeables.Interfaces;
|
|
using PixiEditor.ChangeableDocument.Changeables.Interfaces;
|
|
|
using PixiEditor.ChangeableDocument.Rendering;
|
|
using PixiEditor.ChangeableDocument.Rendering;
|
|
|
using Drawie.Backend.Core.Surfaces;
|
|
using Drawie.Backend.Core.Surfaces;
|
|
|
-using Drawie.Backend.Core.Surfaces.PaintImpl;
|
|
|
|
|
using Drawie.Numerics;
|
|
using Drawie.Numerics;
|
|
|
using PixiEditor.ChangeableDocument.Changeables.Animations;
|
|
using PixiEditor.ChangeableDocument.Changeables.Animations;
|
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
|
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
|
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
|
|
using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
|
|
|
using PixiEditor.Models.Handlers;
|
|
using PixiEditor.Models.Handlers;
|
|
|
|
|
+using PixiEditor.Models.Position;
|
|
|
|
|
|
|
|
namespace PixiEditor.Models.Rendering;
|
|
namespace PixiEditor.Models.Rendering;
|
|
|
|
|
|
|
|
-internal class SceneRenderer : IDisposable
|
|
|
|
|
|
|
+internal class SceneRenderer
|
|
|
{
|
|
{
|
|
|
public const double ZoomDiffToRerender = 20;
|
|
public const double ZoomDiffToRerender = 20;
|
|
|
|
|
+ public const float OversizeFactor = 1.25f;
|
|
|
public IReadOnlyDocument Document { get; }
|
|
public IReadOnlyDocument Document { get; }
|
|
|
public IDocument DocumentViewModel { get; }
|
|
public IDocument DocumentViewModel { get; }
|
|
|
public bool HighResRendering { get; set; } = true;
|
|
public bool HighResRendering { get; set; } = true;
|
|
|
|
|
|
|
|
- private Dictionary<string, Texture> cachedTextures = new();
|
|
|
|
|
- private bool lastHighResRendering = true;
|
|
|
|
|
|
|
+ public IReadOnlyDictionary<Guid, RenderState> LastRenderedStates => lastRenderedStates;
|
|
|
|
|
+ private Dictionary<Guid, RenderState> lastRenderedStates = new();
|
|
|
private int lastGraphCacheHash = -1;
|
|
private int lastGraphCacheHash = -1;
|
|
|
private KeyFrameTime lastFrameTime;
|
|
private KeyFrameTime lastFrameTime;
|
|
|
private Dictionary<Guid, bool> lastFramesVisibility = new();
|
|
private Dictionary<Guid, bool> lastFramesVisibility = new();
|
|
|
|
|
|
|
|
- private ChunkResolution? lastResolution;
|
|
|
|
|
|
|
+ private TextureCache textureCache = new();
|
|
|
|
|
|
|
|
public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
|
|
public SceneRenderer(IReadOnlyDocument trackerDocument, IDocument documentViewModel)
|
|
|
{
|
|
{
|
|
@@ -35,110 +40,220 @@ internal class SceneRenderer : IDisposable
|
|
|
DocumentViewModel = documentViewModel;
|
|
DocumentViewModel = documentViewModel;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void RenderScene(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
|
|
|
|
|
- string? targetOutput = null)
|
|
|
|
|
|
|
+ public async Task RenderAsync(Dictionary<Guid, ViewportInfo> stateViewports, AffectedArea affectedArea,
|
|
|
|
|
+ bool updateDelayed, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures, bool immediateRender)
|
|
|
{
|
|
{
|
|
|
- if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
|
|
|
|
|
- target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
|
|
|
|
|
- RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
|
|
|
|
|
|
|
+ if (immediateRender)
|
|
|
|
|
+ {
|
|
|
|
|
+ Render(stateViewports, affectedArea, updateDelayed, previewTextures);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- string adjustedTargetOutput = targetOutput ?? "";
|
|
|
|
|
|
|
+ await DrawingBackendApi.Current.RenderingDispatcher.InvokeInBackgroundAsync(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ Render(stateViewports, affectedArea, updateDelayed, previewTextures);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- IReadOnlyNodeGraph finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
|
|
|
|
|
- bool shouldRerender = ShouldRerender(target, resolution, adjustedTargetOutput, finalGraph);
|
|
|
|
|
|
|
+ private void Render(Dictionary<Guid, ViewportInfo> stateViewports, AffectedArea affectedArea, bool updateDelayed,
|
|
|
|
|
+ Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures)
|
|
|
|
|
+ {
|
|
|
|
|
+ using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
|
|
|
|
|
+ int renderedCount = 0;
|
|
|
|
|
+ foreach (var viewport in stateViewports)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (viewport.Value.Delayed && !updateDelayed)
|
|
|
|
|
+ {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // TODO: Check if clipping to visible area improves performance on full resolution
|
|
|
|
|
- // Meaning zoomed big textures
|
|
|
|
|
|
|
+ if (viewport.Value.RealDimensions.ShortestAxis <= 0) continue;
|
|
|
|
|
|
|
|
- if (shouldRerender)
|
|
|
|
|
- {
|
|
|
|
|
- if (cachedTextures.ContainsKey(adjustedTargetOutput))
|
|
|
|
|
|
|
+ var rendered = RenderScene(viewport.Value, affectedArea, previewTextures);
|
|
|
|
|
+ if (DocumentViewModel.SceneTextures.TryGetValue(viewport.Key, out var texture) && texture != rendered)
|
|
|
{
|
|
{
|
|
|
- cachedTextures[adjustedTargetOutput]?.Dispose();
|
|
|
|
|
|
|
+ texture.Dispose();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
|
|
|
|
|
- cachedTextures[adjustedTargetOutput] = rendered;
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ DocumentViewModel.SceneTextures[viewport.Key] = rendered;
|
|
|
|
|
+ viewport.Value.InvalidateVisual();
|
|
|
|
|
+ renderedCount++;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var cachedTexture = cachedTextures[adjustedTargetOutput];
|
|
|
|
|
- Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
|
|
|
|
|
- int saved = target.Canvas.Save();
|
|
|
|
|
- target.Canvas.SetMatrix(matrixDiff);
|
|
|
|
|
- if (samplingOptions == SamplingOptions.Default)
|
|
|
|
|
|
|
+ if (renderedCount == 0 && previewTextures is { Count: > 0 })
|
|
|
{
|
|
{
|
|
|
- target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
|
|
|
|
|
|
|
+ RenderOnlyPreviews(affectedArea, previewTextures);
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void RenderOnlyPreviews(AffectedArea affectedArea,
|
|
|
|
|
+ Dictionary<Guid, List<PreviewRenderRequest>> previewTextures)
|
|
|
|
|
+ {
|
|
|
|
|
+ ViewportInfo previewGenerationViewport = new()
|
|
|
{
|
|
{
|
|
|
- using var img = cachedTexture.DrawingSurface.Snapshot();
|
|
|
|
|
- target.Canvas.DrawImage(img, 0, 0, samplingOptions);
|
|
|
|
|
|
|
+ RealDimensions = new VecD(1, 1),
|
|
|
|
|
+ Transform = Matrix3X3.Identity,
|
|
|
|
|
+ Id = Guid.NewGuid(),
|
|
|
|
|
+ Resolution = ChunkResolution.Full,
|
|
|
|
|
+ Sampling = SamplingOptions.Bilinear,
|
|
|
|
|
+ VisibleDocumentRegion = null,
|
|
|
|
|
+ RenderOutput = "DEFAULT",
|
|
|
|
|
+ Delayed = false
|
|
|
|
|
+ };
|
|
|
|
|
+ var rendered = RenderScene(previewGenerationViewport, affectedArea, previewTextures);
|
|
|
|
|
+ rendered.Dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Texture? RenderScene(ViewportInfo viewport, AffectedArea affectedArea,
|
|
|
|
|
+ Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ /*if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
|
|
|
|
|
+ target.DeviceClipBounds.Size.ShortestAxis <= 0) return;*/
|
|
|
|
|
+
|
|
|
|
|
+ /*TODO:
|
|
|
|
|
+ - [ ] Rendering optimizer
|
|
|
|
|
+ - [?] Render thread and proper locking/synchronization - check render-thread branch (both drawie and pixieditor)
|
|
|
|
|
+ but be aware, this is a nightmare and good luck
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ VecI renderTargetSize = (VecI)viewport.RealDimensions;
|
|
|
|
|
+ Matrix3X3 targetMatrix = viewport.Transform;
|
|
|
|
|
+ Guid viewportId = viewport.Id;
|
|
|
|
|
+ ChunkResolution resolution = viewport.Resolution;
|
|
|
|
|
+ SamplingOptions samplingOptions = viewport.Sampling;
|
|
|
|
|
+ RectI? visibleDocumentRegion = viewport.VisibleDocumentRegion;
|
|
|
|
|
+ string? targetOutput = viewport.RenderOutput.Equals("DEFAULT", StringComparison.InvariantCultureIgnoreCase)
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : viewport.RenderOutput;
|
|
|
|
|
+
|
|
|
|
|
+ IReadOnlyNodeGraph finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
|
|
|
|
|
+
|
|
|
|
|
+ float oversizeFactor = 1;
|
|
|
|
|
+ if (visibleDocumentRegion != null && viewport.IsScene &&
|
|
|
|
|
+ visibleDocumentRegion.Value != new RectI(0, 0, Document.Size.X, Document.Size.Y))
|
|
|
|
|
+ {
|
|
|
|
|
+ visibleDocumentRegion = (RectI)visibleDocumentRegion.Value.Scale(OversizeFactor,
|
|
|
|
|
+ visibleDocumentRegion.Value.Center);
|
|
|
|
|
+ oversizeFactor = OversizeFactor;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- target.Canvas.RestoreToCount(saved);
|
|
|
|
|
|
|
+ bool shouldRerender =
|
|
|
|
|
+ ShouldRerender(renderTargetSize, targetMatrix, resolution, viewportId, targetOutput, finalGraph,
|
|
|
|
|
+ previewTextures, visibleDocumentRegion, oversizeFactor, out bool fullAffectedArea);
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldRerender)
|
|
|
|
|
+ {
|
|
|
|
|
+ affectedArea = fullAffectedArea && viewport.VisibleDocumentRegion.HasValue
|
|
|
|
|
+ ? new AffectedArea(OperationHelper.FindChunksTouchingRectangle(viewport.VisibleDocumentRegion.Value,
|
|
|
|
|
+ ChunkyImage.FullChunkSize))
|
|
|
|
|
+ : affectedArea;
|
|
|
|
|
+ return RenderGraph(renderTargetSize, targetMatrix, viewportId, resolution, samplingOptions, affectedArea,
|
|
|
|
|
+ visibleDocumentRegion, targetOutput, viewport.IsScene, oversizeFactor, finalGraph, previewTextures);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var cachedTexture = DocumentViewModel.SceneTextures[viewportId];
|
|
|
|
|
+ return cachedTexture;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
|
|
|
|
|
|
|
+ private Texture RenderGraph(VecI renderTargetSize, Matrix3X3 targetMatrix, Guid viewportId,
|
|
|
|
|
+ ChunkResolution resolution,
|
|
|
|
|
+ SamplingOptions samplingOptions,
|
|
|
|
|
+ AffectedArea area,
|
|
|
|
|
+ RectI? visibleDocumentRegion,
|
|
|
string? targetOutput,
|
|
string? targetOutput,
|
|
|
- IReadOnlyNodeGraph finalGraph)
|
|
|
|
|
|
|
+ bool canRenderOnionSkinning,
|
|
|
|
|
+ float oversizeFactor,
|
|
|
|
|
+ IReadOnlyNodeGraph finalGraph, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures)
|
|
|
{
|
|
{
|
|
|
- DrawingSurface renderTarget = target;
|
|
|
|
|
|
|
+ DrawingSurface renderTarget = null;
|
|
|
Texture? renderTexture = null;
|
|
Texture? renderTexture = null;
|
|
|
int restoreCanvasTo;
|
|
int restoreCanvasTo;
|
|
|
|
|
|
|
|
VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
|
|
VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
|
|
|
- if (RenderInOutputSize(finalGraph))
|
|
|
|
|
|
|
+ if (RenderInOutputSize(finalGraph, renderTargetSize, finalSize))
|
|
|
{
|
|
{
|
|
|
finalSize = (VecI)(finalSize * resolution.Multiplier());
|
|
finalSize = (VecI)(finalSize * resolution.Multiplier());
|
|
|
|
|
|
|
|
- renderTexture = Texture.ForProcessing(finalSize, Document.ProcessingColorSpace);
|
|
|
|
|
|
|
+ renderTexture =
|
|
|
|
|
+ textureCache.RequestTexture(viewportId.GetHashCode(), finalSize, Document.ProcessingColorSpace);
|
|
|
renderTarget = renderTexture.DrawingSurface;
|
|
renderTarget = renderTexture.DrawingSurface;
|
|
|
|
|
+ renderTarget.Canvas.Save();
|
|
|
renderTexture.DrawingSurface.Canvas.Save();
|
|
renderTexture.DrawingSurface.Canvas.Save();
|
|
|
renderTexture.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
|
|
renderTexture.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
|
|
|
-
|
|
|
|
|
- restoreCanvasTo = target.Canvas.Save();
|
|
|
|
|
- target.Canvas.Scale((float)resolution.InvertedMultiplier());
|
|
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- renderTexture = Texture.ForProcessing(renderTarget.DeviceClipBounds.Size, Document.ProcessingColorSpace);
|
|
|
|
|
|
|
+ var bufferedSize = (VecI)(renderTargetSize * oversizeFactor);
|
|
|
|
|
+ renderTexture = textureCache.RequestTexture(viewportId.GetHashCode(), bufferedSize,
|
|
|
|
|
+ Document.ProcessingColorSpace);
|
|
|
|
|
+
|
|
|
|
|
+ var bufferedMatrix = targetMatrix.PostConcat(Matrix3X3.CreateTranslation(
|
|
|
|
|
+ (bufferedSize.X - renderTargetSize.X) / 2.0,
|
|
|
|
|
+ (bufferedSize.Y - renderTargetSize.Y) / 2.0));
|
|
|
|
|
|
|
|
renderTarget = renderTexture.DrawingSurface;
|
|
renderTarget = renderTexture.DrawingSurface;
|
|
|
|
|
+ renderTarget.Canvas.SetMatrix(bufferedMatrix);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- restoreCanvasTo = target.Canvas.Save();
|
|
|
|
|
- renderTarget.Canvas.Save();
|
|
|
|
|
|
|
+ bool renderOnionSkinning = canRenderOnionSkinning &&
|
|
|
|
|
+ DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable;
|
|
|
|
|
|
|
|
- renderTarget.Canvas.SetMatrix(target.Canvas.TotalMatrix);
|
|
|
|
|
- target.Canvas.SetMatrix(Matrix3X3.Identity);
|
|
|
|
|
- renderTarget.Canvas.ClipRect(new RectD(0, 0, finalSize.X, finalSize.Y));
|
|
|
|
|
- resolution = ChunkResolution.Full;
|
|
|
|
|
|
|
+ var animationData = Document.AnimationData;
|
|
|
|
|
+ double onionOpacity = animationData.OnionOpacity / 100.0;
|
|
|
|
|
+ double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
|
|
|
|
|
+ if (renderOnionSkinning)
|
|
|
|
|
+ {
|
|
|
|
|
+ for (int i = 1; i <= animationData.OnionFrames; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame - i;
|
|
|
|
|
+ if (frame < DocumentViewModel.AnimationHandler.FirstVisibleFrame)
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
|
|
|
|
|
+ RenderContext onionContext = new(renderTarget, frame, resolution, finalSize, Document.Size,
|
|
|
|
|
+ Document.ProcessingColorSpace, samplingOptions, finalOpacity);
|
|
|
|
|
+ onionContext.TargetOutput = targetOutput;
|
|
|
|
|
+ onionContext.VisibleDocumentRegion = visibleDocumentRegion;
|
|
|
|
|
+ finalGraph.Execute(onionContext);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
|
|
RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
|
|
|
resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
|
|
resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
|
|
|
context.TargetOutput = targetOutput;
|
|
context.TargetOutput = targetOutput;
|
|
|
|
|
+ context.AffectedArea = area;
|
|
|
|
|
+ context.VisibleDocumentRegion = visibleDocumentRegion;
|
|
|
|
|
+ context.PreviewTextures = previewTextures;
|
|
|
finalGraph.Execute(context);
|
|
finalGraph.Execute(context);
|
|
|
|
|
|
|
|
- if (renderTexture != null)
|
|
|
|
|
|
|
+ if (renderOnionSkinning)
|
|
|
{
|
|
{
|
|
|
- if (samplingOptions == SamplingOptions.Default)
|
|
|
|
|
- {
|
|
|
|
|
- target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
|
|
+ for (int i = 1; i <= animationData.OnionFrames; i++)
|
|
|
{
|
|
{
|
|
|
- using var snapshot = renderTexture.DrawingSurface.Snapshot();
|
|
|
|
|
- target.Canvas.DrawImage(snapshot, 0, 0, samplingOptions);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame + i;
|
|
|
|
|
+ if (frame >= DocumentViewModel.AnimationHandler.LastFrame)
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- target.Canvas.RestoreToCount(restoreCanvasTo);
|
|
|
|
|
|
|
+ double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
|
|
|
|
|
+ RenderContext onionContext = new(renderTarget, frame, resolution, finalSize, Document.Size,
|
|
|
|
|
+ Document.ProcessingColorSpace, samplingOptions, finalOpacity);
|
|
|
|
|
+ onionContext.TargetOutput = targetOutput;
|
|
|
|
|
+ onionContext.VisibleDocumentRegion = visibleDocumentRegion;
|
|
|
|
|
+ finalGraph.Execute(onionContext);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ renderTarget.Canvas.Restore();
|
|
|
|
|
+
|
|
|
return renderTexture;
|
|
return renderTexture;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph, VecI documentSize)
|
|
|
|
|
|
|
+ private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph,
|
|
|
|
|
+ VecI documentSize)
|
|
|
{
|
|
{
|
|
|
VecI finalSize = documentSize;
|
|
VecI finalSize = documentSize;
|
|
|
if (targetOutput != null)
|
|
if (targetOutput != null)
|
|
@@ -162,38 +277,67 @@ internal class SceneRenderer : IDisposable
|
|
|
return finalSize;
|
|
return finalSize;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private bool RenderInOutputSize(IReadOnlyNodeGraph finalGraph)
|
|
|
|
|
|
|
+ private bool RenderInOutputSize(IReadOnlyNodeGraph finalGraph, VecI renderTargetSize, VecI finalSize)
|
|
|
{
|
|
{
|
|
|
- return !HighResRendering || !HighDpiRenderNodePresent(finalGraph);
|
|
|
|
|
|
|
+ return !HighResRendering ||
|
|
|
|
|
+ (!HighDpiRenderNodePresent(finalGraph) && renderTargetSize.Length > finalSize.Length);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private bool ShouldRerender(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
|
|
|
|
|
- IReadOnlyNodeGraph finalGraph)
|
|
|
|
|
|
|
+ private bool ShouldRerender(VecI targetSize, Matrix3X3 matrix, ChunkResolution resolution,
|
|
|
|
|
+ Guid viewportId,
|
|
|
|
|
+ string targetOutput,
|
|
|
|
|
+ IReadOnlyNodeGraph finalGraph, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures,
|
|
|
|
|
+ RectI? visibleDocumentRegion, float oversizeFactor, out bool fullAffectedArea)
|
|
|
{
|
|
{
|
|
|
- if (!cachedTextures.TryGetValue(targetOutput ?? "", out var cachedTexture) || cachedTexture == null ||
|
|
|
|
|
|
|
+ fullAffectedArea = false;
|
|
|
|
|
+ if (!DocumentViewModel.SceneTextures.TryGetValue(viewportId, out var cachedTexture) ||
|
|
|
|
|
+ cachedTexture == null ||
|
|
|
cachedTexture.IsDisposed)
|
|
cachedTexture.IsDisposed)
|
|
|
{
|
|
{
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (lastResolution != resolution)
|
|
|
|
|
|
|
+ if (previewTextures is { Count: > 0 })
|
|
|
{
|
|
{
|
|
|
- lastResolution = resolution;
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (lastHighResRendering != HighResRendering)
|
|
|
|
|
|
|
+ var renderState = new RenderState
|
|
|
|
|
+ {
|
|
|
|
|
+ ChunkResolution = resolution,
|
|
|
|
|
+ HighResRendering = HighResRendering,
|
|
|
|
|
+ TargetOutput = targetOutput,
|
|
|
|
|
+ OnionFrames = Document.AnimationData.OnionFrames,
|
|
|
|
|
+ OnionOpacity = Document.AnimationData.OnionOpacity,
|
|
|
|
|
+ OnionSkinning = DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable,
|
|
|
|
|
+ GraphCacheHash = finalGraph.GetCacheHash(),
|
|
|
|
|
+ ZoomLevel = matrix.ScaleX,
|
|
|
|
|
+ VisibleDocumentRegion =
|
|
|
|
|
+ (RectD?)visibleDocumentRegion ?? new RectD(0, 0, Document.Size.X, Document.Size.Y)
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (lastRenderedStates.TryGetValue(viewportId, out var lastState))
|
|
|
{
|
|
{
|
|
|
- lastHighResRendering = HighResRendering;
|
|
|
|
|
|
|
+ if (lastState.ShouldRerender(renderState))
|
|
|
|
|
+ {
|
|
|
|
|
+ lastRenderedStates[viewportId] = renderState;
|
|
|
|
|
+ fullAffectedArea = lastState.ZoomLevel > renderState.ZoomLevel;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ lastRenderedStates[viewportId] = renderState;
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- bool renderInDocumentSize = RenderInOutputSize(finalGraph);
|
|
|
|
|
|
|
+ VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
|
|
|
|
|
+ bool renderInDocumentSize = RenderInOutputSize(finalGraph, targetSize, finalSize);
|
|
|
VecI compareSize = renderInDocumentSize
|
|
VecI compareSize = renderInDocumentSize
|
|
|
? (VecI)(Document.Size * resolution.Multiplier())
|
|
? (VecI)(Document.Size * resolution.Multiplier())
|
|
|
- : target.DeviceClipBounds.Size;
|
|
|
|
|
|
|
+ : targetSize;
|
|
|
|
|
|
|
|
- if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
|
|
|
|
|
|
|
+ if (cachedTexture.Size != (VecI)(compareSize * oversizeFactor))
|
|
|
{
|
|
{
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
@@ -221,37 +365,10 @@ internal class SceneRenderer : IDisposable
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!renderInDocumentSize)
|
|
|
|
|
- {
|
|
|
|
|
- double lengthDiff = target.LocalClipBounds.Size.Length -
|
|
|
|
|
- cachedTexture.DrawingSurface.LocalClipBounds.Size.Length;
|
|
|
|
|
- if (lengthDiff > 0 || target.LocalClipBounds.Pos != cachedTexture.DrawingSurface.LocalClipBounds.Pos ||
|
|
|
|
|
- lengthDiff < -ZoomDiffToRerender)
|
|
|
|
|
- {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- int currentGraphCacheHash = finalGraph.GetCacheHash();
|
|
|
|
|
- if (lastGraphCacheHash != currentGraphCacheHash)
|
|
|
|
|
- {
|
|
|
|
|
- lastGraphCacheHash = currentGraphCacheHash;
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private Matrix3X3 SolveMatrixDiff(DrawingSurface target, Texture cachedTexture)
|
|
|
|
|
- {
|
|
|
|
|
- Matrix3X3 old = cachedTexture.DrawingSurface.Canvas.TotalMatrix;
|
|
|
|
|
- Matrix3X3 current = target.Canvas.TotalMatrix;
|
|
|
|
|
-
|
|
|
|
|
- Matrix3X3 solveMatrixDiff = current.Concat(old.Invert());
|
|
|
|
|
- return solveMatrixDiff;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
private bool HighDpiRenderNodePresent(IReadOnlyNodeGraph documentNodeGraph)
|
|
private bool HighDpiRenderNodePresent(IReadOnlyNodeGraph documentNodeGraph)
|
|
|
{
|
|
{
|
|
|
bool highDpiRenderNodePresent = false;
|
|
bool highDpiRenderNodePresent = false;
|
|
@@ -265,61 +382,36 @@ internal class SceneRenderer : IDisposable
|
|
|
|
|
|
|
|
return highDpiRenderNodePresent;
|
|
return highDpiRenderNodePresent;
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling, string? targetOutput)
|
|
|
|
|
|
|
+readonly struct RenderState
|
|
|
|
|
+{
|
|
|
|
|
+ public ChunkResolution ChunkResolution { get; init; }
|
|
|
|
|
+ public bool HighResRendering { get; init; }
|
|
|
|
|
+ public string TargetOutput { get; init; }
|
|
|
|
|
+ public int GraphCacheHash { get; init; }
|
|
|
|
|
+ public RectD VisibleDocumentRegion { get; init; }
|
|
|
|
|
+ public double ZoomLevel { get; init; }
|
|
|
|
|
+ public int OnionFrames { get; init; }
|
|
|
|
|
+ public double OnionOpacity { get; init; }
|
|
|
|
|
+ public bool OnionSkinning { get; init; }
|
|
|
|
|
+
|
|
|
|
|
+ public bool ShouldRerender(RenderState other)
|
|
|
{
|
|
{
|
|
|
- var animationData = Document.AnimationData;
|
|
|
|
|
- if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
|
|
|
|
|
- {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- double onionOpacity = animationData.OnionOpacity / 100.0;
|
|
|
|
|
- double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
|
|
|
|
|
-
|
|
|
|
|
- var finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
|
|
|
|
|
- var renderOutputSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
|
|
|
|
|
-
|
|
|
|
|
- // Render previous frames'
|
|
|
|
|
- for (int i = 1; i <= animationData.OnionFrames; i++)
|
|
|
|
|
- {
|
|
|
|
|
- int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame - i;
|
|
|
|
|
- if (frame < DocumentViewModel.AnimationHandler.FirstVisibleFrame)
|
|
|
|
|
- {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
|
|
|
|
|
- Document.ProcessingColorSpace, sampling, finalOpacity);
|
|
|
|
|
- onionContext.TargetOutput = targetOutput;
|
|
|
|
|
- finalGraph.Execute(onionContext);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Render next frames
|
|
|
|
|
- for (int i = 1; i <= animationData.OnionFrames; i++)
|
|
|
|
|
- {
|
|
|
|
|
- int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame + i;
|
|
|
|
|
- if (frame >= DocumentViewModel.AnimationHandler.LastFrame)
|
|
|
|
|
- {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return !ChunkResolution.Equals(other.ChunkResolution) || HighResRendering != other.HighResRendering ||
|
|
|
|
|
+ TargetOutput != other.TargetOutput || GraphCacheHash != other.GraphCacheHash ||
|
|
|
|
|
+ OnionFrames != other.OnionFrames || Math.Abs(OnionOpacity - other.OnionOpacity) > 0.05 ||
|
|
|
|
|
+ OnionSkinning != other.OnionSkinning ||
|
|
|
|
|
+ VisibleRegionChanged(other) || ZoomDiff(other) > 0;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
|
|
|
|
|
- RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
|
|
|
|
|
- Document.ProcessingColorSpace, sampling, finalOpacity);
|
|
|
|
|
- onionContext.TargetOutput = targetOutput;
|
|
|
|
|
- finalGraph.Execute(onionContext);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private bool VisibleRegionChanged(RenderState other)
|
|
|
|
|
+ {
|
|
|
|
|
+ return !other.VisibleDocumentRegion.IsFullyInside(VisibleDocumentRegion);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void Dispose()
|
|
|
|
|
|
|
+ private double ZoomDiff(RenderState other)
|
|
|
{
|
|
{
|
|
|
- foreach (var texture in cachedTextures)
|
|
|
|
|
- {
|
|
|
|
|
- texture.Value?.Dispose();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return Math.Abs(ZoomLevel - other.ZoomLevel);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|