diff --git a/Makefile b/Makefile index 2bd1859..0219b10 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ -CFLAGS = -I/usr/local/include -O2 -Wall -Wextra +CFLAGS = -I/usr/local/include -O0 -g -Wall -Wextra LDFLAGS = -L/usr/local/lib -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /usr/local/lib -CC = gcc +CC = clang +GDB = lldb SOURCES = $(wildcard src/*.c) @@ -39,3 +40,9 @@ clean: clean_compdb: rm -rf .compdb rm compile_commands.json + +run: spacegame + ./spacegame + +debug: spacegame + $(GDB) spacegame diff --git a/src/main.c b/src/main.c index 19ea8d7..98e3bbd 100644 --- a/src/main.c +++ b/src/main.c @@ -1,12 +1,72 @@ +#define VK_USE_PLATFORM_MACOS_MVK #include "vulkan/vulkan_core.h" #define GLFW_INCLUDE_VULKAN #include +#define GLFW_EXPOSE_NATIVE_COCOA +#include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include #include +#include + +typedef struct QueueIndicesStruct { + uint32_t graphics_family; + uint32_t graphics_index; + + uint32_t present_family; + uint32_t present_index; +} QueueIndices; + +typedef struct QueuesStruct { + VkQueue graphics; + VkQueue present; +} Queues; + +typedef struct SwapchainDetailsStruct { + VkSurfaceCapabilitiesKHR capabilities; + + VkSurfaceFormatKHR* formats; + uint32_t formats_count; + + VkPresentModeKHR* present_modes; + uint32_t present_modes_count; +} SwapchainDetails; + +typedef struct VulkanContextStruct { + VkInstance instance; + VkDebugUtilsMessengerEXT debug_messenger; + VkPhysicalDevice physical_device; + + QueueIndices queue_indices; + VkDevice device; + Queues queues; + + VkSurfaceKHR surface; + + SwapchainDetails swapchain_details; + VkSwapchainKHR swapchain; +} VulkanContext; + +const char * validation_layers[] = { + "VK_LAYER_KHRONOS_validation", +}; +uint32_t validation_layer_count = sizeof(validation_layers) / sizeof(const char *); + +const char * instance_extensions[] = { + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + VK_MVK_MACOS_SURFACE_EXTENSION_NAME, + VK_KHR_SURFACE_EXTENSION_NAME, +}; +uint32_t instance_extension_count = sizeof(instance_extensions) / sizeof(const char *); + +const char * device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, +}; +uint32_t device_extension_count = sizeof(device_extensions) / sizeof(const char *); void glfw_error(int error, const char* description) { fprintf(stderr, "GLFW_ERR: 0x%02x - %s\n", error, description); @@ -24,10 +84,193 @@ GLFWwindow* init_window(int width, int height) { return window; } +bool check_validation_layers(const char ** layers, uint32_t num_layers) { + uint32_t layer_count; + VkResult result; + + result = vkEnumerateInstanceLayerProperties(&layer_count, 0); + if(result != VK_SUCCESS) { + return false; + } + + VkLayerProperties* available_layers = malloc(sizeof(VkLayerProperties)*layer_count); + + result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers); + + for(uint32_t i = 0; i < num_layers; i++) { + bool found = false; + for(uint32_t j = 0; j < layer_count; j++) { + if(strcmp(layers[i], available_layers[j].layerName) == 0) { + found = true; + } + } + if(found == false) { + free(available_layers); + return false; + } + } + + free(available_layers); + return true; +}; + +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, + void* user_data) { + + (void)severity; + (void)type; + (void)user_data; + + fprintf(stderr, "Validation layer: %s\n", callback_data->pMessage); + + return VK_FALSE; +}; + +VkSurfaceKHR create_surface_khr(VkInstance instance, GLFWwindow* window) { + VkSurfaceKHR surface; + VkResult result = glfwCreateWindowSurface(instance, window, 0, &surface); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return surface; +} + +VkPhysicalDevice get_best_physical_device(VkInstance instance) { + VkPhysicalDevice device = VK_NULL_HANDLE; + + uint32_t device_count = 0; + VkResult result; + result = vkEnumeratePhysicalDevices(instance, &device_count, 0); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count); + result = vkEnumeratePhysicalDevices(instance, &device_count, devices); + if(result != VK_SUCCESS) { + free(devices); + return VK_NULL_HANDLE; + } + + int top_score = -1; + for(uint32_t i = 0; i < device_count; i++) { + int score = 0; + + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(devices[i], &properties); + + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceFeatures(devices[i], &features); + + switch(properties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score += 100; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score += 50; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score += 25; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + score += 0; + break; + default: + continue; + } + + if(score > top_score) { + top_score = score; + device = devices[i]; + } + } + + free(devices); + + return device; +} + +struct MaybeQueueIndices { + bool valid; + QueueIndices indices; +}; + +struct MaybeQueueIndices get_queue_indices(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { + struct MaybeQueueIndices ret = {}; + ret.indices.graphics_family = 0xFFFFFFFF; + ret.indices.graphics_index = 0xFFFFFFFF; + ret.indices.present_family = 0xFFFFFFFF; + ret.indices.present_index = 0xFFFFFFFF; + + uint32_t queue_family_count; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, 0); + + VkQueueFamilyProperties* queue_families = malloc(sizeof(VkQueueFamilyProperties)*queue_family_count); + + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families); + + for(uint32_t i = 0; + (i < queue_family_count) + && ((ret.indices.graphics_family == 0xFFFFFFFF) || (ret.indices.present_family == 0xFFFFFFFF)); + i++) { + if(queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + ret.indices.graphics_family = i; + ret.indices.graphics_index = 0; + } + + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, surface, &present_support); + + if(present_support == VK_TRUE) { + ret.indices.present_family = i; + ret.indices.present_index = 0; + } + } + + if((ret.indices.graphics_family != 0xFFFFFFFF) && (ret.indices.present_family != 0xFFFFFFFF)) { + ret.valid = true; + } + + free(queue_families); + + return ret; +} + +VkDebugUtilsMessengerEXT create_debug_messenger(VkInstance instance) { + VkDebugUtilsMessengerEXT debug_messenger; + + VkDebugUtilsMessengerCreateInfoEXT messenger_info = {}; + messenger_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + messenger_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + messenger_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + messenger_info.pfnUserCallback = debug_callback; + messenger_info.pUserData = 0; + + PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + + VkResult result; + result = func(instance, &messenger_info, 0, &debug_messenger); + if(result != VK_SUCCESS) { + fprintf(stderr, "failed to create debug messenger\n"); + return VK_NULL_HANDLE; + } + + return debug_messenger; +} + VkInstance create_instance() { VkInstance instance; - VkApplicationInfo app_info; + if(check_validation_layers(validation_layers, validation_layer_count) == false) { + fprintf(stderr, "requested validation layers not supported"); + return VK_NULL_HANDLE; + } + + VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = "spacegame"; app_info.applicationVersion = VK_MAKE_VERSION(0, 0, 1); @@ -35,33 +278,36 @@ VkInstance create_instance() { app_info.engineVersion = VK_MAKE_VERSION(0, 0, 1); app_info.apiVersion = VK_API_VERSION_1_3; - VkInstanceCreateInfo instance_info; + VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pApplicationInfo = &app_info; + instance_info.enabledLayerCount = validation_layer_count; + instance_info.ppEnabledLayerNames = validation_layers; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - const char** requested_extensions = malloc(sizeof(char*)*(glfwExtensionCount+1)); + const char** requested_extensions = malloc(sizeof(char*)*(glfwExtensionCount + instance_extension_count)); for (uint32_t i = 0; i < glfwExtensionCount; i++) { requested_extensions[i] = glfwExtensions[i]; } - requested_extensions[glfwExtensionCount] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + for (uint32_t i = 0; i < instance_extension_count; i++) { + requested_extensions[glfwExtensionCount + i] = instance_extensions[i]; + } - instance_info.enabledExtensionCount = glfwExtensionCount + 1; + instance_info.enabledExtensionCount = glfwExtensionCount + instance_extension_count; instance_info.ppEnabledExtensionNames = requested_extensions; - instance_info.enabledLayerCount = 0; instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - VkResult result = vkCreateInstance(&instance_info, 0, &instance); if(result != VK_SUCCESS) { fprintf(stderr, "vkCreateInstance: 0x%02x\n", result); - return 0; + return VK_NULL_HANDLE; } free(requested_extensions); @@ -69,14 +315,242 @@ VkInstance create_instance() { return instance; } -int init_vulkan() { +VkDevice create_logical_device(VkPhysicalDevice physical_device, QueueIndices queue_indices) { + VkDevice device; + + VkDeviceQueueCreateInfo queue_create_info[2] = {}; + uint32_t queue_create_count; + float default_queue_priority = 1.0f; + if(queue_indices.graphics_family == queue_indices.present_family) { + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = queue_indices.graphics_family; + queue_create_info[0].queueCount = 1; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + queue_create_count = 1; + } else { + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = queue_indices.graphics_family; + queue_create_info[0].queueCount = 1; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + + queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[1].queueFamilyIndex = queue_indices.present_family; + queue_create_info[1].queueCount = 1; + queue_create_info[1].pQueuePriorities = &default_queue_priority; + queue_create_count = 2; + } + + + VkPhysicalDeviceFeatures device_features = {}; + + VkDeviceCreateInfo device_create_info = {}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pQueueCreateInfos = queue_create_info; + device_create_info.queueCreateInfoCount = queue_create_count; + device_create_info.pEnabledFeatures = &device_features; + + device_create_info.enabledExtensionCount = device_extension_count; + device_create_info.ppEnabledExtensionNames = device_extensions; + + device_create_info.enabledLayerCount = validation_layer_count; + device_create_info.ppEnabledLayerNames = validation_layers; + + VkResult result = vkCreateDevice(physical_device, &device_create_info, 0, &device); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return device; +} + +struct MaybeSwapchainDetails { + bool valid; + SwapchainDetails details; +}; + +struct MaybeSwapchainDetails get_swapchain_details(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { + struct MaybeSwapchainDetails ret = {}; + ret.valid = false; + ret.details.formats = 0; + ret.details.present_modes = 0; + + VkResult result; + + result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &ret.details.capabilities); + if(result != VK_SUCCESS) { + return ret; + } + + result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &ret.details.formats_count, 0); + if(result != VK_SUCCESS) { + return ret; + } + ret.details.formats = malloc(sizeof(VkSurfaceFormatKHR)*ret.details.formats_count); + result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &ret.details.formats_count, ret.details.formats); + if(result != VK_SUCCESS) { + free(ret.details.formats); + return ret; + } + + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &ret.details.present_modes_count, 0); + if(result != VK_SUCCESS) { + free(ret.details.formats); + return ret; + } + ret.details.present_modes = malloc(sizeof(VkPresentModeKHR)*ret.details.present_modes_count); + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &ret.details.present_modes_count, ret.details.present_modes); + if(result != VK_SUCCESS) { + free(ret.details.formats); + free(ret.details.present_modes); + return ret; + } + + ret.valid = true; + return ret; +} + +VkSurfaceFormatKHR choose_swapchain_format(SwapchainDetails swapchain_details) { + for(uint32_t i = 0; i < swapchain_details.formats_count; i++) { + VkSurfaceFormatKHR format = swapchain_details.formats[i]; + if(format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + return swapchain_details.formats[0]; +} + +VkPresentModeKHR choose_present_mode(SwapchainDetails swapchain_details) { + for(uint32_t i = 0; i < swapchain_details.present_modes_count; i++) { + if(swapchain_details.present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + return VK_PRESENT_MODE_MAILBOX_KHR; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D choose_swapchain_extent(SwapchainDetails swapchain_details) { + return swapchain_details.capabilities.currentExtent; +} + +VkSwapchainKHR create_swapchain(VkDevice device, SwapchainDetails swapchain_details, VkSurfaceKHR surface, QueueIndices indices, VkSwapchainKHR old_swapchain) { + VkSurfaceFormatKHR format = choose_swapchain_format(swapchain_details); + VkPresentModeKHR present_mode = choose_present_mode(swapchain_details); + VkExtent2D extent = choose_swapchain_extent(swapchain_details); + + uint32_t image_count = swapchain_details.capabilities.minImageCount + 1; + uint32_t max_images = swapchain_details.capabilities.maxImageCount; + if((max_images > 0) && (image_count > max_images)) { + image_count = max_images; + } + + VkSwapchainCreateInfoKHR swapchain_info = {}; + swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_info.surface = surface; + swapchain_info.minImageCount = image_count; + swapchain_info.imageFormat = format.format; + swapchain_info.imageColorSpace = format.colorSpace; + swapchain_info.imageExtent = extent; + swapchain_info.imageArrayLayers = 1; + swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + uint32_t queue_families[2] = {indices.graphics_family, indices.present_index}; + if(indices.graphics_family != indices.present_family) { + swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchain_info.queueFamilyIndexCount = 2; + swapchain_info.pQueueFamilyIndices = queue_families; + } else { + swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_info.queueFamilyIndexCount = 0; + swapchain_info.pQueueFamilyIndices = 0; + } + + swapchain_info.preTransform = swapchain_details.capabilities.currentTransform; + swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + swapchain_info.presentMode = present_mode; + swapchain_info.clipped = VK_TRUE; + + swapchain_info.oldSwapchain = old_swapchain; + + VkSwapchainKHR swapchain; + VkResult result; + result = vkCreateSwapchainKHR(device, &swapchain_info, 0, &swapchain); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return swapchain; +} + +VulkanContext* init_vulkan(GLFWwindow* window) { + VulkanContext* context = (VulkanContext*)malloc(sizeof(VulkanContext)); + VkInstance instance = create_instance(); - if(instance == 0) { - return 1; + if(instance == VK_NULL_HANDLE) { + fprintf(stderr, "failed to initialize vulkan instance\n"); + return 0; + } else { + context->instance = instance; } - vkDestroyInstance(instance, 0); - return 0; + VkDebugUtilsMessengerEXT debug_messenger = create_debug_messenger(context->instance); + if(debug_messenger == VK_NULL_HANDLE) { + fprintf(stderr, "failed to initialize vulkan debug messenger\n"); + return 0; + } else { + context->debug_messenger = debug_messenger; + } + + VkPhysicalDevice physical_device = get_best_physical_device(context->instance); + if(physical_device == VK_NULL_HANDLE) { + fprintf(stderr, "failed to pick vulkan physical device\n"); + return 0; + } else { + context->physical_device = physical_device; + } + + VkSurfaceKHR surface = create_surface_khr(context->instance, window); + if(surface == VK_NULL_HANDLE) { + fprintf(stderr, "failed to create vulkan surface\n"); + return 0; + } else { + context->surface = surface; + } + + struct MaybeQueueIndices maybe_indices = get_queue_indices(context->physical_device, context->surface); + if(maybe_indices.valid == false) { + fprintf(stderr, "failed to get vulkan queue indices\n"); + return 0; + } else { + context->queue_indices = maybe_indices.indices; + } + + VkDevice device = create_logical_device(context->physical_device, context->queue_indices); + if(device == VK_NULL_HANDLE) { + fprintf(stderr, "failed to create vulkan logical device\n"); + return 0; + } else { + context->device = device; + } + vkGetDeviceQueue(device, context->queue_indices.graphics_family, context->queue_indices.graphics_index, &context->queues.graphics); + vkGetDeviceQueue(device, context->queue_indices.present_family, context->queue_indices.present_index, &context->queues.present); + + struct MaybeSwapchainDetails maybe_details = get_swapchain_details(context->physical_device, context->surface); + if(maybe_details.valid == false) { + fprintf(stderr, "failed to create vulkan logical device\n"); + return 0; + } else { + context->swapchain_details = maybe_details.details; + } + + VkSwapchainKHR swapchain = create_swapchain(context->device, context->swapchain_details, context->surface, context->queue_indices, VK_NULL_HANDLE); + if(swapchain == VK_NULL_HANDLE) { + return 0; + } else { + context->swapchain = swapchain; + } + + return context; } void main_loop(GLFWwindow* window) { @@ -85,10 +559,35 @@ void main_loop(GLFWwindow* window) { } } -void cleanup(GLFWwindow* window) { - glfwDestroyWindow(window); +void cleanup(GLFWwindow* window, VulkanContext* context) { + if(context != 0) { + if(context->instance != VK_NULL_HANDLE) { + if(context->swapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(context->device, context->swapchain, 0); + } + + if(context->surface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(context->instance, context->surface, 0); + } - glfwTerminate(); + if(context->device != VK_NULL_HANDLE) { + vkDestroyDevice(context->device, 0); + } + + if(context->debug_messenger != VK_NULL_HANDLE) { + PFN_vkDestroyDebugUtilsMessengerEXT destroy_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(context->instance, "vkDestroyDebugUtilsMessengerEXT"); + destroy_messenger(context->instance, context->debug_messenger, 0); + } + + vkDestroyInstance(context->instance, 0); + } + free(context); + } + + if(window != 0) { + glfwDestroyWindow(window); + glfwTerminate(); + } } int main() { @@ -98,15 +597,15 @@ int main() { return 1; } - int result = init_vulkan(); - if (result != 0) { - fprintf(stderr, "failed to initialize vulkan: err %d\n", result); + VulkanContext* context = init_vulkan(window); + if (context == 0) { + fprintf(stderr, "failed to initialize vulkan context\n"); return 2; } main_loop(window); - cleanup(window); + cleanup(window, context); return 0; }