#include #include #include #include #include #include #include #include "pugl/pugl.h" #include "vk_mem_alloc.h" #define ERRFQ(fmt, ...) \ do { \ fprintf(stderr, fmt __VA_OPT__(,) __VA_ARGS__); \ exit(1); \ } while(0) #define ERRQ(str) ERRFQ("%s\n", str) #define PI 3.14159265359f #define TWOPI 6.28318530718f #define DEGREES(x) ((x)*PI/180.0f) #define MAT4X4_SIZE (16*sizeof(float)) #define MIN(a, b) ((a) <= (b) ? (a) : (b)) #define MAX(a, b) ((a) >= (b) ? (a) : (b)) #define CLAMP(x, l, h) ((x) <= (l) ? (l) : (x) >= (h) ? (h) : (x)) #ifdef HAVE_MSAA static const unsigned msaaEnabled = 1; static const VkSampleCountFlagBits msaaSampleCount = VK_SAMPLE_COUNT_8_BIT; #else static const unsigned msaaEnabled = 0; static const VkSampleCountFlagBits msaaSampleCount = VK_SAMPLE_COUNT_1_BIT; #endif static const VkFormat depthFormat = VK_FORMAT_D24_UNORM_S8_UINT; #define TIMER_INTERVAL 500 static ssize_t get_file_size(FILE *fp) { #if defined(__linux__) #include const int fd = fileno(fp); struct stat st; if (fstat(fd, &st) == -1) { return -1; } return st.st_size; #else #error "Cannot get file sizes on this system" #endif } uint8_t *read_file(const char *path, size_t *size) { #if 0 SDL_RWops *rw = SDL_RWFromFile(path, "r"); if (!rw) { *size = 0; return NULL; } *size = SDL_RWsize(rw); uint8_t *buf = malloc(*size); SDL_RWread(rw, buf, *size, 1); SDL_RWclose(rw); return buf; #else FILE *fp = fopen(path, "rb"); if (!fp) { *size = 0; return NULL; } const ssize_t result = get_file_size(fp); if (result == -1) { *size = 0; return NULL; } else { *size = result; } uint8_t *buf = malloc(*size); size_t r = 0; while ((r = fread(buf, *size, 1, fp))) { } fclose(fp); return buf; #endif } /* ALL matrices are column major * * Modifications for Vulkan: * Invert y axis * Expect z clip coordinates on range [0, 1] */ static void load_perspective(float matrix[4][4], const float fov, const float aspect, const float znear, const float zfar) { memset(matrix, 0, MAT4X4_SIZE); const float f = 1.0f/tanf(0.5f*fov); matrix[0][0] = f/aspect; matrix[1][1] = -f; matrix[2][2] = -zfar/(zfar - znear); matrix[3][2] = -zfar*znear/(zfar - znear); matrix[2][3] = -1.0f; } static void matmul4(const float A[4][4], const float B[4][4], float C[4][4]) { memset(C, 0, MAT4X4_SIZE); int i, j, k; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { for (k = 0; k < 4; ++k) { C[j][i] += A[k][i] * B[j][k]; } } } } static void matvecmul4(const float M[4][4], const float *restrict V, float *restrict C) { int i, j; for (i = 0; i < 4; ++i) { C[i] = 0; for (j = 0; j < 4; ++j) { C[i] += M[j][i] * V[j]; } } } static void load_identity(float matrix[4][4]) { memset(matrix, 0, MAT4X4_SIZE); matrix[0][0] = 1.0f; matrix[1][1] = 1.0f; matrix[2][2] = 1.0f; matrix[3][3] = 1.0f; } static void load_rotation_y(float matrix[4][4], const float angle) { memset(matrix, 0, MAT4X4_SIZE); const float cosangle = cosf(angle); const float sinangle = sinf(angle); matrix[0][0] = cosangle; matrix[2][0] = sinangle; matrix[0][2] = -sinangle; matrix[2][2] = cosangle; matrix[1][1] = 1.0; matrix[3][3] = 1.0; } static void load_location(float matrix[4][4], const float x, const float y, const float z) { load_identity(matrix); matrix[3][0] = x; matrix[3][1] = y; matrix[3][2] = z; } struct Uniform { float model[4][4]; float view[4][4]; float proj[4][4]; }; /* Set once when physical device is chosen, do not modify otherwise */ static VkPhysicalDeviceMemoryProperties memoryProperties; static VkPhysicalDeviceProperties deviceProperties; static VmaAllocator vmaAllocator; //SDL_Window *win; PuglWorld *world; PuglView *view; static VkDevice device; static VkInstance instance; static VkDebugReportCallbackEXT callback; static VkSurfaceKHR surface; static VkPhysicalDevice physicalDevice; static uint32_t graphicsIndex; static uint32_t transferIndex; static VkQueue transferQueue; static VkQueue graphicsQueue; static VkCommandPool commandPool; static VkCommandPool transferPool; static VkCommandBuffer *commandBuffers; static VkSwapchainKHR rawSwapchain; static VkSwapchainKHR oldRawSwapchain; static uint32_t nImages; static VkSurfaceFormatKHR surfaceFormat; static VkImage *swapchainImages; static VkImageView *swapchainImageViews; static VkExtent2D swapchainExtent; static VkImage depthImage; static VmaAllocation depthVma; static VkImageView depthImageView; #if 1 /* TODO: Get rid of this because we're using VMA */ static uint32_t getMemType(const VkMemoryRequirements *memReq, const VkMemoryPropertyFlags flags) { const uint32_t bits = memReq->memoryTypeBits; uint32_t type; for (type = 0; type < memoryProperties.memoryTypeCount; ++type) { if (bits & (1<= memoryProperties.memoryTypeCount) { ERRFQ("No suitable memory type found\n"); } return type; } #endif /* Undefined behavior if `offset` is not a power of two */ static VkDeviceSize getNearestAlignment(const VkDeviceSize offset, const VkDeviceSize alignment) { const VkDeviceSize mask = alignment - 1; if (offset & mask) { return offset + alignment - (offset & mask); } return offset; } static void createInstance() { const 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_MAKE_VERSION(1, 0, 0) }; unsigned i, nRequired; #if 0 if (!SDL_Vulkan_GetInstanceExtensions(win, &nRequired, NULL)) { ERRFQ("Could not get number of instance extensions\n"); } #else nRequired = 0; #endif static const char *const additional[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME, 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 0 if (!SDL_Vulkan_GetInstanceExtensions(win, &nRequired, extensions)) { /* OS will handle memory freeing */ ERRQ(SDL_GetError()); } #endif for (i = 0; i < nAdditional; ++i) { extensions[nRequired + i] = additional[i]; } /* Just printing verbosely */ for (i = 0; i < nExtensions; ++i) { printf("Using instance extension:\t%s\n", extensions[i]); } #ifdef VULKAN_VALIDATION static const char *const layers[] = { "VK_LAYER_LUNARG_standard_validation" }; const uint32_t nLayers = sizeof(layers)/sizeof(layers[0]); #endif VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledExtensionCount = nExtensions, .ppEnabledExtensionNames = extensions, #ifdef VULKAN_VALIDATION .enabledLayerCount = nLayers, .ppEnabledLayerNames = layers #endif }; VkResult result; if ((result = vkCreateInstance(&createInfo, NULL, &instance))) { /* OS will handle memory freeing */ ERRFQ("Could not create Vulkan instance: %d\n", result); } free(extensions); } #ifdef VULKAN_VALIDATION 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, "\nValidation layer: %s\n\n", msg); return VK_FALSE; } VkResult createDebugReportCallback( VkInstance instance, const VkDebugReportCallbackCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugReportCallbackEXT *pCallback ) { printf("Creating debug reporter\n"); PFN_vkCreateDebugReportCallbackEXT func = (void*)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); if (func) { return func(instance, pCreateInfo, pAllocator, pCallback); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void destroyDebugReportCallback(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks *pAllocator) { PFN_vkDestroyDebugReportCallbackEXT func = (void*)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); if (func) { func(instance, callback, pAllocator); } } 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 (createDebugReportCallback(instance, &createInfo, NULL, &callback)) { ERRQ("Could not create debug reporter"); } } #endif 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) { vkGetPhysicalDeviceProperties(devices[i], &deviceProperties); printf("Found physical device:\t`%s`\n", deviceProperties.deviceName); } /* FIXME: Blindly choosing the first device */ physicalDevice = devices[0]; uint32_t nQueueFamilies = 0; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &nQueueFamilies, NULL); VkQueueFamilyProperties *queueProperties = malloc(nQueueFamilies*sizeof(*queueProperties)); vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &nQueueFamilies, queueProperties); for (uint32_t i = 0; i < nQueueFamilies; ++i) { printf("Queue Family %d queueCount:\t%d\n", i, queueProperties[i].queueCount); } for (graphicsIndex = 0; graphicsIndex < nQueueFamilies; ++graphicsIndex) { if (queueProperties[graphicsIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { VkBool32 canSurface; vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, graphicsIndex, surface, &canSurface); if (canSurface) break; } } if (graphicsIndex >= nQueueFamilies) { /* Some devices may have separate graphics and present queue families, or none at all. * We only support graphics and present on the same queue family. */ ERRQ("No queue families capable of graphics and presentation"); } /* Look for a queue family with transfer capabilities but no graphics or compute. * Hardcoded literals are for conciseness and come from the `VkQueueFlagBits` definitions. */ const VkQueueFlagBits want = 0x4; for (transferIndex = 0; transferIndex < nQueueFamilies; ++transferIndex) { if ((queueProperties[transferIndex].queueFlags & 0x7) == want) { goto found; } } /* Look for a queue family with transfer capabilities that isn't the same as before. * Graphics and compute queue families implicitly support transfer. */ for (transferIndex = 0; transferIndex < nQueueFamilies; ++transferIndex) { if (queueProperties[transferIndex].queueFlags & 0x3) { if (transferIndex != graphicsIndex) { goto found; } } } //ERRQ("FIXME: Fallback transfer queue to graphics family"); transferIndex = graphicsIndex; found: printf("Graphics Queue Family Index:\t%d\nTransfer Queue Family Index:\t%d\n", graphicsIndex, transferIndex); vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties); 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(queueProperties); free(devices); } static void createLogicalDevice() { VkPhysicalDeviceFeatures features; vkGetPhysicalDeviceFeatures(physicalDevice, &features); const float queuePriorities[] = { 1.0f, 0.125f }; const VkDeviceQueueCreateInfo queueCreateInfos[] = { { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = graphicsIndex, .queueCount = 1, .pQueuePriorities = &queuePriorities[0] }, { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = transferIndex, .queueCount = 1, .pQueuePriorities = &queuePriorities[1] } }; const char *const swapchainName = VK_KHR_SWAPCHAIN_EXTENSION_NAME; const VkDeviceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = queueCreateInfos, .queueCreateInfoCount = 2, .pEnabledFeatures = &features, .enabledExtensionCount = 1, .ppEnabledExtensionNames = &swapchainName }; VkResult result; if ((result = vkCreateDevice(physicalDevice, &createInfo, NULL, &device))) { ERRFQ("Could not create logical device: %d\n", result); } const VmaAllocatorCreateInfo allocatorInfo = { //.flags = VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT, .physicalDevice = physicalDevice, .device = device }; if ((result = vmaCreateAllocator(&allocatorInfo, &vmaAllocator))) { ERRFQ("Could not create Vulkan Memory Allocator: %d\n", result); } } static void destroyLogicalDevice() { vmaDestroyAllocator(vmaAllocator); vkDestroyDevice(device, NULL); } static void createCommandPools() { VkCommandPoolCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = graphicsIndex }; VkResult result; if ((result = vkCreateCommandPool(device, &createInfo, NULL, &commandPool))) { ERRFQ("Could not create command pool:\t%d\n", result); } createInfo.queueFamilyIndex = transferIndex; if ((result = vkCreateCommandPool(device, &createInfo, NULL, &transferPool))) { ERRFQ("Could not create transfer pool:\t%d\n", result); } } static void destroyCommandPools() { vkDestroyCommandPool(device, transferPool, NULL); vkDestroyCommandPool(device, commandPool, NULL); } static void allocateCommandBuffers() { const VkCommandBufferAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = commandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = nImages }; commandBuffers = realloc(commandBuffers, nImages*sizeof(VkCommandBuffer)); VkResult result; if ((result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers))) { ERRFQ("Could not create command buffers: %d\n", result); } } static void freeCommandBuffers() { if (commandBuffers) { vkFreeCommandBuffers(device, commandPool, nImages, commandBuffers); } } // Kludge over SDL_Vulkan_GetDrawableSize() void getFrame(PuglView *view, int *width, int *height) { const PuglRect rect = puglGetFrame(view); *width = rect.width; *height = rect.height; //printf("Pugl frame:\t%d, %d\n", *width, *height); } static void createSwapchain() { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR *formats; VkPresentModeKHR *presentModes; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities); uint32_t nFormats; vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &nFormats, NULL); if (!nFormats) { ERRQ("No surface formats available"); } formats = malloc(nFormats*sizeof(*formats)); vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &nFormats, formats); uint32_t nPresentModes; vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &nPresentModes, NULL); if (!nPresentModes) { ERRQ("No surface present modes available"); } presentModes = malloc(nPresentModes*sizeof(*presentModes)); vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &nPresentModes, presentModes); for (uint32_t i = 0; i < nFormats; ++i) { VkSurfaceFormatKHR want = { VK_FORMAT_B8G8R8A8_SRGB, // BGRA sRGB VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; if (formats[i].format == VK_FORMAT_UNDEFINED) { surfaceFormat = want; //SDL_Log("Obtained wanted surface format on first try\n"); break; } if (formats[i].format == want.format && formats[i].colorSpace == want.colorSpace) { surfaceFormat = want; //SDL_Log("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; //VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR; VkPresentModeKHR presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; //VkPresentModeKHR presentMode = VK_PRESENT_MODE_MAILBOX_KHR; int width, height; //SDL_Vulkan_GetDrawableSize(win, &width, &height); getFrame(view, &width, &height); width = CLAMP(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); height = CLAMP(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); swapchainExtent.width = width; swapchainExtent.height = height; //swapchainExtent = capabilities.currentExtent; //nImages = capabilities.minImageCount + 1; nImages = capabilities.minImageCount; //nImages = capabilities.maxImageCount; if (capabilities.maxImageCount > 0 && nImages > capabilities.maxImageCount) { /* Clamp to max image count if too many requested */ nImages = capabilities.maxImageCount; } //printf("Swapchain Images:\t%d\n", nImages); const VkSwapchainCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = nImages, .imageFormat = surfaceFormat.format, .imageExtent = swapchainExtent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, // assuming same family .preTransform = capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = presentMode, .clipped = VK_TRUE, .oldSwapchain = oldRawSwapchain }; VkResult result; if ((result = vkCreateSwapchainKHR(device, &createInfo, NULL, &rawSwapchain))) { ERRFQ("Could not create swapchain: %d\n", result); } if (oldRawSwapchain != VK_NULL_HANDLE) { vkDestroySwapchainKHR(device, oldRawSwapchain, NULL); oldRawSwapchain = VK_NULL_HANDLE; } free(presentModes); free(formats); } static void destroySwapchain() { if (rawSwapchain != VK_NULL_HANDLE) { vkDestroySwapchainKHR(device, rawSwapchain, NULL); } rawSwapchain = VK_NULL_HANDLE; } /* Allocates and reallocates memory */ static void createSwapchainImageViews() { vkGetSwapchainImagesKHR(device, rawSwapchain, &nImages, NULL); swapchainImages = malloc(nImages*sizeof(*swapchainImages)); vkGetSwapchainImagesKHR(device, rawSwapchain, &nImages, swapchainImages); swapchainImageViews = malloc(nImages*sizeof(*swapchainImageViews)); for (uint32_t i = 0; i < nImages; ++i) { const VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = swapchainImages[i], .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = surfaceFormat.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, &swapchainImageViews[i])) { ERRQ("Could not create swapchain image views"); } } } static void destroySwapchainImageViews() { for (uint32_t i = 0; i < nImages; ++i) { vkDestroyImageView(device, swapchainImageViews[i], NULL); } free(swapchainImageViews); free(swapchainImages); swapchainImageViews = NULL; swapchainImages = NULL; } static void createDepthImage() { const VkImageCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = depthFormat, .extent.width = swapchainExtent.width, .extent.height = swapchainExtent.height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = msaaSampleCount, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VmaAllocationCreateInfo allocInfo = { .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, .usage = VMA_MEMORY_USAGE_GPU_ONLY }; VkResult result; if ((result = vmaCreateImage(vmaAllocator, &createInfo, &allocInfo, &depthImage, &depthVma, NULL))) { fprintf(stderr, "Could not create depth image: %d\n", result); exit(1); } const VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = depthImage, .format = depthFormat, .components.r = VK_COMPONENT_SWIZZLE_R, .components.g = VK_COMPONENT_SWIZZLE_G, .components.b = VK_COMPONENT_SWIZZLE_B, .components.a = VK_COMPONENT_SWIZZLE_A, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, .viewType = VK_IMAGE_VIEW_TYPE_2D, }; if ((result = vkCreateImageView(device, &viewInfo, NULL, &depthImageView))) { ERRFQ("Could not create depth buffer image view: %d\n", result); } } static void destroyDepthImage() { vkDestroyImageView(device, depthImageView, NULL); vmaDestroyImage(vmaAllocator, depthImage, depthVma); depthImageView = VK_NULL_HANDLE; depthVma = VK_NULL_HANDLE; depthImage = VK_NULL_HANDLE; } static VkImage msaaImage; static VkImageView msaaImageView; static VmaAllocation msaaVma; static void createMultisampleImage() { const VkImageCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = surfaceFormat.format, .extent.width = swapchainExtent.width, .extent.height = swapchainExtent.height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = msaaSampleCount, .usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .tiling = VK_IMAGE_TILING_OPTIMAL }; const VmaAllocationCreateInfo allocInfo = { .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, .usage = VMA_MEMORY_USAGE_GPU_ONLY }; VkResult result; if ((result = vmaCreateImage(vmaAllocator, &createInfo, &allocInfo, &msaaImage, &msaaVma, NULL))) { ERRFQ("Could not create multisample image: %d\n", result); } const VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = msaaImage, .format = VK_FORMAT_B8G8R8A8_SRGB, .components.r = VK_COMPONENT_SWIZZLE_R, .components.g = VK_COMPONENT_SWIZZLE_G, .components.b = VK_COMPONENT_SWIZZLE_B, .components.a = VK_COMPONENT_SWIZZLE_A, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, .viewType = VK_IMAGE_VIEW_TYPE_2D, }; if ((result = vkCreateImageView(device, &viewInfo, NULL, &msaaImageView))) { ERRFQ("Could not create multisample image view: %d\n", result); } } static void destroyMultisampleImage() { vkDestroyImageView(device, msaaImageView, NULL); vmaDestroyImage(vmaAllocator, msaaImage, msaaVma); msaaImageView = VK_NULL_HANDLE; msaaVma = VK_NULL_HANDLE; msaaImage = VK_NULL_HANDLE; } static VkRenderPass renderPass; static void createRenderPass() { VkAttachmentDescription attachments[] = {{ /* Color image */ .format = surfaceFormat.format, .samples = msaaSampleCount, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = msaaEnabled ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = msaaEnabled ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR }, { /* Resolve image */ .format = surfaceFormat.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR }, { /* Depth stencil image */ .format = depthFormat, .samples = msaaSampleCount, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }}; if (!msaaEnabled) { attachments[1] = attachments[2]; } const VkAttachmentReference colorAttachmentRef = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; const VkAttachmentReference resolveAttachmentRef = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; const VkAttachmentReference depthStencilAttachmentRef = { .attachment = msaaEnabled ? 2 : 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; const VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentRef, .pResolveAttachments = msaaEnabled ? &resolveAttachmentRef : NULL, .pDepthStencilAttachment = &depthStencilAttachmentRef }; const VkRenderPassCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pAttachments = attachments, .subpassCount = 1, .attachmentCount = msaaEnabled ? 3 : 2, .pSubpasses = &subpass }; VkResult result; if ((result = vkCreateRenderPass(device, &createInfo, NULL, &renderPass))) { ERRFQ("Could not create render pass: %d\n", result); } } static void destroyRenderPass() { vkDestroyRenderPass(device, renderPass, NULL); renderPass = VK_NULL_HANDLE; } static VkFramebuffer *swapchainFramebuffers; static void createSwapchainFramebuffers() { swapchainFramebuffers = malloc(nImages*sizeof(*swapchainFramebuffers)); for (uint32_t i = 0; i < nImages; ++i) { const VkImageView attachments[] = { msaaImageView, swapchainImageViews[i], depthImageView }; const VkFramebufferCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = renderPass, .attachmentCount = msaaEnabled ? 3 : 2, .pAttachments = msaaEnabled ? attachments : &attachments[1], .width = swapchainExtent.width, .height = swapchainExtent.height, .layers = 1 }; VkResult result; if ((result = vkCreateFramebuffer(device, &createInfo, NULL, &swapchainFramebuffers[i]))) { ERRFQ("Could not create swapchainFramebuffers: %d\n", result); } } } static void destroySwapchainFramebuffers() { for (uint32_t i = 0; i < nImages; ++i) { vkDestroyFramebuffer(device, swapchainFramebuffers[i], NULL); } free(swapchainFramebuffers); swapchainFramebuffers = NULL; } void initVulkan() { createInstance(); #ifdef VULKAN_VALIDATION createDebugReporter(); #endif #if 0 if (!SDL_Vulkan_CreateSurface(win, instance, &surface)) { ERRQ(SDL_GetError()); } #else #include "pugl/detail/x11.h" // HACK: Vulkan needs access to this // Eventually, Pugl should wrap the call to XXX, where XXX is one of: // * vkCreateXlibSurfaceKHR() // * vkCreateWin32SurfaceKHR() // * vkCreateMacOSSurfaceMVK() (using MoltenVK compatibility over Metal) // * possibly (?) others in the future // However, it should do it differently than how SDL does it, because SDL loads the Vulkan Loader // using hidden global state, which goes against Pugl's embedded model. // This could be negotiated at a later time. const VkXlibSurfaceCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, .dpy = ((struct PuglWorldImpl*)world)->impl->display, .window = puglGetNativeWindow(view) }; VkResult result; if ((result = vkCreateXlibSurfaceKHR(instance, &createInfo, NULL, &surface))) { ERRFQ("Could not create Xlib surface: %d\n", result); } #endif selectPhysicalDevice(NULL); createLogicalDevice(); vkGetDeviceQueue(device, transferIndex, 0, &transferQueue); vkGetDeviceQueue(device, graphicsIndex, 0, &graphicsQueue); createCommandPools(); } void deinitVulkan() { destroyCommandPools(); destroyLogicalDevice(); vkDestroySurfaceKHR(instance, surface, NULL); #ifdef VULKAN_VALIDATION destroyDebugReportCallback(instance, callback, NULL); #endif } void beginVulkan() { createSwapchain(); createSwapchainImageViews(); allocateCommandBuffers(); createDepthImage(); if (msaaEnabled) createMultisampleImage(); createRenderPass(); createSwapchainFramebuffers(); } void endVulkan() { destroySwapchainFramebuffers(); destroyRenderPass(); if (msaaEnabled) destroyMultisampleImage(); destroyDepthImage(); destroySwapchainImageViews(); destroySwapchain(); } static struct Uniform uniformData; static VkBuffer uniformBuffer; //static VkDeviceMemory uniformMemory; static VmaAllocation uniformVma; VkDeviceSize uniformAlignment; static void createUniformBuffer() { const VkDeviceSize alignMask = deviceProperties.limits.minUniformBufferOffsetAlignment - 1; uniformAlignment = (sizeof(uniformData) + alignMask) & ~alignMask; printf("Uniform Alignment:\t%lu\n", uniformAlignment); const VkBufferCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, .size = nImages*uniformAlignment, .sharingMode = VK_SHARING_MODE_EXCLUSIVE }; const VmaAllocationCreateInfo allocInfo = { //.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT, .usage = VMA_MEMORY_USAGE_CPU_TO_GPU }; VkResult result; if ((result = vmaCreateBuffer(vmaAllocator, &createInfo, &allocInfo, &uniformBuffer, &uniformVma, NULL))) { ERRFQ("Could not create uniform buffer: %d\n", result); } #if 0 VkResult result; if ((result = vkCreateBuffer(device, &createInfo, NULL, &uniformBuffer))) { ERRFQ("Could not create uniform buffer: %d\n", result); } /* TODO: Figure best memory properties for uniform data */ VkMemoryRequirements memReq; vkGetBufferMemoryRequirements(device, uniformBuffer, &memReq); const uint32_t props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; const uint32_t memType = getMemType(&memReq, props); const VkMemoryAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memReq.size, .memoryTypeIndex = memType }; if ((result = vkAllocateMemory(device, &allocInfo, NULL, &uniformMemory))) { ERRFQ("Could not allocate uniform buffer memory: %d\n", result); } if ((result = vkBindBufferMemory(device, uniformBuffer, uniformMemory, 0))) { ERRFQ("Could not bind uniform buffer memory: %d\n", result); } #endif } static void destroyUniformBuffer() { //vkFreeMemory(device, uniformMemory, NULL); //vkDestroyBuffer(device, uniformBuffer, NULL); vmaDestroyBuffer(vmaAllocator, uniformBuffer, uniformVma); } static void populateUniformBuffer() { load_identity(uniformData.model); load_identity(uniformData.view); load_perspective(uniformData.proj, DEGREES(45), 16.0f/9.0f, 0.1f, 10.0f); /* Could map and copy into the GPU here but we could also do that per-frame. */ } static VkDescriptorPool descriptorPool; static void createDescriptorPool() { const VkDescriptorPoolSize poolSize = { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount = 1 }; const VkDescriptorPoolCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = 1, .pPoolSizes = &poolSize, .maxSets = 1 }; VkResult result; if ((result = vkCreateDescriptorPool(device, &createInfo, NULL, &descriptorPool))) { ERRFQ("Could not create descriptor pool: %d\n", result); } } static void destroyDescriptorPool() { vkDestroyDescriptorPool(device, descriptorPool, NULL); } static VkDescriptorSetLayout descriptorSetLayout; static void createDescriptorSetLayout() { const VkDescriptorSetLayoutBinding layoutBinding = { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT }; const VkDescriptorSetLayoutCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &layoutBinding }; VkResult result; if ((result = vkCreateDescriptorSetLayout(device, &createInfo, NULL, &descriptorSetLayout))) { ERRFQ("Could not create descriptor set layout: %d\n", result); } } static void destroyDescriptorSetLayout() { vkDestroyDescriptorSetLayout(device, descriptorSetLayout, NULL); } static VkDescriptorSet descriptorSet; static void allocateDescriptorSets() { const VkDescriptorSetAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = descriptorPool, .descriptorSetCount = 1, .pSetLayouts = &descriptorSetLayout }; VkResult result; if ((result = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet))) { ERRFQ("Could not allocate descriptor set: %d\n", result); } } static void updateDescriptorSets() { const VkDescriptorBufferInfo bufferInfo = { .buffer = uniformBuffer, .offset = 0, .range = sizeof(uniformData) }; const VkWriteDescriptorSet descriptorWrite = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = descriptorSet, .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount = 1, .pBufferInfo = &bufferInfo }; VkResult result; vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, NULL); } static VkPipelineLayout pipelineLayout; static void createPipelineLayout() { const VkPipelineLayoutCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &descriptorSetLayout, }; VkResult result; if ((result = vkCreatePipelineLayout(device, &createInfo, NULL, &pipelineLayout))) { ERRFQ("Could not create pipeline layout: %d\n", result); } } static void destroyPipelineLayout() { vkDestroyPipelineLayout(device, pipelineLayout, NULL); } static VkShaderModule vShaderModule; static VkShaderModule fShaderModule; static void createShaderModules() { size_t vertLen, fragLen; uint8_t *vertCode = read_file("vert.spv", &vertLen); if (!vertCode) { ERRQ("Could not read vert.spv"); } uint8_t *fragCode = read_file("frag.spv", &fragLen); if (!fragCode) { ERRQ("Could not read frag.spv"); } VkShaderModuleCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = vertLen, .pCode = (void*)vertCode }; VkResult result; if ((result = vkCreateShaderModule(device, &createInfo, NULL, &vShaderModule))) { ERRFQ("Could not create vertex shader module: %d\n", result); } createInfo.codeSize = fragLen; createInfo.pCode = (void*)fragCode; if ((result = vkCreateShaderModule(device, &createInfo, NULL, &fShaderModule))) { ERRFQ("Could not create fragment shader module: %d\n", result); } free(vertCode); free(fragCode); } static void destroyShaderModules() { vkDestroyShaderModule(device, vShaderModule, NULL); vkDestroyShaderModule(device, fShaderModule, NULL); } // TODO: These should be dynamically allocated as arrays maybe static VkSemaphore imageAvailableSemaphore; static VkSemaphore renderFinishedSemaphore; static VkFence *imageFences; static void createSyncObjects() { VkResult result; const VkSemaphoreCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; if ((result = vkCreateSemaphore(device, &createInfo, NULL, &imageAvailableSemaphore))) { ERRFQ("Could not create image available semaphore: %d\n", result); } if ((result = vkCreateSemaphore(device, &createInfo, NULL, &renderFinishedSemaphore))) { ERRFQ("Could not create render finished semaphore: %d\n", result); } const VkFenceCreateInfo fenceInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT }; imageFences = malloc(nImages*sizeof(VkFence*)); for (uint32_t i = 0; i < nImages; ++i) { if ((result = vkCreateFence(device, &fenceInfo, NULL, &imageFences[i]))) { ERRFQ("Could not create render finished fence: %d\n", result); } } } static void destroySyncObjects() { vkDestroySemaphore(device, imageAvailableSemaphore, NULL); vkDestroySemaphore(device, renderFinishedSemaphore, NULL); if (imageFences) { for (uint32_t i = 0; i < nImages; ++i) { vkDestroyFence(device, imageFences[i], NULL); } free(imageFences); imageFences = NULL; } } static VkBuffer meshBuffer; static VkDeviceMemory meshMemory; struct VertexLayout { float position[3]; float normal[3]; float uv[2]; }; static uint32_t vertexCount; static uint32_t indexCount; VkDeviceSize vertexSize; VkDeviceSize indexSize; VkDeviceSize indexOffset; VkIndexType indexType; static void createVertexBuffer() { size_t rawsize; void *rawData = read_file("export.jch", &rawsize); if (!rawData) { ERRFQ("Cannot open model file\n"); } const struct { uint32_t signature; uint32_t numVerts; uint32_t numPolys; } *header = rawData; if (header->signature != 0x0048434a) { ERRFQ("File is not model file\n"); } printf("Verts:\t%d\nPolys:\t%d\n", header->numVerts, header->numPolys); vertexCount = header->numVerts; indexCount = 3*header->numPolys; vertexSize = vertexCount*sizeof(struct VertexLayout); indexSize = vertexCount > 65536 ? 4*indexCount : 2*indexCount; indexType = vertexCount > 65536 ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_UINT16; if ((vertexSize + indexSize + sizeof(*header)) != rawsize) { ERRFQ("Model file is corrupt\n"); } indexOffset = vertexSize; /* Make a simple staging buffer large enough to hold both vertex and index data */ const VkDeviceSize stagingSize = vertexSize + indexSize; VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, .size = vertexSize + indexSize, .sharingMode = VK_SHARING_MODE_EXCLUSIVE // FIXME: Transfer ownership to graphics queue family? }; VkResult result; if ((result = vkCreateBuffer(device, &createInfo, NULL, &meshBuffer))) { ERRFQ("Could not create mesh buffer: %d\n", result); } createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; createInfo.size = stagingSize; if ((result = vkCreateBuffer(device, &createInfo, NULL, &stagingBuffer))) { ERRFQ("Could not create staging buffer: %d\n", result); } const uint32_t hostProps = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; const uint32_t deviceProps = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; VkMemoryRequirements meshMemReq; vkGetBufferMemoryRequirements(device, meshBuffer, &meshMemReq); uint32_t memType = getMemType(&meshMemReq, deviceProps); VkMemoryAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = meshMemReq.size, .memoryTypeIndex = memType }; if ((result = vkAllocateMemory(device, &allocInfo, NULL, &meshMemory))) { ERRFQ("Could not allocate mesh buffer memory: %d\n", result); } VkMemoryRequirements stagingMemReq; vkGetBufferMemoryRequirements(device, stagingBuffer, &stagingMemReq); memType = getMemType(&stagingMemReq, hostProps); allocInfo.allocationSize = stagingMemReq.size; allocInfo.memoryTypeIndex = memType; if ((result = vkAllocateMemory(device, &allocInfo, NULL, &stagingMemory))) { ERRFQ("Could not allocate staging buffer memory: %d\n", result); } if ((result = vkBindBufferMemory(device, meshBuffer, meshMemory, 0))) { ERRFQ("Could not bind mesh buffer memory: %d\n", result); } if ((result = vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0))) { ERRFQ("Could not bind staging buffer memory: %d\n", result); } void *data; vkMapMemory(device, stagingMemory, 0, stagingMemReq.size, 0, &data); memcpy(data, rawData + 12, vertexSize + indexSize); vkUnmapMemory(device, stagingMemory); free(rawData); const VkCommandBufferAllocateInfo copyAllocInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = transferPool, .commandBufferCount = 1 }; VkCommandBuffer copyBuffer; if ((result = vkAllocateCommandBuffers(device, ©AllocInfo, ©Buffer))) { ERRFQ("Could not allocate copy command buffer %d\n", result); } const VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; if ((result = vkBeginCommandBuffer(copyBuffer, &beginInfo))) { ERRFQ("Could not begin recording copy command buffer %d\n", result); } const VkBufferCopy copyRegions[] = { { .srcOffset = 0, .dstOffset = 0, .size = vertexSize }, { .srcOffset = vertexSize, .dstOffset = vertexSize, .size = indexSize } }; vkCmdCopyBuffer(copyBuffer, stagingBuffer, meshBuffer, 2, copyRegions); vkEndCommandBuffer(copyBuffer); const VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = ©Buffer }; vkQueueSubmit(transferQueue, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(transferQueue); /* Data is guaranteed to be fully written here. * If we didn't choose to synchronize here to free the command buffer * right away, then we would need to wait on a Fence or Semaphore * before touching it later on. */ vkFreeCommandBuffers(device, transferPool, 1, ©Buffer); vkFreeMemory(device, stagingMemory, NULL); vkDestroyBuffer(device, stagingBuffer, NULL); } static void destroyVertexBuffer() { vkDestroyBuffer(device, meshBuffer, NULL); vkFreeMemory(device, meshMemory, NULL); } static VkPipeline graphicsPipeline; static void createGraphicsPipeline() { const VkPipelineShaderStageCreateInfo shaderStages[] = {{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vShaderModule, .pName = "main" }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fShaderModule, .pName = "main" }}; const VkVertexInputBindingDescription bindingDescription = { .binding = 0, .stride = sizeof(struct VertexLayout), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX }; const VkVertexInputAttributeDescription attribDescriptions[3] = { { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct VertexLayout, position) }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct VertexLayout, normal) }, { .binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(struct VertexLayout, uv) }}; const VkPipelineVertexInputStateCreateInfo vertexInputState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = 3, .pVertexAttributeDescriptions = attribDescriptions }; const VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE }; const VkViewport viewport = { .x = 0.0f, .y = 0.0f, .width = swapchainExtent.width, .height = swapchainExtent.height, .minDepth = 0.0f, .maxDepth = 1.0f }; const VkRect2D scissor = { .offset = { 0, 0 }, .extent = swapchainExtent }; const VkPipelineViewportStateCreateInfo viewportState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor }; const VkPipelineRasterizationStateCreateInfo rasterizationState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .lineWidth = 1.0f, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = VK_FALSE }; const VkPipelineMultisampleStateCreateInfo multisampleState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = msaaSampleCount, .sampleShadingEnable = VK_FALSE }; VkPipelineDepthStencilStateCreateInfo depthStencilState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL, .front = { .failOp = VK_STENCIL_OP_KEEP, .passOp = VK_STENCIL_OP_KEEP, .compareOp = VK_COMPARE_OP_ALWAYS, .compareMask = 0, .reference = 0, .depthFailOp = VK_STENCIL_OP_KEEP, .writeMask = 0 } }; depthStencilState.back = depthStencilState.front; const VkPipelineColorBlendAttachmentState colorBlendAttachment = { .colorWriteMask = 0xf, .blendEnable = VK_FALSE, }; const VkPipelineColorBlendStateCreateInfo colorBlendState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .logicOp = VK_LOGIC_OP_COPY, .attachmentCount = 1, .pAttachments = &colorBlendAttachment, .blendConstants[0] = 0.0f, .blendConstants[1] = 0.0f, .blendConstants[2] = 0.0f, .blendConstants[3] = 0.0f }; const VkGraphicsPipelineCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shaderStages, .pVertexInputState = &vertexInputState, .pInputAssemblyState = &inputAssemblyState, .pTessellationState = NULL, .pViewportState = &viewportState, .pRasterizationState = &rasterizationState, .pMultisampleState = &multisampleState, .pDepthStencilState = &depthStencilState, .pColorBlendState = &colorBlendState, .pDynamicState = NULL, .layout = pipelineLayout, .renderPass = renderPass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0 }; VkResult result; if ((result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &createInfo, NULL, &graphicsPipeline))) { ERRFQ("Could not create graphics pipeline: %d\n", result); } } static void destroyGraphicsPipeline() { vkDestroyPipeline(device, graphicsPipeline, NULL); graphicsPipeline = VK_NULL_HANDLE; } void setupScene() { createUniformBuffer(); createDescriptorPool(); createDescriptorSetLayout(); allocateDescriptorSets(); updateDescriptorSets(); createPipelineLayout(); createShaderModules(); createSyncObjects(); createVertexBuffer(); createGraphicsPipeline(); } void teardownScene() { destroyGraphicsPipeline(); destroyVertexBuffer(); destroySyncObjects(); destroyShaderModules(); destroyPipelineLayout(); destroyDescriptorSetLayout(); destroyDescriptorPool(); destroyUniformBuffer(); } // This may look like it's drawing, but it's not. // This is pre-recording drawing commands into a buffer // that can be submitted to the GPU over and over again. // Of course, this could instead happen inside of `onDisplay` // if drawing commands vary per-frame. void recordCommandBuffers() { VkClearValue clearValues[3] = {{ .color.float32[0] = 0.0f, .color.float32[1] = 0.0f, .color.float32[2] = 0.0f, .color.float32[3] = 1.0f, }, { .color.float32[0] = 0.0f, .color.float32[1] = 0.0f, .color.float32[2] = 0.0f, .color.float32[3] = 1.0f, }, { .depthStencil.depth = 1.0f, .depthStencil.stencil = 0 }}; if (!msaaEnabled) { clearValues[1] = clearValues[2]; } for (uint32_t i = 0; i < nImages; ++i) { const VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, .pInheritanceInfo = NULL }; vkBeginCommandBuffer(commandBuffers[i], &beginInfo); const VkRenderPassBeginInfo renderPassBeginInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = renderPass, .framebuffer = swapchainFramebuffers[i], .renderArea.offset = { 0, 0 }, .renderArea.extent = swapchainExtent, .clearValueCount = msaaEnabled ? 3 : 2, .pClearValues = clearValues }; vkCmdBeginRenderPass(commandBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); const uint32_t dynamicOffsets[] = { i*uniformAlignment }; vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 1, dynamicOffsets); const VkDeviceSize vertexOffsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &meshBuffer, vertexOffsets); vkCmdBindIndexBuffer(commandBuffers[i], meshBuffer, indexOffset, indexType); /*vkCmdDraw(commandBuffers[i], vertexCount, 1, 0, 0);*/ vkCmdDrawIndexed(commandBuffers[i], indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); VkResult result; if ((result = vkEndCommandBuffer(commandBuffers[i]))) { ERRFQ("Could not record command buffer: %d\n", result); } } } // TODO: Vulkan swapchains can be created by "realloc"-ing an existing swapchain. // However, data connected to the old swapchain must not be deleted until // they are no longer in-flight, so we accumulate garbage. // The easy solution is to simply wait for device idle and then delete then recreate everything, // but user interfaces expect the ability to drag-resize quickly and without graphical glitches or stuttering. // FIXME: Memory leak void recreateSwapchain() { oldRawSwapchain = rawSwapchain; //destroyGraphicsPipeline(); // endVulkan(); //destroySwapchainFramebuffers(); //destroyRenderPass(); //if (msaaEnabled) destroyMultisampleImage(); //destroyDepthImage(); //destroySwapchainImageViews(); //destroySwapchain(); // beginVulkan(); createSwapchain(); createSwapchainImageViews(); allocateCommandBuffers(); createDepthImage(); if (msaaEnabled) createMultisampleImage(); createRenderPass(); createSwapchainFramebuffers(); createGraphicsPipeline(); recordCommandBuffers(); } /** BEGIN APPLICATION SECTION *****************************************************************************************/ float angle = 0.0f; uint32_t syncIndex; float location[4][4]; void *data; // Persistent memory map to GPU uniform buffer uint32_t imageIndex; PuglStatus onResize(PuglView *view) { //vkDeviceWaitIdle(device); //SDL_Vulkan_GetDrawableSize(win, &width, &height); //int width, height; //getFrame(view, &width, &height); /* Apparently Vulkan already knows the new size internally and doesn't need it passed */ recreateSwapchain(); /* Adjust aspect ratio */ load_perspective( uniformData.proj, DEGREES(45), (float)swapchainExtent.width/swapchainExtent.height, 0.1f, 10.0f ); } // Draws one frame to an image acquired from the swapchain (vkAcquireNextImageKHR, vkQueueSubmitKHR) // and then gives it to the presentation engine for displaying (vkQueuePresentKHR) PuglStatus onDisplay(PuglView *view) { float rotation[4][4]; //angle += PI/60.0f; //if (angle > TWOPI) { // angle -= TWOPI; //} load_rotation_y(rotation, angle); matmul4(location, rotation, uniformData.model); VkResult result; retry: switch(vkAcquireNextImageKHR( device, rawSwapchain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex )) { case VK_SUCCESS: break; case VK_SUBOPTIMAL_KHR: case VK_ERROR_OUT_OF_DATE_KHR: printf("REACTIVE resize\n"); onResize(view); goto retry; default: fprintf(stderr, "vkAcquireNextImageKHR: %d\n", result); exit(1); } vkWaitForFences(device, 1, &imageFences[imageIndex], VK_TRUE, UINT64_MAX); memcpy(data + imageIndex*uniformAlignment, &uniformData, sizeof(uniformData)); //VkMappedMemoryRange uniformRange = { // .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, // .memory = uniformMemory, // .offset = imageIndex*uniformAlignment, // .size = sizeof(uniformData) //}; //vkFlushMappedMemoryRanges(device, 1, &uniformRange); vmaFlushAllocation(vmaAllocator, uniformVma, imageIndex*uniformAlignment, sizeof(uniformData)); vkResetFences(device, 1, &imageFences[imageIndex]); VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; const VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = &imageAvailableSemaphore, .pWaitDstStageMask = &waitStage, .commandBufferCount = 1, .pCommandBuffers = &commandBuffers[imageIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &renderFinishedSemaphore }; if ((result = vkQueueSubmit(graphicsQueue, 1, &submitInfo, imageFences[imageIndex]))) { ERRFQ("Could not submit draw command buffer: %d\n", result); } const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &renderFinishedSemaphore, .swapchainCount = 1, .pSwapchains = &rawSwapchain, .pImageIndices = &imageIndex, .pResults = NULL }; vkQueuePresentKHR(graphicsQueue, &presentInfo); //printf("I:\t%d\tS:\t%d\n", imageIndex, syncIndex); syncIndex = (syncIndex+1) >= nImages ? 0 : (syncIndex+1); //vkQueueWaitIdle(graphicsQueue); return PUGL_SUCCESS; } void cleanup() { vkDeviceWaitIdle(device); //vkUnmapMemory(device, uniformMemory); vmaUnmapMemory(vmaAllocator, uniformVma); teardownScene(); endVulkan(); deinitVulkan(); puglFreeView(view); puglFreeWorld(world); /* * Vulkan library MUST be unloaded AFTER call to XCloseDisplay() or it will segfault */ vkDestroyInstance(instance, NULL); } int shouldResize(PuglView *view, const PuglEvent *e) { if (swapchainExtent.width != e->configure.width || swapchainExtent.height != e->configure.height) { return 1; } return 0; } void transformCoords(const PuglView *v, const PuglEvent *e, float *x, float *y) { const PuglRect rect = puglGetFrame(v); const double nx = e->motion.x/rect.width; const double ny = e->motion.y/rect.height; *x = 2.0*(nx-0.5); *y = 2.0*(ny-0.5); } int running = 1; PuglStatus onEvent(PuglView *view, const PuglEvent *e) { float mx, my, x, y; const float z = -4.0f; switch (e->type) { case PUGL_CONFIGURE: if (shouldResize(view, e)) onResize(view); break; case PUGL_EXPOSE: onDisplay(view); break; case PUGL_CLOSE: running = 0; break; case PUGL_KEY_PRESS: break; case PUGL_MOTION_NOTIFY: transformCoords(view, e, &mx, &my); x = -mx*z/uniformData.proj[0][0]; y = -my*z*(uniformData.proj[0][0]/uniformData.proj[1][1]); load_location(location, x, y, z); puglPostRedisplay(view); break; default: break; } return PUGL_SUCCESS; } int main(int argc, char **argv) { //if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { // ERRQ(SDL_GetError()); //} world = puglNewWorld(); if (!world) { ERRQ("Could not create world\n"); } view = puglNewView(world); if (!view) { ERRQ("Could not create view\n"); } puglSetClassName(world, "Pugl Vulkan"); const PuglRect frame = { 0, 0, 1280, 720 }; puglSetFrame(view, frame); puglSetBackend(view, NULL); puglSetViewHint(view, PUGL_RESIZABLE, 1); PuglStatus status; if ((status = puglCreateWindow(view, "Blank Window"))) { fprintf(stderr, "Could not create window: %d\n", status); exit(1); } puglShowWindow(view); //puglPollEvents(world, -1); //SDL_Vulkan_GetDrawableSize(win, &width, &height); int width, height; getFrame(view, &width, &height); initVulkan(); beginVulkan(width, height); setupScene(); populateUniformBuffer(); recordCommandBuffers(); load_location(location, 0.0f, 0.5f, -4.0f); //vkMapMemory(device, uniformMemory, 0, nImages*uniformAlignment, 0, &data); vmaMapMemory(vmaAllocator, uniformVma, &data); puglSetEventFunc(view, onEvent); while (running) { puglPollEvents(world, -1); puglDispatchEvents(world); } cleanup(); //SDL_Quit(); return 0; }