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

Core - Added the ability to change graphics context at runtime

Settings - Added setting for switching between software-based and Vulkan-based rendering
This commit is contained in:
Robert 2021-03-22 19:29:53 +01:00
parent d7e302fb23
commit f888fb5697
19 changed files with 281 additions and 270 deletions

View File

@ -2,9 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Artemis.Core.JsonConverters; using Artemis.Core.JsonConverters;
using Artemis.Core.Services;
using Artemis.Core.Services.Core; using Artemis.Core.Services.Core;
using Artemis.Core.SkiaSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -118,20 +119,10 @@ namespace Artemis.Core
typeof(decimal) typeof(decimal)
}; };
private static GRContext? _skiaGraphicsContext;
/// <summary> /// <summary>
/// Gets or sets the graphics context to be used for rendering by SkiaSharp /// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via
/// <see cref="IRgbService.UpdateGraphicsContext" />.
/// </summary> /// </summary>
public static GRContext? SkiaGraphicsContext public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
{
get => _skiaGraphicsContext;
set
{
if (_skiaGraphicsContext != null)
throw new ArtemisCoreException($"{nameof(SkiaGraphicsContext)} can only be set once.");
_skiaGraphicsContext = value;
}
}
} }
} }

View File

@ -0,0 +1,25 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents SkiaSharp graphics-context related errors
/// </summary>
public class ArtemisGraphicsContextException : Exception
{
/// <inheritdoc />
public ArtemisGraphicsContextException()
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message) : base(message)
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -36,11 +36,11 @@ namespace Artemis.Core
int width = (int) pathBounds.Width; int width = (int) pathBounds.Width;
int height = (int) pathBounds.Height; int height = (int) pathBounds.Height;
SKImageInfo imageInfo = new SKImageInfo(width, height); SKImageInfo imageInfo = new(width, height);
if (Constants.SkiaGraphicsContext == null) if (Constants.ManagedGraphicsContext?.GraphicsContext == null)
Surface = SKSurface.Create(imageInfo); Surface = SKSurface.Create(imageInfo);
else else
Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, imageInfo); Surface = SKSurface.Create(Constants.ManagedGraphicsContext.GraphicsContext, true, imageInfo);
Path = new SKPath(path); Path = new SKPath(path);
Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1)); Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1));

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics; using Artemis.Core.SkiaSharp;
using System.Runtime.InteropServices;
using RGB.NET.Core; using RGB.NET.Core;
using RGB.NET.Presets.Textures.Sampler; using RGB.NET.Presets.Textures.Sampler;
using SkiaSharp; using SkiaSharp;
@ -17,14 +16,14 @@ namespace Artemis.Core
#region Constructors #region Constructors
internal SKTexture(int width, int height, float renderScale) internal SKTexture(IManagedGraphicsContext? managedGraphicsContext, int width, int height, float renderScale)
: base(width, height, 4, new AverageByteSampler()) : base(width, height, 4, new AverageByteSampler())
{ {
ImageInfo = new SKImageInfo(width, height); ImageInfo = new SKImageInfo(width, height);
if (Constants.SkiaGraphicsContext == null) if (managedGraphicsContext == null)
Surface = SKSurface.Create(ImageInfo); Surface = SKSurface.Create(ImageInfo);
else else
Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, ImageInfo); Surface = SKSurface.Create(managedGraphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = renderScale; RenderScale = renderScale;
} }

View File

