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

Merge remote-tracking branch 'origin/development' into development

This commit is contained in:
Robert 2021-03-26 00:18:16 +01:00
commit cf25d9f146
15 changed files with 154 additions and 72 deletions

View File

@ -16,7 +16,7 @@ namespace Artemis.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(this float number) public static int RoundToInt(this float number)
{ {
return (int) Math.Round(number, MidpointRounding.AwayFromZero); return (int) MathF.Round(number, MidpointRounding.AwayFromZero);
} }
} }
} }

View File

@ -31,5 +31,10 @@ namespace Artemis.Core
rectangle.Size.Height rectangle.Size.Height
); );
} }
public static SKRectI ToSKRectI(this Rectangle rectangle)
{
return SKRectI.Round(ToSKRect(rectangle));
}
} }
} }

View File

@ -4,7 +4,6 @@ using System.Linq;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Newtonsoft.Json;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
@ -153,8 +152,10 @@ namespace Artemis.Core
SKPath path = new() {FillType = SKPathFillType.Winding}; SKPath path = new() {FillType = SKPathFillType.Winding};
foreach (ProfileElement child in Children) foreach (ProfileElement child in Children)
{
if (child is RenderProfileElement effectChild && effectChild.Path != null) if (child is RenderProfileElement effectChild && effectChild.Path != null)
path.AddPath(effectChild.Path); path.AddPath(effectChild.Path);
}
Path = path; Path = path;
@ -168,7 +169,7 @@ namespace Artemis.Core
#region Rendering #region Rendering
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPoint basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
@ -192,12 +193,12 @@ namespace Artemis.Core
SKPaint layerPaint = new(); SKPaint layerPaint = new();
try try
{ {
SKRect rendererBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height); SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint); baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint);
canvas.SaveLayer(layerPaint); canvas.SaveLayer(layerPaint);
canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y); canvas.Translate(Bounds.Left - basePosition.X, 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)
@ -212,7 +213,7 @@ namespace Artemis.Core
// 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(canvas, new SKPoint(Path.Bounds.Left, Path.Bounds.Top)); Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint); baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint);

View File

@ -277,7 +277,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPoint basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
@ -312,7 +312,7 @@ namespace Artemis.Core
} }
} }
private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPoint basePosition) private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPointI 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");
@ -329,12 +329,11 @@ namespace Artemis.Core
try try
{ {
canvas.Save(); canvas.Save();
canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y); canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
using SKPath clipPath = new(Path); using SKPath clipPath = new(Path);
clipPath.Transform(SKMatrix.CreateTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1)); clipPath.Transform(SKMatrix.CreateTranslation(Bounds.Left * -1, Bounds.Top * -1));
canvas.ClipPath(clipPath); canvas.ClipPath(clipPath, SKClipOperation.Intersect, true);
SKRectI layerBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
SKRect layerBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height);
// Apply blend mode and color // Apply blend mode and color
layerPaint.BlendMode = General.BlendMode.CurrentValue; layerPaint.BlendMode = General.BlendMode.CurrentValue;
@ -435,7 +434,7 @@ namespace Artemis.Core
OnRenderPropertiesUpdated(); OnRenderPropertiesUpdated();
} }
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool applyTranslation, bool zeroBased) internal SKPoint GetLayerAnchorPosition(bool applyTranslation, bool zeroBased)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
@ -444,14 +443,14 @@ namespace Artemis.Core
// Start at the center of the shape // Start at the center of the shape
SKPoint position = zeroBased SKPoint position = zeroBased
? new SKPoint(layerPath.Bounds.MidX - layerPath.Bounds.Left, layerPath.Bounds.MidY - layerPath.Bounds.Top) ? new SKPointI(Bounds.MidX - Bounds.Left, Bounds.MidY - Bounds.Top)
: new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY); : new SKPointI(Bounds.MidX, Bounds.MidY);
// Apply translation // Apply translation
if (applyTranslation) if (applyTranslation)
{ {
position.X += positionProperty.X * layerPath.Bounds.Width; position.X += positionProperty.X * Bounds.Width;
position.Y += positionProperty.Y * layerPath.Bounds.Height; position.Y += positionProperty.Y * Bounds.Height;
} }
return position; return position;
@ -479,7 +478,7 @@ namespace Artemis.Core
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
float rotationProperty = Transform.Rotation.CurrentValue; float rotationProperty = Transform.Rotation.CurrentValue;
SKPoint anchorPosition = GetLayerAnchorPosition(Path, true, zeroBased); SKPoint anchorPosition = GetLayerAnchorPosition(true, zeroBased);
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor // Translation originates from the unscaled center of the shape and is tied to the anchor

View File

