Skip to content

Commit 9630059

Browse files
committed
Fix vulkan threading warnings
This is a temporary fix. See #175 for more details. In short it creates a separate vulkan queue for asset transfers and all the background thread code uses it. No locks are used. This is fine since the code currently limits itself to having one background thread for loading/importing maps running at a given moment. We'll want the ability to have multiple threads loading maps at the same time for user convenience and for SP map loading. So in the near future a better approach should be implemented. For now this works so we can get the first version of the C# rewrite up and running quickly.
1 parent 82f2a8a commit 9630059

File tree

8 files changed

+149
-56
lines changed

8 files changed

+149
-56
lines changed

Nanoforge/Gui/ViewModels/Documents/RendererTestDocumentViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ private MeshInstanceData LoadRfgStaticMesh(string cpuFilePath)
169169
private Mesh LoadRfgStaticMeshFromPackfile(RenderContext context, string cpuFilePath)
170170
{
171171
MeshInstanceData meshData = LoadRfgStaticMesh(cpuFilePath);
172-
Mesh mesh = new Mesh(context, meshData.Vertices, meshData.Indices, meshData.Config.NumVertices, meshData.Config.NumIndices, (uint)meshData.Config.IndexSize);
172+
Mesh mesh = new Mesh(context, meshData.Vertices, meshData.Indices, meshData.Config.NumVertices, meshData.Config.NumIndices, (uint)meshData.Config.IndexSize, context.TransferCommandPool, context.TransferQueue);
173173
return mesh;
174174
}
175175

@@ -227,7 +227,7 @@ private Texture2D LoadTextureFromPackfile(RenderContext context, string cpuFileP
227227
ImageTiling.Optimal, ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
228228
MemoryPropertyFlags.DeviceLocalBit,
229229
ImageAspectFlags.ColorBit);
230-
texture.SetPixels(pixels, generateMipMaps: false);
230+
texture.SetPixels(pixels, context.TransferCommandPool, context.TransferQueue, generateMipMaps: false);
231231
texture.CreateTextureSampler();
232232
texture.CreateImageView();
233233

Nanoforge/Render/Misc/QueueFamilyIndices.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ namespace Nanoforge.Render.Misc;
33
internal struct QueueFamilyIndices
44
{
55
public uint? GraphicsFamily { get; set; }
6+
public uint? GraphicsFamilyQueueCount { get; set; }
7+
public uint? TransferFamily { get; set; }
8+
public uint? TransferFamilyQueueCount { get; set; }
69

710
public bool IsComplete()
811
{
9-
return GraphicsFamily.HasValue;
12+
return GraphicsFamily.HasValue && TransferFamily.HasValue;
1013
}
1114
}

Nanoforge/Render/RenderContext.cs

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
7+
using System.Threading;
68
using Nanoforge.Render.Misc;
79
using Nanoforge.Render.Resources;
810
using Serilog;
@@ -23,7 +25,9 @@ public unsafe class RenderContext : IDisposable
2325
public Instance Instance;
2426
public DebugUtilsMessengerEXT DebugUtilsMessenger;
2527
public CommandPool CommandPool;
28+
public CommandPool TransferCommandPool;
2629
public Queue GraphicsQueue;
30+
public Queue TransferQueue;
2731
private ExtDebugUtils? _debugUtils;
2832

2933
public VkBuffer StagingBuffer = null!;
@@ -47,8 +51,6 @@ public unsafe class RenderContext : IDisposable
4751

4852
};
4953