@ -59,6 +59,7 @@ namespace Artemis.Core.Services
UpdatePluginCache(); UpdatePluginCache();
_rgbService.IsRenderPaused = true;
_rgbService.Surface.Updating += SurfaceOnUpdating; _rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
@ -242,6 +243,7 @@ namespace Artemis.Core.Services
IsElevated IsElevated
); );
_rgbService.IsRenderPaused = false;
OnInitialized(); OnInitialized();
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core; using RGB.NET.Core;
namespace Artemis.Core.Services namespace Artemis.Core.Services
@ -50,6 +51,16 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
void CloseRender(); void CloseRender();
/// <summary>
/// Updates the graphics context to the provided <paramref name="managedGraphicsContext"></paramref>.
/// <para>Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.</para>
/// </summary>
/// <param name="managedGraphicsContext">
/// The new managed graphics context. If <see langword="null" />, software rendering
/// is used.
/// </param>
void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
/// <summary> /// <summary>
/// Adds the given device provider to the <see cref="Surface" /> /// Adds the given device provider to the <see cref="Surface" />
/// </summary> /// </summary>

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Services.Models; using Artemis.Core.Services.Models;
using Artemis.Core.SkiaSharp;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core; using RGB.NET.Core;
@ -51,7 +52,7 @@ 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);
} }
public TimerUpdateTrigger UpdateTrigger { get; } public TimerUpdateTrigger UpdateTrigger { get; }
protected virtual void OnDeviceRemoved(DeviceEventArgs e) protected virtual void OnDeviceRemoved(DeviceEventArgs e)
@ -118,9 +119,7 @@ namespace Artemis.Core.Services
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);
/// <inheritdoc />
public RGBSurface Surface { get; set; } public RGBSurface Surface { get; set; }
public bool IsRenderPaused { get; set; } public bool IsRenderPaused { get; set; }
public bool RenderOpen { get; private set; } public bool RenderOpen { get; private set; }
@ -215,6 +214,8 @@ namespace Artemis.Core.Services
#region Rendering #region Rendering
private IManagedGraphicsContext? _newGraphicsContext;
public SKTexture OpenRender() public SKTexture OpenRender()
{ {
if (RenderOpen) if (RenderOpen)
@ -231,7 +232,7 @@ namespace Artemis.Core.Services
{ {
if (!RenderOpen) if (!RenderOpen)
throw new ArtemisCoreException("Render pipeline is already closed"); throw new ArtemisCoreException("Render pipeline is already closed");
RenderOpen = false; RenderOpen = false;
_texture?.CopyPixelData(); _texture?.CopyPixelData();
} }
@ -241,15 +242,38 @@ namespace Artemis.Core.Services
if (RenderOpen) if (RenderOpen)
throw new ArtemisCoreException("Cannot update the texture while rendering"); throw new ArtemisCoreException("Cannot update the texture while rendering");
SKTexture? oldTexture = _texture; 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
_logger.Debug("Creating SKTexture with software-based graphics context");
float renderScale = (float) _renderScaleSetting.Value; float renderScale = (float) _renderScaleSetting.Value;
int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt()); int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt()); int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt());
_texture = new SKTexture(width, height, renderScale); _texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale);
_textureBrush.Texture = _texture; _textureBrush.Texture = _texture;
oldTexture?.Dispose();
if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext))
{
Constants.ManagedGraphicsContext?.Dispose();
Constants.ManagedGraphicsContext = _newGraphicsContext;
_newGraphicsContext = null;
}
}
public void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext)
{
if (ReferenceEquals(managedGraphicsContext, Constants.ManagedGraphicsContext))
return;
_newGraphicsContext = managedGraphicsContext;
_texture?.Invalidate();
} }
#endregion #endregion

View File

@ -0,0 +1,16 @@
using System;
using SkiaSharp;
namespace Artemis.Core.SkiaSharp
{
/// <summary>
/// Represents a managed wrapper around a SkiaSharp context
/// </summary>
public interface IManagedGraphicsContext : IDisposable
{
/// <summary>
/// Gets the graphics context created by this wrapper
/// </summary>
GRContext GraphicsContext { get; }
}
}

View File

