Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various updates of Avalonia.Vulkan and SilkNetDemo projects #9885

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions samples/SilkNetDemo/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
x:Class="SilkNetDemo.MainWindow"
Title="SilkNetDemo"
xmlns:local="clr-namespace:SilkNetDemo">
<local:VulkanDemo />
</Window>
<local:VulkanDemo x:Name="VulkanDemo1"/>
</Window>
10 changes: 9 additions & 1 deletion samples/SilkNetDemo/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;

namespace SilkNetDemo
Expand All @@ -9,6 +10,13 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();

this.Unloaded += delegate(object? sender, RoutedEventArgs args)
{
var vulkanDemo = this.Get<VulkanDemo>("VulkanDemo1");
vulkanDemo.Dispose();
};

#if DEBUG
this.AttachDevTools();
#endif
Expand All @@ -19,4 +27,4 @@ private void InitializeComponent()
AvaloniaXamlLoader.Load(this);
}
}
}
}
8 changes: 6 additions & 2 deletions samples/SilkNetDemo/VulkanDemo.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ namespace SilkNetDemo;

public class VulkanDemo : UserControl
{

}
public void Dispose()
{
var vulkanDemoControl = this.Get<VulkanDemoControl>("Vulkan");
vulkanDemoControl?.Dispose();
}
}
29 changes: 14 additions & 15 deletions samples/SilkNetDemo/VulkanDemoControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ static VulkanDemoControl()
private DeviceMemory _depthImageMemory;
private ImageView _depthImageView;
private VulkanCommandBufferPool _commandPool;
private Fence _fence;
private Vk _vk;

private byte[] GetShader(bool fragment)
Expand Down Expand Up @@ -129,10 +128,12 @@ protected override void OnVulkanRender(IVulkanSharedDevice sharedDevice, ISwapch

_previousSwapchain = swapchain;

var pixelSize = swapchain.Size;

var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll);
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height),
Matrix4x4.CreatePerspectiveFieldOfView((float)Math.PI / 4f, (float)pixelSize.Width / (float)pixelSize.Height,
0.01f, 1000);

var vertexConstant = new VertextPushConstant()
Expand All @@ -159,8 +160,8 @@ protected override void OnVulkanRender(IVulkanSharedDevice sharedDevice, ISwapch
api.CmdSetViewport(commandBufferHandle, 0, 1,
new Viewport()
{
Width = (float)Bounds.Width,
Height = (float)Bounds.Height,
Width = (float)pixelSize.Width,
Height = (float)pixelSize.Height,
MaxDepth = 1,
MinDepth = 0,
X = 0,
Expand All @@ -169,7 +170,7 @@ protected override void OnVulkanRender(IVulkanSharedDevice sharedDevice, ISwapch

var scissor = new Rect2D
{
Extent = new Extent2D((uint?)Bounds.Width, (uint?)Bounds.Height)
Extent = new Extent2D((uint?)pixelSize.Width, (uint?)pixelSize.Height)
};

api.CmdSetScissor(commandBufferHandle, 0, 1, &scissor);
Expand All @@ -187,7 +188,7 @@ protected override void OnVulkanRender(IVulkanSharedDevice sharedDevice, ISwapch
RenderPass = _renderPass,
Framebuffer = _framebuffers[swapchain.CurrentImageIndex],
RenderArea =
new Rect2D(new Offset2D(0, 0), new Extent2D((uint?)Bounds.Width, (uint?)Bounds.Height)),
new Rect2D(new Offset2D(0, 0), new Extent2D((uint?)pixelSize.Width, (uint?)pixelSize.Height)),
ClearValueCount = 2,
PClearValues = clearValue
};
Expand Down Expand Up @@ -238,7 +239,6 @@ protected override void OnVulkanDeInit(IVulkanSharedDevice sharedDevice)

api.DestroyBuffer(device, _indexBuffer, null);
api.FreeMemory(device, _indexBufferMemory, null);
api.DestroyFence(device, _fence, null);

_commandPool.Dispose();
}
Expand Down Expand Up @@ -274,16 +274,16 @@ public unsafe void DestroyTemporalObjects(Vk api, Device device)
}
}

private unsafe void CreateDepthAttachment(Vk api, Device device, PhysicalDevice physicalDevice)
private unsafe void CreateDepthAttachment(Vk api, Device device, PhysicalDevice physicalDevice, PixelSize size)
{
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
Format = Format.D32Sfloat,
Extent =
new Extent3D((uint?)Bounds.Width,
(uint?)Bounds.Height, 1),
new Extent3D((uint?)size.Width,
(uint?)size.Height, 1),
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Expand Down Expand Up @@ -343,7 +343,7 @@ private unsafe void CreateTemporalObjects(Vk api, Device device, PhysicalDevice
{
DestroyTemporalObjects(api, device);

CreateDepthAttachment(api, device, physicalDevice);
CreateDepthAttachment(api, device, physicalDevice, swapchain.Size);

var current = swapchain.GetImage(swapchain.CurrentImageIndex);
// create renderpasses
Expand Down Expand Up @@ -486,8 +486,8 @@ private unsafe void CreateTemporalObjects(Vk api, Device device, PhysicalDevice
{
X = 0,
Y = 0,
Width = (float)Bounds.Width,
Height = (float)Bounds.Height,
Width = (float)swapchain.Size.Width,
Height = (float)swapchain.Size.Height,
MinDepth = 0,
MaxDepth = 1
};
Expand Down Expand Up @@ -784,7 +784,6 @@ protected override void OnVulkanInit(IVulkanSharedDevice sharedDevice)
Flags = FenceCreateFlags.FenceCreateSignaledBit
};

api.CreateFence(device, fenceCreateInfo, null, out _fence);
_commandPool = new VulkanCommandBufferPool(api, device, new Queue(sharedDevice.Device.MainQueueHandle),
sharedDevice.Device.GraphicsQueueFamilyIndex);
}
Expand Down Expand Up @@ -904,4 +903,4 @@ public VulkanDemoControl()
}

}
}
}
21 changes: 10 additions & 11 deletions samples/SilkNetDemo/VulkanUtils/VulkanCommandBufferPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ public void BeginRecording()
{
if (!_hasStarted)
{
_hasStarted = true;
_api.WaitForFences(_device, 1, _fence, true, ulong.MaxValue);

_api.ResetFences(_device, 1, _fence);


var beginInfo = new CommandBufferBeginInfo
{
Expand All @@ -137,6 +140,8 @@ public void BeginRecording()
};

_api.BeginCommandBuffer(InternalHandle, beginInfo);

_hasStarted = true;
}
}

