1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Rendering - Made the way layering is rendered 99.5% less stupid

This commit is contained in:
Robert 2021-03-23 00:06:58 +01:00
parent c444ff4e59
commit fa49424cf6
11 changed files with 99 additions and 83 deletions

View File

@ -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();
}
}
}

View File

@ -168,7 +168,7 @@ namespace Artemis.Core
#region Rendering #region Rendering
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas) public override void Render(SKCanvas canvas, SKPoint basePosition)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
@ -189,41 +189,38 @@ namespace Artemis.Core
baseLayerEffect.Update(Timeline.Delta.TotalSeconds); baseLayerEffect.Update(Timeline.Delta.TotalSeconds);
} }
SKPaint layerPaint = new();
try try
{ {
canvas.Save(); SKRect rendererBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height);
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;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) 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 required, apply the opacity override of the module to the root folder
if (IsRootFolder && Profile.Module.OpacityOverride < 1) if (IsRootFolder && Profile.Module.OpacityOverride < 1)
{ {
double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); 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 // 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; return;
// Iterate the children in reverse because the first layer must be rendered last to end up on top // 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--) 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)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(Renderer.Surface.Canvas, rendererBounds, Renderer.Paint); baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint);
canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint);
} }
finally finally
{ {
canvas.Restore(); canvas.Restore();
Renderer.Close(); layerPaint.DisposeSelfAndProperties();
} }
Timeline.ClearDelta(); Timeline.ClearDelta();
@ -239,7 +236,6 @@ namespace Artemis.Core
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Dispose(); profileElement.Dispose();
Renderer.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -154,7 +154,6 @@ namespace Artemis.Core
_layerBrush?.Dispose(); _layerBrush?.Dispose();
_general.Dispose(); _general.Dispose();
_transform.Dispose(); _transform.Dispose();
Renderer.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -183,7 +182,7 @@ namespace Artemis.Core
General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet; General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet;
ApplyShapeType(); ApplyShapeType();
ActivateLayerBrush(); ActivateLayerBrush();
Reset(); Reset();
} }
@ -278,7 +277,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas) public override void Render(SKCanvas canvas, SKPoint basePosition)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
@ -290,9 +289,9 @@ namespace Artemis.Core
if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false) if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false)
return; return;
RenderTimeline(Timeline, canvas); RenderTimeline(Timeline, canvas, basePosition);
foreach (Timeline extraTimeline in Timeline.ExtraTimelines.ToList()) foreach (Timeline extraTimeline in Timeline.ExtraTimelines.ToList())
RenderTimeline(extraTimeline, canvas); RenderTimeline(extraTimeline, canvas, basePosition);
Timeline.ClearDelta(); 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) if (Path == null || LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering"); throw new ArtemisCoreException("The layer is not yet ready for rendering");
@ -322,26 +321,31 @@ namespace Artemis.Core
return; return;
ApplyTimeline(timeline); ApplyTimeline(timeline);
if (LayerBrush?.BrushType != LayerBrushType.Regular) if (LayerBrush?.BrushType != LayerBrushType.Regular)
return; return;
SKPaint layerPaint = new();
try try
{ {
canvas.Save(); canvas.Save();
Renderer.Open(Path, Parent as Folder); canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y);
if (Renderer.Surface == null || Renderer.Path == null || Renderer.Paint == null) using SKPath clipPath = new(Path);
throw new ArtemisCoreException("Failed to open layer render context"); 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 // Apply blend mode and color
Renderer.Paint.BlendMode = General.BlendMode.CurrentValue; layerPaint.BlendMode = General.BlendMode.CurrentValue;
Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); layerPaint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f));
using SKPath renderPath = new(); using SKPath renderPath = new();
if (General.ShapeType.CurrentValue == LayerShapeType.Rectangle) if (General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
renderPath.AddRect(Renderer.Path.Bounds); renderPath.AddRect(layerBounds);
else else
renderPath.AddOval(Renderer.Path.Bounds); renderPath.AddOval(layerBounds);
if (General.TransformMode.CurrentValue == LayerTransformMode.Normal) if (General.TransformMode.CurrentValue == LayerTransformMode.Normal)
{ {
@ -356,54 +360,50 @@ namespace Artemis.Core
if (LayerBrush.SupportsTransformation) if (LayerBrush.SupportsTransformation)
{ {
SKMatrix rotationMatrix = GetTransformMatrix(true, false, false, true); 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 DelegateRendering(canvas, renderPath, renderPath.Bounds, layerPaint);
Renderer.Surface.Canvas.ClipPath(renderPath);
DelegateRendering(renderPath.Bounds);
} }
else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip) else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip)
{ {
SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, true); SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, true);
renderPath.Transform(renderPathMatrix); renderPath.Transform(renderPathMatrix);
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off DelegateRendering(canvas, renderPath, layerBounds, layerPaint);
Renderer.Surface.Canvas.ClipPath(renderPath);
DelegateRendering(Renderer.Path.Bounds);
} }
canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint);
} }
finally finally
{ {
try canvas.Restore();
{ layerPaint.DisposeSelfAndProperties();
canvas.Restore();
}
catch
{
// ignored
}
Renderer.Close();
} }
} }
private void DelegateRendering(SKRect bounds) private void DelegateRendering(SKCanvas canvas, SKPath renderPath, SKRect bounds, SKPaint layerPaint)
{ {
if (LayerBrush == null) if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering"); 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)) 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)) // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
baseLayerEffect.PostProcess(Renderer.Surface.Canvas, bounds, Renderer.Paint); 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() internal void CalculateRenderProperties()

View File

@ -81,7 +81,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas) public override void Render(SKCanvas canvas, SKPoint basePosition)
{ {
lock (_lock) lock (_lock)
{ {
@ -91,7 +91,7 @@ namespace Artemis.Core
throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Render(canvas); profileElement.Render(canvas, basePosition);
} }
} }

View File

@ -104,7 +104,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Renders the element /// Renders the element
/// </summary> /// </summary>
public abstract void Render(SKCanvas canvas); public abstract void Render(SKCanvas canvas, SKPoint basePosition);
/// <summary> /// <summary>
/// Resets the internal state of the element /// Resets the internal state of the element

View File

@ -19,7 +19,6 @@ namespace Artemis.Core
internal RenderProfileElement(Profile profile) : base(profile) internal RenderProfileElement(Profile profile) : base(profile)
{ {
Timeline = new Timeline(); Timeline = new Timeline();
Renderer = new Renderer();
ExpandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
@ -127,7 +126,6 @@ namespace Artemis.Core
{ {
base.Parent = value; base.Parent = value;
OnPropertyChanged(nameof(Parent)); 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 // 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 // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive
Bounds = value?.Bounds ?? SKRect.Empty; Bounds = value?.Bounds ?? SKRect.Empty;
Renderer.Invalidate();
} }
} }
@ -157,7 +154,6 @@ namespace Artemis.Core
private set => SetAndNotify(ref _bounds, value); private set => SetAndNotify(ref _bounds, value);
} }
internal Renderer Renderer { get; }
#region Property group expansion #region Property group expansion

