forked from amkovkov/GranuSightSoftware2
feat: GSS2.Test - UI+DI, Vulkan render test
This commit is contained in:
@@ -357,6 +357,11 @@ public class CameraService : IDisposable
|
|||||||
|
|
||||||
_logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers);
|
_logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers);
|
||||||
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
||||||
|
_logger.LogDebug("Request buffer planes: ");
|
||||||
|
foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
|
||||||
|
foreach (var (i, plane) in buffer.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void ImageCaptureFrameBuffersInitialize(CancellationToken cancellationToken)
|
private void ImageCaptureFrameBuffersInitialize(CancellationToken cancellationToken)
|
||||||
@@ -401,6 +406,10 @@ public class CameraService : IDisposable
|
|||||||
|
|
||||||
_logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers);
|
_logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers);
|
||||||
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
||||||
|
foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
|
||||||
|
foreach (var (i, plane) in buffer.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
|
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
|
||||||
@@ -649,6 +658,91 @@ public class CameraService : IDisposable
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(FrameBuffer, StreamConfiguration)?> CaptureViewFinderBuffer(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_camera is null)
|
||||||
|
throw new InvalidOperationException("Camera not initialized");
|
||||||
|
if (_viewFinderStream is null)
|
||||||
|
throw new InvalidOperationException("ViewFinderStream not initialized");
|
||||||
|
if (_viewFinderStreamConfiguration is null)
|
||||||
|
throw new InvalidOperationException("ViewFinderStreamConfiguration not initialized");
|
||||||
|
if (_viewFinderFrameBufferAllocator is null)
|
||||||
|
throw new InvalidOperationException("ViewFinderFrameBufferAllocator not initialized");
|
||||||
|
|
||||||
|
var effectiveCancellationToken = CancellationTokenSource
|
||||||
|
.CreateLinkedTokenSource(_disposeCts.Token, cancellationToken)
|
||||||
|
.Token;
|
||||||
|
|
||||||
|
var buffers = _viewFinderFrameBufferAllocator.Buffers(_viewFinderStream);
|
||||||
|
var buffer = buffers.First();
|
||||||
|
|
||||||
|
var request = _camera.CreateRequest(checked((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()));
|
||||||
|
var result = request.AddBuffer(_viewFinderStream, buffer);
|
||||||
|
if (result < 0)
|
||||||
|
throw new Exception("Error while request create");
|
||||||
|
_logger.LogDebug("Request(s) created");
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var cts = new TaskCompletionSource();
|
||||||
|
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
if (request.Handle != e.Request.Handle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var buffer = e.Request.FindBuffer(_viewFinderStream);
|
||||||
|
if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError ||
|
||||||
|
buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status);
|
||||||
|
e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers);
|
||||||
|
var result = _camera.QueueRequest(e.Request);
|
||||||
|
if (result >= 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Request complited: \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", e.Request.Status, e.Request.Cookie, e.Request.Sequence, e.Request.HasPendingBuffers);
|
||||||
|
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
||||||
|
_logger.LogDebug("Request buffer planes: ");
|
||||||
|
foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
|
||||||
|
foreach (var (i, plane) in buffer.Planes.Enumerate())
|
||||||
|
_logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset);
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
cts.SetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
_camera.RequestCompleted += RequestCompleted;
|
||||||
|
result = _camera.QueueRequest(request);
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
_camera.RequestCompleted -= RequestCompleted;
|
||||||
|
throw new Exception("Error while request queue");
|
||||||
|
}
|
||||||
|
_logger.LogDebug("Request queued");
|
||||||
|
await cts.Task.WaitAsync(effectiveCancellationToken);
|
||||||
|
_camera.RequestCompleted -= RequestCompleted;
|
||||||
|
_logger.LogDebug("Request complited");
|
||||||
|
|
||||||
|
return new (buffer, _viewFinderStreamConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Test()
|
||||||
|
{
|
||||||
|
var config = _viewFinderStreamConfiguration!;
|
||||||
|
var size = config.Size;
|
||||||
|
var fourcc = config.PixelFormat.Fourcc;
|
||||||
|
var modifier = config.PixelFormat.Modifier;
|
||||||
|
var primariesEnum = config.ColorSpace?.Primaries;
|
||||||
|
var transferFunctionEnum = config.ColorSpace?.TransferFunction;
|
||||||
|
var ycbcrEncodingEnum = config.ColorSpace?.YcbcrEncoding;
|
||||||
|
var rangeEnum = config.ColorSpace?.Range;
|
||||||
|
var stride = config.Stride;
|
||||||
|
_logger.LogInformation($"Cannot find decoder for fourcc=0x{fourcc:X}({new string(BitConverter.GetBytes(fourcc).Select(b => (char)b).ToArray())}) modifier=0x{modifier:X} primariesEnum={primariesEnum} transferFunctionEnum={transferFunctionEnum} ycbcrEncodingEnum={ycbcrEncodingEnum} rangeEnum={rangeEnum}");
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!_disposedValue)
|
if (!_disposedValue)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:GSS2.Test"
|
||||||
x:Class="GSS2.Test.App"
|
x:Class="GSS2.Test.App"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Dark">
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
<Application.DataTemplates>
|
||||||
|
<local:DependencyInjectionViewLocator/>
|
||||||
|
</Application.DataTemplates>
|
||||||
</Application>
|
</Application>
|
||||||
+4
-62
@@ -1,83 +1,25 @@
|
|||||||
using System.Drawing;
|
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
using GSS2.Core;
|
|
||||||
using GSS2.Core.Analysis.AmineContent.Database;
|
|
||||||
using GSS2.Core.Hardware;
|
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Logging.Console;
|
|
||||||
|
|
||||||
namespace GSS2.Test;
|
namespace GSS2.Test;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
private readonly IHost _host = BuildHostApp();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
_host.Start();
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IHost BuildHostApp()
|
|
||||||
{
|
|
||||||
var builder = Host.CreateApplicationBuilder();
|
|
||||||
|
|
||||||
builder.Logging.ClearProviders();
|
|
||||||
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
|
|
||||||
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
|
|
||||||
builder.Logging.AddProvider(new FileLoggerProvider("app.log"));
|
|
||||||
builder.Services.AddSingleton<LibCameraLogSink>();
|
|
||||||
|
|
||||||
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
|
|
||||||
builder.Services.AddAmineContentResultsContext(builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddLightsService("Hardware:Lights");
|
|
||||||
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
|
||||||
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
|
||||||
builder.Services.AddCameraService("Hardware:Camera", true);
|
|
||||||
|
|
||||||
var host = builder.Build();
|
|
||||||
host.Services.GetRequiredService<LibCameraLogSink>();
|
|
||||||
|
|
||||||
using (var scope = host.Services.CreateScope())
|
|
||||||
{
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
|
|
||||||
context.Database.Migrate();
|
|
||||||
}
|
|
||||||
using (var scope = host.Services.CreateScope())
|
|
||||||
{
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
|
|
||||||
context.Database.Migrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
|
var mainWindowViewModel = Program.ApplicationHost.Services.GetRequiredService<MainWindowViewModel>();
|
||||||
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
desktop.MainWindow = new MainWindow()
|
||||||
var lightsService = _host.Services.GetRequiredService<LightsService>();
|
|
||||||
_ = lightsService.Run(CancellationToken.None);
|
|
||||||
_ = lightsService.SnowfallAsync(0.3, 1, Color.Aqua);
|
|
||||||
|
|
||||||
var cameraService = _host.Services.GetRequiredService<CameraService>();
|
|
||||||
var illuminatorService = _host.Services.GetRequiredService<IlluminatorService>();
|
|
||||||
|
|
||||||
desktop.MainWindow = new MainWindow(cameraService, illuminatorService, lightsService);
|
|
||||||
desktop.MainWindow.Closed += async (_, _) =>
|
|
||||||
{
|
{
|
||||||
await lightsService.DisconnectAsync();
|
DataContext = mainWindowViewModel
|
||||||
await _host.StopAsync();
|
|
||||||
_host.Dispose();
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Control xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="using:GSS2.Test"
|
||||||
|
x:Class="GSS2.Test.CameraView"
|
||||||
|
x:DataType="local:CameraViewModel">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<local:CameraViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
</Control>
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Rendering.Composition;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
using GSS2.Vulkan;
|
||||||
|
|
||||||
|
using Iot.Device.FtCommon;
|
||||||
|
|
||||||
|
using LibCameraSharp;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
interface IImage
|
||||||
|
{
|
||||||
|
public (IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties, Silk.NET.Vulkan.Semaphore semaphore) Export();
|
||||||
|
}
|
||||||
|
interface IRenderer
|
||||||
|
{
|
||||||
|
public abstract IImage ImportOrUpdate(int fd, int width, int height);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VkImage
|
||||||
|
{
|
||||||
|
public int Fd { get; set; }
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public ulong MemorySize { get; set; }
|
||||||
|
public Silk.NET.Vulkan.Semaphore Semaphore { get; set; }
|
||||||
|
|
||||||
|
public (IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties, Silk.NET.Vulkan.Semaphore semaphore) Export()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
new PlatformHandle(new IntPtr(Fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor),
|
||||||
|
new PlatformGraphicsExternalImageProperties
|
||||||
|
{
|
||||||
|
Width = Width,
|
||||||
|
Height = Height,
|
||||||
|
Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
|
||||||
|
MemorySize = MemorySize
|
||||||
|
},
|
||||||
|
Semaphore
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class VkRenderer
|
||||||
|
{
|
||||||
|
readonly VulkanContext context = new VulkanContext("GSS2.Test", "CameraView");
|
||||||
|
|
||||||
|
public IPlatformHandle ExportSemaphore(bool renderFinished)
|
||||||
|
{
|
||||||
|
int fd = context.ExportSemaphoreFd(renderFinished ? context.RenderFinishedSemaphore : context.ImageAvailableSemaphore);
|
||||||
|
return new PlatformHandle(new IntPtr(fd), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe public VkImage ImportOrUpdate(int fd, int width, int height)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var extImageInfo = new ExternalMemoryImageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ExternalMemoryImageCreateInfo,
|
||||||
|
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
|
||||||
|
};
|
||||||
|
|
||||||
|
var imageInfo = new ImageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageCreateInfo,
|
||||||
|
PNext = &extImageInfo,
|
||||||
|
ImageType = ImageType.Type2D,
|
||||||
|
Format = Format.R8G8B8A8Unorm,
|
||||||
|
Extent = new Extent3D
|
||||||
|
{
|
||||||
|
Width = checked((uint)width),
|
||||||
|
Height = checked((uint)height),
|
||||||
|
Depth = 1
|
||||||
|
},
|
||||||
|
MipLevels = 1,
|
||||||
|
ArrayLayers = 1,
|
||||||
|
Samples = SampleCountFlags.Count1Bit,
|
||||||
|
Tiling = ImageTiling.Optimal,
|
||||||
|
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||||
|
SharingMode = SharingMode.Exclusive,
|
||||||
|
InitialLayout = ImageLayout.Undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Api.CreateImage(context.Device, &imageInfo, null, out var image).ThrowOnError();
|
||||||
|
context.Api.GetImageMemoryRequirements(context.Device, image, out var memReq);
|
||||||
|
|
||||||
|
var exportAllocInfo = new ExportMemoryAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ExportMemoryAllocateInfo,
|
||||||
|
HandleTypes = ExternalMemoryHandleTypeFlags.OpaqueFDBit
|
||||||
|
};
|
||||||
|
|
||||||
|
var allocInfo = new MemoryAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryAllocateInfo,
|
||||||
|
PNext = &exportAllocInfo,
|
||||||
|
AllocationSize = memReq.Size,
|
||||||
|
MemoryTypeIndex = checked((uint)FindMemoryType(memReq.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit))
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Api.AllocateMemory(context.Device, &allocInfo, null, out var memory).ThrowOnError();
|
||||||
|
context.Api.BindImageMemory(context.Device, image, memory, 0).ThrowOnError();
|
||||||
|
|
||||||
|
var poolInfo = new CommandPoolCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandPoolCreateInfo,
|
||||||
|
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
|
||||||
|
QueueFamilyIndex = context.QueueFamilyIndex
|
||||||
|
};
|
||||||
|
context.Api.CreateCommandPool(context.Device, &poolInfo, null, out var commandPool).ThrowOnError();
|
||||||
|
|
||||||
|
var cmdBufferAllocInfo = new CommandBufferAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferAllocateInfo,
|
||||||
|
CommandPool = commandPool,
|
||||||
|
Level = CommandBufferLevel.Primary,
|
||||||
|
CommandBufferCount = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Api.AllocateCommandBuffers(context.Device, &cmdBufferAllocInfo, out var cmd).ThrowOnError();
|
||||||
|
|
||||||
|
var clearColor = new ClearColorValue((float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble(), (float)Random.Shared.NextDouble());
|
||||||
|
var range = new ImageSubresourceRange
|
||||||
|
{
|
||||||
|
AspectMask = ImageAspectFlags.ColorBit,
|
||||||
|
BaseMipLevel = 0,
|
||||||
|
LevelCount = 1,
|
||||||
|
BaseArrayLayer = 0,
|
||||||
|
LayerCount = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var beginInfo = new CommandBufferBeginInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferBeginInfo,
|
||||||
|
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
|
||||||
|
};
|
||||||
|
context.Api.BeginCommandBuffer(cmd, &beginInfo).ThrowOnError();
|
||||||
|
|
||||||
|
TransitionImageLayout(cmd, image, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
|
||||||
|
|
||||||
|
context.Api.CmdClearColorImage(cmd, image, ImageLayout.TransferDstOptimal, &clearColor, 1, &range);
|
||||||
|
|
||||||
|
TransitionImageLayout(cmd, image, ImageLayout.TransferDstOptimal, ImageLayout.General);
|
||||||
|
|
||||||
|
context.Api.EndCommandBuffer(cmd).ThrowOnError();
|
||||||
|
|
||||||
|
var renderFinished = context.RenderFinishedSemaphore;
|
||||||
|
|
||||||
|
var submitInfo = new SubmitInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.SubmitInfo,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
PCommandBuffers = &cmd,
|
||||||
|
PSignalSemaphores = &renderFinished,
|
||||||
|
SignalSemaphoreCount = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Api.QueueSubmit(context.Queue, 1, &submitInfo, default).ThrowOnError();
|
||||||
|
|
||||||
|
var getFdInfo = new MemoryGetFdInfoKHR
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryGetFDInfoKhr,
|
||||||
|
Memory = memory,
|
||||||
|
HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
|
||||||
|
};
|
||||||
|
if (context.Api.TryGetDeviceExtension<KhrExternalMemoryFd>(context.Instance, context.Device, out var khrExternalMemoryFd))
|
||||||
|
Console.WriteLine("KhrExternalMemoryFd import success");
|
||||||
|
else
|
||||||
|
throw new Exception("KhrExternalMemoryFd import error");
|
||||||
|
khrExternalMemoryFd.GetMemoryF(context.Device, &getFdInfo, out var fd1).ThrowOnError();
|
||||||
|
Console.WriteLine($"Image export success {fd1}");
|
||||||
|
|
||||||
|
return new VkImage()
|
||||||
|
{
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
Fd = fd1,
|
||||||
|
MemorySize = memReq.Size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.Message);
|
||||||
|
Console.WriteLine(e.StackTrace);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
|
||||||
|
{
|
||||||
|
context!.Api.GetPhysicalDeviceMemoryProperties(context.PhysicalDevice, out var memProps);
|
||||||
|
for (int i = 0; i < memProps.MemoryTypeCount; i++)
|
||||||
|
if ((typeFilter & (1 << i)) != 0 && (memProps.MemoryTypes[i].PropertyFlags & properties) == properties)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
unsafe private void TransitionImageLayout(CommandBuffer cmd, Silk.NET.Vulkan.Image image, ImageLayout oldLayout, ImageLayout newLayout)
|
||||||
|
{
|
||||||
|
var barrier = new ImageMemoryBarrier
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
|
OldLayout = oldLayout,
|
||||||
|
NewLayout = newLayout,
|
||||||
|
SrcQueueFamilyIndex = 0,
|
||||||
|
DstQueueFamilyIndex = 0,
|
||||||
|
Image = image,
|
||||||
|
SubresourceRange = new ImageSubresourceRange
|
||||||
|
{
|
||||||
|
AspectMask = ImageAspectFlags.ColorBit,
|
||||||
|
BaseMipLevel = 0,
|
||||||
|
LevelCount = 1,
|
||||||
|
BaseArrayLayer = 0,
|
||||||
|
LayerCount = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PipelineStageFlags srcStage;
|
||||||
|
PipelineStageFlags dstStage;
|
||||||
|
|
||||||
|
if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
|
||||||
|
{
|
||||||
|
barrier.SrcAccessMask = 0;
|
||||||
|
barrier.DstAccessMask = AccessFlags.TransferWriteBit;
|
||||||
|
|
||||||
|
srcStage = PipelineStageFlags.TopOfPipeBit;
|
||||||
|
dstStage = PipelineStageFlags.TransferBit;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal)
|
||||||
|
{
|
||||||
|
|
||||||
|
barrier.SrcAccessMask = AccessFlags.TransferWriteBit;
|
||||||
|
barrier.DstAccessMask = AccessFlags.ShaderReadBit;
|
||||||
|
|
||||||
|
srcStage = PipelineStageFlags.TransferBit;
|
||||||
|
dstStage = PipelineStageFlags.FragmentShaderBit;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
srcStage = PipelineStageFlags.AllCommandsBit;
|
||||||
|
dstStage = PipelineStageFlags.AllCommandsBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
context!.Api.CmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, null, 0, null, 1, &barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class CameraView : Control
|
||||||
|
{
|
||||||
|
private bool _initialized = false;
|
||||||
|
private Compositor? _compositor;
|
||||||
|
private CompositionSurfaceVisual? _visual;
|
||||||
|
private ICompositionGpuInterop? _gpuInterop;
|
||||||
|
private CompositionDrawingSurface? Surface { get; set; }
|
||||||
|
private bool _compositionRequested;
|
||||||
|
private VkRenderer? _renerer;
|
||||||
|
private VkImage? _latestImage;
|
||||||
|
|
||||||
|
public CameraView()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToVisualTree(e);
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
Surface?.Dispose();
|
||||||
|
|
||||||
|
_initialized = false;
|
||||||
|
base.OnDetachedFromLogicalTree(e);
|
||||||
|
}
|
||||||
|
private async void Initialize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var selfVisual = ElementComposition.GetElementVisual(this)!;
|
||||||
|
_compositor = selfVisual.Compositor;
|
||||||
|
|
||||||
|
Surface = _compositor.CreateDrawingSurface();
|
||||||
|
_visual = _compositor.CreateSurfaceVisual();
|
||||||
|
_visual.Surface = Surface;
|
||||||
|
ElementComposition.SetElementChildVisual(this, _visual);
|
||||||
|
_gpuInterop = await _compositor.TryGetCompositionGpuInterop();
|
||||||
|
_renerer = new VkRenderer();
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error while initialization: \n{e.Message}\n{e.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
if (_visual is not null && change.Property == BoundsProperty)
|
||||||
|
{
|
||||||
|
_visual.Size = new Vector(Bounds.Width, Bounds.Height);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
if (change.Property == DataContextProperty)
|
||||||
|
{
|
||||||
|
(DataContext as CameraViewModel)?.Update += (_, ea) => Update(ea.FrameBuffer, ea.StreamConfiguration);
|
||||||
|
}
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderToSurface(VkImage image)
|
||||||
|
{
|
||||||
|
var (handle, properties, semaphore) = image.Export();
|
||||||
|
|
||||||
|
var imageRenderedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(false));
|
||||||
|
var renderCompletedSemaphore = _gpuInterop!.ImportSemaphore(_renerer!.ExportSemaphore(true));
|
||||||
|
|
||||||
|
var importedImage = _gpuInterop!.ImportImage(handle, properties);
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Invoke(async () =>
|
||||||
|
{
|
||||||
|
await importedImage.ImportCompleted;
|
||||||
|
await Surface!.UpdateWithSemaphoresAsync(importedImage, renderCompletedSemaphore, imageRenderedSemaphore)
|
||||||
|
.ContinueWith((t) =>
|
||||||
|
{
|
||||||
|
if (t.Exception is not null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error while surface update: \n{t.Exception.Message}\n{t.Exception.StackTrace} ");
|
||||||
|
foreach (var e in t.Exception.InnerExceptions)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.Message);
|
||||||
|
Console.WriteLine(e.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private void OnComposition()
|
||||||
|
{
|
||||||
|
_compositionRequested = false;
|
||||||
|
|
||||||
|
var image = _latestImage;
|
||||||
|
if (image is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RenderToSurface(image);
|
||||||
|
}
|
||||||
|
private void Update(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null)
|
||||||
|
{
|
||||||
|
if (!_initialized || _compositor is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (frameBuffer is not null && streamConfiguration is not null && _renerer is not null)
|
||||||
|
{
|
||||||
|
var image = _renerer.ImportOrUpdate(frameBuffer.Planes.First().Fd.Get(), checked((int)streamConfiguration.Size.Width), checked((int)streamConfiguration.Size.Height));
|
||||||
|
_latestImage = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_compositionRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_compositionRequested = true;
|
||||||
|
_compositor?.RequestCompositionUpdate(OnComposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using LibCameraSharp;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
public class CameraViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public class UpdateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public FrameBuffer? FrameBuffer { get; }
|
||||||
|
public StreamConfiguration? StreamConfiguration { get; }
|
||||||
|
|
||||||
|
public UpdateEventArgs(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null)
|
||||||
|
{
|
||||||
|
FrameBuffer = frameBuffer;
|
||||||
|
StreamConfiguration = streamConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private readonly ILogger<CameraViewModel> _logger;
|
||||||
|
|
||||||
|
public event EventHandler<UpdateEventArgs>? Update;
|
||||||
|
|
||||||
|
public CameraViewModel(ILogger<CameraViewModel> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_logger.LogInformation("Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CallUpdate(FrameBuffer? frameBuffer = null, StreamConfiguration? streamConfiguration = null)
|
||||||
|
{
|
||||||
|
Update?.Invoke(this, new UpdateEventArgs(frameBuffer, streamConfiguration));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Protocols.Configuration;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using GSS2.Core.Hardware;
|
||||||
|
using GSS2.Core.Analysis.AmineContent.Database;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
public static class DependencyInjectionHelper
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddUi(this IServiceCollection services) =>
|
||||||
|
services
|
||||||
|
.AddTransient<CameraView>()
|
||||||
|
.AddTransient<CameraViewModel>()
|
||||||
|
.AddTransient<MainWindowViewModel>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
public class DependencyInjectionViewLocator : IDataTemplate
|
||||||
|
{
|
||||||
|
public Control? Build(object? data)
|
||||||
|
{
|
||||||
|
if (data is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var name = data.GetType().FullName?.Replace("ViewModel", "View");
|
||||||
|
if (name is null)
|
||||||
|
return new TextBlock { Text = $"Not found: {data}" };
|
||||||
|
var type = Type.GetType(name);
|
||||||
|
if (type is null)
|
||||||
|
return new TextBlock { Text = $"Not found type: {name} for {data}" };
|
||||||
|
|
||||||
|
var control = Program.ApplicationHost.Services.GetRequiredService(type) as Control;
|
||||||
|
if (control is null)
|
||||||
|
return new TextBlock { Text = $"Not found in DI: {type} for {data}" };
|
||||||
|
|
||||||
|
control.DataContext = data;
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(object? data) => data is ViewModelBase;
|
||||||
|
}
|
||||||
@@ -38,7 +38,6 @@ public sealed class FileLogger : ILogger
|
|||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
File.AppendAllText(_filePath, line + Environment.NewLine);
|
File.AppendAllText(_filePath, line + Environment.NewLine);
|
||||||
|
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
File.AppendAllText(_filePath, exception + Environment.NewLine);
|
File.AppendAllText(_filePath, exception + Environment.NewLine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ public sealed class FileLoggerProvider : ILoggerProvider
|
|||||||
{
|
{
|
||||||
private readonly string _filePath;
|
private readonly string _filePath;
|
||||||
|
|
||||||
public FileLoggerProvider(string filePath) => _filePath = filePath;
|
public FileLoggerProvider(string filePath, bool append = true)
|
||||||
|
{
|
||||||
|
_filePath = filePath;
|
||||||
|
if (!append && File.Exists(filePath))
|
||||||
|
File.Create(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _filePath);
|
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _filePath);
|
||||||
|
|
||||||
|
|||||||
@@ -8,23 +8,31 @@
|
|||||||
<UserSecretsId>dotnet-GSS2.Test-191ece0a-855d-46ec-9ecb-44378fc3db83</UserSecretsId>
|
<UserSecretsId>dotnet-GSS2.Test-191ece0a-855d-46ec-9ecb-44378fc3db83</UserSecretsId>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||||
<PackageReference Include="Avalonia" Version="11.3.9" />
|
<PackageReference Include="Avalonia" Version="11.3.11" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.9" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.9" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.9" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.11" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.9">
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11">
|
||||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
|
<PackageReference Include="Silk.NET.Direct3D11" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Direct3D.Compilers" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\GSS2.Core\GSS2.Core.csproj" />
|
<ProjectReference Include="..\GSS2.Core\GSS2.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\GSS2.Vulkan\GSS2.Vulkan.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,8 +2,15 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
xmlns:local="using:GSS2.Test"
|
||||||
x:Class="GSS2.Test.MainWindow"
|
x:Class="GSS2.Test.MainWindow"
|
||||||
|
x:DataType="local:MainWindowViewModel"
|
||||||
Title="GSS2.Test">
|
Title="GSS2.Test">
|
||||||
<Image x:Name="image"/>
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<local:MainWindowViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<ContentControl x:Name="cameraView" Content="{Binding CameraViewModel}"/>
|
||||||
|
<!-- <local:CameraView x:Name="cameraView"/> -->
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,60 +1,26 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
|
|
||||||
using GSS2.Core.Hardware;
|
|
||||||
|
|
||||||
namespace GSS2.Test;
|
namespace GSS2.Test;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly CameraService _cameraService;
|
public MainWindow()
|
||||||
private readonly IlluminatorService _illuminatorService;
|
|
||||||
private readonly LightsService _lightsService;
|
|
||||||
|
|
||||||
public MainWindow(CameraService cameraService, IlluminatorService illuminatorService, LightsService lightsService)
|
|
||||||
{
|
{
|
||||||
_cameraService = cameraService;
|
|
||||||
_illuminatorService = illuminatorService;
|
|
||||||
_lightsService = lightsService;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
PointerPressed += async (_, _) =>
|
PointerPressed += async (_, _) =>
|
||||||
{
|
{
|
||||||
_lightsService.CpuLoad(0.5, 1, System.Drawing.Color.Aqua);
|
var viewModel = DataContext as MainWindowViewModel;
|
||||||
|
if (viewModel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
var result = await viewModel.CaptureImage();
|
||||||
|
if (result is not null)
|
||||||
{
|
{
|
||||||
_illuminatorService.SetIntensity(0.01, 0, 0);
|
var (frameBuffer, streamConfiguration) = result.Value;
|
||||||
_illuminatorService.TurnOn();
|
// cameraView.Update(frameBuffer, streamConfiguration);
|
||||||
using var img = await _cameraService.CaptureImage(CancellationToken.None, 5);
|
(cameraView.Content as CameraViewModel)?.CallUpdate(frameBuffer, streamConfiguration);
|
||||||
|
|
||||||
if (img is not null)
|
|
||||||
{
|
|
||||||
var bytes = img.ToBytes(".png");
|
|
||||||
using var stream = new MemoryStream(bytes);
|
|
||||||
image.Source = new Bitmap(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
_lightsService.Flash(0.5, 2, System.Drawing.Color.Green);
|
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
_lightsService.Flash(0.5, 2, System.Drawing.Color.Red);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_illuminatorService.TurnOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
var snowfallTimer = new System.Timers.Timer(3000);
|
|
||||||
snowfallTimer.Elapsed += (_, _) =>
|
|
||||||
{
|
|
||||||
lightsService.Snowfall(0.3, 1, System.Drawing.Color.Aqua);
|
|
||||||
snowfallTimer.Dispose();
|
|
||||||
};
|
|
||||||
snowfallTimer.Start();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
using GSS2.Core.Hardware;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
public partial class MainWindowViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly CameraService _cameraService;
|
||||||
|
private readonly IlluminatorService _illuminatorService;
|
||||||
|
private readonly LightsService _lightsService;
|
||||||
|
private readonly ILogger<MainWindowViewModel> _logger;
|
||||||
|
[ObservableProperty] private CameraViewModel _cameraViewModel;
|
||||||
|
|
||||||
|
public MainWindowViewModel(
|
||||||
|
CameraService cameraService,
|
||||||
|
IlluminatorService illuminatorService,
|
||||||
|
LightsService lightsService,
|
||||||
|
ILogger<MainWindowViewModel> logger,
|
||||||
|
CameraViewModel cameraViewModel
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_cameraService = cameraService;
|
||||||
|
_illuminatorService = illuminatorService;
|
||||||
|
_lightsService = lightsService;
|
||||||
|
_logger = logger;
|
||||||
|
CameraViewModel = cameraViewModel;
|
||||||
|
_logger.LogInformation("Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(LibCameraSharp.FrameBuffer, LibCameraSharp.StreamConfiguration)?> CaptureImage()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Capture image");
|
||||||
|
_lightsService.CpuLoad(0.5, 1, System.Drawing.Color.Aqua);
|
||||||
|
|
||||||
|
(LibCameraSharp.FrameBuffer, LibCameraSharp.StreamConfiguration)? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_illuminatorService.SetIntensity(0.01, 0, 0);
|
||||||
|
_illuminatorService.TurnOn();
|
||||||
|
result = await _cameraService.CaptureViewFinderBuffer(CancellationToken.None);
|
||||||
|
_lightsService.Flash(0.5, 2, System.Drawing.Color.Green);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error while image capture");
|
||||||
|
_lightsService.Flash(0.5, 2, System.Drawing.Color.Red);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_illuminatorService.TurnOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
var snowfallTimer = new System.Timers.Timer(3000);
|
||||||
|
snowfallTimer.Elapsed += (_, _) =>
|
||||||
|
{
|
||||||
|
_lightsService.Snowfall(0.3, 1, System.Drawing.Color.Aqua);
|
||||||
|
snowfallTimer.Dispose();
|
||||||
|
};
|
||||||
|
snowfallTimer.Start();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
-3
@@ -1,25 +1,82 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging.Console;
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
|
||||||
using GSS2.Core;
|
using GSS2.Core;
|
||||||
using GSS2.Core.Analysis.AmineContent.Database;
|
using GSS2.Core.Analysis.AmineContent.Database;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Vulkan;
|
||||||
|
|
||||||
namespace GSS2.Test;
|
namespace GSS2.Test;
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
|
public static IHost ApplicationHost { get; }
|
||||||
|
|
||||||
|
static Program()
|
||||||
|
{
|
||||||
|
ApplicationHost = BuildHostApp();
|
||||||
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
ApplicationHost.RunAsync();
|
||||||
BuildAvaloniaApp()
|
BuildAvaloniaApp()
|
||||||
.UseStandardRuntimePlatformSubsystem()
|
|
||||||
.StartWithClassicDesktopLifetime(args);
|
.StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
private static IHost BuildHostApp()
|
||||||
|
{
|
||||||
|
var builder = Host.CreateApplicationBuilder();
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
|
||||||
|
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
|
||||||
|
builder.Logging.AddProvider(new FileLoggerProvider("full.log", true));
|
||||||
|
builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false));
|
||||||
|
builder.Services.AddSingleton<LibCameraLogSink>();
|
||||||
|
|
||||||
|
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
|
||||||
|
builder.Services.AddAmineContentResultsContext(builder.Configuration);
|
||||||
|
|
||||||
|
builder.Services.AddLightsService("Hardware:Lights");
|
||||||
|
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
||||||
|
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
||||||
|
builder.Services.AddCameraService("Hardware:Camera", true);
|
||||||
|
|
||||||
|
builder.Services.AddUi();
|
||||||
|
|
||||||
|
var host = builder.Build();
|
||||||
|
host.Services.GetRequiredService<LibCameraLogSink>();
|
||||||
|
|
||||||
|
using (var scope = host.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
|
||||||
|
context.Database.Migrate();
|
||||||
|
}
|
||||||
|
using (var scope = host.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
|
||||||
|
context.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.With(new X11PlatformOptions
|
||||||
|
{
|
||||||
|
RenderingMode = [X11RenderingMode.Vulkan]
|
||||||
|
})
|
||||||
|
.With(new VulkanOptions
|
||||||
|
{
|
||||||
|
VulkanInstanceCreationOptions = new VulkanInstanceCreationOptions
|
||||||
|
{
|
||||||
|
UseDebug = true
|
||||||
|
}
|
||||||
|
})
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace GSS2.Test;
|
||||||
|
|
||||||
|
public abstract class ViewModelBase : ObservableObject
|
||||||
|
{ }
|
||||||
+12
-11
@@ -1,4 +1,5 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
using GSS2.Core.Analysis.AmineContent.Database;
|
using GSS2.Core.Analysis.AmineContent.Database;
|
||||||
using GSS2.Core.Hardware;
|
using GSS2.Core.Hardware;
|
||||||
|
|
||||||
@@ -24,24 +25,24 @@ public class Worker(
|
|||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _cameraService.InitializeAsync(cancellationToken);
|
_cameraService.Test();
|
||||||
|
|
||||||
_ = _lightsService.Run(cancellationToken);
|
// _ = _lightsService.Run(cancellationToken);
|
||||||
_ = _lightsService.SnowfallAsync(0.3, 1, Color.Aqua, cancellationToken);
|
// _ = _lightsService.SnowfallAsync(0.3, 1, Color.Aqua, cancellationToken);
|
||||||
|
|
||||||
_illuminatorService.SetIntensity(0.01, 0, 0);
|
// _illuminatorService.SetIntensity(0.01, 0, 0);
|
||||||
_illuminatorService.TurnOn();
|
// _illuminatorService.TurnOn();
|
||||||
|
|
||||||
await Task.Delay(1000);
|
// await Task.Delay(1000);
|
||||||
|
|
||||||
var img = await _cameraService.CaptureImage(cancellationToken, 50);
|
// var img = await _cameraService.CaptureImage(cancellationToken, 50);
|
||||||
img?.SaveImage("IMAGE.png");
|
// // img?.SaveImage("IMAGE.png");
|
||||||
|
|
||||||
await Task.Delay(1000);
|
// await Task.Delay(1000);
|
||||||
|
|
||||||
_illuminatorService.TurnOff();
|
// _illuminatorService.TurnOff();
|
||||||
|
|
||||||
await _lightsService.DisconnectAsync(cancellationToken);
|
// await _lightsService.DisconnectAsync(cancellationToken);
|
||||||
|
|
||||||
applicationLifetime.StopApplication();
|
applicationLifetime.StopApplication();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
||||||
"GSS2.Core.Hardware.LightsService": "Information",
|
"GSS2.Core.Hardware.LightsService": "Information",
|
||||||
"GSS2.Core.Hardware.IlluminatorService": "Information",
|
"GSS2.Core.Hardware.IlluminatorService": "Information",
|
||||||
"GSS2.Core.Hardware.CameraService": "Debug",
|
"GSS2.Core.Hardware.CameraService": "Information",
|
||||||
"LibCamera": "Information",
|
"LibCamera": "Information",
|
||||||
"LibCamera-Camera": "Information",
|
"LibCamera-Camera": "Information",
|
||||||
"LibCamera-RPI": "Information",
|
"LibCamera-RPI": "Information",
|
||||||
@@ -36,11 +36,11 @@
|
|||||||
},
|
},
|
||||||
"Camera": {
|
"Camera": {
|
||||||
"CameraId": "",
|
"CameraId": "",
|
||||||
"ViewFinderFrameBufferCount": 10,
|
"ViewFinderFrameBufferCount": 2,
|
||||||
"ViewFinderWidth": 1920,
|
"ViewFinderWidth": 1920,
|
||||||
"ViewFinderHeight": 1080,
|
"ViewFinderHeight": 1080,
|
||||||
"ViewFinderFps": 30,
|
"ViewFinderFps": 30,
|
||||||
"ImageCaptureFrameBufferCount": 5,
|
"ImageCaptureFrameBufferCount": 2,
|
||||||
"ImageCaptureWidth": 4056,
|
"ImageCaptureWidth": 4056,
|
||||||
"ImageCaptureHeight": 3040
|
"ImageCaptureHeight": 3040
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Основан на примере GpuInterop Avalonia
|
||||||
|
// https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/ByteString.cs
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace GSS2.Vulkan;
|
||||||
|
|
||||||
|
unsafe sealed class ByteString : IDisposable
|
||||||
|
{
|
||||||
|
private bool _disposedValue;
|
||||||
|
private readonly byte* _pointer;
|
||||||
|
|
||||||
|
public ByteString(string s)
|
||||||
|
{
|
||||||
|
_pointer = (byte*)Marshal.StringToHGlobalAnsi(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(new IntPtr(_pointer));
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~ByteString() => Dispose(disposing: false);
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator byte*(ByteString obj) => obj._pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe sealed class ByteStringList : IDisposable
|
||||||
|
{
|
||||||
|
private bool _disposedValue;
|
||||||
|
private readonly List<ByteString> _list;
|
||||||
|
private readonly byte** _pointer;
|
||||||
|
|
||||||
|
public int Count => _list.Count;
|
||||||
|
|
||||||
|
public ByteStringList(IEnumerable<string> list)
|
||||||
|
{
|
||||||
|
_list = list.Select(x => new ByteString(x)).ToList();
|
||||||
|
_pointer = (byte**)Marshal.AllocHGlobal(IntPtr.Size * _list.Count + 1);
|
||||||
|
for (var c = 0; c < _list.Count; c++)
|
||||||
|
_pointer[c] = (byte*)_list[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(new IntPtr(_pointer));
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~ByteStringList() => Dispose(disposing: false);
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator byte**(ByteStringList obj) => obj._pointer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.11" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Silk.NET.Direct3D11" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Direct3D.Compilers" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
// Основан на примере GpuInterop Avalonia
|
||||||
|
// https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/VulkanContext.cs
|
||||||
|
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
using Semaphore = Silk.NET.Vulkan.Semaphore;
|
||||||
|
|
||||||
|
namespace GSS2.Vulkan;
|
||||||
|
|
||||||
|
unsafe public class VulkanContext : IDisposable
|
||||||
|
{
|
||||||
|
private bool _disposedValue;
|
||||||
|
|
||||||
|
public Vk Api { get; init; }
|
||||||
|
public Instance Instance { get; init; }
|
||||||
|
public PhysicalDevice PhysicalDevice { get; init; }
|
||||||
|
public Device Device { get; init; }
|
||||||
|
public uint QueueFamilyIndex { get; init; }
|
||||||
|
public Queue Queue { get; init; }
|
||||||
|
public Semaphore ImageAvailableSemaphore { get; init; }
|
||||||
|
public Semaphore RenderFinishedSemaphore { get; init; }
|
||||||
|
|
||||||
|
public VulkanContext(string applicationName, string engineName)
|
||||||
|
{
|
||||||
|
Api = Vk.GetApi();
|
||||||
|
|
||||||
|
List<string> instanceExtensions =
|
||||||
|
[
|
||||||
|
"VK_KHR_get_physical_device_properties2",
|
||||||
|
"VK_KHR_external_memory_capabilities",
|
||||||
|
"VK_KHR_external_semaphore_capabilities",
|
||||||
|
"VK_EXT_debug_utils"
|
||||||
|
];
|
||||||
|
List<string> instanceLayers =
|
||||||
|
[
|
||||||
|
|
||||||
|
];
|
||||||
|
List<string> deviceExtensions =
|
||||||
|
[
|
||||||
|
"VK_KHR_external_memory",
|
||||||
|
"VK_KHR_external_memory_fd",
|
||||||
|
"VK_KHR_external_semaphore",
|
||||||
|
"VK_KHR_external_semaphore_fd"
|
||||||
|
];
|
||||||
|
|
||||||
|
Instance = CreateInstance(Api, applicationName, engineName, instanceExtensions, instanceLayers);
|
||||||
|
PhysicalDevice = FindPhysicalDevice(Api, Instance, deviceExtensions);
|
||||||
|
(Device, QueueFamilyIndex) = CreateDevice(Api, Instance, PhysicalDevice, deviceExtensions);
|
||||||
|
Queue = CreateQueue(Api, Device, QueueFamilyIndex);
|
||||||
|
ImageAvailableSemaphore = CreateSemaphore(Api, Device);
|
||||||
|
RenderFinishedSemaphore = CreateSemaphore(Api, Device);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExportSemaphoreFd(Semaphore semaphore)
|
||||||
|
{
|
||||||
|
if (!Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(Instance, Device, out var extension))
|
||||||
|
throw new InvalidOperationException($"No {KhrExternalSemaphoreFd.ExtensionName} device extension");
|
||||||
|
|
||||||
|
var info = new SemaphoreGetFdInfoKHR()
|
||||||
|
{
|
||||||
|
SType = StructureType.SemaphoreGetFDInfoKhr,
|
||||||
|
Semaphore = semaphore,
|
||||||
|
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
|
||||||
|
};
|
||||||
|
extension.GetSemaphoreF(Device, in info, out var fd).ThrowOnError();
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Instance CreateInstance(Vk api, string applicationName, string engineName, List<string> extentions, List<string> layers)
|
||||||
|
{
|
||||||
|
using var pApplicationName = new ByteString(applicationName);
|
||||||
|
using var pEngineName = new ByteString(engineName);
|
||||||
|
var applicationInfo = new ApplicationInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ApplicationInfo,
|
||||||
|
PApplicationName = pApplicationName,
|
||||||
|
PEngineName = pEngineName,
|
||||||
|
ApiVersion = new Version32(1, 1, 0),
|
||||||
|
EngineVersion = new Version32(1, 0, 0),
|
||||||
|
ApplicationVersion = new Version32(1, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
using var pExtensions = new ByteStringList(extentions);
|
||||||
|
using var pLayers = new ByteStringList(layers);
|
||||||
|
var instanceCreateInfo = new InstanceCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.InstanceCreateInfo,
|
||||||
|
PApplicationInfo = &applicationInfo,
|
||||||
|
PpEnabledExtensionNames = pExtensions,
|
||||||
|
EnabledExtensionCount = checked((uint)pExtensions.Count),
|
||||||
|
PpEnabledLayerNames = pLayers,
|
||||||
|
EnabledLayerCount = checked((uint)pLayers.Count),
|
||||||
|
Flags = default
|
||||||
|
};
|
||||||
|
|
||||||
|
api.CreateInstance(in instanceCreateInfo, null, out var Instance).ThrowOnError();
|
||||||
|
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
private static PhysicalDevice FindPhysicalDevice(Vk api, Instance instance, List<string> extensions)
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
api.EnumeratePhysicalDevices(instance, ref count, null).ThrowOnError();
|
||||||
|
var physicalDevices = stackalloc PhysicalDevice[(int)count];
|
||||||
|
api.EnumeratePhysicalDevices(instance, ref count, physicalDevices).ThrowOnError();
|
||||||
|
|
||||||
|
for (uint c = 0; c < count; c++)
|
||||||
|
{
|
||||||
|
if (extensions.Any(e => !api.IsDeviceExtensionPresent(physicalDevices[c], e)))
|
||||||
|
continue;
|
||||||
|
return physicalDevices[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Suшtable Device not found");
|
||||||
|
}
|
||||||
|
private static (Device device, uint queueFamilyIndex) CreateDevice(Vk api, Instance instance, PhysicalDevice physicalDevice, List<string> extensions)
|
||||||
|
{
|
||||||
|
uint queueFamilyCount = 0;
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, null);
|
||||||
|
var queueFamilyProperties = new QueueFamilyProperties[(int)queueFamilyCount];
|
||||||
|
fixed (QueueFamilyProperties* pQueueFamilyProperties = queueFamilyProperties)
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, pQueueFamilyProperties);
|
||||||
|
|
||||||
|
for (uint i = 0; i < queueFamilyCount; i++)
|
||||||
|
{
|
||||||
|
var queueFamily = queueFamilyProperties[i];
|
||||||
|
if (!queueFamily.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var queuePriorities = new float[(int)queueFamily.QueueCount];
|
||||||
|
|
||||||
|
for (var j = 0; j < queueFamily.QueueCount; j++)
|
||||||
|
queuePriorities[j] = 1f;
|
||||||
|
|
||||||
|
var features = new PhysicalDeviceFeatures();
|
||||||
|
|
||||||
|
Device device;
|
||||||
|
fixed (float* pQueuePriorities = queuePriorities)
|
||||||
|
{
|
||||||
|
var queueCreateInfo = new DeviceQueueCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.DeviceQueueCreateInfo,
|
||||||
|
QueueFamilyIndex = i,
|
||||||
|
QueueCount = queueFamily.QueueCount,
|
||||||
|
PQueuePriorities = pQueuePriorities
|
||||||
|
};
|
||||||
|
|
||||||
|
using var pEnabledDeviceExtensions = new ByteStringList(extensions);
|
||||||
|
var deviceCreateInfo = new DeviceCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.DeviceCreateInfo,
|
||||||
|
QueueCreateInfoCount = 1,
|
||||||
|
PQueueCreateInfos = &queueCreateInfo,
|
||||||
|
PpEnabledExtensionNames = pEnabledDeviceExtensions,
|
||||||
|
EnabledExtensionCount = checked((uint)pEnabledDeviceExtensions.Count),
|
||||||
|
PEnabledFeatures = &features
|
||||||
|
};
|
||||||
|
|
||||||
|
api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out device).ThrowOnError();
|
||||||
|
|
||||||
|
return (device, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("Cannot create device");
|
||||||
|
}
|
||||||
|
private static Queue CreateQueue(Vk api, Device device, uint queueFamilyIndex)
|
||||||
|
{
|
||||||
|
api.GetDeviceQueue(device, queueFamilyIndex, 0, out var queue);
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
private static Semaphore CreateSemaphore(Vk api, Device device)
|
||||||
|
{
|
||||||
|
var semaphoreExportInfo = new ExportSemaphoreCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ExportSemaphoreCreateInfo,
|
||||||
|
HandleTypes = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
|
||||||
|
};
|
||||||
|
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.SemaphoreCreateInfo,
|
||||||
|
PNext = &semaphoreExportInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
api.CreateSemaphore(device, in semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
|
||||||
|
return semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (Api is null)
|
||||||
|
return;
|
||||||
|
Api.DestroySemaphore(Device, ImageAvailableSemaphore, null);
|
||||||
|
Api.DestroySemaphore(Device, RenderFinishedSemaphore, null);
|
||||||
|
}
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Основан на примере GpuInterop Avalonia
|
||||||
|
// https://github.com/AvaloniaUI/Avalonia/blob/5f3dbae22244e830b464fc680c6c28ca124be41a/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
|
||||||
|
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace GSS2.Vulkan;
|
||||||
|
|
||||||
|
public static class VulkanExtensions
|
||||||
|
{
|
||||||
|
public static void ThrowOnError(this Result result)
|
||||||
|
{
|
||||||
|
if (result != Result.Success)
|
||||||
|
throw new Exception($"Unexpected API error \"{result}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpCl
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.FrameDecoders", "GSS2.FrameDecoders\GSS2.FrameDecoders.csproj", "{05085293-E710-41D6-8F61-701ADBA52FA9}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.FrameDecoders", "GSS2.FrameDecoders\GSS2.FrameDecoders.csproj", "{05085293-E710-41D6-8F61-701ADBA52FA9}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.Vulkan", "GSS2.Vulkan\GSS2.Vulkan.csproj", "{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -73,6 +75,18 @@ Global
|
|||||||
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.Build.0 = Release|Any CPU
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.ActiveCfg = Release|Any CPU
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.Build.0 = Release|Any CPU
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{CA315A5C-A9FA-44F2-A09C-08F3504ADD62}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user