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.JsonConverters;
using Artemis.Core.Services.Core; using Artemis.Core.Services.Core;
using Newtonsoft.Json; using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -116,5 +117,21 @@ namespace Artemis.Core
typeof(double), typeof(double),
typeof(decimal) 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(); canvas.Save();
Renderer.Open(Path, Parent as Folder); 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"); throw new ArtemisCoreException("Failed to open folder render context");
SKRect rendererBounds = Renderer.Path.Bounds; SKRect rendererBounds = Renderer.Path.Bounds;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) 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 required, apply the opacity override of the module to the root folder
if (IsRootFolder && Profile.Module.OpacityOverride < 1) 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 // 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--) 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)) 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 finally
{ {

View File

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

View File

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

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Diagnostics;
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;
@ -10,14 +12,19 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable public sealed class SKTexture : PixelTexture<byte>, IDisposable
{ {
private bool _disposed; private SKPixmap? _pixelData;
private SKImage? _rasterImage;
#region Constructors #region Constructors
internal SKTexture(int width, int height, float renderScale) internal SKTexture(int width, int height, float renderScale)
: base(width, height, 4, new AverageByteSampler()) : 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; RenderScale = renderScale;
} }
@ -25,10 +32,20 @@ namespace Artemis.Core
#region Methods #region Methods
internal void CopyPixelData()
{
using SKImage skImage = Surface.Snapshot();
_rasterImage?.Dispose();
_pixelData?.Dispose();
_rasterImage = skImage.ToRasterImage();
_pixelData = _rasterImage.PeekPixels();
}
/// <inheritdoc /> /// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<byte> pixel) 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 #endregion
@ -38,12 +55,17 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the SKBitmap backing this texture /// Gets the SKBitmap backing this texture
/// </summary> /// </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> /// <summary>
/// Gets the color data in RGB format /// Gets the color data in RGB format
/// </summary> /// </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> /// <summary>
/// Gets the render scale of the texture /// Gets the render scale of the texture
@ -67,8 +89,8 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_disposed = true; Surface.Dispose();
Bitmap.Dispose(); _pixelData?.Dispose();
} }
#endregion #endregion

View File

@ -139,22 +139,24 @@ namespace Artemis.Core.Services
module.InternalUpdate(args.DeltaTime); module.InternalUpdate(args.DeltaTime);
// Render all active modules // 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); foreach (Module module in modules.Where(m => m.IsActivated))
canvas.Clear(new SKColor(0, 0, 0)); module.InternalRender(args.DeltaTime, canvas, texture.ImageInfo);
// 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));
} }
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
canvas.RestoreToCount(-1);
canvas.Flush();
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface)); OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
} }
catch (Exception e) catch (Exception e)

View File

@ -8,7 +8,6 @@ using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core; using RGB.NET.Core;
using Serilog; using Serilog;
using SkiaSharp;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {
@ -234,6 +233,7 @@ namespace Artemis.Core.Services
throw new ArtemisCoreException("Render pipeline is already closed"); throw new ArtemisCoreException("Render pipeline is already closed");
RenderOpen = false; RenderOpen = false;
_texture?.CopyPixelData();
} }
public void CreateTexture() public void CreateTexture()

View File

@ -149,6 +149,7 @@
<PackageReference Include="RawInput.Sharp" Version="0.0.3" /> <PackageReference Include="RawInput.Sharp" Version="0.0.3" />
<PackageReference Include="Serilog" Version="2.10.0" /> <PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="2.80.2" /> <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="Stylet" Version="1.3.5" />
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <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;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Threading; using System.Windows.Threading;
using Artemis.Core;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject; using Artemis.UI.Ninject;
@ -13,9 +14,11 @@ using Artemis.UI.Screens;
using Artemis.UI.Services; using Artemis.UI.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.SkiaSharp;
using Artemis.UI.Stylet; using Artemis.UI.Stylet;
using Ninject; using Ninject;
using Serilog; using Serilog;
using SkiaSharp;
using Stylet; using Stylet;
namespace Artemis.UI namespace Artemis.UI
@ -69,6 +72,8 @@ namespace Artemis.UI
{ {
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
@ -128,6 +133,22 @@ 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

@ -83,18 +83,20 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{ {
Execute.OnUIThreadSync(() => Execute.OnUIThreadSync(() =>
{ {
SKImageInfo bitmapInfo = e.Texture.Bitmap.Info; SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height; RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width; RenderWidth = bitmapInfo.Width;
// ReSharper disable twice CompareOfFloatsByEqualityOperator // ReSharper disable twice CompareOfFloatsByEqualityOperator
if (CurrentFrame is not WriteableBitmap writable || writable.Width != bitmapInfo.Width || writable.Height != bitmapInfo.Height) 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; return;
} }
using SKImage skImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels()); using SKImage skImage = e.Texture.Surface.Snapshot();
if (_frameTargetPath != null) 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.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": { "Stylet": {
"type": "Direct", "type": "Direct",
"requested": "[1.3.5, )", "requested": "[1.3.5, )",
@ -513,6 +523,15 @@
"resolved": "1.7.1", "resolved": "1.7.1",
"contentHash": "ljl9iVpmGOjgmxXxyulMBfl7jCLEMmTOSIrQwJJQLIm5PFhtaxRRgdQPY5ElXz+vfPKqX7Aj3RGnAN+SUN7V3w==" "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": { "SkiaSharp": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.80.2", "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/=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>