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

Core - Reworked rendering pipeline to work with different contexts

Core - Added GPU-based Vulkan rendering context
This commit is contained in:
Robert 2021-03-21 22:40:23 +01:00
parent 34b9d69ed8
commit a02431bb82
17 changed files with 489 additions and 56 deletions

View File

@ -4,6 +4,7 @@ using System.IO;
using Artemis.Core.JsonConverters;
using Artemis.Core.Services.Core;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core
{
@ -116,5 +117,21 @@ namespace Artemis.Core
typeof(double),
typeof(decimal)
};
private static GRContext? _skiaGraphicsContext;
/// <summary>
/// Gets or sets the graphics context to be used for rendering by SkiaSharp
/// </summary>
public static GRContext? SkiaGraphicsContext
{
get => _skiaGraphicsContext;
set
{
if (_skiaGraphicsContext != null)
throw new ArtemisCoreException($"{nameof(SkiaGraphicsContext)} can only be set once.");
_skiaGraphicsContext = value;
}
}
}
}

View File

@ -193,12 +193,12 @@ namespace Artemis.Core
{
canvas.Save();
Renderer.Open(Path, Parent as Folder);
if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null)
if (Renderer.Surface == null || Renderer.Path == null || Renderer.Paint == null)
throw new ArtemisCoreException("Failed to open folder render context");
SKRect rendererBounds = Renderer.Path.Bounds;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PreProcess(Renderer.Canvas, rendererBounds, Renderer.Paint);
baseLayerEffect.PreProcess(Renderer.Surface.Canvas, rendererBounds, Renderer.Paint);
// If required, apply the opacity override of the module to the root folder
if (IsRootFolder && Profile.Module.OpacityOverride < 1)
@ -213,12 +213,12 @@ namespace Artemis.Core
// Iterate the children in reverse because the first layer must be rendered last to end up on top
for (int index = Children.Count - 1; index > -1; index--)
Children[index].Render(Renderer.Canvas);
Children[index].Render(Renderer.Surface.Canvas);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(Renderer.Canvas, rendererBounds, Renderer.Paint);
baseLayerEffect.PostProcess(Renderer.Surface.Canvas, rendererBounds, Renderer.Paint);
canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint);
canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint);
}
finally
{

View File

@ -330,8 +330,7 @@ namespace Artemis.Core
{
canvas.Save();
Renderer.Open(Path, Parent as Folder);
if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null)
if (Renderer.Surface == null || Renderer.Path == null || Renderer.Paint == null)
throw new ArtemisCoreException("Failed to open layer render context");
// Apply blend mode and color
@ -357,11 +356,11 @@ namespace Artemis.Core
if (LayerBrush.SupportsTransformation)
{
SKMatrix rotationMatrix = GetTransformMatrix(true, false, false, true);
Renderer.Canvas.SetMatrix(Renderer.Canvas.TotalMatrix.PreConcat(rotationMatrix));
Renderer.Surface.Canvas.SetMatrix(Renderer.Surface.Canvas.TotalMatrix.PreConcat(rotationMatrix));
}
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
Renderer.Canvas.ClipPath(renderPath);
Renderer.Surface.Canvas.ClipPath(renderPath);
DelegateRendering(renderPath.Bounds);
}
else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip)
@ -370,11 +369,11 @@ namespace Artemis.Core
renderPath.Transform(renderPathMatrix);
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
Renderer.Canvas.ClipPath(renderPath);
Renderer.Surface.Canvas.ClipPath(renderPath);
DelegateRendering(Renderer.Path.Bounds);
}
canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint);
canvas.DrawSurface(Renderer.Surface, Renderer.TargetLocation, Renderer.Paint);
}
finally
{
@ -395,16 +394,16 @@ namespace Artemis.Core
{
if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering");
if (Renderer.Canvas == null || Renderer.Paint == null)
if (Renderer.Surface == null || Renderer.Paint == null)
throw new ArtemisCoreException("Failed to open layer render context");
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PreProcess(Renderer.Canvas, bounds, Renderer.Paint);
baseLayerEffect.PreProcess(Renderer.Surface.Canvas, bounds, Renderer.Paint);
LayerBrush.InternalRender(Renderer.Canvas, bounds, Renderer.Paint);
LayerBrush.InternalRender(Renderer.Surface.Canvas, bounds, Renderer.Paint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(Renderer.Canvas, bounds, Renderer.Paint);
baseLayerEffect.PostProcess(Renderer.Surface.Canvas, bounds, Renderer.Paint);
}
internal void CalculateRenderProperties()

View File

@ -9,8 +9,7 @@ namespace Artemis.Core
private bool _disposed;
private SKRect _lastBounds;
private SKRect _lastParentBounds;
public SKBitmap? Bitmap { get; private set; }
public SKCanvas? Canvas { get; private set; }
public SKSurface? Surface { get; private set; }
public SKPaint? Paint { get; private set; }
public SKPath? Path { get; private set; }
public SKPoint TargetLocation { get; private set; }
@ -31,22 +30,26 @@ namespace Artemis.Core
if (path.Bounds != _lastBounds || (parent != null && parent.Bounds != _lastParentBounds))
Invalidate();
if (!_valid || Canvas == null)
if (!_valid || Surface == null)
{
SKRect pathBounds = path.Bounds;
int width = (int) pathBounds.Width;
int height = (int) pathBounds.Height;
Bitmap = new SKBitmap(width, height);
SKImageInfo imageInfo = new SKImageInfo(width, height);
if (Constants.SkiaGraphicsContext == null)
Surface = SKSurface.Create(imageInfo);
else
Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, imageInfo);
Path = new SKPath(path);
Canvas = new SKCanvas(Bitmap);
Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1));
TargetLocation = new SKPoint(pathBounds.Location.X, pathBounds.Location.Y);
if (parent != null)
TargetLocation -= parent.Bounds.Location;
Canvas.ClipPath(Path);
Surface.Canvas.ClipPath(Path);
_lastParentBounds = parent?.Bounds ?? new SKRect();
_lastBounds = path.Bounds;
@ -55,8 +58,8 @@ namespace Artemis.Core
Paint = new SKPaint();
Canvas.Clear();
Canvas.Save();
Surface.Canvas.Clear();
Surface.Canvas.Save();
IsOpen = true;
}
@ -66,7 +69,7 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("Renderer");
Canvas?.Restore();
Surface?.Canvas.Restore();
Paint?.Dispose();
Paint = null;
@ -86,15 +89,13 @@ namespace Artemis.Core
if (IsOpen)
Close();
Canvas?.Dispose();
Surface?.Dispose();
Paint?.Dispose();
Path?.Dispose();
Bitmap?.Dispose();
Canvas = null;
Surface = null;
Paint = null;
Path = null;
Bitmap = null;
_disposed = true;
}

