#define STB_IMAGE_IMPLEMENTATION #define TINYOBJ_LOADER_C_IMPLEMENTATION #include #include #include #include #include #include #include #include "linmath.h" #include "stb_image.h" #define MAX_FRAMES_INFLIGHT 2 typedef mat4x4 mat4; struct ubo { mat4 model; mat4 view; mat4 proj; }; struct vertex { vec3 pos; vec3 color; vec2 texture_coords; }; struct { size_t len, cap; struct vertex data[]; } *vertices; struct { size_t len, cap; uint32_t data[]; } *indices; VkVertexInputBindingDescription get_vertex_description() { return (VkVertexInputBindingDescription) { .binding = 0, .stride = sizeof(struct vertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX }; } void get_attribute_description(VkVertexInputAttributeDescription out[3]) { out[0] = (VkVertexInputAttributeDescription) { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct vertex, pos) }; out[1] = (VkVertexInputAttributeDescription) { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct vertex, color) }; out[2] = (VkVertexInputAttributeDescription) { .binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(struct vertex, texture_coords) }; } static SDL_Window *window = NULL; static VkDebugUtilsMessengerEXT debug_messenger; static VkInstance instance = VK_NULL_HANDLE; static VkPhysicalDevice phy_gpu = VK_NULL_HANDLE; static VkDevice gpu = VK_NULL_HANDLE; static VkSurfaceKHR surface = VK_NULL_HANDLE; static VkQueue gfx_queue; static VkQueue present_queue; static VkSwapchainKHR swapchain; static VkFormat swapchain_format; static VkExtent2D swapchain_extent; static VkRenderPass render_pass; static VkDescriptorSetLayout desc_layout; static VkPipelineLayout pipeline_layout; static VkCommandPool command_pool; static VkCommandBuffer command_buffers[MAX_FRAMES_INFLIGHT]; static VkPipeline graphics_pipeline; static VkSemaphore image_avail[MAX_FRAMES_INFLIGHT]; static VkSemaphore render_finished[MAX_FRAMES_INFLIGHT]; static VkFence in_flight_fence[MAX_FRAMES_INFLIGHT]; static VkBuffer uniform_buffers[MAX_FRAMES_INFLIGHT]; static VkDeviceMemory uniform_memory[MAX_FRAMES_INFLIGHT]; static void *mapped_buffers[MAX_FRAMES_INFLIGHT]; static VkDescriptorPool desc_pool; static VkDescriptorSet desc_sets[MAX_FRAMES_INFLIGHT]; static uint32_t current_frame = 0; static VkBuffer vertex_buffer; static VkDeviceMemory vertex_buffer_memory; static VkBuffer index_buffer; static VkDeviceMemory index_buffer_memory; static VkImage texture_image; static VkDeviceMemory texture_image_memory; static VkImageView texture_view; static VkSampler texture_sampler; static VkImage depth_image; static VkDeviceMemory depth_memory; static VkImageView depth_image_view; static struct { size_t len; VkImage data[]; } *swapchain_images; static struct { size_t len; VkImageView data[]; } *swapchain_image_views; static struct { size_t len; VkFramebuffer data[]; } *framebuffers; struct code { size_t len; uint8_t data[]; }; struct swapchain_details { VkSurfaceCapabilitiesKHR caps; struct surface_formats { size_t len; VkSurfaceFormatKHR data[]; } *formats; struct present_modes { size_t len; VkPresentModeKHR data[]; } *present_modes; }; void free_swapchain_details(struct swapchain_details details) { free(details.formats); free(details.present_modes); } struct swapchain_details query_swapchain(VkPhysicalDevice dev) { struct swapchain_details details = {0}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surface, &details.caps); uint32_t format_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &format_count, NULL); if (format_count != 0) { details.formats = malloc(sizeof(*details.formats) + sizeof(*details.formats->data) * format_count); vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &format_count, details.formats->data); details.formats->len = format_count; } uint32_t present_mode_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &present_mode_count, NULL); if (present_mode_count != 0) { details.present_modes = malloc(sizeof(*details.present_modes) + sizeof(*details.present_modes->data) * format_count); vkGetPhysicalDeviceSurfacePresentModesKHR(dev, surface, &present_mode_count, details.present_modes->data); details.present_modes->len = present_mode_count; } return details; } VkSurfaceFormatKHR pick_swapchain_format(const struct surface_formats *formats) { for (size_t i = 0; i < formats->len; i++) if (formats->data[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats->data[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) return formats->data[i]; return formats->data[0]; } VkPresentModeKHR pick_present_mode(const struct present_modes *modes) { for (size_t i = 0; i < modes->len; i++) if (modes->data[i] == VK_PRESENT_MODE_FIFO_RELAXED_KHR) return modes->data[i]; return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D pick_swap_extent(const VkSurfaceCapabilitiesKHR *caps) { if (caps->currentExtent.width != UINT32_MAX) return caps->currentExtent; int32_t w, h; SDL_GetWindowSize(window, &w, &h); VkExtent2D actual_extent = { .width = w > caps->minImageExtent.width ? w < caps->maxImageExtent.width ? w : caps->maxImageExtent.width : caps->minImageExtent.width, .height = h > caps->minImageExtent.height ? h < caps->maxImageExtent.height ? h : caps->maxImageExtent.height : caps->minImageExtent.height, }; return actual_extent; } struct queue_indices { enum { GRAPHICS = (1 << 0), PRESENT = (1 << 1) } found_queues; uint32_t graphics; uint32_t present; }; bool found_queue_indx(struct queue_indices inds) { return inds.found_queues == (GRAPHICS | PRESENT); } void cleanup_swapchain() { vkDestroyImageView(gpu, depth_image_view, NULL); vkDestroyImage(gpu, depth_image, NULL); vkFreeMemory(gpu, depth_memory, NULL); for (size_t i = 0; i < framebuffers->len; i++) { vkDestroyFramebuffer(gpu, framebuffers->data[i], NULL); } for (size_t i = 0; i < swapchain_image_views->len; i++) { vkDestroyImageView(gpu, swapchain_image_views->data[i], NULL); } vkDestroySwapchainKHR(gpu, swapchain, NULL); } void cleanup() { vkDeviceWaitIdle(gpu); for (size_t i = 0; i < MAX_FRAMES_INFLIGHT; i++) { vkDestroySemaphore(gpu, image_avail[i], NULL); vkDestroySemaphore(gpu, render_finished[i], NULL); vkDestroyFence(gpu, in_flight_fence[i], NULL); vkDestroyBuffer(gpu, uniform_buffers[i], NULL); vkFreeMemory(gpu, uniform_memory[i], NULL); } cleanup_swapchain(); vkDestroyBuffer(gpu, index_buffer, NULL); vkFreeMemory(gpu, index_buffer_memory, NULL); vkDestroySampler(gpu, texture_sampler, NULL); vkDestroyImageView(gpu, texture_view, NULL); vkDestroyImage(gpu, texture_image, NULL); vkFreeMemory(gpu, texture_image_memory, NULL); vkDestroyDescriptorPool(gpu, desc_pool, NULL); vkDestroyDescriptorSetLayout(gpu, desc_layout, NULL); vkDestroyBuffer(gpu, vertex_buffer, NULL); vkFreeMemory(gpu, vertex_buffer_memory, NULL); vkDestroyCommandPool(gpu, command_pool, NULL); vkDestroyPipeline(gpu, graphics_pipeline, NULL); vkDestroyPipelineLayout(gpu, pipeline_layout, NULL); vkDestroyRenderPass(gpu, render_pass, NULL); vkDestroySurfaceKHR(instance, surface, NULL); ((PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"))(instance, debug_messenger, NULL); vkDestroyDevice(gpu, NULL); vkDestroyInstance(instance, NULL); SDL_DestroyWindow(window); } static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *data) { fprintf(stderr, "validation layer: %s\n", callback_data->pMessage); return VK_FALSE; } void create_instance() { VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "vk", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "void", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_0 }; uint32_t sdl_exts = 0; SDL_Vulkan_GetInstanceExtensions(window, &sdl_exts, NULL); const char *sdl_exts_names[sdl_exts + 1]; SDL_Vulkan_GetInstanceExtensions(window, &sdl_exts, sdl_exts_names); sdl_exts_names[sdl_exts++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; for (size_t i = 0; i < sdl_exts; i++) { puts(sdl_exts_names[i]); }; const char *validation_layers[] = { "VK_LAYER_KHRONOS_validation" }; VkInstanceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledLayerCount = 1, .ppEnabledLayerNames = validation_layers, .enabledExtensionCount = sdl_exts, .ppEnabledExtensionNames = sdl_exts_names, }; VkResult res = vkCreateInstance(&create_info, NULL, &instance); if (res != VK_SUCCESS) { fputs("failed to create vk instance", stderr); exit(-1); } VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, .pfnUserCallback = debug_callback }; ((PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"))(instance, &debug_create_info, NULL, &debug_messenger); } struct queue_indices find_queue_families(VkPhysicalDevice dev) { struct queue_indices queues_ind = {0}; uint32_t queue_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_count, NULL); VkQueueFamilyProperties *queues = calloc(queue_count, sizeof(*queues)); vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_count, queues); for (size_t i = 0; i < queue_count; i++) { if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { queues_ind.found_queues |= GRAPHICS; queues_ind.graphics = i; } VkBool32 present_support = false; vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surface, &present_support); if (present_support) { queues_ind.found_queues |= PRESENT; queues_ind.present = i; } } free(queues); return queues_ind; } bool is_device_ok(VkPhysicalDevice dev) { uint32_t ext_count; vkEnumerateDeviceExtensionProperties(dev, NULL, &ext_count, NULL); VkExtensionProperties *props = calloc(ext_count, sizeof(*props)); vkEnumerateDeviceExtensionProperties(dev, NULL, &ext_count, props); bool swapchain = false; for (size_t i = 0; i < ext_count; i++) { if (strcmp(props[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { swapchain = true; break; } } free(props); VkPhysicalDeviceFeatures supported_features; vkGetPhysicalDeviceFeatures(dev, &supported_features); bool adequate_swapchain = false; if (swapchain) { struct swapchain_details details = query_swapchain(dev); adequate_swapchain = details.present_modes != NULL && details.formats != NULL; free_swapchain_details(details); } return found_queue_indx(find_queue_families(dev)) && swapchain && adequate_swapchain && supported_features.samplerAnisotropy; } void pick_physical_dev() { uint32_t dev_count = 0; vkEnumeratePhysicalDevices(instance, &dev_count, NULL); if (dev_count == 0) { fputs("no vulkan enabled gpus", stderr); exit(-1); } VkPhysicalDevice *devs = calloc(dev_count, sizeof(*devs)); vkEnumeratePhysicalDevices(instance, &dev_count, devs); for (size_t i = 0; i < dev_count; i++) if (is_device_ok(devs[i])) { phy_gpu = devs[i]; break; } free(devs); if (phy_gpu == VK_NULL_HANDLE) { fputs("couldn't find appropriate gpu", stderr); exit(-1); } } void create_logical_dev() { struct queue_indices inds = find_queue_families(phy_gpu); float queue_prio = 1.0f; VkDeviceQueueCreateInfo queue_create_info[] = { { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = inds.graphics, .queueCount = 1, .pQueuePriorities = &queue_prio }, { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = inds.present, .queueCount = 1, .pQueuePriorities = &queue_prio }, }; VkPhysicalDeviceFeatures dev_features = { .samplerAnisotropy = VK_TRUE }; const char * const ext = VK_KHR_SWAPCHAIN_EXTENSION_NAME; VkDeviceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = queue_create_info, .queueCreateInfoCount = 2, .pEnabledFeatures = &dev_features, .enabledExtensionCount = 1, .ppEnabledExtensionNames = &ext, }; VkResult res = vkCreateDevice(phy_gpu, &create_info, NULL, &gpu); if (res != VK_SUCCESS) { fputs("failed to create logical device", stderr); exit(-1); } vkGetDeviceQueue(gpu, inds.graphics, 0, &gfx_queue); vkGetDeviceQueue(gpu, inds.present, 0, &present_queue); } void create_swapchain() { struct swapchain_details details = query_swapchain(phy_gpu); VkSurfaceFormatKHR surface_format = pick_swapchain_format(details.formats); VkPresentModeKHR present_mode = pick_present_mode(details.present_modes); VkExtent2D extent = pick_swap_extent(&details.caps); swapchain_format = surface_format.format; swapchain_extent = extent; uint32_t image_count = details.caps.minImageCount + 1; if (details.caps.maxImageCount > 0 && image_count > details.caps.maxImageCount) image_count = details.caps.maxImageCount; VkSwapchainCreateInfoKHR create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = image_count, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, .imageExtent = extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .preTransform = details.caps.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = present_mode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; struct queue_indices indx = find_queue_families(phy_gpu); uint32_t fam_indx[] = { indx.graphics, indx.present }; if (indx.graphics != indx.present) { create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; create_info.queueFamilyIndexCount = 2; create_info.pQueueFamilyIndices = fam_indx; } else { create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } VkResult res = vkCreateSwapchainKHR(gpu, &create_info, NULL, &swapchain); if (res != VK_SUCCESS) { fputs("failed to create swapchain", stderr); exit(-1); } free_swapchain_details(details); uint32_t img_count = 0; vkGetSwapchainImagesKHR(gpu, swapchain, &img_count, NULL); swapchain_images = malloc(sizeof(*swapchain_images) + sizeof(*swapchain_images->data) * img_count); swapchain_images->len = img_count; vkGetSwapchainImagesKHR(gpu, swapchain, &img_count, swapchain_images->data); } VkImageView create_image_view(VkImage image, VkFormat format, VkImageAspectFlags aspect_flags) { VkImageViewCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, .subresourceRange = { .aspectMask = aspect_flags, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 } }; VkImageView view; VkResult res = vkCreateImageView(gpu, &create_info, NULL, &view); if (res != VK_SUCCESS) { fputs("failed to create image view", stderr); exit(-1); } return view; } void create_image_views() { swapchain_image_views = malloc(sizeof(*swapchain_image_views) + sizeof(*swapchain_image_views->data) * swapchain_images->len); swapchain_image_views->len = swapchain_images->len; for (size_t i = 0; i < swapchain_images->len; i++) { swapchain_image_views->data[i] = create_image_view(swapchain_images->data[i], swapchain_format, VK_IMAGE_ASPECT_COLOR_BIT); } } struct code *readfile(const char *filename) { FILE *fp = fopen(filename, "rb"); if (!fp) { fprintf(stderr, "failed to open file %s", filename); exit(-1); } fseek(fp, 0, SEEK_END); size_t len = ftell(fp); rewind(fp); struct code *bytes = malloc(sizeof(*bytes) + sizeof(*bytes->data) * len); bytes->len = len; size_t read = fread(bytes->data, sizeof(*bytes->data), len, fp); fclose(fp); return bytes; } VkShaderModule create_shader_module(const struct code *code) { VkShaderModuleCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = code->len, .pCode = (uint32_t *)code->data }; VkShaderModule module; VkResult res = vkCreateShaderModule(gpu, &create_info, NULL, &module); if (res != VK_SUCCESS) { fputs("failed to create shader module.", stderr); exit(-1); } return module; } void create_graphics_pipeline() { struct code *vert_shader_code = readfile("vert.spv"); struct code *frag_shader_code = readfile("frag.spv"); VkShaderModule vert_shader = create_shader_module(vert_shader_code); VkShaderModule frag_shader = create_shader_module(frag_shader_code); VkPipelineShaderStageCreateInfo vert_shader_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert_shader, .pName = "main" }; VkPipelineShaderStageCreateInfo frag_shader_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag_shader, .pName = "main" }; VkPipelineShaderStageCreateInfo shader_stages[] = { vert_shader_info, frag_shader_info }; VkVertexInputBindingDescription desc = get_vertex_description(); VkVertexInputAttributeDescription attrs[3]; get_attribute_description(attrs); VkPipelineVertexInputStateCreateInfo vertex_input_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &desc, .vertexAttributeDescriptionCount = sizeof(attrs) / sizeof(*attrs), .pVertexAttributeDescriptions = attrs }; VkPipelineInputAssemblyStateCreateInfo input_assembly = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE }; VkViewport viewport = { .x = 0.0f, .y = 0.0f, .width = swapchain_extent.width, .height = swapchain_extent.height, .minDepth = 0.0f, .maxDepth = 1.0f }; VkRect2D scissor = { .offset = { 0, 0 }, .extent = swapchain_extent }; VkDynamicState dyn_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamic_state_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = 2, .pDynamicStates = dyn_states }; VkPipelineViewportStateCreateInfo viewport_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .scissorCount = 1 }; VkPipelineRasterizationStateCreateInfo rasterizer = { .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, }; VkPipelineMultisampleStateCreateInfo multisampling = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .minSampleShading = 1.0f, }; VkPipelineColorBlendAttachmentState color_state = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_FALSE }; VkPipelineColorBlendStateCreateInfo color_blending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .attachmentCount = 1, .pAttachments = &color_state }; VkPipelineDepthStencilStateCreateInfo depth_stencil = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, .depthBoundsTestEnable = VK_FALSE, .minDepthBounds = 0.0f, .maxDepthBounds = 1.0, .stencilTestEnable = VK_FALSE, }; VkPipelineLayoutCreateInfo pipeline_layout_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &desc_layout }; VkResult res = vkCreatePipelineLayout(gpu, &pipeline_layout_create_info, NULL, &pipeline_layout); if (res != VK_SUCCESS) { fputs("failed to create pipeline layout", stderr); exit(-1); } VkGraphicsPipelineCreateInfo pipeline_info = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shader_stages, .pVertexInputState = &vertex_input_info, .pInputAssemblyState = &input_assembly, .pViewportState = &viewport_state, .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, .pColorBlendState = &color_blending, .pDynamicState = &dynamic_state_info, .pDepthStencilState = &depth_stencil, .layout = pipeline_layout, .renderPass = render_pass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1 }; res = vkCreateGraphicsPipelines(gpu, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &graphics_pipeline); if (res != VK_SUCCESS) { fputs("failed to create graphics pipeline", stderr); exit(-1); } vkDestroyShaderModule(gpu, frag_shader, NULL); vkDestroyShaderModule(gpu, vert_shader, NULL); } uint32_t find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags props) { VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(phy_gpu, &mem_props); for (size_t i = 0; i < mem_props.memoryTypeCount; i++) if (type_filter & (1 << i) && (mem_props.memoryTypes[i].propertyFlags & props) == props) return i; fputs("failed to find memory type", stderr); exit(-1); } void create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props, VkBuffer *buffer, VkDeviceMemory *buffer_memory) { VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE }; VkResult res = vkCreateBuffer(gpu, &buffer_info, NULL, buffer); if (res != VK_SUCCESS) { fputs("failed to create vertex buffer", stderr); exit(-1); } VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(gpu, *buffer, &mem_reqs); VkMemoryAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = mem_reqs.size, .memoryTypeIndex = find_memory_type(mem_reqs.memoryTypeBits, props) }; res = vkAllocateMemory(gpu, &alloc_info, NULL, buffer_memory); if (res != VK_SUCCESS) { fputs("failed to allocate memory", stderr); exit(-1); } vkBindBufferMemory(gpu, *buffer, *buffer_memory, 0); } VkCommandBuffer being_single_command() { VkCommandBufferAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = command_pool, .commandBufferCount = 1 }; VkCommandBuffer cmd_buf; vkAllocateCommandBuffers(gpu, &alloc_info, &cmd_buf); VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; vkBeginCommandBuffer(cmd_buf, &begin_info); return cmd_buf; } void end_single_command(VkCommandBuffer command_buffer) { vkEndCommandBuffer(command_buffer); VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &command_buffer }; vkQueueSubmit(gfx_queue, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(gfx_queue); vkFreeCommandBuffers(gpu, command_pool, 1, &command_buffer); } void copy_buffer_to_image(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer command_buffer = being_single_command(); VkBufferImageCopy region = { .bufferRowLength = 0, .bufferOffset = 0, .bufferImageHeight = 0, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 }, .imageOffset = { 0, 0, 0 }, .imageExtent = { width, height, 1 } }; vkCmdCopyBufferToImage(command_buffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); end_single_command(command_buffer); } void copy_buffer(VkBuffer src, VkBuffer dst, VkDeviceSize size) { VkCommandBuffer cmd_buf = being_single_command(); vkCmdCopyBuffer(cmd_buf, src, dst, 1, &(VkBufferCopy) { .size = size }); end_single_command(cmd_buf); } void create_vertex_buffer() { VkBuffer tmp_buffer; VkDeviceMemory tmp_mem; VkDeviceSize size = vertices->len * sizeof(*vertices->data); create_buffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &tmp_buffer, &tmp_mem); void *data; vkMapMemory(gpu, tmp_mem, 0, size, 0, &data); memcpy(data, vertices->data, size); vkUnmapMemory(gpu, tmp_mem); create_buffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertex_buffer, &vertex_buffer_memory); copy_buffer(tmp_buffer, vertex_buffer, size); vkDestroyBuffer(gpu, tmp_buffer, NULL); vkFreeMemory(gpu, tmp_mem, NULL); } void create_render_pass() { VkAttachmentDescription color_attach = { .format = swapchain_format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .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 }; VkAttachmentReference color_attach_ref = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkAttachmentDescription depth_attach = { .format = VK_FORMAT_D32_SFLOAT, .samples = VK_SAMPLE_COUNT_1_BIT, .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 }; VkAttachmentReference depth_attach_ref = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &color_attach_ref, .pDepthStencilAttachment = &depth_attach_ref }; VkSubpassDependency dep = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT }; VkAttachmentDescription attachs[] = { color_attach, depth_attach }; VkRenderPassCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = sizeof(attachs) / sizeof(*attachs), .pAttachments = attachs, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dep }; VkResult res = vkCreateRenderPass(gpu, &create_info, NULL, &render_pass); if (res != VK_SUCCESS) { fputs("failed to create render pass", stderr); exit(-1); } } void create_framebuffers() { framebuffers = malloc(sizeof(*framebuffers) + sizeof(*framebuffers->data) * swapchain_image_views->len); framebuffers->len = swapchain_image_views->len; for (size_t i = 0; i < swapchain_image_views->len; i++) { VkImageView attachs[] = { swapchain_image_views->data[i], depth_image_view }; VkFramebufferCreateInfo framebuffer_info = { .sType =VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = render_pass, .attachmentCount = sizeof(attachs) / sizeof(*attachs), .pAttachments = attachs, .width = swapchain_extent.width, .height = swapchain_extent.height, .layers = 1 }; VkResult res = vkCreateFramebuffer(gpu, &framebuffer_info, NULL, &framebuffers->data[i]); if (res != VK_SUCCESS) { fputs("failed to create framebuffer", stderr); exit(-1); } } } void create_command_pool() { struct queue_indices indx = find_queue_families(phy_gpu); VkCommandPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = indx.graphics }; VkResult res = vkCreateCommandPool(gpu, &pool_info, NULL, &command_pool); if (res != VK_SUCCESS) { fputs("failed to create command pool", stderr); exit(-1); } } void create_command_buffers() { VkCommandBufferAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = MAX_FRAMES_INFLIGHT }; VkResult res = vkAllocateCommandBuffers(gpu, &alloc_info, command_buffers); if (res != VK_SUCCESS) { fputs("failed to allocate command buffer", stderr); exit(-1); } } void record_command_buffer(VkCommandBuffer buffer, uint32_t image_index) { VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; VkResult res = vkBeginCommandBuffer(buffer, &begin_info); if (res != VK_SUCCESS) { fputs("failed to begin command buffer", stderr); exit(-1); } VkClearValue clear_color[] = { { .color = {0.0f, 0.0f, 0.0f, 1.0f}}, { .depthStencil = { 1.0f, 0 }} }; VkRenderPassBeginInfo render_pass_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = render_pass, .framebuffer = framebuffers->data[image_index], .renderArea = { .extent = swapchain_extent, .offset = {0, 0} }, .clearValueCount = sizeof(clear_color) / sizeof(*clear_color), .pClearValues = clear_color }; vkCmdBeginRenderPass(buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); VkViewport viewport = { .x = 0.0f, .y = 0.0f, .width = swapchain_extent.width, .height = swapchain_extent.height, .minDepth = 0.0f, .maxDepth = 1.0f, }; vkCmdSetViewport(buffer, 0, 1, &viewport); VkRect2D scissor = { .offset = {0, 0}, .extent = swapchain_extent }; vkCmdSetScissor(buffer, 0, 1, &scissor); VkBuffer vertex_buffers[] = { vertex_buffer }; VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(buffer, 0, 1, vertex_buffers, offsets); vkCmdBindIndexBuffer(buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdBindDescriptorSets(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &desc_sets[current_frame], 0, NULL); vkCmdDrawIndexed(buffer, indices->len, 1, 0, 0, 0); vkCmdEndRenderPass(buffer); res = vkEndCommandBuffer(buffer); if (res != VK_SUCCESS) { fputs("failed to record command buffer", stderr); exit(-1); } } void create_sync_objects() { VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; VkFenceCreateInfo fence_info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT }; for (size_t i = 0; i < MAX_FRAMES_INFLIGHT; i++) if (vkCreateSemaphore(gpu, &semaphore_info, NULL, &image_avail[i]) != VK_SUCCESS || vkCreateSemaphore(gpu, &semaphore_info, NULL, &render_finished[i]) != VK_SUCCESS || vkCreateFence(gpu, &fence_info, NULL, &in_flight_fence[i]) != VK_SUCCESS) { fputs("failed to create semaphores and fence", stderr); exit(-1); } } void create_index_buffer() { VkBuffer tmp_buffer; VkDeviceMemory tmp_mem; VkDeviceSize size = indices->len * sizeof(*indices->data); create_buffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &tmp_buffer, &tmp_mem); void *data; vkMapMemory(gpu, tmp_mem, 0, size, 0, &data); memcpy(data, indices->data, size); vkUnmapMemory(gpu, tmp_mem); create_buffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &index_buffer, &index_buffer_memory); copy_buffer(tmp_buffer, index_buffer, size); vkDestroyBuffer(gpu, tmp_buffer, NULL); vkFreeMemory(gpu, tmp_mem, NULL); } void create_descriptor_layout() { VkDescriptorSetLayoutBinding layout_bind[] = { { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT }, { .binding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImmutableSamplers = NULL, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT } }; VkDescriptorSetLayoutCreateInfo desc_create_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = sizeof(layout_bind) / sizeof(*layout_bind), .pBindings = layout_bind }; VkResult res = vkCreateDescriptorSetLayout(gpu, &desc_create_info, NULL, &desc_layout); if (res != VK_SUCCESS) { fputs("failed to create descriptor set layout", stderr); exit(-1); } } void create_uniform_buffers() { for (size_t i = 0; i < MAX_FRAMES_INFLIGHT; i++) { create_buffer(sizeof(struct ubo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniform_buffers[i], &uniform_memory[i]); vkMapMemory(gpu, uniform_memory[i], 0, sizeof(struct ubo), 0, &mapped_buffers[i]); } } void create_descriptor_pool() { VkDescriptorPoolSize pool_size[] = { { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = MAX_FRAMES_INFLIGHT }, { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = MAX_FRAMES_INFLIGHT } }; VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = sizeof(pool_size) / sizeof(*pool_size), .pPoolSizes = pool_size, .maxSets = MAX_FRAMES_INFLIGHT }; VkResult res = vkCreateDescriptorPool(gpu, &pool_info, NULL, &desc_pool); if (res != VK_SUCCESS) { fputs("failed to create descriptor pool", stderr); exit(-1); } } void create_descriptor_sets() { VkDescriptorSetLayout layouts[MAX_FRAMES_INFLIGHT] = { desc_layout, desc_layout }; VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = desc_pool, .descriptorSetCount = MAX_FRAMES_INFLIGHT, .pSetLayouts = layouts }; VkResult res = vkAllocateDescriptorSets(gpu, &alloc_info, desc_sets); if (res != VK_SUCCESS) { fputs("failed to allocate descriptor sets", stderr); exit(-1); } for (size_t i = 0; i < MAX_FRAMES_INFLIGHT; i++) { VkDescriptorBufferInfo buffer_info = { .buffer = uniform_buffers[i], .offset = 0, .range = sizeof(struct ubo) }; VkDescriptorImageInfo image_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = texture_view, .sampler = texture_sampler }; VkWriteDescriptorSet desc_write[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = desc_sets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &buffer_info, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = desc_sets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &image_info, } }; vkUpdateDescriptorSets(gpu, sizeof(desc_write) / sizeof(*desc_write), desc_write, 0, NULL); } } void create_image(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags props, VkImage *img, VkDeviceMemory *memory) { VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent = { .width = width, .height = height, .depth = 1 }, .mipLevels = 1, .arrayLayers = 1, .format = format, .tiling = tiling, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .samples = VK_SAMPLE_COUNT_1_BIT }; VkResult res = vkCreateImage(gpu, &image_info, NULL, img); if (res != VK_SUCCESS) { fputs("failed to create image", stderr); exit(-1); } VkMemoryRequirements mem_reqs; vkGetImageMemoryRequirements(gpu, *img, &mem_reqs); VkMemoryAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = mem_reqs.size, .memoryTypeIndex = find_memory_type(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) }; res = vkAllocateMemory(gpu, &alloc_info, NULL, memory); if (res != VK_SUCCESS) { fputs("failed to allocate memory", stderr); exit(-1); } vkBindImageMemory(gpu, *img, *memory, 0); } void transition_image_layout(VkImage image, VkFormat format, VkImageLayout old_layout, VkImageLayout new_layout) { VkCommandBuffer command_buffer = being_single_command(); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = old_layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 }, .srcAccessMask = 0, .dstAccessMask = 0 }; VkPipelineStageFlags src_flags, dst_flags; if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; src_flags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dst_flags = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; src_flags = VK_PIPELINE_STAGE_TRANSFER_BIT; dst_flags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { fputs("unsupported layout transition", stderr); exit(-1); } vkCmdPipelineBarrier(command_buffer, src_flags, dst_flags, 0, 0, NULL, 0, NULL, 1, &barrier); end_single_command(command_buffer); } void create_texture_image() { int32_t width, height, channels; stbi_uc *pixels = stbi_load("texture.png", &width, &height, &channels, STBI_rgb_alpha); if (!pixels) { fputs("failed to open texture", stderr); exit(-1); } VkDeviceSize image_size = width * height * 4; VkBuffer tmp_buffer; VkDeviceMemory tmp_mem; create_buffer(image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &tmp_buffer, &tmp_mem); void *data; vkMapMemory(gpu, tmp_mem, 0, image_size, 0, &data); memcpy(data, pixels, image_size); vkUnmapMemory(gpu, tmp_mem); stbi_image_free(pixels); create_image(width, height, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &texture_image, &texture_image_memory); transition_image_layout(texture_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); copy_buffer_to_image(tmp_buffer, texture_image, width, height); transition_image_layout(texture_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); vkDestroyBuffer(gpu, tmp_buffer, NULL); vkFreeMemory(gpu, tmp_mem, NULL); } void create_texture_view() { texture_view = create_image_view(texture_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); } void create_texture_sampler() { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(phy_gpu, &props); VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .anisotropyEnable = VK_TRUE, .maxAnisotropy = props.limits.maxSamplerAnisotropy, .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, .compareEnable = VK_FALSE, .compareOp = VK_COMPARE_OP_ALWAYS, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .mipLodBias = 0.0f, .minLod = 0.0f, .maxLod = 0.0f }; VkResult res = vkCreateSampler(gpu, &sampler_info, NULL, &texture_sampler); if (res != VK_SUCCESS) { fputs("failed to create image sampler", stderr); exit(-1); } } void create_depth_resources() { VkFormat depth_format = VK_FORMAT_D32_SFLOAT; create_image(swapchain_extent.width, swapchain_extent.height, VK_FORMAT_D32_SFLOAT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &depth_image, &depth_memory); depth_image_view = create_image_view(depth_image, VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT); } void recreate_swapchain() { int32_t width = 0, height = 0; SDL_GetWindowSize(window, &width, &height); while (width == 0 || height == 0) { SDL_Event e; SDL_PollEvent(&e); SDL_GetWindowSize(window, &width, &height); } vkDeviceWaitIdle(gpu); cleanup_swapchain(); create_swapchain(); create_image_views(); create_depth_resources(); create_framebuffers(); } void get_data(void *ctx, const char *filename, const int is_mtl, const char *obj_filename, char **data, size_t *len) { (void) ctx; if (!filename) { fputs("null filename", stderr); *data = NULL; *len = 0; return; } FILE *fp = fopen(filename, "rb"); fseek(fp, 0, SEEK_END); *len = ftell(fp); rewind(fp); *data = calloc(*len, 1); fread(*data, 1, *len + 1, fp); fclose(fp); } void load_model() { vertices = calloc(1, sizeof(*vertices) + sizeof(*vertices->data) * 65535); vertices->cap = 65535; indices = calloc(1, sizeof(*indices) + sizeof(*indices->data) * 65535); indices->cap = 65535; FILE *fp = fopen("model.obj", "r"); struct { struct { size_t len, cap; struct vert { float x, y, z; } *data; } verts; struct { size_t len, cap; struct coord { float x, y; } *data; } coords; struct { size_t len, cap; struct face { size_t vert[3]; size_t coord[3]; } *data; } faces; } obj = { .verts = { .cap = 65535, .data = calloc(obj.verts.cap, sizeof(*obj.verts.data)) }, .coords = { .cap = 65535, .data = calloc(obj.coords.cap, sizeof(*obj.coords.data)) }, .faces = { .cap = 65535, .data = calloc(obj.faces.cap, sizeof(*obj.faces.data)) } }; size_t len = 0; char *line = NULL; while (getline(&line, &len, fp) != -1) { float x, y, z; x = y = z = 0; if (sscanf(line, "v %f %f %f", &x, &y, &z) == 3) { if (obj.verts.len + 1 >= obj.verts.cap) obj.verts.data = realloc(obj.verts.data, (obj.verts.cap *= 2) * sizeof(*obj.verts.data)); obj.verts.data[obj.verts.len++] = (struct vert) { x, y, z }; continue; } if (sscanf(line, "vt %f %f", &x, &y) == 2) { if (obj.coords.len + 1 >= obj.coords.cap) obj.coords.data = realloc(obj.coords.data, (obj.coords.cap *= 2) * sizeof(*obj.coords.data)); obj.coords.data[obj.coords.len++] = (struct coord) { x, y }; continue; } size_t vert[3], coords[3]; if (sscanf(line, "f %ld/%ld/%*d %ld/%ld/%*d %ld/%ld/%*d", &vert[0], &coords[0], &vert[1], &coords[1], &vert[2], &coords[2]) == 6) { if (obj.faces.len + 1 >= obj.faces.cap) obj.faces.cap *= 2, obj.faces.data = realloc(obj.faces.data, obj.faces.cap * sizeof(*obj.faces.data)); obj.faces.data[obj.faces.len++] = (struct face) { .vert = { vert[0] - 1, vert[1] - 1, vert[2] - 1 }, .coord = { coords[0] - 1, coords[1] - 1, coords[2] - 1 } }; continue; } } fclose(fp); for (size_t i = 0; i < obj.faces.len; i++) { if (indices->len + 3 >= indices->cap) indices->cap *= 2, indices = realloc(indices, sizeof(*indices) + sizeof(*indices->data) * indices->cap); if (vertices->len + 3 >= vertices->cap) vertices->cap *= 2, vertices = realloc(vertices, sizeof(*vertices) + sizeof(*vertices->data) * vertices->cap); for (size_t j = 0; j < 3; j++) { indices->data[indices->len++] = vertices->len; vertices->data[vertices->len++] = (struct vertex) { .pos = { obj.verts.data[obj.faces.data[i].vert[j]].x, obj.verts.data[obj.faces.data[i].vert[j]].y, obj.verts.data[obj.faces.data[i].vert[j]].z }, .texture_coords = { obj.coords.data[obj.faces.data[i].coord[j]].x, 1.0f - obj.coords.data[obj.faces.data[i].coord[j]].y, }, .color = { 1.0f, 1.0f, 1.0f } }; } } free(obj.verts.data); free(obj.coords.data); free(obj.faces.data); } void init() { SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow("vk", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 800, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); if (!window) { fputs("failed to create sdl window", stderr); exit(-1); } uint32_t extension_count = 0; vkEnumerateInstanceExtensionProperties(NULL, &extension_count, NULL); VkExtensionProperties *exts = calloc(extension_count, sizeof(*exts)); vkEnumerateInstanceExtensionProperties(NULL, &extension_count, exts); puts("available extensions:"); for (size_t i = 0; i < extension_count; i++) { printf("\t%s\n", exts[i].extensionName); } free(exts); create_instance(); if (SDL_Vulkan_CreateSurface(window, instance, &surface) != SDL_TRUE) { fputs("failed to create surface", stderr); exit(-1); } pick_physical_dev(); create_logical_dev(); create_swapchain(); create_image_views(); create_render_pass(); create_descriptor_layout(); create_graphics_pipeline(); create_command_pool(); create_depth_resources(); create_framebuffers(); create_texture_image(); create_texture_view(); create_texture_sampler(); load_model(); create_vertex_buffer(); create_index_buffer(); create_uniform_buffers(); create_descriptor_pool(); create_descriptor_sets(); create_command_buffers(); create_sync_objects(); } void update_uniform_buffer(uint32_t current_image) { static double rotate = 0.1; struct ubo ubo = {0}; mat4 id; mat4x4_identity(id); mat4x4_rotate(ubo.model, id, 0, 0, 1.0f, rotate * 90); mat4x4_translate_in_place(ubo.model, 0, 0, 1); mat4x4_look_at(ubo.view, (vec3){ 2, 2, 2 }, (vec3){ 0, 0, 1}, (vec3){ 0, 0, 1 }); mat4x4_perspective(ubo.proj, 45, swapchain_extent.width / (float) swapchain_extent.height, 0.1f, 10.0f); ubo.proj[1][1] *= -1; memcpy(mapped_buffers[current_frame], &ubo, sizeof(ubo)); rotate += 0.0001; } void draw() { vkWaitForFences(gpu, 1, &in_flight_fence[current_frame], VK_TRUE, UINT64_MAX); uint32_t image_index; VkResult res = vkAcquireNextImageKHR(gpu, swapchain, UINT64_MAX, image_avail[current_frame], VK_NULL_HANDLE, &image_index); switch (res) { case VK_SUCCESS: case VK_SUBOPTIMAL_KHR: break; case VK_ERROR_OUT_OF_DATE_KHR: recreate_swapchain(); return; default: fputs("failed to acquire swapchain images", stderr); exit(-1); } vkResetFences(gpu, 1, &in_flight_fence[current_frame]); vkResetCommandBuffer(command_buffers[current_frame], 0); record_command_buffer(command_buffers[current_frame], image_index); VkSemaphore wait_semaphores[] = { image_avail[current_frame] }; VkSemaphore signal_semaphores[] = { render_finished[current_frame] }; VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = wait_semaphores, .pWaitDstStageMask = wait_stages, .signalSemaphoreCount = 1, .pSignalSemaphores = signal_semaphores, .commandBufferCount = 1, .pCommandBuffers = &command_buffers[current_frame] }; update_uniform_buffer(current_frame); res = vkQueueSubmit(gfx_queue, 1, &submit_info, in_flight_fence[current_frame]); if (res != VK_SUCCESS) { fputs("failed to submit draw command buffer", stderr); exit(-1); } VkSwapchainKHR swapchains[] = { swapchain }; VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = signal_semaphores, .swapchainCount = 1, .pSwapchains = swapchains, .pImageIndices = &image_index }; res = vkQueuePresentKHR(present_queue, &present_info); switch (res) { case VK_SUCCESS: break; case VK_SUBOPTIMAL_KHR: case VK_ERROR_OUT_OF_DATE_KHR: recreate_swapchain(); return; default: fputs("failed to acquire swapchain images", stderr); exit(-1); } current_frame = (current_frame + 1) % MAX_FRAMES_INFLIGHT; } int main() { init(); bool running = true; SDL_Event e; while (running) { SDL_PollEvent(&e); switch (e.type) { case SDL_WINDOWEVENT: if (e.window.event == SDL_WINDOWEVENT_RESIZED) recreate_swapchain(); break; case SDL_KEYDOWN: if (e.key.keysym.sym == SDLK_q) running = false; break; case SDL_QUIT: running = false; break; } draw(); } cleanup(); return 0; }