View File

@ -108,5 +108,11 @@ namespace Artemis.Core
_disposed = true; _disposed = true;
} }
~Renderer()
{
if (IsOpen)
Close();
}
} }
} }

View File

@ -177,7 +177,7 @@ namespace Artemis.Core.Modules
lock (_lock) lock (_lock)
{ {
// Render the profile // Render the profile
ActiveProfile?.Render(canvas); ActiveProfile?.Render(canvas, SKPoint.Empty);
} }
ProfileRendered(deltaTime, canvas, canvasInfo); ProfileRendered(deltaTime, canvas, canvasInfo);

View File

@ -30,7 +30,7 @@ namespace Artemis.Core
public void Render(double deltaTime, SKCanvas canvas) public void Render(double deltaTime, SKCanvas canvas)
{ {
AnimationProfile.Update(deltaTime); AnimationProfile.Update(deltaTime);
AnimationProfile.Render(canvas); AnimationProfile.Render(canvas, SKPoint.Empty);
} }
private Profile CreateIntroProfile() private Profile CreateIntroProfile()

View File

@ -90,9 +90,24 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) 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(() => Execute.OnUIThreadSync(() =>
{ {
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height; RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width; RenderWidth = bitmapInfo.Width;
@ -103,25 +118,10 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
return; 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(); 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); skImage.ReadPixels(pixmap, 0, 0);
} }
@ -140,6 +140,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
_frames = 0; _frames = 0;
_frameCountStart = DateTime.Now; _frameCountStart = DateTime.Now;
} }
_frames++; _frames++;
} }
} }

View File

@ -225,6 +225,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=pixmap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=snackbar/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=snackbar/@EntryIndexedValue">True</s:Boolean>