View File

@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using RGB.NET.Core;
using RGB.NET.Presets.Textures.Sampler;
using SkiaSharp;
@ -10,14 +12,19 @@ namespace Artemis.Core
/// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable
{
private bool _disposed;
private SKPixmap? _pixelData;
private SKImage? _rasterImage;
#region Constructors
internal SKTexture(int width, int height, float renderScale)
: base(width, height, 4, new AverageByteSampler())
{
Bitmap = new SKBitmap(new SKImageInfo(width, height, SKColorType.Rgb888x));
ImageInfo = new SKImageInfo(width, height);
if (Constants.SkiaGraphicsContext == null)
Surface = SKSurface.Create(ImageInfo);
else
Surface = SKSurface.Create(Constants.SkiaGraphicsContext, true, ImageInfo);
RenderScale = renderScale;
}
@ -25,10 +32,20 @@ namespace Artemis.Core
#region Methods
internal void CopyPixelData()
{
using SKImage skImage = Surface.Snapshot();
_rasterImage?.Dispose();
_pixelData?.Dispose();
_rasterImage = skImage.ToRasterImage();
_pixelData = _rasterImage.PeekPixels();
}
/// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<byte> pixel)
{
return new(pixel[0], pixel[1], pixel[2]);
return new(pixel[2], pixel[1], pixel[0]);
}
#endregion
@ -38,12 +55,17 @@ namespace Artemis.Core
/// <summary>
/// Gets the SKBitmap backing this texture
/// </summary>
public SKBitmap Bitmap { get; }
public SKSurface Surface { get; }
/// <summary>
/// Gets the image info used to create the <see cref="Surface" />
/// </summary>
public SKImageInfo ImageInfo { get; }
/// <summary>
/// Gets the color data in RGB format
/// </summary>
protected override ReadOnlySpan<byte> Data => _disposed ? new ReadOnlySpan<byte>() : Bitmap.GetPixelSpan();
protected override ReadOnlySpan<byte> Data => _pixelData != null ? _pixelData.GetPixelSpan() : ReadOnlySpan<byte>.Empty;
/// <summary>
/// Gets the render scale of the texture
@ -63,12 +85,12 @@ namespace Artemis.Core
{
IsInvalid = true;
}
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
Bitmap.Dispose();
Surface.Dispose();
_pixelData?.Dispose();
}
#endregion

