diff --git a/src/Artemis.Core/Extensions/SKPaintExtensions.cs b/src/Artemis.Core/Extensions/SKPaintExtensions.cs new file mode 100644 index 000000000..ec2ebe6e2 --- /dev/null +++ b/src/Artemis.Core/Extensions/SKPaintExtensions.cs @@ -0,0 +1,16 @@ +using SkiaSharp; + +namespace Artemis.Core +{ + internal static class SKPaintExtensions + { + internal static void DisposeSelfAndProperties(this SKPaint paint) + { + paint.ImageFilter?.Dispose(); + paint.ColorFilter?.Dispose(); + paint.MaskFilter?.Dispose(); + paint.Shader?.Dispose(); + paint.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 0c8b909eb..1d273c71b 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -168,7 +168,7 @@ namespace Artemis.Core #region Rendering /// - public override void Render(SKCanvas canvas) + public override void Render(SKCanvas canvas, SKPoint basePosition) { if (Disposed) throw new ObjectDisposedException("Folder"); @@ -189,41 +189,38 @@ namespace Artemis.Core baseLayerEffect.Update(Timeline.Delta.TotalSeconds); } + SKPaint layerPaint = new(); try { - canvas.Save(); - Renderer.Open(Path, Parent as Folder); - if (Renderer.Surface == null || Renderer.Path == null || Renderer.Paint == null) - throw new ArtemisCoreException("Failed to open folder render context"); - - SKRect rendererBounds = Renderer.Path.Bounds; + SKRect rendererBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(Renderer.Surface.Canvas, rendererBounds, Renderer.Paint); + baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint); + + canvas.SaveLayer(layerPaint); + canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y); // If required, apply the opacity override of the module to the root folder if (IsRootFolder && Profile.Module.OpacityOverride < 1) { double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); - Renderer.Paint.Color = Renderer.Paint.Color.WithAlpha((byte) (Renderer.Paint.Color.Alpha * multiplier)); + layerPaint.Color = layerPaint.Color.WithAlpha((byte) (layerPaint.Color.Alpha * multiplier)); } // No point rendering if the alpha was set to zero by one of the effects - if (Renderer.Paint.Color.Alpha == 0) + if (layerPaint.Color.Alpha == 0) return; // Iterate the children in reverse because the first layer must be rendered last to end up on top for (int index = Children.Count - 1; index > -1; index--) - Children[index].Render(Renderer.Surface.Canvas); + Children[index].Render(canvas, new SKPoint(Path.Bounds.Left, Path.Bounds.Top)); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(Renderer.Surface.Canvas, rendererBounds, Renderer.Paint); - - canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint); + baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint); } finally { canvas.Restore(); - Renderer.Close(); + layerPaint.DisposeSelfAndProperties(); } Timeline.ClearDelta(); @@ -239,7 +236,6 @@ namespace Artemis.Core foreach (ProfileElement profileElement in Children) profileElement.Dispose(); - Renderer.Dispose(); base.Dispose(disposing); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 2208c7d0e..3feac187a 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -154,7 +154,6 @@ namespace Artemis.Core _layerBrush?.Dispose(); _general.Dispose(); _transform.Dispose(); - Renderer.Dispose(); base.Dispose(disposing); } @@ -183,7 +182,7 @@ namespace Artemis.Core General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet; ApplyShapeType(); ActivateLayerBrush(); - + Reset(); } @@ -278,7 +277,7 @@ namespace Artemis.Core } /// - public override void Render(SKCanvas canvas) + public override void Render(SKCanvas canvas, SKPoint basePosition) { if (Disposed) throw new ObjectDisposedException("Layer"); @@ -290,9 +289,9 @@ namespace Artemis.Core if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false) return; - RenderTimeline(Timeline, canvas); + RenderTimeline(Timeline, canvas, basePosition); foreach (Timeline extraTimeline in Timeline.ExtraTimelines.ToList()) - RenderTimeline(extraTimeline, canvas); + RenderTimeline(extraTimeline, canvas, basePosition); Timeline.ClearDelta(); } @@ -313,7 +312,7 @@ namespace Artemis.Core } } - private void RenderTimeline(Timeline timeline, SKCanvas canvas) + private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPoint basePosition) { if (Path == null || LayerBrush == null) throw new ArtemisCoreException("The layer is not yet ready for rendering"); @@ -322,26 +321,31 @@ namespace Artemis.Core return; ApplyTimeline(timeline); - + if (LayerBrush?.BrushType != LayerBrushType.Regular) return; + SKPaint layerPaint = new(); try { canvas.Save(); - Renderer.Open(Path, Parent as Folder); - if (Renderer.Surface == null || Renderer.Path == null || Renderer.Paint == null) - throw new ArtemisCoreException("Failed to open layer render context"); + canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y); + using SKPath clipPath = new(Path); + clipPath.Transform(SKMatrix.CreateTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1)); + canvas.ClipPath(clipPath); + + SKRect layerBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height); // Apply blend mode and color - Renderer.Paint.BlendMode = General.BlendMode.CurrentValue; - Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); + layerPaint.BlendMode = General.BlendMode.CurrentValue; + layerPaint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); using SKPath renderPath = new(); + if (General.ShapeType.CurrentValue == LayerShapeType.Rectangle) - renderPath.AddRect(Renderer.Path.Bounds); + renderPath.AddRect(layerBounds); else - renderPath.AddOval(Renderer.Path.Bounds); + renderPath.AddOval(layerBounds); if (General.TransformMode.CurrentValue == LayerTransformMode.Normal) { @@ -356,54 +360,50 @@ namespace Artemis.Core if (LayerBrush.SupportsTransformation) { SKMatrix rotationMatrix = GetTransformMatrix(true, false, false, true); - Renderer.Surface.Canvas.SetMatrix(Renderer.Surface.Canvas.TotalMatrix.PreConcat(rotationMatrix)); + canvas.SetMatrix(canvas.TotalMatrix.PreConcat(rotationMatrix)); } - // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off - Renderer.Surface.Canvas.ClipPath(renderPath); - DelegateRendering(renderPath.Bounds); + DelegateRendering(canvas, renderPath, renderPath.Bounds, layerPaint); } else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip) { SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, true); renderPath.Transform(renderPathMatrix); - // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off - Renderer.Surface.Canvas.ClipPath(renderPath); - DelegateRendering(Renderer.Path.Bounds); + DelegateRendering(canvas, renderPath, layerBounds, layerPaint); } - - canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint); } finally { - try - { - canvas.Restore(); - } - catch - { - // ignored - } - - Renderer.Close(); + canvas.Restore(); + layerPaint.DisposeSelfAndProperties(); } } - private void DelegateRendering(SKRect bounds) + private void DelegateRendering(SKCanvas canvas, SKPath renderPath, SKRect bounds, SKPaint layerPaint) { if (LayerBrush == null) throw new ArtemisCoreException("The layer is not yet ready for rendering"); - if (Renderer.Surface == null || Renderer.Paint == null) - throw new ArtemisCoreException("Failed to open layer render context"); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(Renderer.Surface.Canvas, bounds, Renderer.Paint); + baseLayerEffect.PreProcess(canvas, bounds, layerPaint); - LayerBrush.InternalRender(Renderer.Surface.Canvas, bounds, Renderer.Paint); + try + { + canvas.SaveLayer(layerPaint); - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(Renderer.Surface.Canvas, bounds, Renderer.Paint); + // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off + canvas.ClipPath(renderPath); + LayerBrush.InternalRender(canvas, bounds, layerPaint); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PostProcess(canvas, bounds, layerPaint); + } + + finally + { + canvas.Restore(); + } } internal void CalculateRenderProperties() diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 1432d3157..96616f104 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -81,7 +81,7 @@ namespace Artemis.Core } /// - public override void Render(SKCanvas canvas) + public override void Render(SKCanvas canvas, SKPoint basePosition) { lock (_lock) { @@ -91,7 +91,7 @@ namespace Artemis.Core throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (ProfileElement profileElement in Children) - profileElement.Render(canvas); + profileElement.Render(canvas, basePosition); } } diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 086a27886..1072e4a94 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -104,7 +104,7 @@ namespace Artemis.Core /// /// Renders the element /// - public abstract void Render(SKCanvas canvas); + public abstract void Render(SKCanvas canvas, SKPoint basePosition); /// /// Resets the internal state of the element diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 7af947574..899a01ff9 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -19,7 +19,6 @@ namespace Artemis.Core internal RenderProfileElement(Profile profile) : base(profile) { Timeline = new Timeline(); - Renderer = new Renderer(); ExpandedPropertyGroups = new List(); LayerEffectsList = new List(); @@ -127,7 +126,6 @@ namespace Artemis.Core { base.Parent = value; OnPropertyChanged(nameof(Parent)); - Renderer.Invalidate(); } } @@ -144,7 +142,6 @@ namespace Artemis.Core // I can't really be sure about the performance impact of calling Bounds often but // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive Bounds = value?.Bounds ?? SKRect.Empty; - Renderer.Invalidate(); } } @@ -157,7 +154,6 @@ namespace Artemis.Core private set => SetAndNotify(ref _bounds, value); } - internal Renderer Renderer { get; } #region Property group expansion diff --git a/src/Artemis.Core/Models/Profile/Renderer.cs b/src/Artemis.Core/Models/Profile/Renderer.cs index f8cd1025e..4de0fa2d5 100644 --- a/src/Artemis.Core/Models/Profile/Renderer.cs +++ b/src/Artemis.Core/Models/Profile/Renderer.cs @@ -108,5 +108,11 @@ namespace Artemis.Core _disposed = true; } + + ~Renderer() + { + if (IsOpen) + Close(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index ed9201809..fb1a8ccad 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -177,7 +177,7 @@ namespace Artemis.Core.Modules lock (_lock) { // Render the profile - ActiveProfile?.Render(canvas); + ActiveProfile?.Render(canvas, SKPoint.Empty); } ProfileRendered(deltaTime, canvas, canvasInfo); diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 4b6d36ffd..06cd3d765 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -30,7 +30,7 @@ namespace Artemis.Core public void Render(double deltaTime, SKCanvas canvas) { AnimationProfile.Update(deltaTime); - AnimationProfile.Render(canvas); + AnimationProfile.Render(canvas, SKPoint.Empty); } private Profile CreateIntroProfile() diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index 6e0f9cdf2..d7532cb2c 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -90,9 +90,24 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) { + using SKImage skImage = e.Texture.Surface.Snapshot(); + SKImageInfo bitmapInfo = e.Texture.ImageInfo; + + if (_frameTargetPath != null) + { + using (SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100)) + { + using (FileStream stream = File.OpenWrite(_frameTargetPath)) + { + data.SaveTo(stream); + } + } + + _frameTargetPath = null; + } + Execute.OnUIThreadSync(() => { - SKImageInfo bitmapInfo = e.Texture.ImageInfo; RenderHeight = bitmapInfo.Height; RenderWidth = bitmapInfo.Width; @@ -103,25 +118,10 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs return; } - using SKImage skImage = e.Texture.Surface.Snapshot(); - - if (_frameTargetPath != null) - { - using (SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100)) - { - using (FileStream stream = File.OpenWrite(_frameTargetPath)) - { - data.SaveTo(stream); - } - } - - _frameTargetPath = null; - } - - SKImageInfo info = new(skImage.Width, skImage.Height); writable.Lock(); - using (SKPixmap pixmap = new(info, writable.BackBuffer, writable.BackBufferStride)) + using (SKPixmap pixmap = new(bitmapInfo, writable.BackBuffer, writable.BackBufferStride)) { + // ReSharper disable once AccessToDisposedClosure - Looks fine skImage.ReadPixels(pixmap, 0, 0); } @@ -140,6 +140,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs _frames = 0; _frameCountStart = DateTime.Now; } + _frames++; } } diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings index ca870ba3a..3a843dade 100644 --- a/src/Artemis.sln.DotSettings +++ b/src/Artemis.sln.DotSettings @@ -225,6 +225,7 @@ True True True + True True