/* Copyright (c) 2019, Jordan Halase Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #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 // XXX: Eventually Pugl should return the platform-specific string(s) of the name(s) // of the required surface (and) type(s) for that platform, e.g., VK_KHR_XLIB_SURFACE_EXTENSION_NAME for X11 // This could be negotiated at a later time. 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; } #include "pugl/detail/x11.h" void initVulkan() { createInstance(); #ifdef VULKAN_VALIDATION createDebugReporter(); #endif #if 0 if (!SDL_Vulkan_CreateSurface(win, instance, &surface)) { ERRQ(SDL_GetError()); } #else // HACK: Vulkan needs access to this // Eventually, Pugl should wrap the call to XXX, where XXX is one of: // * vkCreateXlibSurfaceKHR() // * vkCreateWin32SurfaceKHR() // * vkCreateMacOSSurfaceMVK() // * 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; } #include "pugl/pugl_stub_backend.h" 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, puglStubBackend()); 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; }