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

Software rendering - Fixed memory leak

Rendering - Swap back and forth between software and GPU without crash
This commit is contained in:
Robert 2021-03-22 21:00:37 +01:00
parent 97668ee932
commit c444ff4e59
8 changed files with 105 additions and 79 deletions

View File

@ -9,6 +9,7 @@ namespace Artemis.Core
private bool _disposed; private bool _disposed;
private SKRect _lastBounds; private SKRect _lastBounds;
private SKRect _lastParentBounds; private SKRect _lastParentBounds;
private GRContext? _lastGraphicsContext;
public SKSurface? Surface { get; private set; } public SKSurface? Surface { get; private set; }
public SKPaint? Paint { get; private set; } public SKPaint? Paint { get; private set; }
public SKPath? Path { get; private set; } public SKPath? Path { get; private set; }
@ -27,7 +28,7 @@ namespace Artemis.Core
if (IsOpen) if (IsOpen)
throw new ArtemisCoreException("Cannot open render context because it is already open"); 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(); Invalidate();
if (!_valid || Surface == null) if (!_valid || Surface == null)
@ -53,6 +54,7 @@ namespace Artemis.Core
_lastParentBounds = parent?.Bounds ?? new SKRect(); _lastParentBounds = parent?.Bounds ?? new SKRect();
_lastBounds = path.Bounds; _lastBounds = path.Bounds;
_lastGraphicsContext = Constants.ManagedGraphicsContext?.GraphicsContext;
_valid = true; _valid = true;
} }
@ -70,7 +72,14 @@ namespace Artemis.Core
throw new ObjectDisposedException("Renderer"); throw new ObjectDisposedException("Renderer");
Surface?.Canvas.Restore(); 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?.Dispose();
Paint = null; Paint = null;
IsOpen = false; IsOpen = false;

View File

@ -51,6 +51,8 @@ namespace Artemis.Core.Services
UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
Surface.RegisterUpdateTrigger(UpdateTrigger); Surface.RegisterUpdateTrigger(UpdateTrigger);
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
} }
public TimerUpdateTrigger UpdateTrigger { get; } public TimerUpdateTrigger UpdateTrigger { get; }
@ -66,6 +68,11 @@ namespace Artemis.Core.Services
_texture?.Invalidate(); _texture?.Invalidate();
} }
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
{
IsRenderPaused = true;
}
private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args)
{ {
UpdateLedGroup(); UpdateLedGroup();
@ -115,6 +122,11 @@ namespace Artemis.Core.Services
DeviceAdded?.Invoke(this, e); DeviceAdded?.Invoke(this, e);
} }
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
{
_texture?.Invalidate();
}
public IReadOnlyCollection<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly();
public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly();
public IReadOnlyDictionary<Led, ArtemisLed> LedMap => new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap); public IReadOnlyDictionary<Led, ArtemisLed> LedMap => new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap);
@ -195,11 +207,6 @@ namespace Artemis.Core.Services
} }
} }
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
{
_texture?.Invalidate();
}
public void Dispose() public void Dispose()
{ {
Surface.UnregisterUpdateTrigger(UpdateTrigger); Surface.UnregisterUpdateTrigger(UpdateTrigger);

View File

@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Artemis.UI": { "Artemis.UI": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--force-elevation" "commandLineArgs": "--force-elevation --pcmr"
} }
} }
} }

View File

@ -32,11 +32,9 @@
<TextBlock Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,5,0"> <TextBlock Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,5,0">
<Run Text="FPS: " /> <Run Text="FPS: " />
<Run FontWeight="Bold" Text="{Binding CurrentFps}" /> <Run FontWeight="Bold" Text="{Binding CurrentFps}" />
<Run Text=" at " /> <Run Text="at" />
<Run Text="{Binding RenderWidth}" /><Run Text="x" /> <Run Text="{Binding RenderWidth}" /><Run Text="x" /><Run Text="{Binding RenderHeight}" />
<Run Text="{Binding RenderHeight}" /> <Run Text="- Renderer:"></Run>
<Run Text=" - Renderer: "></Run>
<Run Text="{Binding Renderer}"></Run> <Run Text="{Binding Renderer}"></Run>
</TextBlock> </TextBlock>
</Grid> </Grid>

View File

