|
@@ -1,211 +0,0 @@
|
|
|
-using Avalonia.Threading;
|
|
|
-using ChunkyImageLib.DataHolders;
|
|
|
-using ChunkyImageLib.Operations;
|
|
|
-using PixiEditor.ChangeableDocument.Changeables.Animations;
|
|
|
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
|
|
|
-using Drawie.Backend.Core;
|
|
|
-using Drawie.Backend.Core.ColorsImpl;
|
|
|
-using Drawie.Backend.Core.Surfaces;
|
|
|
-using Drawie.Backend.Core.Surfaces.PaintImpl;
|
|
|
-using PixiEditor.Models.DocumentModels;
|
|
|
-using PixiEditor.Models.Handlers;
|
|
|
-using Drawie.Numerics;
|
|
|
-
|
|
|
-namespace PixiEditor.Models.Rendering;
|
|
|
-#nullable enable
|
|
|
-internal class CanvasUpdater
|
|
|
-{
|
|
|
- private readonly IDocument doc;
|
|
|
- private readonly DocumentInternalParts internals;
|
|
|
-
|
|
|
- private Dictionary<int, Texture> renderedFramesCache = new();
|
|
|
- private int lastRenderedFrameNumber = -1;
|
|
|
- private int lastOnionKeyFrames = -1;
|
|
|
- private double lastOnionOpacity = -1;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Affected chunks that have not been rerendered yet.
|
|
|
- /// </summary>
|
|
|
- private readonly Dictionary<ChunkResolution, HashSet<VecI>> affectedAndNonRerenderedChunks = new()
|
|
|
- {
|
|
|
- [ChunkResolution.Full] = new(),
|
|
|
- [ChunkResolution.Half] = new(),
|
|
|
- [ChunkResolution.Quarter] = new(),
|
|
|
- [ChunkResolution.Eighth] = new()
|
|
|
- };
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Affected chunks that have not been rerendered yet.
|
|
|
- /// Doesn't include chunks that were affected after the last time rerenderDelayed was true.
|
|
|
- /// </summary>
|
|
|
- private readonly Dictionary<ChunkResolution, HashSet<VecI>> nonRerenderedChunksAffectedBeforeLastRerenderDelayed =
|
|
|
- new()
|
|
|
- {
|
|
|
- [ChunkResolution.Full] = new(),
|
|
|
- [ChunkResolution.Half] = new(),
|
|
|
- [ChunkResolution.Quarter] = new(),
|
|
|
- [ChunkResolution.Eighth] = new()
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- public CanvasUpdater(IDocument doc, DocumentInternalParts internals)
|
|
|
- {
|
|
|
- this.doc = doc;
|
|
|
- this.internals = internals;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Don't call this outside ActionAccumulator
|
|
|
- /// </summary>
|
|
|
- public async Task UpdateGatheredChunks
|
|
|
- (AffectedAreasGatherer chunkGatherer, bool rerenderDelayed)
|
|
|
- {
|
|
|
- await Dispatcher.UIThread.InvokeAsync(() => Render(chunkGatherer, rerenderDelayed), DispatcherPriority.Background);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Don't call this outside ActionAccumulator
|
|
|
- /// </summary>
|
|
|
- public void UpdateGatheredChunksSync
|
|
|
- (AffectedAreasGatherer chunkGatherer, bool rerenderDelayed)
|
|
|
- {
|
|
|
- Render(chunkGatherer, rerenderDelayed);
|
|
|
- }
|
|
|
-
|
|
|
- private Dictionary<ChunkResolution, HashSet<VecI>> FindChunksVisibleOnViewports(bool onDelayed, bool all)
|
|
|
- {
|
|
|
- Dictionary<ChunkResolution, HashSet<VecI>> chunks = new()
|
|
|
- {
|
|
|
- [ChunkResolution.Full] = new(),
|
|
|
- [ChunkResolution.Half] = new(),
|
|
|
- [ChunkResolution.Quarter] = new(),
|
|
|
- [ChunkResolution.Eighth] = new()
|
|
|
- };
|
|
|
- foreach (var (_, viewport) in internals.State.Viewports)
|
|
|
- {
|
|
|
- if (onDelayed != viewport.Delayed && !all)
|
|
|
- continue;
|
|
|
-
|
|
|
- var viewportChunks = OperationHelper.FindChunksTouchingRectangle(
|
|
|
- viewport.Center,
|
|
|
- viewport.Dimensions,
|
|
|
- -viewport.Angle,
|
|
|
- ChunkResolution.Full.PixelSize());
|
|
|
- chunks[viewport.Resolution].UnionWith(viewportChunks);
|
|
|
- }
|
|
|
-
|
|
|
- return chunks;
|
|
|
- }
|
|
|
-
|
|
|
- private Dictionary<ChunkResolution, HashSet<VecI>> FindGlobalChunksToRerender(AffectedAreasGatherer areasGatherer,
|
|
|
- bool renderDelayed)
|
|
|
- {
|
|
|
- // find all affected non rerendered chunks
|
|
|
- var chunksToRerender = new Dictionary<ChunkResolution, HashSet<VecI>>();
|
|
|
- foreach (var (res, stored) in affectedAndNonRerenderedChunks)
|
|
|
- {
|
|
|
- chunksToRerender[res] = new HashSet<VecI>(stored);
|
|
|
- chunksToRerender[res].UnionWith(areasGatherer.MainImageArea.Chunks);
|
|
|
- }
|
|
|
-
|
|
|
- // find all chunks that would need to be rerendered if affected
|
|
|
- var chunksToMaybeRerender = FindChunksVisibleOnViewports(false, renderDelayed);
|
|
|
- if (!renderDelayed)
|
|
|
- {
|
|
|
- var chunksOnDelayedViewports = FindChunksVisibleOnViewports(true, false);
|
|
|
- foreach (var (res, stored) in nonRerenderedChunksAffectedBeforeLastRerenderDelayed)
|
|
|
- {
|
|
|
- chunksOnDelayedViewports[res].IntersectWith(stored);
|
|
|
- chunksToMaybeRerender[res].UnionWith(chunksOnDelayedViewports[res]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // find affected chunks that need to be rerendered right now
|
|
|
- foreach (var (res, toRerender) in chunksToRerender)
|
|
|
- {
|
|
|
- toRerender.IntersectWith(chunksToMaybeRerender[res]);
|
|
|
- }
|
|
|
-
|
|
|
- return chunksToRerender;
|
|
|
- }
|
|
|
-
|
|
|
- private void UpdateAffectedNonRerenderedChunks(Dictionary<ChunkResolution, HashSet<VecI>> chunksToRerender,
|
|
|
- AffectedArea chunkGathererAffectedArea)
|
|
|
- {
|
|
|
- if (chunkGathererAffectedArea.Chunks.Count > 0)
|
|
|
- {
|
|
|
- foreach (var (res, chunks) in chunksToRerender)
|
|
|
- {
|
|
|
- affectedAndNonRerenderedChunks[res].UnionWith(chunkGathererAffectedArea.Chunks);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var (res, chunks) in chunksToRerender)
|
|
|
- {
|
|
|
- affectedAndNonRerenderedChunks[res].ExceptWith(chunks);
|
|
|
- nonRerenderedChunksAffectedBeforeLastRerenderDelayed[res].ExceptWith(chunks);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void Render(AffectedAreasGatherer chunkGatherer, bool rerenderDelayed)
|
|
|
- {
|
|
|
- Dictionary<ChunkResolution, HashSet<VecI>> chunksToRerender =
|
|
|
- FindGlobalChunksToRerender(chunkGatherer, rerenderDelayed);
|
|
|
-
|
|
|
- ChunkResolution onionSkinResolution = chunksToRerender.Min(x => x.Key);
|
|
|
-
|
|
|
- bool updatingStoredChunks = false;
|
|
|
- foreach (var (res, stored) in affectedAndNonRerenderedChunks)
|
|
|
- {
|
|
|
- HashSet<VecI> storedCopy = new HashSet<VecI>(stored);
|
|
|
- storedCopy.IntersectWith(chunksToRerender[res]);
|
|
|
- if (storedCopy.Count > 0)
|
|
|
- {
|
|
|
- updatingStoredChunks = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- UpdateAffectedNonRerenderedChunks(chunksToRerender, chunkGatherer.MainImageArea);
|
|
|
-
|
|
|
- bool anythingToUpdate = false;
|
|
|
- foreach (var (_, chunks) in chunksToRerender)
|
|
|
- {
|
|
|
- anythingToUpdate |= chunks.Count > 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (!anythingToUpdate)
|
|
|
- return;
|
|
|
-
|
|
|
- UpdateMainImage(chunksToRerender, updatingStoredChunks ? null : chunkGatherer.MainImageArea.GlobalArea.Value);
|
|
|
- }
|
|
|
-
|
|
|
- private void UpdateMainImage(Dictionary<ChunkResolution, HashSet<VecI>> chunksToRerender,
|
|
|
- RectI? globalClippingRectangle)
|
|
|
- {
|
|
|
- if (chunksToRerender.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- foreach (var (resolution, chunks) in chunksToRerender)
|
|
|
- {
|
|
|
- int chunkSize = resolution.PixelSize();
|
|
|
- RectI? globalScaledClippingRectangle = null;
|
|
|
- if (globalClippingRectangle is not null)
|
|
|
- globalScaledClippingRectangle =
|
|
|
- (RectI?)((RectI)globalClippingRectangle).Scale(resolution.Multiplier()).RoundOutwards();
|
|
|
-
|
|
|
- foreach (var chunkPos in chunks)
|
|
|
- {
|
|
|
- RenderChunk(chunkPos, resolution);
|
|
|
- RectI chunkRect = new(chunkPos * chunkSize, new(chunkSize, chunkSize));
|
|
|
- if (globalScaledClippingRectangle is RectI rect)
|
|
|
- chunkRect = chunkRect.Intersect(rect);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RenderChunk(VecI chunkPos, ChunkResolution resolution)
|
|
|
- {
|
|
|
- doc.Renderer.UpdateChunk(chunkPos, resolution, doc.AnimationHandler.ActiveFrameTime);
|
|
|
- }
|
|
|
-}
|