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:
parent
c444ff4e59
commit
fa49424cf6
16
src/Artemis.Core/Extensions/SKPaintExtensions.cs
Normal file
16
src/Artemis.Core/Extensions/SKPaintExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -108,5 +108,11 @@ namespace Artemis.Core
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
~Renderer()
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user