From 8795be2cde9e1c7941aceaa8a1582b31a4367e15 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 6 Dec 2019 23:00:30 +0100 Subject: [PATCH] Rendering improvements --- .../Models/Profile/Abstract/ProfileElement.cs | 2 +- src/Artemis.Core/Models/Profile/Folder.cs | 4 +- src/Artemis.Core/Models/Profile/Layer.cs | 70 +++++-------------- src/Artemis.Core/Models/Profile/Profile.cs | 4 +- .../Plugins/Abstract/ProfileModule.cs | 2 +- .../Plugins/LayerElement/LayerElement.cs | 23 +++--- .../RotationLayerElement.cs | 12 +--- .../SlideLayerElement.cs | 6 +- .../NoiseLayerElement.cs | 25 ++++--- .../BrushLayerElement.cs | 10 +-- .../Visualization/ProfileViewModel.cs | 12 +++- 11 files changed, 71 insertions(+), 99 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs b/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs index ca259edd1..d007b077b 100644 --- a/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/Abstract/ProfileElement.cs @@ -45,7 +45,7 @@ namespace Artemis.Core.Models.Profile.Abstract /// /// Renders the element /// - public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas); + public abstract void Render(double deltaTime, SKCanvas canvas); /// /// Applies the profile element's properties to the underlying storage entity diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 5d72238ee..c23d6bf1d 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -56,11 +56,11 @@ namespace Artemis.Core.Models.Profile profileElement.Update(deltaTime); } - public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) + public override void Render(double deltaTime, SKCanvas canvas) { // Folders don't render but their children do foreach (var profileElement in Children) - profileElement.Render(deltaTime, surface, canvas); + profileElement.Render(deltaTime, canvas); } public Folder AddFolder(string name) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index ff30fffb9..7a1ab2b63 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -6,7 +6,6 @@ using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.Abstract; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; -using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; using SkiaSharp; @@ -17,8 +16,6 @@ namespace Artemis.Core.Models.Profile { private readonly List _layerElements; private List _leds; - private SKBitmap _renderBitmap; - private SKCanvas _renderCanvas; public Layer(Profile profile, ProfileElement parent, string name) { @@ -58,47 +55,33 @@ namespace Artemis.Core.Models.Profile public override void Update(double deltaTime) { - foreach (var layerElement in LayerElements) - layerElement.Update(deltaTime); + lock (_layerElements) + { + foreach (var layerElement in LayerElements) + layerElement.Update(deltaTime); + } } - public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) + public override void Render(double deltaTime, SKCanvas canvas) { if (RenderPath == null) return; - // TODO Just lock the whole thing, this is asking for deadlock - lock (_renderBitmap) + lock (_layerElements) { - lock (LayerElements) + canvas.Save(); + using (var framePath = new SKPath(RenderPath)) { - canvas.Save(); + canvas.ClipPath(framePath); + foreach (var layerElement in LayerElements) - layerElement.RenderPreProcess(surface, canvas); - - _renderCanvas.Clear(); + layerElement.RenderPreProcess(framePath, canvas); foreach (var layerElement in LayerElements) - layerElement.Render(surface, _renderCanvas); - - var baseShader = SKShader.CreateBitmap(_renderBitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat, SKMatrix.MakeTranslation(RenderRectangle.Left, RenderRectangle.Top)); + layerElement.Render(framePath, canvas); foreach (var layerElement in LayerElements) - { - var newBaseShader = layerElement.RenderPostProcess(surface, _renderBitmap, baseShader); - if (newBaseShader == null) - continue; - - // Dispose the old base shader if the layer element provided a new one - if (!ReferenceEquals(baseShader, newBaseShader)) - baseShader.Dispose(); - - baseShader = newBaseShader; - } - - canvas.ClipPath(RenderPath); - canvas.DrawRect(RenderRectangle, new SKPaint {Shader = baseShader, FilterQuality = SKFilterQuality.Low}); - baseShader.Dispose(); - canvas.Restore(); + layerElement.RenderPostProcess(framePath, canvas); } + canvas.Restore(); } } @@ -165,7 +148,7 @@ namespace Artemis.Core.Models.Profile internal void AddLayerElement(LayerElement layerElement) { - lock (LayerElements) + lock (_layerElements) { _layerElements.Add(layerElement); } @@ -173,7 +156,7 @@ namespace Artemis.Core.Models.Profile internal void RemoveLayerElement(LayerElement layerElement) { - lock (LayerElements) + lock (_layerElements) { _layerElements.Remove(layerElement); } @@ -216,25 +199,6 @@ namespace Artemis.Core.Models.Profile path.AddRect(artemisLed.AbsoluteRenderRectangle); RenderPath = path; - - if (_renderBitmap != null) - { - lock (_renderBitmap) - { - var oldBitmap = _renderBitmap; - var oldCanvas = _renderCanvas; - _renderBitmap = new SKBitmap(new SKImageInfo((int) RenderRectangle.Width, (int) RenderRectangle.Height)); - _renderCanvas = new SKCanvas(_renderBitmap); - oldBitmap?.Dispose(); - oldCanvas?.Dispose(); - } - } - else - { - _renderBitmap = new SKBitmap(new SKImageInfo((int) RenderRectangle.Width, (int) RenderRectangle.Height)); - _renderCanvas = new SKCanvas(_renderBitmap); - } - OnRenderPropertiesUpdated(); } diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 640e7c4a8..bd0c57909 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -56,7 +56,7 @@ namespace Artemis.Core.Models.Profile } } - public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas) + public override void Render(double deltaTime, SKCanvas canvas) { lock (this) { @@ -64,7 +64,7 @@ namespace Artemis.Core.Models.Profile throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (var profileElement in Children) - profileElement.Render(deltaTime, surface, canvas); + profileElement.Render(deltaTime, canvas); } } diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 10954ed4f..7fd760828 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -31,7 +31,7 @@ namespace Artemis.Core.Plugins.Abstract lock (this) { // Render the profile - ActiveProfile?.Render(deltaTime, surface, canvas); + ActiveProfile?.Render(deltaTime, canvas); } } diff --git a/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs b/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs index 6eea5d068..bb5701182 100644 --- a/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs +++ b/src/Artemis.Core/Plugins/LayerElement/LayerElement.cs @@ -1,6 +1,5 @@ using System; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; using SkiaSharp; namespace Artemis.Core.Plugins.LayerElement @@ -20,6 +19,10 @@ namespace Artemis.Core.Plugins.LayerElement public LayerElementSettings Settings { get; } public LayerElementDescriptor Descriptor { get; } + public virtual void Dispose() + { + } + /// /// Called by the profile editor to populate the layer element properties panel /// @@ -38,9 +41,9 @@ namespace Artemis.Core.Plugins.LayerElement /// Allows you to perform rendering on the surface before any layer-clipping is applied /// Called before rendering, in the order configured on the layer /// - /// The surface the layer is rendered on + /// /// The entire surface canvas - public virtual void RenderPreProcess(ArtemisSurface surface, SKCanvas canvas) + public virtual void RenderPreProcess(SKPath framePath, SKCanvas canvas) { } @@ -49,9 +52,9 @@ namespace Artemis.Core.Plugins.LayerElement /// and matches it's width and height. /// Called during rendering, in the order configured on the layer /// - /// The surface the layer is rendered on + /// /// The layer canvas - public virtual void Render(ArtemisSurface surface, SKCanvas canvas) + public virtual void Render(SKPath framePath, SKCanvas canvas) { } @@ -60,16 +63,12 @@ namespace Artemis.Core.Plugins.LayerElement /// . /// Called after rendering, in the order configured on the layer. /// - /// The surface the layer is rendered on + /// + /// /// The bitmap created from the layer canvas /// The current shader used to draw the bitmap on the surface canvas /// The resulting shader used to draw the bitmap on the surface canvas - public virtual SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) - { - return shader; - } - - public virtual void Dispose() + public virtual void RenderPostProcess(SKPath framePath, SKCanvas canvas) { } } diff --git a/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs b/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs index 89dac09c2..08f4f9953 100644 --- a/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs +++ b/src/Artemis.Plugins.LayerElements.Animations/RotationLayerElement.cs @@ -1,6 +1,5 @@ using System; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; using SkiaSharp; @@ -26,15 +25,10 @@ namespace Artemis.Plugins.LayerElements.Animations Rotation = 0; } - public override SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) + public override void RenderPreProcess(SKPath framePath, SKCanvas canvas) { - var rect = Layer.AbsoluteRenderRectangle; - var center = new SKPoint(rect.MidX, rect.MidY); - - var required = (float) Math.Sqrt(rect.Width * rect.Width + rect.Height * rect.Height); - var minSide = Math.Min(rect.Width, rect.Height); - var scale = required / minSide; - return SKShader.CreateLocalMatrix(SKShader.CreateLocalMatrix(shader, SKMatrix.MakeScale(scale, scale, center.X, center.Y)), SKMatrix.MakeRotationDegrees(Rotation, center.X, center.Y)); + canvas.RotateDegrees(Rotation, Layer.RenderRectangle.MidX, Layer.RenderRectangle.MidY); + framePath.Transform(SKMatrix.MakeRotationDegrees(Rotation * -1, Layer.RenderRectangle.MidX, Layer.RenderRectangle.MidY)); } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs b/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs index 8f471d319..9eb9bd5dd 100644 --- a/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs +++ b/src/Artemis.Plugins.LayerElements.Animations/SlideLayerElement.cs @@ -1,6 +1,5 @@ using System; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; using SkiaSharp; @@ -26,9 +25,10 @@ namespace Artemis.Plugins.LayerElements.Animations MovePercentage = 0; } - public override SKShader RenderPostProcess(ArtemisSurface surface, SKBitmap bitmap, SKShader shader) + public override void RenderPreProcess(SKPath framePath, SKCanvas canvas) { - return SKShader.CreateLocalMatrix(shader, SKMatrix.MakeTranslation(Layer.RenderRectangle.Width / 100 * MovePercentage * -1, 0)); + canvas.Translate(Layer.RenderRectangle.Width / 100 * MovePercentage * -1, 0); + framePath.Transform(SKMatrix.MakeTranslation(Layer.RenderRectangle.Width / 100 * MovePercentage, 0)); } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerElements.Noise/NoiseLayerElement.cs b/src/Artemis.Plugins.LayerElements.Noise/NoiseLayerElement.cs index 3610e6619..e6afc64f6 100644 --- a/src/Artemis.Plugins.LayerElements.Noise/NoiseLayerElement.cs +++ b/src/Artemis.Plugins.LayerElements.Noise/NoiseLayerElement.cs @@ -1,6 +1,5 @@ using System; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerElement; using SkiaSharp; @@ -11,12 +10,13 @@ namespace Artemis.Plugins.LayerElements.Noise private static Random _rand = new Random(); private readonly OpenSimplexNoise _noise; private float _z; + private const int Scale = 6; public NoiseLayerElement(Layer layer, Guid guid, NoiseLayerElementSettings settings, LayerElementDescriptor descriptor) : base(layer, guid, settings, descriptor) { Settings = settings; - _z = 1; + _z = _rand.Next(0, 4096); _noise = new OpenSimplexNoise(Guid.GetHashCode()); } @@ -39,25 +39,32 @@ namespace Artemis.Plugins.LayerElements.Noise return new NoiseLayerElementViewModel(this); } - public override void Render(ArtemisSurface surface, SKCanvas canvas) + public override void Render(SKPath framePath, SKCanvas canvas) { // Scale down the render path - var width = Layer.AbsoluteRenderRectangle.Width / 4; - var height = Layer.AbsoluteRenderRectangle.Height / 4; + var width = (int) Math.Round(Math.Max(Layer.RenderRectangle.Width, Layer.RenderRectangle.Height) / Scale); + var height = (int) Math.Round(Math.Max(Layer.RenderRectangle.Width, Layer.RenderRectangle.Height) / Scale); - using (var bitmap = new SKBitmap(new SKImageInfo((int) Layer.AbsoluteRenderRectangle.Width, (int) Layer.AbsoluteRenderRectangle.Height))) + using (var bitmap = new SKBitmap(new SKImageInfo(width, height))) { for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { - // Not setting pixels outside the layer clip would be faster but right now rotation takes place after the rendering, must change that first + // This check is actually more expensive then _noise.Evaluate() ^.^' + // if (!framePath.Contains(x * Scale, y * Scale)) + // continue; + var v = _noise.Evaluate(Settings.XScale * x / width, Settings.YScale * y / height, _z); bitmap.SetPixel(x, y, new SKColor(0, 0, 0, (byte) ((v + 1) * 127))); } } - - canvas.DrawBitmap(bitmap, SKRect.Create(0, 0, width, height), Layer.AbsoluteRenderRectangle, new SKPaint {BlendMode = Settings.BlendMode}); + + using (var sh = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror, SKMatrix.MakeScale(Scale, Scale))) + using (var paint = new SKPaint {Shader = sh}) + { + canvas.DrawPath(framePath, paint); + } } } } diff --git a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs index 0e3f570f0..b0571d11d 100644 --- a/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs +++ b/src/Artemis.Plugins.LayerTypes.Brush/BrushLayerElement.cs @@ -34,7 +34,7 @@ namespace Artemis.Plugins.LayerElements.Brush private void CreateShader() { - var center = new SKPoint(Layer.AbsoluteRenderRectangle.MidX, Layer.AbsoluteRenderRectangle.MidY); + var center = new SKPoint(Layer.RenderRectangle.MidX, Layer.RenderRectangle.MidY); SKShader shader; switch (Settings.BrushType) { @@ -42,10 +42,10 @@ namespace Artemis.Plugins.LayerElements.Brush shader = SKShader.CreateColor(_testColors.First()); break; case BrushType.LinearGradient: - shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.AbsoluteRenderRectangle.Width, 0), _testColors.ToArray(), SKShaderTileMode.Clamp); + shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.RenderRectangle.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case BrushType.RadialGradient: - shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.AbsoluteRenderRectangle.Width, Layer.AbsoluteRenderRectangle.Height), _testColors.ToArray(), SKShaderTileMode.Clamp); + shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.RenderRectangle.Width, Layer.RenderRectangle.Height), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case BrushType.SweepGradient: shader = SKShader.CreateSweepGradient(center, _testColors.ToArray(), null, SKShaderTileMode.Clamp, 0, 360); @@ -69,9 +69,9 @@ namespace Artemis.Plugins.LayerElements.Brush return new BrushLayerElementViewModel(this); } - public override void Render(ArtemisSurface surface, SKCanvas canvas) + public override void Render(SKPath framePath, SKCanvas canvas) { - canvas.DrawRect(Layer.AbsoluteRenderRectangle, _paint); + canvas.DrawPath(framePath, _paint); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index b05ac4275..00a656046 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows; @@ -81,8 +82,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void ApplySurfaceConfiguration(ArtemisSurface surface) { + List devices; + lock (Devices) + { + devices = new List(); + devices.AddRange(surface.Devices); + } + // Make sure all devices have an up-to-date VM - foreach (var surfaceDeviceConfiguration in surface.Devices) + foreach (var surfaceDeviceConfiguration in devices) { // Create VMs for missing devices var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice); @@ -104,7 +112,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization else viewModel.Device = surfaceDeviceConfiguration; } - + // Sort the devices by ZIndex Execute.PostToUIThread(() => {