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 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;

View File

@ -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<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly();
public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly();
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()
{
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

View File

@ -2,7 +2,7 @@
"profiles": {
"Artemis.UI": {
"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">
<Run Text="FPS: " />
<Run FontWeight="Bold" Text="{Binding CurrentFps}" />
<Run Text=" at " />
<Run Text="{Binding RenderWidth}" /><Run Text="x" />
<Run Text="{Binding RenderHeight}" />
<Run Text=" - Renderer: "></Run>
<Run Text="at" />
<Run Text="{Binding RenderWidth}" /><Run Text="x" /><Run Text="{Binding RenderHeight}" />
<Run Text="- Renderer:"></Run>
<Run Text="{Binding Renderer}"></Run>
</TextBlock>
</Grid>

View File

@ -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++;
}
}
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

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