@ -68,12 +68,10 @@ namespace Artemis.UI
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
// Create and bind the root view, this is a tray icon so don't show it with the window manager // Create and bind the root view, this is a tray icon so don't show it with the window manager
Execute.OnUIThread(() => Execute.OnUIThreadSync(() =>
{ {
UIElement view = viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel); UIElement view = viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel);
((TrayViewModel) RootViewModel).SetTaskbarIcon(view); ((TrayViewModel) RootViewModel).SetTaskbarIcon(view);
CreateGraphicsContext();
}); });
// Initialize the core async so the UI can show the progress // Initialize the core async so the UI can show the progress
@ -96,6 +94,11 @@ namespace Artemis.UI
registrationService.RegisterInputProvider(); registrationService.RegisterInputProvider();
registrationService.RegisterControllers(); registrationService.RegisterControllers();
Execute.OnUIThreadSync(() =>
{
registrationService.ApplyPreferredGraphicsContext();
});
// Initialize background services // Initialize background services
Kernel.Get<IDeviceLayoutService>(); Kernel.Get<IDeviceLayoutService>();
} }
@ -133,22 +136,6 @@ namespace Artemis.UI
e.Handled = true; e.Handled = true;
} }
private void CreateGraphicsContext()
{
Win32VkContext vulkanContext = new();
GRVkBackendContext vulkanBackendContext = new()
{
VkInstance = (IntPtr) vulkanContext.Instance.RawHandle.ToUInt64(),
VkPhysicalDevice = (IntPtr) vulkanContext.PhysicalDevice.RawHandle.ToUInt64(),
VkDevice = (IntPtr) vulkanContext.Device.RawHandle.ToUInt64(),
VkQueue = (IntPtr) vulkanContext.GraphicsQueue.RawHandle.ToUInt64(),
GraphicsQueueIndex = vulkanContext.GraphicsFamily,
GetProcedureAddress = vulkanContext.GetProc
};
Constants.SkiaGraphicsContext = GRContext.CreateVulkan(vulkanBackendContext);
}
private void HandleFatalException(Exception e, ILogger logger) private void HandleFatalException(Exception e, ILogger logger)
{ {
logger.Fatal(e, "Fatal exception during initialization, shutting down."); logger.Fatal(e, "Fatal exception during initialization, shutting down.");

View File

@ -0,0 +1,22 @@
using System;
namespace Artemis.UI.Exceptions
{
public class ArtemisGraphicsContextException : Exception
{
/// <inheritdoc />
public ArtemisGraphicsContextException()
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message) : base(message)
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -7,6 +7,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:GeneralSettingsTabViewModel}"> d:DataContext="{d:DesignInstance local:GeneralSettingsTabViewModel}">
@ -307,6 +308,30 @@
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">Rendering</TextBlock> <TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">Rendering</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0"> <materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15"> <StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Preferred render method</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Width="80" SelectedItem="{Binding PreferredGraphicsContext}" >
<system:String>Software</system:String>
<system:String>Vulkan</system:String>
</ComboBox>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />

View File

@ -3,15 +3,10 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.Metadata;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Core.Services.Core;
using Artemis.UI.Properties;
using Artemis.UI.Screens.StartupWizard; using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services; using Artemis.UI.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
@ -30,6 +25,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IKernel _kernel; private readonly IKernel _kernel;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
private readonly IRegistrationService _registrationService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService; private readonly IUpdateService _updateService;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
@ -45,7 +41,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
ISettingsService settingsService, ISettingsService settingsService,
IUpdateService updateService, IUpdateService updateService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IMessageService messageService) IMessageService messageService,
IRegistrationService registrationService)
{ {
DisplayName = "GENERAL"; DisplayName = "GENERAL";
@ -56,6 +53,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
_settingsService = settingsService; _settingsService = settingsService;
_updateService = updateService; _updateService = updateService;
_messageService = messageService; _messageService = messageService;
_registrationService = registrationService;
LogLevels = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel))); LogLevels = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel)));
ColorSchemes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme))); ColorSchemes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme)));
@ -209,6 +207,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
} }
} }
public string PreferredGraphicsContext
{
get => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan").Value;
set
{
_settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan").Value = value;
_settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan").Save();
_registrationService.ApplyPreferredGraphicsContext();
}
}
public double RenderScale public double RenderScale
{ {
get => _settingsService.GetSetting("Core.RenderScale", 0.5).Value; get => _settingsService.GetSetting("Core.RenderScale", 0.5).Value;
@ -316,10 +325,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
try try
{ {
bool taskCreated = false; bool taskCreated = false;
if (!recreate) if (!recreate) taskCreated = SettingsUtilities.IsAutoRunTaskCreated();
{
taskCreated = SettingsUtilities.IsAutoRunTaskCreated();
}
if (StartWithWindows && !taskCreated) if (StartWithWindows && !taskCreated)
SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(AutoRunDelay)); SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(AutoRunDelay));
@ -335,7 +341,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
} }
public enum ApplicationColorScheme public enum ApplicationColorScheme
{ {
Light, Light,

View File

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Controllers; using Artemis.UI.Controllers;
@ -8,6 +9,7 @@ using Artemis.UI.DefaultTypes.PropertyInput;
using Artemis.UI.InputProviders; using Artemis.UI.InputProviders;
using Artemis.UI.Ninject; using Artemis.UI.Ninject;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.SkiaSharp;
using Serilog; using Serilog;
namespace Artemis.UI.Services namespace Artemis.UI.Services
@ -15,28 +17,37 @@ namespace Artemis.UI.Services
public class RegistrationService : IRegistrationService public class RegistrationService : IRegistrationService
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ICoreService _coreService;
private readonly IDataModelUIService _dataModelUIService; private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IInputService _inputService; private readonly IInputService _inputService;
private readonly IWebServerService _webServerService; private readonly IWebServerService _webServerService;
private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService;
private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelDisplays;
private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInDataModelInputs;
private bool _registeredBuiltInPropertyEditors; private bool _registeredBuiltInPropertyEditors;
public RegistrationService(ILogger logger, public RegistrationService(ILogger logger,
ICoreService coreService,
IDataModelUIService dataModelUIService, IDataModelUIService dataModelUIService,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IInputService inputService, IInputService inputService,
IWebServerService webServerService) IWebServerService webServerService,
IRgbService rgbService,
ISettingsService settingsService)
{ {
_logger = logger; _logger = logger;
_coreService = coreService;
_dataModelUIService = dataModelUIService; _dataModelUIService = dataModelUIService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_inputService = inputService; _inputService = inputService;
_webServerService = webServerService; _webServerService = webServerService;
_rgbService = rgbService;
_settingsService = settingsService;
LoadPluginModules(); LoadPluginModules();
pluginManagementService.PluginEnabling += PluginServiceOnPluginEnabling; pluginManagementService.PluginEnabling += PluginServiceOnPluginEnabling;
@ -97,6 +108,39 @@ namespace Artemis.UI.Services
_webServerService.AddController<RemoteController>(); _webServerService.AddController<RemoteController>();
} }
/// <inheritdoc />
public void ApplyPreferredGraphicsContext()
{
if (_coreService.StartupArguments.Contains("--force-software-render"))
{
_logger.Warning("Startup argument '--force-software-render' is applied, forcing software rendering.");
_rgbService.UpdateGraphicsContext(null);
return;
}
PluginSetting<string> preferredGraphicsContext = _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan");
try
{
switch (preferredGraphicsContext.Value)
{
case "Software":
_rgbService.UpdateGraphicsContext(null);
break;
case "Vulkan":
_rgbService.UpdateGraphicsContext(new VulkanContext());
break;
default:
throw new ArgumentOutOfRangeException();
}
}
catch (Exception e)
{
_logger.Warning(e, "Failed to apply preferred graphics context {preferred}", preferredGraphicsContext.Value);
_rgbService.UpdateGraphicsContext(null);
}
}
private void PluginServiceOnPluginEnabling(object sender, PluginEventArgs e) private void PluginServiceOnPluginEnabling(object sender, PluginEventArgs e)
{ {
e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)}); e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)});
@ -116,5 +160,6 @@ namespace Artemis.UI.Services
void RegisterBuiltInPropertyEditors(); void RegisterBuiltInPropertyEditors();
void RegisterInputProvider(); void RegisterInputProvider();
void RegisterControllers(); void RegisterControllers();
void ApplyPreferredGraphicsContext();
} }
} }