Expand All @@ -152,20 +157,16 @@ public void EndRecording()

public void Submit()
{
Submit(null, null, null, _fence);
Submit(null, null, null);
}

public unsafe void Submit(
ReadOnlySpan<Semaphore> waitSemaphores,
ReadOnlySpan<PipelineStageFlags> waitDstStageMask,
ReadOnlySpan<Semaphore> signalSemaphores,
Fence? fence = null)
ReadOnlySpan<Semaphore> signalSemaphores)
{
EndRecording();

if (!fence.HasValue)
fence = _fence;

fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
{
fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
Expand All @@ -182,10 +183,8 @@ public unsafe void Submit(
SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
PSignalSemaphores = pSignalSemaphores,
};

_api.ResetFences(_device, 1, fence.Value);

_api.QueueSubmit(_queue, 1, submitInfo, fence.Value);

_api.QueueSubmit(_queue, 1, submitInfo, _fence);
}
}

Expand Down
34 changes: 28 additions & 6 deletions src/Avalonia.Vulkan/Interop/VulkanDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,21 @@ private VulkanDevice(IVulkanInstance instance, VkDevice handle, VkPhysicalDevice

T CheckAccess<T>(T f)
{
CheckIsDisposed(f);

if (_lockedByThread != Thread.CurrentThread)
throw new InvalidOperationException("This class is only usable when locked");
return f;
}

private T CheckIsDisposed<T>(T f)
{
if (IsDisposed)
throw new VulkanException("Cannot access a disposed VulkanDevice");

return f;
}

public IDisposable Lock()
{
Monitor.Enter(_lock);
Expand All @@ -51,15 +61,27 @@ public IDisposable Lock()
}

public bool IsLost => false;
public IntPtr Handle => _handle.Handle;
public IntPtr PhysicalDeviceHandle => _physicalDeviceHandle.Handle;
public IntPtr Handle => CheckIsDisposed(_handle).Handle;
public IntPtr PhysicalDeviceHandle => CheckIsDisposed(_physicalDeviceHandle).Handle;
public IntPtr MainQueueHandle => CheckAccess(_mainQueue).Handle;
public uint GraphicsQueueFamilyIndex => _graphicsQueueIndex;
public IVulkanInstance Instance { get; }
public bool IsDisposed { get; private set; }

public object? TryGetFeature(Type featureType) => null;


public void Dispose()
{
// TODO
}
if (IsDisposed)
return;

public object? TryGetFeature(Type featureType) => null;
}
if (_handle.Handle != IntPtr.Zero)
{
var api = new VulkanInstanceApi(Instance);
api.DestroyDevice(_handle, IntPtr.Zero);
}

IsDisposed = true;
}
}
5 changes: 4 additions & 1 deletion src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public partial void GetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, u
[GetProcAddress("vkDestroySurfaceKHR")]
public partial void DestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, IntPtr pAllocator);

[GetProcAddress("vkDestroyDevice")]
public partial void DestroyDevice(VkDevice device, IntPtr pAllocator);

