kiba-engine
device.c
1 #include <kiba/renderer/vulkan/device.h>
2 
3 #include <kiba/containers/array.h>
4 #include <kiba/core/string.h>
5 
6 b8 vulkan_select_physical_device(vulkan_context *context);
7 b8 vulkan_select_logical_device(vulkan_context *context);
8 
9 b8 vulkan_physical_device_meets_requirements(VkPhysicalDevice device,
10  VkPhysicalDeviceFeatures *features,
11  VkPhysicalDeviceProperties *props,
12  vulkan_context *context);
13 
14 b8 vulkan_queue_create(VkDevice logical_device, vulkan_queue *queue, const VkAllocationCallbacks *alloc);
15 void vulkan_queue_destroy(VkDevice logical_device, vulkan_queue *queue, const VkAllocationCallbacks *alloc);
16 
17 b8 vulkan_device_create(vulkan_context *context) {
18  if (!vulkan_select_physical_device(context)) {
19  KB_ERROR("failed to find GPU meeting the requirements");
20  return false;
21  }
22  if (!vulkan_select_logical_device(context)) {
23  KB_ERROR("failed to create logical device");
24  return false;
25  }
26 
27  return true;
28 }
29 
30 void vulkan_device_destroy(vulkan_context *context) {
31  vulkan_queue_destroy(context->device.logical, &context->device.graphics_queue, &context->alloc.vulkan_callbacks);
32  vulkan_queue_destroy(context->device.logical, &context->device.transfer_queue, &context->alloc.vulkan_callbacks);
33  vulkan_queue_destroy(context->device.logical, &context->device.present_queue, &context->alloc.vulkan_callbacks);
34  vulkan_queue_destroy(context->device.logical, &context->device.compute_queue, &context->alloc.vulkan_callbacks);
35  vkDestroyDevice(context->device.logical, &context->alloc.vulkan_callbacks);
36 }
37 
38 b8 vulkan_select_physical_device(vulkan_context *context) {
39  u32 physical_device_count = 0;
40  VK_CALL_B8(vkEnumeratePhysicalDevices(context->instance.instance, &physical_device_count, 0));
41  if (physical_device_count == 0) {
42  KB_INFO("found no device that supports vulkan");
43  return false;
44  }
45 
46  array_of(VkPhysicalDevice) physical_devices =
47  array_create(VkPhysicalDevice, physical_device_count, &context->alloc.kiba_alloc);
48  if (physical_devices == KB_NULL) {
49  KB_ERROR("unable to allocate enough memory for list of physical devices");
50  return false;
51  }
52  VK_CALL_B8(vkEnumeratePhysicalDevices(context->instance.instance, &physical_device_count, physical_devices));
53  array_resize(&physical_devices, physical_device_count);
54 
55  b8 ret = false;
56  array_for_each(const VkPhysicalDevice, device, physical_devices) {
57  VkPhysicalDeviceProperties physical_device_properties;
58  VkPhysicalDeviceFeatures physical_device_features;
59  vkGetPhysicalDeviceProperties(*device, &physical_device_properties);
60  vkGetPhysicalDeviceFeatures(*device, &physical_device_features);
61  KB_INFO("checking device {raw_string} against requirements", physical_device_properties.deviceName);
62  if (vulkan_physical_device_meets_requirements(*device,
63  &physical_device_features,
64  &physical_device_properties,
65  context)) {
66  KB_INFO("device {raw_string} meets requirements", physical_device_properties.deviceName);
67  context->device.physical = *device;
68  ret = true;
69  break;
70  }
71  KB_INFO("skipping physical device {raw_string} because it does not match the requirements",
72  physical_device_properties.deviceName);
73  }
74  array_destroy(&physical_devices);
75  return ret;
76 }
77 
78 b8 vulkan_select_logical_device(vulkan_context *context) {
79  const usize max_different_queue = 8;
80 
81  u32 indices[max_different_queue];
82  u32 index = 0;
83  indices[index++] = context->device.graphics_queue.index;
84  if (context->device.graphics_queue.index != context->device.present_queue.index) {
85  indices[index++] = context->device.present_queue.index;
86  }
87  if (context->device.graphics_queue.index != context->device.transfer_queue.index) {
88  indices[index++] = context->device.transfer_queue.index;
89  }
90 
91  f32 high_prio = 1.0f;
92  VkDeviceQueueCreateInfo queue_create_infos[max_different_queue];
93  for (u32 i = 0; i < index; ++i) {
94  queue_create_infos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
95  queue_create_infos[i].queueFamilyIndex = indices[i];
96  // TODO: should be configurable how many
97  queue_create_infos[i].queueCount = 1;
98  queue_create_infos[i].flags = 0;
99  queue_create_infos[i].pNext = 0;
100  queue_create_infos[i].pQueuePriorities = &high_prio;
101  }
102 
103  VkDeviceCreateInfo device_create_info = {
104  .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
105  .pQueueCreateInfos = queue_create_infos,
106  .queueCreateInfoCount = index,
107  .pEnabledFeatures = &context->device.requirements.features,
108  .ppEnabledExtensionNames = context->device.requirements.extensions,
109  .enabledExtensionCount = (u32) array_size(context->device.requirements.extensions),
110  };
111  VK_CALL_B8(vkCreateDevice(context->device.physical,
112  &device_create_info,
113  &context->alloc.vulkan_callbacks,
114  &context->device.logical));
115 
116  if (!vulkan_queue_create(context->device.logical,
117  &context->device.graphics_queue,
118  &context->alloc.vulkan_callbacks)) {
119  KB_ERROR("failed to initialize graphics queue");
120  context->device.graphics_queue.available = false;
121  return false;
122  }
123  if (!vulkan_queue_create(context->device.logical,
124  &context->device.transfer_queue,
125  &context->alloc.vulkan_callbacks)) {
126  KB_ERROR("failed to initialize transfer queue");
127  context->device.transfer_queue.available = false;
128  return false;
129  }
130  if (!vulkan_queue_create(context->device.logical,
131  &context->device.present_queue,
132  &context->alloc.vulkan_callbacks)) {
133  KB_ERROR("failed to initialize present queue");
134  context->device.present_queue.available = false;
135  return false;
136  }
137  if (!vulkan_queue_create(context->device.logical,
138  &context->device.compute_queue,
139  &context->alloc.vulkan_callbacks)) {
140  KB_ERROR("failed to initialize compute queue");
141  context->device.compute_queue.available = false;
142  return false;
143  }
144  return true;
145 }
146 
147 b8 vulkan_physical_device_meets_requirements(VkPhysicalDevice device,
148  VkPhysicalDeviceFeatures *features,
149  VkPhysicalDeviceProperties *props,
150  vulkan_context *context) {
151  UNUSED(features);
152  // discrete gpu requirement
153  if (context->device.requirements.discrete_gpu && props->deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
154  KB_INFO("device is not viable as its not a discrete GPU");
155  return false;
156  }
157 
158  // queue families
159  vulkan_queue graphics_queue;
160  vulkan_queue transfer_queue;
161  vulkan_queue present_queue;
162  vulkan_queue compute_queue;
163 
164  u32 queue_family_count = 0;
165  vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, 0);
166 
167  array_of(VkQueueFamilyProperties) queue_props =
168  array_create(VkQueueFamilyProperties, queue_family_count, &context->alloc.kiba_alloc);
169  if (queue_props == KB_NULL) {
170  KB_INFO("unable to allocate enough memory for list of queue properties");
171  return false;
172  }
173  vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_props);
174  array_resize(&queue_props, queue_family_count);
175 
176  for (u32 i = 0; i < queue_family_count; ++i) {
177  if (queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT && !context->device.graphics_queue.available) {
178  graphics_queue.available = true;
179  graphics_queue.index = i;
180  VkBool32 present_support = false;
181  VK_CALL_B8(vkGetPhysicalDeviceSurfaceSupportKHR(device, i, context->surface, &present_support));
182  if (present_support) {
183  present_queue.available = true;
184  present_queue.index = i;
185  }
186  }
187  if (queue_props[i].queueFlags & VK_QUEUE_TRANSFER_BIT) {
188  transfer_queue.available = true;
189  transfer_queue.index = i;
190  }
191  if (queue_props[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
192  compute_queue.available = true;
193  compute_queue.index = i;
194  }
195  }
196  array_destroy(&queue_props);
197  if (context->device.requirements.graphics && !graphics_queue.available) {
198  KB_INFO("device does not support required graphics queue");
199  return false;
200  }
201  if (context->device.requirements.transfer && !transfer_queue.available) {
202  KB_INFO("device does not support required transfer queue");
203  return false;
204  }
205  if (context->device.requirements.present && !present_queue.available) {
206  KB_INFO("device does not support required present queue");
207  return false;
208  }
209  if (context->device.requirements.compute && !compute_queue.available) {
210  KB_INFO("device does not support required compute queue");
211  return false;
212  }
213 
214  // device extensions
215  u32 extension_count = 0;
216  vkEnumerateDeviceExtensionProperties(device, 0, &extension_count, 0);
217 
218  array_of(VkExtensionProperties) extensions =
219  array_create(VkExtensionProperties, extension_count, &context->alloc.kiba_alloc);
220  if (extensions == KB_NULL) {
221  KB_INFO("unable to allocate enough memory for list of extensions");
222  return false;
223  }
224  vkEnumerateDeviceExtensionProperties(device, 0, &extension_count, extensions);
225  array_resize(&extensions, extension_count);
226 
227  u32 required_extension_count = (u32) array_size(context->device.requirements.extensions);
228  b8 has_extensions = true;
229  if (extension_count >= required_extension_count) {
230  array_for_each(const char *, extension, context->device.requirements.extensions) {
231  b8 found = false;
232  for (u32 j = 0; j < extension_count; ++j) {
233  if (string_equal(string_from_raw(*extension), string_from_raw(extensions[j].extensionName))) {
234  found = true;
235  break;
236  }
237  }
238  if (!found) {
239  KB_INFO("required extension {raw_string} not supported by device", *extension);
240  has_extensions = false;
241  }
242  }
243  } else {
244  KB_INFO("device cannot support all required extensions");
245  has_extensions = false;
246  }
247 
248  array_destroy(&extensions);
249 
250  context->device.graphics_queue = graphics_queue;
251  context->device.transfer_queue = transfer_queue;
252  context->device.present_queue = present_queue;
253  context->device.compute_queue = compute_queue;
254 
255  return has_extensions;
256 }
257 
258 b8 vulkan_queue_create(VkDevice logical_device, vulkan_queue *queue, const VkAllocationCallbacks *alloc) {
259  if (!queue->available)
260  return true;
261  vkGetDeviceQueue(logical_device, queue->index, 0, &queue->queue);
262  // command pool
263  VkCommandPoolCreateInfo pool_create_info = {
264  .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
265  .queueFamilyIndex = queue->index,
266  .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
267  };
268  VK_CALL_B8(vkCreateCommandPool(logical_device, &pool_create_info, alloc, &queue->command_pool));
269  // command buffer
270  VkCommandBufferAllocateInfo cb_allocate_info = {
271  .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
272  .commandPool = queue->command_pool,
273  .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
274  .commandBufferCount = 1,
275  };
276  VK_CALL_B8(vkAllocateCommandBuffers(logical_device, &cb_allocate_info, &queue->command_buffer));
277  return true;
278 }
279 
280 void vulkan_queue_destroy(VkDevice logical_device, vulkan_queue *queue, const VkAllocationCallbacks *alloc) {
281  if (queue->available) {
282  vkDestroyCommandPool(logical_device, queue->command_pool, alloc);
283  }
284 }
b8 string_equal(const string lhs, const string rhs)
Check if the contents of two strings are equal.
Definition: string.c:118
string string_from_raw(const char *raw)
Construct a string from a raw string.
Definition: string.c:13
Custom library for interactions with strings using string views.
#define UNUSED(x)
Mark parameter as unused.
Definition: defines.h:21
#define KB_NULL
Value of an invalid ptr (nullptr).
Definition: defines.h:18
#define KB_ERROR(...)
Log entry with error log level.
Definition: log.h:142
#define KB_INFO(...)
Log entry with info log level.
Definition: log.h:162