diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 14dda2b79..2208c7d0e 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -318,7 +318,7 @@ namespace Artemis.Core if (Path == null || LayerBrush == null) throw new ArtemisCoreException("The layer is not yet ready for rendering"); - if (timeline.IsFinished) + if (!Leds.Any() || timeline.IsFinished) return; ApplyTimeline(timeline); diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index 70d4e5bac..8224cc826 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Module; @@ -186,13 +187,17 @@ namespace Artemis.Core.Modules internal virtual void InternalUpdate(double deltaTime) { + StartUpdateMeasure(); if (IsUpdateAllowed) Update(deltaTime); + StopUpdateMeasure(); } internal virtual void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { + StartRenderMeasure(); Render(deltaTime, canvas, canvasInfo); + StopRenderMeasure(); } internal virtual void Activate(bool isOverride) diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 94fbd3125..ed9201809 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -150,6 +150,7 @@ namespace Artemis.Core.Modules internal override void InternalUpdate(double deltaTime) { + StartUpdateMeasure(); if (IsUpdateAllowed) Update(deltaTime); @@ -165,10 +166,12 @@ namespace Artemis.Core.Modules } ProfileUpdated(deltaTime); + StopUpdateMeasure(); } internal override void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { + StartRenderMeasure(); Render(deltaTime, canvas, canvasInfo); lock (_lock) @@ -178,6 +181,7 @@ namespace Artemis.Core.Modules } ProfileRendered(deltaTime, canvas, canvasInfo); + StopRenderMeasure(); } internal async Task ChangeActiveProfileAnimated(Profile? profile, IEnumerable devices) diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index f2fb01fad..8b9943a0a 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Artemis.Storage.Entities.Plugins; @@ -10,9 +11,11 @@ namespace Artemis.Core /// public abstract class PluginFeature : CorePropertyChanged, IDisposable { + private readonly Stopwatch _renderStopwatch = new(); + private readonly Stopwatch _updateStopwatch = new(); private bool _isEnabled; private Exception? _loadException; - + /// /// Gets the plugin feature info related to this feature /// @@ -46,6 +49,16 @@ namespace Artemis.Core /// public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable + /// + /// Gets the last measured update time of the feature + /// + public TimeSpan UpdateTime { get; private set; } + + /// + /// Gets the last measured render time of the feature + /// + public TimeSpan RenderTime { get; private set; } + internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction /// @@ -58,6 +71,66 @@ namespace Artemis.Core /// public abstract void Disable(); + /// + /// Occurs when the feature is enabled + /// + public event EventHandler? Enabled; + + /// + /// Occurs when the feature is disabled + /// + public event EventHandler? Disabled; + + /// + /// Releases the unmanaged resources used by the plugin feature and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) InternalDisable(); + } + + /// + /// Triggers the Enabled event + /// + protected virtual void OnEnabled() + { + Enabled?.Invoke(this, EventArgs.Empty); + } + + /// + /// Triggers the Disabled event + /// + protected virtual void OnDisabled() + { + Disabled?.Invoke(this, EventArgs.Empty); + } + + internal void StartUpdateMeasure() + { + _updateStopwatch.Start(); + } + + internal void StopUpdateMeasure() + { + UpdateTime = _updateStopwatch.Elapsed; + _updateStopwatch.Reset(); + } + + internal void StartRenderMeasure() + { + _renderStopwatch.Start(); + } + + internal void StopRenderMeasure() + { + RenderTime = _renderStopwatch.Elapsed; + _renderStopwatch.Reset(); + } + internal void SetEnabled(bool enable, bool isAutoEnable = false) { if (enable == IsEnabled) @@ -133,25 +206,6 @@ namespace Artemis.Core Disable(); } - #region IDisposable - - /// - /// Releases the unmanaged resources used by the plugin feature and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - InternalDisable(); - } - } - - #endregion - /// public void Dispose() { @@ -187,35 +241,5 @@ namespace Artemis.Core } #endregion - - #region Events - - /// - /// Occurs when the feature is enabled - /// - public event EventHandler? Enabled; - - /// - /// Occurs when the feature is disabled - /// - public event EventHandler? Disabled; - - /// - /// Triggers the Enabled event - /// - protected virtual void OnEnabled() - { - Enabled?.Invoke(this, EventArgs.Empty); - } - - /// - /// Triggers the Disabled event - /// - protected virtual void OnDisabled() - { - Disabled?.Invoke(this, EventArgs.Empty); - } - - #endregion } } \ 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 cc0ebc4c9..77a540282 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Artemis.Core.SkiaSharp; using RGB.NET.Core; using RGB.NET.Presets.Textures.Sampler; @@ -11,34 +12,39 @@ namespace Artemis.Core /// public sealed class SKTexture : PixelTexture, IDisposable { - private SKPixmap? _pixelData; - private SKImage? _rasterImage; + private readonly SKPixmap _pixelData; + private readonly IntPtr _pixelDataPtr; #region Constructors - internal SKTexture(IManagedGraphicsContext? managedGraphicsContext, int width, int height, float renderScale) - : base(width, height, 4, new AverageByteSampler()) + internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, 4, new AverageByteSampler()) { ImageInfo = new SKImageInfo(width, height); - if (managedGraphicsContext == null) - Surface = SKSurface.Create(ImageInfo); - else - Surface = SKSurface.Create(managedGraphicsContext.GraphicsContext, true, ImageInfo); - RenderScale = renderScale; + Surface = graphicsContext == null + ? SKSurface.Create(ImageInfo) + : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); + RenderScale = scale; + + _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); + _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); } #endregion #region Methods + /// + /// Invalidates the texture + /// + public void Invalidate() + { + IsInvalid = true; + } + internal void CopyPixelData() { using SKImage skImage = Surface.Snapshot(); - - _rasterImage?.Dispose(); - _pixelData?.Dispose(); - _rasterImage = skImage.ToRasterImage(); - _pixelData = _rasterImage.PeekPixels(); + skImage.ReadPixels(_pixelData); } /// @@ -64,7 +70,7 @@ namespace Artemis.Core /// /// Gets the color data in RGB format /// - protected override ReadOnlySpan Data => _pixelData != null ? _pixelData.GetPixelSpan() : ReadOnlySpan.Empty; + protected override ReadOnlySpan Data => _pixelData.GetPixelSpan(); /// /// Gets the render scale of the texture @@ -77,19 +83,29 @@ namespace Artemis.Core /// public bool IsInvalid { get; private set; } - /// - /// Invalidates the texture - /// - public void Invalidate() + #endregion + + #region IDisposable + + private void ReleaseUnmanagedResources() { - IsInvalid = true; + Marshal.FreeHGlobal(_pixelDataPtr); } /// public void Dispose() { Surface.Dispose(); - _pixelData?.Dispose(); + _pixelData.Dispose(); + + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + /// + ~SKTexture() + { + ReleaseUnmanagedResources(); } #endregion diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml index 629db8ada..69169e4f8 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml @@ -35,6 +35,9 @@ + + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index 1d207cc88..1b515ccd9 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -20,6 +20,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private int _renderWidth; private int _renderHeight; private string _frameTargetPath; + private string _renderer; public RenderDebugViewModel(ICoreService coreService) { @@ -51,6 +52,12 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs set => SetAndNotify(ref _renderHeight, value); } + public string Renderer + { + get => _renderer; + set => SetAndNotify(ref _renderer, value); + } + public void SaveFrame() { VistaSaveFileDialog dialog = new VistaSaveFileDialog {Filter = "Portable network graphic (*.png)|*.png", Title = "Save render frame"}; @@ -69,6 +76,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { _coreService.FrameRendered += CoreServiceOnFrameRendered; _coreService.FrameRendering += CoreServiceOnFrameRendering; + + Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; base.OnActivate(); } @@ -88,8 +97,6 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs RenderWidth = bitmapInfo.Width; // ReSharper disable twice CompareOfFloatsByEqualityOperator - - if (CurrentFrame is not WriteableBitmap writable || writable.Width != bitmapInfo.Width || writable.Height != bitmapInfo.Height) { CurrentFrame = e.Texture.Surface.Snapshot().ToWriteableBitmap(); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index 9193ba8ff..c8caa051e 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -42,7 +42,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.General IUpdateService updateService, IPluginManagementService pluginManagementService, IMessageService messageService, - IRegistrationService registrationService) + IRegistrationService registrationService, + ICoreService coreService + ) { DisplayName = "GENERAL"; @@ -64,6 +66,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.General TargetFrameRates = new List>(); for (int i = 10; i <= 30; i += 5) TargetFrameRates.Add(new Tuple(i + " FPS", i)); + if (coreService.StartupArguments.Contains("--pcmr")) + { + TargetFrameRates.Add(new Tuple("60 FPS (lol)", 60)); + TargetFrameRates.Add(new Tuple("144 FPS (omegalol)", 144)); + } List layerBrushProviders = pluginManagementService.GetFeaturesOfType();