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:
parent
34b9d69ed8
commit
a02431bb82
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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.");
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
24
src/Artemis.UI/SkiaSharp/Kernel32.cs
Normal file
24
src/Artemis.UI/SkiaSharp/Kernel32.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
109
src/Artemis.UI/SkiaSharp/User32.cs
Normal file
109
src/Artemis.UI/SkiaSharp/User32.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Artemis.UI/SkiaSharp/VkContext.cs
Normal file
35
src/Artemis.UI/SkiaSharp/VkContext.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
83
src/Artemis.UI/SkiaSharp/Win32VkContext.cs
Normal file
83
src/Artemis.UI/SkiaSharp/Win32VkContext.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/Artemis.UI/SkiaSharp/Win32Window.cs
Normal file
97
src/Artemis.UI/SkiaSharp/Win32Window.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user