From c444ff4e593420d2561fc632db1012ba7d504071 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 22 Mar 2021 21:00:37 +0100 Subject: [PATCH] Software rendering - Fixed memory leak Rendering - Swap back and forth between software and GPU without crash --- src/Artemis.Core/Models/Profile/Renderer.cs | 13 +- src/Artemis.Core/Services/RgbService.cs | 19 ++- src/Artemis.UI/Properties/launchSettings.json | 2 +- .../Settings/Debug/Tabs/RenderDebugView.xaml | 8 +- .../Debug/Tabs/RenderDebugViewModel.cs | 14 ++- .../Services/RegistrationService.cs | 4 +- .../SkiaSharp/Vulkan/Win32VkContext.cs | 119 +++++++++--------- src/Artemis.UI/SkiaSharp/VulkanContext.cs | 5 +- 8 files changed, 105 insertions(+), 79 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Renderer.cs b/src/Artemis.Core/Models/Profile/Renderer.cs index 05c2fd8e1..f8cd1025e 100644 --- a/src/Artemis.Core/Models/Profile/Renderer.cs +++ b/src/Artemis.Core/Models/Profile/Renderer.cs @@ -9,6 +9,7 @@ namespace Artemis.Core private bool _disposed; private SKRect _lastBounds; private SKRect _lastParentBounds; + private GRContext? _lastGraphicsContext; public SKSurface? Surface { get; private set; } public SKPaint? Paint { get; private set; } public SKPath? Path { get; private set; } @@ -27,7 +28,7 @@ namespace Artemis.Core if (IsOpen) throw new ArtemisCoreException("Cannot open render context because it is already open"); - if (path.Bounds != _lastBounds || (parent != null && parent.Bounds != _lastParentBounds)) + if (path.Bounds != _lastBounds || (parent != null && parent.Bounds != _lastParentBounds) || _lastGraphicsContext != Constants.ManagedGraphicsContext?.GraphicsContext) Invalidate(); if (!_valid || Surface == null) @@ -41,7 +42,7 @@ namespace Artemis.Core Surface = SKSurface.Create(imageInfo); else Surface = SKSurface.Create(Constants.ManagedGraphicsContext.GraphicsContext, true, imageInfo); - + Path = new SKPath(path); Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1)); @@ -53,6 +54,7 @@ namespace Artemis.Core _lastParentBounds = parent?.Bounds ?? new SKRect(); _lastBounds = path.Bounds; + _lastGraphicsContext = Constants.ManagedGraphicsContext?.GraphicsContext; _valid = true; } @@ -70,7 +72,14 @@ namespace Artemis.Core throw new ObjectDisposedException("Renderer"); Surface?.Canvas.Restore(); + + // Looks like every part of the paint needs to be disposed :( + Paint?.ColorFilter?.Dispose(); + Paint?.ImageFilter?.Dispose(); + Paint?.MaskFilter?.Dispose(); + Paint?.PathEffect?.Dispose(); Paint?.Dispose(); + Paint = null; IsOpen = false; diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 7be1f8c17..40b502208 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -51,6 +51,8 @@ namespace Artemis.Core.Services UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; Surface.RegisterUpdateTrigger(UpdateTrigger); + + Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; } public TimerUpdateTrigger UpdateTrigger { get; } @@ -66,6 +68,11 @@ namespace Artemis.Core.Services _texture?.Invalidate(); } + private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) + { + IsRenderPaused = true; + } + private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) { UpdateLedGroup(); @@ -115,6 +122,11 @@ namespace Artemis.Core.Services DeviceAdded?.Invoke(this, e); } + private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) + { + _texture?.Invalidate(); + } + public IReadOnlyCollection EnabledDevices => _enabledDevices.AsReadOnly(); public IReadOnlyCollection Devices => _devices.AsReadOnly(); public IReadOnlyDictionary LedMap => new ReadOnlyDictionary(_ledMap); @@ -195,11 +207,6 @@ namespace Artemis.Core.Services } } - private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) - { - _texture?.Invalidate(); - } - public void Dispose() { Surface.UnregisterUpdateTrigger(UpdateTrigger); @@ -245,7 +252,7 @@ namespace Artemis.Core.Services IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext; if (!ReferenceEquals(graphicsContext, _newGraphicsContext)) graphicsContext = _newGraphicsContext; - + if (graphicsContext != null) _logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name); else diff --git a/src/Artemis.UI/Properties/launchSettings.json b/src/Artemis.UI/Properties/launchSettings.json index be8d62f45..1268340dc 100644 --- a/src/Artemis.UI/Properties/launchSettings.json +++ b/src/Artemis.UI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Artemis.UI": { "commandName": "Project", - "commandLineArgs": "--force-elevation" + "commandLineArgs": "--force-elevation --pcmr" } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml index 69169e4f8..4a5c4205d 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml @@ -32,11 +32,9 @@ - - - - - + + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index 1b515ccd9..6e0f9cdf2 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -21,6 +21,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private int _renderHeight; private string _frameTargetPath; private string _renderer; + private int _frames; + private DateTime _frameCountStart; public RenderDebugViewModel(ICoreService coreService) { @@ -76,8 +78,6 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { _coreService.FrameRendered += CoreServiceOnFrameRendered; _coreService.FrameRendering += CoreServiceOnFrameRendering; - - Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; base.OnActivate(); } @@ -132,7 +132,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) { - CurrentFps = Math.Round(1.0 / e.DeltaTime, 2); + if (DateTime.Now - _frameCountStart >= TimeSpan.FromSeconds(1)) + { + CurrentFps = _frames; + Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; + + _frames = 0; + _frameCountStart = DateTime.Now; + } + _frames++; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 6da26b430..9cbda99e6 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -28,6 +28,7 @@ namespace Artemis.UI.Services private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInPropertyEditors; + private VulkanContext _vulkanContext; public RegistrationService(ILogger logger, ICoreService coreService, @@ -128,7 +129,8 @@ namespace Artemis.UI.Services _rgbService.UpdateGraphicsContext(null); break; case "Vulkan": - _rgbService.UpdateGraphicsContext(new VulkanContext()); + _vulkanContext ??= new VulkanContext(); + _rgbService.UpdateGraphicsContext(_vulkanContext); break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs b/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs index eff84d8ac..0bf695f5a 100644 --- a/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs +++ b/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs @@ -8,76 +8,79 @@ namespace Artemis.UI.SkiaSharp.Vulkan { internal sealed class Win32VkContext : VkContext { - private static readonly NativeWindow window = new NativeWindow(); + public NativeWindow Window { get; } - public Win32VkContext() - { - Instance = Instance.Create(null, new[] { "VK_KHR_surface", "VK_KHR_win32_surface" }); + public Win32VkContext() + { + Window = new NativeWindow(); + Instance = Instance.Create(null, new[] {"VK_KHR_surface", "VK_KHR_win32_surface"}); + PhysicalDevice = Instance.EnumeratePhysicalDevices().First(); + Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, Window.Handle); - PhysicalDevice = Instance.EnumeratePhysicalDevices().First(); + (GraphicsFamily, PresentFamily) = FindQueueFamilies(); - Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, window.Handle); + DeviceQueueCreateInfo[] queueInfos = + { + new() {QueueFamilyIndex = GraphicsFamily, QueuePriorities = new[] {1f}}, + new() {QueueFamilyIndex = PresentFamily, QueuePriorities = new[] {1f}} + }; + Device = PhysicalDevice.CreateDevice(queueInfos, null, null); + GraphicsQueue = Device.GetQueue(GraphicsFamily, 0); + PresentQueue = Device.GetQueue(PresentFamily, 0); - (GraphicsFamily, PresentFamily) = FindQueueFamilies(); + GetProc = (name, instanceHandle, deviceHandle) => + { + if (deviceHandle != IntPtr.Zero) + return Device.GetProcedureAddress(name); - DeviceQueueCreateInfo[]? queueInfos = new[] - { - new DeviceQueueCreateInfo { QueueFamilyIndex = GraphicsFamily, QueuePriorities = new[] { 1f } }, - new DeviceQueueCreateInfo { QueueFamilyIndex = PresentFamily, QueuePriorities = new[] { 1f } }, - }; - Device = PhysicalDevice.CreateDevice(queueInfos, null, null); + return Instance.GetProcedureAddress(name); + }; - GraphicsQueue = Device.GetQueue(GraphicsFamily, 0); + SharpVkGetProc = (name, instance, device) => + { + if (device != null) + return device.GetProcedureAddress(name); + if (instance != null) + return instance.GetProcedureAddress(name); - PresentQueue = Device.GetQueue(PresentFamily, 0); + // SharpVk includes the static functions on Instance, but this is not actually correct + // since the functions are static, they are not tied to an instance. For example, + // VkCreateInstance is not found on an instance, it is creating said instance. + // Other libraries, such as VulkanCore, use another type to do this. + return Instance.GetProcedureAddress(name); + }; + } - GetProc = (name, instanceHandle, deviceHandle) => - { - if (deviceHandle != IntPtr.Zero) - return Device.GetProcedureAddress(name); + public override void Dispose() + { + base.Dispose(); + Window.DestroyHandle(); + } - return Instance.GetProcedureAddress(name); - }; + private (uint, uint) FindQueueFamilies() + { + QueueFamilyProperties[] queueFamilyProperties = PhysicalDevice.GetQueueFamilyProperties(); - SharpVkGetProc = (name, instance, device) => - { - if (device != null) - return device.GetProcedureAddress(name); - if (instance != null) - return instance.GetProcedureAddress(name); + var graphicsFamily = queueFamilyProperties + .Select((properties, index) => new {properties, index}) + .SkipWhile(pair => !pair.properties.QueueFlags.HasFlag(QueueFlags.Graphics)) + .FirstOrDefault(); - // SharpVk includes the static functions on Instance, but this is not actually correct - // since the functions are static, they are not tied to an instance. For example, - // VkCreateInstance is not found on an instance, it is creating said instance. - // Other libraries, such as VulkanCore, use another type to do this. - return Instance.GetProcedureAddress(name); - }; - } + if (graphicsFamily == null) + throw new Exception("Unable to find graphics queue"); - private (uint, uint) FindQueueFamilies() - { - QueueFamilyProperties[]? queueFamilyProperties = PhysicalDevice.GetQueueFamilyProperties(); + uint? presentFamily = default; - var graphicsFamily = queueFamilyProperties - .Select((properties, index) => new { properties, index }) - .SkipWhile(pair => !pair.properties.QueueFlags.HasFlag(QueueFlags.Graphics)) - .FirstOrDefault(); + for (uint i = 0; i < queueFamilyProperties.Length; ++i) + { + if (PhysicalDevice.GetSurfaceSupport(i, Surface)) + presentFamily = i; + } - if (graphicsFamily == null) - throw new Exception("Unable to find graphics queue"); + if (!presentFamily.HasValue) + throw new Exception("Unable to find present queue"); - uint? presentFamily = default; - - for (uint i = 0; i < queueFamilyProperties.Length; ++i) - { - if (PhysicalDevice.GetSurfaceSupport(i, Surface)) - presentFamily = i; - } - - if (!presentFamily.HasValue) - throw new Exception("Unable to find present queue"); - - return ((uint)graphicsFamily.index, presentFamily.Value); - } - } -} + return ((uint) graphicsFamily.index, presentFamily.Value); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/SkiaSharp/VulkanContext.cs b/src/Artemis.UI/SkiaSharp/VulkanContext.cs index a9aee5dd0..2c96684db 100644 --- a/src/Artemis.UI/SkiaSharp/VulkanContext.cs +++ b/src/Artemis.UI/SkiaSharp/VulkanContext.cs @@ -50,14 +50,13 @@ namespace Artemis.UI.SkiaSharp { throw new ArtemisGraphicsContextException("Failed to create Vulkan graphics context", e); } + + GraphicsContext.Flush(); } /// public void Dispose() { - _vulkanBackendContext?.Dispose(); - _vulkanContext?.Dispose(); - GraphicsContext?.Dispose(); } public GRContext GraphicsContext { get; }