[GetProcAddress("vkGetPhysicalDeviceSurfaceFormatsKHR")]
public partial VkResult GetPhysicalDeviceSurfaceFormatsKHR(
VkPhysicalDevice physicalDevice,
Expand All @@ -74,4 +77,4 @@ public partial VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice
[GetProcAddress("vkGetPhysicalDeviceSurfacePresentModesKHR")]
public partial VkResult GetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
ref uint32_t pPresentModeCount, VkPresentModeKHR* pPresentModes);
}
}
32 changes: 19 additions & 13 deletions src/Avalonia.Vulkan/VulkanControlBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@

namespace Avalonia.Vulkan;

public abstract class VulkanControlBase : Control
public abstract class VulkanControlBase : Control, IDisposable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure that making a control to be disposable is a good idea. Any cleanup logic should happen in OnDetachedFromVisualTree

Copy link
Author

@abenedik abenedik Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnDetachedFromVisualTree is also called when VulkanControlBase is inside a TabControl and when user changes Tabs. Also, if user decides to move the control from one parent to another, this should not dispose all Vulkan resources. Ok, the VulkanBitmapChain that is used by the VulkanControlBase could be disposed in OnDetachedFromVisualTree. But calling OnVulkanDeInit is too "heavy" - if user would be showing a complex 3D scene we should not dispose all Vulkan resources because the control was hidden or moved. The user of VulkanControlBase should be aware that this control creates a lot of unmanaged resources and that it is his responsibility to clear that (for example, when the MainWindow is closed - as in the updated SilkNetDemo). When using only a single VulkanControlBase, then there is actually no need to clean anything because the OS will clean all resources; but when using multiple VulkanControlBase objects, user will need to manually dispose the resources when he knows they will no longer be used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VulkanControlBase is responsible for managing a swapchain and associated resources. When it's detached the swapchain becomes invalid and any associated resources like framebuffers should be disposed as well.

I'm currently working on something like SwapChainPanel and generic non-primary context creation APIs that would allow to manually manage resources that are shared between different vulkan-powered controls.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will remove the IDisposable from VulkanControlBase and will update the code so that in OnDetachedFromVisualTree the resources created by the VulkanControlBase will be disposed: VulkanBitmapChain and VulkanCommandBufferPool. But I would not call OnVulkanDeInit because OnDetachedFromVisualTree does not mean that all Vulkan resources should be disposed (actually I think OnVulkanDeInit should be removed because the VulkanControlBase cannot know when the Vulkan resources should be disposed). I would preserve the manual disposal of the Vulkan resource that are created in the VulkanDemoControl (they are disposed when the MainWindow is Unloaded). Is this ok?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still needs to inform the user code that swapchain images are no longer valid and that corresponding framebuffers should be removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ok, I will update the PR with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#9925 - see external memory interop spec.

{
private Task<bool>? _initialization;
private VulkanControlContext? _currentContext;

public bool IsDisposed { get; private set; }

private bool EnsureInitialized()
{
if (IsDisposed)
return false;

if (_initialization != null)
{
// Check if we've previously failed to initialize on this platform
Expand All @@ -47,7 +52,7 @@ await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature(
typeof(IVulkanSharedDeviceGraphicsContextFeature));
if (feature?.SharedDevice == null)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("VulkanControlBase",
Logger.TryGet(LogEventLevel.Error, "Vulkan")?.Log("VulkanControlBase",
"Unable to obtain Vulkan device from the renderer");
return false;
}
Expand Down Expand Up @@ -76,27 +81,23 @@ protected virtual void OnVulkanRender(IVulkanSharedDevice device, ISwapchain ima

public sealed override void Render(DrawingContext context)
{
if(!EnsureInitialized())
if(IsDisposed || !EnsureInitialized())
return;
_currentContext!.Render(context);
}

void DoCleanup()
public void Dispose()
{
if (_currentContext != null)
if (_currentContext != null && !IsDisposed)
{
OnVulkanDeInit(_currentContext.SharedDevice);
_currentContext.Dispose();
_currentContext = null;
_initialization = null;

IsDisposed = true;
}
}

protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DoCleanup();
base.OnDetachedFromVisualTree(e);
}


class VulkanControlContext : IDisposable
Expand Down Expand Up @@ -193,7 +194,11 @@ public void Render(IDrawingContextImpl context)
var bitmap = _chain.GetConsumerBitmap();
if (bitmap == null)
return;
context.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, Bounds, Bounds);

context.DrawBitmap(source: RefCountable.CreateUnownedNotClonable(bitmap),
opacity: 1,
sourceRect: new Rect(0, 0, _chain.Size.Width, _chain.Size.Height),
destRect: Bounds);
}

public bool Equals(ICustomDrawOperation? other) => false;
Expand All @@ -203,6 +208,7 @@ protected interface ISwapchain
{
public int ImageCount { get; }
public int CurrentImageIndex { get; }
public PixelSize Size { get; }
public VulkanImageInfo GetImage(int index);
}

Expand Down Expand Up @@ -318,4 +324,4 @@ public VulkanImageInfo GetImage(int index)
return _images[index].ImageInfo;
}
}
}
}
Loading