#include #include #include #include #include #define ERRFQ(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ exit(1); \ } while(0) #define ERRQ(str) \ do { \ fputs((str), stderr); \ fputc('\n', stderr); \ exit(1); \ } while(0) uint8_t *read_file(const char *path, size_t size) { FILE *file = fopen(path, "rb"); if (!file) { return NULL; } fseek(file, 0, SEEK_END); size_t len = ftell(file); rewind(file); uint8_t *buf = malloc(len); fread(buf, len, 1, file); fclose(file); *size = len; return buf; } /* * Modifications for Vulkan: * Row-column flipped * Negative y * Expect coordinates on range [0, 1] */ void perspective_init(float matrix[4][4], const float fov, const float aspect, const float znear, const float zfar) { memset(matrix, 0, 16*sizeof(float)); const float f = 1.0f / tanf(0.5*fov); matrix[0][0] = f / aspect; matrix[1][1] = -f; matrix[2][2] = -znear/(zfar - znear) - 1.0f; matrix[3][2] = -zfar*znear/(zfar - znear); matrix[2][3] = -1.0f; } void identity_init(float matrix[4][4]) { memset(matrix, 0, 16*sizeof(float)); matrix[0][0] = 1.0f; matrix[1][1] = 1.0f; matrix[2][2] = 1.0f; matrix[3][3] = 1.0f; } struct Uniform { float model[4][4]; float view[4][4]; float proj[4][4]; }; static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char *layerPrefix, const char *msg, void *userData ) { fprintf(stderr, "Validation layer: %s\n", msg); return VK_FALSE; } VkResult CreateDebugReportCallbackEXT( VkInstance instance, const VkDebugReportCallbackCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugReportCallbackEXT *pCallback ) { PFN_vkCreateDebugReportCallbackEXT func = (void*)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); if (func) { return func(instance, pCreateInfo, pAllocator, pCallback); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks *pAllocator) { PFN_vkDestroyDebugReportCallbackEXT func = (void*)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); if (func) { func(instance, callback, pAllocator); } } SDL_Window *win; VkInstance instance; VkDebugReportCallbackEXT callback; VkSurfaceKHR surface; VkPhysicalDevice physicalDevice; uint32_t qIndex; uint32_t nImages; VkDevice device; VkSurfaceFormatKHR format; VkCommandPool cmdPool; VkCommandBuffer cmdBuffers; VkSwapchainKHR swapchain; VkImage *scImages; VkImageView *scImageViews; VkImage depthImage; VkDeviceMemory depthMem; VkImageView depthView; VkExtent2D extent; VkQueue graphicsQueue; VkQueue presentQueue; static void createInstance() { VkApplicationInfo appInfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "VulkanGame", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "VulkanGameEngine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_0 }; unsigned i, nRequired; if (!SDL_Vulkan_GetInstanceExtensions(win, &nRequired, NULL)) { ERRFQ("Could not get number of instance extensions\n"); } static const char *const additional[] = { VK_EXT_DEBUG_REPORT_EXTENSION_NAME }; const unsigned nAdditional = sizeof(additional) / sizeof(additional[0]); const uint32_t nExtensions = nRequired + nAdditional; const char **extensions = malloc(sizeof(const char*) * nExtensions); if (!SDL_Vulkan_GetInstanceExtensions(win, &nRequired, extensions)) { /* OS will handle memory freeing */ ERRQ(SDL_GetError()); } for (i = 0; i < nAdditional; ++i) { extensions[nRequired + i] = additional[i]; } /* Just printing verbosely */ for (i = 0; i < nExtensions; ++i) { printf("%s\n", extensions[i]); } printf("\n"); #if 1 const uint32_t nLayers = 1; static const char *const layers[] = { "VK_LAYER_LUNARG_standard_validation" }; #endif VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledExtensionCount = nExtensions, .ppEnabledExtensionNames = extensions, #if 1 .enabledLayerCount = nLayers, .ppEnabledLayerNames = layers #endif }; VkResult result; if ((result = vkCreateInstance(&createInfo, NULL, &instance)) != VK_SUCCESS) { /* OS will handle memory freeing */ ERRFQ("Could not create Vulkan instance: %d\n", result); } free(extensions); } static void createDebugReporter() { VkDebugReportCallbackCreateInfoEXT createInfo = { .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT, .pfnCallback = debugCallback }; if (CreateDebugReportCallbackEXT(instance, &createInfo, NULL, &callback) != VK_SUCCESS) { ERRQ("Could not create debug reporter"); } } static void selectPhysicalDevice(const char *hint) { uint32_t nDevices; vkEnumeratePhysicalDevices(instance, &nDevices, NULL); if (!nDevices) { ERRQ("No physical devices found"); } VkPhysicalDevice *devices = malloc(nDevices * sizeof(*devices)); vkEnumeratePhysicalDevices(instance, &nDevices, devices); /* Just printing verbosely */ for (uint32_t i = 0; i < nDevices; ++i) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(devices[i], &props); printf("%s\n", props.deviceName); } /* FIXME: Blindly choosing the first device */ physicalDevice = devices[0]; uint32_t nQueueFamilies = 0; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &nQueueFamilies, NULL); VkQueueFamilyProperties *qprops = malloc(nQueueFamilies * sizeof(*qprops)); vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &nQueueFamilies, qprops); for (qIndex = 0; qIndex < nQueueFamilies; ++qIndex) { if (qprops[qIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { break; } } if (qIndex >= nQueueFamilies || nQueueFamilies <= 0) { ERRQ("No queue families capable of graphics"); } VkBool32 canSurface; vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, qIndex, surface, &canSurface); if (!canSurface) { /* Strange devices may be accommodated for but not for now */ ERRQ("Graphics device cannot output a surface"); } VkBool32 canSwapchain = 0; uint32_t nExtensions; vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &nExtensions, NULL); VkExtensionProperties *availableExtensions = malloc(nExtensions * sizeof(*availableExtensions)); vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &nExtensions, availableExtensions); for (uint32_t i = 0; i < nExtensions; ++i) { if (!strcmp(availableExtensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { canSwapchain = 1; break; } } if (!canSwapchain) { ERRQ("Graphics device not swapchain capable"); } free(availableExtensions); free(qprops); free(devices); } static void createLogicalDevice() { VkPhysicalDeviceFeatures features; vkGetPhysicalDeviceFeatures(physicalDevice, &features); float qPriority = 1.0f; VkDeviceQueueCreateInfo qCreateInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = qIndex, .queueCount = 1, .pQueuePriorities = &qPriority }; const char *const swapchainName = VK_KHR_SWAPCHAIN_EXTENSION_NAME; VkDeviceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = &qCreateInfo, .queueCreateInfoCount = 1, .pEnabledFeatures = &features, .enabledExtensionCount = 1, .ppEnabledExtensionNames = &swapchainName }; if (vkCreateDevice(physicalDevice, &createInfo, NULL, &device)) { ERRQ("Could not create logical device"); } } static void destroyLogicalDevice() { vkDestroyDevice(device, NULL); } static void createCommandPool() { VkCommandPoolCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = qIndex }; VkResult result; if ((result = vkCreateCommandPool(device, &createInfo, NULL, &cmdPool)) != VK_SUCCESS) { ERRFQ("Could not create command pool: %d\n", result); } } static void destroyCommandPool() { vkDestroyCommandPool(device, cmdPool, NULL); } static void createCommandBuffers() { VkCommandBufferAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cmdPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1 }; VkResult result; if ((result = vkAllocateCommandBuffers(device, &allocInfo, &cmdBuffers)) != VK_SUCCESS) { ERRFQ("Could not create command buffers: %d\n", result); } } static void destroyCommandBuffers() { vkFreeCommandBuffers(device, cmdPool, 1, &cmdBuffers); } static void createSwapchain() { struct { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR *formats; VkPresentModeKHR *presentModes; } sc = {0}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &sc.capabilities); uint32_t nFormats; vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &nFormats, NULL); if (!nFormats) { ERRQ("No surface formats available"); } sc.formats = malloc(nFormats * sizeof(*sc.formats)); vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &nFormats, sc.formats); uint32_t nPresentModes; vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &nPresentModes, NULL); if (!nPresentModes) { ERRQ("No surface present modes available"); } sc.presentModes = malloc(nPresentModes * sizeof(*sc.presentModes)); vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &nPresentModes, sc.presentModes); for (uint32_t i = 0; i < nFormats; ++i) { VkSurfaceFormatKHR want = { VK_FORMAT_B8G8R8A8_UNORM, // BGRA VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; if (sc.formats[i].format == VK_FORMAT_UNDEFINED) { format = want; fprintf(stderr, "Obtained wanted surface format on first try\n"); break; } if (sc.formats[i].format == want.format && sc.formats[i].colorSpace == want.colorSpace) { format = want; fprintf(stderr, "Obtained wanted format on try %d/%d\n", i+1, nFormats); break; } } /* FIXME: Just assuming this is available */ VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; int width, height; SDL_Vulkan_GetDrawableSize(win, &width, &height); extent.width = width; extent.height = height; nImages = sc.capabilities.minImageCount + 1; if (sc.capabilities.maxImageCount > 0 && nImages > sc.capabilities.maxImageCount) { /* Clamp to max image count if too many requested */ nImages = sc.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = nImages, .imageFormat = format.format, .imageExtent = extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, // assuming same family .preTransform = sc.capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = presentMode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE }; VkResult result; if ((result = vkCreateSwapchainKHR(device, &createInfo, NULL, &swapchain)) != VK_SUCCESS) { ERRFQ("Could not create swapchain: %d\n", result); } free(sc.presentModes); free(sc.formats); } static void destroySwapchain() { if (swapchain) { vkDestroySwapchainKHR(device, swapchain, NULL); } swapchain = NULL; } /* Allocates and reallocates memory */ static void createSwapchainImages() { vkGetSwapchainImagesKHR(device, swapchain, &nImages, NULL); scImages = realloc(scImages, nImages * sizeof(*scImages)); vkGetSwapchainImagesKHR(device, swapchain, &nImages, scImages); scImageViews = realloc(scImageViews, nImages * sizeof(*scImageViews)); for (uint32_t i = 0; i < nImages; ++i) { VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = scImages[i], .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format.format, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1 }; if (vkCreateImageView(device, &createInfo, NULL, &scImageViews[i]) != VK_SUCCESS) { ERRQ("Could not create swapchain image views"); } } } static void destroySwapchainImages() { for (uint32_t i = 0; i < nImages; ++i) { vkDestroyImageView(device, scImageViews[i], NULL); } free(scImageViews); free(scImages); scImageViews = NULL; scImages = NULL; } static void createDepthBuffer() { VkImageCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = VK_FORMAT_D32_SFLOAT, .extent.width = extent.width, .extent.height = extent.height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VkResult result; if ((result = vkCreateImage(device, &createInfo, NULL, &depthImage)) != VK_SUCCESS) { ERRFQ("Could not create depth image: %d\n", result); } VkPhysicalDeviceMemoryProperties memProps; vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); VkMemoryRequirements memReq; vkGetImageMemoryRequirements(device, depthImage, &memReq); uint32_t typeFilter = memReq.memoryTypeBits; uint32_t memType; uint32_t props = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; for (memType = 0; memType < memProps.memoryTypeCount; ++memType) { if (typeFilter & (1<