View File

@ -139,21 +139,23 @@ namespace Artemis.Core.Services
module.InternalUpdate(args.DeltaTime);
// Render all active modules
SKTexture texture =_rgbService.OpenRender();
SKTexture texture = _rgbService.OpenRender();
using (SKCanvas canvas = new(texture.Bitmap))
SKCanvas canvas = texture.Surface.Canvas;
canvas.Save();
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0));
// While non-activated modules may be updated above if they expand the main data model, they may never render
if (!ModuleRenderingDisabled)
{
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0));
// While non-activated modules may be updated above if they expand the main data model, they may never render
if (!ModuleRenderingDisabled)
{
foreach (Module module in modules.Where(m => m.IsActivated))
module.InternalRender(args.DeltaTime, canvas, texture.Bitmap.Info);
}
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
foreach (Module module in modules.Where(m => m.IsActivated))
module.InternalRender(args.DeltaTime, canvas, texture.ImageInfo);
}
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
canvas.RestoreToCount(-1);
canvas.Flush();
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
}

View File

@ -8,7 +8,6 @@ using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core;
using Serilog;
using SkiaSharp;
namespace Artemis.Core.Services
{
@ -52,7 +51,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)
@ -232,8 +231,9 @@ namespace Artemis.Core.Services
{
if (!RenderOpen)
throw new ArtemisCoreException("Render pipeline is already closed");
RenderOpen = false;
_texture?.CopyPixelData();
}
public void CreateTexture()

View File

@ -149,6 +149,7 @@
<PackageReference Include="RawInput.Sharp" Version="0.0.3" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="2.80.2" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.80.2" />
<PackageReference Include="Stylet" Version="1.3.5" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Threading;
using Artemis.Core;
using Artemis.Core.Ninject;
using Artemis.Core.Services;
using Artemis.UI.Ninject;
@ -13,9 +14,11 @@ using Artemis.UI.Screens;
using Artemis.UI.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.SkiaSharp;
using Artemis.UI.Stylet;
using Ninject;
using Serilog;
using SkiaSharp;
using Stylet;
namespace Artemis.UI
@ -69,6 +72,8 @@ namespace Artemis.UI
{
UIElement view = viewManager.CreateAndBindViewForModelIfNecessary(RootViewModel);
((TrayViewModel) RootViewModel).SetTaskbarIcon(view);
CreateGraphicsContext();
});
// Initialize the core async so the UI can show the progress
@ -90,7 +95,7 @@ namespace Artemis.UI
IRegistrationService registrationService = Kernel.Get<IRegistrationService>();
registrationService.RegisterInputProvider();
registrationService.RegisterControllers();
// Initialize background services
Kernel.Get<IDeviceLayoutService>();
}
@ -128,6 +133,22 @@ 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.");

View File

