diff options
author | gramanas <anastasis.gramm2@gmail.com> | 2024-05-24 15:09:53 +0300 |
---|---|---|
committer | gramanas <anastasis.gramm2@gmail.com> | 2024-05-24 15:09:53 +0300 |
commit | fcbd6e48a2a291be1d15c87b94d0045771dbb088 (patch) | |
tree | b72b16d58fd30f2c2b70a347d0b7f7f1a1c5746e /src/render.c | |
parent | 0898bfdf5d8e1c468a2d0aa8a3a7e320c4578dd3 (diff) | |
download | cgame-fcbd6e48a2a291be1d15c87b94d0045771dbb088.tar.gz cgame-fcbd6e48a2a291be1d15c87b94d0045771dbb088.tar.bz2 cgame-fcbd6e48a2a291be1d15c87b94d0045771dbb088.zip |
rename
Diffstat (limited to 'src/render.c')
-rw-r--r-- | src/render.c | 1950 |
1 files changed, 1950 insertions, 0 deletions
diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..50f686e --- /dev/null +++ b/src/render.c @@ -0,0 +1,1950 @@ +#include <stddef.h> +#include <stdlib.h> +#include <time.h> + +#include <shaderc/shaderc.h> + +#define SDL_MAIN_HANDLED +#define VK_USE_PLATFORM_XCB_KHR +#include <SDL2/SDL.h> +#include <SDL2/SDL_vulkan.h> + +#include <vulkan/vulkan.h> + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "../lib/stb_image.h" + +//#include "cplusplus.h" +#include "vkutil.h" +#include "state.h" + +// embedded clgm library +uint32_t currentFrame = 0; +state_t s; + +const char *const validation_layers[] = { + "VK_LAYER_KHRONOS_validation" +}; +const uint32_t validation_layer_count = VK_ARRAY_LEN(validation_layers); + +const char *const device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, +}; +const uint32_t deviceExtensionCount = VK_ARRAY_LEN(device_extensions); + + +#ifdef VKDEBUG + const bool enableValidationLayers = true; +#else + const bool enableValidationLayers = false; +#endif + +typedef struct { + float x; + float y; +} V2; + +typedef struct { + float x; + float y; + float z; +} V3; + +typedef struct { + V3 pos; + V3 color; + V2 texCoord; +} Vertex; + +/* Vertex vertices[] = { */ +/* (Vertex) { (V2) {-0.2f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */ +/* (Vertex) { (V2) {0.5f, 0.3f}, (V3) {0.0f, 0.0f, 1.0f}}, */ +/* (Vertex) { (V2) {-0.5f, 0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */ + +/* (Vertex) { (V2) {0.2f, -0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */ +/* (Vertex) { (V2) {0.5f, 0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */ +/* (Vertex) { (V2) {-0.5f, 0.3f}, (V3) {0.0f, 1.0f, 0.0f}}, */ + +/* (Vertex) { (V2) {0.0f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}}, */ +/* (Vertex) { (V2) {0.5f, 0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */ +/* (Vertex) { (V2) {-0.5f, 0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */ +/* }; */ +/* const int VERTICES_SIZE = VK_ARRAY_LEN(vertices); */ + +/* const uint16_t indices[] = { */ +/* 0, 1, 2, 3, 4, 5, 6, 7, 8, */ +/* }; */ +/* const int INDICES_SIZE = VK_ARRAY_LEN(indices); */ + +Vertex vertices[] = { + (Vertex) { (V3) {-0.5f, -0.5f, 0.0f}, (V3) {1.0f, 0.0f, 0.0f}, (V2) {0.0f, 0.0f}}, + (Vertex) { (V3) {0.5f, -0.5f, 0.0f}, (V3) {0.0f, 1.0f, 0.0f}, (V2) {1.0f, 0.0f}}, + (Vertex) { (V3) {0.5f, 0.5f, 0.0f}, (V3) {0.0f, 0.0f, 1.0f}, (V2) {1.0f, 1.0f}}, + (Vertex) { (V3) {-0.5f, 0.5f, 0.0f}, (V3) {1.0f, 1.0f, 1.0f}, (V2) {0.0f, 1.0f}}, + (Vertex) { (V3) {-0.5f, -0.5f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}, (V2) {0.0f, 0.0f}}, + (Vertex) { (V3) {0.5f, -0.5f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}, (V2) {1.0f, 0.0f}}, + (Vertex) { (V3) {0.5f, 0.5f, -0.5f}, (V3) {0.0f, 0.0f, 1.0f}, (V2) {1.0f, 1.0f}}, + (Vertex) { (V3) {-0.5f, 0.5f, -0.5f}, (V3) {1.0f, 1.0f, 1.0f}, (V2) {0.0f, 1.0f}}, +}; +const int VERTICES_SIZE = VK_ARRAY_LEN(vertices); + +const uint16_t indices[] = { + 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 +}; +const int INDICES_SIZE = VK_ARRAY_LEN(indices); + +static int resizing_event_watcher(void* data, SDL_Event* event) { + if (event->type == SDL_WINDOWEVENT && + event->window.event == SDL_WINDOWEVENT_RESIZED) { + s.sdl_window_resized = 1; + } + return 0; +} + +bool +init() +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + vk_log(VK_INFO, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + s.sdl_window = SDL_CreateWindow("Vulkanizater", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + s.window_w, + s.window_h, + SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN); + if (s.sdl_window == NULL) { + vk_log(VK_INFO, "Window could not be created! SDL_Error: %s\n", SDL_GetError()); + return false; + } + + SDL_AddEventWatch(resizing_event_watcher,NULL); + + return true; +} + +void +closeSDL() +{ + SDL_DestroyWindow(s.sdl_window); + s.sdl_window = NULL; + SDL_Quit(); +} + +bool +checkValidationLayerSupport() +{ + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, NULL); + + VkLayerProperties availableLayers[layerCount]; + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); + + for (uint32_t i = 0; i < validation_layer_count; i++) { + bool layerFound = false; + + for (uint32_t j = 0; j < layerCount; j++) { + if (strcmp(validation_layers[i], availableLayers[j].layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + vk_log(VK_ERROR, "validation layer: %s\n", pCallbackData->pMessage); + return VK_FALSE; +} + +void +vulkan_setup_debug_messenger() +{ + if (!enableValidationLayers) return; + VkDebugUtilsMessengerCreateInfoEXT createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.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; + createInfo.pfnUserCallback = debugCallback; + createInfo.pUserData = NULL; // Optional + + + // TODO: func pointers returned are NULL + s.pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkCreateDebugUtilsMessengerEXT"); + s.pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkDestroyDebugUtilsMessengerEXT"); + + VkResult result = s.pfnCreateDebugUtilsMessengerEXT(s.vk_instance, &createInfo, NULL, &s.vk_debug_messenger); + if (result != VK_SUCCESS) { + vk_log(VK_WARN, "failed to set up debug messenger!\n"); + } else { + vk_log(VK_INFO, "Debug messanger created!\n"); + } +} + +void +vulkan_create_instance() +{ + if (enableValidationLayers && !checkValidationLayerSupport()) { + vk_log(VK_ERROR, "validation layers requested, but not available!\n"); + } + + uint32_t instanceVersion; + VkResult result = vkEnumerateInstanceVersion(&instanceVersion); + if (result == VK_SUCCESS) { + if (instanceVersion < VK_MAKE_API_VERSION(0, 1, 3, 0)) { + vk_log(VK_ERROR, "Vulkan version 1.3 or greater required!\n"); + exit(1); + } + vk_log(VK_INFO, "Vulkan version found (%d) %d.%d.%d\n", + VK_API_VERSION_VARIANT(instanceVersion), + VK_API_VERSION_MAJOR(instanceVersion), + VK_API_VERSION_MINOR(instanceVersion), + VK_API_VERSION_PATCH(instanceVersion)); + } else { + vk_log(VK_ERROR, "Failed to retrieve vulkan version, is vulkan supported in this system?\n"); + exit(1); + } + + // Load Vulkan and create instance + VkApplicationInfo appInfo = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Vulkan Application", + .applicationVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), + .pEngineName = NULL, + .engineVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), + .apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), + }; + + uint32_t sdlExtensionCount = 0; + + if (SDL_Vulkan_GetInstanceExtensions(s.sdl_window, &sdlExtensionCount, NULL) == SDL_FALSE) { + vk_log(VK_ERROR, "SDL_Vulkan_GetInstanceExtensions failed: %s\n", SDL_GetError()); + } + + // make space for debug extenetion + if (enableValidationLayers) { + sdlExtensionCount++; + } + + const char* sdlExtensions[sdlExtensionCount]; + + if (SDL_Vulkan_GetInstanceExtensions(s.sdl_window, &sdlExtensionCount, sdlExtensions) == SDL_FALSE) { + vk_log(VK_ERROR, "SDL_Vulkan_GetInstanceExtensions failed: %s\n", SDL_GetError()); + } + + // add debug extenetion + if (enableValidationLayers) { + sdlExtensions[sdlExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + } + + vk_log(VK_INFO, "The sdl extensions:\n"); + for (uint32_t i = 0; i < sdlExtensionCount; i++) { + vk_log(VK_INFO, "\t%s\n", sdlExtensions[i]); + } + + VkInstanceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledExtensionCount = sdlExtensionCount, + .ppEnabledExtensionNames = sdlExtensions, + .enabledLayerCount = 0, + }; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = validation_layer_count; + createInfo.ppEnabledLayerNames = validation_layers; + } + + if (vkCreateInstance(&createInfo, NULL, &s.vk_instance) != VK_SUCCESS) { + vk_log(VK_ERROR, "Can't start vulkan instance\n"); + } + vk_log(VK_INFO, "Vulkan instance created\n"); +} + +VkExtent2D +chooseSwapExtent(const VkSurfaceCapabilitiesKHR * capabilities) +{ + if (capabilities->currentExtent.width != UINT32_MAX) { + return capabilities->currentExtent; + } else { + int width, height; + SDL_GetWindowSize(s.sdl_window, &width, &height); + + VkExtent2D actualExtent; + actualExtent.width = (uint32_t) width; + actualExtent.height = (uint32_t) height; + + // Manual implementation of std::clamp since it is not available in C + actualExtent.width = (actualExtent.width < capabilities->minImageExtent.width) ? capabilities->minImageExtent.width : + (actualExtent.width > capabilities->maxImageExtent.width) ? capabilities->maxImageExtent.width : + actualExtent.width; + + actualExtent.height = (actualExtent.height < capabilities->minImageExtent.height) ? capabilities->minImageExtent.height : + (actualExtent.height > capabilities->maxImageExtent.height) ? capabilities->maxImageExtent.height : + actualExtent.height; + + return actualExtent; + } +} + +VkSurfaceFormatKHR +chooseSwapSurfaceFormat(const VkSurfaceFormatKHR * availableFormats, uint32_t formatCount) +{ + for (uint32_t i = 0 ; i < formatCount; i ++) { + if (availableFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormats[i]; + } + } + + // if it fails pick the first one + return availableFormats[0]; +} + +VkPresentModeKHR +chooseSwapPresentMode(const VkPresentModeKHR * presentModes, uint32_t presentModeCount) +{ + for (uint32_t i = 0 ; i < presentModeCount; i ++) { + if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + return presentModes[i]; + } + } + + // if it fails pick the the FIFO one + return VK_PRESENT_MODE_FIFO_KHR; +} + +typedef struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + VkSurfaceFormatKHR formats[100]; + uint32_t formatCount; + VkPresentModeKHR presentModes[100]; + uint32_t presentModeCount; +} SwapChainSupportDetails; + +SwapChainSupportDetails +querySwapChainSupport(VkPhysicalDevice device) +{ + // TODO Make SwapChainSupportDetails malloc it;s arrays and free it after it is used. + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, s.vk_surface, &details.capabilities); + + vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, NULL); + + if (details.formatCount != 0) { + // todo alloc format arrray + vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, details.formats); + } + + vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, NULL); + + if (details.presentModeCount != 0) { + // todo alloc presentModes array + vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, details.presentModes); + } + + return details; +} + +typedef struct QueueFamilyIndices { + uint32_t graphicsFamily; + bool graphicsFlag; + uint32_t presentFamily; + bool presentFlag; +} QueueFamilyIndices; + +bool +vulkan_queue_family_check_flags(QueueFamilyIndices x) +{ + return x.graphicsFlag && x.presentFlag; +} + +QueueFamilyIndices vulkan_find_queue_families(VkPhysicalDevice device) { + QueueFamilyIndices indices; + indices.graphicsFlag = false; + indices.presentFlag = false; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL); + + VkQueueFamilyProperties queueFamilies[queueFamilyCount]; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies); + + for (uint32_t i = 0; i < queueFamilyCount; i++) { + if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + indices.graphicsFlag = true; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, s.vk_surface, &presentSupport); + if (presentSupport) { + indices.presentFamily = i; + indices.presentFlag = true; + } + + if (vulkan_queue_family_check_flags(indices)) break; + } + + return indices; +} + +bool +vulkan_check_device_extension_support(VkPhysicalDevice device) +{ + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, NULL); + + VkExtensionProperties availableExtensions[extensionCount]; + vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, availableExtensions); + + uint32_t flag = 0; + + for (uint32_t i = 0; i < deviceExtensionCount; i++) { + for (uint32_t j = 0; j < extensionCount; j++) { + if (strcmp(device_extensions[i], availableExtensions[j].extensionName) == 0) { + flag++; + break; + } + } + } + + return flag == deviceExtensionCount; +} + +bool +vulkan_is_device_suitable(VkPhysicalDevice device) +{ + QueueFamilyIndices indices = vulkan_find_queue_families(device); + bool extensionsSupported = vulkan_check_device_extension_support(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !(swapChainSupport.formatCount == 0) && !(swapChainSupport.presentModeCount == 0); + } + + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return vulkan_queue_family_check_flags(indices) && extensionsSupported + && swapChainAdequate && supportedFeatures.samplerAnisotropy; +} + +void +vulkan_pick_physical_device() +{ + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, NULL); + if (deviceCount == 0) { + vk_log(VK_INFO, "failed to find GPUs with Vulkan support!\n"); + } + + VkPhysicalDevice devices[deviceCount]; + vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, devices); + + for (uint32_t i = 0; i < deviceCount; i++) { + if (vulkan_is_device_suitable(devices[i])) { + s.vk_physical_device = devices[i]; + break; + } + } + + if (s.vk_physical_device == VK_NULL_HANDLE) { + vk_log(VK_ERROR, "failed to find a suitable GPU!\n"); + } + + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(s.vk_physical_device, &deviceProperties); + vk_log(VK_INFO, "Picked [%s] physical device.\n", deviceProperties.deviceName); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL); + VkExtensionProperties extensions[extensionCount]; + VkResult result = vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions); + + vk_log(VK_INFO, "Vulkan enabled extensions: %s\n", string_VkResult(result)); + for (uint32_t i = 0; i < extensionCount; i++) { + vk_log(VK_INFO, "\t%s\n", extensions[i].extensionName); + } + +} + +void +vulkan_create_logical_device() +{ + QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device); + + // TODO CREATE MULPILE QUEUES + // https://vulkan-tutorial.com/en/Drawing_a_triangle/Presentation/Window_surface#page_Creating-the-presentation-queue + + VkDeviceQueueCreateInfo queueCreateInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = indices.graphicsFamily, + .queueCount = 1, + }; + + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + VkPhysicalDeviceFeatures deviceFeatures = {0}; + vkGetPhysicalDeviceFeatures(s.vk_physical_device, &deviceFeatures); + deviceFeatures.samplerAnisotropy = VK_TRUE; +#ifndef NDEBUG + /* Disable robust buffer access when building without debug */ + deviceFeatures.robustBufferAccess = VK_FALSE; +#endif + + VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamic_rendering_feature = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, + .dynamicRendering = VK_TRUE, + }; + + VkDeviceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queueCreateInfo, + .queueCreateInfoCount = 1, + .pNext = &dynamic_rendering_feature, + .pEnabledFeatures = &deviceFeatures, + .enabledExtensionCount = deviceExtensionCount, + .ppEnabledExtensionNames = device_extensions, + .enabledLayerCount = 0, + }; + + if (vkCreateDevice(s.vk_physical_device, &createInfo, NULL, &s.vk_device) != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to create logical device!\n"); + } + vk_log(VK_INFO, "Vulkan logical device created\n"); + + vkGetDeviceQueue(s.vk_device, indices.graphicsFamily, 0, &s.vk_graphics_queue); + vkGetDeviceQueue(s.vk_device, indices.presentFamily, 0, &s.vk_present_queue); +} + +void +update_camera(float xoffset, float yoffset) +{ + s.camera.yaw += xoffset; + s.camera.pitch += yoffset; + + // Make sure that when pitch is out of bounds, the screen doesn't get flipped + if (s.camera.pitch > 89.0f) + s.camera.pitch = 89.0f; + if (s.camera.pitch < -89.0f) + s.camera.pitch = -89.0f; + + /* vec3 front; */ + /* front[0] = -sin(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */ + /* front[1] = sin(glm_rad(s.camera.pitch)); */ + /* front[2] = -cos(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */ + /* glm_normalize_to(front, s.camera.front); */ +} + +void +mouseCallback(SDL_Event *event) +{ + if (event->type != SDL_MOUSEMOTION) return; + if (!s.mouse_pressed) return; + float xoffset = event->motion.xrel; + float yoffset = -event->motion.yrel; // Reversed since y-coordinates range from bottom to top + + float sensitivity = 0.1f; // Change this value to your liking + xoffset *= sensitivity; + yoffset *= sensitivity; + + update_camera(xoffset, yoffset); +} + +void +vulkan_create_surface() +{ + if (SDL_Vulkan_CreateSurface(s.sdl_window, s.vk_instance, &s.vk_surface) == SDL_FALSE) { + vk_log(VK_ERROR, "Failed to create surface\n"); + } else { + vk_log(VK_INFO, "Vulkan surface created\n"); + } +} + +void +vulkan_create_swap_chain() +{ + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(s.vk_physical_device); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats, swapChainSupport.formatCount); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes, swapChainSupport.presentModeCount); + VkExtent2D extent = chooseSwapExtent(&swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = s.vk_surface, + .minImageCount = imageCount, + .imageFormat = surfaceFormat.format, + .imageColorSpace = surfaceFormat.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + }; + + QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device); + + uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = NULL; // Optional + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + VK_CHECK(vkCreateSwapchainKHR(s.vk_device, &createInfo, NULL, &s.vk_swap_chain)); + /* if (result != VK_SUCCESS) { */ + /* vk_log(VK_ERROR, "ERROR: failed to create swap chain! %s\n", string_VkResult(result)); */ + /* } */ + + VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, NULL)); + //vk_log(VK_INFO, "vk_swap_chain_images count: %d\n", s.vk_swap_chain_image_count); + // todo alloc space for images + VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, s.vk_swap_chain_images)); + + s.vk_swap_chain_image_format = surfaceFormat.format; + s.vk_swap_chain_extent = extent; + + vk_log(VK_INFO, "Vulkan swapchain created!\n"); +} + + +VkImageView +create_image_view(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) +{ + VkImageViewCreateInfo viewInfo = {0}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + VK_CHECK(vkCreateImageView(s.vk_device, &viewInfo, NULL, &imageView)); + + return imageView; +} + +void +vulkan_create_image_views() +{ + for (size_t i = 0; i < s.vk_swap_chain_image_count; i++) { + s.vk_swap_chain_image_views[i] = create_image_view(s.vk_swap_chain_images[i], s.vk_swap_chain_image_format, VK_IMAGE_ASPECT_COLOR_BIT); + } + vk_log(VK_INFO, "Vulkan image views created!\n"); +} + +VkShaderModule +createShaderModule(const char * code, long size) +{ + VkShaderModuleCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = size; + createInfo.pCode = (const uint32_t *)code; + + VkShaderModule shaderModule; + if (vkCreateShaderModule(s.vk_device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to create shader module!\n"); + } + + return shaderModule; +} + +shaderc_compilation_result_t +load_compile_shader_data(const char * path, shaderc_shader_kind shader_kind) +{ + FILE* file = fopen(path, "r"); + if (!file) { + vk_log(VK_ERROR, "SHADER COMPILATION: Failed to open file: %s\n", path); + return NULL; + } + + fseek(file, 0, SEEK_END); + long glsl_length = ftell(file); + fseek(file, 0, SEEK_SET); + + char* glsl_src = (char*)malloc(glsl_length + 1); + if (!glsl_src) { + vk_log(VK_ERROR, "SHADER COMPILATION: Failed to allocate memory\n"); + fclose(file); + return NULL; + } + + fread(glsl_src, 1, glsl_length, file); + glsl_src[glsl_length] = '\0'; + fclose(file); + + shaderc_compiler_t compiler = shaderc_compiler_initialize(); + if (!compiler) { + vk_log(VK_ERROR, "SHADER COMPILATION: Failed to initialize shader compiler\n"); + free(glsl_src); + return NULL; + } + + shaderc_compile_options_t options = shaderc_compile_options_initialize(); + shaderc_compilation_result_t result = shaderc_compile_into_spv(compiler, + glsl_src, + glsl_length, + shader_kind, + path, "main", options); + + if (shaderc_result_get_compilation_status(result) != shaderc_compilation_status_success) { + vk_log(VK_ERROR, "SHADER COMPILATION: error: %s\n", shaderc_result_get_error_message(result)); + shaderc_result_release(result); + shaderc_compiler_release(compiler); + free(glsl_src); + return NULL; + } + + shaderc_compiler_release(compiler); + free(glsl_src); + + return result; +} + +uint32_t +findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(s.vk_physical_device, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + vk_log(VK_ERROR, "failed to find suitable memory type!\n"); + return 9999; +} + +void +createImage(uint32_t width, uint32_t height, VkFormat format, + VkImageTiling tiling, VkImageUsageFlags usage, + VkMemoryPropertyFlags properties, VkImage *image, + VkDeviceMemory *imageMemory) +{ + VkImageCreateInfo imageInfo = {0}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK(vkCreateImage(s.vk_device, &imageInfo, NULL, image)); + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(s.vk_device, *image, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + VK_CHECK(vkAllocateMemory(s.vk_device, &allocInfo, NULL, imageMemory)); + + vkBindImageMemory(s.vk_device, *image, *imageMemory , 0); +} + +VkCommandBuffer +beginSingleTimeCommands() +{ + VkCommandBufferAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = s.vk_command_pool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &commandBuffer)); + + VkCommandBufferBeginInfo beginInfo = {0}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + return commandBuffer; +} + +void +endSingleTimeCommands(VkCommandBuffer commandBuffer) +{ + VK_CHECK(vkEndCommandBuffer(commandBuffer)); + + VkSubmitInfo submitInfo = {0}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VK_CHECK(vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, VK_NULL_HANDLE)); + VK_CHECK(vkQueueWaitIdle(s.vk_graphics_queue)); + + vkFreeCommandBuffers(s.vk_device, s.vk_command_pool, 1, &commandBuffer); +} + +int +hasStencilComponent(VkFormat format) +{ + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} + +void +transitionImageLayout(VkImage image, VkFormat format, + VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, + VkImageLayout oldLayout, VkImageLayout newLayout) +{ + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + // barrier.subresourceRange.aspectMask = + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } else { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier(commandBuffer, + srcStageMask, dstStageMask, + 0, + 0, NULL, + 0, NULL, + 1, &barrier); + endSingleTimeCommands(commandBuffer); +} + +VkFormat +findSupportedFormat(VkFormat *candidates, size_t n, + VkImageTiling tiling, + VkFormatFeatureFlags features) +{ + for (size_t i = 0; i < n; i++) { + VkFormat format = candidates[i]; + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(s.vk_physical_device, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + vk_log(VK_ERROR, "failed to find supported format!\n"); + abort(); +} + +VkFormat +findDepthFormat() +{ + VkFormat formats[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}; + return findSupportedFormat(formats, VK_ARRAY_LEN(formats), + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} + +void +vulkan_create_depth_resources() +{ + VkFormat depthFormat = findDepthFormat(); + createImage(s.vk_swap_chain_extent.width, s.vk_swap_chain_extent.height, + depthFormat, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &s.vk_depth_image, &s.vk_depth_image_memory); + s.vk_depth_image_view = create_image_view(s.vk_depth_image, depthFormat, + VK_IMAGE_ASPECT_DEPTH_BIT); + + transitionImageLayout(s.vk_depth_image, depthFormat, + VK_ACCESS_NONE, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); +} + +void +vulkan_create_graphics_pipeline() +{ + shaderc_compilation_result_t vert_result = load_compile_shader_data("src/shader.vert", shaderc_vertex_shader); + if (!vert_result) { + vk_log(VK_ERROR, "Can't load vertex shader\n"); + if (s.prev_vert_result) { + vert_result = s.prev_vert_result; + } + } + if (s.prev_vert_result && vert_result != s.prev_vert_result) shaderc_result_release(s.prev_vert_result); + s.prev_vert_result = vert_result; + shaderc_compilation_result_t frag_result = load_compile_shader_data("src/shader.frag", shaderc_fragment_shader); + if (!frag_result) { + vk_log(VK_ERROR, "Can't load fragment shader\n"); + if (s.prev_frag_result) { + frag_result = s.prev_frag_result; + } + } + if (s.prev_frag_result && frag_result != s.prev_frag_result) shaderc_result_release(s.prev_frag_result); + s.prev_frag_result = frag_result; + + const char * vert_data = shaderc_result_get_bytes(vert_result); + long vert_size = shaderc_result_get_length(vert_result); + const char * frag_data = shaderc_result_get_bytes(frag_result); + long frag_size = shaderc_result_get_length(frag_result); + + vk_log(VK_INFO, "Shaders loaded\n"); + + VkShaderModule vertShaderModule = createShaderModule(vert_data, vert_size); + VkShaderModule fragShaderModule = createShaderModule(frag_data, frag_size); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {0}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {0}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkDynamicState dynamicStates[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE, + VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE + }; + + VkPipelineDynamicStateCreateInfo dynamicState = {0}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = VK_ARRAY_LEN(dynamicStates); + dynamicState.pDynamicStates = dynamicStates; + + VkVertexInputBindingDescription bindingDescription = {0}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription attributeDescriptions[3] = {0}; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {0}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = VK_ARRAY_LEN(attributeDescriptions); + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {0}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) s.vk_swap_chain_extent.width; + viewport.height = (float) s.vk_swap_chain_extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {0}; + scissor.offset = (VkOffset2D){0, 0}; + scissor.extent = s.vk_swap_chain_extent; + + VkPipelineViewportStateCreateInfo viewportState = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + VkPipelineRasterizationStateCreateInfo rasterizer = {0}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + //rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + //rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // Optional + rasterizer.depthBiasClamp = 0.0f; // Optional + rasterizer.depthBiasSlopeFactor = 0.0f; // Optional + + VkPipelineMultisampleStateCreateInfo multisampling = {0}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // Optional + multisampling.pSampleMask = NULL; // Optional + multisampling.alphaToCoverageEnable = VK_FALSE; // Optional + multisampling.alphaToOneEnable = VK_FALSE; // Optional + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {0}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + /* colorBlendAttachment.blendEnable = VK_FALSE; */ + /* colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional */ + /* colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional */ + /* colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional */ + /* colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional */ + /* colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional */ + /* colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional */ + + VkPipelineColorBlendStateCreateInfo colorBlending = {0}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_TRUE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; // Optional + colorBlending.blendConstants[1] = 0.0f; // Optional + colorBlending.blendConstants[2] = 0.0f; // Optional + colorBlending.blendConstants[3] = 0.0f; // Optional + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &s.vk_descriptor_set_layout; + pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional + pipelineLayoutInfo.pPushConstantRanges = NULL; // Optional + + if (vkCreatePipelineLayout(s.vk_device, &pipelineLayoutInfo, NULL, &s.vk_pipeline_layout) != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to create pipeline layout!\n"); + } + + VkPipelineDepthStencilStateCreateInfo depthStencil = {0}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.minDepthBounds = 0.0f; // Optional + depthStencil.maxDepthBounds = 1.0f; // Optional + depthStencil.front = (VkStencilOpState){0}; // Optional + depthStencil.back = (VkStencilOpState){0}; // Optional + + // TODO depthAttachment + VkPipelineRenderingCreateInfo pipeline_rendering_create_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &s.vk_swap_chain_image_format, + .depthAttachmentFormat = findDepthFormat(), + }; + VkGraphicsPipelineCreateInfo pipelineInfo = {0}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + + pipelineInfo.pNext = &pipeline_rendering_create_info, + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = s.vk_pipeline_layout; + pipelineInfo.renderPass = VK_NULL_HANDLE; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional + pipelineInfo.basePipelineIndex = -1; // Optional + + if (vkCreateGraphicsPipelines(s.vk_device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &s.vk_graphics_pipeline) != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to create graphics pipeline!\n"); + } + + if (s.prev_vert_result != vert_result) { + shaderc_result_release(vert_result); + } + if (s.prev_frag_result != frag_result) { + shaderc_result_release(frag_result); + } + + vkDestroyShaderModule(s.vk_device, fragShaderModule, NULL); + vkDestroyShaderModule(s.vk_device, vertShaderModule, NULL); +} + +void +vulkan_create_command_pool() +{ + QueueFamilyIndices queueFamilyIndices = vulkan_find_queue_families(s.vk_physical_device); + + VkCommandPoolCreateInfo poolInfo = {0}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + + VK_CHECK(vkCreateCommandPool(s.vk_device, &poolInfo, NULL, &s.vk_command_pool)); +} + +void +vulkan_create_command_buffer() +{ + // TODO Find a way to group allocation + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkCommandBufferAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = s.vk_command_pool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &s.frames[i].vk_command_buffer)); + } +} + +void +recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) +{ + VkCommandBufferBeginInfo beginInfo = {0}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = NULL; // Optional + + VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + vkCmdSetDepthTestEnable(commandBuffer, 1); + vkCmdSetDepthWriteEnable(commandBuffer, 1); + + transitionImageLayout( + s.vk_swap_chain_images[imageIndex], VK_FORMAT_R8G8B8A8_SRGB, 0, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + VkRenderingAttachmentInfo colorAttachment = {0}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = s.vk_swap_chain_image_views[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue.color = + (VkClearColorValue){0.0f, 0.0f, 0.0f, 1.0f}; + + VkRenderingAttachmentInfo depthAttachment = {0}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = s.vk_depth_image_view; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.clearValue.depthStencil = (VkClearDepthStencilValue){1.0f, 0u}; + + VkRenderingInfo renderingInfo = {}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = + (VkRect2D){{0, 0}, s.vk_swap_chain_extent}; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + s.vk_graphics_pipeline); + + VkViewport viewport = {0}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)(s.vk_swap_chain_extent.width); + viewport.height = (float)(s.vk_swap_chain_extent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor = {0}; + scissor.offset = (VkOffset2D){0, 0}; + scissor.extent = s.vk_swap_chain_extent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {s.vk_vertex_buffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + vkCmdBindIndexBuffer(commandBuffer, s.vk_index_buffer, 0, + VK_INDEX_TYPE_UINT16); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + s.vk_pipeline_layout, 0, 1, + &s.frames[currentFrame].vk_descriptor_set, 0, NULL); + + + vkCmdDrawIndexed(commandBuffer, INDICES_SIZE, 1, 0, 0, 0); + vkCmdEndRendering(commandBuffer); + + transitionImageLayout(s.vk_swap_chain_images[imageIndex], + VK_FORMAT_R8G8B8A8_SRGB, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + VK_CHECK(vkEndCommandBuffer(commandBuffer)); +} + +void +vulkan_create_sync_objects() +{ + VkSemaphoreCreateInfo semaphoreInfo = {0}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {0}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].image_available_semaphore)); + VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].render_finished_semaphore)); + VK_CHECK(vkCreateFence(s.vk_device, &fenceInfo, NULL, &s.frames[i].in_flight_fence)); + } +} + +void +createBuffer(VkDeviceSize size, + VkBufferUsageFlags usage, + VkMemoryPropertyFlags properties, + VkBuffer *buffer, VkDeviceMemory *bufferMemory) +{ + VkBufferCreateInfo bufferInfo = {0}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK(vkCreateBuffer(s.vk_device, &bufferInfo, NULL, buffer)); + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(s.vk_device, *buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + // TODO: group allocations etc... (allocations limited by hardware) + VK_CHECK(vkAllocateMemory(s.vk_device, &allocInfo, NULL, bufferMemory)); + + VK_CHECK(vkBindBufferMemory(s.vk_device, *buffer, *bufferMemory, 0)); +} + +void copy_buffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + VkBufferCopy copyRegion = {0}; + copyRegion.srcOffset = 0; // Optional + copyRegion.dstOffset = 0; // Optional + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + endSingleTimeCommands(commandBuffer); +} + +void +vulkan_create_vertex_buffer() +{ + VkDeviceSize bufferSize = sizeof(vertices[0]) * VERTICES_SIZE; + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + + createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &stagingBuffer, &stagingBufferMemory); + + void* data; + VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data)); + memcpy(data, vertices, (size_t) bufferSize); + vkUnmapMemory(s.vk_device, stagingBufferMemory); + + createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &s.vk_vertex_buffer, &s.vk_vertex_buffer_memory); + + copy_buffer(stagingBuffer, s.vk_vertex_buffer, bufferSize); + + vkDestroyBuffer(s.vk_device, stagingBuffer, NULL); + vkFreeMemory(s.vk_device, stagingBufferMemory, NULL); +} + +void +vulkan_create_index_buffer() +{ + VkDeviceSize bufferSize = sizeof(indices[0]) * INDICES_SIZE; + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + + createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &stagingBuffer, &stagingBufferMemory); + + void* data; + VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data)); + memcpy(data, indices, (size_t) bufferSize); + vkUnmapMemory(s.vk_device, stagingBufferMemory); + + createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &s.vk_index_buffer, &s.vk_index_buffer_memory); + + copy_buffer(stagingBuffer, s.vk_index_buffer, bufferSize); + + vkDestroyBuffer(s.vk_device, stagingBuffer, NULL); + vkFreeMemory(s.vk_device, stagingBufferMemory, NULL); +} + +void +vulkan_create_descriptor_set_layout() +{ + VkDescriptorSetLayoutBinding uboLayoutBinding = {0}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL; + //uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + uboLayoutBinding.pImmutableSamplers = NULL; // optional + + VkDescriptorSetLayoutBinding samplerLayoutBinding = {0}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = NULL; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutBinding bindings[2] = {uboLayoutBinding, samplerLayoutBinding}; + + VkDescriptorSetLayoutCreateInfo layoutInfo = {0}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = VK_ARRAY_LEN(bindings); + layoutInfo.pBindings = bindings; + + VK_CHECK(vkCreateDescriptorSetLayout(s.vk_device, &layoutInfo, NULL, &s.vk_descriptor_set_layout)); +} + +void +vulkan_create_uniform_buffers() +{ + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &s.frames[i].vk_uniform_buffer, &s.frames[i].vk_uniform_buffer_memory); + + VK_CHECK(vkMapMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, 0, bufferSize, 0, &s.frames[i].vk_uniform_buffer_mapped)); + } +} + +void +vulkan_create_descriptor_pool() +{ + VkDescriptorPoolSize poolSizes[2] = {0}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT; + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT; + + VkDescriptorPoolCreateInfo poolInfo = {0}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = VK_ARRAY_LEN(poolSizes); + poolInfo.pPoolSizes = poolSizes; + poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; + + VK_CHECK(vkCreateDescriptorPool(s.vk_device, &poolInfo, NULL, &s.vk_descriptor_pool)); +} + +void +vulkan_create_descriptor_sets() +{ + VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT] = {0}; + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + layouts[i] = s.vk_descriptor_set_layout; + } + + // TODO Find a way to group allocation + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorSetAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = s.vk_descriptor_pool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = layouts; + + VK_CHECK(vkAllocateDescriptorSets(s.vk_device, &allocInfo, &s.frames[i].vk_descriptor_set)); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo = {0}; + bufferInfo.buffer = s.frames[i].vk_uniform_buffer; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {0}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = s.vk_texture_image_view; + imageInfo.sampler = s.vk_texture_sampler; + + VkWriteDescriptorSet descriptorWrites[2] = {0}; + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = s.frames[i].vk_descriptor_set; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = s.frames[i].vk_descriptor_set; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(s.vk_device, VK_ARRAY_LEN(descriptorWrites), descriptorWrites, 0, NULL); + } +} + +void +cleanupSwapChain() +{ + vkDestroyImageView(s.vk_device, s.vk_depth_image_view, NULL); + vkDestroyImage(s.vk_device, s.vk_depth_image, NULL); + vkFreeMemory(s.vk_device, s.vk_depth_image_memory, NULL); + + for (uint32_t i = 0; i < s.vk_swap_chain_image_count; i++) { + vkDestroyImageView(s.vk_device, s.vk_swap_chain_image_views[i], NULL); + } + vkDestroySwapchainKHR(s.vk_device, s.vk_swap_chain, NULL); +} + +void +recreateSwapChain() +{ + vkDeviceWaitIdle(s.vk_device); + + cleanupSwapChain(); + + vulkan_create_swap_chain(); + vulkan_create_image_views(); + vulkan_create_depth_resources(); +} + +void +handle_input(bool * quit) +{ + SDL_Event e; + + while (SDL_PollEvent(&e) != 0) { + // User requests quit + if (e.type == SDL_QUIT) { + *quit = true; + } + // User presses a key + else if (e.type == SDL_KEYDOWN) { + switch (e.key.keysym.sym) { + case SDLK_w: + s.camera.pos[0] += 0.1f; + break; + case SDLK_s: + s.camera.pos[0] -= 0.1f; + break; + case SDLK_LEFT: + break; + case SDLK_g: // reload shaders + recreateSwapChain(); + vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL); + vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL); + vulkan_create_graphics_pipeline(); + break; + case SDLK_l: + s.rotate = s.rotate ? 0 : 1; + break; + case SDLK_r: + s.camera.pos[0] = 1.0f; + s.camera.pos[1] = 1.0f; + s.camera.pos[2] = 1.0f; + + s.camera.front[0] = 0.0f; + s.camera.front[1] = 0.0f; + s.camera.front[2] = 0.0f; + + s.camera.up[0] = 0.0f; + s.camera.up[1] = 0.0f; + s.camera.up[2] = 1.0f; + + s.camera.yaw = 0.0f; + s.camera.pitch = 90.0f; + s.camera.lastX = 400.0f; + s.camera.lastY = 300.0f; + s.camera.fov = 45.0f; + update_camera(0,0); + break; + } + } + else if (e.type == SDL_MOUSEWHEEL) { + if(e.wheel.y > 0) // scroll up + { + s.zoom += 1; + } + else if(e.wheel.y < 0) // scroll down + { + s.zoom -= 1; + if (s.zoom == -100) s.zoom = 1; + } + } + else if (e.type == SDL_MOUSEBUTTONDOWN) { + s.mouse_pressed = 1; + } + else if (e.type == SDL_MOUSEBUTTONUP) { + s.mouse_pressed = 0; + } + mouseCallback(&e); + } +} + +void +copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, + uint32_t height) +{ + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region = {0}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = (VkOffset3D){0, 0, 0}; + region.imageExtent = (VkExtent3D){width, height, 1}; + + vkCmdCopyBufferToImage(commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); +} + +void +vulkan_create_texture_image() +{ + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("assets/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + vk_log(VK_ERROR, "failed to load texture image!\n"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory); + void* data; + vkMapMemory(s.vk_device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, (size_t)imageSize); + vkUnmapMemory(s.vk_device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &s.vk_texture_image, + &s.vk_texture_image_memory); + + transitionImageLayout( + s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB, 0, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, s.vk_texture_image, (uint32_t)texWidth, + (uint32_t)texHeight); + transitionImageLayout( + s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(s.vk_device, stagingBuffer, NULL); + vkFreeMemory(s.vk_device, stagingBufferMemory, NULL); +} + +void +vulkan_create_texture_image_view() +{ + s.vk_texture_image_view = create_image_view(s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); +} + +void vulkan_create_texture_sampler() { + VkSamplerCreateInfo samplerInfo = {0}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + + VkPhysicalDeviceProperties properties = {0}; + vkGetPhysicalDeviceProperties(s.vk_physical_device, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + VK_CHECK(vkCreateSampler(s.vk_device, &samplerInfo, NULL, &s.vk_texture_sampler)); +} + +void +init_vulkan() +{ + vk_log(VK_WARN, "====================================\n"); + vk_log(VK_WARN, " DEBUG ON \n"); + vk_log(VK_WARN, "====================================\n"); + + vulkan_create_instance(); + // vulkan_setup_debug_messenger(); + vulkan_create_surface(); + vulkan_pick_physical_device(); + vulkan_create_logical_device(); + vulkan_create_swap_chain(); + vulkan_create_image_views(); + vulkan_create_descriptor_set_layout(); + vulkan_create_graphics_pipeline(); + vulkan_create_command_pool(); + vulkan_create_depth_resources(); + vulkan_create_texture_image(); + vulkan_create_texture_image_view(); + vulkan_create_texture_sampler(); + vulkan_create_vertex_buffer(); + vulkan_create_index_buffer(); + vulkan_create_uniform_buffers(); + vulkan_create_descriptor_pool(); + vulkan_create_descriptor_sets(); + vulkan_create_command_buffer(); + vulkan_create_sync_objects(); +} + +void +close_vulkan() +{ + vkDeviceWaitIdle(s.vk_device); + // Cleanup + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(s.vk_device, s.frames[i].image_available_semaphore, NULL); + vkDestroySemaphore(s.vk_device, s.frames[i].render_finished_semaphore, NULL); + vkDestroyFence(s.vk_device, s.frames[i].in_flight_fence, NULL); + } + + vkDestroyCommandPool(s.vk_device, s.vk_command_pool, NULL); + + cleanupSwapChain(); + + vkDestroySampler(s.vk_device, s.vk_texture_sampler, NULL); + vkDestroyImageView(s.vk_device, s.vk_texture_image_view, NULL); + + vkDestroyImage(s.vk_device, s.vk_texture_image, NULL); + vkFreeMemory(s.vk_device, s.vk_texture_image_memory, NULL); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(s.vk_device, s.frames[i].vk_uniform_buffer, NULL); + vkFreeMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, NULL); + } + vkDestroyDescriptorPool(s.vk_device, s.vk_descriptor_pool, NULL); + vkDestroyDescriptorSetLayout(s.vk_device, s.vk_descriptor_set_layout, NULL); + + vkDestroyBuffer(s.vk_device, s.vk_vertex_buffer, NULL); + vkFreeMemory(s.vk_device, s.vk_vertex_buffer_memory, NULL); + + vkDestroyBuffer(s.vk_device, s.vk_index_buffer, NULL); + vkFreeMemory(s.vk_device, s.vk_index_buffer_memory, NULL); + + vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL); + vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL); + vkDestroyDevice(s.vk_device, NULL); + vkDestroySurfaceKHR(s.vk_instance, s.vk_surface, NULL); + /* if (enableValidationLayers) { */ + /* DestroyDebugUtilsMessengerEXT(s.vk_instance, s.vk_debug_messenger, NULL); */ + /* } */ + vkDestroyInstance(s.vk_instance, NULL); + + if (s.prev_vert_result) { + shaderc_result_release(s.prev_vert_result); + } + if (s.prev_frag_result) { + shaderc_result_release(s.prev_frag_result); + } +} + +float +current_time() +{ + static struct timespec startTime; + static int isStartTimeInitialized = 0; + + if (!isStartTimeInitialized) { + clock_gettime(CLOCK_MONOTONIC, &startTime); + isStartTimeInitialized = 1; + } + + struct timespec currentTime; + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + + return (currentTime.tv_sec - startTime.tv_sec) + + (currentTime.tv_nsec - startTime.tv_nsec) / 1e9f; +} + +void +updateUniformBuffer(uint32_t currentImage) +{ + float time = current_time(); + + UniformBufferObject ubo = {0}; + + ubo.resolution[0] = s.vk_swap_chain_extent.width; + ubo.resolution[1] = s.vk_swap_chain_extent.height; + + glm_mat4_identity(ubo.model); + + if (s.rotate) + glm_rotate(ubo.model, glm_rad(50 * time * glm_rad(90.0f)), GLM_ZUP); + + vec3 eye = GLM_VEC3_ONE_INIT; + vec3 center = GLM_VEC3_ZERO_INIT; + +// vk_log(VK_INFO, "{%.2f, %.2f, %.2f}\n", s.camera.front[0], s.camera.front[1], s.camera.front[2]); + /* glm_vec3_add(s.camera.pos, s.camera.front, center); */ + glm_lookat(s.camera.pos, s.camera.front, s.camera.up, ubo.view); + + /* glm_lookat(eye, center, GLM_ZUP, ubo.view); */ + + float aspect = s.vk_swap_chain_extent.width / (float)s.vk_swap_chain_extent.height; + glm_perspective(glm_rad(45.0f ), aspect, 1.0f, 10.0f, ubo.proj); + + // Inverting the Y axis for Vulkan + ubo.proj[1][1] *= -1; + + memcpy(s.frames[currentImage].vk_uniform_buffer_mapped, &ubo, sizeof(ubo)); +} + +void +draw_frame() { + vkWaitForFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence, VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(s.vk_device, s.vk_swap_chain, UINT64_MAX, s.frames[currentFrame].image_available_semaphore, VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + vk_log(VK_ERROR, "failed to acquire swap chain image!\n"); + } + + updateUniformBuffer(currentFrame); + + vkResetFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence); + + vkResetCommandBuffer(s.frames[currentFrame].vk_command_buffer, 0); + recordCommandBuffer(s.frames[currentFrame].vk_command_buffer, imageIndex); + + VkSubmitInfo submitInfo = {0}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {s.frames[currentFrame].image_available_semaphore}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &s.frames[currentFrame].vk_command_buffer; + + VkSemaphore signalSemaphores[] = {s.frames[currentFrame].render_finished_semaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, s.frames[currentFrame].in_flight_fence) != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to submit draw command buffer!\n"); + } + + VkSwapchainKHR swapChains[] = {s.vk_swap_chain}; + VkPresentInfoKHR presentInfo = {0}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pResults = NULL; + + result = vkQueuePresentKHR(s.vk_present_queue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || s.sdl_window_resized) { + s.sdl_window_resized = 0; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + vk_log(VK_ERROR, "failed to present swap chain image!\n"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} + +int +main(int argc, char* args[]) +{ + init_state(&s); + if (!init()) { + vk_log(VK_INFO, "Failed to initialize!\n"); + } + else { + init_vulkan(); + + bool quit = false; + + + /* VMA POC */ + VmaVulkanFunctions vulkanFunctions = {0}; + vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr; + vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorCreateInfo = {0}; + allocatorCreateInfo.flags = VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2; + allocatorCreateInfo.physicalDevice = s.vk_physical_device; + allocatorCreateInfo.device = s.vk_device; + allocatorCreateInfo.instance = s.vk_instance; + allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions; + + VmaAllocator allocator; + vmaCreateAllocator(&allocatorCreateInfo, &allocator); + + // Entire program... + + // At the end, don't forget to: + vmaDestroyAllocator(allocator); + + // Game loop + update_camera(0, 0); + while (!quit) { + handle_input(&quit); + draw_frame(); + //SDL_Delay(16); + } + + close_vulkan(); + } + + // Free resources and close SDL + closeSDL(); + + return 0; + +} |