From 027a512c762e0ac5e46908f533b94127010de565 Mon Sep 17 00:00:00 2001 From: Jordan Halase Date: Fri, 25 Oct 2019 16:53:00 -0500 Subject: Initial commit --- main.c | 1906 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1906 insertions(+) create mode 100644 main.c (limited to 'main.c') diff --git a/main.c b/main.c new file mode 100644 index 0000000..9f5f10a --- /dev/null +++ b/main.c @@ -0,0 +1,1906 @@ +#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; + +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); + } +} + +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 + 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(); +} + +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); + } + } +} + +// 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(); +} + +float angle = 0.0f; +uint32_t syncIndex; +float location[4][4]; +void *data; +uint32_t imageIndex; + +PuglStatus onResize(PuglView *view) +{ + //vkDeviceWaitIdle(device); + //SDL_Vulkan_GetDrawableSize(win, &width, &height); + //int width, height; + //getFrame(view, &width, &height); + recreateSwapchain(); + /* Adjust aspect ratio */ + load_perspective( + uniformData.proj, + DEGREES(45), + (float)swapchainExtent.width/swapchainExtent.height, 0.1f, 10.0f + ); +} + +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); + } +quit: + cleanup(); + //SDL_Quit(); + return 0; +} -- cgit v1.2.1