50-
//TODO: Will have to change this to not use IWindow when porting this to Avalonia
51-
//TODO: Should some of this init code be stuck in a bootstrap class or something?
5254
public RenderContext()
5355
{
5456
#region Create Instance
@@ -148,25 +150,53 @@ public RenderContext()
148150
#endregion
149151

150152
#region Create logical device
151-
var indices = FindQueueFamilies(PhysicalDevice);
153+
QueueFamilyIndices queueFamilyIndices = FindQueueFamilies(PhysicalDevice);
154+
Debug.Assert(queueFamilyIndices.GraphicsFamily != null, "queueFamilyIndices.GraphicsFamily != null");
155+
Debug.Assert(queueFamilyIndices.TransferFamily != null, "queueFamilyIndices.TransferFamily != null");
156+
Log.Information($"Selected vulkan queues. Graphics queue: {queueFamilyIndices.GraphicsFamily.Value}, Transfer queue: {queueFamilyIndices.TransferFamily.Value}");
152157

153-
var uniqueQueueFamilies = new[] { indices.GraphicsFamily!.Value };
158+
uint[] uniqueQueueFamilies = [queueFamilyIndices.GraphicsFamily!.Value, queueFamilyIndices.TransferFamily!.Value];
154159
uniqueQueueFamilies = uniqueQueueFamilies.Distinct().ToArray();
155160

156161
using var mem = GlobalMemory.Allocate(uniqueQueueFamilies.Length * sizeof(DeviceQueueCreateInfo));
157162
var queueCreateInfos = (DeviceQueueCreateInfo*)Unsafe.AsPointer(ref mem.GetPinnableReference());
158163

159164
float queuePriority = 1.0f;
160-
for (int i = 0; i < uniqueQueueFamilies.Length; i++)
161-
{
162-
queueCreateInfos[i] = new()
165+
uint graphicsQueueIndex;
166+
uint trafficsQueueIndex;
167+
if (uniqueQueueFamilies.Length == 1)
168+
{
169+
//The graphics and transfer queue are in the same family, so we want two of this queue type with each queue having a different index.
170+
graphicsQueueIndex = 0;
171+
trafficsQueueIndex = 1;
172+
queueCreateInfos[0] = new()
163173
{
164174
SType = StructureType.DeviceQueueCreateInfo,
165-
QueueFamilyIndex = uniqueQueueFamilies[i],
166-
QueueCount = 1,
175+
QueueFamilyIndex = uniqueQueueFamilies[0],
176+
QueueCount = 2,
167177
PQueuePriorities = &queuePriority
168178
};
169179
}
180+
else if (uniqueQueueFamilies.Length >= 2)
181+
{
182+
//The graphics and transfer queue are in different families. So we want 1 of each with each being at index 0.
183+
graphicsQueueIndex = 0;
184+
trafficsQueueIndex = 0;
185+
for (int i = 0; i < uniqueQueueFamilies.Length; i++)
186+
{
187+
queueCreateInfos[i] = new()
188+
{
189+
SType = StructureType.DeviceQueueCreateInfo,
190+
QueueFamilyIndex = uniqueQueueFamilies[i],
191+
QueueCount = 1,
192+
PQueuePriorities = &queuePriority
193+
};
194+
}
195+
}
196+
else
197+
{
198+
throw new Exception($"Unexpected unique vulkan queue family count. Expected either 1 or 2. Found {uniqueQueueFamilies.Length}.");
199+
}
170200

171201
PhysicalDeviceFeatures deviceFeatures = new()
172202
{
@@ -200,7 +230,8 @@ public RenderContext()
200230
throw new Exception("failed to create logical device!");
201231
}
202232

203-
Vk.GetDeviceQueue(Device, indices.GraphicsFamily!.Value, 0, out GraphicsQueue);
233+
Vk.GetDeviceQueue(Device, queueFamilyIndices.GraphicsFamily!.Value, graphicsQueueIndex, out GraphicsQueue);
234+
Vk.GetDeviceQueue(Device, queueFamilyIndices.TransferFamily!.Value, trafficsQueueIndex, out TransferQueue);
204235

205236
if (EnableValidationLayers)
206237
{
@@ -211,7 +242,6 @@ public RenderContext()
211242
#endregion
212243

213244
#region Create main command pool
214-
var queueFamilyIndices = FindQueueFamilies(PhysicalDevice);
215245

216246
CommandPoolCreateInfo poolInfo = new()
217247
{
@@ -222,7 +252,21 @@ public RenderContext()
222252

223253
if (Vk.CreateCommandPool(Device, in poolInfo, null, out CommandPool) != Result.Success)
224254
{
225-
throw new Exception("failed to create command pool!");
255+
throw new Exception("Failed to create primary command pool!");
256+
}
257+
#endregion
258+
259+
#region Create command pool for background thread data transfers
260+
CommandPoolCreateInfo transferPoolInfo = new()
261+
{
262+
SType = StructureType.CommandPoolCreateInfo,
263+
QueueFamilyIndex = queueFamilyIndices.TransferFamily!.Value,
264+
Flags = CommandPoolCreateFlags.ResetCommandBufferBit
265+
};
266+
267+
if (Vk.CreateCommandPool(Device, in transferPoolInfo, null, out TransferCommandPool) != Result.Success)
268+
{
269+
throw new Exception("Failed to create transfer command pool!");
226270
}
227271
#endregion
228272

@@ -272,21 +316,62 @@ private QueueFamilyIndices FindQueueFamilies(PhysicalDevice device)
272316
Vk.GetPhysicalDeviceQueueFamilyProperties(device, ref queueFamilityCount, queueFamiliesPtr);
273317
}
274318

275-
276-
uint i = 0;
277-
foreach (var queueFamily in queueFamilies)
319+
//First find any graphics capable queue
320+
for (uint i = 0; i < queueFamilies.Length; i++)
278321
{
322+
QueueFamilyProperties queueFamily = queueFamilies[i];
279323
if (queueFamily.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
280324
{
281325
indices.GraphicsFamily = i;
326+
indices.GraphicsFamilyQueueCount = queueFamily.QueueCount;
327+
break;
282328
}
329+
}
283330

284-
if (indices.IsComplete())
331+
//TODO: Figure out how to do this. The transfer only queue on my device apparently couldn't support the pipeline barrier in the Texture2D layout transition code
332+
//Next try to find a queue that only has transfer and not graphics
333+
// for (uint i = 0; i < queueFamilies.Length; i++)
334+
// {
335+
// QueueFamilyProperties queueFamily = queueFamilies[i];
336+
// if (queueFamily.QueueFlags.HasFlag(QueueFlags.TransferBit) && !queueFamily.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
337+
// {
338+
// indices.TransferFamily = i;
339+
// indices.TransferFamilyQueueCount = queueFamily.QueueCount;
340+
// break;
341+
// }
342+
// }
343+
344+
//If we failed to find a transfer only queue then just use whatever queue that's available for transfer
345+
if (!indices.TransferFamily.HasValue)
346+
{
347+
for (uint i = 0; i < queueFamilies.Length; i++)
285348
{
286-
break;
349+
QueueFamilyProperties queueFamily = queueFamilies[i];
350+
if (queueFamily.QueueFlags.HasFlag(QueueFlags.TransferBit))
351+
{
352+
indices.TransferFamily = i;
353+
indices.TransferFamilyQueueCount = queueFamily.QueueCount;
354+
break;
355+
}
287356
}
288-
289-
i++;
357+
}
358+
359+
//Make sure there's enough queues available if the graphics and transfer queue are in the same family
360+
if (indices is { GraphicsFamily: not null, TransferFamily: not null } && indices.GraphicsFamily.Value == indices.TransferFamily.Value)
361+
{
362+
if (indices.GraphicsFamilyQueueCount < 2)
363+
{
364+
string err = $"Graphics and transfer queue families are the same and there isn't enough queues available. Require 2, only {indices.GraphicsFamilyQueueCount} are available.";
365+
Log.Error(err);
366+
throw new Exception(err);
367+
}
368+
}
369+
370+
if (!indices.IsComplete())
371+
{
372+
string err = "Failed to find valid vulkan queue families for graphics and transfer.";
373+
Log.Error(err);
374+
throw new Exception(err);
290375
}
291376

292377
return indices;
@@ -354,13 +439,13 @@ public uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
354439
throw new Exception("failed to find suitable memory type!");
355440
}
356441

357-
public CommandBuffer BeginSingleTimeCommands()
442+
public CommandBuffer BeginSingleTimeCommands(CommandPool pool)
358443
{
359444
CommandBufferAllocateInfo allocateInfo = new()
360445
{
361446
SType = StructureType.CommandBufferAllocateInfo,
362447
Level = CommandBufferLevel.Primary,
363-
CommandPool = CommandPool,
448+
CommandPool = pool,
364449
CommandBufferCount = 1,
365450
};
366451

@@ -377,7 +462,7 @@ public CommandBuffer BeginSingleTimeCommands()
377462
return commandBuffer;
378463
}
379464

380-
public void EndSingleTimeCommands(CommandBuffer commandBuffer)
465+
public void EndSingleTimeCommands(CommandBuffer commandBuffer, CommandPool pool, Queue queue)
381466
{
382467
Vk.EndCommandBuffer(commandBuffer);
383468

@@ -388,23 +473,23 @@ public void EndSingleTimeCommands(CommandBuffer commandBuffer)
388473
PCommandBuffers = &commandBuffer,
389474
};
390475

391-
Vk.QueueSubmit(GraphicsQueue, 1, in submitInfo, default);
392-
Vk.QueueWaitIdle(GraphicsQueue);
476+
Vk.QueueSubmit(queue, 1, in submitInfo, default);
477+
Vk.QueueWaitIdle(queue);
393478

394-
Vk.FreeCommandBuffers(Device, CommandPool, 1, in commandBuffer);
479+
Vk.FreeCommandBuffers(Device, pool, 1, in commandBuffer);
395480
}
396481

397482
private void CreateStagingBuffer()
398483
{
399484
StagingBuffer = new VkBuffer(this, StagingBufferSize, BufferUsageFlags.TransferSrcBit, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, canGrow: true);
400485
}
401486

402-
public CommandBuffer AllocateCommandBuffer()
487+
public CommandBuffer AllocateCommandBuffer(CommandPool pool)
403488
{
404489
var allocInfo = new CommandBufferAllocateInfo
405490
{
406491
SType = StructureType.CommandBufferAllocateInfo,
407-
CommandPool = CommandPool,
492+
CommandPool = pool,
408493
CommandBufferCount = 1,
409494
Level = CommandBufferLevel.Primary
410495
};
@@ -416,6 +501,7 @@ public void Dispose()
416501
{
417502
StagingBuffer.Destroy();
418503
Vk.DestroyCommandPool(Device, CommandPool, null);
504+
Vk.DestroyCommandPool(Device, TransferCommandPool, null);
419505
Vk.DestroyDevice(Device, null);
420506
_debugUtils?.DestroyDebugUtilsMessenger(Instance, DebugUtilsMessenger, null);
421507
Vk.DestroyInstance(Instance, null);

Nanoforge/Render/Resources/Mesh.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ public class Mesh
1818

1919
public bool Destroyed { get; private set; } = false;
2020

21-
public Mesh(RenderContext context, Span<byte> vertices, Span<byte> indices, uint vertexCount, uint indexCount, uint indexSize)
21+
public Mesh(RenderContext context, Span<byte> vertices, Span<byte> indices, uint vertexCount, uint indexCount, uint indexSize, CommandPool pool, Queue queue)
2222
{
2323
_context = context;
2424
VertexCount = vertexCount;
2525
IndexCount = indexCount;
2626

2727
_vertexBuffer = new VkBuffer(_context, (ulong)vertices.Length, BufferUsageFlags.TransferDstBit | BufferUsageFlags.VertexBufferBit, MemoryPropertyFlags.DeviceLocalBit);
2828
_context.StagingBuffer.SetData(vertices);
29-
_context.StagingBuffer.CopyTo(_vertexBuffer, (ulong)vertices.Length);
29+
_context.StagingBuffer.CopyTo(_vertexBuffer, (ulong)vertices.Length, pool, queue);
3030

3131
_indexBuffer = new VkBuffer(_context, (ulong)indices.Length, BufferUsageFlags.TransferDstBit | BufferUsageFlags.IndexBufferBit, MemoryPropertyFlags.DeviceLocalBit);
3232
_context.StagingBuffer.SetData(indices);
33-
_context.StagingBuffer.CopyTo(_indexBuffer, (ulong)indices.Length);
33+
_context.StagingBuffer.CopyTo(_indexBuffer, (ulong)indices.Length, pool, queue);
3434

3535
_indexType = indexSize switch
3636
{

Nanoforge/Render/Resources/Texture2D.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,24 @@ private void CreateImage()
9494
Context.Vk.BindImageMemory(Context.Device, _textureImage, Memory, 0);
9595
}
9696

97-
public void SetPixels(byte[] pixels, bool generateMipMaps = false)
97+
public void SetPixels(byte[] pixels, CommandPool pool, Queue queue, bool generateMipMaps = false)
9898
{
99-
TransitionLayoutImmediate(ImageLayout.TransferDstOptimal);
99+
TransitionLayoutImmediate(ImageLayout.TransferDstOptimal, pool, queue);
100100
Context.StagingBuffer.SetData(pixels);
101-
Context.StagingBuffer.CopyToImage(_textureImage, _width, _height);
102-
TransitionLayoutImmediate(ImageLayout.ShaderReadOnlyOptimal);
101+
Context.StagingBuffer.CopyToImage(_textureImage, _width, _height, pool, queue);
102+
TransitionLayoutImmediate(ImageLayout.ShaderReadOnlyOptimal, pool, queue);
103103

104104
if (generateMipMaps)
105105
{
106-
GenerateMipMaps(Context, _textureImage, _format, _width, _height, _mipLevels);
106+
GenerateMipMaps(Context, _textureImage, _format, _width, _height, _mipLevels, pool, queue);
107107
}
108108
}
109109

110-
public void TransitionLayoutImmediate(ImageLayout newLayout)
110+
public void TransitionLayoutImmediate(ImageLayout newLayout, CommandPool pool, Queue queue)
111111
{
112-
CommandBuffer cmd = Context.BeginSingleTimeCommands();
112+
CommandBuffer cmd = Context.BeginSingleTimeCommands(pool);
113113
TransitionLayout(cmd, newLayout);
114-
Context.EndSingleTimeCommands(cmd);
114+
Context.EndSingleTimeCommands(cmd, pool, queue);
115115
}
116116

117117
public void TransitionLayout(CommandBuffer cmd, ImageLayout newLayout)
@@ -185,7 +185,7 @@ public void TransitionLayout(CommandBuffer cmd, ImageLayout newLayout)
185185
_currentLayout = newLayout;
186186
}
187187

188-
private static void GenerateMipMaps(RenderContext context, Image image, Format imageFormat, uint width, uint height, uint mipLevels)
188+
private static void GenerateMipMaps(RenderContext context, Image image, Format imageFormat, uint width, uint height, uint mipLevels, CommandPool pool, Queue queue)
189189
{
190190
context.Vk.GetPhysicalDeviceFormatProperties(context.PhysicalDevice, imageFormat, out var formatProperties);
191191

@@ -194,7 +194,7 @@ private static void GenerateMipMaps(RenderContext context, Image image, Format i
194194
throw new Exception("texture image format does not support linear blitting!");
195195
}
196196

197-
var commandBuffer = context.BeginSingleTimeCommands();
197+
var commandBuffer = context.BeginSingleTimeCommands(pool);
198198

199199
ImageMemoryBarrier barrier = new()
200200
{
@@ -287,7 +287,7 @@ private static void GenerateMipMaps(RenderContext context, Image image, Format i
287287
0, null,
288288
1, in barrier);
289289

290-
context.EndSingleTimeCommands(commandBuffer);
290+
context.EndSingleTimeCommands(commandBuffer, pool, queue);
291291
}
292292

293293
public void CreateImageView()
@@ -367,7 +367,7 @@ public void CopyToBuffer(CommandBuffer cmd, Buffer buffer)
367367
Vk.CmdCopyImageToBuffer(cmd, _textureImage, _currentLayout, buffer, 1, copyRegion);
368368
}
369369

370-
public static Texture2D FromFile(RenderContext context, string path)
370+
public static Texture2D FromFile(RenderContext context, CommandPool pool, Queue queue, string path)
371371
{
372372
using var img = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(path);
373373
ulong imageSize = (ulong)(img.Width * img.Height * img.PixelType.BitsPerPixel / 8);
@@ -382,7 +382,7 @@ public static Texture2D FromFile(RenderContext context, string path)
382382
Texture2D texture = new Texture2D(context, (uint)img.Width, (uint)img.Height, mipLevels, vulkanFormat,
383383
ImageTiling.Optimal, ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit, MemoryPropertyFlags.DeviceLocalBit,
384384
ImageAspectFlags.ColorBit);
385-
texture.SetPixels(pixelData, generateMipMaps: false);
385+
texture.SetPixels(pixelData, pool, queue, generateMipMaps: false);
386386
texture.CreateTextureSampler();
387387
texture.CreateImageView();
388388

0 commit comments

Comments
 (0)