@ -21,6 +21,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
private int _renderHeight; private int _renderHeight;
private string _frameTargetPath; private string _frameTargetPath;
private string _renderer; private string _renderer;
private int _frames;
private DateTime _frameCountStart;
public RenderDebugViewModel(ICoreService coreService) public RenderDebugViewModel(ICoreService coreService)
{ {
@ -76,8 +78,6 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{ {
_coreService.FrameRendered += CoreServiceOnFrameRendered; _coreService.FrameRendered += CoreServiceOnFrameRendered;
_coreService.FrameRendering += CoreServiceOnFrameRendering; _coreService.FrameRendering += CoreServiceOnFrameRendering;
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
base.OnActivate(); base.OnActivate();
} }
@ -132,7 +132,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) 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++;
} }
} }
} }

View File

@ -28,6 +28,7 @@ namespace Artemis.UI.Services
private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelDisplays;
private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInDataModelInputs;
private bool _registeredBuiltInPropertyEditors; private bool _registeredBuiltInPropertyEditors;
private VulkanContext _vulkanContext;
public RegistrationService(ILogger logger, public RegistrationService(ILogger logger,
ICoreService coreService, ICoreService coreService,
@ -128,7 +129,8 @@ namespace Artemis.UI.Services
_rgbService.UpdateGraphicsContext(null); _rgbService.UpdateGraphicsContext(null);
break; break;
case "Vulkan": case "Vulkan":
_rgbService.UpdateGraphicsContext(new VulkanContext()); _vulkanContext ??= new VulkanContext();
_rgbService.UpdateGraphicsContext(_vulkanContext);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();

View File

@ -8,76 +8,79 @@ namespace Artemis.UI.SkiaSharp.Vulkan
{ {
internal sealed class Win32VkContext : VkContext internal sealed class Win32VkContext : VkContext
{ {
private static readonly NativeWindow window = new NativeWindow(); public NativeWindow Window { get; }
public Win32VkContext() public Win32VkContext()
{ {
Instance = Instance.Create(null, new[] { "VK_KHR_surface", "VK_KHR_win32_surface" }); 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[] return Instance.GetProcedureAddress(name);
{ };
new DeviceQueueCreateInfo { QueueFamilyIndex = GraphicsFamily, QueuePriorities = new[] { 1f } },
new DeviceQueueCreateInfo { QueueFamilyIndex = PresentFamily, QueuePriorities = new[] { 1f } },
};
Device = PhysicalDevice.CreateDevice(queueInfos, null, null);
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) => public override void Dispose()
{ {
if (deviceHandle != IntPtr.Zero) base.Dispose();
return Device.GetProcedureAddress(name); Window.DestroyHandle();
}
return Instance.GetProcedureAddress(name); private (uint, uint) FindQueueFamilies()
}; {
QueueFamilyProperties[] queueFamilyProperties = PhysicalDevice.GetQueueFamilyProperties();
SharpVkGetProc = (name, instance, device) => var graphicsFamily = queueFamilyProperties
{ .Select((properties, index) => new {properties, index})
if (device != null) .SkipWhile(pair => !pair.properties.QueueFlags.HasFlag(QueueFlags.Graphics))
return device.GetProcedureAddress(name); .FirstOrDefault();
if (instance != null)
return instance.GetProcedureAddress(name);
// SharpVk includes the static functions on Instance, but this is not actually correct if (graphicsFamily == null)
// since the functions are static, they are not tied to an instance. For example, throw new Exception("Unable to find graphics queue");
// 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);
};
}
private (uint, uint) FindQueueFamilies() uint? presentFamily = default;
{
QueueFamilyProperties[]? queueFamilyProperties = PhysicalDevice.GetQueueFamilyProperties();
var graphicsFamily = queueFamilyProperties for (uint i = 0; i < queueFamilyProperties.Length; ++i)
.Select((properties, index) => new { properties, index }) {
.SkipWhile(pair => !pair.properties.QueueFlags.HasFlag(QueueFlags.Graphics)) if (PhysicalDevice.GetSurfaceSupport(i, Surface))
.FirstOrDefault(); presentFamily = i;
}
if (graphicsFamily == null) if (!presentFamily.HasValue)
throw new Exception("Unable to find graphics queue"); throw new Exception("Unable to find present queue");
uint? presentFamily = default; return ((uint) graphicsFamily.index, presentFamily.Value);
}
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);
}
}
} }

View File

@ -50,14 +50,13 @@ namespace Artemis.UI.SkiaSharp
{ {
throw new ArtemisGraphicsContextException("Failed to create Vulkan graphics context", e); throw new ArtemisGraphicsContextException("Failed to create Vulkan graphics context", e);
} }
GraphicsContext.Flush();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_vulkanBackendContext?.Dispose();
_vulkanContext?.Dispose();
GraphicsContext?.Dispose();
} }
public GRContext GraphicsContext { get; } public GRContext GraphicsContext { get; }