@ -81,7 +81,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPoint basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition)
{ {
lock (_lock) lock (_lock)
{ {

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, SKPoint basePosition); public abstract void Render(SKCanvas canvas, SKPointI basePosition);
/// <summary> /// <summary>
/// Resets the internal state of the element /// Resets the internal state of the element

View File

@ -16,6 +16,9 @@ namespace Artemis.Core
/// </summary> /// </summary>
public abstract class RenderProfileElement : ProfileElement public abstract class RenderProfileElement : ProfileElement
{ {
private SKPath? _path;
private SKRectI _bounds;
internal RenderProfileElement(Profile profile) : base(profile) internal RenderProfileElement(Profile profile) : base(profile)
{ {
Timeline = new Timeline(); Timeline = new Timeline();
@ -113,7 +116,6 @@ namespace Artemis.Core
#region Properties #region Properties
private SKPath? _path;
internal abstract RenderElementEntity RenderElementEntity { get; } internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary> /// <summary>
@ -141,14 +143,14 @@ namespace Artemis.Core
SetAndNotify(ref _path, value); SetAndNotify(ref _path, value);
// 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 = SKRectI.Round(value?.Bounds ?? SKRect.Empty);
} }
} }
/// <summary> /// <summary>
/// The bounds of this entity /// The bounds of this entity
/// </summary> /// </summary>
public SKRect Bounds public SKRectI Bounds
{ {
get => _bounds; get => _bounds;
private set => SetAndNotify(ref _bounds, value); private set => SetAndNotify(ref _bounds, value);
@ -158,7 +160,6 @@ namespace Artemis.Core
#region Property group expansion #region Property group expansion
internal List<string> ExpandedPropertyGroups; internal List<string> ExpandedPropertyGroups;
private SKRect _bounds;
/// <summary> /// <summary>
/// Determines whether the provided property group is expanded /// Determines whether the provided property group is expanded

View File

@ -8,8 +8,8 @@ namespace Artemis.Core
/// </summary> /// </summary>
public class ArtemisLed : CorePropertyChanged public class ArtemisLed : CorePropertyChanged
{ {
private SKRect _absoluteRectangle; private SKRectI _absoluteRectangle;
private SKRect _rectangle; private SKRectI _rectangle;
internal ArtemisLed(Led led, ArtemisDevice device) internal ArtemisLed(Led led, ArtemisDevice device)
{ {
@ -31,7 +31,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the rectangle covering the LED positioned relative to the<see cref="Device" /> /// Gets the rectangle covering the LED positioned relative to the<see cref="Device" />
/// </summary> /// </summary>
public SKRect Rectangle public SKRectI Rectangle
{ {
get => _rectangle; get => _rectangle;
private set => SetAndNotify(ref _rectangle, value); private set => SetAndNotify(ref _rectangle, value);
@ -40,7 +40,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the rectangle covering the LED /// Gets the rectangle covering the LED
/// </summary> /// </summary>
public SKRect AbsoluteRectangle public SKRectI AbsoluteRectangle
{ {
get => _absoluteRectangle; get => _absoluteRectangle;
private set => SetAndNotify(ref _absoluteRectangle, value); private set => SetAndNotify(ref _absoluteRectangle, value);
@ -59,8 +59,8 @@ namespace Artemis.Core
internal void CalculateRectangles() internal void CalculateRectangles()
{ {
Rectangle = RgbLed.Boundary.ToSKRect(); Rectangle = RgbLed.Boundary.ToSKRectI();
AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect(); AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRectI();
} }
} }
} }

View File

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

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using RGB.NET.Core; using RGB.NET.Core;
@ -12,25 +13,54 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable public sealed class SKTexture : PixelTexture<byte>, IDisposable
{ {
private readonly bool _isScaledDown;
private readonly SKPixmap _pixelData; private readonly SKPixmap _pixelData;
private readonly IntPtr _pixelDataPtr; private readonly IntPtr _pixelDataPtr;
#region Constructors #region Constructors
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, 4, new AverageByteSampler()) internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler())
{ {
ImageInfo = new SKImageInfo(width, height); ImageInfo = new SKImageInfo(width, height);
Surface = graphicsContext == null Surface = graphicsContext == null
? SKSurface.Create(ImageInfo) ? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = scale; RenderScale = scale;
_isScaledDown = Math.Abs(scale - 1) > 0.001;
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes);
} }
#endregion #endregion
private void ReleaseUnmanagedResources()
{
Marshal.FreeHGlobal(_pixelDataPtr);
}
/// <inheritdoc />
~SKTexture()
{
ReleaseUnmanagedResources();
}
/// <inheritdoc />
public void Dispose()
{
Surface.Dispose();
_pixelData.Dispose();
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
#region Constants
private const int STACK_ALLOC_LIMIT = 1024;
private const int DATA_PER_PIXEL = 4;
#endregion
#region Methods #region Methods
/// <summary> /// <summary>
@ -53,6 +83,61 @@ namespace Artemis.Core
return new(pixel[2], pixel[1], pixel[0]); return new(pixel[2], pixel[1], pixel[0]);
} }
/// <inheritdoc />
public override Color this[in Rectangle rectangle]
{
get
{
if (Data.Length == 0) return Color.Transparent;
SKRectI skRectI = CreatedFlooredRectI(
Size.Width * rectangle.Location.X.Clamp(0, 1),
Size.Height * rectangle.Location.Y.Clamp(0, 1),
Size.Width * rectangle.Size.Width.Clamp(0, 1),
Size.Height * rectangle.Size.Height.Clamp(0, 1)
);
if (skRectI.Width == 0 || skRectI.Height == 0) return Color.Transparent;
if (skRectI.Width == 1 && skRectI.Height == 1) return GetColor(GetPixelData(skRectI.Left, skRectI.Top));
int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<byte> buffer = stackalloc byte[bufferSize];
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.SampleColor(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
byte[] rent = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(rent).Slice(0, bufferSize);
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.SampleColor(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
ArrayPool<byte>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
private static SKRectI CreatedFlooredRectI(float x, float y, float width, float height)
{
return new(
width <= 0.0 ? checked((int) Math.Floor(x)) : checked((int) Math.Ceiling(x)),
height <= 0.0 ? checked((int) Math.Floor(y)) : checked((int) Math.Ceiling(y)),
width >= 0.0 ? checked((int) Math.Floor(x + width)) : checked((int) Math.Ceiling(x + width)),
height >= 0.0 ? checked((int) Math.Floor(y + height)) : checked((int) Math.Ceiling(y + height))
);
}
#endregion #endregion
#region Properties & Fields #region Properties & Fields
@ -84,30 +169,5 @@ namespace Artemis.Core
public bool IsInvalid { get; private set; } public bool IsInvalid { get; private set; }
#endregion #endregion
#region IDisposable
private void ReleaseUnmanagedResources()
{
Marshal.FreeHGlobal(_pixelDataPtr);
}
/// <inheritdoc />
public void Dispose()
{
Surface.Dispose();
_pixelData.Dispose();
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
/// <inheritdoc />
~SKTexture()
{
ReleaseUnmanagedResources();
}
#endregion
} }
} }

View File

@ -144,7 +144,8 @@ namespace Artemis.Core.Services
SKCanvas canvas = texture.Surface.Canvas; SKCanvas canvas = texture.Surface.Canvas;
canvas.Save(); canvas.Save();
canvas.Scale(texture.RenderScale); if (Math.Abs(texture.RenderScale - 1) > 0.001)
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0)); canvas.Clear(new SKColor(0, 0, 0));
// While non-activated modules may be updated above if they expand the main data model, they may never render // While non-activated modules may be updated above if they expand the main data model, they may never render

View File

@ -258,9 +258,17 @@ namespace Artemis.Core.Services
else else
_logger.Debug("Creating SKTexture with software-based graphics context"); _logger.Debug("Creating SKTexture with software-based graphics context");
float evenWidth = Surface.Boundary.Size.Width;
if (evenWidth % 2 != 0)
evenWidth++;
float evenHeight = Surface.Boundary.Size.Height;
if (evenHeight % 2 != 0)
evenHeight++;
float renderScale = (float) _renderScaleSetting.Value; float renderScale = (float) _renderScaleSetting.Value;
int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt()); int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt()); int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
_texture?.Dispose(); _texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale); _texture = new SKTexture(graphicsContext, width, height, renderScale);
_textureBrush.Texture = _texture; _textureBrush.Texture = _texture;

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, SKPoint.Empty); AnimationProfile.Render(canvas, SKPointI.Empty);
} }
private Profile CreateIntroProfile() private Profile CreateIntroProfile()

