You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
13 KiB
433 lines
13 KiB
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <vulkan/vulkan.h>
|
|
#include <vulkan/vulkan_core.h>
|
|
|
|
// check for half precision floating point support, for x86 this is equivalent to
|
|
// checking for SSE2
|
|
#define SUPPORTS_NATIVE_FP16 (__x86_64__ == 1 && __SSE2__ == 1)
|
|
// print debug messages
|
|
#define DEBUG 1
|
|
#define VERBOSE 0
|
|
|
|
// define half precision floating point
|
|
#if SUPPORTS_NATIVE_FP16
|
|
// extension is needed due to -pedantic
|
|
__extension__ typedef _Float16 half;
|
|
#endif
|
|
|
|
// useful macros
|
|
#define TEST_BIT(f, b) (!!(f & b))
|
|
#define GIB(x) ((uint64_t)x * 1024u * 1024u * 1024u)
|
|
#define MIB(x) ((uint64_t)x * 1024u * 1024u)
|
|
#define KIB(x) ((uint64_t)x * 1024u)
|
|
|
|
const char *vk_validation_layer[] = {"VK_LAYER_KHRONOS_validation"};
|
|
const uint32_t vk_validation_layer_no = 1;
|
|
|
|
// FIXME: including vulkan/vk_enum_string_helper.h does not compile
|
|
extern const char *vk_Result_to_str(VkResult input);
|
|
|
|
// like printf but on stderr
|
|
int err(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int ret = vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
// print out all the instance extensions
|
|
// NOTE: these are different from device and shader extensions
|
|
int vk_enumerate_instance_extensions(void)
|
|
{
|
|
uint32_t ex_no = 0;
|
|
#if VERBOSE > 0
|
|
vkEnumerateInstanceExtensionProperties(NULL, &ex_no, NULL);
|
|
VkExtensionProperties *ex_arr =
|
|
malloc(sizeof(VkExtensionProperties) * ex_no);
|
|
if (ex_arr == NULL) {
|
|
err("ERROR: in %s: %s\n", __func__, strerror(errno));
|
|
return -1;
|
|
}
|
|
vkEnumerateInstanceExtensionProperties(NULL, &ex_no, ex_arr);
|
|
printf("Available Properties: \n");
|
|
for (uint32_t i = 0; i < ex_no; i++) {
|
|
printf("\t%s\n", ex_arr[i].extensionName);
|
|
}
|
|
free(ex_arr);
|
|
#endif
|
|
return ex_no;
|
|
}
|
|
|
|
// on debug check for support of validation layers and activate one, a validation
|
|
// layer is useful to do more error checking at runtime like ckecking for invalid
|
|
// arguments, validation layers are available only if vulkan-sdk is installed
|
|
// (vulkan-devel on arch)
|
|
int vk_activate_validation_layer(VkInstanceCreateInfo *cinfo)
|
|
{
|
|
uint32_t prop_no = 0;
|
|
#if DEBUG > 0
|
|
vkEnumerateInstanceLayerProperties(&prop_no, NULL);
|
|
|
|
VkLayerProperties *prop_arr = malloc(sizeof(VkLayerProperties) * prop_no);
|
|
if (prop_arr == NULL) {
|
|
err("ERROR: in %s: %s\n", __func__, strerror(errno));
|
|
return -1;
|
|
}
|
|
vkEnumerateInstanceLayerProperties(&prop_no, prop_arr);
|
|
|
|
for (uint32_t i = 0; i < prop_no; i++) {
|
|
if (strcmp(prop_arr[i].layerName, vk_validation_layer[0]) == 0) {
|
|
cinfo->enabledLayerCount = vk_validation_layer_no;
|
|
cinfo->ppEnabledLayerNames = vk_validation_layer;
|
|
free(prop_arr);
|
|
return 0;
|
|
}
|
|
}
|
|
free(prop_arr);
|
|
return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
VkInstance vk_init(void)
|
|
{
|
|
// create a vulkan instance and fill it with the application data
|
|
VkResult res;
|
|
VkInstance vk_instance = VK_NULL_HANDLE;
|
|
|
|
VkApplicationInfo vk_appinfo = {
|
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
|
.pNext = NULL,
|
|
.pApplicationName = __FILE__,
|
|
.applicationVersion = VK_MAKE_VERSION(0, 1, 0),
|
|
.pEngineName = "no engine",
|
|
.engineVersion = VK_MAKE_VERSION(0, 0, 0),
|
|
.apiVersion =
|
|
VK_API_VERSION_1_2, // api version 1.2 is more widely available
|
|
};
|
|
|
|
vk_enumerate_instance_extensions();
|
|
|
|
// TODO: check for extension availability
|
|
// TODO: does the lifetime of VkInstanceCreateInfo has to be the same as the
|
|
// lifetime of VkInstance?
|
|
const char *vk_instance_extensions[] = {
|
|
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
|
};
|
|
const uint32_t vk_instance_extensions_no =
|
|
(uint32_t)(sizeof(vk_instance_extensions) / sizeof(char *));
|
|
|
|
VkInstanceCreateInfo vk_instanceinfo = {
|
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
|
.pApplicationInfo = &vk_appinfo,
|
|
.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR,
|
|
.enabledExtensionCount = vk_instance_extensions_no,
|
|
.ppEnabledExtensionNames = vk_instance_extensions,
|
|
.enabledLayerCount = 0,
|
|
};
|
|
|
|
int e = 0;
|
|
if ((e = vk_activate_validation_layer(&vk_instanceinfo))) {
|
|
err("Could not activate validation layers%s\n",
|
|
e > 0 ? ": No validation layers found" : "");
|
|
}
|
|
|
|
res = vkCreateInstance(&vk_instanceinfo, NULL, &vk_instance);
|
|
if (res != VK_SUCCESS) {
|
|
err("ERROR: Could not create vulkan instance %s",
|
|
vk_Result_to_str(res));
|
|
return VK_NULL_HANDLE;
|
|
} else {
|
|
#if VERBOSE > 0
|
|
printf("Created vulkan instance\n");
|
|
#endif
|
|
}
|
|
return vk_instance;
|
|
}
|
|
|
|
void vk_destroy(VkInstance vk_instance)
|
|
{
|
|
// ...
|
|
vkDestroyInstance(vk_instance, NULL);
|
|
}
|
|
|
|
VkPhysicalDevice vk_physical_device_get(VkInstance vk_instance)
|
|
{
|
|
// get the physical devices list
|
|
VkPhysicalDevice vk_phydev = VK_NULL_HANDLE;
|
|
|
|
uint32_t vk_phydevs_no = 0;
|
|
VkPhysicalDevice *vk_phydevs;
|
|
vkEnumeratePhysicalDevices(vk_instance, &vk_phydevs_no, NULL);
|
|
|
|
if (vk_phydevs_no == 0) {
|
|
return vk_phydev;
|
|
}
|
|
|
|
vk_phydevs = malloc(sizeof(VkPhysicalDevice) * vk_phydevs_no);
|
|
if (vk_phydevs == NULL) {
|
|
err("ERROR: in %s: %s\n", __func__, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
vkEnumeratePhysicalDevices(vk_instance, &vk_phydevs_no, vk_phydevs);
|
|
|
|
// print out information about each device
|
|
printf("Available Physical Devices: \n");
|
|
for (uint32_t i = 0; i < vk_phydevs_no; i++) {
|
|
VkPhysicalDevice dev = vk_phydevs[i];
|
|
VkPhysicalDeviceProperties dev_properties;
|
|
VkPhysicalDeviceFeatures dev_features;
|
|
VkPhysicalDeviceMemoryProperties dev_memory;
|
|
|
|
vkGetPhysicalDeviceProperties(dev, &dev_properties);
|
|
vkGetPhysicalDeviceFeatures(dev, &dev_features);
|
|
vkGetPhysicalDeviceMemoryProperties(dev, &dev_memory);
|
|
|
|
printf(
|
|
"\tDevice %d: %s, Discrete: %s\n",
|
|
i,
|
|
dev_properties.deviceName,
|
|
dev_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
|
? "true"
|
|
: "false"
|
|
);
|
|
|
|
for (unsigned x = 0; x < dev_memory.memoryHeapCount; x++) {
|
|
uint64_t mem_size = dev_memory.memoryHeaps[x].size;
|
|
uint32_t mem_flags = dev_memory.memoryHeaps[x].flags;
|
|
char is_local =
|
|
TEST_BIT(mem_flags, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT);
|
|
printf(
|
|
"\t\tHeap %.2d: local: %d, size: %.3f MiB\n",
|
|
x,
|
|
is_local,
|
|
(float)mem_size / (1024.0 * 1024.0)
|
|
);
|
|
}
|
|
}
|
|
|
|
// TODO: find the most suitable physical device, but for now every vulkan
|
|
// device has to be compatible with compute shaders
|
|
vk_phydev = vk_phydevs[0];
|
|
|
|
free(vk_phydevs);
|
|
return vk_phydev;
|
|
}
|
|
|
|
// returns the index of a usable memory type in the device that is also backed by
|
|
// a heap with a size of at least min_size bytes
|
|
int vk_device_get_usable_memory_type_index(
|
|
VkPhysicalDevice vk_phydev, uint64_t min_size
|
|
)
|
|
{
|
|
int memtype_idx = -1;
|
|
VkPhysicalDeviceMemoryProperties dev_memory;
|
|
vkGetPhysicalDeviceMemoryProperties(vk_phydev, &dev_memory);
|
|
|
|
VkMemoryPropertyFlags flags = 0;
|
|
uint32_t idx = 0;
|
|
VkMemoryHeap mem;
|
|
for (unsigned i = 0; i < dev_memory.memoryTypeCount; i++) {
|
|
flags = dev_memory.memoryTypes[i].propertyFlags;
|
|
idx = dev_memory.memoryTypes[i].heapIndex;
|
|
mem = dev_memory.memoryHeaps[idx];
|
|
|
|
// TODO: do we need more flags to be set?
|
|
if (TEST_BIT(flags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
|
|
TEST_BIT(flags, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) &&
|
|
mem.size >= min_size) {
|
|
// as the name suggests we only care about the memory type
|
|
// and not the heap itself
|
|
memtype_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return memtype_idx;
|
|
}
|
|
|
|
// do an allocation on the device of size bytes, according to krhonos it is a good
|
|
// idea to do one or few allocations and subdivide them on the host
|
|
// https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/memory_allocation.adoc
|
|
// this memory has to be freed using vkFreeMemory(device, mem, NULL);
|
|
VkDeviceMemory
|
|
vk_allocate_memory(VkDevice vk_logdev, uint32_t memtype_index, uint64_t size)
|
|
{
|
|
VkMemoryAllocateInfo alloc_info = {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.pNext = NULL,
|
|
.allocationSize = size,
|
|
.memoryTypeIndex = memtype_index,
|
|
};
|
|
VkDeviceMemory mem = VK_NULL_HANDLE;
|
|
|
|
VkResult res = vkAllocateMemory(vk_logdev, &alloc_info, NULL, &mem);
|
|
if (res != VK_SUCCESS) {
|
|
err("Error allocating memory on device: %s\n",
|
|
vk_Result_to_str(res));
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
return mem;
|
|
}
|
|
|
|
void vk_physical_device_destroy(VkPhysicalDevice vk_phydev)
|
|
{
|
|
if (vk_phydev != VK_NULL_HANDLE) {
|
|
// ...
|
|
}
|
|
}
|
|
|
|
// return the index of the first queue family that supports compute on the device,
|
|
// returns a negative index on error
|
|
// A better approach would be to find a queue that only handled compute workloads
|
|
// (but you need to ignore the transfer bit and for our purposes the sparse binding
|
|
// bit too)
|
|
int vk_device_get_compute_queue_index(VkPhysicalDevice vk_phydev)
|
|
{
|
|
uint32_t vk_qfamilies_no = 0;
|
|
VkQueueFamilyProperties *vk_qfamilies;
|
|
int qfamily_idx = -1;
|
|
|
|
vkGetPhysicalDeviceQueueFamilyProperties(vk_phydev, &vk_qfamilies_no, NULL);
|
|
|
|
vk_qfamilies = malloc(sizeof(VkQueueFamilyProperties) * vk_qfamilies_no);
|
|
if (vk_qfamilies == NULL) {
|
|
err("ERROR: in %s: %s\n", __func__, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
vkGetPhysicalDeviceQueueFamilyProperties(
|
|
vk_phydev, &vk_qfamilies_no, vk_qfamilies
|
|
);
|
|
|
|
for (uint32_t i = 0; i < vk_qfamilies_no; i++) {
|
|
if (TEST_BIT(vk_qfamilies[i].queueFlags, VK_QUEUE_COMPUTE_BIT)) {
|
|
qfamily_idx = i;
|
|
}
|
|
}
|
|
|
|
free(vk_qfamilies);
|
|
return qfamily_idx;
|
|
}
|
|
|
|
VkDevice vk_logical_device_create(VkPhysicalDevice vk_phydev, int qfamily_idx)
|
|
{
|
|
VkResult res;
|
|
VkDevice vk_logdev = VK_NULL_HANDLE;
|
|
float vk_queue_priority = 1.0f;
|
|
|
|
// specify which command queues to use for the physical device
|
|
VkDeviceQueueCreateInfo vk_queueinfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
|
.pNext = NULL,
|
|
.flags = 0,
|
|
.queueFamilyIndex = qfamily_idx,
|
|
.queueCount = 1,
|
|
.pQueuePriorities = &vk_queue_priority,
|
|
};
|
|
|
|
// specify which device features to use
|
|
// TODO: this
|
|
VkPhysicalDeviceFeatures vk_phydev_features = {0};
|
|
|
|
// actually create the logical device
|
|
// TODO: figure out what device extensions are
|
|
// FIXME: here validation layers are ignored but it is still better to define
|
|
// them for compatibility
|
|
VkDeviceCreateInfo vk_createinfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
|
.pQueueCreateInfos = &vk_queueinfo,
|
|
.queueCreateInfoCount = 1,
|
|
.pEnabledFeatures = &vk_phydev_features,
|
|
.ppEnabledExtensionNames = NULL,
|
|
.enabledExtensionCount = 0,
|
|
.ppEnabledLayerNames = NULL,
|
|
.enabledLayerCount = 0,
|
|
};
|
|
|
|
res = vkCreateDevice(vk_phydev, &vk_createinfo, NULL, &vk_logdev);
|
|
if (res != VK_SUCCESS) {
|
|
err("ERROR: Could not create vulkan logical device %s",
|
|
vk_Result_to_str(res));
|
|
return VK_NULL_HANDLE;
|
|
} else {
|
|
#if VERBOSE > 0
|
|
printf("Created vulkan logical device\n");
|
|
#endif
|
|
}
|
|
|
|
return vk_logdev;
|
|
}
|
|
|
|
void vk_logical_device_destroy(VkDevice vk_logdev)
|
|
{
|
|
vkDestroyDevice(vk_logdev, NULL);
|
|
}
|
|
|
|
// get the queue handle from it's index
|
|
VkQueue vk_queue_get(VkDevice vk_logdev, int qfamily_idx)
|
|
{
|
|
VkQueue vk_queue = VK_NULL_HANDLE;
|
|
vkGetDeviceQueue(vk_logdev, qfamily_idx, 0, &vk_queue);
|
|
return vk_queue;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
#if VERBOSE > 0
|
|
if (SUPPORTS_NATIVE_FP16) {
|
|
printf("Processor supports half precision floating point\n");
|
|
} else {
|
|
printf("Processor doesn't support half precision floating point\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
VkInstance vk_instance = vk_init();
|
|
if (vk_instance == VK_NULL_HANDLE) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
VkPhysicalDevice vk_phydev = vk_physical_device_get(vk_instance);
|
|
int qfamily_idx = vk_device_get_compute_queue_index(vk_phydev);
|
|
if (qfamily_idx < 0) {
|
|
err("The device does not support compute queues\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
VkDevice vk_logdev = vk_logical_device_create(vk_phydev, qfamily_idx);
|
|
|
|
int devmem_idx = vk_device_get_usable_memory_type_index(vk_phydev, GIB(1));
|
|
if (devmem_idx < 0) {
|
|
err("Could not find a suitable device memory heap\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
VkDeviceMemory mem = vk_allocate_memory(vk_logdev, devmem_idx, MIB(256));
|
|
if (mem == VK_NULL_HANDLE) {
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
printf("Successfully allocated memory on device\n");
|
|
}
|
|
|
|
// TODO: create buffers with vkCreateBuffer and VkCreateBufferInfo
|
|
// TODO: bind the buffer to the allocated memory with vkBindBufferMemory
|
|
// TODO: actually use that memory
|
|
|
|
vkFreeMemory(vk_logdev, mem, NULL);
|
|
|
|
vk_logical_device_destroy(vk_logdev);
|
|
vk_physical_device_destroy(vk_phydev);
|
|
vk_destroy(vk_instance);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
|