View File

@ -1,109 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Artemis.UI.SkiaSharp
{
internal class User32
{
private const string user32 = "user32.dll";
public const uint IDC_ARROW = 32512;
public const uint IDI_APPLICATION = 32512;
public const uint IDI_WINLOGO = 32517;
public const int SW_HIDE = 0;
public const uint CS_VREDRAW = 0x1;
public const uint CS_HREDRAW = 0x2;
public const uint CS_OWNDC = 0x20;
public const uint WS_EX_CLIENTEDGE = 0x00000200;
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern ushort RegisterClass(ref Win32Window.WNDCLASS lpWndClass);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern ushort UnregisterClass([MarshalAs(UnmanagedType.LPTStr)] string lpClassName, IntPtr hInstance);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
[DllImport(user32, CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern IntPtr CreateWindowEx(uint dwExStyle, [MarshalAs(UnmanagedType.LPTStr)] string lpClassName, [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName, WindowStyles dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
public static IntPtr CreateWindow(string lpClassName, string lpWindowName, WindowStyles dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam)
{
return CreateWindowEx(0, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
}
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyWindow(IntPtr hWnd);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindow(IntPtr hWnd);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AdjustWindowRectEx(ref RECT lpRect, WindowStyles dwStyle, bool bMenu, uint dwExStyle);
[DllImport(user32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[Flags]
public enum WindowStyles : uint
{
WS_BORDER = 0x800000,
WS_CAPTION = 0xc00000,
WS_CHILD = 0x40000000,
WS_CLIPCHILDREN = 0x2000000,
WS_CLIPSIBLINGS = 0x4000000,
WS_DISABLED = 0x8000000,
WS_DLGFRAME = 0x400000,
WS_GROUP = 0x20000,
WS_HSCROLL = 0x100000,
WS_MAXIMIZE = 0x1000000,
WS_MAXIMIZEBOX = 0x10000,
WS_MINIMIZE = 0x20000000,
WS_MINIMIZEBOX = 0x20000,
WS_OVERLAPPED = 0x0,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUP = 0x80000000u,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_SIZEFRAME = 0x40000,
WS_SYSMENU = 0x80000,
WS_TABSTOP = 0x10000,
WS_VISIBLE = 0x10000000,
WS_VSCROLL = 0x200000
}
}
}

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Artemis.UI.SkiaSharp namespace Artemis.UI.SkiaSharp.Vulkan
{ {
internal class Kernel32 internal class Kernel32
{ {

View File

@ -6,7 +6,7 @@ using Instance = SharpVk.Instance;
using PhysicalDevice = SharpVk.PhysicalDevice; using PhysicalDevice = SharpVk.PhysicalDevice;
using Queue = SharpVk.Queue; using Queue = SharpVk.Queue;
namespace Artemis.UI.SkiaSharp namespace Artemis.UI.SkiaSharp.Vulkan
{ {
internal class VkContext : IDisposable internal class VkContext : IDisposable
{ {

View File

@ -4,7 +4,7 @@ using System.Windows.Forms;
using SharpVk; using SharpVk;
using SharpVk.Khronos; using SharpVk.Khronos;
namespace Artemis.UI.SkiaSharp namespace Artemis.UI.SkiaSharp.Vulkan
{ {
internal sealed class Win32VkContext : VkContext internal sealed class Win32VkContext : VkContext
{ {

View File

@ -0,0 +1,65 @@
using System;
using Artemis.Core.SkiaSharp;
using Artemis.UI.Exceptions;
using Artemis.UI.SkiaSharp.Vulkan;
using SkiaSharp;
namespace Artemis.UI.SkiaSharp
{
public class VulkanContext : IManagedGraphicsContext
{
private readonly GRVkBackendContext _vulkanBackendContext;
private readonly Win32VkContext _vulkanContext;
public VulkanContext()
{
// Try everything in separate try-catch blocks to provide some accuracy in error reporting
try
{
_vulkanContext = new Win32VkContext();
}
catch (Exception e)
{
throw new ArtemisGraphicsContextException("Failed to create Vulkan context", e);
}
try
{
_vulkanBackendContext = new GRVkBackendContext
{
VkInstance = (IntPtr) _vulkanContext.Instance.RawHandle.ToUInt64(),
VkPhysicalDevice = (IntPtr) _vulkanContext.PhysicalDevice.RawHandle.ToUInt64(),
VkDevice = (IntPtr) _vulkanContext.Device.RawHandle.ToUInt64(),
VkQueue = (IntPtr) _vulkanContext.GraphicsQueue.RawHandle.ToUInt64(),
GraphicsQueueIndex = _vulkanContext.GraphicsFamily,
GetProcedureAddress = _vulkanContext.GetProc
};
}
catch (Exception e)
{
throw new ArtemisGraphicsContextException("Failed to create Vulkan backend context", e);
}
try
{
GraphicsContext = GRContext.CreateVulkan(_vulkanBackendContext);
if (GraphicsContext == null)
throw new ArtemisGraphicsContextException("GRContext.CreateVulkan returned null");
}
catch (Exception e)
{
throw new ArtemisGraphicsContextException("Failed to create Vulkan graphics context", e);
}
}
/// <inheritdoc />
public void Dispose()
{
_vulkanBackendContext?.Dispose();
_vulkanContext?.Dispose();
GraphicsContext?.Dispose();
}
public GRContext GraphicsContext { get; }
}
}

View File

@ -1,97 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Artemis.UI.SkiaSharp
{
internal class Win32Window : IDisposable
{
private ushort classRegistration;
public string WindowClassName { get; }
public IntPtr WindowHandle { get; private set; }
public IntPtr DeviceContextHandle { get; private set; }
public Win32Window(string className)
{
WindowClassName = className;
var wc = new WNDCLASS
{
cbClsExtra = 0,
cbWndExtra = 0,
hbrBackground = IntPtr.Zero,
hCursor = User32.LoadCursor(IntPtr.Zero, (int)User32.IDC_ARROW),
hIcon = User32.LoadIcon(IntPtr.Zero, (IntPtr)User32.IDI_APPLICATION),
hInstance = Kernel32.CurrentModuleHandle,
lpfnWndProc = (WNDPROC)User32.DefWindowProc,
lpszClassName = WindowClassName,
lpszMenuName = null,
style = User32.CS_HREDRAW | User32.CS_VREDRAW | User32.CS_OWNDC
};
classRegistration = User32.RegisterClass(ref wc);
if (classRegistration == 0)
throw new Exception($"Could not register window class: {className}");
WindowHandle = User32.CreateWindow(
WindowClassName,
$"The Invisible Man ({className})",
User32.WindowStyles.WS_OVERLAPPEDWINDOW,
0, 0,
1, 1,
IntPtr.Zero, IntPtr.Zero, Kernel32.CurrentModuleHandle, IntPtr.Zero);
if (WindowHandle == IntPtr.Zero)
throw new Exception($"Could not create window: {className}");
DeviceContextHandle = User32.GetDC(WindowHandle);
if (DeviceContextHandle == IntPtr.Zero)
{
Dispose();
throw new Exception($"Could not get device context: {className}");
}
}
public void Dispose()
{
if (WindowHandle != IntPtr.Zero)
{
if (DeviceContextHandle != IntPtr.Zero)
{
User32.ReleaseDC(WindowHandle, DeviceContextHandle);
DeviceContextHandle = IntPtr.Zero;
}
User32.DestroyWindow(WindowHandle);
WindowHandle = IntPtr.Zero;
}
if (classRegistration != 0)
{
User32.UnregisterClass(WindowClassName, Kernel32.CurrentModuleHandle);
classRegistration = 0;
}
}
public delegate IntPtr WNDPROC(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct WNDCLASS
{
public uint style;
[MarshalAs(UnmanagedType.FunctionPtr)]
public WNDPROC lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszMenuName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszClassName;
}
}
}