View File

@ -11,6 +11,7 @@ using Artemis.UI.Shared.Services.Models;
using Ninject; using Ninject;
using Ninject.Parameters; using Ninject.Parameters;
using Serilog; using Serilog;
using SkiaSharp;
using SkiaSharp.Views.WPF; using SkiaSharp.Views.WPF;
using Stylet; using Stylet;
@ -350,7 +351,10 @@ namespace Artemis.UI.Shared.Services
public List<ArtemisLed> GetLedsInRectangle(Rect rect) public List<ArtemisLed> GetLedsInRectangle(Rect rect)
{ {
return _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(led => led.AbsoluteRectangle.IntersectsWith(rect.ToSKRect())).ToList(); return _rgbService.EnabledDevices
.SelectMany(d => d.Leds)
.Where(led => led.AbsoluteRectangle.IntersectsWith(SKRectI.Round(rect.ToSKRect())))
.ToList();
} }
#region Copy/paste #region Copy/paste

View File

@ -59,9 +59,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
LogLevels = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel))); LogLevels = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel)));
ColorSchemes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme))); ColorSchemes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme)));
RenderScales = new List<Tuple<string, double>> {new("10%", 0.1)}; RenderScales = new List<Tuple<string, double>>
for (int i = 25; i <= 100; i += 25) {
RenderScales.Add(new Tuple<string, double>(i + "%", i / 100.0)); new("25%", 0.25),
new("50%", 0.5),
new("100%", 1),
};
TargetFrameRates = new List<Tuple<string, int>>(); TargetFrameRates = new List<Tuple<string, int>>();
for (int i = 10; i <= 30; i += 5) for (int i = 10; i <= 30; i += 5)