diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 068524a1d..21c2ca973 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.IO; using Artemis.Core.JsonConverters; +using Artemis.Core.Services; using Artemis.Core.Services.Core; +using Artemis.Core.SkiaSharp; using Newtonsoft.Json; -using SkiaSharp; namespace Artemis.Core { @@ -118,20 +119,10 @@ namespace Artemis.Core typeof(decimal) }; - private static GRContext? _skiaGraphicsContext; - /// - /// 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 + /// . /// - public static GRContext? SkiaGraphicsContext - { - get => _skiaGraphicsContext; - set - { - if (_skiaGraphicsContext != null) - throw new ArtemisCoreException($"{nameof(SkiaGraphicsContext)} can only be set once."); - _skiaGraphicsContext = value; - } - } + public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Exceptions/ArtemisGraphicsContextException.cs b/src/Artemis.Core/Exceptions/ArtemisGraphicsContextException.cs new file mode 100644 index 000000000..975dd8c04 --- /dev/null +++ b/src/Artemis.Core/Exceptions/ArtemisGraphicsContextException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents SkiaSharp graphics-context related errors + /// + public class ArtemisGraphicsContextException : Exception + { + /// + public ArtemisGraphicsContextException() + { + } + + /// + public ArtemisGraphicsContextException(string message) : base(message) + { + } + + /// + public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Renderer.cs b/src/Artemis.Core/Models/Profile/Renderer.cs index fee081904..05c2fd8e1 100644 --- a/src/Artemis.Core/Models/Profile/Renderer.cs +++ b/src/Artemis.Core/Models/Profile/Renderer.cs @@ -36,11 +36,11 @@ namespace Artemis.Core int width = (int) pathBounds.Width; int height = (int) pathBounds.Height; - SKImageInfo imageInfo = new SKImageInfo(width, height); - if (Constants.SkiaGraphicsContext == null) + SKImageInfo imageInfo = new(width, height); + if (Constants.ManagedGraphicsContext?.GraphicsContext == null) Surface = SKSurface.Create(imageInfo); else - Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, imageInfo); + Surface = SKSurface.Create(Constants.ManagedGraphicsContext.GraphicsContext, true, imageInfo); Path = new SKPath(path); Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1)); diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs index 20f80b066..cc0ebc4c9 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -1,6 +1,5 @@ using System; -using System.Diagnostics; -using System.Runtime.InteropServices; +using Artemis.Core.SkiaSharp; using RGB.NET.Core; using RGB.NET.Presets.Textures.Sampler; using SkiaSharp; @@ -17,14 +16,14 @@ namespace Artemis.Core #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()) { ImageInfo = new SKImageInfo(width, height); - if (Constants.SkiaGraphicsContext == null) + if (managedGraphicsContext == null) Surface = SKSurface.Create(ImageInfo); else - Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, ImageInfo); + Surface = SKSurface.Create(managedGraphicsContext.GraphicsContext, true, ImageInfo); RenderScale = renderScale; } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index eac279fe1..17961a9e2 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -59,6 +59,7 @@ namespace Artemis.Core.Services UpdatePluginCache(); + _rgbService.IsRenderPaused = true; _rgbService.Surface.Updating += SurfaceOnUpdating; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); @@ -242,6 +243,7 @@ namespace Artemis.Core.Services IsElevated ); + _rgbService.IsRenderPaused = false; OnInitialized(); } diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 099b4bb3f..00c5f85d0 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Artemis.Core.SkiaSharp; using RGB.NET.Core; namespace Artemis.Core.Services @@ -50,6 +51,16 @@ namespace Artemis.Core.Services /// void CloseRender(); + /// + /// Updates the graphics context to the provided . + /// Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards. + /// + /// + /// The new managed graphics context. If , software rendering + /// is used. + /// + void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext); + /// /// Adds the given device provider to the /// diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 2e34d27dd..7be1f8c17 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.DeviceProviders; using Artemis.Core.Services.Models; +using Artemis.Core.SkiaSharp; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Repositories.Interfaces; using RGB.NET.Core; @@ -51,7 +52,7 @@ namespace Artemis.Core.Services UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; Surface.RegisterUpdateTrigger(UpdateTrigger); } - + public TimerUpdateTrigger UpdateTrigger { get; } protected virtual void OnDeviceRemoved(DeviceEventArgs e) @@ -118,9 +119,7 @@ namespace Artemis.Core.Services public IReadOnlyCollection Devices => _devices.AsReadOnly(); public IReadOnlyDictionary LedMap => new ReadOnlyDictionary(_ledMap); - /// public RGBSurface Surface { get; set; } - public bool IsRenderPaused { get; set; } public bool RenderOpen { get; private set; } @@ -215,6 +214,8 @@ namespace Artemis.Core.Services #region Rendering + private IManagedGraphicsContext? _newGraphicsContext; + public SKTexture OpenRender() { if (RenderOpen) @@ -231,7 +232,7 @@ namespace Artemis.Core.Services { if (!RenderOpen) throw new ArtemisCoreException("Render pipeline is already closed"); - + RenderOpen = false; _texture?.CopyPixelData(); } @@ -241,15 +242,38 @@ namespace Artemis.Core.Services if (RenderOpen) 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; 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()); - _texture = new SKTexture(width, height, renderScale); + _texture?.Dispose(); + _texture = new SKTexture(graphicsContext, width, height, renderScale); _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 diff --git a/src/Artemis.Core/SkiaSharp/IManagedGraphicsContext.cs b/src/Artemis.Core/SkiaSharp/IManagedGraphicsContext.cs new file mode 100644 index 000000000..d2e7ff03b --- /dev/null +++ b/src/Artemis.Core/SkiaSharp/IManagedGraphicsContext.cs @@ -0,0 +1,16 @@ +using System; +using SkiaSharp; + +namespace Artemis.Core.SkiaSharp +{ + /// + /// Represents a managed wrapper around a SkiaSharp context + /// + public interface IManagedGraphicsContext : IDisposable + { + /// + /// Gets the graphics context created by this wrapper + /// + GRContext GraphicsContext { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 29383b8fe..888851231 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -68,12 +68,10 @@ namespace Artemis.UI 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 - Execute.OnUIThread(() => + Execute.OnUIThreadSync(() => { UIElement view = viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel); ((TrayViewModel) RootViewModel).SetTaskbarIcon(view); - - CreateGraphicsContext(); }); // Initialize the core async so the UI can show the progress @@ -96,6 +94,11 @@ namespace Artemis.UI registrationService.RegisterInputProvider(); registrationService.RegisterControllers(); + Execute.OnUIThreadSync(() => + { + registrationService.ApplyPreferredGraphicsContext(); + }); + // Initialize background services Kernel.Get(); } @@ -133,22 +136,6 @@ namespace Artemis.UI 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) { logger.Fatal(e, "Fatal exception during initialization, shutting down."); diff --git a/src/Artemis.UI/Exceptions/ArtemisGraphicsContextException.cs b/src/Artemis.UI/Exceptions/ArtemisGraphicsContextException.cs new file mode 100644 index 000000000..653d65ef4 --- /dev/null +++ b/src/Artemis.UI/Exceptions/ArtemisGraphicsContextException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Artemis.UI.Exceptions +{ + public class ArtemisGraphicsContextException : Exception + { + /// + public ArtemisGraphicsContextException() + { + } + + /// + public ArtemisGraphicsContextException(string message) : base(message) + { + } + + /// + public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml index 6aef820f3..ecd070137 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:GeneralSettingsTabViewModel}"> @@ -307,6 +308,30 @@ Rendering + + + + + + + + + + + Preferred render method + + Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration + + + + + Software + Vulkan + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index 86e8fdb7b..9193ba8ff 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -3,15 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection.Metadata; -using System.Security.Principal; using System.Threading.Tasks; -using System.Xml.Linq; using Artemis.Core; using Artemis.Core.LayerBrushes; using Artemis.Core.Services; -using Artemis.Core.Services.Core; -using Artemis.UI.Properties; using Artemis.UI.Screens.StartupWizard; using Artemis.UI.Services; using Artemis.UI.Shared; @@ -30,6 +25,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General private readonly IDialogService _dialogService; private readonly IKernel _kernel; private readonly IMessageService _messageService; + private readonly IRegistrationService _registrationService; private readonly ISettingsService _settingsService; private readonly IUpdateService _updateService; private readonly IWindowManager _windowManager; @@ -45,7 +41,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.General ISettingsService settingsService, IUpdateService updateService, IPluginManagementService pluginManagementService, - IMessageService messageService) + IMessageService messageService, + IRegistrationService registrationService) { DisplayName = "GENERAL"; @@ -56,6 +53,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General _settingsService = settingsService; _updateService = updateService; _messageService = messageService; + _registrationService = registrationService; LogLevels = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel))); ColorSchemes = new BindableCollection(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 { get => _settingsService.GetSetting("Core.RenderScale", 0.5).Value; @@ -316,10 +325,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General try { bool taskCreated = false; - if (!recreate) - { - taskCreated = SettingsUtilities.IsAutoRunTaskCreated(); - } + if (!recreate) taskCreated = SettingsUtilities.IsAutoRunTaskCreated(); if (StartWithWindows && !taskCreated) SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(AutoRunDelay)); @@ -335,7 +341,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.General } - public enum ApplicationColorScheme { Light, diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 0d51ce376..6da26b430 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Controllers; @@ -8,6 +9,7 @@ using Artemis.UI.DefaultTypes.PropertyInput; using Artemis.UI.InputProviders; using Artemis.UI.Ninject; using Artemis.UI.Shared.Services; +using Artemis.UI.SkiaSharp; using Serilog; namespace Artemis.UI.Services @@ -15,28 +17,37 @@ namespace Artemis.UI.Services public class RegistrationService : IRegistrationService { private readonly ILogger _logger; + private readonly ICoreService _coreService; private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; private readonly IPluginManagementService _pluginManagementService; private readonly IInputService _inputService; private readonly IWebServerService _webServerService; + private readonly IRgbService _rgbService; + private readonly ISettingsService _settingsService; private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInPropertyEditors; public RegistrationService(ILogger logger, + ICoreService coreService, IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService, IInputService inputService, - IWebServerService webServerService) + IWebServerService webServerService, + IRgbService rgbService, + ISettingsService settingsService) { _logger = logger; + _coreService = coreService; _dataModelUIService = dataModelUIService; _profileEditorService = profileEditorService; _pluginManagementService = pluginManagementService; _inputService = inputService; _webServerService = webServerService; + _rgbService = rgbService; + _settingsService = settingsService; LoadPluginModules(); pluginManagementService.PluginEnabling += PluginServiceOnPluginEnabling; @@ -97,6 +108,39 @@ namespace Artemis.UI.Services _webServerService.AddController(); } + /// + 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 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) { e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)}); @@ -116,5 +160,6 @@ namespace Artemis.UI.Services void RegisterBuiltInPropertyEditors(); void RegisterInputProvider(); void RegisterControllers(); + void ApplyPreferredGraphicsContext(); } } \ No newline at end of file diff --git a/src/Artemis.UI/SkiaSharp/User32.cs b/src/Artemis.UI/SkiaSharp/User32.cs deleted file mode 100644 index a53212d5f..000000000 --- a/src/Artemis.UI/SkiaSharp/User32.cs +++ /dev/null @@ -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 - } - } -} diff --git a/src/Artemis.UI/SkiaSharp/Kernel32.cs b/src/Artemis.UI/SkiaSharp/Vulkan/Kernel32.cs similarity index 94% rename from src/Artemis.UI/SkiaSharp/Kernel32.cs rename to src/Artemis.UI/SkiaSharp/Vulkan/Kernel32.cs index 6fd86aab0..8f094b4ca 100644 --- a/src/Artemis.UI/SkiaSharp/Kernel32.cs +++ b/src/Artemis.UI/SkiaSharp/Vulkan/Kernel32.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; -namespace Artemis.UI.SkiaSharp +namespace Artemis.UI.SkiaSharp.Vulkan { internal class Kernel32 { diff --git a/src/Artemis.UI/SkiaSharp/VkContext.cs b/src/Artemis.UI/SkiaSharp/Vulkan/VkContext.cs similarity index 96% rename from src/Artemis.UI/SkiaSharp/VkContext.cs rename to src/Artemis.UI/SkiaSharp/Vulkan/VkContext.cs index 48937cf46..c7cb8de7f 100644 --- a/src/Artemis.UI/SkiaSharp/VkContext.cs +++ b/src/Artemis.UI/SkiaSharp/Vulkan/VkContext.cs @@ -6,7 +6,7 @@ using Instance = SharpVk.Instance; using PhysicalDevice = SharpVk.PhysicalDevice; using Queue = SharpVk.Queue; -namespace Artemis.UI.SkiaSharp +namespace Artemis.UI.SkiaSharp.Vulkan { internal class VkContext : IDisposable { diff --git a/src/Artemis.UI/SkiaSharp/Win32VkContext.cs b/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs similarity index 98% rename from src/Artemis.UI/SkiaSharp/Win32VkContext.cs rename to src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs index 1ddc7c539..eff84d8ac 100644 --- a/src/Artemis.UI/SkiaSharp/Win32VkContext.cs +++ b/src/Artemis.UI/SkiaSharp/Vulkan/Win32VkContext.cs @@ -4,7 +4,7 @@ using System.Windows.Forms; using SharpVk; using SharpVk.Khronos; -namespace Artemis.UI.SkiaSharp +namespace Artemis.UI.SkiaSharp.Vulkan { internal sealed class Win32VkContext : VkContext { diff --git a/src/Artemis.UI/SkiaSharp/VulkanContext.cs b/src/Artemis.UI/SkiaSharp/VulkanContext.cs new file mode 100644 index 000000000..a9aee5dd0 --- /dev/null +++ b/src/Artemis.UI/SkiaSharp/VulkanContext.cs @@ -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); + } + } + + /// + public void Dispose() + { + _vulkanBackendContext?.Dispose(); + _vulkanContext?.Dispose(); + GraphicsContext?.Dispose(); + } + + public GRContext GraphicsContext { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/SkiaSharp/Win32Window.cs b/src/Artemis.UI/SkiaSharp/Win32Window.cs deleted file mode 100644 index c7dd0c48c..000000000 --- a/src/Artemis.UI/SkiaSharp/Win32Window.cs +++ /dev/null @@ -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; - } - } -}