diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index b8b81ec5b..bfd9501ff 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -59,8 +59,18 @@ namespace Artemis.Core internal void CalculateRectangles() { - Rectangle = RgbLed.Boundary.ToSKRect(); - AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect(); + Rectangle = Utilities.CreateScaleCompatibleRect( + RgbLed.Boundary.Location.X, + RgbLed.Boundary.Location.Y, + RgbLed.Boundary.Size.Width, + RgbLed.Boundary.Size.Height + ); + AbsoluteRectangle = Utilities.CreateScaleCompatibleRect( + RgbLed.AbsoluteBoundary.Location.X, + RgbLed.AbsoluteBoundary.Location.Y, + RgbLed.AbsoluteBoundary.Size.Width, + RgbLed.AbsoluteBoundary.Size.Height + ); } } } \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs index 6bb77132a..bc91841c0 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections.Generic; using System.Runtime.InteropServices; using Artemis.Core.SkiaSharp; using RGB.NET.Core; @@ -13,22 +14,36 @@ namespace Artemis.Core /// public sealed class SKTexture : PixelTexture, IDisposable { - private readonly bool _isScaledDown; private readonly SKPixmap _pixelData; private readonly IntPtr _pixelDataPtr; + private readonly Dictionary _ledRects; #region Constructors - internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler()) + internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection devices) : base(width, height, DATA_PER_PIXEL, + new AverageByteSampler()) { ImageInfo = new SKImageInfo(width, height); Surface = graphicsContext == null ? SKSurface.Create(ImageInfo) : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); RenderScale = scale; - _isScaledDown = Math.Abs(scale - 1) > 0.001; _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); + + _ledRects = new Dictionary(); + foreach (ArtemisDevice artemisDevice in devices) + { + foreach (ArtemisLed artemisLed in artemisDevice.Leds) + { + _ledRects[artemisLed.RgbLed] = SKRectI.Create( + (int) (artemisLed.AbsoluteRectangle.Left * RenderScale), + (int) (artemisLed.AbsoluteRectangle.Top * RenderScale), + (int) (artemisLed.AbsoluteRectangle.Width * RenderScale), + (int) (artemisLed.AbsoluteRectangle.Height * RenderScale) + ); + } + } } #endregion @@ -80,63 +95,11 @@ namespace Artemis.Core /// protected override Color GetColor(in ReadOnlySpan pixel) { - return new(pixel[2], pixel[1], pixel[0]); + return new Color(pixel[2], pixel[1], pixel[0]); } /// - 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 buffer = stackalloc byte[bufferSize]; - GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); - - Span pixelData = stackalloc byte[DATA_PER_PIXEL]; - Sampler.Sample(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); - - return GetColor(pixelData); - } - else - { - byte[] rent = ArrayPool.Shared.Rent(bufferSize); - - Span buffer = new Span(rent).Slice(0, bufferSize); - GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); - - Span pixelData = stackalloc byte[DATA_PER_PIXEL]; - Sampler.Sample(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); - - ArrayPool.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)) - ); - } + public override Color this[in Rectangle rectangle] => Color.Transparent; #endregion @@ -169,5 +132,40 @@ namespace Artemis.Core public bool IsInvalid { get; private set; } #endregion + + internal Color GetColorAtRenderTarget(in RenderTarget renderTarget) + { + if (Data.Length == 0) return Color.Transparent; + SKRectI skRectI = _ledRects[renderTarget.Led]; + + if (skRectI.Width <= 0 || skRectI.Height <= 0) + return Color.Transparent; + + int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL; + if (bufferSize <= STACK_ALLOC_LIMIT) + { + Span buffer = stackalloc byte[bufferSize]; + GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); + + Span pixelData = stackalloc byte[DATA_PER_PIXEL]; + Sampler.Sample(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); + + return GetColor(pixelData); + } + else + { + byte[] rent = ArrayPool.Shared.Rent(bufferSize); + + Span buffer = new Span(rent).Slice(0, bufferSize); + GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); + + Span pixelData = stackalloc byte[DATA_PER_PIXEL]; + Sampler.Sample(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); + + ArrayPool.Shared.Return(rent); + + return GetColor(pixelData); + } + } } } \ No newline at end of file diff --git a/src/Artemis.Core/RGB.NET/SKTextureBrush.cs b/src/Artemis.Core/RGB.NET/SKTextureBrush.cs new file mode 100644 index 000000000..be238d83f --- /dev/null +++ b/src/Artemis.Core/RGB.NET/SKTextureBrush.cs @@ -0,0 +1,44 @@ +using RGB.NET.Core; + +namespace Artemis.Core +{ + internal class SKTextureBrush : AbstractBrush + { + #region Properties & Fields + + private SKTexture? _texture; + /// + /// Gets or sets the texture drawn by this brush. + /// + public SKTexture? Texture + { + get => _texture; + set => SetProperty(ref _texture, value); + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The texture drawn by this brush. + public SKTextureBrush(SKTexture? texture) + { + this.Texture = texture; + } + + #endregion + + #region Methods + + /// + protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget) + { + return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent; + } + + #endregion + } +} diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index ce91feb94..4b096a9d1 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -25,7 +25,7 @@ namespace Artemis.Core.Services private readonly IPluginManagementService _pluginManagementService; private readonly PluginSetting _renderScaleSetting; private readonly PluginSetting _targetFrameRateSetting; - private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute}; + private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute}; private Dictionary _ledMap; private ListLedGroup? _surfaceLedGroup; private SKTexture? _texture; @@ -39,6 +39,7 @@ namespace Artemis.Core.Services _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); Surface = new RGBSurface(); + Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value); // Let's throw these for now Surface.Exception += SurfaceOnException; @@ -56,7 +57,7 @@ namespace Artemis.Core.Services UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; SetRenderPaused(true); Surface.RegisterUpdateTrigger(UpdateTrigger); - + Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; } @@ -93,7 +94,7 @@ namespace Artemis.Core.Services if (_surfaceLedGroup == null) { - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; OnLedsChanged(); return; } @@ -104,7 +105,7 @@ namespace Artemis.Core.Services _surfaceLedGroup.Detach(); // Apply the application wide brush and decorator - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; OnLedsChanged(); } } @@ -113,7 +114,6 @@ namespace Artemis.Core.Services if (changedRenderPaused) SetRenderPaused(false); } - } private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) @@ -134,7 +134,12 @@ namespace Artemis.Core.Services private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) { + Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value); + _texture?.Invalidate(); + foreach (ArtemisDevice artemisDevice in Devices) + artemisDevice.CalculateRenderProperties(); + OnLedsChanged(); } public IReadOnlyCollection EnabledDevices { get; } @@ -315,7 +320,7 @@ namespace Artemis.Core.Services int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); _texture?.Dispose(); - _texture = new SKTexture(graphicsContext, width, height, renderScale); + _texture = new SKTexture(graphicsContext, width, height, renderScale, Devices); _textureBrush.Texture = _texture; diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 16e737689..9f97250bc 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; using Artemis.Core.Modules; +using Artemis.Core.Properties; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; @@ -42,8 +43,7 @@ namespace Artemis.Core.Services _profileCategoryRepository = profileCategoryRepository; _pluginManagementService = pluginManagementService; _profileRepository = profileRepository; - _profileCategories = new List(_profileCategoryRepository.GetAll() - .Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); + _profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); _rgbService.LedsChanged += RgbServiceOnLedsChanged; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs index c55b07c93..cd9299be2 100644 --- a/src/Artemis.Core/Utilities/Utilities.cs +++ b/src/Artemis.Core/Utilities/Utilities.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.AccessControl; using System.Security.Principal; +using SkiaSharp; namespace Artemis.Core { @@ -18,8 +19,8 @@ namespace Artemis.Core public static void PrepareFirstLaunch() { CreateAccessibleDirectory(Constants.DataFolder); - CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"plugins")); - CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"user layouts")); + CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "plugins")); + CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "user layouts")); } /// @@ -102,6 +103,30 @@ namespace Artemis.Core RestartRequested?.Invoke(null, e); } + #region Scaling + + internal static int RenderScaleMultiplier { get; set; } = 2; + + internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height) + { + int roundX = (int) MathF.Floor(x); + int roundY = (int) MathF.Floor(y); + int roundWidth = (int) MathF.Ceiling(width); + int roundHeight = (int) MathF.Ceiling(height); + + if (RenderScaleMultiplier == 1) + return SKRectI.Create(roundX, roundY, roundWidth, roundHeight); + + return SKRectI.Create( + roundX - (roundX % RenderScaleMultiplier), + roundY - (roundY % RenderScaleMultiplier), + roundWidth - (roundWidth % RenderScaleMultiplier), + roundHeight - (roundHeight % RenderScaleMultiplier) + ); + } + + #endregion + #region Events ///