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
/// <inheritdoc />
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);
}

View File

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

View File

@ -81,7 +81,7 @@ namespace Artemis.Core
}
/// <inheritdoc />
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);
}
}

View File

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

View File

@ -19,7 +19,6 @@ namespace Artemis.Core
internal RenderProfileElement(Profile profile) : base(profile)
{
Timeline = new Timeline();
Renderer = new Renderer();
ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>();
@ -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

View File

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

View File

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

View File

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

View File

@ -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++;
}
}

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_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@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>