@ -83,18 +83,20 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
Execute.OnUIThreadSync(() =>
{
SKImageInfo bitmapInfo = e.Texture.Bitmap.Info;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
// ReSharper disable twice CompareOfFloatsByEqualityOperator
if (CurrentFrame is not WriteableBitmap writable || writable.Width != bitmapInfo.Width || writable.Height != bitmapInfo.Height)
{
CurrentFrame = e.Texture.Bitmap.ToWriteableBitmap();
CurrentFrame = e.Texture.Surface.Snapshot().ToWriteableBitmap();
return;
}
using SKImage skImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels());
using SKImage skImage = e.Texture.Surface.Snapshot();
if (_frameTargetPath != null)
{

View File

@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
namespace Artemis.UI.SkiaSharp
{
internal class Kernel32
{
private const string kernel32 = "kernel32.dll";
static Kernel32()
{
CurrentModuleHandle = Kernel32.GetModuleHandle(null);
if (CurrentModuleHandle == IntPtr.Zero)
{
throw new Exception("Could not get module handle.");
}
}
public static IntPtr CurrentModuleHandle { get; }
[DllImport(kernel32, CallingConvention = CallingConvention.Winapi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern IntPtr GetModuleHandle([MarshalAs(UnmanagedType.LPTStr)] string lpModuleName);
}
}

View File

@ -0,0 +1,109 @@
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

@ -0,0 +1,35 @@
using System;
using SharpVk.Khronos;
using SkiaSharp;
using Device = SharpVk.Device;
using Instance = SharpVk.Instance;
using PhysicalDevice = SharpVk.PhysicalDevice;
using Queue = SharpVk.Queue;
namespace Artemis.UI.SkiaSharp
{
internal class VkContext : IDisposable
{
public virtual Instance Instance { get; protected set; }
public virtual PhysicalDevice PhysicalDevice { get; protected set; }
public virtual Surface Surface { get; protected set; }
public virtual Device Device { get; protected set; }
public virtual Queue GraphicsQueue { get; protected set; }
public virtual Queue PresentQueue { get; protected set; }
public virtual uint GraphicsFamily { get; protected set; }
public virtual uint PresentFamily { get; protected set; }
public virtual GRVkGetProcedureAddressDelegate GetProc { get; protected set; }
public virtual GRSharpVkGetProcedureAddressDelegate SharpVkGetProc { get; protected set; }
public virtual void Dispose() => Instance?.Dispose();
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Windows.Forms;
using SharpVk;
using SharpVk.Khronos;
namespace Artemis.UI.SkiaSharp
{
internal sealed class Win32VkContext : VkContext
{
private static readonly NativeWindow window = new NativeWindow();
public Win32VkContext()
{
Instance = Instance.Create(null, new[] { "VK_KHR_surface", "VK_KHR_win32_surface" });
PhysicalDevice = Instance.EnumeratePhysicalDevices().First();
Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, window.Handle);
(GraphicsFamily, PresentFamily) = FindQueueFamilies();
DeviceQueueCreateInfo[]? queueInfos = new[]
{
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);
PresentQueue = Device.GetQueue(PresentFamily, 0);
GetProc = (name, instanceHandle, deviceHandle) =>
{
if (deviceHandle != IntPtr.Zero)
return Device.GetProcedureAddress(name);
return Instance.GetProcedureAddress(name);
};
SharpVkGetProc = (name, instance, device) =>
{
if (device != null)
return device.GetProcedureAddress(name);
if (instance != null)
return instance.GetProcedureAddress(name);
// 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);
};
}
private (uint, uint) FindQueueFamilies()
{
QueueFamilyProperties[]? queueFamilyProperties = PhysicalDevice.GetQueueFamilyProperties();
var graphicsFamily = queueFamilyProperties
.Select((properties, index) => new { properties, index })
.SkipWhile(pair => !pair.properties.QueueFlags.HasFlag(QueueFlags.Graphics))
.FirstOrDefault();
if (graphicsFamily == null)
throw new Exception("Unable to find graphics 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);
}
}
}

View File

@ -0,0 +1,97 @@
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;
}
}
}

View File

@ -126,6 +126,16 @@
"SkiaSharp.Views.Desktop.Common": "2.80.2"
}
},
"SkiaSharp.Vulkan.SharpVk": {
"type": "Direct",
"requested": "[2.80.2, )",
"resolved": "2.80.2",
"contentHash": "qiqlbgMsSdxTsaPErtE1lXoMXolVVF9E6irmSTzlW++6BbW8tzA89n7GNsgMYJgyo2ljHZhX5ydhFn0Rkj7VHw==",
"dependencies": {
"SharpVk": "0.4.2",
"SkiaSharp": "2.80.2"
}
},
"Stylet": {
"type": "Direct",
"requested": "[1.3.5, )",
@ -513,6 +523,15 @@
"resolved": "1.7.1",
"contentHash": "ljl9iVpmGOjgmxXxyulMBfl7jCLEMmTOSIrQwJJQLIm5PFhtaxRRgdQPY5ElXz+vfPKqX7Aj3RGnAN+SUN7V3w=="
},
"SharpVk": {
"type": "Transitive",
"resolved": "0.4.2",
"contentHash": "0CzZJWKw6CTmxKOXzCCyTKCD7tZB6g2+tm2VSSCXWTHlIMHxlRzbH5BaqkYCGo9Y23wp0hPuz2U3NifMH1VI6w==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.4.0",
"System.ValueTuple": "4.4.0"
}
},
"SkiaSharp": {
"type": "Transitive",
"resolved": "2.80.2",

View File

@ -228,4 +228,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=snackbar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Timelines/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Timelines/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vulkan/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>